mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-15 02:13:43 +08:00
Ford: derive C0 from selected desired curvature
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
# Desired-curvature C0 on continuous PI
|
||||
|
||||
This local candidate changes C0's reference from the live model path's lateral
|
||||
position at 7 m to a circular arc of the selected, upstream-limited desired
|
||||
curvature. It is based on v7 (`08b3a14ad`), with the same P=0.50/I=0.25 controller.
|
||||
No new gain, state, release condition, reference delay or output limit is added.
|
||||
The candidate is on `codex/ford-curvature-c0-trial`; this evaluation does not
|
||||
publish it over the v7 onroad branch.
|
||||
|
||||
For selected curvature k and the existing 7 m reference distance:
|
||||
|
||||
```text
|
||||
arc C0 = (1 − cos(7 × k)) / k, or 0 when k = 0
|
||||
≈ 24.5 × k for small curvature
|
||||
C0 target = clip(arc C0 + 7 × clipped-away base C1, −5.11, +5.11)
|
||||
```
|
||||
|
||||
The implementation uses the equivalent squared-sinc expression to avoid
|
||||
subtracting nearly equal floating-point numbers near zero. C0 keeps its 4 m/s
|
||||
output slew and Float32/CAN quantization. C1 keeps the existing mapping and PI
|
||||
law, ±0.5 rad bound and 0.5 rad/s slew. C2 and C3 stay zero.
|
||||
|
||||
This is a geometric reference choice, not a model of the PSCM. The arc starts
|
||||
at zero lateral position and heading. Independent live model-path position and
|
||||
heading are omitted, while selected curvature can still include the model's
|
||||
centering decision. Valid live model geometry remains a health gate. Short valid
|
||||
paths do not shorten the synthetic 7 m arc. Both C0 and C1 use the selected
|
||||
request, including the maneuver source when selected by controlsd.
|
||||
|
||||
## What the recorded routes show
|
||||
|
||||
All fourteen previous routes were replayed with v7 and this C0 replacement:
|
||||
Lightning 112–117, a0, a2, a5, a9, b8, b9, ca and Raptor 02. On all 1,578,250
|
||||
control cycles, C1, P, I, activation, feedforward, overflow and feedback/PSCM
|
||||
gates match exactly. The v7 baseline also reproduces its archived commands
|
||||
exactly. C0 matches the earlier isolated geometry experiment, but C1 no longer
|
||||
has the release rules that coupled it to C0 in that experiment.
|
||||
|
||||
Duration-weighted clean samples, grouped by requested steering-wheel angle:
|
||||
|
||||
| Absolute requested wheel angle | v7 mean absolute C0 | Curvature C0 | Reduction |
|
||||
| --- | ---: | ---: | ---: |
|
||||
| Under 5° | 0.0136 m | 0.0072 m | 46.7% |
|
||||
| 5–30° | 0.0965 m | 0.0608 m | 37.0% |
|
||||
| 30–90° | 0.5064 m | 0.2916 m | 42.4% |
|
||||
| At least 90° | 2.6314 m | 1.5282 m | 41.9% |
|
||||
|
||||
The clean cohort is 6,654.52 s with the existing quality/driver mask and margins,
|
||||
speed at least 2 m/s and replay feedback enabled. Only 36.20 s have a requested
|
||||
wheel angle of at least 90°. These are command magnitudes, not torque or tracking
|
||||
scores, and do not classify driver interventions as controller failures.
|
||||
|
||||
At route 117, 128.272 s (right entry), C0 changes from −2.51 m to −1.35 m while
|
||||
C1 remains −0.427 rad. At 142.670 s (request relaxing/reversing), C0 changes from
|
||||
+0.18 m to −0.02 m while C1 remains −0.0425 rad. Both comparisons are replayed
|
||||
on the same recorded vehicle motion. The new C0 follows the selected request
|
||||
more directly, but it supplies less C0 during the entry as well as the exit.
|
||||
|
||||
## Validation and interpretation
|
||||
|
||||
- 694 Ford/controlsd, tracked PSCM lab, Sunnylink, params, sender and safety
|
||||
tests pass; 9,145 subtests pass and 178 platform tests skip. Historical
|
||||
untracked offline experiment tests are outside this deployment suite.
|
||||
- The isolated two-controller replay performs 3,156,500 Float32/CAN checks.
|
||||
- Production selection/adapter replay exactly reproduces the isolated candidate
|
||||
on every route cycle, adding 1,578,250 Float32/CAN checks.
|
||||
- A 20,000-cycle scalar geometry/PI stress with mirrored and unrelated model
|
||||
paths adds 60,000 checks. Total: 4,794,750 CAN round trips.
|
||||
- Tests cover zero/tiny curvature, signs, circular geometry, short/malformed
|
||||
paths, selected maneuver requests, overflow, unwind, duplicate measurements,
|
||||
model/driver/PSCM gates, caps/slew and toggle-off upstream Ford fallback.
|
||||
- Ruff, production Ty and diff whitespace checks pass.
|
||||
|
||||
Software C1 parity does not guarantee identical physical unwind: changing C0
|
||||
changes the PSCM's input and therefore the vehicle response and future feedback.
|
||||
Smaller C0 is not established as better or worse tracking. No device build,
|
||||
boot or drive of this candidate is claimed. Collecting the promising v7 drive's
|
||||
logs before replacing it would preserve a useful comparison.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Use the built cereal/opendbc environment and the same local route extracts:
|
||||
|
||||
```sh
|
||||
export PYTHONPATH=.:opendbc_repo:.cache/ford_v6/test_deps
|
||||
export PYTHONDONTWRITEBYTECODE=1
|
||||
export PARAMS_ROOT=/tmp/ford-c0-params
|
||||
export LOG_ROOT=/tmp/ford-c0-logs
|
||||
python -m tools.ford_pscm_lab.curvature_c0_v7_replay .cache/ford_route117 --output .cache/ford_curvature_c0_v7/117
|
||||
python -m tools.ford_pscm_lab.curvature_c0_validate --output .cache/ford_curvature_c0_v7/production --workers 4
|
||||
python -m tools.ford_pscm_lab.curvature_c0_production_stress --cycles 20000 --output .cache/ford_curvature_c0_v7/production_stress.json
|
||||
```
|
||||
|
||||
Repeat the first command for each label before validating all routes. Raptor
|
||||
uses input `.cache/ford_raptor_route02` and output label `raptor02`. The first
|
||||
replay loads isolated copies of pinned v7; the second tests this checkout's
|
||||
actual selector and adapter. The validation JSON records source and extract
|
||||
hashes, settings, example points and both sets of route reports. Older v7-only
|
||||
production validation commands should run from the v7 commit.
|
||||
@@ -0,0 +1,890 @@
|
||||
{
|
||||
"scope": "Curvature-derived C0 candidate on the continuous PI controller; fixed recorded motion, no physical tracking prediction.",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"branch": "codex/ford-curvature-c0-trial",
|
||||
"deployment_at_evaluation": "local candidate; hiimisaac-dev remains v7",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"source_sha256": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 1578250,
|
||||
"route_count": 14,
|
||||
"same_c1_and_integral_on_every_recorded_cycle": true,
|
||||
"baseline_exactly_matches_archived_v7": true,
|
||||
"c0_commands_match_prior_geometry_experiment": true,
|
||||
"can_round_trips": 4794750,
|
||||
"tests": {
|
||||
"passed": 694,
|
||||
"skipped": 178,
|
||||
"subtests_passed": 9145,
|
||||
"scope": "Same Ford controls, tracked PSCM lab, car status, Sunnylink, params, Ford car and safety suite as v7; historical untracked experiments excluded."
|
||||
},
|
||||
"ruff": "pass",
|
||||
"ty_production": "pass",
|
||||
"bins": [
|
||||
{
|
||||
"requested_wheel_angle_degrees": "0-5",
|
||||
"seconds": 4863.242382185041,
|
||||
"mean_abs_c0_m": [
|
||||
0.013572257514406704,
|
||||
0.007239040142159917
|
||||
],
|
||||
"reduction_pct": 46.662962042417725
|
||||
},
|
||||
{
|
||||
"requested_wheel_angle_degrees": "5-30",
|
||||
"seconds": 1576.952442509967,
|
||||
"mean_abs_c0_m": [
|
||||
0.09652124113032315,
|
||||
0.06077627429873566
|
||||
],
|
||||
"reduction_pct": 37.03326481610879
|
||||
},
|
||||
{
|
||||
"requested_wheel_angle_degrees": "30-90",
|
||||
"seconds": 178.12595563801986,
|
||||
"mean_abs_c0_m": [
|
||||
0.5064290157685561,
|
||||
0.2916269430470148
|
||||
],
|
||||
"reduction_pct": 42.4150406144399
|
||||
},
|
||||
{
|
||||
"requested_wheel_angle_degrees": "90-inf",
|
||||
"seconds": 36.19569558700505,
|
||||
"mean_abs_c0_m": [
|
||||
2.6314148997254545,
|
||||
1.528200796263899
|
||||
],
|
||||
"reduction_pct": 41.92474944094366
|
||||
}
|
||||
],
|
||||
"examples": [
|
||||
{
|
||||
"route": "116",
|
||||
"time": 112.00019806100002,
|
||||
"desired_angle": 59.83296203613281,
|
||||
"actual_angle": 60.900001525878906,
|
||||
"c0_left_positive": [
|
||||
0.7599999999999998,
|
||||
0.33999999999999986
|
||||
],
|
||||
"c1_both_left_positive": 0.13550000000000006,
|
||||
"clean": true
|
||||
},
|
||||
{
|
||||
"route": "117",
|
||||
"time": 128.2721161039999,
|
||||
"desired_angle": -226.72189331054688,
|
||||
"actual_angle": -185.1999969482422,
|
||||
"c0_left_positive": [
|
||||
-2.5100000000000002,
|
||||
-1.35
|
||||
],
|
||||
"c1_both_left_positive": -0.427,
|
||||
"clean": true
|
||||
},
|
||||
{
|
||||
"route": "116",
|
||||
"time": 673.6933639450001,
|
||||
"desired_angle": -152.83456420898438,
|
||||
"actual_angle": -272.1000061035156,
|
||||
"c0_left_positive": [
|
||||
-2.21,
|
||||
-0.9199999999999999
|
||||
],
|
||||
"c1_both_left_positive": -0.183,
|
||||
"clean": true
|
||||
},
|
||||
{
|
||||
"route": "117",
|
||||
"time": 142.52000677500018,
|
||||
"desired_angle": 6.6218461990356445,
|
||||
"actual_angle": 40.599998474121094,
|
||||
"c0_left_positive": [
|
||||
0.2599999999999998,
|
||||
0.040000000000000036
|
||||
],
|
||||
"c1_both_left_positive": -0.024499999999999966,
|
||||
"clean": true
|
||||
},
|
||||
{
|
||||
"route": "117",
|
||||
"time": 142.66993146999994,
|
||||
"desired_angle": -2.186957597732544,
|
||||
"actual_angle": 28.5,
|
||||
"c0_left_positive": [
|
||||
0.17999999999999972,
|
||||
-0.019999999999999574
|
||||
],
|
||||
"c1_both_left_positive": -0.04249999999999998,
|
||||
"clean": true
|
||||
}
|
||||
],
|
||||
"replay_reports": [
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 108971,
|
||||
"controller_updates": 217942,
|
||||
"active_cycles_per_controller": 91414,
|
||||
"can_round_trips": 217942,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 919.4963447830002,
|
||||
"clean_seconds": 678.5721400560004,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/route.npz": "2b08a2fb636f7d14556d7df4035eafc1d1b97932237955528562a16db2b31d3e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/model_paths.npz": "9837afe78aab4cad288cad98a595a5777fa8a66bb235986b1272a7f7c54e559a",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/metadata.json": "726d78a7e7aa45307dcfe27cb00775c20ecc9d54538eb7f26a0166fc216226ce",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/angles.npz": "9429813237b4988dd99a1461d1a9bd6f6d3056693a93a5dd1ec63567d19f2aa1",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 49614,
|
||||
"controller_updates": 99228,
|
||||
"active_cycles_per_controller": 27207,
|
||||
"can_round_trips": 99228,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 273.884368861,
|
||||
"clean_seconds": 195.66672679699968,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/route.npz": "774ca4a21b7113c2706d6130bc180c3216ea4833155300ab01e75b3486e36327",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/model_paths.npz": "93c41761eb85263f534f5371b905482cf7c948582eb1e9149966594be1d3768f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/metadata.json": "1c0ca74dd48b90ab9d5444c5ca7f8aa9361700bbdf98bd5e853be50ad2895d7f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/angles.npz": "2f280a1b5973901d878f1e36564d6e7ae6d817cac176149f2cb898ab7591d65b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 61027,
|
||||
"controller_updates": 122054,
|
||||
"active_cycles_per_controller": 48419,
|
||||
"can_round_trips": 122054,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 487.557504183,
|
||||
"clean_seconds": 290.3177928490001,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/route.npz": "83524f07d61104b84b004ddc46bb751ca7f61da67798aa47db56d307765f5b3e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/model_paths.npz": "14d14362d1a3e46398edd8e22b7cc4e36277a7596a73f546192d0e14c6642b07",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/metadata.json": "ec677b9275c1707e477ebd0ffa235d49b5ec63a1ee717b16040c3acdbcd3bdc0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/angles.npz": "63aa2ff6a21e8613519bda476690e2eda6f42cb5dcf108606bc6072c9e29408d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 40037,
|
||||
"controller_updates": 80074,
|
||||
"active_cycles_per_controller": 33976,
|
||||
"can_round_trips": 80074,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 341.89711117800005,
|
||||
"clean_seconds": 176.48340830000006,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/route.npz": "e0df32d9e80f1c6b7d56327b37cf07af60070ffc212b8d111e343306148bff23",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/model_paths.npz": "52f3ed9e188e4947618a57a3ed872fd1966a887fe1014999df741553b6c13bc0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/metadata.json": "d3c73035bad8eb5059e07962fd274c19cb70c8952b6f1514835d312d08b87a16",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/angles.npz": "71415b0f49efbfeda495ae52bde4beb7e16f31a1f5570af515a365e756056d3b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 68793,
|
||||
"controller_updates": 137586,
|
||||
"active_cycles_per_controller": 52456,
|
||||
"can_round_trips": 137586,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 527.966444073,
|
||||
"clean_seconds": 218.019997628,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/route.npz": "13df1d49edd0c92bd4a4236c766ee76baa6dad477824b9de4b79927c98410394",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/model_paths.npz": "85d1112b5e89340e8a955b7fe4040b3135cf1c91a54ff756826c6119cc9cc01d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/metadata.json": "d52fe63d013fa546524ad9522a26943d9372730d397d92d75dfd9d75c63ce766",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/angles.npz": "68e699e01c3ea291e6602ee53edf9cee27c0b461e16c57c3acc0ad12bf29b2aa",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 27301,
|
||||
"controller_updates": 54602,
|
||||
"active_cycles_per_controller": 15841,
|
||||
"can_round_trips": 54602,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 159.4681704720001,
|
||||
"clean_seconds": 74.58932843500065,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/route.npz": "65e4ea6c76dce73018b636479b3777116f1e58bb9813e6c1a7b79018b219140e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/model_paths.npz": "079f6003968287530e1ed6f2243c727e80c2ef3d16fb8a7d945d7eca744d54e7",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/metadata.json": "348396b3059c5f54d7938cd3acf2bb268a813b47ff47561da7a94cb75c3f3114",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/angles.npz": "0575b2d888a6848649b9fb1f7df7c72140f0abbceaa1c451b6eecd034729ef7b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 114582,
|
||||
"controller_updates": 229164,
|
||||
"active_cycles_per_controller": 105382,
|
||||
"can_round_trips": 229164,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 1061.1945591,
|
||||
"clean_seconds": 884.2159145080002,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/route.npz": "01ce8b0361c3154d07d5449525a650baa2cc261675095cb1f04ec9e37a9174d3",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/model_paths.npz": "5d0c69d10a6d13882b47d950ae64c4d98f7ca2b59eab27e4e9557eef3bc4917b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/metadata.json": "00496a11afbee475e9b5d66838a4d1663a3c0063cd562b4d1408cce6d71e586e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/angles.npz": "feb7b558d75292ab69ce8657540f3295bbc1b0c915d27f39d831bdece3fac534",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 71111,
|
||||
"controller_updates": 142222,
|
||||
"active_cycles_per_controller": 43430,
|
||||
"can_round_trips": 142222,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 437.0644468180001,
|
||||
"clean_seconds": 332.6767952400001,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/route.npz": "af4128d278be6be0042f72a02255ddbec96a8a5aa435b9852c8202e4a5cb39e5",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/model_paths.npz": "5a5a7c4fc30c7b3bc7f2413daa8b11fe86b69dd3249e8df2d35b0a0636a49717",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/metadata.json": "cfecd240216e409a1e2591c6c2e6f0bad002ecd2a50c8f6dbf22a8f9174708da",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/angles.npz": "2424b32f02f7e5079f8ae6ffdd30a811820d1d1bfa5b17636c5457eb67d64ecb",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 38961,
|
||||
"controller_updates": 77922,
|
||||
"active_cycles_per_controller": 35677,
|
||||
"can_round_trips": 77922,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 359.186785903,
|
||||
"clean_seconds": 249.91415584299997,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/route.npz": "815d1e248ff5c3e5e5cfc11dfbd0690d0d13ddafe1436890e239975a72dae9b8",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/model_paths.npz": "72e014b844322df6318f6af588c27175ce68d9a37bda62b70eafca5bdfe6dfe4",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/metadata.json": "c533b06d1b7c598330b36b117bdb3624221bc4193ded8354246d5c86f36b2628",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/angles.npz": "5466a674cae3f63eca8758f9f370a8c6cdee3dbb6bcf115f85e3b2e95cf5c1fb",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 286319,
|
||||
"controller_updates": 572638,
|
||||
"active_cycles_per_controller": 152043,
|
||||
"can_round_trips": 572638,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 1530.3877326270015,
|
||||
"clean_seconds": 1093.2748892979992,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/route.npz": "3fa8cabfb729dd42d689de38618d1d21c8965c9d60f914546f4d7cc58db8c975",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/model_paths.npz": "7f3646ad24aa52de5982c65600813de5bbc5f7664d92a2516bbba35da6e7b240",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/metadata.json": "403d35504bf596144845ac2060ce067d571f49a2955d16223af28a79a7a66e93",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/angles.npz": "cfcc9743aacfadc7be97e9ca130453c2ee6f7870edc3e98ccf9a873dac6d4897",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 160431,
|
||||
"controller_updates": 320862,
|
||||
"active_cycles_per_controller": 68217,
|
||||
"can_round_trips": 320862,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 686.4382411050101,
|
||||
"clean_seconds": 390.78219015704235,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/route.npz": "6f5dd369b70eaed4b95b28c8b25c9f2e9b830fa07a334881a185505481667c8b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/model_paths.npz": "939af6cf7e74251d8842581cc078d26d9fbfd22a0d7817cb0e368697d419b615",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/metadata.json": "73b439132d1de37ec187b544c04d2b05c80965065515a4b7dec29ba57ae37e7c",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/angles.npz": "6f4ea54d101595a48d301a0914c940d6ad932249b14d4eaa4fd7e43a11dc6f92",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 90774,
|
||||
"controller_updates": 181548,
|
||||
"active_cycles_per_controller": 86474,
|
||||
"can_round_trips": 181548,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 870.449417766,
|
||||
"clean_seconds": 604.9269013949997,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/route.npz": "b07c789d8155335f5d120d0262fced6e4d5803fe767b0ff49b6413dce4140b5c",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/model_paths.npz": "6b1f87897c050273fdc05af051307a049b6fc3a93072e7cda1721195ce7c3861",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/metadata.json": "9ce452220cab61b81883f32fc2fcaf5db6c78a674cb255a49cc77d5029580fee",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/angles.npz": "ce16212d9b13e41993d2c8df3246ddfe21324ad0cb213310e62eee1d685b197b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 327448,
|
||||
"controller_updates": 654896,
|
||||
"active_cycles_per_controller": 118756,
|
||||
"can_round_trips": 654896,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 1194.5832199259985,
|
||||
"clean_seconds": 894.0687052669919,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/route.npz": "ae9d46770eaf0dbbac6af86aebc926320eed0cf114eb43d5f78b0676e8e0dbf9",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/model_paths.npz": "bf17deb442383aaa79432566cd382df24a1bbbbd0521d0cafab956618f5bdd96",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/metadata.json": "a759d5cdf878df8b05d91db637b1935b6b4bdd87af96f0f256b67e7d809b3525",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/angles.npz": "1681bda6db7dac332abf627d97e7452acd7e0faef0c196b11e55e22291cad073",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
},
|
||||
{
|
||||
"scope": "Circular-arc C0 experiment against the pinned continuous PI v7 controller.\n\nBoth controllers run on identical recorded motion. Replace only the encoder's\nC0 target, retaining path validity, C1 overflow, continuous PI feedback and\noutput limits. No onroad selector or production module is modified.\n",
|
||||
"baseline_revision": "08b3a14ad46260fda0f8d3a1c2cee2d272504153",
|
||||
"baseline_source_sha256": "a899b8595e903d6fc4401392a8d123ef77725f6d6d7fa10392a7b3fe72e732b8",
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"proportional_gain": 0.5,
|
||||
"integral_gain": 0.25,
|
||||
"station_m": 7.0,
|
||||
"cycles": 132881,
|
||||
"controller_updates": 265762,
|
||||
"active_cycles_per_controller": 64776,
|
||||
"can_round_trips": 265762,
|
||||
"same_activation_p_feedforward_overflow_and_feedback_gates": true,
|
||||
"changed_c1_cycles": 0,
|
||||
"max_abs_c1_change_rad": 0.0,
|
||||
"changed_integral_cycles": 0,
|
||||
"pscm_status_fresh_active_seconds": 651.3446633269996,
|
||||
"clean_seconds": 571.0075301469988,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/route.npz": "8a4cfe53994988d053b47d9caadf21ebd064fd20e598fa05ddc2da245bd0e980",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/model_paths.npz": "d5dc6f72d91472b8f2fb1add1680cdaea1e122774ddd6d8519fd7673daf71bb5",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/metadata.json": "9602e08efc2bd784133837c4156c075bb89bad3bedd6b309f15e884ed20e338a",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/angles.npz": "82af2973ad40c1df392e11bdc5dd5895bfc6c170fc894735123af7704d95cd57",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_v7_replay.py": "fcd8a10db3d68024dbc836dce1b4c9a459912837b1a4234f61b2dd508c6d5e9f"
|
||||
},
|
||||
"limitations": [
|
||||
"No new steering trace or physical tracking score: recorded motion stays fixed.",
|
||||
"The baseline is v7 on every route, regardless of its recorded controller version.",
|
||||
"C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.",
|
||||
"Publication time proxies computation time; full SubMaster state is unavailable.",
|
||||
"The synthetic arc keeps the original model-health gates for a controlled comparison."
|
||||
]
|
||||
}
|
||||
],
|
||||
"production_parity": [
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "112",
|
||||
"cycles": 108971,
|
||||
"can_round_trips": 108971,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/route.npz": "2b08a2fb636f7d14556d7df4035eafc1d1b97932237955528562a16db2b31d3e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/model_paths.npz": "9837afe78aab4cad288cad98a595a5777fa8a66bb235986b1272a7f7c54e559a",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route112/metadata.json": "726d78a7e7aa45307dcfe27cb00775c20ecc9d54538eb7f26a0166fc216226ce",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/112/report.json": "1be0b52e00e8f67043ce35e19cc6612f788d6e3853b40d1bc2dd372b2ff4e70d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/112/commands.npz": "3951aa6160295ee6c3835517b39c7c835074b93b1bda5fb013d4ef56f88b8f1e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "113",
|
||||
"cycles": 49614,
|
||||
"can_round_trips": 49614,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/route.npz": "774ca4a21b7113c2706d6130bc180c3216ea4833155300ab01e75b3486e36327",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/model_paths.npz": "93c41761eb85263f534f5371b905482cf7c948582eb1e9149966594be1d3768f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route113/metadata.json": "1c0ca74dd48b90ab9d5444c5ca7f8aa9361700bbdf98bd5e853be50ad2895d7f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/113/report.json": "c6e98c0f2e6f43f1ede8d8104b77606a91fb3e95efa8348f7f089e01a4f6c823",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/113/commands.npz": "0a7aa93e8e3e64659022dac254b476e06fb95649c4b97e1de66e4808fae73d6f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "114",
|
||||
"cycles": 61027,
|
||||
"can_round_trips": 61027,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/route.npz": "83524f07d61104b84b004ddc46bb751ca7f61da67798aa47db56d307765f5b3e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/model_paths.npz": "14d14362d1a3e46398edd8e22b7cc4e36277a7596a73f546192d0e14c6642b07",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route114/metadata.json": "ec677b9275c1707e477ebd0ffa235d49b5ec63a1ee717b16040c3acdbcd3bdc0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/114/report.json": "b7f1d341f4f33d2c16dcfa2694b36cafe0bf76405d0e7b61bc4e469caef6b1dc",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/114/commands.npz": "3b79d094ecb89d3f3554c492d83826ec60662c236d6854891feba95fffcd7bc7",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "115",
|
||||
"cycles": 40037,
|
||||
"can_round_trips": 40037,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/route.npz": "e0df32d9e80f1c6b7d56327b37cf07af60070ffc212b8d111e343306148bff23",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/model_paths.npz": "52f3ed9e188e4947618a57a3ed872fd1966a887fe1014999df741553b6c13bc0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route115/metadata.json": "d3c73035bad8eb5059e07962fd274c19cb70c8952b6f1514835d312d08b87a16",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/115/report.json": "e66d2be86136b0f6fdbcc2998673b2d1e8487f0543f99e4618e46a2f429538e4",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/115/commands.npz": "9a72eb6bab9116cc83210c92dfb12c5d63a2e4b3103ccdd3f5e70214f3f0e142",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "116",
|
||||
"cycles": 68793,
|
||||
"can_round_trips": 68793,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/route.npz": "13df1d49edd0c92bd4a4236c766ee76baa6dad477824b9de4b79927c98410394",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/model_paths.npz": "85d1112b5e89340e8a955b7fe4040b3135cf1c91a54ff756826c6119cc9cc01d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route116/metadata.json": "d52fe63d013fa546524ad9522a26943d9372730d397d92d75dfd9d75c63ce766",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/116/report.json": "501b944c65f41f6c1e05fccdc6c38f3a80450db6c4696adc35bf85640a04df94",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/116/commands.npz": "2469c545d0ce62894d59e07799942ebf40bec583693391aaa1dd9e75c218a396",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "117",
|
||||
"cycles": 27301,
|
||||
"can_round_trips": 27301,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/route.npz": "65e4ea6c76dce73018b636479b3777116f1e58bb9813e6c1a7b79018b219140e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/model_paths.npz": "079f6003968287530e1ed6f2243c727e80c2ef3d16fb8a7d945d7eca744d54e7",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_route117/metadata.json": "348396b3059c5f54d7938cd3acf2bb268a813b47ff47561da7a94cb75c3f3114",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/117/report.json": "896b30e7ded613f1b05b693067fc2f19155016b6a33f26f881bbfff2b193019d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/117/commands.npz": "6d88bfdb54fde00240954db9dbbf1f3e5059bfac9b6161c138f0069213ac35a8",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "a0",
|
||||
"cycles": 114582,
|
||||
"can_round_trips": 114582,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/route.npz": "01ce8b0361c3154d07d5449525a650baa2cc261675095cb1f04ec9e37a9174d3",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/model_paths.npz": "5d0c69d10a6d13882b47d950ae64c4d98f7ca2b59eab27e4e9557eef3bc4917b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea0/metadata.json": "00496a11afbee475e9b5d66838a4d1663a3c0063cd562b4d1408cce6d71e586e",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a0/report.json": "52cd87929fbdc721450c735bd02ac049260cafa439af7dcdcd405e977dc9ea22",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a0/commands.npz": "0f718892b0802cbf754ba33c9c9c6da8a3e915e88fa7a4e008b9b33d63a429e0",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "a2",
|
||||
"cycles": 71111,
|
||||
"can_round_trips": 71111,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/route.npz": "af4128d278be6be0042f72a02255ddbec96a8a5aa435b9852c8202e4a5cb39e5",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/model_paths.npz": "5a5a7c4fc30c7b3bc7f2413daa8b11fe86b69dd3249e8df2d35b0a0636a49717",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea2/metadata.json": "cfecd240216e409a1e2591c6c2e6f0bad002ecd2a50c8f6dbf22a8f9174708da",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a2/report.json": "fbaf59a1499df5dba0f790f76f211efefb3b68f327e9e49c763fb175f6f8e7a9",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a2/commands.npz": "159f76d2d511af7f423941cc9d168c7112a8aec7a34e05bbd043f65a2324b325",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "a5",
|
||||
"cycles": 38961,
|
||||
"can_round_trips": 38961,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/route.npz": "815d1e248ff5c3e5e5cfc11dfbd0690d0d13ddafe1436890e239975a72dae9b8",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/model_paths.npz": "72e014b844322df6318f6af588c27175ce68d9a37bda62b70eafca5bdfe6dfe4",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea5/metadata.json": "c533b06d1b7c598330b36b117bdb3624221bc4193ded8354246d5c86f36b2628",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a5/report.json": "ba9cedd6d9f2bbe2caa3ad0eeb298fa3334750ad60086422d7d48f3b690480f7",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a5/commands.npz": "8ac5c77a29824b8f621c3071309990d320a1a61cc968ad55f76408a530680913",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "a9",
|
||||
"cycles": 286319,
|
||||
"can_round_trips": 286319,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/route.npz": "3fa8cabfb729dd42d689de38618d1d21c8965c9d60f914546f4d7cc58db8c975",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/model_paths.npz": "7f3646ad24aa52de5982c65600813de5bbc5f7664d92a2516bbba35da6e7b240",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routea9/metadata.json": "403d35504bf596144845ac2060ce067d571f49a2955d16223af28a79a7a66e93",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a9/report.json": "677a2f66ecc5696fa44190c5e3ee27cd3dc45f1deda9ffe3a2f330b4e1d0d20d",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/a9/commands.npz": "e109e77124dc2296a00f65563c1b1ec3c960b414473a3cc7c6caaafbe7e65037",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "b8",
|
||||
"cycles": 160431,
|
||||
"can_round_trips": 160431,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/route.npz": "6f5dd369b70eaed4b95b28c8b25c9f2e9b830fa07a334881a185505481667c8b",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/model_paths.npz": "939af6cf7e74251d8842581cc078d26d9fbfd22a0d7817cb0e368697d419b615",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb8/metadata.json": "73b439132d1de37ec187b544c04d2b05c80965065515a4b7dec29ba57ae37e7c",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/b8/report.json": "f48796acdfad87a0caaaddac2a41945c97311e85fa57f9fc73a1730d264ddba4",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/b8/commands.npz": "554891332a496498ff64e08e4eba4210125fd5f2da8307231dd6d9377becfe0a",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "b9",
|
||||
"cycles": 90774,
|
||||
"can_round_trips": 90774,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/route.npz": "b07c789d8155335f5d120d0262fced6e4d5803fe767b0ff49b6413dce4140b5c",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/model_paths.npz": "6b1f87897c050273fdc05af051307a049b6fc3a93072e7cda1721195ce7c3861",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeb9/metadata.json": "9ce452220cab61b81883f32fc2fcaf5db6c78a674cb255a49cc77d5029580fee",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/b9/report.json": "3812983a690e5eafca93ea6af05620fe82e4e0cc8efc07e24505a63664444d66",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/b9/commands.npz": "2ffc685e29463a7b89d39bfb3583a37eb10a57d7201a24394550fcf9a89e3c4c",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "ca",
|
||||
"cycles": 327448,
|
||||
"can_round_trips": 327448,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/route.npz": "ae9d46770eaf0dbbac6af86aebc926320eed0cf114eb43d5f78b0676e8e0dbf9",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/model_paths.npz": "bf17deb442383aaa79432566cd382df24a1bbbbd0521d0cafab956618f5bdd96",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_routeca/metadata.json": "a759d5cdf878df8b05d91db637b1935b6b4bdd87af96f0f256b67e7d809b3525",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/ca/report.json": "753760c726ef1b85daf83b7108f758f9304dab408b49018f303616b99c96b81f",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/ca/commands.npz": "3fa932ac86e14440d325a6cc4677b47cd75c5c6019b929d3a5d078a62c5590df",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"scope": "Verify production commands against the archived curvature-C0 offline candidate.\n\nFixed recorded motion verifies integration parity, not physical tracking.\n",
|
||||
"route": "raptor02",
|
||||
"cycles": 132881,
|
||||
"can_round_trips": 132881,
|
||||
"production_matches_archived_trial_exactly": true,
|
||||
"opendbc_revision": "64aa61b9b3fd26e70a7caa915acab207ff3cd64a",
|
||||
"hypothesis": "model-action-curvature-c0-pi-v8",
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/route.npz": "8a4cfe53994988d053b47d9caadf21ebd064fd20e598fa05ddc2da245bd0e980",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/model_paths.npz": "d5dc6f72d91472b8f2fb1add1680cdaea1e122774ddd6d8519fd7673daf71bb5",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_raptor_route02/metadata.json": "9602e08efc2bd784133837c4156c075bb89bad3bedd6b309f15e884ed20e338a",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/raptor02/report.json": "af6b8fa4e4bfe3d5543794cdde4fbe4fd7560addf02717c2b6144c3987effafa",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/.cache/ford_curvature_c0_v7/raptor02/commands.npz": "99ee65b866eda85e5c5d54f8896a7f952504826753eca31a01a29c7cf36c6482",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_validate.py": "c91ead82b0320725276141b1bc9b84b132cd6345a144ea826ba2b7f069870932",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
}
|
||||
],
|
||||
"production_stress": {
|
||||
"cycles": 20000,
|
||||
"kp": 0.5,
|
||||
"ki": 0.25,
|
||||
"controller_updates": 60000,
|
||||
"can_round_trips": 60000,
|
||||
"seed": 20260913,
|
||||
"independent_c0_comparisons": 20000,
|
||||
"checks": "Independent scalar PI/unwind-first/anti-windup arithmetic, mirror symmetry, exact C0 independence, limits, slew, resets, CAN.",
|
||||
"source_sha256": {
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/tools/ford_pscm_lab/curvature_c0_production_stress.py": "4bf031a793efe9ebc066e5b6db6897ca8e844d5e34c6faecd4701a4d8f04132a",
|
||||
"/Users/ibpersonal/.codex/worktrees/1a1c/sunnypilot/openpilot/selfdrive/controls/lib/ford_model_action.py": "6f8c4a6f7fa54fa2e23f32f1e83ace83da5f0b6f45011941f1902a1dae778ffc"
|
||||
}
|
||||
},
|
||||
"limitations": [
|
||||
"C1 parity applies only to fixed recorded inputs; changing C0 changes physical response and subsequent feedback.",
|
||||
"Curvature arc omits independent live model position/heading; seven meters remains an engineering reference choice.",
|
||||
"Smaller C0 is not proof of better or worse tracking; no candidate hardware drive has been performed."
|
||||
]
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
# Ford selected-action drive-test branch
|
||||
|
||||
The current v7 experiment uses [continuous C1 PI feedback](ford_c1_minimal_pi.md)
|
||||
with **P=0.50 and I=0.25**, replacing the earlier conditional-release rules.
|
||||
Only C0, C1 and accumulated error carry control history. The restored model
|
||||
mapping and [base C1 overflow allocation to C0](ford_c1_overflow.md) remain.
|
||||
This local v8 candidate uses [curvature-derived C0](ford_curvature_c0_v8.md)
|
||||
and [continuous C1 PI feedback](ford_c1_minimal_pi.md)
|
||||
with **P=0.50 and I=0.25**.
|
||||
Only C0, C1 and accumulated error carry control history. C0 is now a 7 m circular arc from selected desired curvature.
|
||||
[Base C1 overflow allocation to C0](ford_c1_overflow.md) remains.
|
||||
It is selectable on **any Ford CAN FD vehicle**
|
||||
through the existing persistent, default-off Sunnylink
|
||||
toggle. Offline checks establish software behavior; physical tracking,
|
||||
turn-exit behavior and closed-loop stability remain unvalidated.
|
||||
|
||||
The v8 implementation is on local branch `codex/ford-curvature-c0-trial`.
|
||||
This evaluation does not push it to `hiimisaac-dev`; the deployed v7 commit is
|
||||
`08b3a14ad`. The selection instructions below apply once a candidate is installed.
|
||||
|
||||
## Select and restore
|
||||
|
||||
1. Install branch `hiimisaac-dev` from `sunnypilot/sunnypilot` using the device's
|
||||
@@ -21,7 +26,7 @@ turn-exit behavior and closed-loop stability remain unvalidated.
|
||||
|
||||
The startup event `Ford path controller selected` should report
|
||||
`FordModelActionController`. Periodic `Ford C2-free path tracking` events
|
||||
identify **`hypothesis=model-action-c1-pi-v7`**. They report desired and measured
|
||||
identify **`hypothesis=model-action-curvature-c0-pi-v8`**. They report desired and measured
|
||||
curvature, base heading, proportional and accumulated correction, applied heading,
|
||||
feedback timing and driver/PSCM gating. `proportional_gain=0.5` and
|
||||
`integral_gain=0.25` identify the trial. `offset_overflow` reports the extra C0
|
||||
@@ -51,7 +56,9 @@ combined feedforward/P/I amplitude and slew envelope. There is no C0 confirmatio
|
||||
threshold or remembered turn direction. Zero error removes P and holds I; it
|
||||
does not trigger a release. Final command limits still apply.
|
||||
|
||||
C0 starts with the original 7 m model-path mapping. When the raw base heading
|
||||
C0 starts with the 7 m circular arc of selected desired curvature. It does not
|
||||
add independent live model-path position or heading. Valid model geometry is
|
||||
still required as a health gate. When the raw base heading
|
||||
exceeds ±0.5 rad, C0 additionally receives 7 m times the clipped-away heading.
|
||||
Accumulated C1 feedback does not spill into C0. The extra target returns to zero
|
||||
as the base heading falls below the cap; applied C0 still follows its 4 m/s slew.
|
||||
@@ -61,8 +68,9 @@ place. An explicit selection flag distinguishes upstream mode from an invalid
|
||||
experimental command; invalid experimental input cannot switch to upstream.
|
||||
The opendbc sender restores upstream behavior when that flag is false.
|
||||
|
||||
See [continuous PI trial and validation](ford_c1_minimal_pi.md) and
|
||||
`ford_c1_minimal_pi_validation.json` for current evidence and reproduction commands.
|
||||
See [curvature-C0 trial and validation](ford_curvature_c0_v8.md) and
|
||||
`ford_curvature_c0_v8_validation.json` for this candidate.
|
||||
[Continuous PI](ford_c1_minimal_pi.md) and its validation JSON record v7.
|
||||
[Proportional feedback](ford_c1_pi.md) and its validation JSON record v6.
|
||||
[Completed-unwind release](ford_unwind_catchup.md) and
|
||||
`ford_unwind_catchup_validation.json` record v5. [Changed-request release](ford_c1_request_release.md) and its
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Opt-in Ford C2-free model mapping with measured-curvature PI feedback.
|
||||
|
||||
C0 samples the model at 7 m, including clipped base-heading overflow. C1
|
||||
C0 samples a desired-curvature arc at 7 m, including base-heading overflow. C1
|
||||
combines the selected curvature's heading with proportional and integrated
|
||||
tracking error. Reference distance and gains are explicit trial choices.
|
||||
"""
|
||||
@@ -34,10 +34,10 @@ def _finite(*values):
|
||||
|
||||
|
||||
def encode_model_action(model, desired_curvature, speed):
|
||||
"""Encode y(7) and max(7, v*1s)*selected limited curvature.
|
||||
"""Encode a 7 m circular-arc offset and max(7, v*1s)*selected curvature.
|
||||
|
||||
Preserve the reviewed core's endpoint hold when the path ends before 7 m.
|
||||
This samples the available geometry; it does not extrapolate an unseen path.
|
||||
The arc starts at zero lateral position and heading. Original model geometry
|
||||
remains a health gate; selected curvature supplies both path commands.
|
||||
"""
|
||||
if not _finite(desired_curvature, speed) or not .3 <= speed <= 55 or abs(desired_curvature) > 1:
|
||||
return FordPath()
|
||||
@@ -47,8 +47,10 @@ def encode_model_action(model, desired_curvature, speed):
|
||||
return FordPath()
|
||||
if path is None or not all(_finite(*values) for values in path):
|
||||
return FordPath()
|
||||
station, _, lateral, _ = path
|
||||
c0 = float(np.interp(min(OFFSET_STATION_M, station[-1]), station, lateral))
|
||||
# (1-cos(S*k))/k, using sinc to avoid cancellation near zero curvature.
|
||||
half_heading = .5*OFFSET_STATION_M*desired_curvature
|
||||
sinc = math.sin(half_heading)/half_heading if half_heading else 1.
|
||||
c0 = .5*desired_curvature*OFFSET_STATION_M**2*sinc**2
|
||||
c1 = max(OFFSET_STATION_M, speed*HEADING_TIME_S)*desired_curvature
|
||||
return FordPath(True, c0, c1, 0., 0.) if _finite(c0, c1) else FordPath()
|
||||
|
||||
@@ -127,7 +129,7 @@ class FordModelActionController:
|
||||
"""
|
||||
def __init__(self, proportional_gain=C1_PROPORTIONAL_GAIN, integral_gain=C1_INTEGRAL_GAIN):
|
||||
self.core = ModelActionController(proportional_gain=proportional_gain, integral_gain=integral_gain)
|
||||
self.hypothesis = 'model-action-c1-pi-v7'
|
||||
self.hypothesis = 'model-action-curvature-c0-pi-v8'
|
||||
self.reset()
|
||||
|
||||
def reset(self, status='inactive'):
|
||||
|
||||
@@ -53,7 +53,7 @@ class TestFordControlsLogging(unittest.TestCase):
|
||||
controls = SimpleNamespace(ford_path_controller=controller, desired_curvature=.03, curvature=.015,
|
||||
sm=SimpleNamespace(logMonoTime={'modelV2': 123456789, 'carState': 123450000}))
|
||||
record = self.emit_controls_event('Ford C2-free path tracking', controls)
|
||||
self.assertEqual(record['hypothesis'], 'model-action-c1-pi-v7')
|
||||
self.assertEqual(record['hypothesis'], 'model-action-curvature-c0-pi-v8')
|
||||
self.assertIs(record['calibration_approved'], False)
|
||||
self.assertEqual(record['command'][2:], [0., 0.])
|
||||
self.assertEqual(record['status'], controller.diagnostics['status'])
|
||||
|
||||
@@ -25,17 +25,18 @@ def straight(offset=0.):
|
||||
return make_model(x, np.full_like(x, offset), np.zeros_like(x))
|
||||
|
||||
|
||||
def test_selected_action_controls_heading_even_when_model_previews_another_turn():
|
||||
def test_selected_action_controls_both_fields_even_when_model_previews_another_turn():
|
||||
model = circle(.02)
|
||||
assert encode_model_action(model, 0., 20.).path_angle == 0.
|
||||
assert encode_model_action(model, -.004, 20.).path_angle == pytest.approx(-.08)
|
||||
assert encode_model_action(model, 0., 20.).path_offset > 0.
|
||||
assert encode_model_action(model, 0., 20.).path_offset == 0.
|
||||
assert encode_model_action(model, -.004, 20.).path_offset < 0.
|
||||
|
||||
|
||||
def test_centering_information_is_independent_of_action_and_not_scaled_with_speed():
|
||||
def test_arc_offset_uses_selected_curvature_and_is_not_scaled_with_speed():
|
||||
for speed in (2., 7., 20., 35.):
|
||||
target = encode_model_action(straight(.4), 0., speed)
|
||||
assert target == FordPath(True, .4, 0., 0., 0.)
|
||||
assert target == FordPath(True, 0., 0., 0., 0.)
|
||||
for sign in (-1, 1):
|
||||
target = encode_model_action(circle(sign*.01), sign*.01, 20.)
|
||||
assert target.path_offset == pytest.approx(sign*(1-math.cos(.07))/.01, abs=1e-6)
|
||||
@@ -74,7 +75,7 @@ def test_current_model_replacement_leaves_only_independent_actuator_slew():
|
||||
controller = ModelActionController()
|
||||
for _ in range(150):
|
||||
controller.update(straight(1.), .04, current_curvature=.04, speed=20., dt=.01)
|
||||
# C0 starts at 1 + 7*(.8-.5) = 3.1 m, including the clipped heading.
|
||||
# C0 starts near .974 + 7*(.8-.5) = 3.074 m, including heading overflow.
|
||||
for _ in range(78):
|
||||
out = controller.update(straight(), 0., current_curvature=0., speed=20., dt=.01)
|
||||
assert out.path_offset == pytest.approx(0.)
|
||||
@@ -126,9 +127,11 @@ def test_selected_core_reversal_through_float32_and_wire_keeps_sign_and_zero_c2(
|
||||
assert decoded['LatCtlCurv_No_Actl'] == decoded['LatCtlCrv_NoRate2_Actl'] == 0.
|
||||
|
||||
|
||||
def test_short_path_holds_available_endpoint_without_extrapolation():
|
||||
def test_short_valid_path_does_not_shorten_the_selected_curvature_arc():
|
||||
model = make_model([0., 1.], [0., .1], [0., 0.])
|
||||
assert encode_model_action(model, .01, 20.) == FordPath(True, .1, .2, 0., 0.)
|
||||
target = encode_model_action(model, .01, 20.)
|
||||
assert target == encode_model_action(straight(), .01, 20.)
|
||||
assert target.path_offset == pytest.approx((1-math.cos(.07))/.01)
|
||||
|
||||
|
||||
def test_overflowing_arc_resets_instead_of_publishing_invalid_geometry():
|
||||
@@ -177,18 +180,31 @@ def test_domain_and_elapsed_time_boundaries(field, value, valid):
|
||||
assert ModelActionController().update(straight(.4), current_curvature=kwargs['desired_curvature'], **kwargs).valid == valid
|
||||
|
||||
|
||||
def test_arc_station_not_forward_x_or_model_heading_determines_offset():
|
||||
def test_unrelated_live_model_position_and_heading_do_not_change_selected_arc():
|
||||
x = np.array([0., 6., 12.])
|
||||
y = .4+x*.75
|
||||
target = encode_model_action(make_model(x, y, [2., -2., 1.]), -.01, 20.)
|
||||
# Arc length is 1.25*x on this line, so y(arc=7)=.4+.75*(7/1.25).
|
||||
assert target.path_offset == pytest.approx(4.6)
|
||||
assert target.path_offset == pytest.approx(-(1-math.cos(.07))/.01)
|
||||
assert target.path_angle == pytest.approx(-.2)
|
||||
|
||||
|
||||
def test_duplicate_stations_keep_valid_geometry_and_first_cycle_slew():
|
||||
model = make_model([0., 0., 10.], [.4, .4, .4], [0., 0., 0.])
|
||||
assert encode_model_action(model, .01, 20.) == FordPath(True, .4, .2, 0., 0.)
|
||||
assert encode_model_action(model, .01, 20.) == encode_model_action(straight(), .01, 20.)
|
||||
out = ModelActionController().update(model, .01, current_curvature=.01, speed=20., dt=.002)
|
||||
assert out.path_offset == pytest.approx(.01)
|
||||
assert out.path_angle == pytest.approx(.001)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('curvature', [-1., -.2, -.1, -.01, -.001, .001, .01, .1, .2, 1.])
|
||||
def test_desired_curvature_arc_matches_circle_geometry(curvature):
|
||||
target = encode_model_action(straight(.4), curvature, 20.)
|
||||
assert target.valid
|
||||
assert target.path_offset == pytest.approx((1-math.cos(7.*curvature))/curvature, abs=1e-12)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('curvature', [-1e-12, -1e-100, 0., 1e-100, 1e-12])
|
||||
def test_near_zero_arc_is_finite_continuous_and_keeps_direction(curvature):
|
||||
target = encode_model_action(straight(.4), curvature, 20.)
|
||||
assert target.valid and math.isfinite(target.path_offset)
|
||||
assert target.path_offset == pytest.approx(24.5*curvature, rel=1e-12, abs=0.)
|
||||
|
||||
@@ -83,7 +83,7 @@ def test_repeated_measurements_do_not_freeze_slew_or_cache_invalid_model_geometr
|
||||
controller = FordModelActionController()
|
||||
for i in range(10):
|
||||
result = update(controller, 1.+i*.01, measurement_time=1., model_time=1., reference_time=1.)
|
||||
assert result.path_offset == pytest.approx(.4)
|
||||
assert result.path_offset == pytest.approx(.24)
|
||||
assert result.path_angle == pytest.approx(.05)
|
||||
broken = straight(.4)
|
||||
broken.position.y[5] = math.nan
|
||||
@@ -106,18 +106,18 @@ def test_reference_source_can_change_to_an_older_but_fresh_publication():
|
||||
assert update(controller, 1.01, reference_time=.98).valid
|
||||
|
||||
|
||||
def test_release_keeps_current_geometry_and_may_grow_c0_while_c1_decreases():
|
||||
def test_relaxing_selected_request_releases_both_fields_despite_growing_model_path():
|
||||
for sign in (-1., 1.):
|
||||
controller = FordModelActionController()
|
||||
for i in range(100):
|
||||
before = update(controller, 1.+i*.01, model=circle(sign*.01), desired_curvature=sign*.005)
|
||||
for i in range(100):
|
||||
after = update(controller, 2.+i*.01, model=circle(sign*.02), desired_curvature=sign*.004)
|
||||
assert abs(after.path_offset) > abs(before.path_offset)
|
||||
assert abs(after.path_offset) < abs(before.path_offset)
|
||||
assert abs(after.path_angle) < abs(before.path_angle)
|
||||
for i in range(100):
|
||||
released = update(controller, 3.+i*.01, model=circle(sign*.02), desired_curvature=0.)
|
||||
assert released.path_offset == after.path_offset
|
||||
assert released.path_offset == pytest.approx(0.)
|
||||
assert released.path_angle == pytest.approx(0.)
|
||||
|
||||
|
||||
@@ -183,7 +183,7 @@ def test_actual_controlsd_selection_limiting_publication_and_downstream_can(pipe
|
||||
assert controller.core.proportional == pytest.approx(.5*20.*expected_curvature)
|
||||
assert controller.core.correction == 0. # First measurement has no elapsed feedback time.
|
||||
assert controls.ford_path.path_angle == pytest.approx((-1 if maneuver else 1)*.0035)
|
||||
assert controls.ford_path.path_offset == pytest.approx(.04)
|
||||
assert controls.ford_path.path_offset == pytest.approx(0.) # Limited curvature arc is below one C0 step.
|
||||
assert cc.latActive and cc.actuators.curvature == 0.
|
||||
assert controller.diagnostics['reference_age'] == pytest.approx(.01 if maneuver else .02)
|
||||
|
||||
@@ -273,7 +273,7 @@ def test_feedback_through_actual_controlsd_publication_and_100hz_sender(pipeline
|
||||
assert core.correction == pytest.approx(expected)
|
||||
assert core.c1 == pytest.approx(sign*.08+expected_p+expected)
|
||||
assert controls.ford_path.path_angle == pytest.approx(core.c1, abs=.00025)
|
||||
assert controls.ford_path.path_offset == pytest.approx(.4)
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*.1)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('service_valid', [False, True])
|
||||
@@ -348,13 +348,13 @@ def test_continuous_pi_reversal_through_selected_limited_request_and_actual_can(
|
||||
assert wire['LatCtlPath_No_Cs'] == calculate_lat_ctl2_checksum(2, frame % 16, packet[1])
|
||||
if frame == 199:
|
||||
assert sign*core.correction < 0. if same_turn else sign*core.correction > 0.
|
||||
assert controls.ford_path_controller.diagnostics['hypothesis'] == 'model-action-c1-pi-v7'
|
||||
assert controls.ford_path_controller.diagnostics['hypothesis'] == 'model-action-curvature-c0-pi-v8'
|
||||
if same_turn:
|
||||
assert controls.desired_curvature == pytest.approx(sign*.01)
|
||||
assert sign*controls.ford_path.path_angle >= speed*.01 # No old unwind correction left below the new base.
|
||||
else:
|
||||
assert sign*controls.ford_path.path_angle < 0.
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*(.2 if same_turn else -.2))
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*(.24 if same_turn else -.02))
|
||||
controls.ford_path_controller.reset()
|
||||
assert core.c0 == core.c1 == core.correction == 0.
|
||||
|
||||
@@ -407,7 +407,7 @@ def test_unwind_and_catchup_through_selected_request_and_actual_can(pipeline, si
|
||||
assert wire['LatCtlPath_No_Cs'] == calculate_lat_ctl2_checksum(2, frame % 16, packet[1])
|
||||
assert controls.ford_path.path_angle == pytest.approx(core.correction, abs=.00025)
|
||||
assert 0. < sign*core.correction < .02
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*.05)
|
||||
assert controls.ford_path.path_offset == pytest.approx(0.)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
@@ -453,10 +453,10 @@ def test_heading_overflow_and_release_through_actual_can(pipeline, sign, fingerp
|
||||
assert wire['LatCtlPath_No_Cs'] == calculate_lat_ctl2_checksum(2, frame % 16, packet[1])
|
||||
if frame == 149:
|
||||
assert controls.desired_curvature == pytest.approx(sign*.1)
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*1.6)
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*((1-math.cos(.7))/.1+1.4), abs=.005)
|
||||
assert controls.ford_path.path_angle == pytest.approx(sign*.5)
|
||||
assert controls.ford_path_controller.diagnostics['offset_overflow'] == pytest.approx(sign*1.4)
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*.2)
|
||||
assert controls.ford_path.path_offset == pytest.approx(sign*(1-math.cos(.28))/.04, abs=.005)
|
||||
assert controls.ford_path.path_angle == pytest.approx(sign*.28)
|
||||
assert controls.ford_path_controller.diagnostics['offset_overflow'] == 0.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ def test_feedback_builds_holds_and_unwinds_without_changing_c0(sign):
|
||||
baseline = tick(matched, sign*.004, sign*.004)
|
||||
assert controller.correction == pytest.approx(sign*.02)
|
||||
assert out.path_angle == pytest.approx(sign*.1)
|
||||
assert out.path_offset == baseline.path_offset == pytest.approx(.4)
|
||||
assert out.path_offset == baseline.path_offset == pytest.approx(sign*.1)
|
||||
for _ in range(100):
|
||||
out = tick(controller, sign*.004, sign*.004)
|
||||
assert controller.correction == pytest.approx(sign*.02)
|
||||
@@ -73,7 +73,7 @@ def test_pscm_limit_only_blocks_feedback_further_into_measured_turn(sign):
|
||||
for _ in range(100):
|
||||
out = tick(controller, 0., 0., pscm_limited=True)
|
||||
assert abs(out.path_angle) < .001
|
||||
assert out.path_offset == pytest.approx(.4)
|
||||
assert out.path_offset == pytest.approx(0.)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
"""C1 overflow allocation and release; no assumptions about PSCM response."""
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import ModelActionController
|
||||
@@ -7,16 +9,18 @@ from openpilot.selfdrive.controls.tests.test_ford_model_action import make_model
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
@pytest.mark.parametrize('speed', [3., 7., 20., 55.])
|
||||
@pytest.mark.parametrize('heading,offset', [(.4, .2), (.5, .2), (.6, .9), (.8, 2.3)])
|
||||
def test_clipped_base_heading_preserves_the_seven_metre_reference(sign, speed, heading, offset):
|
||||
@pytest.mark.parametrize('heading', [.4, .5, .6, .8])
|
||||
def test_clipped_base_heading_preserves_the_seven_metre_reference(sign, speed, heading):
|
||||
controller = ModelActionController()
|
||||
desired = sign*heading/max(7., speed)
|
||||
for _ in range(150):
|
||||
out = controller.update(straight(sign*.2), desired, current_curvature=desired, speed=speed, dt=.01)
|
||||
assert controller.correction == 0.
|
||||
assert out.path_offset == pytest.approx(sign*offset)
|
||||
arc = (1-math.cos(7.*desired))/desired
|
||||
assert controller.c0 == pytest.approx(arc+sign*7.*max(heading-.5, 0.))
|
||||
assert out.path_offset == pytest.approx(controller.c0, abs=.005)
|
||||
assert out.path_angle == pytest.approx(sign*min(heading, .5))
|
||||
assert out.path_offset+7.*out.path_angle == pytest.approx(sign*(.2+7.*heading))
|
||||
assert out.path_offset+7.*out.path_angle == pytest.approx(arc+sign*7.*heading, abs=.005)
|
||||
assert out.curvature == out.curvature_rate == 0.
|
||||
|
||||
|
||||
@@ -24,7 +28,7 @@ def test_clipped_base_heading_preserves_the_seven_metre_reference(sign, speed, h
|
||||
def test_combined_offset_is_clipped_after_allocating_heading(sign):
|
||||
controller = ModelActionController()
|
||||
for _ in range(200):
|
||||
out = controller.update(straight(sign*4.), sign*.1, current_curvature=sign*.1, speed=7., dt=.01)
|
||||
out = controller.update(straight(sign*4.), sign*.2, current_curvature=sign*.2, speed=7., dt=.01)
|
||||
assert out.path_offset == pytest.approx(sign*5.11)
|
||||
assert out.path_angle == pytest.approx(sign*.5)
|
||||
|
||||
@@ -35,7 +39,7 @@ def test_overflow_uses_existing_reference_with_short_model_path(sign):
|
||||
model = make_model([0., 1.], [0., sign*.2], [0., 0.])
|
||||
for _ in range(150):
|
||||
out = controller.update(model, sign*.03, current_curvature=sign*.03, speed=20., dt=.01)
|
||||
assert out.path_offset == pytest.approx(sign*.9)
|
||||
assert out.path_offset == pytest.approx(sign*((1-math.cos(.21))/.03+.7), abs=.005)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
@@ -43,16 +47,18 @@ def test_extra_offset_releases_at_existing_slew_without_stored_overflow(sign):
|
||||
controller = ModelActionController()
|
||||
for _ in range(200):
|
||||
controller.update(straight(sign*.2), sign*.04, current_curvature=sign*.04, speed=20., dt=.01)
|
||||
assert controller.c0 == pytest.approx(sign*2.3)
|
||||
for i in range(60):
|
||||
start = sign*((1-math.cos(.28))/.04+2.1)
|
||||
target = sign*(1-math.cos(.14))/.02
|
||||
assert controller.c0 == pytest.approx(start)
|
||||
for i in range(70):
|
||||
before = controller.c0
|
||||
out = controller.update(straight(sign*.2), sign*.02, current_curvature=sign*.02, speed=20., dt=.01)
|
||||
assert sign*controller.c0 >= .2-1e-10
|
||||
assert sign*controller.c0 >= sign*target-1e-10
|
||||
assert sign*controller.c0 <= sign*before+1e-10
|
||||
assert abs(controller.c0-before) <= .04+1e-10
|
||||
if i == 0:
|
||||
assert controller.c0 == pytest.approx(sign*2.26)
|
||||
assert out.path_offset == pytest.approx(sign*.2)
|
||||
assert controller.c0 == pytest.approx(start-sign*.04)
|
||||
assert out.path_offset == pytest.approx(sign*(1-math.cos(.14))/.02, abs=.005)
|
||||
assert out.path_angle == pytest.approx(sign*.4)
|
||||
assert controller.correction == 0.
|
||||
|
||||
@@ -64,7 +70,7 @@ def test_c1_feedback_saturation_does_not_spill_correction_into_c0(sign):
|
||||
out = controller.update(straight(sign*.2), sign*.02, current_curvature=0., speed=20., dt=.01)
|
||||
assert out.path_angle == pytest.approx(sign*.5)
|
||||
assert controller.correction == pytest.approx(sign*.1)
|
||||
assert out.path_offset == pytest.approx(sign*.2)
|
||||
assert out.path_offset == pytest.approx(sign*(1-math.cos(.14))/.02, abs=.005)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('sign', [-1., 1.])
|
||||
@@ -74,6 +80,6 @@ def test_overflow_is_base_geometry_with_existing_feedback_gates(sign, enabled, l
|
||||
for _ in range(150):
|
||||
out = controller.update(straight(sign*.2), sign*.03, current_curvature=sign*.02, speed=20., dt=.01,
|
||||
feedback_enabled=enabled, pscm_limited=limited)
|
||||
assert out.path_offset == pytest.approx(sign*.9)
|
||||
assert out.path_offset == pytest.approx(sign*((1-math.cos(.21))/.03+.7), abs=.005)
|
||||
assert out.path_angle == pytest.approx(sign*.5)
|
||||
assert controller.correction == 0.
|
||||
|
||||
@@ -30,7 +30,7 @@ def test_aligned_feedback_does_not_replace_current_feedforward_or_path(sign):
|
||||
out = controller.update(straight(sign*.4), sign*.01, current_curvature=sign*.008,
|
||||
feedback_curvature=sign*.008, speed=20., dt=.1)
|
||||
assert controller.proportional == controller.correction == 0.
|
||||
assert out.path_offset == pytest.approx(sign*.4)
|
||||
assert out.path_offset == pytest.approx(sign*(1-math.cos(.07))/.01, abs=.005)
|
||||
assert out.path_angle == pytest.approx(sign*.2)
|
||||
# Latest request is ahead of measured steering, but the delay-aligned target
|
||||
# has already been exceeded. P and I must use the explicit feedback target.
|
||||
|
||||
@@ -48,7 +48,7 @@ def test_actual_startup_priority(candidate, observer, fingerprint):
|
||||
assert type(selected.ford_path_controller) is FordModelActionController
|
||||
assert selected.ford_path_controller.core.proportional_gain == C1_PROPORTIONAL_GAIN == .50
|
||||
assert selected.ford_path_controller.core.integral_gain == C1_INTEGRAL_GAIN == .25
|
||||
assert selected.ford_path_controller.diagnostics['hypothesis'] == 'model-action-c1-pi-v7'
|
||||
assert selected.ford_path_controller.diagnostics['hypothesis'] == 'model-action-curvature-c0-pi-v8'
|
||||
else:
|
||||
assert selected.ford_path_controller is None
|
||||
assert selected.ford_model_action == candidate
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Independent arithmetic and C0-independence stress for production curvature-C0 PI."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openpilot.selfdrive.controls.lib import ford_model_action
|
||||
from tools.ford_pscm_lab.model_action_replay import WireCheck
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import ModelActionController
|
||||
|
||||
|
||||
def stress(cycles, output):
|
||||
wire = WireCheck()
|
||||
kp, ki = .5, .25
|
||||
rng = np.random.default_rng(20260913)
|
||||
core, mirror, other = (ModelActionController() for _ in range(3))
|
||||
for i in range(cycles):
|
||||
desired, measured = rng.uniform(-.1, .1, 2)
|
||||
speed, dt, offset, alternate = rng.uniform(.3, 55.), rng.uniform(.002, .1), *rng.uniform(-8., 8., 2)
|
||||
active, enabled, limited = i % 211 != 0, i % 97 != 0, i % 7 == 0
|
||||
feedback_dt = 0. if i % 5 == 0 else rng.uniform(.002, .15)
|
||||
before = core.c0, core.c1, core.correction
|
||||
args = {'speed': speed, 'dt': dt, 'feedback_dt': feedback_dt, 'active': active,
|
||||
'feedback_enabled': enabled, 'pscm_limited': limited}
|
||||
|
||||
def model(y):
|
||||
return SimpleNamespace(position=SimpleNamespace(x=[0., 20.], y=[y, y]), orientation=SimpleNamespace(z=[0., 0.]))
|
||||
|
||||
outputs = [core.update(model(offset), desired, current_curvature=measured, **args),
|
||||
mirror.update(model(-offset), -desired, current_curvature=-measured, **args),
|
||||
other.update(model(alternate), desired, current_curvature=measured, **args)]
|
||||
state = np.array([core.c0, core.c1, core.correction, core.proportional])
|
||||
np.testing.assert_allclose(state, -np.array([mirror.c0, mirror.c1, mirror.correction, mirror.proportional]), atol=1e-10, rtol=0.)
|
||||
assert (core.c1, core.correction, core.proportional) == (other.c1, other.correction, other.proportional)
|
||||
if active:
|
||||
distance = max(7., speed)
|
||||
base = min(.5, max(-.5, distance*desired))
|
||||
arc = (1.-math.cos(7.*desired))/desired if abs(desired) > 1e-6 else 24.5*desired
|
||||
target_c0 = min(5.11, max(-5.11, arc+7.*(distance*desired-base)))
|
||||
assert abs(core.c0-(before[0]+min(4.*dt, max(-4.*dt, target_c0-before[0])))) <= 1e-10
|
||||
p = kp*distance*(desired-measured) if enabled else 0.
|
||||
integral = 0.
|
||||
if enabled:
|
||||
increment = ki*(desired-measured)*speed*feedback_dt
|
||||
direction = measured if measured else before[1]
|
||||
if limited and increment*direction > 0.:
|
||||
increment = min(max(increment, min(-before[2], 0.)), max(-before[2], 0.))
|
||||
integral = before[2]
|
||||
if increment*integral < 0.:
|
||||
cancel = math.copysign(min(abs(increment), abs(integral)), increment)
|
||||
integral += cancel
|
||||
increment -= cancel
|
||||
lower, upper = max(-.5, before[1]-.5*dt), min(.5, before[1]+.5*dt)
|
||||
request = base+p+integral
|
||||
integral += min(max(increment, min(lower-request, 0.)), max(upper-request, 0.))
|
||||
assert core.proportional == p and abs(core.correction-integral) <= 1e-10
|
||||
request = min(.5, max(-.5, base+p+integral))
|
||||
assert abs(core.c1-(before[1]+min(.5*dt, max(-.5*dt, request-before[1])))) <= 1e-10
|
||||
assert abs(core.c0) <= 5.11+1e-10 and abs(core.c1) <= .5+1e-10 and abs(core.correction) <= 1.+1e-10
|
||||
else:
|
||||
assert np.all(state == 0.)
|
||||
for command in outputs:
|
||||
assert command.curvature == command.curvature_rate == 0.
|
||||
wire.check(command)
|
||||
result = {'cycles': cycles, 'kp': kp, 'ki': ki, 'controller_updates': 3*cycles,
|
||||
'can_round_trips': wire.count, 'seed': 20260913, 'independent_c0_comparisons': cycles,
|
||||
'checks': 'Independent scalar PI/unwind-first/anti-windup arithmetic, mirror symmetry, exact C0 independence, limits, slew, resets, CAN.',
|
||||
'source_sha256': {str(p): hashlib.sha256(p.read_bytes()).hexdigest() for p in
|
||||
(Path(__file__).resolve(), Path(ford_model_action.__file__).resolve())}}
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output.write_text(json.dumps(result, indent=2)+'\n')
|
||||
print(json.dumps(result))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--cycles', type=int, default=20_000)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
stress(args.cycles, args.output)
|
||||
@@ -0,0 +1,158 @@
|
||||
"""Circular-arc C0 experiment against the pinned continuous PI v7 controller.
|
||||
|
||||
Both controllers run on identical recorded motion. Replace only the encoder's
|
||||
C0 target, retaining path validity, C1 overflow, continuous PI feedback and
|
||||
output limits. No onroad selector or production module is modified.
|
||||
"""
|
||||
import argparse
|
||||
from collections import Counter
|
||||
import hashlib
|
||||
import math
|
||||
from pathlib import Path
|
||||
import json
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openpilot.selfdrive.controls.lib.ford_path import FordPath
|
||||
from tools.ford_pscm_lab.feedback_replay import OPENDBC, original_controller
|
||||
from tools.ford_pscm_lab.model_action_replay import WireCheck, field_checks, sample, table, verify_dependency
|
||||
|
||||
|
||||
BASELINE = '08b3a14ad46260fda0f8d3a1c2cee2d272504153'
|
||||
GAIN = .50
|
||||
STATION = 7.
|
||||
|
||||
|
||||
def arc_offset(curvature):
|
||||
"""Circular-arc y at 7 m arc length, starting at zero position/heading.
|
||||
|
||||
Equivalent to (1-cos(7*k))/k; sinc avoids cancellation near zero.
|
||||
This is reference geometry, not an inverse PSCM response model.
|
||||
"""
|
||||
half_heading = .5*STATION*curvature
|
||||
sinc = math.sin(half_heading)/half_heading if half_heading else 1.
|
||||
return .5*curvature*STATION**2*sinc**2
|
||||
|
||||
|
||||
def make_controller(curvature_c0=False):
|
||||
# original_controller creates fresh, isolated module globals each time.
|
||||
# Patching this clone cannot alter production or the other replay controller.
|
||||
original, source_hash = original_controller(BASELINE)
|
||||
controller = type(original)(proportional_gain=GAIN)
|
||||
if curvature_c0:
|
||||
namespace = controller.core.update.__globals__
|
||||
original_encoder = namespace['encode_model_action']
|
||||
|
||||
def encoder(model, desired_curvature, speed):
|
||||
target = original_encoder(model, desired_curvature, speed)
|
||||
if not target.valid:
|
||||
return target
|
||||
return FordPath(True, arc_offset(desired_curvature), target.path_angle, 0., 0.)
|
||||
|
||||
namespace['encode_model_action'] = encoder
|
||||
controller.hypothesis = 'curvature-c0-continuous-pi-offline-v1'
|
||||
controller.reset()
|
||||
return controller, source_hash
|
||||
|
||||
|
||||
def replay(directory, output):
|
||||
directory, output = directory.resolve(), output.resolve()
|
||||
if output == directory or directory in output.parents:
|
||||
raise ValueError('Output must be outside the source route directory')
|
||||
verify_dependency(OPENDBC)
|
||||
metadata = json.loads((directory/'metadata.json').read_text())
|
||||
with np.load(directory/'route.npz', allow_pickle=False) as z:
|
||||
r = {k: table(z, k) for k in ('controls', 'cs', 'cc', 'model', 'params', 'pscm')}
|
||||
if 'maneuver' in z and len(z['maneuver']):
|
||||
raise ValueError('Selected maneuver publications require a separate reference reconstruction')
|
||||
with np.load(directory/'model_paths.npz', allow_pickle=False) as z:
|
||||
model_ns, paths = z['ns'], z['paths']
|
||||
c, t = r['controls'], r['controls']['t']
|
||||
assert all(np.all(np.diff(stream['t']) >= 0.) for stream in r.values())
|
||||
cs, pa, ps = (sample(r[k], t) for k in ('cs', 'params', 'pscm'))
|
||||
cc = sample(r['cc'], t, nearest=True)
|
||||
mi = np.clip(np.searchsorted(r['model']['ns'], c['model_ns']), 0, len(r['model']['ns'])-1)
|
||||
exact = r['model']['ns'][mi] == c['model_ns']
|
||||
np.testing.assert_array_equal(model_ns, r['model']['ns'])
|
||||
models = [SimpleNamespace(position=SimpleNamespace(x=p[0], y=p[1]), orientation=SimpleNamespace(z=p[2])) for p in paths]
|
||||
baseline, baseline_hash = make_controller()
|
||||
candidate, candidate_hash = make_controller(True)
|
||||
assert baseline_hash == candidate_hash
|
||||
controllers = (baseline, candidate)
|
||||
shape = (2, len(t))
|
||||
commands = np.zeros((*shape, 4))
|
||||
valid = np.zeros(shape, bool)
|
||||
names = ('heading_proportional', 'heading_correction', 'heading_feedforward', 'feedback_enabled',
|
||||
'offset_overflow', 'pscm_limited', 'pscm_status_fresh')
|
||||
diagnostic = {name: np.zeros(shape) for name in names}
|
||||
status_counts = [Counter(), Counter()]
|
||||
wire = WireCheck()
|
||||
for i, now in enumerate(t):
|
||||
model_time = r['model']['t'][mi[i]]
|
||||
service_valid = bool(c['valid'][i] and cc['valid'][i] and cs['valid'][i] and cs['can_valid'][i]
|
||||
and pa['valid'][i] and r['model']['valid'][mi[i]] and exact[i]
|
||||
and abs(cc['t'][i]-now) < .005 and 0. <= now-pa['t'][i] <= .15)
|
||||
model = models[mi[i]] if exact[i] else None
|
||||
status = SimpleNamespace(valid=bool(ps['valid'][i] and ps['status_valid'][i]), canMonoTime=round(ps['stamp'][i]*1e9),
|
||||
limit=int(ps['limit'][i]), lateralState=int(ps['lateral_state'][i]), denied=bool(ps['denied'][i]))
|
||||
kwargs = {'speed': cs['speed'][i], 'yaw_rate': cs['yaw'][i], 'now': now, 'measurement_time': cs['t'][i],
|
||||
'model_time': model_time, 'reference_time': model_time, 'active': bool(cc['active'][i]), 'valid': service_valid,
|
||||
'current_curvature': c['measured'][i], 'driver_pressed': bool(cs['pressed'][i]),
|
||||
'driver_torque': cs['torque'][i], 'pscm_status': status}
|
||||
for k, controller in enumerate(controllers):
|
||||
command = controller.update(model, c['desired'][i], **kwargs)
|
||||
commands[k, i] = command.path_offset, command.path_angle, command.curvature, command.curvature_rate
|
||||
valid[k, i] = command.valid
|
||||
d = controller.diagnostics
|
||||
status_counts[k][d['status']] += 1
|
||||
for name in names:
|
||||
diagnostic[name][k, i] = d.get(name, 0.)
|
||||
wire.check(command)
|
||||
np.testing.assert_array_equal(valid[0], valid[1])
|
||||
for name in ('heading_proportional', 'heading_feedforward', 'feedback_enabled', 'offset_overflow', 'pscm_limited', 'pscm_status_fresh'):
|
||||
np.testing.assert_array_equal(diagnostic[name][0], diagnostic[name][1])
|
||||
assert status_counts[0] == status_counts[1]
|
||||
np.testing.assert_array_equal(commands[0, :, 1:], commands[1, :, 1:])
|
||||
np.testing.assert_array_equal(diagnostic['heading_correction'][0], diagnostic['heading_correction'][1])
|
||||
for k in range(2):
|
||||
field_checks(commands[k], valid[k], t)
|
||||
assert np.all(abs(diagnostic['heading_correction'][k]) <= 1.+1e-10)
|
||||
assert np.all(diagnostic['heading_correction'][k, diagnostic['feedback_enabled'][k] == 0.] == 0.)
|
||||
with np.load(directory/'angles.npz') as a:
|
||||
np.testing.assert_allclose(a['t'], t-metadata['t0'], atol=1e-8, rtol=0.)
|
||||
clean = a['clean'] & valid[0] & (diagnostic['feedback_enabled'][0] != 0.)
|
||||
weight = a['weight']
|
||||
report = {'scope': __doc__, 'baseline_revision': BASELINE, 'baseline_source_sha256': baseline_hash,
|
||||
'opendbc_revision': OPENDBC, 'proportional_gain': GAIN, 'integral_gain': .25, 'station_m': STATION,
|
||||
'cycles': len(t), 'controller_updates': 2*len(t), 'active_cycles_per_controller': int(valid[0].sum()),
|
||||
'can_round_trips': wire.count, 'status_counts': dict(status_counts[0]),
|
||||
'same_activation_p_feedforward_overflow_and_feedback_gates': True,
|
||||
'changed_c1_cycles': int(np.sum(commands[0, :, 1] != commands[1, :, 1])),
|
||||
'max_abs_c1_change_rad': float(abs(commands[0, :, 1]-commands[1, :, 1]).max()),
|
||||
'changed_integral_cycles': int(np.sum(diagnostic['heading_correction'][0] != diagnostic['heading_correction'][1])),
|
||||
'pscm_status_fresh_active_seconds': float(weight[valid[0] & (diagnostic['pscm_status_fresh'][0] != 0.)].sum()),
|
||||
'clean_seconds': float(weight[clean].sum()), 'car': metadata['car'], 'identities': metadata['identities'],
|
||||
'source_sha256': {str(p): hashlib.sha256(p.read_bytes()).hexdigest() for p in
|
||||
(directory/'route.npz', directory/'model_paths.npz', directory/'metadata.json',
|
||||
directory/'angles.npz', Path(__file__).resolve())},
|
||||
'limitations': ['No new steering trace or physical tracking score: recorded motion stays fixed.',
|
||||
'The baseline is v7 on every route, regardless of its recorded controller version.',
|
||||
'C1 and integral state match exactly on fixed recorded motion; physical feedback may differ.',
|
||||
'Publication time proxies computation time; full SubMaster state is unavailable.',
|
||||
'The synthetic arc keeps the original model-health gates for a controlled comparison.']}
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
np.savez_compressed(output/'commands.npz', t=t-metadata['t0'], commands=commands, valid=valid, clean=clean, weight=weight,
|
||||
desired_curvature=c['desired'], measured_curvature=c['measured'], desired_angle=c['desired_angle'],
|
||||
actual_angle=c['actual_angle'], speed=cs['speed'], **diagnostic)
|
||||
(output/'report.json').write_text(json.dumps(report, indent=2, allow_nan=False)+'\n')
|
||||
print(json.dumps({k: report[k] for k in ('cycles', 'can_round_trips', 'changed_c1_cycles', 'max_abs_c1_change_rad', 'clean_seconds')}))
|
||||
return report
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('directory', type=Path)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
replay(args.directory, args.output)
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Verify production commands against the archived curvature-C0 offline candidate.
|
||||
|
||||
Fixed recorded motion verifies integration parity, not physical tracking.
|
||||
"""
|
||||
import argparse
|
||||
from concurrent.futures import ProcessPoolExecutor, as_completed
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openpilot.selfdrive.controls.lib import ford_model_action
|
||||
from openpilot.selfdrive.controls.lib.ford_model_action import select_model_action_controller
|
||||
from opendbc.car.ford.values import FordFlags
|
||||
from tools.ford_pscm_lab.feedback_replay import OPENDBC
|
||||
from tools.ford_pscm_lab.model_action_replay import WireCheck, field_checks, sample, table, verify_dependency
|
||||
|
||||
|
||||
ROUTES = ('112', '113', '114', '115', '116', '117', 'a0', 'a2', 'a5', 'a9', 'b8', 'b9', 'ca', 'raptor02')
|
||||
SELECTED = 1
|
||||
|
||||
|
||||
def replay(label, output):
|
||||
directory = Path('.cache/ford_raptor_route02' if label == 'raptor02' else f'.cache/ford_route{label}').resolve()
|
||||
previous = Path('.cache/ford_curvature_c0_v7')/label
|
||||
output = (output/label).resolve()
|
||||
if output == directory or directory in output.parents or output == previous.resolve():
|
||||
raise ValueError('Output must preserve previous experiments and input extracts')
|
||||
verify_dependency(OPENDBC)
|
||||
archived_report = json.loads((previous/'report.json').read_text())
|
||||
assert archived_report['baseline_revision'] == '08b3a14ad46260fda0f8d3a1c2cee2d272504153'
|
||||
assert archived_report['changed_c1_cycles'] == archived_report['changed_integral_cycles'] == 0
|
||||
for filename in ('route.npz', 'model_paths.npz', 'metadata.json'):
|
||||
path = directory/filename
|
||||
assert hashlib.sha256(path.read_bytes()).hexdigest() == archived_report['source_sha256'][str(path)]
|
||||
archived = dict(np.load(previous/'commands.npz', allow_pickle=False))
|
||||
metadata = json.loads((directory/'metadata.json').read_text())
|
||||
with np.load(directory/'route.npz', allow_pickle=False) as raw:
|
||||
r = {key: table(raw, key) for key in ('controls', 'cs', 'cc', 'model', 'params', 'pscm')}
|
||||
if 'maneuver' in raw and len(raw['maneuver']):
|
||||
raise ValueError('Maneuver reference reconstruction is not available in this sweep')
|
||||
with np.load(directory/'model_paths.npz', allow_pickle=False) as raw:
|
||||
model_ns, paths = raw['ns'], raw['paths']
|
||||
c, t = r['controls'], r['controls']['t']
|
||||
assert all(np.all(np.diff(stream['t']) >= 0.) for stream in r.values())
|
||||
np.testing.assert_allclose(archived['t'], t-metadata['t0'], rtol=0., atol=1e-8)
|
||||
cs, pa, ps = (sample(r[key], t) for key in ('cs', 'params', 'pscm'))
|
||||
cc = sample(r['cc'], t, nearest=True)
|
||||
mi = np.clip(np.searchsorted(r['model']['ns'], c['model_ns']), 0, len(r['model']['ns'])-1)
|
||||
exact = r['model']['ns'][mi] == c['model_ns']
|
||||
np.testing.assert_array_equal(model_ns, r['model']['ns'])
|
||||
models = [SimpleNamespace(position=SimpleNamespace(x=p[0], y=p[1]), orientation=SimpleNamespace(z=p[2])) for p in paths]
|
||||
cp = SimpleNamespace(brand='ford', flags=int(FordFlags.CANFD))
|
||||
assert select_model_action_controller(cp, False) is None
|
||||
controller = select_model_action_controller(cp, True)
|
||||
assert controller is not None
|
||||
assert (controller.core.proportional_gain, controller.core.integral_gain) == (.5, .25)
|
||||
shape = (len(t),)
|
||||
commands = np.zeros((*shape, 4))
|
||||
valid = np.zeros(shape, bool)
|
||||
names = ('heading_proportional', 'heading_correction', 'heading_feedforward', 'feedback_enabled', 'offset_overflow', 'pscm_limited')
|
||||
diagnostics = {name: np.zeros(shape) for name in names}
|
||||
wire = WireCheck()
|
||||
for i, now in enumerate(t):
|
||||
model_time = r['model']['t'][mi[i]]
|
||||
services_valid = bool(c['valid'][i] and cc['valid'][i] and cs['valid'][i] and cs['can_valid'][i]
|
||||
and pa['valid'][i] and r['model']['valid'][mi[i]] and exact[i]
|
||||
and abs(cc['t'][i]-now) < .005 and 0. <= now-pa['t'][i] <= .15)
|
||||
model = models[mi[i]] if exact[i] else None
|
||||
status = SimpleNamespace(valid=bool(ps['valid'][i] and ps['status_valid'][i]), canMonoTime=round(ps['stamp'][i]*1e9),
|
||||
limit=int(ps['limit'][i]), lateralState=int(ps['lateral_state'][i]), denied=bool(ps['denied'][i]))
|
||||
kwargs = {'speed': cs['speed'][i], 'yaw_rate': cs['yaw'][i], 'now': now, 'measurement_time': cs['t'][i],
|
||||
'model_time': model_time, 'reference_time': model_time, 'active': bool(cc['active'][i]), 'valid': services_valid,
|
||||
'current_curvature': c['measured'][i], 'driver_pressed': bool(cs['pressed'][i]),
|
||||
'driver_torque': cs['torque'][i], 'pscm_status': status}
|
||||
command = controller.update(model, c['desired'][i], **kwargs)
|
||||
commands[i] = command.path_offset, command.path_angle, command.curvature, command.curvature_rate
|
||||
valid[i] = command.valid
|
||||
for name in names:
|
||||
diagnostics[name][i] = controller.diagnostics.get(name, 0.)
|
||||
wire.check(command)
|
||||
field_checks(commands, valid, t)
|
||||
np.testing.assert_array_equal(commands, archived['commands'][SELECTED])
|
||||
np.testing.assert_array_equal(valid, archived['valid'][SELECTED])
|
||||
for name in names:
|
||||
np.testing.assert_array_equal(diagnostics[name], archived[name][SELECTED])
|
||||
output.mkdir(parents=True, exist_ok=True)
|
||||
sources = (directory/'route.npz', directory/'model_paths.npz', directory/'metadata.json',
|
||||
previous/'report.json', previous/'commands.npz', Path(__file__).resolve(), Path(ford_model_action.__file__).resolve())
|
||||
result = {'scope': __doc__, 'route': label, 'cycles': len(t), 'can_round_trips': wire.count,
|
||||
'production_matches_archived_trial_exactly': True, 'opendbc_revision': OPENDBC,
|
||||
'hypothesis': controller.hypothesis, 'kp': .5, 'ki': .25,
|
||||
'source_sha256': {str(path.resolve()): hashlib.sha256(path.read_bytes()).hexdigest() for path in sources}}
|
||||
(output/'report.json').write_text(json.dumps(result, indent=2, allow_nan=False)+'\n')
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('--routes', nargs='+', choices=ROUTES, default=ROUTES)
|
||||
parser.add_argument('--output', type=Path, required=True)
|
||||
parser.add_argument('--workers', type=int, default=4)
|
||||
args = parser.parse_args()
|
||||
with ProcessPoolExecutor(max_workers=args.workers) as pool:
|
||||
jobs = {pool.submit(replay, label, args.output): label for label in args.routes}
|
||||
for job in as_completed(jobs):
|
||||
result = job.result()
|
||||
print(json.dumps({'completed': jobs[job], 'cycles': result['cycles'], 'can_checks': result['can_round_trips']}), flush=True)
|
||||
Reference in New Issue
Block a user