Normal Modes & Oscillations

The oscillations module provides tools for analyzing small oscillations around equilibrium and computing normal modes.

Overview

For systems near stable equilibrium, small oscillations can be described by coupled linear equations. The module implements:

  • Mass Matrix Extraction: From kinetic energy \(T = \frac{1}{2}\dot{q}^T M \dot{q}\)

  • Stiffness Matrix Extraction: From potential energy \(V = \frac{1}{2}q^T K q\)

  • Normal Mode Computation: Solve generalized eigenvalue problem

  • Modal Analysis: Frequencies, mode shapes, participation factors

  • Coupled Oscillator Systems: Build and analyze multi-DOF systems

Theory

Small Oscillations

Near a stable equilibrium, the equations of motion become:

\[M \ddot{q} + K q = 0\]

where \(M\) is the mass matrix (symmetric, positive definite) and \(K\) is the stiffness matrix (symmetric, positive semi-definite for stable equilibrium).

Normal Modes

Assume harmonic solutions \(q(t) = a \cos(\omega t)\). Substituting gives the generalized eigenvalue problem:

\[K a = \omega^2 M a\]

The eigenvalues \(\omega_n^2\) are squared natural frequencies, and eigenvectors \(a_n\) are mode shapes.

Usage Examples

Extracting Mass and Stiffness Matrices

from mechanics_dsl.domains.classical import (
    extract_mass_matrix,
    extract_stiffness_matrix
)
import sympy as sp

# Two coupled oscillators
m = sp.Symbol('m', positive=True)
k = sp.Symbol('k', positive=True)
x1 = sp.Symbol('x1', real=True)
x2 = sp.Symbol('x2', real=True)
x1_dot = sp.Symbol('x1_dot', real=True)
x2_dot = sp.Symbol('x2_dot', real=True)

# Kinetic energy
T = sp.Rational(1, 2) * m * (x1_dot**2 + x2_dot**2)

# Potential energy (springs between wall-m1-m2-wall)
V = sp.Rational(1, 2) * k * (x1**2 + (x2 - x1)**2 + x2**2)

M = extract_mass_matrix(T, ['x1_dot', 'x2_dot'])
K = extract_stiffness_matrix(V, ['x1', 'x2'])

print("Mass matrix:")
print(M)  # [[m, 0], [0, m]]

print("Stiffness matrix:")
print(K)  # [[2k, -k], [-k, 2k]]

Computing Normal Modes

from mechanics_dsl.domains.classical import NormalModeAnalyzer, compute_normal_modes
import numpy as np

# Numerical mass and stiffness matrices
m_val = 1.0
k_val = 1.0

M = np.array([[m_val, 0], [0, m_val]])
K = np.array([[2*k_val, -k_val], [-k_val, 2*k_val]])

modes = compute_normal_modes(M, K)

for mode in modes:
    print(f"ω = {mode.frequency:.4f}, shape = {mode.shape}")

# Output:
# ω = 1.0000, shape = [0.707, 0.707]   (in-phase mode)
# ω = 1.7321, shape = [0.707, -0.707]  (out-of-phase mode)

Full Modal Analysis

from mechanics_dsl.domains.classical import NormalModeAnalyzer
import sympy as sp

analyzer = NormalModeAnalyzer()

# Define Lagrangian
m = sp.Symbol('m', positive=True)
k = sp.Symbol('k', positive=True)

x1 = analyzer.get_symbol('x1')
x2 = analyzer.get_symbol('x2')
x1_dot = analyzer.get_symbol('x1_dot')
x2_dot = analyzer.get_symbol('x2_dot')

L = sp.Rational(1, 2) * m * (x1_dot**2 + x2_dot**2) - \
    sp.Rational(1, 2) * k * (x1**2 + (x2 - x1)**2 + x2**2)

result = analyzer.analyze(L, ['x1', 'x2'], parameters={'m': 1.0, 'k': 1.0})

print(f"Frequencies: {result.frequencies}")
print(f"Mode shapes: {result.mode_shapes}")
print(f"Mass matrix:\n{result.mass_matrix}")
print(f"Stiffness matrix:\n{result.stiffness_matrix}")

Coupled Pendulums

from mechanics_dsl.domains.classical import CoupledOscillatorSystem
import numpy as np

# Create coupled pendulum system
system = CoupledOscillatorSystem(n_dof=2)

# Set masses
system.set_mass(0, 1.0)
system.set_mass(1, 1.0)

# Set spring constants
system.set_spring(0, None, 1.0)    # Wall to mass 0
system.set_spring(0, 1, 0.2)       # Mass 0 to mass 1 (weak coupling)
system.set_spring(1, None, 1.0)    # Mass 1 to wall

# Compute modes
modes = system.compute_modes()

# Beating phenomenon: energy oscillates between pendulums
# at frequency Δω = ω₂ - ω₁
delta_omega = modes[1].frequency - modes[0].frequency
T_beat = 2 * np.pi / delta_omega
print(f"Beat period: {T_beat:.2f}")

API Reference

Classes

class NormalModeAnalyzer

Analyzer for normal modes of oscillating systems.

analyze(lagrangian, coordinates, parameters)

Full modal analysis from Lagrangian.

Returns:

ModalAnalysisResult

compute_modes_numerical(M, K)

Compute modes from numerical M and K matrices.

Returns:

List of NormalMode objects

modal_decomposition(q0, v0, modes)

Decompose initial conditions into modal coordinates.

class CoupledOscillatorSystem

Builder for coupled oscillator systems.

set_mass(index, mass)

Set mass of oscillator at index.

set_spring(i, j, k)

Set spring constant between masses i and j (or to wall if j=None).

compute_modes()

Build and solve the eigenvalue problem.

class NormalMode

Represents a single normal mode.

frequency

Natural frequency ω (rad/s)

shape

Mode shape (eigenvector)

participation_factor

Participation in overall response

class ModalAnalysisResult

Result of complete modal analysis.

frequencies

List of natural frequencies

mode_shapes

List of mode shape vectors

mass_matrix

Mass matrix M

stiffness_matrix

Stiffness matrix K

Functions

extract_mass_matrix(T, velocity_vars)

Extract mass matrix from kinetic energy expression.

extract_stiffness_matrix(V, position_vars)

Extract stiffness matrix from potential energy expression.

compute_normal_modes(M, K)

Compute normal modes from M and K matrices.

See Also