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 subexpression kwarg to expression macro #3878

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -474,3 +474,30 @@ for file in readdir(joinpath(@__DIR__, "macros"))
include(joinpath(@__DIR__, "macros", file))
end
end

# These methods must come after the macros are included, because they use both
# `@variable` and `@constraint`.

function _build_subexpression(
::Function,
model::AbstractModel,
expr::AbstractJuMPScalar,
name::String,
)
y = @variable(model)
set_name(y, name)
@constraint(model, y == expr)
return y
Copy link
Member Author

Choose a reason for hiding this comment

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

Suggested change
return y
return y, expr

This would enable

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> @expression(model, ex, sin(x), subexpression = true)
(ex, sin(x))

julia> ex
(ex, sin(x))

julia> model[:ex]
(ex, sin(x))

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[1:2])
2-element Vector{VariableRef}:
 x[1]
 x[2]

julia> @expression(model, ex[i in 1:2], sin(x[i]), subexpression = true)
2-element Vector{Tuple{VariableRef, NonlinearExpr}}:
 (ex[1], sin(x[1]))
 (ex[2], sin(x[2]))

julia> first.(ex)
2-element Vector{VariableRef}:
 ex[1]
 ex[2]

julia> last.(ex)
2-element Vector{NonlinearExpr}:
 sin(x[1])
 sin(x[2])

which seems quite nice.

end

function _build_subexpression(
::Function,
model::AbstractModel,
expr::Array{<:AbstractJuMPScalar},
name::String,
)
y = [@variable(model) for _ in expr]
set_name.(y, name)
@constraint(model, y .== expr)
return y
end
21 changes: 19 additions & 2 deletions src/macros/@expression.jl
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,30 @@ macro expression(input_args...)
error_fn,
input_args;
num_positional_args = 2:3,
valid_kwargs = [:container],
valid_kwargs = [:container, :subexpression],
)
if Meta.isexpr(args[2], :block)
error_fn("Invalid syntax. Did you mean to use `@expressions`?")
end
is_subexpression = get(kwargs, :subexpression, false)
name_expr = length(args) == 3 ? args[2] : nothing
name, index_vars, indices = Containers.parse_ref_sets(
error_fn,
name_expr;
invalid_index_variables = [args[1]],
)
name_expr = Containers.build_name_expr(name, index_vars, kwargs)
model = esc(args[1])
expr, build_code = _rewrite_expression(args[end])
code = quote
$build_code
# Don't leak a `_MA.Zero` if the expression is an empty summation, or
# other structure that returns `_MA.Zero()`.
_replace_zero($model, $expr)
if $is_subexpression
_build_subexpression($error_fn, $model, $expr, $name_expr)
else
_replace_zero($model, $expr)
end
end
return _finalize_macro(
model,
Expand All @@ -97,6 +103,17 @@ macro expression(input_args...)
)
end

function _build_subexpression(
error_fn::Function,
::AbstractModel,
expr::Any,
::String,
)
return error_fn(
"Unable to build a subexpression for the type $(typeof(expr))",
)
end

"""
@expressions(model, args...)

Expand Down
88 changes: 88 additions & 0 deletions test/test_macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2487,4 +2487,92 @@ function test_array_scalar_sets()
return
end

function test_subexpression_kwarg()
model = Model()
@variable(model, x)
@expression(model, ex, sin(x), subexpression = true)
@test ex isa VariableRef
@test model[:ex] isa VariableRef
@test model[:ex] === ex
@test occursin(r"ex - sin\(x\) ==? 0", sprint(print, model))
@test num_variables(model) == 2
return
end

function test_subexpression_kwarg_array()
model = Model()
@variable(model, x[1:2])
@expression(model, ex[i in 1:2], sin(x[i]), subexpression = true)
@test ex isa Vector{VariableRef}
@test model[:ex] === ex
@test occursin(r"ex\[1\] - sin\(x\[1\]\) ==? 0", sprint(print, model))
@test occursin(r"ex\[2\] - sin\(x\[2\]\) ==? 0", sprint(print, model))
@test num_variables(model) == 4
return
end

function test_subexpression_kwarg_dense_axis_array()
model = Model()
@variable(model, x[2:3])
@expression(model, ex[i in 2:3], sin(x[i]), subexpression = true)
@test ex isa Containers.DenseAxisArray{VariableRef}
@test model[:ex] === ex
@test occursin(r"ex\[2\] - sin\(x\[2\]\) ==? 0", sprint(print, model))
@test occursin(r"ex\[3\] - sin\(x\[3\]\) ==? 0", sprint(print, model))
@test num_variables(model) == 4
return
end

function test_subexpression_kwarg_dense_axis_array()
model = Model()
@variable(model, x[i in 1:3; isodd(i)])
@expression(model, ex[i in 1:3; isodd(i)], sin(x[i]), subexpression = true)
@test ex isa Containers.SparseAxisArray{VariableRef}
@test model[:ex] === ex
@test occursin(r"ex\[1\] - sin\(x\[1\]\) ==? 0", sprint(print, model))
@test occursin(r"ex\[3\] - sin\(x\[3\]\) ==? 0", sprint(print, model))
@test num_variables(model) == 4
return
end

function test_subexpression_kwarg_vector_element()
model = Model()
@variable(model, x[i in 1:2])
@expression(model, ex, sin.(x), subexpression = true)
@test ex isa Vector{VariableRef}
@test model[:ex] === ex
@test occursin(r"ex - sin\(x\[1\]\) ==? 0", sprint(print, model))
@test occursin(r"ex - sin\(x\[2\]\) ==? 0", sprint(print, model))
@test num_variables(model) == 4
return
end

function test_subexpression_kwarg_no_name()
model = Model()
@variable(model, x)
ex = @expression(model, sin(x), subexpression = true)
@test ex isa VariableRef
@test !haskey(model, :ex)
@test occursin(r"\_\[2\] - sin\(x\) ==? 0", sprint(print, model))
@test num_variables(model) == 2
return
end

function test_subexpression_kwarg_dict_element()
model = Model()
@variable(model, x[i in 1:2])
@test_throws_runtime(
ErrorException(
"In `@expression(model, ex, Dict((i => x[i] for i = 1:2)), subexpression = true)`: Unable to build a subexpression for the type $(Dict{Int,VariableRef})",
),
@expression(
model,
ex,
Dict(i => x[i] for i in 1:2),
subexpression = true,
),
)
return
end

end # module
Loading