systems.builtin.deterministic.discrete.DiscreteOscillator
systems.builtin.deterministic.discrete.DiscreteOscillator(*args, **kwargs)Discrete-time damped harmonic oscillator with external forcing.
Physical System:
The harmonic oscillator models a mass attached to a spring and damper, subject to external force. The continuous-time dynamics are:
m·ẍ + c·ẋ + k·x = F(t)
Dividing by mass and defining natural frequency ωₙ and damping ratio ζ:
ẍ + 2ζωₙ·ẋ + ωₙ²·x = u(t)
where: ωₙ = √(k/m) [rad/s] : Natural (undamped) frequency ζ = c/(2√(km)) [-] : Damping ratio
The discrete-time version depends on the discretization method. This implementation provides several options:
Zero-Order Hold (ZOH) - Exact Discretization: The exact solution assuming piecewise-constant control. For underdamped systems (0 < ζ < 1), this gives:
x[k+1] = e^(-ζωₙdt)·[x[k]·cos(ωd·dt) + v[k]·sin(ωd·dt)/ωd]
v[k+1] = e^(-ζωₙdt)·[-x[k]·ωd·sin(ωd·dt) + v[k]·cos(ωd·dt)] + dt·u[k]
where ωd = ωₙ√(1 - ζ²) is the damped natural frequency.
Tustin/Bilinear Transform: Preserves frequency response characteristics:
(I - 0.5·dt·A)·x[k+1] = (I + 0.5·dt·A)·x[k] + 0.5·dt·B·(u[k] + u[k+1])
This is the standard method for designing discrete filters from continuous prototypes, as it maps the jω axis in s-plane to the unit circle in z-plane.
Forward Euler: Simple first-order approximation:
x[k+1] = x[k] + dt·v[k]
v[k+1] = v[k] + dt·(-ωₙ²·x[k] - 2ζωₙ·v[k] + u[k])
Backward Euler: Implicit method with better stability for stiff systems.
State Space:
State: x[k] = [x[k], v[k]] Displacement state: - x: Position/displacement from equilibrium [m] * Can be positive or negative * Zero at equilibrium (spring relaxed) * Bounded for stable systems: |x| < ∞ * Oscillates around zero for underdamped systems
Velocity state:
- v: Velocity [m/s]
* Rate of change of position
* Zero at equilibrium
* Phase-shifted 90° from position in steady-state oscillation
* Maximum at equilibrium crossing for underdamped
Control: u[k] = [F[k]] - F: External force/acceleration [m/s²] or [N/kg] * Directly enters as acceleration term * Can be used to: - Excite oscillations (resonance testing) - Damp oscillations (active damping) - Track reference trajectories - Reject disturbances
Output: y[k] = [x[k]] or [x[k], v[k]] - Displacement-only measurement (most common): y[k] = x[k] Examples: LVDT, laser displacement, strain gauge - Full state measurement: y[k] = [x[k], v[k]] Examples: accelerometer integration, optical tracking
Dynamics (General Form):
The discrete dynamics can be written in state-space form:
x[k+1] = Ad·x[k] + Bd·u[k]
where the matrices Ad and Bd depend on the discretization method and system parameters (ωₙ, ζ, dt).
Physical Interpretation of Parameters:
Natural Frequency ωₙ [rad/s]: - Frequency of oscillation without damping - Higher ωₙ → faster oscillations, stiffer spring - Related to spring constant: ωₙ = √(k/m) - Typical values: * Building structures: 0.1-10 rad/s * Vehicle suspensions: 5-50 rad/s * Machine tools: 100-1000 rad/s * MEMS devices: 10³-10⁶ rad/s
Damping Ratio ζ [-]: - Dimensionless measure of energy dissipation - Controls how quickly oscillations decay - ζ = 0: Undamped (oscillates forever) - 0 < ζ < 1: Underdamped (oscillates with decay) - ζ = 1: Critically damped (fastest non-oscillatory) - ζ > 1: Overdamped (slow return, no oscillation) - Typical values: * Air damping: ζ ≈ 0.01-0.1 * Automotive shocks: ζ ≈ 0.3-0.7 * Seismic isolators: ζ ≈ 0.1-0.3 * Critically damped instruments: ζ = 1.0
Regime Classification:
- Underdamped (0 < ζ < 1):
- Oscillates with exponentially decaying amplitude
- Damped frequency: ωd = ωₙ√(1 - ζ²) < ωₙ
- Decay rate: σ = ζωₙ
- Period: T = 2π/ωd
- Envelope: A(t) = A₀·e^(-ζωₙt)
- Most common in practice
- Critically Damped (ζ = 1):
- Fastest return to equilibrium without overshoot
- No oscillation
- Optimal for instruments, door closers
- Two equal real eigenvalues: λ = -ωₙ
- Overdamped (ζ > 1):
- Slow return to equilibrium
- No oscillation
- Two distinct real eigenvalues
- Example: heavily damped pendulum in oil
- Undamped (ζ = 0):
- Pure sinusoidal oscillation forever
- No energy dissipation
- Idealization (never exact in reality)
- Eigenvalues on imaginary axis: λ = ±jωₙ
Parameters:
omega_n : float, default=1.0 Natural frequency [rad/s] Must be positive: ωₙ > 0 Controls oscillation speed Related to spring stiffness and mass
zeta : float, default=0.1 Damping ratio [-] Must be non-negative: ζ ≥ 0 Controls energy dissipation ζ = 0: undamped, ζ < 1: underdamped, ζ = 1: critical, ζ > 1: overdamped
dt : float, default=0.1 Sampling/discretization time step [s] Critical parameter affecting: - Accuracy of discrete approximation - Aliasing (must satisfy Nyquist: dt < π/ωₙ) - Numerical stability (depends on method) - Control bandwidth
Guidelines:
- Nyquist: dt < π/ωₙ (sample at least 2× per cycle)
- Shannon: dt < 1/(10·fₙ) for good reconstruction
- Rule of thumb: 10-20 samples per period T = 2π/ωₙ
- For ωₙ = 1 rad/s: dt ≈ 0.1-0.3 s
- For ωₙ = 10 rad/s: dt ≈ 0.01-0.03 s
method : str, default=‘zoh’ Discretization method: - ‘zoh’: Zero-order hold (exact, recommended for control) - ‘tustin’: Bilinear/trapezoidal (preserves frequency response) - ‘euler’: Forward Euler (simple, less accurate) - ‘backward_euler’: Backward Euler (more stable) - ‘matched’: Matched pole-zero (preserves poles)
Equilibria:
Stable equilibrium at origin: x_eq = [0, 0] (rest position, spring relaxed) u_eq = 0 (no external force)
Stability depends on damping: - ζ > 0: Asymptotically stable (returns to origin) - ζ = 0: Marginally stable (oscillates forever)
Eigenvalue Analysis:
Continuous-time poles: λ = -ζωₙ ± jωₙ√(1 - ζ²) (for ζ < 1)
Discrete-time poles (ZOH): z = e^(λdt) = e(-ζωₙdt)·e(±jωd·dt)
Stability requires |z| < 1: |z| = e^(-ζωₙdt) < 1 ⟹ ζωₙ > 0
This is always satisfied for ζ > 0 (damped systems).
For undamped (ζ = 0): |z| = 1 (marginally stable, poles on unit circle)
Forced Oscillation Equilibrium: For constant forcing u = u_ss: x_ss = u_ss/ωₙ² (static deflection) v_ss = 0
This represents a new equilibrium position where spring force balances the constant applied force.
Controllability:
The discrete harmonic oscillator is COMPLETELY CONTROLLABLE for all discretization methods and parameter values (ωₙ > 0).
Controllability Matrix: C = [Bd, Ad·Bd]
Physical Meaning: - Can move to any position with any velocity - Requires at most 2 time steps (for linear systems) - Energy required depends on desired state and time - Practical limits: actuator force, position/velocity bounds
Minimum Energy Control: To reach target state x_target from origin in time T: u*(t) = B’·e^(A’(T-t))·[∫₀ᵀ e(At)·B·B’·e(A’t) dt]⁻¹·x_target
This gives the control that minimizes ∫ u² dt.
Observability:
Displacement-only measurement: y[k] = x[k] C = [1 0]
Observability matrix:
O = [C ] = [1 0 ]
[C·Ad ] [Ad[0,0] Ad[0,1] ]
Fully observable for all parameter values.
Can reconstruct velocity from position measurements over time.
Physical Interpretation: Velocity is estimated from position differences: v[k] ≈ (x[k+1] - x[k])/dt
Better estimates use multiple samples (Kalman filter, least squares).
Full state measurement: y[k] = [x[k], v[k]] Trivially observable - direct measurement of all states.
Frequency Response:
The oscillator exhibits characteristic frequency-dependent behavior.
Continuous-time transfer function: H(s) = 1/(s² + 2ζωₙs + ωₙ²)
Key frequencies:
- Natural frequency ωₙ:
- Resonance frequency for undamped system
- Determines overall response speed
- Damped natural frequency ωd: ωd = ωₙ√(1 - ζ²) [rad/s]
- Actual oscillation frequency for underdamped
- Lower than ωₙ due to damping
- Resonant peak frequency ωᵣ: ωᵣ = ωₙ√(1 - 2ζ²) [rad/s] (for ζ < 1/√2)
- Frequency of maximum gain
- Only exists for light damping
Magnitude response: At resonance (ω = ωᵣ): |H(jωᵣ)| = 1/(2ζ√(1 - ζ²))
For small damping (ζ << 1): |H(jωₙ)| ≈ 1/(2ζ) (quality factor Q = 1/(2ζ))
Phase response: - Low frequency (ω << ωₙ): φ ≈ 0° (in-phase) - Resonance (ω = ωₙ): φ = -90° (quadrature) - High frequency (ω >> ωₙ): φ → -180° (out-of-phase)
Control Objectives:
Common control goals for harmonic oscillators:
Vibration Damping: Goal: Increase effective damping to reduce oscillations Methods:
- Velocity feedback: u = -k_d·v (adds damping)
- LQR with velocity penalty
- Active damping control Applications: building isolation, machine tools, aerospace
Resonance Tracking: Goal: Maintain oscillation at specific frequency and amplitude Methods:
- Sinusoidal forcing at ωd
- Phase-locked loop Applications: clocks, oscillators, sensors
Position Regulation: Goal: Drive to desired position and hold Control: u = -K·[x - x_ref, v] Design: LQR, pole placement
Setpoint Tracking: Goal: Follow time-varying reference position Control: u = -K·[x - x_ref(t), v - v_ref(t)] + u_ff(t) Feedforward: u_ff = ẍ_ref + 2ζωₙv_ref + ωₙ²x_ref
Disturbance Rejection: Goal: Maintain position despite external forces Methods:
- High-gain feedback
- Integral action
- Disturbance observer Applications: precision positioning, isolation
State Constraints:
Physical constraints that must be enforced:
- Position limits: x_min ≤ x[k] ≤ x_max
- Physical stops, workspace boundaries
- Spring compression/extension limits
- Typical: |x| ≤ 0.1 m for suspensions
- Velocity limits: |v[k]| ≤ v_max
- Material stress limits (fatigue)
- Safety considerations
- Typical: |v| ≤ 1 m/s for mechanical systems
- Control limits: |u[k]| ≤ u_max
- Actuator force saturation
- Power constraints
- Most critical practical constraint
- Typical: |u| ≤ 10 m/s² for active systems
- Frequency limits (for tracking):
- Cannot track above Nyquist frequency: f < 1/(2dt)
- Anti-aliasing required for higher frequencies
- Practical limit: f < 0.1/dt for good control
Numerical Considerations:
Stability:
For explicit methods (Euler), stability requires: dt < 2/(ζωₙ + √(ζ²ωₙ² + ωₙ²))
Approximately: dt < 1/ωₙ for ζ small
For ZOH and Tustin, the discretization is always stable if the continuous system is stable (ζ > 0).
Accuracy: - ZOH: Exact for underdamped systems with constant control - Tustin: O(dt²) error, preserves frequency response - Euler: O(dt) error, can become unstable - Matched: Exact pole locations, approximate zeros
Aliasing: If excited at frequencies above Nyquist (π/dt), aliasing occurs. The response appears at lower frequencies: f_apparent = |f_actual - n·f_sample|
Prevention: Use anti-aliasing filters before sampling.
Resonance Amplification: At resonance with small damping: Gain ≈ Q = 1/(2ζ)
For ζ = 0.01: Q = 50 (34 dB amplification!) This can cause: - Large displacements from small inputs - Control saturation - Numerical overflow if not handled
Control Design Examples:
1. Damping Augmentation (Velocity Feedback): u[k] = -k_d·v[k]
Effective damping: ζ_eff = ζ + k_d/(2ωₙ)
Choose k_d to achieve desired damping:
k_d = 2ωₙ(ζ_desired - ζ)
Example: ωₙ = 10 rad/s, ζ = 0.1, ζ_desired = 0.7
k_d = 2(10)(0.7 - 0.1) = 12
2. LQR (Optimal State Feedback): Minimize: J = Σ (x’·Q·x + u’·R·u)
Tuning guidelines:
- Large Q[0,0]: Penalize position error (stiff response)
- Large Q[1,1]: Penalize velocity (add damping)
- Large R: Penalize control effort (smooth, slow)
Example:
Q = diag([100, 10]) # Care about position
R = 1
Result: Fast settling, moderate control
3. Pole Placement: Desired continuous-time poles: s = -ζ_d·ωₙ_d ± jωₙ_d√(1 - ζ_d²)
Map to discrete:
z = e^(s·dt)
Example: ωₙ_d = 10, ζ_d = 0.7, dt = 0.01
s = -7 ± j7.14
z = 0.932 ± j0.069
4. Notch Filter (Resonance Suppression): For systems with known resonance, add notch filter: H_notch(z) = (z² - 2cos(ω_notch·dt)z + 1)/(z² - 2r·cos(ω_notch·dt)z + r²)
where r < 1 controls notch width.
Example Usage:
Create underdamped oscillator with natural frequency 2π rad/s (1 Hz)
system = DiscreteOscillator(omega_n=2*np.pi, zeta=0.1, dt=0.01)
Initial condition: displaced 1m, at rest
x0 = np.array([1.0, 0.0]) # [position, velocity]
Free oscillation (no control)
result_free = system.simulate( … x0=x0, … u_sequence=None, … n_steps=500 … )
Plot free oscillation
import matplotlib.pyplot as plt t = result_free[‘time_steps’] * system.dt fig, axes = plt.subplots(3, 1, figsize=(10, 8))
Position
axes[0].plot(t, result_free[‘states’][:, 0]) axes[0].set_ylabel(‘Position [m]’) axes[0].grid() axes[0].set_title(‘Free Oscillation (Underdamped)’)
Velocity
axes[1].plot(t, result_free[‘states’][:, 1]) axes[1].set_ylabel(‘Velocity [m/s]’) axes[1].grid()
Phase portrait
axes[2].plot(result_free[‘states’][:, 0], result_free[‘states’][:, 1]) axes[2].set_xlabel(‘Position [m]’) axes[2].set_ylabel(‘Velocity [m/s]’) axes[2].grid() axes[2].set_title(‘Phase Portrait (Spiral to Origin)’)
plt.tight_layout() plt.show()
Compute theoretical decay envelope
A0 = 1.0 # Initial amplitude envelope = A0 * np.exp(-system.zeta * system.omega_n * t)
Verify decay rate
peaks = result_free[‘states’][::int(np.pi/(system.omega_d * system.dt)), 0] print(f”Theoretical decay: {envelope[-1]:.4f}“) print(f”Simulated decay: {np.abs(peaks[-1]):.4f}“)
Design damping augmentation controller
zeta_desired = 0.7 # Critical damping k_d = system.design_damping_controller(zeta_desired) print(f”Damping gain: k_d = {k_d:.2f}“)
def damping_controller(x, k): … return -k_d * x[1] # Velocity feedback only
result_damped = system.rollout(x0, damping_controller, n_steps=500)
Compare settling times
settling_threshold = 0.02 * A0 # 2% criterion free_settling = np.where(np.abs(result_free[‘states’][:, 0]) < settling_threshold)[0] damped_settling = np.where(np.abs(result_damped[‘states’][:, 0]) < settling_threshold)[0]
if len(free_settling) > 0 and len(damped_settling) > 0: … print(f”Free settling time: {free_settling[0] * system.dt:.2f} s”) … print(f”Damped settling time: {damped_settling[0] * system.dt:.2f} s”)
Resonance testing - sweep frequency
frequencies = np.logspace(-1, 1, 50) # 0.1 to 10 rad/s gains = [] phases = []
for omega in frequencies: … # Apply sinusoidal input … t_sim = np.arange(0, 20, system.dt) … u_sin = np.sin(omega * t_sim) … … result = system.simulate( … x0=np.zeros(2), … u_sequence=u_sin, … n_steps=len(t_sim) … ) … … # Measure steady-state amplitude and phase … x_ss = result[‘states’][-100:, 0] … u_ss = u_sin[-100:] … … # Compute gain (amplitude ratio) … gain = np.max(np.abs(x_ss)) / np.max(np.abs(u_ss)) … gains.append(gain) … … # Compute phase (via FFT) … X_fft = np.fft.fft(x_ss) … U_fft = np.fft.fft(u_ss) … phase = np.angle(X_fft[1]) - np.angle(U_fft[1]) … phases.append(np.rad2deg(phase))
Plot Bode diagram
fig, axes = plt.subplots(2, 1, figsize=(10, 8))
Magnitude
axes[0].semilogx(frequencies, 20*np.log10(gains), ‘b-’, label=‘Simulated’) axes[0].axvline(system.omega_n, color=‘r’, linestyle=‘–’, label=f’ωₙ = {system.omega_n:.2f}‘) axes[0].set_ylabel(’Magnitude [dB]’) axes[0].grid(which=‘both’) axes[0].legend() axes[0].set_title(‘Frequency Response (Bode Plot)’)
Phase
axes[1].semilogx(frequencies, phases, ‘b-’) axes[1].axvline(system.omega_n, color=‘r’, linestyle=‘–’) axes[1].axhline(-90, color=‘g’, linestyle=‘:’, label=‘-90° at resonance’) axes[1].set_xlabel(‘Frequency [rad/s]’) axes[1].set_ylabel(‘Phase [deg]’) axes[1].grid(which=‘both’) axes[1].legend()
plt.tight_layout() plt.show()
Design LQR controller
Ad, Bd = system.linearize(np.zeros(2), np.zeros(1)) Q = np.diag([100.0, 1.0]) # Care more about position R = np.array([[1.0]]) lqr_result = system.control.design_lqr( … Ad, Bd, Q, R, system_type=‘discrete’ … ) K = lqr_result[‘gain’] print(f”LQR gain: K = {K}“)
Check closed-loop damping
zeta_cl, omega_cl = system.compute_closed_loop_damping(K) print(f”Closed-loop damping: ζ = {zeta_cl:.3f}“) print(f”Closed-loop frequency: ω = {omega_cl:.2f} rad/s”)
Simulate with LQR
def lqr_controller(x, k): … return -K @ x
result_lqr = system.rollout(x0, lqr_controller, n_steps=500)
Tracking example: follow sinusoidal reference
omega_ref = 0.5 # Below resonance A_ref = 0.5 # Amplitude t_track = np.arange(0, 10, system.dt) x_ref = A_ref * np.sin(omega_ref * t_track) v_ref = A_ref * omega_ref * np.cos(omega_ref * t_track) a_ref = -A_ref * omega_ref**2 * np.sin(omega_ref * t_track)
def tracking_controller(x, k): … if k >= len(x_ref): … return np.array([0.0]) … … # Feedforward + feedback … u_ff = a_ref[k] + 2system.zetasystem.omega_n*v_ref[k] + system.omega_n**2*x_ref[k] … u_fb = -K @ (x - np.array([x_ref[k], v_ref[k]])) … return u_fb + u_ff
result_track = system.rollout(np.zeros(2), tracking_controller, n_steps=len(t_track))
Plot tracking performance
plt.figure(figsize=(10, 6)) plt.plot(t_track, x_ref, ‘r–’, label=‘Reference’, linewidth=2) plt.plot(t_track, result_track[‘states’][:, 0], ‘b-’, label=‘Actual’) plt.xlabel(‘Time [s]’) plt.ylabel(‘Position [m]’) plt.legend() plt.grid() plt.title(‘Tracking Performance’) plt.show()
Compute tracking error
error = result_track[‘states’][:, 0] - x_ref rms_error = np.sqrt(np.mean(error**2)) max_error = np.max(np.abs(error)) print(f”RMS tracking error: {rms_error:.4f} m”) print(f”Max tracking error: {max_error:.4f} m”)
Physical Insights:
Energy Considerations: The harmonic oscillator continuously exchanges energy between kinetic and potential forms:
E_kinetic = 0.5·m·v²
E_potential = 0.5·k·x²
E_total = E_kinetic + E_potential
For undamped (ζ = 0): Total energy is conserved For damped (ζ > 0): Energy decreases exponentially E(t) = E₀·e^(-2ζωₙt)
Power dissipated by damping: P_damped = c·v² = 2mζωₙ·v²
Quality Factor: The quality factor Q measures how underdamped the system is: Q = 1/(2ζ)
High Q (low damping): - Sharp resonance peak - Long ringing time - Narrow bandwidth - Examples: tuning forks (Q ~ 1000), quartz crystals (Q ~ 10⁶)
Low Q (high damping): - Broad response - Fast settling - Wide bandwidth - Examples: shock absorbers (Q ~ 1), damped doors (Q ~ 0.5)
Decay Rate vs Oscillation Frequency: For underdamped systems, there’s a tradeoff: - Decay rate: σ = ζωₙ (controls settling time) - Oscillation frequency: ωd = ωₙ√(1 - ζ²)
As damping increases: - Decay rate increases (faster settling) - Oscillation frequency decreases - At critical damping: ωd = 0 (no oscillation)
Resonance Phenomenon: At resonance, even small periodic forces can produce large displacements. Examples: - Tacoma Narrows Bridge collapse (1940) - Wine glass shattering from sound - Building damage in earthquakes - Mechanical vibration failures
Prevention strategies: - Avoid excitation near ωₙ - Increase damping (ζ > 0.1 typically safe) - Use vibration isolators - Active control
Time Scales: The system has characteristic time scales:
- Natural period: T = 2π/ωₙ
- Time for one complete oscillation (undamped)
- Damped period: Td = 2π/ωd
- Actual oscillation period (underdamped)
- Td > T (damping slows oscillation)
- Time constant: τ = 1/(ζωₙ)
- Time for amplitude to decay by factor e
- Envelope decay: e^(-t/τ)
- Settling time (2% criterion): ts ≈ 4τ = 4/(ζωₙ)
- Time to reach 2% of steady-state
- Often used as design specification
Relationship to Electrical Circuits: The RLC circuit is mathematically identical:
L·d²q/dt² + R·dq/dt + (1/C)·q = V(t)
Analogies: Mechanical ↔︎ Electrical Position x ↔︎ Charge q Velocity v ↔︎ Current i Mass m ↔︎ Inductance L Damping c ↔︎ Resistance R Spring k ↔︎ 1/Capacitance
This enables unified analysis and design across domains.
Common Pitfalls:
Aliasing in high-frequency oscillators: If ωₙ > π/dt (Nyquist), the discrete system appears slower. Fix: Increase sampling rate or add anti-aliasing filter.
Resonance amplification: Small disturbances at ωₙ cause large response for small ζ. Fix: Avoid excitation near ωₙ or increase damping.
Incorrect damping ratio: Using damping coefficient c instead of ratio ζ. Remember: ζ = c/(2√(km)) is dimensionless.
Forgetting frequency shift: Damped frequency ωd ≠ ωₙ for ζ > 0. Use: ωd = ωₙ√(1 - ζ²)
Numerical instability with Euler: For stiff systems (large ωₙ or ζ), Euler requires tiny dt. Fix: Use ZOH, Tustin, or implicit methods.
Control saturation near resonance: At resonance, control effort can be excessive. Fix: Use anti-windup, gain scheduling, or notch filters.
Extensions:
This basic oscillator can be extended to:
Nonlinear oscillator: Add nonlinear spring: k → k₁x + k₃x³ (Duffing oscillator) Creates amplitude-dependent frequency
Coupled oscillators: Multiple masses connected by springs Normal modes and mode shapes
Parametric oscillator: Time-varying parameters: ωₙ(t) Can cause parametric resonance
Forced oscillator with multiple frequencies: Beat phenomena, combination tones
With Coulomb friction: Add dry friction: F_friction = μ·N·sign(v) Creates limit cycles
Nonlinear damping: Quadratic drag: F_drag = c·v² Common in aerodynamics
See Also:
DiscreteDoubleIntegrator : Undamped limit (ζ = 0, ωₙ = 0)
Attributes
| Name | Description |
|---|---|
| damped_period | Damped period Td = 2π/ωd [s] (for underdamped only). |
| damping_coefficient | Damping coefficient c = 2ζ√(km) [N·s/m]. |
| natural_period | Natural period T = 2π/ωₙ [s]. |
| omega_d | Damped natural frequency [rad/s]. |
| omega_n | Natural frequency [rad/s]. |
| quality_factor | Quality factor Q = 1/(2ζ) [-]. |
| settling_time | Settling time (2% criterion) ts ≈ 4τ [s]. |
| spring_constant | Spring constant k = m·ωₙ² [N/m]. |
| time_constant | Time constant τ = 1/(ζωₙ) [s]. |
| zeta | Damping ratio [-]. |
Methods
| Name | Description |
|---|---|
| compute_closed_loop_damping | Compute closed-loop damping ratio and natural frequency. |
| compute_eigenvalues | Compute discrete-time eigenvalues. |
| compute_frequency_response | Compute frequency response (Bode plot data). |
| compute_resonant_frequency | Compute resonant peak frequency ωᵣ [rad/s]. |
| compute_resonant_peak | Compute resonant peak magnitude (gain at resonance). |
| compute_step_response_characteristics | Compute step response characteristics. |
| compute_system_matrices | Compute discrete-time state-space matrices Ad, Bd. |
| define_system | Define discrete-time harmonic oscillator dynamics. |
| design_damping_controller | Design velocity feedback gain to achieve desired damping ratio. |
| design_stiffness_controller | Design position feedback gain to achieve desired natural frequency. |
| generate_chirp_signal | Generate chirp (frequency sweep) signal for system identification. |
| setup_equilibria | Set up equilibrium points. |
compute_closed_loop_damping
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_closed_loop_damping(
K,
)Compute closed-loop damping ratio and natural frequency.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| K | np.ndarray | State feedback gain [k_p, k_d] | required |
Returns
| Name | Type | Description |
|---|---|---|
| tuple | (zeta_cl, omega_cl) closed-loop damping and frequency |
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.1, dt=0.01)
>>> K = np.array([[50, 10]])
>>> zeta_cl, omega_cl = system.compute_closed_loop_damping(K)
>>> print(f"Closed-loop: ζ = {zeta_cl:.3f}, ω = {omega_cl:.2f}")compute_eigenvalues
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_eigenvalues()Compute discrete-time eigenvalues.
Returns
| Name | Type | Description |
|---|---|---|
| np.ndarray | Complex eigenvalues (2,) |
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.1, dt=0.01)
>>> eigs = system.compute_eigenvalues()
>>> print(f"Eigenvalues: {eigs}")
>>> print(f"Magnitude: {np.abs(eigs)}")
>>> print(f"Stable: {np.all(np.abs(eigs) < 1)}")Notes
For continuous-time poles s = -ζωₙ ± jωₙ√(1-ζ²), discrete poles are z = e^(s·dt).
Magnitude |z| = e^(-ζωₙdt) determines stability: - |z| < 1: Stable (decays) - |z| = 1: Marginally stable (ζ = 0) - |z| > 1: Unstable (grows)
compute_frequency_response
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_frequency_response(
frequencies,
)Compute frequency response (Bode plot data).
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| frequencies | np.ndarray | Frequencies to evaluate [rad/s] | required |
Returns
| Name | Type | Description |
|---|---|---|
| tuple | (magnitude_dB, phase_deg) where: - magnitude_dB: Magnitude in decibels - phase_deg: Phase in degrees |
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.1, dt=0.01)
>>> freqs = np.logspace(-1, 2, 100)
>>> mag_dB, phase_deg = system.compute_frequency_response(freqs)
>>>
>>> import matplotlib.pyplot as plt
>>> fig, axes = plt.subplots(2, 1)
>>> axes[0].semilogx(freqs, mag_dB)
>>> axes[0].set_ylabel('Magnitude [dB]')
>>> axes[1].semilogx(freqs, phase_deg)
>>> axes[1].set_ylabel('Phase [deg]')
>>> axes[1].set_xlabel('Frequency [rad/s]')
>>> plt.show()compute_resonant_frequency
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_resonant_frequency(
)Compute resonant peak frequency ωᵣ [rad/s].
Returns
| Name | Type | Description |
|---|---|---|
| float | Resonant frequency (0 if ζ ≥ 1/√2) |
Notes
For ζ < 1/√2: ωᵣ = ωₙ√(1 - 2ζ²)
The resonant frequency is where the magnitude response peaks. It’s lower than the natural frequency and only exists for lightly damped systems.
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.1, dt=0.01)
>>> omega_r = system.compute_resonant_frequency()
>>> print(f"Resonant frequency: {omega_r:.2f} rad/s")
>>> print(f"Natural frequency: {system.omega_n:.2f} rad/s")compute_resonant_peak
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_resonant_peak(
)Compute resonant peak magnitude (gain at resonance).
Returns
| Name | Type | Description |
|---|---|---|
| float | Peak magnitude (infinity if ζ = 0) |
Notes
At resonance (ω = ωᵣ): |H(jωᵣ)| = 1/(2ζ√(1 - ζ²))
For small damping (ζ << 1): |H(jωₙ)| ≈ Q = 1/(2ζ)
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.05, dt=0.01)
>>> peak = system.compute_resonant_peak()
>>> print(f"Resonant peak: {peak:.1f} ({20*np.log10(peak):.1f} dB)")
>>> print(f"Quality factor: {system.quality_factor:.1f}")compute_step_response_characteristics
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_step_response_characteristics(
)Compute step response characteristics.
Returns
| Name | Type | Description |
|---|---|---|
| dict | Dictionary containing: - ‘rise_time’: 10%-90% rise time [s] - ‘peak_time’: Time to first peak [s] - ‘overshoot’: Peak overshoot [%] - ‘settling_time’: 2% settling time [s] |
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.3, dt=0.01)
>>> chars = system.compute_step_response_characteristics()
>>> for key, val in chars.items():
... print(f"{key}: {val:.3f}")Notes
These formulas are for underdamped systems (0 < ζ < 1). For critically/overdamped systems, some metrics don’t apply.
compute_system_matrices
systems.builtin.deterministic.discrete.DiscreteOscillator.compute_system_matrices(
)Compute discrete-time state-space matrices Ad, Bd.
Returns
| Name | Type | Description |
|---|---|---|
| tuple | (Ad, Bd) where: - Ad: State transition matrix (2×2) - Bd: Control input matrix (2×1) |
Examples
>>> system = DiscreteOscillator(omega_n=2*np.pi, zeta=0.1, dt=0.01)
>>> Ad, Bd = system.compute_system_matrices()
>>> print(f"Ad =\n{Ad}")
>>> print(f"Bd =\n{Bd}")define_system
systems.builtin.deterministic.discrete.DiscreteOscillator.define_system(
omega_n=1.0,
zeta=0.1,
dt=0.1,
method='zoh',
mass=1.0,
spring_constant=None,
damping_coefficient=None,
)Define discrete-time harmonic oscillator dynamics.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| omega_n | float | Natural frequency [rad/s], must be positive | 1.0 |
| zeta | float | Damping ratio [-], must be non-negative | 0.1 |
| dt | float | Sampling time step [s] | 0.1 |
| method | str | Discretization method: - ‘zoh’: Zero-order hold (exact, recommended) - ‘tustin’: Bilinear/trapezoidal transform - ‘euler’: Forward Euler (simple) - ‘backward_euler’: Backward Euler (implicit) - ‘matched’: Matched pole-zero method | 'zoh' |
| mass | float | System mass [kg] (only used with spring_constant/damping_coefficient) | 1.0 |
| spring_constant | Optional[float] | Spring constant k [N/m] (alternative to omega_n) If provided: omega_n = sqrt(k/m) | None |
| damping_coefficient | Optional[float] | Damping coefficient c [N·s/m] (alternative to zeta) If provided: zeta = c/(2sqrt(km)) | None |
design_damping_controller
systems.builtin.deterministic.discrete.DiscreteOscillator.design_damping_controller(
zeta_desired,
)Design velocity feedback gain to achieve desired damping ratio.
Control law: u[k] = -k_d·v[k]
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| zeta_desired | float | Desired closed-loop damping ratio | required |
Returns
| Name | Type | Description |
|---|---|---|
| float | Velocity feedback gain k_d |
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.1, dt=0.01)
>>> k_d = system.design_damping_controller(zeta_desired=0.7)
>>> print(f"Damping gain: k_d = {k_d:.2f}")Notes
This adds damping without changing natural frequency (approximately). The effective damping becomes: ζ_eff ≈ ζ + k_d/(2ωₙ)
Solving for k_d: k_d = 2ωₙ(ζ_desired - ζ)
design_stiffness_controller
systems.builtin.deterministic.discrete.DiscreteOscillator.design_stiffness_controller(
omega_n_desired,
)Design position feedback gain to achieve desired natural frequency.
Control law: u[k] = -k_p·x[k]
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| omega_n_desired | float | Desired closed-loop natural frequency [rad/s] | required |
Returns
| Name | Type | Description |
|---|---|---|
| float | Position feedback gain k_p |
Examples
>>> system = DiscreteOscillator(omega_n=5, zeta=0.1, dt=0.01)
>>> k_p = system.design_stiffness_controller(omega_n_desired=10)
>>> print(f"Stiffness gain: k_p = {k_p:.2f}")Notes
This changes the effective natural frequency: ωₙ_eff = √(ωₙ² + k_p)
Solving for k_p: k_p = ωₙ_desired² - ωₙ²
generate_chirp_signal
systems.builtin.deterministic.discrete.DiscreteOscillator.generate_chirp_signal(
f_start,
f_end,
duration,
amplitude=1.0,
)Generate chirp (frequency sweep) signal for system identification.
Parameters
| Name | Type | Description | Default |
|---|---|---|---|
| f_start | float | Starting frequency [Hz] | required |
| f_end | float | Ending frequency [Hz] | required |
| duration | float | Signal duration [s] | required |
| amplitude | float | Signal amplitude | 1.0 |
Returns
| Name | Type | Description |
|---|---|---|
| np.ndarray | Chirp signal |
Examples
>>> system = DiscreteOscillator(omega_n=10, zeta=0.1, dt=0.01)
>>> chirp = system.generate_chirp_signal(
... f_start=0.1,
... f_end=5.0,
... duration=10.0
... )
>>> result = system.simulate(
... x0=np.zeros(2),
... u_sequence=chirp,
... n_steps=len(chirp)
... )setup_equilibria
systems.builtin.deterministic.discrete.DiscreteOscillator.setup_equilibria()Set up equilibrium points.
Adds the origin (rest position) as the stable equilibrium for damped systems.