9.3 Advanced Use of Eplex Instances
9.3.1 Obtaining Solver State Information
The black-box interface binds both the objective value (Cost) and the
problem variables by bindings these variables. On the other hand, eplex_solve/1 binds the objective value, but does not bind the problem
variables. These values can be obtained by:
-
EplexInstance:eplex_var_get(+Var, +What, -Value)
Retrieve information about the solver state associated with the eplex
instance for the variable Var. If What is solution or
typed_solution, then the value assigned to this variable by the
solver state to obtain the optimal solution is returned in Value. solution returns the value as a float, and typed_solution returns the value as either a float or a rounded integer, depending
on if the variable was constrained to an integer in the eplex problem.
- EplexInstance:eplex_get(+What, -Value)
Retrieve information about solver state associated with the eplex instance.
This returns information such as the problem type, the constraints for the
eplex problem. See the reference manual for more details.
- EplexInstance:eplex_set(+What, +Value)
-
Set a solver option for the eplex instance.
- EplexInstance:eplex_write(+Format, +File)
-
Write out the problem in the the eplex instance's solver state to the file
File in format Format. The writing is done by the external solver. Use the
use_var_name(yes) option in
eplex_solver_setup/4
so that the written file uses ECLiPSevariable names. Also the write_before_solve option of eplex_solver_setup/4 can be used to write out
a problem just before it is solved by the external solver: this allows
problem to be written in places where eplex_write/2 cannot be added
(e.g. for probing problems)..
- EplexInstance:eplex_read(+Format, +File)
-
Read a MP problem in the file File in format Format into a solver state,
and associate the solver with the eplex instance. No solver must already be
setup for the eplex instance. The solver state that is setup can only be
triggered explicitly.
So for the simple MIP example:
:- lib(eplex).
:- eplex_instance(my_instance).
mip_example2([X,Y], Cost) :-
my_instance: (X+Y $>= 3),
my_instance: (X-Y $= 0),
my_instance: integers([X]),
my_instance: eplex_solver_setup(min(X)),
my_instance: eplex_solve(Cost),
my_instance: eplex_var_get(X, typed_solution, X),
my_instance: eplex_var_get(Y, typed_solution, Y).
....
[eclipse 2]: mip_example2([X,Y],C).
X = 2
Y = 2.0
C = 2.0
In the example, only X is returned as an integer, as Y was not explicitly
constrained to be an integer.
Note that if there are multiple eplex instances, and a variable is shared
between the instances, then the solver state for each instance can have a
different optimal value to the variable.
9.3.2 Creating Eplex Instances Dynamically
So far, we have shown the use of eplex_instance/1 as a directive to
declare an eplex instance. For some applications, it might be necessary to
create eplex instances dynamically at run-time. The can be done by calling
eplex_instance/1 at run-time.
In this case, the instance name should not be used to module-qualify
any predicates in the code, since this will raise a compiler warning
complaining about an unknown module.
new_pool(X,Y) :- % INCORRECT
eplex_instance(pool),
pool: (X $>= Y), % will generate a warning
...
Of course, in the above code, the instance name pool is already known
at compile time, so it can always be declared by a directive.
If the name is truly generated dynamically, this can be done as follows:
new_pool(Pool,X,Y) :-
eplex_instance(Pool),
Pool: (X $>= Y),
....
Obtaining Bounds on the Objective Value
The external solver does not always return the optimal objective
value, for example when the optimisation was aborted. However, even when
the solver returns an optimal solution, it may actually not be the exact
optimal, because of solver settings (e.g. for MIP problems, the MIP search
will terminate when the solution found is within certain tolerance of the
best possible solution value). In these cases, it may be useful to obtain
some bounds on the optimal objective value. The best and worst bounds on
the optimal objective can be obtained using the best_bound and worst_bound
options of eplex_get/2,
respectively.
9.3.3 Interface for CLP-Integration: Solver Demons
To implement hybrid algorithms where a run of a simplex/MIP solver is only
a part of the global solving process, the black-box model presented above
is not appropriate anymore. With eplex instances, we can call eplex_solve/1 repeatedly to re-solve the problem, perhaps after adding
more constraints to the problem or after changes in the variable
bounds. However, the solver must be invoked explicitly. We require more
sophisticated methods of invoking the solver. This can be done by setting
up a solver demon, and specifying the conditions in which the demon is to
wake up and invoke the external solver.
This is a more sophisticated set up for a new solver state than
eplex_solver_setup/1 (in fact eplex_solver_setup/1 is a special case
of eplex_solver_setup/4).
The main idea is that a list of trigger conditions
are specified in TriggerModes, and along with setting up the solver
state, a demon goal is created which is woken up when one of the
specified trigger condition is met. This demon goal will then invoke the
solver, with any
constraints posted to the eplex instance since the solver was last invoked
taken into account, to re-solve the problem.
The ListOfOptions is a list of solver options for setting up the
solver state. Some of these affect the way the external solver solves the
problem, such as if presolve should be applied before solving the problem.
See the reference manual for eplex_solver_setup/4 for
details on the available options and trigger modes.
As the solver is designed to be invoked repeatedly, it is inappropriate to
directly bind Cost to the objective value. Instead, the objective
value is exported as a bound to Cost:
For a minimisation problem, each solution's
cost becomes a lower bound, for maximisation an upper bound on Cost.
This technique allows for repeated re-solving with reduced variable bounds
or added constraints. Note that the bound update is done only if the
solution is optimal. Note also that Cost is not automatically
made a problem variable, and thus may not have bounds associated
with in. In order for the bounds information not to be lost, some bounds
should be given to Cost (e.g. making it a problem variable (but
this might introduce unnecessarily self-waking on bounds change), or via
another solver with bounds (e.g. ic)).
Note that when a solver demon runs frequently on relatively small problems,
it can be important for efficiency to switch the external solver's
presolving off for this demon as part of the ListOfOptions during the
setup of the problem to reduce overheads.
Example
The simplest case of having a simplex solver automatically cooperating
with a CLP program, is to set up a solver demon which will repeatedly
check whether the continuous relaxation of a set of constraints
is still feasible.
The code could look as follows (we use the eplex instance in this example):
simplex :-
eplex:eplex_solver_setup(min(0), C,
[solution(no)], [bounds]).
First, the constraints are normalised and checked for linearity.
Then a solver with a dummy objective function is set up. The option
solution(no) indicates that we are not interested in solution values.
Then we start a solver demon which will re-examine the problem
whenever a change of variable bounds occurs.
The demon can be regarded as a compound constraint implementing the
conjunction of the individual constraints. It is able to detect
some infeasibilities that for instance could not be detected by a
finite domains solver, e.g.
[eclipse 2]: eplex:(X+Y+Z >= K), eplex:(X+Y+Z =< 1),
eplex:eplex_solver_setup(min(0), C,
[solution(no)], [bounds]),
K = 2.
No (0.00s cpu)
In the example, the initial simplex is successful, but instantiating
K wakes the demon again, and the simplex fails this time.
A further step is to take advantage of the cost bound that the simplex
procedure provides. To do this, we need to give the objective
The setup is similar to above, but we accept an objective function and
add a cost variable. The bounds of the cost variable will be updated
whenever a simplex invocation finds a better cost bound on the problem.
In the example below, an upper bound for the cost of 1.5 is found
initially:
[eclipse 5]: ic: (Cost $:: -1.0Inf..1.0Inf),
eplex:(X+Y $=< 1), eplex:(Y+Z $=< 1), eplex:(X+Z $=< 1),
eplex:eplex_solver_setup(max(X+Y+Z), Cost,
[solution(no)], [bounds]).
X = X{-1e+20 .. 1e+20}
Y = Y{-1e+20 .. 1e+20}
Z = Z{-1e+20 .. 1e+20}
Cost = Cost{-1.0Inf .. 1.500001}
Delayed goals:
lp_demon(prob(...), ...)
Yes (0.00s cpu)
(Note that the ranges for X, Y and Z is -1e+20 .. 1e+20 as 1e+20 is this
external solver's notion of infinity).
If the variable bounds change subsequently, the solver will be re-triggered,
improving the cost bound to 1.3:
[eclipse 6]: ic: (Cost $:: -1.0Inf..1.0Inf),
eplex:(X+Y $=< 1), eplex:(Y+Z $=< 1), eplex:(X+Z $=< 1),
eplex:eplex_solver_setup(max(X+Y+Z), Cost,
[solution(no)], [bounds]),
eplex:(Y =< 0.3).
X = X{-1e+20 .. 1e+20}
Z = Z{-1e+20 .. 1e+20}
Cost = Cost{-1.0Inf .. 1.300001}
Y = Y{-1e+20 .. 0.3}
Delayed goals:
lp_demon(prob(...), ...)
Yes (0.00s cpu)
A further example is the implementation of a MIP-style branch-and-bound
procedure. Source code is provided in the library file mip.pl.
9.3.4 Encapsulated Modification of the Problem: Probing
The external mathematical programming solvers often provides the facility
for the user to change the problem being solved. This includes the addition
or removal of constraints, and the changing of the objective function.
We have already seen how extra constraints can be added. As ECLiPSe is a
logic programming language, removal of constraints is automatically
achieved by backtracking. We do not allow the user to explicitly remove
constraints that have been collected by the external solver, as this makes
the problem non-monotonic.
For the same reason, we do not allow the objective function to be
changed.1
However, we do allow the problem (including the objective function) to be
temporarily changed in certain specified ways. This allows the
problem to be `probed' with these changes:
Similar to eplex_solve/1, but the problem is first temporarily modified as
specified in Probes before the optimisation. The Cost value
is instantiated to the objective value for this new modified problem, and
any solution state requested are also updated.
9.3.5 Destroying the Solver State
Destroy the specified solver, free all memory, etc.
Note that ECLiPSe will normally do the cleanup automatically,
for instance when execution fails across the solver setup, or
when a solver handle gets garbage collected. The solver is disassociated
with the eplex instance, and any outstanding constraints not yet collected
by the solver are removed, with a warning to the user. In effect, the
eplex instance is reinitialised.
Note that this is a non-logical operation. Backtracking into code before
eplex_cleanup/0 will not restore the solver state, and any attempt to
reuse the solver state will not be possible (the execution will abort with
an error). Normally, it is recommended to let ECLiPSe perform the cleanup
automatically,
for instance when execution fails across the solver setup, or
when an unused solver state handle gets garbage collected.
However, calling eplex_cleanup/0 may
cause resources (memory and licence) to be freed earlier.
9.3.6 Eplex Instance Interface Example: definition of optimize/2:
A black-box setup-and-solve predicate optimize/2 can be defined as:
optimize(OptExpr, ObjVal) :-
eplex:eplex_solver_setup(OptExpr),
eplex:eplex_solve(ObjVal),
eplex:eplex_get(vars, VArr),
eplex:eplex_get(typed_solution, SolutionVector),
VArr = SolutionVector, % do the bindings
eplex:eplex_cleanup.
A solver state is set up for the eplex instance eplex, to allow
constraints that were previously posted to eplex to be collected.
This happens once the solver is invoked by eplex_solve/1. If there
is a solution, the solution vector is obtained, and the
variables are instantiated to those solutions.