Docs / Nodes
Nodes
Nodes#
Every built-in operation a policy can call, and what it does. For the language itself see the Language Reference.
The catalogue#
Every built-in operation lives in the prelude: no imports (the few that come in libraries are listed at the end). Each is a pure function of its inputs. Literal arguments are frozen constants; an argument that depends on a tensor is trainable.
Notation: name(arg: Type = default, …) -> Out. ? marks an optional argument that may simply be left out. T is any quantity type. Points means Cloud or Section, and "same" means the output has the input's type. All values are in canonical units (metres, radians).
Math and core#
abs sqrt exp log sin cos tan asin acos atan atan2 min max sum mean dot cross norm normalize lenclamp(x, lo, hi) where(cond, a, b)argmin(v) argmax(v) # of a Vector: the label (or index) of its smallest / largest elementlen(v) -> Scalar # the number of elements of a Vectorrange(n) -> Vector[Scalar] # 0, 1, …, n − 1 (n a whole number ≥ 0, may come from data)warn(message) error(message) # statements: a run warning (W0710) / stop the entry's run (R0020)meta(v, at) -> Record # the metadata of the element labelled `at` (or at position `at`)point(x, y, z) -> Point direction(x, y, z) -> Directioncross(a, b) -> Vector[Scalar] # of two Directions: NOT normalised, magnitude sin θ; normalize(cross(a, b)) is a Directionnormalize(v) -> Direction # of a Point, a Direction or a Vector of 3 quantitiesp.x p.y p.z # a Point's coordinates (Length), or a Direction's (Scalar)pi up forward right # constants: up = +z, forward = +y (frame forward), right = +x (frame right)Typing follows Types and dimensions. The unary functions, atan2, clamp, where, and min and max of two values apply per element to vectors of numbers; sum, mean, argmin, argmax, and min and max of one vector reduce a vector of quantities (see Vectors and map). These functions take positional arguments only (clamp(x, lo, hi), not clamp(x, lo: a, hi: b)). range(n) walks a vector by position (map i in range(len(r) - 1) { distance(r[i], r[i + 1]) }, see range and len); len and range carry no gradient. warn and error are statements; if and fold are language constructs, not nodes (an if or a fold draws as a group). meta(v, at) reads one element's metadata: meta(brackets, best).number (fields checked at compile time when the collection's schema is known: E0211; else R0015 / R0016 at run time). Runs of pure arithmetic, math functions, member access, method calls (path.at(s), curve(u)) and ° are fused into a single node, labelled with its source text, so graphs stay readable.
Search spaces#
one_of(values…: T) -> SearchSpace[T] # one or more constant values of the same dimensionbetween(lo: T, hi: T) -> SearchSpace[T] # a continuous range; inf allowed; bounds may be computed per scanNodes that accept a search space optimise over it on every scan. A bare array is always data.
Geometry#
plane(origin: Point, normal: Direction) -> Planeplane3(p1: Point, p2: Point, p3: Point) -> Planeplane_through_line(a: Point, b: Point, normal: Direction) -> Planefit_plane(points: Cloud) -> Planeslice(cloud: Cloud, plane: Plane, thickness: Length = 4 cm) -> Sectionmidline(cloud: Cloud, heading: Direction = forward, resolution: Length = 1 cm, smoothing: Scalar = 0.15) -> Pathproject_onto(path: Path, point: Point) -> Lengthgeodesic_point(section: Section, distance: Length, side: "left" | "right", from: Length = 0 m) -> Pointsurface_normal(cloud: Cloud, near: Point, radius: Length = 2 cm) -> Directionmean_normal(cloud: Cloud, from: Point, to: Point, radius: Length = 2 cm, samples: Scalar = 16) -> Directiongravity_align(cloud: Cloud, gravity: Direction) -> Cloudto_frame(x, frame: Frame) -> same # x: Cloud | Section | Point | Direction | Plane | Path | Framerotate(x, angle: Angle, axis: Direction = up, about: Point = point(0 m, 0 m, 0 m)) -> sametranslate(x, by: Point) -> sameplace(section: Section, du: Length = 0 m, dv: Length = 0 m, angle: Angle = 0°) -> Sectionalign(template: Section, to: Section, du: Bool = false, dv: Bool = true, angle: Bool = false, about: (Length, Length) = (0 m, 0 m), angle_max: Angle = 15°) -> (placed: Section, mse: Length^2)center(x: Points, axis: "x" | "y" | "z" | "u" | "v" | "all" = "all") -> sameflip(x: Points, axis: "x" | "y" | "z") -> samehorizontal(d: Direction) -> Directiondistance(a: Point, b: Point) -> Lengthhorizontal_distance(a: Point, b: Point) -> Lengthnearest_distance(from: Points, to: Points) -> Vector[Length]chord(path: Path, s0: Length, s1: Length) -> Lengtharc_length(path: Path, s0: Length, s1: Length) -> Lengthpath.at(s) -> Point path.tangent(s) -> Direction path.height -> Curve # s ↦ zpath.reorigin(s0) -> Path path.length -> Length path.frame -> Frameplane.origin -> Point plane.normal -> Direction plane.u -> Direction plane.v -> Direction| Node | What it does |
|---|---|
plane | A plane through origin with in-plane axes v (the in-plane direction closest to up) and u (perpendicular, toward +x). |
plane3 | The plane through three points (origin p1); fails with R0101 if they're collinear. |
plane_through_line | The plane containing the line a→b and normal; u runs along a→b, v along normal, origin at a. |
fit_plane | The weighted least-squares plane through the points: origin at the weighted centroid, normal along the direction of least spread, u and v as for plane. A normal within about 84° of vertical points up (a floor or a shelf); a steeper plane (a wall) gets the normal that faces the frame origin, where the scan started. Fails with R0132 on fewer than 3 weighted points, or collinear ones. |
slice | Keeps the points within thickness / 2 of the plane and projects them into its (u, v) frame. |
midline | The ridge line along the top of the cloud (in each slice across it, the highest point near the centre), smoothed, as a Path by arc length s. It refines the frame by rotating about z so +y follows the path, and picks which end is which, and so the direction of increasing s, by the direction closest to heading. |
project_onto | Arc length s of the path point closest to point. |
geodesic_point | Walks the section's upper contour from u = from a distance distance toward side, and returns that 3-D point. |
surface_normal | The surface normal near a point (from the neighbourhood's principal axes), pointing away from the cloud's centroid. |
mean_normal | The mean surface normal along the segment from from to to. |
gravity_align | Rotates the cloud so gravity points down (−z). |
to_frame, rotate, translate | Re-express or move a cloud, section, point, direction, plane, path or frame (a direction is rotated, never translated). On a section they move its plane, not its (u, v) coordinates. |
place | Moves a section within its plane: rotates its (u, v) by angle about (0, 0), counter-clockwise (from +u toward +v), then shifts them by (du, dv). The plane stays the same, so the points move in 3-D too. Differentiable in the coordinates, du, dv and angle. |
align | Moves template within its plane to where it best fits the section to: the placement with the lowest mean squared distance from the template's points to the nearest points of to. Choose what may move: the shift along v (dv, on by default), the shift along u (du) and a rotation about about of at most angle_max (angle); the rest stay put. It starts with the template lowered onto to from above until it first touches (with angle, from three tilts: none and ±angle_max, keeping the best), then slides it to the nearest best fit; that is a local best, so start the template near the right place sideways. Points of to with weight 0 are left out. Returns the moved template (placed) and its mean squared distance (mse, a Length^2), differentiable in both sections' coordinates. Fails with R0133 when to or template has no points. |
center | Moves the weighted centroid to 0, on all axes or one. |
flip | Rotates 180° about an axis (that coordinate kept, the others negated); a reflection on a section. |
horizontal | Projects a direction onto the ground plane (for "held vertical" conventions). |
distance, horizontal_distance, chord, arc_length | Straight-line, ground-plane, chord and along-the-path distances, for choosing a distance convention. |
nearest_distance | For each point of from, in order, the distance to the nearest point of to: two sections (in their (u, v) coordinates) or two clouds (in 3-D). Points of to with weight 0 are left out; the weights of from don't change the distances. Exact, with bounded memory on large point sets. Differentiable in both point sets' coordinates. Fails with R0133 when to has no points. mean(nearest_distance(a, b)^2) is the mean squared distance from a to b. |
axis = midline(cloud)sec = slice(cloud, plane(axis.at(0 in), normal: axis.tangent(0 in)), thickness: 1 cm)q = geodesic_point(sec, distance: 3 in, side: "left")floor = cloud |> largest_plane(threshold: 1 cm) |> fit_planealign compares a section with a template: it finds the template's best placement and how far off it still is. nearest_distance and place let you score placements of your own. The shape_match library scores a whole collection of templates.
fit = align(template, to: profile, du: true) # lowered onto the profile, then slid into placeerr = fit.mse # mean squared distance, a Length^2placed, mse = align(template, to: profile, angle: true, angle_max: 5°)A plane's members .origin, .normal, .u and .v are its origin, unit normal and in-plane axes; gradients flow through them to whatever built the plane.
Clean#
keep(points: Points, where: Predicate) -> samediscard(points: Points, where: Predicate) -> sameclip(points: Points, where: Predicate) -> samebump(points: Points, where: Predicate, direction: "down" | "up") -> sametop(coord, fraction: Scalar, at_least: Length = 0 m) -> Predicatebottom(coord, fraction: Scalar, at_least: Length = 0 m) -> Predicatenear(plane: Plane, within: Length) -> Predicateweight(points: Points, by: coord, prior: Gaussian[Length]) -> sameremove_outliers(points: Points, sigma: Scalar) -> samelargest_plane(points: Points, threshold: Length, iterations: Scalar = 100, seed: Scalar = 0) -> samelargest_cluster(points: Points, eps: Length, min_samples: Scalar = 10) -> samesymmetrize(points: Points, axis: "x" = "x") -> same| Node | What it does |
|---|---|
keep | Keeps the points that satisfy the predicate. |
discard | Drops the points that satisfy the predicate. |
clip | Clamps one coordinate into its bound; never removes points. |
bump | Moves the points inside each interval of the predicate to that interval's lower ("down") or upper ("up") edge. |
top, bottom | Predicates for the band of size max(fraction × range, at_least) at the top or bottom of a coordinate. |
near | A predicate: the points within within of the plane (by distance along its normal). Combines with and, or, not and coordinate comparisons. Clouds only (E0244). |
weight | Multiplies each point's weight by the prior's density at its coordinate by (.z, …), scaled so the peak is 1: points near the prior's mean keep their weight, far ones fade without being removed. Weighted nodes downstream (largest_plane's consensus, fit_plane, centroids) then treat the prior as soft evidence. A Gaussian over a length only. |
remove_outliers | Drops points whose distance from the centroid exceeds the mean distance by more than sigma standard deviations of those distances. |
largest_plane | RANSAC: keeps the points within threshold of the best-supported plane (a line on a section). Isolates the dominant surface; a far point that lies on it is kept. |
largest_cluster | DBSCAN: keeps the largest density-connected cluster (a core point has at least min_samples points, itself included, within eps). Isolates the dominant blob, curved or not. |
symmetrize | Unions the points with their mirror image across the body's midplane (each copy at half weight). |
Predicates are written with per-point coordinates: see Predicates.
cloud = largest_cluster(cloud, eps: 5 mm)sec = keep(sec, -0.3 m <= .u <= 0.3 m and .v > 2 cm)sec = keep(sec, top(.v, 0.6, at_least: 1 ft))sec = clip(sec, .v <= cut)sec = bump(sec, -1 cm <= .u < 0 cm or 0 cm < .u <= 1 cm, direction: "down")cloud = discard(cloud, near(floor, within: 2 cm))cloud = weight(cloud, by: .z, prior: gaussian(80 cm, sd: 5 cm))Curves#
lowess(section: Section, span: Scalar = 2/3, iterations: Scalar = 3, degree: Scalar = 1) -> Curvespline(section: Section, control_points: Scalar = 8, degree: Scalar = 3) -> Curvefourier(section: Section, terms: Scalar, frequencies: Vector[Scalar] = []) -> Record{coefficients: Vector[Length], frequencies: Vector[Scalar], residual: Length}trapezoid(section: Section, widths: SearchSpace[Length] = between(0 m, inf m), angles: SearchSpace[Angle] = between(1°, 179°), lock_x: Length?, lock_y: Length?, lock_angle: Angle?, open: Bool = true) -> (width: Length, angle: Angle)level_at_knee(curve: Curve) -> Curvequadratic_vertex(curve: Curve, near: T, window: T) -> Tlevel_set_midpoint(curve: Curve, kind: "min" | "max", epsilon: <quantity>, over: SearchSpace?) -> Tflat_extremum(curve: Curve, kind: "min" | "max", slope_penalty: T, over: SearchSpace?) -> T # T: the curve's input, e.g. Lengthcurve(u) curve.slope(u) curve.curvature(u) curve.arcselect(a, b, weightleft = 0.5)curve.arcmid(a, b) curve.domain -> (lo: T, hi: T)| Node | What it does |
|---|---|
lowess | Cleveland LOWESS: a robust, locally weighted fit of v = f(u). Flat outside its data. |
spline | A NURBS curve fit. Not differentiable (it can't sit on an adam path). |
fourier | A truncated Fourier series of the section's contour. |
trapezoid | Fits an open (or closed) trapezoid to a section, searching widths and angles; each lock_* pins one placement coordinate: lock_x and lock_y where its first (lower-left, before rotation) corner sits, lock_angle its rotation. Returns the width of its top edge (always in metres) and the angle between its two sides. |
level_at_knee | Rotates the curve about its point of greatest curvature so the slope there is zero (the smaller rotation). |
quadratic_vertex | The vertex of a least-squares quadratic fitted within ±window/2 of near. |
level_set_midpoint | The midpoint of the interval where the curve is within epsilon of its extremum. |
flat_extremum | The extremum of f(u) penalised by slope, so it settles in flat regions. |
Curve methods: curve(u) evaluates, slope and curvature (κ = f″ / (1 + f′²)^(3/2)) differentiate, arcselect(a, b, weightleft) returns the u at a weighted position along the contour between a and b (1 → a, 0 → b), arcmid(a, b) is its midpoint, and domain is the fitted range.
curve = lowess(sec, span: 0.5)knee = level_at_knee(curve)width, angle = trapezoid(sec, widths: one_of(60 cm, 75 cm, 90 cm), angles: between(60°, 120°), lock_angle: 0°)Extrema#
argmin(curve: Curve, over: SearchSpace[T] = between(-inf, inf), noise: <quantity>?) -> T, or Gaussian[T] with noiseargmax(curve: Curve, over: SearchSpace[T] = between(-inf, inf), noise: <quantity>?) -> T, or Gaussian[T] with noise| Node | What it does |
|---|---|
argmin, argmax | The location of the curve's minimum or maximum over a range. With noise: σ_h (the noise on the curve's height), the result is a Gaussian whose spread is √(2σ_h / κ), κ being the curvature at the extremum: a sharp dip is well localised, a shallow one is not. On a curve the node shows the curve with the chosen point marked (curve_plot). |
s_low = argmin(edge.height, over: between(20 cm, 80 cm), noise: 0.5 mm) # Gaussian[Length]: where a shelf sags mostDistributions#
gaussian(mean: T, sd: T) -> Gaussian[T]gaussian(mean: Vector[T], cov: Matrix[T^2]) -> Gaussian[Vector[T]]noise(sd: T) -> Gaussian[T]mixture(weights: Vector[Scalar], components: (D…) | Vector[D]) -> Mixture[T]grid(support: Vector[T], logp: Vector[Scalar]) -> Grid[T]samples(values: Vector[T], weights: Vector[Scalar]?) -> Samples[T]categorical(probs: Vector[Scalar]?, logits: Vector[Scalar]?, labels: Vector[Label]?) -> Categoricalprob(d, x) -> Scalar log_prob(d, y) -> Scalar log_pdf(d, y) -> Scalar # log_pdf is another name for log_probquantile(d, p) interval(d, level = 0.9) -> (lo: T, hi: T) cdf(d, x)d.mean d.var d.sd d.mode # a Categorical has only .mode (a Label)moment_match(d: Gaussian | Mixture | Grid | Samples) -> Gaussian[T]discretize(d, support: Vector[T]) -> Grid[T]sample(d, n: Scalar, seed: Scalar = 0) -> Samples[T]to_grid(c: Categorical, support: Vector[T]) -> Grid[T]to_categorical(g: Grid[T]) -> Categoricalmargin(c: Categorical) -> Scalarexpect(c: Categorical, v: Vector[T], of: .field) -> the field's typelikelihood(observed: T, noise: T) -> Likelihood[T]bayes_update(prior: Gaussian | Mixture | Grid | Samples, likelihood: Likelihood[T] | Grid[T]) -> (posterior, evidence: Scalar)| Node | What it does |
|---|---|
gaussian, noise | A new Gaussian: a scalar; independent elements when mean or sd is a vector; a correlated vector with cov; or, given a Gaussian mean, that Gaussian plus independent noise. noise(sd) is zero-mean. Each is a fresh, independent noise source. |
mixture, grid, samples | A weighted mixture, a distribution on a grid (support strictly increasing), a set of samples. A grid used as the likelihood in bayes_update contributes its unnormalised exp(logp), so the evidence keeps its scale. |
categorical | From exactly one of probs or logits; labels default to the input vector's labels. |
prob, log_prob, log_pdf | Mass (categorical) or density (continuous, in canonical SI units). y may be an interval {lo, hi}, or a measurement {mean, sd} from a Gaussian column, whose noise is integrated: a Gaussian N(μ, σ²) gives N(mean; μ, σ² + sd²), a mixture does so per component, a grid's density is convolved with N(0, sd²), samples widen their kernel by sd². y is one value: over a vector of values, map (map y in ys { log_prob(d, y) } is a Vector[Scalar]). |
quantile, interval, cdf, .mean, .var, .sd, .mode | Summaries. |
moment_match, discretize, to_grid, to_categorical | Explicit conversions; each drops correlation with other values. |
margin | p(best) − p(second): a confidence for a categorical (1 for a single label). Differentiable. |
expect | Σ p(label) × field: the probability-weighted mean of a numeric metadata field (of: .number) of v's elements, over the Categorical's labels, which must all be labels of v (R0016). In the field's units; differentiable in the Categorical's probabilities. A field the elements lack (or some of them lack) is E0245, a label or true/false field, or one of mixed types, E0246 (at run time R0015). The most probable label's field is meta(v, c.mode).number. |
sample | n Monte Carlo draws, seeded per entry. Correlation survives: with the same seed, a noise source gets the same draws wherever it's sampled, so samples of correlated Gaussians stay correlated. |
likelihood | ℓ(x) = N(observed; x, noise²): "I observed this, with this much noise". |
bayes_update | Prior × likelihood. Gaussian × Gaussian is the closed-form precision-weighted update; mixtures update per component; grids multiply on the grid; a Gaussian or mixture prior against a grid likelihood is discretised onto the grid; a samples prior is reweighted. Returns posterior and evidence (destructure them, or take one: bayes_update(…).evidence). |
# two noisy thermometers in one greenhouse: probe A as the prior, probe B as the observationt_fused, evidence = bayes_update(gaussian(mean: t_a, sd: 0.5), likelihood(t_b, noise: 0.3))band = interval(t_fused, 0.9)Features#
along_path_grid(path: Path, from: Length, to: Length, step: Length) -> Vector[Length]width_at_drop(cloud: Cloud, path: Path, s: Length, drop: Length, thickness: Length = 1 cm) -> Lengthlateral_curvature(cloud: Cloud, path: Path, s: Length, offset: Length, thickness: Length = 1 cm) -> 1/Lengthconcavity_depth(cloud: Cloud, path: Path, s: Length, offset: Length, thickness: Length = 1 cm) -> Lengthhinge_likelihood(support: Vector[Length], values: Vector[T], noise: T) -> Grid[Length]correlate(a: Vector[T], b: Vector[T]) -> Scalarstandardize(v: Vector[T]) -> Vector[Scalar]| Node | What it does |
|---|---|
along_path_grid | A grid of arc-length positions along a path. |
width_at_drop | Width of the transverse slice at s, drop below its top. |
lateral_curvature | Curvature of the transverse profile offset off the midline. |
concavity_depth | Depth of the concavity beside the path, between the path and offset to the side. |
hinge_likelihood | A single change point: the likelihood, for each support point, of a piecewise-linear hinge there. Needs no labels. |
correlate | Normalised cross-correlation (template matching). |
standardize | (v − mean) / sd, dimensionless. |
Feature nodes take one position s. To get one value per position of a grid, map over the grid:
s_grid = along_path_grid(top, from: 5 cm, to: 50 cm, step: 5 mm)width = standardize(map s in s_grid { width_at_drop(cloud, top, s, drop: 2 cm) })Fit#
These nodes own trainable tensors. The three marked with a fitter can fit their tensors themselves, in closed form or by a convex solve, when the policy declares optimiser delegate (see Training and evaluation).
linear(features: (…) | Vector[F] | F?, coef: <quantity> | Vector?, noise: T, intercept: T?, offset: T | Gaussian[T] = 0, method: "ml" | "ols" = "ml", ridge: Scalar | "auto" = 0, fit: "delegate" | "optimiser" = "delegate") -> Gaussian[T]stack(components: (D…) | Vector[D], weights: Vector[Scalar], method: "stacking" | "bic" = "stacking", inner_k: Scalar = 5, fit: "delegate" | "optimiser" = "delegate") -> Mixture[T]conditional_logit(support: Vector[T], features: (Vector[Scalar]…), coef: Vector[Scalar], offset: Vector[Scalar] = 0, ridge: Scalar = 0, fit: "delegate" | "optimiser" = "delegate") -> Grid[T]mlp(x: Vector[Scalar] | (Scalar…), hidden: (Scalar…) = (64,), out: Scalar, activation: "relu" | "tanh" | "gelu" | "identity" = "relu", weights: Weights?, init: "xavier" | "kaiming" | "normal" | "zeros" = "xavier", in_dim: Scalar?) -> Vector[Scalar]pointnet(cloud: Cloud, out: Scalar, hidden: Scalar = 64, npoint: Scalar = 64, nsample: Scalar = 16, weights: Weights?, init: "xavier" | "kaiming" | "normal" | "zeros" = "xavier") -> Vector[Scalar]| Node | What it does | Fitter |
|---|---|---|
linear | offset + intercept + Σ coefᵢ × featureᵢ + noise(σ), as a Gaussian. Several features take a vector coef; a single feature takes a single coefficient (usually a Scalar tensor); leave both out for an intercept-only model. method: "ml" (the default) fits by maximum likelihood including each entry's own feature and offset uncertainty, so σ measures only what the features don't explain, not sensor noise. "ols" is plain least squares with σ² = RSS / (n − p). ridge: "auto" picks the ridge strength by inner cross-validation. | yes (nll, mse): coefficient table ± SE, σ, R², residuals |
stack | A weighted mixture of predictive distributions. "stacking" picks weights that maximise out-of-fold log predictive density, using an inner K-fold; "bic" weights by BIC. | yes (nll): weights, per-fold log predictive density |
conditional_logit | p(s = sⱼ) ∝ exp(offsetⱼ + coef · featuresⱼ) over a grid: a softmax over positions with, typically, a prior as the offset. Convex. | yes (nll): convergence, coefficients |
mlp | A multilayer perceptron. Without weights: it creates a trainable <binding>.weights tensor. in_dim (the length of x) is inferred from x; pass it only when it can't be. | no (the global optimiser trains it) |
pointnet | A PointNet++ encoder of the whole cloud into out features. Creates <binding>.weights. | no (the global optimiser trains it) |
Pass fit: "optimiser" to linear, stack or conditional_logit to make the global optimiser train its tensors instead of its fitter.
length = linear(raw.length_raw, coef: k, intercept: a, offset: raw.length_raw, noise: sigma_3) # k: a Scalar tensorheight = linear(intercept: bias, offset: tip_height, noise: sigma_1) # intercept onlyh = mlp(feats, hidden: (64, 64), out: 1, init: "xavier") # creates h.weightsMetrics#
For metrics {} blocks (any expression is allowed there; these are conveniences):
agreement(pred: Categorical | Label, truth: Label, within: Scalar = 0, by: .field | (.f, …, .field)?, among: Vector[T]?) -> Scalarabs_error(pred: T | D, truth: T) -> Tsq_error(pred: T | D, truth: T) -> T^2log_predictive_density(pred: D, truth: T) -> Scalarcoverage(pred: D, truth: T, level: Scalar = 0.9) -> Scalar| Node | What it does |
|---|---|
agreement | 1 if the predicted label (a categorical's mode) matches, else 0. Labels made of a family and a number (L4, L5, L6) are ordered within their family, and within: 1 also accepts the neighbouring label of the same family. With by: and among: (always together), the order comes from the collection's element metadata instead: by: .number orders every element by that numeric field; by: (.family, .number) compares only elements whose leading fields are equal, ordered by the last. A step is one distinct value of the field (equal values are 0 steps apart). |
abs_error, sq_error | Error of the prediction (a distribution's mean). Against a Gaussian truth ({mean, sd}), its mean. |
log_predictive_density | Log density of the truth under the prediction (a Gaussian truth's noise is integrated, as in nll). |
coverage | 1 if the truth (a Gaussian truth's mean) lies inside the prediction's central level interval. |
When every loss term is nll, training summaries also report BIC and AIC automatically.
Libraries#
First-party libraries ship with Seabiscuit and are versioned with it. Import one by its bare name and call its members through the alias: import library geom_util as g, then g.midpoint(a, b) (see import library).
# geom_util — fns, inlined at each call like your own fnshalf(x: Length) -> Length # x / 2mean2(a: Length, b: Length) -> Length # (a + b) / 2midpoint(a: Point, b: Point) -> Point # the point halfway between a and bdistance(a: Point, b: Point) -> Length # the straight-line distance between a and b# num_util — operations, one node eachlerp(a: Length, b: Length, t: Scalar = 0.5) -> Length # a + (b − a)·t| Library | Member | What it does |
|---|---|---|
geom_util | half, mean2 | Half a length; the mean of two lengths. |
geom_util | midpoint, distance | The midpoint of two points, coordinate by coordinate; the Euclidean distance between them. |
num_util | lerp | Linear interpolation between two lengths: t: 0 gives a, t: 1 gives b. |
foldlib#
Importing foldlib turns on fold in your policy, and gives a few running reductions written with fold each (one value per element, with the vector's labels; an empty vector gives an empty vector):
# foldlib 1.0.0 — fns, inlined at each callcumsum(v: Vector[T]) -> Vector[T] # element i is v[0] + … + v[i]running_max(v: Vector[T]) -> Vector[T] # element i is max(v[0], …, v[i])running_min(v: Vector[T]) -> Vector[T] # element i is min(v[0], …, v[i])# route/profile.sb — distance travelled along a route, point by pointpolicy(route: Vector[Point]) -> (along: Vector[Length], highest: Vector[Length])import library foldlib as fllegs = map i in range(len(route) - 1) { distance(route[i], route[i + 1]) }along = fl.cumsum(legs)highest = fl.running_max(map p in route { p.z })An infinite element makes the rest of a cumsum undefined (NaN).
shape_match#
shape_match says which of a collection of template shapes a 2-D profile is: which bracket a slice of a scanned part matches, which extrusion a cut end shows, which lid a box has. The templates are your data, a labelled Vector[Section] tensor (Collections of shapes) that you digitise, upload and import (import tensor shelf/brackets as brackets); none ship with Seabiscuit.
A template's error at a placement is the mean, over the template's points, of the squared distance to the nearest profile point (profile points with weight 0 are ignored), a Length^2. Its score is the error at the placement align finds: the template lowered onto the profile from above, then slid to its best fit. The best template has the lowest score. Every member takes align's placement options and passes them on: by default only the shift along v is solved; add du: true to let templates shift sideways and angle: true to let them tilt (about about, at most angle_max).
# shape_match 2.0.0 — fns, inlined at each call; … = du: Bool = false, dv: Bool = true,# angle: Bool = false, about: (Length, Length) = (0 m, 0 m), angle_max: Angle = 15°mse(profile: Section, template: Section, …) -> Length^2scores(profile: Section, templates: Vector[Section], …) -> Vector[Length^2]best(profile: Section, templates: Vector[Section], …) -> Labelsoft(profile: Section, templates: Vector[Section], temp: Length^2 = 1 mm^2, …) -> Categoricalplaced(profile: Section, template: Section, …) -> Section| Member | What it does |
|---|---|
mse | The score of one template: align(template, to: profile, …).mse. |
scores | The score of every template, labelled and ordered like templates. |
best | The label of the template with the lowest score. |
soft | A softmin over the templates: a Categorical with logits −score / temp. A smaller temp is sharper; make it a Length^2 tensor and training calibrates it. |
placed | template moved to its best placement, for display. |
Each score is one align. Measured on a 2,000-point profile against 20 templates, a scores call takes about 0.04 s with 200-point templates and 0.17 s with 2,000-point ones (shift along v only); with du: true, about 0.11 s and 0.8 s; with du: true, angle: true, about 0.4 s and 8 s. Thin out dense templates, and solve only what can actually move. best, soft and scores each compute every score, so when a policy has a soft result, read its .mode rather than also calling best.
# shelf/bracket.sb — which bracket profile does a scanned part have?policy(cloud: Cloud) -> (bracket: Categorical, sure: Scalar, fitted: Section)import library shape_match as smimport tensor shelf/brackets as brackets # Vector[Section]: one labelled profile per bracketoptimiser cma_es(sigma0: 0.3)loss { bracket: nll }metrics { exact: agreement(bracket, y.bracket), near: agreement(bracket, y.bracket, within: 1) }part = cloud |> discard(.z < 5 mm) |> largest_cluster(eps: 1 cm)profile = slice(part, plane(point(0 m, 0 m, 0 m), normal: forward), thickness: 1 cm)temp = tensor(1 mm^2, lower: 0 mm^2, scale: 1 mm^2, name: "temp")bracket = sm.soft(profile, brackets, temp: temp, du: true) # trained: calibrates the probabilitiessure = margin(bracket) # p(best) − p(second)fitted = sm.placed(profile, brackets[bracket.mode], du: true) # the winning template, placedtempis the only trained value, a job forcma_es. Its fitted value sets how quickly the probability falls off as a template's score rises.- Without training,
sm.best(profile, brackets, du: true)returns the winning label directly. sm.soft(profile, brackets, temp: temp, du: true, angle: true, angle_max: 5°)also lets each template tilt by up to 5°.
Writing your own libraries, in Seabiscuit or in Python, is coming; documentation will follow.
