From 803fb573eaf3ac177192c4661c8a4696f5404acf Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sun, 23 Aug 2026 22:05:03 +0000 Subject: [PATCH] Restore the SciML common interface on `using MATLABDiffEq` (#97) `Document MATLAB solver API and enable strict QA` (4fdd039, #94) dropped `@reexport using DiffEqBase` from src/MATLABDiffEq.jl. That reexport was the only thing putting `ODEProblem`, `solve`, `ReturnCode` and the rest of the common interface into scope, so the README's documented workflow using MATLABDiffEq, ParameterizedFunctions prob = ODEProblem(f, u0, tspan) sol = solve(prob, MATLABDiffEq.ode45()) stopped working with `UndefVarError`. The same commit is its own best evidence: it had to rewrite both README examples to `using MATLAB, MATLABDiffEq, ParameterizedFunctions, SciMLBase` and to write the new docs/src/index.md example as `using MATLABDiffEq, SciMLBase`. Papering over the documentation is not the same as keeping the interface. Rather than restore the blanket reexport (420 names, most of them for equation classes MATLAB's ODE suite cannot solve), export exactly the interface a user works with: * the problem, function and solution types for ODEs, the only class `__solve` accepts here: `ODEProblem`, `ODEFunction`, `ODESolution`, plus `DEStats` and `NullParameters`; * `solve` and `remake`; * `ReturnCode` and `successful_retcode`; * the ensemble types, which drive `solve` generically. Deliberately left out: DAE/SDE/DDE and every other non-ODE problem type, the callbacks (`__solve` errors with "Callbacks are not supported in MATLABDiffEq.jl"), and the integrator interface (MATLABDiffEq implements `__solve` only). Those names error identically before and after. The MATLAB algorithms themselves stay `@public` but unexported, so they are still written qualified as `MATLABDiffEq.ode45()`. Restore both README examples to `using MATLABDiffEq, ParameterizedFunctions` and the docs/src/index.md example to a bare `using MATLABDiffEq`. Document the reexported surface on a new rendered API page, docs/src/api.md, grouped by role with SciMLBase named and linked as the owner of every name, a closing boundary line, and the reasoning for each deliberate omission. Wire the page into docs/make.jl. The list is declared through `reexports_allow` in test/qa/qa.jl and covered by a test that every approved name is in `names(MATLABDiffEq)` and actually in scope from `using MATLABDiffEq`, so the docs list, the `export` block and the allow-list cannot drift apart. Adding back names that used to be exported is not breaking. Claude-Session: https://claude.ai/code/session_01Rmh6B9eoW3N8oCbuuVPVxJ Co-authored-by: Claude --- README.md | 4 ++-- docs/make.jl | 2 +- docs/src/api.md | 35 +++++++++++++++++++++++++++++++++++ docs/src/index.md | 9 ++++++--- src/MATLABDiffEq.jl | 16 ++++++++++++++++ test/qa/qa.jl | 26 ++++++++++++++++++++++++-- 6 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 docs/src/api.md diff --git a/README.md b/README.md index 1032677..0dfa4d2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ exported, so use qualified names such as `MATLABDiffEq.ode45()`. ## Example ```julia -using MATLAB, MATLABDiffEq, ParameterizedFunctions, SciMLBase +using MATLABDiffEq, ParameterizedFunctions f = @ode_def LotkaVolterra begin dx = 1.5x - x*y @@ -105,7 +105,7 @@ MATLAB.show_msession() Generally, for long enough problems the overhead is minimal. Example: ```julia -using MATLAB, MATLABDiffEq, ParameterizedFunctions, SciMLBase +using MATLABDiffEq, ParameterizedFunctions f = @ode_def_bare RigidBodyBench begin dy1 = -2*y2*y3 dy2 = 1.25*y1*y3 diff --git a/docs/make.jl b/docs/make.jl index 7b2cd1f..62d9922 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -12,7 +12,7 @@ makedocs(; canonical = "https://docs.sciml.ai/MATLABDiffEq/stable/", prettyurls = get(ENV, "CI", "false") == "true", ), - pages = ["Home" => "index.md"], + pages = ["Home" => "index.md", "API" => "api.md"], ) deploydocs(; repo = "github.com/SciML/MATLABDiffEq.jl.git", push_preview = true) diff --git a/docs/src/api.md b/docs/src/api.md new file mode 100644 index 0000000..f62921a --- /dev/null +++ b/docs/src/api.md @@ -0,0 +1,35 @@ +# API + +The MATLABDiffEq algorithm markers are documented under +[Public API](index.md#Public-API) on the home page. + +## Reexported SciML common interface + +`using MATLABDiffEq` also brings in the parts of the SciML common interface needed to +build an ODE problem, solve it, and inspect the result, so they do not have to be +imported separately. MATLABDiffEq does not define these names -- they are owned and +documented by [SciMLBase](https://docs.sciml.ai/SciMLBase/stable/), and that is where +their documentation lives: + + - Problems: `ODEProblem`, `EnsembleProblem` + - Functions: `ODEFunction` + - Solutions: `ODESolution`, `EnsembleSolution`, `EnsembleSummary`, `DEStats` + - Ensemble algorithms: `EnsembleSerial`, `EnsembleThreads`, `EnsembleDistributed`, + `EnsembleSplitThreads`, and the `EnsembleAnalysis` module + - Solving: `solve`, `remake` + - Return status: `ReturnCode`, `successful_retcode` + - `NullParameters` + +Note that the MATLAB algorithms themselves are public but *not* exported, so they are +still written qualified: `MATLABDiffEq.ode45()`. + +Anything else from SciMLBase must be imported from SciMLBase directly. Three groups are +deliberately absent: + + - **DAE, SDE, DDE and every other non-ODE problem type.** `SciMLBase.__solve` is + defined here only for `AbstractODEProblem`. (`ode15i` names MATLAB's implicit solver, + but it is still reached through an `ODEProblem`.) + - **Callbacks.** `__solve` errors on a `callback` keyword ("Callbacks are not supported + in MATLABDiffEq.jl"), so `ContinuousCallback` and friends are not part of its surface. + - **The integrator interface** (`init`, `step!`, `solve!`, `reinit!`, ...). + MATLABDiffEq implements `SciMLBase.__solve` only; it has no integrator. diff --git a/docs/src/index.md b/docs/src/index.md index 588d902..3eeb7ae 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -10,11 +10,12 @@ models; native Julia solvers are the recommended choice for production workloads ## Basic Usage -Load `SciMLBase` for the problem and solve interfaces, then pass a qualified MATLABDiffEq +`using MATLABDiffEq` brings the SciML common interface -- `ODEProblem`, `solve` and the +solution types -- into scope along with the algorithms, so pass a qualified MATLABDiffEq algorithm to `solve`: ```julia -using MATLABDiffEq, SciMLBase +using MATLABDiffEq function lorenz!(du, u, p, t) du[1] = 10.0 * (u[2] - u[1]) @@ -33,7 +34,9 @@ supported. ## Public API Algorithm types are public but not exported, which avoids collisions with similarly named -algorithms from other solver packages. Use them through the `MATLABDiffEq` namespace. +algorithms from other solver packages. Use them through the `MATLABDiffEq` namespace. The +SciML common interface that `using MATLABDiffEq` reexports alongside them is listed on the +[API page](api.md). ```@docs MATLABAlgorithm diff --git a/src/MATLABDiffEq.jl b/src/MATLABDiffEq.jl index 5d4edde..1a2ed24 100644 --- a/src/MATLABDiffEq.jl +++ b/src/MATLABDiffEq.jl @@ -10,6 +10,22 @@ import SciMLBase: __solve using SciMLPublic: @public using Symbolics: MATLABTarget, build_function +# The SciML common interface that MATLABDiffEq reexports (see the `export` block below), +# so that `using MATLABDiffEq` on its own is enough to build an ODE problem, solve it, +# and inspect the result -- the workflow the README and docs/src/index.md document. Every +# name stays owned and documented upstream. +using SciMLBase: EnsembleAnalysis, EnsembleDistributed, EnsembleProblem, EnsembleSerial, + EnsembleSolution, EnsembleSplitThreads, EnsembleSummary, EnsembleThreads, + NullParameters, ODEFunction, ODEProblem, ODESolution, ReturnCode, remake, solve, + successful_retcode + +# Reexported SciML common interface; approved via `reexports_allow` in test/qa/qa.jl. +# `DEStats` is imported above. +export DEStats, EnsembleAnalysis, EnsembleDistributed, EnsembleProblem, EnsembleSerial, + EnsembleSolution, EnsembleSplitThreads, EnsembleSummary, EnsembleThreads, + NullParameters, ODEFunction, ODEProblem, ODESolution, ReturnCode, remake, solve, + successful_retcode + # MATLAB only supports Float64 arrays. Check if a type is MATLAB-compatible. # Note: We specifically accept standard Julia integer types that MATLAB can convert, # but NOT BigInt since MATLAB doesn't support arbitrary precision integers. diff --git a/test/qa/qa.jl b/test/qa/qa.jl index 86406ce..920007a 100644 --- a/test/qa/qa.jl +++ b/test/qa/qa.jl @@ -1,3 +1,25 @@ -using MATLABDiffEq, SciMLTesting +using MATLABDiffEq, SciMLTesting, Test -run_qa(MATLABDiffEq) +# The SciML common interface MATLABDiffEq deliberately reexports so that +# `using MATLABDiffEq` is enough to build an ODE problem, solve it, and inspect the +# result. Owned and documented upstream; kept in sync with the reexport `export` block +# in src/MATLABDiffEq.jl and the API page in docs/src/api.md. +const REEXPORTS = ( + :DEStats, :EnsembleAnalysis, :EnsembleDistributed, :EnsembleProblem, :EnsembleSerial, + :EnsembleSolution, :EnsembleSplitThreads, :EnsembleSummary, :EnsembleThreads, + :NullParameters, :ODEFunction, :ODEProblem, :ODESolution, :ReturnCode, :remake, + :solve, :successful_retcode, +) + +run_qa(MATLABDiffEq; reexports_allow = REEXPORTS) + +@testset "Reexport surface" begin + # Every approved reexport must actually be reachable from `using MATLABDiffEq`, so + # the allow-list cannot drift into approving names the package no longer provides. + # `isdefined(@__MODULE__, ...)` tests the property directly: this file's + # `using MATLABDiffEq` is what has to bring the name into scope. + @testset "$name" for name in REEXPORTS + @test name in names(MATLABDiffEq) + @test isdefined(@__MODULE__, name) + end +end