#[derive(Debug, Clone, PartialEq)]
pub struct Bounds {
n: usize,
m: Vec<f64>,
}
impl Bounds {
#[must_use]
pub fn new(n: usize, lo: f64, hi: f64) -> Self {
let mut m = vec![0.0; n * n];
for i in 0..n {
for j in 0..n {
if i < j {
m[i * n + j] = hi;
} else if i > j {
m[i * n + j] = lo;
}
}
}
Self { n, m }
}
#[must_use]
pub fn len(&self) -> usize {
self.n
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.n == 0
}
#[must_use]
pub fn upper(&self, i: usize, j: usize) -> f64 {
let (a, b) = if i < j { (i, j) } else { (j, i) };
self.m[a * self.n + b]
}
#[must_use]
pub fn lower(&self, i: usize, j: usize) -> f64 {
let (a, b) = if i < j { (i, j) } else { (j, i) };
self.m[b * self.n + a]
}
pub fn set_upper(&mut self, i: usize, j: usize, v: f64) {
let (a, b) = if i < j { (i, j) } else { (j, i) };
self.m[a * self.n + b] = v;
}
pub fn set_lower(&mut self, i: usize, j: usize, v: f64) {
let (a, b) = if i < j { (i, j) } else { (j, i) };
self.m[b * self.n + a] = v;
}
#[must_use]
pub fn from_row_major(n: usize, m: Vec<f64>) -> Option<Self> {
(m.len() == n * n).then_some(Self { n, m })
}
#[must_use]
pub fn as_row_major(&self) -> &[f64] {
&self.m
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SmoothError {
Infeasible {
pair: (usize, usize),
},
}
pub fn triangle_smooth(b: &mut Bounds) -> Result<(), SmoothError> {
let n = b.len();
for k in 0..n {
for i in 0..n {
if i == k {
continue;
}
let u_ik = b.upper(i, k);
let l_ik = b.lower(i, k);
for j in (i + 1)..n {
if j == k {
continue;
}
let u_kj = b.upper(k, j);
if b.upper(i, j) > u_ik + u_kj {
b.set_upper(i, j, u_ik + u_kj);
}
let cand = (l_ik - u_kj).max(b.lower(j, k) - u_ik);
if b.lower(i, j) < cand {
b.set_lower(i, j, cand);
}
if b.lower(i, j) > b.upper(i, j) {
return Err(SmoothError::Infeasible { pair: (i, j) });
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_upper_bound_is_squeezed_by_the_path_through_the_third_atom() {
let mut b = Bounds::new(3, 0.0, 100.0);
b.set_upper(0, 1, 2.0);
b.set_upper(1, 2, 2.0);
b.set_upper(0, 2, 10.0); triangle_smooth(&mut b).expect("这组约束不矛盾");
assert!(
(b.upper(0, 2) - 4.0).abs() < 1e-12,
"A–C 上限该被压到 4,实得 {}",
b.upper(0, 2)
);
}
#[test]
fn a_lower_bound_is_pushed_up_by_the_triangle_inequality() {
let mut b = Bounds::new(3, 0.0, 100.0);
b.set_lower(0, 2, 5.0);
b.set_upper(1, 2, 2.0);
triangle_smooth(&mut b).expect("这组约束不矛盾");
assert!(
b.lower(0, 1) >= 3.0 - 1e-12,
"A–B 下限该被推到 3,实得 {}",
b.lower(0, 1)
);
}
#[test]
fn an_impossible_constraint_set_names_a_genuinely_broken_pair() {
let mut b = Bounds::new(3, 0.0, 100.0);
b.set_lower(0, 1, 10.0);
b.set_upper(0, 2, 1.0);
b.set_upper(1, 2, 1.0);
match triangle_smooth(&mut b) {
Err(SmoothError::Infeasible { pair: (i, j) }) => {
assert!(
b.lower(i, j) > b.upper(i, j),
"报的是 ({i},{j}),可它的下限 {} 并没有超过上限 {} —— 见证是假的",
b.lower(i, j),
b.upper(i, j)
);
}
Ok(()) => panic!("这组约束是矛盾的,该报不可行"),
}
}
#[test]
fn the_smoothed_upper_bounds_are_themselves_a_metric() {
let n = 6;
let mut b = Bounds::new(n, 0.5, 50.0);
for i in 0..(n - 1) {
b.set_upper(i, i + 1, 1.5);
b.set_lower(i, i + 1, 1.4);
}
b.set_upper(0, 5, 40.0);
b.set_upper(1, 4, 30.0);
b.set_lower(0, 3, 2.0);
triangle_smooth(&mut b).expect("该可行");
let mut checked = 0;
for i in 0..n {
for j in 0..n {
for k in 0..n {
if i == j || j == k || i == k {
continue;
}
assert!(
b.upper(i, j) <= b.upper(i, k) + b.upper(k, j) + 1e-9,
"三角不等式破了:U({i},{j})={} > U({i},{k})+U({k},{j})={}",
b.upper(i, j),
b.upper(i, k) + b.upper(k, j)
);
checked += 1;
}
}
}
assert!(checked >= 100, "只验了 {checked} 个三元组");
}
#[test]
fn smoothing_twice_changes_nothing() {
let n = 7;
let mut b = Bounds::new(n, 0.8, 60.0);
for i in 0..(n - 1) {
b.set_upper(i, i + 1, 1.52);
b.set_lower(i, i + 1, 1.50);
}
for i in 0..(n - 2) {
b.set_upper(i, i + 2, 2.6);
b.set_lower(i, i + 2, 2.4);
}
triangle_smooth(&mut b).expect("该可行");
let once = b.clone();
triangle_smooth(&mut b).expect("再来一次也该可行");
assert_eq!(
b, once,
"第二趟光滑化改动了矩阵 —— 单趟不是不动点,那结果就依赖遍历次序"
);
}
#[test]
fn tiny_inputs_are_fine() {
for n in 0..3 {
let mut b = Bounds::new(n, 1.0, 5.0);
triangle_smooth(&mut b).expect("小输入该直接过");
assert_eq!(b.len(), n);
}
assert!(Bounds::new(0, 1.0, 5.0).is_empty());
}
}