Skip to content

Performance evidence

Equivalent finite-J2 return-map execution (2026-10-25)

The batched finite-J2 update reuses its existing elastic trial SVD to recover the new plastic gradient. It no longer reconstructs and then solves against the same elastic gradient. The scalar implementation remains an independent arithmetic reference; the material law, state schema, tangent and acceptance criteria are unchanged. Cyclic loading, unloading, reversal, rotations and isochoric prehistory with condition numbers up to 10^4 are checked, including batch/chunk boundaries.

A fresh 64-increment serial periodic-J2/plane-contact confirmation alternates baseline/candidate/candidate/baseline after warm-up. Solve-and-observe means are 28.36284 -> 27.46857 s (3.15% less); all four runs retain 64 accepted increments and 208 Newton corrections, with identical line-search and linear reason histories. Maximum displacement/state/stress difference is 5.042e-11; maximum per-point relative tangent error is 5.355e-12. Individual timings, source hashes, comparisons and the fixed baseline are retained in evidence/performance/2026-10-25-j2-svd-return-confirmation.json.

This is a bounded implicit solve measurement, excluding setup and publication. It is not the complete explicit composite run below, which uses another material route. Do not add this percentage to earlier tangent, preconditioner or predictor measurements, or use it as evidence of contact mesh independence. Ordinary installed partial/restart/continuous paths are verified separately in evidence/contact/2026-10-25-installed-periodic-contact.json.

Complete installed trajectory (2026-10-22)

The frozen native wheel at 1ed3c1e1 completed the unchanged 255,041-Hex8, 100,000-increment review model with 25 field frames in 12,295.686 s (3 h 24 min 56 s), measured from process launch through preparation, calculation, publication and shutdown. No competing numerical job or local build was launched during this run; light source review and metadata work continued. This is an observed local runtime, not a promise for another machine.

The project's own comparable timer was 12,293.966 s versus 14,932.091 s for the previous frozen 743f10b4 run (17.67% less). The older run had competing load and a sleep interval, so this difference is not an isolated causal speedup. Its UTC elapsed duration was 15,322.966 s; do not mix that clock with the project timer. The controlled alternating late-path screen supports 12.11% less advancement time for the selected combined optimization.

All 25 frames (177 numeric visualization datasets), five final material arrays, 60 checkpoint arrays, 100,000 accepted times, 10,000 accepted-history records and 800 observation records compare equal to the frozen baseline. Numeric arrays are finite and both history hash chains verify. The XDMF view now retains 17-digit coordinates rather than the old 16-digit text rounding. Independent VTK 9.6 reads all 25 frames as one Hex8 volume, matches every field to its HDF5 data and gives zero Warp coordinate error. The field companions occupy approximately 544 MiB; mesh data are shared between frames.

The sufficient cell screen, topology/coordinate identity reuse and operational restart compatibility are reusable core changes. Material law, mass, time grid, geometry, contact settings and worker budgets are unchanged. Execution parity and visualization checks do not establish vendor equivalence, mass-scaling accuracy or a general thermodynamic first law. Confidential model assets and full numerical archives remain local.

Portable decisions, not machine-specific requirements

Performance settings are choices to measure, not physical properties of a model. Do not turn this page's worker counts, backend, mesh or time step into requirements for unrelated problems or another computer. Separate three questions:

  • Equivalent execution: can caching, batching or scheduling remove work while preserving accepted fields, internal variables and work histories?
  • Numerical method: does a different time step, mass, penalty or spatial approximation retain the required physical accuracy? This needs convergence evidence, not just an execution-parity check.
  • Platform policy: which available solver/backend and worker budget suits the new machine? Record the selected policy and actual measured runtime.

For an implicit problem, explicitly selecting a supported linear backend can matter more than changing Python code. For example, where the PETSc installation provides MUMPS:

from agentfem import solvers

solver_options = solvers.newton(
    linear_solver=solvers.direct_solver(package="mumps"),
)

Pass the policy through the ordinary model.step(..., solver_options=...) route. This is an option, not a universal default; an unavailable requested backend must fail, not silently select another algorithm. Check the actual solver summary, convergence and representative accepted states. Reusing a KSP owner does not mean reusing an obsolete numerical factorization after its matrix changes. An implicit linear-solver improvement does not accelerate an explicit central-difference problem that has no such solve.

For native explicit execution, body and contact may overlap. Inclusive body and contact times therefore cannot be added, and a short owner wait does not mean contact is cheap. Use the phase ledger to locate the critical path, then compare unprofiled advancement and, when claiming full-run performance, the complete installed process. History encoding and visualization cadence are separate output choices; retain scientific samples and accepted-state semantics.

Completed core migration and next installed candidate (2026-10-14)

The frozen b962a777 generic-core run has now completed the same 255,041-cell, 100,000-increment review path in 15,465.05 s (4 h 17 min 45 s), including preparation, export and worker shutdown. The previous migrated-core run took 19,119.11 s; the earlier private implementation took 13,529.51 s. The new measured total is 19.11% below the former and remains 14.31% above the latter. These are local complete timings, not a hardware-normalized commercial-solver comparison or an isolated causal experiment: lightweight source/metadata work overlapped the new run, and the earlier core run had additional build/test load.

Final compared nodal/material/damage/contact arrays and all 10,000 shared reporting stations agreed exactly. Actual ParaView read one volume mesh with 255,041 Hex8 cells, a complete displacement vector and zero Warp coordinate error. The retained fixed-inertia, initial-adjustment and penalty-contact experiment has not been independently validated against the source solver.

Candidate 608c50bb additionally reuses instance-owned sleeping native workers and keeps rigid-contact residuals in indexed boundary-node storage through serial assembly. Two fresh 100→600-increment spans took 81.678/80.525 s, versus 86.036 s for a current-HEAD baseline: about 5.7% less mean segment time. Early checkpoint comparisons passed; body-only replays at steps 20,000/60,000/90,000 retained all 12 arrays bitwise, but worker reuse alone gave no meaningful late body-time reduction. Do not infer full-run savings from that short segment or attribute the combined change to a faster material law.

The selected macOS source-built wheel passed 238 targeted installed tests, nine contact tests per MPI rank, and clean-checkout installed platform acceptance. Its source-archive/native-header packaging is checked separately. The frozen project subsequently completed its full fresh run in 14,646.987 s (4 h 4 min 7 s), 5.29% below the preceding core baseline. All 374 final checkpoint checks pass, including 60 bitwise-equal numeric arrays, and all 10,000 reporting records agree. Actual ParaView confirms one 3D Hex8 volume with complete displacement and zero Warp coordinate error. This is measured end-to-end improvement, not a strictly sub-four-hour result. Lightweight source/metadata work and read-only diagnostics overlapped the run, so it is not an isolated causal benchmark. No mass, loading duration, increment, contact tolerance or material law was changed for this execution optimization.

Frozen-project delivery preflight

Before starting a shared frozen project, run the standalone tool from a source checkout or copy it alongside the trusted delivery (it needs only Python 3.11+ for metadata checks):

python tools/check_frozen_project.py /path/to/project --requirements /path/to/requirements.json
python tools/check_frozen_project.py /path/to/project --requirements /path/to/requirements.json --probe --output new-preflight.json

The project must declare a agentfem.frozen-core-project.v2 manifest containing an installed-site path and nonempty file SHA-256 map. The separate requirements document uses agentfem.frozen-project-requirements.v1, names the artifact's system, machine and python_cache_tag, and declares imports and pinned native_libraries. This is a bounded frozen-delivery format, not a general environment solver or automatic installer. Only probe code you trust: the probe imports modules and loads libraries, though it does not execute the case.

Missing/corrupt files and incompatible binary platforms fail before imports. The optional probe checks the pinned AgentFEM import, reports missing native dependencies and has a timeout. runtime_ready is separate from scientifically_validated; neither elapsed time nor a matching machine makes a physical model correct. For another platform, rebuild the artifact and verify it there instead of relaxing file checks or changing the engineering Model.

Accepted state and reporting boundaries (2026-10-12)

The first frozen generalized native run exposed a migration regression: private parallel cell auxiliaries had become serial, and mutable auxiliary outputs needed full-cell copies at the immutable execution boundary. The candidate correction provides explicit, bounded cell_workers and creates detached gradient, viscosity and gathered-cell outputs directly in immutable storage. Each cell retains its original arithmetic order. This fixes missing execution paths, not the physical model. Byte-identical tests do not establish a speedup: compare frozen installed runs and multiple nonlinear stations before promoting a worker budget. Keep the original slower run and its timing.

Installed migration check (2026-10-13)

The first frozen generic-core full run (runtime 38b007c0) completed 100,000 increments in 19,119.11 s, versus 13,529.51 s for the earlier private execution. All compared final scientific fields, material histories and work/contact records agreed exactly. This is a recorded regression, not a speedup. Local builds, short tests and a three-second diagnostic overlapped the generic run, so these two complete timings are not an isolated performance experiment.

After it exited, installed candidate b962a777 was compared sequentially with installed baseline 7e3c567c, on the same 255,041-cell model and Apple Silicon host (4 performance and 6 efficiency cores, 32 GiB RAM). Both used 8 material workers, the same contact settings, process-local TCP transport, and one OpenBLAS/OMP thread. The candidate explicitly selected 4 auxiliary workers; physics, inertia, fixed time grid and reporting cadence were unchanged.

Fresh-run pair; increments 100–600 Baseline Candidate Time reduction
1 93.416 s 86.286 s 7.6%
2 93.054 s 88.776 s 4.6%

Each run started from zero and advanced 600 increments; the table excludes preparation and its first 100 increments. Both final checkpoint comparisons passed with identical numerical arrays and shared numeric histories. The variation and early loading range limit the interpretation of these savings.

Read-only replay of the same archived states separately measured the complete body trial: gather, geometry admission, material/force update, viscosity and scatter. Two warmups preceded 15 timed repetitions per station.

Archived accepted step Baseline median Candidate median Time reduction
20,000 96.737 ms 66.544 ms 31.2%
60,000 107.008 ms 66.582 ms 37.8%
90,000 112.418 ms 70.755 ms 37.1%

All 12 output arrays at each station were byte-identical, including stress, hidden state, internal work, force and viscosity response. Replay did not migrate checkpoint identity or commit a global increment. It excludes contact, global acceptance and I/O. It therefore does not establish a 31–38% whole-run speedup. The private input assets and detailed archives are retained locally, not distributed as a public benchmark. A new frozen complete run is the next timing gate; independent vendor agreement remains unverified.

The reusable core now has immutable field-record transport and exact live-field comparison before borrowing an accepted material-history snapshot. External field writes remain observable. The optional finite-Hex record cache is off by default: correctness tests alone do not establish a throughput benefit.

results.AcceptedHistoryJournal(path).append_records(new_records) provides single-writer, append-only accepted reporting, avoiding repeated serialization of an ever-growing history. Submit only new records, preserve the returned sha256_tip in final evidence, and read with read_accepted_history(path, expected_tip=...). It rejects invalid batches before writing and poisons the writer after I/O failure. It is not a solver checkpoint, MPI collector or scientific certification.

The architecture priority is one authoritative accepted state per lifecycle: Operator produces trial response, State accepts/rejects it, Result publishes observations. An exclusive array owner must not silently replace live fields when callbacks may access them. The reusable native fused execution and local exact repair have since moved into the optional built-in execution modules, with arbitrary state-width, repair-failure and observer tests (see native_explicit_execution.md). Private material laws remain external; no fixed native ABI state width belongs in the generic core. Installed full-model timing remains a separate promotion gate.

For performance promotion, compare repeated early/nonlinear/late segments at identical accepted stations, including hidden history, work, damage and rollback. Then time one complete installed-project run including preparation, checkpoints and export. A projected wall time is not a full-run result. Thread counts, numerical inertia and private contact assumptions are not defaults for other cases. Avoid a broad Model/Procedure redesign merely to accommodate one fast experimental implementation.

Bounded constitutive stress conversion measurement (2026-10-09)

The shared finite-strain quadrature driver converts Cauchy to first Piola stress in NumPy blocks of 1,024 points, replacing a Python loop of individual 3-by-3 inversions. For 20,000 float64 points on the development Apple Silicon host, five warm repetitions gave median 0.06990 s for the scalar expression and 0.00533 s for the chunked expression (13.1x for conversion only). Inputs used NumPy seed 823, F = I + normal(0, 0.03) and random stress arrays; OpenBLAS/OMP thread counts were one. The maximum absolute difference was 8.88e-16. Empty partitions, invalid determinants and nonfinite input/output remain explicitly checked. This excludes constitutive updates, assembly, I/O and the global solve; no whole-solver acceleration factor follows from it.

The private finite-Hex residual also separates array-based rollback snapshots from JSON-ready durable snapshots. On a 4,096-cell (16x16x16) unit cube with the finite-strain J2 state schema, five warm snapshots measured median 0.00379 s for list expansion versus 0.000156 s for copied arrays (24.2x for snapshot creation only). tracemalloc peak allocations were 22.54 MB versus 4.36 MB. These are Python-tracked allocations, not process RSS. Real disk checkpoints originally retained the JSON auxiliary contract. The subsequent optional numeric-tree encoding below also removes that expansion from durable serial material snapshots; neither measurement is a whole-solver speedup.

tools/benchmark_checkpoint_arrays.py --size 16 separately compares durable auxiliary serialization for 4,096 finite-J2 cells, including copied state, encoding and atomic file writes, but excluding nodal archives and solving. JSON uses 14,048,099 bytes; typed numeric arrays plus metadata use 4,365,404 bytes (about 69% less). Python-traced peak allocations are 79,684,017 versus 8,730,193 bytes (about 89% less); these are not process RSS measurements. Every restored gradient and material field is compared with its source. This measurement overlapped a capacity run, so its wall times are not presented as a speedup. The optional v6 format uses non-executable NumPy arrays and a small JSON tree; existing JSON auxiliary checkpoints retain v5 and remain readable. This does not extend serial material history to MPI-portable restart.

tools/benchmark_finite_hex_trial.py --size 16 --repeats 7 measures a complete private serial trial (material, geometry admission, force and spectral screen), excluding mesh preparation, commit, restore and I/O. On the same host with OMP_NUM_THREADS=1 and OPENBLAS_NUM_THREADS=1, caching immutable state-layout sizes and replacing scalar NumPy energy sum checks with the identical scalar tolerance reduced the warm median from 0.27001 s to 0.22358 s (17.2%). The force norm remained 0.7198301095792459. Neither material equations nor rejection tolerances changed; boundary tests compare both sides of the former NumPy tolerance. --profile is diagnostic only and must not be enabled for comparable wall-clock measurements.

These environment settings do not prove that every runtime uses one thread. A subsequent process sample found the old MPI launcher supervisor consuming CPU in two libfabric sockets threads while waiting for its child. NumPy build metadata alone does not establish the library actually loaded at runtime; the October 10 environment loads conda OpenBLAS through its BLAS/LAPACK dylibs. Treat the measurements above as bounded same-environment observations, not certified single-thread benchmarks. The lightweight installed MPI entry now probes the linked vendor without initializing MPI in the supervisor; the numerical child retains its normal runtime and environment. Timeout and interruption still terminate the owned process group. The transport itself is not silently reconfigured. The underlying mechanisms are documented in the mpi4py initialization controls and libfabric sockets provider. This supervisor saving is not an acceleration factor for the numerical kernel.

Optional MPI idle-CPU diagnostic (2026-10-10)

tools/diagnose_mpi_idle.py measures bounded, isolated single-rank children. It never initializes MPI in its parent or changes the user's environment:

python tools/diagnose_mpi_idle.py --compare-provider tcp --output /tmp/mpi-idle.json

On this macOS ARM64 host with MPICH 5.0.1 and libfabric 2.5.1, three paired two-second observations measured 1.963–1.995 equivalent CPU cores while idle with FI_PROVIDER unset, versus less than 0.000036 with FI_PROVIDER=tcp. A process sample attributed two busy background threads to the sockets connection listener and endpoint connection manager. These are MPI runtime threads, not material integration or an unbounded Python loop. Raw observations are archived in evidence/hex8/2026-10-10-mpi-idle.json.

The same installed AgentFEM wheel passed the same 50 selected tests per rank on two ranks with both the default provider and TCP. The selection covers finite Hex state/energy, ordinary Step output, checkpoint restore, small-strain Hex and transient rollback. This is local compatibility evidence, not a multi-node scaling study or a Windows guarantee. Use a process-local FI_PROVIDER=tcp only after checking the relevant installed MPI runtime; AgentFEM does not set it automatically. The libfabric project documents the TCP provider and marks the older sockets provider deprecated in its provider overview.

Reducing idle CPU is useful independently of solve time, but is not a solver speedup factor. Keep the provider identical on both sides of a performance comparison. Thread-limit environment variables alone do not prove that MPI has no background threads.

The serial 32,768-cell, 500-increment affine endurance diagnostic completed with maximum displacement/stress/reference-energy absolute errors of 4.44e-16 / 7.00e-13 / 6.00e-14. Bounded provider batches reduced observed peak process RSS from 1,054,834,688 to 724,598,784 bytes (about 31%). The older run was a dirty development diagnostic; the newer run identifies clean commit f5991141. Both had brief diagnostic overlap, so their 1,090.9 / 1,062.4 s wall times are not offered as a controlled speedup. The check is an affine path endurance test, not a spatial convergence or industrial validation result.

A further local trial optimization admits a frozen displacement copy once, then reuses that same admitted geometry through material and force evaluation. It does not cache admission across increments or skip standalone geometry checks. Nine warmed 4,096-cell trials measured 0.21909 s before and 0.19580 s after (about 10.6% less trial time), with identical force norm. Those runs used OMP/OpenBLAS/vecLib limits of one and no concurrent numerical workload. Input-mutation isolation, folded-cell rejection and downstream rollback remain covered by targeted tests. This is again a trial measurement, not a whole-solve or cross-software performance claim.

AgentFEM records execution cost as a first-class part of SimulationResult. Performance evidence explains the cost of a computation; it does not turn a completed solve into a scientifically verified result.

The public result contains a stable agentfem.performance-evidence record:

result = model.step(target=displacement).solve_result()

print(result.performance["wall_seconds"])
print(result.performance["workload"]["global_dofs"])
print(result.performance["solver"]["convergence"])

The same record is written to result.json and travels into scientific datasets through the ordinary result summary.

wall_seconds normally covers the measured public execution call, including solve and result/output construction. A transient result instead accumulates its recorded run() calls and the final result construction, so checkpointed or deliberately segmented execution remains visible. Work completed before the declared boundary is not retroactively counted. In particular, mesh/model construction and a JIT compilation completed while creating the numerical problem remain outside the boundary; compilation triggered lazily during a measured stage remains inside it. Every manifest records the exact boundary so a warm solve cannot be presented as an end-to-end cold-start measurement.

What is recorded

The common contract separates four kinds of evidence:

record meaning
stages wall-clock time and call count for solve, result/output, assembly, history, or provider-owned stages
workload global degrees of freedom, global cells, dimensions, and finite-element identity
solver declared solver policy and convergence/iteration evidence
parallel MPI rank count and the timing reduction rule

Runtime versions, operating system, machine architecture, scalar precision, PETSc and MPI identity remain in the result's runtime provenance. They are referenced rather than copied into the performance record, so the two sources cannot silently disagree.

MPI interpretation

Timing is measured on every rank. Before publication, AgentFEM reduces each stage to minimum, mean, and maximum rank time. seconds is the maximum: the parallel critical path that determines elapsed solve time. The spread between minimum and maximum exposes load imbalance.

Stage timings may be nested and therefore must not be added. For example, residual_assembly may be included inside run_wall. Call counts are retained as a single value when every rank agrees and as a min/max range otherwise.

Comparing two programs

A defensible performance comparison requires the same physical model and a declared accuracy target. Record at least:

  • equations, material laws, loads, constraints, and time interval;
  • mesh, element order, global degrees of freedom, and time increments;
  • nonlinear and linear tolerances, algorithms, and preconditioners;
  • requested fields, output cadence, and checkpoint policy;
  • hardware, rank/thread count, software versions, and timing boundary;
  • field norms, quantities of interest, balances, and convergence evidence.

Startup, mesh generation, JIT compilation, solve, output, and verification should be compared separately. A faster run with different physics or a looser accuracy target is not an AgentFEM performance claim.

Do not confuse recording a discretization with requiring it to remain fixed for every comparison. Execution optimizations hold it fixed; numerical-method studies may vary mesh or time grid while comparing error, histories and quantities at common physical stations. Bitwise equality is appropriate for unchanged arithmetic, not a universal gate on new algorithms or hardware. Wall-time targets and winning worker counts are observations on a declared machine, not scientific correctness requirements or global defaults.

Contact search measurement

tools/benchmark_contact_search.py measures the existing serial triangle BVH against exhaustive projection on a deterministic planar grid. It checks exact facet/status identity and tolerance-bounded points, normals, gaps and barycentric coordinates before timing. Tree construction is reported separately; warmed query batches alternate execution order and retain individual samples. Geometry, query and search implementation hashes identify the comparison.

OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1 PYTHONPATH=src python tools/benchmark_contact_search.py --output /tmp/contact-search.json

The 2026-10-09 local 128/512/2048-facet, 16-query audit evaluates only 32 exact facets per batch in each case, versus exhaustive counts of 2048/8192/32768. All projections agree. This confirms existing pruning; it is not a new algorithmic improvement, an end-to-end FEM speedup, or distributed scaling evidence. Timings depend on the machine and are not CI acceptance thresholds. Curved/ambiguous geometry and MPI correctness remain covered by separate regressions, not by this planar performance fixture. Profile a representative full solve before investing in search optimization; do not extrapolate this microbenchmark to industrial forming. Raw local samples are retained under evidence/contact/2026-10-09-search-microbenchmark.json in the repository.

Finite-material transport comparison

The 2026-10-09 controlled comparison uses clean source 07ee074b, macOS ARM64, Python 3.11.15, DOLFINx 0.11.0, PETSc 3.25.3 and MPICH 5.0.1. Other numerical jobs were finished first. Each pair alternates execution order after warmup; raw times, runtime versions and thread environment are retained. The two routes use the identical native J2 integration and tangent implementation: only the ordered per-point object protocol versus optional columnar transport differs.

Fixed workload Ordered median Columnar median Time reduction
4,096-cell complete trial, nine measured pairs 0.17674 s 0.08102 s 54.2%
4,096 cells, 200 increments, three measured pairs 38.8836 s 17.9949 s 53.7%

The first boundary excludes commit, restore and I/O. The second includes time integration, geometry/stability checks, transactions and scheduled monitoring, but excludes setup and disk I/O. This is approximately 2.16 times faster for the measured trajectory, not a general application or industrial-forming speedup. Both routes retain the same equations, tolerances and acceptance checks. All compared nodal/material fields agree within 2e-12 absolute/relative tolerance. The affine trajectory remains in the elastic branch of finite-strain J2 and independently matches displacement, stress and stored energy. It does not measure plastic-path throughput or validate the private route for public use.

Reproduction tools are tools/benchmark_material_transport.py and tools/benchmark_finite_hex_transport_run.py; raw records are evidence/hex8/2026-10-09-columnar-trial.json and evidence/hex8/2026-10-09-columnar-trajectory.json. Do not multiply these factors by separate checkpoint or geometry microbenchmark factors.

Bounded finite-plastic kernel consolidation (2026-10-10)

After the earlier capacity job exited, clean numerical source b8ac0ddd was measured on Apple M5 (10 logical cores, 32 GiB RAM), macOS ARM64, with the same default MPI provider on both sides and OMP/OpenBLAS/Accelerate limits requested as one. This does not suppress the separately diagnosed MPI background threads. Three paired 4,096-cell, 1,000-increment plastic trajectories alternate execution order after warmup. The reference reinstates the committed nine-column analytic tangent, general signed spectral algebra and former geometry contraction; the optimized route uses batched tangent directions, the applicable spherical reference-Gram bound and dense cached geometry contraction.

Same plastic trajectory Reference Optimized
Pair 1 85.580 s 53.751 s
Pair 2 88.388 s 54.628 s
Pair 3 88.648 s 55.055 s
Median 88.388 s 54.628 s

This is 1.62x, or 38.2% less elapsed integration time, for this workload. All final displacement, velocity, stress and state arrays are identical; numeric history entries agree within 1e-12. Material equations, time increments, energy accounting and rejection checks are unchanged. Preparation and disk I/O are excluded. This comparison already uses columnar transport on both sides and must not be multiplied by the earlier 2.16x transport result. It is not an arbitrary distorted-mesh, interface, contact, MPI-scaling or forming speed claim.

tools/benchmark_finite_hex_kernels.py --size 16 --pairs 3 reproduces this comparison from the development Git history, including pinned reference 0c0df953; it is not a wheel-only benchmark. Raw samples, source hash, runtime environment and separate microkernel timings are retained in evidence/hex8/2026-10-10-finite-kernels-trajectory.json. Microkernel speedups are not whole-solver speedups and must not be multiplied.

Scientific trust boundary

SimulationResult.performance is operational evidence. It does not modify trust_level, satisfy a verification claim, or establish accuracy. A release or publication performance claim must pair this record with benchmark and convergence evidence under a named applicability boundary.