-
-
Notifications
You must be signed in to change notification settings - Fork 399
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
Macro to update expression in-place #3931
Comments
There is already julia> x = 1
1
julia> @expression(model, x += 2)
ERROR: UndefVarError: `x` not defined
Stacktrace:
[1] macro expansion
@ ~/.julia/dev/MutableArithmetics/src/rewrite.jl:376 [inlined]
[2] macro expansion
@ ~/.julia/packages/JuMP/CU7H5/src/macros.jl:264 [inlined]
[3] macro expansion
@ ~/.julia/packages/JuMP/CU7H5/src/macros/@expression.jl:86 [inlined]
[4] macro expansion
@ ~/.julia/packages/JuMP/CU7H5/src/macros.jl:400 [inlined]
[5] top-level scope |
But also, |
@expression(EP, ePowerBalance_VRE_STOR[t = 1:T, z = 1:Z], JuMP.AffExpr())
for t in 1:T, z in 1:Z
if !isempty(resources_in_zone_by_rid(gen_VRE_STOR, z))
ePowerBalance_VRE_STOR[t, z] += sum(EP[:vP][y, t]
for y in resources_in_zone_by_rid(gen_VRE_STOR,
z))
end
end should be @expression(EP, ePowerBalance_VRE_STOR[t = 1:T, z = 1:Z], zero(JuMP.AffExpr))
for z in 1:Z
resources = resources_in_zone_by_rid(gen_VRE_STOR, z)
if !isempty(resources)
vP = EP[:vP]
for t in 1:T
ePowerBalance_VRE_STOR[t, z] =
@expression(EP, sum(vP[y, t] for y in resources))
end
end
end |
I don't know that other macro would have helped. It's things like "don't compute |
We can't make julia> a = BigInt(1)
1
julia> b = a
1
julia> b += BigInt(2)
3
julia> a
1
|
Here was my diff to MA diff --git a/src/rewrite_generic.jl b/src/rewrite_generic.jl
index de2234f..a8c1477 100644
--- a/src/rewrite_generic.jl
+++ b/src/rewrite_generic.jl
@@ -101,6 +101,22 @@ function _rewrite_generic(stack::Expr, expr::Expr)
root = gensym()
push!(stack.args, :($root = $if_expr))
return root, is_mutable
+ elseif Meta.isexpr(expr, :+=, 2)
+ # x += y -> operate!!(add_mul, x, y)
+ y = expr.args[2]
+ rhs = Expr(:call, operate!!, add_mul, esc(expr.args[1]))
+ if Meta.isexpr(y, :call) && y.args[1] == :*
+ for i in 2:length(y.args)
+ arg_root, _ = _rewrite_generic(stack, y.args[i])
+ push!(rhs.args, arg_root)
+ end
+ else
+ y_root, _ = _rewrite_generic(stack, y)
+ push!(rhs.args, y_root)
+ end
+ root = gensym()
+ push!(stack.args, :($root = $rhs))
+ return root, true
elseif !Meta.isexpr(expr, :call)
# In situations like `x[i]`, we do not attempt to rewrite. Return `expr`
# and don't let future callers mutate. julia> @macroexpand @expression(model, y += x)
quote
#= REPL[20]:1 =#
JuMP._valid_model(model, :model)
begin
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:399 =#
let model = model
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:400 =#
begin
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros/@expression.jl:86 =#
begin
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:264 =#
var"#112###246" = begin
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:374 =#
let
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:375 =#
begin
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:371 =#
var"#113###247" = (MutableArithmetics.operate!!)(MutableArithmetics.add_mul, y, x)
end
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:376 =#
var"#113###247"
end
end
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:265 =#
var"#114###248" = (JuMP.flatten!)(var"#112###246")
end
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros/@expression.jl:89 =#
JuMP._replace_zero(model, var"#114###248")
end
end
end
end
julia> @macroexpand @expression(model, y += 2 * x)
quote
#= REPL[21]:1 =#
JuMP._valid_model(model, :model)
begin
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:399 =#
let model = model
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:400 =#
begin
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros/@expression.jl:86 =#
begin
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:264 =#
var"#120###249" = begin
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:374 =#
let
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:375 =#
begin
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:371 =#
var"#121###250" = (MutableArithmetics.operate!!)(MutableArithmetics.add_mul, y, 2, x)
end
#= /Users/oscar/.julia/dev/MutableArithmetics/src/rewrite.jl:376 =#
var"#121###250"
end
end
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros.jl:265 =#
var"#122###251" = (JuMP.flatten!)(var"#120###249")
end
#= /Users/oscar/.julia/packages/JuMP/CU7H5/src/macros/@expression.jl:89 =#
JuMP._replace_zero(model, var"#122###251")
end
end
end
end |
I think this is another issue that is really asking for a way to accurately profile where time is spent in JuMP. The issue is that the Julia profiler doesn't really help if you have lots of dispersed See GenXProject/GenX.jl#815 (comment), where half the remaining time is GC. @joaquimg have you tried profiling allocations instead of time? |
More details about GenX is what I am currently looking at.
yup I have been checking for improvements by looking at allocations reduction, so we can reduce GC pressure. Still thinking about the best way to report. |
I dont fully get it. The example is showing that b is assigned to a new expression, which is the same as in JuMP: Using in JuMP: Your example reads the same: julia> a = 0 * x + 1
1
julia> b = a
1
julia> b += 2 # new expression created
3
julia> a
1 and julia> a = 0 * x + 1
1
julia> b = a
1
julia> add_to_expression!(b, 2)
3
julia> a
3 so I would expect: julia> a = 0 * x + 1
1
julia> b = a
1
julia> @expression(model, b += 2) # in-place
3
julia> a
3 On the other hand, if the argument is coming from: "macro never modify their inputs (except model)", then I understand. In which case another macro would make sense: |
|
Also, analogous to julia> a = [1]
1-element Vector{Int64}:
1
julia> b = a
1-element Vector{Int64}:
1
julia> @. b += [2]
1-element Vector{Int64}:
3
julia> a
1-element Vector{Int64}:
3 |
Thanks! I have been using PProf allocation tracker. I will check is there are other cool stuff there. |
The rewrite macros shouldn't change the semantics of the code that runs. They should strictly be a performance improvement only. That's why we're so careful to track which things are mutable (meaning we created the object, and can thus change it without worrying that the user holds a reference to it).
Ah now this is very subtle. Because you are updating the elements of |
This is a fair point, it is the JuMP rule. So I agree we should not do Still, a new macro might make sense. |
You mean this macro: julia> using JuMP
julia> macro add_to(expr)
@assert Meta.isexpr(expr, :+=)
x, y = expr.args
ret = Expr(:call, JuMP.add_to_expression!, x)
if Meta.isexpr(y, :call) && y.args[1] == :*
append!(ret.args, y.args[2:end])
else
push!(ret.args, y)
end
return esc(ret)
end
@add_to (macro with 1 method)
julia> model = Model()
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none
julia> @variable(model, x)
x
julia> y = zero(AffExpr)
0
julia> @macroexpand @add_to y += 2 * x
:((JuMP.add_to_expression!)(y, 2, x))
julia> @macroexpand @add_to y += x
:((JuMP.add_to_expression!)(y, x))
julia> @macroexpand @add_to y += x / 2
:((JuMP.add_to_expression!)(y, x / 2)) This doesn't seem like it offers much benefit over the functional form. I think the bar for adding a new macro should really be: can this be achieved any other way? So far, our only utility macro is |
Besides: @add_to y += 2 * x A more sophisticated @add_to y += sum(x[i] for i in 1:N) sure you could: add_to_expression!(y, @expression(model, sum(x[i] for i in 1:N))) but you would be potentially allocating a massive array and then throwing it away. |
We don't have access to I think just no. I'm strongly against this. # Equivalent to `y += 2 * x`, but it modifies `y` in-place without allocating.
add_to_expression!(y, 2, x) |
Recording my latest idea here: @add_to_expression!(y, 2 * x)
@add_to_expression!(y, sum(x[i] for i in 1:N)) |
There doesn't seem much benefit compared to add_to_expression!(y, 2, x)
Okay, I understand the motivation for this one, but still: expr = @expression(model, sum(x[i] for i in 1:N))
add_to_expression!(y, expr) I tend to think that people writing |
I agree,
I agree in part.
|
Then do: for i in 1:N
add_to_expression!(y, x[i])
end
Yip. See p131 of https://www.osti.gov/servlets/purl/1771935 We could also just document |
The concrete proposal for discussion is a suggestion to add some variation of this comment: julia> using JuMP
julia> macro add_to(expr)
@assert Meta.isexpr(expr, :+=)
x, y = expr.args
ret = Expr(:call, JuMP.add_to_expression!, x)
if Meta.isexpr(y, :call) && y.args[1] == :*
append!(ret.args, y.args[2:end])
else
push!(ret.args, y)
end
return esc(ret)
end
@add_to (macro with 1 method)
julia> model = Model()
A JuMP Model
├ solver: none
├ objective_sense: FEASIBILITY_SENSE
├ num_variables: 0
├ num_constraints: 0
└ Names registered in the model: none
julia> @variable(model, x)
x
julia> y = zero(AffExpr)
0
julia> @macroexpand @add_to y += 2 * x
:((JuMP.add_to_expression!)(y, 2, x))
julia> @macroexpand @add_to y += x
:((JuMP.add_to_expression!)(y, x))
julia> @macroexpand @add_to y += x / 2
:((JuMP.add_to_expression!)(y, x / 2)) Pros:
Cons:
|
Are very common operations for JuMP users.
add_to_expression!
is there to solve the performance issue here.However, should we consider a macro to properly rewrite and keep readability?
It could also be
@update
,@operate
etcThis pattern is heavily used here: https://github.com/GenXProject/GenX.jl/blob/main/src/model/resources/vre_stor/vre_stor.jl
The text was updated successfully, but these errors were encountered: