Skip to content

Commit c9750b7

Browse files
committed
Document iterative solving usage
1 parent 4c6849e commit c9750b7

File tree

2 files changed

+129
-0
lines changed

2 files changed

+129
-0
lines changed

docs/howto/create-environments.rst

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,104 @@ The constructor arguments are forwarded from the :py:meth:`~ecole.environment.En
127127
Similarily, extra arguments given to the environemnt :py:meth:`~ecole.environment.Environment.reset` and
128128
:py:meth:`~ecole.environment.Environment.step` are forwarded to the associated
129129
:py:class:`~ecole.typing.Dynamics` methods.
130+
131+
Using Control Inversion
132+
-----------------------
133+
When using a traditional SCIP callback, the user has to add the callback to SCIP, call ``SCIPsolve``, and wait for the
134+
solving process to terminate.
135+
We say that *SCIP has the control*.
136+
This has some downsides, such a having to forward all the data the agent will use to the callback, making it harder to
137+
stop the solving process, and reduce interactivity.
138+
For instance when using a callback in a notebook, if the user forgot to fetch some data, then they have to re-execute
139+
the whole solving process.
140+
141+
On the contrary, when using an Ecole environment such as :py:class:`~ecole.environment.Branching`, the environment
142+
pauses on every branch-and-bound node (*i.e.* every branchrule callback call) to let the user make a decision,
143+
or inspect the :py:class:`~ecole.scip.Model`.
144+
We say that the *user (or the agent) has the control*.
145+
To do so, we did not reconstruct the solving algorithm ``SCIPsolve`` to fit our needs.
146+
Rather, we have implemented a general *inversion of control* mechanism to let SCIP pause and be resumed on every
147+
callback call (using a form of *stackful coroutine*).
148+
We call this approach *iterative solving* and it runs exactly the same ``SCIPsolve`` algorithm, without noticable
149+
overhead, while perfectly forwarding all information available in the callback.
150+
151+
To use this tool, the user start by calling :py:meth:`ecole.scip.Model.solve_iter`, with a set of call callback
152+
constructor arguments.
153+
Iterative solving will then add these callbacks, start solving, and return the first time that one of these callback
154+
is executed.
155+
The return value describes where the solving has stopped, and the parameters of the callback where it has stopped.
156+
This is the time for the user to perform whichever action they would have done in the callback.
157+
Solving can be resumed by calling :py:meth:`ecole.scip.Model.solve_iter_continue` with the
158+
:py:class:`ecole.scip.callback.Result` that would have been set in the callback.
159+
Solving is finished when one of the iterative solving function returns ``None``.
160+
The :py:class:`ecole.scip.Model` can safely be deleted an any time (SCIP termination is handled automatically).
161+
162+
For instance, iterative solving an environement while pausing on branchrule and heuristic callbacks look like the
163+
following.
164+
165+
.. testcode::
166+
167+
model = ecole.scip.Model.from_file("path/to/file")
168+
169+
# Start solving until the first pause, if any.
170+
fcall = model.solve_iter(
171+
# Stop on branchrule callback.
172+
ecole.scip.callback.BranchruleConstructor(),
173+
# Stop on heuristic callback after node.
174+
ecole.scip.callback.HeuristicConstructor(timing_mask=ecole.scip.HeurTiming.AfterNode),
175+
)
176+
# While solving is not finished, `fcall` contains information about the current stop.
177+
while fcall is not None:
178+
# Solving stopped on a branchrule callback.
179+
if isinstance(fcall, ecole.scip.callback.BranchruleCall):
180+
# Perform some branching (through PyScipOpt).
181+
...
182+
# Resume solving until next pause.
183+
fcall = model.solve_iter_continue(ecole.scip.callback.Result.Branched)
184+
# Solving stopped on a heurisitc callback.
185+
elif isinstance(fcall, ecole.scip.callback.HeuristicCall):
186+
# Return as no heuristic was performed (only data collection)
187+
fcall = model.solve_iter_continue(ecole.scip.callback.Result.DidNotRun)
188+
189+
See :py:class:`~ecole.scip.callback.BranchruleConstructor`, :py:class:`~ecole.scip.callback.HeuristicConstructor` for
190+
callback constructor parameters, as well as :py:class:`~ecole.scip.callback.BranchruleCall` and
191+
:py:class:`~ecole.scip.callback.BranchruleCall` for callbacks functions parameters passed by SCIP to the callback
192+
methods.
193+
194+
.. note::
195+
196+
By default callback parameters such as ``priority``, ``frequency``, and ``max_depth`` taht control how when
197+
the callback are evaluated by SCIP are set to run as often as possible.
198+
However, it is entirely possible to run it with lower priority or frequency for create specific environments or
199+
whatever other purpose.
200+
201+
To create dynamics using iterative solving, one should call :py:meth:`ecole.scip.Model.solve_iter` in
202+
:py:meth:`~ecole.typing.Dynamics.reset_dynamics` and :py:meth:`ecole.scip.Model.solve_iter_continue` in
203+
:py:meth:`~ecole.typing.Dynamics.step_dynamics`.
204+
For instance, a branching environment could be created with the following dynamics.
205+
206+
.. testcode::
207+
:skipif: pyscipopt is None
208+
209+
class MyBranchingDynamics:
210+
def __init__(self, pseudo_candidates=False, max_depth=ecole.scip.callback.max_depth_none):
211+
self.pseudo_candidates = pseudo_candidates
212+
self.max_depth = max_depth
213+
214+
def action_set(self, model):
215+
if self.pseudo_candidates:
216+
return model.as_pyscipopt().getPseudoBranchCands()
217+
else:
218+
return model.as_pyscipopt().getLPBranchCands()
219+
return ...
220+
221+
def reset_dynamics(self, model):
222+
fcall = model.solve_iter(
223+
ecole.scip.callback.BranchruleConstructor(max_depth=self.max_depth)
224+
)
225+
return (fcall is None), self.action_set(model)
226+
227+
def step_dynamics(self, model, action):
228+
model.as_pyscipopt().branchVar(action)
229+
fcall = model.solve_iter_continue(ecole.scip.callback.Result.Branched)
230+
return (fcall is None), self.action_set(model)

docs/reference/scip-interface.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,31 @@ SCIP Interface
44
Model
55
-----
66
.. autoclass:: ecole.scip.Model
7+
8+
Callbacks
9+
---------
10+
Branchrule
11+
^^^^^^^^^^
12+
.. autoclass:: ecole.scip.callback.BranchruleConstructor
13+
.. autoclass:: ecole.scip.callback.BranchruleCall
14+
15+
Heuristic
16+
^^^^^^^^^
17+
.. autoclass:: ecole.scip.callback.HeuristicConstructor
18+
.. autoclass:: ecole.scip.callback.HeuristicCall
19+
20+
Utilities
21+
^^^^^^^^^
22+
.. autoattribute:: ecole.scip.callback.priority_max
23+
.. autoattribute:: ecole.scip.callback.max_depth_none
24+
.. autoattribute:: ecole.scip.callback.max_bound_distance_none
25+
.. autoattribute:: ecole.scip.callback.frequency_always
26+
.. autoattribute:: ecole.scip.callback.frequency_offset_none
27+
28+
.. autoclass:: ecole.scip.callback.Result
29+
.. autoclass:: ecole.scip.callback.Type
30+
31+
SCIP Data Types
32+
---------------
33+
.. autoclass:: ecole.scip.Stage
34+
.. autoclass:: ecole.scip.HeurTiming

0 commit comments

Comments
 (0)