Skip to content

The great restructuring, episode 15: the monorepo strikes back, single SciML version #1082

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
ChrisRackauckas opened this issue Apr 2, 2025 · 44 comments

Comments

@ChrisRackauckas
Copy link
Member

ChrisRackauckas commented Apr 2, 2025

As SciML has grown, so has its structural needs. Back in 2017, we realized that DifferentialEquations.jl was too large and so it needed to be split into component packages for OrdinaryDiffEq, StochasticDiffEq, etc. in order to allow for loading only a part of the system as DiffEq itself was too big. In 2021, we expanded SciML to have LinearSolve, NonlinearSolve, etc. as separate interfaces so they can be independently used, and as such independent packages. In 2024 we split many of the solver packages into independent packages like OrdinaryDiffEqTsit5, in order to accomodate users who only wanted lean dependencies to simple solvers. And now we have hundreds of packages across hundreds of repos, though many of the subpackages are in the same repo.

If we had started this system from scratch again, I don't think we'd have so many repos. With the subrepo infrastructure of modern Julia, keeping OrdinaryDiffEq and StochasticDiffEq together would have been nice. In fact, DelayDiffEq.jl touches many of the OrdinaryDiffEqCore internals, so it should be versioned together with OrdinaryDiffEq. This leads to issues like JuliaLang/julia#55516 being asked of the compiler team.

The core issue here is that the module structure does not necessarily reflect the public/private boundaries, but rather the module system is designed for separate loading, separate compilation, conditional dependencies, and ultimately handling startup/latency issues. This means that it really does not make sense for OrdinaryDiffEqTsit5 and OrdinaryDiffEqCore to have different versions, since they really should be handled in lock-step, and any versioning that isn't matching is always suspect. This leads to odd issues with bumping, and nasty resolution of manifests. The issue is that the semver system is designed around package boundaries being built around public interfaces with announcing public breakage, but this simply does not work out when the common breakage is non-public internals simply because the package boundary is at some internal function, again not because it's a generally good idea for the packages but instead because it's a requirement to achieve lower startup times.

So what can we do? The proposal is not so simple, but it can work. Instead of waiting on a fix for JuliaLang/julia#55516, we can do some clever tricks on the tools that we now have in Julia 2025 land in order to pull this off. It would look like this:

  • We can have a single basic repository that contains all of the core code. For example, OrdinaryDiffEq contains what is now the OrdinaryDiffEqCore code
  • We then make all of the add on code into extensions. For example, OrdinaryDiffEqTsit5 would effectively be an empty package, that only exists to have a Project.toml for declaring new dependencies, and an OrdinaryDiffEqOrdinaryDiffEqTsit5 extension to OrdinaryDiffEq would be created to trigger on using OrdinaryDiffEqTsit5 that actually loads the extension code.
  • Importantly, this means that the code lives in OrdinaryDiffEq.jl, and thus the solver code only has one version, the OrdinaryDiffEq version. All solver code is then single versioned and updated in lock step. The only reason to bump the version on the pseudo packages is to bump dependency versions. Now because the code does not actually live in OrdinaryDiffEqTsit5, the only way to do a code update is to first make a version of OrdinaryDiffEqTsit5 that is a major bump with the new dependency versions, and then update OrdinaryDiffEq.jl to allow the new OrdinaryDiffEqTsit5 major. This means that every release of the psuedo packages is a considered a breaking release, but the only times they need a release are for compat bumps.

With this, the user code would look like:

using OrdinaryDiffEq, OrdinaryDiffEqTsit5
function lorenz!(du, u, p, t)
    du[1] = 10.0(u[2] - u[1])
    du[2] = u[1] * (28.0 - u[3]) - u[2]
    du[3] = u[1] * u[2] - (8 / 3) * u[3]
end
u0 = [1.0; 0.0; 0.0]
tspan = (0.0, 100.0)
prob = ODEProblem(lorenz!, u0, tspan)
sol = solve(prob, Tsit5())
using Plots;
plot(sol, idxs = (1, 2, 3))

Note that using OrdinaryDiffEqTsit5 would be a requirement, as OrdinaryDiffEq would no longer trigger any solvers. If this isn't nice, we could have the single version package be OrdinaryDiffEqCore, with a higher level OrdinaryDiffEq that just uses a few solvers.

Small Questions

  1. The issue "Note that using OrdinaryDiffEqTsit5 would be a requirement, as OrdinaryDiffEq would no longer trigger any solvers. If this isn't nice, we could have the single version package be OrdinaryDiffEqCore, with a higher level OrdinaryDiffEq that just uses a few solvers. " - Is that to janky?
  2. Any potential issues not considered?

Big Question

If we go to monorepo, what's in and what's out? The advantage of having more things in a single repo is clear. Most of the issues around downstream testing, keeping package versions together, etc. are all gone. You know your code is good to go if it passes tests in this repo because that would have "everything".

However, what is everything? Should we have the following:

  • DifferentialEquations
  • NonlinearSolve
  • LinearSolve
  • ...

Or if we're going to do this, should we just have a single SciML?

using SciML, OrdinaryDiffEqTsit5
function lorenz!(du, u, p, t)
    du[1] = 10.0(u[2] - u[1])
    du[2] = u[1] * (28.0 - u[3]) - u[2]
    du[3] = u[1] * u[2] - (8 / 3) * u[3]
end
u0 = [1.0; 0.0; 0.0]
tspan = (0.0, 100.0)
prob = ODEProblem(lorenz!, u0, tspan)
sol = solve(prob, Tsit5())
using Plots;
plot(sol, idxs = (1, 2, 3))

Then you can just ask, "what version is your SciML?"

I think that would actually be really nice because splitting the interface between SciMLOperators, SciMLBase, DiffEqBase, NonlinearSolveBase, OptimizationBase, OrdinaryDiffEqCore, etc. are also somewhat arbitrary distinctions and moving code at the interface level there has always been a multi-repo mess due to the aritificiality of the split and release process w.r.t. semver at this level. Additionally, SciMLSensitivity is a package adding sensitivity analysis to all solvers, even NonlinearSolve.jl, so it would neatly fit as an extension to all of the packages. If that's the case, is the core package here just SciMLBase, and everything else is an extension to SciMLBase?

That said, how far do we go? Is ModelingToolkit in there? Is Catalyst in there? Symbolics.jl and SymbolicUtils.jl? Do we then restructure the whole SciML documentation as a single Vitepress doc? Or build many different Documenter docs from one repo?

Some CI questions

  1. Testing the subpackages: do we put the tests all in the main repo, or associate them with the psuedo packages? That way users could trigger subsets of tests?
  2. Could the tests know/understand the dependency graph, so say a change to SciMLBase itself triggers all tests, while changing something in OrdinaryDiffEqCore only triggers the OrdinaryDiffEq tests and other downstream of that? That would be pretty essential for managing the growth of the CI budget, since more tests is more costs.
  3. Are there downsides with precompilation, download sizes, and system image building to consider?
  4. How will the VS Code Language Server feel about this?

Conclusion

This is going to be a lot of work, so I'm looking for comments before commencing.

@devmotion @oscardssmith @isaacsas @TorkelE @thazhemadam @asinghvi17

@KristofferC
Copy link

KristofferC commented Apr 2, 2025

A drawback to extensions is that you cannot really access symbols defined in them. So they have to work pretty much purely by method extensions. Does all the code that is desired to be put into extensions do that here?

In the code given as:

With this, the user code would look like:
sol = solve(prob, Tsit5())

does the Tsit5 symbol come from OrdinaryDiffEqTsit5, which is then dispatched on in the extension? What about solvers that need more complicated constructors, you then start to need to put more code into the packages that are supposed to be empty, which works against the idea of having all code in the base package.

Note that using OrdinaryDiffEqTsit5 would be a requirement, as OrdinaryDiffEq would no longer trigger any solvers.

I think the requirement is so that you get access to the Tsit5 symbol, no?

@ranocha
Copy link
Member

ranocha commented Apr 2, 2025

What about packages that extend SciML packages but are not part of SciML? We cannot depend on package extensions, can we?

Moreover, I would have expected Tsit5() to be defined in OrdinaryDiffeqTsit5.jl. What if we change the algorithm interface? This will still require synchronizing versions across packages.
Edit: Just saw that a related comment was posted above.

@TorkelE
Copy link
Member

TorkelE commented Apr 2, 2025

Keeping a look at this, but given the magnitude of things I will have to think through things before I can contribute any useful opinion.

From experience, working with extensions is a proper pain. E.g. If I have function f in CatalystBifurcationKitExt, I cannot do CatalystBifurcationKitExt.f or Catalyst.f or something to reach it. I do agree that coupling versions would make things a lot easier though, and maybe given the niche use case of extensions it can work out.

I guess we have no idea when a solution to JuliaLang/julia#55516 could drop?

@hodgensc
Copy link

hodgensc commented Apr 2, 2025

regarding "Small question" #1: Say I was still in the troubleshooting phase of a project and getting my infrastructure in place and I'm trying out different solvers to see which is most appropriate to use. Would the workflow in this suggestion be to 1) test with OrdinaryDiffEqCore because it has a bunch of the solvers packaged in, then 2) when I figure out which solver I need I would switch to OrdinaryDiffEq and import only that solver?

@ChrisRackauckas
Copy link
Member Author

does the Tsit5 symbol come from OrdinaryDiffEqTsit5, which is then dispatched on in the extension? What about solvers that need more complicated constructors, you then start to need to put more code into the packages that are supposed to be empty, which works against the idea of having all code in the base package.

Well there's two different choices that could be made here.

  1. It's still defined in the core package, in which case we'd need to manually guard against solve(prob, Tsit5()) not having loaded the extension. The downside here is that these manual load checks will be nasty to get right and have to be manually maintained (it's somewhat what's already in place in LinearSolve.jl)
  2. It's defined in the extension, in which case yes it's not just an empty piece of code. The nice thing here is that would handle some dependency issues, since yes some algorithms may have a default argument that depends on a dependency (like threads = Threads.PolyesterThreads() or something of the sort), and so it needs to be in the subpackage in order to not take the dependency.

I would be inclined to go with 2 for a mostly different reason than those, which is that we know large files and just loading large amounts of code is one of the biggest latency issues right now, and that's not necessarily going to get any smaller. So having a giant file of algorithm struct constructors would be something that would add up in terms of load time. IIRC the OrdinaryDiffEq algorithms page was like 0.15 seconds itself, so all solvers together could be a floor of like 0.4 seconds to loading (in its current implementation) just from the algorithm structs. So, putting those into the package shims could be the right thing to do not just for handling dependencies but also to make the loading floor lower.

@MasonProtter
Copy link

MasonProtter commented Apr 2, 2025

I think I'm generally in favour of this. One thing I do want to mention though is that on this issue:

Small Questions

  1. The issue "Note that using OrdinaryDiffEqTsit5 would be a requirement, as OrdinaryDiffEq would no longer trigger any solvers. If this isn't nice, we could have the single version package be OrdinaryDiffEqCore, with a higher level OrdinaryDiffEq that just uses a few solvers. " - Is that to janky?

Why not keep the same behaviour we currently have? using OrdinaryDiffEqCore loads no solvers, and using OrdinaryDiffEq loads the (ordinary) kitchen sink.


Beyond that, I want to flag the fact that there's still currently a lot of UX issues with extensions, e.g. various things are broken with them in 1.10 (crytic errors about being unable to merge manifests when running test suites for example), and most users don't know how to actually access an extension module (i.e. Base.get_extension is kinda wonky to use).

I don't think any of that are reasons to not do it, but they are maybe reasons to be cautious about this.

I would suggest perhaps starting with a smaller set of consolidations. For instance, we could turn OrdinaryDiffEq back into a single-version monorepo using the techniques described above, and see how that goes. Or we could try this as a project to separate out the solvers in e.g. StochasticDiffEq.

Doing this on a smaller scale might help us find sharp edges and work out solutions to them before we go all in and consoldiate everything into one mega-repo.

@ChrisRackauckas
Copy link
Member Author

ChrisRackauckas commented Apr 2, 2025

I guess we have no idea when a solution to JuliaLang/julia#55516 could drop?

I was told today that it's unlikely to happen in v1.13, and so probably not within the next ~2 years. I don't think we can keep this version hell going on for that long without having a riot, so given that's not happening with new Base Julia tooling, we need to figure out a solution with duck tape and bubble gum.

That said, one non extension way to do this that was proposed is a purely CI solution. What @oscardssmith had sent me last night is that could do this through a CI-based lock step versioning system. Quoting:

  1. New compat bot that when triggered makes a commit that bumps the versions of all packages in a repo and and bumps all compats so that the compats are in lockstep.
  2. JuliaRegister bot support for bulk registration. (likey @JuliaRegistrator register_all() or something similar)
  3. Tagbot and Dependabot support for multiple projects in a repo

This approach has a few advantages over introducing subpackages:

  1. We can use this ~now rather than in ~2 years when we are comfortable lower bounding on 1.13
  2. This isn't a Pkg/language change so if it doesn't work out, we haven't committed to a new feature

@JuliaRegistrator
Copy link

Error while trying to register: Action not recognized: register_all

@ChrisRackauckas
Copy link
Member Author

Go away registrator, not now. Read the room.

@JuliaRegistrator
Copy link

Error while trying to register: Action not recognized: register_all

@ChrisRackauckas
Copy link
Member Author

I would suggest perhaps starting with a smaller set of consolidations. For instance, we could turn OrdinaryDiffEq back into a single-version monorepo using the techniques described above, and see how that goes. Or we could try this as a project to separate out the solvers in e.g. StochasticDiffEq.

Doing this on a smaller scale might help us find sharp edges and work out solutions to them before we go all in and consoldiate everything into one mega-repo.

That's definitely the plan. I think we'd do just the DiffEq stuff first and see how that goes. But I'm curious then for the next step if people would prefer NonlinearSolve separate or together with it. At least I know I want all of DiffEq together, since DelayDiffEq makes no sense in a different repo and that has been an issue for at least 7 years at this point.

@ChrisRackauckas
Copy link
Member Author

#1: Say I was still in the troubleshooting phase of a project and getting my infrastructure in place and I'm trying out different solvers to see which is most appropriate to use. Would the workflow in this suggestion be to 1) test with OrdinaryDiffEqCore because it has a bunch of the solvers packaged in, then 2) when I figure out which solver I need I would switch to OrdinaryDiffEq and import only that solver?

Somewhat other way around. using OrdinaryDiffEq as a top level would give you all of the solvers. You REPL play with that. And then when you realize you just want Tsit5, you do using OrdinaryDiffEqCore, OrdinaryDiffEqTsit5 in the package and now it just has the one dependency.

@hodgensc
Copy link

hodgensc commented Apr 2, 2025

#1: Say I was still in the troubleshooting phase of a project and getting my infrastructure in place and I'm trying out different solvers to see which is most appropriate to use. Would the workflow in this suggestion be to 1) test with OrdinaryDiffEqCore because it has a bunch of the solvers packaged in, then 2) when I figure out which solver I need I would switch to OrdinaryDiffEq and import only that solver?

Somewhat other way around. using OrdinaryDiffEq as a top level would give you all of the solvers. You REPL play with that. And then when you realize you just want Tsit5, you do using OrdinaryDiffEqCore, OrdinaryDiffEqTsit5 in the package and now it just has the one dependency.

Ah! Okay, that makes sense and seems manageable for someone like me. Thanks.

@ChrisRackauckas
Copy link
Member Author

What about packages that extend SciML packages but are not part of SciML? We cannot depend on package extensions, can we?

Yes, those couldn't be extensions. They would just have to be separate packages as always, with the same warts as before. But normally external packages should just be relying on public interfaces. Really the big issues are for example the solver packages that rely on the exact implementation of ODEIntegrator and such.

Though https://github.com/NumericalMathematics/PositiveIntegrators.jl is this kind of edge case that's using OrdinaryDiffEqCore internals... I'd prefer to just get that into the system at that point as its hard to ever make it fully robust if separate. We could at least keep the same downstream test infra for this kind of thing.

@gdalle
Copy link

gdalle commented Apr 2, 2025

One thing I want to draw attention to with a monorepo approach is testing. I have similar problems with DI, where you could argue that I run 14 downstream tests every time I bat an eyelid. Now imagine if every docstring modification triggered a full run of every single SciML test ever written. This would be nightmarish for user experience and energy consumption alike.
If we go down that road, we'll need a clever way to be selective on the test suites that run. It could probably be done through a mixture of PR labels and automatic GitHub Actions magic, for instance to detect which files have been modified and decide which subpackages should be tested as a result. But even for DI alone it's far from easy to set up.

As a side note, if we're discussing custody of the kids, I'd like to bring ADTypes into the DI repo, for exactly the same lockstep versioning reasons. These days, it is DI which defines the semantics of ADTypes, so it makes no sense that we're able to add something to ADTypes without having it immediately implemented in DI.

@MasonProtter
Copy link

One other thought: I think only reasonably mature and stable packages should be included in this monorepo approach. For example, in Neuroblox.jl we have a hard upper bound on ModelingToolkit.jl that we only increment when we've vetted that new versions don't break our stuff. This is partially our fault for using some MTK internals, but also partially the 'fault' of MTK, where it can be very hard for people working on that library to fully know and understand all the possible negative downstream affects of their changes.

I'd be a little nervous that an approach like this would mean that I'd have to hold back and not update stuff from the entire SciML ecosystem just because I want to protect myself from MTK breakage. On the other hand, maybe that's a good thing, and I can definitely imagine that I'll run into problems caused by having an old MTK and a new version of other stuff.

@ChrisRackauckas
Copy link
Member Author

Beyond that, I want to flag the fact that there's still currently a lot of UX issues with extensions, e.g. various things are broken with them in 1.10 (crytic errors about being unable to merge manifests when running test suites for example), and most users don't know how to actually access an extension module (i.e. Base.get_extension is kinda wonky to use).

I don't disagree, which is why this is a bit scary.

@KristofferC
Copy link

KristofferC commented Apr 2, 2025

It's still defined in the core package, in which case we'd need to manually guard against solve(prob, Tsit5()) not having loaded the extension.

You kind of run into the problem described here, then https://youtu.be/TiIZlQhFzyk?t=1106. Basically, solve(prob, Tsit5()) will work even if a transitive dependency has loaded using OrdinaryDiffEqTsit5. For that to not be the case you kind of need to "prove" that you have a direct dependency on OrdinaryDiffEqTsit5 somehow, either by providing the module or some other "token" provided by that package that can be verified in the extension.

@MasonProtter
Copy link

It's still defined in the core package, in which case we'd need to manually guard against solve(prob, Tsit5()) not having loaded the extension.

You kind of run into the problem described here, then https://youtu.be/TiIZlQhFzyk?t=1106. Basically, solve(prob, Tsit5()) will work even if a transitive dependency has loaded using OrdinaryDiffEqTsit5. For that to not be the case you kind of need to "prove" that you have a direct dependency on OrdinaryDiffEqTsit5 somehow, either by providing the module or some other "token" provided by that package.

I don't think that'd be a bad thing. The only reason we don't want solve(prob, Tsit5()) to work without loading the extension is that we don't want to pay the cost of loading the solver code.

If someone already paid the cost, there's not really any reason to make it error.

@asinghvi17
Copy link
Contributor

asinghvi17 commented Apr 2, 2025

  1. It's defined in the extension, in which case yes it's not just an empty piece of code. The nice thing here is that would handle some dependency issues, since yes some algorithms may have a default argument that depends on a dependency (like threads = Threads.PolyesterThreads() or something of the sort), and so it needs to be in the subpackage in order to not take the dependency.

So is this the structure you are thinking of approximately?

# module OrdinaryDiffEqCore.jl
abstract type Solver end
function solve(prob, alg::Solver)
    throw(BackendNotLoadedError(alg))
end
# module OrdinaryDiffEqTsit5.jl
struct Tsit5 <: Solver
...
end
# module OrdinaryDiffEqCore/OrdinaryDiffEqTsit5Ext.jl
function OrdinaryDiffEqCore.solve(prob, alg::Tsit5)
...
end

If so I don't really see the value of adopting this structure when we could have a bot / Registrator based solution that will bump all versions of packages (regardless of whether they have changed or not) in lockstep with the OrdinaryDiffEq / SciML version.

One issue here, no matter which path we go down, is that a release of SciML + all constituent packages will be required even for a minor bugfix, which could create a lot of noise. We could potentially maintain per-package or per-folder changelogs, which would decrease the noise you have to wade through in a single file, and have a summary in SciML.jl's main changelog. But in general there would be a lot of "irrelevant" releases. You could version each package separately but that doesn't help the release situation for SciML.jl in general.

@ChrisRackauckas
Copy link
Member Author

ChrisRackauckas commented Apr 2, 2025

As a side note, if we're discussing custody of the kids, I'd like to bring ADTypes into the DI repo, for exactly the same lockstep versioning reasons. These days, it is DI which defines the semantics of ADTypes, so it makes no sense that we're able to add something to ADTypes without having it immediately implemented in DI.

Sure, but SciML gets to keep the dog.

But yes since MTK, OrdinaryDiffEq, NonlinearSolve, etc. has now moved to DI, ADTypes is no longer as much of a common interface of DI and SciML, since it's now really just SciML. DI should probably pull it in and downstream test SciMLSensitivity on its changes. We should follow up on that separately. Integrals.jl is I think the last straggler.

@wnoise
Copy link

wnoise commented Apr 2, 2025

Unfortunately I don't know how to write my thoughts down without some duplication of what's already been written here and at JuliaLang/julia#55516 .

The fundamental benefit of monorepos is that they linearize changes. This lets the developers not have to think about compatibility when developing. For monorepos with multiple packages (which we want for many reasons), this is also their fundamental problem: from the perspective of users and julia tooling these packages can skew to versions which don't work with each other.

Keeping everything in lockstep -- (e.g. forcing all versions to be the same and setting the compat bounds to that version) is a solution, and the one proposed here, right?

It does have downsides though: bumping anything bumps everything, with all the follow on from that: duplicated downloads, precompilation, etc. It also linearizes the entire ecosystem around SciML. Consider the cases where libraries build on disparate sciml packages, and a user wants to use two of these libraries that are stuck at different versions of SciML that nonetheless would have worked together. Letting versions float from each other is actually useful!

One approach I have not seen mentioned that would make this easier is to formalize even internal interfaces, i.e. versioning them with nearly empty packages that act like tags. (Like, but not completely the same as the common use of ...Core packages.) But this is of course much more error-prone than the lockstep approach.

@ChrisRackauckas
Copy link
Member Author

One issue here, no matter which path we go down, is that a release of SciML + all constituent packages will be required even for a minor bugfix, which could create a lot of noise. We could potentially maintain per-package or per-folder changelogs, which would decrease the noise you have to wade through in a single file, and have a summary in SciML.jl's main changelog. But in general there would be a lot of "irrelevant" releases. You could version each package separately but that doesn't help the release situation for SciML.jl in general.

Yes, the noise would be... substantial. Stefan said he was calculating statistics of the General registry and noticed that I personally averaged around 2-4 package releases per day. SciML has 200 packages now. So if we did a single SciML.jl with lockstep CI releasing per this suggestion, I personally would open 400-800 general registry PRs every single day. 🤷

But it does seem like it's coming ahead as the leading solution.

@KristofferC
Copy link

KristofferC commented Apr 2, 2025

If someone already paid the cost, there's not really any reason to make it error.

Yes there is because you don't control the set of transitive dependencies that end up getting loaded. You don't want your code to break because some random package in your dependency graph restructured and maybe started using a different diffew solver. By structuring it like that you make users vulnerable to code breakage without an associated breaking change.

@Datseris
Copy link

Datseris commented Apr 2, 2025

Do we then restructure the whole SciML documentation as a single Vitepress doc? Or build many different Documenter docs from one repo?

I don't think that's helpful for beginners. Someone that starts with Julia and wants to learn how to solve an ODE numerically has no reason to first go through the SciML castle until they trickle down to the OrdinaryDiffEq pillar. Separate docs are more accessible in my opinion. The current system does a good job of supporting this by having separate docs that are connected by the MultiDocumenter.jl top-page-header.


I have to say that I read this conversation but it is not transparent to me what is the currently favored approach. Is it the approach proposed in the very first comment, with empty extension packages?


  1. The issue "Note that using OrdinaryDiffEqTsit5 would be a requirement, as OrdinaryDiffEq would no longer trigger any solvers. If this isn't nice, we could have the single version package be OrdinaryDiffEqCore, with a higher level OrdinaryDiffEq that just uses a few solvers. " - Is that to janky?

This doesn't sound like an issue at all to me actually. It just warrants a major version bump. As a user I have already removed entirely all using OrdinaryDiffEq from my code: there is never a project where I would need all solvers. I use directly the Default or Verner packages.

@j-fu
Copy link

j-fu commented Apr 2, 2025

If we go to monorepo, what's in and what's out? The advantage of having more things in a single repo is > clear. Most of the issues around downstream testing, keeping package versions together, etc. are all > > > gone. You know your code is good to go if it passes tests in this repo because that would have
"everything".

However, what is everything? Should we have the following:

DifferentialEquations
NonlinearSolve
LinearSolve
...
Or if we're going to do this, should we just have a single SciML?

If everything would be in one big SciML, wouldn't it be harder for people "from the sidelines" to contribute ? A docstring update in LinearSolve triggering a new version of "everything" might scare people away. In some sense this would be opposite of the trend in Julia core - moving out SparseArrays etc into separate repos in order to be able to develop these independent from the core. I guess we need to have a good balance here, and the boundaries could be just as stated - defined by the different subfields of numerical methods as ODE solvers, linear solvers, nonlinear solvers. And it would be sufficient to be competent in just one of those in order to be not scared to contribute.

@JordiBolibar
Copy link

The fundamental benefit of monorepos is that they linearize changes. This lets the developers not have to think about compatibility when developing. For monorepos with multiple packages (which we want for many reasons), this is also their fundamental problem: from the perspective of users and julia tooling these packages can skew to versions which don't work with each other.

Keeping everything in lockstep -- (e.g. forcing all versions to be the same and setting the compat bounds to that version) is a solution, and the one proposed here, right?

It does have downsides though: bumping anything bumps everything, with all the follow on from that: duplicated downloads, precompilation, etc. It also linearizes the entire ecosystem around SciML. Consider the cases where libraries build on disparate sciml packages, and a user wants to use two of these libraries that are stuck at different versions of SciML that nonetheless would have worked together. Letting versions float from each other is actually useful!

As mentioned, there would be clear advantages and disadvantages to this. Julia is extremely composable as a language, which itself is a double-edged sword, and I feel SciML is a living example of it. As a package developer strongly relying on the SciML ecosystem, it is amazing to be able to access each individual package and to have infinite granularity, but at the same time this highly complicates managing compatibilities, and you can often end up with weird package combinations which make it harder to debug issues. From my point of view, if this new monolithic package approach could still offer some decent level of granularity while not imposing precompilation penalization to downstream users (i.e. having to precompile lots of stuff that you won't need), then this could strike a nice balance. I like the idea of "what SciML version are you using?", although that perimeter should be properly discussed, since there will be different expectations for different people.

@agerlach
Copy link

agerlach commented Apr 3, 2025

Most of this conversation is above my pay grade, but I wanted to say that however you decide to structure it the user experience should be consistent across the entire ecosystem. I am mainly thinking of structuring things in some sort of hierarchy like the OrdinaryDiffEqCore and OrdinaryDiffEq discussion above. I think it is important to have some high-level package people can just load and run with access to a "reasonable" set of solvers. This is currently not the case for something like Optimization.jl. Doing R&D and REPL work with it is annoying and a PITA when you want to experiment with solvers. Obviously a tertiary concern.

@TorkelE
Copy link
Member

TorkelE commented Apr 4, 2025

Right now I am partial to something like Oscar proposed, where we keep everything in CI and checks for OrdinaryDiffEq only. That seems the less risky while we figure stuff out (I am still a bit scared about the extensions, in my experience those are always incredibly messy to develop).

@sloede
Copy link

sloede commented Apr 4, 2025

The SciML ecosystem has often been at the forefront of pushing Julia to its limits (and beyond), and being wildly successful while doing it. Considering the myriads of repos and the associated maintenance hell, I absolutely do see the need for change.

However, the approach as proposed in the OP seems to (ab)use the extension system for something that it was not designed to do. IMHO this is a classic recipe for conservation of pain: It would resolve one set of issues, but only while creating a whole set of new issues.

Given that the SciML ecosystem is too vast and important for such kinds of shenanigans, I kindly suggest to use one of the other - more band-aid-like - solutions that have been proposed (e.g., enhancing the CI infrastructure). This should buy some time to develop a proper solution for the underlying issues, which can then be pushed upstream to Julia, and from which other projects might benefit as well.

Note: I am not an active SciML developer, only a heavy user. I thus might have easily missed some boundary conditions/constraints. Also, I am aware that proper solutions require (much) more time, possibly exceeding the realm of possibilities in an volunteer-driven project such as SciML.

@ChrisRackauckas
Copy link
Member Author

Alright, so I think the summary of the plan that seems to align with the recommendations of this thread is:

  1. Extensions have odd behavior and are scary. Avoid abusing extensions with pseudopackages since there's lot of edge cases that we will run into.
  2. Single versioning of monorepos should be done through CI infrastructure that releases all of the packages as a single set that is version locked.
  3. Keep the boundaries at the solver equation types. This means keep LinearSolve.jl separate from NonlinearSolve.jl, etc.
  4. Focus the high-level packages on being easy for a "reasonable" set of solvers. So using OrdinaryDiffEq should just load and give you Tsit5, Vern, Rosenbrock, BDF, and default switching methods, and no longer load the vast majority of solvers. This will require a major update on the high level interfaces. We already kind of did that with LinearSolve v3 no longer loading sparse arrays, but we haven't done the clean up on ODEs and NonlinearSolve yet.
  5. Merge StochasticDiffEq.jl and DelayDiffEq.jl (and StochasticDelayDiffEq.jl) back into OrdinaryDiffEq.jl. Call this the new DifferentialEquations.jl. Major update that then decreases its deps a bunch.

Some extra comments.

All things in engineering are always questions of trade-offs, so here's some extra comments on the trade-offs we are making.

Extensions

I totally agree with extensions being scary, which is why I have been hesitant to do anything and was asking for Base Julia to give me a feature. @KristofferC has highlighted that they had even more issues that I originally was thinking about, so that makes that path a no-go. But the CI should be a good enough solution.

The CI solution doesn't exist though 😅, so we need to get something fast.

Single Versioning Downside

Single versioning through CI will mean we will average about 100 General registry PRs a day. Hopefully no one gets mad 😅 but it sounds like everyone here agrees it's the best option so if someone wonders why we're flooding General I'll just point to this thread and 🤷 we all agreed it was the best option 😅. It's a known trade-off and not necessarily a bad one with the right email filters, low tech solution is a good solution.

Package Boundaries At Solvers

For the solver boundaries, that's fine. For the most part the solver interfaces are pretty well-defined at this point, so OrdinaryDiffEq doesn't use internals of LinearSolve or NonlinearSolve, it uses documented interfaces. So for the most part those boundaries are working well. The one issue is in the Base libraries. In particular, there was a time where NonlinearProblem was using DiffEqBase. We now have NonlinearSolveBase, but there is still a small vestage of this living in DiffEqBase https://github.com/SciML/DiffEqBase.jl/blob/v6.167.2/src/solve.jl#L1063-L1118. If this was repo'd together, moving that piece of code from DiffEqBase to NonlinearSolveBase will be "breaking" in the sense that there will be incompatible versions if you grab NonlinearSolveBase prior to having this dispatch and DiffEqBase after moving it. It's not breaking in a semver sense because this dispatch actually living in DiffEqBase is an undocumented and unpublic aspect of the API. Should we just do a major version bump with release notes saying "Please just merge this without even testing, no package except NonlinearSolveBase needs to care about this change"? IIUC @devmotion that's what you're asking for?

In the 5 months I've spent a lot of timing doing repo cleaning so for the most part, that's one of the two remaining major issues in our interface packages. That one will happen hopefully in the next month or so, and it would be seamless if we had SciMLBase / DiffEqBase / NonlinearSolveBase / OrdinaryDiffEqCore in the same set, so effectively what we're saying is a value statement, that keeping NonlinearSolve.jl separate from DiffEq is more valuable from a teaching/scary factor "the repo is too big, I don't understand most of this, it will keep new contributors away" perspective than the issue of temporary breakage or CompatHelpers.

The other major issue, which is unsolved by this, is the backwards dependency of SciMLBase to ModelingToolkit, where MTK defines how any symbolic interaction works. This means that SciMLBase defines remake and reinit!, which then if you call them with symbolic functions they call a function with no definition in SciMLBase that is then pirated by ModelingToolkit. This means that fixing symbolic remake and reinit! is still going to be a bit jank because of this interface is naturally defined downstream but necessary in SciMLBase as a primitive for other interactions. At this point I believe remake and reinit! are, as of yesterday, handling all of the cases we want, but this is somewhere where @TorkelE @Datseris @isaacsas @AayushSabharwal we are making a statement that we know any further changes to this will have friction and we are accepting that cost and understanding the issues here. The hope though is that those dispatches no longer need to be touched very often 😅.

DiffEq Reconnection

This is one no one else has really commented on, but I don't think it impacts the comments mentioned about keeping SciML being easy to enter and contribute to. DelayDiffEq and StochasticDiffEq have always relied on internals of OrdinaryDiffEq, so them in separate repositories has always been an issue, especially DelayDiffEq. So moving those back in is something I believe will make @devmotion happy. Those are complex repos so I'm not sure keeping them separate helps newbies suddenly pick up Ito calculus 😅.

Quick Geuestimates

Just so we're on the right page, a quick geuestimation of where breaks come from in the last year are:

  • 40% of breakages have been at solver boundaries, i.e. OrdinaryDiffEqCore versioning vs OrdinaryDiffEqBDF. This will be fixed by the CI changes to version together.
  • 20% of breakages have been at boundaries of OrdinaryDiffEq / StochasticDiffEq / DelayDiffEq, so bringing them back together would fix that.
  • 20% of breakages have been due to the MTK reach back into SciMLBase for the definition of symbolic interfaces for plotting, remake, reinit!. SymbolicIndexingInterface.jl has helped a bit here, but ultimately this is unanswered. But I do think this will mostly go away if we now have the interface right 🤞.
  • 10% of the breakages have been from downstream solvers abusing the Base interface. DiffEqBase existed first, SciMLBase did not, SciMLOperators did not. Sometimes downstream packages do DiffEqBase.solve though it does not originate there, and so removing an import can cause a downstream breakage. This we're planning to fix with more integration of JET testing to force things into the interface, along with asking for Add strict mechanism for opting into stricter subsets of the language  JuliaLang/julia#54903.
  • 10% of breakages have been at SciMLOperators / SciMLBase / DiffEqBase / NonlinearSolveBase / OptimizationBase restructuring, where NonlinearSolveBase and OptimizationBase did not exist until recently, so we have moved a lot from DiffEqBase to the new Base libraries, and moving operators out of SciMLBase so the interface can be more well-defined. Again, a lot of this was because the Base libraries were new, and there is still one thing left to do there. So it should hopefully be a non-issue in the near future. I was suggesting the merger of everything only because of this aspect.

Some Future Planning

2024 had some difficult times due to a few major interface changing events:

  1. Splitting the solver packages (OrdinaryDiffEq / LinearSolve / NonlinearSolve)
  2. Changing to DifferentiationInterface
  3. OptimizationBase and NonlinearSolveBase coming into existence
  4. SymbolicIndexingInterface and the initialization interface coming into existence
  5. Deleting things out of SciMLBase / DiffEqBase to improve load times

That was not a fun year, refactoring to improve loading times is no one's free time hobby. But it's a necessity. In 2025 we're planning major projects:

  1. OrdinaryDiffEq nonlinear solver changes, ImplicitDiscreteSolve SCCs, 10x performance in stiff ODE solvers using the tricks that will be published in the summer. This is OrdinaryDiffEq v7 OrdinaryDiffEq v7 OrdinaryDiffEq.jl#2310
  2. Removing FillArrays, SparseArrays, and LinearAlgebra from base packages as much as possible. This is... going to be a nightmare but in theory is non-breaking except for cases where packages pull functions which assumes these exist.
  3. ModelingToolkit v10 MTKv10 discussion ModelingToolkit.jl#3204 which is mostly about deleting interfaces that are not well-defined to grow robustness, which is then breaking but hopefully most things are not using those interfaces and updated already 😅.
  4. Clean up in Optimization.jl. This one won't be fun and will have probably a major version.
  5. Merging JuliaSymbolics back into SciML. In theory non-breaking.
  6. Refactoring BasicSymbolic for type stability / performance / compile times
  7. Native line searches for optimization and native optimization solvers
  8. Direct Enzyme and static compilation compatibility of core solvers.
  9. Verbosity system Verbosity System Description SciMLBase.jl#962

So the only thing I'm quite scared about in the next year is the FillArray / SparseArray / LinearAlgebra stuff is going to break something making bad assumptions. Optimziation.jl, if collapsing OptimizationBase into the same repo and having this CI solution, can be done in a way that is nicer than how DiffEqBase / NonlinearSolveBase has gone.

@TorkelE @devmotion @Datseris @isaacsas are you okay with these trade-offs?

@ChrisRackauckas
Copy link
Member Author

I am mainly thinking of structuring things in some sort of hierarchy like the OrdinaryDiffEqCore and OrdinaryDiffEq discussion above. I think it is important to have some high-level package people can just load and run with access to a "reasonable" set of solvers. This is currently not the case for something like Optimization.jl. Doing R&D and REPL work with it is annoying and a PITA when you want to experiment with solvers. Obviously a tertiary concern.

Yes, Optimization.jl having some interface breaks and oddities is known. That's what I mean by "Clean up in Optimization.jl. This one won't be fun and will have probably a major version.". It's something I noted in the State of SciML talk at the last JuliaCon that Optimization.jl is the solver library that needs the most work because its solver / base interface is somewhat inverted. If we move OptimizationBase into the same repo as Optimization, then we can iterate on it all in one repo, using the new CI tooling, and then just done one breaking update when the flip is complete. So, this work was mostly just waiting on making a decision on what to do about repo/package splits. Hopefully we get things going on OrdinaryDiffEq with the new CI, and if that's working then we start this project.

@MasonProtter
Copy link

MasonProtter commented Apr 4, 2025

Extensions

@KristofferC has highlighted that they had even more issues that I originally was thinking about, so that makes that path a no-go. But the CI should be a good enough solution.

Regarding this, why not just go with this structure:

# DiffEqCore/src/DiffEqCore.jl
module DiffEqCore
struct Tsit5
    ...
end
function solve end
# Define but don't export Tsit5 struct
end
# DiffEqCore/ext/DiffEqCoreDiffEqTsit5Ext.jl
module DiffEqCoreDiffEqTsit5Ext
import DiffEqCore: Tsit5, solve
# Define the extension
function solve(prob, ::Tsit5)
    ...
end
end

and then your stub package would just do:

module DiffEqTsit5
using DiffEqCore: Tsit5
export Tsit5
end

That way, by default users don't have access to Tsit5 or whatever other solvers or other stuff until they load the DiffEqTsit5 (or OrdinaryDiffEq) package, but the code still all lives in the DiffEqCore repo. Or is there something I'm missing?

@gdalle
Copy link

gdalle commented Apr 4, 2025

Single versioning through CI will mean we will average about 100 General registry PRs a day. Hopefully no one gets mad

Is this big enough to increase the merge time of General Registry PRs for everyone else?

@ChrisRackauckas
Copy link
Member Author

Is this big enough to increase the merge time of General Registry PRs for everyone else?

If there's enough complaints we can take one of the benchmarking nodes and add it to the general registry. Like, if the solution here is just a bit of $$$ then we'll pay the cost, it's worth it.

That way, by default users don't have access to Tsit5 or whatever other solvers or other stuff until they load the DiffEqTsit5 (or OrdinaryDiffEq) package, but the code still all lives in the DiffEqCore repo. Or is there something I'm missing?

That is close to the current form of LinearSolve.jl. The issues are:

  1. No error message at the construction of Tsit5(): you can do that manually though https://github.com/SciML/LinearSolve.jl/blob/main/src/extension_algs.jl#L55-L57, and you have to be diligent (the issue with Sparsepak factorization last month was exactly from missing a manual error about not loading)
  2. If the solver package has dependencies, then the dependency bumps are in a different "package" from the code, so it's necessarily breaking on dependency bumps.
  3. Non-locality can make the error messages go away. That also means that non-locality can also make the error messages suddenly appear, if you were using Tsit5() and it only worked because some dependency did using DiffEqTsit5, and then your package code no longer worked because that dependency dropped doing using DiffEqTsit5, that's a similar kind of breakage to type piracy kinds of non-locality breakage.

@Datseris
Copy link

Datseris commented Apr 4, 2025

This means that fixing symbolic remake and reinit! is still going to be a bit jank because of this interface is naturally defined downstream but necessary in SciMLBase as a primitive for other interactions. At this point I believe remake and reinit! are, as of yesterday, handling all of the cases we want, but this is somewhere where @TorkelE @Datseris @isaacsas @AayushSabharwal we are making a statement that we know any further changes to this will have friction and we are accepting that cost and understanding the issues here. The hope though is that those dispatches no longer need to be touched very often 😅.

As far as I am concerned the recent PRs from Aayush fixed this! (pending testing in the wild)

@TorkelE @devmotion @Datseris @isaacsas are you okay with these trade-offs?

In general I am on board. I like the locking single versioning approach and I am not too worried about many PRs at the registry, things have been looking fantastic in Julia the last year w.r.t. the automated registry. I am also on board with not going this package extensions route, seems too brittle.

Some things are not clear to me, please clarify:

  • OrdinaryDiffEq.jl will not contain delay and stochastic stuff. Rather, all types of DiffEq will be merged into DifferentialEquations.jl. Correct?
  • What is the situation with solver specific packages? Can I still only depend on OrdinaryDiffEqVerner and get the minimum dependencies for my project after all is said and done?
  • What is the state of NonlinearSolve + OrdinaryDiffEq in the future? Will I still be depending on NonlinearSolve if I use OrdinaryDiffEqVerner? For me this has been a pain because, at least in principle, neither linear nor nonlinear solves should be directly relevant for the Verner methods. Yet NonlinearSolve.jl brings in lots of dependencies and compile times, and the unavoidable risk of breakage (that will hopefully be solved with the new design)

@KristofferC
Copy link

Single versioning through CI will mean we will average about 100 General registry PRs a day.

I personally don't think that is acceptable. You might have to stagger releases a bit, so that users might have to wait a week or so to get the latest typo fix.

@JordiBolibar
Copy link

Some Future Planning

  1. Clean up in Optimization.jl. This one won't be fun and will have probably a major version.
  2. Direct Enzyme and static compilation compatibility of core solvers.

I'm extremely looking forward to these two 😅

@asinghvi17
Copy link
Contributor

In terms of noise in General - if we guarantee that all version bumps will be synchronized for a set of repos, does it make sense to then add a method to Registrator that will create a single PR for a monorepo version bump? Then we don't have to worry about noise either, and presumably the version bump will be well reviewed enough on this end.

@MasonProtter
Copy link

MasonProtter commented Apr 7, 2025

It's really too bad that there isn't just a way to do this with the new [sources] / [workspace] features in Pkg. I keep thinking there must a way to get this done using that.

@KristofferC
Copy link

method to Registrator that will create a single PR for a monorepo version bump? Then we don't have to worry about noise either, and presumably the version bump will be well reviewed enough on this end.

If we are literally talking about hundreds of package registrations per day that get their Project.toml version bumped that means that people that depend on this stuff will also have to download hundreds of new package versions per day. I think they shouldn't need to re-precompile since the project file is not part of the package precompilation but that could still be fairly annoying to download new package versions over and over with just a version bump in the project file.

It's really too bad that there isn't just a way to do this with the new [sources] / [workspace] features in Pkg.

This could indeed be built upon the workspace feature. Some things would have to be tweaked:

  • Packages without a version field in the project file that are in a workspace inherit the version from a "parent" project.
  • Syntax for loading a package in a workspace, something likeusing WorkpacePackage::SubPackage (for example using DiffEq::Tsit5).
  • JuliaRegistrator feature to register all packages in a workspace.
  • Compat is already effectively "inherited" by packages in a workspace so that wouldn't need to change

The suggestions presented here make me scared enough to want to start working on proper support for this heh...

@MasonProtter
Copy link

MasonProtter commented Apr 8, 2025

One thing I was thinking about is that the [extensions] mechanism is nearly what we want here, we just need a way to directly using an extension module, rather than having it be implicitly loaded when the weakdeps are all loaded.

I managed to hack together a little demo here: https://github.com/MasonProtter/ExampleMonorepo/ where I was able to trick Base.run_extension_callbacks into loading an extension module on demand, and then I used Base.get_extension to access and using that module.

This basically seems like what we want here, right? It just needs a blessed mechanism to trigger it and nicer syntax.


What's neat about this is that you only need to register the monorepo, and only the monorepo needs to go in your Project.toml

Then, people who use the monorepo just choose which extensions are loaded at the toplevel of their package and they only pay for what they use.

E.g. in the DiffEq example using it would just look like

(SomePackage) pkg> add DiffEqCore

and then

module SomePackage

using DiffEqCore
DiffEqCore.@using Tsit5

...

end

and then you'd only load the Tsit5 solver and whatnot but not any of the other solvers defined in other extensions.

DifferentialEquations.jl would then just be something like

module DifferentialEquations

using DiffEqCore
DiffEqCore.@using Tsit5
DiffEqCore.@using SomeOtherSolver
...

export DiffEqCore, Tsit5, SomeOtherSolver,

end

@MasonProtter
Copy link

MasonProtter commented Apr 8, 2025

So dealing with extensions directly runs into a wall when dealing with extension-only dependencies. I put together a demo of an extension-like functionality here: JuliaLang/julia#58051 that'd be more adapted for this usecase.

@ChrisRackauckas
Copy link
Member Author

In terms of noise in General - if we guarantee that all version bumps will be synchronized for a set of repos, does it make sense to then add a method to Registrator that will create a single PR for a monorepo version bump? Then we don't have to worry about noise either, and presumably the version bump will be well reviewed enough on this end.

Yes, that would get rid of effectively all of the noise. And probably lessen the CI burden.

OrdinaryDiffEq.jl will not contain delay and stochastic stuff. Rather, all types of DiffEq will be merged into DifferentialEquations.jl. Correct?

We might just have it all be the current OrdinaryDiffeq repo, if only because most stuff is already there and because the DiffEq repo is like 2GB of old nonsense, and so using the cleaner repo is better in the end. We would lose the star count though 😅. Maybe there's a nicer way to clean the repo, move to main branch and purge master or something.

What is the situation with solver specific packages? Can I still only depend on OrdinaryDiffEqVerner and get the minimum dependencies for my project after all is said and done?

That's in any of the plans. We do not want to lose the functionality of having minimal exports. That has been a major win for startup times. We're now just trying to figure out how to deal with the version mess and the bumping nightmares that it's causing.

Will I still be depending on NonlinearSolve if I use OrdinaryDiffEqVerner?

It already does not depend on NonlinearSolve:

(@v1.11) pkg> st
Status `~/.julia/environments/v1.11/Project.toml`
⌃ [98e50ef6] JuliaFormatter v1.0.62
  [79d7bb75] OrdinaryDiffEqVerner v1.1.1
Info Packages marked with ⌃ have new versions available and may be upgradable.

(@v1.11) pkg> st -m
Status `~/.julia/environments/v1.11/Manifest.toml`
⌃ [47edcb42] ADTypes v1.13.0
⌃ [7d9f7c33] Accessors v0.1.41
⌃ [79e6a3ab] Adapt v4.1.1
  [4fba245c] ArrayInterface v7.18.0
  [62783981] BitTwiddlingConvenienceFunctions v0.1.6
  [2a0fbf3d] CPUSummary v0.2.6
  [00ebfdb7] CSTParser v3.4.3
  [fb6a15b2] CloseOpenIntervals v0.1.13
⌅ [a80b9123] CommonMark v0.8.15
  [38540f10] CommonSolve v0.2.4
  [f70d9fcc] CommonWorldInvalidations v1.0.0
  [34da2185] Compat v4.16.0
  [a33af91c] CompositionsBase v0.1.2
  [2569d6c7] ConcreteStructs v0.2.3
  [187b0558] ConstructionBase v1.5.8
  [adafc99b] CpuId v0.3.1
  [a8cc5b0e] Crayons v4.1.1
  [9a962f9c] DataAPI v1.16.0
⌃ [864edb3b] DataStructures v0.18.20
  [e2d170a0] DataValueInterfaces v1.0.0
  [2b5f629d] DiffEqBase v6.164.0 `~/.julia/dev/DiffEqBase`
⌃ [ffbed154] DocStringExtensions v0.9.3
⌃ [4e289a0a] EnumX v1.0.4
  [f151be2c] EnzymeCore v0.8.8
  [e2ba6199] ExprTools v0.1.10
  [55351af7] ExproniconLite v0.10.14
  [7034ab61] FastBroadcast v0.3.5
  [9aa1b823] FastClosures v0.3.2
⌃ [a4df4552] FastPower v1.1.1
  [1a297f60] FillArrays v1.13.0
  [069b7b12] FunctionWrappers v1.1.3
  [77dc65aa] FunctionWrappersWrappers v0.1.3
  [46192b85] GPUArraysCore v0.2.0
  [c27321d9] Glob v1.3.1
  [615f187c] IfElse v0.1.1
  [3587e190] InverseFunctions v0.1.17
  [82899510] IteratorInterfaceExtensions v1.0.0
  [ae98c720] Jieko v0.2.1
⌃ [98e50ef6] JuliaFormatter v1.0.62
  [10f19ff3] LayoutPointers v0.1.17
  [1914dd2f] MacroTools v0.5.15
  [d125e4d3] ManualMemory v0.1.8
  [2e0e35c7] Moshi v0.3.5
  [46d2c3a1] MuladdMacro v0.2.4
  [bac558e1] OrderedCollections v1.8.0
  [bbf590c4] OrdinaryDiffEqCore v1.18.0 `../../dev/OrdinaryDiffEq/lib/OrdinaryDiffEqCore`
  [79d7bb75] OrdinaryDiffEqVerner v1.1.1
  [d96e819e] Parameters v0.12.3
  [f517fe37] Polyester v0.7.16
  [1d0040c9] PolyesterWeave v0.2.2
⌅ [aea7be01] PrecompileTools v1.2.1
  [21216c6a] Preferences v1.4.3
  [3cdcf5f2] RecipesBase v1.3.4
⌃ [731186ca] RecursiveArrayTools v3.29.0
  [189a3867] Reexport v1.2.2
⌃ [ae029012] Requires v1.3.0
  [7e49a35a] RuntimeGeneratedFunctions v0.5.13
  [94e857df] SIMDTypes v0.1.0
⌃ [0bca4576] SciMLBase v2.77.2
⌃ [c0aeaf25] SciMLOperators v0.3.12
⌃ [53ae85a6] SciMLStructures v1.6.1
⌃ [efcf1570] Setfield v1.1.1
  [ce78b400] SimpleUnPack v1.1.0
⌃ [aedffcd0] Static v1.1.1
  [0d7ed370] StaticArrayInterface v1.8.0
  [1e83bf80] StaticArraysCore v1.4.3
  [10745b16] Statistics v1.11.1
  [7792a7ef] StrideArraysCore v0.5.7
⌃ [2efcf032] SymbolicIndexingInterface v0.3.37
  [3783bdb8] TableTraits v1.0.1
  [bd369af6] Tables v1.12.0
  [8290d209] ThreadingUtilities v0.5.2
  [0796e94c] Tokenize v0.5.29
  [781d530d] TruncatedStacktraces v1.4.0
  [3a884ed6] UnPack v1.0.2
  [56f22d72] Artifacts v1.11.0
  [2a0f44e3] Base64 v1.11.0
  [ade2ca70] Dates v1.11.0
  [8ba89e20] Distributed v1.11.0
  [9fa8497b] Future v1.11.0
  [b77e0a4c] InteractiveUtils v1.11.0
  [76f85450] LibGit2 v1.11.0
  [8f399da3] Libdl v1.11.0
  [37e2e46d] LinearAlgebra v1.11.0
  [56ddb016] Logging v1.11.0
  [d6f4376e] Markdown v1.11.0
  [ca575930] NetworkOptions v1.2.0
  [de0858da] Printf v1.11.0
  [9a3f8284] Random v1.11.0
  [ea8e919c] SHA v0.7.0
  [9e88b42a] Serialization v1.11.0
  [6462fe0b] Sockets v1.11.0
  [fa267f1f] TOML v1.0.3
  [cf7118a7] UUIDs v1.11.0
  [4ec0a83e] Unicode v1.11.0
  [e66e0078] CompilerSupportLibraries_jll v1.1.1+0
  [e37daf67] LibGit2_jll v1.7.2+0
  [29816b5a] LibSSH2_jll v1.11.0+1
  [c8ffd9c3] MbedTLS_jll v2.28.6+0
  [4536629a] OpenBLAS_jll v0.3.27+1
  [8e850b90] libblastrampoline_jll v5.11.0+0
Info Packages marked with ⌃ and ⌅ have new versions available. Those with ⌃ may be upgradable, but those with ⌅ are restricted by compatibility constraints from upgrading. To see why use `status --outdated -m`

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

No branches or pull requests