Monocurl Essays
Series Download

Simple harmonic motion: springs and pendulums

Why a mass on a spring takes the same time per swing however far it is pulled: Hooke's law and the shadow of circular motion, and why a pendulum nearly does.

21 min·High school·

AssumesNewton's second law, F = ma: what force really doesConservation of energy: a roller coaster's bookkeepingSine waves: amplitude, frequency and phase

Two identical 0.5 kg blocks on identical springs, in real time. One is pulled out 10 cm and the other 5 cm, and they are released together. The block with twice as far to go returns at the same moment, swing after swing.
Monocurl source
let S = 8
let EQ = -1.85
let fh = Frame(0, 3.2, -12, 12, [2.2, 0.15, 0], 2.7, 2.9)
let LANES = [[0.85, 0.1, EMBER], [-1.05, 0.05, AZURE]]
mesh plot = Panel(fh, [0.5, 1, 1.5, 2, 2.5, 3], [-10, -5, 5, 10], [0, 1, 2, 3], [-10, -5, 0, 5, 10], "time (s)", "displacement (cm)")
mesh rulers = block {
    for (ln in LANES) {
        for (c in [-10, -5, 0, 5, 10]) {
            . Seg([EQ + S * c / 100, ln[0] - 0.1, 0], [EQ + S * c / 100, ln[0], 0], SLATE, 2.5)
            . center{[EQ + S * c / 100, ln[0] - 0.32, 0]} Math(to_string(c), 0.72, SLATE)
        }
        . center{[EQ, ln[0] + 0.95, 0]} Cap(str_join(["pulled out ", to_string(ln[1] * 100), " cm"]), 0.74, ln[2])
    }
    . center{[EQ, -1.72, 0]} Note("displacement (cm)", 0.72)
}
let Swing = |t| block {
    let n = to_int(max(2, ceil(t * 40)))
    for (ln in LANES) {
        let x = ln[1] * cos(W * t)
        . Rig(-3.75, -0.75, ln[0], EQ + S * x, ln[2])
        . stroke{ln[2], 3.5} Polyline(map(sample(0, max(t, 0.02), n), |u| at(fh, u, 100 * ln[1] * cos(W * u))))
        . Pt(at(fh, t, 100 * x), ln[2], 0.08)
    }
}
mesh swing = Swing(t: 0)

slide "swing"
    play Wait(0.7)
    swing.t = 2 * PERIOD
    play Lerp(2 * PERIOD, [&swing], linear)
    play Wait(1.4)

Uses the shared definitions for this lesson's figures.

Hook a block to a spring on a smooth table, pull it out 5 cm, and let go. It slides back through its resting position, overshoots, stops, and returns to your hand after some time. Now pull it out 10 cm instead. The block has twice as far to travel on every leg of the trip. Does the round trip take twice as long?

It takes exactly the same time, and so does a 1 cm pull. That is not true of most back-and-forth motions: a ball bouncing on the floor takes longer per bounce when dropped from higher. Something about a spring makes distance irrelevant to timing, and the same property is what makes a pendulum keep time.

Hooke’s law

A spring has a natural length. Stretch it and it pulls back; compress it and it pushes back. Measure the force for the spring in the animation and a simple pattern appears: 0.4 N at 5 cm of stretch, 0.8 N at 10 cm, 1.2 N at 15 cm. Each extra centimeter costs the same extra force.

Set up a coordinate. Let $x$ be the block’s displacement: its position measured from the place where the spring has its natural length, positive when the spring is stretched and negative when it is compressed. At $x = 0$ the spring exerts no force, so that point is the equilibrium position.

The spring's force on the block at three displacements, and the same three points on a graph of force against displacement. The force always points toward equilibrium, and its size is proportional to the displacement.
The spring’s force on the block at three displacements, and the same three points on a graph of force against displacement. The force always points toward equilibrium, and its size is proportional to the displacement.
Monocurl source
let S = 8
let EQ = -1.9
let ff = Frame(-12, 12, -1, 1, [2.45, 0.1, 0], 2.4, 2.8)
let CASES = [[1.05, -0.1], [-0.15, 0], [-1.35, 0.1]]
mesh plot = Panel(ff, [-10, -5, 5, 10], [-0.8, -0.4, 0.4, 0.8], [-10, 0, 10], [-0.8, -0.4, 0, 0.4, 0.8], "displacement x (cm)", "spring force F (N)")
mesh eqline = [
    Dash([EQ, -1.5, 0], [EQ, 1.95, 0], MIST, 2.5),
    center{[EQ, -1.78, 0]} Math("x = 0", 0.8, SLATE),
    center{[-0.12, 1.85, 0]} Note("x", 0.76)
]
mesh rigs = block {
    for (c in CASES) {
        let xb = EQ + S * c[1]
        . Rig(-3.75, -0.6, c[0], xb, AZURE)
        . center{[-0.12, c[0] + 0.3, 0]} Math(str_join([to_string(100 * c[1]), "\text{ cm}"]), 0.78, SLATE)
        if (c[1] != 0) {
            . Arrow2([xb, c[0] + 0.7, 0], [-1.25 * K * c[1], 0, 0], EMBER, 5)
            . center{[xb - 1.47 * sign(c[1]), c[0] + 0.7, 0]} Math(str_join([to_string(K * abs(c[1]), 1), "\text{ N}"]), 0.8, EMBER)
        }
    }
}
mesh line = [
    Seg(at(ff, -11.5, 0.92), at(ff, 11.5, -0.92), EMBER, 4),
    Pt(at(ff, -10, 0.8), EMBER, 0.09), Pt(at(ff, 0, 0), EMBER, 0.09), Pt(at(ff, 10, -0.8), EMBER, 0.09),
    center{at(ff, 5.5, 0.6)} Math("F = -kx", 0.85, EMBER)
]

Uses the shared definitions for this lesson's figures.

For our spring $k = 0.4 \text{ N} / 0.05 \text{ m} = 8$ N/m. Hooke’s law is an experimental rule about springs, with limits: stretch a spring too far and it deforms permanently, and the proportionality fails well before that. Within its range it is accurate to a percent or so for a good steel spring. We also take the spring’s own mass to be negligible and the table frictionless. That set of assumptions defines the ideal spring.

A force that changes as the block moves

Newton’s second law for the block, of mass $m$, says $ma = -kx$, so

$$ a = -\frac{k}{m}\,x. $$

The acceleration is not constant. It depends on where the block is: largest at the ends, zero at the center, and always pointing toward the center. The constant-acceleration equations do not apply, so we cannot read off how long a swing takes. Still, a rough estimate is possible.

Pause and try

The block ($m = 0.5$ kg, $k = 8$ N/m) is released from rest at $x = A$. Pretend the acceleration keeps its initial value all the way to the center, and estimate the time to get there, for $A = 10$ cm and for $A = 5$ cm. Is the estimate too short or too long?

Show answerHide answer

The initial acceleration has size $kA/m$. Starting from rest and covering a distance $A$ at that constant acceleration: $A = \tfrac12 (kA/m)\,t^2$, so $t = \sqrt{2m/k}$. The amplitude $A$ cancels. For both pulls, $t = \sqrt{2 \times 0.5/8} = 0.354$ s. It is too short, because the real acceleration fades as the block approaches the center. (The true time is 0.393 s.)

The estimate is crude, but the cancellation in it is exact, and here is why. Suppose some motion $x(t)$ obeys $ma = -kx$ at every instant. Double it: consider the motion in which the block is always twice as far from equilibrium. Its velocity and its acceleration are doubled at every instant, so it needs twice the force at every instant, and at twice the displacement the spring supplies exactly twice the force. So the doubled motion also obeys the law, on the same schedule. A larger swing has farther to go and is pushed proportionally harder all the way.

This argument uses only the proportionality of force to displacement. It tells us the period cannot depend on the amplitude. It does not tell us what the period is, or what the motion looks like in between. For that we need a function whose acceleration is, at every moment, a negative constant times its own value.

The shadow of a circle

One familiar motion has an acceleration that points back toward a center and is proportional to the distance from it: steady motion around a circle.

Let a peg ride on the rim of a wheel of radius $A$ that turns at a constant rate. Measure angles in radians, and let the wheel turn through $\omega$ radians each second, so that after $t$ seconds the peg is at angle $\omega t$, having started on the positive $x$ axis. By the definition of cosine on the unit circle, the peg’s $x$ coordinate is $A\cos(\omega t)$. That coordinate is the peg’s shadow on the floor under a light shining straight down.

A wheel of radius 10 cm turning at 4 radians per second, in real time, above the block on its spring. The block’s position is computed step by step from $F = ma$ with the spring force. It stays directly under the peg.
Monocurl source
let S = 12
let EQ = -1.3
let C = [EQ, 0.72, 0]
let R = S * 0.1
let DT = 0.005
let Sim = |x0| {
    var x = x0
    var v = 0
    var out = []
    for (i in range(0, 640)) {
        out .= x
        let vh = v - (K / M) * x * DT / 2
        x = x + vh * DT
        v = vh - (K / M) * x * DT / 2
    }
    return out
}
let SIM = Sim(0.1)
mesh wheel = [
    fill{tint(SLATE, 0.08)} stroke{SLATE, 3} center{C} Circle(R, 96),
    Pt(C, SLATE, 0.05),
    Seg([EQ - R - 0.2, -1.55, 0], [EQ + R + 0.2, -1.55, 0], SLATE, 3)
]
mesh ruler = block {
    for (c in [-10, -5, 0, 5, 10]) {
        . Seg([EQ + S * c / 100, -1.65, 0], [EQ + S * c / 100, -1.55, 0], SLATE, 2.5)
        . center{[EQ + S * c / 100, -1.87, 0]} Math(to_string(c), 0.72, SLATE)
    }
}
mesh names = [
    center{[2.55, 1.6, 0]} Note("wheel angle (rad)", 0.74),
    center{[2.55, 0.35, 0]} Note("peg's shadow (cm)", 0.74),
    center{[2.55, -0.9, 0]} Note("block, from F = ma (cm)", 0.74)
]
let Turn = |t| {
    let p = [C[0] + R * cos(W * t), C[1] + R * sin(W * t), 0]
    let xb = EQ + S * look(SIM, t, DT)
    return [
        Rig(-3.8, EQ + R + 0.45, -1.55, xb, AZURE),
        Dash(p, [p[0], -1.05, 0], EMBER, 2.5),
        Seg(C, p, EMBER, 3),
        Pt(p, EMBER, 0.1),
        center{[2.55, 1.1, 0]} scale{1.25} color{INK} Number(W * t, 2),
        center{[2.55, -0.15, 0]} scale{1.25} color{EMBER} Number(tidy(10 * cos(W * t)), 1),
        center{[2.55, -1.4, 0]} scale{1.25} color{AZURE} Number(tidy(100 * look(SIM, t, DT)), 1)
    ]
}
mesh turn = Turn(t: 0)

slide "turn"
    play Wait(0.6)
    turn.t = 2 * PERIOD
    play Lerp(2 * PERIOD, [&turn], linear)
    play Wait(1.4)

Uses the shared definitions for this lesson's figures.

In the animation the block is not told about the wheel. Its position is advanced in steps of 5 milliseconds using nothing but $a = -(k/m)x$, and it stays under the peg to within a tenth of a millimeter. To see why, we need the peg’s acceleration.

The peg’s position is an arrow of fixed length $A$ from the center, turning at $\omega$ radians per second. Its tip moves along the rim, covering an arc of $A\omega$ meters each second, in a direction perpendicular to the arrow. So the velocity has size $\omega A$ and points a quarter turn ahead of the position arrow. Now apply the same reasoning to the velocity arrow. It too has fixed length, $\omega A$, and turns at the same rate $\omega$. So its tip moves at $\omega \cdot \omega A$, a quarter turn ahead of the velocity, which is a half turn ahead of the position. The acceleration has size $\omega^2 A$ and points from the peg straight back to the center. (This is a sketch, since it takes for granted that the tip of a turning arrow moves perpendicular to the arrow at $\omega$ times its length. With $v = \omega A$ and $r = A$, the result $\omega^2 A$ is the acceleration $v^2/r$ of circular motion, which the lesson on circular motion and orbits proves in full with a velocity triangle.)

  1. The peg is at angle ωt on a circle of radius A, so its horizontal position is A cos ωt.
  2. Its velocity has size ωA along the tangent. The horizontal part is −ωA sin ωt.
  3. Its acceleration has size ω²A toward the center. The horizontal part is −ω²A cos ωt, which is −ω² times x.
The peg at one instant, with its position, velocity and acceleration, and the horizontal part of each. The horizontal part of the acceleration is $-\omega^2$ times the horizontal part of the position.
Monocurl source
let C = [-1.75, -0.3, 0]
let R = 1.5
let TH = deg_to_rad(50)
let P = C + [R * cos(TH), R * sin(TH), 0]
let VL = 1.45
let AL = 1.15
mesh base = [
    fill{tint(SLATE, 0.06)} stroke{SLATE, 3} center{C} Circle(R, 96),
    Seg([C[0] - R - 0.25, C[1], 0], [C[0] + R + 0.25, C[1], 0], MIST, 2),
    Pt(C, SLATE, 0.05)
]
mesh pos = []
mesh vel = []
mesh vpart = []
mesh acc = []
mesh rows = []

slide "The peg is at angle ωt on a circle of radius A, so its horizontal position is A cos ωt."
    pos = [
        stroke{SLATE, 2.5} Angle(C, C + [1, 0, 0], P, 0.42),
        center{C + [0.68, 0.24, 0]} Math("\omega t", 0.8, SLATE),
        Dash(P, [P[0], C[1], 0], SLATE, 2),
        Seg(C, P, SLATE, 3),
        Arrow2(C + [0, -0.14, 0], [R * cos(TH), 0, 0], AZURE, 5),
        center{C + [R * cos(TH) / 2, -0.5, 0]} Math("x", 0.9, AZURE),
        Pt(P, INK, 0.09)
    ]
    rows = [
        center{[2.35, 1.65, 0]} Note("position", 0.74),
        center{[2.35, 1.15, 0]} Math("x = A\cos\omega t", 0.88, AZURE)
    ]
    play Fade(0.8)

slide "Its velocity has size ωA along the tangent. The horizontal part is −ωA sin ωt."
    vel = Arrow2(P, [-VL * sin(TH), VL * cos(TH), 0], PLUM, 5.5)
    vpart = [
        Dash(P + [-VL * sin(TH), VL * cos(TH), 0], P + [-VL * sin(TH), 0, 0], PLUM, 2),
        Arrow2(P, [-VL * sin(TH), 0, 0], PLUM, 4)
    ]
    rows .= center{[2.35, 0.3, 0]} Note("velocity", 0.74)
    rows .= center{[2.35, -0.2, 0]} Math("v_x = -\omega A\sin\omega t", 0.88, PLUM)
    play Fade(0.8)

slide "Its acceleration has size ω²A toward the center. The horizontal part is −ω²A cos ωt, which is −ω² times x."
    vpart = []
    play Fade(0.3, [&vpart])
    acc = [
        Arrow2(P, [-AL * cos(TH), -AL * sin(TH), 0], EMBER, 5.5),
        Dash(P + [-AL * cos(TH), -AL * sin(TH), 0], P + [-AL * cos(TH), 0, 0], EMBER, 2),
        Arrow2(P, [-AL * cos(TH), 0, 0], EMBER, 4)
    ]
    rows .= center{[2.35, -1.05, 0]} Note("acceleration", 0.74)
    rows .= center{[2.35, -1.55, 0]} Math("a_x = -\omega^2 x", 0.92, EMBER)
    play Fade(0.8)

Uses the shared definitions for this lesson's figures.

Take the horizontal part of each arrow. The acceleration arrow has length $\omega^2 A$ and points opposite to the position arrow, so its horizontal part is $-\omega^2$ times the horizontal part of the position:

$$ a_x = -\omega^2 A\cos(\omega t) = -\omega^2\,x. $$

The shadow’s acceleration is a negative constant times its position, which is exactly the property the block needs. The two equations $a = -\omega^2 x$ and $a = -(k/m)\,x$ are the same equation when $\omega^2 = k/m$.

The theorem says this motion satisfies the law. It is also the only motion that does, for that release, because a starting position and velocity, together with a rule giving the acceleration at each position, determine the motion that follows. The step-by-step computation in the animation makes this believable, since it never had a choice to make; the full proof is the uniqueness theorem for differential equations, which we are citing. Motion of the form $A\cos(\omega t)$, or the same shifted in time, is called simple harmonic motion, $A$ is its amplitude, and $\omega$ is its angular frequency, in rad/s. Its graph against time is a sine wave.

The block completes one cycle when the peg completes one turn, $\omega T = 2\pi$. So the period is

$$ T = \frac{2\pi}{\omega} = 2\pi\sqrt{\frac{m}{k}}, $$

and the frequency, the number of cycles per second, is $f = 1/T$, in hertz (Hz). For our block, $\omega = \sqrt{8/0.5} = 4$ rad/s and $T = 2\pi/4 = 1.57$ s. A quarter of that, 0.393 s, is the time from release to center. Check the units: $m/k$ is $\text{kg} \div (\text{N/m}) = \text{kg·m/N} = \text{s}^2$, so the square root is a time.

More mass makes the motion slower and a stiffer spring makes it faster, each through a square root: four times the mass doubles the period.

Energy goes back and forth

The block is fastest at the center, where the spring is relaxed, and momentarily at rest at the ends, where the spring is most deformed. In the language of energy, kinetic energy is being traded for something stored in the spring.

To find how much is stored, compute the work the spring does on the block as it moves from displacement $x$ back to 0. The force is not constant, so use the area under the force-displacement graph, as the last two lessons did for work and impulse. The force falls in a straight line from size $kx$ to 0 over a distance $x$, so the area is a triangle: $\tfrac12 \cdot x \cdot kx$.

Pause and try

Using $x = A\cos\omega t$ and $v = -\omega A\sin\omega t$, compute the total $\tfrac12 mv^2 + \tfrac12 kx^2$ at time $t$. You will need $m\omega^2 = k$.

Show answerHide answer

$\tfrac12 m\omega^2A^2\sin^2\omega t + \tfrac12 kA^2\cos^2\omega t = \tfrac12 kA^2(\sin^2\omega t + \cos^2\omega t) = \tfrac12 kA^2$, by the Pythagorean identity. The total does not depend on $t$. For our block with $A = 0.1$ m it is $\tfrac12(8)(0.01) = 0.04$ J.

The 0.5 kg block released at 10 cm, in real time, above a graph of energy against displacement. The blue bar is the energy stored in the spring and the orange bar the kinetic energy. They always add up to 40 mJ.
Monocurl source
let fe = Frame(-10, 10, 0, 45, [-0.75, -0.42, 0], 4.2, 1.9)
let pe = |x| 0.5 * K * x * x / 10
mesh plot = Panel(fe, [-10, -5, 0, 5, 10], [10, 20, 30, 40], [-10, -5, 0, 5, 10], [0, 10, 20, 30, 40], "displacement (cm)", "energy (mJ)")
mesh curves = [
    Seg(at(fe, -10, 40), at(fe, 10, 40), LEAF, 3),
    Graph(fe, pe, -10, 10, AZURE, 3, 80)
]
mesh names = [
    center{[2.85, 1.3, 0]} Cap("spring (mJ)", 0.74, AZURE),
    center{[2.85, 0.05, 0]} Cap("kinetic (mJ)", 0.74, EMBER),
    center{[2.85, -1.2, 0]} Cap("total (mJ)", 0.74, LEAF),
    center{[2.85, -1.7, 0]} Math(to_string(pe(10), 1), 0.95, LEAF)
]
let Slosh = |t| {
    let x = 10 * cos(W * t)
    let u = pe(x)
    return [
        Rig(-3.85, fx(fe, 10) + 0.5, 1.3, fx(fe, x), SLATE),
        Seg(at(fe, x, 0), at(fe, x, max(u, 0.3)), AZURE, 9),
        Seg(at(fe, x, min(u, 39.7)), at(fe, x, 40), EMBER, 9),
        Pt(at(fe, x, u), INK, 0.075),
        center{[2.85, 0.8, 0]} scale{1.2} color{AZURE} Number(u, 1),
        center{[2.85, -0.45, 0]} scale{1.2} color{EMBER} Number(tidy(40 - u), 1)
    ]
}
mesh slosh = Slosh(t: 0)

slide "slosh"
    play Wait(0.6)
    slosh.t = 2 * PERIOD
    play Lerp(2 * PERIOD, [&slosh], linear)
    play Wait(1.4)

Uses the shared definitions for this lesson's figures.

Conservation of energy gives the speed at any position without mentioning time. From $\tfrac12 mv^2 + \tfrac12 kx^2 = \tfrac12 kA^2$,

$$ v = \pm\,\omega\sqrt{A^2 - x^2}, $$

with the largest speed, $\omega A$, at the center. For our block that is $4 \times 0.1 = 0.4$ m/s.

The pendulum

A pendulum has no spring, and it oscillates for a similar reason. Model it as a small bob of mass $m$ on a string of length $L$ whose own mass is negligible, swinging in a vertical plane from a fixed pivot, with no friction or air resistance. Let $\theta$ be the angle of the string from the vertical, in radians, positive to the right. The bob moves on an arc of a circle, and its distance along the arc from the lowest point is $s = L\theta$.

The forces on a pendulum bob. The string's tension acts along the string and cannot speed the bob up or slow it down. The part of the weight along the arc, of size mg\sin\theta, pulls the bob back toward the lowest point.
The forces on a pendulum bob. The string’s tension acts along the string and cannot speed the bob up or slow it down. The part of the weight along the arc, of size $mg\sin\theta$, pulls the bob back toward the lowest point.
Monocurl source
let PV = [-1.9, 1.95, 0]
let L = 2.7
let TH = deg_to_rad(32)
let B = PV + [L * sin(TH), -L * cos(TH), 0]
let LOW = PV + [0, -L, 0]
let WT = 1.15
mesh frame = [
    Seg([PV[0] - 0.7, PV[1], 0], [PV[0] + 0.7, PV[1], 0], SLATE, 4),
    Dash(PV, LOW, SLATE, 2),
    dashed{[0.12, 0.08]} stroke{MIST, 2.5} Polyline(map(sample(-0.72, 0.72, 40), |a| PV + [L * sin(a), -L * cos(a), 0])),
    stroke{SLATE, 2.5} Angle(PV, LOW, B, 0.75),
    center{PV + [0.25, -1.05, 0]} Math("\theta", 0.9, SLATE),
    Seg(PV, B, INK, 3),
    center{PV + [L * sin(TH) / 2 + 0.3, -L * cos(TH) / 2 + 0.15, 0]} Math("L", 0.9, INK),
    stroke{SLATE, 4} Polyline(map(sample(0, TH, 16), |a| PV + [L * sin(a), -L * cos(a), 0])),
    center{LOW + [0.55, 0.42, 0]} Math("s", 0.9, SLATE)
]
mesh forces = [
    Dash(B + [0, -WT, 0], B + [WT * cos(TH) * sin(TH), -WT * cos(TH) * cos(TH), 0], SLATE, 2),
    Dash(B + [0, -WT, 0], B + [-WT * sin(TH) * cos(TH), -WT * sin(TH) * sin(TH), 0], SLATE, 2),
    Arrow2(B, [WT * cos(TH) * sin(TH), -WT * cos(TH) * cos(TH), 0], SLATE, 3),
    Arrow2(B, [-1.0 * sin(TH), 1.0 * cos(TH), 0], AZURE, 4.5),
    Arrow2(B, [0, -WT, 0], EMBER, 4.5),
    Arrow2(B, [-WT * sin(TH) * cos(TH), -WT * sin(TH) * sin(TH), 0], LEAF, 5),
    Pt(B, INK, 0.15),
    center{B + [-0.75, 0.75, 0]} Math("T", 0.9, AZURE),
    center{B + [0, -WT - 0.3, 0]} Math("mg", 0.9, EMBER),
    center{B + [-1.1, -0.78, 0]} Math("mg\sin\theta", 0.82, LEAF),
    center{B + [1.3, -0.75, 0]} Math("mg\cos\theta", 0.82, SLATE)
]
mesh rows = [
    center{[2.6, 1.7, 0]} Note("force along the arc", 0.74),
    center{[2.6, 1.2, 0]} Math("F = -mg\sin\theta", 0.88, LEAF),
    center{[2.6, 0.45, 0]} Note("arc length", 0.74),
    center{[2.6, -0.05, 0]} Math("s = L\theta", 0.88),
    center{[2.6, -0.8, 0]} Note("for small angles", 0.74),
    center{[2.6, -1.4, 0]} Math("F \approx -\frac{mg}{L}\,s", 0.88, LEAF)
]

Uses the shared definitions for this lesson's figures.

Two forces act on the bob: its weight $mg$, straight down, and the string’s tension, along the string. The tension is perpendicular to the arc, so only the weight has a part along the arc, and that part is

$$ F = -mg\sin\theta, $$

negative because it points back toward $\theta = 0$. The second law along the arc reads $ma = -mg\sin\theta$, where $a$ is the acceleration along the arc. This is a restoring force, but it is proportional to $\sin\theta$, not to the displacement $s = L\theta$. So a pendulum is not exactly a spring.

It is nearly one when the swing is small, because for small angles in radians $\sin\theta$ is close to $\theta$ itself.

Pause and try

With a calculator in radian mode, compare $\theta$ and $\sin\theta$ at $10°$ and at $30°$. By what percentage does $\theta$ overstate $\sin\theta$ in each case?

Show answerHide answer

$10° = 0.17453$ rad and $\sin 10° = 0.17365$: the difference is 0.00088, which is 0.51% of $\sin\theta$. $30° = 0.52360$ rad and $\sin 30° = 0.50000$: the difference is 0.0236, or 4.7%. Tripling the angle made the percentage error about nine times larger.

The angle \theta in radians and \sin\theta, plotted against the angle in degrees. The two agree closely up to about 20 degrees, and the gap, shown as a percentage of \sin\theta, grows quickly after that.
The angle $\theta$ in radians and $\sin\theta$, plotted against the angle in degrees. The two agree closely up to about 20 degrees, and the gap, shown as a percentage of $\sin\theta$, grows quickly after that.
Monocurl source
let fs = Frame(0, 90, 0, 1.6, [0.05, 0.2, 0], 6.2, 2.9)
let MARKS = [[10, 0.42], [30, 0.62], [60, 0.5]]
mesh plot = Panel(fs, [15, 30, 45, 60, 75, 90], [0.25, 0.5, 0.75, 1, 1.25, 1.5], [0, 15, 30, 45, 60, 75, 90], [0, 0.5, 1, 1.5], "angle (degrees)", "the angle in radians, and its sine")
mesh curves = [
    Seg(at(fs, 0, 0), at(fs, 90, PI / 2), EMBER, 4),
    Graph(fs, |d| sin(deg_to_rad(d)), 0, 90, AZURE, 4, 90),
    center{at(fs, 78, 1.52)} Math("\theta", 0.9, EMBER),
    center{at(fs, 82, 0.84)} Math("\sin\theta", 0.9, AZURE)
]
mesh gaps = block {
    for (mk in MARKS) {
        let d = mk[0]
        let r = deg_to_rad(d)
        . Seg(at(fs, d, sin(r)), at(fs, d, r), ROSE, 5)
        . center{[fx(fs, d) - 0.3, fy(fs, r) + mk[1], 0]} Math(str_join([to_string(100 * (r - sin(r)) / sin(r), 1), "\%%"]), 0.8, ROSE)
    }
}

Uses the shared definitions for this lesson's figures.

Replace $\sin\theta$ by $\theta = s/L$. The force along the arc becomes

$$ F \approx -mg\,\frac{s}{L} = -\left(\frac{mg}{L}\right)s. $$

This is Hooke’s law with the arc length $s$ as the displacement and $mg/L$ playing the part of $k$. The theorem applies with $\omega = \sqrt{k/m} = \sqrt{(mg/L)/m} = \sqrt{g/L}$.

The mass has canceled: a heavier bob is pulled harder in exactly the proportion needed to accelerate it the same. And within the approximation, the amplitude $\theta_0$ does not appear, which is what Galileo is said to have noticed watching a lamp swing in the cathedral at Pisa. A pendulum 1 m long has $T = 2\pi\sqrt{1/9.8} = 2.01$ s.

How good is the approximation? The exact period can be computed (the calculation needs calculus beyond this lesson, and we quote the result): for small $\theta_0$ it exceeds $2\pi\sqrt{L/g}$ by a fraction close to $\theta_0^2/16$, with $\theta_0$ in radians. The exact excess is 0.19% for a release at $10°$, 1.7% at $30°$, and 7.3% at $60°$, where $\theta_0^2/16$ gives 6.9% and has begun to fall short. A swing of a few degrees is, for most purposes, simple harmonic; a wide swing is noticeably slow.

Two 1 m pendulums released together, in real time, computed step by step from the exact force $-mg\sin\theta$. The 10 degree swing keeps the small-angle period of 2.01 s. The 60 degree swing takes 7% longer and falls behind.
Monocurl source
let L = 1
let DT = 0.01
let Sim = |deg| {
    var th = deg_to_rad(deg)
    var w = 0
    var out = []
    for (i in range(0, 620)) {
        out .= th
        let wh = w - (G / L) * sin(th) * DT / 2
        th = th + wh * DT
        w = wh - (G / L) * sin(th) * DT / 2
    }
    return out
}
let SMALL = Sim(10)
let BIG = Sim(60)
let T0 = TAU * sqrt(L / G)
let LEN = 2.7
let PA = [-2.6, 1.95, 0]
let PB = [0.75, 1.95, 0]
let Bob = |pv, th, col| {
    let b = pv + [LEN * sin(th), -LEN * cos(th), 0]
    return [Seg(pv, b, INK, 2.5), Pt(b, col, 0.15)]
}
let Ghost = |pv, deg| {
    let th = deg_to_rad(deg)
    let b = pv + [LEN * sin(th), -LEN * cos(th), 0]
    return [Seg(pv, b, MIST, 2), Ring(b, MIST, 0.15, 2.5)]
}
mesh rig = [
    Seg([-3.6, 1.95, 0], [3.6, 1.95, 0], SLATE, 4),
    Ghost(PA, 10), Ghost(PB, 60),
    center{[-2.6, -1.3, 0]} Cap("released at 10°", 0.74, AZURE),
    center{[0.75, -1.3, 0]} Cap("released at 60°", 0.74, EMBER),
    center{[-1.0, -1.85, 0]} Note("time (s)", 0.74)
]
let Go = |t| [
    Bob(PA, look(SMALL, t, DT), AZURE),
    Bob(PB, look(BIG, t, DT), EMBER),
    center{[0.1, -1.85, 0]} scale{1.2} color{INK} Number(t, 2)
]
mesh go = Go(t: 0)

slide "drift"
    play Wait(0.6)
    go.t = 3 * T0
    play Lerp(3 * T0, [&go], linear)
    play Wait(1.6)

Uses the shared definitions for this lesson's figures.

Worked examples

Limits and common mistakes

Simple harmonic motion is what $F = -kx$ produces, and real systems obey $F = -kx$ only approximately. The pendulum’s wide swings show one kind of failure. Friction and air resistance are another: they drain energy, so the amplitude decays, although for light damping the period hardly changes. The reason the ideal case matters so much is that almost any object resting at a stable equilibrium feels, for small enough displacements, a restoring force proportional to the displacement. Atoms in a crystal, a guitar string and a floating buoy all oscillate this way.

Check yourself

A mass on an ideal spring oscillates with period 1.2 s and amplitude 4 cm. The amplitude is increased to 8 cm, and the mass is also replaced by one four times as heavy. What is the new period?

In the next lesson a whole row of such oscillators, the beads of a rope, pull on their neighbors, and the result is a wave.

Problems

Use $g = 9.8$ m/s². Angles in formulas are in radians.

Problem 1

A force of 1.2 N stretches a spring by 3 cm. Find the spring constant. What force does the spring exert on a block attached to it when the spring is compressed by 5 cm, and in which direction?

Show solutionHide solution

$k = 1.2/0.03 = 40$ N/m. Compressed by 5 cm means $x = -0.05$ m, so $F = -kx = -40 \times (-0.05) = +2$ N: a force of 2 N in the positive direction, pushing the block away from the compressed spring, back toward equilibrium.

Problem 2

A 0.25 kg mass oscillates on a spring with $k = 100$ N/m and amplitude 4 cm. Find the angular frequency, period, frequency, maximum speed and maximum acceleration.

Show solutionHide solution

$\omega = \sqrt{100/0.25} = 20$ rad/s. $T = 2\pi/20 = 0.314$ s. $f = 1/T = 3.18$ Hz. Maximum speed $\omega A = 20 \times 0.04 = 0.8$ m/s. Maximum acceleration $\omega^2 A = 400 \times 0.04 = 16$ m/s², which is 1.6 times $g$.

Problem 3

The lesson’s block ($m = 0.5$ kg, $k = 8$ N/m) is released from rest at $x = 10$ cm at $t = 0$. (a) Find its position and velocity at $t = 0.2$ s, and check that the energy is 0.04 J. (b) When does it first reach $x = 5$ cm? Compare with the time to reach $x = 0$ and explain.

Show solutionHide solution

(a) $\omega t = 4 \times 0.2 = 0.8$ rad. $x = 0.1\cos 0.8 = 0.0697$ m. $v = -4 \times 0.1 \times \sin 0.8 = -0.287$ m/s, moving toward the center. Energy: $\tfrac12(0.5)(0.287)^2 + \tfrac12(8)(0.0697)^2 = 0.0206 + 0.0194 = 0.0400$ J. (b) $0.1\cos(4t) = 0.05$ gives $\cos(4t) = 0.5$, so $4t = \pi/3$ and $t = 0.262$ s. It reaches the center at $T/4 = 0.393$ s. The first half of the distance takes two thirds of the time, because the block starts from rest and is moving slowly near the end of its range.

Problem 4

For each change, say what happens to the period, with a reason. (a) A mass on a spring: the mass is quadrupled. (b) The same spring is replaced by one twice as stiff. (c) The amplitude is tripled. (d) The spring system is taken to the Moon ($g = 1.62$ m/s²). (e) A pendulum is taken to the Moon. (f) A pendulum’s bob is replaced by one twice as heavy.

Show solutionHide solution

Use $T = 2\pi\sqrt{m/k}$ and $T = 2\pi\sqrt{L/g}$. (a) Doubles, since $\sqrt4 = 2$. (b) Divided by $\sqrt2$: about 71% of the original. (c) Unchanged; amplitude does not appear. (d) Unchanged; $g$ does not appear. Gravity only shifts the equilibrium point of a hanging spring. (e) Multiplied by $\sqrt{9.8/1.62} = 2.46$. (f) Unchanged; the mass cancels, because both the restoring force and the inertia are proportional to it.

Problem 5

A student measures $g$ with a pendulum 0.750 m long. Released at a small angle, it completes 20 full swings in 34.8 s. What value of $g$ does this give? Why time 20 swings and not one?

Show solutionHide solution

$T = 34.8/20 = 1.74$ s. From $T = 2\pi\sqrt{L/g}$, $g = 4\pi^2 L/T^2 = 39.48 \times 0.750/3.028 = 9.78$ m/s². Timing 20 swings divides the error of starting and stopping the watch by 20: a 0.2 s reaction error is 11% of one period and 0.6% of twenty.

Problem 6

A block on a spring oscillates with amplitude $A$. (a) At $x = A/2$, what fraction of the energy is kinetic, and what is the speed as a fraction of the maximum speed? (b) At what displacement are the kinetic and potential energies equal?

Show solutionHide solution

(a) The potential energy is $\tfrac12 k(A/2)^2 = \tfrac14 \cdot \tfrac12 kA^2$, one quarter of the total, so three quarters is kinetic. Since kinetic energy goes as $v^2$, $v = \sqrt{3/4}\,v_{\max} = 0.866\,v_{\max}$. Check: $\omega\sqrt{A^2 - A^2/4} = \omega A\sqrt{3}/2$. (b) Equal shares means $\tfrac12 kx^2 = \tfrac12 \cdot \tfrac12 kA^2$, so $x = \pm A/\sqrt2 = \pm 0.707A$.

Problem 7

A 0.5 kg cart rests at equilibrium on a spring with $k = 8$ N/m. A 0.5 kg lump of clay moving at 2 m/s along the line of the spring hits the cart and sticks. Find the amplitude and period of the oscillation that follows.

Show solutionHide solution

Collision. It is over quickly, before the spring has compressed appreciably, so the spring delivers negligible impulse and momentum is conserved: $0.5 \times 2 = (0.5 + 0.5)\,v$, so $v = 1$ m/s. Kinetic energy is not conserved here (it falls from 1 J to 0.5 J). Oscillation. Now a 1 kg mass passes through equilibrium at 1 m/s, which is its maximum speed. Energy: $\tfrac12(1)(1)^2 = \tfrac12(8)A^2$, so $A = \sqrt{1/8} = 0.354$ m. Equivalently $\omega = \sqrt{8/1} = 2.83$ rad/s and $A = v_{\max}/\omega$. Period: $T = 2\pi/2.83 = 2.22$ s, longer than the 1.57 s of the cart alone because the mass has doubled.

Problem 8

A pendulum clock is adjusted to keep perfect time when its swing is very small. It is then run with an amplitude of $5°$. Using the fact that the period exceeds the small-angle value by the fraction $\theta_0^2/16$, estimate how many seconds the clock loses per day.

Show solutionHide solution

$\theta_0 = 5° = 0.0873$ rad, so the fractional increase in period is $0.0873^2/16 = 4.76 \times 10^{-4}$. Each swing takes longer by that fraction, so the clock counts fewer swings in a day and runs slow by the same fraction of a day: $4.76 \times 10^{-4} \times 86\,400 \text{ s} = 41$ s per day. This is why precise pendulum clocks use small, carefully controlled amplitudes.

Shared definitions used by this lesson's figures
# house kit: prepended to every essay figure after the std imports

# palette. AZURE and EMBER are the two actors, LEAF marks an answer, ROSE a problem
let PAPER = [1, 1, 1, 1]
let INK = [0.08, 0.09, 0.11, 1]
let SLATE = [0.40, 0.44, 0.50, 1]
let MIST = [0.85, 0.86, 0.88, 1]
let AZURE = [0.13, 0.42, 0.68, 1]
let EMBER = [0.89, 0.42, 0.12, 1]
let LEAF = [0.13, 0.55, 0.36, 1]
let ROSE = [0.78, 0.22, 0.24, 1]
let PLUM = [0.47, 0.30, 0.66, 1]
let GOLD = [0.85, 0.62, 0.08, 1]

background = PAPER

# an opaque pastel of col; use for fills so overlaps do not darken
let tint = |col, amount = 0.18| [
    1 - (1 - col[0]) * amount,
    1 - (1 - col[1]) * amount,
    1 - (1 - col[2]) * amount,
    1
]

# text: Cap for labels, Note for quiet labels, Math for typeset maths
let Cap = |txt, size = 0.75, col = INK| color{col} Text(txt, size, "Charter")
let Note = |txt, size = 0.65| color{SLATE} Text(txt, size, "Charter")
let Math = |tex, size = 0.9, col = INK| color{col} Tex(tex, size)

# a paper rectangle to sit behind text that crosses lines
let Plate = |pos, size| center{pos} fill{PAPER} stroke{alpha{0} PAPER, 0} Rect(size)

# shapes with fill and stroke both set, so no stray black appears
let Pt = |pos, col = INK, r = 0.075| center{pos} fill{col} stroke{col, 1} Circle(r, 48)
let Ring = |pos, col = INK, r = 0.1, w = 3| center{pos} fill{PAPER} stroke{col, w} Circle(r, 48)
let Seg = |a, b, col = INK, w = 3| stroke{col, w} Line(a, b)
let Path = |pts, col = INK, w = 3| stroke{col, w} Polyline(pts)
let Region = |pts, col| fill{col} stroke{alpha{0} col, 0} Polygon(pts)
let Outline = |pts, col = INK, w = 3| fill{alpha{0} PAPER} stroke{col, w} Polygon(pts)
let Shape = |pts, col, amount = 0.18, w = 3| fill{tint(col, amount)} stroke{col, w} Polygon(pts)
let Disc = |pos, r, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Circle(r, 96)
let Box = |pos, size, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Rect(size)
# a line from a to b ending in a small solid arrowhead
let Tip = |a, b, col = INK, w = 3, head = 0.17| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let n = [-d[1], d[0], 0]
    let base = [b[0] - d[0] * head, b[1] - d[1] * head, 0]
    return [
        stroke{col, w} Line(a, base),
        fill{col} stroke{col, 1} Polygon([
            b,
            [base[0] + n[0] * head * 0.42, base[1] + n[1] * head * 0.42, 0],
            [base[0] - n[0] * head * 0.42, base[1] - n[1] * head * 0.42, 0]
        ])
    ]
}

# plots. a frame is [x0, x1, y0, y1, centre, width, height]: a data window and the scene box it fills
let Frame = |x0, x1, y0, y1, c = [0, 0, 0], w = 6.4, h = 3.6| [x0, x1, y0, y1, c, w, h]
let fx = |fr, x| fr[4][0] + (x - (fr[0] + fr[1]) / 2) * fr[5] / (fr[1] - fr[0])
let fy = |fr, y| fr[4][1] + (y - (fr[2] + fr[3]) / 2) * fr[6] / (fr[3] - fr[2])
let at = |fr, x, y| [fx(fr, x), fy(fr, y), 0]

# axes through the data origin (or the nearest edge of the window), with optional names
let Axes = |fr, x_name = "", y_name = "", col = SLATE| {
    let ox = clamp(fr[0], 0, fr[1])
    let oy = clamp(fr[2], 0, fr[3])
    var parts = [
        Tip(at(fr, fr[0], oy), [fx(fr, fr[1]) + 0.25, fy(fr, oy), 0], col, 2.5, 0.15),
        Tip(at(fr, ox, fr[2]), [fx(fr, ox), fy(fr, fr[3]) + 0.25, 0], col, 2.5, 0.15)
    ]
    if (x_name != "") {
        parts .= center{[fx(fr, fr[1]) + 0.5, fy(fr, oy), 0]} Math(x_name, 0.8, col)
    }
    if (y_name != "") {
        parts .= center{[fx(fr, ox), fy(fr, fr[3]) + 0.5, 0]} Math(y_name, 0.8, col)
    }
    return parts
}

# tick marks with numbers at the listed data values
let XTicks = |fr, xs, col = SLATE, places = 0| block {
    let oy = fy(fr, clamp(fr[2], 0, fr[3]))
    for (x in xs) {
        . Seg([fx(fr, x), oy - 0.07, 0], [fx(fr, x), oy + 0.07, 0], col, 2)
        . center{[fx(fr, x), oy - 0.33, 0]} Math(to_string(x, places), 0.72, col)
    }
}
let YTicks = |fr, ys, col = SLATE, places = 0| block {
    let ox = fx(fr, clamp(fr[0], 0, fr[1]))
    for (y in ys) {
        . Seg([ox - 0.07, fy(fr, y), 0], [ox + 0.07, fy(fr, y), 0], col, 2)
        . center{[ox - 0.36, fy(fr, y), 0]} Math(to_string(y, places), 0.72, col)
    }
}

# faint grid lines at the listed data values
let GridLines = |fr, xs, ys, col = MIST| block {
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), at(fr, x, fr[3]), col, 1.5)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), at(fr, fr[1], y), col, 1.5)
    }
}

# the graph of f over [a, b], drawn in the frame. values are clamped to the window
let Graph = |fr, f, a, b, col = AZURE, w = 4, n = 160|
    stroke{col, w} Polyline(map(sample(a, b, n), |x| at(fr, x, clamp(fr[2], f(x), fr[3]))))

# the region between the graph of f and the x axis over [a, b]
let AreaUnder = |fr, f, a, b, col = AZURE, amount = 0.2, n = 80| {
    var pts = [at(fr, a, 0)]
    for (x in sample(a, b, n)) {
        pts .= at(fr, x, clamp(fr[2], f(x), fr[3]))
    }
    pts .= at(fr, b, 0)
    return fill{tint(col, amount)} stroke{alpha{0} col, 0} Polygon(pts)
}

let G = 9.8
# plot furniture: grid, left and bottom edges, a zero line, numbers along the edges, names
let Panel = |f, gx, gy, lx, ly, xname, title, col = SLATE| block {
    . GridLines(f, gx, gy)
    if (f[2] < 0) {
        . Seg(at(f, f[0], 0), at(f, f[1], 0), SLATE, 2)
    }
    . Path([at(f, f[0], f[3]), at(f, f[0], f[2]), at(f, f[1], f[2])], SLATE, 2.5)
    for (x in lx) {
        . center{[fx(f, x), fy(f, f[2]) - 0.3, 0]} Math(to_string(x), 0.72, SLATE)
    }
    for (y in ly) {
        . center{[fx(f, f[0]) - 0.36, fy(f, y), 0]} Math(to_string(y), 0.72, SLATE)
    }
    if (xname != "") {
        . center{[(fx(f, f[0]) + fx(f, f[1])) / 2, fy(f, f[2]) - 0.64, 0]} Note(xname, 0.72)
    }
    . center{[(fx(f, f[0]) + fx(f, f[1])) / 2, fy(f, f[3]) + 0.3, 0]} Cap(title, 0.74, col)
}
let Arrow2 = |tail, vec, col, w = 4| Tip(tail, [tail[0] + vec[0], tail[1] + vec[1], 0], col, w, 0.18)
let Dash = |a, b, col = SLATE, w = 2.5| dashed{[0.12, 0.08]} stroke{col, w} Line(a, b)

# the running example: a 0.5 kg block on a spring of stiffness 8 N/m
let M = 0.5
let K = 8
let W = sqrt(K / M)
let PERIOD = TAU / W
# a coiled spring from x0 to x1 at height y
let Spring = |x0, x1, y, col = SLATE, n = 9, amp = 0.13| {
    let a = x0 + 0.12
    let b = x1 - 0.12
    var pts = [[x0, y, 0], [a, y, 0]]
    for (i in range(0, 2 * n)) {
        pts .= [a + (b - a) * (i + 0.5) / (2 * n), y + amp * (1 - 2 * mod(i, 2)), 0]
    }
    pts .= [b, y, 0]
    pts .= [x1, y, 0]
    return stroke{col, 2.5} Polyline(pts)
}
# wall at xw, floor at height y out to xr, spring, and a block centered at xb
let Rig = |xw, xr, y, xb, col, half = 0.26, tall = 0.46| [
    Path([[xw, y + 0.85, 0], [xw, y, 0], [xr, y, 0]], SLATE, 3.5),
    Spring(xw, xb - half, y + tall / 2),
    Box([xb, y + tall / 2 + 0.02, 0], [2 * half, tall], col, 0.35, 2.5)
]
let tidy = |v, eps = 0.05| {
    if (abs(v) < eps) { return 0 }
    return v
}
# value at time t from a table sampled every dt seconds
let look = |tbl, t, dt| {
    let u = clamp(0, t / dt, len(tbl) - 1.001)
    let i = to_int(floor(u))
    return lerp(tbl[i], tbl[i + 1], u - i)
}