The gate pipeline¶
GateGenerator runs a Program too. Its spec is a gate pipeline:
BatchSpec.max_gates is set, the result is a GateSequence, and the stages take gate anchors, the plan-view points a generator emits, to ordered,
separated, elevated, framed and validated gates, one sequence per env and
generation. Every rule of Pipeline stages applies to it unchanged (the
planner, versioned rewrites of channels, fail bits, the commit,
taps, capture classes, the parameter latch); this page lists
what differs.
Gate presets: presets.gates shows the user-facing side.
from track_gen import GateGenConfig, GateGenerator
from track_gen.pipeline import presets
spec = presets.gates(num_envs=64, device="cpu") # bezier anchors, ccw order
spec.ids
# ('generator', 'order', 'gate_relax', 'elevation', 'lift', 'tangents',
# 'gate_frames', 'validity')
gen = GateGenerator(spec) # env e gets seed e
gates = gen.generate()
gates.fail_mask # [E] int32: one bit per failed check
gates.fail_bits # {'count': 0, 'finite': 1, ..., 'grade': 5}
gen.program.tap("anchors.xy") # the anchors, before ordering
# The same Program from the flat config (byte-identical):
GateGenerator(GateGenConfig(num_envs=64, device="cpu"))
# Another order and elevation, by registered name:
presets.gates(num_envs=64, device="cpu", generator="polar", order="raw",
elevation=("noise", {"z_min": -0.3, "z_max": 0.3,
"z_noise_amplitude": 0.3}))
# Gates for a track generator: arc-length stations on its centerline.
presets.gates(num_envs=64, device="cpu", generator="bezier", source="stations",
gates=12)
The gate stages¶
legacy.to_gate_pipeline_spec(config) and presets.gates(...) produce this list
from a native gate generator. Stages in italics below are present only when their
condition holds (Launches counts what they launch).
id |
stage |
reads |
writes |
|---|---|---|---|
|
|
nothing (its seed is |
|
|
|
|
|
normalize |
|
|
|
carry_<attr> |
|
|
|
carry_attributes |
|
every |
the matching |
|
|
|
|
gate_arclen |
|
|
|
elevation |
|
|
|
|
|
|
|
tangents |
|
|
|
|
|
|
|
|
|
the seven frame outputs, |
|
The built-in stages’ params keep the flat GateGenConfig field names, copied from the
config: gate_relax.spheres takes gate_radius and gate_solve_iters (and refuses
a pair that would not move anything: use gate_relax.none); gate_frames.default
takes gate_width and gate_align, gate_frames.posed gate_align only; the
elevation stages take the z_* fields their profile uses (elevation.flat takes
z_base on gates); validity.gate_default takes min_gates, gate_radius,
gate_width, z_valid_grade and check_crossing (see Gate fail bits; its
gate_width and check_crossing are inert). gate_frames.default’s
gate_width is a dynamic field (per env) (Per-env bar width and course size). The generator’s
params are its generator_params; the order and carry stages take none, and
normalize.extent takes size (NormalizeExtentParams, a dynamic per-env
multiplier, default 1.0, GateGenConfig.size for the polar, voronoi and
checkpoint gate generators): it scales to ops.constants.TARGET_EXTENT *
BatchSpec.scale * size[e].
Then the commit (The gate commit) and the generation write. A legacy
GateGeneratorSpec replaces generator, order and normalize with one
adapter slot (Legacy gate generators), and a centerline generator replaces
generator with generator + stations (Stations: gates from any track
generator).
Two point domains¶
A gate pipeline has two point domains. The anchors domain is [E * A], where A is
the anchors producer’s capacity()["gates"]: a generator writes its anchors at its
own stride, as it always did. The gate slots are [E * G], G =
BatchSpec.max_gates, the GateSequence layout. The order stage maps the first
onto the second; every stage after it works on gate slots.
The planner gives each stage its own Layout, derived from its ports before its params
are validated: b.layout.stride is G for a gate-domain stage (an anchors
generator, stations.raw, an order, the gate stages, a validity plugin) and N_max
for the centerline generator of a stations pipeline and for any stage with a cl.*
port (the resample, width and relax stages of source="relaxed_stations",
with elevation_on="centerline" also its frames and elevation, and
stations.relaxed). A stage with both cl.* and gates.* ports raises
PlanError: a stage runs on one point domain. Size a buffer from the port, not from the
layout: b.stride_of(port) is the row stride of the channel a port is wired to
(raw, N_max, A or G; 1 for an [E] channel) and b.per_of(port)
its per.
Gate channels¶
channel |
per |
dtype |
meaning ( |
|---|---|---|---|
|
anchors |
vec2f |
The generator’s (or |
|
env |
int32 |
Real anchors per env. |
|
anchors |
float32, vec3f, float32, float32 |
Per-anchor attributes an anchors generator |
|
anchors |
float32, int32, vec2f, vec3f |
A plugin attribute per anchor: written by a gate generator, carried by the stations from a centerline family (Gate plugin channels). |
|
gates |
vec2f |
Plan-view gate centers: ordered ( |
|
env |
int32 |
Real gates per env ( |
|
gates, env |
float32 |
Plan-view cumulative chord length and loop length: the elevation stages’ arc tables. |
|
gates |
float32 |
Per-gate altitude. |
|
gates |
vec3f |
Lifted centers and 3D tangents, rewritten by |
|
gates |
vec3f |
The frame’s forward axis and bar endpoints (the |
|
gates |
quatf |
The gate pose ( |
|
gates |
float32 |
Opening half-extent ( |
|
gates |
float32 |
A provided roll, carried from |
|
env |
int32 |
|
|
env |
int32 |
The fail bitmask, shared with tracks ( |
|
gates or env |
any |
A gate-domain plugin channel; exported ones become |
GateSequence.generation is not a channel. As for tracks, the final version of each
exported channel is the GateSequence array. A gate-domain stage uses
gates.<package>.<name> plugin channels; a port on a cl.<package>.<name> channel
raises PlanError (Plugin channels).
Anchors generators¶
A native gate generator is a Stage with kind="gate_generator" and
output="anchors": it reads no channel, writes anchors.xy and anchors.count,
and does not order, normalize or lift anything (Writing a Gate Generator is the
guide, The anchors stage contract the stage’s contract). Its capacity(params, layout) returns {"gates": A, "min_gates": k}: A
is the most anchors it writes per env (its row stride), k the fewest (default 0).
This replaces the legacy GateGeneratorSpec.max_gates callable, which meant both “how
many slots I need” and “how many gates I can make”. The planner checks it against the
batch and the validity stage, with the messages GateGenConfig users already know:
A > BatchSpec.max_gates:PlanError:... BatchSpec.max_gates=8 is too small for gate generator 'polar'; required max_gates 12.validity.gate_default’smin_gates > A:PlanError:... min_gates=16 is too large for gate generator 'polar'; generator can produce at most 12 gates.
The five built-ins are anchors generators: bezier and hull write an unordered
cloud of grid points (A = max_num_points), polar, checkpoint and
voronoi a loop in generation order (A = their knot, checkpoint or control-point
count) that the pipeline then normalizes to TARGET_EXTENT * scale.
Provided attributes¶
An anchors generator may also write anchors.z, anchors.tangent,
anchors.half_size and anchors.roll, declared in provides (a subset of
{"z", "tangent", "half_size", "roll"} that names exactly the attributes it writes;
half_size and roll come together). The preset then drops the stages that would
compute them and carries the provided values to the gate slots instead:
|
dropped |
added or changed |
|---|---|---|
|
|
|
|
|
|
|
nothing |
|
The carry stages copy by anchor index (padding 0.0 for z and roll, NaN for
the others), so they need the index-preserving order.raw: a generator that provides
attributes declares orderings=frozenset({"raw"}). What reaches the GateSequence:
zbecomesposition.zbit for bit;tangentis normalized by the frames stage (GateSequence.tangentis the normalizedanchors.tangent; a unit float32 tangent may change in the last ulp);half_sizebecomesGateSequence.half_sizebit for bit, andgate_widthis ignored (a non-zerogate_widthraisesValueError);rollturns the frame about its forward axis:orientation,leftandrightinclude it.
The plan-view sphere separation still runs after the generator and may move a gate’s
xy, so a provided z or tangent describes the anchor as emitted, not the moved
gate: pass gate_solve_iters=0 to keep the emitted xy.
The planner holds a hand-built or edited spec to the same rules: a provided attribute
that no later stage reads raises PlanError (stage 'generator'
(gate_generator.acme.helix) provides 'z' (anchors.z), but no stage reads it; stage
'elevation' (elevation.noise) writes gates.z instead: carry it with carry.z (presets.gates
drops the stages a generator provides)), and so does a carry stage after an order
other than order.raw. The presets refuse an
explicit elevation (ValueError: ... provides its own altitudes (z); leave elevation
at its default ('flat')), and GateGenConfig a z_profile other than "flat" or a
non-zero z_base, for a generator that provides z.
Gate plugin channels¶
A gate-domain stage (an elevation, a check, an order plugin) reads and writes its own
gates.<package>.<name> channels (per="gates", [E * G], or the default
per="env", [E]), and exports the per-gate ones to GateSequence.attributes:
# a gate check: writes = {"fail": "valid.fail", "clearance": "gates.acme.clearance"}
clearance = b.write("clearance", wp.float32, per="gates", export=True)
gates = gen.generate()
gates.attributes["acme.clearance"] # [E * G] float32, NaN on the padding
Per-anchor attributes reach the gate slots by anchor index, as provided attributes do:
a gate generator writes
anchors.<package>.<name>channels (any dtype of Carried channels, withexport=but nointerp=: they are copied, not interpolated); it must declareorderings=frozenset({"raw"});the built-in stations carry a centerline generator’s
raw.<package>.<name>attributes (theircl.*images, forstations.relaxed) toanchors.<package>.<name>, when a later stage reads them;carry.attributes, whichpresets.gatesinserts after anorder.rawwhen such channels exist, copies everyanchors.<package>.<name>togates.<package>.<name>(padding NaN or-1), one launch per channel, exported or not. After an order other thanorder.rawit raisesPlanError, ascarry.<attr>does.
An exported per-point attribute of a centerline generator thus reaches
GateSequence.attributes along generator -> stations -> order.raw ->
carry.attributes. This is best effort: with another order (order="ccw"), a
plugin stations stage, or a per-point channel a stage writes on the centerline (an
elevation on the relaxed centerline, elevation_on="centerline", exporting
cl.<pkg>.<name>: the stations carry generator families only) there is no index
path, and the key is left out of GateSequence.attributes (Program.attributes
lists the keys that are there); asking
for it through out.attributes raises ValueError (out.attributes['acme.grip']:
this pipeline does not export 'acme.grip'; exported: [...]).
An anchors.* attribute a gate generator or a plugin stations stage writes must be
read by a later stage (carry.z, carry.attributes), else PlanError names the
writer and the stage to add. The plugin channels of an elevation stage are rewired to
the gate slots by prefix (Elevation on gates).
The ordering registry¶
Ordering is a stage of the open kind order, applied by the pipeline: generators no
longer order their own anchors. An order stage reads anchors.xy and
anchors.count, writes the first m = min(count, A, G) gate slots of gates.xy
(NaN after them) and gates.count. The built-ins:
stage |
order |
|---|---|
|
The anchors as the generator emitted them (index-preserving). |
|
Sorted by angle around each env’s centroid. The name is kept for compatibility: the convention is clockwise in standard xy coordinates. |
|
Random gate pairs for training objectives, keyed on the effective seed. |
GateGenConfig.gate_ordering and presets.gates(order=...) name any registered
order, a third party’s included (Writing an ordering). An unknown name raises
ValueError with the registry’s list (GateGenConfig.gate_ordering: unknown order
'spiral'; available: ['ccw', 'random_pairs', 'raw']), a quarantined plugin its
PluginLoadError.
A generator declares which orders it accepts, in one of two ways:
excluded_orderings(withorderings=None): every registered order but these. The built-ins use it:bezierandhullexcluderaw(a grid-point cloud has no order of its own),polar,checkpointandvoronoiexcluderandom_pairs. A third-party order therefore runs with every built-in generator.orderings, a whitelist. Generators that provide attributes declarefrozenset({"raw"}); a legacyGateGeneratorSpec’ssupported_orderingsis one.
preferred = {"order": name, "normalize": name} names the generator’s default order
and its normalization (preferred["normalize"] = "extent" inserts
normalize.extent). An order the generator does not accept raises (PlanError from
the planner, ValueError from the config): gate generator 'polar' does not support
gate_ordering 'random_pairs'; supported: ['ccw', 'raw'] (its preferred order is 'ccw'),
the hint only when the generator declares a preferred order. presets.gates does not
check the order: build (or the generator’s construction) does.
The default order is one rule, for an anchors generator: its preferred["order"] when
it accepts it, else "ccw" when it accepts it, else the first order it accepts
("raw" for stations). presets.gates(order=None),
GateGenConfig.gate_ordering=None (the config default, resolved by
legacy.to_gate_pipeline_spec when GateGenerator or Course is constructed)
and the conformance kit all apply it, so GateGenerator(GateGenConfig(
generator="acme.helix")) runs order.raw. The run config (the config a legacy
GateGeneratorSpec receives) holds the resolved name, never None. For the five
built-ins the rule gives "ccw". An explicit name is checked against the orders the
generator accepts.
Stations: gates from any track generator¶
A track generator (output="centerline") makes gates too: presets.gates inserts a
stations stage that places gates anchors at equal arc-length spacing along its
closed centerline, then the shared gate stages run. It works for every built-in track
generator, repulsive included, and for third-party ones, with no gate-specific code.
|
stages before |
|---|---|
|
|
|
The stages of |
|
|
Each station k sits at arc length L * k / A from point 0 along the closed loop
(A = gates, default 12; L the loop’s length, plan-view by default), so the
anchors are in loop order and the default order is raw. An env whose generator
flagged it invalid, whose loop was truncated, or whose length is not positive gets
anchors.count = 0 (NaN anchors), and fails the count check. capacity() is
{"gates": gates, "min_gates": 0}; max_gates defaults to gates.
The stations carry the centerline’s families that a later stage reads to
anchors.<attr>, through the same resample tables as the anchors: the altitude
(anchors.z, read by carry.z) and the plugin attributes (read by
carry.attributes, Gate plugin channels). stations.relaxed carries the
cl.* families except cl.half_width.
3D station spacing¶
StationsParams.spacing_metric chooses the arc length the stations measure:
"xy" (default, the plan-view length) or
"xyz", the 3D length along the loop, so that the gates are equally spaced in flight
distance on a course that climbs. presets.gates(spacing_metric=...) sets it. The "xyz" branch
accumulates the segment lengths in float64 (the 3D spacing is equal to well within
1e-6, relative), and carries the altitude to the anchors with "linear".
3D spacing needs an altitude on the loop before the stations are placed. It comes from one of two places:
the generator, one that provides
z(raw.z, Generator-provided altitude and width), withsource="stations"or"relaxed_stations";an elevation stage on the relaxed centerline, with
source="relaxed_stations", a non-flat elevation andelevation_on="centerline": the preset runsframesand the elevation on the relaxed loop, beforestations.relaxed.
The stations then carry the altitude to anchors.z, order.raw keeps the anchor
index, carry.z copies it to gates.z, and the gate-level gate_arclen and
elevation stages are dropped. elevation_on ("gates" or "centerline") chooses
where the elevation runs, independently of the metric: None (default) is
"centerline" for "xyz" and "gates" for "xy". The two are separate
controls because an elevation on the centerline is a different course: a profile drawn
along the loop’s arc length, sampled at the stations, instead of one drawn over the
gates (a knot profile such as uniform then puts its knots along the loop, not at the
gates). Toggling spacing_metric with the default elevation_on therefore also
moves the elevation; pass elevation_on explicitly to change one thing at a time. It
also changes which attributes reach the gates: a plugin elevation’s exported channel
(cl.acme.slope, rewired to gates.acme.slope on gates) is in
GateSequence.attributes when the elevation runs on the gates, and left out when it
runs on the centerline (the stations carry generator families only, not channels a
stage writes); Program.attributes lists what is there.
source, metric, altitude |
gate stages |
|---|---|
any source, |
the stations, the order, then gate-level |
an altitude on the loop (a |
|
|
|
|
|
|
|
an explicit |
|
|
|
any explicit |
|
an altitude on the loop and an explicit non- |
|
A hand-built spec (not made by the preset) with spacing_metric="xyz" and no
altitude on the loop raises PlanError (stage 'stations' (stations.raw):
spacing_metric='xyz' measures 3D arc length along the loop, which carries no altitude:
use a generator that provides z (raw.z), or source='relaxed_stations' with a non-flat
elevation). In a gate pipeline,
an elevation slot before the anchors producer keeps its cl.* ports (it runs on
the centerline); the gate rewiring of Elevation on gates applies to the slots after
it.
from track_gen.pipeline import presets
spec = presets.gates(num_envs=64, device="cpu", generator="bezier",
source="relaxed_stations", gates=12,
elevation=("noise", {"z_noise_amplitude": 0.3,
"z_min": -0.3, "z_max": 0.3}),
spacing_metric="xyz")
spec.ids
# ('generator', 'resample', 'width', 'relax', 'frames', 'elevation', 'stations',
# 'order', 'carry_z', 'gate_relax', 'lift', 'tangents', 'gate_frames', 'validity')
# the noise profile runs on the relaxed loop, the stations are 3D-equally spaced
A plugin stations stage gets spacing_metric filled into its params when its Params
class has the field; otherwise "xyz" raises ValueError. Plugin stations stages
carry no centerline channels in this release: one may write anchors.z (it is then
treated as providing the altitude: carry.z, order.raw) and its own
anchors.<package>.<name> channels, and a generator that provides z raises
ValueError with one (stations 'acme.st' carries no centerline channels (plugin
carriers are deferred); generator 'acme.helix_loop' provides z).
gate_arclen measures plan-view arc length, and stays so: its arc table feeds the
gate elevation that produces gates.z, so a 3D metric there would be circular (and
whenever an altitude exists upstream, gate_arclen and the gate elevation are not
in the pipeline). The spacing is decided where the gates are placed, by the stations.
Elevation on gates¶
The gate pipeline runs the same registered elevation stages as tracks, built-ins and
plugins alike: the planner rewires every elevation slot of a gate pipeline from the
centerline channels to their gate counterparts, cl.xy -> gates.xy, cl.count ->
gates.count, cl.arclen2 -> gates.cum, cl.length2 -> gates.perim, cl.z ->
gates.z, and every plugin channel by prefix, cl.<package>.<name> ->
gates.<package>.<name> (seed is unchanged). An explicit StageRef.ports entry
overrides one port. An elevation slot placed before the anchors producer (the
centerline elevation of 3D station spacing) is not rewired.
The presets emit plain StageRef("elevation", name, params), so
spec.replace("elevation", StageRef("elevation", "acme.sinusoid_hills")) works on a
gate spec whose elevation is already non-flat (e.g. presets.gates(...,
elevation="noise")). A flat gate spec has no gate_arclen slot, so also insert the
arc tables, as the PlanError says: spec.replace("elevation", StageRef("elevation",
"acme.sinusoid_hills")).insert_before("elevation", StageRef("gate_arclen",
"default")), which builds the spec of presets.gates(elevation="acme.sinusoid_hills")
(or build with presets.gates(elevation=...) directly). A stage that sizes its buffers with b.layout.stride and
b.write("z", wp.float32, per="cl"), as the hills example of
Writing a Stage does, runs on gates unchanged: per is checked
against the port’s declared channel and the buffer sized by the wired one.
GateGenConfig.z_profile names any registered elevation stage, as
TrackGenConfig.z_profile does; a plugin gets its Params.from_legacy(config),
where config is the GateGenConfig: only the z_* fields are shared with
TrackGenConfig, so a from_legacy written for tracks should read any other field
with getattr(config, name, default) (one that reads a TrackGenConfig-only field
raises ValueError naming it). An unknown name raises at GateGenerator
construction: GateGenConfig.z_profile: unknown elevation 'bogus'; available: ['flat',
'noise', 'random_walk', 'uniform']. An elevation port left on a cl.* channel with no
gate counterpart raises PlanError.
On gates, elevation.uniform and elevation.random_walk draw one altitude per
gate: the gates are the knots, so their z_control_points is not
read (GateGenConfig has no such field; a value set through presets.gates(
elevation=("uniform", {...})) changes nothing). elevation.flat writes z_base
into every real gate (presets.gates(z_base=...), GateGenConfig.z_base); on a
track it launches nothing and lift.flat adds the altitude, so a non-zero z_base
on a track’s elevation.flat raises: ValueError from presets.track,
PlanError from the planner, before anything is allocated. The dynamic fields of the
elevation stages (z_min, z_max, z_max_step, z_noise_amplitude) are per
env on gates too, under the same stage id: presets.gates(randomize={"elevation.z_max":
(lo, hi)}) (Dynamic built-in knobs).
Gate fail bits¶
validity.gate_default runs one fused kernel, _finalize_validity_bars_k, which
writes its own 0/1 flag gates.valid and one bit per check into valid.fail, and
decides the crossing check per env (below). GateSequence.fail_mask is the final
valid.fail, and valid[e] == (fail_mask[e] == 0).
bit |
name |
set when |
|---|---|---|
0 |
|
|
1 |
|
a real gate’s position, tangent, forward, orientation, half_size, left or right is NaN or infinite |
2 |
|
a real gate’s 3D tangent has |
3 |
|
two real gates are closer in xy than |
4 |
|
two gate bars cross (checked in each env that has a real gate with a non-zero
|
5 |
|
a chord’s |
6-15 |
reserved |
TrackGen’s future built-in gate checks |
16-30 |
plugin checks |
assigned by |
track_gen.pipeline.GATE_FAIL_BITS is this table (a read-only mapping, next to
BUILTIN_FAIL_BITS). A gate validity plugin is a validity stage that reads the gate
channels it checks and rewrites valid.fail exactly as a track check does (see
A validity check); add it with presets.gates(...,
checks=("gate_default", "acme.max_height")). In a gate pipeline valid.fail must
start at validity.gate_default.
The crossing check runs per env. While it scans an env’s real gates for the other
checks, the kernel notes whether any has a non-zero half_size (a NaN counts as
non-zero); only then does it run the pairwise bar-crossing loop. A zero-width bar has
left == right == position, and a degenerate segment never crosses, so skipping the
loop for a point-gate env changes no decision: the valid and fail bits are those of a
kernel that checks every env. So a run-time bar width (gate_frames.gate_width drawn or set
from point gates, Per-env bar width and course size) is checked like a width built in, and the
default point-gate pipelines pay nothing (one compare per real gate in an existing loop).
check_crossing ("width" or "always") and validity.gate_default’s
gate_width are inert: both values select the per-env check. They are kept so
stored specs and dicts load, and go at 1.0 (Removed in 1.0, and what did not change). Posed gates (a
generator that provides half_size) are checked the same way under either value, and
the planner accepts gate_frames.posed with "width".
The gate commit¶
The gate Program ends with the commit of The commit, over the GateSequence:
Masked regeneration is exact.
generate(advance=True, mask=m)holds theGateSequencerows of every env withm[e] == 0before the stages and restores them after, bit for bit (position,tangent,forward,left,right,orientation,half_size,count,valid,fail_mask, every attribute;generationis written for the masked envs only).Invalid envs.
BatchSpec.on_invalidapplies:"keep"(default) leaves an invalid env’s computed gates andvalid = 0."poison"leaves it withcount = 0, every per-gate field NaN (orientationandhalf_sizeincluded), every attribute its padding andvalid = 0, keepingfail_mask;CheckpointSet.from_gatesandProgressTrackerthen take their empty-course path for that env. There is noGateGenConfig.on_invalid: usepresets.gates(..., on_invalid="poison")or edit the spec’s batch.Cost. As for tracks: no launch for an unmasked default generation, one for a derived
valid(a check aftervalidity.gate_default) or for poison, two for a masked generation, plus per exported attribute two for a masked generation and one for poison. The held copy costs 80 B per gate slot (plus each attribute’s size) and 12 B per env, allocated once.
Launches¶
A default gate generation is a short, fixed launch sequence: the effective seed, the
parameter latches (Parameter latch), the generator’s kernels, the order (its
gates.xy and a gates.count copy), the normalization for the generators that
prefer it, the sphere separation, the elevation, the lift, the tangents, the frames and
the validity kernel, then the generation write. The normalizing
generators (polar, checkpoint, voronoi) normalize after the order stage, out
of place: _normalize_extent_to_sized_k reads the order’s gates.xy and writes its
own version.
generation (CPU, launches per |
launches |
|---|---|
|
16 (2 latches: |
|
18 (4 latches: the two ring knobs, |
|
19 (4 latches) |
masked ( |
three more: the epoch advance, the hold and the commit |
|
one more (the commit) |
a plugin check after |
one more for the commit (the derived |
A non-flat elevation adds the gate_arclen launch and its latches (2 for uniform,
3 for random_walk / noise); gate_solve_iters=0 drops the separation launch.
Planner rules for gate pipelines¶
Besides the rules of build() and the planner, a gate pipeline raises PlanError
before allocating anything when:
the generator’s
capacity()does not fit the batch or the validity stage’smin_gates(Anchors generators), orcapacity()returns keys other thangates/min_gates,gates < 1, ormin_gatesoutside[0, gates];the order stage is one the generator does not accept (The ordering registry);
a provided attribute, or an
anchors.*attribute a gate generator or a plugin stations stage writes, is not read, or acarrystage (carry.attributesincluded) follows an order other thanorder.raw(Provided attributes, Gate plugin channels);the stations measure
spacing_metric="xyz"on a loop that carries no altitude (3D station spacing);a stage has both
cl.*andgates.*ports, a gate-domain stage uses acl.<package>.<name>plugin channel, or an elevation port after the anchors producer stays on acl.*channel without gate counterpart (Two point domains, Elevation on gates);stages disagree on
gate_widthorgate_radius(gate_framesholds the initial bar width,gate_relax.spheresthe separation target,validity.gate_defaulta copy of each): one initial value per pipeline (validity.gate_default’sgate_widthis inert, and the per-env width starts atgate_frames.default’s);a
GateSequencefield has no writer, orvalid.faildoes not start atvalidity.gate_default.
Legacy gate generators¶
A GateGeneratorSpec (the legacy gate plugin ABI, GeneratorSpec and the 0.1 plugin ABIs: generate(seeds, config, out,
scratch[, ctx]) writing ordered plan-view gates into a staging view, alloc_scratch,
max_gates, supported_orderings) keeps working, through an adapter slot that
writes gates.xy and gates.count directly. It orders its own gates as before, so no
order or normalize stage follows it, and it reads the user’s GateGenConfig, exactly
the fields it read before. A capturable=False spec is a HOST generator: it runs
eagerly as the Program’s first segment, ahead of a captured tail (see Capture classes
and segments); an accepts_ctx=False spec runs inside ctx.scope() (see
CUDA Graph — capture, replay, and buffer reuse).
It is deprecated: registering one stays silent, but binding one (GateGenerator,
Course, build, legacy.to_gate_pipeline_spec, the conformance kit) emits a
DeprecationWarning once per registry and name, attributed to the calling code:
GateGeneratorSpec 'acme.ring' (...) is deprecated: write a Stage with
kind='gate_generator' and output='anchors' (docs/contributing/writing-a-generator.rst);
legacy gate generators run through an adapter until 1.0. Port it with
Writing a Gate Generator.