Skip to content

Commit

Permalink
Merge pull request #1960 from JuliaOpt/bl/add_to_function_constant
Browse files Browse the repository at this point in the history
standard_form -> normalized + add_to_function_constant
  • Loading branch information
blegat committed Aug 23, 2019
2 parents be85238 + 48822b5 commit 4ef4c10
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 50 deletions.
59 changes: 44 additions & 15 deletions docs/src/constraints.md
Expand Up @@ -3,7 +3,7 @@ CurrentModule = JuMP
DocTestSetup = quote
using JuMP
end
DocTestFilters = [r"≤|<=", r"≥|>=", r" == | = ", r" ∈ | in "]
DocTestFilters = [r"≤|<=", r"≥|>=", r" == | = ", r" ∈ | in ", r"MathOptInterface|MOI"]
```

# Constraints
Expand Down Expand Up @@ -547,20 +547,20 @@ model with different coefficients.

### Modifying a constant term

Use [`set_standard_form_rhs`](@ref) to modify the right-hand side (constant)
term of a constraint. Use [`standard_form_rhs`](@ref) to query the right-hand
Use [`set_normalized_rhs`](@ref) to modify the right-hand side (constant)
term of a constraint. Use [`normalized_rhs`](@ref) to query the right-hand
side term.

```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> set_normalized_rhs(con, 3)
julia> con
con : 2 x <= 3.0
julia> standard_form_rhs(con)
julia> normalized_rhs(con)
3.0
```

Expand All @@ -574,7 +574,7 @@ julia> standard_form_rhs(con)
```julia
@constraint(model, 2x <= 3)
```
[`set_standard_form_rhs`](@ref) sets the right-hand side term of the
[`set_normalized_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
Expand Down Expand Up @@ -603,22 +603,50 @@ The constraint `con` is now equivalent to `2x <= 2`.
`const_term * x` is bilinear. Fixed variables are not replaced with
constants when communicating the problem to a solver.

Another option is to use [`add_to_function_constant`](@ref). The constant given
is added to the function of a `func`-in-`set` constraint. In the following
example, adding `2` to the function has the effect of removing `2` to the
right-hand side:
```jldoctest con_add; setup = :(model = Model(); @variable(model, x))
julia> @constraint(model, con, 2x <= 1)
con : 2 x <= 1.0
julia> add_to_function_constant(con, 2)
julia> con
con : 2 x <= -1.0
julia> normalized_rhs(con)
-1.0
```

In the case of interval constraints, the constant is removed in each bounds.
```jldoctest con_add_interval; setup = :(model = Model(); @variable(model, x))
julia> @constraint(model, con, 0 <= 2x + 1 <= 2)
con : 2 x ∈ [-1.0, 1.0]
julia> add_to_function_constant(con, 3)
julia> con
con : 2 x ∈ [-4.0, -2.0]
```

### Modifying a variable coefficient

To modify the coefficients for a linear term in a constraint (but
notably not yet the coefficients on a quadratic term), use
[`set_standard_form_coefficient`](@ref). To query
the current coefficient, use [`standard_form_coefficient`](@ref).
[`set_normalized_coefficient`](@ref). To query
the current coefficient, use [`normalized_coefficient`](@ref).
```jldoctest; setup = :(model = Model(); @variable(model, x[1:2]))
julia> @constraint(model, con, 2x[1] + x[2] <= 1)
con : 2 x[1] + x[2] ≤ 1.0
julia> set_standard_form_coefficient(con, x[2], 0)
julia> set_normalized_coefficient(con, x[2], 0)
julia> con
con : 2 x[1] ≤ 1.0
julia> standard_form_coefficient(con, x[2])
julia> normalized_coefficient(con, x[2])
0.0
```

Expand All @@ -632,7 +660,7 @@ julia> standard_form_coefficient(con, x[2])
```julia
@constraint(model, 3x <= 1)
```
[`set_standard_form_coefficient`](@ref) sets the coefficient of the
[`set_normalized_coefficient`](@ref) sets the coefficient of the
normalized constraint.

## Constraint deletion
Expand Down Expand Up @@ -719,10 +747,11 @@ SecondOrderCone
RotatedSecondOrderCone
PSDCone
shadow_price
standard_form_coefficient
set_standard_form_coefficient
standard_form_rhs
set_standard_form_rhs
normalized_coefficient
set_normalized_coefficient
normalized_rhs
set_normalized_rhs
add_to_function_constant
is_valid
JuMP.delete
LowerBoundRef
Expand Down
2 changes: 1 addition & 1 deletion docs/src/solutions.md
Expand Up @@ -125,7 +125,7 @@ end

Given an LP problem and an optimal solution corresponding to a basis, we can
question how much an objective coefficient or standard form rhs coefficient
(c.f., [`standard_form_rhs`](@ref)) can change without violating primal or dual
(c.f., [`normalized_rhs`](@ref)) can change without violating primal or dual
feasibility of the basic solution. Note that not all solvers computes the basis
and the sensitivity analysis requires that the solver interface implements
`MOI.ConstraintBasisStatus`.
Expand Down
99 changes: 80 additions & 19 deletions src/constraints.jl
Expand Up @@ -395,70 +395,70 @@ function add_constraint(model::Model, con::AbstractConstraint, name::String="")
end

"""
set_standard_form_coefficient(con_ref::ConstraintRef, variable::VariableRef, value)
set_normalized_coefficient(con_ref::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_standard_form_coefficient(con, x, 4)` will create the constraint `4x <= 2`.
`set_normalized_coefficient(con, 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_standard_form_coefficient(con, x, 4)
set_normalized_coefficient(con, x, 4)
con
# output
con : 4 x <= 2.0
```
"""
function set_standard_form_coefficient(
function set_normalized_coefficient(
con_ref::ConstraintRef{Model, _MOICON{F, S}}, variable, value
) where {S, T, F <: Union{MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}}
MOI.modify(backend(owner_model(con_ref)), index(con_ref),
MOI.ScalarCoefficientChange(index(variable), convert(T, value)))
return
end
@deprecate set_coefficient set_standard_form_coefficient
@deprecate set_coefficient set_normalized_coefficient

"""
standard_form_coefficient(con_ref::ConstraintRef, variable::VariableRef)
normalized_coefficient(con_ref::ConstraintRef, variable::VariableRef)
Return the coefficient associated with `variable` in `constraint` after JuMP has
normalized the constraint into its standard form. See also
[`set_standard_form_coefficient`](@ref).
[`set_normalized_coefficient`](@ref).
"""
function standard_form_coefficient(
function normalized_coefficient(
con_ref::ConstraintRef{Model, _MOICON{F, S}}, variable
) where {S, T, F <: Union{MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}}
con = JuMP.constraint_object(con_ref)
return _affine_coefficient(con.func, variable)
end

"""
set_standard_form_rhs(con_ref::ConstraintRef, value)
set_normalized_rhs(con_ref::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 constraint. For example, given a constraint `2x + 1 <=
2`, `set_standard_form_rhs(con, 4)` will create the constraint `2x <= 4`, not `2x +
2`, `set_normalized_rhs(con, 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> set_normalized_rhs(con, 4)
julia> con
con : 2 x <= 4.0
```
"""
function set_standard_form_rhs(
function set_normalized_rhs(
con_ref::ConstraintRef{Model, _MOICON{F, S}}, value) where {
T,
S <: Union{MOI.LessThan{T}, MOI.GreaterThan{T}, MOI.EqualTo{T}},
Expand All @@ -469,20 +469,81 @@ function set_standard_form_rhs(
end

"""
standard_form_rhs(con_ref::ConstraintRef)
normalized_rhs(con_ref::ConstraintRef)
Return the right-hand side term of the constraint after JuMP has converted it
into its standard form. See also [`set_standard_form_rhs`](@ref).
Return the right-hand side term of `con_ref` after JuMP has converted the
constraint into its normalized form. See also [`set_normalized_rhs`](@ref).
"""
function standard_form_rhs(
con_ref::ConstraintRef{Model, _MOICON{F, S}}) where {
T,
S <: Union{MOI.LessThan{T}, MOI.GreaterThan{T}, MOI.EqualTo{T}},
function normalized_rhs(con_ref::ConstraintRef{Model, _MOICON{F, S}}) where {
T, S <: Union{MOI.LessThan{T}, MOI.GreaterThan{T}, MOI.EqualTo{T}},
F <: Union{MOI.ScalarAffineFunction{T}, MOI.ScalarQuadraticFunction{T}}}
con = constraint_object(con_ref)
return MOI.constant(con.set)
end

function moi_add_to_function_constant(
model::MOI.ModelLike,
ci::MOI.ConstraintIndex{<:MOI.AbstractScalarFunction,
<:MOI.AbstractScalarSet},
value)
set = MOI.get(model, MOI.ConstraintSet(), ci)
new_set = MOIU.shift_constant(set, convert(Float64, -value))
MOI.set(model, MOI.ConstraintSet(), ci, new_set)
end
function moi_add_to_function_constant(
model::MOI.ModelLike,
ci::MOI.ConstraintIndex{<:Union{MOI.VectorAffineFunction,
MOI.VectorQuadraticFunction},
<:MOI.AbstractVectorSet},
value)
func = MOI.get(model, MOI.ConstraintFunction(), ci)
new_constant = value + MOI.constant(func)
MOI.modify(model, ci, MOI.VectorConstantChange(new_constant))
end

"""
add_to_function_constant(constraint::ConstraintRef, value)
Add `value` to the function constant term.
Note that for scalar constraints, JuMP will aggregate all constant terms onto the
right-hand side of the constraint so instead of modifying the function, the set
will be translated by `-value`. For example, given a constraint `2x <=
3`, `add_to_function_constant(c, 4)` will modify it to `2x <= -1`.
## Examples
For scalar constraints, the set is translated by `-value`:
```jldoctest; setup = :(using JuMP; model = Model(); @variable(model, x)), filter=r"≤|<="
julia> @constraint(model, con, 0 <= 2x - 1 <= 2)
con : 2 x ∈ [1.0, 3.0]
julia> add_to_function_constant(con, 4)
julia> con
con : 2 x ∈ [-3.0, -1.0]
```
For vector constraints, the constant is added to the function:
```jldoctest; setup = :(using JuMP; model = Model(); @variable(model, x); @variable(model, y)), filter=r"≤|<="
julia> @constraint(model, con, [x + y, x, y] in SecondOrderCone())
con : [x + y, x, y] in MOI.SecondOrderCone(3)
julia> add_to_function_constant(con, [1, 2, 2])
julia> con
con : [x + y + 1, x + 2, y + 2] in MOI.SecondOrderCone(3)
```
"""
function add_to_function_constant(constraint::ConstraintRef{Model}, value)
# The type of `backend(model)` is not type-stable, so we use a function
# barrier (`moi_add_to_function_constant`) to improve performance.
moi_add_to_function_constant(backend(owner_model(constraint)),
index(constraint), value)
return
end

"""
value(con_ref::ConstraintRef)
Expand Down
56 changes: 41 additions & 15 deletions test/constraint.jl
Expand Up @@ -468,15 +468,15 @@ end
model = JuMP.Model()
x = @variable(model)
con_ref = @constraint(model, 2 * x == -1)
@test JuMP.standard_form_coefficient(con_ref, x) == 2.0
JuMP.set_standard_form_coefficient(con_ref, x, 1.0)
@test JuMP.standard_form_coefficient(con_ref, x) == 1.0
JuMP.set_standard_form_coefficient(con_ref, x, 3) # Check type promotion.
@test JuMP.standard_form_coefficient(con_ref, x) == 3.0
@test JuMP.normalized_coefficient(con_ref, x) == 2.0
JuMP.set_normalized_coefficient(con_ref, x, 1.0)
@test JuMP.normalized_coefficient(con_ref, x) == 1.0
JuMP.set_normalized_coefficient(con_ref, x, 3) # Check type promotion.
@test JuMP.normalized_coefficient(con_ref, x) == 3.0
quad_con = @constraint(model, x^2 == 0)
@test JuMP.standard_form_coefficient(quad_con, x) == 0.0
JuMP.set_standard_form_coefficient(quad_con, x, 2)
@test JuMP.standard_form_coefficient(quad_con, x) == 2.0
@test JuMP.normalized_coefficient(quad_con, x) == 0.0
JuMP.set_normalized_coefficient(quad_con, x, 2)
@test JuMP.normalized_coefficient(quad_con, x) == 2.0
@test JuMP.isequal_canonical(
JuMP.constraint_object(quad_con).func, x^2 + 2x)
end
Expand All @@ -485,16 +485,42 @@ end
model = JuMP.Model()
x = @variable(model)
con_ref = @constraint(model, 2 * x <= 1)
@test JuMP.standard_form_rhs(con_ref) == 1.0
JuMP.set_standard_form_rhs(con_ref, 2.0)
@test JuMP.standard_form_rhs(con_ref) == 2.0
@test JuMP.normalized_rhs(con_ref) == 1.0
JuMP.set_normalized_rhs(con_ref, 2.0)
@test JuMP.normalized_rhs(con_ref) == 2.0
con_ref = @constraint(model, 2 * x - 1 == 1)
@test JuMP.standard_form_rhs(con_ref) == 2.0
JuMP.set_standard_form_rhs(con_ref, 3)
@test JuMP.standard_form_rhs(con_ref) == 3.0
@test JuMP.normalized_rhs(con_ref) == 2.0
JuMP.set_normalized_rhs(con_ref, 3)
@test JuMP.normalized_rhs(con_ref) == 3.0
con_ref = @constraint(model, 0 <= 2 * x <= 1)
@test_throws MethodError JuMP.set_standard_form_rhs(con_ref, 3)
@test_throws MethodError JuMP.set_normalized_rhs(con_ref, 3)
end

@testset "Add to function constant" begin
model = JuMP.Model()
x = @variable(model)
@testset "Scalar" begin
con_ref = @constraint(model, 2 <= 2 * x <= 3)
con = constraint_object(con_ref)
@test JuMP.isequal_canonical(JuMP.jump_function(con), 2x)
@test JuMP.moi_set(con) == MOI.Interval(2.0, 3.0)
JuMP.add_to_function_constant(con_ref, 1.0)
con = constraint_object(con_ref)
@test JuMP.isequal_canonical(JuMP.jump_function(con), 2x)
@test JuMP.moi_set(con) == MOI.Interval(1.0, 2.0)
end
@testset "Vector" begin
con_ref = @constraint(model, [x + 1, x - 1] in MOI.Nonnegatives(2))
con = constraint_object(con_ref)
@test JuMP.isequal_canonical(JuMP.jump_function(con), [x + 1, x - 1])
@test JuMP.moi_set(con) == MOI.Nonnegatives(2)
JuMP.add_to_function_constant(con_ref, [2, 3])
con = constraint_object(con_ref)
@test JuMP.isequal_canonical(JuMP.jump_function(con), [x + 3, x + 2])
@test JuMP.moi_set(con) == MOI.Nonnegatives(2)
end
end

end

function test_shadow_price(model_string, constraint_dual, constraint_shadow)
Expand Down

0 comments on commit 4ef4c10

Please sign in to comment.