use impulse_response::SparseVector;
use rand::prelude::*;
const NUM_POINTS: usize = if cfg!(debug_assertions) { 100 } else { 1000 };
const NUM_EDGES: usize = 3;
const ACCURACY: f64 = 1e-6;
const DELTA_TIME: f64 = 1e-3;
struct Point {
capacity: f64,
adjacent: [usize; NUM_EDGES],
resistances: [f64; NUM_EDGES],
}
impl Point {
fn new() -> Self {
let mut adjacent = [0; NUM_EDGES];
let mut resistances = [0.0; NUM_EDGES];
for e in 0..NUM_EDGES {
adjacent[e] = random::<usize>() % NUM_POINTS;
resistances[e] = random::<f64>() * 1e9 + 1e6;
}
Point {
capacity: random::<f64>() * 1e5 + 1e2,
adjacent,
resistances,
}
}
fn random_state() -> f64 {
1.0 / (random::<f64>() + 1e-6)
}
fn derivative(state: &SparseVector, deriv: &mut SparseVector, points: &[Point]) {
for (src_idx, &src_state) in state.iter() {
let src = &points[*src_idx];
for (dst_idx, resist) in src.adjacent.iter().zip(&src.resistances) {
let dst_state = state.get(dst_idx).unwrap_or(&0.0);
let flow = (src_state - dst_state) / resist;
let flow = flow.max(0.0); if flow >= f64::EPSILON.powi(2) {
deriv.insert(*src_idx, deriv.get(src_idx).unwrap_or(&0.0) - flow);
deriv.insert(*dst_idx, deriv.get(dst_idx).unwrap_or(&0.0) + flow);
}
}
}
for (idx, value) in deriv.iter_mut() {
*value /= points[*idx].capacity;
}
}
}
#[test]
fn conservation() {
let mut points = Vec::with_capacity(NUM_POINTS);
let mut m = impulse_response::Model::new(
DELTA_TIME,
ACCURACY / 17.0,
DELTA_TIME / 1000.0,
f64::EPSILON,
);
for i in 0..NUM_POINTS {
points.push(Point::new());
m.touch(i);
}
let mut state = Vec::with_capacity(NUM_POINTS);
for _ in 0..NUM_POINTS {
state.push(Point::random_state());
}
let mut next_state = vec![0.0; NUM_POINTS];
let mut crank_nicholson = SparseVector::with_capacity(NUM_POINTS);
for i in 0..NUM_POINTS {
crank_nicholson.insert(i, state[i]);
}
let mut initial_quantity: f64 = state.iter().zip(&points).map(|(s, p)| s * p.capacity).sum();
for i in 0..NUM_POINTS {
let replace: usize = random::<usize>() % NUM_POINTS;
m.delete(replace);
initial_quantity -= state[replace] * points[replace].capacity;
points[replace] = Point::new();
state[replace] = Point::random_state() * 1000.0;
crank_nicholson.insert(replace, state[replace]);
initial_quantity += state[replace] * points[replace].capacity;
m.touch(replace);
let mut derivative =
|s: &SparseVector, d: &mut SparseVector| Point::derivative(s, d, &points);
m.advance(&state, &mut next_state, derivative);
std::mem::swap(&mut state, &mut next_state);
if i == 0 {
dbg!(m.density());
}
if i <= 17 {
crank_nicholson = m.integrate(crank_nicholson, &mut derivative); let crank_nicholson_error = crank_nicholson
.iter()
.map(|(&idx, value)| (state[idx], value))
.map(|(a, b)| 2.0 * f64::abs(a - b) / (a + b))
.fold(-f64::INFINITY, f64::max);
dbg!(crank_nicholson_error);
assert!(crank_nicholson_error <= ACCURACY);
}
}
let mut steady_state = false;
for _ in 0..1_000_000 {
m.advance(
&state,
&mut next_state,
|s: &SparseVector, d: &mut SparseVector| Point::derivative(s, d, &points),
);
std::mem::swap(&mut state, &mut next_state);
let max_pct_diff: f64 = state
.iter()
.zip(&next_state)
.map(|(a, b)| (a - b).abs() / a)
.fold(-1.0 / 0.0, f64::max);
dbg!(max_pct_diff);
if max_pct_diff <= ACCURACY {
steady_state = true;
break;
}
}
assert!(steady_state);
let final_quantity__: f64 = state.iter().zip(&points).map(|(s, p)| s * p.capacity).sum();
let abs_diff = (initial_quantity - final_quantity__).abs();
dbg!(initial_quantity, final_quantity__);
assert!(abs_diff / initial_quantity <= ACCURACY);
}