rules.py

3.6 kB · python · 106 lines

1import numpy as np2from typing import Callable, Iterable, Sequence3from .errors import MrlyError45# THE PARITY UNIVERSE67BASE = 289# ATOMS1011def carpet_rule(parities: Sequence[int]) -> bool:12    return sum(parities) <= 11314def net_rule(parities: Sequence[int]) -> bool:15    dimension = len(parities)16    return sum(parities) >= dimension - 11718def void_rule(parities: Sequence[int]) -> bool:19    return len(set(parities)) == 12021def tree_rule(parities: Sequence[int], fixed_axes: Iterable[int]) -> bool:22    return all(parities[axis] == 0 for axis in fixed_axes)2324# LEVEL-SET (THE SYMMETRIC GENUS)2526def level_set_rule(parities: Sequence[int], levels: Iterable[int]) -> bool:27    return sum(parities) in set(levels)2829def carpet_levels(dimension: int) -> set:30    return {0, 1}3132def net_levels(dimension: int) -> set:33    return {dimension - 1, dimension}3435def void_levels(dimension: int) -> set:36    return {0, dimension}3738# ANTI3940def anti_rule(rule: Callable[[Sequence[int]], bool]) -> Callable[[Sequence[int]], bool]:41    def negated(parities: Sequence[int]) -> bool:42        return not rule(parities)43    return negated4445# RENDERER4647def render(rule: Callable[[Sequence[int]], bool], number: int, dimension: int, base: int = BASE) -> np.ndarray:48    if number < 1:49        raise MrlyError("number must be at least 1.")50    if dimension < 1:51        raise MrlyError("dimension must be at least 1.")52    if base < 1:53        raise MrlyError("base must be at least 1.")54    indices = np.indices((number,) * dimension)55    parities = [(indices[axis] % base) for axis in range(dimension)]56    flat_parities = np.stack([p.ravel() for p in parities], axis=1)57    out = np.empty(flat_parities.shape[0], dtype=np.uint8)58    for i in range(flat_parities.shape[0]):59        out[i] = 1 if rule(tuple(int(v) for v in flat_parities[i])) else 060    return out.reshape((number,) * dimension)6162# VECTORISED RENDERERS6364def _parities(number: int, dimension: int, base: int = BASE):65    indices = np.indices((number,) * dimension)66    return [indices[axis] % base for axis in range(dimension)]6768def carpet(number: int, dimension: int, base: int = BASE) -> np.ndarray:69    parities = _parities(number, dimension, base)70    return (sum(parities) <= 1).astype(np.uint8)7172def net(number: int, dimension: int, base: int = BASE) -> np.ndarray:73    parities = _parities(number, dimension, base)74    return (sum(parities) >= dimension * (base - 1) - 1).astype(np.uint8)7576def void(number: int, dimension: int, base: int = BASE) -> np.ndarray:77    parities = _parities(number, dimension, base)78    first = parities[0]79    same = np.ones_like(first, dtype=bool)80    for p in parities[1:]:81        same &= (p == first)82    return same.astype(np.uint8)8384def level_set(number: int, dimension: int, levels: Iterable[int], base: int = BASE) -> np.ndarray:85    parities = _parities(number, dimension, base)86    total = sum(parities)87    wanted = set(int(v) for v in levels)88    out = np.zeros_like(total, dtype=bool)89    for level in wanted:90        out |= (total == level)91    return out.astype(np.uint8)9293# TREE (THE AXIS GENUS - LINES ONLY, NO SHEETS)9495def tree(number: int, dimension: int, free_axis: int, base: int = BASE) -> np.ndarray:96    if not (0 <= free_axis < dimension):97        raise MrlyError(f"free_axis must be in [0, {dimension - 1}], got {free_axis}.")98    parities = _parities(number, dimension, base)99    out = np.ones_like(parities[0], dtype=bool)100    for axis in range(dimension):101        if axis != free_axis:102            out &= (parities[axis] == 0)103    return out.astype(np.uint8)104105def tree_axes(dimension: int, free_axis: int) -> set:106    return {axis for axis in range(dimension) if axis != free_axis}