7.2 Tier 1 unit and property tests
7.2 Tier 1 unit and property tests¶
| ID | Activity | Acceptance criterion |
|---|---|---|
| VER-01 | Charge conservation under smearing and azimuthal projection | Assembled charge on the deployed mesh within 1 × 10⁻³ relative to Q_net (PHY-19). On the producer path the export lattice's integral against Q_net (producer leg) and the deposit's against the lattice's (consumer leg) are gated separately, beside the quadrature agreement and the boundary ring; each broken construction of the WP28 plan's Design §5 fails its own gate and only it. At Tier 2 on the protonated 2WCD at the default sizes and on the charged walk at size_scale 4 (WP28) |
| VER-02 | Per-z-slice cumulative charge | Cumulative charge below each z plane matches the partial sum over the source charge list, to the VER-01 tolerance. On the producer path the source is the atoms in closed form under ½ erfc((z − p)/s), s = 0.5 nm, at 12 planes over their z-extent, and the export lattice and the deposit are each gated against it (§4.4 NOTE on the producer path; WP28) |
| VER-03 | Correction models reproduce their published check values | f^w_D(0) = 0.0601, f^w_D(0.75) = 0.9910, η⁰/η^w(0) = 0.3790, η⁰/η^w(1.45) = 0.9876, η(5.3 M) = 1.752 × 10⁻³ Pa s, ϱ(5.3 M) = 1194 kg m⁻³, D_Na(5.3 M) = 8.13 × 10⁻¹⁰ m² s⁻¹, D_Cl(5.3 M) = 1.071 × 10⁻⁹ m² s⁻¹, each to four significant figures; clamping above 5.3 M activates and is logged |
| VER-04 | Nernst–Einstein consistency at infinite dilution | μ_i⁰ = D_i⁰/V_T with V_T = 25.693 mV: 5.1922 × 10⁻⁸ (Na⁺) and 7.9089 × 10⁻⁸ (Cl⁻) m² V⁻¹ s⁻¹, to four figures |
| VER-05 | Einstein-ratio drift | D_i/μ_i rises to 1.2–1.7 × kT/e between 0.15 M and 3 M and matches the expected profile. A test asserting D_i/μ_i = kT/e at finite concentration SHALL NOT be written (PHY-14) |
| VER-06 | Wall-distance field | The field is continuous and its gradient jump across element boundaries stays within a stated tolerance on a cylinder, and a further mollification (NUM-31) reduces it; d = 0 on the pore boundary; membrane absent from the distance source set (PHY-02) |
| VER-07 | Integration order on 1/r forms | Every form carrying a 1/r factor asserts integration order ≥ 3; assembly on an axis-touching mesh returns no NaN and no Inf |
| VER-08 | Packing fraction and positivity | Φ = Σ_j N_A a_j³ c_j < 1 and min_i c_i > 0 at every nonlinear iterate; violation aborts with the offending quantity and its location (PHY-06) |
| VER-09 | Case-file schema round-trip | A written and re-read case file yields a semantically identical run configuration; an unknown key is rejected with a diagnostic naming the key |
| VER-10 | Mesh quality gates | On known-bad input the gates fire: min SICN/gamma > 0.3 required, run aborts, diagnostic names the worst element and its location. Reference figures: minimum 0.6378, mean 0.9765 |
| VER-11 | Current-extraction route agreement | On a stored converged fixture, the ψ-domain-integral and the variational reaction flux agree to a tolerance smaller than the rectification signal at the lowest bias in the envelope |
| VER-23 | Artefact content addressing | The hash of a fixed artefact is a stated constant, reproduced in a fresh process under a varied PYTHONHASHSEED; every leaf change of the parameters or of an input hash moves it; representational differences that validation removes (1 against 1.0, -0.0 against 0.0, key order) do not; an unhashable payload is refused naming its path; a payload file edited on disk loads as hand-substituted rather than aborting; a stage computed twice from one recipe writes byte-identical payload files, and no wall-clock time enters a payload, or stage 7's summary, which the manifest's charge group copies (WP32) (§5.3.2, FR-27) |
| VER-24 | Provenance manifest completeness | All eight field groups of §5.3.3 are present, a group no stage contributed carrying a status and a reason rather than being omitted; every switch-typed field of the case schema is classified either as a switch with a validated default or as a configuration choice with a written reason, in both directions, so that a switch added later without a default fails this test; the physics model's own deviation enumeration agrees with the case's on the switches they share; the environment group is populated without importing NGSolve (FR-25, IF-08) |
| VER-25 | Stage protocol | Every stage registered in §5.2 reports its name, number, inputs, outputs and artefact schema, and the facts a walk acts on (whether its constructor takes a workspace and a store, whether its key is its artefact, its progress weight and the case section it needs), without importing its implementation module, asserted on sys.modules in a fresh process; each stage's own description is the registry's, so the two cannot drift; progress is monotone in [0, 1] and ends at 1; a cancellation token raises naming where the stage stopped, and is not a subclass of the numerical gate errors (FR-27, IF-01) |
| VER-27 | Mesh ingestion and tagging | A tagged mesh written as Gmsh MSH 4.1 and read back preserves vertices, connectivity, per-group physical tags and group names (IF-06); a group left unclaimed by inputs.mesh.groups, or a vocabulary name the run selects on that no group supplies, aborts with a diagnostic naming both lists (QR-12); the default ingestion path imports neither gmsh nor netgen's Gmsh reader, asserted on sys.modules in a fresh process (CON-10) |
| VER-28 | Reference-geometry conformance | The assembled (r, z) region has exactly three domains — pore body, one membrane, one electrolyte — and is conformal at the membrane-to-pore junction: the membrane meets the pore on the pore's own outer surface, at r = 2.7524 nm on z = −1.4 and r = 4.88 nm on z = +1.4 within the fragmentation tolerance and read from the fixture rather than hard-coded, and over one shared edge chain rather than two coincident ones; no membrane material lies inside the fluid set; the region carries exactly the §5.3.1 vocabulary and its mesh meets the §5.2.2 quality figures (FR-09) |
| VER-29 | External field ingestion and charge conservation | An (r, z) grid round-trips through the native format and through OpenDX and CCP4 with the singleton-axis convention, origin, spacing and values preserved, on every supported interpreter (the IF-05 NOTE of §3.1), the native .npz carrying its origin and spacing so they read back bit for bit, while one carrying axes alone still reads (WP46); a genuinely two-dimensional array is refused naming its shape; the interpolant's axis order is asserted against a field that is not symmetric in its arguments, so a transposed array fails rather than agreeing on the diagonal; the field is zero outside the grid box rather than continued by its edge value; on the deployed finite-element mesh |Q_mesh − Q_net|/|Q_net| < 10⁻³, reported as the producer and consumer legs of the §4.4 NOTE and gated separately, with the axis-guard deficit and the boundary-ring maximum reported beside them; the ramped per-plane cumulative agrees to the same tolerance at every plane; a grid whose boundary values are not negligible against its interior aborts naming the value and its (r, z); a field declaring no Q_net gates the consumer leg and records the producer leg as not run. The conservation half is asserted at Tier 2 on the reference mesh of §5.2.1, against a field whose Q_net is known in closed form, and a deliberately coarsened mesh there fails the quadrature-agreement gate rather than the conservation gate — which is the distinction the §4.4 NOTE makes normative (QR-03, PHY-18, PHY-19, IF-05, FR-14 in part) |
| VER-30 | Dielectric blend | The sharp solid fraction reproduces PHY-20's piecewise assignment to round-off at every quadrature point; a χ outside [0, 1] and an inverted χ both abort with the offending quantity and its location, the latter on the per-material means; an absolute ε_r field is refused with the §4.4 NOTE named; the ion-exclusion shell is ε_r,f⁰ exactly at 3 M with the permittivity correction on; a smooth χ blends towards ε_r,f⁰ inside a solid and towards the nearest solid's ε_p in the fluid, each checked at a point; exclusion's mean χ is held below 1/2, not to the fluid ceiling, for a supplied χ as for a derived one (FR-15; the last two clauses but one, author ruling 13; the last, WP30 D6) |
| VER-32 | Command-line surface and exit-code contract | Every subcommand parses and dispatches to the stage objects it names; the registry is listed in a fresh process that imports no stage implementation module, no NGSolve and no netgen, asserted on sys.modules; each exit class of the §3.1 IF-02 NOTE is produced by an input that triggers it; every public exception type in the package is either classified by the exit-code enumeration or excluded from it with a written reason, in both directions, so that a type added later fails this test; diagnostics appear on standard error and standard output carries only the command's result; a gate abort prints its QR-12 diagnostic without a traceback unless one is requested; a run given a store writes every file it produces inside that store, asserted by running from a working directory the process-default fallback would land in and requiring it to stay empty; geometry cylinder writes a region file that plot draws, and mesh <geometry file> and mesh reference write an MSH 4.1 file that a case using the inputs.mesh.groups mapping they print ingests through the VER-27 gate unchanged, the hash they print is the mesh hash that run's manifest records, a mesh failing the VER-10 quality gate is refused with its QR-12 diagnostic and exit class 4 and leaves no file behind, and --help on either imports no netgen; stage <name> --export writes each stage's interchange file, the profile and the mesh equal to the store's bytes and the PDB and DCD reloading with the artefact's coordinates, moves no artefact key, and refuses a stage or a suffix it does not write with exit class 2 before any stage runs and without leaving a file (WP25) (IF-02, IF-05, FR-27, the §5.3.2 workspace-locality NOTE, the §3.1 IF-02 generators and export NOTEs; the geometry command §8.2.22 X1, WP57) |
| VER-33 | Field export exactness and the Ω/Ω_w split | A quadratic exported and read back is reproduced exactly at all six nodes of every element, which a permutation of the midside nodes fails; the exported node count is nv + nedge; the two files carry the whole-domain and fluid-only field sets respectively and the fluid file contains no solid node; attribute names carry SI units and the values match the §6.3 scale conversion to round-off, with the 2π of the axisymmetric measure absent; heavy data is compressed (IF-07) |
| VER-34 | Solution-state round trip and descriptor gate | Save followed by restore reproduces every component's coefficients to zero difference; the stored wall-distance vector is restored rather than re-solved; a descriptor differing in the solve-provenance digest, mesh hash, element order, domain restriction, degree-of-freedom count, model options, wall-distance sources or saturation distance, or stabilisation mode each abort naming the key and both values; a payload of the superseded schema version is refused by schema rather than misread; a case differing only in name: or outputs: restores, and keys the same stage-10 artefact (FR-27, QR-08 in part) |
| VER-36 | Sweep plan: substitution, validation and the warm-start forest | A product of axes enumerates the points of §5.3.4 in wave order; an assignment-valued axis moves several case-file paths together; a point identity is stable across processes and unchanged by a value inserted on another axis, while its index is not; every point has exactly one parent one grid step nearer its axis origin, its wave is its depth, and the points of one wave are pairwise independent; an axis rooted away from its first value walks outward in both directions; a misspelt path is refused naming the component and the prefix that exists, and a value of the wrong declared type is refused before any solve; a point the case schema resolves but §6.5 refuses fails when the plan is built, naming the point and the reason; a plan asking for rectification whose axes produce no exactly-opposite bias pair is refused naming the axis; the plan records each input file's hash, moves its own hash with them, and refuses to build a member from a file that has changed or gone, and a plan file whose recorded point ids, hash or file digests no longer match what it enumerates is refused (FR-24, IF-03, QR-12; CODE_REVIEW_003 CR-5, CR-6) |
| VER-37 | Warm start across a sweep step: the descriptor partition | Every leaf of the descriptor a real solve produces appears in exactly one of the space and operator sets, and every entry of both sets appears in the descriptor, in both directions; a warm-start load accepts a payload differing only in operator keys and records each of them; it aborts naming the key and both values on a differing mesh hash, degree count, field record, boundary set, variable branch or stabilisation mode; a payload of a superseded schema version is refused by schema; the loaded state carries no residual and does not read the stored wall-distance vector; and a point reached warm reproduces the same point solved cold to better than the 1 × 10⁻⁶ nonlinear relative tolerance, with the measured difference reported. The partition is verified at Tier 1, on the descriptor alone; the warm-against-cold agreement is a Tier 2 activity, needing a converged pair (FR-24, the §5.3.2 warm-start NOTE) |
| VER-38 | Sweep dispatch and collection | A single-member dispatch exits with that member's own class for each of the case, gate, convergence and cancellation classes, and produces the same scalars run alone into an empty store as it does inside the sweep; a local multi-worker sweep exits 0 with a failed member present and nonzero under fail-fast; a member whose parent artefact is absent falls back to the full ladder and records the reason; the worker thread pinning is in place before the linear-algebra libraries are imported, asserted in a spawned process; the dataset round-trips to identical values, a failed member's quantities are absent rather than defaulted, and the rectification of an exactly-opposite bias pair equals the two-point ratio of §6.7 taken from the same two records. The dispatch and collection surface is verified at Tier 1; the equivalence of a member solved alone and the same member solved inside the sweep is a Tier 2 activity (FR-24, IF-02, FR-23) |
| VER-40 | Wall-distance admissibility and the correction driver clamp | Both wall forms are continuous across d̄ = 0 and return their wall values for every non-positive sample, rather than the sign-reversed values the ion form's root at −P₂ would otherwise give, on the numeric and the symbolic evaluation path alike; a distance field whose minimum falls below the NUM-34 threshold aborts naming the gate, the measured minimum, its location and the fraction of samples below zero, and one above the threshold passes; the field gated is the mollified one wherever NUM-31's smoothing is applied, and a configuration activating no wall correction is not gated; and a mesh coarse enough to violate the gate is refused rather than returning the current whose two extraction routes disagree by 40 %, while a mesh that passes agrees between the routes to better than the NUM-26 tolerance. The clamp and the gate diagnostic are verified at Tier 1, on the correction functions and one field; the route-agreement half needs a converged pair and is a Tier 2 activity (PHY-02, NUM-34, NUM-26, QR-04, QR-12) |
| VER-41 | Stabilisation terms, the mode registry and the Pe_h diagnostic |
The element size the stabilisation parameters are defined against is asserted elementwise against its measured convention rather than assumed, so a backend that changed it fails here rather than retuning the mode in silence; the registry lists exactly the modes of §5.3.1 and refuses an unknown name listing them; the none entry produces an assembled residual identical to the unstabilised one, asserted on the vector and not on the mode string; the streamline parameter takes both branches of ψ(q) = min(q, 1) and is continuous at the crossover; the crosswind viscosity is exactly zero on every element at or below the Péclet number its tuning constant sets, positive on one above it, and bounded by D_i(C Pe_K − 1); the crosswind projector annihilates the advective velocity and is idempotent; the crosswind, streamline and grad-div terms request the integration orders of NUM-15 and the 1/r minimum of NUM-07 respectively, asserted on the quadrature request rather than on a number; an equal-order velocity–pressure pair is refused in every mode that supplies no flow stabilisation, naming both inf-sup and the mode that would permit it, and accepted in the mode that does; a velocity order left unset reproduces the previous two-order model exactly; the Pe_h diagnostic warns naming the species, the value and its (r, z), is silent below the threshold, and runs in the unstabilised mode; and every mode's linearisation is finite at the zero-wind cold state φ̃ = 0, c̃_i = 1, u̅ = 0, asserted on the assembled Jacobian entries rather than on the residual, because the residual is finite there in every mode and only the linearisation is not (NUM-03, NUM-11, NUM-12, NUM-14, NUM-15, QR-12) |
| VER-43 | Desktop shell, schema-generated editor, solver process and packaging probe | The schema walk enumerates exactly the editable dotted paths of the current case schema (nanopnp/case/v0.5), asserted in both directions so that a field added later fails this test rather than becoming silently uneditable, and the switch classification of FR-25 is checked against that same walk rather than a second one; the shell's view-model layer imports neither PySide6 nor NGSolve, asserted on sys.modules in a fresh process, and no PyQt module is reachable from any import path the shell takes (CON-09); every option the editor offers comes from the schema's own declared type or from a live registry, so no value set is written in gui/; a value the schema refuses is refused at the field before any substitution, naming the path, the value and the declared type, and a document the registries refuse produces the same diagnostic text the command line prints for the same file; run control drives the case through a spawned process, forwards a monotone completion fraction ending at 1, receives each stage transition as data — the stage's name and its position in the walk, through a structural hook, never recovered by parsing a progress caption — and cancels through a token the child honours, a cancelled run writing no artefact; a failed run reports the §3.1 exit class the command line would return for the same case; and the packaging probe imports PySide6, QtWebEngineWidgets, NGSolve, Netgen and ngsolve.webgui in one process, its bundle carrying the CON-11 licence notice (IF-09, QR-11, FR-27, CON-09, CON-11, RSK-13, §8.2 criterion 4 as amended by A4) |
| VER-44 | Live convergence monitoring and field visualisation | The rung and the Newton step reach the shell through a structural hook carrying the residual and the undamped relative update as numbers, never recovered from a progress caption; the hook is not an input, asserted by running the same case watched and unwatched and requiring one artefact hash and one store entry; a rung that reports no Newton step yields a rung record all the same, and the plot shows it as a labelled band rather than interpolating a line across it, naming which of the two silences it is: a rung whose model takes no Newton callback and can report no step, or a coupled rung whose damped-Newton solve found the entry residual already below its target and returned before its first step — the NUM-16 warm-start case, which most of a warm ladder does. The hook SHALL carry that distinction as data, taken from the same test that injects the callback, because the two silences are identical from the far side and annotating either as the other states something about the solve that is not true; a solve served from the store is named as such rather than drawn as an empty plot; the plot draws no convergence threshold, the criterion of NUM-16 being per rung and disjunctive, and reports per rung which test the recorded numbers prove ended it rather than which the solver evaluated first: the two tests are not exclusive, so what may be asserted is the exclusion — a forced last step, which NUM-16 bars from the update test, and a last relative update above the rung's own tolerance each leave the residual test as the only one that can have closed the rung, and otherwise the update test is reported as met without also claiming the residual test was not. That tolerance SHALL travel with the rung rather than be assumed from the reference settings, so the reading is against the number the solve used; the band also reports the minimum damping and how many steps were forced; the field viewer renders a solution restored through the stage-10 gate rather than an export, its field names and units come from the IF-07 attribute vocabulary rather than from gui/, the scene reaches the view as a file rather than a data URL, a sample no logarithmic axis can place is omitted and counted rather than drawn at the axis floor, and a document that loaded without its renderer is reported as a diagnostic naming the renderer source rather than shown as a blank panel; the renderer is shipped with the package rather than fetched, is byte-identical to the npm tarball the repository keeps as its corresponding source, that tarball's SHA-512 is npm's published integrity, and its version is the one the installed netgen.webgui pins, so an upgrade that moves the pin fails the gate rather than drawing nothing; and the packaging probe's selftest fails when the bundled renderer does not reach its document (IF-09, QR-11, FR-27, NUM-16, NUM-18, QR-12, CON-09, RSK-13) |
| VER-45 | Documentation surface and the public API | The documentation site builds with the generator's strict mode, so that a broken internal link or cross-reference fails the build, on every push including prose-only ones (§7.6). The generated references (case file, command line and exit codes, public surface) are produced from the schema, the parser and nanopnp.__all__ and are verified at review; import nanopnp imports no ngsolve, netgen or numpy module (§8.2.13 N1, N3) (IF-01, IF-02, IF-03, QR-15 in part; §3.1 IF-01 public-surface NOTE) |
| VER-46 | Executed worked examples | Every command in an example's tagged console blocks is executed verbatim, from a copy of that example's directory, and exits 0, or 4 in a block tagged as a gate refusal (WP25); each example meets an oracle stated in its README that is a property of the model rather than a transcribed number: an uncharged pore with symmetric reservoirs rectifies to unity within solver tolerance; a pore carrying negative fixed charge has a cation transport number above one half; a run with every correction set to none lists each of them under the manifest's deviations from the validated default; the two current-extraction routes of FR-23 agree to the tolerance QR-04 already gates; and fields read back from the IF-07 export carry the attribute names that vocabulary defines. On the geometry pipeline (WP25): the deposited 2WCD entry is refused by stage 1's orientation gate, naming an angle above its limit; the entry prepared outside the pipeline walks stages 1 to 6 through every gate, and the manifest records the artefact of each; the aligned structure's export passes stage 1 under symmetry.axis: z; the exported density map, read in ångströms as a viewer reads it, contains every heavy atom of that export and reads at least the single-frame bound exp(−(√3h/2)²/(σR_min)²) at each atom's nearest node; and the exported stage-4 profile, supplied through inputs.profile, meshes to the same mesh content hash under a different key. On the charge pipeline (WP32): the prepared entry walks every stage to a charged solve under the validated model, listing no deviation; the manifest's Q_net is the integer the exported PQR's charge column sums to; the conservation report is in the manifest with each leg and each worst plane below its tolerance; the negatively charged lumen is cation-selective, t₊ > ½; the exported PQR, supplied through inputs.pqr, gives the same atom table and the same deposit; both FR-15 switches set away from 0 are listed as deviations and mesh an exclusion material; and the exported charge lattice, re-supplied through inputs.charge on a mesh that does not resolve it, is refused by stage 7's gate. Examples 01 and 02 run on every push (02 through its ePNP-NS leg and the VER-62 golden); every other example runs nightly (§8.2.13 N4). No number appears in the user documentation as a result unless an example asserts it. Runs in the Tier 2 directory for its runtime; an example whose solve takes minutes (the reference geometry) is marked slow and is recorded rather than gated, its cheap steps still gated at Tier 1 (QR-15 in part, IF-02, IF-05, FR-01 to FR-10, FR-12 to FR-15, FR-23, FR-24, FR-25, FR-27) |
| VER-47 | Case schema v0.5 and the supported interpreter range | Every case document must declare schema nanopnp/case/v0.5 exactly, and any other schema identifier is refused naming the expected schema and the declared identifier before any structural validation; the Python range declared by requires-python, the trove classifiers, the ruff target and the CI matrix agree with each other and with §2.5 (IF-03, QR-09; §5.3.1) |
| VER-48 | Structure ingestion, the Cₙ axis and the oligomeric state | A synthetic Cₙ assembly (n = 7, 8, 12) about a known axis tilted 35° and offset by (3, −1.5, 40) nm, its chains lettered in a shuffled order, recovers that axis to 1e-9 in direction and in offset; with per-atom noise of 0.02 nm on an assembly whose second moments are isotropic, the permutation axis stays within the 0.01 nm displacement budget over the axial extent while the principal axis nearest the truth does not; a 6 × 2 arrangement, a D6 assembly, a ring whose chains do not turn with it, auto on C1, an axis 30° from the file's z and axis: z 0.02 nm off the detected axis are each refused naming the gate and the measured value; the in-project Kabsch rotation equals MDAnalysis rotation_matrix to 1e-12; a blank element, an alternate location, a missing, surplus, unlisted or truncated chain, a residue-name disagreement, a non-cyclic point group, a chain list not numbering n, a non-positive frame window, a selection MDAnalysis cannot parse, a trajectory of other atoms and an mmCIF model listing its atoms in another order are each refused naming the atom, chain, key or file, while an alternate location in a chain source.chains leaves out is not; PDB and mmCIF of 2WCD read to the same atom table and coordinates within 5e-5 nm, and DCD, XTC, TRR and NetCDF read back the bytes written within each format's precision; the frame stride ends on the last frame, and a last_ns beyond an untimed DCD's recorded span and a count beyond the window are refused naming both; rigidly moved frames superpose to within 1e-5 nm; the deposited 2WCD frame is refused by the orientation gate, and its chains A–L moved rigidly into an admitted frame run as stage 1 with 12 chains and 285 common C-alpha, recover the applied tilt to 1e-6 degrees, keep z = â·x, and re-detect their own axis as z through r = 0 to 1e-6; the artefact round-trips, its key is stable across processes and does not move when structure.source.variant is relabelled, an export of chains keyed by segment identifiers or by keys longer than one character keeps every chain distinct (CODE_REVIEW_003 CR-9, CR-13), a hand edit of its payload is recorded as one, and its PDB and DCD export reloads, with residues that differ only in insertion code kept apart; a full walk and a sweep over a structure: case are refused naming the first stage not yet delivered (stage 2 when WP18 landed; stage 4 since WP19), structure: beside inputs.mesh is refused, and the stage is listed with neither MDAnalysis nor gemmi imported while a missing extra is named when it is created. At Tier 3 the author's ClyA-AS ensemble passes every gate over all 98 frames, recorded; a trajectory with no frame, a second Cα at one chain's residue and a chain wrapped across the box in one frame are each refused naming the file, the chain and residue, or the chain, pair, frame and distance, while the vendored 2WCD and every frame of the Tier-3 ensemble pass; a DCD's frame times equal the decoded ones exactly with no frame decoded (WP46) (IF-04, FR-01, FR-02, FR-03, FR-27, QR-12; §5.3.1 NOTE on structure:; WP18) |
| VER-49 | The density map | One atom gives g exactly and two give g₁ + g₂ − g₁g₂, to 1e-15 in float64 and within the float32 store's rounding; on random clusters with coincident atoms the map is finite and within [0, 1], and a NaN or out-of-range value injected into a slab aborts the run naming the voxel; the truncated map differs from an untruncated evaluation, at every voxel, by no more than the sum of g/(1 − g) over the terms dropped there; two frames of one displaced atom average to (g_a + g_b)/2, not to their union; the grid nodes are integer multiples of the spacing, the axis is a node column, every kept term lies inside the box, and the node set is unchanged by permuting frames; each lookup route of the radius set resolves — a residue, a histidine name, HIS where the histidines agree, ILE CD1, OXT and each terminal patch — while an unknown residue, an unknown atom, a zero radius and a HIS atom the histidines disagree on are each refused naming chain, residue number, residue and atom; the shipped table equals PDB2PQR's CHARMM.DAT entry for entry; the map round-trips through .npz exactly and through OpenDX and CCP4/MRC, written in ångströms (§8.2.2 B10), whose own headers read without conversion carry ten times the origin and spacing in nm, with the origin, spacing and shape exact once read back into nm and the values within each format's precision, and a file whose origin is off the lattice of the spacing is refused, as is one written in nm, whose spacing read in ångströms falls below the stage-2 range (WP46); the stage-2 key is stable across processes, moves with every density key and with the radius file's digest, and a hand edit of the payload is recorded as one; a grid_spacing_nm outside [0.025, 0.05] nm and a non-positive or non-finite sharpness are refused naming the value; a structure: case walks to stage 3, while a full walk and a sweep are refused naming stage 4, geometry: beside inputs.mesh is refused naming both, and the stages are listed without importing their modules or numpy. At Tier 2 every heavy atom of the prepared 2WCD dodecamer resolves and its map is bounded; at Tier 3 the author's ClyA-AS ensemble runs over the paper's final 50 frames, DCD frames 48 to 97, recorded (FR-04, FR-27, IF-05, QR-12; §5.3.1 NOTE on geometry.density; WP19) |
| VER-50 | The reduction to (r, z) | The annular weights sum to each annulus's exact area to 1e-11 relative, each interior cell's weights to its area, and the integral of a map is conserved slice by slice to 1e-12; an off-axis Gaussian matches the closed forms for the azimuthal mean, the Cₙ variance and the raw variance (WP19 plan, Design §2) for n = 7 and 12; an on-axis Gaussian and thin rings give a Cₙ variance below 1e-6, while the same computation without the detrend reads above 1e-4, so the test discriminates; a cos(mθ) modulation of amplitude b gives b²/2 for both variances when n divides m, and otherwise a Cₙ variance of zero and a raw variance of b²/2; a synthetic C12 assembly deposited through stage 2 reduces unchanged to round-off when turned by 90°, to the float32 rounding of its map when turned by 30°, which maps it onto itself, and within the off-axis tolerances when turned by 15°, which does not; no harmonic is used below n h/π, the header names that radius, and the artefact round-trips, exports as three radial grids, and records a hand edit. At Tier 2 the prepared 2WCD reduces with its integral conserved to 1e-9 relative, its Cₙ variance nowhere above its raw variance by more than 1e-4, and an open lumen on the axis (FR-05, FR-06, FR-27, CON-04, IF-05; §5.3.1 NOTE on geometry.density; WP19) |
| VER-51 | Contour extraction, conditioning and its gate | A square pyramid 1 − max(|r − r₀|, |z − z₀|)/a centred on a node, linear along every grid edge, is contoured on its square to 1e-12, with the area (2a(1 − l))² − h²/2 to 1e-12; a Gaussian section's contour lies within 1e-3 nm of its circle of radius s√ln 4, against a linear-interpolation bound of 4.7e-4 nm; Taubin scales the area of a regular 64-gon by its closed form f(k₁)^{2N} = 1.002770, where a Laplacian of the same N passes scales it by (1 − λk₁)^{2N} = 0.952933, both to 1e-12; closing and opening by 2h fill a 0.1 nm slot, remove a 0.1 nm fin and keep a 0.3 nm slot, within the corner-rounding bound δ²(1 − π/4) per corner, and the conditioned loop's feature size exceeds 2h; a void is filled and recorded with its area, an island is refused naming its centroid and area, and a lumen closed on the axis is refused naming its z range; each §5.2.1 criterion fires on a loop built to fail it, naming the criterion, the value, the threshold and the (r, z); the probe profile of a ring of atoms matches √(ρ₀² + (z − z₀)²) − R_a to 1e-12 and two frames give their mean; the artefact loads through load_profile as a pipeline profile, its key is stable across processes and moves with each constant that moves a vertex or a verdict, and a hand edit is recorded; the refusals of the §5.3.1 NOTE on geometry.contour hold, and a walk past stage 4 runs once stage 5 is delivered (the stage-5 walk refusal, which held until WP21). At Tier 2 the prepared 2WCD passes the gate and its payload loads as a profile; at Tier 3 the ClyA-AS ensemble's verdict and measurements are recorded (FR-07, FR-08, FR-27, QR-12; §5.2.1; §5.3.1 NOTE on geometry.contour; WP20) |
| VER-52 | CAD assembly on any profile (stage 5) | A parallelogram body's widest-margin chord is its mid-line, with clearance half its perpendicular width; on the fixture the drawn (2.0, 3.5) and the derived chord give face areas equal to 1e-10 relative and equal edge-name counts; a profile translated by Δ with centre_z_nm = Δ gives the untranslated region; a body cut more than twice by a plane is refused by the one-face criterion, naming each face's centroid and area; each criterion of the §5.2.1 NOTE on the membrane junction fires on a profile built to fail it, naming the criterion, the value, the threshold and the (r, z); the nanopnp/region/v1 record round-trips and rebuilds equal areas; the key is stable across processes, moves with each constant and records a hand edit; an edge lying on a bilayer plane extends the interval to its far end in each of the four orientations of a step flush with either plane (CODE_REVIEW_003 CR-4); the stage is listed without importing netgen; the refusals of the §5.3.1 NOTE on inputs: for inputs.profile hold. At Tier 2 the fixture through inputs.profile has 193 vertices and 195 edges named as VER-28's, the junction at 2.7524 and 4.88 nm over one node chain and no bilayer in the fluid (FR-09, FR-27, QR-12; §5.2.1 NOTE on the membrane junction; WP21) |
| VER-53 | Meshing a generated region (stage 6) | wall_h_nm: auto is 0.05, 0.05, 0.03505 and 0.02715 nm at 0.05, 1, 3 and 5 M, crossing at 1.4741 M, and λ_D matches NUM-30's check values to 5e-4 nm; size_scale multiplies every size and temperature enters λ_D as √T; each refusal of the §5.3.1 NOTE on numerics.mesh names its key; a salt axis severs warm starts only where it moves the wall size, and two concentrations below 1.4741 M share one stage-6 key while two above it do not (WP46), unless wall_for_concentration_M sets the wall at a design concentration, when every salt at or above it shares one key and its run still reports its own NUM-30 ratio (WP51); a coarse synthetic region meshes through VER-27 and VER-10, round-trips its content hash through MSH 4.1, gives one content hash in two fresh processes without importing Gmsh, fails the wall-size gate with its wall field withheld, naming the segment, and is refused without a protein permittivity; every consumer to the IF-07 export reads stage 6's file; a reproduced mesh with another content hash aborts naming both. At Tier 2 the fixture's element count is within 0.1 % of the drawn reference's and meets §5.2.2's quality band, and the prepared 2WCD meshes at the default sizes, passes both gates and walks to stage 12 with every stage keyed in the manifest (FR-10, FR-27, QR-08, QR-12, CON-10, NUM-30; WP21) |
| VER-54 | The optional Gmsh backend (stage 6) | The region graph read from stage 5's glued shape has the record's edge-name counts and, in closed form, its face areas to 1e-9, every loop closed and counter-clockwise, every arc on the reservoir circle, and needs no Gmsh; an edge neither straight nor a reservoir arc is refused naming its midpoint. The coarse synthetic region meshes on both backends through VER-27, VER-10 and the wall-size gate with one vocabulary, each domain's triangle area equal to the record's less the circular segments its arc chords cut off to 1e-10, every region vertex a mesh node; a clockwise profile meshes with no inverted element, where a loop handed to Gmsh clockwise inverts every triangle of its face; two fresh processes give one content hash and write nothing to standard output; an open Gmsh session keeps its model and options; a Gmsh failure is refused quoting its log, classified with the gates; the stage-6 key names the backend and its settings, and netgen's equals the one recorded before the backend existed, re-recorded once by WP46 when the key dropped λ_D. Without the extra, a missing module or a native library the wheel could not load is refused at stage 6 naming the extra and the error, classified as a case refusal, and resolving and keying a Gmsh case imports no Gmsh; the lock names no component CON-12 excludes. At Tier 2 the reference fixture meshes on both backends to one vocabulary with its 185 profile vertices as nodes, Gmsh holds the wall-size gate at 0.05, 0.03505 and 0.02715 nm and VER-10 at size_scale 1, 2, 4 and 8, and WP22's frozen case conducts alike on both backends' meshes at size_scale 2: |G_gmsh/G_netgen − 1| ≤ 1e-3, eight times the measured 1.24e-4 and below the 2.0e-4 that refining netgen from size_scale 2 to 1 moves its own value. Every Gmsh-dependent test skips naming its reason where Gmsh does not import, and fails instead under NANOPNP_REQUIRE_GMSH=1 (FR-10, QR-12, CON-10, CON-12; WP23) |
| VER-55 | Geometry pipeline surfaced in the desktop shell | A third structural hook reports each stage's artefact after it is in the store, with the run record's schema and hash and whether the store already held it; a re-run reports every stage cached, and a watched walk and an unwatched one write one run record, one set of store entries and one manifest hash, the hook being bound after every key is taken. A spawned upto: mesh walk posts that report for each stage it stores and then finishes, writing no artefact when cancelled and leaving the convergence plot a plot of no solve. The geometry view-models import no Qt, NGSolve, Netgen, MDAnalysis, scikit-image, Shapely or Gmsh in a fresh process. A planted off-axis Gaussian's maximum lands in the display pixel centred on its (r, z), the top row at maximum z and r increasing to the right, the image's edges half a spacing beyond the first and last nodes; the stage-2 section of an axisymmetric map is symmetric about x = 0. The editor's move, insertion, deletion, undo and redo return exactly to the prior vertices; a self-crossing, negative-radius or coincident-vertex edit cannot be saved and is refused with the loader's own text, naming the vertex. The null edit reproduces its parent's vertices bitwise, records source: hand-edit, the parent's canonical digest as sha256 and the parent's name, and re-derives every measurement; the edited reference fixture is refused as a reference. The derived case (with_profile) has no structure:, loads and resolves, and differs from the original only in structure.*, inputs.profile.* and any geometry.density.* or geometry.contour.* the original set away from its default, which it resets because stages 2 to 4 do not run, field by field over the schema walk. measure equals stage 4's gate record wherever the gate passes and reports its first refusal's criterion, value and (r, z) wherever it raises; on stage 4's own loop it reproduces the artefact's gate record exactly, against the stored stage-1 and stage-3 artefacts. A contour stage 4 refused is recomputed from the stored map as stage 4's conditioning computes it, fails first where the refusal named, and a hand edit of it records that map's payload digest. The null edit, run through the derived case, gives stage 5 the same model-frame profile to the bit and stage 6 the same mesh content hash under a new key; moving the constriction vertex 0.1 nm outward changes both, and the manifest records the profile by content and no stage 1–4 artefact. The render child draws a run's stage-6 mesh by material under the run's viewer/, never as an artefact, its element count the MSH file's triangle count and its gate figures the quality report's on that file. The packaging probe declares and exercises MDAnalysis, gemmi, scikit-image, Shapely and Gmsh, failing naming the payload that does not work. At Tier 2 the prepared 2WCD is built, measured, null-edited and moved as above (IF-09, QR-11, FR-27, FR-07, FR-08, CON-04, CON-09, CON-10, CON-11, RSK-13, §8.2.2 B8, B9; WP24) |
| VER-56 | The physics-model interface | A walk of the package source outside physics/ finds no concrete model class, no comparison or match against a registered model name and no model name spelt as a literal, except in default_ladder, which §6.5 defines as a path through named models, and, for the literal alone, the schema's default physics.model and the validated default case; the walk is shown to fire on the registry, on each form of dispatch it names and on each exempt scope once the exemption is lifted. Every refusal a declaration makes — an unhonoured switch value, solids or a supplied inputs.charge or inputs.eps_r beside a model that does not accept them (pb beside a fixed charge among them, PHY-24), an unadmitted numerics.continuation, an undeclared outputs: quantity, a builder's own refusal, a generated mesh beside a model without solids, and a solid domain on the mesh of a model without solids — names the model, the key and what is admitted; resolving a case builds its model and imports no NGSolve or Netgen. A declaration admitting default_ladder for a model the ladder does not end at is refused at registration, and a builder returning a model under another name is refused by create; the ladder's two Poisson–Boltzmann rungs, which state their own λ_D, are built for a salt the case-file pb refuses. Whether the PHY-02 distance field is read is asked of the built model, the rule every rung follows: the quick-start case as pnp-ns with its corrections on is refused naming each correction key, and with them left at none, as pnp-ns reads none (VER-71), runs to stage 12 on the ladder and on a single rung, stores no distance field and restores. The stage-10 keys of epnp-ns, pnp-ns, pnp, pb and pb-linear on the quick-start case, each written with only the leaves its model reads, and the model of every rung of both NUM-18 ladders on it, equal goldens, every one re-recorded by WP42 because the schema identifier is inside each digest (G6: nanopnp/solution/v0.5); for pnp-ns, pnp, pb and pb-linear the residual of each edited case was also shown bit-identical to the unedited case's. A forwarding class defined in the test tree, subclassing no shipped model and registered at run time, runs a case file through stages 10 to 12; its state and stage-11 summary equal pnp's on the same case bitwise, and stage 11 restores the forwarding class. pb-linear and poisson run from case files on a solid-free slab and equal direct API solves to 1e-12 relative, pb-linear's with λ_D from case_debye_length_nm; a poisson case with a membrane and a supplied volume_charge_density equals the API solve given fields.charge.assemble(scales) likewise, and differs from the uncharged solve. At Tier 2 poisson reproduces the three-layer capacitor of PHY-20, whose piecewise-quadratic solution the P2 space contains, to 1e-10 of max |φ̃| (FR-20, FR-19, QR-14, PHY-20, PHY-21, PHY-24, §5.4.3; WP26) |
| VER-57 | Protonation and the PQR artefact | On fragments of 2WCD chain A, every atom's charge equals CHARMM.DAT's, parsed independently of PDB2PQR, to 10⁻⁶ e, and Q_net the hand count of the standard states; titration: none passes neither the pH nor a titration method, and gives identical charges at two pH values; PROPKA at a pH across a residue's pKa changes exactly that residue (GLU 18, ASP 21, ASP 25 between pH 2 and 8); a histidine named HSE, with or without its hydrogens, protonates as the HIS-named one; a terminal pKa PDB2PQR does not apply is recorded as unapplied from PROPKA's groups and the applied charges. Each gate — an atom PDB2PQR could not parameterise, an atom without a charge or radius, a charged atom whose radius is not positive, a Q_net further than 10⁻⁶ e from an integer, a PQR not holding the frame it was given — fires on its defect and names the frame. The PQR reader reads PDB2PQR's fixed columns, its fused coordinates, its four-character residue names and fused atom fields, a patched residue by its variant name, and a whitespace-separated file, and refuses, naming the line, a line the two readings read differently; MODEL frames holding different residues are refused naming the frame; the writer reads back exactly. A supplied inputs.pqr with another frame count, a residue the ensemble lacks or the frames out of order is refused naming the frame; one rigidly moved registers to 0.01 Å; the stage's export re-supplied beside the same structure: gives the atom table bit for bit. One frame changed of two re-protonates one. Without PDB2PQR a structure: case is refused naming the extra before its first frame and inputs.pqr runs. The stage is listed, and a walk that does not name it records it not run, without importing PDB2PQR or PROPKA; each protonation key's refusal names its keys. At Tier 2 the prepared 2WCD dodecamer at pH 7.5 gives its pinned Q_net of −60 e, −5 e on every chain, with the PDB2PQR and PROPKA versions named on failure; every heavy atom's radius equals the stage-2 CHARMM table's; twelve terminal oxygens are added; CYS 285 is unapplied in every chain and the LYS 8 N-terminus exactly where its pKa is below the pH; the export supplied beside the structure gives the payload. At Tier 3, recorded, DCD frames 48–97 are compared residue by residue with the archived PQRs 50–99, and the archived PQRs register through inputs.pqr (FR-12, IF-03, FR-27, QR-12, PHY-16 step 3; WP27) |
| VER-58 | The deposited fixed charge | PHY-16 step 5's kernel integrates to q_i to 10⁻¹² at r_i/w ∈ {0, 0.3, 1, 3, 30, 600}, is finite and even on the axis, and is the limit of the 3D Cartesian deposition binned by exact annular volumes, which converges to it at ≥ 3.5× per halving; the tiled separable sum equals the direct one to 10⁻¹³; an atom on the axis is short by h²/(6w²) unrenormalised and exact renormalised; a spacing above w_min/2 is refused naming the atom. The projection's element moments against every monomial of degree ≤ k equal the lattice's to 10⁻¹², NGSolve's field equals the payload's, and a payload on a permuted mesh is refused by its geometry digest. At Tier 2 one atom in a grounded sphere, solved with poisson, against its Kelvin-image closed form: the r-weighted L² error over a 2 nm disc about the atom falls at a rate ≥ 2.5 between the finest two of three near-atom meshes with the P2 deposit, and P0's falls slower; the on-axis error and an unresolved atom beside P0's are recorded (FR-13, FR-14, QR-03, PHY-16 steps 5–6, PHY-18; WP28) |
| VER-59 | The dielectric field and the ion-exclusion shell | With both keys at 0, the region, mesh, fields and stage-10 keys of a profile-driven case, and its region record's bytes, equal goldens recorded before WP30; the fields and stage-10 keys are recomputed under stage 7's v1 schema strings for that comparison, and also compared at v2 (WP32 D15). The shell: on a parallelogram body every wall node lies at a distance in [a − max(h_c²/a, a/100), a + 10⁻⁶] nm from the body; the shell is one exclusion face, wall outside and interface against the protein and the membrane, and a body edge left facing the electrolyte beside a shell is refused naming it; the membrane, its chord and its junction equal the shell-free region's; a body within a + h_c of the axis is refused naming the z interval; a necked pocket is filled and recorded; 0 < a ≤ 2h_c, the key beside inputs.mesh or without a profile, and a missing structure extra are each refused naming the keys; the record round-trips, rebuilds equal areas without Shapely and keys a only when it is non-zero; the Gmsh region graph reads its four faces; on netgen each shell wall edge is cut into at least ⌈L/(1.1 h)⌉ segments, so the shell meshes at 3 M (0.035 nm), where whole edges were refused; the manifest lists the switch and the mesh-material deviation. The dielectric: on the same body, along the normal at each water-facing edge's midpoint, the lattice χ equals S(s/δ + 1/2) to 4 × 10⁻³ for \|s\| ≤ δ, so its 1/2-level lies on the profile and its width is δ; χ is 1 in the membrane and in the protein away from water; the lattice's boundary samples are 0; W equals the region's protein-to-water edges; the range and registration gates pass, with exclusion below 1/2 on a 0.12 nm shell at δ = 0.2 nm, and an inverted derived χ fails registration; δ < h_c and δ beside inputs.eps_r are refused naming both keys. At Tier 2: through a generated shell on a cylindrical body at 0.1 M, solved with pnp at zero bias, the mid-plane potential across the shell is logarithmic in r and drops by λ_enc ln(R/(R − a)) / (2π ε₀ ε_r,f⁰) to 1 %, where R is the body's inner radius and λ_enc the mobile charge per unit length the shell encloses, and that drop is at least 10 % of the wall potential; VER-31's slab with its shell generated by this construction equals the drawn slab; the prepared 2WCD with a = 0.25 nm has every wall node at least a − max(h_c²/a, a/100) from the body, records the share beyond a + 10⁻⁶ nm that the closing and the filled holes leave, and passes VER-10 and the wall-size gate, at 0.15 M and at 3 M with the wall segments averaging at most 1.1 times the target, and with δ = 0.15 nm added walks to stage 12 with every gate passing and both deviations in its manifest (FR-15, FR-09, FR-10, PHY-02, PHY-20, QR-12; §4.4 NOTE on the derived solid fraction; §5.2.1 NOTE on the ion-exclusion shell; WP30; the 3 M and edge-division clauses WP30 review) |
| VER-60 | Charge pipeline surfaced in the desktop shell | On a profile-driven tube with a two-atom inputs.pqr, the artefact hook reports protonation and then charge after their store entries exist, both cached on a re-run, and a spawned upto: charge walk posts them after region and mesh and finishes, writing no artefact when cancelled. The charge map draws the export lattice as stored, the areal density in its nanopnp/field/v1 quantity and unit, in the model frame, the top row at maximum z: the positive atom's pixel holds its maximum and the negative atom's its minimum; its trapezoid-weighted block means carry the record's q_grid_e to 10⁻¹² relative at k = 1 and at k = 7 with partial last blocks; zero sits at the centre of a diverging table whose limit is the largest magnitude drawn; the recorded worst planes are marked at their z. The conservation view shows each leg's value, tolerance and ratio as the record holds them, a doubled tolerance doubled, and a leg not run as not run with its reason. The protonation view switches the charges and Q_net between the frames of a two-MODEL PQR, and through PDB2PQR and PROPKA on GLU 18–LEU 26 of 2WCD chain A shows 0 e at pH 2 and −3 e at pH 8, each frame's applied charges summing to it, the pKa column equal to the artefact's and the unapplied rows the summary's. The render child draws the coefficient the solve assembles, to viewer/charge.* and viewer/chi.* and never an artefact: ∫ ρ 2πr dA at the solve's order equals q_mesh_e to 10⁻¹², and the derived χ's mean over protein equals material_means. A generated shell's mesh lists exclusion. Each bounded number's spin range equals its schema bounds in both directions; an integer field steps by whole numbers, and a field that admits null is never a spin box, which cannot say "unset" (WP32); and a loaded pH of 7.25 is shown and not rewritten. NEUTRAL_SECTIONS is checked in both directions: a listed section written empty resolves as its absence, and keys every stage of 1 to 10 as its absence but stage 9, whose key is the validated dump and moves as the case hash does; every unlisted optional section is refused empty or resolves differently somewhere. The view-models import no Qt, NGSolve, PDB2PQR or PROPKA in a fresh process. The Charge tab drives the Geometry tab's run control to upto: charge and shows each stage's hash, the protonation row, the marked planes, the five legs and the deployed field under IF-07's name. The probe carries PDB2PQR and PROPKA, each exercised: the shipped fragment is the stage's own PDB of the deposited residues, its Q_net is 0 e at pH 2 and −3 e at pH 8, a stubbed data lookup fails naming pdb2pqr, and removed titration arguments, or PROPKA's parameter file out of reach, fail naming propka; the licence notice has a row for each payload. At Tier 2 the prepared 2WCD's picture carries −60 e to 10⁻¹² relative, its conservation view is its record, and its protonation view shows −60 e, no chain difference and the recorded unapplied rows, CYS 285 and the LYS 8 N-terminus in every chain (IF-09, QR-10, QR-11, FR-12, FR-14, FR-27, CON-09, CON-11, RSK-13, RSK-15, §8.1 Phase 3 increment; WP31) |
| VER-61 | The subpackage layering | The subpackage import relation of src/nanopnp, read from the syntax tree by nanopnp.validation.modularity with no implementation module imported, equals docs/project/modularity-layering.yaml in both directions, separately for the static relation (imports at module scope, inside a function and under TYPE_CHECKING) and the string relation (a non-docstring constant naming a module, as the stage registry and PUBLIC do), and each row's finding is null or a row of docs/project/modularity-findings.md or docs/project/review-findings.md; the module graph of module-scope imports, with each package __init__ an import runs on the way to its target, is acyclic, and a hand-built cycle through a package's __init__ is found; the subpackage top import relation has no strongly connected components of more than one subpackage, verified with Tarjan's algorithm, and a module-scope import nanopnp.mesh.primitives in geometry/analyte.py fails naming the component ('geometry', 'mesh') (H6; WP39); both upward: and deferred_upward: are empty (REV-05; WP39); and the measuring modules add no edge of their own. The top edges (module scope, outside TYPE_CHECKING) that point up the layer order of §8.2.8 H11 equal the file's upward: list in both directions, the deferred edges (imports inside a function) that point up it equal its deferred_upward: list in both directions (§8.2.8 H12), every row of either list names its finding, every subpackage has exactly one place in that order, and an edge from a subpackage the order does not place is refused naming it. An import nanopnp.gui substituted into core/constants.py fails naming core -> gui, the module and the line, and a recorded edge with no import behind it fails as removed; a module-scope import of nanopnp.mesh.primitives substituted into geometry/analyte.py leaves the static relation unchanged and fails as the upward edge geometry -> mesh, naming the module and the line, and the same import inside a function leaves both the static relation and upward: unchanged and fails as the deferred_upward edge geometry -> mesh, naming the module and the line; neither diagnostic offers recording the edge. A synthetic three-subpackage package is classified import by import into the four kinds, a relative import and a module-level try among them, its line numbers written by hand, and the strongly connected components and the lightest internal edges of a hand-solved graph are reproduced (§5.1, §8.2.7 G1, §8.2.8 H11, H12; WP35, WP37) |
| VER-63 | Withdrawn | The findings logs are read at review (§8.2.13 N3). The identifier is not reused |
| VER-64 | Stage conformance | Every registered stage class defines describe, key(inputs) and run(inputs, *, progress, cancel), read from the syntax tree by nanopnp.validation.modularity.stage_conformance with no stage imported, and each constructed stage's name is its registry name; a stage written in the test without key, or with key(inputs, cancel), does not conform; pipeline/run.py writes no collection of stage names, every one the walk uses being read from the registry. A stage is handed only the upstream artefacts it declares as inputs, and its own in the deviations pass; a walk to a stage runs that stage's transitive input closure, in walk order, so a walk to protonation on example 07 is case, structure, protonation, and a walk to solve on a depositing case includes charge. nanopnp.validation.modularity.undeclared_reads reads each registered stage's module, and the functions and methods it reaches, and finds no literal name passed to require, upstream.get or upstream[…] that the stage does not declare; the solve stage with charge taken out of its declaration is found, naming the stage, the name and the line. Each description's facts are the code's: takes_workspace and takes_store are its constructor's parameters, a stage whose key_is_artefact holds returns from key the artefact run returns, summary included and with no payload, and needs_section names an optional top-level case field outside NEUTRAL_SECTIONS, a needs_section naming no case section being refused by the walk, naming the stage and the section. Each declared optional input is one a shipped case drops while walking the stage, and the walks of those cases, written in the test, are unchanged; a stage whose case drops an input it does not declare optional is not walked, and is refused as the walk's target naming the input. register refuses, leaving the registry unchanged, an input that is neither case_path nor a stage registered before it, an optional input that is not one of its inputs and a weight of 0, −1, NaN or ∞; a description missing a fact is a TypeError. The walk's order, registration order with stage 9 first, equals the 13 names written in the test, and a stage registered later is walked after them (FR-27, IF-01; §8.2.8 H1; WP36) |
| VER-65 | The backend guard | The subpackages with a module that imports ngsolve or netgen at module scope, inside a function or under TYPE_CHECKING, or that names a backend submodule or attribute in a non-docstring string ("ngsolve.webgui", not the bare mesher name "netgen"), read from the syntax tree by nanopnp.validation.modularity.backend_imports, equal the backend: list of docs/project/modularity-layering.yaml in both directions, and that list is the eleven subpackages written in the test. An import ngsolve substituted at line 1 of structure/axis.py fails naming structure, the module and line 1; density/grid.py with its one backend import removed fails naming density as lost; a synthetic package is classified reference by reference into the four kinds, its line numbers written by hand, a bare "netgen" string and a package-relative import counted as none (QR-13; §8.2.8 H3; WP37) |
| VER-66 | The backend registries | Each of the mesher, linear solver and stabilisation mode is a registry in the shape of §5.5, and numerics.mesh.backend, numerics.linear.solver and numerics.stabilisation are strings validated against them; an unregistered name of each key is refused naming the registered ones, in the same text in check_document, on the command line and in the editor; register_solver("sparsecholesky", …) is refused naming NUM-21, an unregistered solver mumps is refused naming its NUM-21 reason, and duplicate registration is refused. A stub mesher, solver and mode registered at run time in the test validate through loads_case, check_document and nanopnp validate case, and the editor offers them; on the quick-start pore a solve naming the stub solver forwarding to UMFPACK and stub mode forwarding to none equals the plain run bitwise, and both stubs record calls; a generated mesh using a stub mesher forwarding to netgen gives netgen's vertices and triangles exactly and keys the stub in its recipe. Gmsh registration imports nothing, and in a fresh process registered_meshers() contains gmsh with neither gmsh, nanopnp.mesh.gmsh_backend, netgen nor ngsolve in sys.modules; a borrowed Gmsh session leaves the caller's logger untouched, sequential cleanup steps run with failures attached as notes without masking the meshing error, and the session is always finalised (QR-11, QR-14, IF-03, CON-10, NUM-21, §5.5; §8.2.8 H7, H11, H12; WP39) |
| VER-67 | Withdrawn | No test reads the source for release strings (§8.2.13 N3). The identifier is not reused |
| VER-68 | Withdrawn | The knowledge base is read at review, not by a test (§8.2.13 N3). The identifier is not reused |
| VER-69 | Executed user-testing sessions | Every session directory of docs/user-testing/sessions/ is copied alone and its tagged console blocks are executed verbatim by the runner of VER-46: each run command exits 0 and each refused command 4; a session has at least one tagged command, and every $ nanopnp or $ python line of any console block of its README sits in a tagged block, so a protocol gone stale, or a block mis-tagged so that it runs nothing, fails. Exit status is the oracle; no number and no expected property of a session is asserted, because the expected outcomes are the tester's to judge (§8.2.6 F3; §8.2.7 G7; §8.2.13 N4). Runs in the Tier 2 directory, nightly (IF-02) |
| VER-70 | No gate passes a NaN | Read from the source with no checked module imported: in src/nanopnp outside gui/, every if whose body raises, or collects into a list, an instance of a class that the exit-code table of core/errors.py maps to 4 has no ordering comparison (<, >, <=, >=) in its test, inside a call included, that a NaN makes false and so passes, unless the comparison is under not, its operands are integers by syntax (len, int, .ndim, .size, .shape[i], an integer literal), the test also asks isfinite or isnan, or the test is listed with its reason in NAN_EXEMPT, which fails on an entry naming no test. On a synthetic package each NaN-permissive form, one inside np.any and one collected into a list among them, is found at its file, line and function, and each safe form is not; check_wall_size and check_indicator abort on a NaN statistic or field (QR-12; REV-11; WP42) |
| VER-71 | No case leaf is accepted and never applied | Every leaf of the case schema that case_fields walks is classified once, in both directions, by what reads it: the solve's operator, the solve's iteration, a named stage before the solve, the post stages, a value refused away from its default, a value checked against the parameter file and never applied, or provenance alone; a leaf added to the schema and classified nowhere fails. For every registered model, each solve leaf perturbed from the model's base case is refused at resolution, or changes the top rung's residual applied at a fixed state that is not electroneutral, its essential potential data or its rung sequence. A model's declaration states the leaves it does not read: with their refusal lifted each leaves that operator bit-identical, and every other solve leaf changes it or is refused by another check. The Newton settings reach the iteration of every model that does not declare them unread, and the linear solver reaches every model. The refusals of the §5.4.3 NOTE on what a model does not read, of the PHY-22 NOTE on a switch with nothing to switch and of the narrowed switch sets name the model, each key, its default and what is admitted, verbatim; a pb case leaving flow true beside u: P1 loads and is refused on its flow switch. A provenance leaf moves no solve key (FR-25, PHY-21, PHY-22, IF-03, §5.4.3; REV-12, REV-42, REV-66; WP42) |
| VER-72 | Import guard | Read from the source with no checked module imported: a function-level import in src/nanopnp names only nanopnp, ngsolve, netgen, numpy, scipy, meshio, h5py or an optional extra's package. The check is shown to fail on a substituted violation, naming its file, line and name. Conventions (b) to (h) of the earlier row (private imports, spelling, docstrings, plan shape) and the coverage pragma (f) are withdrawn and read at review (§8.2.13 N2, N3) |
| VER-73 | Withdrawn | The loop has no enforced budget and no budget file (§8.2.11 L3, L8). The identifier is not reused |
| VER-74 | Withdrawn | The commit hook selects no tests: it runs all of Tier 1 (§8.2.11 L2). The identifier is not reused |
| VER-75 | Withdrawn | The prose rule of §8.2.11 L4 is read at review (§8.2.13 N3). The identifier is not reused |
| VER-76 | The published case schema equals the one the editors walk | case_json_schema() returns the case document's JSON Schema, generated from its own model, and schema_leaves reads its leaves by the rules case_fields walks by (a reference is resolved; a union is entered at its first object branch; a list of blocks is entered at index 0; a list or mapping of plain values is one leaf; a mapping of blocks raises NotImplementedError): (a) the two leaf sets are equal, so a field added to the model and missing from either walk fails naming its path; (b) a schema with one leaf added fails, and one with a leaf removed fails, and a case_fields with an extra leaf fails; (c) every Literal leaf carries an enum or a const equal to its arguments and the schema survives json.dumps. The documentation build writes it as reference/case.schema.json (§8.2.12 M2; WP49) |
NOTE (the browser shell, v0.6). When the package that removes the Qt shell lands, VER-43, VER-44,
VER-55 and VER-60 read as below, each over the workbench of docs/design/workbench.md. Until then
the rows above gate the Qt shell (§8.2.17 S13, S17).
- VER-43, Workbench forms, solver process and self-test: "The schema walk enumerates exactly the
editable dotted paths of the current case schema, asserted in both directions so that a field added
later fails this test rather than becoming silently uneditable, and the switch classification of
FR-25 is checked against that same walk rather than a second one; the shell's view-model layer and
its server import no front-end framework and no NGSolve, asserted on
sys.modulesin a fresh process, and noPyQtmodule is reachable from any import path the shell takes (CON-09); every option a form offers comes from the schema's own declared type or from a live registry, so no value set is written ingui/; a value the schema refuses is refused at the field before any substitution, naming the path, the value and the declared type, and a document the registries refuse produces the same diagnostic text the command line prints for the same file; run control drives the case through a spawned process, forwards a monotone completion fraction ending at 1, receives each stage transition as data — the stage's name and its position in the walk, through a structural hook, never recovered by parsing a progress caption — and cancels through a token the child honours, a cancelled run writing no artefact; a failed run reports the §3.1 exit class the command line would return for the same case; and the server's self-test imports NGSolve, Netgen and the shipped renderer in one process, failing naming the payload that does not load (IF-09, QR-11, FR-27, CON-09, RSK-13)". - VER-44, Live convergence monitoring and field visualisation: as the row above, the scene served to the browser as a file rather than a data URL, and with "the server's self-test fails when the shipped renderer does not reach its document" in place of the packaging probe's selftest.
- VER-55, Geometry pipeline surfaced in the workbench: as the row above, for the Continuum suite's tools, with three changes. The geometry view-models import no front-end framework, NGSolve, Netgen, MDAnalysis, scikit-image, Shapely or Gmsh in a fresh process. The contour editor's refusals and measurements are those of the Contour tool. "The server's self-test exercises MDAnalysis, gemmi, scikit-image, Shapely, and Gmsh where its extra is installed, failing naming the payload that does not work" replaces the packaging probe's sentence.
- VER-60, Charge pipeline surfaced in the workbench: as the row above, with four changes. The
view-models import no front-end framework, NGSolve, PDB2PQR or PROPKA in a fresh process. "Each
bounded number's field accepts exactly its schema bounds, in both directions; an integer field
accepts whole numbers only, and a field that admits
nullcan say "unset" (WP32); and a loaded pH of 7.25 is shown and not rewritten" replaces the spin-box sentence. "The study's charge on the mesh drives the walk toupto: chargeand shows each stage's hash, the marked planes, the five legs and the deployed field under IF-07's name; the Protonation tool shows the protonation row" replaces the Charge tab's sentence. "The server's self-test exercises PDB2PQR and PROPKA" replaces "The probe carries PDB2PQR and PROPKA", the rest of that sentence unchanged.