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}