Skip to content

unwrap_expression(symbolic_keep_constants) is exponential on nested constitutive models: 563 s for a 10k-node tree; JIT setup 10-40+ min on the notch #823

Open

Description

@lmoresi

unwrap_expression(expr, mode="symbolic_keep_constants") is pathologically slow on nested constitutive models. That mode is the one _jacobian_unwrap uses before every consistent-tangent derivative, and prepare_for_cache_key uses it in the JIT. On the Spiegelman notch, the Jacobian setup alone took 10 to more than 40 minutes before any solve.

Measured (notch, refinement 1, ViscoPlastic with Drucker-Prager yield, residual strength, viscosity floor, smooth yield law)

library memoised single-pass (prototype)
unwrap of the viscosity, symbolic_keep_constants 563 s 0.11 s, identical srepr
unwrap of the viscosity, nondimensional 0.8 s 0.00 s, identical srepr

_setup_pointwise_functions takes 616 s with the default law and 1,352 s with the powermean law. With a declared plastic_rate_strengthening it exceeds 40 minutes. With the prototype swapped in, the same setup takes 54 s, or 108 s including the JIT, both under cProfile.

The viscosity is a 130-node graph with 18 named sub-expressions. Unwrapped it becomes a 9,913-node tree, so the output is not what costs 563 s.

Cause

unwrap_expression iterates expr.subs(...) passes to a fixed point. Each pass traverses and re-canonicalises the whole growing tree. In symbolic_keep_constants mode, every atom on every pass also calls _is_truly_constant, which is itself a full nondimensional unwrap of that atom. Nothing is memoised.

Fix

Expand each UW atom once (memoised by identity), using the same _unwrap_atom rule and the same truly-constant predicate (also memoised). Then rebuild each expression in one xreplace pass. The output is structurally identical on the notch in both modes.

The rest of the compile (after the fix, profiled)

  • C source generation: 76 s, of which atoms() scans over the expanded trees are 35 s and printing is 43 s.
  • debugging_text, a string comment for every kernel, generated whether or not anyone debugs: 23 s.
  • The sqrt guard's tree-wide .replace in _jacobian_unwrap: 16 s.
  • Constant extraction: 12 s.
  • Differentiation: 11 s.

Fixing these is tier 1. Tier 2 would generate code from the shared graph rather than the expanded tree, with each named sub-expression as one C temporary and the Jacobian built by chain rule. That needs a design note.

Found while running the shear-band width scan for PR #794.

Underworld development team with AI support from Claude Code

Activity

  1. added a commit that references this issue on Oct 7, 2026
  2. lmoresi commented on Oct 7, 2026

    @lmoresi
    MemberAuthor

    Fixed by #830, merged into development as f4c19cf: memoised unwrap, real fields with a real-stand-in derivative, and the power-mean sharpness atom. A Newton setup on the notch is down from 616 s / 1,352 s (sqrt / power mean) to 10 s / 13 s. The graph-based code generator (tier 2) is tracked separately on Project 10, with its design note on feature/jit-graph-codegen.

    Underworld development team with AI support from Claude Code

  3. 23 remaining items

  4. lmoresi commented on Oct 10, 2026

    @lmoresi
    MemberAuthor

    Reopened under the issue rule of 2026-10-11 (PR #839): an issue whose fix has merged to development stays open, labelled fixed-in-development, and closes when the fix is released to main. Fixed by #830 (f4c19cf, tier 1) and #836 (45fc612, tier 2).

  5. added
    fixed-in-developmentFix merged to development; the issue closes when it is released to main
    on Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    fixed-in-developmentFix merged to development; the issue closes when it is released to main

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions