Long commit subjects and bullets are hard to read in narrow terminals and mailed logs. Contributors using `.githooks` via core.hooksPath now get an automatic guard in addition to the two-file pre-commit cap. - Add `.githooks/commit-msg` to fail when any message line exceeds 80 chars - Skip length checks while `.git/MERGE_HEAD` exists for merge commits - Sync AGENTS.md, README.md, CONTRIBUTING.md, and commit.prompt.md
15 KiB
AGENTS.md: Quick guide for AI coding agents working on JuliaFEM.jl
This file is the entry point for any AI agent (Cursor, Copilot, Codex, …) that opens this repository. Read it first; then jump to the linked, authoritative documents for details.
The package version is 0.x (see Project.toml); the repository is in the
middle of a deliberate architectural reset toward a stable 1.0 (monorepo,
type-stable, zero-allocation, GPU-ready). Many older files, README.md sections,
and design notes still describe the previous API. When in doubt, trust the
code and this file over older READMEs.
1. What this project is
JuliaFEM.jl is an open-source finite element framework written in Julia. It is being rebuilt from first principles around four ideas:
- Ciarlet's triple as a type.
Element{K, P, S, N}encodes the reference domainK, polynomial spaceP, DOF specificationSand total DOF countNpurely at the type level. The element instance carries onlyidanddof_indices::NTuple{N, UInt64}. - Element as template, not bag of fields. Heavy structural
information (which local DOF is which field/entity/component) is
produced by
@generatedfunctions over the element type, so the compiler can fold it into constants. The canonical example islocal_dof_layout(::Type{Element{K,P,S,N}})returning anNTuple{N, DOFLayoutEntry}. - Microkernels. Assembly is built from small
evaluate-style functions that compute a single scalar (or block) and are dispatched at compile time. NoDict, noAny, no boxing. - Zero-allocation hot paths. Pre-allocated caches plus trait-based material dispatch let the inner loops run with zero GC allocation sites in the optimized LLVM IR.
The long-term vision (parallelism, GPU, Newton–Krylov, multiphysics,
billions of DOF) is documented in llm/vision/vision_2.0.md.
The filename is historical only; version numbering here is 0.x toward a stable 1.0, and that note captures stretch goals beyond the first stable release rather than a separate product line.
2. Where things live
The authoritative file-organization guide is
docs/src/repository_layout.md. Highlights:
| Folder | What goes there |
|---|---|
src/topology/ |
Reference shapes (Triangle, Tetrahedron, Hexahedron, …). |
src/basis/ |
Shape functions (Lagrange, Serendipity). |
src/quadrature/ |
Integration rules. |
src/mesh/ |
Mesh{N,Topo}, structured/unstructured meshes. |
src/dofs/ |
DOF{Q,E}, @DOFSet, DOFHandler (type-stable), DOF connectivity. |
src/elements/ |
Element{K,P,S,N} + local_dof_layout. |
src/materials/ |
LinearElastic, PerfectPlasticity, …; trait-based dispatch. |
src/assemblers/ |
ElementBasedAssembler, DOFBasedCOOAssembler, caches, scatter routines. |
src/domains/ |
Physics kernels per discipline (continuum/, beams/, plates/, …). |
src/solvers/ |
Linear/nonlinear solvers (still minimal). |
src/physics/ |
High-level user API (BCs, problems). |
src/io/ |
Mesh I/O policy (README.md); format-specific readers → weakdeps / extensions or separate packages; Abaqus + Aster remain in Legacy. |
src/legacy/ |
Older pre-reset API (Problem/Assembly/Solver/Analysis, Dict-based fields, Abaqus reader, …) wrapped in module Legacy; loaded only when JULIAFEM_ENABLE_LEGACY=1. |
test/<topic>/ |
Mirrors src/<topic>/. |
benchmarks/regression/, benchmarks/analysis/ |
Timestamped reports. |
docs/src/ |
Documenter-built index.md and api.md. |
docs/NEWS.md |
Short 0.x changelog (not the Quarto site). |
docs/tutorials/ |
Historical Jupyter notebooks (2015–2016 API; reference only). |
prototypes/, .trash/, llm/ |
Gitignored: experiments, throwaway, AI session logs. |
The per-module READMEs under src/<topic>/ and the user-facing pages
under docs/src/ were rewritten on 2026-05-08 to match the current
type-stable API. If you find a document that still references DOFManager,
register_fields!, count_field_dofs, @NamedTuple{u::Tuple{...}},
the legacy Physics/DirichletBC/add_dirichlet! API or the
nonexistent docs/contributor/ tree, treat it as stale and update or
delete it in the same change.
3. Current architecture (0.x)
3.1 DOF specification
# Single field
S = @DOFSet{u::DOF{Displacement{3}, Vertex}}
# Multi-field (e.g. thermo-mechanical)
S = @DOFSet{T::DOF{Temperature, Vertex},
u::DOF{Displacement{3}, Vertex}}
# `S` is a NamedTuple type whose values are `DOF{Quantity, Entity}`.
Quantity is anything with a defined dof_size (Float64 → 1, Vec{3} → 3,
Tensor{2,3} → 9, …). Entity is one of Vertex, Edge, Face, Cell.
3.2 DOF handler
DOFHandler{M, S, NF} (in src/dofs/dof_handler.jl) is the type-stable
replacement for the legacy Dict-based DOFManager:
- One flat
Vector{Int}per field stores the starting DOF for each entity ID. total_dofsis computed once at element creation.- A
@generated _make_element_dofs(...)unrolls the field/entity/component loop at compile time, so buildingelement.dof_indicesis allocation-free. DOFManageris kept as a backward-compat alias.
elements, handler = create_elements!(mesh, Element{Hex8, Lagrange{1}, S})
# handler isa DOFHandler{Mesh{...}, S, NF}
# elements::Vector{Element{Hex8, Lagrange{1}, S, 24}}
# handler.dof_connectivity::DOFConnectivity already built
3.3 Element template (compile-time DOF layout)
local_dof_layout(::Type{Element{K,P,S,N}}) is a @generated function
returning NTuple{N, DOFLayoutEntry} where each entry describes
(field_idx, entity_local, component) for one local DOF. The compiler
folds this into Core.Const(...) at the call site, so DOF decoding
becomes a tuple lookup with no runtime arithmetic.
Use field_idx, entity_local, component accessors (exported).
3.4 Assemblers
Two production assemblers, both type-stable and zero-allocation after warmup:
ElementBasedAssembler(src/assemblers/element_based/element_based_coo.jl): classical element-by-element assembly, scatter into COO triplets. Gold standard for correctness.DOFBasedCOOAssembler(src/assemblers/dof_based/dof_based_coo.jl): walks DOF rows, dispatches into per-element scratch usinglocal_dof_layout(E). Stepping stone toward a GPU/matrix-free pipeline. Currently single-kernel,ContinuumKernel-only.
3.5 Materials and caches
AbstractMaterial→ behavior trait (StatelessConstantTangent,StatelessStrainDependent,StatefulStrainDependent).compute_stress(material, ε, state, t) → (σ, 𝔻, new_state).- Caches:
GeometryCache: coords,∇N,detJ·w.ElementCache: element-level scratch (DOF mapping, IPs).AssemblyMaterialWorkspace{FieldType, StateType}: per-IP NamedTuples of fields and states (type-stable, zero-allocation).GlobalMaterialCache: state across timesteps.
3.6 Distributed matrix-free matvec (MPI)
MPI is a weak dependency; using MPI after JuliaFEM loads JuliaFEMMPIExt.
Partition metadata lives in src/assemblers/partitioning.jl, halo_exchange.jl,
and packed_layout.jl (build_partition_packed_layout_for_matvec,
build_matvec_halo_exchanges, RankHaloExchange, PartitionPackedLayout).
For Krylov solves without a full-length replicated workspace, use
mpi_partitioned_operator_matvec_owned! with partitioned_mpi_owned_matvec_workspace
and pass a persistent mpi_requests buffer from allocate_exchange_matvec_halo_mpi_requests
so each matvec does not allocate a fresh Vector{MPI.Request}.
Reference drivers: test/mpi/partitioned_matvec_smoke.jl,
test/mpi/partitioned_matvec_cg.jl, test/mpi/partitioned_internal_force_smoke.jl.
CI runs all three under mpiexec (see .github/workflows/CI.yml, job
mpi-partitioned-matvec-smoke).
4. Invariants the agent must preserve
These are non-negotiable. Tests and code analysis enforce them.
- Zero allocations in hot paths.
test/assemblers/test_dof_based_zero_alloc.jlasserts@allocated assemble!(...) == 0and0GC allocation sites in the optimized LLVM IR;test/assemblers/test_dof_based_internal_force.jldoes the same forassemble_internal_force!andapply_f_int_owned_rows!on a reference Hex8 patch. Don't introduceDict,Vector{Any}, untyped closures, orVectorliterals inside loops. For the intended split between tier 1 numeric kernels (always C-speed, no heap churn in loops), tier 2 warmed assembly drivers (setup may allocate), and tier 3 IO/UI convenience code where flexible containers are acceptable, seedocs/src/developer/architecture_layers.md(section Performance tiers). - Type stability everywhere on the hot path.
Base.promote_op(assemble!, ...) === Nothingand the inferred types must be concrete. UseNamedTuple(typed),NTuple, and compile-time helpers, notDict. - Element template == single source of truth.
Anything you'd be tempted to compute as
div/modover local DOF indices probably belongs in a@generatedfunction onElement{K,P,S,N}and queried via accessors likelocal_dof_layout. - Mirror src/ in test/. Tests for
src/foo/bar.jlgo intest/foo/test_bar.jl. A topic-level test file should be wired intotest/runtests.jl. - Zero stdlib drift. New Julia stdlib deps (
InteractiveUtils,Profile, …) must be added to[extras]and the relevant[targets]inProject.toml.
5. Critical workflow rules
Contributor-facing workflow text lives in .github/prompts/commit.prompt.md
and .github/copilot-instructions.md. Optional .githooks/ hooks (enable with
git config core.hooksPath .githooks): pre-commit caps staged paths at
two per commit; commit-msg rejects any log line longer than 80
characters (merge commits skip that check while .git/MERGE_HEAD exists).
Editor-local Cursor rules under .cursor/ are not part of the git tree.
- Commits: prefer small steps (default one file per commit). Combine
multiple files only when they share one logical story (exception—justify
why). Never
git add .orgit add -Aunless the user asks; stage paths deliberately. Read the full staged diff (nohead/tail). Message format: Conventional Commits subject, blank line, 1–3 sentence summary, then bullets scaled to the patch (small change → few or none; large / multi-concern → grouped, substantive bullets). Propose paths and full message and wait for explicit approval before eachgit commit. See.github/prompts/commit.prompt.mdfor the full protocol. - AI agents: commits are not a loop variable. Never drive
git commitfrom a shell or Python loop that stages paths and emits placeholder subjects such asupdate path/to/file.jlorsync <module>derived only from the path. Before everygit commit, read the entiregit diff --stagedyourself (nohead,tail,less, or other truncation when forming the message). The subject and body must describe the actual API and behaviour changes in those hunks; the optional two-path.githooks/pre-commitrule limits batch size, it does not replace reading the diff. Keep every commit message line at 80 columns or fewer when hooks are enabled (see commit-msg). If many low-quality commits already exist locally, repair withgit reset --soft <good_base>and rebuild following this file and.github/prompts/commit.prompt.md, or usegit rebase -ito reword; do not apply a second scripted sweep of generic messages. - Never commit without an explicit user command to start committing. Do not propose or initiate commits unprompted.
- Never create files in the root except for
README*files. Session logs go tollm/sessions/YYYY-MM-DD-topic.md. Throwaway scripts go to.trash/. Experimental code goes toprototypes/. prototypes/,.trash/,llm/are gitignored. Use freely; do not try to commit them.- Run the full test suite before declaring done.
julia --project=. -e 'using Pkg; Pkg.test()'. All bundled topic tests must pass with no errors (the exact test count grows with the tree undertest/).
To run a subset, pass topic directory names from test/runtests.jl (TOPICS), for example
julia --project=. -e 'using Pkg; Pkg.test(; test_args=["assemblers"])'.
test_args does not accept individual file paths; unknown strings are ignored with a warning
and the full suite runs.
- SPDX tags at file tops must use comment syntax for that file type (for example
# …in.jl, HTML comments in Markdown); seedocs/CONTRIBUTING.md.
6. Documentation style
When writing documentation, session logs, READMEs, guides, comments, or design notes:
- Avoid emoji.
- Avoid markdown bold for emphasis. Do not write
boldprose unless preserving an existing quoted source that already uses it. - Prefer plain technical writing with clear headings, short paragraphs, and precise code references.
- Do not over-emphasize ordinary terms. If every second word needs emphasis, the sentence should be rewritten instead.
The goal is professional engineering documentation, not marketing copy.
7. Common entry points
- Build a model and assemble:
mesh = create_structured_box_mesh(...) S = @DOFSet{u::DOF{Displacement{3}, Vertex}} ET = Element{Hex8, Lagrange{1}, S} elements, handler = create_elements!(mesh, ET) material = LinearElastic(E=210e9, ν=0.3) kernel = ContinuumKernel(ContinuumFormulation{ThreeDimensional}(), material, Displacement{3}()) asm = DOFBasedCOOAssembler() # or COOAssembler() cache = create_cache(asm, elements, handler, mesh, kernel) assemble!(cache, asm, kernel, mesh) K, f = extract_system(cache) - Inspect the compile-time DOF layout:
using JuliaFEM S = @DOFSet{u::DOF{Displacement{3}, Vertex}} ET = Element{Hex8, Lagrange{1}, S, 24} local_dof_layout(ET) # NTuple{24, DOFLayoutEntry} - Regression + code analysis:
test/assemblers/test_dof_based_zero_alloc.jl - Top-level architecture overview:
src/README.md. Per-module details: theREADME.mdfiles insidesrc/<topic>/. - Vision and roadmap:
llm/vision/vision_2.0.md(historical filename; not a release label).
Documentation doors (which prose to open first)
| Goal | Start here |
|---|---|
| Shortest runnable assembly in the package docs | docs/src/index.md and docs/src/snippets/minimal_elasticity_quickstart.jl |
| Ordered reading on the Quarto site | juliafem.github.io/learning_path.md |
| Authoritative versus archival site material | juliafem.github.io/docs/documentation-map.md |
| New domain kernel or assembler hook | juliafem.github.io/docs/developer-guide/kernel_extension_contract.md |
| Multi-field thermo-mechanical narrative | docs/src/thermo_elastic_walkthrough.md |
| Quarto site colors / logo / naming | juliafem.github.io/docs/contributor-guide/design_system.md |
| Where new files go in the repo | docs/src/repository_layout.md (pointer: docs/repository_layout.md) |
8. When you are unsure
- Search the code first (
Grep/SemanticSearch). - Check the relevant
test/<topic>/for examples. - If there's a recent
llm/sessions/YYYY-MM-DD-*.mdlog on the topic, read it for context. - Ask the user. Do not guess silently in a high-impact module.