# How interpret constraint values for inequalities?

**URL:** https://discourse.julialang.org/t/how-interpret-constraint-values-for-inequalities/93866
**Category:** Optimization (Mathematical)
**Tags:** jump
**Created:** [February 1, 2023, 10:34am UTC](https://discourse.julialang.org/t/how-interpret-constraint-values-for-inequalities/93866 "2023-02-01T10:34:50Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![hugh\_man](https://avatars.discourse-cdn.com/v4/letter/h/51bf81/32.png) [@hugh\_man](https://discourse.julialang.org/u/hugh_man)
#### Post date: [February 1, 2023, 10:34am UTC](https://discourse.julialang.org/t/how-interpret-constraint-values-for-inequalities/93866/1 "2023-02-01T10:34:50Z")

</div>

Hello,

I’m trying to understand the values outputted when evaluating a constraint. In another thread I received this useful [response](https://discourse.julialang.org/t/how-to-get-ordering-of-constraints-when-calling-eval-constraint/93772/2) on _how_ to evaluate constraints. I’ll use a **slightly modified example** from there (pasted below) to illustrate my confusion:

Why does evaluating a constraint yield the left-hand side of the constraint? I expected that evaluating the constraint would give either A) a binary value indicating whether or not the constraint was satisfied or B) a continuous value indicating how far we are from satisfying the constraint.

Is there a way to extract values like A) or B) from the constraints without modifying the constraints themselves?

Concretely, for `x = 0.3795` and the `2x <= 0.5` constraint, I would expect either A) 0 (or false) because the constraint isn’t satisfied or B) `0.25901` (which is `2x - 0.5`) and tells us how far we are from satisfying the constraint.

Concretely, for `x = 0.3795` and the `0 <= sin(x) <= 0.5` constraint, I would expect either A) 1 (or true) because the constraint is satisfied or B) `0` indicating we need this much change (a.k.a. no change) to satisfy the constraint.

```julia
julia> using JuMP

julia> model = Model();

julia> @variable(model, x >= 0)
x

julia> @constraint(model, c, 2x <= 0.5)
c : 2 x ≤ 0.5

julia> @NLconstraint(model, nl_con, 0 <= sin(x) <= 0.5)
0 ≤ sin(x) ≤ 0.5

julia> variable_values = Dict(v => rand() for v in all_variables(model))
Dict{VariableRef, Float64} with 1 entry:
  x => 0.379509

julia> cons = all_constraints(model; include_variable_in_set_constraints = true)
3-element Vector{ConstraintRef}:
 c : 2 x ≤ 0.5
 x ≥ 0.0
 0 ≤ sin(x) ≤ 0.5

julia> sol = Dict(c => value(xi -> variable_values[xi], c) for c in cons)
Dict{ConstraintRef{Model, C, ScalarShape} where C, Float64} with 3 entries:
  0 ≤ sin(x) ≤ 0.5 => 0.370465
  x ≥ 0.0 => 0.379509
  c : 2 x ≤ 0.5 => 0.759019

julia> sol[c]
0.759018812310488

julia> sol[nl_con]
0.37046482764020316

julia> sol[LowerBoundRef(x)]
0.379509406155244

```

---

<div class="post-metadata">

### Author: ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)
#### Post date: [February 1, 2023, 11:18am UTC](https://discourse.julialang.org/t/how-interpret-constraint-values-for-inequalities/93866/2 "2023-02-01T11:18:00Z")

</div>

The point you are raising is logical. It would be nice to know the **slack** each constraint needs to be satisfied (or even the **margin** in case it is). Currently, by reading the question, it seems the value is the non-constant part of the constraint (i.e. if a constraint is an `Expression` and a `(LowerBound, UpperBound)` pair, then the constraint value is the value of Expression).

---

<div class="post-metadata">

### Author: ![odow](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/odow/32/28685_2.png) [@odow](https://discourse.julialang.org/u/odow)
#### Post date: [February 1, 2023, 7:47pm UTC](https://discourse.julialang.org/t/how-interpret-constraint-values-for-inequalities/93866/3 "2023-02-01T19:47:34Z")

</div>

The answer is actually slightly complicated.

For `@constraint`, JuMP moves all variables to the left-hand side, and all constants to the right-hand side. The `value` of a constraint is the value of the expression on the left-hand side of the constraint.

For `@NLconstraint`, JuMP moves **all terms** to the left-hand side of the constraint. The `value` of the constraint is the value of the expression on the left-hand side of the constraint.

For interval constraints, both `@constraint` and `@NLconstraint`, JuMP does not move terms, and the left- and right-hand sides must be constants. `value` is the value of the expression in the middle of the constraint.

```julia
julia> using JuMP

julia> model = Model();

julia> @variable(model, x >= 0)
x

julia> @constraint(model, con_le_lhs, 2x + 1 <= 0.5)
con_le_lhs : 2 x ≤ -0.5

julia> @constraint(model, con_le_rhs, 0.5 <= 2x + 1)
con_le_rhs : -2 x ≤ 0.5

julia> @constraint(model, con_ge_lhs, 2x + 1 >= 0.5)
con_ge_lhs : 2 x ≥ -0.5

julia> @constraint(model, con_ge_rhs, 0.5 >= 2x + 1)
con_ge_rhs : -2 x ≥ 0.5

julia> @constraint(model, con_eq_lhs, 2x + 1 == 0.5)
con_eq_lhs : 2 x = -0.5

julia> @constraint(model, con_eq_rhs, 0.5 == 2x + 1)
con_eq_rhs : -2 x = 0.5

julia> @constraint(model, con_eq_in_le, 0.5 <= 2x + 1 <= 3)
con_eq_in_le : 2 x ∈ [-0.5, 2.0]

julia> @constraint(model, con_eq_in_ge, 4 >= 2x + 1 >= 0.5)
con_eq_in_ge : 2 x ∈ [-0.5, 3.0]

julia> @NLconstraint(model, nl_con_in, 0 <= x <= 0.5)
0 ≤ x ≤ 0.5

julia> @NLconstraint(model, nl_con_le, x <= 1)
x - 1.0 ≤ 0

julia> @NLconstraint(model, nl_con_ge, x >= 1)
x - 1.0 ≥ 0

julia> @NLconstraint(model, nl_con_eq, x == 1)
x - 1.0 = 0

julia> cons = all_constraints(model; include_variable_in_set_constraints = true)
13-element Vector{ConstraintRef}:
 con_eq_lhs : 2 x = -0.5
 con_eq_rhs : -2 x = 0.5
 con_ge_lhs : 2 x ≥ -0.5
 con_ge_rhs : -2 x ≥ 0.5
 con_le_lhs : 2 x ≤ -0.5
 con_le_rhs : -2 x ≤ 0.5
 con_eq_in_le : 2 x ∈ [-0.5, 2.0]
 con_eq_in_ge : 2 x ∈ [-0.5, 3.0]
 x ≥ 0.0
 0 ≤ x ≤ 0.5
 x - 1.0 ≤ 0
 x - 1.0 ≥ 0
 x - 1.0 = 0

julia> sol = Dict(c => value(xi -> 1.0, c) for c in cons)
Dict{ConstraintRef{Model, C, ScalarShape} where C, Float64} with 13 entries:
  con_ge_rhs : -2 x ≥ 0.5 => -2.0
  x - 1.0 ≤ 0 => 0.0
  con_ge_lhs : 2 x ≥ -0.5 => 2.0
  con_eq_rhs : -2 x = 0.5 => -2.0
  x ≥ 0.0 => 1.0
  x - 1.0 ≥ 0 => 0.0
  con_eq_in_le : 2 x ∈ [-0.5, 2.0] => 2.0
  con_le_rhs : -2 x ≤ 0.5 => -2.0
  0 ≤ x ≤ 0.5 => 1.0
  con_eq_in_ge : 2 x ∈ [-0.5, 3.0] => 2.0
  x - 1.0 = 0 => 0.0
  con_eq_lhs : 2 x = -0.5 => 2.0
  con_le_lhs : 2 x ≤ -0.5 => 2.0

```

If you want the slacks to test feasibility, then you’re probably interested instead in:

- [Solutions · JuMP](https://jump.dev/JuMP.jl/stable/manual/solutions/#Checking-feasibility-of-solutions)

---

<div class="post-metadata">

### Author: ![cvanaret](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cvanaret/32/11594_2.png) [@cvanaret](https://discourse.julialang.org/u/cvanaret)
#### Post date: [February 5, 2023, 11:59pm UTC](https://discourse.julialang.org/t/how-interpret-constraint-values-for-inequalities/93866/4 "2023-02-05T23:59:06Z")

</div>

On top of the other answers: perhaps it’s a matter of terminology.  
You’re right that a **constraint** is a **logical relation** between LHS and RHS. However in modeling and numerical optimization, we work with **constraint functions** , that is usually the evaluation of the LHS when all variables have been moved there (see @odow’s answer).  
We have more latitude to recreate the constraint “g(x) \<= 0” from the two pieces of information “g(x)” (the actual constraint function) and “[-inf, 0]” (the bounds) than vice versa. For example:

- we can differentiate g;
- we can reformulate the constraint, e.g. add a slack s: g(x) - s = 0 and s \le 0;
- we can relax the constraint in the objective: f(x) + \rho (g(x) - s)^2;
- and so on
