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