Repository navigation
Add the parametric core: apply_parametric, ParametricLoss, ParametricDataLoader - #357
Conversation
Port the parametric core of pull request #207 onto main: the concatenation of the system parameters with the state, apply_parametric, ParametricLoss with its two ZygotePullback methods, and ParametricDataLoader on main's gather, batch_over_two_axes and train!. The three names are public and not exported. train! on a ParametricDataLoader trains on ParametricLoss() without a loss. Relates to #207. Co-authored-by: benedict-96 <[email protected]> Co-Authored-By: Claude Opus 5.5 <[email protected]>
…xed μ shapes in ParametricDataLoader The (q, p) constructor takes one array type, as gather does; a pair of two types is a MethodError at construction instead of in the first minibatch. The batch functor throws an ArgumentError for a prediction window other than one, and the constructor one for a μ whose parameterlayout differs from the first. Drop the unread input_dim field and merge the two single-sample apply_parametric methods. Record K21 (empty batch) and K22 (upstream Float32 promotion), and extend K20. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…le method, and drop K21 Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
📝 Summary
Merge Risk:
|
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
4086292a-89c3-4da2-922e-24059cd1d2e3
Files selected for processing: 15-
CHANGELOG.mdKNOWN_ISSUES.mddocs/src/data_loader/data_loader.mddocs/src/reduced_order_modeling/losses.mdsrc/GeometricMachineLearning.jlsrc/data_loader/parametric_data_loader.jlsrc/data_loader/train.jlsrc/loss/losses.jlsrc/parametric.jlsrc/pullbacks/zygote_pullback.jltest/data_loader/parametric_data_loader.jltest/helpers/parametric_affine.jltest/loss/parametric_loss.jltest/parametric.jltest/runtests.jl
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
-
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #357 +/- ##
==========================================
+ Coverage 92.06% 92.32% +0.26%
==========================================
Files 51 53 +2
Lines 1109 1186 +77
==========================================
+ Hits 1021 1095 +74
- Misses 88 91 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The CHANGELOG records K22 and K23 as closed, so the two new entries take the next identifiers after the highest one in use, K27. Co-Authored-By: Claude Opus 5.5 <[email protected]>
michakraus
left a comment
There was a problem hiding this comment.
JuliaGNI/GeometricMachineLearning.jl PR #357 — Add the parametric core: apply_parametric, ParametricLoss, ParametricDataLoader
Verdict: request changes (posted as a comment review)
Reviewed: 1f6670cf5f55a6d1966f4da60ed1aadb4af352aa
CI: the three red jobs are not caused by this PR. Each fails the same way on main at a664f5c9 (run 40200448244, the same day).
- Downgrade - ubuntu-latest: the job fails at the resolver, before any test runs.
forcedeps check failedfor GPUArraysCore 0.2.1 (bound 0.2.0), Zygote 0.7.5 (0.7.0), ChainRulesCore 1.25.1 (1.0.0), NNlib 0.9.30 (0.8.0) and KernelAbstractions 0.9.38 (0.9.0).mainshows the same five lines. The PR does not changeProject.toml. NeuralNetworkParameters resolves to its floor, 0.5.0 (forcedeps check passed). So this job gives no test signal at the floors. I ran a local floor check instead (see below). - Julia pre (1.14.0-alpha1) and Julia nightly (1.15.0-DEV.132): GPUCompiler does not precompile (
UndefVarError: libLLVMExtra not defined in LLVM.API), so the Metal test dependency is skipped and the run stops before any test.mainhas the same error. - All other jobs pass, including Julia min and Julia 1 on all three OSes, Doctests, Documentation and Metal.
Findings
| # | severity | file:line | claim | evidence |
|---|---|---|---|---|
| 1 | correctness-risk | KNOWN_ISSUES.md:135-149 (K29); PR body, Unresolved and Pre-PR verification |
K29 says that the batch gradient's Union{Nothing, NetworkParameters} also shows on main, and that main's ZygotePullback(FeedForwardLoss()) on Chain(Dense(2, 2, identity)) with a 2 × 1 × 8 tensor gives Any[Union{Nothing, NetworkParameters}]. That does not reproduce. The base tree and the head both infer that gradient concretely. The Union comes from this branch: the per-sample map over eachcol in src/parametric.jl:86-101. The location field (src/pullbacks/zygote_pullback.jl:37-43) points at the (input, output) method, which this PR does not change. The decision to defer the instability as "not caused by the branch" therefore rests on a wrong premise. Please correct K29 (cause, location, and remove "the same inference shows on origin/main"). Then decide again whether to fix the instability or accept it. |
Base tree a664f5c9 and head, Julia 1.13.2, Base.return_types(back, (typeof(v),)) for ZygotePullback(FeedForwardLoss()): 2x1x8, 2x8 and vector inputs, NeuralNetwork parameters and hand-built parameters, Float32 and Float64. Every case gives Any[NetworkParameters{Float64 or Float32, (:L1,), …}]. Head, ParametricLoss on a batch of 8: Any[Union{Nothing, NetworkParameters}]; one sample: concrete. Variants of the batch method (map + stack, a comprehension over zip, a map over one iterable with a fixed μ, a plain W * x mapped over eachcol) all give the Union. So the cause is a Zygote map over the columns with a closure that captures ps. It is not specific to the apply_parametric closure. |
| 2 | quality | src/parametric.jl:8,9,11 |
Three of the four _flatten_system_parameters methods have no caller. Only (::Type{T}, ::NamedTuple) is reached, from concatenate_array_with_parameters. The NullParameters methods cannot be reached, because concatenate_array_with_parameters(x, ::NullParameters) returns early. The remaining method only forwards to flatten(T, μ). Add these methods in the PRs that call them. |
grep -rn _flatten_system_parameters src test docs ext finds only the definitions and the calls at :20 and :24. Codecov reports 3 lines of the patch with no coverage. |
| 3 | quality | src/parametric.jl:23-33 |
No code in src/ calls concatenate_array_with_parameters. Only the test helper calls it, and only the vector method (test/helpers/parametric_affine.jl:29). Both matrix methods are unused. The body itself says the matrix–vector method has an O(k²) pullback, and its plan-wide decision forbids that pattern. Shipping it now leaves that pattern for the next layer to call. The simpler file keeps two methods: concatenate_array_with_parameters(x::AbstractArray, ::NullParameters) = x and concatenate_array_with_parameters(x::AbstractVector, μ::NamedTuple) = vcat(x, first(flatten(eltype(x), μ))). Drop _flatten_system_parameters (finding 2) and the two matrix methods. |
grep as in finding 2. The O(k²) figures (5931/16625/59554 B per sample) are from the PR body. I did not measure them again. |
| 4 | quality | src/data_loader/parametric_data_loader.jl:45-50 |
The constructor rejects a μ whose element type differs from the data's, even though the forward pass already converts it to the element type of the state. An integer system parameter (n = j,) and a Float32 μ with Float64 data are both an ArgumentError. concatenate_array_with_parameters([1.0, 2.0], (n = 3,)) returns [1.0, 2.0, 3.0]. test/loss/parametric_loss.jl trains Float32 data with Float64 μ values and keeps Float32. The check protects no path the suite exercises. Convert in the constructor or remove the check. |
Probe at head: ParametricDataLoader(rand(2,5,3), [(n = j,) for j in 1:3]) gives ArgumentError: the data have the element type Float64 and a system parameter has the element type Int64. The same happens for Float32 μ values. concatenate_array_with_parameters(Float32[1, 2], (a = 0.5,)) gives Float32[1.0, 2.0, 0.5]. |
| 5 | quality | src/pullbacks/zygote_pullback.jl:45-58 |
The two new methods have identical bodies. One method whose third slot is <:Union{OptionalParameters, AbstractVector} does the same work. That matches the signature of ParametricLoss (src/loss/losses.jl:252), which already takes the union. The comment at :28-31 then names three shapes, not four methods. |
Read the diff. |
| 6 | nit | PR body | The body is written for the agents that built the branch, not for a reader on GitHub. Examples: "Decided (advisor, 2026-10-25, high)", "Decided (arbitrator, 2026-10-25, high), trigger stop after PASS", "the finish builder opens the PR", "the known-issues rule permits that without a waiver", and the heading "Decisions taken without the user". This review treats these as claims, not as settled decisions. Findings 1 and 3 hold whatever was decided. |
Read the PR body. |
| 7 | nit | test/data_loader/parametric_data_loader.jl:113,121,124 |
occursin("3", message), occursin("2", message) && occursin("3", message) and occursin("0", message) match one digit anywhere in the message. Match the phrase instead, as :116 does with "prediction window of 2". |
Read the file. |
Verified and fine
- Between
24be74bd(where the body reports the suite runs) and the head, onlyKNOWN_ISSUES.mdchanged (diff stat of the two commits). - The batch pullback allocates linearly in the batch size. I measured in a fresh process with
--check-bounds=auto, Julia 1.13.2, NeuralNetworkParameters 0.5.4, AbstractNeuralNetworks 0.9.3 and Zygote 0.7.14, as the minimum of 5 readings after two warm-up calls. Bytes per sample at k = 64/256/1024: array batch 2674/2618/2636,(q, p)batch 4889/4610/4549. These agree with the body's figures. The machine had a load average of 3 to 4. - Compat floor: NeuralNetworkParameters 0.5.0 exports
flatten,parameter_eltypeandparameterlayout(read in itssrc/NeuralNetworkParameters.jl:46,55,59). I used a scratch environment with NeuralNetworkParameters 0.5.0, AbstractNeuralNetworks 0.9.2 and GeometricOptimizers 0.9.0 pinned, on Julia 1.13.2 with--check-bounds=auto. The three new test files passed 3013 of 3013 there. - The shape check on
μis real.ParameterLayout's==compares the children asNamedTuples, so it compares keys and their order too (NeuralNetworkParameterssrc/layout.jl:92-94). ParametricDataLoader(::EnsembleSolution)gets oneμper trajectory even when the problem uses one shared parameter set.EnsembleProblemstoresparameterswith the axes ofics(GeometricEquationssrc/problems/ensemble_problem.jl:111,142-162).- K28 reproduces:
typeof(AbstractNeuralNetworks._norm((q = rand(Float32,2,1,3), p = rand(Float32,2,1,3))))isFloat64on 0.9.3. Test.detect_ambiguities(GeometricMachineLearning; recursive = false)finds 0.Aqua.test_piraciespasses. ExplicitImports reports no improper explicit import of the three new names. JuliaFormatter (sciml) passes on the 11 changed.jlfiles.closingIssuesReferencesis empty. The body writes "relates to #207".- Existing review thread: CodeRabbit flagged the reuse of K22 and K23. It is resolved and fixed in
1f6670c. K28 and K29 each appear once, and theCHANGELOG.mduses neither.
Not checked
- The time cost of the
Unionin finding 1. The body reports equal gradient times for the closure and a callable struct, but that compares two forms that are both unstable. - The full
Pkg.test(), at the head or at the floor (not run, by instruction). The floor run uses Julia 1.13.2, not 1.12. It covered the three new test files only. Zygote, ChainRulesCore, NNlib, KernelAbstractions and GPUArraysCore were not forced to their floors. - Device arrays.
apply_parametricputs a flattened hostμbelow each column of the batch, so a GPU batch is probably not supported. The PR makes no claim about GPUs. - The O(k²) figures for the matrix–vector
concatenate_array_with_parameters.
On a664f5c and on this branch, the FeedForwardLoss batch gradient infers as a concrete NetworkParameters in Float32 and Float64; only the ParametricLoss batch path gives Union{Nothing, NetworkParameters}. Also match the whole phrase in three ArgumentError message tests instead of a single digit. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Resolution of the review findingsCommit c51e94e resolves the findings of the review posted above, as follows. One finding needs a decision by the author.
Not merged. CI runs for c51e94e are in progress. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

Minor · Reject empty batches in check_parametric_batch. · parametric.jl:87-92
src/parametric.jl:87-92
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winReject empty batches in
check_parametric_batch.A
d × 1 × 0input with an empty parameter vector passes the current size check. The mapping produces an emptyoutputscollection, andreduce(hcat, outputs)then throws because no initial value is provided. The(q, p)batch method has the same failure. The public API documents that a batch must contain at least one sample.Suggested fix
- input_size[2] == 1 && input_size[3] == length(μs) || throw(DimensionMismatch( + input_size[2] == 1 && input_size[3] > 0 && input_size[3] == length(μs) || throw(DimensionMismatch(

Minor · Reject unsupported non-Chain models before selecting the default parametric… · train.jl:34-37
src/data_loader/train.jl:34-37
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winReject unsupported non-
Chainmodels before selecting the default parametric loss.The
train!documentation says that any network can use aParametricDataLoader, butParametricLossandapply_parametricdispatch only onChain.AbstractNeuralNetworkspermits valid non-Chainmodels, so such a network can reach the default loss and fail with aMethodError. Constrain and document the default parametric path asChain-only while preserving explicit custom losses.Suggested fix
- and any network on a [`ParametricDataLoader`](@ref) on [`ParametricLoss`](@ref)`() + and any `Chain`-backed network on a [`ParametricDataLoader`](@ref) on [`ParametricLoss`](@ref)`() -default_loss(::NeuralNetwork, ::ParametricDataLoader, ::Batch) = ParametricLoss() +function default_loss(nn::NeuralNetwork, ::ParametricDataLoader, ::Batch) + nn.model isa Chain || throw(ArgumentError( + "`train!` with a `ParametricDataLoader` requires a Chain-backed network; " * + "pass an explicit compatible loss for another model.")) + ParametricLoss() +end

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
e189e7c6-6d12-4583-b935-f7f308282388
Files selected for processing: 2-
KNOWN_ISSUES.mdtest/data_loader/parametric_data_loader.jl
Files skipped from review as they are similar to previous changes: 1-
- KNOWN_ISSUES.md
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.
-
…eters
Zygote's adjoint of map adds the gradients of the mapped closure, which holds ps, from nothing, so the gradient of ps on a batch inferred as Union{Nothing, NetworkParameters}. The batch methods of apply_parametric now map their samples through map_samples, whose ChainRulesCore rule pulls each sample back with Zygote.pullback and adds the gradients of ps, as NetworkParameters via map_cotangent, from the first sample on. A (q, p) sample is stacked into one vector inside the map, so that its cotangent reaches Zygote as an array. Outputs and gradients are bitwise unchanged.
A test asserts @inferred on the pullback of ParametricLoss for one sample and for an array batch, in Float32 and Float64. K29 leaves KNOWN_ISSUES.md. K30 records the upstream Union of AbstractNeuralNetworks' loss on (q, p) data, which main shows too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Finding 1, fixed in c5409c5The author chose to fix the Union in this PR. Cause. Zygote's adjoint of Fix. The batch methods map their samples through Variants that did not work: a broadcast with Evidence, on Julia 1.13.2. The fix and the code at c51e94e were compared in one process.
What stays. The Not merged. |
Summary
This PR adds the parametric core of the generic layers of #207 (relates to #207):
apply_parametric,ParametricLossandParametricDataLoader. The three names arepublicand not exported.apply_parametric(model, x, μ, ps)applies aChainto one sample (a vector or a(q, p)pair of vectors) with oneμ, or to a batch (a2d × 1 × ktensor or a(q, p)pair ofd × 1 × ktensors) with a vector ofkvalues ofμ. The output has the shape of the input. A batch maps over the columns of itsd × kreshape through the internalmap_samples, so the backward pass allocates O(k), not O(k²).map_sampleshas aChainRulesCore.rrulethat sums the sample gradients ofpsin oneNetworkParameters, so the gradient ofParametricLosson an array batch infers a concrete type. The rule gives the mapped closureNoTangent(): a batch has no gradient with respect to the fields ofmodel, andZygotePullbackdifferentiatespsonly. The internalconcatenate_array_with_parametersputs the flattenedμbelow the state, in the element type of the state.ParametricLoss()is the relative norm of the error ofapply_parametric.ZygotePullbacktakes the triple(input, output, μ)with oneμor a vector of them.ParametricDataLoadertakes a2d × nt × marray, a(q, p)pair ofd × nt × marrays of one array type, or anEnsembleSolution, with one named tuple of system parameters per trajectory. It batches onmain'sgatherandbatch_over_two_axes.train!on it trains onParametricLoss()when nolossis given.ArgumentErrorthat names the numbers for: aBatchwith a sequence length or a prediction window other than one, a parameter vector of the wrong length, no trajectory, too few time steps, aμof another element type, and aμof another shape. A(q, p)pair of two array types is aMethodError, as forDataLoader(K20 inKNOWN_ISSUES.md).The code is ported from #207 by Benedikt Brantner, with the fix of its bug 1 (
hcat, notvcat) kept and tested. Differences from #207:concatenate_array_with_parameterskeeps aFloat32state inFloat32, repeats oneμin every column of a matrix, and the counts are checked with anArgumentErrorinstead of@assert. The batching of the2d × nt × marray form is new.Tests
New test files:
test/parametric.jl,test/loss/parametric_loss.jl,test/data_loader/parametric_data_loader.jl, with the helpertest/helpers/parametric_affine.jl. Every expected value is a closed form built by hand in the test. The gradients are checked against central finite differences, with respect to the network parameters and to the input. All numeric tests run inFloat32andFloat64.test/parametric.jlalso holds an allocation-difference test of the backward pass across 64 and 1024 samples, for the array form and the(q, p)form.Known limitations
concatenate_array_with_parameters(src/parametric.jl:28-33) indexes one column per sample, and its pullback allocates O(k²).apply_parametricdoes not call it: it passes one vector per sample._flatten_system_parametersmethods have no caller in this PR. The function is ported from Parametric Generalized Hamiltonian Neural Networks #207 as one unit, and its callers arrive with the later parts of the split of Parametric Generalized Hamiltonian Neural Networks #207.μwith a non-numeric leaf raises the error of NeuralNetworkParameters, not a message of the loader.Float32(q, p)named tuple toFloat64(upstream, K28 inKNOWN_ISSUES.md).ParametricLosson a(q, p)batch still infers asUnion{Nothing, NetworkParameters}. The cause is_compute_lossin AbstractNeuralNetworks, andmainshows the same (upstream, K30 inKNOWN_ISSUES.md).🤖 Generated with Claude Code