refactor diagram files

This commit is contained in:
queue-miscreant 2025-02-18 22:23:57 -06:00
parent f1b2215363
commit b28f7fd65f
8 changed files with 331 additions and 213 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ _site/
/.quarto/
index_files/
.jupyter_cache/
__pycache__/

View File

@ -1,65 +0,0 @@
import numpy as np
import matplotlib.pyplot as plt
class Carry:
def __init__(self, arr):
if not isinstance(arr, np.ndarray):
arr = np.array(arr)
overflow = None
over_pos = None
coeff_sum = 0
#find largest carry
for i, row in enumerate(arr):
for j, val in enumerate(row):
coeff_sum += val
if val > 0:
if overflow is not None:
raise ValueError("array supplied has more than one overflow digit")
overflow = val
over_pos = (i, j)
if coeff_sum < 0:
raise ValueError("sum of coefficients too small")
self._arr = arr
self.over_pos = over_pos
self.overflow = overflow
def _update(self, base, update_tuples, zerowall):
#dim_x, dim_y = base.shape
pos_x, pos_y = self.over_pos
for (x,y) in update_tuples:
idx = base[(x,y)]
dec = (idx // self.overflow) # - 1
wall = zerowall.copy()
sh_x, sh_y = self._arr.shape
x -= pos_x
y -= pos_y
wall[x:x + sh_x, y:y + sh_y] = dec * self._arr
base -= wall
def update(self, base):
zerowall = np.zeros(base.shape, dtype=base.dtype)
while True:
update_tuples = list(zip(*np.where(base >= self.overflow)))
if not update_tuples:
break
self._update(base, update_tuples, zerowall)
def times(self, base, val, center=(0,0)):
#perform multiplication using the carry
base[center] *= val
self.update(base)
return base
def add(self, base, val=1, center=(0,0)):
base[center] += val
self.update(base)
return base
laplace = Carry(np.array([[0,-1,0],[-1,4,-1],[0,-1,0]]))
#test = np.zeros((100, 100))
#test[50,50] = 4

View File

View File

@ -0,0 +1,83 @@
from functools import partial
from typing import Callable, Literal
from matplotlib import animation, pyplot as plt
import numpy as np
from .carry import Carry
writer = animation.writers["ffmpeg"](fps=15, metadata={"artist": "Me"})
def times(base: np.ndarray, carry: Carry, val):
base *= val
carry.apply(base)
return base
def add(base: np.ndarray, carry: Carry, val=1, center=(0, 0)):
base[center] += val
carry.apply(base)
return base
def animate(
fig, frames: list[int] | None, **kwargs
) -> Callable[..., animation.FuncAnimation]:
# Why isn't this how the function is exposed by default?
return lambda func: animation.FuncAnimation(fig, func, frames, **kwargs)
def animate_carry_count(
carry: Carry,
dims: int = 100,
operation: Literal["add", "multiply"] = "add",
op_val=1,
center: tuple[int, int] | None = None,
# Animation parameters
frames: list[int] | None = None,
interval=200,
) -> animation.FuncAnimation:
if center is None:
if carry.over_pos != (0, 0):
center = (dims // 2, dims // 2)
else:
center = (0, 0)
if operation == "add":
next_func = partial(add, carry=carry, val=op_val, center=center)
elif operation == "multiply":
if op_val == 1:
raise ValueError(f"too small value {op_val} for repeated multiplication")
next_func = partial(times, carry=carry, val=op_val)
else:
raise ValueError(f"Cannot use {repr(operation)} for animation")
expansion = np.zeros((dims, dims))
expansion[center] = 1
title = [1]
fig = plt.gcf()
image = plt.imshow(
expansion,
extent=[-center[0], dims - center[0], dims - center[1], -center[1]], # type: ignore
)
def init():
plt.title(f"{title[0]}")
image.set_clim(0, carry.overflow - 1)
fig.tight_layout()
@animate(fig, frames, interval=interval, init_func=init)
def ret(_):
plt.title(f"{title[0]}")
image.set_data(expansion)
fig.tight_layout()
next_func(expansion)
if operation == "add":
title[0] += op_val
else:
title[0] *= op_val
return ret

View File

@ -0,0 +1,81 @@
from typing import Sequence
import numpy as np
def _validate_carry(arr: np.ndarray) -> tuple[int, tuple[int, int]]:
"""
Ensure that `arr` is interpretable as an explicit carry rule.
See `Carry` for more information.
"""
overflow = None
over_pos = None
coeff_sum = 0
# find largest digit for carry
for i, row in enumerate(arr):
for j, val in enumerate(row):
coeff_sum += val
if val < 0:
if overflow is not None:
raise ValueError("Array supplied has more than one overflow digit")
overflow = -val
over_pos = (i, j)
if coeff_sum < 0:
raise ValueError("Sum of coefficients too small")
if overflow is None or over_pos is None:
raise ValueError("No dominating term supplied")
return overflow, over_pos
class Carry:
"""
Two-dimensional carry rule.
This is an (integer) Numpy array which is greedily "applied" to another (integer) Numpy array to
rid it of integers.
Only "explicit" carries are allowed -- these are carries that are all negative except for a
single positive term, such that the sum of all terms is non-negative.
For example, the Laplacian kernel, as a carry rule is:
`laplace = Carry([[0,1,0],[1,-4,1],[0,1,0]])`
Interpreted as a carry rule, this spreads integers above 4 into their neighboring cells.
"""
def __init__(self, arr: np.ndarray | Sequence):
if not isinstance(arr, np.ndarray):
arr = np.array(arr)
overflow, over_pos = _validate_carry(arr)
self._arr: np.ndarray = arr
self.over_pos: tuple[int, int] = over_pos
self.overflow: int = overflow
def _apply(
self,
base: np.ndarray,
update_tuples: list[tuple[int, int]],
zerowall: np.ndarray,
):
"""Apply the carry at the locations in `base` specified by `update_tuples`."""
pos_x, pos_y = self.over_pos
for x, y in update_tuples:
idx = base[(x, y)]
dec = idx // self.overflow # - 1
wall = zerowall.copy()
sh_x, sh_y = self._arr.shape
x -= pos_x
y -= pos_y
wall[x : x + sh_x, y : y + sh_y] = dec * self._arr
base += wall
def apply(self, base: np.ndarray) -> np.ndarray:
"""Update an expansion `base` according to this carry."""
zerowall = np.zeros(base.shape, dtype=base.dtype)
while update_tuples := list(zip(*np.where(base >= self.overflow))):
self._apply(base, update_tuples, zerowall)
return base

View File

@ -0,0 +1,88 @@
import matplotlib.pyplot as plt
import numpy as np
from sympy.abc import x, y
from sympy.plotting import plot_implicit
from .anim import animate
def _first_nonzero(arr: np.ndarray, axis: int):
mask = arr!=0
return min(np.where(mask.any(axis=axis), mask.argmax(axis=axis), arr.shape[axis] + 1))
def latex_polynumber(
arr: np.ndarray,
center: tuple[int, int] | None = None,
show_zero: bool = False
):
upper_left = _first_nonzero(arr, 0), _first_nonzero(arr, 1)
lower_right = (
len(arr) - _first_nonzero(np.flip(arr, 0), 0) - 1,
len(arr[1]) - _first_nonzero(np.flip(arr, 1), 1) - 1
)
center_left, center_top = center or (0, 0)
if center is not None:
# this does not need offset, since we iterate over this range
center_top = center[0]
# but this does for the array environment argument
center_left = center[1] - upper_left[1]
num_columns = lower_right[1] - upper_left[1]
column_layout = ("c" * center_left) + "|" + ("c" * (num_columns - center_left))
# build array output
ret = "\\begin{array}{" + column_layout + "}"
for i in range(upper_left[0], lower_right[0] + 1):
if i == center_top:
ret += " \\hline "
ret += " & ".join([
str(arr[i,j] if arr[i,j] != 0 or show_zero else "")
for j in range(upper_left[1], lower_right[1] + 1)
])
# next row
ret += " \\\\ "
return ret + "\\end{array}"
def poly_from_array(array):
ret = 0
for i, row in enumerate(array):
for j, val in enumerate(row):
ret += val*(x**i * y**j)
return ret
def bindfig(fig):
def ret(**kwargs):
for i, j in kwargs.items():
if i == "figsize":
if j is not None:
fig.set_figwidth(j[0])
fig.set_figheight(j[1])
continue
fig.__dict__["set_" + i](j)
return fig
return ret
def anim_curves(next_func, dims=25, invalid=2, frames=None, interval=200):
zero = np.zeros((dims, dims), dtype=np.int32)
#zero[0,0] = 1
fig = plt.gcf()
#plt.colorbar()
#temp = plt.figure
#I hate doing it, but there's no other way to get the figure before it's plotted
plt.figure = bindfig(fig)
@animate(fig, frames, interval=interval)
def ret(fr):
next_func(zero, invalid)
fig.clf()
plot_implicit(
poly_from_array(zero) - fr,
backend='matplotlib',
adaptive=False
)
plt.title(f"{fr+1}")
print(fr)
return ret

View File

@ -0,0 +1,78 @@
import numpy as np
from .carry import Carry
xy_2 = Carry([[2,-1],[-1,0]])
xy_3 = Carry([[3,-1],[-1,0]])
x2y_3 = Carry([[3,-2],[-1,0]])
x3y_4 = Carry([[4,-3],[-1,0]])
laplace = Carry([[0,-1,0],[-1,4,-1],[0,-1,0]])
laplace3 = Carry([[0,0,0],[-1,3,-1],[0,-1,0]])
almost_folium = Carry([[0,0,-1], [0,2,0], [-1,0,0]])
folium = Carry([
[ 0,0,0,-1],
[ 0,2,0, 0],
[ 0,0,0, 0],
[-1,0,0, 0]
])
folium3 = Carry([
[ 0,0,0,-1],
[ 0,3,0, 0],
[ 0,0,0, 0],
[-1,0,0, 0]
])
folium4 = Carry([
[ 0, 0, 0,-1, 0],
[ 0, 0, 0, 0,-1],
[ 0, 0, 4, 0, 0],
[-1, 0, 0, 0, 0],
[ 0,-1, 0, 0, 0]
])
def triangle_spread(n):
ret = np.zeros((n + 1, n + 1), dtype=np.int32)
ret[0, 0] = -1
ret[1, 1] = 3
ret[n, 0] = -1
ret[0, n] = -1
return ret
triangle1 = Carry(triangle_spread(1))
triangle2 = Carry(triangle_spread(2))
triangle3 = Carry(triangle_spread(3)) #factorable!
triangle4 = Carry(triangle_spread(4))
tri3_rot = Carry([
[ 0, 0,-1, 0, 0],
[ 0, 0, 3, 0, 0],
[-1, 0, 0, 0,-1]
])
tri3_rot_real = Carry([
[ 0, 0, 0,-1, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 3, 0, 0, 0],
[-1, 0, 0, 0, 0, 0,-1]
])
tri3_rot2 = Carry([
[ 0,-1, 0],
[ 0, 3, 0],
[-1, 0,-1]
])
tri3_tall = Carry([
[ 0,-1, 0],
[ 0, 0, 0],
[ 0, 3, 0],
[-1, 0,-1]
])
tri3_tall2 = Carry([
[ 0,-1, 0],
[ 0, 3, 0],
[ 0, 0, 0],
[-1, 0,-1]
])

View File

@ -1,148 +0,0 @@
from functools import partial
import numpy as np
import sympy
import matplotlib.pyplot as plt
from sympy.plotting import plot_implicit
from matplotlib import animation
from carry import Carry
def make_animation(fig, frames, **kwargs):
fig.tight_layout()
#why isn't this how the function is exposed by default
return lambda func: animation.FuncAnimation(fig, func, frames, init_func=lambda: None, **kwargs)
def poly_from_array(array):
ret = 0
for i, row in enumerate(array):
for j, val in enumerate(row):
ret += val*(x**i * y**j)
return ret
x, y = sympy.symbols("x y")
xy_2 = Carry([[2,-1],[-1,0]])
xy_3 = Carry([[3,-1],[-1,0]])
x2y_3 = Carry([[3,-2],[-1,0]])
x3y_4 = Carry([[4,-3],[-1,0]])
laplace = Carry([[0,-1,0],[-1,4,-1],[0,-1,0]])
laplace3 = Carry([[0,0,0],[-1,3,-1],[0,-1,0]])
almost_folium = Carry([[0,0,-1], [0,2,0], [-1,0,0]])
folium = Carry([[0,0,0,-1], [0,2,0,0], [0,0,0,0], [-1,0,0,0]])
folium3 = Carry([[0,0,0,-1], [0,3,0,0], [0,0,0,0], [-1,0,0,0]])
folium4 = Carry([ [ 0, 0,0,-1, 0],
[ 0, 0,0, 0,-1],
[ 0, 0,4, 0, 0],
[-1, 0,0, 0, 0],
[ 0,-1,0, 0, 0]])
def triangle_spread(n):
ret = np.zeros((n + 1, n + 1), dtype=np.int32)
ret[0, 0] = -1
ret[1, 1] = 3
ret[n, 0] = -1
ret[0, n] = -1
return ret
triangle1 = Carry(triangle_spread(1))
triangle2 = Carry(triangle_spread(2))
triangle3 = Carry(triangle_spread(3)) #factorable!
triangle4 = Carry(triangle_spread(4))
tri3_rot = Carry([ [ 0, 0,-1, 0, 0],
[ 0, 0, 3, 0, 0],
[-1, 0, 0, 0,-1] ])
tri3_rot_real = Carry([ [ 0, 0, 0,-1, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 3, 0, 0, 0],
[-1, 0, 0, 0, 0, 0,-1] ])
tri3_rot2 = Carry([ [ 0,-1, 0],
[ 0, 3, 0],
[-1, 0,-1] ])
tri3_tall = Carry([ [ 0,-1, 0],
[ 0, 0, 0],
[ 0, 3, 0],
[-1, 0,-1] ])
tri3_tall2 = Carry([ [ 0,-1, 0],
[ 0, 3, 0],
[ 0, 0, 0],
[-1, 0,-1] ])
def start_anim(dims=100, carry=xy_2, frames=None, interval=200, inter='add', i_val=1, center=(0,0)):
if center == (0,0) and carry.over_pos != center:
center = (dims // 2, dims // 2)
if inter == 'add':
next_func = partial(carry.add, val=i_val, center=center)
elif inter == 'mult':
if i_val == 1:
raise ValueError(f"too small value {i_val} for repeated multiplication")
next_func = partial(carry.mult, val=i_val, center=center)
else:
raise ValueError(f"Cannot use {repr(inter)} for animation")
zero = np.zeros((dims, dims))
zero[center] = 1
val = [1]
fig = plt.gcf()
plt.title('0')
image = plt.imshow(zero)
image.set_clim(0, carry.overflow-1)
@make_animation(fig, frames, interval=interval)
def ret(fr):
next_func(zero)
# if next_func != xy_2.add:
# fr = sum(i*(invalid**j) for i,j in zip(zero[0], range(dims))) - 1
if inter == 'add':
val[0] += i_val
else:
val[0] *= i_val
plt.title(f"{val[0]}")
image.set_data(zero)
fig.tight_layout()
return ret
def anim_curves(dims=25, invalid=2, frames=None, interval=200, next_func=xy_2.add):
zero = np.zeros((dims, dims), dtype=np.int32)
#zero[0,0] = 1
fig = plt.gcf()
#plt.colorbar()
#temp = plt.figure
#I hate doing it, but there's no other way to get the figure before it's plotted
plt.figure = bindfig(fig)
@make_animation(fig, frames, interval=interval)
def ret(fr):
next_func(zero, invalid)
fig.clf()
plot = plot_implicit(poly_from_array(zero) - fr, backend='matplotlib')
plt.title(f"{fr+1}")
print(fr)
return ret
writer = animation.writers['ffmpeg'](fps=15, metadata={'artist': 'Me'})
def bindfig(fig):
def ret(**kwargs):
for i, j in kwargs.items():
if i == "figsize":
if j is not None:
fig.set_figwidth(j[0])
fig.set_figheight(j[1])
continue
fig.__dict__["set_" + i](j)
return fig
return ret