| name | nefes-dev |
|---|---|
| description | Use when developing the Nefes package itself -- editing the solver, the compiled element kernels, the assembly or the derivative engine; adding, changing, or removing an element type; touching the thermochemistry engine or the gas-model boundary; writing or fixing package tests; or updating the docs under `docs/`. Covers the kernel/residual architecture and the complex-step discipline every residual owes, the end-to-end recipe for adding an element, the edit-a-kernel loop (including the numba stale-cache trap), where each source of truth lives, and the formatting/commit gate. This is the counterpart of the `nefes-user` skill: reach for `nefes-user` when the task is to *use* Nefes (build a `Network`, solve, analyze); reach for this when the task is to *change* Nefes. Do NOT use it for user-facing modeling in a notebook or script -- that is `nefes-user`'s job. |
You are developing Nefes, not using it. The package solves the steady mean flow of a compressible-flow network and the linear acoustic/entropy behavior around it; its correctness rests on a few contracts that do not announce themselves when broken, so the work is as much about honoring those contracts as writing the code.
docs/design/ is the source of truth for how the package is built, the developer's analog of the user skill's docs/best-practices.md:
philosophy.md— the principles the rest follows from (kernels over objects, exact derivatives, smoothness over branching).kernel-architecture.md— integerresidual_iddispatch, and one source compiled twice (float64+complex128).complex-step.qmdandsmoothness-contract.md— the exact-derivative engine and the discipline it demands.solver.md,assembly.md,reproducibility.md— the Newton/continuation solver, the residual/Jacobian assembly and acoustic stamps, and determinism.
docs/theory/ is the source of truth for the physics (governing equations, per-element closures, choking, the perturbation network); docs/nomenclature.md is the symbol table.
Read the relevant document before you design, and prefer it over memory.
This skill deliberately does not restate the equations, signatures, or file internals: a second copy drifts and then produces confident, wrong changes.
CLAUDE.md owns the non-negotiable constraints and is always in context; this skill is the how, never a second copy of the what.
The two that govern almost every change:
- Complex-step safety. Every residual must be smooth and complex-analytic: no
abs/sign/min/maxand no branch taken on the flow state. Jacobians come from the complex step, so a non-analytic residual silently corrupts the derivative. - Subsonic scope. Flowing or quiescent, at or below a sonic throat; supersonic/shock-seeding is deferred.
When a step below restates one of these, treat CLAUDE.md (and the linked design doc) as the authority.
From kernel-architecture.md — getting one wrong produces a change that compiles and runs but is wrong where it matters:
- Kernels, not objects. The hot path is a flat sweep over typed edge arrays dispatched on an integer
residual_id(nefes/elements/kernels.pybranchesif rid == …). The object shell builds/names/reports and owns no numerics; the kernels import no shell. - Written once, compiled twice. Each kernel compiles to a
float64specialization (the residual Newton drives to zero) and acomplex128one (the complex-stepped derivative seed) from one body, so a change to a residual is automatically a change to its Jacobian — there is no separate Jacobian code. This is why the analyticity discipline is structural, not stylistic: a state-dependent branch would compile to two different functions and break the correspondence. - Smoothness is a library, not a ban. The physics of a switch (flow reversal, a loss opposing either direction, subsonic-vs-choked, frozen-vs-burnt) is expressed through the regularized primitives in
nefes/assembly/smooth.py; you round the corner, you do not branch on it.
The residual id is the spine; every table below is keyed on it. Touch, in order:
nefes/elements/ids.py— a newresidual_idconstant and itsELEMENT_TYPE_NAMESentry; then the tables that are keyed by id:FIXED_NPORTS(or, for a variable-port manifold, the rule inport_kinds),_PORT_KINDS_FIXED/port_kinds,row_kind_tags,ALLOWS_AREA_CHANGE, andSTREAM_INTRODUCING/BOUNDARY_RIDS/DISALLOWED_NEIGHBORSif the type is a feed, a boundary, or has an adjacency rule.nefes/elements/kernels.py— the residual branch (if rid == YOUR_ID:), built from the smooth primitives per the contract.nefes/elements/catalog.py— the user-facing factory returningElementSpec(YOUR_ID, [fparams…], name, …).nefes/elements/parameters.py— aParamDescriptorinELEMENT_PARAMSfor each named parameter (unit, bounds, validation,fparamsslot).tests/test_parameters.pychecks the declared packing against the factory's actual output, so these cannot drift.nefes/io/yaml_in.py+yaml_out.py— the YAML type-tag loader and writer; confirm a write→read→solve round-trip.tests/test_complex_step_safety.py— a_probe_<name>and itsPROBESentry. The roll-calltest_every_element_kernel_is_sweptfails until it exists; the per-kerneltest_kernel_complex_step_safe_across_regimesthen checks complex-step == finite-difference across forward / reverse / near-zero / near-choke flow. (This is the hard-constraint probe fromCLAUDE.md.)- Docs —
docs/theory/elements.md(the closure) anddocs/reference/atomic-elements.md(parameters); add adocs/reference/modeling-guide.mdrow if the element maps a real restriction (orifice, valve, nozzle…). - A behavioral test — beyond the smoothness sweep, a test that pins the intended physics (a balance it must respect, a limit it must reproduce).
- Rewrite the residual smooth (contract +
smooth.py), keeping it identical on the real and complex paths. - Clear the numba cache — see the trap below — or run with
NUMBA_DISABLE_JIT=1to check against pure Python first. - Run that kernel's complex-step probe (
test_kernel_complex_step_safe_across_regimesfor its id), then the element's behavioral tests. - Update the theory/reference doc for the closure if its behavior changed.
Numba caches compiled kernels. After editing an @njit kernel a stale cache can mask the change, so if results look unchanged after an edit, clear the numba artifacts under nefes/**/__pycache__/ (both *.nbi / *.nbc in assembly/ and elements/ — the assembly layer caches calls into the element kernels) or run with NUMBA_DISABLE_JIT=1. This is a productivity trap, not a correctness one, but it wastes real time when unrecognized.
- Run the suite in the project conda env (
environment.yml) or an editable install (pip install -e .[dev]):pytest(config inpyproject.toml,testpaths = tests). - Scientific claims get tests. A new closure, a benchmark match, a conservation property: assert it, do not assert it in prose only. The roll-call makes the smoothness contract self-policing; extend that habit to the physics.
- Verify against the code, don't recall. Symbols, signatures, and the id-keyed tables drift; read the current
ids.py/catalog.py/parameters.pybefore you assume a name or a slot. The numba caveat means a "verified" result may be stale — re-run after clearing the cache when in doubt.
docs/ is the source of truth, so a feature that changes behavior is not done until its doc changes with it; create one if none fits and you judge it needed.
Keep the notation conventions of docs/nomenclature.md and CLAUDE.md, and keep the prose free of software/control-engineering jargon (the audience is combustion and acoustics).
- Formatting is enforced by pre-commit (
isort→black/black-jupyter→flake8→nbstripout); a commit that leaves unformatted, lint-failing, or output-bearing-notebook code is rejected. Install once withpre-commit install. Line length and lint rules are set inpyproject.toml/.flake8. - Notebook outputs are never committed (
nbstripoutstrips them); plot with plotly and the bundled theme. - Commit-message and branch conventions (no authorship/tool references; delete merged branches) live in
CLAUDE.md.
| Need | Go to |
|---|---|
| Why the package is built this way; the hot-path design | docs/design/philosophy.md, docs/design/kernel-architecture.md |
| The complex-step engine and the smoothness rules | docs/design/complex-step.qmd, docs/design/smoothness-contract.md, nefes/assembly/smooth.py |
| The solver, assembly, acoustic stamps, determinism | docs/design/solver.md, docs/design/assembly.md, docs/design/reproducibility.md |
| Physics: governing equations, per-element closures, choking, perturbation network | docs/theory/ |
| The element-authoring touchpoints (ids, factories, parameter schema) | nefes/elements/ids.py, catalog.py, parameters.py |
| Element parameters and coefficients as documented | docs/reference/atomic-elements.md, docs/reference/composite-elements.md |
| Symbols and notation | docs/nomenclature.md |
| Validation map and literature benchmarks | docs/validation/ |
| The non-negotiable constraints and repo conventions | CLAUDE.md |