use scirs2_core::ndarray::Array1;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum DerivativeOrder {
First,
Second,
Third,
Custom(usize),
}
impl DerivativeOrder {
pub fn order(&self) -> usize {
match self {
Self::First => 1,
Self::Second => 2,
Self::Third => 3,
Self::Custom(n) => *n,
}
}
}
#[derive(Debug, Clone)]
pub struct DerivativeConstraint {
name: String,
order: DerivativeOrder,
dt: f32,
max_magnitude: f32,
history: VecDeque<(f32, Array1<f32>)>, max_history: usize,
}
impl DerivativeConstraint {
pub fn new(
name: impl Into<String>,
order: DerivativeOrder,
dt: f32,
max_magnitude: f32,
) -> Self {
let max_history = order.order() + 2;
Self {
name: name.into(),
order,
dt,
max_magnitude,
history: VecDeque::new(),
max_history,
}
}
pub fn observe(&mut self, time: f32, value: Array1<f32>) {
self.history.push_back((time, value));
if self.history.len() > self.max_history {
self.history.pop_front();
}
}
fn compute_derivative(&self) -> Option<Array1<f32>> {
let n = self.order.order();
if self.history.len() < n + 1 {
return None; }
let values: Vec<_> = self.history.iter().rev().take(n + 1).collect();
match n {
1 => {
let (t0, v0) = values[0];
let (t1, v1) = values[1];
let dt = t0 - t1;
Some((v0 - v1) / dt)
}
2 => {
let (t0, v0) = values[0];
let (t1, v1) = values[1];
let (t2, v2) = values[2];
let dt = (t0 - t1 + t1 - t2) / 2.0;
Some(((v0 - v1) - (v1 - v2)) / (dt * dt))
}
3 => {
if values.len() < 4 {
return None;
}
let (_, v0) = values[0];
let (_, v1) = values[1];
let (_, v2) = values[2];
let (_, v3) = values[3];
Some((v0 - &(v1 * 3.0) + &(v2 * 3.0) - v3) / (self.dt * self.dt * self.dt))
}
_ => None, }
}
pub fn check(&self) -> bool {
if let Some(derivative) = self.compute_derivative() {
let magnitude = derivative.iter().map(|x| x * x).sum::<f32>().sqrt();
magnitude <= self.max_magnitude
} else {
true }
}
pub fn violation(&self) -> f32 {
if let Some(derivative) = self.compute_derivative() {
let magnitude = derivative.iter().map(|x| x * x).sum::<f32>().sqrt();
(magnitude - self.max_magnitude).max(0.0)
} else {
0.0
}
}
pub fn get_derivative(&self) -> Option<Array1<f32>> {
self.compute_derivative()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn reset(&mut self) {
self.history.clear();
}
}
#[derive(Debug, Clone)]
pub struct IntegralConstraint {
name: String,
window_duration: f32,
max_integral: f32,
min_integral: f32,
history: VecDeque<(f32, Array1<f32>)>, }
impl IntegralConstraint {
pub fn new(
name: impl Into<String>,
window_duration: f32,
min_integral: f32,
max_integral: f32,
) -> Self {
Self {
name: name.into(),
window_duration,
max_integral,
min_integral,
history: VecDeque::new(),
}
}
pub fn observe(&mut self, time: f32, value: Array1<f32>) {
self.history.push_back((time, value));
let cutoff_time = time - self.window_duration;
while let Some((t, _)) = self.history.front() {
if *t < cutoff_time {
self.history.pop_front();
} else {
break;
}
}
}
fn compute_integral(&self) -> Option<Array1<f32>> {
if self.history.len() < 2 {
return None;
}
let dim = self.history[0].1.len();
let mut integral = Array1::zeros(dim);
for i in 0..self.history.len() - 1 {
let (t1, v1) = &self.history[i];
let (t2, v2) = &self.history[i + 1];
let dt = t2 - t1;
integral += &((v1 + v2) * (dt / 2.0));
}
Some(integral)
}
pub fn check(&self) -> bool {
if let Some(integral) = self.compute_integral() {
integral
.iter()
.all(|&x| x >= self.min_integral && x <= self.max_integral)
} else {
true
}
}
pub fn violation(&self) -> f32 {
if let Some(integral) = self.compute_integral() {
let mut total_violation = 0.0;
for &x in integral.iter() {
if x < self.min_integral {
total_violation += self.min_integral - x;
} else if x > self.max_integral {
total_violation += x - self.max_integral;
}
}
total_violation
} else {
0.0
}
}
pub fn get_integral(&self) -> Option<Array1<f32>> {
self.compute_integral()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn reset(&mut self) {
self.history.clear();
}
}
#[derive(Debug, Clone)]
pub struct DifferentialAlgebraicConstraint {
name: String,
constraint_fn: fn(&Array1<f32>, &Array1<f32>, f32) -> Array1<f32>,
tolerance: f32,
history: VecDeque<(f32, Array1<f32>)>,
#[allow(dead_code)]
dt: f32,
}
impl DifferentialAlgebraicConstraint {
pub fn new(
name: impl Into<String>,
constraint_fn: fn(&Array1<f32>, &Array1<f32>, f32) -> Array1<f32>,
tolerance: f32,
dt: f32,
) -> Self {
Self {
name: name.into(),
constraint_fn,
tolerance,
history: VecDeque::new(),
dt,
}
}
pub fn observe(&mut self, time: f32, value: Array1<f32>) {
self.history.push_back((time, value));
if self.history.len() > 2 {
self.history.pop_front();
}
}
fn compute_derivative(&self) -> Option<Array1<f32>> {
if self.history.len() < 2 {
return None;
}
let (t1, v1) = &self.history[0];
let (t2, v2) = &self.history[1];
let dt = t2 - t1;
Some((v2 - v1) / dt)
}
pub fn check(&self) -> bool {
if self.history.is_empty() {
return true;
}
let (t, x) = &self.history[self.history.len() - 1];
if let Some(dx_dt) = self.compute_derivative() {
let residual = (self.constraint_fn)(x, &dx_dt, *t);
let residual_norm = residual.iter().map(|r| r * r).sum::<f32>().sqrt();
residual_norm <= self.tolerance
} else {
true
}
}
pub fn violation(&self) -> f32 {
if self.history.is_empty() {
return 0.0;
}
let (t, x) = &self.history[self.history.len() - 1];
if let Some(dx_dt) = self.compute_derivative() {
let residual = (self.constraint_fn)(x, &dx_dt, *t);
let residual_norm = residual.iter().map(|r| r * r).sum::<f32>().sqrt();
(residual_norm - self.tolerance).max(0.0)
} else {
0.0
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn reset(&mut self) {
self.history.clear();
}
}
#[derive(Debug, Clone)]
pub struct PathIntegralConstraint {
name: String,
cost_fn: fn(&Array1<f32>, &Array1<f32>, f32) -> f32,
max_cost: f32,
trajectory: VecDeque<(f32, Array1<f32>)>,
}
impl PathIntegralConstraint {
pub fn new(
name: impl Into<String>,
cost_fn: fn(&Array1<f32>, &Array1<f32>, f32) -> f32,
max_cost: f32,
) -> Self {
Self {
name: name.into(),
cost_fn,
max_cost,
trajectory: VecDeque::new(),
}
}
pub fn observe(&mut self, time: f32, state: Array1<f32>) {
self.trajectory.push_back((time, state));
}
fn compute_path_integral(&self) -> f32 {
if self.trajectory.len() < 2 {
return 0.0;
}
let mut total_cost = 0.0;
for i in 0..self.trajectory.len() - 1 {
let (t1, x1) = &self.trajectory[i];
let (t2, x2) = &self.trajectory[i + 1];
let dt = t2 - t1;
let dx_dt = (x2 - x1) / dt;
let t_mid = (t1 + t2) / 2.0;
let x_mid = (x1 + x2) / 2.0;
let cost = (self.cost_fn)(&x_mid, &dx_dt, t_mid);
total_cost += cost * dt;
}
total_cost
}
pub fn check(&self) -> bool {
let cost = self.compute_path_integral();
cost <= self.max_cost
}
pub fn violation(&self) -> f32 {
let cost = self.compute_path_integral();
(cost - self.max_cost).max(0.0)
}
pub fn get_path_cost(&self) -> f32 {
self.compute_path_integral()
}
pub fn name(&self) -> &str {
&self.name
}
pub fn reset(&mut self) {
self.trajectory.clear();
}
pub fn trajectory_length(&self) -> usize {
self.trajectory.len()
}
}
#[derive(Debug, Clone)]
pub struct DifferentialConstraintSet {
derivative_constraints: Vec<DerivativeConstraint>,
integral_constraints: Vec<IntegralConstraint>,
dae_constraints: Vec<DifferentialAlgebraicConstraint>,
path_integral_constraints: Vec<PathIntegralConstraint>,
}
impl DifferentialConstraintSet {
pub fn new() -> Self {
Self {
derivative_constraints: Vec::new(),
integral_constraints: Vec::new(),
dae_constraints: Vec::new(),
path_integral_constraints: Vec::new(),
}
}
pub fn add_derivative(&mut self, constraint: DerivativeConstraint) {
self.derivative_constraints.push(constraint);
}
pub fn add_integral(&mut self, constraint: IntegralConstraint) {
self.integral_constraints.push(constraint);
}
pub fn add_dae(&mut self, constraint: DifferentialAlgebraicConstraint) {
self.dae_constraints.push(constraint);
}
pub fn add_path_integral(&mut self, constraint: PathIntegralConstraint) {
self.path_integral_constraints.push(constraint);
}
pub fn observe(&mut self, time: f32, state: Array1<f32>) {
for constraint in &mut self.derivative_constraints {
constraint.observe(time, state.clone());
}
for constraint in &mut self.integral_constraints {
constraint.observe(time, state.clone());
}
for constraint in &mut self.dae_constraints {
constraint.observe(time, state.clone());
}
for constraint in &mut self.path_integral_constraints {
constraint.observe(time, state.clone());
}
}
pub fn check_all(&self) -> bool {
self.derivative_constraints.iter().all(|c| c.check())
&& self.integral_constraints.iter().all(|c| c.check())
&& self.dae_constraints.iter().all(|c| c.check())
&& self.path_integral_constraints.iter().all(|c| c.check())
}
pub fn total_violation(&self) -> f32 {
let mut total = 0.0;
for c in &self.derivative_constraints {
total += c.violation();
}
for c in &self.integral_constraints {
total += c.violation();
}
for c in &self.dae_constraints {
total += c.violation();
}
for c in &self.path_integral_constraints {
total += c.violation();
}
total
}
pub fn reset(&mut self) {
for c in &mut self.derivative_constraints {
c.reset();
}
for c in &mut self.integral_constraints {
c.reset();
}
for c in &mut self.dae_constraints {
c.reset();
}
for c in &mut self.path_integral_constraints {
c.reset();
}
}
pub fn num_constraints(&self) -> usize {
self.derivative_constraints.len()
+ self.integral_constraints.len()
+ self.dae_constraints.len()
+ self.path_integral_constraints.len()
}
}
impl Default for DifferentialConstraintSet {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_derivative_constraint() {
let mut constraint =
DerivativeConstraint::new("velocity_limit", DerivativeOrder::First, 0.1, 10.0);
constraint.observe(0.0, Array1::from_vec(vec![0.0]));
constraint.observe(0.1, Array1::from_vec(vec![0.5]));
assert!(constraint.check());
constraint.observe(0.2, Array1::from_vec(vec![2.0])); assert!(!constraint.check()); }
#[test]
fn test_integral_constraint() {
let mut constraint = IntegralConstraint::new("energy_limit", 1.0, 0.0, 100.0);
constraint.observe(0.0, Array1::from_vec(vec![10.0]));
constraint.observe(0.5, Array1::from_vec(vec![20.0]));
constraint.observe(1.0, Array1::from_vec(vec![10.0]));
assert!(constraint.check());
assert!(constraint.get_integral().is_some());
}
#[test]
fn test_dae_constraint() {
fn dae_fn(x: &Array1<f32>, dx_dt: &Array1<f32>, _t: f32) -> Array1<f32> {
x + dx_dt
}
let mut constraint = DifferentialAlgebraicConstraint::new("simple_dae", dae_fn, 1.0, 0.1);
constraint.observe(0.0, Array1::from_vec(vec![1.0]));
constraint.observe(0.1, Array1::from_vec(vec![0.9]));
assert!(constraint.check());
}
#[test]
fn test_path_integral_constraint() {
fn cost_fn(_x: &Array1<f32>, dx_dt: &Array1<f32>, _t: f32) -> f32 {
dx_dt.iter().map(|v| v * v).sum()
}
let mut constraint = PathIntegralConstraint::new("min_energy", cost_fn, 100.0);
constraint.observe(0.0, Array1::from_vec(vec![0.0]));
constraint.observe(0.1, Array1::from_vec(vec![1.0]));
constraint.observe(0.2, Array1::from_vec(vec![2.0]));
assert!(constraint.check());
assert_eq!(constraint.trajectory_length(), 3);
}
#[test]
fn test_differential_constraint_set() {
let mut set = DifferentialConstraintSet::new();
set.add_derivative(DerivativeConstraint::new(
"velocity",
DerivativeOrder::First,
0.1,
10.0,
));
set.observe(0.0, Array1::from_vec(vec![0.0]));
set.observe(0.1, Array1::from_vec(vec![0.5]));
assert!(set.check_all());
assert_eq!(set.num_constraints(), 1);
}
}