moonmcl

    MoonBit Motion Control Library: contracts for models, controllers, estimators, trajectories and planners, with dense linear algebra, PID and LQR/MPC control, Kalman and particle filtering, jerk-limited trajectories, sampling-based planning over user spaces, and robot kinematics.

    moonbit
    mcl
    motion-control
    robotics
    control
    controller
    pid
    anti-windup
    feedforward
    cascade-control
    lqr
    dare
    ilqr
    mpc
    model-predictive-control
    quadratic-programming
    path-following
    pure-pursuit
    stanley
    computed-torque
    impedance-control
    control-barrier-function
    safety-filter
    kalman-filter
    extended-kalman-filter
    unscented-kalman-filter
    particle-filter
    state-estimation
    state-observer
    rts-smoother
    complementary-filter
    alpha-beta-gamma
    trajectory
    trajectory-generation
    s-curve
    jerk
    trapezoidal
    polynomial
    spline
    b-spline
    time-synchronisation
    path-planning
    motion-planning
    rrt
    rrt-star
    rrt-connect
    informed-rrt-star
    prm
    dwa
    dynamic-window
    artificial-potential-field
    dubins
    reeds-shepp
    grid
    dijkstra
    astar
    a-star
    kinematics
    forward-kinematics
    inverse-kinematics
    jacobian
    damped-least-squares
    differential-drive
    ackermann
    bicycle-model
    mecanum
    omnidirectional
    delta-robot
    scara
    gantry
    linear-algebra
    matrix
    cholesky
    qr-decomposition
    eigenvalues
    state-space
    discretisation
    robot
    embedded
    mctl
    Download zip
    Version
    0.0.1
    License
    Apache-2.0
    Last updated
    21 hours ago
    Downloads
    9

    #MoonMCL — MoonBit Motion Control Library

    Motion control for robots, machines and simulations, written in MoonBit and laid out contract first: a plant, a controller, an estimator, a trajectory, a mechanism and a planner are each a trait, and every algorithm in the library is written against those traits rather than against a concrete type. Implement one of them for your own type and the filters, the combinators, the synchroniser and the sampling planners take it — without a change to this repository.

    import {
    "moonbitstack/moonmcl" @mctl,
    }

    // A jerk-limited move over twelve metres.
    let profile = @mctl.scurve(distance=12.0, v_max=2.0, a_max=1.0, j_max=4.0)
    let end = profile.sample(profile.duration())
    assert_eq((end.position - 12.0).abs() < 1.0e-6, true)
    assert_eq(end.velocity.abs() < 1.0e-6, true)

    Install it and run the examples:

    moon add moonbitstack/moonmcl moon run src/examples/01-pid

    #The contracts

    TraitWhat a caller suppliesWho uses it
    Modelstep(x, u, dt), observe(x), and optionally the two Jacobiansextended, unscented and particle filters; iterative LQR
    Controllerupdate(reference, measurement, dt), resetcascade, saturation and rate limiting
    Estimatorpredict(u, dt), correct(z), state, covarianceanything that wants a filtered state
    Trajectoryduration, sample(t)multi-axis synchronisation
    Kinematicsforward(q), inverse(x, seed?), optionally jacobian(q)computed torque, Jacobian-based inverse kinematics
    Spacesample(rng), distance(a, b), interpolate(a, b, t)every sampling planner, Dubins, the grid
    Validityvalid(x), optionally motion(space, a, b)every sampling planner
    Plannerplan(start, goal, budget)—

    The Jacobians are default methods: a plant that gives only step and observe gets a central difference with the published step jacobian_step, and one that knows its analytic Jacobian overrides the same method. Validity::motion is a default too, walking the space's own interpolation in motion_samples steps.

    import {
    "moonbitstack/moonmcl" @mctl,
    }

    let plant = @mctl.Dynamics::new(
    f=fn (x, u, dt) {
    ignore(u)
    @mctl.Vec::of([x.get(0) + dt])
    },
    h=fn (x) { @mctl.Vec::of([x.get(0)]) },
    )
    let filter = @mctl.ExtendedKalman::new(
    model=plant,
    process=@mctl.Mat::of([[0.01]]),
    measurement=@mctl.Mat::of([[0.1]]),
    state=@mctl.Vec::of([0.0]),
    covariance=@mctl.Mat::of([[1.0]]),
    )
    filter.predict(@mctl.Vec::of([0.0]), 1.0)
    filter.correct(@mctl.Vec::of([2.0]))
    assert_eq(filter.state().len(), 1)

    #Packages

    Every package can be imported on its own ("moonbitstack/moonmcl/control", "moonbitstack/moonmcl/planning", …) or through the root facade under any alias. The examples use mctl.

    #linear

    Dense vectors and matrices in double precision, and the factorisations the rest of the library leans on. A Vec is a column of Double; a Mat is row-major with rows, cols and data.

    ItemWhat it does
    Vec::zeros, Vec::of, Vec::linspace, Vec::filledconstruction
    Vec::add, sub, hadamard, scale, axpy, dot, norm, normalizedarithmetic
    Mat::zeros, Mat::identity, Mat::of, Mat::diagonal, Mat::from_fixedconstruction
    Mat::mul, Mat::apply, Mat::apply_transposed, Mat::transpose, Mat::augment, Mat::stack, Mat::blockproducts and assembly
    solve, solve_vec, inv, det, lu, qr, cholesky, cholesky_solve, eigh, norm, expmfactorisations, named as in numpy.linalg

    lu keeps the factorisation so several right-hand sides share it; eigh is the symmetric eigendecomposition by Jacobi rotations with ascending eigenvalues; expm is scaling and squaring, which is what turns a continuous state matrix into a discrete one.

    Errors are one type, Refused, with a variant per kind of failure: Dimension, Empty, Singular(at~), NotPositiveDefinite(at~), Gain(got~), Range(got~, low~, high~), Exceeded(limit~, got~) and Unreachable.

    #contract

    The eight traits, plus Sample (position, velocity, acceleration, jerk), Budget (iterations, optional wall clock), StateSpace, Dynamics, Discretize and Integrator.

    StateSpace is a linear plant: continuous(a~, b~, c~, d~) or discrete(a~, b~, c~, d~, dt~), and discretize(dt~, discretize~ = Zoh) turns the first into the second by zero-order hold — the input held over the period, which is what a digital controller actually does. Dynamics is a nonlinear plant from two functions and an integrator (Euler, Midpoint, Rk4, the preset). Both implement Model.

    #control

    ItemWhat it does
    Pid, PidConfig, pid_gains, pid_configconditional and back-calculation anti-windup, setpoint weighting, derivative on the measurement, a first-order filter on it
    Feedforward, FeedforwardRunnervelocity and acceleration feedforward, on a sample or on a reference sequence
    Cascade, Saturating, RateLimitedcombinators over &Controller
    dare, Riccati, lqr, Lqrthe discrete Riccati equation and the infinite-horizon gain
    integral_lqr, IntegralLqrthe gain with an integrator on the output error
    ilqr, IlqrConfigiterative LQR over any Model
    Mpc, MpcConfig, box_qp, QpConfigreceding-horizon control with box limits on the input and on its rate, and the quadratic program underneath
    Pursuit, Stanleypure pursuit and Stanley on a polyline
    ComputedTorque, Impedancemodel-based effort
    cbf_filter, CbfConfigthe closest safe input to a nominal one

    import {
    "moonbitstack/moonmcl" @mctl,
    }

    // Saturation, rate limiting and a cascade all take any controller.
    let raw = @mctl.Pid::new(config=@mctl.pid_gains(kp=10.0))
    let limited = @mctl.Saturating::new(inner=raw, output={ low: -1.0, high: 1.0 })
    let command = limited.update(@mctl.Vec::of([1.0]), @mctl.Vec::of([0.0]), 0.1)
    assert_eq(command.get(0), 1.0)

    The quadratic program is solved by ADMM: the horizon of a predictive controller is re-solved every period with the same Hessian, so a method whose iteration is one triangular solve and one clamp keeps the period short and has no active set to warm start.

    #estimation

    ItemWhat it does
    Kalmanthe linear filter for a discrete StateSpace, Joseph form
    ExtendedKalman, UnscentedKalman, UnscentedConfigthe same recursion over any Model, linearised or with sigma points
    ParticleFiltera cloud of hypotheses, weighted by the innovation and resampled when the effective size halves
    Complementary, AlphaBetaGammathe two single-axis trackers
    rtsthe Rauch-Tung-Striebel backward pass

    #trajectory

    ItemWhat it does
    trapezoidaccelerate, cruise, decelerate, in closed form
    scurvejerk-limited and time optimal from any boundary velocity and acceleration
    cubic, quinticpolynomials from their boundary states
    cubic_splinenatural cubic through time-stamped knots
    b_splineclamped B-spline, evaluated by de Boor
    Syncedseveral axes on one clock

    import {
    "moonbitstack/moonmcl" @mctl,
    }

    let fast = @mctl.trapezoid(distance=1.0, v_max=1.0, a_max=1.0)
    let far = @mctl.trapezoid(distance=4.0, v_max=1.0, a_max=1.0)
    let set = @mctl.Synced::new(axes=[fast, far])
    let ends = set.sample_all(set.duration())
    assert_eq((ends[0].position - 1.0).abs() < 1.0e-9, true)

    #planning

    ItemWhat it does
    Rrt, RrtStar, RrtConnect, InformedRrtStarsampling planners over any Space and Validity
    Prma roadmap built once and queried many times
    Dubins, DubinsPath, DubinsWordthe forward-only car: six words, as a Space
    ReedsShepp, ReedsSheppPath, ReedsSheppSegment, ReedsSheppWordthe reversing car: forty-eight words, as a Space
    DynamicWindow, PotentialFieldthe two local planners
    grid_path, GridSearch, GridSpacethe grid search of the graph package, and the grid as a Space

    Every planner carries a seeded generator, so a run replays exactly; Budget bounds it in iterations and optionally in wall-clock seconds.

    Both car models implement Space, so a sampling planner plans in the car's own geometry: every edge of the tree is a drivable arc or line, and a reversing car can back out of a corner a forward-only one cannot.

    import {
    "moonbitstack/moonmcl" @mctl,
    }

    let car = @mctl.ReedsShepp::new(turning_radius=1.0)
    let start = @mctl.Vec::of([0.0, 0.0, 0.0])
    let goal = @mctl.Vec::of([-1.0, 0.4, 0.0])
    let path = car.path(start~, goal~)
    let end = path.at(path.length)
    assert_eq((end.get(0) - -1.0).abs() < 1.0e-9, true)
    assert_eq((end.get(1) - 0.4).abs() < 1.0e-9, true)
    assert_eq(path.length < @mctl.Dubins::new(turning_radius=1.0).path(start~, goal~).length, true)

    #kinematics

    ItemWhat it does
    Differentialtwo wheels to a body twist, and the exact arc for the pose
    Bicycle, Ackermannsteering bases, with the inner and outer angles
    Mecanum, Omnidirectionalwheels that push sideways; the inverse is the least-squares one
    PlanarArman n-link chain with an analytic Jacobian and a damped-least-squares inverse
    Scara, Delta, Gantrythe three structures

    #Units and conventions

    SI throughout: metres, seconds, radians. A plane pose is (x, y, theta) with theta counter-clockwise. A wheeled base's joint space is the wheel command and its task space the body twist, because that is what a drive commands; integrating the twist into a pose is a separate, explicit step (Differential::advance).

    #Consistency

    Each family with a mature reference replays it: the tables under scripts/vectors/ are produced by the scripts beside them, and moon test reads them without the reference installed. Where a comparison needed a tolerance wider than the last digits of a double, or where the two sides do not agree at all, it is listed here.

    • The jerk-limited profile matches the reference durations to nine digits on every replayed move, with the start and end accelerations folded into the first and last ramp. A move that would have to reverse mid-way — from 3 m/s to 1 m/s over half a metre, say — is refused: this profile is written for motion along one direction, and the reference solves the reversing case with a different structure. The refusal is part of the contract and is tested as such. A boundary acceleration that points against the move is carried into the first ramp, so a profile can begin with a short stretch of negative velocity — a few centimetres per second across the swept domain — before the move itself starts; that is the state it was handed rather than a reversal of the move.
    • Reeds-Shepp matches the reference length, the number of cusps and the pose along the path. Two words of exactly equal length meet at the same place, so which of the two is reported can differ from the reference; both are shortest. A second, separately written implementation agrees on the length to 1e-9 over twenty-four poses of its own.
    • Dubins matches the reference length and the pose along the path for all six words, and a second implementation agrees on the length to 1e-9 over twenty-four poses of its own.
    • The unscented filter is compared at 1e-7 rather than 1e-9: the reference takes the matrix square root by Cholesky and this library takes it by symmetric eigen decomposition, so the sigma points differ in the last digits. The linear and extended filters agree to 1e-9. A second implementation, which spreads its sigma points at a different scale as well, is compared at 5e-3: at a spread of 1e-3 the second-order term that separates the two vanishes, and at a spread of 1 it does not, so the loose tolerance is the one that compares the recursion rather than the tuning. A wrong weight is off by far more than that, which the mutation tests show. The linear filter and the backward pass are checked against the same second implementation at 1e-9, where nothing separates the two.
    • The Riccati solution is compared at 1e-8: the reference solves the equation with a Schur decomposition and this library iterates the recursion to a fixed point.
    • Sampling plans are compared by cost and feasibility, not point by point: two planners drawing from different generators find different routes of the same order of magnitude, and a route is judged by whether the map allows it.
    • Arm kinematics are compared on the forward map exactly; the inverse is judged by where the solved angles put the tip, because a chain with more than two links has more than one set of angles for the same tip.
    • Dense linear algebra matches the reference solve, inverse, determinant, Cholesky factor, symmetric eigenvalues and matrix exponential to 1e-9. A QR factorisation is unique only up to the sign of each column, so both sides orient the columns the same way before comparing, at 1e-10.
    • The default numerical Jacobians are compared at 1e-8. The reference differentiates by complex step, which is accurate to machine precision; the contracts use a central difference with a step of 1e-6, so the difference is the truncation and cancellation error of that step.
    • Discretisation matches the reference for all three rules to 1e-12 — the zero-order hold, the trapezoidal rule and forward Euler — on plants from a double integrator to a three-state chain and random matrices whose spectrum sits in the left half plane. The hold is the one that matters downstream, and it is the matrix exponential of the augmented plant, so the comparison covers both the augmentation and expm.
    • The backward smoothing pass is replayed against the reference on its own forward record: the filtered means, covariances and transitions are the ones the reference held, and only the smoothed output is compared, at 1e-9. That separates a smoother that disagrees from a filter that does. A second implementation, given the same record, agrees at 1e-9 as well.

    #Licence

    Apache-2.0.

    Ackermann

    An Ackermann steering base: the inner and outer front wheels turn by different angles about a common centre.

    AlphaBetaGamma

    An alpha-beta-gamma tracker: position, velocity and acceleration of one axis from a position measurement.

    The three gains trade noise against lag the way a Kalman filter does with a fixed model: alpha corrects the position, beta the velocity and gamma the acceleration. The preset is the critically damped set for a steady-state model of a constant acceleration.

    AntiWindup

    How a PID stops its integrator while the output is saturated.

    BSpline

    A clamped uniform B-spline over a control polygon.

    The curve is pulled towards the control points without passing through them, which is what makes it the profile for a path that only has to be smooth. The first and last control points are interpolated, so it starts and ends where the polygon does.

    Bicycle

    A bicycle model: the joint space is (speed, steering angle) and the task space the body twist (vx, vy, omega).

    Budget

    How long a planner may search.

    Cascade

    Controllers in series: the output of one stage is the reference of the next.

    A cascade is how a current loop inside a velocity loop inside a position loop is written. The measurement vector carries one entry per stage, so stage i is handed measurement[i].

    CbfConfig

    The knobs of the control-barrier-function filter.

    iterations bounds the alternating projection. The filter is exact when it converges, and the bound is there so a pathological gradient cannot stall a control period.

    Complementary

    A complementary filter for one angle: the gyro is trusted for the fast motion, the measurement for the slow drift.

    alpha is the fraction of the integrated rate kept each period. At 0.98 with a period of a hundredth of a second the time constant is about half a second, which is the usual balance for a hobby-grade inertial sensor.

    ComputedTorque

    A computed-torque law: the inertia and gravity of the mechanism plus a proportional-derivative correction in joint space.

    inertia and gravity are supplied by the caller because the mass matrix of a mechanism belongs to the mechanism, not to the controller; a model that has them can pass them straight in, and a model that does not can pass an identity and get plain joint-space feedback.

    Controller

    A feedback law: the input to apply for a reference and a measurement.

    The combinators in control (cascade, saturation, rate limiting) take any &Controller, so a controller written outside this library composes the same way the ones here do.

    CubicSpline

    A natural cubic spline through time-stamped positions, continuous in velocity and acceleration, with zero curvature at both ends.

    Delta

    A delta robot: three parallel arms from a fixed base to a moving platform.

    The joint space is the three upper-arm angles measured from horizontal and the task space the platform centre (x, y, z) under the base.

    Differential

    A two-wheel differential drive.

    track is the distance between the wheels and wheel_radius the radius of each. The joint space is (omega_left, omega_right) in radians per second and the task space (v, omega) in metres and radians per second.

    Discretize

    Which rule turns a continuous state matrix into a discrete one.

    Dubins

    A Dubins car.

    DubinsPath

    The Dubins path between two poses: the word and the length of each of its three pieces.

    DubinsWord

    One of the six Dubins words: three pieces, each a left arc, a right arc or a straight line.

    DynamicWindow

    The dynamic window approach: sample the velocities the drives can reach in one period, roll each one forward, and keep the best by heading, clearance and speed.

    Dynamics

    A nonlinear plant given as a derivative and an output map, integrated with a named rule.

    The derivative is f(x, u, dt), taking the period as well because a plant may be defined for a fixed control period; the rule treats it as the vector field at x.

    Eigh

    The eigenvalues and eigenvectors of a symmetric matrix.

    values is ascending and vectors has the matching eigenvector in its matching column, so a * v[:, i] = values[i] * v[:, i].

    Estimator

    A state estimator: propagate on the input, then fold in a measurement.

    The two steps are separate because a sensor does not always deliver on every control period; a caller that has no measurement that cycle calls predict alone.

    ExtendedKalman

    An extended Kalman filter: the linear recursion with the Jacobians the model supplies, whether analytically or by the contract's default differences.

    Feedforward

    A velocity- and acceleration-feedforward term on the reference trajectory.

    A feedforward does not close a loop: it removes the lag the feedback would otherwise have to work off by itself. On a servo that follows a planned trajectory the velocity gain dominates and the acceleration gain covers the inertia.

    FeedforwardRunner

    A feedforward driven by the reference sequence: the velocity and the acceleration are differenced from the references it is handed.

    Gantry

    A gantry: two motors drive one gantry axis from either end, so the pair sets the position and the tilt of the beam.

    The joint space is (x, y_left, y_right, z) and the task space (x, y, z, tilt), where y is the middle of the beam and tilt the angle the difference between its ends makes.

    Grid

    A board of width × height cells, each free or blocked, numbered y * width + x.

    GridSearch

    Which search a grid query runs.

    GridSpace

    A grid used as a planning space, so a sampling planner can search a lattice.

    IlqrConfig

    The knobs of the iterative LQR.

    state_weight and input_weight are the diagonal weights of the quadratic cost: every state deviation costs state_weight / 2 per unit squared and every input input_weight / 2. regularisation is added to the input Hessian so a direction the model cannot move stays solvable.

    Impedance

    An impedance controller: a mass-spring-damper between the tool and the target, with the contact force measured rather than modelled.

    The rendered position follows the admittance m x'' = f_ext - d x' - k (x - x_d) and the effort returned is the spring-damper wrench at that position, so a tool pushed off its target yields and then returns.

    InformedRrtStar

    An RRT* that only samples where a path shorter than the best one could still run: the ellipse whose foci are the start and the goal.

    IntegralLqr

    An LQR with an integral of the output error appended to the state.

    The extra state is what removes a constant disturbance's offset: the proportional part of an LQR leaves a standing error when the plant is pushed by a bias, and the integrator integrates that error away. The integral acts on the output c x, not on the whole state, because integrating a state the input cannot reach independently would leave the augmented plant unstabilisable.

    Integrator

    How Dynamics integrates its derivative.

    Kalman

    A linear Kalman filter for a discrete state-space plant.

    Kinematics

    A mechanism: joint values to a task-space pose, and back.

    Both spaces are plain Vecs so that one contract covers a two-wheel base and a six-axis arm; a model that has typed joint values offers them alongside.

    Lqr

    A linear-quadratic regulator: the infinite-horizon gain for a discrete plant and quadratic weights.

    The control law is u = -k (x - reference), so a regulator that drives the plant to a setpoint is the same object as the one that drives it to zero.

    An LU factorisation with partial pivoting, perm * A = lower * upper, where lower is unit lower triangular and upper is upper triangular.

    sign is the parity of the row swaps, so det = sign * product(upper). The factorisation is kept so that several right-hand sides can reuse it, which is what the estimators do inside a filter loop.

    Mat

    A dense matrix of Double in row-major order.

    Element (i, j) lives at data[i * cols + j]. The arithmetic methods return new matrices; set is the only writer.

    Mecanum

    A four-wheel mecanum base. The joint space is the four wheel speeds in the order front left, front right, rear left, rear right, and the task space the body twist (vx, vy, omega).

    Model

    A plant: how the state moves under an input, and what a sensor sees.

    step and observe are the model. step_jacobian and observe_jacobian have default implementations built on them by central differences, so a plant that gives only the two functions works with the extended Kalman filter, the unscented Kalman filter and the particle filter as it is.

    Mpc

    A receding-horizon controller for a discrete linear plant with box limits on the input and on its rate of change.

    The prediction is condensed into one quadratic program over the stacked inputs, which is what makes the period predictable: the matrices are built and factorised once per call, and the solve is a fixed number of ADMM iterations rather than a search whose length depends on the active set.

    MpcConfig

    The knobs of the model-predictive controller.

    penalty is the ADMM step of the quadratic program, input the box on the command and rate the box on its change per second. iterations bounds the solve, so a period can never take longer than the budget says.

    Omnidirectional

    An omnidirectional base with three or more driven wheels on a circle.

    The wheels are mounted tangentially, so each one drives along theta and the map from twist to wheel speeds is one row per wheel. With three wheels it inverts exactly; with more the inverse is the least-squares one, which spreads the effort over the wheels that can help.

    Output

    An inclusive band [low, high] for one scalar.

    ParticleFilter

    A particle filter over any plant that implements the model contract.

    The particles are drawn around the prior at construction and then carried by the model itself, so the filter never assumes the state distribution stays Gaussian. Resampling is systematic and only runs when the effective sample size falls below half the cloud, which keeps the diversity a plain resample-every-step filter loses.

    Pid

    A proportional-integral-derivative controller.

    The integrator, the previous derivative input and the filtered derivative are the only state; reset clears them so a loop can start from rest.

    PidConfig

    Every number a PID uses that is not one of its three gains.

    weight_p and weight_d are the setpoint weights of the standard form: the proportional term acts on weight_p * reference - measurement and the derivative on weight_d * reference - measurement. A weight of zero takes the reference out of that term, which is what "derivative on measurement" means; derivative_on_measurement does exactly that for the derivative without the caller having to reason about the weight.

    filter is the fraction of the raw derivative kept each period: 1 is no filtering, 0.1 smooths heavily. Values outside (0, 1] are read as 1.

    PlanarArm

    A planar chain of n links. The joint space is the joint angles and the task space the (x, y) of the tip.

    Planner

    A planner: a start, a goal and a budget, out comes a path of states from the start to the goal.

    Polynomial

    A polynomial in time, position = sum(coefficients[i] * t^i).

    A polynomial is the profile to reach for when the boundary conditions are the whole requirement: it matches a position, a velocity and an acceleration at both ends exactly, and it does not care about a jerk limit on the way.

    PotentialField

    An artificial potential field: attraction to the goal, repulsion from the obstacles.

    Prm

    A probabilistic roadmap.

    Pursuit

    The steering a pure-pursuit follower asks for.

    The follower looks a fixed distance ahead on the path, which is what makes it stable at speed: a short lookahead cuts corners and oscillates, a long one turns early. The returned angle is the one that puts the rear axle on an arc through the lookahead point.

    QpConfig

    The knobs of the box-constrained quadratic program.

    penalty is the ADMM step; too small and the dual converges slowly, too large and the primal oscillates. Twice the largest diagonal of h is the usual choice for a well-scaled problem.

    An economy QR factorisation, a = q * r, with q of orthonormal columns and r upper triangular.

    q has the same shape as a and r is square, which is the form the least-squares step and the UKF's square root need.

    RateLimited

    A controller whose output may not change faster than rate per second.

    The limit applies to the first entry of the command, which is the one a single-drive loop carries; a higher-dimensional command passes through the slowest component.

    ReedsShepp

    A Reeds-Shepp car.

    ReedsSheppPath

    A Reeds-Shepp path: the word it spells, its pieces, and the pose it starts from.

    ReedsSheppSegment

    One straight or curved piece of a Reeds-Shepp path.

    ReedsSheppWord

    One of the forty-eight reversable-car path words.

    Refused

    A refusal: the input was not acceptable, or a computation could not reach a result.

    Dimension carries a size that does not fit the operation; Singular and NotPositiveDefinite name the pivot index that failed; Exceeded reports an iteration budget that ran out with the value it reached.

    Riccati

    The solution of a discrete algebraic Riccati equation: the stabilising gain and the cost-to-go matrix it came with.

    Rrt

    A rapidly-exploring random tree.

    step is the length of the edge a new sample is allowed to reach, and goal_bias the chance that a sample is the goal itself rather than a random state. The generator is seeded from the planner, so a run replays exactly.

    RrtConnect

    An RRT grown from both ends at once: the two trees take turns reaching for each other, which closes a narrow passage far faster than one tree alone.

    RrtStar

    An RRT that rewires the tree as it grows, so the path converges on the shortest one inside the budget.

    Sample

    One instant of a single-axis motion, with every derivative a drive may need.

    The jerk is what separates a smooth profile from a trapezoid: a drive that bounds it never excites the resonance the way a step in acceleration does.

    Saturating

    A controller whose output is clamped into a band.

    Scara

    A SCARA arm: two revolute joints about the vertical axis and one prismatic joint along it.

    Scurve

    A jerk-limited S-curve.

    Space

    A state space a planner can search: where to draw random states, how far apart two are, and how to walk between them.

    A vehicle model implements this with its (x, y, heading) states and its own steering primitives, which is what lets a sampling planner plan a Dubins path without knowing what Dubins is.

    Stanley

    The steering a Stanley follower asks for.

    The law is the heading error plus a term that grows with the distance from the path: the first brings the vehicle parallel, the second brings it back on. soft stands in for the speed in the usual atan(k e / v), so the follower does not need a speed input.

    StateSpace

    A linear time-invariant plant, x' = a x + b u, y = c x + d u.

    With dt set the record is already discrete and step applies a x + b u once; without it the record is continuous and step integrates over the period it is given. A discrete record is what the Kalman filter and the model-predictive controller want; a continuous one is what a plant model in physical units looks like.

    Synced

    Several single-axis profiles run together.

    Axes are slowed by sampling them on a stretched clock: axis i is sampled at t * duration_i / duration, and its velocity and acceleration are scaled by the same factor and its square. The path is preserved, the timing is not.

    Trajectory

    A single-axis motion profile: a duration, and the state at any time in it.

    Sampling outside [0, duration] is defined rather than refused: before the start the motion is held at its first sample and after the end at its last, so a caller can run a clock past the end without a branch.

    Trapezoid

    A trapezoidal velocity profile: accelerate, cruise, decelerate.

    It is the cheapest profile that respects a velocity and an acceleration limit, and the one that puts a step in the acceleration at each corner. The boundary velocities may be anything up to the peak the distance allows.

    UnscentedConfig

    The spread of the sigma points in the unscented transform.

    alpha sets how far the points sit from the mean, beta folds in the knowledge that the distribution is Gaussian, and kappa is the usual secondary scaling. The preset is the one the reference implementations use.

    UnscentedKalman

    An unscented Kalman filter.

    Validity

    Which states and motions a motion planner may use.

    Collision checking lives here, so the planners stay free of geometry.

    Vec

    A dense vector of Double.

    The storage is a fixed array, so a vector is cheap to pass around and a set writes through to it. Every arithmetic method returns a new vector; none of them mutate the receiver.

    b_spline

    fn b_spline(control~ : ArrayView[Double], degree? : Int, duration? : Double) ->
    BSpline
    raise
    Refused

    A clamped B-spline of degree over control, running for duration seconds.

    box_qp

    The minimiser of 0.5 z' h z + f' z inside the box [lower, upper].

    ADMM rather than an active-set method: the horizon of a predictive controller is re-solved every period with the same h, so a method whose cost per iteration is one triangular solve and one clamp keeps the period short, and there is no active set to warm start.

    budget

    The default budget: iterations samples and no wall-clock limit, so a run is reproducible.

    cbf_config

    The preset the barrier filter runs with.

    cbf_filter

    The safe input closest to the nominal one.

    The barrier h is the one the caller computed for the current state; gradient is d h / d u, drift is d h / d t without the input, and alpha is the class-K gain that decides how hard the filter pushes towards a larger barrier. The result satisfies drift + gradient * u + alpha * barrier >= 0 inside the box, or the closest point to that set when the box and the half-space do not meet.

    Dykstra's alternating projection rather than plain alternation: plain alternation lands on a point of the intersection but not on the nearest one, and the whole point of a safety filter is to change the command as little as possible.

    cholesky

    The lower Cholesky factor of a symmetric positive-definite matrix a, so that a = l * l^T.

    The factor is what the Kalman gain is solved with: a Cholesky solve costs half an LU solve and never needs pivoting for a covariance.

    cholesky_solve

    Solve a * x = b given the lower Cholesky factor l of a. b has one column per right-hand side.

    cubic

    fn cubic(start~ : Double, end~ : Double, start_velocity~ : Double, end_velocity~ : Double, duration~ : Double) ->
    Polynomial
    raise
    Refused

    A cubic from one boundary state to another over duration.

    A cubic matches the two positions and the two velocities exactly; its acceleration jumps at both ends, which is what a drive without a jerk limit does anyway.

    cubic_spline

    A natural cubic spline through (time, position) knots, which must be ordered in time.

    dare

    Solve the discrete algebraic Riccati equation for (a, b) at the weights (q, r) by iterating the Riccati recursion to a fixed point.

    The recursion is monotone from p = q, so it converges to the stabilising solution without a Schur decomposition. A system that cannot be stabilised does not converge, and the iteration says so with Refused::Exceeded rather than returning a gain that would drive it further away.

    det

    The determinant of a square matrix.

    dynamic_window

    The preset dynamic window: a metre per second, two radians per second, and a prediction of a second and a half.

    eigh

    The symmetric eigendecomposition of a by cyclic Jacobi rotations.

    The rotations are stable for the covariance-shaped matrices this library hands it, and they need no shift strategy. Only the symmetric part of a is used, so a small skew from rounding does not matter. Raises Refused::Dimension when a is not square and Refused::Exceeded when the sweep limit is reached.

    expm

    The matrix exponential of a square matrix, by scaling and squaring followed by a truncated Taylor series.

    This is what turns a continuous state matrix into a discrete one; the scaling keeps the series inside the range where twenty terms reach machine precision.

    feedforward

    A feedforward with unit velocity gain and no acceleration gain.

    grid_path

    The shortest path between two grid cells, as node indices, from the grid package's own search.

    ilqr

    The input sequence that drives start to goal over the horizon the initial guess fixes.

    The initial guess gives the horizon, the control dimension and the region the solve starts in; the returned sequence has the same length. The backward pass uses the model's state Jacobian and a central difference for the input one, because a plant contract only promises the former.

    ilqr_config

    The preset the iterative LQR runs with.

    integral_lqr

    The integral LQR for a discrete (a, b) pair with outputs c, accumulating the output error at dt seconds.

    jacobian_step

    let jacobian_step : Double

    The step the numerical Jacobians use, in the units of the state.

    A central difference with this step reaches about seven correct digits for a state of order one, which is the accuracy a filter or a planner needs and far cheaper than a symbolic derivative. An implementation that knows its Jacobian should override the default method instead.

    lqr

    The infinite-horizon LQR for a discrete (a, b) pair. An element of r that is not positive is refused: it would let the input be free.

    Factorise a, a square matrix, into perm * a = lower * upper.

    Raises Refused::Singular at the first column whose pivot is zero; Refused::Dimension when a is not square.

    motion_samples

    let motion_samples : Int

    How many points the default Validity::motion checks between two states.

    mpc_config

    The preset the model-predictive controller runs with: a horizon of twenty periods, no limit on the command and no limit on its rate.

    norm

    The Frobenius norm of a matrix.

    output_unbounded

    A band that never binds.

    pid_config

    A PID with no gain and no limit: the preset the constructors start from.

    Conditional anti-windup, no setpoint weighting, the derivative on the error and no filtering are the textbook defaults; a drive with a noisy encoder switches derivative_on_measurement on and sets filter.

    pid_gains

    fn pid_gains(kp~ : Double, ki? : Double, kd? : Double) ->
    PidConfig
    raise
    Refused

    A PID configuration from the three gains. A negative gain is refused: a loop with a negative proportional gain is unstable by construction, and refusing it here names the mistake where it is made.

    potential_field

    The preset potential field.

    pursuit

    The preset pure-pursuit follower: one metre of lookahead on a one-metre wheelbase.

    qp_config

    The preset the quadratic program runs with.

    Factorise a as q * r with Householder reflections.

    Raises Refused::Dimension when a has fewer rows than columns: an economy factorisation cannot represent that shape.

    quintic

    fn quintic(start~ : Double, end~ : Double, start_velocity~ : Double, start_acceleration~ : Double, end_velocity~ : Double, end_acceleration~ : Double, duration~ : Double) ->
    Polynomial
    raise
    Refused

    A quintic from one boundary state to another over duration.

    A quintic also matches the two accelerations, so the acceleration is continuous with whatever profile came before and whatever comes after.

    rts

    The Rauch-Tung-Striebel smoother: the forward filter's estimates refined by a backward pass over the same record.

    states and covariances are the filtered estimates, transitions the state matrices between consecutive steps, and process the process covariance. The result is the smoothed state and covariance at every step, whose covariances are never larger than the filtered ones.

    scurve

    fn scurve(distance~ : Double, v_max~ : Double, a_max~ : Double, j_max~ : Double, start_velocity? : Double, start_acceleration? : Double, end_velocity? : Double, end_acceleration? : Double) ->
    Scurve
    raise
    Refused

    An S-curve over distance with the given limits and boundary conditions.

    The start and end accelerations are folded into the first and last ramp, so a motion that is already accelerating keeps its acceleration instead of spending time on coming back to zero. A boundary acceleration outside the limit is refused, and so is a boundary velocity against the direction of travel or above the velocity limit: none of those has a jerk-limited profile that starts from it.

    solve_vec

    Solve a * x = b for a square a and a single right-hand side.

    stanley

    The preset Stanley follower.

    trapezoid

    fn trapezoid(distance~ : Double, v_max~ : Double, a_max~ : Double, start_velocity? : Double, end_velocity? : Double) ->
    Trapezoid
    raise
    Refused

    A trapezoid over distance with the given limits and boundary velocities.

    The boundary velocities are read along the direction of travel: a negative distance with a start velocity of -0.5 means the axis is already moving the way it will go. A velocity against the direction of travel is refused, since the profile would have to reverse inside itself.

    unscented_config

    The preset spread: a narrow alpha, the Gaussian beta and a zero kappa.

    Source Files