Skip to content
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

[Bridges] add Constraint.FunctionConversionBridge #2235

Merged
merged 20 commits into from
Aug 15, 2023
Merged

[Bridges] add Constraint.FunctionConversionBridge #2235

merged 20 commits into from
Aug 15, 2023

Conversation

odow
Copy link
Member

@odow odow commented Jun 29, 2023

x-ref #2233

@odow odow requested a review from blegat June 29, 2023 23:00
@odow
Copy link
Member Author

odow commented Jun 29, 2023

So I was simplifying things, when I realized that there was a better design (FunctionConversionBridge). It seems pointless to have both, but the new bridge makes it easy to cover every transform.

I guess our options are:

  • Don't add. Keep what we have
  • Keep old and add new
  • Change the signature of AbstractFunctionConversionBridge to accept G so that we can convert both ways.

@odow odow mentioned this pull request Jun 29, 2023
3 tasks
@odow
Copy link
Member Author

odow commented Jun 29, 2023

I've moved some stuff around. We could leave removing ScalarFunctionizeBridge and VectorFunctionizeBridge for MOI 2.0.

@odow odow changed the title [Bridges] simplify AbstractFunctionConversionBridge [Bridges] add Constraint.FunctionConversionBridge Jun 29, 2023
@odow
Copy link
Member Author

odow commented Jun 29, 2023

@blegat
Copy link
Member

blegat commented Jun 30, 2023

Change the signature of AbstractFunctionConversionBridge to accept G so that we can convert both ways.

We can add a new subtype in between to make it non-breaking.

It still make sense to have one different bridge by output function. Here, we have a chain: variable -> affine -> quadratic -> nl
so it makes sense to have a bridge that converts to the next one of the chain but it's not so composable.
For instance, PolyJuMP defines the polynomial function type which sits in between quadratic and nl.
If things are hardcoded then it's not so easy.
Maybe we could have something like

struct FunctionConversionBridge{T,F,G} ... end
const ScalarFunctionizeBridge{T} = FunctionConversionBridge{T,ScalarAffineFunction}
...
``
so that implementing a bridge is one-line but the final function is still specified by the bridge.
We would have to add a `convertible` function to the API to make this work though.

@odow
Copy link
Member Author

odow commented Jul 1, 2023

It should work if you define:

function MOI.supports_constraint(
    ::Type{<:FunctionConversionBridge{T}},
    ::Type{PolyJuMP.Polynomial{T}},
    ::Type{<:MOI.AbstractScalarSet},
) where {T}
    return true
end

function concrete_bridge_type(
    ::Type{<:FunctionConversionBridge{T}},
    ::Type{PolyJuMP.Polynomial{T}},
    ::Type{S},
) where {T,S<:MOI.AbstractScalarSet}
    return FunctionConversionBridge{
        T,
        PolyJuMP.Polynomial{T},
        MOI.ScalarNonlinearFunction,
        S,
    }
end

@blegat
Copy link
Member

blegat commented Jul 4, 2023

That converts a polynomial to nonlinear but how do you create a bridge from nonlinear to polynomial ?
Also, since it's only one bridge, it makes it difficult to use remove_bridge to disable one particular conversion.

@odow
Copy link
Member Author

odow commented Jul 7, 2023

but how do you create a bridge from nonlinear to polynomial

You can't, because that's not possible in general?

since it's only one bridge, it makes it difficult to use remove_bridge to disable one particular conversion.

This is a reasonable point. But it's pretty niche...

@blegat
Copy link
Member

blegat commented Jul 8, 2023

but how do you create a bridge from nonlinear to polynomial

You can't, because that's not possible in general?

This is exactly the goal of quadratic to RSOC. Because a conic solver does not support quadratic and this bridge might then you better try

since it's only one bridge, it makes it difficult to use remove_bridge to disable one particular conversion.

This is a reasonable point. But it's pretty niche...

My point with these two examples is that these are the tip of the iceberg pointing towards the fact that we're loosing some of the nice extensibility features of the bridges here. And this comes from the fact that we hardcode the next function to convert to in the chain of conversions.

@odow
Copy link
Member Author

odow commented Aug 2, 2023

You could write a different bridge for SNF to polynomial. This bridge is a simple attempt at covering common use cases. I can't think of a situation in which you might like to disable ScalarAffine -> ScalarNonlinear, for example.

@blegat
Copy link
Member

blegat commented Aug 2, 2023

This bridge is a simple attempt at covering common use cases

It's actually as simple to cover all use cases.
It's also that hardcoding the conversion like that is quite unnatural for bridges. What is a bridge doing "all conversions" ? It's quite ill defined. It's much more natural to write a bridge "conversion to affine" or "conversion to quadratic" or "conversion to nonlinear".
We are actually disabling the "conversion from SOC to quadratic" by default and the argument was that SOC is convex while the quadratic formulation could be solved with a nonlinear program that may not guarantee global optimality and it thought this could be counter intuitive. The same argument could apply to the "linear -> nonlinear" bridge actually.

You PR is a big improvement over the current bridges and you does not need much change to become FunctionConvertionBridge{T,F}; const ScalarAffineFunctionConvertionBridge{T} = FunctionConversionBridge{T,ScalarAffineFunction{T}}; ...

@blegat
Copy link
Member

blegat commented Aug 2, 2023

Once we have a function conversion like that, it's also easy to add something like function conversion from SNF to a DisciplineConvexFunction that have meta information about convexity like Convex.jl.
Then, a solver could choose to support DCF and it will be bridged for them.
Then, we can add bridges from DCF -> separate conic constraints by adding slacks as well.
With black box functions, if we go with three functions: 0th order black box, 1st order black box and 2nd order black box, there are also trivial conversions there.
I'm not saying we will define these functions in the future, I'm just saying I don't want to bet that we will define none. And the design of this PR is already closing many doors I want leave open (already for PolyJuMP). Of course we could then create a new bridge but then that will be a bit confusing and inconsistent and the goal of trying to make things simpler of this PR (which I understand) would have an opposite effect.

@odow
Copy link
Member Author

odow commented Aug 2, 2023

You PR is a big improvement over the current bridges and you does not need much change to become

This doesn't fix the deletion issue though?

@odow
Copy link
Member Author

odow commented Aug 2, 2023

Shall we hold off on this PR for a bit then? I originally wanted to get it in for v1.19, but if ScalarQuadraticToScalarNonlinearBridge is going to stay, then there is no rush.

@blegat blegat mentioned this pull request Aug 3, 2023
11 tasks
@blegat
Copy link
Member

blegat commented Aug 3, 2023

Which deletion issue ? It's not going to stay, it's going to be replaced by ToScalarNonlinearBridge that converts from anything to SNF, not only quadratic

@odow
Copy link
Member Author

odow commented Aug 3, 2023

Maybe we should call about this. I thought you wanted separate bridges for each change, rather than one that tried everything.

@blegat
Copy link
Member

blegat commented Aug 4, 2023

IIUC, this PR is "Any-to-Any", the current status is "fixed function-to-fixed function" and I am suggesting "Any-to-fixed function"

@odow
Copy link
Member Author

odow commented Aug 6, 2023

IIUC, this PR is "Any-to-Any", the current status is "fixed function-to-fixed function" and I am suggesting "Any-to-fixed function"

I'd say this PR is "F-to-G" for an explicit list of functions. The current status is "scalarquadratic to scalarnonlinear." I don't think we want an any-to-fix because it just leads to weird errors that are hard to interpret.

@odow
Copy link
Member Author

odow commented Aug 8, 2023

@odow
Copy link
Member Author

odow commented Aug 14, 2023

I've reverted the name changes and the switch from Int to Float64.

This make the PR a lot smaller and easier to digest. The change required to support bridge-specific costs is actually very minor.

Open questions would be:

  • How do we choose weights?
  • Are Int weights sufficient?

@odow
Copy link
Member Author

odow commented Aug 14, 2023

@blegat
Copy link
Member

blegat commented Aug 14, 2023

How do we choose weights?

It seems to be difficult to give rules. Note that in the future we could imagine the user changing the weights of a solver defined in MOI instead of having to do remove_bridge but we can wait for it to be useful.

Are Int weights sufficient?

I can see it being annoying, see #2235 (comment)

@odow
Copy link
Member Author

odow commented Aug 14, 2023

We could change to Float64. Alternatively, we could make the default cost 100, to give people room to go lower.

@odow
Copy link
Member Author

odow commented Aug 14, 2023

We spoke offline and decided to switch to Float64

src/Bridges/graph.jl Outdated Show resolved Hide resolved
@odow
Copy link
Member Author

odow commented Aug 14, 2023

One last run of solver-tests, and then I'm marking this as good to go:

https://github.com/jump-dev/MathOptInterface.jl/actions/runs/5860994743

@mlubin
Copy link
Member

mlubin commented Aug 14, 2023

Can we add tests that certain important combinations like VariableIndex-in-LessThan get bridged exactly as we expect with a hypothetical conic solver?

@odow
Copy link
Member Author

odow commented Aug 15, 2023

That's what this test is:
https://github.com/jump-dev/MathOptInterface.jl/pull/2235/files#diff-0bac7c57a5e8c70eefceef8d3791b77dd904969489fd87b638be82f6f92e9efeR2050-R2092

although it's a little opaque. We could be more explicit.

@mlubin
Copy link
Member

mlubin commented Aug 15, 2023

Ah, I missed that. The test is named test_bridging_cost and it checks the numerical values of the bridging costs, so I assumed that was all it covered. It make sense to split this into multiple tests.

@odow
Copy link
Member Author

odow commented Aug 15, 2023

I've tweaked the tests to check that we get the costs expected, and that the internal model is built as expected.

@odow
Copy link
Member Author

odow commented Aug 15, 2023

Merging because I discussed this change with @blegat last night. Once merged, I'll update the prep for v1.19, and he can double check things before we tag the release.

@odow odow merged commit 8c7fbbf into master Aug 15, 2023
15 of 16 checks passed
@odow odow deleted the od/functioniz branch August 15, 2023 02:03
@odow odow added the Project: next-gen nonlinear support Issues relating to nonlinear support label Sep 4, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Project: next-gen nonlinear support Issues relating to nonlinear support
Development

Successfully merging this pull request may close these issues.

3 participants