wiki-nodal-domains.rs

3.5 kB · rust · 123 lines

1use mrlycore::errors::Result;2use mrlyfig::{field, ink, save, Board, Frame, Ramp};3use std::f64::consts::PI;45const RES: usize = 256;6const REACH: usize = 2;78fn main() -> Result<()> {9    let mut board = Board::square();10    let frame = board.frame(0.08);11    let gap = frame.w * 0.06;12    let side = (frame.w - gap) / 2.0;13    let panels = [14        Frame::new(frame.x, frame.y, side, side),15        Frame::new(frame.x + side + gap, frame.y, side, side),16        Frame::new(frame.x, frame.y + side + gap, side, side),17        Frame::new(frame.x + side + gap, frame.y + side + gap, side, side),18    ];19    let modes: [(Box<dyn Fn(f64, f64) -> f64>, u32, usize); 4] = [20        (Box::new(|x, y| mode(1, 1, x, y)), 2, 1),21        (Box::new(|x, y| mode(2, 1, x, y)), 5, 2),22        (Box::new(|x, y| mode(2, 2, x, y)), 8, 4),23        (Box::new(|x, y| mode(1, 3, x, y) + mode(3, 1, x, y)), 10, 2),24    ];25    assert_eq!([index(2), index(5), index(8), index(10)], [1, 2, 4, 5]);26    let ramp = Ramp::new(vec![ink::orange(), ink::ground(), ink::blue()]);27    for (panel, (f, lambda, want)) in panels.iter().zip(modes.iter()) {28        let signs = signs(f);29        let domains = domains(&signs);30        assert_eq!(domains, *want);31        assert!(domains <= index(*lambda));32        let values = nodal(&signs);33        field::draw_range(&mut board, *panel, RES, RES, &values, (-1.0, 1.0), &ramp);34    }35    save("wiki-nodal-domains", &board)?;36    Ok(())37}3839fn mode(m: u32, n: u32, x: f64, y: f64) -> f64 {40    (m as f64 * PI * x).sin() * (n as f64 * PI * y).sin()41}4243fn index(lambda: u32) -> usize {44    let mut below = 0;45    for m in 1..=10 {46        for n in 1..=10 {47            if m * m + n * n < lambda {48                below += 1;49            }50        }51    }52    below + 153}5455fn signs(f: &dyn Fn(f64, f64) -> f64) -> Vec<i8> {56    let mut out = Vec::with_capacity(RES * RES);57    for row in 0..RES {58        for col in 0..RES {59            let x = (col as f64 + 0.5) / RES as f64;60            let y = (row as f64 + 0.5) / RES as f64;61            out.push(if f(x, y) >= 0.0 { 1 } else { -1 });62        }63    }64    out65}6667fn neighbours(i: usize) -> impl Iterator<Item = usize> {68    let (row, col) = (i / RES, i % RES);69    let mut out = Vec::with_capacity(4);70    if row > 0 {71        out.push(i - RES);72    }73    if row + 1 < RES {74        out.push(i + RES);75    }76    if col > 0 {77        out.push(i - 1);78    }79    if col + 1 < RES {80        out.push(i + 1);81    }82    out.into_iter()83}8485fn domains(signs: &[i8]) -> usize {86    let mut seen = vec![false; signs.len()];87    let mut count = 0;88    for start in 0..signs.len() {89        if seen[start] {90            continue;91        }92        count += 1;93        let mut stack = vec![start];94        seen[start] = true;95        while let Some(i) = stack.pop() {96            for j in neighbours(i) {97                if !seen[j] && signs[j] == signs[i] {98                    seen[j] = true;99                    stack.push(j);100                }101            }102        }103    }104    count105}106107fn nodal(signs: &[i8]) -> Vec<f64> {108    (0..signs.len())109        .map(|i| {110            let (row, col) = (i / RES, i % RES);111            let rows = row.saturating_sub(REACH)..(row + REACH + 1).min(RES);112            let cols = col.saturating_sub(REACH)..(col + REACH + 1).min(RES);113            let crossed = rows114                .flat_map(|r| cols.clone().map(move |c| r * RES + c))115                .any(|j| signs[j] != signs[i]);116            if crossed {117                0.0118            } else {119                signs[i] as f64120            }121        })122        .collect()123}