Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions CHANGELOG.md

Large diffs are not rendered by default.

96 changes: 77 additions & 19 deletions KNOWN_ISSUES.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,13 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.

### K20 · The `DataLoader((q = …, p = …))` constructors require one array type for `q` and `p`.

- **location:** `src/data_loader/data_loader.jl:216`
- **location:** `ReducedComplexityModeling` `src/data_loader/data_loader.jl:220`
- **kind:** defect
- **found:** #353 (2026-10-23); the constructors are older than that pull request
- **evidence:**
The three methods at `src/data_loader/data_loader.jl:216-245` take
`NamedTuple{(:q, :p), Tuple{AT, AT}}` (and `Tuple{VT, VT}`), so `q` and `p` must have one
array type, while the `TransformerLoss` method at `src/loss/losses.jl:92` and the
The three methods at `ReducedComplexityModeling` `src/data_loader/data_loader.jl:220-249`
take `NamedTuple{(:q, :p), Tuple{AT, AT}}` (and `Tuple{VT, VT}`), so `q` and `p` must have one
array type, while the `TransformerLoss` method at `src/loss/losses.jl:98` and the
`MultiHeadAttention` call take `NamedTupleOfArrays`, which admits two. A view for `q` and a
`Matrix` for `p` raises:

Expand All @@ -122,10 +122,47 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.
```

The same call with `q = A` gives a `DataLoader` with `input_dim == 4`. The docstring at
`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.
`ParametricDataLoader`'s `(q, p)` constructor has the same one-type restriction and widens with
it.
`ReducedComplexityModeling` `src/data_loader/data_loader.jl:208` states the one-type signature.
Closing it means widening the three signatures to `NamedTupleOfArrays{T, (:q, :p)}`, and the
docstring with them. `ParametricDataLoader`'s `(q, p)` constructor has the same one-type
restriction and widens with it.

### K37 · `metal_abort_runs.jl audit` throws when a run's crash report is gone, although its log records the signature.

- **location:** `scripts/utilities/metal_abort_runs.jl:293`
- **kind:** defect
- **found:** #365
- **evidence:**
`read_run` calls `report_signature(report_path(path, index, file, reports))` for every `M8-IPS` line.
`report_path` returns the copy beside the log when it exists and else `joinpath(reports, file)`, and
`report_signature` is `read(path, String)` on it (`:101-102`), so a copy that is missing with the
original deleted from `~/Library/Logs/DiagnosticReports` raises a `SystemError` and the audit of the
arm stops. Each `M8-IPS` line already holds `signature=` (`:426-427`), which the audit could use when
the file is absent. Read from the code; the failure is not reproduced.

### K38 · The session arm's reader stamps `R0` after `yield()`, so `R0` is not the instant its first read finished.

- **location:** `scripts/utilities/metal_abort_session.jl:134-137`
- **kind:** defect
- **found:** #365
- **evidence:**
The docstring of `reader` (`:117-123`) says that `R0` is the instant the first read finished, and the
loop assigns it after `sum(v)`, `yield()` and `reads += 1`. When `yield()` hands the thread to another
task for a long stretch, `R0` lies later than the read, and `T0 < R0 < T1` can fail for a run whose
first read overlapped the training call, so the run counts as void and not as concurrent. The error
is on the side of a void run. Read from the code; not measured.

### K39 · The session arm shares `M8_T0`, `M8_T1` and `M8_TRAINER` between two threads with no synchronization.

- **location:** `scripts/utilities/metal_abort_session.jl:102-106`
- **kind:** not verified
- **found:** #365
- **evidence:**
`training()` assigns the three globals and `reader()` polls and reads them (`:127`, `:133`,
`:141-142`) on another thread, with no lock and no atomic. Julia gives no guarantee for such reads of
a global that another thread writes. CodeRabbit marks the finding as one it could not verify, and no
wrong value has been observed: each global is a single `Int`, assigned around the training
call.

## Upstream

Expand Down Expand Up @@ -276,7 +313,7 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.

### K33 · An epoch of `train!` on a Metal array advances the CPU random stream, so a two-epoch run draws its second epoch's minibatches differently from the host's.

- **location:** `src/data_loader/batch.jl:195`
- **location:** `ReducedComplexityModeling` `src/data_loader/batch.jl:186`
- **kind:** not verified
- **found:** 2026-10-26, part M7
- **evidence:**
Expand Down Expand Up @@ -385,17 +422,38 @@ What is known to be wrong in GeometricMachineLearning and is not fixed.
- **found:** 2026-10-01
- **evidence:**
Julia 1.13.2, `mutate.jl <repository> <file> <from> <to> quality/jet.jl`, one mutant per run.
Three mutants that put `Base.inferencebarrier` on an argument of a kernel launch SURVIVED, each
with 50 pass, 0 fail: `kernel!(c, Base.inferencebarrier(a), b, ndrange = size(c))` in
`src/kernels/tensor_tensor_mul.jl`; `tensor_mat_skew_sym_assign_k!(C,
Two mutants that put `Base.inferencebarrier` on an argument of a kernel launch SURVIVED, with no
assertion failing: `kernel!(c, Base.inferencebarrier(a), b, ndrange = size(c))` in
`src/kernels/tensor_tensor_mul.jl`, and `tensor_mat_skew_sym_assign_k!(C,
Base.inferencebarrier(Z), A, ndrange = size(C))` in
`src/kernels/inverses/tensor_mat_skew_sym_assign.jl`; and `gather_kernel!(backend)(gathered,
Base.inferencebarrier(data), indices, offset, ndrange = size(gathered))` in
`src/data_loader/batch.jl`. At `target_modules = (GeometricMachineLearning,)` a barrier on a
launch argument gives 0 reports, and the same barrier in an `@assert` of the same launcher is
CAUGHT with 3 reports (47 pass, 3 fail): the kernel functor takes `args...`, so the dispatch sits
in KernelAbstractions' frames, which the filter drops. A barrier on a value inside the kernel
body of `tensor_tensor_mul_kernel!` is CAUGHT (47 pass, 3 fail).
`src/kernels/inverses/tensor_mat_skew_sym_assign.jl`. At
`target_modules = (GeometricMachineLearning,)` a barrier on a launch argument gives 0 reports, and
the same barrier in an `@assert` of the same launcher is CAUGHT: the kernel functor takes
`args...`, so the dispatch sits in KernelAbstractions' frames, which the filter drops. A barrier
on a value inside the kernel body of `tensor_tensor_mul_kernel!` is CAUGHT.

The loader's gather launch is outside this file's reach. Its launcher,
`gather_kernel!(backend)(gathered, data, indices, offset, ndrange = size(gathered))`, is
`ReducedComplexityModeling`'s `src/data_loader/batch.jl`, which `target_modules =
(GeometricMachineLearning,)` filters out, and gating it belongs to that package's
`test/quality/jet.jl`.

### K36 · Three files of `test/data_loader/` test code that is no longer in `src/data_loader/`.

- **location:** `test/data_loader/batch.jl`
- **kind:** defect
- **found:** #365
- **evidence:**
`src/data_loader/` holds `train.jl` alone, since the loader is `ReducedComplexityModeling`'s. Three
test files of `test/data_loader/` have no source file of that name:
`batch.jl` tests the `Batch{:Transformer}` method of `integrate`, which is in
`src/architectures/transformer_integrator.jl`; `data_loader.jl` tests `solve!` of a `PSDArch`,
which is in `src/architectures/psd.jl`; and `parametric_data_loader.jl` tests `train!` on a
`ParametricDataLoader`, which is in `src/data_loader/train.jl`. The layout rule is that
`test/<path>.jl` tests `src/<path>.jl`; `test-layout.jl --check` prints nothing for the branch, so
the check does not see the three. Moving them to `test/architectures/transformer_integrator.jl`,
`test/architectures/psd.jl` and `test/data_loader/train.jl` also changes the lines that list them
in `test/runtests.jl`.

### K14 · `train!` infers its history as an abstract `Vector`, although the run-time history is `Vector{T}`.

Expand Down
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
GeometricBase = "9a0b12b7-583b-4f04-aa1f-d8551b6addc9"
GeometricEquations = "c85262ba-a08a-430a-b926-d29770767bf2"
GeometricOptimizers = "fc236c15-5557-4942-aa65-b650f329279e"
GeometricSolutions = "7843afe4-64f4-4df4-9231-049495c56661"
KernelAbstractions = "63c18a36-062a-441e-b654-da1e3ab1ce7c"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
NNlib = "872c559c-99b0-510c-b3b7-b6c96a88d5cd"
NeuralNetworkParameters = "67f4d93a-60e9-472b-8cdd-1ccf6005724a"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReducedComplexityModeling = "abb4722c-1d8e-48eb-a3bd-45b3116bfaba"
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"

[compat]
Expand All @@ -28,12 +28,12 @@ ForwardDiff = "1"
GeometricBase = "0.15"
GeometricEquations = "0.21.5"
GeometricOptimizers = "0.9"
GeometricSolutions = "0.6.6"
KernelAbstractions = "0.9"
LinearAlgebra = "1"
NNlib = "0.8, 0.9"
NeuralNetworkParameters = "0.5"
ProgressMeter = "1"
Random = "1"
ReducedComplexityModeling = "0.4.1"
Zygote = "0.7"
julia = "1.12"
5 changes: 2 additions & 3 deletions docs/check_references.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ function resolve(text::AbstractString, mod::Module = Main; exact_signature::Bool
DocSystem.iskeyword(binding) && return (true, "")
DocSystem.defined(binding) || return (false, "undefined binding `$binding`")
# A `@docs` entry has to match a specific method, but a `@ref` resolves to whatever anchor the
# manual created for the binding -- `[`accuracy(::Chain, ::Tuple, ::DataLoader)`](@ref)` links to
# `@docs accuracy` even though no method has that exact signature. Checking a `@ref` against the
# signature would reject those.
# manual created for the binding, so a `@ref` whose signature no method has exactly still links
# to the binding's `@docs` block. Checking a `@ref` against the signature would reject those.
Comment on lines +46 to +47

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor · Maintainability & Code Quality · Quick win

Correct the changed comment.

The phrase whose signature no method has exactly is incomplete. State that an explicit @ref signature can match no method and still resolve through the binding.

Proposed wording
--- "a/docs/check_references.jl"
+++ "b/docs/check_references.jl"
@@ -43,7 +43,7 @@
     DocSystem.iskeyword(binding) && return (true, "")
     DocSystem.defined(binding) || return (false, "undefined binding `$binding`")
     # A `@docs` entry has to match a specific method, but a `@ref` resolves to whatever anchor the
-    # manual created for the binding, so a `@ref` whose signature no method has exactly still links
+    # manual created for the binding, so a `@ref` whose explicit signature matches no method still links
     # to the binding's `@docs` block. Checking a `@ref` against the signature would reject those.
     typesig = if exact_signature
         try

(raw.githubusercontent.com)

Committable suggestion

IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# manual created for the binding, so a `@ref` whose signature no method has exactly still links
# to the binding's `@docs` block. Checking a `@ref` against the signature would reject those.
# manual created for the binding, so a `@ref` whose explicit signature matches no method still links
# to the binding's `@docs` block. Checking a `@ref` against the signature would reject those.

typesig = if exact_signature
try
Core.eval(mod, DocSystem.signature(ex, String(text)))
Expand Down
1 change: 0 additions & 1 deletion docs/src/architectures/transformer.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ StandardTransformerIntegrator
Transformer
ClassificationTransformer
ClassificationLayer
GeometricMachineLearning.assign_output_estimate
```

```@raw latex
Expand Down
28 changes: 15 additions & 13 deletions docs/src/data_loader/data_loader.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# The Data Loader

The `DataLoader` in `GeometricMachineLearning` is designed to make training convenient.
The `DataLoader` that `GeometricMachineLearning` re-exports from
[ReducedComplexityModeling.jl](https://github.com/JuliaRCM/ReducedComplexityModeling.jl) is designed
to make training convenient.

The data loader can be called with various types of arrays as input, for example a [snapshot matrix](@ref "Snapshot Matrix"):

Expand Down Expand Up @@ -49,7 +51,7 @@ dl.input_dim

## Drawing Batches with `GeometricMachineLearning`

If we want to draw mini batches from a data set, we need to allocate an instance of [`Batch`](@ref). If we call the corresponding functor on an instance of [`DataLoader`](@ref) we get the following result[^1]:
If we want to draw mini batches from a data set, we need to allocate an instance of `Batch`. If we call the corresponding functor on an instance of `DataLoader` we get the following result[^1]:

[^1]: We first demonstrate how to sample data on the example of a matrix. The case of sampling from a tensor is slightly more complicated and is explained below.

Expand Down Expand Up @@ -146,7 +148,7 @@ Sampling from a tensor is done the following way (``\mathcal{I}_i`` again denote
3. ``\mathcal{I}_i \leftarrow \mathtt{complete\_indices[}(i - 1) \cdot \mathtt{batch\_size} + 1 : i \cdot \mathtt{batch\_size]}\text{ for }i=1, \ldots, (\mathrm{last} -1),``
4. ``\mathcal{I}_\mathrm{last} \leftarrow \mathtt{complete\_indices[}(\mathrm{last} - 1) \cdot \mathtt{batch\_size} + 1\mathtt{:end]}.``

Note that we supplied two additional arguments to the [`Batch`](@ref) constructor here: `seq_length` and `prediction_window`. These two arguments specify how many time are considered in one mini batch and how long the prediction runs into the future respectively. These two numbers are explained when we talk about [structure on product spaces](@ref "How is Structure Preserved?").
Note that we supplied two additional arguments to the `Batch` constructor here: `seq_length` and `prediction_window`. These two arguments specify how many time are considered in one mini batch and how long the prediction runs into the future respectively. These two numbers are explained when we talk about [structure on product spaces](@ref "How is Structure Preserved?").

This algorithm can be visualized the following way (here `batch_size = 4`):

Expand All @@ -157,13 +159,13 @@ Here the sampling is performed over the second axis (the *time step dimension*)

## Library Functions

```@docs
DataLoader
Batch
Batch(::Int)
Batch(::Int, ::Int, ::Int)
GeometricMachineLearning.number_of_batches
GeometricMachineLearning.convert_input_and_batch_indices_to_array
GeometricMachineLearning.accuracy
GeometricMachineLearning.ParametricDataLoader
```
`DataLoader`, `Batch` and the loader's routines — `number_of_batches`,
`convert_input_and_batch_indices_to_array`, `accuracy`, `assign_output_estimate` and
`ParametricDataLoader` — are `ReducedComplexityModeling`'s, and
[that package's manual](https://juliarcm.github.io/ReducedComplexityModeling.jl/stable/) documents
them. This package re-exports the first two, so `using GeometricMachineLearning` alone gives both.

[`train!`](@ref) is that package's generic as well, re-exported here. This package defines the
method that trains a network on a `DataLoader` or a `ParametricDataLoader`, and this manual's
`train!` entry is its docstring; the generic's own docstring, `train!(model, …)`, is in that
package's manual.
4 changes: 2 additions & 2 deletions docs/src/data_loader/snapshot_matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ The snapshot tensor fulfills the same role as the snapshot matrix but has a thir
![The three axes of the snapshot tensor are (i) dimension, (ii) time and (iii) parameters.](https://juliagni.github.io/GeometricFigures.jl/figures/data/tensor/tensor_light.svg)
![The three axes of the snapshot tensor are (i) dimension, (ii) time and (iii) parameters.](https://juliagni.github.io/GeometricFigures.jl/figures/data/tensor/tensor_dark.svg)

When drawing samples from the snapshot tensor to train a neural networks we also need to specify a *sequence length* (as an argument to the [`Batch`](@ref) struct). When sampling a batch from the snapshot tensor we sample over the second axis (the *time dimension*) and the third axis of the tensor (the *parameter dimension*). The total number of batches[^2] is
When drawing samples from the snapshot tensor to train a neural networks we also need to specify a *sequence length* (as an argument to the `Batch` struct). When sampling a batch from the snapshot tensor we sample over the second axis (the *time dimension*) and the third axis of the tensor (the *parameter dimension*). The total number of batches[^2] is

[^2]: The number of batches shown here is for the case ``\mathtt{prediction\_window} = 0.`` If ``\mathtt{prediction\_window} \neq 0,`` this number may be smaller. The corresponding function is [`GeometricMachineLearning.number_of_batches`](@ref).
[^2]: The number of batches shown here is for the case ``\mathtt{prediction\_window} = 0.`` If ``\mathtt{prediction\_window} \neq 0,`` this number may be smaller. The corresponding function is `ReducedComplexityModeling.number_of_batches`.

```math
\lceil\mathtt{(dl.input\_time\_steps - batch.seq\_length) * dl.n\_params / batch.batch\_size}\rceil,
Expand Down
2 changes: 1 addition & 1 deletion docs/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Closely linked to the research presented here is the development of a software p
train!
```

So the docstring shows the name of the type or method, in most cases how to call it and then gives some information explaining what it does and potentially hyperlinks to other similar docstrings ([`DataLoader`](@ref) in this case); all of this information is indented by a tab. Docstrings may include other information under subheaders **Arguments** (showing the arguments the method can be supplied with), **Examples** (giving more detailed examples (including results) of how to use the method) and **Implementation** (giving details on how the method is implemented). When we reference a docstring it is always printed in blue (e.g. [`train!`](@ref)), indicating a hyperlink. In addition there is an *index of docstrings* showing all docstrings in chronological order with the associated page number.
So the docstring shows the name of the type or method, in most cases how to call it and then gives some information explaining what it does and potentially hyperlinks to other similar docstrings ([`TransformerLoss`](@ref) in this case); all of this information is indented by a tab. Docstrings may include other information under subheaders **Arguments** (showing the arguments the method can be supplied with), **Examples** (giving more detailed examples (including results) of how to use the method) and **Implementation** (giving details on how the method is implemented). When we reference a docstring it is always printed in blue (e.g. [`train!`](@ref)), indicating a hyperlink. In addition there is an *index of docstrings* showing all docstrings in chronological order with the associated page number.

Similar to **Library Functions**, which is included in most sections, almost every chapter concludes with a section **Chapter Summary** and an additional section **References** that shows further related reading material. The **Chapter Summary** recaps the important aspects of the corresponding chapter, states again what is new (this may be mathematical or software aspects) and gives information to what other parts of the dissertation the contents of the present chapter are relevant.

Expand Down
8 changes: 4 additions & 4 deletions docs/src/tutorials/symplectic_autoencoder.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Main.remark(raw"As with any neural network we have to make the following choices
" * Main.indentation * raw"5. choose a *number of epochs*,
" * Main.indentation * raw"where points 1 and 3 depend on a variable number of hyperparameters.")
```
For the symplectic autoencoder point 1 is done by calling [`SymplecticAutoencoder`](@ref), point 2 is done by calling `NeuralNetwork`, point 3 is done by calling [`TrainingOptimizer`](@extref GeometricOptimizers GeometricOptimizers.TrainingOptimizer) and point 4 is done by calling [`Batch`](@ref).
For the symplectic autoencoder point 1 is done by calling [`SymplecticAutoencoder`](@ref), point 2 is done by calling `NeuralNetwork`, point 3 is done by calling [`TrainingOptimizer`](@extref GeometricOptimizers GeometricOptimizers.TrainingOptimizer) and point 4 is done by calling `Batch`.

## The system

Expand Down Expand Up @@ -85,16 +85,16 @@ sol = integrate(pr, ImplicitMidpoint())
nothing # hide
```

We then put the data in the correct format by calling [`DataLoader`](@ref)[^1]:
We then put the data in the correct format by calling `DataLoader`[^1]:

[^1]: For more information on [`DataLoader`](@ref) see the [corresponding section](@ref "The Data Loader").
[^1]: For more information on `DataLoader` see the [corresponding section](@ref "The Data Loader").

```@example toda_lattice
dl_cpu = DataLoader(sol; autoencoder = true, suppress_info = true)
nothing # hide
```

Also note that the keyword `autoencoder` was set to true when calling [`DataLoader`](@ref). The keyword argument `supress_info` determines whether data loader provides some additional information on the data it is called on.
Also note that the keyword `autoencoder` was set to true when calling `DataLoader`. The keyword argument `supress_info` determines whether data loader provides some additional information on the data it is called on.

## Train the network

Expand Down
2 changes: 1 addition & 1 deletion docs/src/tutorials/sympnet_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ solution = integrate(ho_problem, ImplicitMidpoint())
nothing # hide
```

We call [`DataLoader`](@ref) in order to conveniently handle the data:
We call `DataLoader` in order to conveniently handle the data:

```@example sympnet
using GeometricMachineLearning # hide
Expand Down
1 change: 1 addition & 0 deletions scripts/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ NeuralNetworkParameters = "67f4d93a-60e9-472b-8cdd-1ccf6005724a"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
ReducedComplexityModeling = "abb4722c-1d8e-48eb-a3bd-45b3116bfaba"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"

Expand Down
Loading
Loading