Repository navigation
Conversation
, tier 2) Tier 2 of #823, measured against tier 1 (#830) as the competitor. The note sets out where the JIT should be (named quantities as the unit of compilation, SymPy acting locally, one walk for C and manifest, canonical and readable C), the mechanism (each named quantity an applied function of its leaves whose fdiff gives the partial per argument slot, so SymPy's chain rule serves every derivative call site), the scorecard against tier 1, the risks with the test that closes each, the benchmark plan and the staging. scripts/sessions/jit_graph holds the prototype that the measurements come from: kernel_graph.py (the lowering and canonical emission), graph_vs_library.py (residual, Picard and Newton kernels compiled both ways and compared), fixtures.py, library_setup_profile.py and graph_size_probe.py. Underworld development team with AI support from Claude Code
…d 3) The JIT lowers each non-constant UWexpression to a node, an applied function of the leaves it reads, instead of expanding it into the tree. getext() emits one C temporary per distinct computation, ordered and merged by a hash of the C it computes, and takes the constants manifest from the leaves. _jacobian_unwrap builds guarded nodes, so the Newton tangent is formed by SymPy's chain rule through fdiff. The unwrappers expand nodes on request, for code that evaluates a lowered block. Selected by the private development switch UW_JIT_GRAPH=1; unset, every path is the tier 1 route unchanged. test_0024 closes the design note's risks (slot-keyed partial derivatives, two constants with one name, a stale node class, the guard placement, canonical emission, the manifest), each shown to fail under a mutation of the lowering. test_0022's guard test is pinned to the tree route. Session scripts: route_ab.py (end-to-end setup, solve, assembly timing), route_assemble.py (residual and Jacobian at a fixed state, both routes), rank_agreement_752.py, plain_diff_probe.py. Underworld development team with AI support from Claude Code
…ndex (#823) Two defects the serial suite found on the graph route, both in the fault-network end-to-end tests (test_0850, test_0851): - the per-body common sub-expression split made a repeated Piecewise condition into a node, a value, which Piecewise refuses as a condition. A body that is not an Expr now stays inline (test_0024::test_a_repeated_condition_stays_a_condition, shown failing first); - a coordinate leaf can be a UWCoordinate that SymPy's cache returned for its equal base scalar, without the C name the mesh set. The tree route recovers the name before printing; the graph route spells leaves earlier, during emission, so the speller now recovers it from the coordinate's index and system. Design note: the measurements in the library against tier 1 (setup, C size, assembly, Newton iterations, operator agreement at three states, hash seeds, the plain sympy.diff probe), and the code-size estimate corrected: about even, not 530 lines out for 360 in. route_ab.py gains a body-force perturbation for the notch's iteration spread. Underworld development team with AI support from Claude Code
…oute (#823) The serial suite passes on the graph route (3,023 passed). The notch's Newton iteration count over 17 round-off-sized perturbations per route: tree 60-111 and one non-convergence, graph 43-86; the same distribution within the sample. The #752 fixture no longer disagrees across ranks on either route (0 of 10 at np = 2), so it cannot test canonical emission's effect on #752. Underworld development team with AI support from Claude Code
…fixture's history (#823) The floor pass kept the VEP fixture's stress-history store and timestep, so the solve set dt_elastic on a law without one (found on Hyperion). Underworld development team with AI support from Claude Code
Run on Hyperion by another session: same solver paths and ratios as the Mac; gcc's -fmath-errno accounts for about two-thirds of the tree's box callback cost and none of the VEP's; #752 fixture agrees across ranks at np 3 and 4; ptest_jit_cache passes at np 4 on both routes. Underworld development team with AI support from Claude Code
getext() always lowers its callbacks onto the shared graph and emits one C temporary per distinct computation; _jacobian_unwrap always builds guarded nodes. The UW_JIT_GRAPH switch is gone, and with it the expanded-tree route: _reveal_constants, the two manifest consistency guards, the whole-kernel unwrap, the coordinate recovery, the opt-in UW_JIT_CSE path, _collect_constant_atoms, _xreplace_shared, _unique_symbols, the tree's sqrt guard, and the unused prepare_for_cache_key and _createext. Leaves are spelled by an explicit map built for each compile (_leaf_spellings, _spell_leaf) instead of C names patched onto the field classes: a field class no longer carries the last compile's array slot into the next, and a field the compile does not own is an unconvertible-symbol error instead of a silent read of another field's data. The integration-point gradient refusal and the unconvertible-symbol message are kept. _extract_constants is the manifest of the lowered kernels. The verbose "Processing JIT" line prints the lowered kernel (the mathematics, with named quantities as nodes); test_0004 reads it. Tests: test_0022 loses the three tests of deleted helpers; test_0023 reads free_symbols; test_0024 holds the guarded lowering to a guarded tree built in the test, and pins the manifest of a nested law. Session scripts take the route from the build (this branch or development); the prototype lowering and its A/B harness are removed, superseded by the library module. Underworld development team with AI support from Claude Code
- jit-cache.md: what is compiled (one temporary per named quantity, canonical source); the MPI section was stale before this change (it said a cross-rank mismatch raises and every rank compiles): adopting rank 0's source, the collective compile decision. - expressions-functions.md: the JIT no longer unwraps whole kernels; the unwrappers expand graph nodes. - jacobian-consistent-tangent.md and the plasticity guide: the Newton source is built from nodes, the same tangent without the expanded tree. - The design note: steps 2-4 implemented; one change, UW_JIT_CSE retired (decisions); the prototype scripts' removal; the final line counts. - _jacobian_unwrap's docstring pointed at a design doc that does not exist. Underworld development team with AI support from Claude Code
Correctness (each with a test in test_0024, shown failing first): - a mesh.X coordinate beside a field in one named quantity lost its explicit derivative: the per-body cse rebuilt the UWCoordinate with a cloned coordinate system. Leaves and child nodes are hidden behind placeholders while cse runs, and lowering finds UWCoordinates by type; - a matrix- or vector-valued atom became a scalar node and failed to compile; such bodies are expanded in place (_is_scalar); - constancy was decided by a complete unwrap of each atom, exponential in the nesting depth; it is decided bottom-up on the graph with the same rule; - a number symbol (EulerGamma, Catalan) in a temporary wrote its declaration into the initialiser; declarations are hoisted, once each. test_0024 also finds that the expanded tree escaped its own sqrt guard on a power law on a named invariant (a NaN Newton flux at rest) where the graph does not. Tests and docs: - test_0105 sweeps hash seeds over a Newton viscoplastic law that emits temporaries; test_0022's guard identity test is restored against the graph's guard; test_0024 adds a power law, a twelve-layer law, coordinate spelling, matrix atoms, a constant law with named constants. - Nodes print as their quantity's name (str, latex); srepr and the C are unchanged. - One manifest helper (_manifest_of); generate_c_source takes the lowered callbacks and the substitution map; dead fallbacks, imports and the _ccodestr constancy branch removed; the stale allowlist entry dropped. - The solver's consistent_jacobian docstring no longer promises bit-identical Picard kernels; comments and doc pointers describe the graph route; expressions and mathematical-objects docs no longer describe the two-phase unwrap; the design note is consistent on #752, lists what was not measured, and records the review findings. Underworld development team with AI support from Claude Code
…tier 2) The bottom-up constancy decision is structural. Where tier 1 decided by value, a collapsing expression such as (1 + T**2)**(-m) + 1 at m = 0 banked as one constants[] slot and raised when m ramped (test_0104, the "rampable constant in exponent position does not ramp" report). Now the expression is compiled as a node reading T and m's slot, and m ramps with no recompile: test_0104 pins that against a fresh build at each value, and keeps the slot-stops-being-constant error tested through a constant whose content is replaced by one that reads a field. test_0024 pins where the structural rule and _is_truly_constant differ. Design note: the final suite (2,910 passed), the final comparison against development with -fno-math-errno, and the manifest's one exception. Underworld development team with AI support from Claude Code
Member
Author
Adversarial review 1 of 2: mechanism and correctness (before opening), and what we didNo blocker. Should-fix, all fixed, each with a test in
Nits:
Attacks that found nothing:
|
Member
Author
Adversarial review 2 of 2: tests, CI, docs, Charter, behaviour (before opening), and what we didNo blocker. Every should-fix is fixed.
Nits fixed: the design-note references ( Behaviour changes the review asked to be stated are in the PR body. Checks that found nothing:
|
Louis, 2026-10-09: a regression, or a case nobody considered, can only be told from a
fault in the model if the old way can still be tried; if both routes are wrong, the
model likely is. The graph route stays the default; the expanded route (the JIT
before tier 2) is restored beside it, as development's code moved and not edited:
- uw.use_jit_route("graph" | "expanded" | None) and UW_JIT_ROUTE choose the process
default; solver.jit_route overrides it for one solver, which rebuilds its kernels
at the next solve and keeps its state. getext and _jacobian_unwrap take the route.
- generate_c_source builds its kernels with _graph_equations or _expanded_equations;
the latter is development's class patching and per-kernel loop, verbatim, as are
_reveal_constants, the scanning _extract_constants, _xreplace_shared,
_unique_symbols, _collect_constant_atoms and the UW_JIT_CSE option. The expanded
route's C is byte-identical to development's on all six fixtures (linear, power law,
box, VEP, TI, notch).
Tests: test_0026 (the setting; both routes give the same iteration counts and
solutions on a viscoplastic box, Newton and Picard; switching one solver). The
expanded route keeps its own tests: test_0022's tree guard test, test_0104's
collapse-and-raise behaviour, test_0105's seed sweep on both routes. Graph tests pin
route="graph". Docs: the design note's decision, jit-cache.md, and a "rule the JIT
out" paragraph in the plasticity guide.
Underworld development team with AI support from Claude Code
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tier 2 of #823. Closes #752.
What changes
The JIT no longer expands every named sub-expression (
UWexpression) into one tree before it differentiates and prints.src/underworld3/utilities/_jit_graph.py:fdiffis the partial derivative of its body, taken against real placeholders and itself a node.Two routes, switchable. The graph is the default. The JIT before tier 2 is kept beside it as the
"expanded"route: a fallback, and a reference for telling a JIT regression from a fault in the model. If a model misbehaves on both routes, the model is likely wrong.uw.use_jit_route("expanded"), orUW_JIT_ROUTE=expandedin the environment.solver.jit_route = "expanded". The solver rebuilds its kernels at the next solve and keeps its state;Nonereturns to the default.development's code, moved and not edited (_expanded_equations,_extract_constants,_reveal_constants, the class patching, theUW_JIT_CSEoption). Its C is byte-identical todevelopment's on all six fixtures (linear, power law, box, VEP, TI, notch).On the graph route:
_leaf_spellings), replaces the C names patched onto the field classes.prepare_for_cache_keyand_createextare removed.Design note:
docs/developer/design/jit-shared-graph-codegen.md(measurements, risks and the test that closes each, decisions). Also updated:jit-cache.md, whose MPI section was already stale;expressions-functions.mdandUW3_Developers_MathematicalObjects.md;jacobian-consistent-tangent.md;Measurements
Against
developmentwith tier 1 and-fno-math-errno(#834, PR #835). Mac, single runs on a loaded machine.On the notch, the Jacobian callbacks cost 2.67 → 0.49 µs per quadrature point. The rest of each assembly is PETSc's finite-element machinery, which is the same on both routes.
Linux (gcc 14.3): the same ratios hold. gcc's default
-fmath-errnoaccounts for part of the tree's callback cost on some laws (hence #834), but none of it on the VEP.Agreement:
Behaviour that changes
(1 + T**2)**(-m) + 1atm = 0used to bank as oneconstants[]slot and raise whenmramped.mnow has its own slot and ramps without a recompile, which fixes the "rampable constant in exponent position does not ramp" report;test_0104is updated to pin it.stokes._uu_G3and friends hold node applications, which print as their quantity's name.unwrap_expressionandevaluateexpand them;lambdifyneedsunwrap_expressionfirst.-fp_trap: temporaries are computed even on an untaken Piecewise branch. Values are unaffected; under PETSc-fp_trapsuch a branch could trap, and nothing in the repository traps.Tests
test_0026_jit_route_switch.pycovers the switch. Both routes give the same nonlinear and linear iteration counts and the same solution on a viscoplastic box, Newton and Picard.test_0022's tree guard test,test_0104's collapse-and-raise behaviour, andtest_0105's hash-seed sweep, which now runs on both routes.test_0024_jit_graph_lowering.py(15 tests, pinned to the graph route) covers the design note's risks. Every test was checked against a deliberate break of the lowering, and each of the second review's fixes has a test shown to fail first.test_0105adds a hash-seed sweep over a Newton viscoplastic law that emits temporaries.test_0022keeps its guard identity test, now pointed at the graph's guard function.test_0023,test_0103andtest_0104are updated for the removed internals and the structural constancy rule.scripts/test.shbatches, not tier C) passed before the routes were made switchable: 2,910 passed, 0 failed. Runs on both routes are in progress and will be posted. It also passed after step 4 alone (3,082) and with the graph behind a switch (3,023, once two fault-network defects were fixed).Review
Two adversarial reviews ran before this PR. Neither found a blocker; their findings and what was done are in comments below.
Open questions for the maintainer
test_0024is tier B.TESTING-RELIABILITY-SYSTEM.mdsays tier C, and recent practice is tier A._peel_exceptadopt the nodes when they rebase?Merge PR #835 first, so that this is measured against the improved JIT.
Underworld development team with AI support from Claude Code