formulas.py
18.2 kB · python · 530 lines
1import numpy as np2from fractions import Fraction3from math import comb, ceil, floor, log4from typing import Callable, Dict, Iterable, Tuple5from .errors import MrlyError6from . import binary78# GRID910def grid_lines(number: int, level: int) -> int:11 return number**level1213def grid_squares(number: int, level: int) -> int:14 return (number**2)**level1516def grid_cubes(number: int, level: int) -> int:17 return (number**3)**level1819# CARPET2021def carpet_fill_squares(number: int, level: int) -> int:22 return (number**2-floor(number/2)**2)**level2324def carpet_void_squares(number: int, level: int) -> int:25 return grid_squares(number, level) - carpet_fill_squares(number, level)2627def carpet_fill_cubes(number: int, level: int) -> int:28 E = ceil(number/2)29 O = floor(number/2)30 return (E**3 + 3*O*E**2)**level3132def carpet_void_cubes(number: int, level: int) -> int:33 return grid_cubes(number, level) - carpet_fill_cubes(number, level)3435# NET3637def net_fill_squares(number: int, level: int) -> int:38 return (number**2-floor((number+1)/2)**2)**level3940def net_void_squares(number: int, level: int) -> int:41 return grid_squares(number, level) - net_fill_squares(number, level)4243def net_fill_cubes(number: int, level: int) -> int:44 E = ceil(number/2)45 O = floor(number/2)46 return (O**3 + 3*E*O**2)**level4748def net_void_cubes(number: int, level: int) -> int:49 return grid_cubes(number, level) - net_fill_cubes(number, level)5051# TREE5253def tree_fill_squares(number: int, level: int) -> int:54 return (number*floor((number+1)/2))**level5556def tree_void_squares(number: int, level: int) -> int:57 return grid_squares(number, level) - tree_fill_squares(number, level)5859def tree_fill_cubes(number: int, level: int) -> int:60 return (number * ceil(number/2)**2)**level6162def tree_void_cubes(number: int, level: int) -> int:63 return grid_cubes(number, level) - tree_fill_cubes(number, level)6465# VOID6667def void_fill_squares(number: int, level: int) -> int:68 return (ceil(number**2/2))**level6970def void_void_squares(number: int, level: int) -> int:71 return grid_squares(number, level) - void_fill_squares(number, level)7273def void_fill_cubes(number: int, level: int) -> int:74 E = ceil(number/2)75 O = floor(number/2)76 return (E**3 + O**3)**level7778def void_void_cubes(number: int, level: int) -> int:79 return grid_cubes(number, level) - void_fill_cubes(number, level)8081# LEVEL-SET (THE SYMMETRIC GENUS)8283def _level_set_base(number: int, dimension: int, levels: Iterable[int]) -> int:84 even = ceil(number / 2)85 odd = floor(number / 2)86 chosen = set(int(j) for j in levels)87 return sum(comb(dimension, j) * (odd ** j) * (even ** (dimension - j))88 for j in chosen if 0 <= j <= dimension)8990def level_set_fill(number: int, level: int, dimension: int, levels: Iterable[int]) -> int:91 return _level_set_base(number, dimension, levels) ** level9293def level_set_fill_squares(number: int, level: int, levels: Iterable[int]) -> int:94 return level_set_fill(number, level, 2, levels)9596def level_set_fill_cubes(number: int, level: int, levels: Iterable[int]) -> int:97 return level_set_fill(number, level, 3, levels)9899def level_set_void_squares(number: int, level: int, levels: Iterable[int]) -> int:100 return grid_squares(number, level) - level_set_fill_squares(number, level, levels)101102def level_set_void_cubes(number: int, level: int, levels: Iterable[int]) -> int:103 return grid_cubes(number, level) - level_set_fill_cubes(number, level, levels)104105# RATIOS106107def carpet_ratio_2d(number: int, level: int) -> float:108 return carpet_fill_squares(number, level) / grid_squares(number, level)109110def carpet_ratio_3d(number: int, level: int) -> float:111 return carpet_fill_cubes(number, level) / grid_cubes(number, level)112113def net_ratio_2d(number: int, level: int) -> float:114 return net_fill_squares(number, level) / grid_squares(number, level)115116def net_ratio_3d(number: int, level: int) -> float:117 return net_fill_cubes(number, level) / grid_cubes(number, level)118119def tree_ratio_2d(number: int, level: int) -> float:120 return tree_fill_squares(number, level) / grid_squares(number, level)121122def tree_ratio_3d(number: int, level: int) -> float:123 return tree_fill_cubes(number, level) / grid_cubes(number, level)124125def void_ratio_2d(number: int, level: int) -> float:126 return void_fill_squares(number, level) / grid_squares(number, level)127128def void_ratio_3d(number: int, level: int) -> float:129 return void_fill_cubes(number, level) / grid_cubes(number, level)130131def carpet_ratio_6d(number: int, level: int) -> float:132 return carpet_fill_triangles(number, level) / grid_triangles(number, level)133134def net_ratio_6d(number: int, level: int) -> float:135 return net_fill_triangles(number, level) / grid_triangles(number, level)136137def tree_ratio_6d(number: int, level: int) -> float:138 return tree_fill_triangles(number, level) / grid_triangles(number, level)139140def void_ratio_6d(number: int, level: int) -> float:141 return void_fill_triangles(number, level) / grid_triangles(number, level)142143# DIMENSIONS144145def calculate_dimension(design: Callable, number: int, dimension: int) -> float:146 if number == 1:147 return float(dimension)148 grid = design(number)149 fill = np.sum(grid)150 if fill <= 0:151 return 0.0152 return log(fill) / log(number)153154def carpet_2d_dimension(number: int) -> float:155 return calculate_dimension(binary.carpet_2d, number, 2)156157def carpet_3d_dimension(number: int) -> float:158 return calculate_dimension(binary.carpet_3d, number, 3)159160def net_2d_dimension(number: int) -> float:161 return calculate_dimension(binary.net_2d, number, 2)162163def net_3d_dimension(number: int) -> float:164 return calculate_dimension(binary.net_3d, number, 3)165166def tree_2d_dimension(number: int) -> float:167 return calculate_dimension(binary.tree_2d, number, 2)168169def tree_3d_dimension(number: int) -> float:170 return calculate_dimension(binary.tree_3d, number, 3)171172def void_2d_dimension(number: int) -> float:173 return calculate_dimension(binary.void_2d, number, 2)174175def void_3d_dimension(number: int) -> float:176 return calculate_dimension(binary.void_3d, number, 3)177178# TRIANGLES179180_fill_diag_cache: Dict[str, np.ndarray] = {}181_total_diag_cache: Dict[int, np.ndarray] = {}182183def _fill_by_diag(grid: np.ndarray, n: int) -> np.ndarray:184 max_d = 3 * (n - 1)185 f = np.zeros(max_d + 1, dtype=np.int64)186 for cx in range(n):187 for cy in range(n):188 for cz in range(n):189 if grid[cx, cy, cz]:190 f[cx + cy + cz] += 1191 return f192193def _total_by_diag(n: int) -> np.ndarray:194 if n in _total_diag_cache:195 return _total_diag_cache[n]196 max_d = 3 * (n - 1)197 t = np.zeros(max_d + 1, dtype=np.int64)198 for cx in range(n):199 for cy in range(n):200 for cz in range(n):201 t[cx + cy + cz] += 1202 _total_diag_cache[n] = t203 return t204205def _diag_convolve(f_prev: np.ndarray, f_base: np.ndarray, n: int) -> np.ndarray:206 len_prev = len(f_prev)207 len_base = len(f_base)208 max_s = n * (len_prev - 1) + (len_base - 1)209 f_new = np.zeros(max_s + 1, dtype=np.int64)210 for a in range(len_prev):211 if f_prev[a] == 0:212 continue213 for b in range(len_base):214 if f_base[b] == 0:215 continue216 f_new[n * a + b] += f_prev[a] * f_base[b]217 return f_new218219def _visited_diags(m: int) -> Tuple[list, list]:220 k = (3 * (4 * m - 1)) // 2221 d = k // 4222 if m % 2 == 0:223 return [d - 1, d], [4, 4]224 return [d - 2, d - 1, d], [1, 6, 1]225226def _cut_counts(design_fn: Callable, number: int, level: int) -> Tuple[int, int]:227 key = f"{design_fn.__name__}_{number}"228 if key not in _fill_diag_cache:229 _fill_diag_cache[key] = _fill_by_diag(design_fn(number), number)230 f_base = _fill_diag_cache[key]231 t_base = _total_by_diag(number)232 f_cur = f_base.copy()233 t_cur = t_base.copy()234 for _ in range(1, level):235 f_cur = _diag_convolve(f_cur, f_base, number)236 t_cur = _diag_convolve(t_cur, t_base, number)237 m = number ** level238 diags, weights = _visited_diags(m)239 fill = 0240 total = 0241 for d, w in zip(diags, weights):242 if d < len(f_cur):243 fill += w * int(f_cur[d])244 if d < len(t_cur):245 total += w * int(t_cur[d])246 return int(fill), int(total - fill)247248def grid_triangles(number: int, level: int) -> int:249 return 6 * number ** (2 * level)250251def carpet_fill_triangles(number: int, level: int) -> int:252 fill, _ = _cut_counts(binary.carpet_3d, number, level)253 return fill254255def carpet_void_triangles(number: int, level: int) -> int:256 _, void = _cut_counts(binary.carpet_3d, number, level)257 return void258259def net_fill_triangles(number: int, level: int) -> int:260 fill, _ = _cut_counts(binary.net_3d, number, level)261 return fill262263def net_void_triangles(number: int, level: int) -> int:264 _, void = _cut_counts(binary.net_3d, number, level)265 return void266267def tree_fill_triangles(number: int, level: int) -> int:268 fill, _ = _cut_counts(binary.tree_3d, number, level)269 return fill270271def tree_void_triangles(number: int, level: int) -> int:272 _, void = _cut_counts(binary.tree_3d, number, level)273 return void274275def void_fill_triangles(number: int, level: int) -> int:276 fill, _ = _cut_counts(binary.void_3d, number, level)277 return fill278279def void_void_triangles(number: int, level: int) -> int:280 _, void = _cut_counts(binary.void_3d, number, level)281 return void282283# SURFACE AREA284285def _visible_faces(occ: np.ndarray) -> int:286 total = 0287 for axis in range(3):288 lo = np.take(np.pad(occ, [(1, 0) if i == axis else (0, 0) for i in range(3)]),289 range(occ.shape[axis]), axis=axis)290 hi = np.take(np.pad(occ, [(0, 1) if i == axis else (0, 0) for i in range(3)]),291 range(1, occ.shape[axis] + 1), axis=axis)292 total += int(np.clip(occ - lo, 0, None).sum())293 total += int(np.clip(occ - hi, 0, None).sum())294 return total295296def _vh_state(grid: np.ndarray) -> Tuple[int, int]:297 occ = (grid != 0).astype(np.int64)298 vf = _visible_faces(occ)299 return vf, 6 * int(occ.sum()) - vf300301def _solve_2x2(a, b, c, d, p, q):302 det = a * d - b * c303 x = Fraction(p * d - b * q, det)304 y = Fraction(a * q - p * c, det)305 return x, y306307def _surface_transfer(design_fn: Callable, number: int):308 base = design_fn(number)309 g1 = base310 g2 = np.kron(g1, base).astype(np.uint8)311 g3 = np.kron(g2, base).astype(np.uint8)312 (v0, h0), (v1, h1), (v2, h2) = _vh_state(g1), _vh_state(g2), _vh_state(g3)313 m00, m01 = _solve_2x2(v0, h0, v1, h1, v1, v2)314 m10, m11 = _solve_2x2(v0, h0, v1, h1, h1, h2)315 return ((m00, m01), (m10, m11)), (v0, h0)316317def _surface_general(design_fn: Callable, number: int, level: int) -> int:318 grid = design_fn(number)319 s1, h1 = _vh_state(grid)320 if level == 1:321 return s1322 if h1 == 0:323 return s1 * int((grid != 0).sum()) ** (level - 1)324 M, v1 = _surface_transfer(design_fn, number)325 R = [[Fraction(1), Fraction(0)], [Fraction(0), Fraction(1)]]326 for _ in range(level - 1):327 R = [[R[0][0] * M[0][0] + R[0][1] * M[1][0], R[0][0] * M[0][1] + R[0][1] * M[1][1]],328 [R[1][0] * M[0][0] + R[1][1] * M[1][0], R[1][0] * M[0][1] + R[1][1] * M[1][1]]]329 return int(R[0][0] * v1[0] + R[0][1] * v1[1])330331def carpet_surface(number: int, level: int) -> int:332 return _surface_general(binary.carpet_3d, number, level)333334def net_surface(number: int, level: int) -> int:335 return _surface_general(binary.net_3d, number, level)336337def tree_surface(number: int, level: int) -> int:338 return _surface_general(binary.tree_3d, number, level)339340def void_surface(number: int, level: int) -> int:341 return _surface_general(binary.void_3d, number, level)342343def surface_visible_faces(design_fn: Callable, number: int, level: int) -> int:344 grid = design_fn(number)345 if level > 1:346 base = grid347 for _ in range(1, level):348 grid = np.kron(grid, base).astype(np.uint8)349 occ = (grid != 0).astype(np.int64)350 total = 0351 for axis in range(3):352 lo = np.take(np.pad(occ, [(1, 0) if i == axis else (0, 0) for i in range(3)]),353 range(occ.shape[axis]), axis=axis)354 hi = np.take(np.pad(occ, [(0, 1) if i == axis else (0, 0) for i in range(3)]),355 range(1, occ.shape[axis] + 1), axis=axis)356 total += int(np.clip(occ - lo, 0, None).sum())357 total += int(np.clip(occ - hi, 0, None).sum())358 return total359360_GRID_FN = {2: grid_squares, 3: grid_cubes}361362_FILL_FN = {363 (binary.carpet_2d.__name__, 2): carpet_fill_squares,364 (binary.net_2d.__name__, 2): net_fill_squares,365 (binary.tree_2d.__name__, 2): tree_fill_squares,366 (binary.htree_2d.__name__, 2): tree_fill_squares,367 (binary.vtree_2d.__name__, 2): tree_fill_squares,368 (binary.void_2d.__name__, 2): void_fill_squares,369 (binary.carpet_3d.__name__, 3): carpet_fill_cubes,370 (binary.net_3d.__name__, 3): net_fill_cubes,371 (binary.tree_3d.__name__, 3): tree_fill_cubes,372 (binary.xtree_3d.__name__, 3): tree_fill_cubes,373 (binary.ytree_3d.__name__, 3): tree_fill_cubes,374 (binary.ztree_3d.__name__, 3): tree_fill_cubes,375 (binary.void_3d.__name__, 3): void_fill_cubes,376}377378def _shift_occ(occ: np.ndarray, axis: int) -> np.ndarray:379 s = np.zeros_like(occ)380 src = [slice(None)] * occ.ndim381 dst = [slice(None)] * occ.ndim382 src[axis] = slice(1, None)383 dst[axis] = slice(0, -1)384 s[tuple(dst)] = occ[tuple(src)]385 return s386387def _core_edges_grid(grid: np.ndarray) -> int:388 occ = (grid != 0)389 total = 0390 for axis in range(occ.ndim):391 total += int((occ & _shift_occ(occ, axis)).sum())392 return total393394# CORE GRAPH395396def core_nodes(design_fn: Callable, number: int, level: int) -> int:397 dim = design_fn(number).ndim398 fill = _FILL_FN[(design_fn.__name__, dim)](number, level)399 return fill400401def core_edges(design_fn: Callable, number: int, level: int) -> int:402 base = design_fn(number)403 e1 = _core_edges_grid(base)404 if e1 == 0:405 return 0406 if level == 1:407 return e1408 g = base409 seq = [e1]410 for _ in range(3):411 g = np.kron(g, base).astype(np.uint8)412 seq.append(_core_edges_grid(g))413 det = seq[1] * seq[1] - seq[0] * seq[2]414 M = [[seq[1], -seq[0]], [seq[2], -seq[1]]]415 rhs = [seq[2], seq[3]]416 d = M[0][0] * M[1][1] - M[0][1] * M[1][0]417 s = Fraction(rhs[0] * M[1][1] - M[0][1] * rhs[1], d)418 p = Fraction(M[0][0] * rhs[1] - rhs[0] * M[1][0], d)419 out = list(seq)420 while len(out) < level:421 out.append(int(s * out[-1] - p * out[-2]))422 return int(out[level - 1])423424# TUNNEL GRAPH425426def tunnel_nodes(design_fn: Callable, number: int, level: int) -> int:427 dim = design_fn(number).ndim428 grid = _GRID_FN[dim](number, level)429 fill = _FILL_FN[(design_fn.__name__, dim)](number, level)430 return grid - fill431432def _tunnel_edges_grid(grid: np.ndarray) -> int:433 inverted = 1 - (grid != 0).astype(np.uint8)434 return _core_edges_grid(inverted)435436def tunnel_edges(design_fn: Callable, number: int, level: int) -> int:437 grid = design_fn(number)438 base = grid439 for _ in range(1, level):440 grid = np.kron(grid, base).astype(np.uint8)441 return _tunnel_edges_grid(grid)442443def _slice_start(number: int, level: int) -> int:444 return 0445446def _slice_triangle(x: int, y: int, start: int):447 north = (x + y + start) % 2 == 0448 if north:449 return [(x, 2 * y + 2), (x + 1, 2 * y), (x + 2, 2 * y + 2)]450 return [(x, 2 * y), (x + 1, 2 * y + 2), (x + 2, 2 * y)]451452def _slice_edges_of(corners):453 a, b, c = corners454 return [tuple(sorted((a, b))), tuple(sorted((b, c))), tuple(sorted((a, c)))]455456def _slice_cut_types(design_fn: Callable, number: int, level: int):457 from mrlypy.six.geometry import cut458 from mrlypy.three.models import Cell3d459 grid = design_fn(number)460 base = grid461 for _ in range(1, level):462 grid = np.kron(grid, base).astype(np.uint8)463 cell6 = cut(Cell3d(types=grid))464 return cell6._cell.types, int(cell6.start)465466def _slice_adjacency_counts(types: np.ndarray, start: int, value: int):467 height, width = types.shape468 cells = [(x, y) for y in range(height) for x in range(width) if int(types[y, x]) == value]469 edge_to_cells: Dict[Tuple, list] = {}470 for (x, y) in cells:471 for e in _slice_edges_of(_slice_triangle(x, y, start)):472 edge_to_cells.setdefault(e, []).append((x, y))473 edges = sum(1 for shared in edge_to_cells.values() if len(shared) == 2)474 index = {c: i for i, c in enumerate(cells)}475 adj = {i: [] for i in range(len(cells))}476 for shared in edge_to_cells.values():477 if len(shared) == 2:478 a, b = shared479 adj[index[a]].append(index[b])480 adj[index[b]].append(index[a])481 seen = [False] * len(cells)482 comps = 0483 for s in range(len(cells)):484 if seen[s]:485 continue486 comps += 1487 stack = [s]488 seen[s] = True489 while stack:490 u = stack.pop()491 for v in adj[u]:492 if not seen[v]:493 seen[v] = True494 stack.append(v)495 return edges, comps496497def slice_core_nodes(design_fn: Callable, number: int, level: int) -> int:498 types, start = _slice_cut_types(design_fn, number, level)499 return int((types == 1).sum())500501def slice_tunnel_nodes(design_fn: Callable, number: int, level: int) -> int:502 types, start = _slice_cut_types(design_fn, number, level)503 return int((types == 0).sum())504505def slice_core_edges(design_fn: Callable, number: int, level: int) -> int:506 types, start = _slice_cut_types(design_fn, number, level)507 edges, _ = _slice_adjacency_counts(types, start, 1)508 return edges509510def slice_tunnel_edges(design_fn: Callable, number: int, level: int) -> int:511 types, start = _slice_cut_types(design_fn, number, level)512 edges, _ = _slice_adjacency_counts(types, start, 0)513 return edges514515def slice_core_components(design_fn: Callable, number: int, level: int) -> int:516 types, start = _slice_cut_types(design_fn, number, level)517 _, comps = _slice_adjacency_counts(types, start, 1)518 return comps519520def solid_slice_core_edges(number: int) -> int:521 if number % 2 == 0:522 raise MrlyError("solid slice closed form is defined for odd number = 2k-1.")523 k = (number + 1) // 2524 return 36 * k ** 2 - 42 * k + 12525526def solid_slice_core_nodes(number: int) -> int:527 if number % 2 == 0:528 raise MrlyError("solid slice closed form is defined for odd number = 2k-1.")529 k = (number + 1) // 2530 return 24 * k ** 2 - 24 * k + 6