Files
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

327 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`](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`](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
```julia
# 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.
```julia
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:
```julia
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:
```julia
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.