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
    random_engine = random.Random()
    while True:
        # Randomly pick a generator
        gen = random_engine.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.random_engine = random.Random()

    def __next__(self):
        return next(self.random_engine.choice(self.generators))

    def __iter__(self):
        return self

    def seed(self, val):
        self.random_engine.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 RandomEngine. 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_random_engine = 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_random_engine = ecole.spawn_random_engine()

    def __next__(self):
        return ecole.instance.SetCoverGenerator(
            n_cols=self.py_random_engine.randint(*self.n_cols_range),
            n_rows=self.py_random_engine.randint(*self.n_rows_range),
            random_engine=self.ecole_random_engine,
        )

    def __iter__(self):
        return self

    def seed(self, val):
        self.py_random_engine.seed(val)
        self.ecole_random_engine.seed(val)

See the discussion on seeding for an explanation of ecole.spawn_random_engine().