Repository navigation
Build PDE solution wrappers array-wise - #718
Merged
Merged
Conversation
Compares every stored grid value of the PDE solution wrapper with the raw solution's value for that discrete variable, on the DAE path (all grid points unknowns), the compiled ODE path (boundary points observed), a staggered Yee layout, and a steady-state NonlinearProblem. Agent-Harness: Claude Code 2.1.296 Agent-Model: claude-opus-5-5 Agent-Session: 644ed0ff-c8c9-4cc3-b4c6-f326231a4030 Agent-Review: unreviewed Co-authored-by: Claude Code <agent@noreply.juliahub.com>
The solution wrappers built a separate observed function for every observed grid point, such as boundary points that mtkcompile eliminates. Each build costs time proportional to the system size, so wrapping a compiled 2D system grew faster than linearly with the grid (22 s at 6,241 states). Collect each variable's observed elements and evaluate them with a single getsym call. Agent-Harness: Claude Code 2.1.296 Agent-Model: claude-opus-5-5 Agent-Session: 644ed0ff-c8c9-4cc3-b4c6-f326231a4030 Agent-Review: unreviewed Co-authored-by: Claude Code <agent@noreply.juliahub.com>
On a compiled system the observed equation of a field is one array literal with a statement per grid point. array_observed_solution evaluated it, and compiling that function grew faster than the grid: wrapping a 2D heat solution took 22 s at 6,241 states. Read the unknown elements by state index and evaluate the observed ones together. A field with no unknowns, such as a reduced order reconstruction (#676), still goes through the array function. Agent-Harness: Claude Code 2.1.296 Agent-Model: claude-opus-5-5 Agent-Session: 644ed0ff-c8c9-4cc3-b4c6-f326231a4030 Agent-Review: unreviewed Co-authored-by: Claude Code <agent@noreply.juliahub.com>
Resolves the conflict with #716 in the solution wrappers the same way as the tested integration branch (integration/mol-2026-10, 2952823). Agent-Harness: Claude Code 2.1.304 Agent-Model: claude-opus-5-5 Agent-Session: 644ed0ff-c8c9-4cc3-b4c6-f326231a4030 Agent-Review: unreviewed Co-authored-by: Claude Code <agent@noreply.juliahub.com>
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.
Build PDE solution wrappers array-wise
Summary
On a system compiled with
mtkcompile, building the PDE solution wrapper grew much faster than the grid: 1.5 s at 1,521 states, 5.6 s at 3,481 and 22.0 s at 6,241 for a 2D heat equation. This PR reads each grid point that is an unknown by its state index. The remaining observed points, such as eliminated boundary points, are evaluated together with one observed function. With that change the same three cases wrap in 0.12 s, 0.26 s and 0.20 s.The
DAEProblempath was already fast on master and is unchanged here. The 70 s that TinyFDTD measured at 19k states was on code from before #701. On master, the staggered 2D TMDAEProblemwraps in 0.07 s at 19,683 states, and TinyFDTD measured 0.043 s at 19,220.Cause
PDETimeSeriesSolutionfirst triedarray_observed_solutionfor each field. On a compiled system the field's observed equation isu(t) ~ array_literal(u[1, 1], u[2, 1], ...), with one statement per grid point. Evaluating it calls the observed function generated for it, and the first call compiles that function. At 41 × 41 points the compile took 1.43 s (the second call takes 0.7 ms), and its cost grows faster than the number of grid points. A profile of the first wrap at 1,521 states puts 684 of 748 samples inside that generated function.Change
grid_indiceslooks up every grid point withSymbolicIndexingInterface.variable_index, which is O(1) through the index cache. Grid points that are unknowns are read withsol[i, :].grid_observed_valuesevaluates the rest with a singlegetsymcall, instead of building and compiling one observed function per element. At 1,521 states (160 observed boundary points), single-element observed calls cost about 8 ms each. One batched call covering all 160 took 0.13 s.array_observed_solution. That is the case Reconstruct array observables once per PDE field #676 added it for, and its test still passes.PDENoTimeSolutionuses the same two helpers.fd1f976cadded the batched observed evaluation, and its message credits it with the 22 s cost. That was wrong: the cost came from the array observed function, whichae3cb73droutes around. The batching still matters, because the boundary points now go through it.Timings
SciMLBase.wrap_solon a raw solution with 11 saves, after a warm-up wrap of a small system (Julia 1.12.6, ModelingToolkit 11.45.3).ODEProblem(mtkcompile(sys))ODEProblem(mtkcompile(sys))ODEProblem(mtkcompile(sys))DAEProblemI did not go past 6,241 states on the compiled path. There,
mtkcompileitself takes 468 s to build the problem, so the wrapper is no longer the bottleneck.Tests
"Test 10" and "Test 11" in
test/Sol_Interface/solution_interface.jlcompare every stored grid value of the wrapper with the raw solution's value for that discrete variable. They cover theDAEProblempath, where every grid point is an unknown; the compiled ODE path with observed boundary points and a time-dependent boundary value; a staggered Yee layout; and a steady-stateNonlinearProblem. They also check the interpolant at the grid points. They check against the raw solution, not against master's output, so they guard the routing against regressions; they do not reproduce a slowdown.Local runs on
ae3cb73d(GROUP=<g> julia --project -e 'using Pkg; Pkg.test()', Julia 1.12.6). Every group exits 0, and the broken counts are the existing@test_brokenmarkers:Runic is clean.
Overlap
This overlaps #716 (gpu-solution-wrap), which also edits
PDETimeSeriesSolutionandPDENoTimeSolution, to read from a host copy of the solution. They should compose, since the element reads here would simply read from #716's host copy, but I have not tested the combination. Whichever merges second needs a small rebase intimedep.jlandtimeindep.jl.🤖 Posted by an AI agent · harness: Claude Code 2.1.296 · model: claude-opus-5-5
Review: unreviewed
Session: 644ed0ff-c8c9-4cc3-b4c6-f326231a4030