Repository navigation
Add the M1 end-to-end slice: problems, Euler–Maruyama and solver - #12
Conversation
First slice of milestone M1, from the problem definition to the solution: - `ItoSDE` / `SDEProblem` with initial time, initial value and noise structure; `NoiseStructure` with declared and verified flags; `Calculus`. - `StepNoise` and the `NoiseSource` interface; `BrownianPath` is a source. - `Scheme`, `OneStepScheme` and `EulerMaruyama`. - `Solver` with checks before the time loop, and `Solution`. - Benchmark problems `GeometricBrownianMotion` and `LinearSystem` (commuting matrices, exact solution without a matrix exponential), registered in `numsde.benchmarks.PROBLEMS`. - Theory pages for the scheme and both problems; decision log updated.
Review of commit 690cbc0Recommendation: address the following two findings before merging. [P1] Internally sampled noise bypasses problem compatibility checksIn solver.py, lines 154–158, _check_noise is called only when the caller supplies a noise source. Internally sampled paths therefore bypass the initial-time and x0 namespace/dtype/device checks. Reproduced example: import numsde as ns
problem = ns.benchmarks.GeometricBrownianMotion(mu=1.0, sigma=0.0, t0=2.0)
solver = ns.Solver(problem, ns.schemes.EulerMaruyama())
grid = ns.uniform_grid(1.0, 4)
solution = solver.solve(times=grid, seed=0)
# Succeeds on [0, 1], although the initial condition is prescribed at t0=2.
# solution.final == [[2.44140625]]
path = ns.WienerProcess(1).sample_path(grid, seed=0)
solver.solve(path)
# Correctly raises ValueError: grid starts at 0.0, but t0=2.0.A separate reproduction with a custom ItoSDE and a float64 array x0 confirmed that solve(times=..., dtype=np.float32) silently downcasts the initial condition, contrary to the documented requirement that an array x0 agree with the noise source. Suggested fix: Separate validation of mutually exclusive sampling arguments from noise/problem compatibility validation. Run the latter for both supplied and internally sampled noise. Add regression tests for mismatched initial times and array x0 dtype/backend/device. [P2] The stated assumptions do not guarantee strong order 1 for additive noiseThe EM theory page, lines 48–66 assumes only 1/2-Hölder continuity in time. Under that assumption, additive noise and smooth drift do not suffice for strong order 1. For example, on [0,1], consider This has additive noise, zero (hence smooth) drift, and a diffusion coefficient satisfying the stated time regularity. By the Itô isometry, on a uniform grid with h=1/N the squared terminal error is Thus even the terminal L2 error is not O(h). The spatial Milstein correction vanishes, but that does not eliminate the error from freezing a time-dependent diffusion coefficient. Suggested fix: Explicitly add sufficient time regularity of g (for example, Lipschitz continuity in time), alongside suitable drift regularity, to the additive-noise order-1 claim. Document this qualification for expected_strong_order as well; additive=True alone is not a regularity certificate. Validation
The initial cached mypy invocation encountered an internal tool error; the clean non-incremental run succeeded. PyTorch and CUDA tests were not run because PyTorch was not installed in the review environment. The bibliography/section references marked † remain unverified. No source changes were made during this review. |
P1: The solver now runs one compatibility check for supplied and sampled noise alike (noise dimension, grid starting at t0, namespace, dtype and device of an array x0). Explicit sampling options must agree with an array x0. Regression tests cover mismatched initial times, dtypes and backends. P2: The Euler–Maruyama theory page states the time regularity that strong order 1 for additive noise requires (diffusion Lipschitz continuous in t) and gives the counterexample dX = sqrt(t) dW. The docstring of expected_strong_order notes that noise-structure flags certify no time regularity. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
|
Thanks for the review. Both findings are accepted and addressed in 9057fec. P1: sampled noise bypassed the compatibility checksConfirmed with both reproductions. Regression tests ( P2: assumptions for strong order 1 with additive noiseConfirmed; the closed form of your example gives E|X(1) − X_h^N|²/h² = 0.74, 0.97, 1.20, 1.43, 1.66 for N = 2^4, 2^8, …, 2^20, growing like log(1/h). The EM theory page now requires g Lipschitz continuous in t and f twice continuously differentiable in x with bounded derivatives and Lipschitz in t for order 1, explains why (the freezing error of g), and includes your example. The docstrings of Both points are recorded in the decision log. No errata entry, since the code was never merged. Validation
Still open: the CUDA test (maintainer's machine) and the references marked †. Generated by Claude Code |
Follow-up review of commit 9057fecBoth findings from the previous review are substantively addressed:
Remaining documentation fix: literal control characters in LaTeXIn docs/theory/schemes/euler_maruyama.md, lines 68–81, some LaTeX commands have been stored as control characters:
Affected lines: 68 and 80 (big), 79 (vert, twice), and 81 (asymp). Please replace these sequences with the literal LaTeX commands so that the formulas render correctly. The intended displayed equation is: \mathbb{E}\,\vert X(1) - X_h^N \vert^2
= \sum_{n=0}^{N-1} \int_{nh}^{(n+1)h} \big(\sqrt{t} - \sqrt{nh}\big)^2\,\mathrm{d}t
\asymp h^2 \log(1/h).Validation and conclusionAll 12 GitHub checks for the reviewed commit are successful, including the CPU PyTorch tests, documentation, lint/type checks, wheel tests, and the aggregate CI passed check. Tests were not rerun locally during this follow-up because the execution environment was unavailable; this assessment is based on inspection of the changes, regression tests, raw documentation content, and GitHub check results. After the small documentation correction above, this follow-up review identifies no further merge blockers. The previously outstanding bibliography/section verifications remain unverified. No source changes were made. |
Three LaTeX commands on the theory page had been turned into control characters by escape sequences in an editing script: \big (U+0008), \vert (U+000B) and \asymp (U+0007), on lines 68 and 79-81. They are replaced by the literal commands. A new test rejects control characters other than tab, line feed and carriage return in all text files of the repository, since the Sphinx build with -W does not notice them. Reported in the follow-up review of PR #12. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
|
Thanks, confirmed and fixed in 2132d7f. The three commands had passed through a non-raw string in an editing script, where Since All 12 checks pass on 2132d7f. #13, stacked on this branch, has been rebased and is green as well. Generated by Claude Code |
`nox -s gpu` installed PyTorch with its CUDA libraries (about 3 GB) without any output, which looked like a hang, and recreated that environment on every run. If PyTorch could not use the GPU, the GPU tests were only skipped and the session passed. The session now shows the installation output, reuses its environment (NumSDE itself is reinstalled every run), accepts NUMSDE_TORCH_INDEX for a PyTorch build that matches the local driver, and fails with a hint to nvidia-smi unless a CUDA device is usable. CONTRIBUTING.md describes this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
Following the maintainer's literature check: - Kloeden–Platen, Theorem 10.2.2 covers the final-time error in L^1 (not L^2, as the page said); the weak order (Section 14.1) is confirmed. - The final-time error in L^2 follows from the fundamental theorem of Milstein–Tretyakov (2021), Theorem 1.1.1. - The error uniformly on the grid for p = 2: Mao (2007), Chapter 2, Theorem 7.3. - Divergence for superlinear coefficients: Hutzenthaler, Jentzen and Kloeden (2011), Theorem 2.1, now stated with its assumptions. - Kanagawa (1988), said to cover p >= 2, is listed as unchecked and kept out of references.bib. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
As checked by the maintainer: Mao's second edition appeared in 2008 (Horwood Publishing, Chichester), and Hutzenthaler, Jentzen and Kloeden was published in December 2010, Proc. R. Soc. A 467(2130), 1563–1576. The keys become Mao2008 and HutzenthalerJentzenKloeden2010. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
Volume 467, issue 2130 and pages 1563–1576 belong to the printed issue of 2011 (as in MathSciNet, MR2795791); December 2010 was the online-first version. The key is HutzenthalerJentzenKloeden2011 again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
royalsocietypublishing.org answers automated requests with 403 Forbidden, so the DOI 10.1098/rspa.2010.0348 added in #12 broke the nightly link check although the link works in a browser. The link check now ignores DOIs with the Royal Society prefix 10.1098 and the publisher's domain; such DOIs are checked by hand when they enter references.bib. Claude-Session: https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp Co-authored-by: Claude <noreply@anthropic.com>
First slice of milestone M1 Core and Euler–Maruyama (see the roadmap in
docs/development/architecture.md): everything from the problem definition to the solution, tested end to end on geometric Brownian motion and a linear system with multidimensional noise.What is added
ItoSDE(baseSDEProblem),NoiseStructure(declared and verified flags),CalculusStepNoise,NoiseSource;BrownianPathis now a noise source (steps(requires))Scheme,OneStepScheme,EulerMaruyama,matvecSolver(checks scheme, noise and coefficient shapes before the loop;save="all" | "final"),SolutionGeometricBrownianMotion,LinearSystem, registrynumsde.benchmarks.PROBLEMS,get_problemasarray_like(public),asarray_in,device_ofschemes/euler_maruyama,problems/geometric_brownian_motion,problems/linear_system; API reference; changelogThe part of the API sketch up to
solvenow runs astests/unit/test_api_sketch.py.Decisions (recorded in the decision log)
steps(requires)instead ofsteps(grid)); other step sizes come fromBrownianPath.coarsen.t0; the noise grid must start there, andexact_solution(t, W)receivesW(t) − W(t0).driftanddiffusionreceivetas a Python float.x0must agree with it. Without a source,solve(times=..., n_paths=..., seed=...)samples a Wiener path followingx0or explicitbackend,dtype,device.LinearSystemis built from a common eigenbasis, so its matrices commute (commutativity recorded as verified) and the exact solution needs no matrix exponential and no SciPy.Tests
dX = dW, coarsened paths as the same realization.slow): fitted strong order 0.5 ± 0.1 on both problems (error uniformly on the grid, p = 2), weak error ofE[X_h^N]for GBM against its closed form.x0selects the backend; a CUDA test with thegpumarker.Please check
references.bib. Deferred to Check Kanagawa (1988) for the strong order of Euler–Maruyama in L^p, p ≥ 2 #14; not a merge blocker.Mao2008: 2nd ed., Horwood Publishing, Chichester, 2008, ISBN 978-1-904275-34-3, confirmed by the maintainer (8661bb9).HutzenthalerJentzenKloeden2011: Proc. R. Soc. A 467(2130), 1563–1576, 2011 (printed issue, as in MR2795791; online first December 2010), confirmed by the maintainer (69e7d5a).tests/unit/test_solver_torch.py::test_cuda_solution_matches_numpyon your machine (nox -s gpu): passed on 2026-11-03 together withtest_brownian_torch.py::test_cuda_path_matches_numpy(NVIDIA RTX A4000, driver 595.99.02, PyTorch 2.15.0+cu132, Python 3.14).Next in M1
Streaming noise source,
StrongConvergenceStudywith pathwise bootstrap, Brownian motion on the circle, first tutorial, ports of the GBM legacy scripts.https://claude.ai/code/session_018KaTyMUfivRVAGYQR6kGnp
Generated by Claude Code