Observation Functions#
In this section we describe the observation function protocol, the observation function registry, and how to write your own custom observation functions.
The ObservationFunction Protocol#
An observation function is a generative function which represents a
(stochastic) mapping from a state to an observation. Using the
typing
standard library, the observation function type is defined as
a callable typing.Protocol
which receives a
State
and an optional
numpy.random.Generator
, and returns an
Observation
.
- class ObservationFunction(*args, **kwargs)[source]
- __call__(state, *, rng=None)[source]
Call self as a function.
- Return type
Note
An observation function may (and often does) accept additional arguments;
this is possible so long as the extra arguments either have default
values, or are binded to specific values later on, e.g., using
functools.partial()
.
The Observation Function Registry#
The observation_functions
module contains some
pre-defined observation functions and the
observation_function_registry
,
a dictionary-like object through which to register and retrieve observation
functions. Observation functions are registered using the
register()
method,
which can be used as a decorator (also see
Custom Observation Functions). As a dictionary,
observation_function_registry
has a keys()
method which returns the names of registered
functions.
Custom Observation Functions#
Note
While writing your own observation function directly is indeed a possibility,
the most common way to implement new observation functions is by writing a
custom VisibilityFunction
and using it with the
from_visibility()
observation function.
Custom observation functions can be defined so long as they satisfy some basic rules; A custom observation function:
MUST satisfy the
ObservationFunction
protocol.SHOULD use the
rng
argument as the source for any stochasticity.MUST use
get_gv_rng_if_none()
(only if therng
is used at all).
Warning
The rng
argument is used to control the source of randomness and allow
for the environment to be seeded via
set_seed()
, which in turn
guarantees the reproducibility of traces, runs, and experiments; if you wish
to use external sources of randomness, you will have to manage them and their
seeding yourself.
Practical Example#
Note
The examples shown here can be found in the examples/
folder.
In this example, we are going to write an observation function which simulates a satellite view of the agent, i.e., a view from the top (being able to see through walls), and with the agent being off-center in each observation.
from typing import Optional
import numpy.random as rnd
from gym_gridverse.agent import Agent
from gym_gridverse.envs.observation_functions import (
observation_function_registry,
)
from gym_gridverse.geometry import Area, Position, Shape
from gym_gridverse.observation import Observation
from gym_gridverse.rng import get_gv_rng_if_none
from gym_gridverse.state import State
@observation_function_registry.register
def satellite(
state: State,
*,
shape: Shape,
rng: Optional[rnd.Generator] = None,
) -> Observation:
rng = get_gv_rng_if_none(rng) # necessary to use rng object!
# randomly sample an area of the given shape which includes the agent
# (also avoids putting the agent on the edge)
y0: int = rng.integers(
state.agent.position.y - shape.height + 2,
state.agent.position.y - 1,
endpoint=True,
)
x0: int = rng.integers(
state.agent.position.x - shape.width + 2,
state.agent.position.x - 1,
endpoint=True,
)
area = Area((y0, y0 + shape.height - 1), (x0, x0 + shape.width - 1))
observation_grid = state.grid.subgrid(area)
observation_agent = Agent(
state.agent.position - Position(y0, x0),
state.agent.orientation,
state.agent.grid_object,
)
return Observation(observation_grid, observation_agent)