JAlcocerTech E-books

Concepts Primer

Top-level map: Field Guide

A standalone reference for the signal-processing and constrained-dynamics concepts that the slider-crank / multi-cylinder chapters lean on. Each section is short, concrete, and grounded in the code and numbers we’ve actually produced in this project — not a textbook retread.

Intended audience: a mechanical engineer who’s comfortable with rigid-body mechanics but may want a quick refresher on the maths side before (or after) reading the analysis chapters.

Jump table:

  1. FFT (Fast Fourier Transform)
  2. Phasors and harmonics (incl. 2.1 2D phasors · 2.2 Firing-cycle phasors)
  3. Vibration spectrum
  4. DC component
  5. Saddle-point system
  6. Signal processing
  7. Transmissibility
  8. Transfer function vs FRF (and Nyquist)
  9. LMS adaptive cancellation
  10. Glossary of symbols used in the series

1. FFT (Fast Fourier Transform)

What it is. An algorithm that takes a time-domain signal (a list of numbers sampled at uniform intervals) and returns the complex amplitudes of each sinusoidal frequency that adds up to it. The mathematical object is the discrete Fourier transform (DFT); the “F” in FFT just means the clever O(N log N) algorithm for computing it.

Input: x(t)x(t) sampled at N points with spacing dtdt. Output: N complex numbers X[k]X[k] where each X[k]X[k] is the amplitude + phase of the sinusoidal component at frequency fk=k/(Ndt)f_k = k / (N \cdot dt).

Reconstruction (inverse):

x(tn)=1NkX[k]exp ⁣(j2πknN)x(t_n) = \frac{1}{N} \sum_k X[k] \cdot \exp\!\left(j \cdot 2\pi \cdot \frac{k \cdot n}{N}\right)

For real-valued signals (everything we deal with), X[k]X[k] and X[Nk]X[N-k] are complex conjugates — they encode the same physical information — so numpy provides rfft which returns only the first N/2+1N/2 + 1 values.

Where we use it. In fft_amplitude() in _common.py:

def fft_amplitude(signal, N):
    """One-sided amplitude spectrum: |rfft(x)| * 2/N with DC un-doubled."""
    raw = np.abs(rfft(signal))
    amp = raw * 2.0 / N
    amp[0] *= 0.5
    return amp

Three things worth knowing about this formula:

  • The 2/N scaling converts the raw FFT output into peak amplitudes of the sinusoidal components. If x(t)=Acos(2πft)x(t) = A \cdot \cos(2\pi \cdot f \cdot t), then fft_amplitude(x)[k] =A= A at the bin kk corresponding to ff. Without this scaling, you’d get AN/2A \cdot N/2 instead — correct but unphysical.
  • DC is un-doubled (amp[0] *= 0.5) because a DC component isn’t a “cosine peak” that’s spread across positive and negative frequencies — it’s just itself. It should read as the constant offset, not twice the offset.
  • Sampling must be exact. In our scripts we use n_samples = 1024 over n_rev = 10 full crank revolutions with endpoint=False. The endpoint=False makes the sampled grid perfectly periodic with period nrevTcrankn_\text{rev} \cdot T_\text{crank}, so harmonics of fcrankf_\text{crank} fall on exact FFT bins (bin=n×nrev\text{bin} = n \times n_\text{rev}). No spectral leakage, no windowing needed. If sampling isn’t exact, harmonics smear across neighbouring bins and you’d need a Hanning or similar window to fix it.

Concrete example. Feed the single-cylinder driving torque τ(t)\tau(t) into fft_amplitude() and read bin 2nrev=202 \cdot n_\text{rev} = 20:

fft_amplitude(tau_drive, 1024)[20]=4.36 N\cdotpm\texttt{fft\_amplitude(tau\_drive, 1024)[20]} = 4.36 \text{ N·m}

That’s the 2nd-harmonic amplitude — the “secondary” component of the driving torque.

This is the number that we then multiply by a phasor-sum factor when building multi-cylinder engines.


2. Phasors and harmonics

Harmonics. A periodic signal with fundamental frequency ff can always be written as a sum of sinusoids at f,2f,3f,f, 2f, 3f, \ldots (the harmonics of the fundamental, also called 1×, 2×, 3× in shorthand).

For our engine running at 10 RPM, the fundamental is fcrank=10/60=0.167f_\text{crank} = 10/60 = 0.167 Hz. Every vibration signal the engine produces can be expanded as

x(t)=DC+A1cos(2πfcrankt+ϕ1)+A2cos(2π(2fcrank)t+ϕ2)+A3cos(2π(3fcrank)t+ϕ3)+x(t) = \text{DC} + A_1 \cos(2\pi f_\text{crank} t + \phi_1) + A_2 \cos(2\pi (2 f_\text{crank}) t + \phi_2) + A_3 \cos(2\pi (3 f_\text{crank}) t + \phi_3) + \cdots

Each term has two numbers attached: an amplitude AnA_n (how strong it is) and a phase ϕn\phi_n (when its peak occurs within the cycle).

Phasors. Packaging those two numbers into a single complex number is the phasor trick:

Anexp(jϕn)"amplitude An at phase ϕn"A_n \cdot \exp(j \phi_n) \quad \equiv \quad \text{"amplitude } A_n \text{ at phase } \phi_n\text{"}

Using Euler’s formula exp(jϕ)=cosϕ+jsinϕ\exp(j\phi) = \cos\phi + j\sin\phi, this gets us Acos(2πft+ϕ)=Re[Aexp(jϕ)exp(j2πft)]A \cos(2\pi f t + \phi) = \operatorname{Re}[A \exp(j\phi) \cdot \exp(j 2\pi f t)]. The phasor absorbs the amplitude and phase; the exp(j2πft)\exp(j 2\pi f t) rotates at the harmonic’s angular speed.

Why phasors are amazing for multi-cylinder engines. Each cylinder in a multi-cylinder engine contributes the same waveform but shifted in time by its crank-phase offset ϕi\phi_i. In phasor land, a time shift of ϕi/ω\phi_i / \omega turns into a multiplication by exp(jnϕi)\exp(j \cdot n \cdot \phi_i) — so summing across cylinders at harmonic nn is

F^total[n]=F^single[n](iexp(jnϕi))\hat{F}_\text{total}[n] = \hat{F}_\text{single}[n] \cdot \left( \sum_i \exp(j \cdot n \cdot \phi_i) \right)

The spatial/timing geometry of the engine condenses into a single complex number per harmonic: iexp(jnϕi)\sum_i \exp(j \cdot n \cdot \phi_i). Magnitude zero = cancellation, magnitude 4 = four-fold reinforcement. No trigonometry required.

This is exactly what phasor_sum() computes:

def phasor_sum(phases_rad, harmonic, signs=None):
    """sum_i s_i · exp(j·n·phi_i) — the complex multiplier for harmonic n."""
    ...

And its moment counterpart phasor_moment_sum() adds a position weight xix_i for rocking-couple analysis.

Concrete example: the I4 at 1× vs 2×. With kinematic phases [0°,180°,180°,0°][0°, 180°, 180°, 0°]:

  • At 1×: exp(j1ϕi)=111+1=0\sum \exp(j \cdot 1 \cdot \phi_i) = 1 - 1 - 1 + 1 = 0. Cancellation.
  • At 2×: exp(j2ϕi)=1+1+1+1=4\sum \exp(j \cdot 2 \cdot \phi_i) = 1 + 1 + 1 + 1 = 4. 4× reinforcement.

Two additions, four complex numbers — and that’s the entire story of I4 primary balance and secondary imbalance.

2.1 2D phasors (bank-angle extension)

The basic phasor sum iexp(jnϕi)\sum_i \exp(j \cdot n \cdot \phi_i) collapses cylinders that all push along the same world axis — fine for inline and flat layouts where the bore axes are parallel. V-engines have banks at different angles, so each cylinder’s force is a 2D vector with a different direction per cylinder. The generalisation introduces a bank angle βi\beta_i (the orientation of cylinder i’s bore axis, measured from world +x):

F^x[n]=F^single[n]icos(βi)exp(jnϕi)\hat{F}_x[n] = \hat{F}_\text{single}[n] \cdot \sum_i \cos(\beta_i) \cdot \exp(j \cdot n \cdot \phi_i)

F^y[n]=F^single[n]isin(βi)exp(jnϕi)\hat{F}_y[n] = \hat{F}_\text{single}[n] \cdot \sum_i \sin(\beta_i) \cdot \exp(j \cdot n \cdot \phi_i)

Two independent phasor sums per harmonic, one per world axis. The scalar-sign case is recovered when all βi{0°,180°}\beta_i \in \{0°, 180°\} (then sinβi=0\sin \beta_i = 0 and cosβi=±1\cos \beta_i = \pm 1). Implemented in phasor_sum_2d() and phasor_moment_sum_2d() in _common.py, used from the V-engines chapter onward.

Why this is still 2D in the simulator’s sense: every (cosβi,sinβi)(\cos \beta_i, \sin \beta_i) lies in the simulator’s working plane (the (x, y) cross- section perpendicular to the crankshaft). The framework never needs to leave that plane — see the Dimensional Reduction reference chapter for why this is exact under the rigid-crankshaft / identical-cylinder hypotheses.

2.2 Firing-cycle phasors (720° vs 360°)

All the previous phasor sums use the inertial cycle of 360° (one crank revolution). For combustion analysis the natural cycle is 720° (one full four-stroke cycle: intake-compression- power-exhaust). The phasor structure is identical, but with three re-interpretations:

Inertial (360°)Combustion (720°)
crank phase ϕi\phi_i (mod 360°)firing angle ρi\rho_i (mod 720°)
harmonic index nn of crankharmonic index kk of 720° cycle =n2= n \cdot 2 of crank
per-cyl source F^single[n]\hat{F}_\text{single}[n]per-cyl pulse F^pulse[k]\hat{F}_\text{pulse}[k]
DC, 1×, 2×, 3×, …DC, ½×, 1×, 1½×, 2×, … of crank

The new feature is half-integer crank harmonics (½×, 1½×, 2½×, …) that the inertial analysis cannot see — they exist because the forcing function repeats only every two revolutions. The same phasor_sum_2d() helper handles both: just feed firing angles scaled to [0,2π)[0, 2\pi) over 720°. See the combustion chapter for the worked example.


3. Vibration spectrum

The spectrum of a signal is the set of amplitudes (or complex phasors) of all its harmonic components, plotted or tabulated against frequency. It’s the frequency-domain picture of what the time-domain picture is doing.

For a periodic signal sampled over an integer number of periods, the spectrum is discrete — amplitude only at the harmonic frequencies, zero between them. That’s why we plot it as a bar chart (vibration_slider_crank_spectrum.png, boxer4_spectrum.png, etc.) rather than as a continuous curve.

Why it matters. Every mechanical structure has its own resonances — natural frequencies where even a small forcing input produces a large response.

If the engine’s vibration spectrum has significant energy at the same frequency as a vehicle chassis mode or a cabin panel mode, you get an audible hum or a felt vibration disproportionate to the input amplitude.

Understanding which harmonics carry energy is therefore the prerequisite for everything downstream: mount design, balance-shaft sizing, chassis stiffness tuning.

A designer can’t tell you “we need to isolate 12 Hz” without first having the spectrum and knowing that 12 Hz is where the engine’s 2× at idle sits.

Concrete example. Our I4 at 10 RPM:

Harmonicτ\tau [N·m]RxR_x [N]RyR_y [N]
DC0.000.0039.24
0.030.242.22
17.4416.442.06
0.240.001.07
1.901.180.07

The engine’s entire vibration signature lives in this table.

Every downstream engineering decision — motor sizing, bearing loads, mount compliance, balance-shaft mass — is a reaction to a specific cell in a table like this, at a specific operating RPM.


4. DC component

DC stands for “direct current” — a term from electrical engineering that’s been co-opted into signal processing to mean the zero-frequency (constant) component of a signal. Despite the name, it has nothing to do with electricity in our context.

Mathematically, the DC component is the time average over one period:

xDC=1T0Tx(t)dtx_\text{DC} = \frac{1}{T} \int_0^T x(t)\, dt

In the FFT it’s the bin k=0k = 0 (frequency = 0).

What DC means in our engine analysis:

  • DC of τ(t)\tau(t) = the average driving torque over the cycle. For a dynamic system with only inertial reactions (gravity off), this should be zero — if it weren’t, the engine would gain or lose rotational kinetic energy every cycle. We see DCτ=0\text{DC}\,\tau = 0 for every engine configuration, as expected.

  • DC of Rx(t)R_x(t) (horizontal bearing force) = the average sideways force on the main bearing. Always zero in our setups, because no horizontal body force exists in a gravity-off inline layout.

  • DC of Ry(t)R_y(t) (vertical bearing force) = the static weight the bearing must support. This is the only column where DC is non-zero in a gravity-on single-cylinder run: DCRy=39.24 N=(mcrank+mrod/2)g\text{DC}\, R_y = 39.24\text{ N} = (m_\text{crank} + m_\text{rod}/2) \cdot g. The DC literally reports the static-statics sanity-check of the model.

Why DC is important enough to call out separately: engine NVH engineers generally don’t care about DC (it’s a static load, not a vibration), but they do care that DC is correct, because a wrong DC is always a sign that the mass matrix or gravity vector is wrong. “Does my simulated DC RyR_y match the static weight hand-calc?” is the single cheapest sanity check you can do on a multibody model.

When we subtract DC vs keep it:

  • For vibration analysis (spectra, harmonic amplitudes): we treat DC as informational only and focus on the AC content.
  • For force/load analysis (sizing bearings, mounts): DC matters because it contributes to the total load on the hardware.

In the plots in this series, DC is shown as the “0×” bar on the bar charts, usually smaller than or comparable to 1×. The interesting physics is almost always in the non-DC harmonics.


5. Saddle-point system

A specific flavour of linear system that pops up anywhere you have constrained optimisation or, equivalently, constrained dynamics. It looks like this:

[ABTB0][xy]=[fg]\begin{bmatrix} A & B^T \\ B & 0 \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = \begin{bmatrix} f \\ g \end{bmatrix}
  • AA is a square matrix (positive semi-definite in our case).
  • BB is a rectangular matrix (the constraints).
  • The zero in the lower-right block is the defining feature.
  • xx is the “primary” unknown; yy is the Lagrange multipliers.

The “saddle point” name comes from the fact that if you rewrite the problem as finding a stationary point of the Lagrangian L(x,y)=12xTAxxTf+yT(Bxg)\mathcal{L}(x, y) = \tfrac{1}{2} x^T A x - x^T f + y^T (B x - g), the solution is a minimum in xx and a maximum in yy — i.e. a saddle of L\mathcal{L}.

Where this shows up in our multibody solver. The constrained equations of motion are

M(q)a+CqTλ=Qtotal(q,v,t)\mathbf{M}(\mathbf{q}) \cdot \mathbf{a} + \mathbf{C}_q^T \cdot \boldsymbol{\lambda} = \mathbf{Q}_\text{total}(\mathbf{q}, \mathbf{v}, t)

Cqa=γ(q,v,t)\mathbf{C}_q \cdot \mathbf{a} = \boldsymbol{\gamma}(\mathbf{q}, \mathbf{v}, t)

Rearranged into a single block:

[MCqTCq0][aλ]=[Qtotalγ]\begin{bmatrix} \mathbf{M} & \mathbf{C}_q^T \\ \mathbf{C}_q & \mathbf{0} \end{bmatrix} \begin{bmatrix} \mathbf{a} \\ \boldsymbol{\lambda} \end{bmatrix} = \begin{bmatrix} \mathbf{Q}_\text{total} \\ \boldsymbol{\gamma} \end{bmatrix}
  • M\mathbf{M} is the mass matrix (from body masses and inertias). Positive semi-definite.
  • Cq\mathbf{C}_q is the constraint Jacobian. One row per constraint equation, one column per coordinate.
  • a\mathbf{a} is the vector of generalised accelerations — this is what we want for the dynamics.
  • λ\boldsymbol{\lambda} is the vector of Lagrange multipliers. Each one is a constraint reaction force — e.g. the (Rx,Ry)(R_x, R_y) pair at a revolute joint, or the minus-driving-torque at a user constraint.
  • Qtotal\mathbf{Q}_\text{total} is the sum of applied generalised forces (gravity, spring, contact, user).
  • γ=(C˙qv+2C/t2)\boldsymbol{\gamma} = -(\dot{\mathbf{C}}_q \cdot \mathbf{v} + \partial^2 C / \partial t^2) is the constraint’s acceleration- level right-hand side.

Solving this system IS the multibody dynamics step. Every call to solve_dynamics_scipy() invokes the saddle-point solve internally at every RHS evaluation. Every call to solve_inverse_dynamics() does the same. The function that actually does it is solve_acceleration_with_lagrange() in 2D-Kinematics/solver.py, which just assembles the block matrix and calls np.linalg.solve.

Why this framework is powerful. The same saddle-point system handles:

  • Forward dynamics — given forces, solve for accelerations.
  • Inverse dynamics — given motion (via user constraints making DOF = 0), solve for the Lagrange multipliers that enforce it. Those multipliers include the driving torque and bearing reactions we’ve been reporting.
  • Constrained optimisation — same math, different interpretation (accelerations become design variables, multipliers become shadow prices).

Connection to vibration analysis. Each call to the saddle-point solver gives us one time-step’s worth of (a(t),λ(t))(\mathbf{a}(t), \boldsymbol{\lambda}(t)). Collecting λ(t)\boldsymbol{\lambda}(t) over time and running it through the FFT produces the vibration spectrum we’ve been plotting. The saddle-point solver is the source that feeds the FFT pipeline.


6. Signal processing (in this project)

“Signal processing” is an enormous discipline; we use a small, well-chosen subset.

What we use

  • Sampling — converting a continuous-time function x(t)x(t) into a finite list of values by picking NN evaluations at uniformly spaced times tk=kdtt_k = k \cdot dt. Our inverse-dynamics solver samples at the tspan we hand it.

  • FFT — decomposing the sampled signal into harmonic amplitudes and phases. Covered in §1.

  • Phasor arithmetic — complex-number algebra on the Fourier components. Covered in §2. This is how we superpose cylinders at different phases without ever re-running a simulation.

  • Circular time shiftnp.roll(signal, -k) shifts a periodic signal forward in time by kk samples. We use this to simulate the effect of each cylinder’s crank-phase offset in the time domain, without recomputing its trajectory. Mathematically equivalent to multiplying the FFT by exp(j2πkfreq)\exp(j \cdot 2\pi \cdot k \cdot \text{freq}).

  • Peak-amplitude normalisation — the 2/N scaling in fft_amplitude() so the FFT output reads as physical force amplitudes in N, not abstract FFT units.

What we carefully avoid (and why)

  • Windowing — a common FFT trick where you multiply the signal by a smooth taper (Hanning, Hamming, Blackman) before the FFT to reduce “spectral leakage” when the sample doesn’t cover exact integer periods. We don’t need it because our sampling is deliberately exact (endpoint=False over integer revolutions), so every harmonic lands on an exact bin. If we used arbitrary sampling lengths, we’d need windowing and the spectrum would have small smearing artefacts.

  • Anti-aliasing / low-pass filters — matter only if the signal has content above the Nyquist frequency (1/(2dt)1/(2 \cdot dt)). Our single-cylinder content decays as (R/L)n(R/L)^n beyond the 2nd harmonic, and our sample rate is more than enough, so nothing above Nyquist exists to alias.

  • Convolution / digital filters — we don’t need to apply any transfer function to the signals; we’re measuring the source. When the project grows into chassis response, we’ll need them.

A mental model: time ↔ frequency

For any periodic signal, these three views contain the same information:

  1. Time-domain samples: [x(t0),x(t1),,x(tN1)][x(t_0), x(t_1), \ldots, x(t_{N-1})]. A list of numbers. Plotted as a wiggly curve.
  2. Frequency-domain phasors: [X[0],X[1],,X[N/2]][X[0], X[1], \ldots, X[N/2]]. A list of complex numbers, one per harmonic. Plotted as a bar chart (amplitudes) with optional phase annotations.
  3. Fourier-series coefficients: {(An,ϕn)}\{(A_n, \phi_n)\}. The “humanly readable” form of (2).

All three are isomorphic — you can convert between them without loss of information. Which one you pick depends on what question you’re asking:

QuestionBest view
What is the force right now at crank angle 90°?Time domain
What is the peak shake force over the cycle?Time domain
What frequency is the engine humming at?Frequency domain
How does vibration change if I add a balance shaft?Frequency domain
How do 4 cylinders superpose to cancel at 1×?Phasor (frequency domain)
What does the time history look like if I shift phase by 180°?Back to time domain for plotting, frequency domain for shifting

We use all three. The FFT is just the bridge between views.


7. Transmissibility

What it is. The dimensionless ratio of the force a mount transmits to the chassis to the force the engine applies to the mount, as a function of frequency. The textbook 1-DOF result for a mass-spring-damper:

T(r,ζ)=1+(2ζr)2(1r2)2+(2ζr)2T(r, \zeta) = \frac{\sqrt{1 + (2\zeta r)^2}}{\sqrt{(1 - r^2)^2 + (2\zeta r)^2}}

with three parameters:

SymbolMeaning
r=ω/ωnr = \omega / \omega_nfrequency ratio — excitation frequency / mount natural frequency
ωn=K/M\omega_n = \sqrt{K/M}natural frequency — set by mount stiffness KK and engine mass MM
ζ=C/(2MK)\zeta = C / (2\sqrt{MK})damping ratio — set by the mount’s loss factor

Three operating regions:

rrTTInterpretation
r1r \ll 11\approx 1quasi-static; the mount just transmits
r1r \approx 1up to severalresonance — amplification, not isolation
r=2r = \sqrt{2}exactly 1crossover
r1r \gg 1falls as 1/r21/r^2isolation regime — the design target

Three-DOF generalisation. A real engine block on N mounts is not a 1-DOF system — it has three planar DOF (x,y,θz)(x, y, \theta_z) and multiple mount points. Replace the scalars M,K,CM, K, C with 3×3 matrices and solve in the frequency domain:

(ω2[M]+jω[C]+[K])q^=F^\left(-\omega^2 [\mathbf{M}] + j\omega [\mathbf{C}] + [\mathbf{K}]\right) \cdot \hat{\mathbf{q}} = \hat{\mathbf{F}}

Same shape of curve per excitation axis, with cross-coupling between translation and rotation through mount geometry (an off-CG mount couples FyF_y to θz\theta_z, and so on).

Where this lives in the project. Implemented in engine_mount_transmissibility.py and used in the engine mounts chapter to demonstrate the soft-vs-stiff mount tradeoff numerically.

Connection to phasors. The phasor framework produces source phasors F^engine[n]\hat{F}_\text{engine}[n]; transmissibility filters them into chassis phasors F^chassis[n]=T(nω)F^engine[n]\hat{F}_\text{chassis}[n] = T(n\omega) \cdot \hat{F}_\text{engine}[n]. Same complex- number language, one extra multiplier per harmonic.


8. Transfer function vs FRF (and where Nyquist comes in)

Same math, two engineering vocabularies. The distinction matters because the e-book uses both — transmissibility and cabin transfer function are FRFs evaluated analytically, while LMS stability is a control-systems Nyquist-style argument.

8.1 The mathematical relationship

In Laplace-transform language, the transfer function of a linear system is the ratio of output to input in the complex ss-plane:

H(s)=Y(s)X(s),s=σ+jωH(s) = \frac{Y(s)}{X(s)}, \qquad s = \sigma + j\omega

The Frequency Response Function (FRF) is H(s)H(s) restricted to the imaginary axis — i.e., assuming the transient parts have died out and we’re observing only the steady-state sinusoidal response:

FRF(ω)=H(jω)\text{FRF}(\omega) = H(j\omega)

H(s)H(s) is the “master equation” (transients + steady state, stability

  • response). H(jω)H(j\omega) is the slice that tells you how the system vibrates at each steady-state frequency.

8.2 Two engineering cultures

Mechanical / NVH engineers and control engineers grew up with the same math but different vocabularies. The terminology in practice:

FRF (mechanical / NVH)Transfer function (control)
Domainjωj\omega only (steady state)full ss-plane (transient + steady)
Typical originexperimental — hammer test, shaker, dynoanalytical — derived from differential equations
What you studyresonances, modal damping, frequency contentstability, gain margin, transient response
Common plotBode (magnitude / phase vs frequency)Bode, Nyquist, root locus

Slight nuance worth being honest about: “FRF = experimental” is the industry default (NVH engineers measure them in the lab) but not a rule. Our T(ω)T(\omega) and Hcabin(ω)H_\text{cabin}(\omega) are FRFs derived analytically from linear models — same mathematical object as a measured FRF, just a different origin. Conversely, control engineers do measure transfer functions experimentally too (system identification).

8.3 Where the e-book uses each

PlaceObjectWhy this name
§7 Transmissibility T(r,ζ)T(r, \zeta)mount-side FRFsteady-state harmonic source
Engine Mounts chapter (ω2M+jωC+K)q^=F^(-\omega^2 M + j\omega C + K)\hat{q} = \hat{F}3-DOF block FRFsteady-state, evaluated per harmonic
Chassis Response chapter H(ω)=ω2/ZH(\omega) = \omega^2/Zcabin SDOF FRFanalytical, steady-state
§9 LMS closed-loop stabilityopen-loop transfer function H(s)H(s)feedback loop — stability matters, transients matter

The first three live happily in FRF land. The moment you close a feedback loop (LMS, FXLMS, active mounts), you’re in control-systems territory and need the full ss-plane view to talk about stability.

8.4 Where Nyquist comes in

Nyquist’s stability criterion answers: will this closed-loop system blow up?

A unity-feedback closed-loop system is stable iff the polar plot of the open-loop FRF L(jω)L(j\omega) doesn’t encircle the 1-1 point in the complex plane.

In the Active Damping chapter we showed that actuator delay drives naive LMS unstable above ~2.5 ms. That is a Nyquist-family failure: delay introduces a per-cycle phase shift that pushes the open-loop polar plot around the critical point, and the closed-loop “canceller” turns into a closed-loop “amplifier”.

Honest nuance: the analysis we actually used in the chapter was the simpler statement “the LMS gradient direction rotates by ωΔtD\omega \Delta t_D radians per update; above π/2\pi/2 the step points uphill”. That is the same family of phase-margin reasoning that Nyquist captures rigorously, just expressed without drawing the polar plot. Filtered-X LMS, the standard fix, can be derived either way: pre-filter the reference through the actuator response model, and the open-loop polar plot stays clear of the critical point regardless of delay.

8.5 Three-line summary

  • Transfer function H(s)H(s): full ss-plane, captures transient + steady state.
  • FRF H(jω)H(j\omega): jωj\omega-axis slice, captures steady-state vibration.
  • Nyquist: the test you apply to H(s)H(s) (or its open-loop FRF) to make sure your active controller doesn’t turn the engine into a self-oscillating mechanical disaster.

Same underlying maths, three lenses. The e-book uses the first passively (linear analysis, no feedback), the second pervasively (transmissibility, mount + cabin response), and the third implicitly in the LMS-delay story.


9. LMS adaptive cancellation

What it is. The Least-Mean-Squares (Widrow & Hoff, 1960) algorithm is a gradient-descent law for finding the linear combination of reference signals that best cancels a target signal in the mean-square sense. In our context: closed-loop active vibration cancellation at a single harmonic.

The setup. Pick a cancellation harmonic nn and grab two reference signals from the crank-angle sensor:

cn(t)=cos(nωt),sn(t)=sin(nωt)c_n(t) = \cos(n \cdot \omega \cdot t), \qquad s_n(t) = \sin(n \cdot \omega \cdot t)

Define the actuator output as

Fact(t)=wc(t)cn(t)+ws(t)sn(t)F_\text{act}(t) = w_c(t) \cdot c_n(t) + w_s(t) \cdot s_n(t)

The weights (wc,ws)(w_c, w_s) are a single phasor in disguise: amplitude wc2+ws2\sqrt{w_c^2 + w_s^2}, phase atan2(ws,wc)\operatorname{atan2}(-w_s, w_c). They are updated continuously to drive the residual

e(t)=Fengine(t)+Fact(t)e(t) = F_\text{engine}(t) + F_\text{act}(t)

toward zero by gradient descent on e2e^2:

wc(t+Δt)=wc(t)μe(t)cn(t)w_c(t + \Delta t) = w_c(t) - \mu \cdot e(t) \cdot c_n(t)

ws(t+Δt)=ws(t)μe(t)sn(t)w_s(t + \Delta t) = w_s(t) - \mu \cdot e(t) \cdot s_n(t)

The single tunable parameter μ\mu (the step size) sets the trade between convergence speed and steady-state ripple.

Time constant. With unit-amplitude references, E[c2]=E[s2]=0.5\mathbb{E}[c^2] = \mathbb{E}[s^2] = 0.5 and the expected weight update is

E[Δwc]=μ0.5(Acosϕ+wc)\mathbb{E}[\Delta w_c] = -\mu \cdot 0.5 \cdot (A \cos\phi + w_c)

giving a first-order exponential approach to the steady-state weight wcAcosϕw_c \to -A \cos\phi, with time constant

τ=2μ samples\tau = \frac{2}{\mu} \text{ samples}

At μ=5×103\mu = 5 \times 10^{-3} and Δt=100μs\Delta t = 100\,\mu\text{s} that’s τ=40 ms\tau = 40\text{ ms} — convergence to within 1 % takes 5τ=200 ms{\approx}5\tau = 200\text{ ms}.

Stability under actuator delay. Naive LMS uses the current reference in its update law. If the actuator has delay DD samples, the gradient direction rotates by DωΔtD \cdot \omega \cdot \Delta t radians per cycle. Above π/2{\approx}\pi/2 of rotation the update step starts pointing uphill and the system becomes unstable:

D<π2ωΔtfor stable convergenceD < \frac{\pi}{2 \cdot \omega \cdot \Delta t} \quad \text{for stable convergence}

At ω=628 rad/s\omega = 628\text{ rad/s} (100 Hz) and Δt=100μs\Delta t = 100\,\mu\text{s} that’s D<25 samples=2.5 msD < 25\text{ samples} = 2.5\text{ ms}. Filtered-X LMS (FXLMS) extends the stable range by pre-filtering the references through an identified model of the actuator response, handling arbitrary delays. Three lines of code change.

Where this lives in the project. Implemented as the ActiveDamper class in active_mass_damping.py, with four demos (steady-state convergence, throttle step, RPM step, delay-induced instability) covered in the active damping chapter.

Connection to balance shafts. A passive Lanchester balancer is the open-loop limit of this scheme: weights (wc,ws)(w_c, w_s) are designed once and frozen, with no feedback. Active damping replaces the design-time decision with a continuously-adapted one, which is what lets it track load, RPM, manufacturing tolerances, and cylinder deactivation — none of which a fixed mechanical balance shaft can do.


10. Glossary of symbols used in the series

SymbolMeaning
q\mathbf{q}Generalised coordinates of the mechanism (positions, angles)
v\mathbf{v}Generalised velocities (dq/dtd\mathbf{q}/dt)
a\mathbf{a}Generalised accelerations (dv/dtd\mathbf{v}/dt)
M(q)\mathbf{M}(\mathbf{q})Mass matrix (position-dependent)
C(q,t)C(\mathbf{q}, t)Constraint equations (C=0C = 0 on the manifold)
Cq\mathbf{C}_qConstraint Jacobian (C/q\partial C / \partial \mathbf{q})
λ\boldsymbol{\lambda}Lagrange multipliers = constraint reaction forces
Qtotal\mathbf{Q}_\text{total}Total generalised applied force (gravity + springs + contact + user)
γ\boldsymbol{\gamma}Constraint acceleration RHS (C˙qv2C/t2-\dot{\mathbf{C}}_q \cdot \mathbf{v} - \partial^2 C / \partial t^2)
F(t)F(t)Bearing reaction or driving torque time series
F^[n]\hat{F}[n]nnth harmonic amplitude of F(t)F(t) from FFT
ωcrank\omega_\text{crank}Crank angular velocity (rad/s)
fcrankf_\text{crank}Crank frequency in Hz (=ωcrank/2π= \omega_\text{crank} / 2\pi)
n×n\timesShorthand for “nth harmonic of crank frequency”
ϕi\phi_iKinematic phase of cylinder ii on the crankshaft (radians)
sis_i±1\pm 1 sign flip on cylinder ii for mirror-opposed banks (boxer, flat)
xix_iPosition of cylinder ii along the crankshaft axis (metres)
RRCrank throw length
LLConnecting rod length
R/LR/LCrank-to-rod ratio; controls secondary imbalance strength
DCZero-frequency / time-average component
τ(t)\tau(t)Driving torque at the crank (N·m)
Rx(t),Ry(t)R_x(t), R_y(t)Crankshaft bearing reaction forces in world x, y (N)
TDC / BDCTop dead centre / bottom dead centre (slider extremes)
primary / secondaryEngine-NVH shorthand for 1× / 2× harmonic shake
Δ\Delta\ellCrankpin offset in a boxer engine (metres)
2D phasors / V-engines(added in §2.1)
βi\beta_iBank angle of cylinder ii (bore-axis direction from world +x)
F^x[n],F^y[n]\hat{F}_x[n], \hat{F}_y[n]Per-harmonic 2D force phasors (world x, world y)
M^pitch[n],M^yaw[n]\hat{M}_\text{pitch}[n], \hat{M}_\text{yaw}[n]Per-harmonic 3D moment phasors (about y, x axes)
M^roll[n]\hat{M}_\text{roll}[n]Per-harmonic torque ripple about the crankshaft axis
Combustion(added in §2.2)
ρi\rho_iFiring angle of cylinder ii in the 720° four-stroke cycle
Fpulse(t)F_\text{pulse}(t)Per-cylinder combustion pressure-pulse force
F^pulse[k]\hat{F}_\text{pulse}[k]kk-th harmonic of the pulse over the 720° cycle
FpeakF_\text{peak}, dutyPulse amplitude (N) and active fraction of the 720° cycle
Balance shafts
nspinn_\text{spin}Balance-shaft spin frequency as integer multiple of crank
mbsrbsm_\text{bs} \cdot r_\text{bs}Balance-shaft mass × eccentricity (kg·m)
Engine mounts and transmissibility(added in §7)
M,IzM, I_zEngine-block mass and in-plane inertia
kx,kyk_x, k_yPer-mount translational stiffness in N/m
cx,cyc_x, c_yPer-mount translational damping in N·s/m
ωn,fn\omega_n, f_nMount natural frequency in rad/s and Hz
ζ\zetaDamping ratio (dimensionless)
r=ω/ωnr = \omega/\omega_nFrequency ratio (dimensionless)
T(r,ζ)T(r, \zeta)Force transmissibility ratio
LMS / active damping(added in §9)
cn(t),sn(t)c_n(t), s_n(t)Cosine / sine reference signals at harmonic nn
wc,wsw_c, w_sLMS weight pair (cosine and sine components)
μ\muLMS step size (gain)
e(t)e(t)Residual / error signal in a closed-loop canceller
DDActuator delay in samples
τ=2/μ\tau = 2/\muLMS convergence time constant in samples
Chassis modal response & perception(chassis-response chapter)
Mcabin,fn,cabin,ζcabinM_\text{cabin}, f_{n,\text{cabin}}, \zeta_\text{cabin}Effective modal mass / natural frequency / damping ratio of a body-shell mode
Hcabin(ω)=ω2/ZH_\text{cabin}(\omega) = \omega^2/ZTransfer function from chassis-side force to cabin acceleration (m/s²/N)
Wk(f)W_k(f)ISO 2631-1 weighting curve for vertical seated whole-body vibration
awa_wWeighted r.m.s. cabin acceleration (m/s²); compared to ISO 2631 comfort thresholds

These ebook chapters each exercise a subset of the concepts above. Use this primer as the dictionary when any of them seems unclear.

Multi-cylinder series

Reference companions