mathmethods-mcp 0.1.0__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,269 @@
1
+ import numpy as np
2
+ import sympy as sp
3
+ import math
4
+ from typing import Dict, Any, List
5
+
6
+ class Dynamic2DLinearService:
7
+ @staticmethod
8
+ def clasificar_sistema(A) -> str:
9
+ traza = float(np.trace(A))
10
+ determinante = float(np.linalg.det(A))
11
+ discriminante = traza**2 - 4 * determinante
12
+ autovalores = np.linalg.eigvals(A)
13
+
14
+ if abs(determinante) < 1e-10:
15
+ return "Caso degenerado / determinante cero"
16
+ if determinante < 0:
17
+ return "Silla"
18
+ if discriminante > 1e-10:
19
+ if traza < 0:
20
+ return "Nodo estable"
21
+ elif traza > 0:
22
+ return "Nodo inestable"
23
+ else:
24
+ return "Nodo con traza cero / caso especial"
25
+ elif abs(discriminante) <= 1e-10:
26
+ if traza < 0:
27
+ return "Nodo degenerado estable"
28
+ elif traza > 0:
29
+ return "Nodo degenerado inestable"
30
+ else:
31
+ return "Caso no hiperbolico"
32
+ else:
33
+ parte_real = float(np.real(autovalores[0]))
34
+ if abs(parte_real) < 1e-10:
35
+ return "Centro"
36
+ elif parte_real < 0:
37
+ return "Foco estable"
38
+ else:
39
+ return "Foco inestable"
40
+
41
+ @staticmethod
42
+ def descripcion_comportamiento(clasificacion: str) -> str:
43
+ if "Silla" in clasificacion:
44
+ return "Las trayectorias se acercan en una direccion y se alejan en otra."
45
+ if "Nodo estable" in clasificacion:
46
+ return "Todas las trayectorias cercanas tienden al equilibrio sin oscilar."
47
+ if "Nodo inestable" in clasificacion:
48
+ return "Las trayectorias se alejan del equilibrio sin oscilar."
49
+ if "Foco estable" in clasificacion:
50
+ return "Las trayectorias giran en espiral hacia el equilibrio."
51
+ if "Foco inestable" in clasificacion:
52
+ return "Las trayectorias giran en espiral alejandose del equilibrio."
53
+ if "Centro" in clasificacion:
54
+ return "Las trayectorias cerradas rodean el equilibrio."
55
+ if "degenerado" in clasificacion:
56
+ return "El sistema tiene autovalores repetidos; puede requerir analizar autovectores."
57
+ return "Caso especial; requiere analisis adicional."
58
+
59
+ @staticmethod
60
+ def rk4_sistema(A, B, X0, t0, t_fin, h):
61
+ n_steps_t = max(1, int(round((t_fin - t0) / h)))
62
+ t_vals = np.linspace(t0, t_fin, n_steps_t + 1)
63
+ X_vals = np.zeros((len(t_vals), 2))
64
+ X_vals[0] = X0
65
+
66
+ for i in range(len(t_vals) - 1):
67
+ ti = t_vals[i]
68
+ Xi = X_vals[i]
69
+ h_loc = t_vals[i + 1] - ti
70
+
71
+ k1 = A @ Xi + B
72
+ k2 = A @ (Xi + h_loc * k1 / 2) + B
73
+ k3 = A @ (Xi + h_loc * k2 / 2) + B
74
+ k4 = A @ (Xi + h_loc * k3) + B
75
+
76
+ X_vals[i + 1] = Xi + (h_loc / 6) * (k1 + 2 * k2 + 2 * k3 + k4)
77
+
78
+ return t_vals, X_vals
79
+
80
+ @staticmethod
81
+ def solve(payload: Dict[str, Any]) -> Dict[str, Any]:
82
+ a = float(payload.get('a', 3.0))
83
+ b = float(payload.get('b', 1.0))
84
+ c = float(payload.get('c', 1.0))
85
+ d = float(payload.get('d', 3.0))
86
+ e = float(payload.get('e', 0.0))
87
+ f = float(payload.get('f', 0.0))
88
+ x0 = float(payload.get('x0', 1.0))
89
+ y0 = float(payload.get('y0', 1.0))
90
+ t0 = float(payload.get('t0', 0.0))
91
+ t_fin = float(payload.get('t_fin', 10.0))
92
+ h = float(payload.get('h', 0.01))
93
+ x_min = float(payload.get('x_min', -5.0))
94
+ x_max = float(payload.get('x_max', 5.0))
95
+ y_min = float(payload.get('y_min', -5.0))
96
+ y_max = float(payload.get('y_max', 5.0))
97
+ cantidad_trayectorias = int(payload.get('cantidad_trayectorias', 16))
98
+
99
+ A = np.array([[a, b], [c, d]], dtype=float)
100
+ B = np.array([e, f], dtype=float)
101
+
102
+ det_A = float(np.linalg.det(A))
103
+ equilibrio_unico = abs(det_A) > 1e-12
104
+ equilibrio_pnto = None
105
+ if equilibrio_unico:
106
+ eq = -np.linalg.solve(A, B)
107
+ equilibrio_pnto = {"x": float(eq[0]), "y": float(eq[1])}
108
+
109
+ autovalores, autovectores = np.linalg.eig(A)
110
+ traza = float(np.trace(A))
111
+ discriminante = traza**2 - 4 * det_A
112
+ clasificacion = Dynamic2DLinearService.clasificar_sistema(A)
113
+ comportamiento = Dynamic2DLinearService.descripcion_comportamiento(clasificacion)
114
+
115
+ list_autovalores = []
116
+ for av in autovalores:
117
+ list_autovalores.append({
118
+ "real": float(np.real(av)),
119
+ "imag": float(np.imag(av))
120
+ })
121
+
122
+ list_autovectores = []
123
+ for i in range(len(autovalores)):
124
+ list_autovectores.append({
125
+ "vx": float(np.real(autovectores[0, i])),
126
+ "vy": float(np.real(autovectores[1, i]))
127
+ })
128
+
129
+ X0 = np.array([x0, y0], dtype=float)
130
+ t_vals, X_vals = Dynamic2DLinearService.rk4_sistema(A, B, X0, t0, t_fin, h)
131
+
132
+ dxdt_vals = [float(val) for val in (A @ X_vals.T + B[:, None])[0]]
133
+ dydt_vals = [float(val) for val in (A @ X_vals.T + B[:, None])[1]]
134
+
135
+ principal_trajectory = {
136
+ "t": t_vals.tolist(),
137
+ "x": X_vals[:, 0].tolist(),
138
+ "y": X_vals[:, 1].tolist(),
139
+ "dxdt": dxdt_vals,
140
+ "dydt": dydt_vals
141
+ }
142
+
143
+ automatic_trajectories = []
144
+ n_side = int(math.ceil(math.sqrt(cantidad_trayectorias)))
145
+ if n_side > 1:
146
+ x0_auto = np.linspace(x_min, x_max, n_side)
147
+ y0_auto = np.linspace(y_min, y_max, n_side)
148
+ for xi in x0_auto:
149
+ for yi in y0_auto:
150
+ _, X_auto = Dynamic2DLinearService.rk4_sistema(A, B, np.array([xi, yi]), t0, t_fin, h)
151
+ automatic_trajectories.append({
152
+ "x": X_auto[::5, 0].tolist(),
153
+ "y": X_auto[::5, 1].tolist()
154
+ })
155
+
156
+ # Limpieza de decimales nulos (.0) para SymPy
157
+ def to_num(val):
158
+ return int(val) if val.is_integer() else val
159
+
160
+ a_sym, b_sym = to_num(a), to_num(b)
161
+ c_sym, d_sym = to_num(c), to_num(d)
162
+ e_sym, f_sym = to_num(e), to_num(f)
163
+ x0_sym, y0_sym = to_num(x0), to_num(y0)
164
+ t0_sym = to_num(t0)
165
+
166
+ xs = sp.Function('x')
167
+ ys = sp.Function('y')
168
+ t_sym = sp.Symbol('t', real=True)
169
+
170
+ sol_homogenea_latex = []
171
+ try:
172
+ # Para la solución homogénea, forzamos B a ser [0, 0]
173
+ eq1_h = sp.Eq(sp.diff(xs(t_sym), t_sym), a_sym * xs(t_sym) + b_sym * ys(t_sym))
174
+ eq2_h = sp.Eq(sp.diff(ys(t_sym), t_sym), c_sym * xs(t_sym) + d_sym * ys(t_sym))
175
+ sol_homogenea = sp.dsolve([eq1_h, eq2_h], ics={xs(t0_sym): x0_sym, ys(t0_sym): y0_sym})
176
+
177
+ if isinstance(sol_homogenea, list):
178
+ sol_homogenea_latex = [sp.latex(eq) for eq in sol_homogenea]
179
+ else:
180
+ sol_homogenea_latex = [sp.latex(sol_homogenea)]
181
+ except Exception:
182
+ sol_homogenea_latex = ["SymPy no pudo resolver la parte homogénea."]
183
+
184
+ # Solución General (No Homogénea)
185
+ sol_general_latex = []
186
+ try:
187
+ eq1_g = sp.Eq(sp.diff(xs(t_sym), t_sym), a_sym * xs(t_sym) + b_sym * ys(t_sym) + e_sym)
188
+ eq2_g = sp.Eq(sp.diff(ys(t_sym), t_sym), c_sym * xs(t_sym) + d_sym * ys(t_sym) + f_sym)
189
+ sol_general = sp.dsolve([eq1_g, eq2_g], ics={xs(t0_sym): x0_sym, ys(t0_sym): y0_sym})
190
+
191
+ if isinstance(sol_general, list):
192
+ sol_general_latex = [sp.latex(eq) for eq in sol_general]
193
+ else:
194
+ sol_general_latex = [sp.latex(sol_general)]
195
+ except Exception:
196
+ sol_general_latex = ["SymPy no pudo resolver el sistema completo."]
197
+
198
+ # Solución Particular (Punto de equilibrio si B es constante)
199
+ sol_particular_latex = []
200
+ if equilibrio_unico and equilibrio_pnto:
201
+ sol_particular_latex = [
202
+ f"x_p(t) = {equilibrio_pnto['x']}",
203
+ f"y_p(t) = {equilibrio_pnto['y']}"
204
+ ]
205
+
206
+ # Nulclinas
207
+ nulclina_x_pts = []
208
+ if abs(b) > 1e-9:
209
+ nulclina_x_pts = [[x_min, (-a * x_min - e) / b], [x_max, (-a * x_max - e) / b]]
210
+ elif abs(a) > 1e-9:
211
+ nulclina_x_pts = [[-e / a, y_min], [-e / a, y_max]]
212
+
213
+ nulclina_y_pts = []
214
+ if abs(d) > 1e-9:
215
+ nulclina_y_pts = [[x_min, (-c * x_min - f) / d], [x_max, (-c * x_max - f) / d]]
216
+ elif abs(c) > 1e-9:
217
+ nulclina_y_pts = [[-f / c, y_min], [-f / c, y_max]]
218
+
219
+ # Despejar y(x) en las Nulclinas con SymPy
220
+ x_sym, y_sym_var = sp.symbols('x y', real=True)
221
+
222
+ nul_x_expr = sp.Eq(a_sym * x_sym + b_sym * y_sym_var + e_sym, 0)
223
+ try:
224
+ sol_x = sp.solve(nul_x_expr, y_sym_var)
225
+ nul_x_despejada = f"y = {str(sol_x[0]).replace('**', '^').replace('*', ' ')}" if sol_x else "No despejable como y(x)"
226
+ except Exception:
227
+ nul_x_despejada = "No despejable como y(x)"
228
+
229
+ nul_y_expr = sp.Eq(c_sym * x_sym + d_sym * y_sym_var + f_sym, 0)
230
+ try:
231
+ sol_y = sp.solve(nul_y_expr, y_sym_var)
232
+ nul_y_despejada = f"y = {str(sol_y[0]).replace('**', '^').replace('*', ' ')}" if sol_y else "No despejable como y(x)"
233
+ except Exception:
234
+ nul_y_despejada = "No despejable como y(x)"
235
+
236
+ real_eigenvectors_lines = []
237
+ if equilibrio_unico:
238
+ for i in range(len(autovalores)):
239
+ if abs(np.imag(autovalores[i])) < 1e-10:
240
+ vx, vy = float(np.real(autovectores[0, i])), float(np.real(autovectores[1, i]))
241
+ norm = math.sqrt(vx**2 + vy**2)
242
+ if norm > 1e-9:
243
+ vx, vy = vx / norm, vy / norm
244
+ real_eigenvectors_lines.append([
245
+ [equilibrio_pnto["x"] - 20 * vx, equilibrio_pnto["y"] - 20 * vy],
246
+ [equilibrio_pnto["x"] + 20 * vx, equilibrio_pnto["y"] + 20 * vy]
247
+ ])
248
+
249
+ return {
250
+ "matrix_a": [[a, b], [c, d]],
251
+ "vector_b": [e, f],
252
+ "traza": traza,
253
+ "determinante": det_A,
254
+ "discriminante": discriminante,
255
+ "clasificacion": clasificacion,
256
+ "comportamiento": comportamiento,
257
+ "equilibrio": equilibrio_pnto,
258
+ "equilibrio_unico": equilibrio_unico,
259
+ "autovalores": list_autovalores,
260
+ "autovectores": list_autovectores,
261
+ "solucion_homogenea_latex": sol_homogenea_latex,
262
+ "solucion_particular_latex": sol_particular_latex,
263
+ "solucion_general_latex": sol_general_latex,
264
+ "nulclina_x": {"puntos": nulclina_x_pts, "ecuacion_despejada": nul_x_despejada},
265
+ "nulclina_y": {"puntos": nulclina_y_pts, "ecuacion_despejada": nul_y_despejada},
266
+ "principal_trajectory": principal_trajectory,
267
+ "automatic_trajectories": automatic_trajectories,
268
+ "real_eigenvectors_lines": real_eigenvectors_lines
269
+ }