diagonal.rs

25.7 kB · rust · 709 lines

1use crate::bang::factory;2use crate::bang::universe::Code;3use crate::formulas::profile_of_tile;4use mrlycore::colors::{5    BLUE, BROWN, CYAN, GREEN, INDIGO, MINT, ORANGE, PINK, PURPLE, RED, TEAL, YELLOW,6};7use mrlycore::errors::{value_error, Result};8use mrlycore::Color;910const PALETTE: [Color; 12] = [11    RED, ORANGE, YELLOW, GREEN, MINT, TEAL, CYAN, BLUE, INDIGO, PURPLE, PINK, BROWN,12];1314use crate::formulas::diagonal::WIDEST;1516struct Solid {17    filled: Vec<bool>,18    ranks: Vec<usize>,19    count: usize,20    number: usize,21    level: usize,22    side: usize,23    top: usize,24}2526impl Solid {27    fn new(code: Code, number: usize, level: usize, base: usize) -> Result<Solid> {28        if level < 1 {29            return value_error("level must be at least 1.");30        }31        let side = match number.checked_pow(level as u32) {32            Some(side) if side <= WIDEST => side,33            _ => return value_error(format!("the side must stay at or below {WIDEST}.")),34        };35        let pattern = factory::create(code, number, 3, base, 1)?;36        let filled: Vec<bool> = pattern.bytes().iter().map(|&byte| byte != 0).collect();37        let mut ranks = vec![0usize; filled.len()];38        let mut count = 0;39        for (flat, &live) in filled.iter().enumerate() {40            if !live {41                continue;42            }43            ranks[flat] = count;44            count += 1;45        }46        Ok(Solid {47            filled,48            ranks,49            count,50            number,51            level,52            side,53            top: side / number,54        })55    }5657    fn holds(&self, x: usize, y: usize, z: usize) -> bool {58        let (mut a, mut b, mut c) = (x, y, z);59        for _ in 0..self.level {60            let flat =61                ((a % self.number) * self.number + b % self.number) * self.number + c % self.number;62            if !self.filled[flat] {63                return false;64            }65            a /= self.number;66            b /= self.number;67            c /= self.number;68        }69        true70    }7172    fn rank(&self, point: [u32; 3]) -> usize {73        let (x, y, z) = (74            point[0] as usize / self.top,75            point[1] as usize / self.top,76            point[2] as usize / self.top,77        );78        self.ranks[(x * self.number + y) * self.number + z]79    }80}8182// PROFILE8384/// Counts the filled cells on every diagonal plane `x + y + z = s`, for `s` in `0..=3*(side - 1)`.85///86/// The count is the coefficient of the digit polynomial, the product over the scales of the87/// pattern's weight sums, so no cell of the solid is ever built.88///89/// ```90/// let counts = mrlymath::three::profile(126, 2, 4, 2).unwrap();91/// assert_eq!(counts[15..=30].iter().copied().collect::<Vec<u128>>(), vec![81u128; 16]);92/// ```93pub fn profile(code: Code, number: usize, level: usize, base: usize) -> Result<Vec<u128>> {94    let tile = factory::create(code, number, 3, base, 1)?;95    profile_of_tile(&tile, level as u32)96}9798/// Returns the first and last height a profile fills, or none when the design is empty.99pub fn support(counts: &[u128]) -> Option<(usize, usize)> {100    let low = counts.iter().position(|&count| count > 0)?;101    let high = counts.iter().rposition(|&count| count > 0)?;102    Some((low, high))103}104105// SLICE106107/// Lists the filled cells on the diagonal plane `x + y + z = height`, as `x, y, z` triples.108///109/// ```110/// assert_eq!(mrlymath::three::diagonal_slice(126, 2, 3, 2, 10).unwrap().len(), 27);111/// ```112pub fn slice(113    code: Code,114    number: usize,115    level: usize,116    base: usize,117    height: usize,118) -> Result<Vec<[u32; 3]>> {119    let solid = Solid::new(code, number, level, base)?;120    let last = solid.side - 1;121    let mut out = Vec::new();122    if height > 3 * last {123        return Ok(out);124    }125    for x in height.saturating_sub(2 * last)..=last.min(height) {126        let rest = height - x;127        for y in rest.saturating_sub(last)..=last.min(rest) {128            let z = rest - y;129            if solid.holds(x, y, z) {130                out.push([x as u32, y as u32, z as u32]);131            }132        }133    }134    Ok(out)135}136137// PROJECTION138139/// Projects a cell down the `(1,1,1)` axis: `u = (x - y)/sqrt 2`, `v = (x + y - 2z)/sqrt 6`.140pub fn project(point: [u32; 3]) -> (f64, f64) {141    let (x, y, z) = (point[0] as f64, point[1] as f64, point[2] as f64);142    ((x - y) / 2f64.sqrt(), (x + y - 2.0 * z) / 6f64.sqrt())143}144145/// Returns the integer shadow `(x - y, x + y - 2z)`, the projection with its irrational scales dropped.146pub fn shadow(point: [u32; 3]) -> (i64, i64) {147    let (x, y, z) = (point[0] as i64, point[1] as i64, point[2] as i64);148    (x - y, x + y - 2 * z)149}150151// SVG152153/// Draws the given diagonal slices as one circle per cell, coloured by height slot and top-scale corner.154///155/// The frame is tight around the projected points and carries no background, so the drawing sits on156/// whatever the page is.157pub fn svg(158    code: Code,159    number: usize,160    level: usize,161    base: usize,162    heights: &[usize],163    scale: usize,164) -> Result<String> {165    let solid = Solid::new(code, number, level, base)?;166    let mut circles: Vec<(f64, f64, Color)> = Vec::new();167    for (slot, &height) in heights.iter().enumerate() {168        for point in slice(code, number, level, base, height)? {169            let (u, v) = project(point);170            let key = slot * solid.count.max(1) + solid.rank(point);171            circles.push((u, v, PALETTE[key % PALETTE.len()]));172        }173    }174    if circles.is_empty() {175        return value_error("nothing to render.");176    }177    let low_u = circles.iter().map(|c| c.0).fold(f64::MAX, f64::min);178    let high_u = circles.iter().map(|c| c.0).fold(f64::MIN, f64::max);179    let low_v = circles.iter().map(|c| c.1).fold(f64::MAX, f64::min);180    let high_v = circles.iter().map(|c| c.1).fold(f64::MIN, f64::max);181    let radius = 0.35 * scale as f64;182    let width = (high_u - low_u) * scale as f64 + 2.0 * radius;183    let height = (high_v - low_v) * scale as f64 + 2.0 * radius;184    let mut out = vec![format!(185        "<svg width=\"{width:.2}\" height=\"{height:.2}\" viewBox=\"0 0 {width:.2} {height:.2}\" xmlns=\"http://www.w3.org/2000/svg\">"186    )];187    for (u, v, fill) in circles {188        let cx = (u - low_u) * scale as f64 + radius;189        let cy = (high_v - v) * scale as f64 + radius;190        out.push(format!(191            "<circle cx=\"{cx:.2}\" cy=\"{cy:.2}\" r=\"{radius:.2}\" fill=\"{}\"/>",192            fill.to_hex()193        ));194    }195    out.push("</svg>".to_string());196    Ok(out.join("\n"))197}198199#[cfg(test)]200mod tests {201    use super::*;202    use std::collections::{BTreeMap, HashSet};203204    fn scan(code: Code, number: usize, level: usize, base: usize) -> Vec<Vec<[u32; 3]>> {205        let cell = crate::three::create(code, number, level, base).unwrap();206        let grid = cell.types();207        let side = grid.shape[0];208        let mut out = vec![Vec::new(); 3 * (side - 1) + 1];209        for (flat, &site) in grid.bytes().iter().enumerate() {210            if site == 0 {211                continue;212            }213            let (x, rest) = (flat / (side * side), flat % (side * side));214            let (y, z) = (rest / side, rest % side);215            out[x + y + z].push([x as u32, y as u32, z as u32]);216        }217        out218    }219220    fn digit_build(level: usize) -> Vec<[u32; 3]> {221        let corners = factory::code_to_corners(126, 3, 2).unwrap();222        let mut points = vec![[0u32, 0, 0]];223        for _ in 0..level {224            let mut next = Vec::with_capacity(points.len() * corners.len());225            for point in &points {226                for corner in &corners {227                    next.push([228                        2 * point[0] + corner[0] as u32,229                        2 * point[1] + corner[1] as u32,230                        2 * point[2] + corner[2] as u32,231                    ]);232                }233            }234            points = next;235        }236        points237    }238239    fn constant_gasket(level: usize, weight_two: bool) -> HashSet<[u32; 3]> {240        let corners = if weight_two {241            [[0u32, 1, 1], [1, 0, 1], [1, 1, 0]]242        } else {243            [[1u32, 0, 0], [0, 1, 0], [0, 0, 1]]244        };245        let mut set: HashSet<[u32; 3]> = HashSet::new();246        set.insert([0, 0, 0]);247        for k in 0..level {248            let mut next = HashSet::new();249            for point in &set {250                for corner in &corners {251                    next.insert([252                        point[0] + (corner[0] << k),253                        point[1] + (corner[1] << k),254                        point[2] + (corner[2] << k),255                    ]);256                }257            }258            set = next;259        }260        set261    }262263    fn odd_trinomials(layer: usize) -> usize {264        let width = layer + 1;265        let mut current = vec![0u8; width * width];266        current[0] = 1;267        for step in 1..=layer {268            let mut next = vec![0u8; width * width];269            for i in 0..=step {270                for j in 0..=step - i {271                    let mut sum = current[i * width + j];272                    if i > 0 {273                        sum += current[(i - 1) * width + j];274                    }275                    if j > 0 {276                        sum += current[i * width + j - 1];277                    }278                    next[i * width + j] = sum % 2;279                }280            }281            current = next;282        }283        current.iter().filter(|&&value| value == 1).count()284    }285286    fn det3(m: [[i64; 3]; 3]) -> i64 {287        m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])288            - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])289            + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])290    }291292    #[test]293    fn the_slices_agree_with_a_scan_of_the_solid() {294        for code in [23u128, 105, 126, 127] {295            for number in [2usize, 3] {296                for level in 1..3 {297                    let scanned = scan(code, number, level, 2);298                    let counts = profile(code, number, level, 2).unwrap();299                    assert_eq!(counts.len(), scanned.len());300                    for (height, wanted) in scanned.iter().enumerate() {301                        assert_eq!(302                            counts[height],303                            wanted.len() as u128,304                            "code {code} number {number} level {level} height {height}"305                        );306                        let mut got = slice(code, number, level, 2, height).unwrap();307                        let mut want = wanted.clone();308                        got.sort_unstable();309                        want.sort_unstable();310                        assert_eq!(got, want, "code {code} number {number} level {level}");311                    }312                }313            }314        }315    }316317    #[test]318    fn the_digit_build_pairs_with_every_slice_to_level_eight() {319        for level in 1..=8usize {320            let last = (1usize << level) - 1;321            let mut buckets: Vec<Vec<[u32; 3]>> = vec![Vec::new(); 3 * last + 1];322            for point in digit_build(level) {323                buckets[(point[0] + point[1] + point[2]) as usize].push(point);324            }325            let counts = profile(126, 2, level, 2).unwrap();326            assert_eq!(counts.len(), buckets.len());327            for (height, bucket) in buckets.iter_mut().enumerate() {328                assert_eq!(329                    counts[height],330                    bucket.len() as u128,331                    "level {level} height {height}"332                );333                let mut got = slice(126, 2, level, 2, height).unwrap();334                got.sort_unstable();335                bucket.sort_unstable();336                assert_eq!(got, *bucket, "level {level} height {height}");337            }338        }339    }340341    #[test]342    fn every_slice_of_one_two_six_holds_three_to_the_level() {343        for level in 1..=14usize {344            let counts = profile(126, 2, level, 2).unwrap();345            let (low, high) = support(&counts).unwrap();346            assert_eq!((low, high), ((1 << level) - 1, (1 << (level + 1)) - 2));347            let wanted = 3u128.pow(level as u32);348            assert!(counts[low..=high].iter().all(|&count| count == wanted));349            assert_eq!(counts.iter().sum::<u128>(), 6u128.pow(level as u32));350        }351    }352353    #[test]354    fn the_two_central_heights_carry_two_times_three_to_the_level() {355        for (index, &total) in [18u128, 54, 162, 486, 1458, 4374, 13122].iter().enumerate() {356            let level = index + 2;357            let counts = profile(126, 2, level, 2).unwrap();358            let low = ((1usize << level) - 1) + (1 << (level - 1)) - 1;359            assert_eq!(counts[low] + counts[low + 1], total);360            assert_eq!(counts[low], 3u128.pow(level as u32));361        }362    }363364    #[test]365    fn the_central_union_splits_evenly_by_coordinate_order() {366        let orders = [367            [0, 1, 2],368            [0, 2, 1],369            [1, 0, 2],370            [1, 2, 0],371            [2, 0, 1],372            [2, 1, 0],373        ];374        for level in 2..=8usize {375            let low = ((1usize << level) - 1) + (1 << (level - 1)) - 1;376            let mut union = slice(126, 2, level, 2, low).unwrap();377            union.extend(slice(126, 2, level, 2, low + 1).unwrap());378            let mut classes = [0usize; 6];379            let mut ties = 0;380            for point in &union {381                if point[0] == point[1] || point[1] == point[2] || point[0] == point[2] {382                    ties += 1;383                    continue;384                }385                for (index, order) in orders.iter().enumerate() {386                    if point[order[0]] < point[order[1]] && point[order[1]] < point[order[2]] {387                        classes[index] += 1;388                    }389                }390            }391            assert_eq!(ties, 6);392            let each = 3usize.pow(level as u32 - 1) - 1;393            assert!(classes.iter().all(|&size| size == each), "level {level}");394            assert_eq!(union.len(), 2 * 3usize.pow(level as u32));395        }396    }397398    #[test]399    fn the_central_union_is_six_pieces_of_seven_two_nine_at_level_seven() {400        let low = 127 + 63;401        let mut pieces: BTreeMap<(usize, usize), usize> = BTreeMap::new();402        let mut shadows = HashSet::new();403        let mut total = 0;404        for (slot, height) in [low, low + 1].into_iter().enumerate() {405            for point in slice(126, 2, 7, 2, height).unwrap() {406                let corner = ((point[0] >> 6) * 4 + (point[1] >> 6) * 2 + (point[2] >> 6)) as usize;407                *pieces.entry((slot, corner)).or_default() += 1;408                shadows.insert(shadow(point));409                total += 1;410            }411        }412        assert_eq!(total, 4374);413        assert_eq!(pieces.len(), 6);414        assert!(pieces.values().all(|&size| size == 729));415        assert_eq!(shadows.len(), 4374);416    }417418    #[test]419    fn the_central_union_is_six_gaskets_from_the_level_below() {420        for level in 2..=8usize {421            let base = (1usize << level) - 1;422            let half = 1usize << (level - 1);423            let mut pieces: Vec<HashSet<[u32; 3]>> = Vec::new();424            let mut slices: HashSet<[u32; 3]> = HashSet::new();425            for offset in [half - 1, half] {426                let gasket = constant_gasket(level - 1, offset & 1 == 1);427                let corners = if (offset >> (level - 1)) & 1 == 1 {428                    [[0u32, 1, 1], [1, 0, 1], [1, 1, 0]]429                } else {430                    [[1u32, 0, 0], [0, 1, 0], [0, 0, 1]]431                };432                for corner in &corners {433                    pieces.push(434                        gasket435                            .iter()436                            .map(|point| {437                                [438                                    corner[0] * half as u32 + point[0],439                                    corner[1] * half as u32 + point[1],440                                    corner[2] * half as u32 + point[2],441                                ]442                            })443                            .collect(),444                    );445                }446                slices.extend(slice(126, 2, level, 2, base + offset).unwrap());447            }448            assert_eq!(pieces.len(), 6);449            let each = 3usize.pow(level as u32 - 1);450            assert!(451                pieces.iter().all(|piece| piece.len() == each),452                "level {level}"453            );454            for (index, piece) in pieces.iter().enumerate() {455                for other in &pieces[index + 1..] {456                    assert!(piece.is_disjoint(other), "level {level}");457                }458            }459            let union: HashSet<[u32; 3]> = pieces.iter().flatten().copied().collect();460            assert_eq!(union.len(), 2 * 3usize.pow(level as u32));461            assert_eq!(union, slices, "level {level}");462        }463    }464465    #[test]466    fn the_central_union_carries_the_order_twelve_symmetry() {467        let orders = [468            [0, 1, 2],469            [0, 2, 1],470            [1, 0, 2],471            [1, 2, 0],472            [2, 0, 1],473            [2, 1, 0],474        ];475        for level in 2..=8usize {476            let low = ((1usize << level) - 1) + (1 << (level - 1)) - 1;477            let below: HashSet<[u32; 3]> =478                slice(126, 2, level, 2, low).unwrap().into_iter().collect();479            let above: HashSet<[u32; 3]> = slice(126, 2, level, 2, low + 1)480                .unwrap()481                .into_iter()482                .collect();483            let union: HashSet<[u32; 3]> = below.union(&above).copied().collect();484            for order in &orders {485                let turned: HashSet<[u32; 3]> = union486                    .iter()487                    .map(|point| [point[order[0]], point[order[1]], point[order[2]]])488                    .collect();489                assert_eq!(turned, union, "level {level}");490            }491            let last = (1u32 << level) - 1;492            let flip = |set: &HashSet<[u32; 3]>| -> HashSet<[u32; 3]> {493                set.iter()494                    .map(|point| [last - point[0], last - point[1], last - point[2]])495                    .collect()496            };497            assert_eq!(flip(&below), above, "level {level}");498            assert_eq!(flip(&above), below, "level {level}");499            let mut ties: Vec<[u32; 3]> = union500                .iter()501                .copied()502                .filter(|point| {503                    point[0] == point[1] || point[1] == point[2] || point[0] == point[2]504                })505                .collect();506            ties.sort_unstable();507            let m = (1u32 << (level - 1)) - 1;508            let mut wanted: Vec<[u32; 3]> = Vec::new();509            for triple in [[m, m, m + 1], [m, m + 1, m + 1]] {510                for order in &orders {511                    wanted.push([triple[order[0]], triple[order[1]], triple[order[2]]]);512                }513            }514            wanted.sort_unstable();515            wanted.dedup();516            assert_eq!(wanted.len(), 6);517            assert_eq!(ties, wanted, "level {level}");518        }519    }520521    #[test]522    fn every_scheduled_slice_is_the_digit_gasket() {523        let ones = [[1u32, 0, 0], [0, 1, 0], [0, 0, 1]];524        let twos = [[0u32, 1, 1], [1, 0, 1], [1, 1, 0]];525        for level in 1..=6usize {526            let low = (1usize << level) - 1;527            for offset in 0..(1usize << level) {528                let mut set: HashSet<[u32; 3]> = HashSet::new();529                set.insert([0, 0, 0]);530                for k in 0..level {531                    let corners = if (offset >> k) & 1 == 1 { twos } else { ones };532                    let mut next = HashSet::new();533                    for point in &set {534                        for corner in &corners {535                            next.insert([536                                point[0] + (corner[0] << k),537                                point[1] + (corner[1] << k),538                                point[2] + (corner[2] << k),539                            ]);540                        }541                    }542                    set = next;543                }544                let got: HashSet<[u32; 3]> = slice(126, 2, level, 2, low + offset)545                    .unwrap()546                    .into_iter()547                    .collect();548                assert_eq!(got, set, "level {level} offset {offset}");549            }550        }551    }552553    #[test]554    fn the_neighbours_profile_at_level_four() {555        for (code, span, nonempty, low, high) in [556            (63u128, (0usize, 30usize), 31usize, 1u128, 81u128),557            (105, (0, 30), 16, 1, 81),558            (111, (0, 30), 31, 1, 111),559            (126, (15, 30), 16, 81, 81),560            (127, (0, 30), 31, 1, 162),561        ] {562            let counts = profile(code, 2, 4, 2).unwrap();563            assert_eq!(support(&counts).unwrap(), span);564            let live: Vec<u128> = counts[span.0..=span.1]565                .iter()566                .copied()567                .filter(|&count| count > 0)568                .collect();569            assert_eq!(live.len(), nonempty, "code {code}");570            assert_eq!(*live.iter().min().unwrap(), low, "code {code}");571            assert_eq!(*live.iter().max().unwrap(), high, "code {code}");572            let flat = live.len() == span.1 - span.0 + 1 && low == high;573            assert_eq!(flat, code == 126);574        }575    }576577    #[test]578    fn the_one_two_seven_cut_matches_no_closed_form() {579        for (index, &top) in [3u128, 12, 45, 162, 594, 2187].iter().enumerate() {580            let level = index + 1;581            let counts = profile(127, 2, level, 2).unwrap();582            assert_eq!(*counts.iter().max().unwrap(), top);583            assert_eq!(*counts.iter().filter(|&&count| count > 0).min().unwrap(), 1);584            assert_eq!(counts.iter().sum::<u128>(), 7u128.pow(level as u32));585            let closed = 4 * (level as u128 + 5) * 3u128.pow(level as u32 - 1);586            assert_ne!(top, closed);587        }588    }589590    #[test]591    fn the_flat_slice_is_the_odd_layer_of_pascals_pyramid() {592        for (index, &wanted) in [3usize, 9, 27, 81, 243].iter().enumerate() {593            let level = index + 1;594            let low = (1usize << level) - 1;595            let points = slice(126, 2, level, 2, low).unwrap();596            assert_eq!(points.len(), 3usize.pow(level as u32));597            assert_eq!(points.len(), wanted);598            assert_eq!(odd_trinomials(low), wanted);599        }600    }601602    #[test]603    fn the_flat_slice_has_pairwise_disjoint_binary_supports() {604        for level in 1..=6usize {605            let last = (1u32 << level) - 1;606            let got: HashSet<[u32; 3]> = slice(126, 2, level, 2, last as usize)607                .unwrap()608                .into_iter()609                .collect();610            let mut wanted: HashSet<[u32; 3]> = HashSet::new();611            for x in 0..=last {612                for y in 0..=last - x {613                    let z = last - x - y;614                    if x & y == 0 && y & z == 0 && x & z == 0 {615                        wanted.insert([x, y, z]);616                    }617                }618            }619            assert_eq!(got, wanted, "level {level}");620            assert_eq!(got.len(), 3usize.pow(level as u32));621        }622    }623624    #[test]625    fn the_twenty_three_slices_are_three_to_the_digit_sum() {626        assert_eq!(627            &profile(23, 2, 3, 2).unwrap()[..8],628            &[1u128, 3, 3, 9, 3, 9, 9, 27]629        );630        for level in 1..=6u32 {631            let counts = profile(23, 2, level as usize, 2).unwrap();632            for (height, &count) in counts.iter().enumerate() {633                let wanted = if height < (1 << level) {634                    3u128.pow((height as u32).count_ones())635                } else {636                    0637                };638                assert_eq!(count, wanted, "level {level} height {height}");639            }640        }641    }642643    #[test]644    fn the_six_cut_codes_are_canonical() {645        for code in [23u128, 63, 105, 111, 126, 127] {646            let orbit = crate::bang::universe::orbit(code, 3);647            assert_eq!(*orbit.iter().next().unwrap(), code);648        }649    }650651    #[test]652    fn the_centred_corners_are_the_octahedron_axes() {653        let corners = factory::code_to_corners(126, 3, 2).unwrap();654        let doubled: Vec<[i64; 3]> = corners655            .iter()656            .map(|corner| {657                [658                    2 * corner[0] as i64 - 1,659                    2 * corner[1] as i64 - 1,660                    2 * corner[2] as i64 - 1,661                ]662            })663            .collect();664        assert_eq!(doubled.len(), 6);665        for axis in &doubled {666            assert!(doubled.contains(&[-axis[0], -axis[1], -axis[2]]));667        }668        let basis = [doubled[0], doubled[1], doubled[2]];669        let mat = [670            [basis[0][0], basis[1][0], basis[2][0]],671            [basis[0][1], basis[1][1], basis[2][1]],672            [basis[0][2], basis[1][2], basis[2][2]],673        ];674        let det = det3(mat);675        assert_eq!(det, -4);676        for axis in &doubled {677            let mut hits = 0;678            for k in 0..3 {679                let mut probe = mat;680                probe[0][k] = axis[0];681                probe[1][k] = axis[1];682                probe[2][k] = axis[2];683                let value = det3(probe);684                if value == 0 {685                    continue;686                }687                assert_eq!(value.abs(), det.abs());688                hits += 1;689            }690            assert_eq!(hits, 1);691        }692    }693694    #[test]695    fn the_svg_draws_one_circle_for_every_cell() {696        let art = svg(126, 2, 3, 2, &[10, 11], 4).unwrap();697        assert_eq!(art.matches("<circle").count(), 54);698        assert!(!art.contains("<rect"));699        let fills: HashSet<&str> = art700            .split("fill=\"")701            .skip(1)702            .map(|piece| &piece[..7])703            .collect();704        assert_eq!(fills.len(), 6);705        assert!(fills.contains(RED.to_hex().as_str()));706        assert!(svg(0, 2, 2, 2, &[3], 4).is_err());707        assert!(profile(126, 2, 0, 2).is_err());708    }709}