app
Adom Fields
Public Made by Adomby adom
Adom Fields: where the current and the heat go on your board.
main
John Lauer
0.10.0: lean by default; optional modules with board-specific hints; grouped findings; lean fields.json + detail.json;…
4bbb86d
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
//! A multi-layer grid conductance system solved by preconditioned conjugate gradients.
//! Unknowns: one value per cell per layer. Edges: right and down neighbours on a layer (gx, gy),
//! the cell below on the next layer (gz), and a leak from every cell to ground.
#[derive(Clone)]
pub struct System { pub nl: usize, pub h: usize, pub w: usize, pub gx: Vec<Vec<f64>>, pub gy: Vec<Vec<f64>>, pub gz: Vec<Vec<f64>>, pub leak: Vec<Vec<f64>>, pub extra_leak: Vec<f64>, pub edges: Vec<(usize, usize, f64)> }
impl System {
pub fn new(nl: usize, h: usize, w: usize) -> System {
System { nl, h, w, extra_leak: Vec::new(), edges: Vec::new(), gx: vec![vec![0.0; h * (w - 1)]; nl], gy: vec![vec![0.0; (h - 1) * w]; nl], gz: vec![vec![0.0; h * w]; nl.saturating_sub(1)], leak: vec![vec![0.0; h * w]; nl] }
}
pub fn n(&self) -> usize { self.nl * self.h * self.w + self.extra_leak.len() }
pub fn add_node(&mut self, leak: f64) -> usize { let i = self.n(); self.extra_leak.push(leak); i }
fn matvec(&self, x: &[f64], y: &mut [f64]) {
let (h, w) = (self.h, self.w); let n = h * w;
for k in 0..self.nl {
let (xo, yo) = (&x[k * n..(k + 1) * n], &mut y[k * n..(k + 1) * n]);
for i in 0..n { yo[i] = self.leak[k][i] * xo[i]; }
let gx = &self.gx[k];
for r in 0..h { for c in 0..w - 1 { let g = gx[r * (w - 1) + c]; if g != 0.0 { let (a, b) = (r * w + c, r * w + c + 1); let d = g * (xo[a] - xo[b]); yo[a] += d; yo[b] -= d; } } }
let gy = &self.gy[k];
for r in 0..h - 1 { for c in 0..w { let g = gy[r * w + c]; if g != 0.0 { let (a, b) = (r * w + c, (r + 1) * w + c); let d = g * (xo[a] - xo[b]); yo[a] += d; yo[b] -= d; } } }
}
for k in 0..self.nl.saturating_sub(1) {
let gz = &self.gz[k];
for i in 0..n { let g = gz[i]; if g != 0.0 { let (a, b) = (k * n + i, (k + 1) * n + i); let d = g * (x[a] - x[b]); y[a] += d; y[b] -= d; } }
}
for (i, g) in self.extra_leak.iter().enumerate() { let a = self.nl * n + i; y[a] = g * x[a]; }
for &(a,b,g) in &self.edges { let q = g * (x[a]-x[b]); y[a] += q; y[b] -= q; }
}
fn diagonal(&self) -> Vec<f64> {
let (h, w) = (self.h, self.w); let n = h * w;
let mut d = vec![0.0; self.n()];
for k in 0..self.nl {
for i in 0..n { d[k * n + i] = self.leak[k][i]; }
for r in 0..h { for c in 0..w - 1 { let g = self.gx[k][r * (w - 1) + c]; d[k * n + r * w + c] += g; d[k * n + r * w + c + 1] += g; } }
for r in 0..h - 1 { for c in 0..w { let g = self.gy[k][r * w + c]; d[k * n + r * w + c] += g; d[k * n + (r + 1) * w + c] += g; } }
}
for k in 0..self.nl.saturating_sub(1) { for i in 0..n { let g = self.gz[k][i]; d[k * n + i] += g; d[(k + 1) * n + i] += g; } }
for (i,g) in self.extra_leak.iter().enumerate() { d[self.nl*n+i] += g; }
for &(a,b,g) in &self.edges { d[a] += g; d[b] += g; }
d
}
/// Solve A x = b to a TRUE relative residual of `tol`: CG, then iterative refinement (A dx = b - Ax) while the
/// recomputed residual is over tol, because CG's own recursive residual drifts on badly conditioned nets (issue 11).
/// Returns (x, iterations, true residual).
pub fn solve(&self, b: &[f64], tol: f64, max_iter: usize) -> (Vec<f64>, usize, f64) {
let (mut x, mut its, _) = self.cg(b, tol, max_iter);
let bnorm = b.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-30);
let mut ax = vec![0.0; x.len()];
let mut true_res = 0.0;
for _ in 0..6 {
self.matvec(&x, &mut ax);
let r: Vec<f64> = b.iter().zip(&ax).map(|(bi, ai)| bi - ai).collect();
true_res = r.iter().map(|v| v * v).sum::<f64>().sqrt() / bnorm;
if true_res < tol || its >= max_iter { break; }
let (dx, it2, _) = self.cg(&r, (tol * bnorm / r.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-300)).min(0.5), max_iter - its);
for (xi, d) in x.iter_mut().zip(&dx) { *xi += d; }
its += it2;
}
(x, its, true_res)
}
/// The same solve started from a guess (the still-air temperatures for the moving-air pass): only the correction
/// A·dx = b − A·x0 is solved, to the same true relative residual against b. Returns (x, iterations, true residual).
pub fn solve_from(&self, b: &[f64], x0: &[f64], tol: f64, max_iter: usize) -> (Vec<f64>, usize, f64) {
if x0.len() != b.len() { return self.solve(b, tol, max_iter); }
let bnorm = b.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-30);
let mut ax = vec![0.0; b.len()]; self.matvec(x0, &mut ax);
let r: Vec<f64> = b.iter().zip(&ax).map(|(bi, ai)| bi - ai).collect();
let rn = r.iter().map(|v| v * v).sum::<f64>().sqrt();
if rn / bnorm < tol { return (x0.to_vec(), 0, rn / bnorm); }
let (dx, its, _) = self.solve(&r, (tol * bnorm / rn).min(0.5), max_iter);
let x: Vec<f64> = x0.iter().zip(&dx).map(|(a, d)| a + d).collect();
self.matvec(&x, &mut ax);
let res = b.iter().zip(&ax).map(|(bi, ai)| (bi - ai) * (bi - ai)).sum::<f64>().sqrt() / bnorm;
(x, its, res)
}
fn cg(&self, b: &[f64], tol: f64, max_iter: usize) -> (Vec<f64>, usize, f64) {
let n = self.n();
let diag = self.diagonal();
let minv: Vec<f64> = diag.iter().map(|d| if *d > 0.0 { 1.0 / d } else { 0.0 }).collect();
let mut x = vec![0.0; n];
let mut r = b.to_vec();
let mut z: Vec<f64> = r.iter().zip(&minv).map(|(a, m)| a * m).collect();
let mut p = z.clone();
let mut ap = vec![0.0; n];
let bnorm = b.iter().map(|v| v * v).sum::<f64>().sqrt().max(1e-30);
let mut rz: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
let mut it = 0; let mut res = if b.iter().all(|v| *v == 0.0) { 0.0 } else { 1.0 };
while it < max_iter {
self.matvec(&p, &mut ap);
let pap: f64 = p.iter().zip(&ap).map(|(a, b)| a * b).sum();
if pap.abs() < 1e-300 { break; }
let alpha = rz / pap;
for i in 0..n { x[i] += alpha * p[i]; r[i] -= alpha * ap[i]; }
res = r.iter().map(|v| v * v).sum::<f64>().sqrt() / bnorm;
it += 1;
if res < tol { break; }
for i in 0..n { z[i] = r[i] * minv[i]; }
let rz2: f64 = r.iter().zip(&z).map(|(a, b)| a * b).sum();
let beta = rz2 / rz; rz = rz2;
for i in 0..n { p[i] = z[i] + beta * p[i]; }
}
(x, it, res)
}
}
pub fn harm(a: f64, b: f64) -> f64 { if a + b > 0.0 { 2.0 * a * b / (a + b) } else { 0.0 } }