Framework-LED-Matrix 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli.py +669 -0
- framework_led_matrix/__init__.py +0 -0
- framework_led_matrix/apps/__init__.py +0 -0
- framework_led_matrix/apps/background_runner.py +125 -0
- framework_led_matrix/apps/runtime.py +185 -0
- framework_led_matrix/core/__init__.py +0 -0
- framework_led_matrix/core/led_commands.py +406 -0
- framework_led_matrix/core/math_engine.py +294 -0
- framework_led_matrix/simulations/BihamMiddletonLevineTrafficModel.py +238 -0
- framework_led_matrix/simulations/HardyPomeauPazzis.py +241 -0
- framework_led_matrix/simulations/__init__.py +0 -0
- framework_led_matrix/simulations/inner_totalistic.py +47 -0
- framework_led_matrix/simulations/outer_totalistic.py +112 -0
- framework_led_matrix/utils/__init__.py +0 -0
- framework_led_matrix/utils/anagrams.py +39 -0
- framework_led_matrix/utils/text_rendering.py +281 -0
- framework_led_matrix-0.1.1.dist-info/METADATA +159 -0
- framework_led_matrix-0.1.1.dist-info/RECORD +22 -0
- framework_led_matrix-0.1.1.dist-info/WHEEL +5 -0
- framework_led_matrix-0.1.1.dist-info/entry_points.txt +2 -0
- framework_led_matrix-0.1.1.dist-info/licenses/LICENSE +674 -0
- framework_led_matrix-0.1.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import math
|
|
3
|
+
from typing import Callable, List
|
|
4
|
+
from framework_led_matrix.core.led_commands import log, WIDTH, HEIGHT, draw_matrix_on_board
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
unguessable_constant = 42067691101123456789
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def plot_function(func, x_min, x_max, num_points=400, title="Plot of Function", label="f(x)"):
|
|
13
|
+
"""
|
|
14
|
+
Visualizes a given mathematical function using matplotlib.
|
|
15
|
+
|
|
16
|
+
Parameters:
|
|
17
|
+
func (callable): The function to plot (e.g., lambda x: x**2).
|
|
18
|
+
This function MUST be vectorized (i.e., accept a numpy array).
|
|
19
|
+
x_min (float): The minimum value of the x-range.
|
|
20
|
+
x_max (float): The maximum value of the x-range.
|
|
21
|
+
num_points (int): The number of points to use for the plot's resolution.
|
|
22
|
+
title (str): The title for the plot.
|
|
23
|
+
label (str): The label for the plot line.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# You can't plot a range where the start is at or after the end.
|
|
27
|
+
if x_min >= x_max:
|
|
28
|
+
print(f"Invalid range: x_min ({x_min}) must be strictly less than x_max ({x_max}).")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
# 1. Create the domain (x-values)
|
|
32
|
+
# np.linspace generates an array of 'num_points' evenly spaced
|
|
33
|
+
# values between x_min and x_max.
|
|
34
|
+
x_values = np.linspace(x_min, x_max, num_points)
|
|
35
|
+
|
|
36
|
+
# 2. Create the range (y-values)
|
|
37
|
+
# We apply the user's lambda function to the entire x_values array.
|
|
38
|
+
try:
|
|
39
|
+
y_values = func(x_values)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
print(f"Failed to evaluate the function: {e}")
|
|
42
|
+
print("Ensure your function is vectorized (use numpy functions like np.sin).")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# 3. Create the plot
|
|
46
|
+
plt.figure(figsize=(10, 6))
|
|
47
|
+
|
|
48
|
+
# Handle discontinuities like 1/x by masking 'inf' values
|
|
49
|
+
y_values = np.ma.masked_invalid(y_values)
|
|
50
|
+
|
|
51
|
+
plt.plot(x_values, y_values, label=label)
|
|
52
|
+
|
|
53
|
+
# 4. Add styling and labels
|
|
54
|
+
plt.title(title)
|
|
55
|
+
plt.xlabel("x")
|
|
56
|
+
plt.ylabel("f(x)")
|
|
57
|
+
plt.grid(True, linestyle='--', alpha=0.7)
|
|
58
|
+
|
|
59
|
+
# Add centered x and y axes for better visualization
|
|
60
|
+
plt.axhline(0, color='black', linewidth=0.8)
|
|
61
|
+
plt.axvline(0, color='black', linewidth=0.8)
|
|
62
|
+
|
|
63
|
+
# Set y-limits to be reasonable if there are large values
|
|
64
|
+
# (e.g., for 1/x near zero)
|
|
65
|
+
mean = np.mean(y_values)
|
|
66
|
+
std = np.std(y_values)
|
|
67
|
+
plt.ylim(mean - 4*std, mean + 4*std) # Clamp to 4 standard deviations
|
|
68
|
+
|
|
69
|
+
plt.legend()
|
|
70
|
+
plt.show()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def safe_log(x) -> np.ndarray:
|
|
74
|
+
"""Logarithm that is safe for values <= 0."""
|
|
75
|
+
return np.log(np.abs(x) + 1e-6)
|
|
76
|
+
|
|
77
|
+
def safe_sqrt(x) -> np.ndarray:
|
|
78
|
+
"""Square root that is safe for negative values."""
|
|
79
|
+
return np.sqrt(np.abs(x))
|
|
80
|
+
|
|
81
|
+
def safe_div(a, b) -> np.ndarray:
|
|
82
|
+
"""Division that is safe for division by zero."""
|
|
83
|
+
return np.where(np.abs(b) < 1e-6, a / 1e-6, a / b)
|
|
84
|
+
|
|
85
|
+
def safe_sinc(x) -> np.ndarray:
|
|
86
|
+
"""Wrapper for numpy's sinc function."""
|
|
87
|
+
return np.sinc(x)
|
|
88
|
+
|
|
89
|
+
# --- New Safe Wrappers for Domain-Restricted Functions ---
|
|
90
|
+
|
|
91
|
+
def safe_arcsin(x):
|
|
92
|
+
"""Arcsin clipped to the domain [-1, 1]"""
|
|
93
|
+
return np.arcsin(np.clip(x, -1.0, 1.0))
|
|
94
|
+
|
|
95
|
+
def safe_arccos(x):
|
|
96
|
+
"""Arccos clipped to the domain [-1, 1]"""
|
|
97
|
+
return np.arccos(np.clip(x, -1.0, 1.0))
|
|
98
|
+
|
|
99
|
+
def safe_arccosh(x):
|
|
100
|
+
"""Arccosh clipped to the domain [1, inf]"""
|
|
101
|
+
# Clip to minimum value of 1.0
|
|
102
|
+
return np.arccosh(np.clip(x, 1.0, None))
|
|
103
|
+
|
|
104
|
+
def safe_arctanh(x):
|
|
105
|
+
"""Arctanh clipped to the domain [-1, 1]"""
|
|
106
|
+
# Clip just inside the bounds to avoid infinity
|
|
107
|
+
return float(np.arctanh(np.clip(x, -0.999999, 0.999999)))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
X_AXIS_HORIZONTAL = {
|
|
111
|
+
-4: 0, -3: 1, -2: 2, -1: 3, 0: 4, 1: 5, 2: 6, 3: 7, 4: 8
|
|
112
|
+
}
|
|
113
|
+
Y_AXIS_HORIZONTAL = {
|
|
114
|
+
-16: 33, -15: 32, -14: 31, -13: 30, -12: 29, -11: 28, -10: 27, -9: 26,
|
|
115
|
+
-8: 25, -7: 24, -6: 23, -5: 22, -4: 21, -3: 20, -2: 19, -1: 18, 0: 17,
|
|
116
|
+
1: 16, 2: 15, 3: 14, 4: 13, 5: 12, 6: 11, 7: 10, 8: 9, 9: 8, 10: 7,
|
|
117
|
+
11: 6, 12: 5, 13: 4, 14: 3, 15: 2, 16: 1, 17: 0
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
X_AXIS_VERTICAL = {
|
|
121
|
+
-16: 33, -15: 32, -14: 31, -13: 30, -12: 29, -11: 28, -10: 27, -9: 26,
|
|
122
|
+
-8: 25, -7: 24, -6: 23, -5: 22, -4: 21, -3: 20, -2: 19, -1: 18, 0: 17,
|
|
123
|
+
1: 16, 2: 15, 3: 14, 4: 13, 5: 12, 6: 11, 7: 10, 8: 9, 9: 8, 10: 7,
|
|
124
|
+
11: 6, 12: 5, 13: 4, 14: 3, 15: 2, 16: 1, 17: 0
|
|
125
|
+
}
|
|
126
|
+
Y_AXIS_VERTICAL = {
|
|
127
|
+
-4: 0, -3: 1, -2: 2, -1: 3, 0: 4, 1: 5, 2: 6, 3: 7, 4: 8
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
REGULAR_OPERATIONS = {
|
|
131
|
+
"sin": lambda x: np.sin(x),
|
|
132
|
+
"cos": lambda x: np.cos(x),
|
|
133
|
+
"tan": lambda x: np.tan(x),
|
|
134
|
+
"exp": lambda x: np.exp(x),
|
|
135
|
+
"log": lambda x: np.log(np.abs(x) + 1e-6),
|
|
136
|
+
"sqrt": lambda x: np.sqrt(np.abs(x)),
|
|
137
|
+
"sinc": lambda x: np.sinc(x),
|
|
138
|
+
"arcsin": lambda x: np.arcsin(np.clip(x, -1.0, 1.0)),
|
|
139
|
+
"arccos": lambda x: np.arccos(np.clip(x, -1.0, 1.0)),
|
|
140
|
+
"arctanh": lambda x: np.arctanh(np.clip(x, -0.999999, 0.999999)),
|
|
141
|
+
"arccosh": lambda x: np.arccosh(x)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
MATH_OPERATIONS = [
|
|
147
|
+
# 1-85: Curated Functions (Preserved)
|
|
148
|
+
lambda x: 16 * np.sin(x), lambda x: 16 * np.cos(x), lambda x: 16 * np.sin(x * 3), lambda x: 16 * np.sin(x * 0.5), lambda x: 10 * np.sin(x) + 6 * np.cos(x * 2), lambda x: 8 * np.sin(x) + 4 * np.cos(x * 2) + 3 * np.sin(x * 3),
|
|
149
|
+
lambda x: 8 * (np.sin(x * 1.0) + np.sin(x * 1.1)), lambda x: 16 * np.cos(x * 1.5 - 1), lambda x: 7*np.sin(x) + 3*np.sin(2*x) + 2*np.sin(3*x) + 1*np.sin(4*x), lambda x: 8 * np.sin(x) * np.cos(x), lambda x: 17 * np.tanh(x), lambda x: 17 * np.tanh(x * 2),
|
|
150
|
+
lambda x: 17 * np.tanh(x * 0.4), lambda x: 10 * np.arctan(x), lambda x: 16 * np.arctan(x) * (2 / np.pi), lambda x: 0.6 * (x + 0.5)**2 - 10, lambda x: 17 * (x / 5.0)**3, lambda x: (x**4) / 40.0 - 10,
|
|
151
|
+
lambda x: x**3 / 8.0, lambda x: 0.6 * x**2, lambda x: 16 * np.sin(x**2 / 3.0), lambda x: 15 * np.cos(x * 3) * np.exp(-(x**2) / 8.0), lambda x: 3 * x * np.sin(x * 2), lambda x: (x + 5) * np.cos(x * 4) - 15,
|
|
152
|
+
lambda x: 16 * np.sin(x) * (x / 5.0), lambda x: 17 * np.sin(np.pi * x / (5.0 if x < 0 else 4.0)), lambda x: 15 * np.abs(np.sin(x * 1.5)) - 5, lambda x: 5 * np.sqrt(np.abs(x)) * np.copysign(1, x), lambda x: 8 * np.sin(x) - 8 * np.cos(x), lambda x: 10 * np.tanh(x * 5) + 5 * np.tanh(x * 2),
|
|
153
|
+
lambda x: 17 * (x + 0.5) / 4.5 - 8.5, lambda x: 15 * np.tanh(np.sin(x) * 3), lambda x: 16 * np.sin(x * 2) * np.exp(-x / 5.0), lambda x: 8 * (x - np.floor(x)) * 2 - 8, lambda x: 16 * np.sin(x / 5.0 * np.pi) * np.cos(x * 4), lambda x: 16 * np.sinc(x / 2.0),
|
|
154
|
+
lambda x: 16 * np.cos(x * 2) * np.exp(-np.abs(x) / 8.0), lambda x: 15 * np.abs(np.sin(x * 0.8)) * np.abs(np.cos(x * 2.5)), lambda x: 17 * np.sin(x * 0.5), lambda x: 4 * np.tan(np.sin(x * 0.5)), lambda x: 8 * np.sin(x) + 4 * np.sin(x*3.1) + 2 * np.cos(x*5.3), lambda x: 10 * (np.sin(x) + 0.3 * np.sin(x*3)),
|
|
155
|
+
lambda x: 0.1 * (x**2) + 5 * np.cos(x * 2) - 10, lambda x: 17 - 25 * np.exp(-(x**2) / 0.5), lambda x: 17 / (1 + np.exp(-x)) - 8.5, lambda x: 0.5 * np.cosh(x*0.5) - 10, lambda x: np.floor(x/2) + 3*np.sin(x*3), lambda x: 10 / (x + 0.1) if x > 0 else (10 / (x - 0.1) if x < 0 else 0),
|
|
156
|
+
lambda x: 8 * np.log(np.abs(x) + 1) * np.copysign(1, x), lambda x: 4 * np.sqrt(np.maximum(0, 16 - (x/2)**2)), lambda x: 16 * np.cos(np.sqrt(x**2 + 0.1)), lambda x: 8 * (np.sin(np.abs(x - 5)) + np.sin(np.abs(x + 5))), lambda x: 17 / (1 + (x/2)**2) - 8, lambda x: 15 * np.exp(-((x % 5 - 2.5)**2) / 0.5),
|
|
157
|
+
lambda x: 16 * (x/3 - np.floor(x/3)) - 8, lambda x: 9 * np.sin(x * 0.7) + 5 * np.sin(x * 1.8) + 3 * np.sin(x * 4.2), lambda x: 16 * np.i0(x * 0.8) / 10 - 8, lambda x: 15 * np.tanh(x * 0.5) * np.cos(x * 0.5), lambda x: 10 * np.sin(x) if np.all(np.floor(x) % 2 == 0) else 16 * np.sin(x), lambda x: 3 / (np.cos(x) + 1.1) - 1,
|
|
158
|
+
lambda x: 16 * (x/16) * np.sin(x*5), lambda x: 16 * np.exp(-x/8) * np.cos(x*2) if np.all(x > 0) else 16 * np.exp(x/8) * np.cos(x*2), lambda x: 15 * np.exp(-((x % 4 - 1)**2) / 0.1) + 10 * np.exp(-((x % 4 - 1.5)**2) / 0.1), lambda x: 16 / (1 + np.abs(x)), lambda x: 16 * np.tanh(4 * np.sin(x)), lambda x: 4 * (1 / (np.tan(x/2) + 1e-6)),
|
|
159
|
+
lambda x: 4 * np.round(x / 2), lambda x: 0.1 * x**2 + 5 * np.sin(x*3) - 12, lambda x: 17 * (x / (1 + np.abs(x))), lambda x: 16 * np.cos(x * 8) * np.exp(-((x-5)**2) / 8) if np.all(x > 0) else 16 * np.cos(x * 8) * np.exp(-((x-5)**2) / 8), lambda x: 4 * np.sin(x * 10), lambda x: 4 * (x**2) - 4,
|
|
160
|
+
lambda x: 4 * (x**3) / 10, lambda x: 4 * np.exp(-(x**2) / 0.5) * 2 - 4, lambda x: np.where(np.abs(x) < 2, 4, -4), lambda x: 4 * (np.abs(x % 2 - 1) * 2 - 1), lambda x: 4 * np.abs(x) - 4, lambda x: 2 * (x**4) - 4 * (x**2),
|
|
161
|
+
lambda x: 4 * np.tanh(x * 3), lambda x: 4 * np.cos(x * (np.pi / 4)), lambda x: 4 * (np.exp(-((x-2)**2) / 0.2) + np.exp(-((x+2)**2) / 0.2)) - 4, lambda x: 4 * np.sinc(x * 2), lambda x: 4 * np.sin(x * (np.pi / 4)), lambda x: 2 * np.round(x),
|
|
162
|
+
lambda x: np.where(np.abs(x) > 0.5, 4 / x, 0),
|
|
163
|
+
# --- Machine-Generated Functinons ---
|
|
164
|
+
lambda x: safe_log(x), lambda x: np.exp(x), lambda x: np.expm1(x), lambda x: safe_arctanh((np.tan((x + -94.0)) + x)), lambda x: np.cbrt(safe_log(np.cosh(x))),
|
|
165
|
+
lambda x: (safe_log(np.cosh(x)) ** 1.5), lambda x: np.floor((-10.0 * np.exp(x))), lambda x: safe_arcsin(np.arcsinh(x)), lambda x: np.tan(x), lambda x: np.cos(x), lambda x: (x + x),
|
|
166
|
+
lambda x: (np.sin(((np.cosh(np.exp(-96.0)) + x) * np.exp2(x))) + x), lambda x: np.exp(((-0.214 * safe_sinc(73.0)) + x)), lambda x: (np.expm1(0.84) + np.ceil(safe_arccosh(((x - -9.0) * x)))), lambda x: np.cbrt(np.cbrt((x + x))), lambda x: (-17.0 * np.cbrt((x * (x * safe_sinc(np.exp((safe_arccos(np.exp(safe_sqrt(safe_sqrt(np.tanh(np.tan(np.ceil(np.cos(x)))))))) + x))))))), lambda x: (np.exp(x) ** 1.0),
|
|
167
|
+
lambda x: (safe_arcsin(np.exp(x)) ** 1.4), lambda x: safe_log((x - safe_sqrt((np.square(np.cbrt(-2.0)) ** 0.9)))), lambda x: (np.exp2(x) + safe_arccosh(np.exp2((safe_sinc(x) * safe_log(x))))), lambda x: np.sign(np.cos(x)), lambda x: (x + safe_log(x)), lambda x: (((x * x) + 0.396) + (x + np.cos(x))),
|
|
168
|
+
lambda x: np.log1p(np.exp(x)), lambda x: np.expm1(np.cosh(x)), lambda x: np.abs(np.sinh(np.cosh(((1.36 * x) * (x + -1.96))))), lambda x: np.ceil(((x + ((-2.46 ** 2.0) + np.expm1(-0.24))) + x)), lambda x: safe_arcsin(((np.floor((0.71 * x)) * np.tan((x - 2.04))) + x)), lambda x: (np.expm1((2.5 * (np.floor(x) - x))) * 2.9),
|
|
169
|
+
lambda x: (-3.79 * x), lambda x: np.arctan(np.cos(x)), lambda x: (safe_sinc(np.exp((-2.58 - x))) * np.tanh(np.sign((-1.12 ** 2.1)))), lambda x: np.exp((x * np.arcsinh(1.48))), lambda x: np.arcsinh(np.cosh(safe_arccos((np.expm1(x) + -0.11)))), lambda x: safe_arccosh(np.ceil(x)),
|
|
170
|
+
lambda x: np.cbrt(np.exp((x * 3.09))), lambda x: np.sin((((np.sin(2.72) ** 0.6) ** 1.7) * (np.cosh(x) - ((0.44 + -2.4) + safe_log(x))))), lambda x: np.cbrt((x + ((x + np.cbrt(-3.71)) - np.exp(safe_log(-0.23))))), lambda x: np.tanh(np.cos(x)), lambda x: ((x - np.floor(np.abs(x))) - 3.4), lambda x: (np.cos((-2.57 - safe_arccosh(np.cbrt(x)))) - x),
|
|
171
|
+
lambda x: ((x * np.tanh(-3.37)) + safe_sinc(x)), lambda x: (safe_log(safe_arccosh(np.exp2((-3.9 * x)))) + x), lambda x: (np.expm1((x - ((x - 2.84) - (-3.33 * 1.4)))) - ((((-0.39 * x) + safe_arcsin(x)) * (-0.36 * safe_arcsin(x))) * x)), lambda x: (np.ceil(x) + (-2.28 + x)), lambda x: np.expm1(safe_log(np.cosh((np.abs(1.28) + (1.53 - x))))), lambda x: (np.exp(np.arctan(-2.72)) * (x + (safe_sinc(x) + safe_arccos((x + x))))),
|
|
172
|
+
lambda x: (np.sign(x) - safe_sqrt(x)), lambda x: (np.exp(x) - ((x * np.floor(safe_log((safe_arcsin(np.arctan(x)) - np.sign(np.expm1(np.sign((x * 2.78)))))))) - np.sign(np.sinh(-3.88)))), lambda x: np.tanh(np.ceil(x)), lambda x: np.tanh(np.cbrt(x)), lambda x: safe_log((safe_sqrt((x - np.exp((x + np.expm1(safe_arccosh(np.cbrt(safe_arcsin(0.77)))))))) * safe_arcsin((((x * 1.52) - x) - np.floor((safe_arccos(0.0) * np.exp2(x))))))), lambda x: (np.cosh(x) ** 0.6),
|
|
173
|
+
lambda x: (safe_sinc((safe_arcsin((x + safe_arccosh(x))) * np.abs(-2.0))) + np.exp2(x)), lambda x: (np.expm1(x) - (np.sin(2.92) * -1.67)), lambda x: ((np.sinh(np.cos((np.tan(safe_sinc(np.cbrt(np.cos(x)))) ** 1.2))) + (((np.log1p(-0.57) - np.cos(-3.2)) * x) + x)) - -3.55), lambda x: np.floor(safe_log((np.expm1((np.arctan((x + np.exp2((x - 2.54)))) * (-1.58 * ((np.cbrt(-0.63) + np.expm1(x)) - np.tan(x))))) ** 1.0))), lambda x: np.floor(np.expm1(x)), lambda x: (-1.83 + (np.cosh(x) ** 2.5)),
|
|
174
|
+
lambda x: (x * -1.98), lambda x: np.cosh((np.sinh(np.sign(1.68)) + x)), lambda x: (safe_sinc(np.arcsinh(np.square(np.expm1(0.18)))) + np.expm1(np.expm1(np.arcsinh((-0.96 + x))))), lambda x: (np.exp2((x + safe_arctanh(-1.89))) - x), lambda x: (-2.71 - np.sin(((-3.88 ** 1.0) * np.floor((1.65 - x))))), lambda x: ((np.expm1(0.76) ** 1.9) + np.tanh(np.sinh(x))),
|
|
175
|
+
lambda x: (np.expm1(x) ** 1.0), lambda x: ((np.sin(x) + (2.01 - -1.72)) + -2.45), lambda x: (((np.square(np.abs(x)) * (np.ceil((np.cos(np.ceil(x)) + np.sin(np.abs(safe_log((-3.68 ** 1.1)))))) ** 1.3)) ** 0.9) ** 2.3), lambda x: (safe_log(x) + x), lambda x: (3.45 * np.cos(x)), lambda x: np.sinh(safe_arccos(np.exp(x))),
|
|
176
|
+
lambda x: np.expm1((-0.81 - x)), lambda x: (safe_sqrt(x) + x), lambda x: safe_log(safe_sqrt(np.expm1(x))), lambda x: safe_arccos(np.exp2(x)), lambda x: np.tan(safe_log(np.exp(x))), lambda x: np.square(np.tan(x)),
|
|
177
|
+
lambda x: (np.cosh(np.tan(np.expm1(((((np.floor(0.74) - -3.58) * np.sign((2.84 + 0.84))) ** 2.6) - x)))) - np.sign(np.tanh(safe_sqrt(x)))), lambda x: np.tanh((x - np.cbrt(safe_sinc((np.square(np.arcsinh(safe_sqrt((np.tanh(3.99) * x)))) - np.exp2((safe_sqrt(1.55) ** 0.9))))))), lambda x: (x + ((x + x) - np.sinh(x))), lambda x: np.cos(np.abs(x)), lambda x: (0.6 + np.cbrt(x)), lambda x: ((np.exp2(x) + np.sinh(np.cos(np.cbrt(-1.5)))) ** 1.7),
|
|
178
|
+
lambda x: (np.abs(2.27) + np.arcsinh(x)), lambda x: np.cos((-3.54 * x)), lambda x: (x * (np.exp2(np.arctan(((safe_sqrt(np.square(np.expm1((-3.26 ** 1.7)))) ** 2.6) + x))) + (0.84 ** 2.9))), lambda x: (x + ((-0.62 ** 2.2) + x)), lambda x: (np.exp2(np.log1p(3.46)) - np.cosh((3.98 + x))), lambda x: (np.ceil(np.cosh(x)) + (x + 0.66)),
|
|
179
|
+
lambda x: np.exp2((safe_arccosh(x) ** 1.4)), lambda x: (x - np.ceil(np.expm1(x))), lambda x: np.tanh((2.02 - safe_arccos(x))), lambda x: (3.05 + np.tan(np.sin(np.ceil(x)))), lambda x: (safe_sqrt(np.expm1(x)) ** 2.9), lambda x: ((np.square(np.abs(2.41)) + np.cbrt(1.38)) * np.cbrt(np.tan((x - 1.23)))),
|
|
180
|
+
lambda x: ((x + x) - -3.58), lambda x: (x * -2.17), lambda x: safe_log((x + np.arctan((np.sinh(x) * np.abs(np.abs(safe_arcsin(((x + np.cbrt(3.96)) - safe_arccos((x * -0.46)))))))))), lambda x: (((x + -3.82) + x) + np.cos((-3.93 * np.abs(0.97)))), lambda x: (x * ((1.28 * np.abs(2.5)) ** 0.9)), lambda x: (x + (np.exp2(np.square(np.abs(safe_arccos((x * x))))) - 1.26)),
|
|
181
|
+
lambda x: np.tanh(((x - ((-0.59 ** 2.1) * (0.29 ** 1.0))) + safe_sinc(3.1))), lambda x: (x + (-1.27 + x)), lambda x: (np.sign(np.cosh(safe_log(x))) + (x - np.expm1(x))), lambda x: (x * np.ceil(np.arctan(x))), lambda x: ((x * (np.ceil(-0.63) + np.sinh(np.tanh((np.sinh(safe_arcsin((x - np.log1p(safe_arcsin(-0.44))))) * np.exp(2.65)))))) - safe_arccosh(x)), lambda x: np.tan(((-1.94 * x) - np.sin(x))),
|
|
182
|
+
lambda x: safe_arccosh(np.ceil((x + (-1.47 * x)))), lambda x: safe_arccosh(np.floor(np.exp(x))), lambda x: np.exp(safe_arcsin(x)), lambda x: safe_arcsin((safe_arccos(x) ** 0.6)), lambda x: (safe_arccosh((x ** 3.0)) ** 1.5), lambda x: safe_sqrt((np.exp(x) + -1.46)),
|
|
183
|
+
lambda x: (x * safe_arccos((-3.39 + (3.85 ** 0.8)))), lambda x: (x - safe_arcsin(np.tanh(np.abs((3.86 + x))))), lambda x: np.cos(np.sinh(np.floor(np.arctan(x)))), lambda x: np.tanh(np.cbrt((np.tanh(np.tan(1.38)) - x))), lambda x: np.sinh((-0.79 * np.cos(x))), lambda x: (x * -3.72),
|
|
184
|
+
lambda x: (np.sin(x) - np.abs((safe_arcsin(np.sin((-1.93 * np.tanh(x)))) + safe_log(1.4)))), lambda x: (x * 0.99), lambda x: safe_sqrt((np.sinh(x) + -2.86)), lambda x: safe_sqrt((np.exp2(0.21) + (-2.46 + np.abs(x)))), lambda x: np.arcsinh((x - (x + np.exp(x)))), lambda x: (np.exp(x) + (((x - safe_sqrt(x)) ** 1.0) + x)),
|
|
185
|
+
lambda x: np.abs(safe_arccos(np.arctan(np.arcsinh(safe_arccosh(x))))), lambda x: (x - np.cosh(np.arcsinh(x))), lambda x: np.exp((np.abs(np.exp(np.tan((x * 0.96)))) + x)), lambda x: np.cos((x - np.cosh(-2.89))), lambda x: np.cos(((np.square(-1.07) * safe_arccosh((x + x))) + x)), lambda x: np.log1p(safe_arccosh((x + (-3.05 + -1.42)))),
|
|
186
|
+
lambda x: (x * -3.79), lambda x: np.floor(np.tan(np.floor(x))), lambda x: np.exp2((x + -0.85)), lambda x: np.cosh(((-2.54 * np.square(safe_sqrt(x))) + x)), lambda x: (safe_arccosh(2.23) - (x + x)), lambda x: safe_arcsin(safe_arccos(x)),
|
|
187
|
+
lambda x: np.cbrt(np.arctan(x)), lambda x: (safe_log(np.floor(x)) - (x * (x + (0.13 ** 2.4)))), lambda x: safe_arccosh(np.expm1(x)), lambda x: (x + np.expm1(np.ceil(x))), lambda x: np.sinh((np.cbrt(np.abs(x)) - x)), lambda x: np.square((np.cbrt(safe_log((x + x))) - x)),
|
|
188
|
+
lambda x: (x + safe_sqrt(x)),
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def ridiculously_safe_round(x: int | float) -> int:
|
|
193
|
+
"""
|
|
194
|
+
Rounds a scalar numeric value with extreme safety.
|
|
195
|
+
Returns 0 for complex numbers, NaN, Inf, or any non-numeric type.
|
|
196
|
+
"""
|
|
197
|
+
if isinstance(x, complex):
|
|
198
|
+
return unguessable_constant
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
if not math.isfinite(x):
|
|
202
|
+
return unguessable_constant
|
|
203
|
+
except (TypeError, ValueError):
|
|
204
|
+
return unguessable_constant
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
return round(x)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def pick_largest_graph(x) -> List[List[int]]:
|
|
211
|
+
vert_graph = create_graph_with_vertical_x_axis(False, x, False)
|
|
212
|
+
horiz_graph = create_graph_with_horizontal_x_axis(False, x, False)
|
|
213
|
+
vert_count = sum(1 for row in vert_graph for cell in row if cell == 1)
|
|
214
|
+
horiz_count = sum(1 for row in horiz_graph for cell in row if cell == 1)
|
|
215
|
+
return horiz_graph if horiz_count >= vert_count else vert_graph
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def create_graph_with_horizontal_x_axis(axis: bool = False, function: Callable[[float], float] = lambda x: x**2, draw_function: bool = True):
|
|
220
|
+
"""
|
|
221
|
+
Creates a graph on the LED matrix.
|
|
222
|
+
axis (bool): If True, draws the X and Y axes.
|
|
223
|
+
function (Callable[[float], float]): The mathematical function to graph.
|
|
224
|
+
"""
|
|
225
|
+
log(f"create_graph_with_horizontal_x_axis: start axis={axis} function={function}")
|
|
226
|
+
matrix = [[0] * WIDTH for _ in range(HEIGHT)]
|
|
227
|
+
if function:
|
|
228
|
+
matrix = [[0] * WIDTH for _ in range(HEIGHT)]
|
|
229
|
+
if axis:
|
|
230
|
+
log("create_graph_with_horizontal_x_axis: drawing axes")
|
|
231
|
+
for i in range(0, WIDTH):
|
|
232
|
+
matrix[17][i] = 1
|
|
233
|
+
for i in range(0, HEIGHT):
|
|
234
|
+
matrix[i][4] = 1
|
|
235
|
+
points_plotted = 0
|
|
236
|
+
for x in np.arange(-4,5, 0.01):
|
|
237
|
+
xf = float(x)
|
|
238
|
+
y_val = function(xf)
|
|
239
|
+
if isinstance(y_val, np.ndarray):
|
|
240
|
+
y_val = y_val.item()
|
|
241
|
+
y = ridiculously_safe_round(y_val)
|
|
242
|
+
if y == unguessable_constant:
|
|
243
|
+
continue
|
|
244
|
+
xi = round(xf)
|
|
245
|
+
if abs(y) > (HEIGHT//2)-1 or abs(xi) > (WIDTH//2):
|
|
246
|
+
continue
|
|
247
|
+
else:
|
|
248
|
+
matrix[Y_AXIS_HORIZONTAL[y]][X_AXIS_HORIZONTAL[xi]] = 1
|
|
249
|
+
points_plotted += 1
|
|
250
|
+
if draw_function:
|
|
251
|
+
draw_matrix_on_board(matrix, 'both')
|
|
252
|
+
log(f"create_graph_with_horizontal_x_axis: drawbw sent, plotted {points_plotted} points")
|
|
253
|
+
return matrix
|
|
254
|
+
|
|
255
|
+
def create_graph_with_vertical_x_axis(axis: bool = False, function: Callable[[float], float] = lambda x: x**2, draw_function: bool = True):
|
|
256
|
+
"""
|
|
257
|
+
Creates a graph on the LED matrix. (Rotated 90 degrees)
|
|
258
|
+
X-Axis: long (34 rows)
|
|
259
|
+
Y-Axis: short (9 cols)
|
|
260
|
+
"""
|
|
261
|
+
log(f"create_graph_with_vertical_x_axis: start axis={axis} function={function}")
|
|
262
|
+
matrix = [[0] * WIDTH for _ in range(HEIGHT)]
|
|
263
|
+
if function:
|
|
264
|
+
if axis:
|
|
265
|
+
log("create_graph_with_vertical_x_axis: drawing axes")
|
|
266
|
+
for i in range(0, HEIGHT):
|
|
267
|
+
matrix[i][4] = 1
|
|
268
|
+
for i in range(0, WIDTH):
|
|
269
|
+
matrix[17][i] = 1
|
|
270
|
+
points_plotted = 0
|
|
271
|
+
for x_val in np.arange(-16, 17, 0.01):
|
|
272
|
+
xf = float(x_val)
|
|
273
|
+
y_val = function(xf)
|
|
274
|
+
if isinstance(y_val, np.ndarray):
|
|
275
|
+
y_val = y_val.item()
|
|
276
|
+
y = ridiculously_safe_round(y_val)
|
|
277
|
+
if y == unguessable_constant:
|
|
278
|
+
continue
|
|
279
|
+
xi = round(xf)
|
|
280
|
+
if y not in Y_AXIS_VERTICAL or xi not in X_AXIS_VERTICAL:
|
|
281
|
+
continue
|
|
282
|
+
else:
|
|
283
|
+
matrix[X_AXIS_VERTICAL[xi]][Y_AXIS_VERTICAL[y]] = 1
|
|
284
|
+
points_plotted += 1
|
|
285
|
+
if draw_function:
|
|
286
|
+
draw_matrix_on_board(matrix, 'both')
|
|
287
|
+
log(f"create_graph_with_vertical_x_axis: drawbw sent, plotted {points_plotted} points")
|
|
288
|
+
return matrix
|
|
289
|
+
|
|
290
|
+
# for func in MATH_OPERATIONS:
|
|
291
|
+
# try:
|
|
292
|
+
# plot_function(func, -20, 20, 999999, func.__name__, str(f"{func}"))
|
|
293
|
+
# except Exception as e:
|
|
294
|
+
# log(f"Error plotting function {func}: {e}")
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import cellpylib as cpl
|
|
2
|
+
import numpy as np
|
|
3
|
+
import time
|
|
4
|
+
import random
|
|
5
|
+
from framework_led_matrix.core.led_commands import log, clear_graph, WIDTH, HEIGHT, draw_greyscale_on_board
|
|
6
|
+
from typing import List
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
from matplotlib.colors import ListedColormap
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
BML_EMPTY = 0
|
|
12
|
+
BML_RED_RIGHT = 1
|
|
13
|
+
BML_BLUE_DOWN = 2
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def draw_bml_board(board: List[List[int]], which: str):
|
|
17
|
+
"""
|
|
18
|
+
Converts the 3-state BML board (0,1,2) to a greyscale
|
|
19
|
+
matrix (0, 255, 128) and draws it.
|
|
20
|
+
"""
|
|
21
|
+
# We must use greyscale for three states
|
|
22
|
+
greyscale_matrix = [[0 for _ in range(WIDTH)] for _ in range(HEIGHT)]
|
|
23
|
+
for r in range(HEIGHT):
|
|
24
|
+
for c in range(WIDTH):
|
|
25
|
+
state = board[r][c]
|
|
26
|
+
if state == BML_RED_RIGHT:
|
|
27
|
+
greyscale_matrix[r][c] = 255 # Bright
|
|
28
|
+
elif state == BML_BLUE_DOWN:
|
|
29
|
+
greyscale_matrix[r][c] = 128 # Grey
|
|
30
|
+
# else: 0 (Empty)
|
|
31
|
+
|
|
32
|
+
draw_greyscale_on_board(greyscale_matrix, which)
|
|
33
|
+
|
|
34
|
+
def bml_rule(neighbourhood: np.ndarray, c_coord: tuple, t: int):
|
|
35
|
+
"""
|
|
36
|
+
The BML rule for cellpylib's evolve2d.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
neighbourhood: The 2D (3x3) NumPy array for the Moore neighborhood (r=1).
|
|
40
|
+
[[0,0(NW), 0,1(N), 0,2(NE)],
|
|
41
|
+
[1,0(W), 1,1(C), 1,2(E) ],
|
|
42
|
+
[2,0(SW), 2,1(S), 2,2(SE)]]
|
|
43
|
+
c_coord (tuple): The (row, col) coordinate. We don't use this.
|
|
44
|
+
t (int): The current timestep.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# --- Get the neighbors we care about from the 3x3 array ---
|
|
48
|
+
# The (0,0) coordinate of the 3x3 grid is NW
|
|
49
|
+
|
|
50
|
+
N = neighbourhood[0, 1] # North neighbor
|
|
51
|
+
W = neighbourhood[1, 0] # West neighbor
|
|
52
|
+
c = neighbourhood[1, 1] # The CURRENT cell's value (Center)
|
|
53
|
+
E = neighbourhood[1, 2] # East neighbor
|
|
54
|
+
S = neighbourhood[2, 1] # South neighbor
|
|
55
|
+
|
|
56
|
+
# t=0 is the initial state.
|
|
57
|
+
# t=1, 3, 5... should be Red's turn
|
|
58
|
+
# t=2, 4, 6... should be Blue's turn
|
|
59
|
+
|
|
60
|
+
# On t=0 (initial state), do nothing, just return the current state.
|
|
61
|
+
if t == 0:
|
|
62
|
+
return c
|
|
63
|
+
|
|
64
|
+
is_red_turn = (t % 2 == 1)
|
|
65
|
+
|
|
66
|
+
if is_red_turn:
|
|
67
|
+
# --- Red Turn Logic (Blue cars are static) ---
|
|
68
|
+
|
|
69
|
+
# 1. Am I a RED car?
|
|
70
|
+
if c == BML_RED_RIGHT:
|
|
71
|
+
# Check my EAST neighbor. If it's EMPTY, I move out.
|
|
72
|
+
return BML_EMPTY if E == BML_EMPTY else BML_RED_RIGHT
|
|
73
|
+
|
|
74
|
+
# 2. Am I an EMPTY spot?
|
|
75
|
+
if c == BML_EMPTY:
|
|
76
|
+
# Check my WEST neighbor. If it's RED, it will move into me.
|
|
77
|
+
return BML_RED_RIGHT if W == BML_RED_RIGHT else BML_EMPTY
|
|
78
|
+
|
|
79
|
+
# 3. Am I a BLUE car?
|
|
80
|
+
# if c == BML_BLUE_DOWN:
|
|
81
|
+
return BML_BLUE_DOWN # Blue cars don't move on red turns
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
# --- Blue Turn Logic (Red cars are static) ---
|
|
85
|
+
|
|
86
|
+
# 1. Am I a BLUE car?
|
|
87
|
+
if c == BML_BLUE_DOWN:
|
|
88
|
+
# Check my SOUTH neighbor. If it's EMPTY, I move out.
|
|
89
|
+
return BML_EMPTY if S == BML_EMPTY else BML_BLUE_DOWN
|
|
90
|
+
|
|
91
|
+
# 2. Am I an EMPTY spot?
|
|
92
|
+
if c == BML_EMPTY:
|
|
93
|
+
# Check my NORTH neighbor. If it's BLUE, it will move into me.
|
|
94
|
+
return BML_BLUE_DOWN if N == BML_BLUE_DOWN else BML_EMPTY
|
|
95
|
+
|
|
96
|
+
# 3. Am I a RED car?
|
|
97
|
+
# if c == BML_RED_RIGHT:
|
|
98
|
+
return BML_RED_RIGHT # Red cars don't move on blue turns
|
|
99
|
+
|
|
100
|
+
def create_bml_board_np(density: float = 0.35) -> np.ndarray:
|
|
101
|
+
"""
|
|
102
|
+
Creates a new random board for the BML traffic model as a NumPy array.
|
|
103
|
+
(Adapted from your BihamMiddletonLevineTrafficModel.py)
|
|
104
|
+
"""
|
|
105
|
+
log(f"BML (cpl): Creating new NumPy board with density {density}")
|
|
106
|
+
board = np.full((HEIGHT, WIDTH), BML_EMPTY, dtype=int)
|
|
107
|
+
|
|
108
|
+
total_cells = WIDTH * HEIGHT
|
|
109
|
+
num_cars = int(total_cells * density)
|
|
110
|
+
num_red = num_cars // 2
|
|
111
|
+
num_blue = num_cars - num_red
|
|
112
|
+
|
|
113
|
+
cells = [(r, c) for r in range(HEIGHT) for c in range(WIDTH)]
|
|
114
|
+
random.shuffle(cells)
|
|
115
|
+
|
|
116
|
+
# Place red (right-moving) cars
|
|
117
|
+
for i in range(num_red):
|
|
118
|
+
r, c = cells.pop()
|
|
119
|
+
board[r, c] = BML_RED_RIGHT
|
|
120
|
+
|
|
121
|
+
# Place blue (down-moving) cars
|
|
122
|
+
for i in range(num_blue):
|
|
123
|
+
r, c = cells.pop()
|
|
124
|
+
board[r, c] = BML_BLUE_DOWN
|
|
125
|
+
|
|
126
|
+
log(f"BML (cpl): Seeded board with {num_red} red and {num_blue} blue cars.")
|
|
127
|
+
return board
|
|
128
|
+
|
|
129
|
+
def show_bml_local_animation(
|
|
130
|
+
density: float = 0.35,
|
|
131
|
+
steps: int = 500
|
|
132
|
+
):
|
|
133
|
+
"""
|
|
134
|
+
Runs the BML simulation and displays the result
|
|
135
|
+
on the laptop screen using Matplotlib.
|
|
136
|
+
"""
|
|
137
|
+
log(f"BML (Local): Starting local animation. density={density}, steps={steps}")
|
|
138
|
+
|
|
139
|
+
# 1. Create the initial state (reuses your function)
|
|
140
|
+
initial_state_2d = create_bml_board_np(density)
|
|
141
|
+
initial_state_3d = np.array([initial_state_2d])
|
|
142
|
+
|
|
143
|
+
log(f"BML (Local): Evolving {steps} half-steps...")
|
|
144
|
+
|
|
145
|
+
# 2. Run the *entire* evolution (reuses your rule)
|
|
146
|
+
all_half_steps = cpl.evolve2d(
|
|
147
|
+
cellular_automaton=initial_state_3d,
|
|
148
|
+
timesteps=steps,
|
|
149
|
+
neighbourhood='Moore',
|
|
150
|
+
apply_rule=bml_rule,
|
|
151
|
+
r=1
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
log(f"BML (Local): Evolution complete. Shape: {all_half_steps.shape}. Starting animation.")
|
|
155
|
+
|
|
156
|
+
# 3. Define a custom colormap for our 3 states
|
|
157
|
+
# State 0 (BML_EMPTY) -> 'black'
|
|
158
|
+
# State 1 (BML_RED_RIGHT) -> 'red'
|
|
159
|
+
# State 2 (BML_BLUE_DOWN) -> 'blue'
|
|
160
|
+
bml_cmap = ListedColormap(['black', 'red', 'blue'])
|
|
161
|
+
|
|
162
|
+
# 4. Use cellpylib's built-in animator.
|
|
163
|
+
# This will create a new window and play the animation.
|
|
164
|
+
cpl.plot2d_animate(
|
|
165
|
+
all_half_steps,
|
|
166
|
+
colormap=bml_cmap, # type: ignore
|
|
167
|
+
title=f'BML Traffic (Density: {density})'
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
# 5. Show the plot window.
|
|
171
|
+
# The script will pause here until you close the animation window.
|
|
172
|
+
plt.show()
|
|
173
|
+
|
|
174
|
+
log("BML (Local): Animation window closed.")
|
|
175
|
+
|
|
176
|
+
def run_bml(
|
|
177
|
+
density: float = 0.35,
|
|
178
|
+
steps: int = 500, # Total half-steps (same as your original function)
|
|
179
|
+
delay_sec: float = 0.1,
|
|
180
|
+
which: str = 'both'
|
|
181
|
+
):
|
|
182
|
+
"""
|
|
183
|
+
Runs the BML Traffic Model simulation using cellpylib.
|
|
184
|
+
"""
|
|
185
|
+
log(f"BML (cpl): Starting simulation. density={density}, steps={steps}")
|
|
186
|
+
|
|
187
|
+
initial_state_2d = create_bml_board_np(density)
|
|
188
|
+
initial_state_3d = np.array([initial_state_2d])
|
|
189
|
+
|
|
190
|
+
log(f"BML (cpl): Evolving {steps} half-steps...")
|
|
191
|
+
|
|
192
|
+
all_half_steps = cpl.evolve2d(
|
|
193
|
+
cellular_automaton=initial_state_3d,
|
|
194
|
+
timesteps=steps,
|
|
195
|
+
neighbourhood='Moore',
|
|
196
|
+
apply_rule=bml_rule,
|
|
197
|
+
r=1
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
log(f"BML (cpl): Evolution complete. Result shape: {all_half_steps.shape}")
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
stable_counter = 0
|
|
204
|
+
total_frames = all_half_steps.shape[0]
|
|
205
|
+
for i in range(total_frames):
|
|
206
|
+
current_board_np = all_half_steps[i]
|
|
207
|
+
current_board_list = current_board_np.tolist()
|
|
208
|
+
draw_bml_board(current_board_list, which)
|
|
209
|
+
time.sleep(delay_sec)
|
|
210
|
+
if i == 0:
|
|
211
|
+
turn_name = "Initial State"
|
|
212
|
+
else:
|
|
213
|
+
turn_name = "Red (Right)" if (i % 2 == 1) else "Blue (Down)"
|
|
214
|
+
if i % 20 == 0 or i == total_frames - 1:
|
|
215
|
+
log(f"BML (cpl): Step {i}/{steps} ({turn_name}).")
|
|
216
|
+
if i > 2:
|
|
217
|
+
if np.array_equal(current_board_np, all_half_steps[i-2]):
|
|
218
|
+
stable_counter += 1
|
|
219
|
+
else:
|
|
220
|
+
stable_counter = 0
|
|
221
|
+
|
|
222
|
+
if stable_counter >= 20:
|
|
223
|
+
log("BML (cpl): GRIDLOCK. State stable for 10 full cycles. Halting.")
|
|
224
|
+
time.sleep(2)
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
except KeyboardInterrupt:
|
|
228
|
+
log("BML (cpl): KeyboardInterrupt received, stopping.")
|
|
229
|
+
finally:
|
|
230
|
+
log(f"BML (cpl): simulation finished.")
|
|
231
|
+
clear_graph()
|
|
232
|
+
log("BML (cpl): cleared display.")
|
|
233
|
+
|
|
234
|
+
if __name__ == "__main__":
|
|
235
|
+
try:
|
|
236
|
+
show_bml_local_animation(density=0.6, steps=5000)
|
|
237
|
+
finally:
|
|
238
|
+
clear_graph()
|