Introduce TimeInvariantSystem trait.

This trait simplifies writing dynamics equations that do not depend on
time. In the future, implementations may specialize on this trait to
simulate large timestamps. Gracefully ignores time parameter if called
as System.
This commit is contained in:
Hayden Heroux 2026-11-02 15:44:26 -05:00
parent c8b15c949f
commit ea326d4a55
6 changed files with 70 additions and 16 deletions

View file

@ -2,7 +2,7 @@ use crate::units::*;
use crate::{
integrators::traits::Accumulate,
traits::{Integrator, System, TimeDerivative},
traits::{Integrator, System, TimeDerivative, TimeInvariantSystem},
};
pub struct Rk4;
@ -32,3 +32,35 @@ where
.add_scaled(dt / 6.0, &k4)
}
}
impl Rk4 {
pub fn step_ti<S, const N: usize, const M: usize>(
&self,
system: &S,
x: &S::State,
u: &S::Input,
dt: Time,
) -> S::State
where
S: TimeInvariantSystem<N, M>,
S::State: Clone + TimeDerivative + Accumulate<<S::State as TimeDerivative>::Derivative>,
{
let half_dt = dt * 0.5;
let k1 = system.derivatives(x, u);
let x2 = x.add_scaled(half_dt, &k1);
let k2 = system.derivatives(&x2, u);
let x3 = x.add_scaled(half_dt, &k2);
let k3 = system.derivatives(&x3, u);
let x4 = x.add_scaled(dt, &k3);
let k4 = system.derivatives(&x4, u);
x.add_scaled(dt / 6.0, &k1)
.add_scaled(dt / 3.0, &k2)
.add_scaled(dt / 3.0, &k3)
.add_scaled(dt / 6.0, &k4)
}
}

View file

@ -18,4 +18,4 @@ pub use units::{BackEmfConstant, CurrentRate, FrictionCoefficient, TorqueConstan
pub use controllers::linear_feedback::LinearFeedback;
pub use integrators::rk4::Rk4;
pub use systems::linear_system::LinearSystem;
pub use traits::{Controller, Integrator, System, TimeDerivative};
pub use traits::{Controller, Integrator, System, TimeDerivative, TimeInvariantSystem};

View file

@ -2,7 +2,7 @@ use crate::units::*;
use crate::inputs::{CurrentInput, VoltageInput};
use crate::states::{CurrentRateState, CurrentState, OmegaAlphaState, ThetaOmegaState};
use crate::traits::{Saturate, System, TimeDerivative};
use crate::traits::{Saturate, TimeDerivative, TimeInvariantSystem};
#[derive(Clone, Copy, Debug)]
pub struct DCMotor {
@ -110,13 +110,12 @@ impl DCMotor {
}
}
impl System<1, 1> for DCMotor {
impl TimeInvariantSystem<1, 1> for DCMotor {
type State = CurrentState;
type Input = VoltageInput;
fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {
@ -160,13 +159,12 @@ impl DCMotorPosition {
}
}
impl System<2, 1> for DCMotorPosition {
impl TimeInvariantSystem<2, 1> for DCMotorPosition {
type State = ThetaOmegaState;
type Input = VoltageInput;
fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {

View file

@ -1,6 +1,6 @@
use crate::inputs::AccelerationInput;
use crate::states::{PositionVelocityState, VelocityAccelerationState};
use crate::traits::{System, TimeDerivative, VectorValue};
use crate::traits::{TimeDerivative, TimeInvariantSystem, VectorValue};
use crate::units::*;
use nalgebra::{DMatrix, DVector, SMatrix, SVector};
@ -28,13 +28,12 @@ impl<const N: usize, const M: usize> LinearSystem<N, M> {
}
}
impl<const N: usize, const M: usize> System<N, M> for LinearSystem<N, M> {
impl<const N: usize, const M: usize> TimeInvariantSystem<N, M> for LinearSystem<N, M> {
type State = SVector<f64, N>;
type Input = SVector<f64, M>;
fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {
@ -85,13 +84,12 @@ impl DoubleIntegrator {
}
}
impl System<2, 1> for DoubleIntegrator {
impl TimeInvariantSystem<2, 1> for DoubleIntegrator {
type State = PositionVelocityState;
type Input = AccelerationInput;
fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {

View file

@ -4,7 +4,7 @@ use crate::inputs::CurrentInput;
use crate::states::{OmegaAlphaState, ThetaOmegaState};
use crate::systems::LinearSystem;
use crate::systems::dc_motor::DCMotor;
use crate::traits::{Saturate, System, TimeDerivative};
use crate::traits::{Saturate, TimeDerivative, TimeInvariantSystem};
use nalgebra::{SMatrix, SVector};
#[derive(Clone, Copy, Debug)]
@ -63,13 +63,12 @@ impl Pendulum {
}
}
impl System<2, 1> for Pendulum {
impl TimeInvariantSystem<2, 1> for Pendulum {
type State = ThetaOmegaState;
type Input = CurrentInput;
fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {

View file

@ -17,7 +17,6 @@ pub trait System<const N: usize, const M: usize> {
type Input;
// Evaluate the derivative vector x_dot at (t, x, u)
// TODO Create time-invariant system trait?
fn derivatives(
&self,
t: Time,
@ -26,6 +25,34 @@ pub trait System<const N: usize, const M: usize> {
) -> <Self::State as TimeDerivative>::Derivative;
}
pub trait TimeInvariantSystem<const N: usize, const M: usize> {
type State: TimeDerivative;
type Input;
fn derivatives(
&self,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative;
}
impl<T, const N: usize, const M: usize> System<N, M> for T
where
T: TimeInvariantSystem<N, M>,
{
type State = T::State;
type Input = T::Input;
fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {
self.derivatives(x, u)
}
}
// Computes the evolution of a system over (t, x, u)
pub trait Integrator<S, const N: usize, const M: usize>
where