To implement hybrid algorithms where a run of a simplex solver is only a part of the global solving process, the black-box model presented above is not appropriate any more. As a more convenient model, we introduce the concept of a simplex demon. A simplex demon collects linear constraints and re-solves the problem whenever bounds change or new constraints appear.
Declaratively, this can be seen as a compound constraint representing all the individual linear constraints that have been set so far and are going to be set up later. Operationally, the delayed constraints are collected and an external solver is set up (as with lp_setup/4). Then the problem is solved once initially (as with lp_solve/2) and a delayed goal lp_demon/7 is set up which will re-trigger the solver when certain conditions are met.
Handle refers to the created solver state (as in lp_setup/4 or lp_read/3 described below). It can be used to access and modify the state of the solver, retrieve solution information etc.
Unlike with lp_solve/2, Cost will not be instantiated to a solution's cost, but only be bounded by it: For a minimization problem, each solution's cost becomes a lower bound, for maximization an upper bound on Cost. This technique allows for repeated re-solving with reduced bounds or added constraints.
ListOfOptions is a list of solver options as described is section 8.6.1.1 for lp_setup/4.
Priority is the scheduling priority with which the solver gets woken up. This priority determines whether the solver is run before or after other constraints. It is recommended to choose a priority that lies below the priority of more efficient propagation constraints, e.g. 5.
TriggerModes specifies under which conditions the solver demon will be re-triggered. It can be a list of the following specifiers
Some common invocations patterns for this predicate are the following. The first triggers the solver only on instantiation of variables to values that don't fit with the simplex solution:
lp_demon_setup(min(Expr), C, [], 5, [deviating_inst], H)The next one is more eager and triggers on significant bound changes or whenever new constraints arrive:
lp_demon_setup(max(Expr), C, [], 5, [new_constraint,deviating_bounds], H)The solver can also be triggered explicitly by setting it up with
lp_demon_setup(min(Expr), C, [], 5, [trigger(run_simplex)], H)and then issuing the command
schedule_suspensions(run_simplex),wakeIf several trigger conditions are specified, then any of them will trigger the solver.
When a solver demon runs frequently on relatively small problems, it can be important for efficieny to switch the external solver's presolving off (lp_set(presolve,0)) to reduce overheads.
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:
simplex :- lp_demon_setup(min(0), C, [solution(no)], 5, [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 the finite domains solver, e.g.
[eclipse]: X+Y+Z $>= K, X+Y+Z $=< 1, lp_demon_setup(min(0), C, [solution(no)], 5, [bounds], _), K = 2. no (more) solution.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. 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 14]: X+Y $=< 1, Y+Z $=< 1, X+Z $=< 1, lp_demon_setup(max(X+Y+Z), Cost, [solution(no)], 5, [bounds], _). X = X{-1e+20 .. 1e+20} Y = Y{-1e+20 .. 1e+20} Z = Z{-1e+20 .. 1e+20} Cost = Cost{-1e+20 .. 1.500001} Delayed goals: lp_demon(prob(...), ...) yes.If the variable bounds change subsequently, the solver will be re-triggered, possibly improving the cost bound to 1.3:
[eclipse 16]: X+Y $=< 1, Y+Z $=< 1, X+Z $=< 1, lp_demon_setup(max(X+Y+Z), Cost, [solution(no)], 5, [bounds], _), Y $=< 0.3. X = X{-1e+20 .. 1e+20} Z = Z{-1e+20 .. 1e+20} Cost = Cost{-1e+20 .. 1.300001} Y = Y{-1e+20 .. 0.3} Delayed goals: lp_demon(prob(...), ...) yes.
A further example is the implementation of a MIP-style branch-and-bound procedure. Source code is provided in the library file mip.pl.