spectrum.rs

3.4 kB · rust · 98 lines

1use crate::{code_of, Fault};2use mrlycore::{json, Json};3use mrlymath::six;4use mrlymath::three;5use mrlymath::two;6use mrlynum::graph::{census, largest_component, Network};7use mrlynum::spectrum as spectra;8use wasm_bindgen::prelude::*;910const LIMIT: usize = 1100;11const TOLERANCE: f64 = 1e-9;12const TOP: usize = 8;1314fn graph_of(15    kind: &str,16    code: &str,17    number: usize,18    level: usize,19) -> Result<(Network, usize), Fault> {20    let code = code_of(code)?;21    match kind {22        "flat" => {23            let whole = two::graph::core_graph(&two::create(code, number, level, 0, 2)?)?;24            let pieces = census::components(&whole);25            Ok((whole, pieces))26        }27        "slice" => {28            let cell = six::cut(&three::create(code, number, level, 2)?)?;29            let whole = six::graph::slice_core_graph(&cell)?;30            let pieces = census::components(&whole);31            Ok((largest_component(&whole), pieces))32        }33        _ => Err(Fault::new(format!(34            "kind {kind:?} is neither \"flat\" nor \"slice\"."35        ))),36    }37}3839/// Diagonalises the Laplacian of a design's graph: the spectrum, its clusters, the pinned multiplicities and the spectral exponent, as JSON.40///41/// The kind is `"flat"` for the cell graph of a two-dimensional design or `"slice"` for the42/// giant piece of a cube's diagonal section, whose whole-section piece count is reported43/// alongside. The Laplacian is normalised or combinatorial, clustering runs at `1e-9`, and44/// anything over 1100 nodes is refused. The fit is the log-log intercept and slope the45/// exponent doubles, over the first `fitted` of the `stair` points the page draws.46#[wasm_bindgen]47pub fn spectrum(48    kind: &str,49    code: &str,50    number: usize,51    level: usize,52    normalised: bool,53    window: f64,54) -> Result<String, Fault> {55    let (network, components) = graph_of(kind, code, number, level)?;56    let nodes = network.nodes.len();57    if nodes > LIMIT {58        return Err(Fault::new(format!(59            "{nodes} nodes is past the {LIMIT} the spectrum allows."60        )));61    }62    let eigenvalues = spectra::laplacian_spectrum(&network, normalised)?;63    let groups = spectra::clusters(&eigenvalues, TOLERANCE);64    let fit = spectra::spectral_fit(&eigenvalues, window);65    let repeated: usize = groups.iter().filter(|g| g.1 > 1).map(|g| g.1).sum();66    let fraction = if nodes == 0 {67        0.068    } else {69        (repeated as f64 / nodes as f64 * 10000.0).round() / 10000.070    };71    let root = 30f64.sqrt() / 6.0;72    let top: Vec<Vec<Json>> = groups73        .iter()74        .rev()75        .take(TOP)76        .map(|(value, size)| vec![json!(value), json!(size)])77        .collect();78    Ok(json!({79        "nodes": nodes,80        "edges": network.branches.len(),81        "components": components,82        "eigenvalues": eigenvalues,83        "distinct": groups.len(),84        "classes": groups.iter().filter(|g| g.1 > 1).count(),85        "repeated": fraction,86        "one": spectra::multiplicity(&eigenvalues, 1.0, TOLERANCE),87        "pair": [88            spectra::multiplicity(&eigenvalues, 1.0 - root, TOLERANCE),89            spectra::multiplicity(&eigenvalues, 1.0 + root, TOLERANCE),90        ],91        "exponent": fit.map(|(_, slope, _)| 2.0 * slope),92        "fit": fit.map(|(a, b, _)| vec![a, b]),93        "fitted": fit.map(|(_, _, count)| count),94        "stair": spectra::spectral_points(&eigenvalues),95        "top": top,96    })97    .to_string())98}