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.
@@ -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()