mirror of
https://github.com/JuliaFEM/JuliaFEM.jl.git
synced 2026-08-06 04:21:33 +00:00
2bfc78a37c
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
327 lines
15 KiB
Markdown
327 lines
15 KiB
Markdown
# 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, Newton–Krylov, 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 (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
|
||
|
||
```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, **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 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.
|