Task Spaces#
Syllabus provides the TaskSpace
class as a way to represent the entire space of classes that are playable in an environment. Task spaces extend the Gymnasium Spaces by encoding the original task description into a more efficient representation. Encoding tasks provides several benefits:
Interoperability: Encoding task spaces into their most simple representation makes it easy to implement new curriculum learning methods, without worrying about different possible task encodings.
Ease of Use: By using a different representation for tasks in the curriculum and the environment, we are able to use the most convenient representation for the environment without affecting the complexity of the curricula.
Optimization: An efficient encoding makes our multiprocessing synchronization faster and more effective. Specifically, we encode tasks into picklable objects that can be sent between processes.
Similar to Gymnasiumn Spaces, we implement separate TaskSpace
classes for different types of tasks, as well as composite TaskSpace
objects for more complex task spaces.
Usage#
You can define a task space wherever you want, typically in the task wrapper or main training script. You will need to pass this task space to both the environment synchronization wrapper and the curriculum.
from syllabus.task_space import DiscreteTaskSpace
task_space = DiscreteTaskSpace(200) # 200 discrete tasks
Task Space#
The TaskSpace is an abstract base class with default behaviors for every task space. If you choose to implement your own TaskSpace class, you should NOT override the encode
or decode
methods directly, because they provide universal type checks and error handling. Instead you should override the _encode
and _decode
methods, which are called by the default implementations of encode
and decode
.
- class syllabus.task_space.task_space.TaskSpace(space_or_value: Space | int | List | Tuple, tasks: List[Any] | None = None)[source]#
Bases:
object
TaskSpace is an extension of gym spaces that allows for efficient encoding and decoding of tasks. This is useful for environments that have a large number of tasks or require complex task representations.
Encoding tasks provides several advantages: 1. Minimizing the bandwidth required to transfer tasks between processes 2. Simplifying the task formats that curricula need to support 3. Allowing the environment to use a convenient and interpretable task format, with no impact on performance
- contains(encoding: Any) bool [source]#
Check if the encoding is a valid task in the task space.
- Parameters:
encoding (Any) – Encoding of the task
- Returns:
Boolean specifying if the encoding is a valid task
- Return type:
bool
- decode(encoding: Any) Any [source]#
Convert the task encoding to the original task representation. This method provides generic decoding safety checks for all task spaces, and calls the specific _decode method for each task space. It will throw a UsageError if the encoding cannot be decoded into the task space.
- Parameters:
encoding (Any) – Encoding of the task
- Returns:
Decoded task that can be used by the environment
- Return type:
Any
- encode(task: Any) Any [source]#
Convert the task to an efficient encoding to speed up multiprocessing. This method provides generic encoding safety checks for all task spaces, and calls the specific _encode method for each task space. It will throw a UsageError if the task is not in the task space or cannot be encoded.
- Parameters:
task (Any) – Task to encode
- Returns:
Encoded task
- Return type:
Any
- property num_tasks: int#
Return the number of tasks in the task space.
- Returns:
Number of tasks
- Return type:
int
- task_name(task: int) str [source]#
Return the name of the task.
- Parameters:
task (int) – Task to get the name of
- Returns:
Name of the task
- Return type:
str
- property tasks: List[Any]#
Return the list of all tasks in the task space.
- Returns:
List of all tasks
- Return type:
List[Any]
Discrete Task Space#
The DiscreteTaskSpace
represents a discrete set of tasks. If you do not provide any task names to the intializer, it will use the range of integers from 0 to n-1, where n is the number of tasks. The following are some valid ways to initialize a DiscreteTaskSpace
:
from gymnasium.spaces import Discrete
from syllabus.task_space import DiscreteTaskSpace
task_space = DiscreteTaskSpace(200)
task_space = DiscreteTaskSpace(Discrete(200))
task_space = DiscreteTaskSpace(200, task_names=[f"task_{i}" for i in range(200)])
task_space = DiscreteTaskSpace(Discrete(200), task_names=[f"task_{i}" for i in range(200)])
Box Task Space#
The BoxTaskSpace
represents one or more continuous parameters that define a task. It does not encode or decode the task, it simply checks if the task is within the bounds of the task space. The following are some valid ways to initialize a BoxTaskSpace
:
from gymnasium.spaces import Box
from syllabus.task_space import BoxTaskSpace
task_space = BoxTaskSpace(Box(low=0.0, high=1.0))
task_space = BoxTaskSpace(Box(low=[0, 0], high=[1, 1], shape=(2,)))
task_space = BoxTaskSpace(Box(low=[[0, 0], 0.5, 0.5], high=[[1, 1], [1.5, 1.5]], shape=(2, 2)))
- class syllabus.task_space.task_space.BoxTaskSpace(space_or_value: Space | int | List | Tuple, tasks: List[Any] | None = None)[source]#
Bases:
TaskSpace
Task space for continuous tasks.
- contains(encoding: ndarray) bool [source]#
Return boolean specifying if encoding is a valid member of this space.
- Parameters:
encoding (np.ndarray) – Encoding of the task
- Returns:
Boolean specifying if encoding is a valid task
- Return type:
bool
- property num_tasks: int#
Return the number of tasks in the task space.
- Returns:
Number of tasks
- Return type:
int
- sample() ndarray [source]#
Sample a task from the task space.
- Returns:
Sampled task
- Return type:
np.ndarray
- task_name(task: ndarray) str [source]#
Return the name of the task.
- Parameters:
task (np.ndarray) – Task to get the name of
- Returns:
Name of the task
- Return type:
str
- property tasks: List[Any]#
Return the list of all tasks in the task space.
- Returns:
List of all tasks
- Return type:
List[Any]
MultiDiscrete Task Space#
The MultiDiscreteTaskSpace
represents more than one discrete parameters that define a task. It can either encode each component of the task separately, or if you set flatten=True
, it can encode the entire task as a single integer. The following are some valid ways to initialize a MultiDiscreteTaskSpace
:
from gymnasium.spaces import MultiDiscrete
from syllabus.task_space import MultiDiscreteTaskSpace
task_space = MultiDiscreteTaskSpace([2, 3, 4])
task_space = MultiDiscreteTaskSpace(MultiDiscrete([2, 3, 4]))
task_space = MultiDiscreteTaskSpace([2, 3, 4], [["a", "b"], [0.2, 3.4, -1.1], [5, 6, 7, 8]])
task_space = MultiDiscreteTaskSpace(MultiDiscrete([2, 3, 4]), [["a", "b"], [0.2, 3.4, -1.1], [5, 6, 7, 8]])
- class syllabus.task_space.task_space.MultiDiscreteTaskSpace(space_or_value: MultiDiscrete | int, tasks: List[Any] | Tuple[Any] | None = None, flatten: bool = False)[source]#
Bases:
TaskSpace
Task space for multi-discrete tasks.
- property num_tasks: int#
Return the number of tasks in the task space.
- Returns:
Number of tasks
- Return type:
int
- property tasks: List[Any]#
Return the list of all tasks in the task space.
- Returns:
List of all tasks
- Return type:
List[Any]
Tuple Task Space#
The TupleTaskSpace
represents a tuple of task spaces. It is useful for representing complex task spaces that are composed of simpler task spaces. It can encode tasks as tuples, where each element of the tuple is encoded by the corresponding task space. You can also set flatten=True
to encode the entire task as a single integer. The following are some valid ways to initialize a TupleTaskSpace
:
from syllabus.task_space import TupleTaskSpace
task_space = TupleTaskSpace([DiscreteTaskSpace(2), BoxTaskSpace(Box(low=0.0, high=1.0))])
task_space = TupleTaskSpace([DiscreteTaskSpace(2), BoxTaskSpace(Box(low=0.0, high=1.0))], flatten=True)
Note: We do not use the Tuple
space from Gymnasium to initialize the TupleTaskSpace
.
- class syllabus.task_space.task_space.TupleTaskSpace(task_spaces: Tuple[TaskSpace], space_names: Tuple | None = None, flatten: bool = False)[source]#
Bases:
TaskSpace
Task space for tuple tasks. Can be used to combine multiple task spaces into a single task space.
- contains(encoding: int) bool [source]#
Check if the encoding is a valid task in the task space.
- Parameters:
encoding (int) – Encoding of the task
- Returns:
Boolean specifying if the encoding is a valid task
- Return type:
bool
- property num_tasks: int#
Return the number of tasks in the task space.
- Returns:
Number of tasks
- Return type:
int
- sample() Tuple[Any] [source]#
Sample a task from the task space.
- Returns:
Sampled task
- Return type:
Tuple[Any]
- task_name(task: Tuple[int]) str [source]#
Return the name of the task.
- Parameters:
task (Tuple[int]) – Task to get the name of
- Returns:
Name of the task
- Return type:
str
- property tasks: List[Any]#
Return the list of all tasks in the task space.
- Returns:
List of all tasks
- Return type:
List[Any]