use crate::core::scalar::ControlScalar;
pub use crate::allocation::weighted_pseudo::AllocationError;
#[derive(Debug, Clone, Copy)]
pub struct AllocationTask<S, const M: usize> {
pub b_row: [S; M],
pub v_des: S,
}
impl<S: ControlScalar, const M: usize> AllocationTask<S, M> {
pub fn new(b_row: [S; M], v_des: S) -> Self {
Self { b_row, v_des }
}
}
pub struct PriorityAllocator<S, const M: usize, const TASKS: usize> {
u_min: [S; M],
u_max: [S; M],
weights: [S; M],
singular_tol: S,
}
impl<S: ControlScalar, const M: usize, const TASKS: usize> PriorityAllocator<S, M, TASKS> {
pub fn new(u_min: [S; M], u_max: [S; M]) -> Result<Self, AllocationError> {
for i in 0..M {
if u_min[i] > u_max[i] {
return Err(AllocationError::InvalidBounds);
}
}
Ok(Self {
u_min,
u_max,
weights: [S::ONE; M],
singular_tol: S::from_f64(1e-10),
})
}
pub fn set_weights(&mut self, weights: [S; M]) -> Result<(), AllocationError> {
for &wi in weights.iter() {
if wi <= S::ZERO {
return Err(AllocationError::InvalidWeight);
}
}
self.weights = weights;
Ok(())
}
pub fn allocate(
&self,
tasks: &[AllocationTask<S, M>; TASKS],
) -> Result<[S; M], AllocationError> {
let mut u = [S::ZERO; M];
for (j, u_j) in u.iter_mut().enumerate() {
*u_j = u_j.clamp_val(self.u_min[j], self.u_max[j]);
}
let mut delta_min = [S::ZERO; M];
let mut delta_max = [S::ZERO; M];
for j in 0..M {
delta_min[j] = self.u_min[j] - u[j];
delta_max[j] = self.u_max[j] - u[j];
}
for task in tasks.iter() {
let bu: S = task
.b_row
.iter()
.zip(u.iter())
.fold(S::ZERO, |acc, (&bij, &uj)| acc + bij * uj);
let residual = task.v_des - bu;
let b_winv_b: S = task
.b_row
.iter()
.zip(self.weights.iter())
.fold(S::ZERO, |acc, (&bij, &wj)| acc + bij * bij / wj);
if b_winv_b < self.singular_tol {
continue;
}
let alpha = residual / b_winv_b;
let mut delta_u = [S::ZERO; M];
for (j, (du, (&bij, &wj))) in delta_u
.iter_mut()
.zip(task.b_row.iter().zip(self.weights.iter()))
.enumerate()
{
let _ = j; *du = alpha * bij / wj;
}
let mut scale = S::ONE;
for (j, &du) in delta_u.iter().enumerate() {
if du > S::ZERO && du > delta_max[j] && delta_max[j] >= S::ZERO {
let s = delta_max[j] / du;
if s < scale {
scale = s;
}
} else if du < S::ZERO && du < delta_min[j] && delta_min[j] <= S::ZERO {
let s = delta_min[j] / du;
if s < scale {
scale = s;
}
}
}
for j in 0..M {
let applied = scale * delta_u[j];
u[j] += applied;
delta_min[j] -= applied;
delta_max[j] -= applied;
}
}
for (j, u_j) in u.iter_mut().enumerate() {
*u_j = u_j.clamp_val(self.u_min[j], self.u_max[j]);
}
Ok(u)
}
pub fn task_residuals(&self, u: &[S; M], tasks: &[AllocationTask<S, M>; TASKS]) -> [S; TASKS] {
let mut residuals = [S::ZERO; TASKS];
for (i, task) in tasks.iter().enumerate() {
let bu: S = task
.b_row
.iter()
.zip(u.iter())
.fold(S::ZERO, |acc, (&bij, &uj)| acc + bij * uj);
let diff = bu - task.v_des;
residuals[i] = if diff < S::ZERO { -diff } else { diff };
}
residuals
}
pub fn is_feasible(&self, u: &[S; M]) -> bool {
u.iter().enumerate().all(|(j, &uj)| {
uj >= self.u_min[j] - S::from_f64(1e-9) && uj <= self.u_max[j] + S::from_f64(1e-9)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_single_task_pseudo_inverse() {
let u_min = [0.0f64; 3];
let u_max = [10.0f64; 3];
let alloc = PriorityAllocator::<f64, 3, 1>::new(u_min, u_max).expect("ok");
let tasks = [AllocationTask::new([1.0f64, 0.0, 1.0], 2.0)];
let u = alloc.allocate(&tasks).expect("ok");
let v = u[0] + u[2];
assert!(approx(v, 2.0, 1e-8), "v={}", v);
assert!(approx(u[1], 0.0, 1e-8));
}
#[test]
fn test_two_orthogonal_tasks() {
let u_min = [-10.0f64; 2];
let u_max = [10.0f64; 2];
let alloc = PriorityAllocator::<f64, 2, 2>::new(u_min, u_max).expect("ok");
let tasks = [
AllocationTask::new([1.0f64, 0.0], 3.0),
AllocationTask::new([0.0f64, 1.0], 2.0),
];
let u = alloc.allocate(&tasks).expect("ok");
assert!(approx(u[0], 3.0, 1e-6), "u[0]={}", u[0]);
assert!(approx(u[1], 2.0, 1e-6), "u[1]={}", u[1]);
}
#[test]
fn test_priority_ordering() {
let u_min = [0.0f64; 2];
let u_max = [5.0f64; 2];
let alloc = PriorityAllocator::<f64, 2, 2>::new(u_min, u_max).expect("ok");
let tasks = [
AllocationTask::new([1.0f64, 1.0], 4.0),
AllocationTask::new([1.0f64, -1.0], 0.0),
];
let u = alloc.allocate(&tasks).expect("ok");
let v0 = u[0] + u[1];
assert!(approx(v0, 4.0, 1e-5), "high-priority residual: v0={}", v0);
assert!(alloc.is_feasible(&u), "u={:?} out of bounds", u);
}
#[test]
fn test_saturation() {
let u_min = [0.0f64];
let u_max = [5.0f64];
let alloc = PriorityAllocator::<f64, 1, 1>::new(u_min, u_max).expect("ok");
let tasks = [AllocationTask::new([1.0f64], 10.0)];
let u = alloc.allocate(&tasks).expect("ok");
assert!(u[0] <= 5.0 + 1e-9);
assert!(u[0] >= -1e-9);
}
#[test]
fn test_task_residuals() {
let u_min = [-10.0f64; 2];
let u_max = [10.0f64; 2];
let alloc = PriorityAllocator::<f64, 2, 2>::new(u_min, u_max).expect("ok");
let tasks = [
AllocationTask::new([1.0f64, 0.0], 3.0),
AllocationTask::new([0.0f64, 1.0], 2.0),
];
let u = alloc.allocate(&tasks).expect("ok");
let residuals = alloc.task_residuals(&u, &tasks);
assert!(residuals[0] < 1e-6, "task 0 residual: {}", residuals[0]);
assert!(residuals[1] < 1e-6, "task 1 residual: {}", residuals[1]);
}
#[test]
fn test_is_feasible() {
let u_min = [0.0f64; 2];
let u_max = [5.0f64; 2];
let alloc = PriorityAllocator::<f64, 2, 2>::new(u_min, u_max).expect("ok");
assert!(alloc.is_feasible(&[2.5f64, 3.0]));
assert!(!alloc.is_feasible(&[6.0f64, 3.0]));
assert!(!alloc.is_feasible(&[-1.0f64, 3.0]));
}
}