rows.rs
14.2 kB · rust · 447 lines
1use mrlyrs::core::rng::Rng;2use mrlyrs::core::tensor::Tensor;3use mrlyrs::core::{png, Color, Colorizer};4use mrlyrs::font;5use mrlyrs::gen;6use mrlyrs::life::{self, Config, Source};7use mrlyrs::math::bang::Code;8use mrlyrs::math::graph::Network;9use mrlyrs::math::name::Named;10use mrlyrs::math::two::Cell2d;11use mrlyrs::math::{atoms, bang, counts, spectrum, three, two};12use mrlyrs::num::{factor, prime, series};13use serde_json::{json, Map, Value};1415#[path = "sha256.rs"]16pub mod sha256;1718pub const MODULES: [&str; 6] = ["core", "num", "math", "gen", "life", "font"];1920const SEED: u64 = 2026;2122const CARPET_NAME: &str = r#"{"kind":"tile","code":7,"side":3,"level":2}"#;2324pub fn rows(module: &str) -> Vec<Value> {25 match module {26 "core" => core_rows(),27 "num" => num_rows(),28 "math" => math_rows(),29 "gen" => gen_rows(),30 "life" => life_rows(),31 "font" => font_rows(),32 other => panic!("no fixture module {other:?}"),33 }34}3536// SHAPES3738fn row(path: &str, input: Value, out: Value, note: &str) -> Value {39 let mut map = Map::new();40 map.insert("fn".to_string(), json!(path));41 map.insert("in".to_string(), input);42 map.insert("out".to_string(), out);43 map.insert("note".to_string(), json!(note));44 Value::Object(map)45}4647fn tensor(grid: &Tensor) -> Value {48 json!({"shape": grid.shape, "data": grid.bytes().unwrap()})49}5051fn cell(flat: &Cell2d) -> Value {52 serde_json::from_str(&two::to_json(flat)).unwrap()53}5455fn big(number: u128) -> Value {56 json!(number.to_string())57}5859fn f12(value: f64) -> Value {60 json!(format!("{value:.12}").parse::<f64>().unwrap() + 0.0)61}6263fn sha(bytes: &[u8]) -> Value {64 json!(sha256::hex(bytes))65}6667// CORE6869fn core_rows() -> Vec<Value> {70 let source: Vec<u8> = (0..12).collect();71 let turned = Tensor::of(source.clone(), vec![3, 4])72 .unwrap()73 .rot90(1, (0, 1))74 .unwrap();75 let red = Color::from_hex("#ff3d40").unwrap();76 let corners = [77 [255u8, 0, 0, 255],78 [0, 255, 0, 255],79 [0, 0, 255, 255],80 [255, 255, 255, 255],81 ];82 let bytes = png(&corners, 2, 2, 3).unwrap();83 let ramp = Colorizer::gradient_bins(84 Color::from_hex("#000000").unwrap(),85 &[86 Color::from_hex("#ff3d40").unwrap(),87 Color::from_hex("#3d40ff").unwrap(),88 ],89 4,90 )91 .unwrap();92 let swatches: Vec<String> = (0..4)93 .map(|value| mrlyrs::core::ramp::color(&ramp, value, 9).to_hex())94 .collect();95 vec![96 row(97 "core::Tensor::rot90",98 json!({"data": source, "shape": [3, 4], "k": 1, "axes": [0, 1]}),99 tensor(&turned),100 "one quarter turn in the (0, 1) plane, the numpy law",101 ),102 row(103 "core::Color::from_hex",104 json!({"hex": "#ff3d40"}),105 json!({"rgba": [red.r, red.g, red.b, red.a], "hex": red.to_hex()}),106 "the house red parsed, then printed back by to_hex",107 ),108 row(109 "core::png",110 json!({"colors": corners, "width": 2, "height": 2, "scale": 3}),111 sha(&bytes),112 "sha256 of the png bytes, which pins the encoder and the scale",113 ),114 row(115 "core::Colorizer::color",116 json!({117 "background": "#000000",118 "ramp": ["#ff3d40", "#3d40ff"],119 "shades": 4,120 "values": [0, 1, 2, 3],121 "max": 9,122 }),123 json!(swatches),124 "gradient_bins first; four shades against a max of 9 bin every live value at the ramp head",125 ),126 ]127}128129// NUM130131fn num_rows() -> Vec<Value> {132 let pairs = [(1071u128, 462u128), (1u128 << 100, 1u128 << 60)];133 let named: Vec<Vec<String>> = pairs134 .iter()135 .map(|&(a, b)| vec![a.to_string(), b.to_string()])136 .collect();137 let gcds: Vec<String> = pairs138 .iter()139 .map(|&(a, b)| factor::gcd(a, b).to_string())140 .collect();141 let primes: Vec<usize> = (1..=100).filter(|&n| prime::is_prime(n)).collect();142 vec![143 row(144 "num::factor::factorial",145 json!({"number": 25}),146 big(factor::factorial(25).unwrap()),147 "the u128 crossing: 25! as a decimal string",148 ),149 row(150 "num::factor::gcd",151 json!({"pairs": named}),152 json!(gcds),153 "two calls, (1071, 462) and (2^100, 2^60), u128 in and out as decimal strings",154 ),155 row(156 "num::factor::divisors",157 json!({"number": 360}),158 json!(factor::divisors(360)),159 "the 24 divisors of 360 in ascending order",160 ),161 row(162 "num::factor::mobius_sieve",163 json!({"limit": 30}),164 json!(factor::mobius_sieve(30)),165 "OEIS A008683 with a leading pad: 31 entries, mu(n) at index n",166 ),167 row(168 "num::prime::is_prime",169 json!({"from": 1, "to": 100}),170 json!(primes),171 "the 25 numbers in 1..=100 the test keeps",172 ),173 row(174 "num::series::zeta",175 json!({"s": 2.0, "terms": 1000}),176 f12(series::zeta(2.0, 1000).unwrap()),177 "the f64 crossing: pi squared over six, rounded to 12 decimals",178 ),179 ]180}181182// MATH183184fn math_rows() -> Vec<Value> {185 let atom = atoms::carpet_2d(3);186 let flat = two::carpet(3, 2).unwrap();187 let survey = two::census::census(&flat).unwrap();188 let filled = counts::fill(Code::from(23u64), 3, 3, 2, 2).unwrap();189 let universe = bang::bang(3).unwrap();190 let cube = three::carpet(3, 1).unwrap();191 let solid = three::census::census(&cube).unwrap();192 let mut network = Network::new(1);193 for node in 0..5 {194 network.add_node(vec![node as f64]).unwrap();195 }196 for branch in 0..4 {197 network.add_branch(branch, branch + 1, 1.0).unwrap();198 }199 let spread = spectrum::laplacian_spectrum(&network, false).unwrap();200 vec![201 row(202 "math::atoms::carpet_2d",203 json!({"n": 3}),204 tensor(&atom),205 "the 3 by 3 seed every carpet design rests on",206 ),207 row(208 "math::two::carpet",209 json!({"number": 3, "level": 2}),210 cell(&flat),211 "the byte-grid crossing: the cell as math::two::to_json writes it",212 ),213 row(214 "math::two::census::census",215 json!({"cell": {"fn": "math::two::carpet", "in": {"number": 3, "level": 2}}}),216 serde_json::to_value(&survey).unwrap(),217 "a struct crossing: the Census through its serde derive",218 ),219 row(220 "math::counts::fill",221 json!({"code": "23", "number": 3, "dimension": 3, "level": 2, "base": 2}),222 big(filled),223 "the sponge's filled cells in closed form, u128 as a decimal string",224 ),225 row(226 "math::bang::bang",227 json!({"dimension": 3}),228 json!(universe.distinct()),229 "Universe::distinct() of the dimension-3 universe",230 ),231 row(232 "math::three::census::census",233 json!({"cell": {"fn": "math::three::carpet", "in": {"number": 3, "level": 1}}}),234 big(solid.surface),235 "the .surface field only, u128 as a decimal string",236 ),237 row(238 "math::spectrum::laplacian_spectrum",239 json!({240 "network": {241 "dim": 1,242 "nodes": [[0.0], [1.0], [2.0], [3.0], [4.0]],243 "branches": [[0, 1, 1.0], [1, 2, 1.0], [2, 3, 1.0], [3, 4, 1.0]],244 },245 "normalised": false,246 }),247 Value::Array(spread.iter().map(|&value| f12(value)).collect()),248 "the five eigenvalues of a 5-node path, ascending, to 12 decimals",249 ),250 ]251}252253// GEN254255fn gen_rows() -> Vec<Value> {256 let name = gen::name::Tile::from_json(CARPET_NAME).unwrap();257 let recipe = name.recipe().unwrap();258 let folded = gen::name::Tile::of(&recipe).unwrap();259 let built = gen::build::build_2d(&recipe).unwrap();260 let mut drawing = Rng::new(SEED);261 let drawn = gen::draw::create(262 &gen::draw::ConfigNd::<2>::default(),263 |_, rng| rng.below(4),264 &mut drawing,265 )266 .unwrap();267 let mut varying = Rng::new(SEED);268 let made = gen::variation::create(&gen::variation::Config::default(), &mut varying).unwrap();269 vec![270 row(271 "gen::name::Tile::recipe",272 json!({"name": CARPET_NAME}),273 json!({274 "recipe": serde_json::to_value(&recipe).unwrap(),275 "json": folded.to_json(),276 "id": folded.to_id(),277 }),278 "the name read, unfolded to a recipe, folded back and hashed",279 ),280 row(281 "gen::draw::create",282 json!({"config": "ConfigNd::<2>::default()", "rotation": "rng.below(4)", "seed": SEED}),283 json!(gen::name::Tile::of(&drawn).unwrap().to_json()),284 "the drawn recipe as the canonical json of its name",285 ),286 row(287 "gen::build::build_2d",288 json!({"tile": {"fn": "gen::name::Tile::recipe", "in": {"name": CARPET_NAME}}}),289 cell(&built),290 "the classic carpet recipe built into its 9 by 9 cell",291 ),292 row(293 "gen::variation::create",294 json!({"config": "variation::Config::default()", "seed": SEED}),295 json!({296 "key": made.key,297 "seed": made.seed.to_string(),298 "edition": made.edition.name(),299 "tile": gen::name::Tile::of(&made.tile).unwrap().to_json(),300 }),301 "seed is a u64 drawn from the stream, so it crosses as a decimal string",302 ),303 row(304 "gen::background",305 json!({"seed": 1, "width": 2, "height": 2}),306 sha(&gen::background(1, 2, 2).unwrap()),307 "sha256 of the png bytes of a whole seeded artwork",308 ),309 ]310}311312// LIFE313314fn glider() -> Cell2d {315 let mut grid = Tensor::new(vec![8, 8]);316 for (y, x) in [(0, 1), (1, 2), (2, 0), (2, 1), (2, 2)] {317 grid.set(&[y, x], 1).unwrap();318 }319 Cell2d::new(grid).unwrap()320}321322fn glider_in() -> Value {323 json!({324 "seed": tensor(glider().types()),325 "config": {326 "mask": "life::moore",327 "birth": [3],328 "survive": [2, 3],329 "boundary": "Constant",330 "max_generations": 64,331 "grid_size": 1,332 "padding": 0,333 },334 })335}336337fn life_rows() -> Vec<Value> {338 let mut seed = vec![0u8; 31];339 seed[15] = 1;340 let space = life::history(&seed, 30, 16, true).unwrap();341 let config = Config::new(life::moore().unwrap(), vec![3].into(), vec![2, 3].into());342 let run = life::animate::animate(&glider(), &config).unwrap();343 let small = two::carpet(2, 2).unwrap();344 let blank = Cell2d::new(Tensor::new(vec![4, 4])).unwrap();345 let pair = [blank, small.clone()];346 let heat = life::heatmap(&run.grids, 1).unwrap();347 vec![348 row(349 "life::history",350 json!({"row": seed, "rule": 30, "steps": 16, "wrap": true}),351 tensor(&space),352 "the space-time diagram, row 0 the seed and one row per step",353 ),354 row(355 "life::animate::animate",356 glider_in(),357 json!({358 "fate": run.fate.name(),359 "count": run.count,360 "loop_length": run.loop_length,361 "last": cell(run.last().unwrap()),362 }),363 "the glider walks into the dead border and settles",364 ),365 row(366 "life::entropy",367 json!({"grid": {"fn": "math::two::carpet", "in": {"number": 2, "level": 2}}}),368 json!(life::entropy(&small)),369 "the binary Shannon entropy of the grid, in millibits",370 ),371 row(372 "life::churn",373 json!({374 "grids": [375 {"zeros": {"shape": [4, 4]}},376 {"fn": "math::two::carpet", "in": {"number": 2, "level": 2}},377 ],378 }),379 f12(life::churn(&pair)),380 "the mean fraction of sites changed, to 12 decimals",381 ),382 row(383 "life::counts",384 json!({385 "seq": "Primes",386 "max_neighbors": 8,387 "include_zeros": false,388 "include_ones": false,389 }),390 json!(life::counts(Source::Primes, 8, false, false).unwrap()),391 "the primes up to eight, laid down as neighbor counts",392 ),393 row(394 "life::heatmap",395 json!({"grids": {"fn": "life::animate::animate", "in": glider_in()}, "scale": 1}),396 json!(heat),397 "the row crossing: the raw png bytes of one heat frame per generation",398 ),399 ]400}401402// FONT403404fn font_rows() -> Vec<Value> {405 let a = font::glyph('a').unwrap();406 let write = font::animate::animate("MRLY", 1);407 let book = font::map();408 let text = serde_json::to_string(&book).unwrap();409 vec![410 row(411 "font::glyph",412 json!({"c": "a"}),413 json!(a.rows),414 "the five bitmap rows of the rounded lowercase a",415 ),416 row(417 "font::raster::raster",418 json!({"text": "42"}),419 json!(font::raster::raster("42")),420 "the text as one 0/1 grid, its trimmed glyphs a blank column apart",421 ),422 row(423 "font::path",424 json!({"c": "M"}),425 json!(font::path('M')),426 "the tuple-list crossing: the stroke order as (row, col) pairs",427 ),428 row(429 "font::animate::animate",430 json!({"text": "MRLY", "pad": 1}),431 json!({432 "rows": write.rows,433 "cols": write.cols,434 "fps": write.fps,435 "frames": write.frames.len(),436 "last": write.frames.last().unwrap(),437 }),438 "the board, the rate, the frame count and the finished frame",439 ),440 row(441 "font::map",442 json!({}),443 json!({"sha256": sha256::hex(text.as_bytes()), "len": book.len()}),444 "sha256 of serde's json of the whole table, which pins all 108 glyphs",445 ),446 ]447}