Vector¶
Exported from sekai and sekai.vector (shared from sekai.vector only). See the
Vector environments guide.
Vector environments¶
VecEnv
¶
Bases: ABC, Generic[ObsType, ActType]
Abstract base for vectorised environments.
Runs N environments in parallel and returns batched results.
Key differences from gymnasium's VectorEnv: - observation_space is always the single-env space - batch_observation_space is always the batched space - step/reset return structured dataclasses, not tuples - infos is list[dict], not dict[str, ndarray]
Autoreset (on by default): when a sub-env finishes an episode it is reset
in the same step() call. The returned observation for that env is the
first observation of the new episode; the terminal observation and info
are in infos[i]["final_observation"] and infos[i]["final_info"], and the
reset info in infos[i]["reset_info"]. This matches the AutoReset wrapper.
Without autoreset (autoreset=False), a sub-env that finishes an episode is
frozen until the next reset(): it is not stepped again and its action is
ignored, and every later step() returns, for it, its final observation,
reward 0, its final terminated / truncated flags (so dones stays True) and
an empty info. A sub-env that resets itself (e.g. wrapped in AutoReset, so
its info carries final_observation) is not frozen.
Every VecEnv has async_step()/async_reset(); by default they run the sync methods (AsyncVecEnv overrides them to run sub-envs concurrently). As with Env, a subclass that overrides only step()/reset() gets async methods that run its overrides.
batch_observation_space
property
¶
The space of a batched observation, with a leading num_envs axis.
batch_action_space
property
¶
The space of a batched action, with a leading num_envs axis.
step
abstractmethod
¶
step(actions: Any) -> VecStepResult[Any]
Step all environments with a batch of actions.
reset
abstractmethod
¶
reset(*, seed: list[SeedType] | SeedType = None, options: list[dict[str, Any] | None] | dict[str, Any] | None = None) -> VecResetResult[Any]
Reset all environments.
seed: an int derives one independent seed per sub-env; a list gives each sub-env its own seed; None continues each sub-env's RNG stream. options: one dict for all sub-envs, or a list with one per sub-env.
close
abstractmethod
¶
Close all sub-environments and release resources.
Every sub-env is closed even if closing another one raises; the first such error is re-raised once everything is released.
async_step
async
¶
async_step(actions: Any) -> VecStepResult[Any]
Async step; the default runs step().
async_reset
async
¶
async_reset(*, seed: list[SeedType] | SeedType = None, options: list[dict[str, Any] | None] | dict[str, Any] | None = None) -> VecResetResult[Any]
Async reset; the default runs reset().
call
¶
Call a method (or read an attribute) on each sub-environment.
Like get_attr(), the name is looked up through each sub-env's wrapper stack.
get_attr
¶
Get an attribute from each sub-environment, searching its wrapper stack.
SyncVecEnv
¶
SyncVecEnv(env_fns: Sequence[Callable[[], Env[ObsType, ActType]]], *, autoreset: bool = True, copy: bool = True)
Bases: VecEnv[ObsType, ActType]
Vectorised env that runs N sub-environments sequentially in a single process.
Best for fast environments where multiprocessing overhead would dominate. Use ProcVecEnv for CPU-bound simulators.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env_fns
|
Sequence[Callable[[], Env[ObsType, ActType]]]
|
Callables that each build one sub-environment. If one raises, the sub-envs already built are closed before the error propagates. |
required |
autoreset
|
bool
|
Reset sub-envs automatically when their episode ends (see VecEnv). |
True
|
copy
|
bool
|
Kept for backwards compatibility. Batched observations are always freshly allocated arrays, so this has no effect. |
True
|
AsyncVecEnv
¶
Bases: VecEnv[ObsType, ActType]
Vectorised env using asyncio to run N environments concurrently.
Unlike ProcVecEnv (multiprocessing), this uses asyncio.gather — ideal for I/O-bound environments such as live market data feeds, REST-based simulators, or any env with async_step/async_reset overrides.
CPU-bound envs with blocking step() calls will NOT benefit from this; use ProcVecEnv instead.
From async code, use await vec.async_step(...) / await vec.async_reset(...).
The sync step()/reset() run those coroutines on a private event loop owned
by this VecEnv, so they must not be called from inside a running loop.
If an env_fn raises, the sub-envs already built are closed before the error propagates.
ProcVecEnv
¶
ProcVecEnv(env_fns: Sequence[Callable[[], Env[ObsType, ActType]]], *, autoreset: bool = True, context: str | None = None, close_timeout: float = 5.0, num_workers: int | None = None, shared_memory: bool | None = None)
Bases: VecEnv[ObsType, ActType]
Vectorised env using worker processes for true CPU-bound parallelism.
Sub-environments run in worker processes and communicate via
multiprocessing Pipes. Use this for CPU-intensive simulators; for I/O-bound
or async environments use AsyncVecEnv, and for simulators that vectorise
with array operations prefer a native vector env (see sekai.make_vec).
Two things keep the inter-process overhead low:
- Shared memory (on by default when the observation space has fixed shapes: Box, Discrete, MultiDiscrete, MultiBinary and Dict/Tuple/MaskedBox of them). Workers write observations straight into one shared block, so only rewards, flags and infos are pickled. Large observations (order books, multi-asset windows) no longer pay a serialisation cost per step.
- Several sub-envs per worker (
num_workers): with more sub-envs than CPU cores, grouping them avoids oversubscription and amortises each round trip over several envs.
The default start method is "forkserver" ("spawn" on Windows), so the
script that creates a ProcVecEnv must guard its entry point with
if __name__ == "__main__":. env_fns are sent with cloudpickle, so
lambdas and locally defined classes work.
An exception inside a sub-env is re-raised in the parent as
VecEnvWorkerError, carrying the sub-env index and the worker's traceback.
The other workers' replies are still collected, so the VecEnv stays usable
afterwards (the step results of that call are discarded, including those of
sub-envs that did advance); replies left unread by an interrupted call (e.g.
Ctrl-C, which workers ignore and leave to the parent) are discarded before
the next command. A worker that dies also raises VecEnvWorkerError: its
rank is the sub-env it ran, or, for a worker that ran several (so which one
failed is unknown), the first of them, and the message names the worker and
all its sub-envs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env_fns
|
Sequence[Callable[[], Env[ObsType, ActType]]]
|
Callables that each build one sub-environment (in its worker). If one raises (or anything else in the constructor fails), the sub-envs already built are closed and the workers stopped before the error propagates. |
required |
autoreset
|
bool
|
Reset sub-envs automatically when their episode ends (see VecEnv). |
True
|
context
|
str | None
|
multiprocessing start method; defaults to "forkserver" ("spawn" on Windows). |
None
|
close_timeout
|
float
|
Seconds close() waits for workers before terminating them. |
5.0
|
num_workers
|
int | None
|
Worker processes; sub-envs are split evenly between them. Defaults to one process per sub-env. |
None
|
shared_memory
|
bool | None
|
Pass observations through shared memory. None (default) enables it when the observation space allows; True requires it. |
None
|
close
¶
Close every sub-env, waiting up to close_timeout seconds before terminating workers.
Workers close all their sub-envs even if one fails; the first failure is re-raised as VecEnvWorkerError once every worker has stopped.
NativeVecEnv
¶
NativeVecEnv(num_envs: int, observation_space: Space[ObsType], action_space: Space[ActType], *, autoreset: bool = True, max_episode_steps: int | None = None)
Bases: VecEnv[ObsType, ActType]
Base class for environments that simulate every sub-env at once with array operations.
SyncVecEnv steps N separate Python environments one after another; a native
vector env keeps the state of all N in arrays with a leading num_envs axis
and advances them together, which is typically 10-100x faster for
simulators that vectorise naturally (most market models do). Register one
with sekai.register(..., vector_entry_point=...) and sekai.make_vec()
uses it automatically.
Subclasses implement:
_reset_envs(indices, options): (re)initialise the sub-envs inindices(an int array), with one options dict (or None) per index._observe(): the batched observation of all sub-envs._step(actions): advance every sub-env; return(rewards, terminated, truncated, infos)where the first three have shape(num_envs,)and infos maps keys to arrays with a leadingnum_envsaxis.- optionally
_reset_infos(indices): info arrays for freshly reset sub-envs.
The base class provides seeding (one numpy.random.Generator, self.rng,
shared by all sub-envs), per-env options, same-step autoreset with
final_observation / final_info / reset_info in the infos (the
contract of every sekai VecEnv), conversion of info arrays to per-env
dicts, ResetRequired from a step() before the first reset(), and an
optional time limit: with max_episode_steps=n every sub-env is truncated
after n steps and its infos carry elapsed_steps, exactly as TimeLimit
does for each sub-env of a SyncVecEnv. The elapsed_steps property counts
each sub-env's steps, limit or not.
With autoreset=False, a sub-env that finishes is frozen until the next
reset(), as in every VecEnv: step() returns its final observation, reward 0,
its final terminated / truncated flags and an empty info, whatever _step()
computes for it. _step() still advances every sub-env, so it is passed a
frozen sub-env's last action again (when actions are a numeric array), and
self.frozen tells which sub-envs are frozen: a simulation that would read
past its data, or that should keep a finished sub-env's state (e.g. for
get_attr), must leave those sub-envs as they are.
Because all sub-envs share one generator, a native env is not
draw-for-draw identical to the same env under SyncVecEnv; it is identical
in distribution, and exactly identical whenever the dynamics are
deterministic given the options (e.g. historical data with fixed starts).
reset(seed=...) takes what every VecEnv takes: an int seeds the
generator; a list of num_envs seeds is combined into one seed, where a
None entry contributes a draw from the current stream (so a list of only
None continues it, like seed=None); a Generator raises TypeError.
get_attr(name) / call(name) read attributes and methods of this object:
an array result with a leading num_envs axis is split per env, anything
else is repeated for every env, and so is everything a method or property
marked with @shared returns. set_attr(name, values) changes an existing
attribute (a list of num_envs values is stored as one array) and raises
AttributeError for a name this object does not have, since a new attribute
would have no effect on the simulation.
shared
¶
Mark a method (or property getter) of a NativeVecEnv as shared by every sub-env.
get_attr() / call() then give its value to every sub-env as it is, instead of
splitting an array with a leading num_envs axis, which would mistake e.g. a
schedule of n_steps rows for per-env values whenever n_steps == num_envs.
Example
Results¶
VecStepResult
dataclass
¶
VecStepResult(observations: ObsType, rewards: NDArray[float64], terminated: NDArray[bool_], truncated: NDArray[bool_], infos: list[InfoDict])
Bases: Generic[ObsType]
Structured result from VecEnv.step().
observations is the batch obs with shape (N, *obs_shape). infos is a list of per-env InfoDicts (not stacked numpy arrays).
VecResetResult
dataclass
¶
VecResetResult(observations: ObsType, infos: list[InfoDict])
Bases: Generic[ObsType]
Structured result from VecEnv.reset(). dones is all False, mirroring VecStepResult.
stack_infos
¶
Stack a list of per-env info dicts into {key: np.array}.
Keys that appear in all dicts are stacked; missing keys are skipped. Useful when a downstream algorithm wants numpy-array info values.
Wrapper base classes¶
VecWrapper
¶
VecWrapper(venv: VecEnv[ObsType, ActType])
Bases: VecEnv[WObsType, WActType], Generic[WObsType, WActType, ObsType, ActType]
Transparent wrapper around a VecEnv.
Like Wrapper, observation_space / action_space delegate to the wrapped
VecEnv unless assigned (always the single-env spaces). Both the sync and
async APIs are delegated, so wrapping an AsyncVecEnv keeps
await vec.async_step(...) concurrent.
VecObservationWrapper
¶
VecObservationWrapper(venv: VecEnv[ObsType, ActType])
Bases: VecWrapper[WObsType, ActType, ObsType, ActType], Generic[WObsType, ActType, ObsType]
Applies a transformation to every batch of observations.
Terminal observations that autoreset stores in infos[i]["final_observation"]
are transformed too, via final_observation(), so value bootstrapping sees
the same representation as the policy.
final_observation
¶
Transform one sub-env's terminal observation (from autoreset infos).
The default runs observation() on a batch of one. Override it if your transform is not valid for a single-row batch or has side effects (e.g. updating running statistics) that terminal observations should not cause.
VecRewardWrapper
¶
VecRewardWrapper(venv: VecEnv[ObsType, ActType])
Bases: VecWrapper[ObsType, ActType, ObsType, ActType], Generic[ObsType, ActType]
Applies a transformation to every batch of rewards.
VecActionWrapper
¶
VecActionWrapper(venv: VecEnv[ObsType, ActType])
Bases: VecWrapper[ObsType, WActType, ObsType, ActType], Generic[ObsType, WActType, ActType]
Applies a transformation to every batch of actions.
Ready-made wrappers¶
VecRecordEpisodeStatistics
¶
VecRecordEpisodeStatistics(venv: VecEnv[ObsType, ActType], tracker: StatsTracker | None = None, deque_size: int = 100)
Bases: VecWrapper[ObsType, ActType, ObsType, ActType]
Per-sub-env episode statistics for a VecEnv.
When sub-env i finishes an episode, infos[i]["episode"] is an EpisodeStats,
exactly as with the single-env RecordEpisodeStatistics, and the stats are
reported to the tracker. Works with autoreset on or off: without autoreset,
a finished sub-env is frozen (see VecEnv) and keeps reporting done, so it is
ignored until the next reset() and stepping the others on (e.g.
while not finished.all()) records each episode once.
VecNormalizeObservation
¶
VecNormalizeObservation(venv: VecEnv[Any, ActType], epsilon: float = 1e-08, *, stats: StatsKind = 'running', halflife: float = 1000.0, clip: float | None = None)
Bases: VecObservationWrapper[Any, ActType, Any]
Normalises observations with running statistics shared across all sub-envs.
The vectorised counterpart of NormalizeObservation, with the same options:
any observation space (Box leaves normalised, MaskedBox leaves over occupied
slots only, other leaves passed through), stats="running" or "ewm" (the
halflife counts vector steps), and optional clipping.
infos[i]["final_observation"] from autoreset is normalised too, with the
current statistics but without updating them.
Set update_running_mean = False to freeze the statistics.
VecNormalizeReward
¶
Bases: VecWrapper[ObsType, ActType, ObsType, ActType]
Scales rewards by the running std of each sub-env's discounted return.
The vectorised counterpart of NormalizeReward (OpenAI baselines' VecNormalize reward scaling). Returns are reset per sub-env when its episode ends. Set update_running_mean = False to freeze the statistics.