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}