The multirate MPC at 5 ms over a non-uniform shooting grid - #26
Merged
Merged
Conversation
baggepinnen
force-pushed
the
mpc/nonuniform-grid-5ms
branch
from
September 11, 2026 10:51
263e119 to
bb0a49d
Compare
`FurutaMPCMultirate` solved at 8 ms over 75 uniform shooting intervals, because the period and the horizon were one number: keeping the 0.6 s horizon at 5 ms would have cost 120 decision variables. JuliaComputing/MPCComponents.jl#44 separates them. `ACADOSMPC`'s `time_steps` gives each interval its own length and `linear_time_steps(Ts, Np, horizon)` grows them from `Ts` to span the horizon, so the controller now solves at 5 ms over 60 intervals that still cover 0.6 s, with the encoders read and the state estimated at 1 ms as before. The first interval stays at `Ts`, the one whose control is applied and held for a clock period. `FurutaMPCMultirate` gains a `horizon` parameter beside `Ts` and `Np` (`horizon = Np * Ts` recovers the uniform grid) and an `integrator_stages` parameter, since a stretched grid makes the integrator's order a real choice. The single-rate `FurutaMPC` is unchanged. Measured with `test/mpc_rollouts.jl`, which gains a multirate mode: it ticks `FurutaMPCMultirateHardware` against the simulated pendulum with the QUBE's encoder quantization, separates the solving ticks from the ticks between them, and takes its whole configuration from `key=value` arguments so that two of them are measured from the same seeds. 50 rollouts of 10 s balanced in all 50 at every configuration; against the previous default the solve falls from 1.23 to 0.98 ms at the median and from 2.78 to 1.77 ms at the 99th percentile of a period that is itself 3 ms shorter, the catch from rest from 1.04 to 0.35 s and from the random operating space from 1.14 to 0.99 s, and the arm goes past the end stops in 7 of 50 rest starts instead of 27. How far the grid is stretched is limited by the integrator, which takes one step per interval: over the last interval of this grid, 15 ms, the 2-stage scheme is off by up to 0.60 rad/s against a fine reference, where it is off by 0.034 over the first interval and by 0.13 over the 8 ms intervals the previous default integrated everywhere. At `horizon = 0.8` and `Np = 50` the last interval is 27 ms and the error is 4 rad/s, which predicts nothing. `integrator_stages = 4` removes the error but costs 66 % more solve time and does not swing up better, so the default stays 2. The horizon itself is not the source of the improvement: a uniform grid at the same `Ts` and `Np`, half the span, still catches sooner (0.80 against 0.99 s at the median from random starts, 1.26 against 1.74 at the 90th percentile) and returns fewer nonzero acados statuses. `energy_weight = 1e5` on the pendulum's energy makes the stage cost a shaping term that plans the pump without seeing the catch, and stretching the grid dilutes it while pushing the terminal LQR cost further out. The horizon is kept at what the 8 ms controller had and what the weights were tuned against; NOTES.md records the comparison, so that `horizon = Np * Ts` is one word away. `qp_cond_N = 5` survives the re-sweep on this grid, where 3 to 10 are again within a few percent and 2 and the uncondensed horizon are again worse. The clock documentation asked for a power-of-two ratio between the two periods. What the code needs is an integer ratio whose tick instants coincide exactly in floating point, which 5 x 1 ms does; the three components and `src/mpc.jl` say that now. Both manifests take MPCComponents from the `main` that carries #44, and `Project.toml`'s `[sources]` note names it as the second pull request this package needs from that branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013damsLv5TqkpHHcMi8RdKT
baggepinnen
force-pushed
the
mpc/nonuniform-grid-5ms
branch
from
September 21, 2026 08:45
bb0a49d to
d13c92a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
FurutaMPCMultiratenow solves at 5 ms over 60 shooting intervals that still cover 0.6 s, with theencoders read and the state estimated at 1 ms as before. The interval count and the horizon are no
longer the same number: JuliaComputing/MPCComponents.jl#44 gave
ACADOSMPCatime_stepsparameter, and
MPCComponents.linear_time_steps(Ts, Np, horizon)lays outNpintervals that growfrom
Tsto span the horizon, so 0.6 s costs 60 decision variables instead of the 120 a uniform5 ms grid would need. The first interval stays at
Ts, the one whose control is applied and heldfor a clock period, and each stage's cost is weighted by its interval's length over
Ts.FurutaMPCMultiratetherefore gains ahorizonparameter besideTsandNp(withhorizon = Np * Tsrecovering the uniform grid) and anintegrator_stagesparameter, since astretched grid makes the integrator's order a real choice rather than a constant. The single-rate
FurutaMPCis untouched.Measurements
test/mpc_rollouts.jlgained a multirate mode: it ticksFurutaMPCMultirateHardwareagainst thesimulated pendulum with the QUBE's encoder quantization, reports the solving ticks against the MPC
period and the ticks in between against the sensing period, and takes its whole configuration from
key=valuearguments so that two of them can be measured from the same seeds. 50 rollouts of 10 s,identified plant, every configuration swinging up and balancing in all 50.
From rest near hanging, as the rig starts:
Ts/Np/ horizonintegrator_stages = 4From the random operating space:
Ts/Np/ horizonThe computational budget is comfortable: 0.98 ms of a 5 ms period at the median and 1.77 ms at the
99th percentile, 20 % utilization against the 15 % the 8 ms controller had, with 0.3 % of solves
running over the period (the allocation tail NOTES.md already documents). The solve does not fit a
1 ms sensing slot, as it did not before; that remains jitter on the sensing clock, and the rig's
dtandexeclog columns are what measure it.How far the grid is stretched is limited by the integrator. acados takes one step of the same
scheme per interval, so the maximum one-step error over the states a swing-up traverses, against a
finely integrated reference, is:
in rad/s. At 0.6 s over 60 intervals the last one is 15 ms and its error is a factor of five above
what the previous default accepted on every interval; at 0.8 s over 50 it would be 27 ms and
4 rad/s, which predicts nothing. Recovering the accuracy with
integrator_stages = 4costs 66 %more solve time and does not swing up better, so the default stays 2.
The horizon is not the source of the improvement. A uniform grid at the same
TsandNp--half the span, 0.3 s -- still catches sooner at every quantile (0.80 against 0.99 s at the median
from random starts, 1.26 against 1.74 at the 90th percentile, 1.56 against 5.99 in the worst
rollout) and returns fewer nonzero acados statuses, at the same solve time.
energy_weight = 1e5on the pendulum's energy makes the stage cost a shaping term that plans the pump without needing to
see the catch, and stretching the grid dilutes that while pushing the terminal LQR cost twice as
far out. The horizon is kept at 0.6 s because that is what the 8 ms controller had and what the
weights were tuned against;
horizon = Np * Tsis the whole change to the other, and NOTES.mdrecords the comparison.
qp_cond_N = 5survives the re-sweep on this grid: 3 to 10 are within a few percent, and 2 and theuncondensed horizon are worse, the same shape as at
Np = 60on the single-rate controller.Also here
Manifest.tomlandtest/Manifest.tomltake MPCComponents from themainthat carries #44(the tree hash only; the environment is otherwise untouched), and
Project.toml's[sources]note names the second pull request this package needs from that branch.
needs is an integer ratio whose tick instants coincide exactly in floating point, which 5 x 1 ms
does (
m * 0.005 == 5m * 0.001for everym); the prose in the three components and insrc/mpc.jlsays that now.test/mpc_tests.jlpins the new defaults: divisors(1, 5), the MPC solving on ticks 1, 6, 11,16 with one motor write each.
Not covered: no rig run. The device sustaining 1 kHz reads under a 5 ms solve is a measurement on
the hardware, and the log's
dtandexeccolumns are where it shows. Separately,#27 tracks the
run_ode!/mpc_guiroute, which the multiratemodel cannot use because of the upstream clock-inference bug JuliaComputing/StateSelection.jl#159.
🤖 Generated with Claude Code
https://claude.ai/code/session_013damsLv5TqkpHHcMi8RdKT