Files
JuliaFEM.jl/AGENTS.md
T
Jukka Aho 2bfc78a37c docs: describe commit-msg summary and bullet requirements
Contributor guides now match what the hook enforces so failures are obvious
before push when core.hooksPath points at .githooks.

- Refresh commit.prompt.md hook bullets and the canonical message checklist
- Update AGENTS.md workflow paragraph for commit-msg structure plus line cap
2026-05-11 02:46:14 +03:00

15 KiB
Raw Blame History

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:

  1. Ciarlet's triple as a type. Element{K, P, S, N} encodes the reference domain K, polynomial space P, DOF specification S and total DOF count N purely at the type level. The element instance carries only id and dof_indices::NTuple{N, UInt64}.
  2. Element as template, not bag of fields. Heavy structural information (which local DOF is which field/entity/component) is produced by @generated functions over the element type, so the compiler can fold it into constants. The canonical example is local_dof_layout(::Type{Element{K,P,S,N}}) returning an NTuple{N, DOFLayoutEntry}.
  3. Microkernels. Assembly is built from small evaluate-style functions that compute a single scalar (or block) and are dispatched at compile time. No Dict, no Any, no boxing.
  4. 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, NewtonKrylov, 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 (20152016 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_dofs is computed once at element creation.
  • A @generated _make_element_dofs(...) unrolls the field/entity/component loop at compile time, so building element.dof_indices is allocation-free.
  • DOFManager is 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 using local_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.

  1. Zero allocations in hot paths. test/assemblers/test_dof_based_zero_alloc.jl asserts @allocated assemble!(...) == 0 and 0 GC allocation sites in the optimized LLVM IR; test/assemblers/test_dof_based_internal_force.jl does the same for assemble_internal_force! and apply_f_int_owned_rows! on a reference Hex8 patch. Don't introduce Dict, Vector{Any}, untyped closures, or Vector literals 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, see docs/src/developer/architecture_layers.md (section Performance tiers).
  2. Type stability everywhere on the hot path. Base.promote_op(assemble!, ...) === Nothing and the inferred types must be concrete. Use NamedTuple (typed), NTuple, and compile-time helpers, not Dict.
  3. Element template == single source of truth. Anything you'd be tempted to compute as div/mod over local DOF indices probably belongs in a @generated function on Element{K,P,S,N} and queried via accessors like local_dof_layout.
  4. Mirror src/ in test/. Tests for src/foo/bar.jl go in test/foo/test_bar.jl. A topic-level test file should be wired into test/runtests.jl.
  5. Zero stdlib drift. New Julia stdlib deps (InteractiveUtils, Profile, …) must be added to [extras] and the relevant [targets] in Project.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 enforces subject, blank, summary paragraph, blank, then - bullets (with line length ≤80); merge commits skip 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 . or git add -A unless the user asks; stage paths deliberately. Read the full staged diff (no head/tail). Message format: Conventional Commits subject, blank line, 13 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 each git commit. See .github/prompts/commit.prompt.md for the full protocol.
  • AI agents: commits are not a loop variable. Never drive git commit from a shell or Python loop that stages paths and emits placeholder subjects such as update path/to/file.jl or sync <module> derived only from the path. Before every git commit, read the entire git diff --staged yourself (no head, 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-commit rule 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 with git reset --soft <good_base> and rebuild following this file and .github/prompts/commit.prompt.md, or use git rebase -i to 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 to llm/sessions/YYYY-MM-DD-topic.md. Throwaway scripts go to .trash/. Experimental code goes to prototypes/.
  • 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 under test/).

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); see docs/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 bold prose 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: the README.md files inside src/<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

  1. Search the code first (Grep / SemanticSearch).
  2. Check the relevant test/<topic>/ for examples.
  3. If there's a recent llm/sessions/YYYY-MM-DD-*.md log on the topic, read it for context.
  4. Ask the user. Do not guess silently in a high-impact module.