Parametric Generalized Hamiltonian Neural Networks - #207
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #207 +/- ##
==========================================
+ Coverage 66.51% 69.46% +2.95%
==========================================
Files 99 108 +9
Lines 2888 3285 +397
==========================================
+ Hits 1921 2282 +361
- Misses 967 1003 +36 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Reconstructs PR #207 on top of 0.5.0. The branch's own 53 commits are collapsed into this one; the three that followed them are not carried over. `cc1a786f` "Fix GML compilation and add generated artifacts" was 123 MB of build output -- `docs/build.zip` (77 MB), `docs/build 2.zip` (47 MB), eleven LaTeX `.aux`/`.log`/`.fls`/`.fdb_latexmk` files and two `go_migration_inspection_*.txt` -- around four lines of source. `.gitignore` already covers all of it bar the inspection logs. `8d243592` "Resolve merge conflicts" was main's own HDF5 migration re-applied by hand, byte-identical blobs, redundant after a rebase. `43d89206` merged main only as far as `d07b4c26`, so the branch never saw the GeometricOptimizers separation, and its conflict resolution left `src/GeometricMachineLearning.jl` half from each side: `go_bridges.jl`, `src/arrays/` and `src/manifolds/` were `include`d again next to main's `import GeometricOptimizers`, which is `Method overwriting is not permitted during Module precompilation` on Julia 1.10. `4c0e2684` is kept -- it is a real seven-line fix to the symbolic `Jacobian` broadcast and two `build_nn_function` calls. Root cause of the repeated bad merges: main reformatted `src/GeometricMachineLearning.jl` from a 4-space-indented module body to column 0 and the branch never did, so every merge conflicted on the whole file. Here main's version is taken as-is and only the branch's `include`s and `export`s are re-applied. What the feature adds `GeneralizedHamiltonianArchitecture` is implemented; it used to be a stub whose constructor threw. It composes `n_integrators` symplectic Euler steps, each differentiating a learned kinetic or potential energy. Around it: `ForcedGeneralizedHamiltonianArchitecture` and `ForcedSympNet` with `ForcingLayer`s, `ParametricDataLoader`, `ParametricLoss`, a `SymbolicPullback` for it, `ParametricResNet`, and `QPT2`/`QPTOAT2`. Adaptation to the current dependencies The branch predates SymbolicNeuralNetworks 0.5. `symbolize!` is gone -- `symbolic_variables` replaces it -- and symbolic variables are scalar `Num`s rather than `Symbolics.Arr`s (SNN#14), so the parametric `SymbolicPullback` is rewritten in the shape of `SymbolicNeuralNetworks.SymbolicPullback`, with `symbolic_derivative` and `ParameterGradient` in place of the hand-rolled closure and `semi_flatten_network_parameters`. Five `Symbolics.Arr` special cases, three of them type piracy, are unreachable now and deleted. `GeometricProblems.default_parameters` is a function since 0.8, so the three tests call it. `src/optimizers/optimizer.jl` keeps main's version: the branch widened `_optimization_step!` and added `rgrad` methods for `NeuralNetworkParameters` and `nothing` gradients, and the 0.5.0 rewrite covers all of it -- `_tree_optim_step!` already skips a `nothing` block. NeuralNetworkParameters instead of ParameterHandling The system parameters are flattened into the network input. The branch used `ParameterHandling` and pirated three `flatten` methods on it; that does not work, because GeometricOptimizers defines `ParameterHandling.flatten(x)` with an unbound type parameter and that method wins -- D6 in NeuralNetworkParameters' PLAN.md, hit in practice. `flatten`/`unflatten` from NeuralNetworkParameters replace it. Since AbstractNeuralNetworks already exports `params`, no method on a foreign type is needed, and the layout stored in a layer is now a value rather than a closure. This makes registering NeuralNetworkParameters a prerequisite for merging: GML supports Julia 1.10, where `[sources]` does not exist. Bugs fixed on the way -- all on the training path, which nothing ran `concatenate_array_with_parameters(::AbstractMatrix, ::AbstractVector)` used `vcat` where it needs `hcat`, collapsing a batch into one long vector. `optimize_for_one_epoch!` called `_unpack_tuple`, which has never existed in this package. And `Zygote` differentiates *through* the `NeuralNetworkParameters` struct, so every nesting level of the gradient comes back wrapped in `(params = ...,)`; these architectures nest, and `_get_params` only unwraps the top, so `_unwrap_gradient` recurses. `test/generalized_hamiltonian_neural_networks/pghnn_training_test.jl` now covers that path. The other two tests are rewritten: the data-loader one asserted which shuffled batch holds which parameters, which depends on the RNG stream of the Julia version, and now asserts the correspondence itself; the pullback one had no `@test` at all and re-defined in the test file what `src/` now provides, and now checks the symbolic gradient against the summed per-sample `Zygote` gradients, which agree to 4e-16. Type piracy Deleted where it was free: `Base.NamedTuple(::NeuralNetworkParameters)` is `params`, and the `ParameterHandling` and `Symbolics.Arr` methods are gone with their callers. What remains -- `applychain`, four `Chain` functors, `SymbolicNeuralNetworks.Jacobian`, `networkbackend(::LazyArrays.ApplyArray)`, `h5save(::HDF5.Group, ::NeuralNetworkParameters, ...)` and the `SymbolicPullback` call operators -- carries a `TODO` naming its proper home. Also: `GeometricProblems` and `Printf` are dropped from `[deps]`, where the branch had duplicated them out of `[extras]` and nothing in `src/` uses them; `SymplecticEuler`, `SymplecticEulerA` and `SymplecticEulerB` are no longer exported, the training methods that used to carry those names being `SymplecticEulerIntegrator*` now; and the training loop takes `Union{DataLoader, ParametricDataLoader}` rather than an untyped argument. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
43d8920 to
633878f
Compare
benedict-96
left a comment
There was a problem hiding this comment.
Commented code with the thought of handing this review over to claude in the first iteration.
Something that I haven't mentioned yet is missing documentation on the parametric forced generalized Hamiltonian neural network, the forced SympNet and the WideResNet. For making this documentation more visually appealing I could provide the tikz pictures that are part of the paper pertaining to this pr.
| """ | ||
| Turn a vector of numbers into a vector of `NamedTuple`s to be used by `ParametricDataLoader`. | ||
| """ | ||
| function turn_parameters_into_correct_format(t::AbstractVector, IC::AbstractVector{<:NamedTuple}) |
There was a problem hiding this comment.
This functionality should most likely be in GeometricProblems or GMLDataSets.
| vcat((vec_of_params for _ in axes(IC, 1))...) | ||
| end | ||
|
|
||
| for i in 1:nt+1 |
There was a problem hiding this comment.
The data set should be generated with GeometricProblems (I would think). The problem may be that there is no good way at the moment to obtain analytic solutions (which is what happens here).
| (q = [1.0 2.0; 4.0 5.0;;; 2.0 3.0; 5.0 6.0], p = [1.5 2.5; 4.5 5.5;;; 2.5 3.5; 5.5 6.5]) | ||
| ``` | ||
| """ | ||
| function turn_q_p_data_into_correct_format(qp::QPT2{T, 2}) where {T} |
There was a problem hiding this comment.
This should be in GMLDataSets (I think).
| """ | ||
| This takes time as a single additional parameter (third axis). | ||
| """ | ||
| function load_time_dependent_harmonic_oscillator_with_parametric_data_loader(qp::QPT{T}, t::AbstractVector{T}, IC::AbstractVector) where {T} |
There was a problem hiding this comment.
Again, probably better in GMLDataSets.
| """ | ||
| Turn a vector of numbers into a vector of `NamedTuple`s to be used by `ParametricDataLoader`. | ||
| """ | ||
| function turn_parameters_into_correct_format(t::AbstractVector, IC::AbstractVector{<:NamedTuple}) |
There was a problem hiding this comment.
This seems to be a copy of the code that I commented already above. We should try to avoid duplicates like this (and probably outsource this functionality to GMLDataSets).
| ParametricDataLoader(data, params) | ||
| end | ||
|
|
||
| # """ |
There was a problem hiding this comment.
Is this still needed (potentially). If it is obsolete we can just delete it.
| # parameters_rearranged = zeros(_eltype(parameters), ) | ||
| # end | ||
|
|
||
| # function batch_over_two_axes(batch::Batch, number_columns::Int, third_dim::Int, dl::ParametricDataLoader) |
There was a problem hiding this comment.
Confer comment above.
| (x + d.activation.(ps.downscale_weight * d.activation.(ps.upscale_weight * input .+ ps.upscale_bias) .+ ps.bias), problem_params) | ||
| end | ||
|
|
||
| # function (d::ParametricResNetLayer{M, M, F, PT, false})(x::AbstractArray{T, 3}, problem_params::AbstractVector, ps::NamedTuple) where {M, F, PT, T} |
There was a problem hiding this comment.
Is this obsolete? Confer comment above.
| `reduce = +`: the loss of a batch is the sum of the losses of its samples, so its gradient is the | ||
| sum of the per-sample gradients. | ||
| """ | ||
| function SymbolicPullback(nn::NeuralNetwork, loss::ParametricLoss, |
There was a problem hiding this comment.
With the SymbolicPullback there seem to be massive performance issues that have to be investigated. Also: should parts of this be in SymplecticNeuralNetworks?
|
|
||
| The layout is a *value*, not a closure, so a layer can store it in a field and stay inferable. | ||
| """ | ||
| _flatten_system_parameters(parameters::NamedTuple) = flatten(parameters) |
There was a problem hiding this comment.
This is used for symbolic expressions. It is probably debatable if this is the cleanest way of implementing this functionality. May deserve a new issue but may not be solved in this pr.
Force-pushed a rebase onto
|
| commit | verdict |
|---|---|
cc1a786f "Fix GML compilation and add generated artifacts" |
dropped. 123 MB of build output — docs/build.zip (77 MB), docs/build 2.zip (47 MB), eleven LaTeX .aux/.log/.fls/.fdb_latexmk files, two go_migration_inspection_*.txt — and about four lines of source. .gitignore already covers all of it bar the inspection logs. The four lines are kept. It did not, in fact, fix compilation. |
8d243592 "Resolve merge conflicts" |
dropped. Not this branch's work: it is main's own HDF5 migration re-applied by hand. ext/HDF5Ext.jl, test/hdf5_support.jl, docs/utils/adjust_image_size.jl, scripts/convert_jld2_to_h5.jl, src/architectures/symplectic_autoencoder.jl and src/layers/bias_layer.jl were byte-identical blobs to main's. Redundant once rebased. |
43d89206 "Merge main into…" |
dropped. It merged main only as far as d07b4c26, so the branch never saw the GeometricOptimizers separation, and its resolution left src/GeometricMachineLearning.jl half from each side: go_bridges.jl, src/arrays/ and src/manifolds/ included again next to main's import GeometricOptimizers. That is the Method overwriting is not permitted during Module precompilation that all 13 jobs were failing on. |
4c0e2684 "Fix Hamiltonian symbolic code generation" |
kept. A real seven-line fix — the Jacobian broadcast (dx.(scalarize(f)) rather than scalarize(dx(f))) and inplace = false on two build_nn_function calls. |
The other 53 commits are collapsed into one, still authored by @benedict-96.
Why the merges kept going wrong: main reformatted src/GeometricMachineLearning.jl from a 4-space-indented module body to column 0, and this branch never did — so every merge of main conflicts on the whole file, and each has been resolved a bit differently by hand. Here I took main's version as-is and re-applied only this branch's includes and exports. Worth keeping in mind for any other long-lived branch.
Nothing that moved to GeometricOptimizers came back: git diff --name-only main...HEAD -- src/arrays src/manifolds src/optimizers/go_bridges.jl test/manifolds test/optimizers is empty, and no file main deleted is present.
⚠️ This cannot merge until NeuralNetworkParameters.jl is registered
Every job now fails with one error, at dependency resolution — nothing compiles or tests wrong:
ERROR: LoadError: expected package `NeuralNetworkParameters [67f4d93a]` to be registered
The branch used ParameterHandling to flatten the system parameters into the network input, and pirated three flatten methods on it. That does not work at all: GeometricOptimizers defines ParameterHandling.flatten(x) with an unbound type parameter, and that method is the one that wins —
ERROR: UndefVarError: `T` not defined in static parameter matching
[1] flatten(x::@NamedTuple{})
@ GeometricOptimizers …/src/optimizers/named_tuple_wrapper.jl:14
which is D6 in NeuralNetworkParameters' PLAN.md, hit in practice. flatten/unflatten from NeuralNetworkParameters replace it — and because AbstractNeuralNetworks already exports params, that needs no method on a foreign type, so three pirated methods disappear rather than move. GML supports julia = "1.10", where [sources] does not exist, so registering NNP 0.1.0 is a hard prerequisite.
Three bugs, all on the training path
test/training_phnn.jl is not in runtests.jl, so the loop this PR exists for had never run:
concatenate_array_with_parameters(::AbstractMatrix, ::AbstractVector)usedvcatwhere it needshcat, collapsing a batch into one long vector.optimize_for_one_epoch!called_unpack_tuple, which has never existed in this package.Zygotedifferentiates through theNeuralNetworkParametersstruct, so every nesting level of the gradient comes back wrapped in(params = …,). These architectures nest — aSymplecticEulerlayer holds a whole sub-network — and_get_paramsonly unwraps the top, so_unwrap_gradientrecurses.
New pghnn_training_test.jl covers that path. The two existing tests are rewritten: parametric_data_loader_test.jl pinned which shuffled batch holds which parameters, which depends on the Julia version's RNG stream, and now asserts the correspondence itself; pghnn_symbolic_pullback_single_layer_test.jl had no @test at all and re-defined in the test file what src/ now provides, and now checks the symbolic gradient against the summed per-sample Zygote gradients — they agree to 4e-16.
Dependency drift
The branch predates SymbolicNeuralNetworks 0.5: symbolize! is gone (symbolic_variables replaces it) and symbolic variables are scalar Nums rather than Symbolics.Arrs (SNN#14). The parametric SymbolicPullback is rewritten in the shape of SymbolicNeuralNetworks.SymbolicPullback, and five Symbolics.Arr special cases — three of them piracy — became unreachable and are deleted. GeometricProblems.default_parameters is a function since 0.8. src/optimizers/optimizer.jl keeps main's version, since the 0.5.0 rewrite covers what the branch added there.
The three items from the original description
- Type piracy — removed where it was free (
Base.NamedTuple(::NeuralNetworkParameters)is justparams; theParameterHandlingandSymbolics.Arrmethods went with their callers). The rest each carry aTODOnaming their proper home and are collected in Type piracy introduced by the parametric Hamiltonian architectures (#207) #243; they need releases of AbstractNeuralNetworks and SymbolicNeuralNetworks, so they are deliberately deferred. NNP'sPLAN.mddid not cover any of this — the two entries that are its to own are now D9 and D10 there. QPT2— introduced and documented,QPTwidened. ReplacingQPTwith it is still open.- Parameter-dependent
DataLoader— done, asParametricDataLoader.
Verified locally
Pkg.test() passes (exit 0; the four new testsets are 2 + 8 + 4 + 2 assertions, no failures) and docs/make.jl builds clean, both against a deved NeuralNetworkParameters.
🤖 Generated with Claude Code
|
Thanks — worked through all of these. Summary, then the two where the answer turned out to be different from what the comment assumed. DoneDead code ( The Script duplication — you were right, and it was worse than the comments suggest: Missing documentation — partly.
|
network (dim, width, n_int) |
params | ZygotePullback |
SymbolicPullback |
|---|---|---|---|
| 2, 2, 1 | 32 | 10.4 ms | 0.1 ms |
| 2, 4, 1 | 80 | 23.6 ms | 0.1 ms |
| 4, 4, 1 | 88 | 33.6 ms | 0.1 ms |
| 4, 4, 2 | 176 | 140.1 ms | — never builds |
What explodes is the build, and multiplicatively in n_integrators. Phase by phase at dim = 4, width = 4, nhidden = 1:
| phase | n_int = 1 |
n_int = 2 |
|---|---|---|
| build the symbolic loss | 0.87 s, 3.4 ⋅ 10⁵ chars | 0.03 s, 1.4 ⋅ 10⁹ chars |
symbolic_derivative |
0.66 s, 1.5 ⋅ 10⁸ chars | never finishes |
build_nn_function |
1.34 s | — |
The loss expression grows ~4,100× going from one integrator to two, before any differentiation, and differentiating multiplies by a further ~440×. At two integrators the process passes 8 GB and does not return.
Cause: each SymplecticEuler layer's forward pass calls the executable gradient build_gradient produced for its energy network. Traced on symbolic Nums that inlines the entire gradient expression, so stacking integrators inlines it inside itself. cse = true cannot help — it runs at code generation, long after the expression has been materialised.
Fix: stop tracing the inlined chain and compose the pullback layer by layer from the gradients each SymplecticEuler has already built, which keeps the expression linear in depth. That answers your other question too — yes, parts of this belong in SymbolicNeuralNetworks: it needs either a layerwise pullback protocol there, or a build_nn_function that can take a composition without inlining it. Written up with all the numbers in #245.
I did not fix it here — it is a redesign of the symbolic pipeline, not a tweak. What this PR does is make the failure honest: SymbolicPullback(nn, ::ParametricLoss, μ) now throws an ArgumentError for n_integrators > 1 explaining why and pointing at ZygotePullback, instead of appearing to hang. One integrator builds in ≈1.4 s and is fully supported; the default training path uses ZygotePullback and is unaffected.
Everything is on 2463e03b; Pkg.test() and docs/check_references.jl both pass locally.
🤖 Generated with Claude Code
Reconstructs PR #207 on top of 0.5.0. The branch's own 53 commits are collapsed into this one; the three that followed them are not carried over. `cc1a786f` "Fix GML compilation and add generated artifacts" was 123 MB of build output -- `docs/build.zip` (77 MB), `docs/build 2.zip` (47 MB), eleven LaTeX `.aux`/`.log`/`.fls`/`.fdb_latexmk` files and two `go_migration_inspection_*.txt` -- around four lines of source. `.gitignore` already covers all of it bar the inspection logs. `8d243592` "Resolve merge conflicts" was main's own HDF5 migration re-applied by hand, byte-identical blobs, redundant after a rebase. `43d89206` merged main only as far as `d07b4c26`, so the branch never saw the GeometricOptimizers separation, and its conflict resolution left `src/GeometricMachineLearning.jl` half from each side: `go_bridges.jl`, `src/arrays/` and `src/manifolds/` were `include`d again next to main's `import GeometricOptimizers`, which is `Method overwriting is not permitted during Module precompilation` on Julia 1.10. `4c0e2684` is kept -- it is a real seven-line fix to the symbolic `Jacobian` broadcast and two `build_nn_function` calls. Root cause of the repeated bad merges: main reformatted `src/GeometricMachineLearning.jl` from a 4-space-indented module body to column 0 and the branch never did, so every merge conflicted on the whole file. Here main's version is taken as-is and only the branch's `include`s and `export`s are re-applied. What the feature adds `GeneralizedHamiltonianArchitecture` is implemented; it used to be a stub whose constructor threw. It composes `n_integrators` symplectic Euler steps, each differentiating a learned kinetic or potential energy. Around it: `ForcedGeneralizedHamiltonianArchitecture` and `ForcedSympNet` with `ForcingLayer`s, `ParametricDataLoader`, `ParametricLoss`, a `SymbolicPullback` for it, `ParametricResNet`, and `QPT2`/`QPTOAT2`. Adaptation to the current dependencies The branch predates SymbolicNeuralNetworks 0.5. `symbolize!` is gone -- `symbolic_variables` replaces it -- and symbolic variables are scalar `Num`s rather than `Symbolics.Arr`s (SNN#14), so the parametric `SymbolicPullback` is rewritten in the shape of `SymbolicNeuralNetworks.SymbolicPullback`, with `symbolic_derivative` and `ParameterGradient` in place of the hand-rolled closure and `semi_flatten_network_parameters`. Five `Symbolics.Arr` special cases, three of them type piracy, are unreachable now and deleted. `GeometricProblems.default_parameters` is a function since 0.8, so the three tests call it. `src/optimizers/optimizer.jl` keeps main's version: the branch widened `_optimization_step!` and added `rgrad` methods for `NeuralNetworkParameters` and `nothing` gradients, and the 0.5.0 rewrite covers all of it -- `_tree_optim_step!` already skips a `nothing` block. NeuralNetworkParameters instead of ParameterHandling The system parameters are flattened into the network input. The branch used `ParameterHandling` and pirated three `flatten` methods on it; that does not work, because GeometricOptimizers defines `ParameterHandling.flatten(x)` with an unbound type parameter and that method wins -- D6 in NeuralNetworkParameters' PLAN.md, hit in practice. `flatten`/`unflatten` from NeuralNetworkParameters replace it. Since AbstractNeuralNetworks already exports `params`, no method on a foreign type is needed, and the layout stored in a layer is now a value rather than a closure. This makes registering NeuralNetworkParameters a prerequisite for merging: GML supports Julia 1.10, where `[sources]` does not exist. Bugs fixed on the way -- all on the training path, which nothing ran `concatenate_array_with_parameters(::AbstractMatrix, ::AbstractVector)` used `vcat` where it needs `hcat`, collapsing a batch into one long vector. `optimize_for_one_epoch!` called `_unpack_tuple`, which has never existed in this package. And `Zygote` differentiates *through* the `NeuralNetworkParameters` struct, so every nesting level of the gradient comes back wrapped in `(params = ...,)`; these architectures nest, and `_get_params` only unwraps the top, so `_unwrap_gradient` recurses. `test/generalized_hamiltonian_neural_networks/pghnn_training_test.jl` now covers that path. The other two tests are rewritten: the data-loader one asserted which shuffled batch holds which parameters, which depends on the RNG stream of the Julia version, and now asserts the correspondence itself; the pullback one had no `@test` at all and re-defined in the test file what `src/` now provides, and now checks the symbolic gradient against the summed per-sample `Zygote` gradients, which agree to 4e-16. Type piracy Deleted where it was free: `Base.NamedTuple(::NeuralNetworkParameters)` is `params`, and the `ParameterHandling` and `Symbolics.Arr` methods are gone with their callers. What remains -- `applychain`, four `Chain` functors, `SymbolicNeuralNetworks.Jacobian`, `networkbackend(::LazyArrays.ApplyArray)`, `h5save(::HDF5.Group, ::NeuralNetworkParameters, ...)` and the `SymbolicPullback` call operators -- carries a `TODO` naming its proper home. Also: `GeometricProblems` and `Printf` are dropped from `[deps]`, where the branch had duplicated them out of `[extras]` and nothing in `src/` uses them; `SymplecticEuler`, `SymplecticEulerA` and `SymplecticEulerB` are no longer exported, the training methods that used to carry those names being `SymplecticEulerIntegrator*` now; and the training loop takes `Union{DataLoader, ParametricDataLoader}` rather than an untyped argument. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
codecov put the patch at 55%, with six of the new files at 0%: nothing built a
forcing layer, a wide or parametric ResNet layer, a `ParametricResNet`, a
`ForcedSympNet` or a `ForcedGeneralizedHamiltonianArchitecture`. Writing a
construct-and-evaluate test for each turned up two defects.
`ForcedGeneralizedHamiltonianArchitecture` could not be evaluated at all.
`(nn::NeuralNetwork{GT})(qp, problem_params)` and the `Optimizer` entry point
were both written for `GT <: GeneralizedHamiltonianArchitecture`, and the forced
architecture is a *sibling* of that under `HamiltonianArchitecture`, not a
subtype. So `nn(x, mu)` fell through to AbstractNeuralNetworks' generic functor,
which read the system parameters as the network parameters and reached the first
layer as a `Float64`. Both methods are now defined for it too, in its own file;
widening to `HamiltonianArchitecture` would be wrong, since
`StandardHamiltonianArchitecture` takes no system parameters.
`ParametricResNet(::DataLoader, n_blocks, width; parameters = ...)` accepted
`parameters` and did not forward it, so that constructor always built a network
with no parameter dependence.
The test also pins the `ForcingLayer` convention, which is the opposite of what
the names suggest: `Q`/`P`/`QP` say what the forcing *depends on*, not what it
changes. All three add to `p` and leave `q` alone, which is what a force does to
the `p` equation. It perturbs one coordinate at a time and checks the output only
moves for a coordinate the layer is named after.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dead code he asked about, all three confirmed obsolete rather than merely unused. `rearrange_parameters` was never finished -- `zeros(_eltype(parameters), )` has no dimensions -- and never called. The commented `batch_over_two_axes` for `ParametricDataLoader` is superseded by the `n_batches::Integer` method in `batch.jl`, which the live code calls. The commented three-dimensional `ParametricResNetLayer` methods were also wrong: both compute `input` and then use `x`, so they were not a starting point for anything. The hardcoded `D:\RESEARCH - UTWENTE\...` output path is now `joinpath(get(ENV, "GML_OUTPUT_DIR", @__DIR__), ...)`. The three scripts each carried a verbatim copy of the same dataset helpers -- `turn_q_p_data_into_correct_format` was byte-identical in all three -- and the two forced-oscillator scripts also shared their analytic solution. All of it moves to `scripts/parametric_data_helpers.jl`, about 200 lines less duplication. The damped oscillator keeps its own analytic solution: it looks similar but is a different system, not a duplicate. That file is a staging post; GMLDatasets #5 tracks moving the helpers where they belong. Docstrings for `WideResNetLayer`, `ParametricResNetLayer` and `ParametricResNet`, which had none at all, and they are in the manual now. They passed `missing_docs` only because Documenter checks bindings that already carry a docstring. SymbolicPullback performance ---------------------------- Measured rather than assumed, and it is the opposite way round from what the report suggested. Per call the symbolic pullback is 100-1000x *faster* than Zygote and flat at 0.1 ms, where Zygote goes 10 -> 24 -> 34 -> 140 ms as the network grows. What explodes is the build, multiplicatively in `n_integrators`. At `dim = 4, width = 4, nhidden = 1`, phase by phase: n_integrators = 1: loss 3.4e5 chars, its derivative 1.5e8 chars, build 1.4 s n_integrators = 2: loss 1.4e9 chars, never returns, past 8 GB Each `SymplecticEuler` layer's forward pass calls the executable gradient `build_gradient` produced for its energy network; traced on symbolic `Num`s that inlines the whole gradient expression, so stacking integrators inlines it inside itself. `cse = true` cannot help -- it runs at code generation, long after the expression exists. The real fix is to stop tracing the inlined chain and compose the pullback layer by layer from the gradients each layer has already built, which is upstream-shaped work; issue #245 has the numbers and the argument. Here the build refuses `n_integrators > 1` with an explanation instead of appearing to hang. Not addressed, deliberately: narrative documentation of the forced and parametric architectures, which is waiting on the tikz figures he offered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The parameter container moved to `NeuralNetworkParameters` and ANN 0.7 removed
the old name outright, so `NeuralNetworkParameters` as a *type* is
`NetworkParameters` throughout. The module keeps its name where it is a module.
`Project.toml` is now identical to main's: this work needs no dependency main
does not already have. The `[sources]` pin and the CI step that went with it are
gone -- NeuralNetworkParameters 0.1.1 is registered and main depends on it. A
local `path =` entry that had survived from an earlier resolve goes with them.
Three pirated methods delete rather than move, because upstream now covers them:
* `applychain(::Tuple, ::Tuple{<:QPTOAT2, <:OptionalParameters}, ::Tuple)`.
ANN 0.7 leaves `applychain`'s data argument untyped, and its comment gives
the reason -- the old signature "forced downstream packages to commit type
piracy to push anything else through a `Chain`". The generic method carries
the `(state, system parameters)` tuple already.
* `networkbackend(::LazyArrays.ApplyArray)`. LazyArrays is not a dependency of
GML any more, so this would not have compiled.
* `h5save(::HDF5.Group, ::NetworkParameters, ::AbstractString)`.
NeuralNetworkParameters' own extension has the `::H5DataStore` method, and
`HDF5.Group` is one. GML's was not merely redundant but worse: being more
specific it shadowed the upstream one, which writes the `kind` attribute
that records what to rebuild a leaf as.
The four three-argument `Chain` functors become `apply_parametric`, a function
GML owns. That is newly possible for the same reason the first deletion is: with
`applychain` generic in its data argument, the tuple threads through the ordinary
two-argument functor, so these were only an entry point and never needed to be
methods on someone else's type.
Two pirated groups are left, both genuinely upstream and both still marked:
`SymbolicNeuralNetworks.Jacobian(f, nn, dim2)`, which 0.6 did not add, and the
three-tuple `SymbolicPullback` call operators.
The `n_integrators > 1` guard stays. SNN 0.6 shipped the layerwise construction
(SNN #49) and its changelog names this case, but it does not reach it: the seam
is a plain `Vector{Num}`, so a `SymplecticEuler` with `return_parameters = true`
-- which passes the system parameters on and returns a `Tuple` -- cannot be
seeded. `composes_layerwise` still says the chain decomposes, so `:auto` commits
and then raises, where it is documented never to. Filed as SNN #54; measured at
`n_integrators = 1`, where `layerwise = false` builds in 3.2 s and `:auto`
throws.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2463e03 to
6db0b98
Compare
Implements parametric generalized Hamiltonian neural networks (PGHNNs): architectures whose forward pass takes the parameters of the system alongside the state, so one network covers a parameter range rather than a single problem instance.
Rebased onto
main(0.6.0). The branch's 53 commits are collapsed into one; see the commit message for the detail. In short:cc1a786fwas 123 MB of build output —docs/build.zip(77 MB),docs/build 2.zip(47 MB), eleven LaTeX artefacts, twogo_migration_inspection_*.txt— and about four lines of source. All of it except the inspection logs is already in.gitignore.8d243592wasmain's own HDF5 migration re-applied by hand (byte-identical blobs), redundant after a rebase.43d89206mergedmainonly as far asd07b4c26, so the branch never saw the GeometricOptimizers separation, and its resolution leftsrc/GeometricMachineLearning.jlhalf from each side —go_bridges.jl,src/arrays/andsrc/manifolds/included again next tomain'simport GeometricOptimizers. That is theMethod overwriting is not permitted during Module precompilationfailure all 13 CI jobs hit.4c0e2684is kept: a real seven-line fix to the symbolicJacobianbroadcast and twobuild_nn_functioncalls.The repeated bad merges have one cause:
mainreformattedsrc/GeometricMachineLearning.jlfrom a 4-space-indented module body to column 0 and the branch never did, so every merge conflicted on the whole file. Heremain's version is taken as-is and only the branch'sincludes andexports re-applied. Nothing that moved to GeometricOptimizers comes back —git diff --name-only main...HEAD -- src/arrays src/manifolds src/optimizers/go_bridges.jl test/manifolds test/optimizersis empty.What it adds
GeneralizedHamiltonianArchitectureis implemented — it used to be a stub whose constructor threwerror("GHNN still has to be implemented!"). It composesn_integratorssymplectic Euler steps, each differentiating a learned kinetic or potential energy. Around it:ForcedGeneralizedHamiltonianArchitectureandForcedSympNetwithForcingLayers,ParametricDataLoader,ParametricLoss, aSymbolicPullbackfor the parametric case,ParametricResNetas the non-structure-preserving baseline, andQPT2/QPTOAT2.Flattening the system parameters
The system parameters are flattened into the network input. The branch used
ParameterHandlingand pirated threeflattenmethods on it. That could not work: GeometricOptimizers definesParameterHandling.flatten(x)with an unbound type parameter, and that method wins —UndefVarError: T not defined in static parameter matching, which is D6 in NeuralNetworkParameters'PLAN.md, hit in practice.flatten/unflattenfrom NeuralNetworkParameters replace it, and because AbstractNeuralNetworks exportsparamsthis needs no method on a foreign type; the layout a layer stores is a value rather than a closure. NeuralNetworkParameters is registered as of 0.1.1 and main depends on it directly, so this needs no dependency main does not already have —Project.tomlon this branch is byte-identical to main's.Five bugs, all on paths nothing ran
test/training_phnn.jlis not inruntests.jl, so the loop the PR is for had never executed:concatenate_array_with_parameters(::AbstractMatrix, ::AbstractVector)usedvcatwhere it needshcat, collapsing a batch into one long vector.optimize_for_one_epoch!called_unpack_tuple, which has never existed in this package.Zygotedifferentiates through theNeuralNetworkParametersstruct, so every nesting level of the gradient comes back wrapped in(params = …,). These architectures nest — aSymplecticEulerlayer holds a whole sub-network — and_get_paramsonly unwraps the top, so_unwrap_gradientrecurses.Codecov then showed six of the new files at 0% — nothing constructed a forcing layer, a wide or parametric ResNet layer, a
ParametricResNet, aForcedSympNetor aForcedGeneralizedHamiltonianArchitecture. Writing a construct-and-evaluate test for each turned up two more:ForcedGeneralizedHamiltonianArchitecturecould not be evaluated at all. The parameter-dependentNeuralNetworkfunctor and theOptimizerentry point were written forGeneralizedHamiltonianArchitecture, and the forced architecture is a sibling of it underHamiltonianArchitecture, not a subtype — sonn(x, μ)fell through to AbstractNeuralNetworks' generic functor, which read the system parameters as the network parameters and reached the first layer as aFloat64.ParametricResNet(::DataLoader, n_blocks, width; parameters = …)acceptedparametersand did not forward it, always building a network with no parameter dependence.New
pghnn_training_test.jlandparametric_layers_and_architectures_test.jlcover those paths. The latter also pins theForcingLayerconvention, which is the opposite of what the names suggest:Q/P/QPsay what the forcing depends on, not what it changes — all three add topand leaveqalone, which is what a force does to theṗequation. The two existing tests are rewritten: the data-loader one pinned which shuffled batch holds which parameters, which depends on the Julia version's RNG stream, and now asserts the correspondence itself; the pullback one had no@testat all and re-defined in the test file whatsrc/now provides, and now checks the symbolic gradient against the summed per-sampleZygotegradients (they agree to 4e-16).Adapting to the current dependencies
The branch predates SymbolicNeuralNetworks 0.5.
symbolize!is gone (symbolic_variablesreplaces it) and symbolic variables are scalarNums rather thanSymbolics.Arrs (SNN#14), so the parametricSymbolicPullbackis rewritten in the shape ofSymbolicNeuralNetworks.SymbolicPullback, usingsymbolic_derivativeandParameterGradientinstead of a hand-rolled closure andsemi_flatten_network_parameters. FiveSymbolics.Arrspecial cases — three of them type piracy — are unreachable now and deleted.GeometricProblems.default_parametersis a function since 0.8, so the tests call it.src/optimizers/optimizer.jlkeepsmain's version: the branch'srgradmethods forNeuralNetworkParametersandnothinggradients are all covered by the 0.5.0 rewrite, whose_tree_optim_step!already skips anothingblock.The three items from the original description
applychain(AbstractNeuralNetworks 0.7 leaves its data argument untyped for this reason),networkbackend(::LazyArrays.ApplyArray)(LazyArrays is not a GML dependency any more) andh5save(::HDF5.Group, ::NetworkParameters, …)(NeuralNetworkParameters' extension has the::H5DataStoremethod — and GML's, being more specific, was shadowing it and losing thekindattribute). The four three-argumentChainfunctors becameapply_parametric, a function GML owns, which theapplychainchange made possible. What is left —SymbolicNeuralNetworks.Jacobian(f, nn, dim2)and the four three-tupleSymbolicPullbackoperators — is genuinely upstream, marked with aTODO, and collected in Type piracy introduced by the parametric Hamiltonian architectures (#207) #243.QPT2. Introduced and documented, andQPTwidened. ReplacingQPTwith it is still open.DataLoader. Done, asParametricDataLoader.Also
GeometricProblemsandPrintfdropped from[deps], where the branch had duplicated them out of[extras]; nothing insrc/uses either.SymplecticEuler,SymplecticEulerAandSymplecticEulerBare no longer exported. Those names are the layer types of the generalized architectures now; the training methods that used to carry them areSymplecticEulerIntegrator,SymplecticEulerIntegratorAandSymplecticEulerIntegratorB.SEuler,SEulerAandSEulerBare unchanged.Union{DataLoader, ParametricDataLoader}rather than an untyped argument.Dependencies
Follows main onto AbstractNeuralNetworks 0.7, SymbolicNeuralNetworks 0.6 and Zygote 0.7. The parameter container is
NeuralNetworkParameters.NetworkParametersnow — ANN 0.7 removed the old name outright rather than aliasing it — so every type position is renamed.On #245. SymbolicNeuralNetworks 0.6 shipped the layerwise pullback (SNN #49), which its changelog says fixes this case. Measured, it does not reach it yet: SNN's seam is a plain
Vector{Num}, so aSymplecticEulerbuilt withreturn_parameters = true— which passes the system parameters on and returns aTuple— cannot be seeded, andlayerwise = :autoraises rather than declining, which is SNN #54. Then_integrators > 1guard therefore stays; the findings are in #245.Verification
Pkg.test()passes (exit 0, no failures) anddocs/make.jlbuilds clean, both against adeved NeuralNetworkParameters. The four new testsets:Generalized Hamiltonian Neural NetworkSymbolic pullback for a single-layer PGHNNPGHNN training on a ParametricDataLoaderParametric and forced layers and architecturesParametric DataLoaderThe previous tip of this branch was
43d89206, still reachable by SHA if anything needs recovering.🤖 Generated with Claude Code