2-DOF Vibration & Dynamic Absorber Simulator Back
Interactive Tool — Structural Dynamics

2-DOF Coupled Vibration &
Dynamic Absorber Simulator

Compare frequency response curves with and without a Tuned Mass Damper (TMD) in real time. Interactively observe how anti-resonance forms as you vary the mass ratio and tuning frequency.

Primary System Parameters
Primary Mass m1
kg
Primary Stiffness k1
Changes natural frequency fn1
Primary Damping Ratio ζ1
%
Tuned Mass Damper (TMD) Parameters
TMD Mass m2
kg
Mass ratio μ = m₂/m₁
TMD Stiffness k2
N/m
TMD Damping Ratio ζ2
%
Frequency Response Function |X₁/F| vs Frequency fn1 = —
Frf
System Parameter Summary
Results
fn1 Primary Natural Frequency [Hz]
fn2 TMD Natural Frequency [Hz]
μ Mass Ratio [–]
fopt Optimal TMD Frequency [Hz]
Two-Mass Animation — Coupled Vibration

Theory Note — Dynamic Absorber (TMD) Principle

A tuned mass damper works by attaching a secondary mass tuned near the primary system's natural frequency, splitting the single resonance peak into two smaller peaks and driving the response near the original resonance frequency toward zero (anti-resonance).

$$f_{TMD}^{opt}= \frac{f_{n1}}{1+\mu}, \quad \zeta_{TMD}^{opt}= \sqrt{\frac{3\mu}{8(1+\mu)^3}}$$

Den Hartog (1956) optimal tuning rule. A larger mass ratio μ yields better vibration suppression, but involves trade-offs with design constraints (weight and space). In skyscraper TMDs (e.g., Taipei 101) μ ≈ 0.3–0.5% is typical.

What is a Tuned Mass Damper (TMD)?

🙋
What exactly is a Tuned Mass Damper? I see it mentioned for skyscrapers, but how can a small mass stop a huge building from swaying?
🎓
Basically, it's a "vibration thief." A TMD is a secondary mass-spring-damper system attached to a primary structure (like a building). When the primary structure starts to vibrate at its natural frequency, the TMD is tuned to vibrate out-of-phase, stealing energy from the main system. In practice, you can see this in the simulator: set the primary mass high and the TMD mass much lower, then watch the main system's vibration amplitude drop dramatically at resonance.
🙋
Wait, really? So it's all about the tuning. What happens if I get the tuning wrong? Can I break it?
🎓
Great question! A poorly tuned TMD can make things worse. If the TMD's natural frequency isn't matched to the primary system's, you might get two sharp resonance peaks instead of one broad, suppressed one. Try it in the simulator: take the "Primary Stiffness" slider way down and the "TMD Stiffness" way up. You'll see two tall peaks in the frequency response curve, meaning vibration is amplified at two different frequencies instead of one.
🙋
That anti-resonance dip on the graph is super sharp. Is that realistic, or do we need the damper?
🎓
Exactly! A perfectly tuned mass with zero damping creates that infinitely deep, but infinitely narrow, anti-resonance. In the real world, frequencies shift slightly, so we need damping to "broaden" that dip into a useful valley. This is where Den Hartog's optimal rules come in. Adjust the "TMD Damping Ratio" slider in the simulator—you'll see the sharp dip widen into a more robust, flat region of low vibration.

Physical Model & Key Equations

The simulator solves the equations of motion for a 2-degree-of-freedom system, where the primary mass (m1) is connected to ground and the TMD mass (m2) is connected to m1. The key output is the Frequency Response Function (FRF), which shows how much the primary mass moves when a harmonic force acts on it.

$$ \begin{bmatrix}m_1 & 0 \\ 0 & m_2 \end{bmatrix}\begin{Bmatrix}\ddot{x}_1 \\ \ddot{x}_2 \end{Bmatrix}+ \begin{bmatrix}c_1+c_2 & -c_2 \\ -c_2 & c_2 \end{bmatrix}\begin{Bmatrix}\dot{x}_1 \\ \dot{x}_2 \end{Bmatrix}+ \begin{bmatrix}k_1+k_2 & -k_2 \\ -k_2 & k_2 \end{bmatrix}\begin{Bmatrix}x_1 \\ x_2 \end{Bmatrix}= \begin{Bmatrix}F_0 \\ 0 \end{Bmatrix}e^{i \omega t}$$

Where \( m_1, k_1, c_1 \) are the primary system's mass, stiffness, and damping coefficient. \( m_2, k_2, c_2 \) are the TMD's parameters. \( x_1, x_2 \) are displacements, and \( \omega \) is the excitation frequency. The solution gives the amplitude ratio \( X_1 / (F_0/k_1) \) plotted in the graph.

For optimal design, we use Den Hartog's formulas. These tell us the best frequency and damping for the TMD to minimize the maximum amplitude of the primary mass, given a mass ratio \( \mu = m_2 / m_1 \).

$$ f_{TMD}^{opt}= \frac{f_{n1}}{1+\mu}, \quad \zeta_{TMD}^{opt}= \sqrt{\frac{3\mu}{8(1+\mu)^3}}$$

Here, \( f_{n1}\) is the natural frequency of the primary system alone, \( \mu \) is the mass ratio, and \( \zeta_{TMD}^{opt} \) is the optimal damping ratio for the TMD. In the simulator, if you set the TMD stiffness and damping to match these formulas, you'll achieve the flattest possible frequency response curve.

Real-World Applications

Skyscrapers & Towers: The most famous application. To counteract wind-induced sway that causes occupant discomfort, massive TMDs are installed in upper floors. For instance, the Taipei 101 tower uses a 660-tonne spherical pendulum TMD. Engineers use tools like this simulator to determine the initial tuning before detailed analysis.

Bridge Decks & Footbridges: Bridges can experience dangerous vibrations from wind (like flutter) or synchronized pedestrian loading. TMDs are often attached underneath the deck to suppress these motions. A common case is the Millennium Bridge in London, which required retrofitted dampers after unexpected lateral vibrations occurred.

Power Transmission Lines & Stacks: Long, slender structures like chimneys and power lines suffer from vortex shedding, which can cause fatigue failure. Small, distributed TMDs are attached to shift the system's resonance and add damping, dramatically extending the structure's lifespan.

Precision Manufacturing & Optics: Vibration isolation is critical for semiconductor lithography machines and telescope mirrors. Here, TMD principles are used in reverse—a "vibration absorber" is tuned to isolate a sensitive instrument from floor vibrations, ensuring nanometer-scale stability.

Common Misconceptions and Points of Caution

Model assumptions: The mathematical model used here relies on simplifying assumptions such as linearity, homogeneity, and isotropy. Always verify that your real system satisfies these assumptions before applying results directly to design decisions.

Units and scale: Many calculation errors arise from unit conversion mistakes or order-of-magnitude errors. Pay close attention to the units shown next to each parameter input.

Validating results: Always sanity-check simulator output against physical intuition or hand calculations. If a result seems unexpected, review your input parameters or verify with an independent method.

// 2-DOF coupled oscillation animation (function() { const el = document.getElementById('twoDofAnimCanvas'); const ctx = el.getContext('2d'); let phase = 0; function getParams() { const m1 = parseFloat(document.getElementById('sl-m1').value) || 1000; const k1 = parseFloat(document.getElementById('sl-k1').value) || 1e6; const z1 = (parseFloat(document.getElementById('sl-z1').value) || 2) / 100; const m2 = parseFloat(document.getElementById('sl-m2').value) || 50; const k2 = parseFloat(document.getElementById('sl-k2').value) || 50000; const z2 = (parseFloat(document.getElementById('sl-z2').value) || 5) / 100; const f1 = Math.sqrt(k1 / m1) / (2 * Math.PI); const f2 = Math.sqrt(k2 / m2) / (2 * Math.PI); // Mode 1 ≈ f1, Mode 2 ≈ higher freq const mu = m2 / m1; const fL = f1 / (1 + mu); // lower split peak const fU = f1 * (1 + mu); // upper split peak (approximate) return { m1, k1, z1, m2, k2, z2, f1, f2, fL, fU, mu }; } function resize() { const dpr = window.devicePixelRatio || 1; const w = el.parentElement.clientWidth - 48; const H = Math.round(Math.min(w * 0.45, 300)); if (Math.abs(el.width - w * dpr) > 2 || el.height !== H * dpr) { el.width = w * dpr; el.height = H * dpr; el.style.height = H + 'px'; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); } return { W: w, H }; } function drawSpring(x1, y1, x2, y2, n, color) { const dx = x2 - x1, dy = y2 - y1; const len = Math.sqrt(dx*dx + dy*dy); const nx = -dy/len, ny = dx/len; const amp = 7; ctx.beginPath(); ctx.strokeStyle = color; ctx.lineWidth = 1.5; ctx.moveTo(x1, y1); for (let i = 1; i <= n*2; i++) { const t = i / (n*2); const px = x1 + dx * t + nx * amp * (i % 2 === 0 ? 1 : -1); const py = y1 + dy * t + ny * amp * (i % 2 === 0 ? 1 : -1); ctx.lineTo(px, py); } ctx.lineTo(x2, y2); ctx.stroke(); } function drawMass(cx, cy, w, h, label, color) { ctx.fillStyle = color; ctx.beginPath(); ctx.roundRect(cx - w/2, cy - h/2, w, h, 6); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,0.3)'; ctx.lineWidth = 1; ctx.stroke(); ctx.fillStyle = '#fff'; ctx.font = 'bold 12px Roboto Mono, monospace'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(label, cx, cy); } function frame() { const { W, H } = resize(); const { m1, f1, f2, fL, fU, mu } = getParams(); ctx.clearRect(0, 0, W, H); ctx.fillStyle = '#f8f9fa'; ctx.fillRect(0, 0, W, H); const ceilY = 20; const m1W = Math.min(W * 0.35, 160), m1H = 48; const m2W = Math.min(W * 0.18, 80), m2H = 32; const m1CX = W / 2; const baseY = H - 20; // Two-mode superposition: mode 1 at fL, mode 2 at fU const w1 = 2 * Math.PI * fL; const w2 = 2 * Math.PI * fU; const amp1 = 25; const amp2 = 12; const x1disp = amp1 * Math.sin(w1 * phase) + amp2 * Math.sin(w2 * phase * 0.5); const x2disp = amp1 * Math.sin(w1 * phase) * (1 / (1 + mu)) - amp2 * 1.5 * Math.sin(w2 * phase * 0.5); const m1Y = H * 0.52 + x1disp; const m2Y = m1Y - m1H/2 - m2H/2 - 20 + x2disp; // Ceiling ctx.fillStyle = '#001F3F'; ctx.fillRect(0, ceilY, W, 8); ctx.strokeStyle = '#003875'; ctx.lineWidth = 1; for (let i = 0; i < W; i += 16) { ctx.beginPath(); ctx.moveTo(i, ceilY); ctx.lineTo(i - 10, ceilY - 8); ctx.stroke(); } // Ground ctx.fillStyle = '#001F3F'; ctx.fillRect(0, baseY, W, H - baseY); // Main spring (k1): ceiling to m1 drawSpring(m1CX, ceilY + 8, m1CX, m1Y - m1H/2, 8, '#007BFF'); // TMD spring (k2): m1 to m2 drawSpring(m1CX, m1Y - m1H/2, m1CX, m2Y + m2H/2, 5, '#00B4D8'); // Main mass const m1Color = Math.abs(x1disp) > 18 ? '#e17055' : '#007BFF'; drawMass(m1CX, m1Y, m1W, m1H, 'm₁ (primary)', m1Color); // TMD mass const m2Color = '#00B4D8'; drawMass(m1CX, m2Y, m2W, m2H, 'm₂', m2Color); // Labels ctx.fillStyle = '#6c757d'; ctx.font = '10px Roboto Mono, monospace'; ctx.textAlign = 'left'; ctx.fillText(`k₁ = ${(getParams().k1/1000).toFixed(0)} kN/m`, m1CX + m1W/2 + 8, m1Y); ctx.fillText(`k₂ = ${(getParams().k2/1000).toFixed(1)} kN/m`, m1CX + m2W/2 + 8, m2Y); ctx.fillText(`μ = ${(mu*100).toFixed(1)}%`, 10, 50); ctx.fillText(`f₁ = ${f1.toFixed(2)} Hz`, 10, 64); ctx.textAlign = 'center'; phase += 1 / 60; requestAnimationFrame(frame); } frame(); })();