Better integration with JuMP
The examples in this documentation generally used the simple and straightforward method of converting the trees and values to JuMP system, which depends on algebraic operators working transparently with JuMP values within function substitute
.
Substitution folding problem
Despite the simplicity, this approach is sometimes sub-optimal, especially in cases when the result of the substitution is recalculated with added values. For example, in the naive case, JuMP is forced to successively build representations for all intermediate expressions with incomplete variables, until all variables are in place. In turn, this may very easily reach a quadratic computational complexity.
More generally, any representation of substitution result that "does not reduce()
easily" will suffer from this problem. A different (often specialized) approach is thus needed.
Solution: Prevent successive folding
For such cases, it is recommended to replace the substitute
calls with a custom function that can interpret the required Value
s itself, and converts them without the overhead of creating temporary values.
import ConstraintTrees as C
First, let's create a lot of variables, and a constraint that will usually trigger this problem (and a JuMP warning) if used with normal substitute
:
x = :vars^C.variables(keys = Symbol.("x$i" for i = 1:1000), bounds = C.Between(0, 10))
x *= :sum^C.Constraint(sum(C.value.(values(x.vars))))
ConstraintTree with 2 elements:
:sum => Constraint(LinearValue(#= ... =#))
:vars => ConstraintTree(#= 1000 elements =#)
Now, imagine the expressions are represented e.g. by sparse vectors of fixed size (as common in linear-algebraic systems). We can produce the vectors efficiently as follows:
import SparseArrays: sparsevec
v = x.sum.value
value_in_a_vector = sparsevec(v.idxs, v.weights, 1000)
1000-element SparseArrays.SparseVector{Float64, Int64} with 1000 stored entries:
[1 ] = 1.0
[2 ] = 1.0
[3 ] = 1.0
[4 ] = 1.0
[5 ] = 1.0
[6 ] = 1.0
[7 ] = 1.0
⋮
[993 ] = 1.0
[994 ] = 1.0
[995 ] = 1.0
[996 ] = 1.0
[997 ] = 1.0
[998 ] = 1.0
[999 ] = 1.0
[1000] = 1.0
This usually requires only a single memory allocation, and runs in time linear with the number of variables in the value. As an obvious downside, you need to implement this functionality for all kinds of Value
s you encounter.
Solution for JuMP
LinearValue
s can be translated to JuMP's AffExpr
s:
using JuMP, GLPK
function substitute_jump(val::C.LinearValue, vars)
e = AffExpr() # unfortunately @expression(model, 0) is not type stable and gives an Int
for (i, w) in zip(val.idxs, val.weights)
if i == 0
add_to_expression!(e, w)
else
add_to_expression!(e, w, vars[i])
end
end
e
end
model = Model(GLPK.Optimizer)
@variable(model, V[1:1000])
jump_value = substitute_jump(x.sum.value, V)
\[ V_{1} + V_{2} + V_{3} + V_{4} + V_{5} + V_{6} + V_{7} + V_{8} + V_{9} + V_{10} + V_{11} + V_{12} + V_{13} + V_{14} + V_{15} + V_{16} + V_{17} + V_{18} + V_{19} + V_{20} + V_{21} + V_{22} + V_{23} + V_{24} + V_{25} + V_{26} + V_{27} + V_{28} + V_{29} + V_{30} + [[\ldots\text{940 terms omitted}\ldots]] + V_{971} + V_{972} + V_{973} + V_{974} + V_{975} + V_{976} + V_{977} + V_{978} + V_{979} + V_{980} + V_{981} + V_{982} + V_{983} + V_{984} + V_{985} + V_{986} + V_{987} + V_{988} + V_{989} + V_{990} + V_{991} + V_{992} + V_{993} + V_{994} + V_{995} + V_{996} + V_{997} + V_{998} + V_{999} + V_{1000} \]
This function can be re-used in functions like optimized_vars
as shown in other examples in the documentation.
For QuadraticValue
s, the same approach extends only with a minor modification:
function substitute_jump(val::C.QuadraticValue, vars)
e = QuadExpr() # unfortunately @expression(model, 0) is not type stable and gives an Int
for ((i, j), w) in zip(val.idxs, val.weights)
if i == 0 && j == 0
add_to_expression!(e, w)
elseif i == 0 # the symmetric case is prohibited
add_to_expression!(e, w, vars[j])
else
add_to_expression!(e, w, vars[i], vars[j])
end
end
e
end
qvalue = 123 + (x.vars.x1.value + x.vars.x2.value) * (x.vars.x3.value - 321)
jump_qvalue = substitute_jump(qvalue, V)
\[ V_{1}\times V_{3} + V_{2}\times V_{3} - 321 V_{1} - 321 V_{2} + 123 \]
This page was generated using Literate.jl.