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.
- mathmethods/__init__.py +7 -0
- mathmethods/__main__.py +6 -0
- mathmethods/compiler.py +202 -0
- mathmethods/core/__init__.py +36 -0
- mathmethods/core/differentiation.py +122 -0
- mathmethods/core/dynamic_1d.py +567 -0
- mathmethods/core/dynamic_2d_conservative.py +283 -0
- mathmethods/core/dynamic_2d_lanchester.py +264 -0
- mathmethods/core/dynamic_2d_linear.py +269 -0
- mathmethods/core/dynamic_2d_non_homogeneous.py +710 -0
- mathmethods/core/dynamic_2d_nonlinear.py +298 -0
- mathmethods/core/integration.py +396 -0
- mathmethods/core/interpolation.py +126 -0
- mathmethods/core/monte_carlo.py +312 -0
- mathmethods/core/ode.py +154 -0
- mathmethods/core/root_finding.py +339 -0
- mathmethods/core/utils.py +226 -0
- mathmethods/server.py +1414 -0
- mathmethods_mcp-0.1.0.dist-info/METADATA +436 -0
- mathmethods_mcp-0.1.0.dist-info/RECORD +24 -0
- mathmethods_mcp-0.1.0.dist-info/WHEEL +5 -0
- mathmethods_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- mathmethods_mcp-0.1.0.dist-info/licenses/LICENSE +21 -0
- mathmethods_mcp-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Integracion Numerica - Formulas de Newton-Cotes
|
|
3
|
+
- Trapecio (simple y compuesto)
|
|
4
|
+
- Rectangulo (punto medio compuesto)
|
|
5
|
+
- Simpson 1/3 (simple y compuesto)
|
|
6
|
+
- Simpson 3/8 (simple y compuesto)
|
|
7
|
+
"""
|
|
8
|
+
import numpy as np
|
|
9
|
+
import sympy as sp
|
|
10
|
+
from typing import Dict, Callable
|
|
11
|
+
import warnings
|
|
12
|
+
from .utils import safe_sympify
|
|
13
|
+
|
|
14
|
+
class IntegrationService:
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def compilar_funcion(texto_funcion: str) -> Callable:
|
|
18
|
+
try:
|
|
19
|
+
x_sym = sp.Symbol('x')
|
|
20
|
+
diccionario_local = {'e': sp.E, 'pi': sp.pi}
|
|
21
|
+
expr = safe_sympify(texto_funcion, local_dict=diccionario_local, allowed_symbols=['x'])
|
|
22
|
+
func_base = sp.lambdify(x_sym, expr, 'numpy')
|
|
23
|
+
|
|
24
|
+
def func_inteligente(x_val):
|
|
25
|
+
with warnings.catch_warnings():
|
|
26
|
+
warnings.simplefilter("ignore")
|
|
27
|
+
y_val = func_base(x_val)
|
|
28
|
+
|
|
29
|
+
# Intentar convertir a float, si genera complejos pasa a NaN
|
|
30
|
+
try:
|
|
31
|
+
y_array = np.asarray(y_val, dtype=float)
|
|
32
|
+
except TypeError:
|
|
33
|
+
y_array = np.full_like(x_val, np.nan, dtype=float)
|
|
34
|
+
|
|
35
|
+
if np.isscalar(x_val) and y_array.ndim == 0:
|
|
36
|
+
if np.isnan(y_array) or np.isinf(y_array):
|
|
37
|
+
lim = sp.limit(expr, x_sym, x_val)
|
|
38
|
+
# Solo aceptamos limites reales finitos
|
|
39
|
+
if lim.is_finite and lim.is_real:
|
|
40
|
+
val = float(lim)
|
|
41
|
+
if getattr(func_inteligente, 'grabar_rescates', False):
|
|
42
|
+
func_inteligente.limites_calculados.append((float(x_val), val))
|
|
43
|
+
return val
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError(f"IMPROPIA: Indeterminación o resultado imaginario en x = {float(x_val):.4f}")
|
|
46
|
+
return float(y_array)
|
|
47
|
+
|
|
48
|
+
else:
|
|
49
|
+
if y_array.ndim == 0:
|
|
50
|
+
y_array = np.full_like(x_val, float(y_array))
|
|
51
|
+
|
|
52
|
+
if np.any(np.isnan(y_array)) or np.any(np.isinf(y_array)):
|
|
53
|
+
intentos = 0
|
|
54
|
+
for i in range(len(y_array)):
|
|
55
|
+
if np.isnan(y_array[i]) or np.isinf(y_array[i]):
|
|
56
|
+
intentos += 1
|
|
57
|
+
if intentos > 15:
|
|
58
|
+
raise ValueError("IMPROPIA: Función no definida o compleja en gran parte del intervalo.")
|
|
59
|
+
|
|
60
|
+
lim = sp.limit(expr, x_sym, x_val[i])
|
|
61
|
+
if lim.is_finite and lim.is_real:
|
|
62
|
+
val = float(lim)
|
|
63
|
+
y_array[i] = val
|
|
64
|
+
if getattr(func_inteligente, 'grabar_rescates', False):
|
|
65
|
+
func_inteligente.limites_calculados.append((float(x_val[i]), val))
|
|
66
|
+
else:
|
|
67
|
+
raise ValueError(f"IMPROPIA: Asíntota o raíz imaginaria en x = {x_val[i]:.4f}")
|
|
68
|
+
return y_array
|
|
69
|
+
|
|
70
|
+
func_inteligente.limites_calculados = []
|
|
71
|
+
func_inteligente.grabar_rescates = False
|
|
72
|
+
func_inteligente.expr_sympy = expr
|
|
73
|
+
func_inteligente.x_symbol = x_sym
|
|
74
|
+
return func_inteligente
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise ValueError(f"Error compilando funcion: {str(e)}")
|
|
77
|
+
|
|
78
|
+
@staticmethod
|
|
79
|
+
def _orden_derivada_et(metodo: str) -> int:
|
|
80
|
+
if metodo in ["rectangulo", "trapecio"]:
|
|
81
|
+
return 2
|
|
82
|
+
if metodo in ["simpson13", "simpson38"]:
|
|
83
|
+
return 4
|
|
84
|
+
raise ValueError("Método no soportado para error de truncamiento")
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _coeficiente_et(a: float, b: float, n: int, metodo: str) -> float:
|
|
88
|
+
if metodo == "rectangulo":
|
|
89
|
+
return ((b - a) ** 3) / (24 * (n ** 2))
|
|
90
|
+
if metodo == "trapecio":
|
|
91
|
+
return -((b - a) ** 3) / (12 * (n ** 2))
|
|
92
|
+
if metodo == "simpson13":
|
|
93
|
+
return -((b - a) ** 5) / (180 * (n ** 4))
|
|
94
|
+
if metodo == "simpson38":
|
|
95
|
+
return -((b - a) ** 5) / (80 * (n ** 4))
|
|
96
|
+
raise ValueError("Método no soportado para error de truncamiento")
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def _derivada_n_valor(expr: sp.Expr, x_sym: sp.Symbol, n: int, punto: float) -> float:
|
|
100
|
+
return float(sp.diff(expr, x_sym, n).subs(x_sym, punto))
|
|
101
|
+
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _calcular_error_truncamiento_epsilon(
|
|
104
|
+
f: Callable,
|
|
105
|
+
a: float,
|
|
106
|
+
b: float,
|
|
107
|
+
n: int,
|
|
108
|
+
metodo: str,
|
|
109
|
+
epsilon: float,
|
|
110
|
+
precision: int,
|
|
111
|
+
):
|
|
112
|
+
if epsilon is None:
|
|
113
|
+
return "No definido (falta epsilon)"
|
|
114
|
+
|
|
115
|
+
if epsilon < min(a, b) or epsilon > max(a, b):
|
|
116
|
+
return "No definido (epsilon fuera del intervalo [a,b])"
|
|
117
|
+
|
|
118
|
+
expr = getattr(f, "expr_sympy", None)
|
|
119
|
+
x_sym = getattr(f, "x_symbol", None)
|
|
120
|
+
if expr is None or x_sym is None:
|
|
121
|
+
return "No definido (función simbólica no disponible)"
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
orden = IntegrationService._orden_derivada_et(metodo)
|
|
125
|
+
coeficiente = IntegrationService._coeficiente_et(a, b, n, metodo)
|
|
126
|
+
derivada = IntegrationService._derivada_n_valor(expr, x_sym, orden, epsilon)
|
|
127
|
+
et = coeficiente * derivada
|
|
128
|
+
return {
|
|
129
|
+
"epsilon": round(float(epsilon), precision),
|
|
130
|
+
"orden_derivada": orden,
|
|
131
|
+
"derivada_en_epsilon": round(float(derivada), precision),
|
|
132
|
+
"error_truncamiento": round(float(et), precision),
|
|
133
|
+
}
|
|
134
|
+
except Exception:
|
|
135
|
+
return "Indefinido matemáticamente en epsilon"
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _verificar_asintota(y_array: np.ndarray):
|
|
139
|
+
if np.any(np.isnan(y_array)) or np.any(np.isinf(y_array)):
|
|
140
|
+
raise ValueError("IMPROPIA: Indeterminación o valores imaginarios detectados.")
|
|
141
|
+
if np.any(np.abs(y_array) > 1e10):
|
|
142
|
+
raise ValueError("IMPROPIA: Asíntota vertical detectada. El valor se dispara al infinito.")
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def _validar_integral(f: Callable, a: float, b: float):
|
|
146
|
+
# Escaneo estrictamente interior para evitar colapsos con bordes complejos
|
|
147
|
+
margen = (b - a) * 0.001
|
|
148
|
+
x_test = np.linspace(a + margen, b - margen, 1000)
|
|
149
|
+
with warnings.catch_warnings():
|
|
150
|
+
warnings.simplefilter("ignore")
|
|
151
|
+
y_test = f(x_test)
|
|
152
|
+
IntegrationService._verificar_asintota(y_test)
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def _segunda_derivada_numerica(f: Callable, x: np.ndarray, dx: float = 1e-4) -> np.ndarray:
|
|
156
|
+
return (f(x + dx) - 2 * f(x) + f(x - dx)) / (dx**2)
|
|
157
|
+
|
|
158
|
+
@staticmethod
|
|
159
|
+
def _cuarta_derivada_numerica(f: Callable, x: np.ndarray, dx: float = 1e-3) -> np.ndarray:
|
|
160
|
+
return (f(x + 2*dx) - 4*f(x + dx) + 6*f(x) - 4*f(x - dx) + f(x - 2*dx)) / (dx**4)
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _calcular_cota_error(f: Callable, a: float, b: float, n: int, metodo: str, precision: int) -> str:
|
|
164
|
+
"""Calcula el error de forma super blindada evitando salir de los bordes."""
|
|
165
|
+
try:
|
|
166
|
+
# Apagamos los avisos de rescate para no ensuciar la libreta
|
|
167
|
+
estado_previo = getattr(f, 'grabar_rescates', False)
|
|
168
|
+
f.grabar_rescates = False
|
|
169
|
+
|
|
170
|
+
if metodo in ["rectangulo", "trapecio"]:
|
|
171
|
+
dx = 1e-4
|
|
172
|
+
margen = 2 * dx
|
|
173
|
+
if b - a <= 2 * margen: return "Intervalo muy chico"
|
|
174
|
+
x_fino = np.linspace(a + margen, b - margen, 1000)
|
|
175
|
+
deriv = IntegrationService._segunda_derivada_numerica(f, x_fino, dx)
|
|
176
|
+
deriv = deriv[~np.isnan(deriv)]
|
|
177
|
+
if len(deriv) == 0: return "Indefinido"
|
|
178
|
+
max_deriv = np.max(np.abs(deriv))
|
|
179
|
+
|
|
180
|
+
if max_deriv > 1e7: return "Diverge (Derivada no acotada)"
|
|
181
|
+
val = ((b - a)**3 / (24 * n**2)) if metodo == "rectangulo" else ((b - a)**3 / (12 * n**2))
|
|
182
|
+
ans = round(float(val * max_deriv), precision)
|
|
183
|
+
|
|
184
|
+
elif metodo in ["simpson13", "simpson38"]:
|
|
185
|
+
dx = 1e-3
|
|
186
|
+
margen = 3 * dx
|
|
187
|
+
if b - a <= 2 * margen: return "Intervalo muy chico"
|
|
188
|
+
x_fino = np.linspace(a + margen, b - margen, 1000)
|
|
189
|
+
deriv = IntegrationService._cuarta_derivada_numerica(f, x_fino, dx)
|
|
190
|
+
deriv = deriv[~np.isnan(deriv)]
|
|
191
|
+
if len(deriv) == 0: return "Indefinido"
|
|
192
|
+
max_deriv = np.max(np.abs(deriv))
|
|
193
|
+
|
|
194
|
+
if max_deriv > 1e7: return "Diverge (Derivada no acotada)"
|
|
195
|
+
val = ((b - a)**5 / (180 * n**4)) if metodo == "simpson13" else ((b - a)**5 / (80 * n**4))
|
|
196
|
+
ans = round(float(val * max_deriv), precision)
|
|
197
|
+
|
|
198
|
+
f.grabar_rescates = estado_previo
|
|
199
|
+
return str(ans)
|
|
200
|
+
except Exception:
|
|
201
|
+
return "Indefinido matemáticamente"
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def rectangulo_compuesto(f: Callable, a: float, b: float, n: int, precision: int = 8, epsilon: float = None) -> Dict:
|
|
205
|
+
IntegrationService._validar_integral(f, a, b)
|
|
206
|
+
h = (b - a) / n
|
|
207
|
+
x = np.linspace(a, b, n + 1)
|
|
208
|
+
x_medio = np.linspace(a + h/2, b - h/2, n)
|
|
209
|
+
|
|
210
|
+
f.limites_calculados = []
|
|
211
|
+
f.grabar_rescates = True
|
|
212
|
+
with warnings.catch_warnings():
|
|
213
|
+
warnings.simplefilter("ignore")
|
|
214
|
+
y_medio = f(x_medio)
|
|
215
|
+
f.grabar_rescates = False
|
|
216
|
+
|
|
217
|
+
IntegrationService._verificar_asintota(y_medio)
|
|
218
|
+
integral = h * np.sum(y_medio)
|
|
219
|
+
cota_error = IntegrationService._calcular_cota_error(f, a, b, n, "rectangulo", precision)
|
|
220
|
+
et_puntual = IntegrationService._calcular_error_truncamiento_epsilon(f, a, b, n, "rectangulo", epsilon, precision)
|
|
221
|
+
|
|
222
|
+
tabla = [{"i": 0, "x_n": round(float(x[0]), precision), "x_medio": None, "f_x_medio": None}]
|
|
223
|
+
for i in range(n):
|
|
224
|
+
tabla.append({"i": i + 1, "x_n": round(float(x[i+1]), precision), "x_medio": round(float(x_medio[i]), precision), "f_x_medio": round(float(y_medio[i]), precision)})
|
|
225
|
+
|
|
226
|
+
intermedios = " + ".join([f"{yi:.{precision}f}" for yi in y_medio])
|
|
227
|
+
desarrollo = f"I ≈ {h:.4f} [ {intermedios} ]"
|
|
228
|
+
|
|
229
|
+
rescates_crudos = getattr(f, 'limites_calculados', [])
|
|
230
|
+
notas_unicas = list(dict.fromkeys([f"Indeterminación en x = {pt:.4f}. Se aplicó Límite: f(x) = {val:.4f}" for pt, val in rescates_crudos]))
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
"metodo": "Rectangulo Compuesto", "a": a, "b": b, "n": n, "h": round(h, precision),
|
|
234
|
+
"integral": round(float(integral), precision), "cota_error": cota_error,
|
|
235
|
+
"error_truncamiento": et_puntual["error_truncamiento"] if isinstance(et_puntual, dict) else et_puntual,
|
|
236
|
+
"detalle_error_truncamiento": et_puntual if isinstance(et_puntual, dict) else None,
|
|
237
|
+
"tabla": tabla, "desarrollo": desarrollo, "notas": notas_unicas
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def trapecio_compuesto(f: Callable, a: float, b: float, n: int, precision: int = 8, epsilon: float = None) -> Dict:
|
|
242
|
+
IntegrationService._validar_integral(f, a, b)
|
|
243
|
+
h = (b - a) / n
|
|
244
|
+
x = np.linspace(a, b, n + 1)
|
|
245
|
+
|
|
246
|
+
f.limites_calculados = []
|
|
247
|
+
f.grabar_rescates = True
|
|
248
|
+
with warnings.catch_warnings():
|
|
249
|
+
warnings.simplefilter("ignore")
|
|
250
|
+
y = f(x)
|
|
251
|
+
f.grabar_rescates = False
|
|
252
|
+
|
|
253
|
+
IntegrationService._verificar_asintota(y)
|
|
254
|
+
S = y[0] + y[-1] + 2 * np.sum(y[1:-1])
|
|
255
|
+
integral = (h / 2) * S
|
|
256
|
+
cota_error = IntegrationService._calcular_cota_error(f, a, b, n, "trapecio", precision)
|
|
257
|
+
et_puntual = IntegrationService._calcular_error_truncamiento_epsilon(f, a, b, n, "trapecio", epsilon, precision)
|
|
258
|
+
|
|
259
|
+
tabla = [{"i": i, "x_n": round(float(x[i]), precision), "f_x_n": round(float(y[i]), precision)} for i in range(len(x))]
|
|
260
|
+
desarrollo = f"I ≈ {h:.4f}/2 [ {y[0]:.{precision}f} + {y[-1]:.{precision}f} ]" if n == 1 else f"I ≈ {h:.4f}/2 [ {y[0]:.{precision}f} + 2({' + '.join([f'{yi:.{precision}f}' for yi in y[1:-1]])}) + {y[-1]:.{precision}f} ]"
|
|
261
|
+
|
|
262
|
+
rescates_crudos = getattr(f, 'limites_calculados', [])
|
|
263
|
+
notas_unicas = list(dict.fromkeys([f"Indeterminación en x = {pt:.4f}. Se aplicó Límite: f(x) = {val:.4f}" for pt, val in rescates_crudos]))
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"metodo": "Trapecio Compuesto" if n > 1 else "Trapecio Simple", "a": a, "b": b, "n": n, "h": round(h, precision),
|
|
267
|
+
"integral": round(float(integral), precision), "cota_error": cota_error,
|
|
268
|
+
"error_truncamiento": et_puntual["error_truncamiento"] if isinstance(et_puntual, dict) else et_puntual,
|
|
269
|
+
"detalle_error_truncamiento": et_puntual if isinstance(et_puntual, dict) else None,
|
|
270
|
+
"tabla": tabla, "desarrollo": desarrollo, "notas": notas_unicas
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
@staticmethod
|
|
274
|
+
def simpson_13_compuesto(f: Callable, a: float, b: float, n: int, precision: int = 8, epsilon: float = None) -> Dict:
|
|
275
|
+
IntegrationService._validar_integral(f, a, b)
|
|
276
|
+
if n % 2 != 0: raise ValueError("n debe ser PAR para Simpson 1/3")
|
|
277
|
+
|
|
278
|
+
h = (b - a) / n
|
|
279
|
+
x = np.linspace(a, b, n + 1)
|
|
280
|
+
|
|
281
|
+
f.limites_calculados = []
|
|
282
|
+
f.grabar_rescates = True
|
|
283
|
+
with warnings.catch_warnings():
|
|
284
|
+
warnings.simplefilter("ignore")
|
|
285
|
+
y = f(x)
|
|
286
|
+
f.grabar_rescates = False
|
|
287
|
+
|
|
288
|
+
IntegrationService._verificar_asintota(y)
|
|
289
|
+
impares = y[1:n:2]
|
|
290
|
+
pares = y[2:n-1:2]
|
|
291
|
+
|
|
292
|
+
S = y[0] + y[-1] + 4 * np.sum(impares) + 2 * np.sum(pares)
|
|
293
|
+
integral = (h / 3) * S
|
|
294
|
+
cota_error = IntegrationService._calcular_cota_error(f, a, b, n, "simpson13", precision)
|
|
295
|
+
et_puntual = IntegrationService._calcular_error_truncamiento_epsilon(f, a, b, n, "simpson13", epsilon, precision)
|
|
296
|
+
|
|
297
|
+
tabla = [{"i": i, "x_n": round(float(x[i]), precision), "f_x_n": round(float(y[i]), precision)} for i in range(len(x))]
|
|
298
|
+
desarrollo = f"I ≈ {h:.4f}/3 [ {y[0]:.{precision}f} + 4({y[1]:.{precision}f}) + {y[2]:.{precision}f} ]" if n == 2 else f"I ≈ {h:.4f}/3 [ {y[0]:.{precision}f} + 4({' + '.join([f'{yi:.{precision}f}' for yi in impares])}) + 2({' + '.join([f'{yi:.{precision}f}' for yi in pares])}) + {y[-1]:.{precision}f} ]"
|
|
299
|
+
|
|
300
|
+
rescates_crudos = getattr(f, 'limites_calculados', [])
|
|
301
|
+
notas_unicas = list(dict.fromkeys([f"Indeterminación en x = {pt:.4f}. Se aplicó Límite: f(x) = {val:.4f}" for pt, val in rescates_crudos]))
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
"metodo": "Simpson 1/3 Compuesto" if n > 2 else "Simpson 1/3 Simple", "a": a, "b": b, "n": n, "h": round(h, precision),
|
|
305
|
+
"integral": round(float(integral), precision), "cota_error": cota_error,
|
|
306
|
+
"error_truncamiento": et_puntual["error_truncamiento"] if isinstance(et_puntual, dict) else et_puntual,
|
|
307
|
+
"detalle_error_truncamiento": et_puntual if isinstance(et_puntual, dict) else None,
|
|
308
|
+
"tabla": tabla, "desarrollo": desarrollo, "notas": notas_unicas
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def simpson_38_compuesto(f: Callable, a: float, b: float, n: int, precision: int = 8, epsilon: float = None) -> Dict:
|
|
313
|
+
IntegrationService._validar_integral(f, a, b)
|
|
314
|
+
if n % 3 != 0: raise ValueError("n debe ser multiplo de 3 para Simpson 3/8")
|
|
315
|
+
|
|
316
|
+
h = (b - a) / n
|
|
317
|
+
x = np.linspace(a, b, n + 1)
|
|
318
|
+
|
|
319
|
+
f.limites_calculados = []
|
|
320
|
+
f.grabar_rescates = True
|
|
321
|
+
with warnings.catch_warnings():
|
|
322
|
+
warnings.simplefilter("ignore")
|
|
323
|
+
y = f(x)
|
|
324
|
+
f.grabar_rescates = False
|
|
325
|
+
|
|
326
|
+
IntegrationService._verificar_asintota(y)
|
|
327
|
+
grupo_1, grupo_2, grupo_3 = y[1:n:3], y[2:n:3], y[3:n-1:3]
|
|
328
|
+
|
|
329
|
+
S = y[0] + y[-1] + 3 * (np.sum(grupo_1) + np.sum(grupo_2)) + 2 * np.sum(grupo_3)
|
|
330
|
+
integral = (3 * h / 8) * S
|
|
331
|
+
cota_error = IntegrationService._calcular_cota_error(f, a, b, n, "simpson38", precision)
|
|
332
|
+
et_puntual = IntegrationService._calcular_error_truncamiento_epsilon(f, a, b, n, "simpson38", epsilon, precision)
|
|
333
|
+
|
|
334
|
+
tabla = [{"i": i, "x_n": round(float(x[i]), precision), "f_x_n": round(float(y[i]), precision)} for i in range(len(x))]
|
|
335
|
+
if n == 3: desarrollo = f"I ≈ 3({h:.4f})/8 [ {y[0]:.{precision}f} + 3({y[1]:.{precision}f} + {y[2]:.{precision}f}) + {y[3]:.{precision}f} ]"
|
|
336
|
+
else: desarrollo = f"I ≈ 3({h:.4f})/8 [ {y[0]:.{precision}f} + 3({' + '.join([f'{yi:.{precision}f}' for yi in np.concatenate((grupo_1, grupo_2))])}){(' + 2(' + ' + '.join([f'{yi:.{precision}f}' for yi in grupo_3]) + ')') if len(grupo_3) > 0 else ''} + {y[-1]:.{precision}f} ]"
|
|
337
|
+
|
|
338
|
+
rescates_crudos = getattr(f, 'limites_calculados', [])
|
|
339
|
+
notas_unicas = list(dict.fromkeys([f"Indeterminación en x = {pt:.4f}. Se aplicó Límite: f(x) = {val:.4f}" for pt, val in rescates_crudos]))
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
"metodo": "Simpson 3/8 Compuesto" if n > 3 else "Simpson 3/8 Simple", "a": a, "b": b, "n": n, "h": round(h, precision),
|
|
343
|
+
"integral": round(float(integral), precision), "cota_error": cota_error,
|
|
344
|
+
"error_truncamiento": et_puntual["error_truncamiento"] if isinstance(et_puntual, dict) else et_puntual,
|
|
345
|
+
"detalle_error_truncamiento": et_puntual if isinstance(et_puntual, dict) else None,
|
|
346
|
+
"tabla": tabla, "desarrollo": desarrollo, "notas": notas_unicas
|
|
347
|
+
}
|
|
348
|
+
@staticmethod
|
|
349
|
+
def comparar_metodos(func_str: str, a: float, b: float, n: int, precision: int = 8, epsilon: float = None) -> Dict:
|
|
350
|
+
"""
|
|
351
|
+
Orquestador que ejecuta los 4 métodos de integración y devuelve un resumen comparativo.
|
|
352
|
+
"""
|
|
353
|
+
resultados = {
|
|
354
|
+
"rectangulo": None,
|
|
355
|
+
"trapecio": None,
|
|
356
|
+
"simpson_13": None,
|
|
357
|
+
"simpson_38": None
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
try:
|
|
361
|
+
f = IntegrationService.compilar_funcion(func_str)
|
|
362
|
+
except Exception as e:
|
|
363
|
+
return {"error_global": str(e)}
|
|
364
|
+
|
|
365
|
+
# 1. Rectángulo Medio Compuesto
|
|
366
|
+
try:
|
|
367
|
+
resultados["rectangulo"] = IntegrationService.rectangulo_compuesto(f, a, b, n, precision, epsilon)
|
|
368
|
+
resultados["rectangulo"]["exito"] = True
|
|
369
|
+
except Exception as e:
|
|
370
|
+
resultados["rectangulo"] = {"exito": False, "error_msg": str(e)}
|
|
371
|
+
|
|
372
|
+
# 2. Trapecio Compuesto
|
|
373
|
+
try:
|
|
374
|
+
resultados["trapecio"] = IntegrationService.trapecio_compuesto(f, a, b, n, precision, epsilon)
|
|
375
|
+
resultados["trapecio"]["exito"] = True
|
|
376
|
+
except Exception as e:
|
|
377
|
+
resultados["trapecio"] = {"exito": False, "error_msg": str(e)}
|
|
378
|
+
|
|
379
|
+
# 3. Simpson 1/3 Compuesto
|
|
380
|
+
try:
|
|
381
|
+
resultados["simpson_13"] = IntegrationService.simpson_13_compuesto(f, a, b, n, precision, epsilon)
|
|
382
|
+
resultados["simpson_13"]["exito"] = True
|
|
383
|
+
except Exception as e:
|
|
384
|
+
resultados["simpson_13"] = {"exito": False, "error_msg": str(e)}
|
|
385
|
+
|
|
386
|
+
# 4. Simpson 3/8 Compuesto
|
|
387
|
+
try:
|
|
388
|
+
resultados["simpson_38"] = IntegrationService.simpson_38_compuesto(f, a, b, n, precision, epsilon)
|
|
389
|
+
resultados["simpson_38"]["exito"] = True
|
|
390
|
+
except Exception as e:
|
|
391
|
+
resultados["simpson_38"] = {"exito": False, "error_msg": str(e)}
|
|
392
|
+
|
|
393
|
+
return {
|
|
394
|
+
"parametros": {"f(x)": func_str, "a": a, "b": b, "n": n, "epsilon": epsilon},
|
|
395
|
+
"comparativa": resultados
|
|
396
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Interpolación Numérica - Polinomio de Lagrange
|
|
3
|
+
"""
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
import sympy as sp
|
|
7
|
+
import math
|
|
8
|
+
from .utils import safe_sympify
|
|
9
|
+
|
|
10
|
+
class InterpolationService:
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def compilar_funcion(texto_funcion: str):
|
|
14
|
+
"""Convierte string a función simbólica."""
|
|
15
|
+
try:
|
|
16
|
+
x = sp.Symbol('x')
|
|
17
|
+
diccionario_local = {'e': sp.E, 'pi': sp.pi}
|
|
18
|
+
expr = safe_sympify(texto_funcion, local_dict=diccionario_local, allowed_symbols=['x'])
|
|
19
|
+
return sp.lambdify(x, expr, 'numpy'), expr
|
|
20
|
+
except Exception as e:
|
|
21
|
+
raise ValueError(f"Error compilando: {str(e)}")
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def lagrange(puntos_x: List[float], x_eval: float = None,
|
|
25
|
+
func_str: str = None, puntos_y: List[float] = None,
|
|
26
|
+
precision: int = 8) -> Dict:
|
|
27
|
+
|
|
28
|
+
x_sym = sp.Symbol('x')
|
|
29
|
+
n = len(puntos_x)
|
|
30
|
+
|
|
31
|
+
# Obtener los valores y
|
|
32
|
+
if func_str:
|
|
33
|
+
f, expr = InterpolationService.compilar_funcion(func_str)
|
|
34
|
+
puntos_y = [float(f(xi)) for xi in puntos_x]
|
|
35
|
+
elif puntos_y is None:
|
|
36
|
+
raise ValueError("Proveer func_str o puntos_y")
|
|
37
|
+
|
|
38
|
+
# Construir polinomio de Lagrange
|
|
39
|
+
P = 0
|
|
40
|
+
terminos_construccion = []
|
|
41
|
+
|
|
42
|
+
for i in range(n):
|
|
43
|
+
L_i = 1
|
|
44
|
+
for j in range(n):
|
|
45
|
+
if i != j:
|
|
46
|
+
L_i *= (x_sym - puntos_x[j]) / (puntos_x[i] - puntos_x[j])
|
|
47
|
+
term = puntos_y[i] * L_i
|
|
48
|
+
P += term
|
|
49
|
+
|
|
50
|
+
# Guardamos el término para mostrarlo en el frontend
|
|
51
|
+
terminos_construccion.append({
|
|
52
|
+
"i": i,
|
|
53
|
+
"l_i_y_i": str(sp.expand(term))
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
P_expanded = sp.expand(P)
|
|
57
|
+
grado_sym = sp.degree(P_expanded, x_sym)
|
|
58
|
+
# Parseo seguro del grado
|
|
59
|
+
grado = int(grado_sym) if hasattr(grado_sym, 'is_number') and grado_sym.is_number and not getattr(grado_sym, 'is_infinite', False) else 0
|
|
60
|
+
|
|
61
|
+
# Tabla de puntos
|
|
62
|
+
tabla = []
|
|
63
|
+
for xi, yi in zip(puntos_x, puntos_y):
|
|
64
|
+
tabla.append({
|
|
65
|
+
"x": round(xi, precision),
|
|
66
|
+
"y": round(float(yi), precision)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
resultado = {
|
|
70
|
+
"metodo": "Lagrange",
|
|
71
|
+
"puntos": tabla,
|
|
72
|
+
"polinomio": str(P_expanded),
|
|
73
|
+
"grado": grado,
|
|
74
|
+
"terminos": terminos_construccion
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Si hay x_eval, evaluar
|
|
78
|
+
if x_eval is not None:
|
|
79
|
+
P_eval = float(P_expanded.subs(x_sym, x_eval).evalf())
|
|
80
|
+
resultado["x_eval"] = x_eval
|
|
81
|
+
resultado["P_eval"] = round(P_eval, precision)
|
|
82
|
+
|
|
83
|
+
# Error local y cota global si tenemos función
|
|
84
|
+
if func_str:
|
|
85
|
+
f_eval = float(f(x_eval))
|
|
86
|
+
error_local = abs(f_eval - P_eval)
|
|
87
|
+
resultado["f_eval"] = round(f_eval, precision)
|
|
88
|
+
resultado["error_local"] = round(error_local, 8)
|
|
89
|
+
|
|
90
|
+
# --- CÁLCULO DE COTA DE ERROR GLOBAL ---
|
|
91
|
+
derivada_orden = n
|
|
92
|
+
derivada_expr = sp.diff(expr, x_sym, derivada_orden)
|
|
93
|
+
|
|
94
|
+
# Rango para buscar máximos [min_x, max_x]
|
|
95
|
+
intervalo_a = min(min(puntos_x), x_eval)
|
|
96
|
+
intervalo_b = max(max(puntos_x), x_eval)
|
|
97
|
+
x_vals = np.linspace(intervalo_a, intervalo_b, 1000)
|
|
98
|
+
|
|
99
|
+
# Máximo de la derivada
|
|
100
|
+
df_lambdify = sp.lambdify(x_sym, derivada_expr, 'numpy')
|
|
101
|
+
df_eval = df_lambdify(x_vals)
|
|
102
|
+
if np.isscalar(df_eval):
|
|
103
|
+
df_eval = np.full_like(x_vals, df_eval, dtype=float)
|
|
104
|
+
max_derivada = float(np.max(np.abs(df_eval)))
|
|
105
|
+
|
|
106
|
+
# Máximo de g(x) = prod(x - x_i)
|
|
107
|
+
g_vals = np.ones_like(x_vals, dtype=float)
|
|
108
|
+
for xi in puntos_x:
|
|
109
|
+
g_vals *= (x_vals - xi)
|
|
110
|
+
max_g = float(np.max(np.abs(g_vals)))
|
|
111
|
+
|
|
112
|
+
factorial_n = math.factorial(n)
|
|
113
|
+
cota_global = (max_derivada / factorial_n) * max_g
|
|
114
|
+
exito = cota_global >= error_local
|
|
115
|
+
|
|
116
|
+
resultado["analisis_error"] = {
|
|
117
|
+
"derivada_orden": n,
|
|
118
|
+
"derivada_expr": str(derivada_expr).replace('**', '^'),
|
|
119
|
+
"max_derivada": max_derivada,
|
|
120
|
+
"max_g": max_g,
|
|
121
|
+
"factorial": factorial_n,
|
|
122
|
+
"cota_global": cota_global,
|
|
123
|
+
"exito": exito
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return resultado
|