Device ops¶
The building blocks the built-in generators are made of, public for plugin generators;
Writing a Generator shows how they fit together. The ops modules import only Warp
and each other, so importing track_gen.ops loads no generator module and no torch.
A device op takes a LaunchContext first and launches on its
stream; it never allocates, never synchronizes and never reads device data back (the
conventions are listed in the module docstring below). grid_cells, hash32_host,
check_array and the scratch classes’ alloc run on the host.
sample_grid_points and normalize_extent take a keyword-only size=: an [E]
float32 per-env size multiplier (the built-in generators’ size,
Size: the sizing rule), each value clamped at use to
SIZE_BOUNDS. size=None (the default) runs the
unchanged kernels, and a size-1 array gives the same bytes; normalize_extent refuses
size= with count= (ValueError). A plugin generator declares its own size
with SIZE_BOUNDS and passes b.param("size") to the op that applies its scale
(A per-env size).
Public device primitives for generator authors (track_gen.ops).
These are the building blocks the built-in generators are made of, so a plugin
generator can be assembled from the same documented pieces instead of private
track_gen._src helpers. Conventions shared by every op:
op(ctx, ...):ctxis atrack_gen.exec.LaunchContext(anything with.launchand.deviceworks; the device is canonicalised, so"cuda"matches arrays on the current CUDA device). Every launch goes throughctx.launch, so the work lands on the context’s stream and can be recorded into a caller’s CUDA graph. An op never callsctx.sync(), never reads device data back to the host and never allocates device memory: working buffers are passed in (e.g. anArcScratchallocated once inalloc_scratch).Flat per-env layout: env
eowns[e*stride, (e+1)*stride)of a flat array ofE*strideelements; per-env scalars are[E]arrays.Argument names:
srcis the input point array andstrideits points per env;numis an exact number of output points per env andout_stridean output row capacity (NaN-padded past the env’s count); outputs areout(pluscount_out/truncated_outfor per-env results).Eissrc.shape[0] // stride(which must divide exactly) for every op that takessrc, andseeds.shape[0]for the samplers; every other per-env array must then be exactly[E].Carried channels:
tables=(aResampleTables) makes a resample also record where each output point lies on its source loop, andresample_channels()carries per-point channels through those tables.Count semantics:
countis always keyword-only. Where it is given, only the firstcount[e]points of a row are real (a count above the stride is clamped to it); slots past it are written(nan, nan)by ops that produce points. Masks are the keyword-onlyactive(active[e] > 0selects the env).Validation is host-side and happens before any launch: data arrays must match their exact shape, dtype and
ctx.device; scratch arrays need the dtype, the device and at least the stated length. Mismatches raiseValueErrornaming the argument, except thatfillraisesTypeErrorfor an unsupported dtype andany_set/all_setfor an argument that is not anint32wp.array.
Device functions for your own kernels live in track_gen.ops.funcs; shared
numeric conventions in track_gen.ops.constants.
Resampling¶
- class ArcScratch[source]¶
Bases:
objectWorking buffers for the resample ops (wraps existing arrays; never allocates).
For
Eenvs and an input stridestride(points per env of the op’ssrc):real:[>= E*stride]vec2f– compacted real (finite) points (arc_resample()only).seg:[>= E*stride]float32– per-segment lengths.s:[>= E*(stride+1)]float32– cumulative arc length.count_real:[>= E]int32– real-point count per env (arc_resample()only; readable after the call).
resample_uniform()andresample_constant_spacing()only usesegands;real/count_realmay beNonefor them.Build one per generator in
alloc_scratchwithalloc(), or wrap buffers you already own (views/slices included) with the constructor.- real¶
- seg¶
- s¶
- count_real¶
- classmethod alloc(E, stride, device, *, compact=True)[source]¶
Allocate scratch for
Eenvs ofstrideinput points each (construction time only: this allocates).- Parameters:
E (int) – number of envs.
stride (int) – input points per env (the
strideof the op’ssrc).device – Warp device.
compact (bool) – also allocate
realandcount_real(needed byarc_resample());Falseleaves themNone.
- Return type:
- arc_resample(ctx, src, stride, num, scratch, out, count_out, *, active=None)[source]¶
NaN-aware arc-length resample of closed loops:
stridepoints in,numout.Per env, the points whose x AND y are finite are compacted in order (interior NaN rows are dropped too) into
Rreal points. WithR >= 2the env’s output row isnumpoints uniformly spaced by arc length along the CLOSED loop through them (point 0 is the first real point; the last segment wraps back to it) andcount_out[e] = num. WithR < 2the row is all NaN andcount_out[e] = 0.scratch.count_real[e]receivesR.With
active, only envs withactive[e] > 0are resampled; every other env keeps itsoutrow bit for bit and getscount_out[e] = scratch.count_real[e] = 0.Two launches (
Escan threads,E*numlookup threads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2finput loops (NaN rows allowed);Eissrc.shape[0] // stride.stride (int) – input points per env (
>= 1).num (int) – output points per env (
>= 1).scratch (ArcScratch) –
ArcScratchwithreal >= E*stride,seg >= E*stride,s >= E*(stride+1),count_real >= E.out (wp.array) –
[E*num]vec2foutput; must not alias a scratch buffer (it may aliassrc: the lookup reads only the scratch).count_out (wp.array) –
[E]int32output.active (wp.array | None) – optional
[E]int32env mask.
- Raises:
ValueError – a wrong type, shape, dtype or device,
stride/numbelow 1, oroutaliasing the scratch (host-side, before any launch).- Return type:
None
- resample_uniform(ctx, src, stride, scratch, out, *, count, tables=None)[source]¶
Count-aware arc-length-uniform resample of closed loops (
stridein and out).Env
e’s firstcount[e]points (which must be finite; no NaN compaction) are treated as a closed loop and resampled tocount[e]points uniformly spaced by arc length, starting at point 0. Slots>= count[e]of the row are written NaN;count[e] <= 0gives an all-NaN row and a count abovestrideis clamped tostride.Two launches (
Escan threads,E*stridelookup threads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2finput loops;Eissrc.shape[0] // stride.stride (int) – points per env of
srcandout(>= 1).scratch (ArcScratch) –
ArcScratchwithseg >= E*strideands >= E*(stride+1)(real/count_realunused, may beNone).out (wp.array) –
[E*stride]vec2foutput; must not aliassrcor the scratch.count (wp.array) –
[E]int32real points per env (keyword-only, required).tables (ResampleTables | None) – optional
ResampleTablesofE*stridepoints overn_src = E*stride, written too (the lookup’s table-writing variant: the sameoutbytes); carry channels through them withresample_channels().
- Raises:
ValueError – a wrong type, shape, dtype or device,
stride < 1, oroutaliasingsrcor the scratch (host-side, before any launch).- Return type:
None
- resample_constant_spacing(ctx, src, stride, spacing, out_stride, scratch, out, count_out, truncated_out, *, tables=None)[source]¶
Resample fully-real closed loops of
stridepoints to a constantspacing.Per env:
total= closed-loop perimeter;count = floor(total/spacing) + 1trimmed so(count-1)*spacing < total, then capped atout_stride. Pointksits at arc lengthk*spacingfrom point 0.truncated_out[e] = 1iff the uncapped count exceededout_stride(the loop’s tail was dropped). A loop whose perimeter is not> 0(zero or NaN) getscount_out[e] = 0andtruncated_out[e] = 0. Slots>= count_out[e]are written NaN.Two launches; no allocation, no host sync (the truncation flag stays on device).
- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2finput loops (every point real);Eissrc.shape[0] // stride.stride (int) – input points per env (
>= 1).spacing (float) – target spacing (finite,
> 0).out_stride (int) – output row capacity per env (
>= 1).scratch (ArcScratch) –
ArcScratchwithseg >= E*strideands >= E*(stride+1).out (wp.array) –
[E*out_stride]vec2foutput; must not aliassrcor the scratch.count_out (wp.array) –
[E]int32output.truncated_out (wp.array) –
[E]int32output (0/1).tables (ResampleTables | None) – optional
ResampleTablesofE*out_stridepoints overn_src = E*stride, written too (the lookup’s table-writing variant: the sameoutbytes); carry channels of the source loop through them withresample_channels().
- Raises:
ValueError – a wrong type, shape, dtype or device, a bad scalar, or
outaliasingsrcor the scratch (host-side, before any launch).- Return type:
None
Carrying per-point channels through a resample (tables= above): the
tables record where each output point lies on its source loop, and
resample_channels interpolates any channel of the source through them; see
Carried channels.
- class ResampleTables[source]¶
Bases:
objectWhere each output point of a resample lies on its source loop (wraps existing arrays; never allocates except
alloc()).For an output of
npoints over a source ofn_srcpoints:i0,i1:[n]int32– the GLOBAL source indices of the segment ends (e * stride + idxande * stride + (idx + 1) % count: the tables describe CLOSED loops);frac:[n]float32– the point’s clamped fraction along the segment (never NaN at a real point);n_src: the source channel length the indices point into.
A padding slot (past the env’s count, or an env that produced no point) holds the sentinel
i0 = i1 = -1,frac = NaN: every table-writing op writes it on every exit path, andresample_channels()writes the channel’s padding there (NaN for floats and every vector component,-1forint32).- i0¶
- i1¶
- frac¶
- n_src¶
- resample_channels(ctx, tables, pairs, interp)[source]¶
Carry per-point channels of a resample’s source loop to its output points.
tablescome from a table-writing resample (tables=ofresample_constant_spacing()/resample_uniform()). For every(src, dst)pair, witha = src[i0],b = src[i1]andf = fracat each output point,dstreceives, by policy:"linear"(float32, vec2f, vec3f):a + f * (b - a);"nearest"(any):a if f <= 0.5 else b(a tie goes to the segment start);"previous"(any):a(zero-order hold);"angle"(float32 radians in[-pi, pi)):wrap(a + f * wrap(b - a)), the short way round, output in[-pi, pi).
Padding slots (
i0 < 0) get NaN (floats, every vector component) or-1(int32).linearandangleblend across the closing segment of the loop. One launch per pair (len(tables.i0)threads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.tables (ResampleTables) – the
ResampleTables(noutput points,n_srcsource points).pairs (Sequence[tuple[wp.array, wp.array]]) –
(src, dst)arrays:src[n_src],dst[n]of the same dtype (float32, int32, vec2f or vec3f);dstmust not aliassrc.interp (Sequence[str]) – one policy per pair.
- Raises:
ValueError – a wrong type, shape or device,
pairsandinterpof different lengths, an unknown policy, ordstaliasingsrc(host-side, before any launch).TypeError – a
dstdtype other thansrc’s, or a policy the dtype does not admit.
- Return type:
None
Polygon geometry¶
- self_intersections(ctx, src, stride, out, *, count)[source]¶
Number of proper self-crossings of each closed polygon.
For env
ethe polygon is its firstN = min(count[e], stride)points (closed: the last edge returns to point 0).out[e]counts the pairs of edgesi < jwith circular distance> 1(adjacent edges never count) that cross properly: each segment’s endpoints must straddle the other’s line by more than1e-3 * len^2of that segment (a scale-relative collinearity tolerance that rejects float32 sign flips on near-collinear edges). Coordinates are read throughnan0()(NaN reads as 0).N <= 3gives 0. Deterministic (integer atomics).Two launches (zero
out;E*stridethreads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fpolygons;Eissrc.shape[0] // stride.stride (int) – points per env in
src(>= 1).out (wp.array) –
[E]int32crossing counts.count (wp.array) –
[E]int32points per env (keyword-only, required; clamped tostride).
- Raises:
ValueError – a wrong type, shape, dtype or device (host-side, before any launch).
- Return type:
None
- turning_number(ctx, src, stride, out, *, count)[source]¶
Signed total turning (radians) of each closed polygon.
Sum over the
N = min(count[e], stride)vertices of the exterior angle between consecutive edges, each wrapped into(-pi, pi]: about+2*pifor a simple counter-clockwise loop,-2*piclockwise. A zero-length edge has angle 0.N <= 0gives 0; a NaN point makes the env’s result NaN. One launch (Ethreads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fpolygons;Eissrc.shape[0] // stride.stride (int) – points per env in
src(>= 1).out (wp.array) –
[E]float32output.count (wp.array) –
[E]int32points per env (keyword-only, required; clamped tostride).
- Raises:
ValueError – a wrong type, shape, dtype or device.
- Return type:
None
- thickness(ctx, src, stride, band, out, *, count)[source]¶
Thickness of each closed polygon:
min(1 / max Menger curvature, 0.5 * min |p_i - p_j|)over vertex pairs with circular index distance> band[e].bandis the neighbour half-window excluded from the distance term, in points. The built-in validity gate usesband = max(1, round(2 * half_width / l0))for a track of half-widthhalf_widthwhose segments have mean lengthl0: pairs closer than a track width along the loop are skipped.Degenerate cases, with
N = min(count[e], stride): the curvature maximum is floored at1e-12and the distance term starts at1e30, so a polygon with no curvature and no vertex pair beyond the band (e.g.N < 3withband[e] >= 1) returns1e12. NaN points are not guarded: a row containing one gives an unspecified, generally finite, result (the runningmin/maxmostly skip NaN distances and curvatures instead of propagating them). One launch (Ethreads,O(N^2)work per env); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fpolygons;Eissrc.shape[0] // stride.stride (int) – points per env in
src(>= 1).band (wp.array) –
[E]int32excluded neighbour half-window per env.out (wp.array) –
[E]float32output.count (wp.array) –
[E]int32points per env (keyword-only, required; clamped tostride).
- Raises:
ValueError – a wrong type, shape, dtype or device.
- Return type:
None
- normalize_extent(ctx, src, stride, target_extent, *, count=None, size=None)[source]¶
Centre each env’s bounding box at the origin and scale it uniformly so its longest side equals
target_extent(in place).The extent is floored at
1e-8before dividing. Withcount=Noneallstridepoints of each env are used (rows are expected to be finite; a NaN coordinate stays NaN). Withcount, only the firstclamp(count[e], 0, stride)points are used and the remaining slots are written NaN. One launch (Ethreads); no allocation, no host sync.sizeis a per-env size multiplier of the target: enve’s longest side becomestarget_extent * size[e], each value clamped at use totrack_gen.ops.constants.SIZE_BOUNDS(by comparisons: a NaN passes). The size multiplies the finite scalar target before the division (no per-point arithmetic changes), so a size-1 array gives thesize=Nonebytes exactly, on CPU and CUDA;size=Noneruns the unchanged kernels.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fpoints, modified in place;Eissrc.shape[0] // stride.stride (int) – points per env (
>= 1).target_extent (float) – longest bbox side after scaling (finite), e.g.
ops.constants.TARGET_EXTENT * config.scale.count (wp.array | None) – optional
[E]int32real points per env (keyword-only).size (wp.array | None) – optional
[E]float32per-env size multiplier of the target (keyword-only), on the op’s device; not combinable withcount.
- Raises:
ValueError – a wrong type, shape, dtype or device,
src.shape[0]not a multiple ofstride, orcountandsizeboth given.- Return type:
None
- angle_sort(ctx, src, stride, keys, out, *, count, out_stride=None)[source]¶
Sort each env’s first points by angle around their centroid.
With
m = clamp(count[e], 0, min(stride, out_stride)), enve’s output row is its firstmsource points ordered ascending byatan2(x - cx, y - cy)(x FIRST: the built-in generators’ ordering), where(cx, cy)is the float64 centroid of thosempoints; the sort is stable. Slots[m, out_stride)are written NaN, socount[e] <= 0gives an all-NaN row. One launch (Ethreads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fpoints;Eissrc.shape[0] // stride.stride (int) – points per env in
src(>= 1).keys (wp.array) –
[>= E*out_stride]float32scratch.out (wp.array) –
[E*out_stride]vec2foutput.count (wp.array) –
[E]int32points to sort per env (keyword-only, required).out_stride (int | None) – points per env in
out(defaultstride).
- Raises:
ValueError – a wrong type, shape, dtype or device.
- Return type:
None
Random sampling¶
Each env’s stream is wp.rand_init(seeds[e] * salt + offset) in int32 wrap-around
arithmetic, the keying the built-in generators use. The (salt, offset) pairs the
built-ins already use are listed in the generator guide.
- grid_cells(min_point_distance)[source]¶
Grid for
sample_grid_points()from the built-inmin_point_distanceknob (host only).Returns
(num_cells, cell_size)=(int(1 / (2*d)), 2*d): the built-in generators’ grid ofnum_cells x num_cellscells of side2*d. The name follows the config knob; it is not a distance guarantee (seesample_grid_points()).- Raises:
ValueError –
min_point_distanceis not a finite float in(0, 0.5](the rangeTrackGenConfigaccepts; above 0.5 the grid would be empty).- Parameters:
min_point_distance (float)
- Return type:
- sample_counts(ctx, seeds, salt, offset, lo, hi, out)[source]¶
One uniformly drawn integer in
[lo, hi]per env.out[e] = min(lo + int(randf * (hi - lo + 1)), hi)with onerandffromwp.rand_init(seeds[e] * salt + offset)(int32 wrap-around). Deterministic per(seed, salt, offset). One launch (Ethreads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.seeds (wp.array) –
[E]int32per-env seeds;Eisseeds.shape[0].salt (int) – int32 stream multiplier.
offset (int) – int32 stream offset.
lo (int) – smallest value (int32).
hi (int) – largest value (int32,
>= lo).out (wp.array) –
[E]int32output.
- Raises:
ValueError – a wrong type, shape, dtype or device,
lo > hior a value out of int32 range (host-side, before any launch).- Return type:
None
- sample_grid_points(ctx, seeds, salt, offset, num, num_cells, cell_size, scale, used, out, *, size=None)[source]¶
numjittered grid points per env, on distinct cells where possible.Per env, from
wp.rand_init(seeds[e] * salt + offset)(int32 wrap-around): for each pointc < numdraw a cellmin(int(randf * num_cells**2), num_cells**2 - 1), redrawing (up to 8 times) while it equals the cell of an earlier point of the env; then drawnx, ny = randf - 0.5and write((cell % num_cells) * cell_size + nx, (cell // num_cells) * cell_size + ny) * scale.usedis filled with -1 first and receives the chosen cells.Guarantees: distinct cells are best effort (a point keeps a duplicate cell after 8 redraws), and the jitter is an absolute
[-0.5, 0.5)(not scaled bycell_size), so there is no minimum distance between points. Each coordinate lies in[-0.5, (num_cells - 1) * cell_size + 0.5) * scale. Usegrid_cells()for the built-in grid. Two launches (fill,Ethreads); no allocation, no host sync.sizeis a per-env size multiplier applied LAST: enve’s points are((x * cell_size + nx) * scale) * size[e], each value clamped at use totrack_gen.ops.constants.SIZE_BOUNDS(by comparisons: a NaN passes).size=Noneruns the unchanged kernels (same launches, same bytes); a size-1 array gives thesize=Nonebytes exactly, on CPU and CUDA: the product feeds no add / sub, so it creates no FMA contraction, andx * 1.0fis exact. A plugin applying its ownsizeshould keep that rule (multiply where no add or subtract consumes the product).- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.seeds (wp.array) –
[E]int32per-env seeds;Eisseeds.shape[0].salt (int) – int32 stream multiplier.
offset (int) – int32 stream offset.
num (int) – points per env (
>= 1).num_cells (int) – grid cells per side (
>= 1).cell_size (float) – cell side length.
scale –
floatapplied to every env, or[E]float32per-env scale (bit-identical to the scalar form for a uniform array).used (wp.array) –
[>= E*num]int32scratch (chosen cell per point).out (wp.array) –
[E*num]vec2foutput.size (wp.array | None) – optional
[E]float32per-env size multiplier (keyword-only), on the op’s device.
- Raises:
ValueError – a wrong type, shape, dtype or device, or a bad scalar (host-side, before any launch).
- Return type:
None
Curves¶
- closed_catmull_rom(ctx, src, stride, per_seg, out, *, count=None, endpoint=False)[source]¶
Sample the closed uniform Catmull-Rom spline (tension 0.5) through each env’s controls.
out[e, i*per_seg + s] = catmull_rom(p[i-1], p[i], p[i+1], p[i+2]; u)with indices modKandu = s / (per_seg - endpoint)(see the module docstring).count=None:K = stridefor every env. Withcount:K = min(count[e], stride); segmentsi >= K(all of them whenK < 2) are written NaN, which the NaN-awarearc_resample()drops. One launch (E*stride*per_segthreads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fcontrols;Eissrc.shape[0] // stride(must divide exactly).stride (int) – controls per env (
>= 1).per_seg (int) – samples per segment (
>= 1;>= 2withendpoint=True).out (wp.array) –
[E*stride*per_seg]vec2foutput.count (wp.array | None) – optional
[E]int32controls per env (keyword-only).endpoint (bool) – include
u = 1in each segment.
- Raises:
ValueError – a wrong type, shape, dtype or device, or a bad scalar.
- Return type:
None
- closed_polyline(ctx, src, stride, per_seg, out, *, count=None, endpoint=False, active=None)[source]¶
Sample the closed straight polyline through each env’s controls.
out[e, i*per_seg + s] = (1-u)*p[i] + u*p[i+1](index modK) withu = s / (per_seg - endpoint).count=None:K = stride; withcount,K = min(count[e], stride)and segmentsi >= K(all whenK < 2) are written NaN. Same layout asclosed_catmull_rom(). Withactive, only envs withactive[e] > 0are written and every other row is left untouched: the drop-in “straight” fallback for the envs whose smooth curve self-crossed. One launch (E*stride*per_segthreads); no allocation, no host sync.- Parameters:
ctx (LaunchContext) – the
LaunchContextto launch on.src (wp.array) –
[E*stride]vec2fcontrols;Eissrc.shape[0] // stride.stride (int) – controls per env (
>= 1).per_seg (int) – samples per segment (
>= 1;>= 2withendpoint=True).out (wp.array) –
[E*stride*per_seg]vec2foutput.count (wp.array | None) – optional
[E]int32controls per env (keyword-only).endpoint (bool) – include
u = 1in each segment.active (wp.array | None) – optional
[E]int32env mask (keyword-only).
- Raises:
ValueError – a wrong type, shape, dtype or device, or a bad scalar.
- Return type:
None
Fills, flags and validation¶
- fill(ctx, arr, value)[source]¶
Set every element of the 1-D array
arrtovalueonctx’s stream.One launch of
arr.shape[0]threads; no allocation, no host sync.- Parameters:
ctx – the
LaunchContextto launch on.arr (array) –
[n](1-D)wp.arrayofint32,uint32,float32,vec2f,vec3forquatfonctx.device.value – a scalar; for
vec2f/vec3fa vector of that type, a sequence of its components, or one scalar broadcast to every component; forquatfawp.quatfor a sequence of its four components (no broadcast).
- Raises:
TypeError – unsupported dtype.
ValueError –
arris not awp.array, not 1-D or not onctx.device, or a scalarvaluefor aquatfarray.
- Return type:
None
- any_set(ctx, mask, flag_out)[source]¶
flag_out[0] = 1if anymask[e] != 0, else0(onctx’s stream).Two launches, both capturable and allocation-free: zero
flag_out, then one thread per element raises it with an integeratomic_max(order-independent, so the result is deterministic).- Parameters:
ctx – the
LaunchContextto launch on.mask (array) – 1-D
int32array (e.g. an[E]per-env mask).flag_out (array) –
[1]int32array on the same device.
- Raises:
TypeError –
maskorflag_outis not anint32wp.array.ValueError – a wrong shape, or the arrays and
ctxon different devices (host-side, before any launch).
- Return type:
None
- all_set(ctx, mask, flag_out)[source]¶
flag_out[0] = 1if everymask[e] != 0(an empty mask: 1), else0(onctx’s stream).Two launches, both capturable and allocation-free: set
flag_outto 1, then one thread per element lowers it with an integeratomic_minwheremask[e] == 0(order-independent, so the result is deterministic).- Parameters:
ctx – the
LaunchContextto launch on.mask (array) – 1-D
int32array (e.g. an[E]per-env mask).flag_out (array) –
[1]int32array on the same device.
- Raises:
TypeError –
maskorflag_outis not anint32wp.array.ValueError – a wrong shape, or the arrays and
ctxon different devices (host-side, before any launch).
- Return type:
None
- check_array(name, arr, shape, dtype, device)[source]¶
Validate a
wp.array’s type, shape, dtype, and device.Raises
ValueErrornamingnameon the first mismatch. Shared body behind the family’s_validate_inputs/_validate_position/_check_arrwrappers;shapeis the expected shape tuple.deviceis canonicalised before the comparison, so"cuda"matches an array oncuda:0when that is the current CUDA device.- Parameters:
name (str) – argument name used in the error message.
arr – the value to check.
shape (tuple) – the exact expected shape, e.g.
(E * N,).dtype – the expected Warp dtype (compared by identity, e.g.
wp.vec2f).device – the expected device (a
wp.Deviceor device string, e.g."cuda","cuda:0"or"cpu").
- Raises:
ValueError –
arris not awp.array, or its shape, dtype or device differs from the expected one.- Return type:
None
Hashing¶
Device functions: track_gen.ops.funcs¶
@wp.func building blocks for plugin kernels.
Each is a wp.Function: call it from inside your own @wp.kernel (or another
@wp.func), e.g. from track_gen.ops import funcs then funcs.catmull_rom(...)
in the kernel body. They are the device functions the built-in generators use: the
same operations in the same order. On CPU a plugin kernel that composes them reproduces
the built-in arithmetic bit for bit. On CUDA (the module option fuse_fp, default
True; wp.set_module_options({"fuse_fp": ...}))
the compiler may contract a multiply and an add into one FMA, and which pairs it
contracts depends on the code around the inlined call, so a different surrounding
kernel can round differently in the last bit.
The three crossing predicates differ on purpose: segments_cross() is the strict
sign test used by gate validity; the polygon crossing counter
(track_gen.ops.self_intersections()) adds a scale-relative collinearity
tolerance on top of ccw().
- ccw(ox: float, oy: float, px: float, py: float, qx: float, qy: float) float[source]¶
Orientation of
(o, p, q):(qy - oy)*(px - ox) - (py - oy)*(qx - ox), twice the signed triangle area; positive wheno -> p -> qturns counter-clockwise.
- safe_normalize2(v: vec2f) vec2f[source]¶
v / max(|v|, 1e-8): a unit vector with a length floor. An all-NaN vector (a pruned corner) stays all-NaN. A NaN length is floored to1e-8like a short one, so a vector with one NaN component keeps the other component, scaled by1e8.
- catmull_rom(p0: vec2f, p1: vec2f, p2: vec2f, p3: vec2f, u: float) vec2f[source]¶
Uniform Catmull-Rom (tension 0.5) segment from
p1top2atuin[0, 1]:u = 0givesp1,u = 1givesp2. The function behindtrack_gen.ops.closed_catmull_rom().
- segments_cross(a: vec2f, b: vec2f, c: vec2f, d: vec2f) int[source]¶
1if segmentsabandcdcross properly, else0. A strict sign test: touching endpoints and collinear overlap are not crossings. It is the gate validity predicate, not the onetrack_gen.ops.self_intersections()uses.
- hash32(x: uint32) uint32[source]
The same function as
track_gen.ops.hash32().
These are the operations the built-in kernels perform, in the same order. A plugin
kernel built from them matches the built-ins bit for bit on CPU. On CUDA, Warp compiles
with FMA contraction on by default (the module option fuse_fp, default True;
wp.set_module_options({"fuse_fp": ...})), and which multiply-add pairs become FMAs
depends on the code around the inlined call, so a differently structured kernel can
differ in the last bit.
Constants¶
Shared numeric conventions of the built-in generators.
TARGET_EXTENT is the longest bounding-box side (per unit config.scale) that the
polar, voronoi and checkpoint generators (and their gate generators) centre
and scale their output to. bezier and hull land near it by construction of their
sampling grid (a longest side of about 1.0 to 1.8 at scale 1, not centred), and
repulsive is larger (about 2.2). The downstream defaults (half_width,
spacing, N_max, relaxation bands) are tuned for coordinates of that size, so a
plugin generator that wants those defaults to behave the same should end with
ops.normalize_extent(ctx, out_centerline, N, ops.constants.TARGET_EXTENT * config.scale).
SIZE_BOUNDS is the declared range of the per-env size multiplier every built-in
generator except external (and normalize.extent) declares, and the range
ops.sample_grid_points(size=) / ops.normalize_extent(size=) clamp each env’s
value to (by comparisons: a NaN passes). It is the convention value a plugin generator
imports for its own size field:
import dataclasses
from track_gen import plugin
from track_gen.ops.constants import SIZE_BOUNDS
@dataclasses.dataclass(frozen=True, kw_only=True)
class MyParams(plugin.Params):
size: float = plugin.dynamic(1.0, lo=SIZE_BOUNDS[0], hi=SIZE_BOUNDS[1])
A legality range: the useful range is set by n_max (truncation), num_points, the
absolute half-width and the utilities’ capacities (the sizing rule in the tuning guide).
- TARGET_EXTENT = 1.44¶
Longest bbox side of a built-in centerline at
config.scale == 1(the bezier baseline’s typical extent).
- SIZE_BOUNDS = (0.1, 10.0)¶
the declared bounds of every built-in
sizefield and the clamp of thesize=arrays ofops.sample_grid_points/ops.normalize_extent.- Type:
(lo, hi)of the per-envsizemultiplier