Core Utilities¶
The machinery the rest of Eden is built on: registries, the configurable mixin that binds an options class to its runtime object, parameter descriptions, multi-GPU helpers, logging and run identity, profiling, and small string and formatting helpers.
Package entry point¶
import eden isolates one GPU per DDP rank before CUDA is initialized, so import Eden before genesis and torch.
It imports neither itself; eden.init() does, and every process that builds a scene calls it. The built-in term
modules and the options namespaces (eden.rewards, eden.robots, eden.scenes, ...) load lazily on first use.
Eden package entry point: per-rank GPU isolation for DDP and runtime initialization.
Importing :mod:eden performs per-rank GPU isolation before CUDA is initialized,
so Eden must be imported before genesis and torch (importing those first would
pin every rank to the same device). :func:init then configures the torch backends,
determinism, and Genesis logging.
import eden imports neither genesis nor torch: between them they would be ~2.8 s of
the import, paid by eden --help, by eden list, and by every script that only reads a
config. Both are imported by :func:init, which every process that builds a scene calls anyway.
The isolation runs at the top of this module, so any import order that starts with
import eden is correct by construction -- but a module that wants genesis must import it
itself rather than relying on Eden having pulled it in.
Common entry points:
- :func:
init— set up the runtime (TF32, determinism, per-rank logging). - :func:
get_log_dir(a.k.a.eden.log_dir) — this run's log directory. eden.envs.base.RLEnvBase.from_config— build a vectorized environment from a config.- :data:
eden.tasks.TASK_REGISTRY— look up built-in task configs by name.
Functions:¶
init
¶
init(backend=_DEFAULT_BACKEND, initialize_genesis: bool = True, debug: bool = False, logging_level: int | None = None, logger_verbose_time: bool = False, log_root_path: str | None = None, run_name: str = '', allow_tf32: bool = True, deterministic: bool = False, performance_mode: bool = False, precision: Literal['32', '64'] = '32', eps: float = 1e-08, *, logger_theme: Literal['auto', 'human', 'agent'] | None = None)
Initialize the Eden environment.
| PARAMETER | DESCRIPTION |
|---|---|
backend
|
The backend device to use for the simulation (
DEFAULT:
|
initialize_genesis
|
Whether to initialize the Genesis here if not already initialized.
TYPE:
|
debug
|
Whether to run in debug mode.
TYPE:
|
logging_level
|
The logging level to use.
TYPE:
|
logger_verbose_time
|
Whether to log the time in the logger.
TYPE:
|
log_root_path
|
The root path to use for the logs.
Defaults to
TYPE:
|
run_name
|
Optional human-facing run name folded into the log-dir basename as
TYPE:
|
allow_tf32
|
Whether to use TF32 precision for faster computation on Ampere+ GPUs.
TYPE:
|
deterministic
|
Whether to use deterministic algorithms for reproducibility.
TYPE:
|
performance_mode
|
Whether to use performance mode for the simulation.
TYPE:
|
precision
|
Floating-point precision passed to
TYPE:
|
eps
|
Global numerical epsilon passed to
TYPE:
|
logger_theme
|
Keyword-only. How Eden (and Genesis) render their console output.
TYPE:
|
Source code in eden/__init__.py
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | |
get_log_dir
¶
get_log_dir() -> str
Return this run's log directory, creating it if it does not exist yet.
eden.log_dir is an alias for this call, so f"{en.log_dir}/video.mp4" and
os.path.join(en.log_dir, "config.json") keep working unchanged.
Creation is deferred to the first access rather than done in :func:init so that a
process which never logs anything — an evaluation, export, or viewer script, all of
which must still call :func:init before they can build a scene — leaves no empty
logs/<timestamp>/ directory behind. That matters beyond tidiness: the usual way
to find a trained run is to take the newest directory under logs/, and an
eagerly-created directory makes that resolve to the current process's own empty one.
| RETURNS | DESCRIPTION |
|---|---|
str
|
Absolute path to the log directory. |
| RAISES | DESCRIPTION |
|---|---|
RuntimeError
|
If :func: |
Source code in eden/__init__.py
get_run_id
¶
get_run_id() -> str | None
Return this run's base-36 id (the uniqueness primitive of the log-dir basename).
None before :func:init has resolved a run. Provenance metadata only — a caller
that needs a directory should use :func:get_log_dir / eden.log_dir.
Source code in eden/__init__.py
Registries¶
A Registry maps a name to an object. Keys default to to_snake_case(obj.__name__). A TermRegistry (every term
kind) takes no name=: a term's key is always its own snake-cased name, because that is what .configure() stamps
into options.name and what the manager looks it up by. A lookup of a missing key raises KeyError with the closest
match and the available keys.
Generic Registry plus term/variant registries for pluggable components.
A :class:Registry maps name -> object so user code can register custom terms, tasks,
robots, etc. Keys default to to_snake_case(obj.__name__); pass name= to choose a
different one. Two naming caveats: keys starting with _ emit a warning, and class names
with a digit suffix (e.g. Foo1) do not gain an underscore before the digit.
Configurables (terms, file handlers, dataset modifiers, terrain generators — anything whose
.configure() stamps options.name and is resolved back out of its registry by that name)
are the exception: their key is to_snake_case(__name__), so :class:TermRegistry takes no
name= at all, and :class:Registry refuses one that would alias a configurable. See
:func:_key_is_derived_elsewhere.
Classes¶
Registry
¶
Bases: Iterable[tuple[str, Any]]
Registry providing a name -> object mapping to support third-party custom modules.
Keys default to to_snake_case(obj.__name__); pass name= to choose a different one.
That override is for objects looked up only by their key — assets, robots, tasks. A
:class:~eden.utils.common.ConfigurableMixin is not one of them: its key is also what
configure() stamps into options.name, so name= on one is refused rather than
honoured (see :func:_key_is_derived_elsewhere), and :class:TermRegistry — whose entries
are all configurable — takes no name= at all.
To create a registry (e.g. a robot registry):
To register an object:
Or:
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of this registry, quoted in every message it raises.
TYPE:
|
populated_by
|
Modules whose import-time |
Source code in eden/utils/registry.py
Methods:¶
register
¶
Register the given object, usable as either a decorator or a direct call.
Both forms return the registered object, so the direct call can be chained and the decorator leaves the decorated name bound to it. See the class docstring for usage.
Source code in eden/utils/registry.py
get
¶
Source code in eden/utils/registry.py
TermRegistry
¶
TermRegistry(name: str, term_class: Type[ManagerTermBase], term_fn_wrapper_class: Type[ManagerTermFuncWrapperBase] | None = None, populated_by: Sequence[str] = ())
Bases: Registry
Registry for manager terms.
Every entry is keyed by its own to_snake_case(__name__) and :meth:register takes no
name=, so two terms can only collide by being named the same thing.
| PARAMETER | DESCRIPTION |
|---|---|
name
|
The name of the registry.
TYPE:
|
term_class
|
The class of the manager term.
TYPE:
|
term_fn_wrapper_class
|
The class of the manager term function wrapper.
TYPE:
|
populated_by
|
Modules whose import-time registrations fill this registry — see
:meth: |
Source code in eden/utils/registry.py
Attributes¶
cli_kind
property
¶
cli_kind: str
What eden terms --kind calls this registry: REWARD_TERM -> reward.
Derived rather than declared so a new registry cannot ship with the wrong one, and pinned
against :data:eden.managers.catalog.KIND_NAMES by tests/utils/test_registry.py, beside
tests/managers/test_catalog.py asserting the catalog covers every registry in the tree.
term_class
property
¶
term_class: Type[ManagerTermBase]
The base class every class term in this registry subclasses.
func_wrapper_class
property
¶
func_wrapper_class: Type[ManagerTermFuncWrapperBase] | None
The wrapper a function term of this kind is configured through, or None.
None means the kind takes class terms only — :meth:_register_term refuses a plain
function there. Exposed (with :attr:term_class) so :mod:eden.managers.catalog can read
the per-kind fields every term carries off the base, rather than reaching into _-private
attributes to describe a registry it does not own.
Methods:¶
catalog_hint
¶
One line pointing at the eden terms command that answers what the error left open.
The roster :meth:Registry.get prints answers "which terms are there?" but not "which one
does what I need?" or "what does this one take?", and a reader who cannot answer those
reaches for the 92 kB generated catalog or greps the installed source. Both
wordings live here so the kind is derived once and the two cannot disagree. One line, no
newline: the lookup caller raises KeyError, whose __str__ is repr(args[0]).
| PARAMETER | DESCRIPTION |
|---|---|
term
|
A registered term to point at, when there is one.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
A sentence beginning with |
Source code in eden/utils/registry.py
registered_class
¶
The class the term registered under key is built as, which is where its defaults live.
A class term is stored as its class — the defaults-carrying subclass when register was
given keyword defaults (:meth:_with_defaults). A function term is stored as
partial(wrapper, func=...), and wrapper is the per-registration subclass carrying
its keyword defaults, not the kind's shared *FuncWrapper: that subclass is the only
place @METRIC_TERM_REGISTRY.register(direction=MetricDirection.LIB) on a function is
recorded, so a default read off the shared wrapper is the kind's, not the term's.
| PARAMETER | DESCRIPTION |
|---|---|
key
|
A registered key. An unknown one raises the :meth:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
type
|
The registered class term, or the wrapper class of a function term. |
Source code in eden/utils/registry.py
register
¶
Register a class term, a function term, or a func-wrapper subclass.
Takes no name: a term's key is always its own to_snake_case(__name__) — see
:data:_WHY_THE_KEY_IS_THE_NAME, the cause this refusal shares with :func:_derive_name.
name is declared only so it can be refused — **term_defaults would otherwise
swallow it and quietly set a class attribute called name on the term instead.
Source code in eden/utils/registry.py
VariantRegistry
¶
Bases: Registry
Registry for asset families with multiple mesh-file variants.
A single decorated class declares its source directory name and the list of
available variant ids. The decorator stamps _base_name and
_default_variant (the first id in variant_list) onto the class,
merges family metadata into cls.metadata, and registers one
partial(cls, variant=N) callable per variant under the key
f"{base_name}_{N}". The decorated class itself is also returned so it
stays importable for type-level use.
Asset families (objaverse, ...) follow this contract by declaring
a base options class that defines _base_name/_default_variant as
ClassVar and an __init__ (or model_post_init) that materializes
file from those + the requested variant.
Calling register() with neither base_name nor variant_list falls
through to :class:Registry's plain-name registration, so procedural
primitives (boxes, spheres) can share the same registry as variant assets.
Source code in eden/utils/registry.py
Methods:¶
register
¶
register(obj: Any = None, *, name: str | None = None, base_name: str = '', variant_list: Sequence[int] | None = None) -> Any
Source code in eden/utils/registry.py
Functions:¶
module_is_initializing
¶
Whether module is still executing its own body — a partial import, not a finished one.
importlib.import_module returns immediately, with the half-built module, when the calling
thread is already importing it; only a different thread blocks until the body is done. This
is the flag the import system itself reads to tell those apart
(importlib._bootstrap._find_and_load), and two callers need it for the same reason — to
know whether what they just got back is complete enough to draw a conclusion from:
:meth:Registry._populate before it memoizes _populated, and
ManagerTermOptions._resolve_term_registry before it reports "no registry claims this
class".
ModuleSpec._initializing is private, hence the defensive getattr chain — though not
obscure: it has been there since the spec protocol landed in 3.4. Absent (or an object with
no __spec__, such as a C extension loaded the old way) reads as finished.
| PARAMETER | DESCRIPTION |
|---|---|
module
|
The module :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
Source code in eden/utils/registry.py
Configurable objects¶
ConfigurableMixin[Options] binds a runtime class (a term, a deployment, a dataset, ...) to its options class:
Cls.configure(**kwargs) returns validated options, and constructing the object from them mirrors the options onto
it.
ConfigurableMixin: bind options classes to their runtime objects.
Attributes¶
Classes¶
ConfigurableMixin
¶
ConfigurableMixin(options: T)
Bind a runtime object (term / entity) to its declarative Options class.
The binding is reflection-driven, so subclasses just declare class attributes and get a matching options class "for free":
- Options class discovery — :meth:
__init_subclass__reads theConfigurableMixin[T]type argument off__orig_bases__and stores it as_options_class_. - Parameter discovery — :meth:
get_parameter_nameswalks the MRO and collects every plain (non-function, non-underscore, non-property/classmethod/staticmethod) class attribute, minus_non_parameter_names; those are the configurable parameters. - Options ↔ attribute mirroring — :meth:
configurepacks the parameters into an_options_class_instance, and__init__mirrors them back onto the runtime object via :func:mirror_options_onto(deep-copying both the options' values and the class-level defaults, so an instance never aliases either).
_extra_option_params (below) are the one documented exception to the mirroring.
Source code in eden/utils/common.py
Attributes¶
Methods:¶
get_parameter_names
classmethod
¶
Source code in eden/utils/common.py
fingerprint_names
classmethod
¶
The parameters :meth:configured_fingerprint reads, name-sorted, leaving out exclude.
Resolving them walks the class's MRO (:meth:get_parameter_names). A caller that fingerprints one object
on every step resolves them once and hands them to :meth:configured_fingerprint as names.
Source code in eden/utils/common.py
configured_fingerprint
¶
configured_fingerprint(exclude: Iterable[str] = (), names: tuple[str, ...] | None = None) -> tuple[tuple[str, Hashable], ...]
Every configured parameter's current value, by name, as :func:frozen_value makes it comparable.
Two objects with equal fingerprints are configured alike right now; one fingerprint taken twice
differs exactly when a parameter changed in between (a curriculum moving it, a nested options model
edited in place, a tensor rebound). Name-sorted, so the pairs line up between two calls. exclude
leaves parameters out; names gives the parameters as :meth:fingerprint_names already resolved them,
instead of exclude.
Source code in eden/utils/common.py
get_parameter_specs
classmethod
¶
:meth:get_parameter_names with each parameter's declared type and default.
Name-sorted, because get_parameter_names collects into a set and its order is
therefore arbitrary — while an error message quoting the accepted set, and the generated
catalogue page, both have to be reproducible.
Nothing is ever reported required: a class parameter is a class attribute, so it
always has a default (an annotation-only declaration is not a parameter at all — see
:meth:get_parameter_names). The required/optional split exists only for function terms,
in :meth:ConfigurableFuncWrapperMixin.get_function_parameter_specs.
Source code in eden/utils/common.py
get_configure_specs
classmethod
¶
Every parameter :meth:configure accepts — what an "invalid parameter" error must quote.
:meth:get_parameter_specs plus the options-only :attr:_extra_option_params, whose type
and default are read off the options class because they have no class attribute to read one
from (that is exactly what makes them options-only).
Source code in eden/utils/common.py
configure
classmethod
¶
configure(**kwargs) -> T
Configure the class with the given parameters.
| PARAMETER | DESCRIPTION |
|---|---|
**kwargs
|
The parameters to configure the class with.
DEFAULT:
|
| RETURNS | DESCRIPTION |
|---|---|
T
|
The options instance for the class. |
Source code in eden/utils/common.py
ConfigurableFuncWrapperMixin
¶
ConfigurableFuncWrapperMixin(options: T)
Bases: ConfigurableMixin[T]
Source code in eden/utils/common.py
Attributes¶
Methods:¶
get_function_parameter_specs
classmethod
¶
get_function_parameter_specs(func: FunctionType) -> tuple[ParamInfo, ...]
The params={...} entries an author supplies for func, typed and defaulted.
The listable counterpart of :meth:get_required_function_parameter_names, which returns
only the subset a rejection may be based on. This is wider on purpose — see
:func:~eden.utils.params.function_param_specs — because describing a term means naming
every key it accepts, not only the ones configure() will refuse to go without.
Source code in eden/utils/common.py
get_required_function_parameter_names
classmethod
¶
get_required_function_parameter_names(func: FunctionType) -> set[str]
Parameters of func without a default that the author must supply via params.
Keyword-only and positional-or-keyword params both count (compute calls
func(env=..., **params), so either is supplied by keyword), matching what
:meth:get_function_parameter_specs advertises as required. _manager_injected_params
(env, envs_idx) are excluded.
Source code in eden/utils/common.py
configure
classmethod
¶
configure(func: FunctionType, **kwargs) -> T
Configure the class with the given parameters.
| PARAMETER | DESCRIPTION |
|---|---|
func
|
The function to configure the class with.
TYPE:
|
**kwargs
|
The parameters to configure the class with.
DEFAULT:
|
| RETURNS | DESCRIPTION |
|---|---|
T
|
The options instance for the class. |
Source code in eden/utils/common.py
Functions:¶
frozen_value
¶
value as a hashable constant that compares equal exactly when the value is the same configuration.
For keying or fingerprinting what a term was configured with (:meth:ConfigurableMixin.configured_fingerprint).
A scalar, string or enum is itself. A tensor is where it lives (its storage address, shape, strides and dtype),
not its values: two terms reading one tensor are configured alike, and a tensor written in place is the same
configuration while one rebound to another tensor is not. A numpy array is its values (a host array is read
as a constant, so one edited in place is another configuration). A list, tuple, set or dict is its items,
and an options model or a dataclass is its fields, so a value edited in place inside one is seen; past
_FROZEN_DEPTH levels, and for any other object, a value is its identity.
Source code in eden/utils/common.py
mirror_options_onto
¶
mirror_options_onto(obj: ConfigurableMixin, options: ConfigurableOptions) -> None
Mirror every configurable parameter of obj from options onto obj itself.
A parameter the options do not carry falls back to obj's own class attribute. Both
sources are deep-copied, so a runtime object never shares a mutable value with
- its class — otherwise every instance is handed the same list/dict, and mutating one instance leaks into all the others and permanently corrupts the class default for the rest of the process; or
- its options — otherwise mutating the runtime attribute in place rewrites the
config
EnvBase.configserializes, and two objects built from one options instance (a config reused across envs) silently share state.
:class:Entity, :class:Sensor and :class:Camera call this directly rather than
delegating __init__ to :class:ConfigurableMixin: their options also carry fields
that are consumed instead of mirrored (morph construction, the nested Genesis
sensor/camera options), which the mixin's unused-parameter check would reject.
Two consequences of copying the options' side. A mirrored attribute and
obj._options.<name> are independent from here on, so code that mutates one must not
expect the other to follow — read a mirrored field off obj, not off the options. And a
built Genesis :class:Surface (the non-SurfaceOptions half of SurfaceLike) is
aliased, not copied — see :func:_copy_mirrored_value for why.
Source code in eden/utils/common.py
Parameter descriptions¶
How a configurable's parameters are described, shared by eden terms, the term catalog and
the configure() errors so all three word a parameter identically.
How a configurable's parameters are described — and the wording that describes them.
A term's accepted parameter set has to be visible outside its signature: an author who guessed a parameter name wrong learns nothing from an error that names only the offending key. This module is the single place that renders a parameter (name, type, default) and the single place that words the "that parameter is not accepted" errors, so:
- the three checks that run at construction (
ManagerTermOptions._validate_param_names), atconfigure()and at build (ConfigurableFuncWrapperMixin) cannot drift in wording, and eden termsand :mod:eden.managers.catalogdescribe a term exactly the way its errors do.
No eden imports (only :mod:inspect, :mod:typing and pydantic's TypeAdapter), so both
:mod:eden.options.managers.base and :mod:eden.utils.common can reach it — which is also why
invalid_function_param_keys lives here rather than with the manager options (see its docstring).
Classes¶
ParamInfo
dataclass
¶
One configurable parameter, as an author has to spell it: name, type, default.
Every field is already-rendered text rather than a live object. The only consumers are
human- and agent-readable listings, and the term modules use from __future__ import
annotations, so a signature's annotations arrive as source text anyway.
Functions:¶
render_annotation
¶
annotation as source text, or "" when it is absent.
An annotation of literal None also renders as "": it is indistinguishable from
"unannotated" for a class attribute (dict.get returns None for both) and no term
declares one.
Source code in eden/utils/params.py
render_default
¶
value as a one-line literal an author could paste back into a config.
Enum members by name — repr gives <ReferenceSource.DEFAULT: 'default'>, which is not
something you can write — and any line breaks collapsed to single spaces, so a multi-line repr
cannot split the markdown table row or terminal line it is rendered into.
Source code in eden/utils/params.py
function_param_specs
¶
function_param_specs(func: FunctionType, *, exclude: Collection[str] = ()) -> tuple[ParamInfo, ...]
The params={...} entries an author supplies for function term func, in signature order.
Every named parameter except those in exclude (the manager-injected env / envs_idx)
is listed, because every one of them is a legal params key: compute calls
func(env=..., **params) and invalid_function_param_keys validates against the whole
signature. That deliberately includes positional-or-keyword params — Eden's built-ins declare
author-supplied params keyword-only, but nothing stops an out-of-tree term from doing otherwise,
and omitting one from the listing would describe the term wrongly.
*args / **kwargs are dropped: neither is a parameter an author can name. A **kwargs
term accepts any key (see invalid_function_param_keys), which a name list cannot express.
Source code in eden/utils/params.py
render_params
¶
specs as one comma-separated line, required first — or "(none)".
Required first because that is the part an author is missing when this text appears in an error; the rest is reference. Stable within each half (input order is signature order).
Source code in eden/utils/params.py
invalid_function_param_keys
¶
Return the params keys that are not parameters of func.
Empty when every key is a parameter of func — or when func accepts **kwargs,
in which case any key is legal. Shared by the construction-time check
(:meth:~eden.options.managers.base.ManagerTermOptions._validate_param_names) and the
build-time backstop (ConfigurableFuncWrapperMixin.__init__) so the two can't drift.
Callers that need the accepted set for a message build it from :func:function_param_specs,
which also carries the types and defaults a bare name list cannot.
Here rather than in :mod:eden.options.managers.base because :mod:eden.utils.common is its
other caller and eden.options.entities reaches back into eden.utils (via
eden.utils.terrains) — so a utils module that imports eden.options at module scope is a
cycle as soon as it is the first of the three to be imported.
This module's contract is exactly that: no eden imports, reachable from either side.
| PARAMETER | DESCRIPTION |
|---|---|
func
|
The function term whose signature defines the legal keys.
TYPE:
|
params
|
The
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
set[str]
|
The offending keys, empty when there are none. |
Source code in eden/utils/params.py
invalid_params_message
¶
invalid_params_message(term_name: str, invalid_keys: Collection[str], accepted: Iterable[ParamInfo]) -> str
The one wording for "these params keys are not parameters of this term".
Shared by the construction-time, configure()-time and build-time checks so an author who
hits it twice reads the same sentence, and so the accepted set is never omitted by one of them.
Source code in eden/utils/params.py
unknown_parameter_names
¶
unknown_parameter_names(extras: Iterable[str], configurable: type, *, tolerated: Collection[str] = ()) -> set[str]
The names in extras (an options object's extra fields) that configurable takes no parameter for.
The one check behind every options class that carries a configurable's parameters as extras (term options,
camera overlay options): a parameter is a class parameter (get_parameter_names) or an options-only one
(_extra_option_params). The options' own metadata (_option_*) always passes, and tolerated names what
else a caller's options carry that is not a parameter (term options' class_name).
Source code in eden/utils/params.py
invalid_parameters_message
¶
invalid_parameters_message(subject: str, invalid_keys: Collection[str], accepted: Iterable[ParamInfo], retired: Mapping[str, str]) -> str
The one wording for "these are not parameters of subject" — a class term or configurable.
The counterpart of :func:invalid_params_message for top-level parameters, shared by the same
three checks. A key in retired (a class's _retired_params) also gets that class's
migration note: a retired name is still invalid, but a config saved before it was retired should
fail with directions, not only with the list of what is accepted now.
Source code in eden/utils/params.py
validated_parameter_values
¶
validated_parameter_values(configurable: type, values: Mapping[str, Any], *, subject: str) -> dict[str, Any]
values with each of configurable's parameters validated against its declared type and converted the way
pydantic converts a declared options field: a list a JSON file read back becomes the declared tuple, "0.5"
becomes 0.5.
For options that carry a configurable's parameters as extras, which pydantic leaves exactly as written. A key that
is not a parameter passes through untouched; :func:unknown_parameter_names is what refuses those.
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
For a value the declared type does not admit ( |
TypeError
|
For a parameter whose annotation cannot be resolved (a type imported only for type checking) or validated. |
Source code in eden/utils/params.py
Multi-GPU (DDP)¶
Rank and world-size queries and process-group setup. Also reachable as eden.distributed. See the
multi-GPU guide.
Distributed Data Parallel (DDP) utilities for Eden.
This module provides a simple, universal interface for multi-GPU training with PyTorch DDP. Eden uses the NCCL backend for collective communication.
import eden restricts each process's CUDA_VISIBLE_DEVICES to one
physical GPU so that Genesis and PyTorch both see cuda:0. The NCCL process group is
initialized early in eden.init() — right after gs.init() and
before any scene building — so that NCCL gets a clean CUDA context.
Usage with torchrun::
torchrun --standalone --nproc_per_node=2 my_script.py
The module reads LOCAL_RANK, RANK, and WORLD_SIZE environment
variables set by torchrun (or any compatible launcher).
Functions:¶
setup
¶
setup(backend: str = 'nccl') -> None
Initialize the torch.distributed process group.
This uses init_method="env://", which expects MASTER_ADDR,
MASTER_PORT, RANK, and WORLD_SIZE to be set by the launcher
(e.g. torchrun).
| PARAMETER | DESCRIPTION |
|---|---|
backend
|
Communication backend (default
TYPE:
|
Source code in eden/utils/distributed.py
cleanup
¶
barrier
¶
main_only
¶
Make a decorated function execute only on the main process.
Source code in eden/utils/distributed.py
Run identity and provenance¶
The per-run reproducibility manifest (run.json).
Eden's canonical run identity is the log-directory basename <timestamp>-<run id>
(resolved in :mod:eden.__init__). This module writes a sibling run.json recording
everything needed to reproduce or trace a run: the applied seed, the eden and Genesis
versions, the eden working-tree git state, the RL library that wrote the checkpoints, the
full command line, and the task.
It is deliberately separate from the per-config config.json envelope (_meta),
which is re-stamped on every save_as_json — including deploy_config.json — and
describes the task, not the run instance. run.json is written exactly once, at
launch, into the (lazily created) log directory.
Attributes¶
RL_LIBRARY_DISTRIBUTIONS
module-attribute
¶
Functions:¶
get_rl_library
¶
The RL library record for backend, or None when there is nothing to record.
| PARAMETER | DESCRIPTION |
|---|---|
backend
|
Eden's runner-backend key — a key of :data:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict | None
|
|
Source code in eden/utils/run_manifest.py
read_run_manifest
¶
Read <log_dir>/run.json, or None if it is missing or unreadable.
Best-effort by contract: callers enrich diagnostics with this, so a run predating the manifest must degrade to "unrecorded", never to an error.
| PARAMETER | DESCRIPTION |
|---|---|
log_dir
|
A run's log directory (the one holding
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict | None
|
The parsed manifest, or |
Source code in eden/utils/run_manifest.py
build_run_manifest
¶
build_run_manifest(*, log_dir: str, run_id: str | None, timestamp: str | None, task: str, seed: int | None, world_size: int, run_name: str, argv: list[str], resumed_from: str | None = None, rl_backend: str | None = None) -> dict[str, Any]
Assemble the run-manifest dict, resolving the eden/Genesis/RL-library versions.
Split from :func:write_run_manifest so the field set can be asserted without
touching the filesystem. Not pure: resolving the versions shells out to git and reads
installed-package metadata, which is why tests stub those three lookups.
| PARAMETER | DESCRIPTION |
|---|---|
log_dir
|
The run's log directory (its basename is the canonical run identity).
TYPE:
|
run_id
|
This run's base-36 id, from :func:
TYPE:
|
timestamp
|
This run's creation timestamp, from :func:
TYPE:
|
task
|
The registered task name being trained.
TYPE:
|
seed
|
The base seed applied to the run's RNGs (
TYPE:
|
world_size
|
Number of DDP ranks (1 for single-GPU). Makes
TYPE:
|
run_name
|
The human-facing run name (
TYPE:
|
argv
|
The full process command line (typically |
resumed_from
|
Basename of the parent run this one resumed from (
TYPE:
|
rl_backend
|
Eden's runner-backend key (
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict
|
The JSON-serializable manifest. |
Source code in eden/utils/run_manifest.py
write_run_manifest
¶
Write manifest to <log_dir>/run.json and return the path.
Overwrites any existing manifest (a run writes it exactly once, at launch). The
directory is assumed to exist: the caller resolves log_dir through
eden.log_dir, which creates the directory on first access.
| PARAMETER | DESCRIPTION |
|---|---|
log_dir
|
The run's (already-created) log directory.
TYPE:
|
manifest
|
The manifest to write, from :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The path to the written |
Source code in eden/utils/run_manifest.py
The name pip install and importlib.metadata know Eden by (not its import name), and how to install an extra.
Eden imports as eden and is distributed as eden-world: the PyPI name eden belongs to an unrelated Kivy
project, so pip install eden installs the wrong package. [project].name in pyproject.toml is the source of
truth; tests/test_pyproject.py pins :data:DISTRIBUTION_NAME to it, and checks that every
pip install "<name>[<extra>]" written in eden/, docs/ and examples/ names this distribution and an
extra it declares.
A message that tells the reader how to install an extra says it with :func:install_hint, which names both commands:
whoever reads it may have Eden from PyPI or a clone. An extra in :data:UV_ONLY_EXTRAS gets the checkout's command
alone, with the reason pip cannot install it.
This module is the one home for what is particular about installing an extra: each such fact is a table here, which
:func:pip_install_extra / :func:uv_sync_extra read and tests/test_pyproject.py ties to pyproject.toml. A
call site never writes its own install command. It imports nothing beyond the standard library, so import eden
can read :func:get_eden_version without loading torch.
Attributes¶
UNPUBLISHED_REQUIREMENTS
module-attribute
¶
UNPUBLISHED_REQUIREMENTS: dict[str, tuple[str, ...]] = {'metaquest': ('televuer @ git+https://github.com/Kashu7100/televuer@a1d8d6a0784fb289ddcde9aa108b92c7cf52f41b',), 'flashsac': ('rsl-rl-flashsac @ git+https://github.com/Holiday-Robot/rsl_rl_flashsac@9822b1b7e0b5cce45e7f79d6b3e2c169fa042faa',)}
REINSTALLED_PACKAGES
module-attribute
¶
UV_ONLY_EXTRAS
module-attribute
¶
UV_ONLY_EXTRAS: dict[str, str] = {'soma-retargeter': "eden-soma-retargeter 0.4.0 pins trimesh==4.11.3 while genesis-world needs trimesh>=5, and only the checkout's uv override reconciles them"}
Functions:¶
get_eden_version
¶
get_eden_version() -> str
The installed version of Eden's distribution (eden-world, e.g. 0.1.0), or "" when it is not
installed under that name (a source tree on PYTHONPATH, say).
eden.__version__ reads it.
The same in a checkout as in a wheel, so it suits a stamp that should change only with a release: a checkout's
commit (:func:eden.utils.misc.get_eden_commit) changes with every commit.
Source code in eden/utils/distribution.py
installed_version
¶
The installed version of distribution name (its pip install name), or None when it is not
installed under that name. Never raises, so a message or a provenance record can call it.
Source code in eden/utils/distribution.py
direct_url_record
¶
direct_url_record(dist: Distribution) -> dict
dist's PEP 610 direct_url.json (how a git+, local-directory or editable install was made), or {}
when it has none: an install from an index records none, and a corrupt record says nothing either.
Source code in eden/utils/distribution.py
editable_checkout
¶
editable_checkout(dist: Distribution) -> Path | None
The checkout an editable install of dist points at, as its direct_url.json records it, or None when
dist is not an editable install of a local directory. The directory may since have moved.
Source code in eden/utils/distribution.py
pip_install_extra
¶
The shell command that installs Eden with extra, e.g. pip install "eden-world[vis]".
For an extra with :data:UNPUBLISHED_REQUIREMENTS the command installs those too, so it is complete as printed.
For one in :data:REINSTALLED_PACKAGES whose wheel is already installed, it then reinstalls that wheel alone at
the installed version: pip install "eden-world[onnx]" && pip install --force-reinstall --no-deps
"onnxruntime==1.27.0" (--no-deps, or pip would reinstall torch with it). So the command reads this
environment. Each requirement is double-quoted: single quotes reach pip verbatim from Windows cmd, double
quotes work in every shell.
Source code in eden/utils/distribution.py
uv_sync_extra
¶
The command that adds extra to a checkout's environment, e.g. uv sync --inexact --extra vis.
--inexact because uv sync is exact by default: it uninstalls every extra and group the command does not
name, so a reader synced with --all-extras --all-groups who pasted a sync of --extra vis alone from an
error message would lose everything else. tests/test_pyproject.py holds the docs and examples to the same rule.
For an extra with :data:UNPUBLISHED_REQUIREMENTS it also syncs the extra's <extra>-git dependency group,
which holds them: uv sync --inexact --extra flashsac --group flashsac-git. For one in
:data:REINSTALLED_PACKAGES it reinstalls that wheel: uv sync --inexact --extra onnx --reinstall-package
onnxruntime.
Source code in eden/utils/distribution.py
install_hint
¶
How to install extra, for an error message or a help text, in both the ways a reader may need: with Eden
from PyPI, and in a clone. For vis it reads::
`pip install "eden-world[vis]"`, or `uv sync --inexact --extra vis` in a checkout
Both commands are complete as printed (see :func:pip_install_extra and :func:uv_sync_extra). An extra in
:data:UV_ONLY_EXTRAS gets the checkout's command alone, and why pip cannot install it.
Source code in eden/utils/distribution.py
Smoke runs¶
Lightweight build-and-step smoke harness for any registered task.
build_and_step("<task>") (or eden smoke --task <name>) constructs a task
config by name, builds its Genesis env, and steps it a few times with no
action — a one-call end-to-end sanity check for refactors and cheap coverage.
Task variants take the same route as the CLI's mod flags:
build_and_step("move_forward", modifier_kwargs={"robot": "go1"}) is eden smoke --task
move_forward --robot go1.
It encapsulates the steps that are otherwise easy to get wrong:
- initialize Genesis first (done here if the caller hasn't),
- pick
MimicRLEnvBasevsRLEnvBasefrom the config type, build()thenreset()before stepping,step(None)advances the sim with no action tensor.
Classes¶
Functions:¶
is_smoke
¶
is_smoke() -> bool
True in "smoke" mode — the few-step, headless run used for CI/sanity checks.
Controlled by EDEN_SMOKE (the canonical knob, set to 1 by eden examples smoke
and the recipe test harness); PYTEST_VERSION (set by pytest) is a fallback so a bare
test run is treated as smoke. Examples branch their step horizon on it:
horizon = 5 if en.is_smoke() else 1000.
EDEN_SMOKE is parsed as a boolean, not by presence: EDEN_SMOKE=0 / false / no
is a real off-switch. An explicitly-set value is authoritative — it wins over
PYTEST_VERSION, so a test can opt back out of smoke mode with EDEN_SMOKE=0. An unset
or empty value falls through to the PYTEST_VERSION presence check. (Falsey vocabulary
matches eden.extensions.policies.onnx._env_bool.)
Source code in eden/utils/smoke.py
build_and_step
¶
build_and_step(task: Union[str, EdenConfig], *, modifier_kwargs: dict[str, Any] | None = None, num_envs: int = 2, num_steps: int = 2, eval_mode: bool = False, show_viewer: bool = False, backend=None) -> RLEnvBase
Build a task's env and step it num_steps times with no action.
One-shot: each call builds a fresh scene and does not destroy it, so it is meant for a single sanity check (or a session-scoped test), not a tight loop over many tasks in one process.
| PARAMETER | DESCRIPTION |
|---|---|
task
|
A registered task name, or an already-constructed config object. A name
goes through
TYPE:
|
modifier_kwargs
|
TaskMod overrides forwarded to |
num_envs
|
Number of parallel envs to build.
TYPE:
|
num_steps
|
Number of no-action
TYPE:
|
eval_mode
|
Build in eval mode (training/randomization off). Defaults to False so the full training path is exercised.
TYPE:
|
show_viewer
|
Open the Genesis viewer.
TYPE:
|
backend
|
Passed to
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
RLEnvBase
|
The built, stepped env — returned so callers can inspect it. Any error in the build/step path propagates; surfacing it is the point. |
Source code in eden/utils/smoke.py
Profiling¶
Per-term cost profilers for the step loop.
On-demand, one-shot per-term cost profiler for a single task.
Why this exists
A term that is correct but slow is invisible. Nothing in a normal run tells the
author that the observation term they just wrote allocates a torch.tensor(...)
constant every step and now dominates rollout collection — the only symptom is a
training run that feels slow. Measured on the Go1 backflip benchmark, two runs of
the same task on the same GPU differed 35x in throughput purely because of
per-call allocations inside a task's own terms (see docs/guides/terms.md).
This profiler makes that cost investigable on demand. eden smoke --task <name>
--profile builds the task, steps it a bounded window, attributes wall time to
each term the per-step managers run, and writes a ranked, machine-readable
term_cost.json — so an author (or a coding agent that just wrote the term) can
see which term is the bottleneck and what fraction of the step it costs.
Relationship to :class:~eden.utils.profiling.step_profiler.RLStepProfiler
That profiler attaches to a caller-driven loop (training / rollout) and also
rolls up per-manager overhead; this one owns a short self-driven window and emits
a compact ranking with no manager rollup. They share the term-discovery and
timing mechanism in :mod:eden.utils.profiling.core, so both stay faithful to
the same manager internals and the same both-sides CUDA sync.
Purely additive, zero overhead when off
Nothing here runs unless a caller asks for it. The profiler wraps term.compute
from the outside for the measurement window only, times it with a CUDA sync on
both sides so the number reflects the term's own GPU work, then restores every
original method. A run without --profile creates no profiler object and wraps
no method anywhere in the framework.
Classes¶
TermCost
dataclass
¶
One term's measured cost, normalized per env step.
ProfileReport
dataclass
¶
Ranked per-term cost for a profiled window. Agent- and human-readable.
Attributes¶
HINT
class-attribute
instance-attribute
¶
HINT = 'Hot-path rules: resolve indices and allocate constant tensors in build(), not compute(); avoid .item()/.cpu()/bool(tensor). See docs/guides/terms.md.'
NOTE
class-attribute
instance-attribute
¶
NOTE = 'With sync=true, us/step and step_us are wall time with CUDA synced on both sides, so they run higher than an unsynced run; the pct_step shares are the faithful signal.'
Methods:¶
as_dict
¶
as_dict() -> dict
Machine-readable summary (JSON-serializable), terms ranked costliest-first.
Source code in eden/utils/profiling/term_profiler.py
render
¶
Human-readable table, costliest term first (top rows, then a rollup).
Source code in eden/utils/profiling/term_profiler.py
Functions:¶
profile_terms
¶
profile_terms(env: 'EnvBase', *, warmup: int = 10, measure: int = 50, step: Callable[[], object] | None = None, sync: bool | None = None) -> ProfileReport
Attribute wall time to each per-step term over a bounded window.
Runs warmup steps uninstrumented (kernel compilation and lazy allocation
land there), then wraps every term's compute, runs measure steps, and
restores the originals. The env is left stepped, not reset — the caller owns it.
Event terms are excluded (they fire on reset / interval, not per step) and so
are action terms (their dominant cost is in apply_actions, not the timed
compute). The reported step_us is the whole env.step() measured
under the same both-sides sync, so it runs high in absolute terms but the
per-term pct_step shares are faithful (see
:class:~eden.utils.profiling.step_profiler.RLStepProfiler for the same caveat).
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built, reset env.
TYPE:
|
warmup
|
Uninstrumented steps before measuring.
TYPE:
|
measure
|
Instrumented steps; per-term cost is normalized by this (a term computed more than once per step accumulates all its calls into that step's cost).
TYPE:
|
step
|
Zero-arg step to call each iteration. Defaults to
TYPE:
|
sync
|
Synchronize CUDA around each timed call. Defaults to whether CUDA is present.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
ProfileReport
|
Ranked per-term cost plus the measured env-step time. |
Source code in eden/utils/profiling/term_profiler.py
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | |
run_smoke_profile
¶
run_smoke_profile(task: 'str | EdenConfig', *, modifier_kwargs: dict[str, Any] | None = None, num_envs: int = 2, warmup: int = 10, measure: int = 50, eval_mode: bool = False, out_dir: str | None = None) -> tuple[ProfileReport, str]
Build a task and profile its per-term cost. The smoke --profile entry point.
| PARAMETER | DESCRIPTION |
|---|---|
task
|
Registered task name or a constructed config.
TYPE:
|
modifier_kwargs
|
TaskMod overrides for a task name, forwarded to
:func: |
num_envs
|
Parallel envs to build.
TYPE:
|
warmup
|
Uninstrumented warmup steps.
TYPE:
|
measure
|
Instrumented steps.
TYPE:
|
eval_mode
|
Build in eval mode (training/randomization off).
TYPE:
|
out_dir
|
Directory for
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple of (ProfileReport, str)
|
The report and the path the JSON was written to. |
Source code in eden/utils/profiling/term_profiler.py
Live-loop per-term profiler for the RL step loop.
Instruments every manager term's compute() — plus each manager's top-level
compute(), reset() and post_reset() and the other hooks the step loop
calls on it (the action manager's apply_actions(), the motion manager's
fill_data(), ...) — so the wall-clock cost of observations / rewards /
terminations / commands / events / curriculum / metrics / actions / motion can be
attributed term by term while a real loop drives the env. Use it to surface the
framework-level overhead Eden spends dispatching and composing terms on top of the
raw Genesis physics step.
The headline numbers are:
- per-term mean/total wall time and share of the step,
- per-manager time split into
terms(its term calls) andoverhead(the manager's own methods around them) — no split for a manager whose terms are not timed (the motion manager's run insidefill_data, not throughcompute), - the step-loop work no manager owns: the Genesis
scene.stepphysics tick (physics) and the env's own reset work around the managers' resets (reset:scene.reset, solver bookkeeping, buffer zeroing, index handling), and - the
unattributedremainder of the step — the Python inenv.stepbetween instrumented calls.
The per-term table and :meth:RLStepProfiler.as_dict's total_ms_per_step are
inclusive: a call's wall time counts every instrumented call nested inside it. The
rollup instead charges each call only its own time (its wall time minus that of
the instrumented calls nested inside it), and only inside :meth:RLStepProfiler.step
windows, so its rows partition the measured step exactly. A RESET event that fills
the motion window is charged its own work under event and the fill under
motion; reset excludes everything instrumented inside it. With sync=True
the syncs around a nested call land in its caller's own time. A call that raises is
still recorded, with its wall time up to the raise (no closing sync).
For the one-shot "profile the term I just wrote" workflow, see
:func:eden.utils.profiling.term_profiler.profile_terms. Both share the
term-discovery and timing mechanism in :mod:eden.utils.profiling.core.
GPU note
Manager terms launch CUDA kernels asynchronously, so naive host-side timing
measures launch cost, not execution. With sync=True (default) the profiler
calls :func:torch.cuda.synchronize around every measured call, yielding true
wall time per term at the cost of serializing kernels that would otherwise
overlap — absolute numbers run high but the relative breakdown is faithful.
With sync=False you instead measure pure Python dispatch cost (useful for
spotting per-term CPU overhead). The two views answer different questions.
Classes¶
TermTiming
dataclass
¶
TermTiming(manager: str, label: str, kind: str, calls: int = 0, total_s: float = 0.0, self_s: float = 0.0, sumsq_s: float = 0.0, max_s: float = 0.0)
RLStepProfiler
¶
Attribute RL-step wall time to individual manager terms.
Typical usage::
profiler = RLStepProfiler(env)
profiler.attach()
for _ in range(warmup):
env.step(sample_action())
profiler.reset() # discard warmup, keep instrumentation
for _ in range(steps):
with profiler.step(): # times the whole env.step()
env.step(sample_action())
print(profiler.report())
profiler.detach()
Or as a context manager that attaches/detaches automatically::
with RLStepProfiler(env) as profiler:
...
Source code in eden/utils/profiling/step_profiler.py
Attributes¶
Methods:¶
attach
¶
attach() -> RLStepProfiler
Source code in eden/utils/profiling/step_profiler.py
detach
¶
reset
¶
Zero all accumulators (e.g. to discard warmup) without detaching.
Source code in eden/utils/profiling/step_profiler.py
step
¶
as_dict
¶
Machine-readable summary (JSON-serializable).
terms, managers (by manager) and loop (by step-loop row: physics,
reset) hold one record per instrumented callable: every call, inclusive of the calls
nested inside it (total_ms_per_step, pct_step), plus its own time inside step
windows (self_ms_per_step). rollup is the report's rollup table: own time per
row, summing to step_ms. outside_step_ms is the wall time of instrumented calls
made outside :meth:step, which only the records count.
Source code in eden/utils/profiling/step_profiler.py
report
¶
Build human-readable breakdown tables.
| PARAMETER | DESCRIPTION |
|---|---|
top
|
If given, only show the
TYPE:
|
sort_by
|
Term ordering, either
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The formatted breakdown tables. |
Source code in eden/utils/profiling/step_profiler.py
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 | |
Functions:¶
Shared instrumentation core for the per-term profilers.
Both front-ends attribute wall time to individual manager terms by the same
mechanism: find every term the step loop runs, wrap its compute() with a
CUDA-synced timer, and restore it afterwards. That mechanism lives here so there
is a single source of truth for
- the coupling to manager term storage (:func:
iter_terms), and - the reversible, sync-both-sides timing wrapper (:func:
time_calltimes one call, :class:Patchinstalls and removes a wrapper, :func:time_methodjoins the two). The one-shot profiler uses :func:time_method; the live-loop one builds its own wrapper from :func:time_calland :class:Patch, to subtract the calls nested inside a call from its own time.
The two front-ends differ only in policy — what they measure and how they report it — not in this mechanism:
- :class:
eden.utils.profiling.step_profiler.RLStepProfilerattaches to a caller-driven loop (training / rollout), also times manager-level methods, and produces a full per-manager rollup. Use it to profile terms in an actual loop. - :func:
eden.utils.profiling.term_profiler.profile_termsowns a short, self-driven window and produces a compact, agent-readable ranking. Use it to profile a term you just wrote, in isolation.
Attributes¶
FLAT_TERM_MANAGERS
module-attribute
¶
Classes¶
Patch
¶
A reversible replacement of one instance method with a wrapper.
Records whether the method was a per-instance attribute or an inherited class
method, so :meth:restore puts back exactly what was there — deleting the
instance shadow in the common class-method case rather than leaving a bound
method attribute behind.
Source code in eden/utils/profiling/core.py
Functions:¶
resolve_sync
¶
Return the CUDA-synchronize callable to use around timed calls, or None.
| PARAMETER | DESCRIPTION |
|---|---|
sync
|
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
callable or None
|
|
Source code in eden/utils/profiling/core.py
iter_terms
¶
iter_terms(env: 'EnvBase', *, include_event: bool = True, include_action: bool = True) -> Iterator[tuple[str, str, Any]]
Yield (manager, label, term) for every term the step loop runs.
Composes the public term iterators of the three manager shapes: the
observation manager's iter_terms() (per group), the event manager's
iter_terms() (per mode), and the flat terms dict every
:class:~eden.managers.base.FlatManagerBase exposes. Managers absent on this
env (e.g. a task with no command manager) are skipped.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built environment.
TYPE:
|
include_event
|
Whether to include event-manager terms. They fire on reset / interval through mode dispatch rather than once per step, so a per-step cost view (the one-shot profiler) excludes them; the live-loop profiler includes them.
TYPE:
|
include_action
|
Whether to include action-manager terms. An action term's dominant cost
is in
TYPE:
|
| YIELDS | DESCRIPTION |
|---|---|
tuple of (str, str, object)
|
The owning manager's name, a human-readable label ( |
Source code in eden/utils/profiling/core.py
can_time
¶
Whether owner can carry the per-instance compute override the timers install.
:func:time_method shadows the method with an instance attribute, so the owner
needs a writable __dict__. A term defining __slots__ without one cannot
be wrapped — the setattr would raise. No built-in term is slotted, but an
author could write one, so :func:discover_timeable skips such a term instead
of crashing the profiler. Manager instances always qualify.
Source code in eden/utils/profiling/core.py
discover_timeable
¶
discover_timeable(env: 'EnvBase', *, include_event: bool = True, include_action: bool = True) -> tuple[list[tuple[str, str, Any]], list[str]]
Split :func:iter_terms into the terms the profilers can time and the labels they can't.
Dedups by term identity (a term shared across groups is timed once, not
double-counted) and drops any term whose compute is missing or which cannot
be wrapped (:func:can_time). Both front-ends discover through this, so the
"which terms are timeable" policy has a single home.
| PARAMETER | DESCRIPTION |
|---|---|
env
|
A built environment.
TYPE:
|
include_event
|
Forwarded to :func:
TYPE:
|
include_action
|
Forwarded to :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple of (list, list)
|
|
Source code in eden/utils/profiling/core.py
warn_untimed
¶
Warn that labels were skipped as un-timeable; a no-op when empty.
Keeps the message identical across both front-ends. Imports eden lazily so
importing this module never triggers the package init.
Source code in eden/utils/profiling/core.py
time_call
¶
time_call(fn: Callable[[], Any], on_dt: Callable[[float], None], synchronize: Callable[[], None] | None) -> Any
Run fn once, reporting its wall time to on_dt.
Syncs on both sides when synchronize is given: the pre-sync drains
kernels queued by earlier work so they are not charged here, the post-sync
waits out this call's own kernels. Returns fn's result.
A call that raises is still reported, with its wall time up to the raise, and the exception propagates. It skips the post-sync: after a CUDA error that sync would raise again, over the original exception.
Source code in eden/utils/profiling/core.py
time_method
¶
time_method(owner: Any, name: str, on_dt: Callable[[float], None], synchronize: Callable[[], None] | None) -> Patch
Wrap owner.name so each call's wall time is reported to on_dt.
Returns a :class:Patch; call :meth:Patch.restore to remove the wrapper.
Timing is delegated to :func:time_call, so both levels of instrumentation
share one clock and one sync policy.
Source code in eden/utils/profiling/core.py
Strings, formatting and warnings¶
term_subject(term) renders the "<Class> on entity '<name>'" subject an entity-bound term opens its errors with
(see Error Messages).
String helpers: glob name-matching, and rendering name lists and term subjects for a log line.
Functions:¶
is_glob_pattern
¶
Best-effort heuristic to determine whether the provided pattern looks like a glob.
Source code in eden/utils/string.py
name_matches
¶
Whether pattern, read as a pattern, matches name.
A glob (per :func:is_glob_pattern, unless use_glob forces the mode) is matched with
:func:fnmatch.fnmatchcase; anything else is a regular expression that must match the whole
name. This is the pattern half of :func:key_selects only: it sees one name, so it cannot take a
key that is exactly one of the names literally. To ask what a config key selects, use
:func:key_selects.
| PARAMETER | DESCRIPTION |
|---|---|
pattern
|
The glob or regular expression.
TYPE:
|
name
|
The candidate name.
TYPE:
|
use_glob
|
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
Source code in eden/utils/string.py
key_selects
¶
Whether the config key key selects name, one of names.
A key that is exactly one of names selects that name alone; any other key is a glob or a regex
(:func:name_matches). A name can carry a pattern metacharacter (pin.1, arm(2), an
exporter's link.001): read as a regex, pin.1 would also select pin_1, and arm(2)
would select nothing, not even itself. :func:resolve_matching_names and
:func:resolve_matching_names_values select with this test, so a caller matching keys one name at
a time agrees with them.
| PARAMETER | DESCRIPTION |
|---|---|
key
|
The name, glob or regex.
TYPE:
|
name
|
The candidate name.
TYPE:
|
names
|
Every name the key could select, which decides whether
TYPE:
|
use_glob
|
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
bool
|
|
Source code in eden/utils/string.py
first_few
¶
Render names for a log line, truncating a long list to its first few plus a count.
For messages that name the joints/DOFs/links behind a diagnostic: a 20-DOF hand or a 29-DOF humanoid would otherwise wrap the console, and past the first few entries the list stops being read anyway. The count of what is hidden is kept, so the reader can tell "a couple of joints" from "the whole robot" without the full list.
| PARAMETER | DESCRIPTION |
|---|---|
names
|
The names to render, in the order they should appear. Any iterable: callers pass
whatever the entity handed them, which is as often |
limit
|
How many names to show before truncating.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
|
Source code in eden/utils/string.py
term_subject
¶
Render the subject an entity-bound term opens its errors with: "<Class> on entity '<name>'".
The same class can be configured twice against different entities (one reward per arm,
one randomization per object), so the class alone does not say which of them failed.
Class and entity only: ManagerBase._build_term already adds the config key as a note,
and a term that restated it would print it twice.
| PARAMETER | DESCRIPTION |
|---|---|
term
|
A term that declares
TYPE:
|
entity_name
|
The entity, for a term that names it in a field of its own (a writing metric's
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
|
Source code in eden/utils/string.py
resolve_matching_names
¶
resolve_matching_names(keys: str | Sequence[str], list_of_strings: Sequence[str], preserve_order: bool = False, use_glob: bool | None = None, *, literal_names: Collection[str] | None = None) -> tuple[list[int], list[str]]
Match a list of query patterns against a list of strings and return the matched indices and names.
When a list of query regular expressions or glob patterns is provided, the function checks each target string against each query and returns the indices of the matched strings and the matched strings.
If the :attr:preserve_order is True, the ordering of the matched indices and names follows the order of
the query patterns: all matches for the first pattern come first, then the second, and so on.
If the :attr:preserve_order is False, the ordering of the matched indices and names is the same as the
order of the provided list of strings.
For example, consider the list of strings is ['a', 'b', 'c', 'd', 'e'] and the patterns are ['a|c', 'b'].
If :attr:preserve_order is False, then the function will return the indices of the matched strings and the
strings as: ([0, 1, 2], ['a', 'b', 'c']). When :attr:preserve_order is True, it will return them as:
([0, 2, 1], ['a', 'c', 'b']).
A key that is exactly one of the strings selects that string alone, even when it contains a pattern
character: pin.1 does not also select pin_1, and arm(2) selects arm(2) (:func:key_selects).
Note: The function does not sort the indices. It returns the indices in the order they are found.
| PARAMETER | DESCRIPTION |
|---|---|
keys
|
A regular expression, glob pattern, or a list of either to match the strings in the list. |
list_of_strings
|
A list of strings to match. |
preserve_order
|
Whether to preserve the order of the query keys in the returned values. Defaults to False.
TYPE:
|
use_glob
|
Optional override for the matching strategy. When True, treat all keys as Unix shell-style glob
patterns; when False, treat them as regular expressions. When None, automatically infer the mode per key
using :func:
TYPE:
|
literal_names
|
The names a key is taken literally against, when
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple[list[int], list[str]]
|
A tuple of lists containing the matched indices and names. |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
When multiple matches are found for a string in the list. |
ValueError
|
When not all query patterns are matched. |
Source code in eden/utils/string.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 | |
columns_by_name
¶
columns_by_name(available_names: Sequence[Any], wanted_names: Sequence[str], *, what: str = 'names') -> list[int]
Column indices of wanted_names within available_names (exact match, query order).
Decodes a motion file's columns by the names it stores beside them — a motion .npz's body_names /
dof_names, a BVH's joints — or by a dataset's documented column order, so the mapping is independent of the
writer's column order and of Genesis's internal link/DOF ordering (which changed breadth-first -> depth-first in
genesis 1.2.0). available_names entries are coerced to str (npz string arrays load as numpy str_).
Unlike :func:resolve_matching_names, this is a plain exact-name lookup (no glob/regex), so
names containing pattern metacharacters are matched literally.
| PARAMETER | DESCRIPTION |
|---|---|
available_names
|
The column labels, in column order (e.g. the npz |
wanted_names
|
The names to locate, in the desired output order. |
what
|
What
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
list[int]
|
One column index per entry in |
| RAISES | DESCRIPTION |
|---|---|
ValueError
|
If any of |
Source code in eden/utils/string.py
resolve_matching_names_values
¶
resolve_matching_names_values(data: dict[str, Any], list_of_strings: Sequence[str], preserve_order: bool = False, use_glob: bool | None = None, *, literal_names: Collection[str] | None = None) -> tuple[list[int], list[str], list[Any]]
Match query patterns against a list of strings, returning matched indices, names, and values.
If the :attr:preserve_order is True, the ordering of the matched indices, names, and values follows the
order of the query patterns (the dictionary keys): all matches for the first key come first, then the
second, and so on. The returned values are NOT positionally aligned with the target list — scatter them
through the returned indices instead.
If the :attr:preserve_order is False, the ordering of the matched indices and names is the same as the
order of the provided list of strings.
As in :func:resolve_matching_names, a key that is exactly one of the strings selects that string alone.
For example, consider the dictionary is {"a|d|e": 1, "b|c": 2}, the list of strings is
['a', 'b', 'c', 'd', 'e']. If :attr:preserve_order is False, then the function will return the indices of the
matched strings, the matched strings, and the values as: ([0, 1, 2, 3, 4], ['a', 'b', 'c', 'd', 'e'],
[1, 2, 2, 1, 1]). When :attr:preserve_order is True, it will return them as: ([0, 3, 4, 1, 2],
['a', 'd', 'e', 'b', 'c'], [1, 1, 1, 2, 2]).
| PARAMETER | DESCRIPTION |
|---|---|
data
|
A dictionary mapping query patterns (regular expressions or globs) to values to match against the list. |
list_of_strings
|
A list of strings to match. |
preserve_order
|
Whether to preserve the order of the query keys in the returned values. Defaults to False.
TYPE:
|
use_glob
|
Optional override for the matching strategy. When True, treat all dictionary keys as glob patterns;
when False, treat them as regular expressions. When None, automatically infer the mode per key using
:func:
TYPE:
|
literal_names
|
The names a key is taken literally against, as in :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
tuple[list[int], list[str], list[Any]]
|
A tuple of lists containing the matched indices, names, and values. |
| RAISES | DESCRIPTION |
|---|---|
TypeError
|
When the input argument :attr: |
ValueError
|
When multiple matches are found for a string in the dictionary. |
ValueError
|
When not all query patterns in the data keys are matched. |
Source code in eden/utils/string.py
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 | |
Miscellaneous helpers (env-index sanitization, entity resolution, hashing, snake_case, key loading, paths).
Attributes¶
Classes¶
DofRuns
¶
Bases: tuple
A non-contiguous DOF selection as its contiguous runs: ((dofs_range, columns_slice), ...).
What :func:as_genesis_control_index hands a RigidEntity.control_dofs_* setter for a selection
with gaps (a parallel gripper's two driver joints, [0, 3]): the setter writes each run through
its own range — the run's DOFs from the value's columns_slice — instead of once through an
index tensor, which Genesis' control_dofs_* turns into a GPU->CPU sync twice per call.
Only the control setters accept it; hand a getter the index tensor or :func:as_genesis_index.
DofPermutation
¶
Bases: tuple
A DOF selection that permutes a contiguous block, as (dofs_range, order).
What :func:as_genesis_control_index hands a RigidEntity.control_dofs_* setter when idx
names every DOF of dofs_range exactly once in another order (a quadruped's joints in leg-major
order over a joint-major asset): the setter writes the whole block through the range from
value.index_select(-1, order) — order[j] is the value column of the block's j-th DOF —
instead of through an index tensor, which Genesis' control_dofs_* syncs on twice per call.
Same DOFs, same values. Only the control setters accept it.
Functions:¶
resolve_named_entity
¶
Look up name in an env's entities, naming what is there when it is missing.
Shared by EnvBase.get_entity, ManagerTermBase.resolve_entity and
PolicyBase.require_entity: the name is always one someone typed, so the useful failure
lists the names that exist, plus hint for where the caller declares its own. Takes the
mapping, not the env, so a bare-entities stub resolves too.
Source code in eden/utils/misc.py
contiguous_range
¶
contiguous_range(idx) -> range | None
idx as the equivalent range if it counts up one at a time from its first entry, else None.
The form to hand Genesis for a fixed DOF / q selection. Genesis turns a range into a slice,
while an index tensor takes its advanced-indexing path: about 5x the host time per call (32 vs
6 us for get_dofs_position on a G1), a GPU->CPU sync in every control_dofs_* call, and no
bool-mask fast path in the setters (RigidEntity.setters_take_every_row). Reads idx on the
host, so resolve it once, in build(), and keep the tensor for anything that does tensor math.
None for a selection that counts from the end: Genesis turns range(-2, 0) into slice(-2, 0),
which selects nothing, where the index tensor [-2, -1] it would replace selects the last two.
Source code in eden/utils/misc.py
as_genesis_index
¶
as_genesis_index(idx: Tensor) -> range | Tensor
idx as its :func:contiguous_range when it has one, else idx itself — the selector to hand Genesis.
Same order, same entries, so the two forms select identical DOFs; only the Genesis code path differs.
Resolve it once, in build() (it reads idx on the host).
Source code in eden/utils/misc.py
as_genesis_control_index
¶
as_genesis_control_index(idx: Tensor, device: device | str | None = None) -> range | DofPermutation | DofRuns | Tensor
idx as the selector to hand a RigidEntity.control_dofs_* setter.
A range when idx is contiguous (as :func:as_genesis_index); a :class:DofPermutation
when it reorders a contiguous block; a :class:DofRuns when it splits into at most a few
contiguous runs; else idx itself. Every form writes the same DOFs from the same value columns.
device is where the permutation's order lives — the values' device; idx's by default.
Resolve it once, in build() (it reads idx on the host).
Source code in eden/utils/misc.py
is_env_mask
¶
is_env_mask(envs_idx) -> bool
Whether envs_idx is a bool env mask (rather than a slice, None or int ids).
sum_over_envs
¶
sum_over_envs(values: Tensor, envs_idx: slice | Tensor) -> Tensor
values[envs_idx].sum(dim=0), without a GPU->CPU sync when envs_idx is a bool mask.
Gathering with a bool mask (values[mask]) has to size its output, which is a sync; the
auto-reset in RLEnvBase.step hands every manager's reset() exactly such a mask
(reset_buf), so a per-term gather for a log value costs one sync per term per step.
A mask is reduced in place instead. A bool values sums to the count of True entries.
Source code in eden/utils/misc.py
mean_over_envs
¶
mean_over_envs(values: Tensor, envs_idx: slice | Tensor) -> Tensor
values[envs_idx].mean(dim=0) — zero when nothing is selected — without a sync for a bool mask.
See :func:sum_over_envs.
Source code in eden/utils/misc.py
assign_over_envs
¶
assign_over_envs(target: Tensor, envs_idx: slice | Tensor, source: Tensor) -> None
target[envs_idx] = source[envs_idx] for a full-batch source, without a sync for a bool mask.
target[mask] = source[mask] gathers first, which is a GPU->CPU sync (see :func:sum_over_envs);
a mask is applied with torch.where into target instead.
Source code in eden/utils/misc.py
env_indices
¶
A bool env mask as the index tensor of its True entries; a slice or an index tensor unchanged.
For code that must gather the selected rows (values[ids]): every values[mask] gather is
a GPU->CPU sync, and the one nonzero() here pays for all of them. Keep the mask for
scalar fills (buf[mask] = 0.0) and torch.where: a scalar written through an index
tensor is the one form that syncs the other way round. Keep it for Genesis' state setters too
where they take one on the device (RigidEntity.setters_take_every_row, with a range
selection): handed ids, a setter writes its error flag through them, a sync per call.
Inside :func:sharing_env_indices the mask it was opened on costs nothing here: its ids were
taken once, for the whole reset.
Source code in eden/utils/misc.py
sharing_env_indices
¶
sharing_env_indices(envs_idx) -> Iterator[None]
Take a bool envs_idx mask's ids once, for every :func:env_indices call on it inside.
The auto-reset hands the same reset_buf to the scene reset, every RESET event and every
manager. Each consumer that gathers selected rows would otherwise take its own nonzero() —
one GPU->CPU sync apiece, 3-9 a step on the shipped tasks. Bracketing the reset in this
takes the one sync up front and hands its ids to all of them, along with the selected count
(:func:sanitize_envs_idx's return_n_envs) that gates the reset.
The ids are matched to the mask by identity, so a mask derived from it (mask & active)
takes its own nonzero(). The mask must not be written while the bracket is open,
or the ids would describe the old selection; the env opens it on a reset_buf that nothing
writes during a reset. Anything but a bool mask (None, a slice, an index tensor) passes
through untouched.
Source code in eden/utils/misc.py
sanitize_envs_idx
¶
sanitize_envs_idx(envs_idx: int | range | slice | tuple[int, ...] | list[int] | Tensor | ndarray | None, num_envs: int, *, prefer_slice: bool = True, name: str = 'envs_idx', return_n_envs: bool = False) -> tuple[slice | Tensor, int] | slice | Tensor
Normalize environment indices with an optional slice fast-path.
| PARAMETER | DESCRIPTION |
|---|---|
envs_idx
|
Environment selector. A mask must be a bool
TYPE:
|
num_envs
|
Total number of environments.
TYPE:
|
prefer_slice
|
If True, preserve slices for faster backend indexing when possible.
TYPE:
|
name
|
Name used in validation errors.
TYPE:
|
return_n_envs
|
If True, also return the number of environments selected by the index.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
envs_idx
|
Normalized environment indices.
TYPE:
|
n_envs
|
Number of environments selected by the index. Only returned if return_n_envs is True.
TYPE:
|
Source code in eden/utils/misc.py
invert_segmentation_idx_dict
¶
Invert Genesis' segmentation_idx_dict into entity_idx -> [segmentation ids].
Genesis maps segmentation id -> scene element, where the element is a bare entity index at
segmentation_level="entity" and an (entity_idx, link_idx, ...) tuple at "link" /
"geom". Callers want the reverse, and at link/geom level an entity owns several ids —
one per link/geom. Keeping only one (what a plain d[entity_idx] = seg_idx loop does, since
the last write wins) silently reduces the entity to a single link: a masked point cloud loses
most of the object, and a background mask stops covering most of the background.
Source code in eden/utils/misc.py
verify_file_hash
¶
Verify file integrity using SHA256.
Source code in eden/utils/misc.py
string_to_sha256_hex
¶
Convert a string to the hex SHA-256 hash of its UTF-8 encoding.
Source code in eden/utils/misc.py
to_snake_case
¶
Convert a CamelCase string to snake_case using regular expressions.
Source code in eden/utils/misc.py
get_package_provenance
¶
Which package_name is installed, for provenance: {"version": ..., "commit": ...}, each None when
unknown (both are, when the package is not installed).
version is the distribution's (1.4.0). commit is the git commit it was installed from: the one a
git+ install recorded (PEP 610's direct_url.json), or the HEAD of the checkout an editable install
points at; an install from an index has none. Two fields, not one holding either, so whoever reads a saved record
never has to tell a commit from a version by its shape. :func:get_eden_provenance is the same record for Eden.
Source code in eden/utils/misc.py
get_eden_commit
¶
get_eden_commit() -> str | None
The HEAD of the git checkout eden is imported from, or None outside one.
The commit is for developers working in a clone. Every install, a pip install "eden-world @ git+..." included,
is identified by its release version alone (:func:get_eden_version), on purpose.
Found by path, the way :func:get_eden_working_tree_dirty finds the dirty flag, so the two always describe the
same tree. Not by distribution name through importlib.metadata, which takes the first match on sys.path:
under python -m that is the repo root, where an in-tree setuptools build leaves an eden_world.egg-info
with no direct_url.json, so the commit would read back as the bare version.
Source code in eden/utils/misc.py
get_eden_provenance
¶
Which Eden this is, for provenance: {"version": "0.1.0", "commit": <HEAD in a clone, else None>}, the
record :func:get_package_provenance gives any other package. A run's own record adds dirty
(:func:get_eden_run_provenance).
Source code in eden/utils/misc.py
get_eden_run_provenance
¶
:func:get_eden_provenance plus dirty (:func:get_eden_working_tree_dirty), for a run's own record
(run.json, an export's policy_spec.json). One checkout lookup answers both, so the commit and the dirty
flag always describe the same tree.
Source code in eden/utils/misc.py
format_provenance
¶
A provenance record for a log line: 0.1.0 (c246ebd), the version and the commit cut to 7 characters, either
alone when the other is unknown, or unknown.
Source code in eden/utils/misc.py
describe_provenance_change
¶
How current differs from the saved record, as saved 0.1.0 (c246ebd), running 0.1.0 (3f2a1b2), or
None when the two match or cannot be compared.
Commits are compared when both records have one, else versions: a file saved in a clone and read from a wheel of
the same release matches. A saved that is not a record (a file written before these fields existed) cannot be
compared.
Source code in eden/utils/misc.py
get_now
¶
sanitize_run_name
¶
Return run_name made safe as a path segment / wandb name (or "" if empty).
Collapses runs of path-separator, URL-reserved, and whitespace characters to a single
-, trims leading/trailing separators, and caps the length at 64. Used both for the
log-dir basename segment and for runner_options.run_name so the two always agree.
| PARAMETER | DESCRIPTION |
|---|---|
run_name
|
The raw, user-supplied run name.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
str
|
The sanitized run name, empty when the input is empty or all-unsafe. |
Source code in eden/utils/misc.py
create_symlink
¶
get_eden_working_tree_dirty
¶
get_eden_working_tree_dirty() -> bool | None
Return whether the eden source tree has uncommitted changes, for run provenance.
Complements the commit reported by :func:get_eden_commit: that sha is a
lie for reproducibility if the tree it names had local edits, and a commit alone can
never convey that. Consumed by the run manifest (:mod:eden.utils.run_manifest).
Best-effort: a non-editable (wheel) install, an unavailable git, or any
subprocess failure yields None (unknown) rather than raising — writing a run
manifest must never abort training.
"Dirty" means the tracked source differs from HEAD (staged or unstaged edits).
Untracked files are excluded (--untracked-files=no): a run always drops new files
into the tree (its own logs, __pycache__, scratch), so counting those would flip
the flag to True on essentially every run and drain it of meaning — the question
for reproducibility is whether the committed code was modified.
For Eden vendored inside a larger repository, the flag is that repository's: an edit anywhere in its tracked files
counts, because the commit beside it (:func:get_eden_commit) is that repository's HEAD and names all of them.
| RETURNS | DESCRIPTION |
|---|---|
bool | None
|
|
Source code in eden/utils/misc.py
set_api_keys_from_files
¶
Load API keys from eden/generators/keys/ into environment variables.
Reads all *.key files from the eden/generators/keys/ directory,
extracts the API key from each file, and sets them as environment variables.
The environment variable name is the filename without its extension, upper-cased: a file named 'my_api.key' sets an environment variable named 'MY_API'.
Source code in eden/utils/misc.py
list_host_ips
¶
Best-effort list of this host's LAN IPv4 addresses, most-likely-reachable first.
Used by the teleop bridges to print the address an XR headset should dial.
Source code in eden/utils/misc.py
get_src_dir
¶
get_assets_dir
¶
Root that Eden downloads Hugging Face assets into, joined with parts.
Robots, objects, scenes, policy checkpoints and benchmark-suite assets are fetched file by file
into this directory. It is $EDEN_ASSETS_DIR when that is set and non-empty (~ expanded), else
get_cache_dir("assets"): $XDG_CACHE_HOME/eden/assets, by default ~/.cache/eden/assets, which
is per-user, writable without root, and shared by every venv and reinstall.
A relative $EDEN_ASSETS_DIR raises ValueError. Read on every call, it would name a different
directory each time the process (or a worker a launcher starts elsewhere) changes its working directory.
Read on every call, like :func:get_cache_dir. Nothing caches it: an unset local_dir names a
subdirectory of it that is resolved at load time (:func:resolve_local_dir), so a saved config's
local_dir stays unset.
Empty parts are skipped, so get_assets_dir("") (an asset family's unset assets_subdir)
is the root itself rather than the root with a trailing separator.
This function neither creates the directory nor checks that it is writable. The first download
does both, and raises naming the path and EDEN_ASSETS_DIR when it cannot write there.
Source code in eden/utils/misc.py
resolve_local_dir
¶
Where an asset owner caches its files: local_dir when set, else assets_subdir under the asset root.
The one statement of the rule. EntityOptions, PCDOptions and PolicyBase each expose it as a
resolve_local_dir() method over their own fields; :func:eden.utils.assets.get_asset_path applies it with
no subdirectory. Resolved on each call, so EDEN_ASSETS_DIR moves every unset local_dir.
Source code in eden/utils/misc.py
get_cache_dir
¶
Per-user Eden cache directory, joined with parts.
Everything Eden writes rather than ships goes here: writable without root (unlike the
installed package), and not shared between users on a multi-tenant host (unlike /tmp).
Source code in eden/utils/misc.py
get_gs_assets_dir
¶
Source code in eden/utils/misc.py
to_jsonish
¶
Normalise a value so it compares equal to itself across a torch.save round-trip.
Tuples come back as lists, and an array-like compares elementwise — comparing one directly
raises Boolean value of Tensor is ambiguous. tolist() covers torch tensors, numpy
arrays and numpy scalars alike.
Source code in eden/utils/misc.py
Terminal styling and formatting helpers (ANSI colors, argparse help formatter).
Whether anything here emits an escape is decided by Eden's log theme — the same
eden.init(logger_theme=...) / EDEN_LOG_THEME / eden --log-theme setting that governs
the logger — so one choice covers the whole of a command's output. The default theme is
agent, which emits none.
The environment's own reading — NO_COLOR / FORCE_COLOR / isatty — is not the switch:
on its own it would color eden train --task X --help on a pty for a run that asked, through the
theme, for no escapes. It is what the auto theme means; :func:color_enabled is that reading,
and :func:eden._logging.resolve_theme calls it.
Attributes¶
Classes¶
ColorHelpFormatter
¶
Argparse help formatter that ANSI-colorizes section headings and --flag tokens.
Suppresses the auto-generated usage: block in full --help output (where it
duplicates the options: section) but keeps it on parse errors so
parser.error(...) still produces a useful diagnostic. Wraps
:class:argparse.RawDescriptionHelpFormatter so descriptions and epilogs preserve
newlines.
Subclassing is deferred to __new__ so argparse is not imported at module load.
Functions:¶
color_enabled
¶
color_enabled() -> bool
Return whether the environment wants color — the auto theme's reading.
Honors the de-facto standards:
NO_COLOR(any value) disables color.FORCE_COLOR(any value) enables color regardless of TTY.- Otherwise, color is enabled only when
sys.stdoutis a TTY.
This is one input to the decision, not the decision: what actually gets emitted is
:func:color_output_enabled, which asks the log theme. A caller reaching for this one wants
"would a person be watching?", which is a different question from "does this run print
escapes?" — and the answer to the second is no by default, on a TTY included.
Source code in eden/utils/format.py
color_output_enabled
¶
color_output_enabled() -> bool
Return whether this process emits ANSI escapes, per Eden's resolved log theme.
human yes, agent no, auto per :func:color_enabled. One switch for the whole of a
command's output, so the logger and everything here (--help included) agree. The switch is
EDEN_LOG_THEME, which both eden.init(logger_theme=...) and eden --log-theme
export, so a theme asked for either way governs this and not just the logger.
| RETURNS | DESCRIPTION |
|---|---|
bool
|
Whether to emit escapes. |
Source code in eden/utils/format.py
format_table
¶
format_table(title: str, field_names: list[str], rows: Iterable[Iterable[Any]], *, align: dict[str, str] | None = None, max_width: dict[str, int] | None = None) -> str
Render a titled table in the resolved log theme.
human gets a boxed :class:prettytable.PrettyTable. agent gets the same cells with
none of the box: a title: col | col header, then one cell | cell line per row. The
borders and the column padding are about half of a boxed table's tokens and carry nothing a
reader of a transcript uses. Under agent a leading Index / No. column is dropped,
since it restates the row order, and a multi-line cell is joined with ", ", since a row
has to be one line to be read as one.
| PARAMETER | DESCRIPTION |
|---|---|
title
|
The table's title.
TYPE:
|
field_names
|
The column headers. |
rows
|
One iterable of cells per row, in |
align
|
PrettyTable alignment ( |
max_width
|
PrettyTable wrap width per column name; |
| RETURNS | DESCRIPTION |
|---|---|
str
|
The rendered table, without a trailing newline. |
Source code in eden/utils/format.py
colorize
¶
Wrap text with the given ANSI escape codes and a reset suffix.
Returns text unchanged when :func:color_output_enabled is False, so it's safe to
use unconditionally. Multiple codes are concatenated (e.g. ANSI_BOLD + ANSI_CYAN).
Source code in eden/utils/format.py
Warning helpers: emitting a warning once where the same one would otherwise repeat.
Functions:¶
warn_once
cached
¶
warn_once(message: str) -> None
Warn once per distinct text — for a diagnostic a loop would otherwise repeat verbatim.
Put the varying part (class name, version, path) in the message and the dedup follows it.
The cache is bounded because it keys on the whole string; cache_clear() resets it.
Source code in eden/utils/warning.py
A fixed-shape float np.ndarray pydantic field type, e.g. FloatArray[3, 3] for a camera intrinsic.
Eden's own replacement for numpydantic.NDArray[Shape["3, 3"], float], which imports cv2
(through numpydantic's video interface) and so would pull it into every task config for two camera-matrix
annotations. Only numpy and pydantic here, so :mod:eden.options.camera can import it without an
eden.utils <-> eden.options cycle.
A saved matrix loads back as an np.ndarray because EdenConfig.load_from_file builds every
ConfigurableOptions with model_validate, which runs :class:FloatArrayShape on the nested
list. Declare a FloatArray field on a ConfigurableOptions subclass only: a genesis-native
Options loads through model_construct, so nothing would validate the field.
Classes¶
FloatArrayShape
dataclass
¶
Annotated metadata that validates a value into a float np.ndarray of shape.
Accepts an np.ndarray or a nested sequence of real numbers. A float array is kept as-is
(same object, same float dtype); anything else is converted with np.asarray and, if it
holds integers, cast to float64. Serializes to nested lists in JSON mode and stays an
np.ndarray in Python mode.
| PARAMETER | DESCRIPTION |
|---|---|
shape
|
The exact shape a value must have. |
FloatArray
¶
See also¶
- Terms guide — registries and
configure() - Error Messages