Plugins¶
The plugin registry and the plugin-facing types: track and gate generators, the
relax, width, elevation and validity stages of the track
pipeline and the order and stations stages of the gate pipeline, selected by name,
built-ins and installed packages alike (entry point group track_gen.plugins), their
Params and the Stage protocol. Writing a Generator and
Writing a Stage show how they fit together. track_gen.plugin is a subpackage
outside track_gen.__all__: import it explicitly. Importing track_gen loads no
generator module and no plugin; the registry loads on its first lookup.
Public extension surface: register track and gate generators, and relax / width / elevation / validity stages of the track pipeline, without editing track_gen.
A pip-installable package adds generators through an entry point in the
"track_gen.plugins" group (ENTRY_POINT_GROUP) whose target is a
register(registry) callable. In the plugin package’s pyproject.toml:
[project.entry-points."track_gen.plugins"]
acme = "acme_tracks:register"
and in acme_tracks/__init__.py (Superformula is the package’s generator, a
Stage subclass; a GeneratorSpec registers the same way):
from acme_tracks.superformula import Superformula
track_gen_api = 1 # optional: the plugin API version this package targets
def register(reg):
reg.add(Superformula) # the class, not an instance
after which TrackGenConfig(generator="acme.superformula") selects it. Names:
third-party names should be dotted ("package.name"); a non-dotted one registers with
a one-time UserWarning. Code that registers at run time (scripts, tests) calls
registry’s add() directly; registry.scoped() snapshots and
restores the registry around a test (scoped(fresh=True) also re-runs discovery).
Loading. import track_gen loads nothing. The registry fills itself lazily on its
first call (get, available, describe, add, unregister,
quarantined, scoped(), and the deprecated track_gen._src registry shims),
once per process and under a lock:
the built-ins, registered explicitly by
track_gen._src.builtin_plugins.register(no entry point needed, so a source checkout without metadata works);third-party plugins: every entry point of the group, sorted by (distribution name, entry-point name), each loaded and called with the registry.
Policy. The TRACK_GEN_PLUGINS environment variable (POLICY_ENV, read when
the registry loads, case-insensitive) selects what happens to a third-party plugin that
fails to load: warn (default: it is quarantined and one RuntimeWarning names
it), strict (raise PluginLoadError at load, e.g. for CI) or off (skip
third-party discovery entirely). Any other value emits a RuntimeWarning and falls
back to warn. A plugin fails to load when importing or calling it raises, when it
declares a newer plugin API than API_VERSION (track_gen_api = <int> in the
module its entry point names, or on the register callable), when it registers a name
that already exists without replace=True (built-ins always win unless a plugin
explicitly overrides them), or when it unregisters a name it did not register.
Parameters and stages. A generator declares its own parameters as a frozen
Params dataclass (fields marked static() or dynamic()) and users
select them with TrackGenConfig(generator=..., generator_params=...) (an instance or
a mapping of its fields). A legacy GeneratorSpec takes a static Params class
through params=; a Stage subclass (kind="generator",
output="centerline", registered as a class) allocates through a
StageBuilder and may declare dynamic, per-env params that callers write
through gen.params["generator"] without re-capturing a graph.
Registry.describe() reports each spec’s Params JSON schema
(SpecInfo.params_schema).
Pipeline stages. A Stage of kind "relax", "width",
"elevation" or "validity" (registered as a class, like a generator Stage)
replaces or extends a stage of the track pipeline: select it with
track_gen.pipeline.presets.track() (relax="acme.taubin",
checks=("default", "acme.max_curvature"), …), or with the flat shortcuts
TrackGenConfig(relax_solver=...) / TrackGenConfig(z_profile=...). TrackGen’s own
stages of these kinds (relax.xpbd, elevation.noise, validity.default, …)
are registered with the built-in generators. track_gen.testing.check_stage() runs
the conformance checks on one.
Quarantine. A quarantined plugin’s registrations are rolled back and it is listed in
Registry.quarantined; an unknown-name error lists the quarantined plugins with
their errors. Asking for a name the plugin tried to register, or a name namespaced under
its entry-point name, entry-point module or distribution name (acme.x for an entry
point named acme), raises PluginLoadError chained from its load error.
Registry¶
- API_VERSION: int = 1¶
Version of the plugin API this TrackGen provides. A plugin declares the version it targets as
track_gen_api = <int>in the module its entry point names (or as an attribute of its register callable); a plugin that declares a newer version is quarantined instead of being called.
- ENTRY_POINT_GROUP: str = "track_gen.plugins"¶
The
importlib.metadataentry-point group scanned for third-party plugins.
- POLICY_ENV: str = "TRACK_GEN_PLUGINS"¶
The environment variable selecting the third-party plugin policy:
warn(default),strictoroff(Environment variables). Any other value emits aRuntimeWarningand falls back towarn.
- registry: track_gen.plugin.Registry¶
The process-global
Registry.TrackGenerator,GateGeneratorandCourseresolveconfig.generatorthrough it, andTrackGenerator,GateGeneratorandbuild()the pipeline stages of the open kinds (config.relax_solver,config.z_profileandGateGenConfig.gate_orderingincluded).
- class Registry[source]¶
Bases:
objectSpecs keyed by
(kind, name). Thread-safe.Loading, the
TRACK_GEN_PLUGINSpolicy and quarantine are described intrack_gen.plugin. The process-global instance isregistry. Construct a separateRegistry()only for isolated experiments; the library’s facades resolve names through the global one.- add(spec, *, kind=None, replace=False)[source]¶
Register
specunder(kind, spec.name).Adding the identical spec object again under the same kind is a no-op.
- Parameters:
spec – The spec (a
Stageclass,GeneratorSpec,GateGeneratorSpec, or any object with anameunder another kind).kind (str | None) – Inferred for a Stage (
Stage.kind),GeneratorSpec("generator") andGateGeneratorSpec("gate_generator"); required otherwise.replace (bool) – Replace an existing spec of that name, emitting a
PluginOverrideWarningthat names both origins.
- Raises:
DuplicateSpecError – The name is already registered in that kind (by a different spec object) and
replaceis false. Raised from a plugin’sregister(reg), it quarantines that plugin (or raises understrict).TypeError – The kind cannot be inferred, or is not a non-empty string, or a
relax/width/elevation/validityspec is not a Stage class.ValueError – The kind does not match the spec type, or the name is not a non-empty string.
- Return type:
None
Third-party names should be dotted (
"package.name"); a new non-dotted name added outside the built-ins emits a one-timeUserWarning.A
Stageis registered as its CLASS (kindcomes fromStage.kind); its declaration is checked first (seetrack_gen.plugin.stage). A spec with aparams=class has the class checked (frozen dataclass, every field with a default, static fields only).
- get(kind, name)[source]¶
The spec registered under
(kind, name).- Raises:
PluginLoadError –
namebelongs to a quarantined plugin (chained from the plugin’s original load error).ValueError – Unknown name; the message lists the available names and the quarantined plugins with their errors.
- Parameters:
- available(kind=None)[source]¶
Sorted registered names of
kind; withkind=None, every spec as"kind:name".
- unregister(kind, name)[source]¶
Remove
(kind, name). RaisesValueErrorwhen it is not registered.Inside a plugin’s
register(reg), a plugin may only unregister names it registered itself (override others withadd(spec, replace=True)).
- describe(kind, name)[source]¶
Provenance and execution flags of
(kind, name)(seeSpecInfo). Raises likeget(), andTypeErrorfor a Stage whosecapture_classdoes not return aCaptureClass.
- property quarantined: Mapping[str, str]¶
A read-only mapping of each quarantined plugin to the error text that quarantined it.
Keys are entry-point names; when two quarantined plugins share an entry-point name, each of their keys is qualified as
"<distribution>:<entry point>".
- class SpecInfo[source]¶
Bases:
objectWhat
Registry.describe()reports about one registered spec.- distribution¶
(name, version)of the distribution whose entry point registered the spec;Nonefor built-ins and for specs added at run time.
- capturable¶
The spec’s
capturableflag (Truewhen it has none). For a Stage:capture != HOST, computed from the DEFAULT Params; a stage whosecapture_class()depends on its params may differ for other values (the generator reports its own).- Type:
- params_schema¶
The JSON schema (draft 2020-12) of the spec’s Params class: per field
type,default,minimum/maximum(from lo / hi),enum(choices),description(doc),x-staticorx-dynamic(withx-dtype) andx-ui.{}for a spec without parameters (a legacy spec without aparams=class, or a Stage withNoParams). Alwaysjson.dumps-able.- Type:
Mapping[str, Any]
- capture¶
A Stage’s
CaptureClassfor its default Params;Nonefor a spec that is not a Stage.- Type:
- output¶
A Stage’s
output("centerline"for a generator,Nonefor a track-pipeline stage);Nonefor a spec that is not a Stage.- Type:
str | None
- writes¶
A Stage’s
writes(local port -> channel);{}otherwise. A centerline generator’s DECLARED writes for its default Params (Stage.declare; other params may declare more, likecapture).
- provides¶
A Stage’s
provides; empty otherwise (a centerline generator: its declaration at the default Params).- Type:
- orderings¶
A gate generator’s order whitelist: a Stage’s
orderings(None: every registered order butexcluded_orderings), a legacy spec’sfrozenset(supported_orderings);Nonefor other specs.- Type:
frozenset | None
- preferred¶
A generator Stage’s
preferred(a gate generator’s{"order": ..., "normalize": ...}, a centerline generator’s{"relax": ...});{}otherwise.
- supported_orderings¶
A gate generator’s accepted orders, resolved against the registry at
describetime and sorted: its whitelist, or every registered order but itsexcluded_orderings(a legacy spec: itssupported_orderings);()for other specs.
- seeded¶
A generator’s
seeded(False: its output does not depend on the seed, e.g. an ingestion generator);Truefor a legacy spec and every other kind.- Type:
- __init__(kind, name, origin, distribution, capturable, accepts_ctx, builtin=False, entry_point=None, params_schema=<factory>, capture=None, output=None, writes=<factory>, provides=frozenset({}), reads=<factory>, orderings=None, excluded_orderings=frozenset({}), preferred=<factory>, supported_orderings=(), seeded=True)¶
- classmethod __new__(*args, **kwargs)¶
- class GeneratorSpec[source]¶
Bases:
objectOne registered generator.
- name: the
config.generatorstring that selects it. Third-party names should be dotted (
"package.name").- alloc_scratch:
(config) -> scratch— allocate this generator’s PRIVATE working buffers ONCE (fixed shapes from config, on config.device). The generation OUTPUT buffers (centerline, valid) are owned by the orchestrator and passed to
generate; they are NOT part of this.- generate:
(seeds_wp, config, out_centerline, out_valid_wp, scratch) -> None— write the closed centerline ([E*num_points] vec2f) into out_centerline and per-env validity ([E] int32) into out_valid_wp, using scratch. Pure Warp, in-place, zero-alloc, no host sync WHEN
capturable=True.- capturable: whether
generateobeys the CUDA-graph-capture contract (pure Warp, no host sync, no per-call allocation). Defaults
Trueso existing three-arg registrations are unchanged and stay on the capture-then-replay CUDA path. Acapturable=Falsegenerator may use host-side loop control and per-call allocation;TrackGeneratorruns it EAGERLY on CUDA every call (the same code path ascpu), never building awp.Graphfor it.- accepts_ctx:
Truewhengenerateaccepts actx=keyword (a track_gen.exec.LaunchContext) and emits all of its device work through it; the orchestrator then callsgenerate(..., ctx=ctx). Legacy plugins leave itFalseand the orchestrator runsgenerateinsidectx.scope(), so thewarp_pipelinehelpers they call (which defaultctx=Noneto the ambient context) inherit the call’s stream; their own directwp.launchcalls still go to Warp’s current stream.- params: optional
track_gen.plugin.Paramssubclass (STATIC fields only) declaring this generator’s own parameters. Users set them with
TrackGenConfig(generator=name, generator_params=...)(an instance or a mapping of its fields;Noneresolvesparams.from_legacy(config)).generate/alloc_scratchread the resolved instance asconfig.generator_params, andregistry.describereports its JSON schema.None(the default): the generator takes nogenerator_params.
A generator written against this function-based form is supported through 0.x; a
Stageis the recommended form for a new generator (GeneratorSpec and the 0.1 plugin ABIs).- __init__(name, alloc_scratch, generate, capturable=True, accepts_ctx=False, params=None)¶
- classmethod __new__(*args, **kwargs)¶
- name: the
- class GateGeneratorSpec[source]¶
Bases:
objectOne registered native gate generator (the legacy gate generator ABI).
Deprecated: write a Stage with
kind="gate_generator"andoutput="anchors"(Writing a Gate Generator). AGateGeneratorSpecruns through an adapter stage until 1.0, and binding one emits aDeprecationWarning. Itsmax_gatescallable is read only as the adapter’s capacity (the most gatesgeneratewrites, checked againstGateGenConfig.min_gates/max_gates).- capturable: whether
generateobeys the CUDA-graph-capture contract (pure Warp, no host sync, no per-call allocation). Defaults
True. Acapturable=Falsegenerator may use host-side loop control and per-call allocation;GateGeneratorruns it EAGERLY on CUDA every call (the same code path ascpu), never building awp.Graph. MirrorsGeneratorSpec.capturable.- accepts_ctx:
Truewhengenerateaccepts actx=keyword (a track_gen.exec.LaunchContext) and emits all of its device work through it; the gate Program’s legacy adapter stage (track_gen.stages.gates._LegacyGateSlot) then callsgenerate(seeds_wp, config, out, scratch, ctx=ctx). Legacy plugins leave itFalseand the adapter runsgenerateinsidectx.scope(), so thewarp_gate/warp_pipelinehelpers they call (which defaultctx=Noneto the ambient context) inherit the call’s stream; their own directwp.launchcalls still go to Warp’s current stream. MirrorsGeneratorSpec.accepts_ctx.- params: optional
track_gen.plugin.Paramssubclass (static fields only) set through
GateGenConfig.generator_params; read asconfig.generator_paramsbygenerate/alloc_scratch/max_gates. MirrorsGeneratorSpec.params.
Deprecated: binding one (
GateGenerator,Course,build) emits aDeprecationWarning; it runs through an adapter until 1.0. Write a gate generatorStageinstead (The anchors stage contract).- __init__(name, alloc_scratch, generate, max_gates, supported_orderings, capturable=True, accepts_ctx=False, params=None)¶
- classmethod __new__(*args, **kwargs)¶
- capturable: whether
- exception DuplicateSpecError[source]¶
Bases:
ValueErrorA spec was added under a
(kind, name)that is already registered (andreplace=Truewas not passed). The message names both origins.
- exception PluginOverrideWarning[source]¶
Bases:
UserWarningadd(..., replace=True)replaced an existing spec. The message names both origins.
- exception PluginLoadError[source]¶
Bases:
RuntimeErrorA third-party plugin failed to load: raised at load under
TRACK_GEN_PLUGINS=strict, and byRegistry.get()when a name provided by a quarantined plugin is requested (chained from the original error).
Parameters¶
A generator’s own parameters, set with generator_params (a config field or a preset
argument) and reported by SpecInfo.params_schema; see Parameters: Params and
Per-generator parameters: generator_params. At run time, TrackGenerator.params and
GateGenerator.params return a ParamTable
(see Runtime parameters and inputs), whose entries hold each dynamic field as
a live per-env array.
- class Params[source]¶
Bases:
objectBase of every stage’s parameters: a frozen, keyword-only dataclass whose fields all have defaults.
Subclasses must be decorated with
@dataclasses.dataclass(frozen=True, kw_only=True); fields usestatic()ordynamic()(a plain field counts as static). Reserved field names:validate,from_legacy,to_dict,from_dict,replace,schema,fields,describeand any name starting with_.A static field annotated
int,floatorboolmust hold a value of that type (int: an integral, not a bool;float: any real, not a bool;bool: a bool, or 0 / 1), checked with itslo/hi/choices.- validate(layout)[source]¶
Validate ONLY this object’s own fields; raise
ValueError.Called at generator construction, and only for the selected generator, before the generic
lo/hi/choiceschecks. The default does nothing.- Parameters:
layout – The run’s
Layout(num_envs,stride,device).- Return type:
None
- classmethod from_legacy(cfg)[source]¶
Map a flat
TrackGenConfig/GateGenConfigto this class.Used when
cfg.generator_paramsisNone. The default returnscls(). A plugin migrating off aTrackGenConfigsubclass overrides it to read its old flat fields (getattr(cfg, "acme_x", default)).- Return type:
- __init__()¶
- Return type:
None
- class NoParams[source]¶
Bases:
ParamsThe Params of a stage without parameters.
- __init__()¶
- Return type:
None
- classmethod __new__(*args, **kwargs)¶
- static(default, *, lo=None, hi=None, choices=None, doc='', ui=None)[source]¶
A field baked in at construction: shapes, iteration counts, enums, Python branches.
- Parameters:
default – The field’s default value (required: every Params field has one).
lo – Inclusive lower bound (JSON schema
minimum), orNone.hi – Inclusive upper bound (JSON schema
maximum), orNone.choices – The allowed values (JSON schema
enum), orNone.doc (str) – A one-line description (JSON schema
description).ui – Free-form UI hint: a widget name such as
"slider", or a mapping withwidget/label/step/groupkeys (JSON schemax-ui).
- dynamic(default, *, lo=None, hi=None, doc='', ui=None)[source]¶
A per-env field: an
[E]device array the stage reads by pointer (param()).The annotation selects the dtype:
float->wp.float32,intandbool->wp.int32. The array is filled with the field’s value at construction and is writable between or inside graph replays with no re-capture (gen.params[stage_id][field],StageParams.set()). Arguments as instatic()(nochoices).- Parameters:
doc (str)
- class ParamTable[source]¶
Bases:
Mappingstage_id -> StageParams(gen.params):"generator", plus every pipeline stage with a dynamic field (a randomizer, an elevation profile with its altitude knobs,gate_frames, …). Created by the generator facades, never by user code (the constructor is private and may change).- __init__(*, _token=None, stages)[source]¶
- Parameters:
stages (Mapping[str, StageParams])
- Return type:
None
- class StageParams[source]¶
Bases:
MappingOne stage’s dynamic parameters:
field -> LIVE [E] array.The arrays have a stable pointer for the generator’s lifetime; iteration yields the dynamic field names only.
paramsis the resolved Params instance (its static values and the INITIAL dynamic values), orNonefor a legacy generator without a Params class.Each generation latches the live arrays into the stage’s working copy for the envs it regenerates (every env, or
mask[e] != 0), so a write takes effect at the next generation and envs left out of a masked regeneration keep the value their current output was built with.Unlatched arrays: a field drawn by a
randomize.*stage (drawn) is a single array OWNED by its randomizer and written by it every generation:self[field]returns it (observable),set()raises (set the randomizer’slo/hiinstead), and a direct write is overwritten by the next draw. Bezier’sstyle_samplingranges draw their knobs the same way, by the stage itself (drawn["rad"] == "generator", the stage’s own id).Curriculum bookkeeping:
in_effect()returns the[E]values the stage READ for the current rows of each env (the latched working copy, or the drawn array), so a masked regeneration’s unmasked envs report what their rows were built with;bounds()the(lo, hi)a built-in kernel clamps a value to where it reads it (the value USED isclamp(in_effect, *bounds)).Created by the generator facades, never by user code: the constructor is private (it raises
TypeError) and may change; the read API is[field],set(),in_effect(),bounds(),params,stage_idanddrawn.- __init__(*, _token=None, stage_id, params, arrays=None, num_envs=0, device=None, default_stream=None, drawn=None, bounds=None, effective=None, hints=None)[source]¶
- property drawn: Mapping[str, str]¶
a
randomize.*stage targeting this stage, orstage_iditself for a field the stage draws itself (bezier’sstyle_samplingranges). Every value is a key ofgen.params; empty when nothing draws this stage’s fields.- Type:
field -> id of the stage that draws itevery generation
- property params: Params | None¶
a legacy generator without one): its static values and the INITIAL values of its dynamic fields. A later
set()(or a write to the live array) does not change it; the current values are the live arrays,self[field].- Type:
The resolved Params instance as of construction (
None
- in_effect(field, *, out=None, stream=None)[source]¶
The
[E]values this stage READ for the current rows of each env: for a latched field the stage’s working copy (the latch writes it at the start of every generation, for every env or only the masked ones, so an env left out of a masked regeneration keeps the value its rows were built with); for a drawn field (arandomize.*target, or a knob bezier’sstyle_samplingdraws) the drawn array itself. Same dtype asself[field].Values read, not values used: an array written out of range is returned as written; a built-in stage clamps it to
bounds()where it reads it, so the value it USED isclamp(in_effect(field), *bounds(field))(equal toin_effectfor every in-range write, scalarset()and randomizer draw). Exceptions:width.from_channelreads itsscale/lo/hias written (it clamps its output), a randomizer itslo/hi(it clamps its draw), bezier’sstyle_samplingdraws are not bounded, andCoursegates mode’s floorgate_frames.gate_width > 0is checked byset()on a scalar only (an array value is clamped to the declared0).Timing: the values are written on the generation’s stream. A latched field is current once the generation’s first segment ran (the latch is its prologue); a drawn field once the segment holding its randomizer ran (before that, the previous generation’s draws). Read it on that stream (a later launch or capture node, or
out=withstream=) or after waiting for the generation; between aset()and the next generation it holds the old values; before any generation, the initial value.- Parameters:
field (str) – A dynamic field of this stage.
out (array | None) – Optional
[E]contiguous array of the field’s dtype on the generator’s device: one copy into it, enqueued on the resolved stream (asset(); capturable), andoutis returned. Without it the INTERNAL array is returned (zero-copy, stable pointer): it aliases the stage’s state, so never write it (a write changes what unmasked envs report and what a randomizer’s unmasked envs redraw).stream – Optional
wp.Stream(or"torch") for theout=copy.
- Raises:
KeyError –
fieldis static or unknown.ValueError –
outof the wrong shape, dtype, device or layout.
- Return type:
array
- bounds(field)[source]¶
(lo, hi)a value of the dynamicfieldis held to: its declared bounds, narrowed per instance (bezier / hull’smin_num_points:(2, max_num_points); a randomizer’slo/hi: the drawn field’s declared range);Nonefor an open side.set()refuses a scalar outside them, and the built-in generator, elevation,normalize.extentandgate_framesstages clamp an array value to them where they read it (comparisons: a NaN passes; the exceptions are listed inin_effect()).
- set(field, value, *, stream=None)[source]¶
Write a dynamic field’s LIVE
[E]array, in place.An ENQUEUE on the resolved stream (
stream=> ambient scope > the generator’s constructor stream > torch capture > Warp’s current stream): one fill launch for a scalar (checked on the host against lo/hi first), one copy for an array (no range check, no host sync). Inside a capture it is recorded: a scalar is baked into the caller’s graph, an array is read at replay. Takes effect at the next generation. Never allocates.- Parameters:
field (str) – A dynamic field of this stage.
value – A host scalar (broadcast to every env) or an
[E]contiguouswp.arrayof the field’s dtype on the generator’s device.stream – Optional
wp.Stream(or"torch") to enqueue on.
- Raises:
KeyError –
fieldis static or unknown.ValueError – wrong shape, dtype or device; a value that is neither a
wp.arraynor a scalar of the field’s type; a scalar outside lo/hi (compared in float32 for a float32 field; for a randomizer’slo/hi: outside the declared range of the field it draws; for bezier / hull’smin_num_points: abovemax_num_points; seebounds()); a field drawn every generation (by a randomizer, or by the stage itself: bezier’sstyle_samplingranges).
- Return type:
None
An array is not range-checked: a built-in stage clamps each value to the field’s declared bounds where it reads it.
The built-in generators’ and elevation stages’ Params have dynamic fields too (the
generators’ shape knobs and size, bezier / hull’s int32
min_num_points, z_min / z_max / z_max_step / z_noise_amplitude, the
gate pipelines’ normalize.size and gate_frames.gate_width), with declared bounds:
see Built-in generator parameters and Per-env shape knobs. in_effect and
bounds are the curriculum bookkeeping of the tuning guide.
Reach a built-in generator’s class with registry.get("generator", name).Params;
.params still works for bezier, hull, polar, voronoi and
checkpoint but emits a DeprecationWarning and is removed in 1.0.
Stages¶
The Stage protocol: a Stage subclass, registered as a class, that allocates
through the StageBuilder it is handed and returns an enqueue(ctx) callable.
Plugins register kind="generator" stages with output="centerline" (see
The Stage contract; an ingestion generator reads per="raw" inputs and
may declare its ports per params with declare, see Writing an ingestion generator),
kind="gate_generator" stages with output="anchors" (see
The anchors stage contract; they also declare provides, orderings or
excluded_orderings, and preferred), and stages of the open pipeline kinds
"relax", "width", "elevation", "validity", "order" and
"stations", which read channels through StageBuilder.read and
rewrite them into StageBuilder.write (see Writing a Stage). The other stages
are TrackGen’s built-ins (see Pipeline stages). A stage’s CaptureClass is
STATIC, BOUNDED (a loop through LaunchContext.bounded_loop, see
Writing an iterative (BOUNDED) stage) or HOST (host work through LaunchContext.host() and
StageBuilder.host_buffer, see Writing a host-driven (HOST) stage).
- class Stage[source]¶
Bases:
ABCBase of a pipeline stage.
Class attributes a stage sets:
kind: the registry kind:"generator","gate_generator", or a pipeline kind"relax","width","elevation","validity","order"or"stations".name: the registered name; third parties use a dotted"package.name".capture: itsCaptureClass(defaultSTATIC;BOUNDEDfor a stage whosectx.bounded_looppassesactive=,HOSTfor one that usesctx.host()); overridecapture_class()when it depends on the params.reads:local port -> channelthe stage reads (StageBuilder.read()). Must be empty for a generator.writes:local port -> channelthe stage writes (StageBuilder.write()); a generator writes"raw.xy"(required) and optionally"gen.valid"and its raw attributes ("raw.z","raw.half_width","raw.<package>.<name>"); a gate generator"anchors.xy"and"anchors.count"(required), the attributes it provides and its"anchors.<package>.<name>"attributes. A channel in bothreadsandwrites, through ONE port, is a rewrite:writereturns a NEW version of the channel (later stages see it),readthe previous one, so the stage writes its whole output into thewritebuffer and never writes thereadone.provides: a gate generator’s per-anchor attributes, a subset of{"z", "tangent", "half_size", "roll"}equal to theanchors.<attr>channels it writes (the gate preset then drops the stages that would compute them); a centerline generator’s, a subset of{"z", "half_width"}equal to theraw.z/raw.half_widthit writes (the presets drop the elevation / the width stage); empty for every other kind.orderings/excluded_orderings(gate generators): theorderstages it supports: a whitelist (orderings, a frozenset of order names), or, withorderings=None, every registered order butexcluded_orderings. A generator that provides attributes declaresorderings=frozenset({"raw"})(they are carried to the gate slots by index).preferred: a gate generator’s{"order": name, "normalize": "extent"}(the order the gate preset picks by default, and whether it normalizes the gates’ extent after ordering); a centerline generator’s{"relax": name}(the relax stagepresets.track(relax="auto")picks,"none"for an ingestion generator whose geometry must not move).seeded:Falsefor a generator whose output does not depend on the seed (an ingestion generator): the conformance kit reportsdiversityas skipped; defaultTrue.topology:"closed"or"any"(the topologies the stage supports;BatchSpec.closed=Falseis refused in 0.x, naming the closed-only stages).output:"centerline"for a generator,"anchors"for a gate generator;Nonefor the other kinds.api_version: the plugin API version the stage targets (<= API_VERSION).
The orchestrator instantiates the class once per pipeline with no arguments.
capture_class(),capacity()andbuild()receive the params with their STATIC fields only: reading a dynamic field there raisesAttributeError(a host scalar taken from it would be baked into the launches and ignore everyset); read it on the device throughStageBuilder.param().Besides
kind,name,Params,reads,writes,provides,capture,outputandtopology, a stage may setseeded(Trueby default;Falsefor a generator whose output does not depend on the seed, see Writing an ingestion generator) andpreferred(a gate generator’s"order"/"normalize", a centerline generator’s"relax").Every built-in generator is a
Stageclass:registry.get("generator", "bezier")(hull,polar,voronoi,checkpoint:STATIC;repulsive:BOUNDED;external) andregistry.get("gate_generator", name). Their module functions (generate_*_warp,*_alloc_scratch, thesample_*helpers and their*_per_envforms) are not public API; see Legacy _src helpers.- capture: ClassVar[CaptureClass] = 0¶
- classmethod capture_class(params)[source]¶
The stage’s capture class for
params(default:capture).- Return type:
- classmethod declare(params)[source]¶
(writes, provides)for these params (default: the class’swritesandprovides).Read for centerline generators only in 0.x: a generator whose raw attributes depend on its params (an ingestion generator writes
raw.zonly when it is given altitudes) overrides it; the declaration is checked at bind time with the class rules (raw.xymapped,providesnaming exactly the catalogue attributes written) and routes the presets (dropped stages, carried attributes) and the planner.paramsholds the static fields only. Overriding it on another kind is refused at registration.
- capacity(params, layout)[source]¶
Buffer capacities this stage needs. A generator may return
{"raw": n}(points per env of its"raw.xy"output, an integern >= 3); the default{}usesTrackGenConfig.num_points. A gate generator (and astationsstage) returns{"gates": A, "min_gates": k}:Aanchor rows per env (the most real anchors it emits,>= 1; it must fitmax_gates) andkthe fewest (default 0, at mostA). Any other key raisesValueError.
- abstractmethod build(b, params)[source]¶
Allocate everything through
b, ONCE, and returnenqueue(ctx).enqueueemits onlyctx.launch/ctx.copy/ctx.fill/ctx.bounded_loop/track_gen.opswork: no allocation, no host sync, no branch on device data or onctx.capturing(aHOSTstage reaches the host throughctx.host()).- Parameters:
b (StageBuilder)
- Return type:
Callable[[LaunchContext], None]
- class StageBuilder[source]¶
Bases:
objectHanded to
Stage.build(); created by the orchestrator, never by plugins.This release implements
read(),write(),temp_like(),scratch(),host_buffer(),param(),input(),salt(),fail_bit(),stride_of(),per_of()anddtype_of(). Every method allocates at build time only, and raises oncebuild()returned.- E¶
The number of envs.
- device¶
The
wp.Device.
- stage_id¶
The stage’s id in the pipeline (
"generator"for the generator).
- scale¶
TrackGenConfig.scale(a core field, not a generator Param).
- seed¶
[E]int32: the effective per-env seed of the current generation (seed and epoch folded); read it inenqueue.
- raw¶
Points per env of the
"raw.xy"channel.
The arrays
param()andinput()return are READ-ONLY for the stage: never write them (not even as scratch), since a generation’s values come from the live arrays the caller writes.- __init__(*, _token=None, stage, params, layout, scale, seed, raw, outputs, dynamic, reserved_inputs=('regen_mask',), stage_id='generator', plan=None, capture=None)[source]¶
- write(port, dtype=None, *, per=None, interp=None, export=False)[source]¶
The output buffer of
writes[port].A generator writes
"raw.xy"(wp.vec2f,per="raw",[E * raw]) and"gen.valid"(wp.int32,per="env",[E]), plus its raw attributes:"raw.z"/"raw.half_width"(float32, the catalogue attributes itprovides) and its own"raw.<package>.<name>"channels, each carried to the centerline (cl.<package>.<name>) by the resamples with theinterppolicy its write declares.persizes the buffer:"env"is[E],"cl"is[E * N_max],"raw"is[E * raw],"anchors"is[E * A]and"gates"is[E * max_gates](gate pipelines).dtype/perare those of the channel the port DECLARES; a port the planner rewired (an elevation’scl.zon gates) gets a buffer sized for the channel it is wired to (per_of(),stride_of()). The buffer is owned by the orchestrator and read directly by later stages (no copy). For a channel the stage also reads (a rewrite) it is a NEW version:read()keeps returning the previous one. A plugin channel (<domain>.<package>.<name>) takes thedtype/perof its first writer; a later writer must match. The prefix fixesperforraw.*("raw") andanchors.*("anchors");cl.*/gates.*default to"env".dtypeandpermay be omitted for a catalogue channel, or a plugin channel an earlier stage wrote: they are then the channel’s (b.write("z")). Given, they must match it. A plugin channel’s first writer passesdtype.- Parameters:
port (str) – A write port of the stage.
dtype – The Warp dtype (see above).
per (str | None) – The size (see above).
interp (str | None) – A raw attribute’s carry policy (required on every
raw.<package>.<name>;"linear","nearest","previous"or"angle"): how the resamples carry it to their output points. Only on a generator’s raw writes.export (bool | str) –
True(or the channel’s key, e.g."acme.surface_id") to export a per-point plugin attribute asTrack.attributes[key]/GateSequence.attributes[key](key: the channel name without its domain prefix). On per-point plugin channels only (raw.*,anchors.*, orcl.*/gates.*written withper="cl"/"gates"), by the channel’s first writer.
- Raises:
ValueError – an undeclared port (lists the declared ones), a channel the stage may not write, a second write of the same port,
interp=on a channel that is not a generator’s raw attribute,export=on a catalogue or per-env channel or naming another key,interp=/export=on a rewrite.TypeError – a
dtype/perthat does not match the channel; a raw attribute withoutinterp=, of a dtype other than float32, int32, vec2f or vec3f, or with a policy its dtype does not admit (int32:"nearest"/"previous";"angle": float32).
- Return type:
array
- dtype_of(port)[source]¶
The Warp dtype of the channel
portis WIRED to (a required read port"cl.*": the channelStageRef.portsnames).- Raises:
ValueError – an undeclared port, or a plugin channel nobody wrote yet.
- Parameters:
port (str)
- per_of(port)[source]¶
The
per("env","raw","cl","anchors"or"gates") of the channelportis WIRED to (after any rewiring: an elevation’szport is"gates"in a gate pipeline).- Raises:
ValueError – an undeclared port, or a plugin channel nobody wrote yet.
- Parameters:
port (str)
- Return type:
- stride_of(port)[source]¶
The row stride (points per env) of the channel
portis WIRED to:rawfor"raw",N_maxfor"cl",A(the anchors producer’scapacity()["gates"]) for"anchors",max_gatesfor"gates", 1 for"env". Index enve’s row ase * stride_of(port) + i.- Raises:
ValueError – an undeclared port, or a plugin channel nobody wrote yet.
- Parameters:
port (str)
- Return type:
- read(port)[source]¶
The buffer of
reads[port]: the latest version of its channel written by an EARLIER stage (for a channel this stage also writes, the version before its own). READ-ONLY for the stage.- Raises:
ValueError – an undeclared read port (lists the declared ones). A generator declares none.
- Parameters:
port (str)
- Return type:
array
- temp_like(port)[source]¶
A private zero-initialised buffer with the dtype and length of
port’s channel (e.g. the ping-pong partner of an in-place rewrite), allocated asscratch()"<port>.tmp<k>"(k = 0, 1, ...per port).- Raises:
ValueError –
portis neither a read nor a write port of the stage.- Parameters:
port (str)
- Return type:
array
- scratch(name, dtype, shape)[source]¶
A private zero-initialised buffer, namespaced
"scratch:<stage_id>/<name>". Never re-zeroed: write before you read. A duplicatenameraisesValueError.- Parameters:
name (str)
- Return type:
array
- host_buffer(dtype, shape)[source]¶
A zero-initialised HOST array (pinned on a CUDA device) for a HOST stage’s
HostOps(via=ofreadback/upload).Allocated once, here; the stage keeps it in its
enqueueclosure.- Raises:
ValueError – the stage’s capture class is not
CaptureClass.HOST.RuntimeError – called after
build()returned.
- Return type:
array
- param(field)[source]¶
The stage-side
[E]array of a DYNAMIC Params field (float32 or int32).This is the working copy, latched from the live array the user writes (
gen.params[stage_id][field]) at the start of every generation, for the envs that generation regenerates. A field arandomize.*stage draws is the drawn array itself (unlatched: written by the randomizer earlier in the same generation). READ-ONLY for the stage: never write it.
- input(name, dtype, default=0, *, per='env')[source]¶
A persistent device input exposed as
gen.inputs[name](andProgram.input_info[name]), filled withdefault.per="env"(default): a stable LIVE[E]float32/int32array, LATCHED into the returned working copy at every generation like a dynamic param (a write takes effect for the envs the next generation regenerates).per="raw"(centerline generators only): a stable[E * raw]float32/int32/vec2f/vec3farray, UNLATCHED: the returned array IS the input, read during the generation (the caller writes it on the generation’s stream, or orders the streams with events). The ingestion inputs (centerline.*) are of this kind.
The returned array is READ-ONLY for the stage, like
param()’s. Name the input with a dotted path in the stage’s own namespace (centerline.*is the ingestion namespacetrack_gen.pipeline.load_centerlines()reads).- Parameters:
- Raises:
TypeError – another dtype.
ValueError – a name clash; an unknown
per;per="raw"on a stage that is not a centerline generator.
- Return type:
array
- salt(stream)[source]¶
A
uint32salt for an independent random stream:hash32_host(fnv1a_32(f"{stage_id}/{stream}".encode())).Frozen for plugin API version 1, so outputs stay reproducible across releases. Key the per-env RNG as
wp.rand_init(int32(hash32(uint32(seed[e]) ^ salt) & 0x7FFFFFFF)). Idempotent per stream; two streams whose salts collide raiseValueError.
- fail_bit(name=None)[source]¶
The index (16..30) of a new fail bit owned by this validity stage.
In the kernel:
fail_out[e] = fail_in[e] | (1 << bit)where the check fails (fail_in/fail_out: the read / write buffers of the stage’svalid.failport). Bits are handed out in pipeline order; call it once per independent check.- Parameters:
name (str | None) – The bit’s key in
Program.fail_bits/Track.fail_bits(default: the stage id). Unique in the pipeline.- Raises:
ValueError – the stage is not a validity stage.
track_gen.pipeline.PlanError – the name is taken, or bits 16-30 are all taken (a pipeline holds at most 15 plugin fail bits).
- Return type:
- class CaptureClass[source]¶
Bases:
IntEnumHow a stage’s
enqueuemay be captured (ordered:STATIC < BOUNDED < HOST; a pipeline’s class is the largest of its stages’).STATIC: a fixed launch sequence, capturable by anything.BOUNDED: data-dependent convergence with a static bound, throughLaunchContext.bounded_loopwithactive=: capturable by anything; its loop may stop early (an eager host poll on CPU, conditional graph nodes inside Warp-registered captures), so every iteration afteractive[0]reads 0 must be an exact no-op (an absorbing freeze; the conformance kit checks it).HOST: host readback / control flow (throughLaunchContext.host): runs eagerly on every call, between the graph launches of the rest of its pipeline (segmented execution); a caller capture raisesNotCapturableErrorbefore any launch. It reaches the host only throughctx.host()(HostOps, overStageBuilder.host_bufferbuffers); a direct.numpy(),wp.synchronize*()or per-call allocation still fails the conformance kit’shot_path.host_sync/hot_path.allocation.
- __new__(value)¶
- __init__(*args, **kwds)¶
- class track_gen.plugin.Layout
The run’s shape handed to
Params.validate,Stage.capacityand theStageBuilder: the same class astrack_gen.results.Layout(see Layout).