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}