-
-
Notifications
You must be signed in to change notification settings - Fork 398
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 modeling with SI units #1350
Comments
You can do this by extending the using JuMP
using Unitful
m = Model()
function unitvar(vref::JuMP.VariableRef, u::Unitful.FreeUnits)
JuMP.GenericAffExpr(0.0 * u, vref => 1.0 * u)
end
struct Unit
u::Unitful.FreeUnits
end
struct UnitVariable <: JuMP.AbstractVariable
v::JuMP.ScalarVariable
u::Unitful.FreeUnits
end
function JuMP.buildvariable(_error::Function, info::JuMP.VariableInfo, u::Unit)
UnitVariable(JuMP.ScalarVariable(info), u.u)
end
function JuMP.addvariable(m::Model, v::UnitVariable, name::String="")
vref = JuMP.addvariable(m, v.v, name)
# add bridge to `m.moibackend` if its does not support constraints with Unitful coefficients
unitvar(vref, v.u)
end
@variable m v Unit(u"m")
@show v + 2v + 1u"m" # -> 3.0 m v + 1.0 m To support adding constraints such as Both the new constraint bridge and the By the way this could be a nice example for the extensions doc don't you think ? |
Yes. This is ridiculous. Hooray for a good abstraction. |
Could be fun to try with https://github.com/IainNZ/RationalSimplex.jl too. |
Pyomo has this: https://pyomo.readthedocs.io/en/stable/advanced_topics/units_container.html It came up yesterday in a discussion with @zolanaj. It's particularly useful if you have a large model with lots of different physical units, and you want to avoid issues like adding meters and millimeters. |
I have been playing around with the above ideas to combine JuMP and Unitful. My primary goal was to use units in combination with the I have made a proof-of-concept package: https://github.com/trulsf/UnitJuMP.jl It runs on simple examples, but is in no way robust or complete. I would be grateful if anyone with a better grasp of JuMP and its inner workings, could comment upon the overall approach. Is it a viable way of approaching the problem? Or would it be better approach to change JuMP to better accomodate units in Here is a simple example of its usage: using UnitJuMP, GLPK
m = Model(GLPK.Optimizer)
@variable(m, x >= 0, u"m/s")
@variable(m, y >= 0, u"ft/s")
max_speed = 60u"km/hr"
@constraint(m, x + y <= max_speed)
@constraint(m, x <= 0.5y)
obj = @objective(m, Max, x + y)
optimize!(m)
println("x = $(value(x)) y = $(value(y))")
#output x = 5.555555555555556 m s^-1 y = 36.45377661125693 ft s^-1 |
Nice! I'm traveling for the next week, but I will take a look when I get a
chance. If it's been 2 weeks and I haven't replied, please ping me as a
reminder.
…On Thu, 13 Jan 2022, 8:52 AM Truls Flatberg, ***@***.***> wrote:
I have been playing around with the above ideas to combine JuMP and
Unitful. My primary goal was to use units in combination with the
@variable and @constraint macros for linear constraints. I started out
with the approach suggested by @blegat <https://github.com/blegat>, but I
found it hard to play nicely with the @rewrite macro. Instead I have
tried an approach where constraints are handled similar to variables and
are bundled with a unit. Being a fairly newcomer to both Julia and JuMP, I
have not made a deep dive into the details of MutableArithmetics, and have
only implemented limited support for the interface for expressions
involving units.
I have made a proof-of-concept package:
https://github.com/trulsf/UnitJuMP.jl
It runs on simple examples, but is in no way robust or complete. I would
be grateful if anyone with a better grasp of JuMP and its inner workings,
could comment upon the overall approach. Is it a viable way of approaching
the problem? Or would it be better approach to change JuMP to better
accomodate units in GenericAffExpr?
Here is a simple example of its usage:
using UnitJuMP, GLPK
m = Model(GLPK.Optimizer)
@variable(m, x >= 0, ***@***.***(m, y >= 0, u"ft/s")
max_speed = 60u"km/hr"
@constraint(m, x + y <= ***@***.***(m, x <= 0.5y)
obj = @objective(m, Max, x + y)
optimize!(m)
println("x = $(value(x)) y = $(value(y))")
#output x = 5.555555555555556 m s^-1 y = 36.45377661125693 ft s^-1
—
Reply to this email directly, view it on GitHub
<#1350 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AB6MQJK4YPNENDDDFQNSZKTUVXLZLANCNFSM4FFTAHUQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
You are receiving this because you commented.Message ID: <jump-dev/JuMP.
***@***.***>
|
I checked the implementation of UnitJuMP.jl, it looks very good. One small comment on performance: the types of the fields of structure should be concrete, |
Thanks for the good feedback.
which should be doable, but some work. In addition, there are also problems with other operators like |
What do you mean ? How would it make sense to add affine expressions of different units ? |
I want e..g. to combine variables of different units in the same constraint (there may e.g. be more natural units for different variables, typicially for our use is different scaling like kW and MW used for different technologies) . Returning to your first implementation above, I would want to support
that will now error due to v and w having different units. On the other hand, the following works (using the automatic promotion of Unitful)
I can make some progress by allowing adding
and continue with the same for the various It is probably possible for one more familiar with the internals of JuMP and MathOptInterface to fix things, but I think it will be too time consuming for me at this point. Having a separate UnitAffExpr to work with makes it easier for me to experiment with different solutions and I think I will continue a bit along that road just to learn a bit more before potentially diving into the details of JuMP/MathOptInterface/MutableArithmetics. |
Sounds good, just wanted to give some advice but feel free to experiment in any direction! |
This is very cool! I like the use of a JuMP extension. The printing of the units beside each constraint is very slick. julia> m = Model(GLPK.Optimizer)
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: EMPTY_OPTIMIZER
Solver name: GLPK
julia> @variable(m, x >= 0, u"m/s")
x m s⁻¹
julia> @variable(m, y >= 0, u"ft/s")
y ft s⁻¹
julia> max_speed = 60u"km/hr"
60 km hr⁻¹
julia> @constraint(m, x + y <= max_speed)
x + 0.3048 y ≤ 16.666666666666668 [m s⁻¹]
julia> @constraint(m, x <= 0.5y)
x - 0.1524 y ≤ 0.0 [m s⁻¹]
julia> obj = @objective(m, Max, x + y)
x + 0.3048 y m s⁻¹
julia> optimize!(m)
julia> println("x = $(value(x)) y = $(value(y))")
x = 5.555555555555556 m s⁻¹ y = 36.45377661125693 ft s⁻¹
One way to simplify everything (and probably reduce the likelihood of bugs) is to not allow this. Instead, force the user to convert their units manually. So instead of julia> @expression(m, x + y)
x + 0.3048 y m s⁻¹ you would go
Automatic promotion seems useful, but it's likely to just complicate things. (It's too easy to accidentally type something weird and have the units all match, but differ from what was intended.) We should throw an error if the user provides mis-matching types. I think this is what the Pyomo package does as well. |
Thanks for the feedback! julia> @variable(m, x>=0, u"m")
x m
julia> @variable(m, y>=0, u"s")
y s
julia> x + y
ERROR: DimensionError: m and s are not dimensionally compatible. In some cases it is more convenient to have some variables in their natural units, e.g. in an energy model it can be that energy input is given in kcal while the energy requirements is based on mechanical work in kJ. I have pushed an update that implements most operators for handling linear expressions with units, e.g. julia> expr = x - 3u"km/hr"*y + 1.5u"km"
x - 0.8333333333333334 y + 1500 [m] |
Perhaps mixing units is okay. I wonder if we can use the tagging feature of constraints as well, which would allow: max_speed = 1u"km/h"
@constraint(model, 2x + y <= max_speed, u"m/s") That would give a little more control over what the final units are! |
There is already optional support for this through using max_speed = 1u"km/h"
@constraint(model, 2x + y <= max_speed, unit=u"m/s") I was not aware it was possible to manage it by tagging it along with the syntax you described. Any tips on how that could be done? |
You can pass positional arguments to Instead of the |
Thanks for the tip! Worked as a charm. I'll update with the new syntax option. |
I was convinced by @trulsf's JuMP-dev talk (https://www.youtube.com/watch?v=JQ6_LZfYRqg) that this functionality should ideally be incorporated into JuMP. |
An open question is whether we should vendor some form of UnitJuMP in as a JuMPExtension within JuMP, or whether we should extend The former is probably easier, although it might make it hard for other JuMP extensions to use units. The latter introduces a whole slew of problems, including the need to have a non-concrete field in |
This is potentially a good candidate for the new We could move UnitJuMP to |
Seems like a good solution. I am currently trying to add support for quadratic expressions into UnitJuMP and a rudimentary first version is available in the 'quadratic' branch. The approach used led to a lot of cases needed to be covered (quadratic expressions with units could arise from a lot of different combinations of variables, unit variables and quantities) and is not yet complete. I think there is room for a lot of improvement and any tips for possible refactoring/design changes are welcome. |
Something that has been mentioned before in passing and might be worth discussing down the track: allow modeling with SI units using https://github.com/ajkeller34/Unitful.jl. It may be fairly trivial, I'm not sure.
DifferentialEquations.jl seems to allow this, for example here.
GPKit is a geometric programming toolkit in Python and it supports specification of units and automatically does dimensional analysis to convert or verify units in expressions/constraints. From the GPKit docs, an example of a variable declaration:
and a simple model:
(There are other ideas we could borrow from GPKit to make JuMP even more user friendly. I like the little solution tables it can print after a solve.)
The text was updated successfully, but these errors were encountered: