topology.rs
14.3 kB · rust · 416 lines
1use super::census::{corners, edges_of, fills_only};2use super::graph::slice_core_graph;3use super::models::Cell6d;4use super::{FILL, VOID};5use mrlycore::errors::{value_error, Result};6use mrlynum::graph::largest_component;7use mrlynum::graph::models::Network;8use mrlynum::spectrum::{laplacian_spectrum, spectral_exponent as slope};9use std::collections::BTreeMap;1011type Point = (i64, i64);12type Edge = (Point, Point);1314fn pieces(network: &Network) -> Vec<usize> {15 let n = network.nodes.len();16 let adjacency = network.adjacency();17 let mut seen = vec![false; n];18 let mut sizes = Vec::new();19 for start in 0..n {20 if seen[start] {21 continue;22 }23 let mut size = 0;24 let mut stack = vec![start];25 seen[start] = true;26 while let Some(current) = stack.pop() {27 size += 1;28 for &neighbor in &adjacency[¤t] {29 if !seen[neighbor] {30 seen[neighbor] = true;31 stack.push(neighbor);32 }33 }34 }35 sizes.push(size);36 }37 sizes38}3940/// Counts the connected pieces of the fill, triangles joined across shared edges.41///42/// ```43/// let slice = mrlymath::six::cut(&mrlymath::three::carpet(5, 1).unwrap()).unwrap();44/// assert_eq!(mrlymath::six::topology::components(&slice).unwrap(), 7);45/// ```46pub fn components(cell: &Cell6d) -> Result<usize> {47 Ok(pieces(&slice_core_graph(cell)?).len())48}4950/// Returns the triangle count of the fill's largest connected piece.51pub fn giant(cell: &Cell6d) -> Result<usize> {52 Ok(pieces(&slice_core_graph(cell)?)53 .into_iter()54 .max()55 .unwrap_or(0))56}5758/// Returns the largest connected piece of the filled-triangle network as a network of its own.59pub fn giant_network(cell: &Cell6d) -> Result<Network> {60 Ok(largest_component(&slice_core_graph(cell)?))61}6263/// Reads the spectral dimension of the giant piece: twice the low-window log-log slope of the normalised Laplacian's integrated density of states.64pub fn spectral_exponent(cell: &Cell6d, window: f64) -> Result<f64> {65 let spectrum = laplacian_spectrum(&giant_network(cell)?, true)?;66 match slope(&spectrum, window) {67 Some(value) => Ok(value),68 None => value_error("The giant piece is too small to fit an exponent."),69 }70}7172/// Counts the holes of the fill, its piece count less the Euler number of the filled sub-mesh.73///74/// ```75/// let slice = mrlymath::six::cut(&mrlymath::three::carpet(3, 1).unwrap()).unwrap();76/// assert_eq!(mrlymath::six::topology::holes(&slice).unwrap(), 1);77/// ```78pub fn holes(cell: &Cell6d) -> Result<usize> {79 let count = components(cell)? as i64;80 Ok((count - fills_only(cell).euler).max(0) as usize)81}8283/// Counts the void regions the rim never reaches, the second route to the hole count.84pub fn rim_holes(cell: &Cell6d) -> Result<usize> {85 let inner = &cell.cell;86 let start = cell.start as i64;87 let (height, width) = (inner.height(), inner.width());88 let mut sites = Vec::new();89 let mut mesh: BTreeMap<Edge, usize> = BTreeMap::new();90 for y in 0..height {91 for x in 0..width {92 let v = inner.types().get(&[y, x]);93 if v != FILL && v != VOID {94 continue;95 }96 for edge in edges_of(&corners(x as i64, y as i64, start)) {97 *mesh.entry(edge).or_insert(0) += 1;98 }99 if v == VOID {100 sites.push((x as i64, y as i64));101 }102 }103 }104 let mut owners: BTreeMap<Edge, Vec<usize>> = BTreeMap::new();105 for (index, &(x, y)) in sites.iter().enumerate() {106 for edge in edges_of(&corners(x, y, start)) {107 owners.entry(edge).or_default().push(index);108 }109 }110 let mut adjacency: Vec<Vec<usize>> = vec![Vec::new(); sites.len()];111 let mut on_rim = vec![false; sites.len()];112 for (edge, shared) in &owners {113 if mesh[edge] == 1 {114 for &index in shared {115 on_rim[index] = true;116 }117 }118 if shared.len() == 2 {119 adjacency[shared[0]].push(shared[1]);120 adjacency[shared[1]].push(shared[0]);121 }122 }123 let mut seen = vec![false; sites.len()];124 let mut enclosed = 0;125 for start in 0..sites.len() {126 if seen[start] {127 continue;128 }129 let mut open = false;130 let mut stack = vec![start];131 seen[start] = true;132 while let Some(current) = stack.pop() {133 open |= on_rim[current];134 for &neighbor in &adjacency[current] {135 if !seen[neighbor] {136 seen[neighbor] = true;137 stack.push(neighbor);138 }139 }140 }141 if !open {142 enclosed += 1;143 }144 }145 Ok(enclosed)146}147148#[cfg(test)]149mod theorems {150 use super::*;151 use crate::bang::universe::orbit;152 use crate::formulas::six::centered_hexagonal;153 use crate::six::census::census;154 use crate::six::geometry::cut;155 use crate::six::GRID;156 use crate::three::{self, Cell3d};157 use mrlynum::graph::census::components as network_components;158159 fn slice(code: u128, number: usize, level: usize) -> Cell6d {160 cut(&three::create(code, number, level, 2).unwrap()).unwrap()161 }162163 fn section_area(cell: &Cell3d) -> u128 {164 let grid = cell.types();165 let side = grid.shape[0];166 let mut total = 0;167 for i in 0..side {168 for j in 0..side {169 for l in 0..side {170 if grid.get(&[i, j, l]) == 0 {171 continue;172 }173 let layer = 3 * side as i64 - 2 * (i + j + l) as i64;174 total += match layer {175 1 | 5 => 1,176 2 | 4 => 4,177 3 => 6,178 _ => 0,179 };180 }181 }182 }183 total184 }185186 #[test]187 fn the_four_families_fill_the_slice_and_name_their_classes() {188 let expected = [189 (23u128, 42usize, 23u128),190 (232, 12, 23),191 (3, 18, 3),192 (129, 12, 24),193 ];194 for (code, fill, class) in expected {195 let cut = slice(code, 3, 1);196 assert_eq!(census(&cut, false).fills, fill, "code={code}");197 assert_eq!(*orbit(code, 3).iter().next().unwrap(), class, "code={code}");198 }199 }200201 #[test]202 fn carpet_and_net_partition_the_hexagon_triangle_by_triangle() {203 let pairs = [204 (3usize, 42u128, 12u128),205 (5, 72, 78),206 (7, 204, 90),207 (9, 210, 276),208 (11, 486, 240),209 ];210 for number in (1..32).step_by(2) {211 let carpet = slice(23, number, 1);212 let net = slice(232, number, 1);213 let (left, right) = (carpet.cell.types(), net.cell.types());214 assert_eq!(left.shape, right.shape, "n={number}");215 let mut filled = 0;216 for (index, &value) in left.bytes().iter().enumerate() {217 let other = right.bytes()[index];218 if value == GRID || other == GRID {219 assert_eq!(value, other, "n={number}");220 continue;221 }222 assert_ne!(value, other, "n={number}");223 if value == FILL || other == FILL {224 filled += 1;225 }226 }227 let (a, b) = (228 census(&carpet, false).fills as u128,229 census(&net, false).fills as u128,230 );231 assert_eq!(filled as u128, 6 * (number as u128).pow(2), "n={number}");232 assert_eq!(a + b, 6 * (number as u128).pow(2), "n={number}");233 if let Some(&(_, want_a, want_b)) = pairs.iter().find(|p| p.0 == number) {234 assert_eq!((a, b), (want_a, want_b), "n={number}");235 }236 if number == 31 {237 assert_eq!((a, b, a + b), (3696, 2070, 5766));238 }239 }240 }241242 #[test]243 fn the_layer_weighted_area_is_a_second_route_to_the_fill() {244 for number in 1..17usize {245 let carpet = three::create(23, number, 1, 2).unwrap();246 let net = three::create(232, number, 1, 2).unwrap();247 let whole = 6 * (number as u128).pow(2);248 assert_eq!(249 section_area(&carpet) + section_area(&net),250 whole,251 "n={number}"252 );253 if number.is_multiple_of(2) {254 assert_eq!(section_area(&carpet), whole / 2, "n={number}");255 continue;256 }257 for code in [23u128, 232, 3, 129] {258 let cell = three::create(code, number, 1, 2).unwrap();259 assert_eq!(260 section_area(&cell),261 census(&cut(&cell).unwrap(), false).fills as u128,262 "code={code} n={number}"263 );264 }265 }266 }267268 #[test]269 fn the_carpet_slice_counts_its_pieces_and_holes_two_ways() {270 let pieces = [1, 1, 7, 1, 19, 1, 37, 1, 61, 1, 91, 1, 127, 1];271 let punctures = [0, 1, 0, 7, 0, 19, 0, 37, 0, 61, 0, 91, 0, 127];272 for (index, (&want_pieces, &want_holes)) in pieces.iter().zip(punctures.iter()).enumerate()273 {274 let k = index + 1;275 let cut = slice(23, 2 * k - 1, 1);276 assert_eq!(components(&cut).unwrap(), want_pieces, "k={k}");277 assert_eq!(278 network_components(&slice_core_graph(&cut).unwrap()),279 want_pieces,280 "k={k}"281 );282 assert_eq!(holes(&cut).unwrap(), want_holes, "k={k}");283 assert_eq!(rim_holes(&cut).unwrap(), want_holes, "k={k}");284 let law = centered_hexagonal(k.div_ceil(2)) as usize;285 assert_eq!(286 if k.is_multiple_of(2) {287 want_holes288 } else {289 want_pieces290 },291 law,292 "k={k}"293 );294 }295 }296297 #[test]298 fn the_other_families_puncture_in_opposite_phase() {299 for k in 1..11usize {300 for code in [3u128, 129] {301 let cut = slice(code, 2 * k - 1, 1);302 assert_eq!(holes(&cut).unwrap(), 0, "code={code} k={k}");303 assert_eq!(rim_holes(&cut).unwrap(), 0, "code={code} k={k}");304 }305 }306 for (k, want) in [(3usize, 1usize), (5, 7), (7, 19), (9, 37)] {307 let cut = slice(232, 2 * k - 1, 1);308 assert_eq!(holes(&cut).unwrap(), want, "k={k}");309 assert_eq!(rim_holes(&cut).unwrap(), want, "k={k}");310 }311 }312313 #[test]314 fn the_carpet_slice_percolates_at_base_three() {315 for (level, triangles) in [(1usize, 42usize), (2, 306), (3, 2250), (4, 16578)] {316 let cut = slice(23, 3, level);317 assert_eq!(census(&cut, false).fills, triangles, "l={level}");318 assert_eq!(components(&cut).unwrap(), 1, "l={level}");319 assert_eq!(giant(&cut).unwrap(), triangles, "l={level}");320 if level == 4 {321 let core = slice_core_graph(&cut).unwrap();322 assert_eq!((core.nodes.len(), core.branches.len()), (16578, 21546));323 }324 }325 }326327 #[test]328 fn the_other_slices_shatter_or_never_grow_at_base_three() {329 for level in 1..5usize {330 let net = slice(232, 3, level);331 assert_eq!(census(&net, false).fills, 12, "l={level}");332 assert_eq!(components(&net).unwrap(), 1, "l={level}");333 }334 let mut tree = 0;335 let mut anti = 0;336 for level in 1..4usize {337 let cut = slice(3, 3, level);338 assert_eq!(giant(&cut).unwrap(), 8, "l={level}");339 assert!(components(&cut).unwrap() > tree, "l={level}");340 tree = components(&cut).unwrap();341 let cut = slice(129, 3, level);342 assert_eq!(giant(&cut).unwrap(), 6, "l={level}");343 assert!(components(&cut).unwrap() > anti, "l={level}");344 anti = components(&cut).unwrap();345 }346 assert_eq!((tree, anti), (40, 55));347 }348349 #[test]350 fn the_carpet_slice_fails_to_percolate_at_base_five() {351 let cut = slice(23, 5, 2);352 assert_eq!(census(&cut, false).fills, 1164);353 assert_eq!(components(&cut).unwrap(), 20);354 assert_eq!(giant(&cut).unwrap(), 192);355 }356}357358#[cfg(test)]359mod spectra {360 use super::*;361 use crate::six::geometry::cut;362 use crate::three;363 use mrlynum::spectrum::laplacian_spectrum;364365 fn slice(code: u128, number: usize, level: usize) -> Cell6d {366 cut(&three::create(code, number, level, 2).unwrap()).unwrap()367 }368369 fn reading(code: u128, number: usize, level: usize) -> (usize, usize, f64) {370 let cell = slice(code, number, level);371 let piece = giant_network(&cell).unwrap();372 let spectrum = laplacian_spectrum(&piece, true).unwrap();373 let zeros = spectrum.iter().filter(|v| **v < 1e-10).count();374 (375 piece.nodes.len(),376 zeros,377 spectral_exponent(&cell, 0.1).unwrap(),378 )379 }380381 #[test]382 fn the_slice_exponent_climbs_with_the_size_it_is_read_at() {383 let rows = [384 (23u128, 3usize, 1usize, 42usize, 0.91),385 (23, 3, 2, 306, 1.25),386 (255, 9, 1, 486, 1.61),387 (23, 5, 2, 192, 1.05),388 ];389 for (code, number, level, nodes, want) in rows {390 let (got_nodes, zeros, exponent) = reading(code, number, level);391 assert_eq!(got_nodes, nodes, "code={code} n={number} l={level}");392 assert_eq!(zeros, 1, "code={code} n={number} l={level}");393 assert!(394 (exponent - want).abs() < 0.005,395 "code={code} n={number} l={level} {exponent}"396 );397 }398 }399400 #[test]401 #[ignore = "two thousand nodes each; run it in release"]402 fn the_deep_slices_hold_their_spectral_exponents() {403 for (code, number, level, nodes, want) in [404 (23u128, 3usize, 3usize, 2250usize, 1.44),405 (255, 19, 1, 2166, 1.79),406 ] {407 let (got_nodes, zeros, exponent) = reading(code, number, level);408 assert_eq!(got_nodes, nodes, "code={code} n={number}");409 assert_eq!(zeros, 1, "code={code} n={number}");410 assert!(411 (exponent - want).abs() < 0.005,412 "code={code} n={number} {exponent}"413 );414 }415 }416}