Skip to content

[AI] Curate the public API instead of blanket-reexporting dependencies - #1286

Merged
ChrisRackauckas merged 9 commits into
SciML:masterfrom
ChrisRackauckas-Claude:curate-exports
Aug 2, 2026
Merged

[AI] Curate the public API instead of blanket-reexporting dependencies#1286
ChrisRackauckas merged 9 commits into
SciML:masterfrom
ChrisRackauckas-Claude:curate-exports

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member

Please ignore until reviewed by @ChrisRackauckas. Opened as a draft. Breaking release across all 28 packages.

Independent of #1283 and #1284 — branched from master.

Why

run_qa's reexport audit has failed on master since SciMLTesting#28 enabled check_reexports by default (2026-07-21, SciMLTesting 2.4.0). The obvious fix is reexports_allow, but the check was pointing at something real, so this curates the API instead.

Every package @reexported its dependencies wholesale:

package before after
Optimization 263 public, 0 owned 39
OptimizationBase 262 public, 6 owned 38
OptimizationOptimJL 298 public, 0 owned 36

using OptimizationOptimJL brought in 298 names, not one of them its own: 155 from SciMLBase, 38 ADTypes, 34 from SciMLOperators (AddVector, BlockDiagonalOperator, update_coefficients!), 30 Optim, 16 SciMLLogging, 4 NLSolversBase, 4 CommonSolve. The surface included DynamicalSDEFunction, DDEProblem, EigenvalueProblem. Of the 263 names Optimization exported, the entire monorepo referenced 65.

What changed

  • OptimizationBase@reexport using SciMLBase, ADTypes, SciMLLogging becomes plain using plus explicit export lists: its own types, the problem/solution API (OptimizationProblem, OptimizationFunction, solve, init, solve!, remake, ReturnCode, senses, stats), and the nine Auto* types the repo actually uses.
  • Optimization — re-exports that curated set and nothing else.
  • 27 solver packages — stop passing OptimizationBase through. Every surviving @reexport surfaces the package's own backend solvers, which is the glue package's job. Also caught OptimizationODE reexporting all of SciMLBase and OptimizationNLPModels all of ADTypes.

Users write using Optimization, OptimizationOptimJL, already the documented pattern on every doc page but one (pycma.md, updated).

Five defects the reexports were masking

  1. OptimizationMOI uses ADTypes.AutoSymbolics/AutoSparse in nlp.jl and never declared ADTypes.
  2. OptimizationIpopt — same, in evaluator.jl. Both compiled only because the reexport chain bound the name.
  3. OptimizationCMAEvolutionStrategy imported the SciMLBase module from OptimizationBase (using OptimizationBase: SciMLBase), which ExplicitImports flags as both ExplicitImportsFromNonOwnerException and NonPublicExplicitImportsException.
  4. OptimizationLBFGSB took ReturnCode from OptimizationBase rather than SciMLBase, which owns it.
  5. OptimizationPyCMA had all_qualified_accesses_via_owners marked @test_broken; it passes once accesses go to owners, so the stale marker is removed.

Separately, 17 packages used @SciMLMessage without depending on SciMLLogging and now declare it.

QA stays enabled, not allow-listed away

Nine glue packages allow exactly their backend module; the two umbrella packages list their curated re-surface explicitly; the remaining 18 need no allow-list at all. Adding a reexport is now a deliberate edit to a short list.

Versions

Breaking. Optimization 5.6.5→6.0.0, OptimizationBase 5.2.2→6.0.0, each sublibrary by its SemVer rule (0.x0.(x+1), 1.x2.0.0), with intra-monorepo compat bounds updated across all package, test and docs environments.

Test plan

Run locally on Julia 1.11, full sweep after the final change:

  • Core28/28 pass (root + all 27 sublibraries)
  • QA27/28 pass
  • The one QA failure, OptimizationLBFGSB, is pre-existing: on a clean master checkout it fails 2 checks (JET and reexport); on this branch it fails only the JET one. Verified by stashing this branch and re-running.
  • All 27 modified test files parse-checked (caught 2 that my edit had broken mid-continuation)
  • Every [deps]/[extras]/[weakdeps] entry re-validated as a UUID
  • Runic clean
  • CI green

Follow-ups, deliberately not in this PR

  • MaxSense/MinSense/ObjSense/OptimizationStats are core user-facing API that SciMLBase does not declare public. They are still re-exported here so nothing breaks, but the clean fix is a SciMLBase PR declaring and documenting them.
  • OptimizationLBFGSB's JET failure on master.

🤖 Generated with Claude Code

https://claude.ai/code/session_014Uj4Pu1LKHYqLSTLfPYDPo

ChrisRackauckas and others added 2 commits July 26, 2026 14:44
Every package in the monorepo `@reexport`ed its dependencies wholesale, so
`using Optimization` put 263 names in scope and `using OptimizationOptimJL` put
298 — none of which either package owned. 34 came from SciMLOperators
(`AddVector`, `BlockDiagonalOperator`, `update_coefficients!`), and the surface
included `DynamicalSDEFunction`, `DDEProblem` and `EigenvalueProblem`. Of the
263, the entire repo referenced 65.

OptimizationBase now `using`s SciMLBase/ADTypes/SciMLLogging and states its
public API in explicit `export` lists: its own types plus the problem/solution
API and the nine `Auto*` types a user needs. Optimization re-exports that
curated set and nothing else. The solver packages stop passing OptimizationBase
through and expose only their own solvers, so `using OptimizationOptimJL` now
means `LBFGS`, `BFGS`, `IPNewton` and friends. Users load `Optimization`
alongside the solver package, which is what the docs already showed everywhere
but one page.

    Optimization         263 public, 0 owned  ->  39
    OptimizationBase     262 public, 6 owned  ->  38
    OptimizationOptimJL  298 public, 0 owned  ->  36

Dropping the reexports exposed five defects they had been masking:

- OptimizationMOI and OptimizationIpopt use `ADTypes.AutoSymbolics` and
  `ADTypes.AutoSparse` qualified without declaring ADTypes at all; they
  compiled only because the reexport chain happened to bind the name.
- OptimizationCMAEvolutionStrategy imported the SciMLBase *module* from
  OptimizationBase, and OptimizationLBFGSB took `ReturnCode` from there rather
  than from SciMLBase, which owns it.
- OptimizationPyCMA marked `all_qualified_accesses_via_owners` as broken; it
  passes once accesses go to their owners, so the marker is removed.

17 packages used `@SciMLMessage` without depending on SciMLLogging and now
declare it. `run_qa`'s reexport audit is satisfied without disabling it: the
nine glue packages allow exactly their backend module, the two umbrella
packages list their curated re-surface, and the remaining 18 need no
allow-list because they expose nothing they do not own.

Breaking: Optimization and OptimizationBase 5 -> 6, sublibraries by their
SemVer rule, with intra-monorepo compat bounds updated to match.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Uj4Pu1LKHYqLSTLfPYDPo

@SebastianM-C SebastianM-C left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What should be the use cases when one uses Optimization vs OptimizationBase?

Comment thread docs/src/optimization_packages/pycma.md Outdated

```@example PyCMA
using OptimizationPyCMA
using Optimization, OptimizationPyCMA

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should users have this or OptimizationBase?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I guess OptimizationBase

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in a37c8f4 — the example is now using OptimizationBase, OptimizationPyCMA.

Verified locally on Julia 1.11 (fresh env, both packages deved from this branch): the block runs to ReturnCode.Success with sol.u = [0.8, 0.64], sol.objective = 0.04.

For the record, my PR description was wrong to call Optimization "the documented pattern on every doc page but one" — getting_started.md, lbfgsb.md, prima.md, sophia.md and the first ipopt.md example already use OptimizationBase. The docs are split roughly 30 Optimization / 6 OptimizationBase, with the front door (getting_started.md) on OptimizationBase. Harmonizing the remaining pages is a separate PR if you want it — say the word.

ChrisRackauckas and others added 3 commits August 1, 2026 17:26
Matches getting_started.md and the LBFGSB/PRIMA/Sophia pages. Addresses
review feedback on the pycma.md hunk.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
The docs were split between `using Optimization` and `using OptimizationBase`,
with getting_started.md, lbfgsb.md, prima.md and sophia.md already on the
latter. Settle on OptimizationBase everywhere, per review feedback on the
pycma.md hunk.

names(Optimization) is names(OptimizationBase) plus the symbol :Optimization,
and solve/init/solve!/reinit!/AutoForwardDiff/OptimizationProblem resolve to
identical bindings in both, so this is behaviour-preserving.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
`@reexport using SciMLBase` exported names(SciMLBase), which always contains
the symbol SciMLBase itself, so the module binding landed in every namespace
that did `using Optimization`. Curating the exports removes it, and the docs
were depending on it in two places:

  - 21 `SciMLBase.OptimizationProblem`/`OptimizationFunction` calls, 18 of them
    in executed @example blocks, which would throw UndefVarError and fail the
    build (@example errors are not covered by the existing warnonly list).
    OptimizationBase exports both names, so drop the qualification.
  - 5 @docs entries under API/ naming SciMLBase types. No page sets
    CurrentModule, so Documenter resolves them in Main; make.jl now binds
    SciMLBase explicitly.

rosenbrock.md, linearandinteger.md and ode.md `using SciMLBase` for real and
are left as they are.

Verified on Julia 1.11: master binds SciMLBase and ADTypes for all four
entry-package x solver combinations tested, this branch binds neither.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member Author

Followed up on the pycma thread by harmonizing the rest of the docs, and that turned up a build-breaking regression in this PR.

1. Docs entry package (2ac8c22)

The docs were split, not uniform — my PR description was wrong to say Optimization was the pattern on every page but one. getting_started.md, lbfgsb.md, prima.md, sophia.md and the first ipopt.md example were already on OptimizationBase. Everything in docs/src is now OptimizationBase: 54 lines, 22 files. Prose ("Optimization.jl"), README.md and docs/make.jl untouched.

Behaviour-preserving, verified on Julia 1.11:

names(Optimization)     = 39
names(OptimizationBase) = 38
EXPORTED ONLY BY Optimization:     [:Optimization]
EXPORTED ONLY BY OptimizationBase: Symbol[]

and solve, init, solve!, reinit!, AutoForwardDiff, OptimizationProblem are === identical through both.

2. This PR breaks the docs build (6788bd3)

@reexport using SciMLBase exports names(SciMLBase), and names(M) always contains M itself — so the module binding SciMLBase was landing in every using Optimization namespace. Curating the exports removes it. Measured on Julia 1.11:

SciMLBase bound ADTypes bound
master true (all 4 entry x solver combos) true
curate-exports false false

Two things depended on it:

  • 21 calls to SciMLBase.OptimizationProblem/OptimizationFunctionoptim.md (7), nlopt.md (4), gcmaes.md (2), multistartoptimization.md (2), plus blackboxoptim, cmaevolutionstrategy, evolutionary, mathoptinterface, metaheuristics, quaddirect. 18 are in executed @example blocks, which throw UndefVarError: SciMLBase and fail the build — @example errors are not in the existing warnonly = [:missing_docs, :cross_references]. Fixed by dropping the qualification, since OptimizationBase exports both names.
  • 5 @docs entries under API/ naming SciMLBase types. No page sets CurrentModule, so Documenter resolves them in Main, and make.jl never did using SciMLBase. Now it does.

rosenbrock.md, linearandinteger.md and ode.md using SciMLBase for real and were left alone.

This slipped through because the test plan covers Core and QA; the docs build is in neither.

Doc blocks re-run verbatim after the fix, on Julia 1.11: nlopt.md NLopt1, optim.md Optim2, reusage_interface.md, polyopt.md all reach ReturnCode.Success. simpleoptimization.md returns MaxIters without throwing, so the build is unaffected — and it is bit-identical under Optimization and OptimizationBase (u = [-0.6215719347368793, 0.3907751561233206] both ways), so it is pre-existing non-convergence, not something this changes. Runic clean.

Still open

  • The branch is conflicting with master (mergeStateStatus: DIRTY) — Sync package versions with the General registry #1287 and Correct OptimizationBase to the next sequential release #1290 landed after the merge base and collide with the version bumps in all 24 Project.toml files plus the root. Not resolved here: each conflict is a semver decision that has to be re-derived from the registry-synced baseline.
  • After this, no doc example uses Optimization at all, while index.md still tells users to install it and says the top-level package "does not add any extra behavior". That probably wants a sentence saying which of the two to install. Not written — say the word.
  • README.md still has using Optimization. Left deliberately: it is spliced in as the module docstring via DocStringExtensions.README.

ChrisRackauckas and others added 4 commits August 1, 2026 20:45
Version conflicts in all 24 Project.toml files: master's SciML#1287/SciML#1290 synced
versions to the General registry with patch bumps, this branch makes breaking
bumps. A major bump subsumes a patch bump, so the branch versions are already
the correct next breaking release off master's (e.g. BBO 0.4.10 -> 0.5.0, root
5.6.6 -> 6.0.0); no versions are skipped. Verified master touched only the
`version =` line in each conflicted file.

Master's new second_order_warning_test.jl does `SciMLBase.requireshessian(...)`,
which only resolved through the reexport this branch removes. It now says
`using SciMLBase` explicitly; SciMLBase is already a dep of OptimizationBase.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
Every example now loads OptimizationBase, so README and the index install
instructions pointed at a package the docs never use. Both now lead with
OptimizationBase, and index.md gains a short section on the relationship.

`Optimization` re-exports OptimizationBase and adds nothing else — it pulls in
Logging/ConsoleProgressMonitor/TerminalLoggers/LoggingExtras but never
references them — so the new text says the two are interchangeable rather than
inventing a distinction between them.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
Removing every `@reexport` from OptimizationBase left Reexport in [deps] and
[compat] with no user, which fails Aqua's stale-dependency check. The
reexport check itself already passed; this is the leftover.

QA on OptimizationBase: 19 pass / 1 fail / 1 broken before, 20 pass / 1 broken
after. The remaining broken is `ei_broken = (:no_implicit_imports,)`, which is
already on master.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
`using SciMLBase: OptimizationStats, OptimizationFunction` predates this
branch, but on master `@reexport using OptimizationBase` re-exported every
name in names(OptimizationBase), OptimizationFunction among them — and
exporting a name counts as using it, so the import was not stale. Removing
the reexport orphans it, and ExplicitImports' no_stale_explicit_imports
errors. The name is used nowhere else in the package.

QA on OptimizationLBFGSB, Julia 1.11:
  master: 18 pass, 2 fail (JET + reexports), 1 broken
  before: 19 pass, 1 fail (JET), 1 error (stale import), 1 broken
  after:  19 pass, 1 fail (JET), 1 broken

The remaining JET failure is byte-identical to master's (same 9 toplevel
errors, from the LBFGSB struct colliding with the LBFGSB package binding)
and is already tracked as a follow-up.

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A9dFnCQK2GVSNTCJP6ZMG2
@ChrisRackauckas-Claude

Copy link
Copy Markdown
Member Author

Rebase + follow-up changes pushed. The branch is MERGEABLE again (was CONFLICTING).

Merge (f6be113)

Done as a merge rather than a rebase: this branch already contains a merge commit and has review comments anchored to it, so a rebase would mean force-pushing over a branch under review. Say the word if you want the literal rebase.

All 24 Project.toml conflicts were version-only — verified master touched nothing but the version = line in each. #1287/#1290 made patch bumps to sync with the registry; a major bump subsumes a patch bump, so the branch versions are already the correct next breaking release off master's and nothing is skipped:

master merged
Optimization 5.6.6 6.0.0
OptimizationBase 5.2.4 6.0.0
OptimizationBBO 0.4.10 0.5.0
OptimizationOptimJL 0.4.17 0.5.0

Three more things this branch broke

Running the suite rather than trusting the test plan turned up three defects, all the same root cause — deleting the reexports orphaned things that were only alive through them.

1. Master's new second_order_warning_test.jl does SciMLBase.requireshessian(...); its includer only does using OptimizationBase. Confirmed it throws UndefVarError: SciMLBase without a fix. Now says using SciMLBase explicitly.

2. OptimizationBase had a stale Reexport dep (96bd3a8) — every @reexport was removed, the dependency stayed. Aqua's stale-dependency check fails on it. QA: 19 pass/1 fail/1 broken → 20 pass/1 broken, exit 0.

3. OptimizationLBFGSB (5ec6b71). The PR description says its QA failure is pre-existing and that this branch "fails only the JET one". Both halves are wrong. On master, @reexport using OptimizationBase exported every name in names(OptimizationBase), OptimizationFunction among them — and exporting a name counts as using it, so using SciMLBase: OptimizationStats, OptimizationFunction was not stale. Remove the reexport and it is:

master branch before branch after
ExplicitImports 5 pass, 1 broken 4 pass, 1 error, 1 broken 5 pass, 1 broken
No unapproved public reexports 1 fail 1 pass 1 pass
total 18 pass, 2 fail, 1 broken 19 pass, 1 fail, 1 error, 1 broken 19 pass, 1 fail, 1 broken

The one remaining fail is JET, byte-identical to master's (same 9 toplevel errors, from the LBFGSB struct colliding with the LBFGSB package binding).

QA sweep, all 28 packages

23/28 pass. Of the 5 failures, 4 (MOI, NLPModels, ODE, SimpleOptimization) are a local environment problem — BracketingNonlinearSolve depends on ChainRulesCore, but no such entry exists in the manifest — which reproduces identically on a clean master worktree, so it is not attributable to this PR. The 5th was LBFGSB, now fixed. So the PR body's "QA — 27/28 pass" was wrong in both directions and should be restated once CI gives a clean-environment number.

Docs

README.md and the index.md install section led users to Optimization while every example now loads OptimizationBase; both now lead with OptimizationBase, and index.md gains a short section on the relationship. Optimization re-exports OptimizationBase and adds nothing else — it pulls in Logging/ConsoleProgressMonitor/TerminalLoggers/LoggingExtras and never references them — so the text says the two are interchangeable rather than inventing a distinction. Those four unused deps are worth dropping separately.

Not done

17 packages still using Reexport with no @reexport left in them — Auglag, BBO, CMAEvolutionStrategy, GCMAES, Ipopt, LBFGSB, MadNLP, MOI, NOMAD, ODE, Polyalgorithms, PyCMA, QuadDIRECT, SciPy, Sophia, SpeedMapping, SimpleOptimization. They use plain export. Aqua does not flag it because using Reexport counts as loading the package, so nothing fails — it is dead code, not a broken check. Cleaning it means removing the using plus the dep/compat entries together in each; happy to do it here or as a follow-up. This also contradicts the description's "every surviving @reexport surfaces the package's own backend solvers" — there are no surviving @reexports in those 17.

Verified locally on Julia 1.11: OptimizationBase Core 54/54 · OptimizationIpopt Core 127 pass/1 broken · root Optimization Core pass · OptimizationBase QA 20 pass/1 broken · OptimizationLBFGSB QA 19 pass/1 broken · README example runs and matches its own documented objective. Every @test_broken encountered traces back to master; this branch adds none. Runic clean.

@ChrisRackauckas
ChrisRackauckas marked this pull request as ready for review August 2, 2026 05:05
@ChrisRackauckas
ChrisRackauckas merged commit 7f25ce1 into SciML:master Aug 2, 2026
145 of 157 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants