Skip to content
Merged
8 changes: 5 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ breaking release).

### Removed (breaking)

- **The Hamiltonian and the Lagrangian neural networks move to two new packages, and SymbolicNeuralNetworks and Symbolics leave `[deps]` and `[compat]`.** [HamiltonianNeuralNetworks.jl](https://github.com/JuliaGML/HamiltonianNeuralNetworks.jl) takes `HamiltonianArchitecture`, `StandardHamiltonianArchitecture`, `GeneralizedHamiltonianArchitecture`, `HNNLoss`, `SymplecticEulerLoss` and `SymbolicPullback(::HamiltonianArchitecture)`, with their tests, the architecture page and the tutorial of the Hamiltonian neural network, and the scripts `hnn_pendulum.jl`, `hnn_pendulum_simulation.jl` and `symplectic_euler_loss_modified_hamiltonian.jl`. [LagrangianNeuralNetworks.jl](https://github.com/JuliaGML/LagrangianNeuralNetworks.jl) takes `LagrangianNeuralNetwork`, `LNNLoss` and `VariationalMidpointLoss`, with their tests, the architecture page of the Lagrangian neural network and the script `lnn_pendulum.jl`. GeometricMachineLearning no longer defines or exports these names, nor re-exports `SymbolicPullback`, and `scripts/utilities/plots.jl` and `scripts/utilities/data_problem.jl`, which only those scripts included, go with them, as do `∇H`, `dH` and `get_data_set` of `scripts/utilities/pendulum.jl`, which only `hnn_pendulum.jl` and `hnn_pendulum_simulation.jl` called. Migrate code that uses these names to `using GeometricMachineLearning, HamiltonianNeuralNetworks` or `using GeometricMachineLearning, LagrangianNeuralNetworks`; `DataLoader`, `Batch` and `train!` stay here. Known issues C7 and K15 are in HamiltonianNeuralNetworks.jl now; C20, a missing Knet.jl example of the Hamiltonian neural network, and K21, the field name `act` of `LagrangianNeuralNetwork`, leave this package's `KNOWN_ISSUES.md` because the code they describe leaves it; neither new package records them yet. Where another entry of this section names these types, losses or `SymbolicPullback`, that part describes code that ships in the two new packages, not in GeometricMachineLearning, and the `SymbolicNeuralNetworks` bound of the `[compat]` entry below does not apply.

- **`NetworkLoss(::TransformerIntegrator, prediction_window)` is gone; the default loss is `NetworkLoss(nn.architecture, batch)`.** `train!`'s default is now the two-argument `NetworkLoss(arch, batch)`, so a transformer's prediction window comes from the `Batch` it trains on instead of from the architecture. The old method took `prediction_window = 1`, which against `Batch`'s default `seq_length` was a conflict; `NetworkLoss(transformer, Batch(seq, pred))` replaces it, and `NetworkLoss(transformer, n::Integer)` is a `MethodError`. Migrate `NetworkLoss(transformer)` and `NetworkLoss(transformer, n)` to `NetworkLoss(transformer, batch)` or `TransformerLoss(batch)`.

- **`train!`'s `pullback` keyword is gone.** `loss` takes a `NetworkLoss` or an `AbstractPullback`, and a `NetworkLoss` is trained through a `ZygotePullback`. Migrate `train!(nn, o, dl, batch, n; loss = HNNLoss(arch), pullback = SymbolicPullback(arch))` to `train!(nn, o, dl, batch, n; loss = SymbolicPullback(arch))`. `train!` builds the pullback once per call, not once per step.
Expand Down Expand Up @@ -122,11 +124,11 @@ breaking release).

- `Pkg.test()` no longer runs the doctests. `test/quality/doctests.jl` is now the group `doctests`, which an empty `ARGS` does not run; `Pkg.test(test_args = ["doctests"])` runs it. In CI the Doctests job stays their runner, so the test matrix no longer runs them a second time.
- **CI uploads coverage from the `Julia 1 - ubuntu-latest` job instead of `Julia min`, and a test job saves the Julia cache only when it succeeds.**
- **The package needs Julia 1.12 or later** (`[compat] julia = "1.12"`, was `"1.11"`). On Julia 1.11.9, the gradient of an `LNNLoss` with respect to the network parameters stops with `StackOverflowError` in type inference ("Internal error: during type inference of … Encountered stack overflow") on macOS and Windows; the `Julia min` job on Linux passes. The overflow comes from SymbolicUtils 4.49.0, whose `rrule` for `SymbolicUtils.Code.create_array` returns an exactly typed tuple of tangents, one per array element; Zygote's reverse pass through the generated Hessian of `lagrangian_acceleration` is then inferred far deeper. With the 4.48.0 rule restored, or in a task with a 64 MB stack, the same gradient passes on 1.11.9; Julia 1.12.7 and 1.13.1 pass it on the root task. This closes #327: the `Julia min` jobs on macOS and Windows had failed in the *Lagrangian Neural Network* testset since 2026-10-01, and every testset after it was never run there.
- **The package needs Julia 1.12 or later** (`[compat] julia = "1.12"`, was `"1.11"`). The floor comes from `[compat] GeometricOptimizers = "0.9"`, whose v0.9.0 `Project.toml` has `julia = "1.12"`. It also closes #327, a `StackOverflowError` in type inference of the gradient of an `LNNLoss` on Julia 1.11.9, where the overflow came from SymbolicUtils 4.49.0's `rrule` for `SymbolicUtils.Code.create_array`; it failed the `Julia min` jobs on macOS and Windows in the *Lagrangian Neural Network* testset from 2026-10-01, and every testset after it never ran there. That loss now ships in LagrangianNeuralNetworks.jl, not here, so the floor does not rest on it.
- **`test/Project.toml` has no `[compat]` entry for a dependency of the package.** The entries for AbstractNeuralNetworks, GeometricOptimizers, KernelAbstractions, LinearAlgebra, NeuralNetworkParameters, Random and Zygote are gone: the test environment contains the package, so the bounds of `Project.toml` apply there, and an entry in `test/Project.toml` can only repeat or narrow them. `NeuralNetworkParameters = "0.3"` was narrower than the package's `"0.3, 0.4"`.
- **The test suite follows one flat layout.** `test/runtests.jl` is one list of `@safetestset "<label>" include("<path>")` lines in the groups `core`, `slow` and `metal`, selected by `ARGS`; with no arguments it runs `core` and `slow`, and `metal` too on an Apple-silicon Mac, so `Pkg.test()` and CI run every test they ran before. The 14 nested `test/<subject>/runtests.jl` drivers are gone. A test file sits where the source it tests sits: `test/attention/` and `test/transformers/` went into `test/layers/`, `test/architectures/` and `test/kernels/`; `test/reduced_order_modeling/` into `test/architectures/` and `test/reduced_system/`; `test/losses/training_method_losses.jl` into `test/loss/`; the tests that span the package, the docstring examples among them, into `test/integration/`; and `aqua.jl`, `exports.jl` and `reachability.jl` into `test/quality/`. `test/docstrings/layers_and_architectures.jl`, named in *Added* above, is now `test/integration/docstrings/layers_and_architectures.jl`. `test/layers/sympnet_upscaling.jl` is in `slow`. Thirteen files that drew random numbers without a seed now call `Random.seed!(1234)`. The shared helper of the two gradient-structure tests is `test/helpers/network_parameters_gradient_structure.jl`, and its `@test_broken` names issue #319.
- **The test suite follows one flat layout.** `test/runtests.jl` is one list of `@safetestset "<label>" include("<path>")` lines in the groups `core`, `slow` and `metal`, selected by `ARGS`; with no arguments it runs `core` and `slow`, and `metal` too on an Apple-silicon Mac, so `Pkg.test()` and CI run every test they ran before. The 14 nested `test/<subject>/runtests.jl` drivers are gone. A test file sits where the source it tests sits: `test/attention/` and `test/transformers/` went into `test/layers/`, `test/architectures/` and `test/kernels/`; `test/reduced_order_modeling/` into `test/architectures/` and `test/reduced_system/`; the tests that span the package, the docstring examples among them, into `test/integration/`; and `aqua.jl`, `exports.jl` and `reachability.jl` into `test/quality/`. `test/docstrings/layers_and_architectures.jl`, named in *Added* above, is now `test/integration/docstrings/layers_and_architectures.jl`. `test/layers/sympnet_upscaling.jl` is in `slow`. Thirteen files that drew random numbers without a seed now call `Random.seed!(1234)`. The shared helper of the two gradient-structure tests is `test/helpers/network_parameters_gradient_structure.jl`, and its `@test_broken` names issue #319.
- **The doctests run in the test suite.** `test/quality/doctests.jl`, in the `slow` group, runs `doctest(GeometricMachineLearning)` as the `Doctests` job of `CI.yml` does. `Documenter` joins `test/Project.toml`, with no `[compat]` bound, as a test-only dependency.
- **The test suite compiles less.** `test/layers/sympnet_upscaling.jl` checks its three symplectic identities on 12 pairs `(N, N2)` instead of all 120 with `N` in `2:2:20` and `N2` in `(2N):2:(4N)`: `(2, 4)`, `(2, 6)`, `(2, 8)`, `(4, 10)`, `(6, 12)`, `(6, 20)`, `(8, 24)`, `(10, 30)`, `(12, 36)`, `(14, 42)`, `(20, 40)` and `(20, 80)`. They keep the smallest pair, `N2 = 2N`, `N2 = 4N`, `N2` not a multiple of `N`, and the largest `N`. The layers carry the sizes in their types, so each pair is a new compilation (about 1.3 s per pair, not once per file as the [0.8.0] entry assumed); the file has 36 assertions instead of 360, and takes 19.7 s cold; it stays in `slow`. The two training tests of `test/loss/training_method_losses.jl` use the network shape of the rest of the file, `StandardHamiltonianArchitecture(2n, 4, 1)` and `LagrangianNeuralNetwork(2; width = 4, nhidden = 1)`, in place of `(2n, 5, 2)` and `width = 5, nhidden = 2`; each still asserts that training reduces the loss. `test/architectures/lagrangian_neural_network_tests.jl` moves from `core` to `slow`: it takes 122.7 s cold, above the 60 s of a `core` file. It still runs in CI and in `Pkg.test()`, and leaves only the local `core` run. Every other `core` file takes at most 37.9 s cold. The times are cold, from one fresh Julia process that runs the `core` files in the order of `test/runtests.jl` (and from one fresh process for each of the two files named), on an Apple M4 Max with Julia 1.13.1, at a load average below 4 at the start.
- **The test suite compiles less.** `test/layers/sympnet_upscaling.jl` checks its three symplectic identities on 12 pairs `(N, N2)` instead of all 120 with `N` in `2:2:20` and `N2` in `(2N):2:(4N)`: `(2, 4)`, `(2, 6)`, `(2, 8)`, `(4, 10)`, `(6, 12)`, `(6, 20)`, `(8, 24)`, `(10, 30)`, `(12, 36)`, `(14, 42)`, `(20, 40)` and `(20, 80)`. They keep the smallest pair, `N2 = 2N`, `N2 = 4N`, `N2` not a multiple of `N`, and the largest `N`. The layers carry the sizes in their types, so each pair is a new compilation (about 1.3 s per pair, not once per file as the [0.8.0] entry assumed); the file has 36 assertions instead of 360, and takes 19.7 s cold; it stays in `slow`. Every `core` file takes at most 37.9 s cold. The times are cold, from one fresh Julia process that runs the `core` files in the order of `test/runtests.jl` (and from one fresh process for the file named), on an Apple M4 Max with Julia 1.13.1, at a load average below 4 at the start.
- **The optimizer reads `show_progress` as a `Bool`** instead of comparing it with `== true`. A non-`Bool` value now raises a `TypeError`: before, `nothing`, `0` or a string hid the progress bar with no error, and `1` or `1.0` showed it. Every call in the tests and the manual passes `false`, and every call in `scripts/` passes `true`.
- **fatou reports no finding in `src/` or `test/`.** Only tests and one lint suppression changed; no behaviour of the package changed. `test/layers/multi_head_attention_stiefel_setup.jl` defined `check_setup(ps::NetworkParameters)` twice, and the later method silently replaced the `mapparameters` one, which never ran. The later method is deleted, and the test now asserts that the matrix method of `check_setup` runs once per leaf of the parameters, counted with `foldparameters`. The unused destructured values `Σ`, `Vt` and `Ũ₂` in `test/optimizers/psd_optim.jl` and `test/optimizers/svd_optim.jl` are now `_`; those files check `Ũ₁` and `Ũ₃` and never checked `Ũ₂`. The unreachable `new{…}(…)` after the `error` in the `GeneralizedHamiltonianArchitecture` constructor stays, because it records the field order for the future constructor, under `# fatou-ignore unreachable-code`.
- **Three test files left the top level of `test/`**, because a test file sits there only where it mirrors `src/<name>.jl`. `test/norm_eltype.jl` tests `_norm` of `src/utils.jl` and is now `test/utils.jl`; `test/map_to_cpu_tests.jl` tests `src/map_to_cpu.jl` and is now `test/map_to_cpu.jl`; `test/documented_defaults.jl` tests the keyword defaults of `src/layers/symplectic_attention.jl`, `src/architectures/symplectic_transformer.jl` and `src/architectures/standard_transformer_integrator.jl`, whose deepest common directory is `src/` itself, and is now `test/integration/documented_defaults.jl`. Only the paths in `test/runtests.jl` changed; the labels, groups and order are the same.
Expand Down
70 changes: 3 additions & 67 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,6 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.
pattern, so the working tree and the net diff are clean, but the blobs are still reachable.
Rewriting the branch would remove them.

### C7 · `SymbolicPullback(::HamiltonianArchitecture)` reaches one private SymbolicNeuralNetworks name.

- **location:** `src/pullbacks/symbolic_hnn_pullback.jl`
- **kind:** upstream
- **found:** 2026-08-16
- **evidence:**
`SymbolicNeuralNetworks.SymbolicPullback(nn, loss)` cannot be used, because it sizes the symbolic
target of the loss with `output_dimension(nn.model)`, which for a Hamiltonian network is `1` — the
scalar Hamiltonian — while `HNNLoss` compares against the Hamiltonian *vector field*, which has the
dimension of the network's input. GML's construction therefore reaches
`SymbolicNeuralNetworks.symbolic_parameter_gradient`, which no exported name replaces:
`flat_parameter_gradient` is flat, so it wants an `unflatten` per step, and `ParameterGradient` is
not used. The pullback is the upstream type `SymbolicPullback(loss, fun)`, with a call method of
this package for `SymbolicPullback{<:HNNLoss, <:_HNNParameterGradient}` that is more specific
than the upstream call in the pullback and the parameters, and as specific in the data. That
private name is the one an upstream refactor breaks.

### C14 · `evaluate_vf_and_compute_∇Ψ` evaluates the decoder twice.

- **location:** `src/reduced_system/reduced_system.jl`
Expand All @@ -58,24 +41,15 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.
- **kind:** upstream
- **found:** 2026-09-18
- **evidence:**
`no_implicit_imports` is off: **37** names arrive through a bare `using` across thirteen packages,
`no_implicit_imports` is off: **33** names arrive through a bare `using` across twelve packages,
and naming them is a rewrite of the module header with a judgement per name.
`all_explicit_imports_are_public` and `all_qualified_accesses_are_public` run with a list of the
names this package reaches in other packages that those packages do not declare `public`: **10**
explicit imports and **9** qualified accesses, none of them in `GeometricOptimizers`.
names this package reaches in other packages that those packages do not declare `public`: **9**
explicit imports and **4** qualified accesses, none of them in `GeometricOptimizers`.
`Architecture`, `AbstractExplicitLayer`, `_compute_loss`, `dim` and the rest are used
deliberately, and the resolution is upstream declaring them `public`. Each check's reason is
written beside it in the test file.

### C20 · There is no Knet.jl example for the Hamiltonian neural network.

- **location:** `TODO.md`
- **kind:** docs
- **found:** 2026-09-19
- **evidence:**
The deleted `TODO.md` asked
for one. Recorded rather than dropped; nothing depends on it.

### C21 · "An untrained autoencoder reduces worse than PSD" is not a property, and the suite no longer asserts it under an explicit integrator.

- **location:** `test/reduced_system/reduced_system.jl`
Expand Down Expand Up @@ -141,28 +115,6 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.
on 1.13 and *rose* on 1.12". The fix states only the present constraint: a 32-epoch budget
drives the learning rate to `η₂` almost at once, and 128 epochs train.

### K15 · `HNNLoss` and `SymbolicPullback(arch)` accept `(q, p)` data in their signatures and fail on it inside the generated vector field.

- **location:** `src/loss/hnn_loss.jl:33`
- **kind:** found late
- **found:** #344, review round 2 (2026-10-14); the `HNNLoss` signatures are older than that pull request
- **evidence:**
`HNNLoss`'s two functors (`src/loss/hnn_loss.jl:31-42`) and the call method of
`SymbolicPullback(arch)` (`src/pullbacks/symbolic_hnn_pullback.jl:38-40`) type the data as
`ArrayOrNamedTuple`, which admits a `(q = …, p = …)` `NamedTuple`. The loss hands that data to
the generated `hvf`, which calls `ndims` on it. With `(q = randn(1, 5), p = randn(1, 5))` as
input and output, the symbolic pullback, `HNNLoss` and `ZygotePullback(HNNLoss(arch))` all
raise:

```
MethodError: no method matching ndims(::@NamedTuple{q::Matrix{Float64}, p::Matrix{Float64}})
```

Narrowing the pullback's method alone changes nothing: the data then reaches the upstream
`SymbolicPullback` call, which also accepts a `NamedTuple` and fails in the same place. The fix
belongs with the data signatures of the losses as a whole: either each loss accepts only the data
its vector field can take, or `(q, p)` data is converted before it reaches the field.

### K17 · `assign_q_and_p` and `PoissonTensor * v` drop the rows from `2N + 1` on without an error.

- **location:** `src/kernels/assign_q_and_p.jl`
Expand Down Expand Up @@ -225,22 +177,6 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.
`src/data_loader/data_loader.jl:205` states the one-type signature. Closing it means widening the
three signatures to `NamedTupleOfArrays{T, (:q, :p)}`, and the docstring with them.

### K21 · `LagrangianNeuralNetwork` stores its activation in the field `act`, where every other architecture with one activation names it `activation`.

- **location:** `src/architectures/lagrangian_neural_network.jl:5`
- **kind:** found late
- **found:** 2026-10-20, by the critics of part M5; the field is the same on `origin/main` at `4c4a8320`
- **evidence:**
```
$ grep -rn " act::\|\.act\b" src/architectures/
src/architectures/lagrangian_neural_network.jl:5: act::AT
src/architectures/lagrangian_neural_network.jl:16: [Dense(nn.width, nn.width, nn.act) for _ in 1:nn.nhidden]
src/architectures/lagrangian_neural_network.jl:20: Dense(nn.dimin, nn.width, nn.act),
```

The constructor's keyword is `activation`; only the field keeps the short name. The fix renames
the field, which changes a public field name.

### K22 · A comment in `symplectic_attention.jl` names a test file under `test/parameters/`, which does not exist.

- **location:** `src/layers/symplectic_attention.jl:131`
Expand Down
4 changes: 0 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ NNlib = "872c559c-99b0-510c-b3b7-b6c96a88d5cd"
NeuralNetworkParameters = "67f4d93a-60e9-472b-8cdd-1ccf6005724a"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
SymbolicNeuralNetworks = "aed23131-dcd0-47ca-8090-d21e605652e3"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"

Expand All @@ -45,8 +43,6 @@ NNlib = "0.8, 0.9"
NeuralNetworkParameters = "0.5"
ProgressMeter = "1"
Random = "1"
SymbolicNeuralNetworks = "0.9"
Symbolics = "7"
TimerOutputs = "0.5, 1"
Zygote = "0.7"
julia = "1.12"
Loading
Loading