Frozen Solve#
In rare cases, the GAMSPy model generation time dominates the solver solution time and GAMSPy itself becomes the bottleneck in an optimization application. For a model instance which is a single mathematical model generated by a GAMSPy solve statement, frozen solve provides a controlled way of modifying a model instance and solving the resulting problem repeatedly in the most efficient way, by communicating only the changes of the model to the solver and doing a hot start (in case of a continuous model like LP) without the use of disk IO.
Frozen solves are defined in two steps:
1. The freeze
call will enable the frozen solve mode and query the symbol information of the modifiable symbols. The modifiables
can be consisted of Parameter
and Variable
or Equation
attributes such as
Variable.lo
, Variable.up
and Variable.fx
.
2. The solve
method uses this data to update the model instance. After the model instance has been updated,
the model is passed to the selected solver. After the completion of the solve method, the container will contain the primal and dual solution of the model
just solved. Moreover, the parameters that are modifiable are also accessible in database with the name of the Parameter plus “_var”. The marginal of
this variable can provide sensitivity information about the parameter setting. The status of the solve is accessible through the Model.model_status
,
Model.solve_status
and Model.objective_value
attributes.
The
unfreeze
function unfreezes the model and releases the resources.
from gamspy import (
Container,
Set,
Parameter,
Variable,
Equation,
Sum,
Model,
Sense,
ModelStatus,
)
import numpy as np
m = Container()
i = Set(m, name="i")
j = Set(m, name="j")
a = Parameter(
m,
name="a",
domain=i,
domain_forwarding=True,
records=[["seattle", 350], ["san-diego", 600]],
)
b = Parameter(
m,
name="b",
domain=j,
domain_forwarding=True,
records=[["new-york", 325], ["chicago", 300], ["topeka", 275]],
)
d = Parameter(
m, name="d", domain=[i, j], records=np.array([[2.5, 1.7, 1.8], [2.5, 1.8, 1.4]])
)
c = Parameter(m, name="c", domain=[i, j])
c[i, j] = 90 * d[i, j] / 1000
x = Variable(m, name="x", domain=[i, j], type="Positive")
z = Variable(m, name="z")
supply = Equation(m, name="supply", domain=i)
demand = Equation(m, name="demand", domain=j)
bmult = Parameter(m, name="bmult", records=1)
cost = Sum((i, j), c[i, j] * x[i, j])
supply[i] = Sum(j, x[i, j]) <= a[i]
demand[j] = Sum(i, x[i, j]) >= bmult * b[j]
transport = Model(
m,
name="transport",
equations=[supply, demand],
problem="LP",
sense=Sense.MIN,
objective=cost,
)
bmult_list = [0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3]
transport.freeze(modifiables=[bmult])
for b_value in bmult_list:
bmult.setRecords(b_value)
transport.solve(solver="conopt")
print(
f'obj:{transport.objective_value if transport.status == ModelStatus.OptimalGlobal else "infeasible"}'
)
transport.unfreeze()
The solver used can be switched in between solves, for example the following script uses conopt for even numbers and cplex for odd numbers:
for index, b_value in enumerate(bmult_list):
bmult.setRecords(b_value)
if index % 2 == 0:
transport.solve(solver="conopt")
else:
transport.solve(solver="cplex")
Note
Modifiable parameters cannot be used in .where
conditions. Variable and equation attributes used in equation
algebra are evaluated once at model generation. Changes in the attibutes will not percolate to the algebra.
For example, the algebra x <= b * x.up
will not change even if the modifiables include x.up
. One needs
a parameter bigM
and algebra x <= b * bigM
in order to modify this algebra in a frozen solve.