extra diagram file refactors

This commit is contained in:
queue-miscreant 2025-02-19 23:01:19 -06:00
parent b28f7fd65f
commit 879cbc909f
4 changed files with 146 additions and 142 deletions

View File

@ -1,29 +1,20 @@
from functools import partial from functools import partial
import time
from threading import Thread
from typing import Callable, Literal from typing import Callable, Literal
from matplotlib import animation, pyplot as plt from matplotlib import animation, pyplot as plt
import numpy as np import numpy as np
from sympy.plotting import plot_implicit
from .carry import Carry from .carry import Carry
from .expansion import add, times, poly_from_array
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( def animate(
fig, frames: list[int] | None, **kwargs fig, frames: list[int] | None, **kwargs
) -> Callable[..., animation.FuncAnimation]: ) -> Callable[..., animation.FuncAnimation]:
"""Decorator-style wrapper for FuncAnimation."""
# Why isn't this how the function is exposed by default? # Why isn't this how the function is exposed by default?
return lambda func: animation.FuncAnimation(fig, func, frames, **kwargs) return lambda func: animation.FuncAnimation(fig, func, frames, **kwargs)
@ -38,6 +29,20 @@ def animate_carry_count(
frames: list[int] | None = None, frames: list[int] | None = None,
interval=200, interval=200,
) -> animation.FuncAnimation: ) -> animation.FuncAnimation:
"""
Create a video of integer expansions using a `carry`.
Expansions use a `dims` x `dims` numpy array.
The video starts from the expansion of one, and subsequent frames come from
either adding `op_val` to the constant term or multiplying each term by `op_val`,
both followed by carrying.
`operation` decides which of these to use.
The constant term of the expansion is specified with `center`.
print("Starting animation")
By default, the constant term is at position (0, 0), in the upper left corner.
If the carry expands leftward or upward, it is instead in the center of the array.
"""
if center is None: if center is None:
if carry.over_pos != (0, 0): if carry.over_pos != (0, 0):
center = (dims // 2, dims // 2) center = (dims // 2, dims // 2)
@ -81,3 +86,90 @@ def animate_carry_count(
title[0] *= op_val title[0] *= op_val
return ret return ret
# writer = animation.writers["ffmpeg"](fps=15, metadata={"artist": "Me"})
def bindfig(fig):
"""
Create a function which is compatible with the signature of `pyplot.figure`,
but alters the already-existing figure `fig` instead.
"""
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 animate_carry_curves(
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:
"""
Create a video of implicit plots of "incremented curves" based on a `carry`.
These are curves for which we re-interpret an expansion as a polynomial equal
equal to the integer it represents.
"""
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), dtype=np.int32)
expansion[center] = 0
count = [0]
# I hate doing it, but there's no other way to control the figure uses
fig = plt.gcf()
plt.figure = bindfig(fig)
plt.tight_layout()
# More sympy.plot_implicit nonsense
t = Thread(target=lambda: time.sleep(1) or plt.close())
t.run()
plt.title(f"{count[0]}")
plot_implicit(
poly_from_array(carry._arr),
backend="matplotlib",
)
@animate(fig, frames)
def ret(_):
next_poly = poly_from_array(expansion) - count[0]
if next_poly != 0:
fig.clf()
plot_implicit(
next_poly,
backend="matplotlib",
)
plt.title(f"{count[0]}")
if operation == "add":
count[0] += op_val
else:
count[0] *= op_val
next_func(expansion)
return ret

View File

@ -20,7 +20,7 @@ def _validate_carry(arr: np.ndarray) -> tuple[int, tuple[int, int]]:
overflow = -val overflow = -val
over_pos = (i, j) over_pos = (i, j)
if coeff_sum < 0: if coeff_sum > 0:
raise ValueError("Sum of coefficients too small") raise ValueError("Sum of coefficients too small")
if overflow is None or over_pos is None: if overflow is None or over_pos is None:

View File

@ -1,88 +0,0 @@
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

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