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}