Adds support for differentable-native solvers#344
Adds support for differentable-native solvers#344PTNobel wants to merge 6 commits intojump-dev:masterfrom
Conversation
Introduces SolverBackedDiff.jl which enables differentiation backed directly by a solver's native capabilities. Integrates it into the DiffOpt optimizer by checking for native solver support before falling back to the existing model constructor approach. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the example file with a comprehensive test suite (17 tests) covering reverse/forward differentiation, auto-detection, index mapping, finite difference cross-checks, and multi-variable problems. Also add ForwardConstraintFunction override to bypass Parameter check. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a docs page covering the solver interface, how auto-detection works, and an example showing KKT factorization reuse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace custom function declarations (reverse_differentiate!, reverse_objective, etc.) with standard MOI get/set/supports on new BackwardDifferentiate and ForwardDifferentiate model attributes. Solvers now implement the standard MOI attribute protocol instead of SolverBackedDiff-specific functions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
FYI, I have something similar for MadDiff's DiffOpt integration, see https://github.com/MadNLP/MadDiff.jl/blob/main/ext/DiffOptExt/DiffOptExt.jl and https://github.com/MadNLP/MadDiff.jl/tree/main/ext/MathOptInterfaceExt The biggest difference is that there, I implement a Curious what the maintainers think. Not sure about how to best implement it, but it would be great to have this capability officially supported in DiffOpt! |
|
Hey, Thanks for the interest in plugging this into DiffOpt! I will take a close look over the weekend. I must confess we did not give much thought to the native diff solver, but this is past due. I'd say that simply reusing the DiffOpt API should be relatively easy: simply overload some getters and setters in your MOI interface. I have this (very) experimental work here: https://github.com/jump-dev/DiffOpt.jl/tree/jg/lpbasis Starting point: If that already exists, send me a pointer! |
|
The Moreau.jl package is working and passing the MOI.Test. It's waiting for approval to be made public, but right now it's very similar to SCS.jl internally |
|
Will Moreau.jl natively support parameters? |
|
I am not sure; what MOI features would that require? |
|
My suggestion is: If the inner solver supports parameters the way to go is supporting the MOI.Parameter set, when implementing MOI.add_constrained_variable so that the variable is a parameter for the solver perspective. |
|
I discussed this with Steven.
I don't think Moreau needs to know about DiffOpt. DiffOpt is effectively a layer for solvers that don't have native differentiability. |
|
I do not agree with that order of priorities. Also, that is not entirely true. DiffOpt is both interface defining and a helper for solvers that do not support differentiability. We did discuss differentiability related attributes in MOI before and the answer was not to touch MOI. If you changed your mind, I think we are ready to push differentiation attributes to MOI. I'd be happy with this. However, I fear that diff attributes in MOI might be a long discussion since there are many important details. About the needed attributes: This two are extremely useful in practice: moreover, we need: In DiffOpt we have been making sure everything is always supported, but we can certainly have some Moreau supports differentiability more explicitly. Houwever, we already have solver with partial support for that that we are not currently exploiting: HiGHS, Gurobi, Xpress (possibly CPLEX). Extremely important things to consider: Therefore, we have absolutely no need to wait for Moreau to discuss the MOI differentiation API. I see absolutely no reason to push a full Moreau MOI-level implementation of differentiation without looking at DiffOpt. Sure, we can see the low-level API, but there is no reason to duplicate work and do it twice (a "first try" and a MOI review) I agree that having an API defined at the MOI level would be very helpful, as we could potentially make Bridges and POI explicitly aware of it. But again, I do not think of this as a quick endeavor. Waiting for all that will simply not allow Moreau to be used cleanly in JuMP, whereas DiffOpt would allow it quickly. So my counter proposal is to have parallel non-blocking efforts:
|
| Walk through CachingOptimizer, bridge, and POI layers to find the innermost | ||
| solver. Returns `nothing` if no natively-differentiable solver is found. | ||
| """ | ||
| function _unwrap_solver(model::MOI.Utilities.CachingOptimizer) |
There was a problem hiding this comment.
We shouldn't need that, the goal of attributes is to flow through the layers so that you never need to unwrap.
|
I agree with Joaquim, I don't see why we should move things to MOI. MOI has reached v1 so anything we move there is frozen. Adding DiffOpt as dependency to Moreau or even as a packages extension is preferrable. I'm wondering if it wouldn't be possible to do something much simpler that what Claude Code suggested here. struct BackwardDifferentiate <: MOI.AbstractModelAttribute endand then checking whether the solver supports it with MOI.supports(model.optimizer, DiffOpt.BackwardDifferentiate())and if it does just triggering the computation with MOI.set(model.optimizer, DiffOpt.BackwardDifferentiate())However, for setting the attributes, it shouldn't do the So in Line 551 in c010b53 in case MOI.supports(model.optimizer, DiffOpt.BackwardDifferentiate()) returns true, then we copy the input_cache directly into model.optimizer, not model.diff. So maybe what we can simply do is makeLine 785 in c010b53 return model.optimizer and model.diff can just be nothing, it won't be used.These parameters are all passed down through the layers, and transformed accordingly (as joaquim says we implement transformation in the bridges on a per-need basis, it's easy but we only do it for a bridge when we need it, don't hesitate to let us know if it's needed for a bridge your solver is using). Then, we request the differentiation to the solver with MOI.set(model.optimizer, DiffOpt.BackwardDifferentiate())Here, to make sure it's passed down to the solver and not the cache, maybe it should be an Line 839 in c010b53 return model.optimizer, and all results will be queried from the solver as the attributes defined in DiffOpt, and transformed across the layers as expected.This was designed with attributes for this use case to work, I just never tried it with an actual solver so there were these remaining pieces to make it work but I'd prefer to first try it this way, which should be much simpler in my opinion. If there are good reasons that this approach doesn't work then we can consider the approach of this PR (I wouldn't want Claude Code to feel offended :) ) |
Hey JuMP team!
I'm working on moreau.so @optimalintellect; and we natively support differentiable optimization, and can reuse some data structures between the forward and backward pass. However, to do that, we need to preserve a handle that points to the data structures inbetween the forward and backward pass.
I wanted to support DiffOpt natively in our Julia frontend, so I am adding support in DiffOpt for an MOI interface to declare that it has native differentiation support and for that to be used.
I also added documentation for this.
This is my first time using Julia, so Claude Code was invaluable. I have read all of the code, and I believe I understand it. That said, I make no promises that I have any idea what the Julia ecosystem expects, etc. so please suggest as many or as extensive of revisions as desired.