Generate Problem Instances
Ecole contains a number of combinatorial optimization instance generators in the``ecole.instance`` module. The various
InstanceGenerator
classes generate instances as ecole.scip.Model
objects.
To use those classes to generate instances, you first instantiate a generator object from the desired class. The various
generator classes take problem-specific hyperparameters as constructor arguments, which can be used to control the type
of instances being generated. The resulting InstanceGenerator
objects are infinite Python
iterators, which can then be iterated over to yield as many instances as desired.
For instance, to generate set covering problems, one would use
SetCoverGenerator
in the following fashion.
from ecole.instance import SetCoverGenerator
generator = SetCoverGenerator(n_rows=100, n_cols=200, density=0.1)
for i in range(50):
instance = next(generator)
# Do anything with the ecole.scip.Model
instance.write_problem("some-folder/set-cover-{i:04}.lp")
Note how we are iterating over a range(50)
and calling next
on the generator, as iterating directly over
the iterator would produce an infinite loop. Another simple syntax would be to use islice
from the standard Python library.
Generator Random States
Internally, an InstanceGenerator
holds a random state , which gets updated after generating an instance.
This state can be reset using the seed()
method of the generator.
generator_a = SetCoverGenerator(n_rows=100, n_cols=200, density=0.1)
generator_b = SetCoverGenerator(n_rows=100, n_cols=200, density=0.1)
# These are not the same instance
instance_a = next(generator_a)
instance_b = next(generator_b)
generator_a.seed(809)
generator_b.seed(809)
# These are exactly the same instances
instance_a = next(generator_a)
instance_b = next(generator_b)
With an Environment
The instance objects generated by Model
, can be passed directly to an environment’s
reset()
method.
A typical example training over 1000 instances/episodes would look like:
import ecole
env = ecole.environment.Branching()
gen = ecole.instance.SetCoverGenerator(n_rows=100, n_cols=200)
for _ in range(1000):
observation, action_set, reward_offset, done, info = env.reset(next(gen))
while not done:
observation, action_set, reward, done, info = env.step(action_set[0])
Note
The generated instance objects can be, in principle, modified between their generation and their usage in an environment
reset()
method. To keep code clean, however, we recommend that such modifications
be wrapped in a custom environment class. Details about custom environments can be found here.
Extending Instance Generators
In various use cases, the provided InstanceGenerator
are too limited. Thankfully, it is easy to extend
the provided generators in various ways. This section presents a few common patterns.
Combining Multiple Generators
To learn over multiple problem types, one can build a generator that, for every instance to generate, chooses a a problem type at random, and returns it.
import random
def CombineGenerators(*generators):
# A random state for choice
rng = random.Random()
while True:
# Randomly pick a generator
gen = rng.choice(generators)
# And yield the instance it generates
yield next(gen)
Note that this is not quite a fully-fledged instance generator, as it is missing a way to set the seed. A more complete instance generator could be written as follows.
class CombinedGenerator:
def __init__(self, *generators):
self.generators = generators
self.rng = random.Random()
def __next__(self):
return next(self.rng.choice(self.generators))
def __iter__(self):
return self
def seed(self, val):
self.rng.seed(val)
for gen in self.generators:
gen.seed(val)
Generator with Random Parameters
The provided instance generators have fixed hyperparameters, but to increase variability it might be desirable to randomly vary them as well.
This can be without creating various InstanceGenerator
objects by using a generator’s
generate_instance()
static method, and manually pass a RandomGenerator
.
For instance, to randomly choose the n_cols
and n_rows
parameters from
SetCoverGenerator
, one could use
import random
import ecole
class VariableSizeSetCoverGenerator:
def __init__(self, n_cols_range, n_rows_range):
self.n_cols_range = n_cols_range
self.n_rows_range = n_rows_range
# A Python random state for randint
self.py_rng = random.Random()
# An Ecole random state to pass to generating functions
# This function returns a random state whose seed depends on Ecole global random state
self.ecole_rng = ecole.spawn_random_generator()
def __next__(self):
return ecole.instance.SetCoverGenerator(
n_cols=self.py_rng.randint(*self.n_cols_range),
n_rows=self.py_rng.randint(*self.n_rows_range),
rng=self.ecole_rng,
)
def __iter__(self):
return self
def seed(self, val):
self.py_rng.seed(val)
self.ecole_rng.seed(val)
See the discussion on seeding for an explanation of ecole.spawn_random_generator()
.