Use Observation Functions

Using any environment, the observation 1 received by the user to take the next action can be customized changing the ObservationFunction used by the solver. The environment is not extracting data directly but delegates that responsibility to an ObservationFunction object. The object has complete access to the solver and extract the data it needs.

Specifying an observation function is as easy as specifying a parameter when creating an environment. For instance with the Branching environment:

>>> env = ecole.environment.Branching(observation_function=ecole.observation.Nothing())
>>> env.observation_function  
ecole.observation.Nothing()
>>> obs, _, _, _, _ = env.reset("path/to/problem")
>>> obs is None
True

Environments have an observation function set as default parameter for convenience.

>>> env = ecole.environment.Branching()
>>> env.observation_function  
ecole.observation.NodeBipartite()
>>> obs, _, _, _, _ = env.reset("path/to/problem")
>>> obs  
ecole.observation.NodeBipartiteObs(...)

See the reference for the list of available observation functions, as well as the documention for explanation on how to create one.

No Observation Function

To not use any observation function, for instance for learning with a bandit algorithm, you can explicitly pass None to the environment constructor.

>>> env = ecole.environment.Branching(observation_function=None)
>>> env.observation_function  
ecole.observation.nothing()
>>> obs, _, _, _, _ = env.reset("path/to/problem")
>>> obs is None
True

Multiple Observation Functions

To use multiple observation functions, wrap them in a list or dict.

>>> obs_func = {
...     "some_name": ecole.observation.NodeBipartite(),
...     "other_name": ecole.observation.Nothing(),
... }
>>> env = ecole.environment.Branching(observation_function=obs_func)
>>> obs, _, _, _, _ = env.reset("path/to/problem")
>>> obs  
{'some_name': ecole.observation.NodeBipartiteObs(), 'other_name': None}

Similarily with a tuple

>>> obs_func = (ecole.observation.NodeBipartite(), ecole.observation.Nothing())
>>> env = ecole.environment.Branching(observation_function=obs_func)
>>> obs, _, _, _, _ = env.reset("path/to/problem")
>>> obs  
[ecole.observation.NodeBipartiteObs(), None]
1

We use the term observation rather than state since the state is really the whole state of the solver, which is unaccessible. Thus, mathematically, we really have a Partially Observable Markov Decision Process.