Skip to main content

Oscillations

The Physics.Oscillations namespace provides one-dimensional oscillator models with both analytic and numerical solutions. All oscillators implement IOscillator for a consistent API.

🎡 Simple Harmonic Oscillator​

Models the undamped system x¨+ω02x=0\ddot{x} + \omega_0^2 x = 0 where ω0=k/m\omega_0 = \sqrt{k/m}.

Integration uses Velocity Verlet (symplectic, O(dtΒ²)) β€” excellent energy conservation for undamped systems.

using CSharpNumerics.Physics.Oscillations;

var sho = new SimpleHarmonicOscillator(mass: 2, stiffness: 50, initialPosition: 0.5);

double w = sho.AngularFrequency; // Ο‰β‚€ = √(k/m) = 5 rad/s
double f = sho.Frequency; // fβ‚€ = Ο‰β‚€/2Ο€ β‰ˆ 0.796 Hz
double T = sho.Period; // T = 1/fβ‚€ β‰ˆ 1.257 s
double A = sho.Amplitude; // A = √(xβ‚€Β² + (vβ‚€/Ο‰β‚€)Β²) = 0.5 m
double phi = sho.PhaseOffset; // Ο† = atan2(-vβ‚€/Ο‰β‚€, xβ‚€)

Analytic solution β€” exact reference for verification:

double x = sho.AnalyticPosition(t: 1.0);   // AΒ·cos(Ο‰β‚€t + Ο†)
double v = sho.AnalyticVelocity(t: 1.0); // -Aω₀·sin(ω₀t + φ)

Simulation & visualisation

sho.Step(dt: 0.001);                         // advance one time step
sho.Reset(); // restore initial conditions

List<Serie> trajectory = sho.Trajectory(tEnd: sho.Period * 5, dt: 0.001);
List<Serie> phase = sho.PhasePortrait(tEnd: sho.Period * 2, dt: 0.001);

Energy β€” conserved for undamped SHM:

double KE = sho.KineticEnergy;      // Β½mvΒ²
double PE = sho.PotentialEnergy; // Β½kxΒ²
double E = sho.TotalEnergy; // KE + PE (constant)

List<Serie> energy = sho.EnergyOverTime(tEnd: sho.Period * 10, dt: 0.001);

Frequency spectrum β€” FFT peak at the natural frequency f0f_0:

List<Serie> spectrum = sho.FrequencySpectrum(tEnd: sho.Period * 20, dt: 0.01);

With initial velocity

var sho2 = new SimpleHarmonicOscillator(mass: 1, stiffness: 25,
initialPosition: 0, initialVelocity: 5);
Console.WriteLine(sho2.Amplitude); // A = √(0² + (5/5)²) = 1.0 m

🌊 Damped Oscillator​

Models x¨+2γx˙+ω02x=0\ddot{x} + 2\gamma\dot{x} + \omega_0^2 x = 0 where γ=c/(2m)\gamma = c/(2m).

Three damping regimes are automatically detected:

  • Underdamped (Ξ³<Ο‰0\gamma < \omega_0) β€” oscillates with exponential decay
  • Critically damped (Ξ³=Ο‰0\gamma = \omega_0) β€” fastest non-oscillatory return
  • Overdamped (Ξ³>Ο‰0\gamma > \omega_0) β€” slow exponential decay

Integration uses RK4 β€” appropriate for dissipative systems.

var osc = new DampedOscillator(mass: 1, stiffness: 100, damping: 4, initialPosition: 1.0);

double w0 = osc.NaturalFrequency; // Ο‰β‚€ = 10 rad/s
double g = osc.Gamma; // Ξ³ = 2 rad/s
double wd = osc.DampedFrequency; // Ο‰_d = √96 β‰ˆ 9.80 rad/s
double Td = osc.DampedPeriod; // 2Ο€/Ο‰_d β‰ˆ 0.641 s
DampingRegime regime = osc.Regime; // Underdamped
double Q = osc.QualityFactor; // Ο‰β‚€/(2Ξ³) = 2.5
double delta = osc.LogarithmicDecrement; // Ξ³Β·T_d β‰ˆ 1.283

Regime detection

var under = new DampedOscillator(1, 100,  4);  // Ξ³=2  < Ο‰β‚€=10 β†’ Underdamped
var crit = new DampedOscillator(1, 100, 20); // Ξ³=10 = Ο‰β‚€=10 β†’ CriticallyDamped
var over = new DampedOscillator(1, 100, 40); // Ξ³=20 > Ο‰β‚€=10 β†’ Overdamped

Analytic solution β€” all three regimes:

// Underdamped:  x(t) = AΒ·e^(βˆ’Ξ³t)Β·cos(Ο‰_dΒ·t + Ο†)
// Critical: x(t) = (C₁ + Cβ‚‚t)Β·e^(βˆ’Ξ³t)
// Overdamped: x(t) = C₁·e^(r₁t) + Cβ‚‚Β·e^(rβ‚‚t)
double x = osc.AnalyticPosition(t: 1.0);
double v = osc.AnalyticVelocity(t: 1.0);

Simulation & visualisation

osc.Step(dt: 0.001);
osc.Reset();

List<Serie> trajectory = osc.Trajectory(tEnd: 5.0, dt: 0.001);
List<Serie> phase = osc.PhasePortrait(tEnd: 5.0, dt: 0.001); // spirals to origin

Energy β€” monotonically decreasing:

double E = osc.TotalEnergy;
List<Serie> energy = osc.EnergyOverTime(tEnd: 5.0, dt: 0.001);
List<Serie> dissipated = osc.EnergyDissipation(tEnd: 5.0, dt: 0.001); // Eβ‚€ βˆ’ E(t)

Envelope & spectrum

double env = osc.Envelope(t: 1.0);                         // AΒ·e^(βˆ’Ξ³t)
List<Serie> envCurve = osc.EnvelopeCurve(tEnd: 5.0, dt: 0.01);
List<Serie> spectrum = osc.FrequencySpectrum(tEnd: 20.0, dt: 0.005); // peak at Ο‰_d

Zero damping reduces to SimpleHarmonicOscillator behaviour.


⚑ Driven Oscillator​

Extends the damped oscillator with a harmonic driving force:

xΒ¨+2Ξ³xΛ™+Ο‰02x=F0mcos⁑(Ο‰dt)\ddot{x} + 2\gamma\dot{x} + \omega_0^2 x = \frac{F_0}{m}\cos(\omega_d t)

var osc = new DrivenOscillator(
mass: 1.0, stiffness: 100.0, damping: 4.0,
driveAmplitude: 10.0, driveFrequency: 7.0,
initialPosition: 0.0, initialVelocity: 0.0);
PropertyFormulaDescription
NaturalFrequencyω0=k/m\omega_0 = \sqrt{k/m}Undamped natural frequency
GammaΞ³=c/(2m)\gamma = c/(2m)Damping rate
DampedFrequencyΟ‰d=Ο‰02βˆ’Ξ³2\omega_d = \sqrt{\omega_0^2 - \gamma^2}Damped frequency
Regimeauto-detectedUnderdamped, CriticallyDamped, or Overdamped
QualityFactorQ=Ο‰0/(2Ξ³)Q = \omega_0/(2\gamma)Sharpness of resonance
ResonanceFrequencyΟ‰r=Ο‰02βˆ’2Ξ³2\omega_r = \sqrt{\omega_0^2 - 2\gamma^2}Frequency of max amplitude (0 if Ο‰02<2Ξ³2\omega_0^2 < 2\gamma^2)
BandwidthΔω=2Ξ³\Delta\omega = 2\gammaWidth of resonance peak

Steady-state response

double A = osc.SteadyStateAmplitude(wd);     // (Fβ‚€/m) / √((Ο‰β‚€Β²βˆ’Ο‰dΒ²)Β² + (2Ξ³Ο‰d)Β²)
double Ο† = osc.SteadyStatePhase(wd); // βˆ’atan2(2Ξ³Ο‰d, Ο‰β‚€Β²βˆ’Ο‰dΒ²)

double Adrive = osc.SteadyStateAmplitudeAtDrive;
double Ο†drive = osc.SteadyStatePhaseAtDrive;

Resonance curve & phase response

List<Serie> resonance = osc.ResonanceCurve(wMin: 0.1, wMax: 20, steps: 500);
List<Serie> phaseResp = osc.PhaseResponse(wMin: 0.1, wMax: 20, steps: 500);

Transfer function

ComplexNumber H   = osc.TransferFunction(s);      // H(s) = 1/(sΒ² + 2Ξ³s + Ο‰β‚€Β²)
ComplexNumber Hiw = osc.FrequencyResponse(w); // H(iω)

Simulation

osc.Step(dt: 0.001);
List<Serie> traj = osc.Trajectory(5.0, 0.001);
List<Serie> phase = osc.PhasePortrait(5.0, 0.001);

Steady-state extraction

List<Serie> steady = osc.SteadyStateTrajectory(
steadyDuration: 10.0, dt: 0.001, transientDuration: null); // auto = 5/Ξ³

double Ameas = osc.MeasuredSteadyStateAmplitude(dt: 0.001);

Energy & power

List<Serie> energy = osc.EnergyOverTime(tEnd: 5.0, dt: 0.001);
List<Serie> power = osc.PowerInput(tEnd: 5.0, dt: 0.001); // F(t)Β·v(t)

Frequency spectrum β€” peak at drive frequency fd=Ο‰d/(2Ο€)f_d = \omega_d / (2\pi):

List<Serie> spectrum = osc.FrequencySpectrum(tEnd: 50.0, dt: 0.005);

Zero driving force reduces to DampedOscillator behaviour.


πŸ”— Coupled Oscillators​

Models an N-mass spring chain with fixed walls:

Mx¨+Cx˙+Kx=0M\ddot{\mathbf{x}} + C\dot{\mathbf{x}} + K\mathbf{x} = 0

wall β€”kβ‚€β€” m₁ β€”k₁— mβ‚‚ β€”kβ‚‚β€” … β€”mβ‚™ β€”kβ‚™β€” wall

General constructor

var osc = new CoupledOscillators(
masses: new[] { 1.0, 2.0, 1.5 },
stiffnesses: new[] { 5.0, 10.0, 8.0, 5.0 }, // N+1 springs
dampings: new[] { 0.1, 0.2, 0.1 }, // optional
initialPositions: new[] { 1.0, 0.0, -0.5 },
initialVelocities: new[] { 0.0, 0.0, 0.0 });

Uniform constructor

var osc = new CoupledOscillators(
count: 5, mass: 1.0, stiffness: 4.0, damping: 0.0,
initialPositions: new[] { 1.0, 0.0, 0.0, 0.0, 0.0 });
PropertyDescription
CountNumber of masses NN
Position(i) / Velocity(i)State of mass ii
Positions / VelocitiesCopy of all current states
KineticEnergy12βˆ‘mivi2\frac{1}{2}\sum m_i v_i^2
PotentialEnergy12βˆ‘kj(Ξ”xj)2\frac{1}{2}\sum k_j (\Delta x_j)^2
TotalEnergyKinetic + Potential

Matrices & normal modes

Matrix K = osc.StiffnessMatrix();   // NΓ—N tridiagonal symmetric
Matrix M = osc.MassMatrix(); // NΓ—N diagonal

List<double> frequencies = osc.NormalModes(); // ω₁ ≀ Ο‰β‚‚ ≀ … ≀ Ο‰β‚™
List<VectorN> shapes = osc.ModeShapes(); // normalised eigenvectors

Normal modes are computed via the Jacobi eigenvalue algorithm on the symmetrised dynamical matrix D=Lβˆ’1KLβˆ’1D = L^{-1}KL^{-1} where L=diag(mi)L = \text{diag}(\sqrt{m_i}).

For a uniform N-mass chain: Ο‰r=2k/m sin⁑ ⁣(rΟ€2(N+1))\omega_r = 2\sqrt{k/m}\,\sin\!\bigl(\frac{r\pi}{2(N+1)}\bigr)

Simulation

osc.Step(dt: 0.001);
osc.Reset();

List<List<Serie>> trajectories = osc.Trajectory(tEnd: 5.0, dt: 0.001); // per mass
List<Serie> phase = osc.PhasePortrait(massIndex: 0, tEnd: 5.0, dt: 0.001);

Energy analysis

List<Serie> energy     = osc.EnergyOverTime(tEnd: 5.0, dt: 0.001);
List<Serie> modeEnergy = osc.ModalEnergy(modeIndex: 0, tEnd: 5.0, dt: 0.001);

Dispersion relation β€” theoretical for uniform periodic chain: Ο‰(k)=2k/mβ€‰βˆ£sin⁑(ka/2)∣\omega(k) = 2\sqrt{k/m}\,|\sin(ka/2)|

List<Serie> dispersion = osc.DispersionRelation(kValues, latticeSpacing: 1.0);
double vp = osc.PhaseVelocity(k: 1.0); // Ο‰/k
double vg = osc.GroupVelocity(k: 1.0); // dω/dk

Frequency spectrum β€” peaks at excited normal mode frequencies:

List<Serie> spectrum = osc.FrequencySpectrum(massIndex: 0, tEnd: 50.0, dt: 0.01);