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

Add support for generic number type #3191

Closed
wants to merge 16 commits into from
Closed

Conversation

blegat
Copy link
Member

@blegat blegat commented Jan 16, 2023

A first approach could be to change Model into Model{T} with Model() = Model{Float64}() and VariableRef into VariableRef{T} with VariableRef() = VariableRef{Float64}().
However, this would be breaking with respect to performance. Users having a Model or VariableRef as a field would not have a concrete field anymore.
Moreover, even if it is nonbreaking, users only caring about Float64 will have non-concrete fields when using Model or VariableRef even if these types seem concrete from the name.

For these reasons, this PR is considering a different approach.
We define GenericModel{T} and const Model = GenericModel{Float64} and GenericVariableRef{T} with const VariableRef = GenericVariableRef{Float64}.
We now have a few advantages:

  • This is non-breaking
  • When the users use GenericModel and GenericVariableRef as a struct field, it's clear from the name that it is not concrete
  • It is consistent with AffExpr/GenericAffExpr and QuadExpr/GenericQuadExpr

This PR also adds the value_type function to the JuMP API. This function gives the return type of the JuMP.value function on variables of the JuMP model. It can be called on a variable or a model. Calling it on an affine or quadratic may be confusing since the coefficient of the expression might be different from the value_type of the variables or even the return type of value on the expression. It is therefore not defined for affine and quadratic expressions.

  • Make current tests pass
  • Add tests
  • Add docs

Note that incorporates the changes of

because otherwise, I would have had to complicate the tests because of this:

julia> string(Float32(1) * im)
"0.0f0 + 1.0f0im"

Here if how it looks like

julia> model = GenericModel{Rational{BigInt}}(CDDLib.Optimizer{Rational{BigInt}})
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: EMPTY_OPTIMIZER
Solver name: CDD

julia> @variable(model, 0 <= x[1:2])
2-element Vector{GenericVariableRef{Rational{BigInt}}}:
 x[1]
 x[2]

julia> @constraint(model, c1, 2x[1] + x[2] <= 1)
c1 : 2//1 x[1] + x[2]  1//1

julia> @constraint(model, c2, x[1] + 3x[2] <= 2)
c2 : x[1] + 3//1 x[2]  2//1

julia> @objective(model, Max, sum(x))
x[1] + x[2]

julia> optimize!(model)

julia> solution_summary(model)
* Solver : CDD

* Status
  Result count       : 1
  Termination status : OPTIMAL
  Message from the solver:
  "Optimal"

* Candidate solution (result #1)
  Primal status      : FEASIBLE_POINT
  Dual status        : NO_SOLUTION
  Objective value    : 4//5

* Work counters


julia> value.(x)
2-element Vector{Rational{BigInt}}:
 1//5
 3//5

julia> value(c1)
1//1

julia> value(c2)
2//1

Closes #2025

Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

I think we're going to need to see a bunch of tests and examples. I don't know if I understand all the implications.

src/JuMP.jl Outdated Show resolved Hide resolved
src/callbacks.jl Show resolved Hide resolved
@blegat
Copy link
Member Author

blegat commented Jan 17, 2023

I think we're going to need to see a bunch of tests and examples. I don't know if I understand all the implications.

Yes, it's still a WIP but I opened it so that we can see that there is already one PR open with many changes all around so that we avoid making other big refactoring that will create conflicts

@blegat blegat force-pushed the bl/generic_number_type branch from 8e649b6 to ef85fe9 Compare January 26, 2023 10:19
@codecov
Copy link

codecov bot commented Jan 26, 2023

Codecov Report

Patch coverage: 99.24% and project coverage change: -0.02 ⚠️

Comparison is base (da93da0) 98.06% compared to head (41d2756) 98.05%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3191      +/-   ##
==========================================
- Coverage   98.06%   98.05%   -0.02%     
==========================================
  Files          34       34              
  Lines        4921     4981      +60     
==========================================
+ Hits         4826     4884      +58     
- Misses         95       97       +2     
Impacted Files Coverage Δ
src/copy.jl 96.19% <92.30%> (+0.03%) ⬆️
src/macros.jl 98.39% <95.00%> (-0.23%) ⬇️
src/JuMP.jl 97.31% <100.00%> (+0.05%) ⬆️
src/aff_expr.jl 97.29% <100.00%> (+0.01%) ⬆️
src/callbacks.jl 100.00% <100.00%> (ø)
src/constraints.jl 96.60% <100.00%> (+0.01%) ⬆️
src/feasibility_checker.jl 100.00% <100.00%> (ø)
src/file_formats.jl 100.00% <100.00%> (ø)
src/lp_sensitivity2.jl 97.79% <100.00%> (ø)
src/mutable_arithmetics.jl 88.34% <100.00%> (ø)
... and 9 more

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

test/test_expr.jl Outdated Show resolved Hide resolved
test/test_macros.jl Outdated Show resolved Hide resolved
Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

I haven't reviewed fully; this is a big PR. I'm a little nervous about making such a big change to JuMP at this point.

One intermediate option that I think we've discussed before is to take data as input in Float64 and to convert it to the type T before passing to the solver. Then we get the benefit of solving in higher fidelity, whilst keeping JuMP simple.

src/JuMP.jl Outdated Show resolved Hide resolved
src/copy.jl Outdated Show resolved Hide resolved
src/feasibility_checker.jl Outdated Show resolved Hide resolved
src/macros.jl Outdated Show resolved Hide resolved
test/test_expr.jl Outdated Show resolved Hide resolved
@blegat
Copy link
Member Author

blegat commented Jan 30, 2023

One intermediate option that I think we've discussed before is to take data as input in Float64 and to convert it to the type T before passing to the solver. Then we get the benefit of solving in higher fidelity, whilst keeping JuMP simple.

There are a few advantages of supporting arbitrary types at the JuMP level:

  • For callbacks, both for MIP and NLP solvers, having to convert to Float64 will give rounding errors in BigFloat and will be a complete blocker for Rational{BigInt} (rational does not make sense for NLP callbacks but it does for MIP callbacks).
  • Sometimes, you data are known to a very high precision and you would like to describe it precisely as well: this is an example https://github.com/blegat/SwitchOnSafety.jl/blob/master/examples/Finiteness_conjecture_counterexample.ipynb
  • Sometimes, you use JuMP in a loop where you get solution, then you do something with it, then modify the model (or create a new one based on the solution) and then solve again. Having to convert to Float64 in between is problematic.
  • When you solve a reational LP, it would be nice to directly get the rational solution without having to call rationalize and to be able to communicate rational coefficients without having the solver to rationalize.
  • Some bridges multiply by irrational numbers etc..., if these are done in Float64 instead of BigFloat, you already loose precision.
  • In Constraint Programming (resp. SAT), people like staying in the realm of Int (resp. Bool). Having to convert to Float64 might refrain people from these communities to use JuMP.

I'd also argue that this PR keeps JuMP simple. There are a lot of changes but it's mostly renamings from Model to GenericModel and VariableRef to GenericVariableRef. Users and even JuMP extension don't even have to do this if they don't care about non-Float64. So basically if you only care about Float64, you won't see a change.

Copy link
Member

@odow odow left a comment

Choose a reason for hiding this comment

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

Heading in the right direction. I don't really know how we're going to test this in its entirety.

test/test_constraint.jl Show resolved Hide resolved
test/test_macros.jl Outdated Show resolved Hide resolved
test/test_macros.jl Outdated Show resolved Hide resolved
test/test_feasibility_checker.jl Outdated Show resolved Hide resolved
src/variables.jl Outdated Show resolved Hide resolved
src/variables.jl Show resolved Hide resolved
src/sets.jl Outdated Show resolved Hide resolved
src/macros.jl Outdated Show resolved Hide resolved
src/macros.jl Show resolved Hide resolved
@blegat
Copy link
Member Author

blegat commented May 9, 2023

@odow Any ideas where the

 Error: reference for '`GenericModel`' could not be found in src/tutorials/getting_started/performance_tips.md.
└ @ Documenter.CrossReferences ~/.julia/packages/Documenter/wMjrb/src/utilities/utilities.jl:44
┌ Error: no doc found for reference '[`ntoh`](@ref)' in src/reference/models.md.
└ @ Documenter.CrossReferences ~/.julia/packages/Documenter/wMjrb/src/utilities/utilities.jl:44
┌ Error: no doc found for reference '[`ltoh`](@ref)' in src/reference/models.md.
└ @ Documenter.CrossReferences ~/.julia/packages/Documenter/wMjrb/src/utilities/utilities.jl:44
┌ Error: no doc found for reference '[`readchomp`](@ref)' in src/reference/models.md.
└ @ Documenter.CrossReferences ~/.julia/packages/Documenter/wMjrb/src/utilities/utilities.jl:44
┌ Error: reference for '`GenericModel`' could not be found in src/tutorials/getting_started/getting_started_with_JuMP.md.
└ @ Documenter.CrossReferences ~/.julia/packages/Documenter/wMjrb/src/utilities/utilities.jl:44

are coming from ?

@blegat blegat force-pushed the bl/generic_number_type branch from 7f9143a to 1b2bfba Compare May 9, 2023 15:52
@odow
Copy link
Member

odow commented May 9, 2023

The other errors are because of

Base.write(::IO, ::Model; ::MOI.FileFormats.FileFormat)
Base.read(::IO, ::Type{Model}; ::MOI.FileFormats.FileFormat)

in docs/src/reference.models.md. These aren't the type signatures anymore, so Documenter tries to bring in the generic Base.copy docstring.

@blegat blegat force-pushed the bl/generic_number_type branch 2 times, most recently from ef9a889 to fde1a3b Compare May 10, 2023 08:36
@blegat
Copy link
Member Author

blegat commented May 10, 2023

Thanks, all green now. Let's try to merge this now before any other PR as it has a tendency of conflicting with everything ^^
We can fix corner cases later by trying to convert more tests into extension tests

src/JuMP.jl Outdated Show resolved Hide resolved
src/constraints.jl Show resolved Hide resolved
src/constraints.jl Show resolved Hide resolved
src/constraints.jl Outdated Show resolved Hide resolved
src/constraints.jl Outdated Show resolved Hide resolved
src/macros.jl Outdated Show resolved Hide resolved
src/macros.jl Outdated Show resolved Hide resolved
src/nlp.jl Outdated Show resolved Hide resolved
src/nlp.jl Outdated Show resolved Hide resolved
src/sets.jl Outdated Show resolved Hide resolved
@odow
Copy link
Member

odow commented May 10, 2023

Here's the extension-tests.yml job: https://github.com/jump-dev/JuMP.jl/actions/runs/4941377839

@odow
Copy link
Member

odow commented May 10, 2023

The PowerModels failure seems unrelated and just a numerical issue. But this is breaking for InfiniteOpt.

@odow
Copy link
Member

odow commented May 10, 2023

Seems like InfiniteOpt also broken with #3350, so this might not be the cause.

@odow odow force-pushed the bl/generic_number_type branch from e59ea54 to 2408234 Compare May 22, 2023 22:31
con::AbstractConstraint,
name::String = "",
)
con = model_convert(model, con)
Copy link
Member

Choose a reason for hiding this comment

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

@blegat: it was sufficient to fix the extension in the docs by adding this here. This means some extra allocations though, so we should probably figure out when we don't need to model_convert objects?

@odow
Copy link
Member

odow commented May 22, 2023

New run with more packages: https://github.com/jump-dev/JuMP.jl/actions/runs/5051033339

Nothing jumps out as breaking in the extension tests. What other packages should we test? I'm still nervous for such a large change to JuMP that's hard to revert or push out in small batches.

src/feasibility_checker.jl Outdated Show resolved Hide resolved
@odow
Copy link
Member

odow commented May 22, 2023

So what if we split this up into a few different PRs?

  1. Add GenericModel and all of the simple replacements. But don't actually test or declare support for anything other than GenericModel{Float64}
  2. Add GenericVariableRef
  3. Add value_type(::Type{AbstractModel}) and update the coefficients in tests
  4. Add model_convert and add tests for arbitrary number types

Then 1, 2, and 3 are all definitely non-breaking. And we can focus on 4 as a source of the issue.

@joaquimg
Copy link
Member

What other packages should we test?

solvers like Alpine

@joaquimg
Copy link
Member

Since it is a big change, we can even consider releasing a beta version and asking in discourse for people to report.

@blegat
Copy link
Member Author

blegat commented May 23, 2023

Yes, we can definitely split the PR into a breaking and non-breaking part

@odow
Copy link
Member

odow commented May 24, 2023

Am I brave enough to try rebasing this on top of #3378?

@odow
Copy link
Member

odow commented May 25, 2023

See #3385 for a replacement.

@pulsipher
Copy link
Contributor

New run with more packages: https://github.com/jump-dev/JuMP.jl/actions/runs/5051033339

InfiniteOpt's master is now up to date and passing tests, should you want to retest.

@odow
Copy link
Member

odow commented Jun 2, 2023

Closing in favor of #3385

@odow odow closed this Jun 2, 2023
@odow odow deleted the bl/generic_number_type branch August 27, 2023 04:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Generic numeric type in JuMP
4 participants