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 standard_form modifications #1935

Merged
merged 5 commits into from
Apr 9, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
64 changes: 52 additions & 12 deletions docs/src/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,13 +547,37 @@ model with different coefficients.

### Modifying a constant term

Most often, modifications involve changing the "right-hand side" of a linear
constraint. This presents a challenge for JuMP because it leads to ambiguities.
For example, what is the right-hand side term of
`@constraint(model, 2x + 1 <= x - 3)`? This applies more generally to any
constant term in a function appearing in the objective or a constraint.
Use [`set_standard_form_rhs`](@ref) to modify the right-hand side (constant)
term of a constraint.

To avoid these ambiguities, JuMP includes the ability to *fix* variables to a
```jldoctest con_fix; setup = :(model = Model(); @variable(model, x))
julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0

julia> set_standard_form_rhs(con, 3)

julia> con
con : 2 x <= 3.0
```

!!! note
JuMP normalizes constraints into a standard form by moving all constant terms
onto the right-hand side of the constraint.
```julia
@constraint(model, 2x - 1 <= 2)
```
will be normalized to
```julia
@constraint(model, 2x <= 3)
```
[`set_standard_form_rhs`](@ref) sets the right-hand side term of the
normalized constraint.

If constraints are complicated, e.g., they are composed of a number of
components, each of which has a constant term, then it may be difficult to
calculate what the right-hand side term should be in the standard form.

For this situation, JuMP includes the ability to *fix* variables to a
value using the [`fix`](@ref) function. Fixing a variable sets its lower
and upper bound to the same value. Thus, changes in a constant term can be
simulated by adding a dummy variable and fixing it to different values. Here is
Expand All @@ -563,11 +587,13 @@ an example:
julia> @variable(model, const_term)
const_term

julia> @constraint(model, con, 2x <= const_term)
con : 2 x - const_term <= 0.0
julia> @constraint(model, con, 2x <= const_term + 1)
con : 2 x - const_term <= 1.0

julia> fix(const_term, 1.0)
```
The constraint `con` is now equivalent to `2x <= 2`.

!!! note
Even though `const_term` is fixed, it is still a decision variable. Thus,
`const_term * x` is bilinear. Fixed variables are not replaced with
Expand All @@ -576,18 +602,31 @@ julia> fix(const_term, 1.0)
### Modifying a variable coefficient

It is also possible to modify the scalar coefficients (but notably *not yet* the
quadratic coefficients) using the [`set_coefficient`](@ref) function. Here
is an example:
quadratic coefficients) using the [`set_standard_form_coefficient`](@ref)
function. Here is an example:
```jldoctest; setup = :(model = Model(); @variable(model, x))
julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0

julia> set_coefficient(con, x, 3)
julia> set_standard_form_coefficient(con, x, 3)

julia> con
con : 3 x <= 1.0
```

!!! note
JuMP normalizes constraints into a standard form by moving all terms
involving variables onto the left-hand side of the constraint.
```julia
@constraint(model, 2x <= 1 - x)
```
will be normalized to
```julia
@constraint(model, 3x <= 1)
```
[`set_standard_form_coefficient`](@ref) sets the coefficient of the
normalized constraint.

## Constraint deletion

Constraints can be deleted from a model using [`delete`](@ref). Just like
Expand Down Expand Up @@ -672,7 +711,8 @@ SecondOrderCone
RotatedSecondOrderCone
PSDCone
shadow_price
set_coefficient
set_standard_form_coefficient
set_standard_form_rhs
is_valid
JuMP.delete
LowerBoundRef
Expand Down
49 changes: 39 additions & 10 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -395,33 +395,62 @@ function add_constraint(model::Model, c::AbstractConstraint, name::String="")
end

"""
set_coefficient(constraint::ConstraintRef, variable::VariableRef, value)
set_standard_form_coefficient(constraint::ConstraintRef, variable::VariableRef, value)

Set the coefficient of `variable` in the constraint `constraint` to `value`.

Note that prior to this step, JuMP will aggregate multiple terms containing the
same variable. For example, given a constraint `2x + 3x <= 2`,
`set_coefficient(c, x, 4)` will create the constraint `4x <= 2`.

`set_standard_form_coefficient(c, x, 4)` will create the constraint `4x <= 2`.

```jldoctest; setup = :(using JuMP), filter=r"≤|<="
model = Model()
@variable(model, x)
@constraint(model, con, 2x + 3x <= 2)
set_coefficient(con, x, 4)
set_standard_form_coefficient(con, x, 4)
con

# output

con : 4 x <= 2.0
```
"""
function set_coefficient(constraint::ConstraintRef{Model, _MOICON{F, S}},
variable, value) where {S, T, F <: Union{
MOI.ScalarAffineFunction{T},
MOI.ScalarQuadraticFunction{T}}}
MOI.modify(backend(constraint.model), index(constraint),
MOI.ScalarCoefficientChange(index(variable), convert(T, value)))
function set_standard_form_coefficient(
constraint::ConstraintRef{Model, _MOICON{F, S}}, variable, value
) where {S, T, F <: Union{MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}}
MOI.modify(backend(owner_model(constraint)), index(constraint),
MOI.ScalarCoefficientChange(index(variable), convert(T, value)))
return
end
@deprecate set_coefficient set_standard_form_coefficient

"""
set_standard_form_rhs(constraint::ConstraintRef, value)

Set the right-hand side term of `constraint` to `value`.

Note that prior to this step, JuMP will aggregate all constant terms onto the
right-hand side of the cosntraint. For example, given a constraint `2x + 1 <=
2`, `set_standard_form_rhs(c, 4)` will create the constraint `2x <= 4`, not `2x +
1 <= 4`.

```jldoctest; setup = :(using JuMP; model = Model(); @variable(model, x)), filter=r"≤|<="
julia> @constraint(model, con, 2x + 1 <= 2)
con : 2 x <= 1.0

julia> set_standard_form_rhs(con, 4)

julia> con
con : 2 x <= 4.0
```
"""
function set_standard_form_rhs(
constraint::ConstraintRef{Model, _MOICON{F, S}}, value) where {
T,
S <: Union{MOI.LessThan{T}, MOI.GreaterThan{T}, MOI.EqualTo{T}},
F <: Union{MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}}
MOI.set(owner_model(constraint), MOI.ConstraintSet(), constraint,
S(convert(T, value)))
return
end

Expand Down
23 changes: 21 additions & 2 deletions test/constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -470,13 +470,32 @@ end
con_ref = @constraint(model, 2 * x == -1)
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.func == 2 * x
JuMP.set_coefficient(con_ref, x, 1.0)
JuMP.set_standard_form_coefficient(con_ref, x, 1.0)
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.func == 1 * x
JuMP.set_coefficient(con_ref, x, 3) # Check type promotion.
JuMP.set_standard_form_coefficient(con_ref, x, 3) # Check type promotion.
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.func == 3 * x
end

@testset "Change rhs" begin
model = JuMP.Model()
x = @variable(model)
con_ref = @constraint(model, 2 * x <= 1)
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.set == MOI.LessThan(1.0)
JuMP.set_standard_form_rhs(con_ref, 2.0)
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.set == MOI.LessThan(2.0)
con_ref = @constraint(model, 2 * x - 1 == 1)
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.set == MOI.EqualTo(2.0)
JuMP.set_standard_form_rhs(con_ref, 3)
con_obj = JuMP.constraint_object(con_ref)
@test con_obj.set == MOI.EqualTo(3.0)
con_ref = @constraint(model, 0 <= 2 * x <= 1)
@test_throws MethodError JuMP.set_standard_form_rhs(con_ref, 3)
end
end

function test_shadow_price(model_string, constraint_dual, constraint_shadow)
Expand Down