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.
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)moon add moonbitstack/moonmcl
moon run src/examples/01-pid| Trait | What a caller supplies | Who uses it |
|---|---|---|
| Model | step(x, u, dt), observe(x), and optionally the two Jacobians | extended, unscented and particle filters; iterative LQR |
| Controller | update(reference, measurement, dt), reset | cascade, saturation and rate limiting |
| Estimator | predict(u, dt), correct(z), state, covariance | anything that wants a filtered state |
| Trajectory | duration, sample(t) | multi-axis synchronisation |
| Kinematics | forward(q), inverse(x, seed?), optionally jacobian(q) | computed torque, Jacobian-based inverse kinematics |
| Space | sample(rng), distance(a, b), interpolate(a, b, t) | every sampling planner, Dubins, the grid |
| Validity | valid(x), optionally motion(space, a, b) | every sampling planner |
| Planner | plan(start, goal, budget) | — |
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)| Item | What it does |
|---|---|
| Vec::zeros, Vec::of, Vec::linspace, Vec::filled | construction |
| Vec::add, sub, hadamard, scale, axpy, dot, norm, normalized | arithmetic |
| Mat::zeros, Mat::identity, Mat::of, Mat::diagonal, Mat::from_fixed | construction |
| Mat::mul, Mat::apply, Mat::apply_transposed, Mat::transpose, Mat::augment, Mat::stack, Mat::block | products and assembly |
| solve, solve_vec, inv, det, lu, qr, cholesky, cholesky_solve, eigh, norm, expm | factorisations, named as in numpy.linalg |
| Item | What it does |
|---|---|
| Pid, PidConfig, pid_gains, pid_config | conditional and back-calculation anti-windup, setpoint weighting, derivative on the measurement, a first-order filter on it |
| Feedforward, FeedforwardRunner | velocity and acceleration feedforward, on a sample or on a reference sequence |
| Cascade, Saturating, RateLimited | combinators over &Controller |
| dare, Riccati, lqr, Lqr | the discrete Riccati equation and the infinite-horizon gain |
| integral_lqr, IntegralLqr | the gain with an integrator on the output error |
| ilqr, IlqrConfig | iterative LQR over any Model |
| Mpc, MpcConfig, box_qp, QpConfig | receding-horizon control with box limits on the input and on its rate, and the quadratic program underneath |
| Pursuit, Stanley | pure pursuit and Stanley on a polyline |
| ComputedTorque, Impedance | model-based effort |
| cbf_filter, CbfConfig | the 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)| Item | What it does |
|---|---|
| Kalman | the linear filter for a discrete StateSpace, Joseph form |
| ExtendedKalman, UnscentedKalman, UnscentedConfig | the same recursion over any Model, linearised or with sigma points |
| ParticleFilter | a cloud of hypotheses, weighted by the innovation and resampled when the effective size halves |
| Complementary, AlphaBetaGamma | the two single-axis trackers |
| rts | the Rauch-Tung-Striebel backward pass |
| Item | What it does |
|---|---|
| trapezoid | accelerate, cruise, decelerate, in closed form |
| scurve | jerk-limited and time optimal from any boundary velocity and acceleration |
| cubic, quintic | polynomials from their boundary states |
| cubic_spline | natural cubic through time-stamped knots |
| b_spline | clamped B-spline, evaluated by de Boor |
| Synced | several 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)| Item | What it does |
|---|---|
| Rrt, RrtStar, RrtConnect, InformedRrtStar | sampling planners over any Space and Validity |
| Prm | a roadmap built once and queried many times |
| Dubins, DubinsPath, DubinsWord | the forward-only car: six words, as a Space |
| ReedsShepp, ReedsSheppPath, ReedsSheppSegment, ReedsSheppWord | the reversing car: forty-eight words, as a Space |
| DynamicWindow, PotentialField | the two local planners |
| grid_path, GridSearch, GridSpace | the grid search of the graph package, and the grid as a Space |
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)| Item | What it does |
|---|---|
| Differential | two wheels to a body twist, and the exact arc for the pose |
| Bicycle, Ackermann | steering bases, with the inner and outer angles |
| Mecanum, Omnidirectional | wheels that push sideways; the inverse is the least-squares one |
| PlanarArm | an n-link chain with an analytic Jacobian and a damped-least-squares inverse |
| Scara, Delta, Gantry | the three structures |
fn cubic(start~ : Double, end~ : Double, start_velocity~ : Double, end_velocity~ : Double, duration~ : Double) -> Polynomial raise Refusedfn grid_path(grid : Grid, start~ : Int, goal~ : Int, search? : GridSearch) -> Array[Int] raise Refusedlet jacobian_step : Doublelet motion_samples : Intfn quintic(start~ : Double, end~ : Double, start_velocity~ : Double, start_acceleration~ : Double, end_velocity~ : Double, end_acceleration~ : Double, duration~ : Double) -> Polynomial raise RefusedInstall
Download zipMoonBit 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.