Add pendulum demo.

This commit is contained in:
Hayden Heroux 2026-10-29 16:47:07 -04:00
parent 2df5212256
commit cae2d2fb45
23 changed files with 690 additions and 197 deletions

View file

@ -1,45 +1,57 @@
use lyapunov::inputs::AccelerationInput; use lyapunov::states::ThetaOmegaState;
use lyapunov::states::PositionVelocityState; use lyapunov::traits::Saturate;
use lyapunov::{Controller, DoubleIntegrator, Integrator, LinearFeedback, Rk4}; use lyapunov::units::*;
use uom::si::acceleration::meter_per_second_squared; use lyapunov::{Controller, DCMotor, Integrator, LinearFeedback, Pendulum, Rk4};
use uom::si::f64::{Acceleration, Length, Time, Velocity};
use uom::si::length::meter;
use uom::si::time::millisecond;
use uom::si::velocity::meter_per_second;
fn main() { fn main() {
let system = DoubleIntegrator::new(1.0, Acceleration::new::<meter_per_second_squared>(-0.1)); let motor = DCMotor::new(
ElectricalResistance::new::<ohm>(1.0),
let u_ff = system Inductance::new::<henry>(0.5),
.constant_cancelling_input() lyapunov::TorqueConstant::from(
.unwrap_or_else(AccelerationInput::zero); Torque::new::<newton_meter>(0.5) / ElectricCurrent::new::<ampere>(1.0),
),
let p = LinearFeedback::<2, 1>::from_gains([2.0, 3.0]); ElectricCurrent::new::<ampere>(2.0),
ElectricPotential::new::<volt>(12.0),
let mut x = PositionVelocityState::new(
Length::new::<meter>(10.0),
Velocity::new::<meter_per_second>(0.0),
);
let r = PositionVelocityState::new(
Length::new::<meter>(0.0),
Velocity::new::<meter_per_second>(0.0),
); );
let dt = Time::new::<millisecond>(50.0); let system = Pendulum::new(
Mass::new::<kilogram>(1.0),
Length::new::<meter>(1.0),
MomentOfInertia::new::<kilogram_square_meter>(1.0),
Acceleration::new::<meter_per_second_squared>(9.81),
motor,
);
for step in 0..200 { // Gains are in A/rad and A/(rad/s).
let feedback = LinearFeedback::<2, 1>::from_gains([8.0, 2.0]);
let rk4 = Rk4;
// Start displaced from the downward equilibrium.
let mut x = ThetaOmegaState::new(
Angle::new::<radian>(0.5),
AngularVelocity::new::<radian_per_second>(0.0),
);
let r = ThetaOmegaState::new(
Angle::new::<radian>(0.0),
AngularVelocity::new::<radian_per_second>(0.0),
);
let dt = Time::new::<millisecond>(10.0);
for step in 0..300 {
let t = dt * (step as f64); let t = dt * (step as f64);
let u_fb = p.calculate(&system, t, &x, &r); let u_fb = feedback.calculate(&system, t, &x, &r);
let u = u_fb + u_ff; let u = u_fb.saturate(&system);
x = Rk4.step(&system, t, &x, &u, dt); x = rk4.step(&system, t, &x, &u, dt);
println!( println!(
"t={:.2}s u={:.3?} pos={:.3?} vel={:.3?}", "t={:.2}s theta={:.3?} omega={:.3?} current={:.3?}",
t.value, t.value,
u.acceleration(), x.angle(),
x.position(), x.angular_velocity(),
x.velocity(), u.current(),
); );
} }
} }

View file

@ -1,7 +1,7 @@
use nalgebra::{SMatrix, SVector}; use crate::units::*;
use uom::si::f64::Time; use nalgebra::SMatrix;
use crate::traits::{Controller, System, VectorState}; use crate::traits::{Controller, System, VectorValue};
pub struct LinearFeedback<const N: usize, const M: usize> { pub struct LinearFeedback<const N: usize, const M: usize> {
pub gain_matrix: SMatrix<f64, M, N>, pub gain_matrix: SMatrix<f64, M, N>,
@ -10,8 +10,8 @@ pub struct LinearFeedback<const N: usize, const M: usize> {
impl<S, const N: usize, const M: usize> Controller<S, N, M> for LinearFeedback<N, M> impl<S, const N: usize, const M: usize> Controller<S, N, M> for LinearFeedback<N, M>
where where
S: System<N, M>, S: System<N, M>,
S::State: Copy + std::ops::Sub<Output = S::State> + VectorState<N>, S::State: VectorValue<N>,
S::Input: From<SVector<f64, M>>, S::Input: VectorValue<M>,
{ {
fn calculate(&self, _system: &S, _t: Time, x: &S::State, r: &S::State) -> S::Input { fn calculate(&self, _system: &S, _t: Time, x: &S::State, r: &S::State) -> S::Input {
let error = *r - *x; let error = *r - *x;

View file

@ -0,0 +1,28 @@
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
use crate::impl_has_vector;
#[derive(Clone, Copy, PartialEq)]
pub struct AccelerationInput {
pub vector: SVector<f64, 1>,
}
impl AccelerationInput {
pub fn new(acceleration: Acceleration) -> Self {
Self {
vector: SVector::from_column_slice(&[acceleration.value]),
}
}
pub fn acceleration(&self) -> Acceleration {
Acceleration::new::<meter_per_second_squared>(self.vector[0])
}
pub fn zero() -> Self {
Self::new(Acceleration::new::<meter_per_second_squared>(0.0))
}
}
impl_has_vector!(AccelerationInput, 1);

View file

@ -1,68 +0,0 @@
use nalgebra::SVector;
use std::convert::From;
use std::fmt;
use std::ops::Add;
use uom::si::f64::Acceleration;
use crate::traits::VectorInput;
#[derive(Clone, Copy, PartialEq)]
pub struct AccelerationInput {
pub vector: SVector<f64, 1>,
}
impl AccelerationInput {
pub fn new(acceleration: Acceleration) -> Self {
Self {
vector: SVector::from_column_slice(&[acceleration.value]),
}
}
pub fn acceleration(&self) -> Acceleration {
Acceleration::new::<uom::si::acceleration::meter_per_second_squared>(self.vector[0])
}
pub fn zero() -> Self {
Self::new(Acceleration::new::<
uom::si::acceleration::meter_per_second_squared,
>(0.0))
}
}
impl From<SVector<f64, 1>> for AccelerationInput {
fn from(vector: SVector<f64, 1>) -> Self {
Self { vector }
}
}
impl From<AccelerationInput> for SVector<f64, 1> {
fn from(input: AccelerationInput) -> Self {
input.vector
}
}
impl VectorInput<1> for AccelerationInput {
fn to_vector(&self) -> SVector<f64, 1> {
self.vector
}
}
impl Add for AccelerationInput {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
vector: self.vector + rhs.vector,
}
}
}
impl fmt::Debug for AccelerationInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"AccelerationInput {{ acceleration: {:?} }}",
self.acceleration()
)
}
}

View file

@ -0,0 +1,24 @@
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
use crate::impl_has_vector;
#[derive(Clone, Copy, PartialEq)]
pub struct CurrentInput {
pub vector: SVector<f64, 1>,
}
impl CurrentInput {
pub fn new(current: ElectricCurrent) -> Self {
Self {
vector: SVector::from_column_slice(&[current.value]),
}
}
pub fn current(&self) -> ElectricCurrent {
ElectricCurrent::new::<ampere>(self.vector[0])
}
}
impl_has_vector!(CurrentInput, 1);

View file

@ -1,3 +1,7 @@
pub mod acceleration_input; pub mod acceleration;
pub mod current;
pub mod voltage;
pub use acceleration_input::AccelerationInput; pub use acceleration::AccelerationInput;
pub use current::CurrentInput;
pub use voltage::VoltageInput;

View file

@ -0,0 +1,25 @@
use crate::units::Voltage;
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
use crate::impl_has_vector;
#[derive(Clone, Copy, PartialEq)]
pub struct VoltageInput {
pub vector: SVector<f64, 1>,
}
impl VoltageInput {
pub fn new(voltage: Voltage) -> Self {
Self {
vector: SVector::from_column_slice(&[voltage.value]),
}
}
pub fn voltage(&self) -> Voltage {
Voltage::new::<volt>(self.vector[0])
}
}
impl_has_vector!(VoltageInput, 1);

View file

@ -1,8 +1,8 @@
use uom::si::f64::Time; use crate::units::*;
use crate::{ use crate::{
integrators::traits::Accumulate, integrators::traits::Accumulate,
traits::{Integrator, System}, traits::{Integrator, System, TimeDerivative},
}; };
pub struct Rk4; pub struct Rk4;
@ -10,7 +10,7 @@ pub struct Rk4;
impl<S, const N: usize, const M: usize> Integrator<S, N, M> for Rk4 impl<S, const N: usize, const M: usize> Integrator<S, N, M> for Rk4
where where
S: System<N, M>, S: System<N, M>,
S::State: Clone + Accumulate<S::Derivative>, S::State: Clone + TimeDerivative + Accumulate<<S::State as TimeDerivative>::Derivative>,
{ {
fn step(&self, system: &S, t: Time, x: &S::State, u: &S::Input, dt: Time) -> S::State { fn step(&self, system: &S, t: Time, x: &S::State, u: &S::Input, dt: Time) -> S::State {
let half_dt = dt * 0.5; let half_dt = dt * 0.5;

View file

@ -1,5 +1,5 @@
use crate::units::*;
use nalgebra::SVector; use nalgebra::SVector;
use uom::si::f64::Time;
pub trait Accumulate<D> { pub trait Accumulate<D> {
fn add_scaled(&self, dt: Time, derivative: &D) -> Self; fn add_scaled(&self, dt: Time, derivative: &D) -> Self;
@ -7,6 +7,6 @@ pub trait Accumulate<D> {
impl<const N: usize> Accumulate<SVector<f64, N>> for SVector<f64, N> { impl<const N: usize> Accumulate<SVector<f64, N>> for SVector<f64, N> {
fn add_scaled(&self, dt: Time, derivative: &SVector<f64, N>) -> Self { fn add_scaled(&self, dt: Time, derivative: &SVector<f64, N>) -> Self {
self + dt.get::<uom::si::time::second>() * derivative self + dt.get::<second>() * derivative
} }
} }

View file

@ -4,13 +4,18 @@ pub mod integrators;
pub mod states; pub mod states;
pub mod systems; pub mod systems;
pub mod traits; pub mod traits;
pub mod units;
pub use inputs::AccelerationInput; pub use inputs::{AccelerationInput, CurrentInput, VoltageInput};
pub use states::{PositionVelocityState, VelocityAccelerationState}; pub use states::{
pub use systems::DoubleIntegrator; CurrentRateState, CurrentState, OmegaAlphaState, PositionVelocityState, ThetaOmegaState,
pub use traits::VectorInput; VelocityAccelerationState,
};
pub use systems::{DCMotor, DCMotorPosition, DoubleIntegrator, Pendulum};
pub use traits::{Saturate, VectorValue};
pub use units::{BackEmfConstant, CurrentRate, FrictionCoefficient, TorqueConstant, Voltage};
pub use controllers::linear_feedback::LinearFeedback; pub use controllers::linear_feedback::LinearFeedback;
pub use integrators::rk4::Rk4; pub use integrators::rk4::Rk4;
pub use systems::linear_system::LinearSystem; pub use systems::linear_system::LinearSystem;
pub use traits::{Controller, Integrator, System, VectorState}; pub use traits::{Controller, Integrator, System, TimeDerivative};

View file

@ -0,0 +1,39 @@
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
use crate::impl_has_vector;
use crate::integrators::traits::Accumulate;
use crate::states::CurrentRateState;
use crate::traits::TimeDerivative;
#[derive(Clone, Copy, PartialEq)]
pub struct CurrentState {
pub vector: SVector<f64, 1>,
}
impl CurrentState {
pub fn new(current: ElectricCurrent) -> Self {
Self {
vector: SVector::from_column_slice(&[current.value]),
}
}
pub fn current(&self) -> ElectricCurrent {
ElectricCurrent::new::<ampere>(self.vector[0])
}
}
impl Accumulate<CurrentRateState> for CurrentState {
fn add_scaled(&self, dt: Time, deriv: &CurrentRateState) -> Self {
Self {
vector: self.vector + dt.value * deriv.vector,
}
}
}
impl_has_vector!(CurrentState, 1);
impl TimeDerivative for CurrentState {
type Derivative = CurrentRateState;
}

View file

@ -0,0 +1,32 @@
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
#[derive(Clone, Copy, PartialEq)]
pub struct CurrentRateState {
pub vector: SVector<f64, 1>,
}
impl CurrentRateState {
pub fn new(rate: CurrentRate) -> Self {
Self {
vector: SVector::from_column_slice(&[rate.value]),
}
}
pub fn rate(&self) -> CurrentRate {
ElectricCurrent::new::<ampere>(self.vector[0]) / Time::new::<second>(1.0)
}
}
impl From<SVector<f64, 1>> for CurrentRateState {
fn from(vector: SVector<f64, 1>) -> Self {
Self { vector }
}
}
impl From<CurrentRateState> for SVector<f64, 1> {
fn from(state: CurrentRateState) -> Self {
state.vector
}
}

View file

@ -1,5 +1,13 @@
pub mod current;
pub mod current_rate;
pub mod omega_alpha;
pub mod position_velocity; pub mod position_velocity;
pub mod theta_omega;
pub mod velocity_acceleration; pub mod velocity_acceleration;
pub use current::CurrentState;
pub use current_rate::CurrentRateState;
pub use omega_alpha::OmegaAlphaState;
pub use position_velocity::PositionVelocityState; pub use position_velocity::PositionVelocityState;
pub use theta_omega::ThetaOmegaState;
pub use velocity_acceleration::VelocityAccelerationState; pub use velocity_acceleration::VelocityAccelerationState;

View file

@ -0,0 +1,42 @@
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OmegaAlphaState {
pub vector: SVector<f64, 2>,
}
impl OmegaAlphaState {
pub fn new(
angular_velocity: AngularVelocity,
angular_acceleration: AngularAcceleration,
) -> Self {
Self {
vector: SVector::from_column_slice(&[
angular_velocity.value,
angular_acceleration.value,
]),
}
}
pub fn angular_velocity(&self) -> AngularVelocity {
AngularVelocity::new::<radian_per_second>(self.vector[0])
}
pub fn angular_acceleration(&self) -> AngularAcceleration {
AngularAcceleration::new::<radian_per_second_squared>(self.vector[1])
}
}
impl From<SVector<f64, 2>> for OmegaAlphaState {
fn from(vector: SVector<f64, 2>) -> Self {
Self { vector }
}
}
impl From<OmegaAlphaState> for SVector<f64, 2> {
fn from(state: OmegaAlphaState) -> Self {
state.vector
}
}

View file

@ -1,12 +1,11 @@
use crate::units::*;
use nalgebra::SVector; use nalgebra::SVector;
use std::convert::From; use std::convert::From;
use std::fmt;
use uom::si::f64::{Length, Velocity};
use crate::impl_has_vector;
use crate::integrators::traits::Accumulate; use crate::integrators::traits::Accumulate;
use crate::states::VelocityAccelerationState; use crate::states::VelocityAccelerationState;
use crate::traits::VectorState; use crate::traits::TimeDerivative;
use uom::si::f64::Time;
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub struct PositionVelocityState { pub struct PositionVelocityState {
@ -21,11 +20,11 @@ impl PositionVelocityState {
} }
pub fn position(&self) -> Length { pub fn position(&self) -> Length {
Length::new::<uom::si::length::meter>(self.vector[0]) Length::new::<meter>(self.vector[0])
} }
pub fn velocity(&self) -> Velocity { pub fn velocity(&self) -> Velocity {
Velocity::new::<uom::si::velocity::meter_per_second>(self.vector[1]) Velocity::new::<meter_per_second>(self.vector[1])
} }
pub fn set_position(&mut self, position: Length) { pub fn set_position(&mut self, position: Length) {
@ -35,39 +34,6 @@ impl PositionVelocityState {
pub fn set_velocity(&mut self, velocity: Velocity) { pub fn set_velocity(&mut self, velocity: Velocity) {
self.vector[1] = velocity.value; self.vector[1] = velocity.value;
} }
pub fn position_clamped(&self, min: Length, max: Length) -> Self {
let position = self.position();
if position > max {
Self::new(max, self.velocity())
} else if position < min {
Self::new(min, self.velocity())
} else {
*self
}
}
}
impl From<SVector<f64, 2>> for PositionVelocityState {
fn from(vector: SVector<f64, 2>) -> Self {
Self { vector }
}
}
impl std::ops::Sub for PositionVelocityState {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
vector: self.vector - rhs.vector,
}
}
}
impl From<PositionVelocityState> for SVector<f64, 2> {
fn from(state: PositionVelocityState) -> Self {
state.vector
}
} }
impl Accumulate<VelocityAccelerationState> for PositionVelocityState { impl Accumulate<VelocityAccelerationState> for PositionVelocityState {
@ -78,19 +44,8 @@ impl Accumulate<VelocityAccelerationState> for PositionVelocityState {
} }
} }
impl VectorState<2> for PositionVelocityState { impl_has_vector!(PositionVelocityState, 2);
fn to_vector(&self) -> SVector<f64, 2> {
self.vector
}
}
impl fmt::Debug for PositionVelocityState { impl TimeDerivative for PositionVelocityState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { type Derivative = VelocityAccelerationState;
write!(
f,
"PositionVelocityState {{ position: {:?}, velocity: {:?} }}",
self.position(),
self.velocity()
)
}
} }

View file

@ -0,0 +1,51 @@
use crate::units::*;
use nalgebra::SVector;
use std::convert::From;
use crate::impl_has_vector;
use crate::integrators::traits::Accumulate;
use crate::states::OmegaAlphaState;
use crate::traits::TimeDerivative;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ThetaOmegaState {
pub vector: SVector<f64, 2>,
}
impl ThetaOmegaState {
pub fn new(angle: Angle, angular_velocity: AngularVelocity) -> Self {
Self {
vector: SVector::from_column_slice(&[angle.value, angular_velocity.value]),
}
}
pub fn angle(&self) -> Angle {
Angle::new::<radian>(self.vector[0])
}
pub fn angular_velocity(&self) -> AngularVelocity {
AngularVelocity::new::<radian_per_second>(self.vector[1])
}
pub fn set_angle(&mut self, angle: Angle) {
self.vector[0] = angle.value;
}
pub fn set_angular_velocity(&mut self, angular_velocity: AngularVelocity) {
self.vector[1] = angular_velocity.value;
}
}
impl Accumulate<OmegaAlphaState> for ThetaOmegaState {
fn add_scaled(&self, dt: Time, deriv: &OmegaAlphaState) -> Self {
Self {
vector: self.vector + dt.value * deriv.vector,
}
}
}
impl_has_vector!(ThetaOmegaState, 2);
impl TimeDerivative for ThetaOmegaState {
type Derivative = OmegaAlphaState;
}

View file

@ -1,6 +1,6 @@
use crate::units::*;
use nalgebra::SVector; use nalgebra::SVector;
use std::convert::From; use std::convert::From;
use uom::si::f64::{Acceleration, Velocity};
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct VelocityAccelerationState { pub struct VelocityAccelerationState {
@ -15,11 +15,11 @@ impl VelocityAccelerationState {
} }
pub fn velocity(&self) -> Velocity { pub fn velocity(&self) -> Velocity {
Velocity::new::<uom::si::velocity::meter_per_second>(self.vector[0]) Velocity::new::<meter_per_second>(self.vector[0])
} }
pub fn acceleration(&self) -> Acceleration { pub fn acceleration(&self) -> Acceleration {
Acceleration::new::<uom::si::acceleration::meter_per_second_squared>(self.vector[1]) Acceleration::new::<meter_per_second_squared>(self.vector[1])
} }
} }

View file

@ -0,0 +1,149 @@
use crate::units::*;
use crate::inputs::{CurrentInput, VoltageInput};
use crate::states::{CurrentRateState, CurrentState, OmegaAlphaState, ThetaOmegaState};
use crate::traits::{Saturate, System, TimeDerivative};
#[derive(Clone, Copy, Debug)]
pub struct DCMotor {
pub resistance: ElectricalResistance,
pub inductance: Inductance,
pub torque_constant: TorqueConstant,
pub max_current: ElectricCurrent,
pub nominal_voltage: ElectricPotential,
}
impl DCMotor {
pub fn new(
resistance: ElectricalResistance,
inductance: Inductance,
torque_constant: TorqueConstant,
max_current: ElectricCurrent,
nominal_voltage: ElectricPotential,
) -> Self {
Self {
resistance,
inductance,
torque_constant,
max_current,
nominal_voltage,
}
}
pub fn torque(&self, current: ElectricCurrent) -> Torque {
Torque::new::<newton_meter>((self.torque_constant * current).value)
}
pub fn saturate_current(&self, current: ElectricCurrent) -> ElectricCurrent {
let clamped = current
.value
.clamp(-self.max_current.value, self.max_current.value);
ElectricCurrent::new::<ampere>(clamped)
}
}
impl System<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 {
let current = x.current().value;
let voltage = u.voltage().value;
// Simplified electrical dynamics: L di/dt = V - R i
let di_dt = (voltage - self.resistance.value * current) / self.inductance.value;
CurrentRateState::new(ElectricCurrent::new::<ampere>(di_dt) / Time::new::<second>(1.0))
}
}
#[derive(Clone, Copy, Debug)]
pub struct DCMotorPosition {
pub inertia: MomentOfInertia,
pub torque_constant: TorqueConstant,
pub back_emf_constant: BackEmfConstant,
pub resistance: ElectricalResistance,
pub friction: FrictionCoefficient,
pub nominal_voltage: ElectricPotential,
}
impl DCMotorPosition {
pub fn new(
inertia: MomentOfInertia,
torque_constant: TorqueConstant,
back_emf_constant: BackEmfConstant,
resistance: ElectricalResistance,
friction: FrictionCoefficient,
nominal_voltage: ElectricPotential,
) -> Self {
Self {
inertia,
torque_constant,
back_emf_constant,
resistance,
friction,
nominal_voltage,
}
}
}
impl System<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 {
let omega = x.angular_velocity();
let voltage = u.voltage().value;
// Instantaneous current: I = (V - K_e * omega) / R
let back_emf = (self.back_emf_constant * omega).value;
let current = (voltage - back_emf) / self.resistance.value;
// Motor torque: tau = K_t * I
let torque = (self.torque_constant * ElectricCurrent::new::<ampere>(current)).value;
// Angular acceleration.
let alpha = (torque - (self.friction * omega).value) / self.inertia.value;
OmegaAlphaState::new(
AngularVelocity::new::<radian_per_second>(omega.value),
AngularAcceleration::new::<radian_per_second_squared>(alpha),
)
}
}
impl Saturate<DCMotor> for VoltageInput {
fn saturate(&self, system: &DCMotor) -> Self {
let clamped = self
.voltage()
.value
.clamp(-system.nominal_voltage.value, system.nominal_voltage.value);
VoltageInput::new(ElectricPotential::new::<volt>(clamped))
}
}
impl Saturate<DCMotorPosition> for VoltageInput {
fn saturate(&self, system: &DCMotorPosition) -> Self {
let clamped = self
.voltage()
.value
.clamp(-system.nominal_voltage.value, system.nominal_voltage.value);
VoltageInput::new(ElectricPotential::new::<volt>(clamped))
}
}
impl Saturate<DCMotor> for CurrentInput {
fn saturate(&self, system: &DCMotor) -> Self {
CurrentInput::new(system.saturate_current(self.current()))
}
}

View file

@ -1,8 +1,8 @@
use crate::inputs::AccelerationInput; use crate::inputs::AccelerationInput;
use crate::states::{PositionVelocityState, VelocityAccelerationState}; use crate::states::{PositionVelocityState, VelocityAccelerationState};
use crate::traits::{System, VectorInput}; use crate::traits::{System, TimeDerivative, VectorValue};
use crate::units::*;
use nalgebra::{DMatrix, DVector, SMatrix, SVector}; use nalgebra::{DMatrix, DVector, SMatrix, SVector};
use uom::si::f64::{Acceleration, Time};
pub struct LinearSystem<const N: usize, const M: usize> { pub struct LinearSystem<const N: usize, const M: usize> {
pub state_matrix: SMatrix<f64, N, N>, pub state_matrix: SMatrix<f64, N, N>,
@ -31,9 +31,13 @@ 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> System<N, M> for LinearSystem<N, M> {
type State = SVector<f64, N>; type State = SVector<f64, N>;
type Input = SVector<f64, M>; type Input = SVector<f64, M>;
type Derivative = SVector<f64, N>;
fn derivatives(&self, _t: Time, x: &Self::State, u: &Self::Input) -> Self::Derivative { fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {
self.state_matrix * x + self.input_matrix * u + self.constant self.state_matrix * x + self.input_matrix * u + self.constant
} }
} }
@ -84,9 +88,13 @@ impl DoubleIntegrator {
impl System<2, 1> for DoubleIntegrator { impl System<2, 1> for DoubleIntegrator {
type State = PositionVelocityState; type State = PositionVelocityState;
type Input = AccelerationInput; type Input = AccelerationInput;
type Derivative = VelocityAccelerationState;
fn derivatives(&self, _t: Time, x: &Self::State, u: &Self::Input) -> Self::Derivative { fn derivatives(
&self,
_t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative {
let u_vector = u.to_vector(); let u_vector = u.to_vector();
let dx = self.state_matrix * x.vector + self.input_matrix * u_vector + self.constant; let dx = self.state_matrix * x.vector + self.input_matrix * u_vector + self.constant;

View file

@ -1,3 +1,7 @@
pub mod dc_motor;
pub mod linear_system; pub mod linear_system;
pub mod pendulum;
pub use dc_motor::{DCMotor, DCMotorPosition};
pub use linear_system::{DoubleIntegrator, LinearSystem}; pub use linear_system::{DoubleIntegrator, LinearSystem};
pub use pendulum::Pendulum;

View file

@ -0,0 +1,68 @@
use crate::units::*;
use crate::inputs::CurrentInput;
use crate::states::{OmegaAlphaState, ThetaOmegaState};
use crate::systems::dc_motor::DCMotor;
use crate::traits::{Saturate, System, TimeDerivative};
#[derive(Clone, Copy, Debug)]
pub struct Pendulum {
pub mass: Mass,
pub length: Length,
pub inertia: MomentOfInertia,
pub gravity: Acceleration,
pub motor: DCMotor,
}
impl Pendulum {
pub fn new(
mass: Mass,
length: Length,
inertia: MomentOfInertia,
gravity: Acceleration,
motor: DCMotor,
) -> Self {
Self {
mass,
length,
inertia,
gravity,
motor,
}
}
}
impl System<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 {
let theta = x.angle().value;
let omega = x.angular_velocity().value;
let current = u.current().value;
let torque = self
.motor
.torque(ElectricCurrent::new::<ampere>(current))
.value;
let alpha = (torque
- self.mass.value * self.gravity.value * self.length.value * theta.sin())
/ self.inertia.value;
OmegaAlphaState::new(
AngularVelocity::new::<radian_per_second>(omega),
AngularAcceleration::new::<radian_per_second_squared>(alpha),
)
}
}
impl Saturate<Pendulum> for CurrentInput {
fn saturate(&self, system: &Pendulum) -> Self {
self.saturate(&system.motor)
}
}

View file

@ -1,15 +1,29 @@
use crate::units::*;
use nalgebra::SVector; use nalgebra::SVector;
use uom::si::f64::*;
// Indicates the time-derivative type of a state.
pub trait TimeDerivative {
type Derivative;
}
// `SVector` is its own derivative type.
impl<const N: usize> TimeDerivative for SVector<f64, N> {
type Derivative = SVector<f64, N>;
}
// Represents a dynamical system with state dimension N and input dimension M. // Represents a dynamical system with state dimension N and input dimension M.
pub trait System<const N: usize, const M: usize> { pub trait System<const N: usize, const M: usize> {
type State; type State: TimeDerivative;
type Input; type Input;
type Derivative;
// Evaluate the derivative vector x_dot at (t, x, u) // Evaluate the derivative vector x_dot at (t, x, u)
// TODO Create time-invariant system trait? // TODO Create time-invariant system trait?
fn derivatives(&self, t: Time, x: &Self::State, u: &Self::Input) -> Self::Derivative; fn derivatives(
&self,
t: Time,
x: &Self::State,
u: &Self::Input,
) -> <Self::State as TimeDerivative>::Derivative;
} }
// Computes the evolution of a system over (t, x, u) // Computes the evolution of a system over (t, x, u)
@ -28,24 +42,91 @@ where
fn calculate(&self, system: &S, t: Time, x: &S::State, r: &S::State) -> S::Input; fn calculate(&self, system: &S, t: Time, x: &S::State, r: &S::State) -> S::Input;
} }
/// A state type that can be viewed as an `N`-dimensional vector. pub trait HasVector<const D: usize> {
pub trait VectorState<const N: usize>: Copy { fn vector(&self) -> &SVector<f64, D>;
fn to_vector(&self) -> SVector<f64, N>; fn vector_mut(&mut self) -> &mut SVector<f64, D>;
} }
impl<const N: usize> VectorState<N> for SVector<f64, N> { impl<const D: usize> HasVector<D> for SVector<f64, D> {
fn to_vector(&self) -> SVector<f64, N> { fn vector(&self) -> &SVector<f64, D> {
*self self
}
fn vector_mut(&mut self) -> &mut SVector<f64, D> {
self
} }
} }
/// An input type that can be viewed as an `M`-dimensional vector. pub trait VectorValue<const D: usize>:
pub trait VectorInput<const M: usize>: Copy { Copy
fn to_vector(&self) -> SVector<f64, M>; + From<SVector<f64, D>>
} + Into<SVector<f64, D>>
+ std::ops::Add<Output = Self>
impl<const M: usize> VectorInput<M> for SVector<f64, M> { + std::ops::Sub<Output = Self>
fn to_vector(&self) -> SVector<f64, M> { + HasVector<D>
*self {
fn to_vector(&self) -> SVector<f64, D> {
*self.vector()
} }
} }
impl<T, const D: usize> VectorValue<D> for T where
T: Copy
+ From<SVector<f64, D>>
+ Into<SVector<f64, D>>
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ HasVector<D>
{
}
#[macro_export]
macro_rules! impl_has_vector {
($type:ty, $dim:expr) => {
impl $crate::traits::HasVector<$dim> for $type {
fn vector(&self) -> &nalgebra::SVector<f64, $dim> {
&self.vector
}
fn vector_mut(&mut self) -> &mut nalgebra::SVector<f64, $dim> {
&mut self.vector
}
}
impl From<nalgebra::SVector<f64, $dim>> for $type {
fn from(vector: nalgebra::SVector<f64, $dim>) -> Self {
Self { vector }
}
}
impl From<$type> for nalgebra::SVector<f64, $dim> {
fn from(value: $type) -> Self {
value.vector
}
}
impl std::ops::Add for $type {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
vector: self.vector + rhs.vector,
}
}
}
impl std::ops::Sub for $type {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
vector: self.vector - rhs.vector,
}
}
}
};
}
pub trait Saturate<S> {
fn saturate(&self, system: &S) -> Self;
}

View file

@ -0,0 +1,26 @@
pub use uom::si::f64::*;
pub use uom::si::acceleration::meter_per_second_squared;
pub use uom::si::angle::radian;
pub use uom::si::angular_acceleration::radian_per_second_squared;
pub use uom::si::angular_velocity::radian_per_second;
pub use uom::si::electric_current::ampere;
pub use uom::si::electric_potential::volt;
pub use uom::si::electrical_resistance::ohm;
pub use uom::si::inductance::henry;
pub use uom::si::length::meter;
pub use uom::si::mass::kilogram;
pub use uom::si::moment_of_inertia::kilogram_square_meter;
pub use uom::si::time::millisecond;
pub use uom::si::time::second;
pub use uom::si::torque::newton_meter;
pub use uom::si::velocity::meter_per_second;
use std::ops::Div;
pub type Voltage = ElectricPotential;
pub type TorqueConstant = <Torque as Div<ElectricCurrent>>::Output;
pub type BackEmfConstant = <ElectricPotential as Div<AngularVelocity>>::Output;
pub type FrictionCoefficient = <Torque as Div<AngularVelocity>>::Output;
pub type CurrentRate = <ElectricCurrent as Div<Time>>::Output;