Chapter 22
25 min read
Section 191 of 353

Forced Oscillations and Resonance

Second-Order Differential Equations

Learning Objectives

By the end of this section you will be able to:

  1. Write the driven oscillator equation mx¨+cx˙+kx=F0cos(ωt)m\ddot{x} + c\dot{x} + kx = F_0\cos(\omega t) and identify which of its terms produce the transient and which produce the steady state.
  2. Derive the steady-state amplitude A(ω)A(\omega) and phase lag φ(ω)\varphi(\omega) using the complex-exponential (impedance) method.
  3. Explain resonance physically — what happens to a lightly damped system when the driving frequency approaches the natural frequency.
  4. Compute the resonant frequency, the maximum amplitude, and the quality factor QQ.
  5. Recognize beats in the undamped limit and the unbounded resonance solution xp(t)=F02mω0tsin(ω0t)x_p(t) = \tfrac{F_0}{2m\omega_0}\,t\sin(\omega_0 t).
  6. Simulate a driven oscillator in Python (RK4) and sweep frequencies in PyTorch with one complex line.

The Driving Question

“A child on a swing weighs 20 kg. You weigh 80 kg. Why can the child push the swing higher than you can?”

Anyone who has watched a playground knows the answer: the child pushes at the right rhythm. Every nudge arrives just as the swing reaches its forward edge, adding energy stride for stride. An adult applying ten times the force at the wrong rhythm gets nowhere — the push sometimes accelerates, sometimes brakes, averaging out.

That intuition is resonance. A small, repeated, perfectly-timed input causes a huge response. The mathematics of this section is just the precise language to say it. By the end you'll know exactly which rhythm, exactly how big the response gets, and exactly why it grows.

🌉 Civil / structural

  • Tacoma Narrows bridge (1940)
  • Skyscraper sway in wind
  • Footbridges & pedestrian sync

📡 Electronics

  • AM/FM radio tuners
  • LC bandpass filters
  • Crystal & MEMS oscillators

🧲 Physics / medicine

  • NMR / MRI spin precession
  • LIGO mirror suspension
  • Microwave ovens (H₂O dipole)

The Equation of a Driven Oscillator

Take the mass–spring–damper from Section 22.06 and add an external force F(t)F(t) pushing the mass. Newton's second law says

mx¨+cx˙+kx=F(t).m\ddot{x} + c\dot{x} + kx = F(t).

The most important and most studied case is a sinusoidal force at a single frequency ω\omega:

mx¨+cx˙+kx=F0cos(ωt).m\ddot{x} + c\dot{x} + kx = F_0\cos(\omega t).
SymbolMeaningUnits
mMasskg
cViscous damping coefficientNs/m
kSpring stiffnessN/m
F₀Amplitude of the driving forceN
ωAngular frequency of the driving forcerad/s
ω₀ = √(k/m)Natural angular frequency of the undamped systemrad/s
ζ = c / (2√(mk))Damping ratio (dimensionless)

Why one frequency is enough

Any reasonable periodic force can be written as a sum of sinusoids (Fourier series). Because the equation is linear, the response to a sum is the sum of the responses. So if you understand the steady state for a single cosine, you can build the response to any forcing by superposition. The single-cosine problem is the atom of the whole subject.


Transient + Steady State

Because the equation is linear, its general solution splits into two pieces:

x(t)  =  xh(t)transient  +  xp(t)steady state.x(t) \;=\; \underbrace{x_h(t)}_{\text{transient}} \;+\; \underbrace{x_p(t)}_{\text{steady state}}.
PieceSolvesLong-time behavior
Homogeneous xₕ(t)m x″ + c x′ + k x = 0Decays to 0 whenever c > 0 (every real system has friction). Carries the initial conditions.
Particular xₚ(t)m x″ + c x′ + k x = F₀ cos(ωt)A sustained oscillation at the DRIVING frequency ω, NOT the natural frequency.

Two punchlines about damped, driven systems

  1. After a few damping times (a few times 1/α1/|\alpha|) only the steady state remains. The system forgets its initial conditions.
  2. The steady state oscillates at the driving frequency ω\omega, not at the natural frequency ω0\omega_0. The natural frequency only controls how big the response is, not how fast it wiggles.

Finding the Steady-State Solution

We need a particular solution. The classical guess is xp=Acos(ωtφ)x_p = A\cos(\omega t - \varphi), but the clean way to derive it uses complex exponentials. Replace the real forcing with F0eiωtF_0 e^{i\omega t} (we'll take the real part at the end) and guess

xp(t)=Xeiωt,XC.x_p(t) = X\,e^{i\omega t},\qquad X\in\mathbb{C}.

Differentiating twice multiplies by iωi\omega, so x˙p=iωXeiωt\dot{x}_p = i\omega X e^{i\omega t} and x¨p=ω2Xeiωt\ddot{x}_p = -\omega^2 X e^{i\omega t}. Plugging in:

(mω2+icω+k)Xeiωt  =  F0eiωt.\bigl(-m\omega^2 + i c\omega + k\bigr)\,X\,e^{i\omega t} \;=\; F_0\, e^{i\omega t}.

Cancel the common factor and solve for XX:

  X(ω)  =  F0(kmω2)  +  icω  \boxed{\;X(\omega) \;=\; \dfrac{F_0}{(k-m\omega^2)\;+\;i\,c\omega}\;}

The denominator is called the (mechanical) impedance Z(ω)Z(\omega), in direct analogy with the electrical impedance Z(ω)=R+iωL+1iωCZ(\omega) = R + i\omega L + \tfrac{1}{i\omega C} of an RLC circuit. One complex number per frequency contains everything: take its modulus to get the amplitude, its argument to get the phase.

Why this complex trick works

Differentiation is ddt[eiωt]=iωeiωt\frac{d}{dt}\big[e^{i\omega t}\big] = i\omega\,e^{i\omega t} — multiplication by iωi\omega. So the differential operator L=md2dt2+cddt+kL = m\frac{d^2}{dt^2} + c\frac{d}{dt} + k becomes the scalar L(iω)=mω2+icω+kL(i\omega) = -m\omega^2 + ic\omega + k when acting on eiωte^{i\omega t}. Differential equations collapse into algebra. This is the seed of the Laplace and Fourier transforms.


Amplitude and Phase Lag — The Core Formulas

Write X=XeiφX = |X|e^{-i\varphi} in polar form (negative sign so φ\varphi is the phase the mass lags the force by). Taking the real part of XeiωtX e^{i\omega t} recovers the real steady state:

xp(t)  =  A(ω)cos ⁣(ωtφ(ω)),x_p(t) \;=\; A(\omega)\,\cos\!\bigl(\omega t - \varphi(\omega)\bigr),

where the amplitude and phase are obtained from Z(ω)|Z(\omega)| and argZ(ω)\arg Z(\omega):

A(ω)  =  F0(kmω2)2+(cω)2A(\omega) \;=\; \dfrac{F_0}{\sqrt{(k - m\omega^2)^2 + (c\omega)^2}}
tanφ(ω)  =  cωkmω2\tan\varphi(\omega) \;=\; \dfrac{c\omega}{k - m\omega^2}

These two formulas are the heart of every vibration textbook in existence. Memorize them — or better, learn to re-derive them in 30 seconds with the impedance trick above.

Three regimes of phase lag

  • Below resonance (ωω0)(\omega \ll \omega_0): denominator is positive, φ0\varphi \approx 0. Mass moves in phase with the force — it follows the push almost instantly.
  • At resonance (ω=ω0)(\omega = \omega_0): kmω2=0k - m\omega^2 = 0, so φ=π/2\varphi = \pi/2. Mass moves a quarter cycle behind the force. The force is doing maximum positive work because it's aligned with the velocity, not the displacement.
  • Above resonance (ωω0)(\omega \gg \omega_0): denominator becomes negative, φπ\varphi \to \pi. Mass moves opposite the force — inertia dominates.

Resonance: Why a Small Push Can Become Huge

Look at the amplitude formula A(ω)=F0/(kmω2)2+(cω)2A(\omega) = F_0/\sqrt{(k-m\omega^2)^2 + (c\omega)^2} and pretend you can pick ω\omega. You want AA to be as large as possible, so you want the denominator to be as small as possible.

The first term (kmω2)2(k - m\omega^2)^2 can be made exactly zero by choosing ω=k/m=ω0\omega = \sqrt{k/m} = \omega_0. Then only the damping term (cω0)2(c\omega_0)^2 is left, and

A(ω0)  =  F0cω0.A(\omega_0) \;=\; \dfrac{F_0}{c\omega_0}.

If c0c \to 0, this blows up. That is the mathematical story behind every “a bridge fell down because the wind oscillated at the bridge's natural frequency” anecdote.

Resonance peak vs natural frequency — a subtlety

If you set dAdω=0\dfrac{dA}{d\omega} = 0 carefully, the actual maximum of A(ω)A(\omega) occurs at the slightly lower ωp=ω012ζ2\omega_p = \omega_0\sqrt{1 - 2\zeta^2}, not exactly at ω0\omega_0. For light damping ζ0\zeta \to 0 the difference vanishes. The peak even disappears entirely once ζ1/20.707\zeta \geq 1/\sqrt{2} \approx 0.707: the system is so over-damped that no resonance is visible.

The quality factor QQ packages the “sharpness” of the peak into one number:

Q  =  12ζ  =  mkc.Q \;=\; \dfrac{1}{2\zeta} \;=\; \dfrac{\sqrt{mk}}{c}.

At resonance A(ω0)=F0/(cω0)=(F0/k)QA(\omega_0) = F_0/(c\omega_0) = (F_0/k)\cdot Q: the static deflection F0/kF_0/k is amplified by Q. A car tire (Q5Q \approx 5), a guitar string (Q1000Q \approx 1000), a crystal oscillator (Q106Q \approx 10^6) and a LIGO mirror (Q107Q \approx 10^7) span twelve orders of magnitude on the same single equation.


Interactive: The Universal Resonance Curve

Below is the dimensionless resonance curve A(r)=1/(1r2)2+(2ζr)2A(r) = 1/\sqrt{(1-r^2)^2 + (2\zeta r)^2} with r=ω/ω0r = \omega/\omega_0. Slide ζ\zeta from 0.05 (sharp peak) toward 1 (peak disappears) and watch the bandwidth widen as the peak shrinks. The phase plot beneath it always crosses π/2\pi/2 at r=1r = 1 — that is the universal signature of resonance.

Loading resonance curve…

Interactive: Live Driven Oscillator

Now watch the whole story unfold in time. Start with the default values (m=1,c=0.4,k=4,F0=2,ω=2m=1, c=0.4, k=4, F_0=2, \omega=2) — the system is being driven exactly at its natural frequency ω0=k/m=2\omega_0 = \sqrt{k/m} = 2. The amber curve starts at zero and builds toward the cyan steady-state envelope at A=2.5A = 2.5. Then try sliding ω\omega off the resonance — the amplitude collapses almost immediately.

Loading driven-oscillator simulation…

Things to try in the explorer

  • Drop cc to 0.050.05 and click Snap ω = ω₀. Resonance becomes spectacular.
  • Push ω\omega to 55 (well above ω0\omega_0). Notice the phase lag plot — the mass barely moves and lags by nearly 180°.
  • Turn off the steady-state overlay and watch only the amber curve. See how it transitions from an “ugly” combination of two frequencies (transient + drive) into a clean single-frequency oscillation as the transient dies.

Worked Example (Step-by-Step)

Let's do every step by hand for the system in the simulator: m=1m = 1, c=0.4c = 0.4, k=4k = 4, F0=2F_0 = 2, ω=2\omega = 2, x(0)=0x(0) = 0, x(0)=0x'(0) = 0.

Click to expand the full hand calculation

Step 1 — Natural frequency and damping ratio

ω0=k/m=4/1=2 rad/s.\omega_0 = \sqrt{k/m} = \sqrt{4/1} = 2 \text{ rad/s}. The system is being driven at exactly ω=ω0\omega = \omega_0. The damping ratio is ζ=c2mk=0.424=0.1\zeta = \tfrac{c}{2\sqrt{mk}} = \tfrac{0.4}{2\sqrt{4}} = 0.1 — lightly damped. We expect a clear, sharp resonance.

Step 2 — Steady-state amplitude

A=F0(kmω2)2+(cω)2=2(44)2+(0.8)2=20.8=2.5.A = \dfrac{F_0}{\sqrt{(k - m\omega^2)^2 + (c\omega)^2}} = \dfrac{2}{\sqrt{(4 - 4)^2 + (0.8)^2}} = \dfrac{2}{0.8} = 2.5.

The steady-state amplitude is 2.52.5 meters per Newton of force — 2.5× larger than the static deflection F0/k=0.5F_0/k = 0.5. The amplification factor is exactly the quality factor Q=1/(2ζ)=5Q = 1/(2\zeta) = 5. Wait — 2.5/0.5 = 5. Confirmed.

Step 3 — Phase lag

φ=tan1 ⁣(cωkmω2)=tan1 ⁣(0.80)=π2=90°.\varphi = \tan^{-1}\!\left(\dfrac{c\omega}{k - m\omega^2}\right) = \tan^{-1}\!\left(\dfrac{0.8}{0}\right) = \dfrac{\pi}{2} = 90°.

At resonance the mass lags the force by a quarter cycle. So xp(t)=2.5cos(2tπ/2)=2.5sin(2t)x_p(t) = 2.5\cos(2t - \pi/2) = 2.5\sin(2t).

Step 4 — Transient

The homogeneous problem is x+0.4x+4x=0x'' + 0.4 x' + 4 x = 0. Characteristic equation: r2+0.4r+4=0r^2 + 0.4 r + 4 = 0. Discriminant Δ=0.1616=15.84<0\Delta = 0.16 - 16 = -15.84 < 0 (underdamped). Roots: r=α±iβr = \alpha \pm i\beta with α=0.2\alpha = -0.2 and β=15.84/21.99\beta = \sqrt{15.84}/2 \approx 1.99.

General homogeneous solution: xh(t)=e0.2t(C1cos(1.99t)+C2sin(1.99t)).x_h(t) = e^{-0.2 t}\bigl(C_1\cos(1.99 t) + C_2 \sin(1.99 t)\bigr).

Step 5 — Apply initial conditions to the full solution

x(t)=xh(t)+2.5sin(2t)x(t) = x_h(t) + 2.5\sin(2t). With x(0)=0x(0) = 0: C1+0=0C1=0C_1 + 0 = 0 \Rightarrow C_1 = 0.

Differentiate: x(t)=e0.2t(0.2C2sin(1.99t)+1.99C2cos(1.99t))+5cos(2t).x'(t) = e^{-0.2 t}\bigl(-0.2\cdot C_2\sin(1.99 t) + 1.99 C_2\cos(1.99 t)\bigr) + 5\cos(2t). At t=0t = 0: 1.99C2+5=0C22.5131.99\,C_2 + 5 = 0 \Rightarrow C_2 \approx -2.513.

Step 6 — Final closed form

x(t)  =  2.513e0.2tsin(1.99t)transient, dies in 25s  +  2.5sin(2t)steady state, persists forever.x(t) \;=\; \underbrace{-2.513\,e^{-0.2 t}\sin(1.99\,t)}_{\text{transient, dies in }\sim 25\text{s}} \;+\; \underbrace{2.5\,\sin(2 t)}_{\text{steady state, persists forever}}.

Step 7 — Sanity checks

  • x(0)=00=0x(0) = 0 - 0 = 0. ✅
  • x(0)=2.5131.99+55.001+50x'(0) = -2.513 \cdot 1.99 + 5 \approx -5.001 + 5 \approx 0. ✅
  • At late times t=30t = 30: e60.0025e^{-6} \approx 0.0025, so the transient is 0.006\sim 0.006 — completely negligible next to the 2.5 amplitude steady state. ✅

The amber curve in the simulator above is exactly this function. Slide ω\omega away from 2 and the amplitude crashes from 2.5 to a fraction of an inch — verifying that the resonance peak is the entire show.


The Undamped Case: Beats and Pure Resonance

Set c=0c = 0. The amplitude formula becomes A(ω)=F0/kmω2A(\omega) = F_0/|k - m\omega^2|, which is finite for every ωω0\omega \neq \omega_0 but blows up at ω=ω0\omega = \omega_0. What actually happens?

Off-resonance: beats

When ω\omega is close to but not equal to ω0\omega_0, with initial conditions zero:

x(t)  =  F0m(ω02ω2)(cosωtcosω0t).x(t) \;=\; \dfrac{F_0}{m(\omega_0^2 - \omega^2)}\bigl(\cos\omega t - \cos\omega_0 t\bigr).

Using the sum-to-product identity cosAcosB=2sin ⁣(A+B2)sin ⁣(AB2)\cos A - \cos B = -2\sin\!\bigl(\tfrac{A+B}{2}\bigr)\sin\!\bigl(\tfrac{A-B}{2}\bigr),

x(t)  =  2F0m(ω02ω2)sin ⁣(ω0+ω2t)sin ⁣(ω0ω2t).x(t) \;=\; \dfrac{2F_0}{m(\omega_0^2 - \omega^2)}\,\sin\!\Bigl(\tfrac{\omega_0+\omega}{2}\,t\Bigr)\sin\!\Bigl(\tfrac{\omega_0-\omega}{2}\,t\Bigr).

The fast factor at frequency (ω0+ω)/2(\omega_0+\omega)/2 looks like a normal oscillation, but it's amplitude-modulated by the slow factor at frequency (ω0ω)/2(\omega_0-\omega)/2 — that's a beat. You hear beats when you tune a guitar string near a reference pitch: the volume swells and fades at the difference frequency.

On resonance: secular (linear-in-t) growth

The above formula has a 0/0 problem at ω=ω0\omega = \omega_0. Take the limit carefully (L'Hôpital, or use the method of undetermined coefficients with xp=Atsin(ω0t)x_p = A t \sin(\omega_0 t)) and you find

xp(t)  =  F02mω0  tsin(ω0t).x_p(t) \;=\; \dfrac{F_0}{2 m \omega_0}\;t\,\sin(\omega_0 t).

The amplitude F02mω0t\tfrac{F_0}{2m\omega_0}\,t grows linearly forever. In an ideal undamped system, a perfectly tuned force pumps unlimited energy into the oscillator. Real systems are saved by either nonzero damping (the system never quite gets there) or by nonlinearity (the spring stops being Hookean at large displacements).

Tacoma Narrows, in one sentence

The 1940 Tacoma Narrows bridge collapse was not simple linear resonance from steady wind. It was a self-excited aeroelastic flutter in which the bridge's motion changed the airflow, which then re-fed the motion — a fundamentally nonlinear feedback loop. The intuition you build from this section still helps: a small input at the right frequency was dramatically amplified. The full mechanism is a nonlinear extension covered in aerodynamics courses.


Computation in Python

Two ingredients: the algebraic A(ω),φ(ω)A(\omega), \varphi(\omega) formulas, and a plain RK4 integrator that solves the full ODE. We check that the integrator's late-time output matches the algebra.

Driven oscillator — closed form + RK4
🐍driven_oscillator.py
1Imports

NumPy gives us vectorized math on the time grid; Matplotlib lets us plot (the print statements below show the same information without a window). Nothing here is exotic — the entire driven-oscillator world is one quadratic and one RK4 loop.

4steady_state(m, c, k, F0, omega) header

Closed-form solver for the long-time response. Given the physical constants and a driving frequency, return two numbers: amplitude A and phase lag φ. Together they completely describe the steady state x_ss(t) = A·cos(ωt − φ). This function does NOT simulate — it uses the algebraic formulas derived in the prose above.

10Real and imaginary parts of the impedance

Think of the LHS m·x'' + c·x' + k·x acting on cos(ωt) as a complex multiplication: x'' contributes −mω², x' contributes +iωc, and x contributes +k. Grouping real + imaginary: (k − mω²) + i(cω). The real part is the 'spring-vs-inertia' balance; the imaginary part is the velocity-coupled damping.

12|Z| — magnitude of the impedance

|Z| = √((k − mω²)² + (cω)²). This is just Pythagoras on the two parts. |Z| tells you how 'hard' the system resists being driven at frequency ω. Small |Z| ⇒ tiny force produces big motion ⇒ resonance.

13Amplitude formula

A = F0 / |Z|. Linear-response gain: the size of the output is the size of the input divided by impedance, exactly like Ohm's law I = V / Z in AC circuits. When ω = ω₀ = √(k/m) the first term in |Z| vanishes and A is limited only by damping.

14Phase lag from atan2

φ = atan2(cω, k − mω²) returns an angle in [0, π]. Below resonance the real part k − mω² > 0 so φ ≈ 0 (mass moves in phase with the force). Above resonance k − mω² < 0 so φ ≈ π (mass moves opposite the force). EXACTLY at ω = ω₀ the real part is zero and φ = π/2 — the famous quarter-cycle lag.

17simulate(...) header

Black-box numerical solver. Integrate the FULL ODE m·x'' + c·x' + k·x = F₀ cos(ωt) including the transient. We will compare this to the closed form to verify that the steady state really is what the system converges to.

19Linear system matrix

Convert the second-order ODE into a first-order system Y = [x, v]: x' = v, v' = −(c/m)v − (k/m)x + (F₀/m)·cos(ωt). The first two terms become the constant matrix A_sys; the forcing becomes the additive vector below. This is the standard 'state-space' form numerical solvers consume.

22Right-hand side rhs(t, Y)

Notice rhs depends on t EXPLICITLY because of cos(ωt). That makes the system nonautonomous. The force enters only the velocity equation (second component) — applying force to position would mean teleporting, which is unphysical.

27Initial state Y₀

Two initial conditions x(0) and x'(0). We pick (0, 0) so the mass starts at rest at equilibrium — then every motion you see is purely the response to the force F₀cos(ωt). This isolates the transient from any 'free-ringing' that an initial displacement would add.

32Classical RK4 step

Four samples of rhs at t, t + h/2, t + h/2, t + h. The weighted combination (k1 + 2k2 + 2k3 + k4)/6 is accurate to O(h⁴) globally. We need a quality integrator here because the driven system can be stiff near resonance — Euler's method would visibly distort the amplitude.

41Pick the worked example

m=1, c=0.4, k=4 ⇒ ω₀ = √(k/m) = 2 rad/s and ζ = c/(2√(mk)) = 0.1 (underdamped). We drive at exactly the resonant frequency ω = 2. This is the setup most likely to make the amplitude formula spectacular.

45Closed-form amplitude / phase

Hand-check: |Z| = √((4 − 4)² + (0.4·2)²) = √(0 + 0.64) = 0.8. A = 2 / 0.8 = 2.5. φ = atan2(0.8, 0) = π/2 = 90°. So x_ss(t) = 2.5·cos(2t − π/2) = 2.5·sin(2t). The output is 90° behind the input — pure quadrature.

49Numerical solution

Integrate from t = 0 to t = 30 with 1500 steps. Initial conditions (0, 0) — completely at rest. RK4 will produce the transient (rising oscillation) plus the steady state. After t ≈ 25 the transient has decayed (e^(-0.2·25) ≈ 0.0067, so transient amplitude ≈ 2.5·0.0067 ≈ 0.017) and x_num should match x_ss to ~3 decimal places.

52Sanity check at t = 30

|x_num(30) − x_ss(30)| should be ~10⁻² or smaller. This is the round-trip proof: the algebraic steady-state formula and the numerical integrator agree, so we trust BOTH. Whenever you derive a closed form, finish by simulating and comparing.

35 lines without explanation
1import numpy as np
2import matplotlib.pyplot as plt
3
4def steady_state(m, c, k, F0, omega):
5    """
6    Closed-form amplitude and phase lag of
7        m x'' + c x' + k x = F0 cos(omega t).
8    Returns (A, phi) so that  x_ss(t) = A cos(omega t - phi).
9    """
10    denom_re = k - m * omega**2
11    denom_im = c * omega
12    Z = np.sqrt(denom_re**2 + denom_im**2)        # |impedance|
13    A   = F0 / Z
14    phi = np.arctan2(denom_im, denom_re)          # in [0, pi]
15    return A, phi
16
17def simulate(m, c, k, F0, omega, x0, v0, t):
18    """RK4 integration of the FULL driven equation."""
19    A_sys = np.array([[0.0,    1.0],
20                      [-k/m, -c/m]])
21    def rhs(tt, Y):
22        force = np.array([0.0, F0 * np.cos(omega * tt) / m])
23        return A_sys @ Y + force
24
25    Y = np.array([x0, v0], dtype=float)
26    out = np.zeros((len(t), 2))
27    out[0] = Y
28    for i in range(1, len(t)):
29        h  = t[i] - t[i - 1]
30        k1 = rhs(t[i - 1],         Y)
31        k2 = rhs(t[i - 1] + h/2,   Y + h * k1 / 2)
32        k3 = rhs(t[i - 1] + h/2,   Y + h * k2 / 2)
33        k4 = rhs(t[i - 1] + h,     Y + h * k3)
34        Y  = Y + h * (k1 + 2*k2 + 2*k3 + k4) / 6
35        out[i] = Y
36    return out[:, 0]
37
38# ---- the worked example ----
39m, c, k = 1.0, 0.4, 4.0
40F0, omega = 2.0, 2.0              # drive AT the natural frequency
41t = np.linspace(0, 30, 1500)
42
43A, phi = steady_state(m, c, k, F0, omega)
44print(f"steady-state amplitude  A   = {A:.4f}")     # 2.5000
45print(f"phase lag               phi = {np.degrees(phi):.2f} deg")  # 90 deg
46
47x_num = simulate(m, c, k, F0, omega, x0=0.0, v0=0.0, t=t)
48x_ss  = A * np.cos(omega * t - phi)
49
50print(f"|x_num(end) - x_ss(end)| = {abs(x_num[-1] - x_ss[-1]):.2e}")  # ~0

PyTorch: Complex-Impedance Sweep

The complex form X(ω)=F0/Z(ω)X(\omega) = F_0 / Z(\omega) is one line of vectorized math. PyTorch's native complex tensors let us sweep hundreds of frequencies at once, then pull out amplitude, phase, and quality factor with a few more lines. This is exactly how vibration engineers compute “frequency-response functions” in production tools.

Resonance sweep with complex tensors
🐍resonance_sweep_torch.py
1Import PyTorch

We use PyTorch's native complex tensor support — torch.complex(real, imag) builds a complex tensor, and .abs() / .angle() extract modulus and phase. The entire resonance curve is one vectorized expression with no Python loop.

3resonance_sweep(m, c, k, F0, omegas) header

Single function that takes the four physical constants and a TENSOR of driving frequencies, and returns the complex steady-state amplitude X(ω) for every frequency at once. This is the workhorse used by every vibration engineer to draw the famous 'frequency-response function'.

11Real part of the impedance

re = k − mω² for every ω in the input tensor. Broadcasting handles the elementwise math: omegas is shape (400,), m is a scalar, so re is shape (400,). When ω = ω₀ = √(k/m), this entire vector entry is zero — the resonance signature.

12Imaginary part of the impedance

im = c·ω. This is the damping term, which scales linearly with frequency. Without it, the impedance would be a pure real number that crosses zero at resonance, giving infinite amplitude. Damping is what saves real physical systems from blowing up at ω = ω₀.

13Build complex impedance Z

torch.complex(re, im) stitches the real and imaginary parts into a complex128 (or complex64) tensor. Z[i] = re[i] + i·im[i]. Geometrically Z is a vector in the complex plane: as ω increases, Z traces a curve that crosses the imaginary axis exactly at ω = ω₀.

14Complex amplitude X(ω) = F₀ / Z

True ohm's-law form. Dividing a real F₀ by the complex Z gives a complex X. Its MODULUS is the physical amplitude; its ARGUMENT is the phase shift between input and output. One line of code captures everything the textbook formulas described in two pages.

17Setup

Same physical constants as the Python example so numbers line up. ω₀ = √(k/m) = √4 = 2 rad/s is the natural frequency we expect to dominate the curve.

22Frequency grid

400 frequencies from 0.05 to 4 rad/s — a band that brackets the resonance peak at 2 rad/s with healthy margin. A denser grid would resolve the peak better; too sparse and you could miss the peak entirely between samples.

25Magnitude and phase

A = |X| gives the amplitude as a real tensor, ready to plot. Phase φ = −angle(X) — the negative sign converts torch's convention (output relative to input) into the engineering convention 'how much does the output LAG the input'. Engineers like positive lag values when the input drives the output.

29Find the peak

torch.argmax(A) returns the index of the largest amplitude. For our (m=1, c=0.4, k=4) setup the exact peak sits at ω_peak = ω₀·√(1 − 2ζ²) = 2·√(1 − 0.02) ≈ 1.98 rad/s, slightly BELOW ω₀. Many students confuse 'peak amplitude frequency' with 'natural frequency'; they coincide only in the undamped limit.

35Half-power amplitude

Engineering convention: the 'bandwidth' Δω of a resonance is the spread of frequencies where the amplitude is ≥ A_max/√2. (Equivalently the power, ∝ A², drops to half — hence 'half-power'.) A_max / √2 is the standard threshold for separating the 'in-band' region from the 'out-of-band' tails.

36Mask + bandwidth

Boolean mask selects all ω for which A ≥ A_max/√2. Then max − min over that selected slice gives Δω. For lightly damped systems this is a narrow window around ω₀; for heavy damping it widens until the peak disappears.

38Quality factor Q

Q = ω₀ / Δω is THE single number summarizing how sharp a resonance is. Q ≈ 1/(2ζ) for light damping, so our ζ = 0.1 gives Q ≈ 5. Crystal oscillators reach Q ≈ 10⁶; MRI magnets Q ≈ 10⁹; LIGO's mirrors Q ≈ 10⁷. Higher Q means a system 'rings' longer and responds to a sharper band of frequencies.

26 lines without explanation
1import torch
2
3def resonance_sweep(m, c, k, F0, omegas):
4    """
5    Sweep driving frequencies and return complex steady-state amplitude X(omega).
6
7        m x'' + c x' + k x = F0 e^{i omega t}    (complex form)
8        guess  x = X e^{i omega t}
9        =>  (-m omega^2 + i c omega + k) X = F0
10        =>  X(omega) = F0 / ( k - m omega^2  +  i c omega )
11    """
12    re = k - m * omegas**2
13    im = c * omegas
14    Z  = torch.complex(re, im)            # impedance, one complex per frequency
15    return F0 / Z                          # complex amplitude X(omega)
16
17# physical setup
18m, c, k = 1.0, 0.4, 4.0
19F0      = 2.0
20omega_0 = (k / m) ** 0.5                  # natural frequency
21
22omegas = torch.linspace(0.05, 4.0, 400)
23X       = resonance_sweep(m, c, k, F0, omegas)
24
25A   = X.abs()                             # amplitude  |X|
26phi = -X.angle()                          # phase lag  (mass lags force by phi)
27
28# locate the resonance peak
29peak_idx = torch.argmax(A)
30print(f"natural freq        omega_0    = {omega_0:.4f}")
31print(f"peak amplitude      A_max      = {A[peak_idx].item():.4f}")
32print(f"peak frequency      omega_peak = {omegas[peak_idx].item():.4f}")
33
34# Q factor and bandwidth (half-power points)
35A_half = A[peak_idx] / (2.0 ** 0.5)
36mask   = A >= A_half
37bw     = omegas[mask].max() - omegas[mask].min()
38print(f"half-power bandwidth Delta_omega = {bw.item():.4f}")
39print(f"quality factor       Q          = {(omega_0 / bw).item():.3f}")

Why PyTorch and not just NumPy?

For this scalar example, NumPy would work identically (it also has complex arrays). PyTorch matters once you want to differentiate through the resonance curve — e.g. optimize a vibration absorber by gradient descent on the peak amplitude, or include this physics inside a neural-network controller. Then having every quantity as a torch.Tensor with requires_grad=Truerequires\_grad=True means PyTorch's autograd computes Amax/c\partial A_{\max}/\partial c for free.


Where Resonance Shows Up

SystemDriving forceWhat ω₀ corresponds toQ typical
Car suspensionBumps in the road at speed v / λ≈ 1 Hz (designed soft)5–10
Tall building under windVortex shedding behind the structure0.1–1 Hz (mass dampers detune it)20–50
Quartz watch crystalElectronic feedback oscillator32 768 Hz (= 2¹⁵)10⁵–10⁶
MRI 1H nucleiRF pulseγ B₀ / 2π ≈ 64 MHz at 1.5 T10³ in tissue
LC radio tunerAntenna voltage at every broadcast frequency1/√(LC)50–200
Microwave oven2.45 GHz EM waveRotational mode of H₂O dipolelow — by design (broad heating band)
LIGO test massSeismic noise + radiation pressure≈ 0.5 Hz pendulum mode10⁷

Every row uses the same mx¨+cx˙+kx=F0cos(ωt)m\ddot{x} + c\dot{x} + kx = F_0\cos(\omega t) equation. The only thing changing is the units of m,c,k,xm, c, k, x. That is the power of having one canonical form: solve it once, recognize it everywhere.


Common Pitfalls

Confusing ω₀, ω_d, and ω_peak

Three frequencies all live near each other and get mixed up. Keep them straight:

  • ω0=k/m\omega_0 = \sqrt{k/m} — the undamped natural frequency. Depends only on kk and mm.
  • ωd=ω01ζ2\omega_d = \omega_0\sqrt{1-\zeta^2} — the damped natural frequency. The frequency you actually see in the transient ringing.
  • ωp=ω012ζ2\omega_p = \omega_0\sqrt{1-2\zeta^2} — the frequency that maximizes the driven amplitude A(ω)A(\omega).

For tiny ζ\zeta all three collapse to the same number. For larger damping they spread out, and confusing them leads to wrong predictions of where the peak sits.

Forgetting to add the transient

The steady state alone is NOT the full solution. It almost never satisfies your initial conditions. Always write x(t)=xh(t)+xp(t)x(t) = x_h(t) + x_p(t) and use BOTH x(0)x(0) and x(0)x'(0) to pin down the two constants in xhx_h.

Dividing by zero in the undamped resonance

For c=0c = 0 and ω=ω0\omega = \omega_0, the formula A=F0/(cω)A = F_0/(c\omega) is undefined. Don't just “assume it's infinite” — the actual particular solution is the secular form xp=F02mω0tsin(ω0t)x_p = \tfrac{F_0}{2m\omega_0}\, t\sin(\omega_0 t) we derived in the undamped section. Numerical solvers that step too far without damping will accumulate spurious energy and explode.

Sign of the phase

Textbooks differ on whether φ\varphi means the angle by which the response lags the input or leads the input. Our convention here: xp=Acos(ωtφ)x_p = A\cos(\omega t - \varphi) with φ[0,π]\varphi \in [0, \pi] a non-negative lag. Always check the convention before reading someone else's Bode plot.


Summary

ConceptFormulaMeaning
Driven oscillator equationm x″ + c x′ + k x = F₀ cos(ωt)Linear, constant-coefficient, sinusoidally forced second-order ODE.
Steady-state ansatz (complex)x_p = X e^{iωt}, X = F₀ / Z(ω)Differential equation collapses into algebra by exp ansatz.
ImpedanceZ(ω) = (k − mω²) + i c ωSingle complex number per frequency. Mechanical analogue of electrical impedance.
AmplitudeA(ω) = F₀ / √((k − mω²)² + (cω)²)Gain. Peaks near ω = ω₀; height controlled by damping.
Phase lagtan φ(ω) = cω / (k − mω²)Crosses π/2 exactly at ω = ω₀ — universal signature of resonance.
Resonance peakω_p = ω₀√(1 − 2ζ²); A_max = (F₀/k)·QSlightly below ω₀ for damped systems. Peak vanishes for ζ ≥ 1/√2.
Quality factorQ = 1/(2ζ) = √(mk)/c = ω₀/ΔωSharpness of the resonance peak; ratio of stored to dissipated energy per cycle.
Undamped on-resonance solutionx_p = (F₀ / 2mω₀) · t · sin(ω₀ t)Linear (secular) growth — the source of every Tacoma-Narrows story.
Beatsx = (2F₀ / m(ω₀² − ω²)) · sin(½(ω₀+ω)t) · sin(½(ω₀−ω)t)Slow envelope modulating a fast carrier when ω is close to but not equal to ω₀.

The one-line takeaway

A linear second-order system, driven at frequency ω\omega, responds at the same frequency, with amplitude F0/Z(ω)F_0/|Z(\omega)| and lag argZ(ω)\arg Z(\omega). The impedance Z(ω)=kmω2+icωZ(\omega) = k - m\omega^2 + i c\omega dips closest to zero near ω=ω0\omega = \omega_0, and that dip — the resonance — is responsible for every story in this section, from a child on a swing to a quartz crystal vibrating thirty thousand times a second inside your wristwatch.

Loading comments...