Extruder smoother fit improvements#909
Conversation
|
Heads-up for reviewers: while auditing the code downstream of this PR's numpy 2.x fix, we found that the smoother-autotune path it unblocks has a further bug — That fix is in the companion PR #913 (no file overlap with this one; both bugs also exist independently of this PR on numpy 1.x installs). Reviewing the two together gives a working end-to-end smoother calibration flow. |
|
Thanks — this is an unusually well-validated PR, and the core claims held up under independent verification: the 3hump_ei comma fix restores the intended expansion structure (each order's coefficients sum to ~0 across impulses, zeroth to 1), and all fitted kernels keep unit integral to ~1e-7 with zero endpoints at machine precision across zeta 0.05/0.1/0.2. One correction for the commit message / PR body: on the numpy versions pinned in scripts/klippy-requirements.txt (2.0.2/2.2.2) Needed before merge: Signed-off-by lines on every commit (docs/CONTRIBUTING.md), and please drop the Co-Authored-By attribution trailers in the same rebase. Squashing the ruff-format fixup commit into the commit it fixes would also be nice. Two minor notes, not blocking:
Will look at #913 alongside this for the end-to-end smoother calibration flow. |
The first row of the amplitude expansion coefficients in get_3hump_ei_shaper was written as '3.29160 - 1.44380', evaluating to a single arithmetic result instead of two list elements. Since the expansion loop derives its order from len(a[0]), this silently truncated the damping-ratio expansion of both A and T to quadratic order for every impulse of the shaper. With the comma restored, the coefficients of each expansion order sum to ~0 across the impulses (and the zeroth order sums to 1), matching the intended structure where sum(A) == 1 independent of damping ratio. Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>
Rework how the extruder PA smoother polynomial is fitted to the shaped step-response velocity profiles: - Compute velocities analytically from the closed-form derivative of the damped step response instead of first-order finite differences of the position response. This removes the differencing error, a half-sample time misalignment, and a dt off-by-one in the old grids. - Solve a proper equality-constrained least squares via its KKT system instead of overwriting rows of the normal equations with constraint rows, which discarded part of the fit information. The constraints themselves are unchanged: unit integral, zero endpoints, and zero endpoint derivatives for smooth_* shapers (right endpoint only for 3hump_ei). - Use a Legendre basis instead of monomials, reducing the condition number of the solve from up to ~7e9 down to ~1e5. - Iteratively penalize negative values of the fitted kernel toward zero. The old row-overwrite happened to produce mostly non-negative kernels; the corrected least squares would otherwise dip up to ~2% of the peak below zero. Relative RMS fit error against the target velocity profile at damping ratio 0.1, before -> after: 3hump_ei 17.9% -> 8.3%, zvd 8.3% -> 7.5%, 2hump_ei 6.5% -> 5.6%, ei 5.8% -> 5.4%, smooth_si 1.4% -> 0.3%, smooth_ei 0.9% -> 0.3%. Polynomial orders and the C interface are unchanged. Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>
Recent numpy removed APIs this code relied on: np.trapz (removed in numpy 2.4 in favor of np.trapezoid) and the implicit conversion of single-element arrays to Python scalars (now a hard error). On the numpy versions currently pinned in scripts/klippy-requirements.txt (2.0.2 / 2.2.2) np.trapz still exists as a deprecated alias and the single-element scalar conversion only warns, so a fresh pinned install does not crash. On newer numpy the failures are real, however -- both verified on 2.5.1: - SHAPER_CALIBRATE raises 'module numpy has no attribute trapz' in _get_smoother_smoothing when fitting any input smoother - scripts/graph_shaper.py raises 'only 0-dimensional arrays can be converted to Python scalars' in bisect() This matters for unpinned environments, tools such as Shake&Tune, and future numpy bumps. Use np.trapezoid when available (keeping an np.trapz fallback for old numpy 1.x installs), and extract the scalar explicitly in graph_shaper. Also parenthesize the find_max_accel lambda so shaper_calibrate.py passes ruff format on both the pinned pre-commit ruff (0.4.5) and the newer version CI resolves from pyproject.toml. Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>
Rework estimate_shaper and estimate_smoother to compute the shaped step-response velocity from the closed-form derivative of the step response instead of first-order finite differences of the position response, mirroring the extruder_smoother rework: - Add step_response_velocity, shared with extruder_smoother (which previously carried its own copy). - estimate_shaper: additionally evaluate the velocity exactly at the impulse times, where it has kinks that the discrete grid straddles; refine smooth grid minima with a parabolic fit. - estimate_smoother: convolve the smoother directly with the analytic velocity; the smoothed velocity is C^1, so parabolic refinement of the discrete minimum suffices. This removes the finite-differencing error, a half-sample time misalignment, and a dt off-by-one. Worst-case absolute error of the residual-vibration estimates vs a converged reference drops from 0.002-0.005 to below 0.002 across all shapers and test damping ratios, and the estimate for an unshaped response is now exact to ~1e-8 (previously ~1e-5). Affects SHAPER_CALIBRATE scoring and scripts/graph_shaper.py output only marginally. Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>
The 3hump_ei coefficient fix and the reworked extruder smoother fit both change motion slightly after updating; record it in Config_Changes.md so users understand the behavior shift after an update. Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>
0cbcd3d to
9c98bdd
Compare
|
@rogerlz All should be fixed now |
Summary
Four related improvements to the input-shaping numerics, all host-side Python (no MCU firmware change, no C interface change):
shaper_defs.py— fix a missing comma that silently truncated the3hump_eishaper's damping-ratio expansion (changes actual toolhead motion for 3hump_ei users).extruder_smoother.py— rework the fit that generates the extruder pressure-advance smoothing kernels: analytic velocities, properly constrained least squares, well-conditioned basis, soft non-negativity.shaper_calibrate.py/graph_shaper.py— fix numpy 2.x incompatibilities (np.trapzremoval in numpy 2.4, size-1-array scalar conversion becoming a hard error) that breakSHAPER_CALIBRATEsmoother fitting on newer numpy. Not on the versions currently pinned inscripts/klippy-requirements.txt(where they only warn), but on unpinned installs, Shake&Tune, and future numpy bumps.shaper_calibrate.py— compute the residual-vibration estimates from analytic velocities with exact evaluation at impulse-time kinks, improving calibration scoring accuracy.Background: what the extruder smoother does
With input shaping active, the toolhead's real velocity differs from the commanded one. To keep pressure advance synchronized, the extruder uses a polynomial kernel
w(t)whose convolution with the commanded motion approximates the shaped toolhead velocity. That smoothed velocity is what every PA model consumes (kin_extruder.c:pa_func(position, pa_velocity, params)), so the kernel quality affects linear and non-linear PA (tanh, reciprocal) alike — the model only maps the smoothed velocity to an advance distance. With PA disabled the kernel is inert.This PR improves how those kernels are fitted, and fixes adjacent correctness/compatibility issues found along the way.
Changes
1.
shaper_defs.py— 3hump_ei expansion coefficient typoThe first row of the amplitude expansion coefficients in
get_3hump_ei_shaperread3.29160 - 1.44380— arithmetic instead of two list elements. Since the expansion loop derives its order fromlen(a[0]), the cubic damping-ratio terms of bothAandTwere silently dropped for every impulse. Verification of the fix: with the comma restored, each expansion order sums to ~0 across the five impulses (zeroth order sums to 1), which is the intended structure makingsum(A) = 1independent of damping ratio.This changes 3hump_ei impulse timings/amplitudes at nonzero damping — the actual shaper behavior, not just its predicted score. Effect on the residual-vibration curve (up to 0.04 in vibration ratio at ζ=0.1) is shown in the figure under change 4.
2.
extruder_smoother.py— kernel fit reworkThree compounding issues fixed in the fitting procedure:
v(t) = ω·e^(−ζωt)·sin(ω_d·t)/√(1−ζ²), instead of first-order finite differences of the position response — removing the differencing error, a half-sample time misalignment, and adtoff-by-one.smooth_*) were previously imposed by overwriting rows of the normal equations, discarding part of the fit information. Now solved exactly as a KKT system.Relative RMS fit error vs the band-averaged target velocity profile (ζ=0.1):
Top: the old 3hump_ei kernel swings anti-phase to its target around t=0; the new one tracks it. Bottom: fit error for every shaper.
3. numpy 2.x compatibility fixes
numpy is deprecating and removing APIs this code relied on:
np.trapz(removed in numpy 2.4 in favor ofnp.trapezoid) and the implicit conversion of single-element arrays to Python scalars (now a hard error). On the versions currently pinned inscripts/klippy-requirements.txt(2.0.2 / 2.2.2)np.trapzstill exists as a deprecated alias and the size-1 scalar conversion only warns, so a fresh pinned install does not crash. On newer numpy the failures are real, though — both verified on 2.5.1:SHAPER_CALIBRATEraisesAttributeError(module numpy has no attribute trapz) in_get_smoother_smoothingwhenever it fits anysmooth_*candidate.scripts/graph_shaper.pyraisesonly 0-dimensional arrays can be converted to Python scalarsinbisect().This matters for unpinned environments, Shake&Tune (which imports these modules from the running Klipper installation), and future numpy bumps. Fixed with
np.trapezoidwhen available (annp.trapzfallback keeps numpy 1.x installs working — the fix is numerically a no-op there) and explicit scalar extraction in graph_shaper.4.
shaper_calibrate.py— analytic velocities in vibration estimatesestimate_shaper/estimate_smootherhad the same finite-difference construction as the old extruder fit. They now share thestep_response_velocityhelper. Two accuracy details beyond the shared change: the shaped step velocity has V-shaped kinks at the impulse times which a discrete grid straddles — it is now evaluated exactly at the impulse times — and smooth grid minima get parabolic refinement (sufficient on its own for smoothers, whose smoothed velocity is C¹).Worst-case absolute error of the estimates vs a converged reference drops from 0.002–0.005 to below 0.002 across all shapers and test damping ratios (0.075/0.1/0.15); the estimate for an unshaped response is now exact to ~1e-8 (previously ~1e-5). Impact on
SHAPER_CALIBRATE/Shake&Tune scoring is marginal (sub-1%) but systematic.Left: the kink-straddling mechanism. Right: effect of the 3hump_ei coefficient fix on the shaper's actual residual-vibration curve. Bottom: estimator worst-case error before/after.
Real-motion validation
Simulated end-to-end on realistic motion (commanded velocity → input shaper/smoother → resonating system at 55.8 Hz, ζ=0.1 → extruder kernel convolution), to establish what the rework does and does not change in practice.
Smooth shapers: behaviorally unchanged (no regression). On gyroid-like alternating-direction moves (100 mm/s, 20 Hz direction modulation, 0.3 mm segments), before/after kernels coincide — sync error RMS 2.12→2.13 mm/s (smooth_ei) and 2.02→2.01 mm/s (smooth_si). Discrete shapers on accel-limited moves are likewise within ±3% in their design frequency ranges. The residual sync error is the kernel's finite time window lagging the direction modulation — identical before and after, not addressable by kernel shape.
Where it visibly improves: 3hump_ei at corner junctions. Step-like velocity changes (sharp corners, segmented-arc junctions) exercise exactly what the fit targets. With the printer resonance inside the band a calibrated 3hump_ei operates in (0.54–0.66 × shaper frequency), a 100→20 mm/s corner produces a +8.4 mm/s transient over-extrusion spike with the old kernel vs 2.3 mm/s with the new one (3.7× lower peak, 1.8× lower RMS). The sweep panel shows the new kernel better across the entire design band; above ~68 Hz (resonance ≈ shaper frequency — outside any calibrated 3hump_ei's operating point) the old kernel happens to score better, shown for completeness.
Verification
input_shaper.pycall path (normalize_coeffs=False+ smoother offset) produces well-formed coefficients for all shapers; polynomial orders unchanged, so the C-side 12-coefficient cap (integrate.h) andextruder_set_smoothing_paramsinterface are untouched.find_best_shaperon synthetic resonance data completes on numpy 2.5 (previously crashed);scripts/graph_shaper.pyandscripts/get_extruder_smoother.pyrender correctly.ruff check/ruff formatpass with both the pinned pre-commit ruff (0.4.5) and the newer ruff that CI resolves frompyproject.toml.Deployment notes
git pull+ restart the Klipper service. No MCU reflash; the chelper C library is unchanged.shaper_calibrate/shaper_defsfrom the running installation.pr-assets/extruder-smoother-fit.Signed-off-by: Åsmund Collin aakjaergaard@gmail.com