binary-equalab 1.0.0__py3-none-any.whl → 2.0.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.
binary_equalab/engine.py CHANGED
@@ -1,405 +1,492 @@
1
- """
2
- Binary EquaLab - Math Engine
3
- Core symbolic computation using SymPy with Spanish function translations.
4
- """
5
-
6
- import sympy as sp
7
- from sympy import (
8
- Symbol, symbols, sin, cos, tan, sqrt, exp, log, ln, pi, E, I,
9
- diff, integrate, limit, summation, simplify, expand, factor, solve,
10
- Abs, factorial, gamma, binomial, floor, ceiling,
11
- Matrix, det, Transpose
12
- )
13
- from sympy.parsing.sympy_parser import (
14
- parse_expr, standard_transformations, implicit_multiplication_application,
15
- convert_xor, function_exponentiation
16
- )
17
- from typing import Any, Union, List, Optional
18
- import re
19
-
20
- # Symbol shortcuts
21
- x, y, z, t, n, k = symbols('x y z t n k')
22
-
23
-
24
- class MathEngine:
25
- """
26
- Core math engine with Spanish function support.
27
- Wraps SymPy with user-friendly Spanish aliases.
28
- """
29
-
30
- def __init__(self):
31
- self.symbols = {'x': x, 'y': y, 'z': z, 't': t, 'n': n, 'k': k}
32
- self.last_result = None
33
- self.history: List[str] = []
34
-
35
- # Spanish SymPy function mapping
36
- self.function_map = {
37
- # Calculus
38
- 'derivar': self._derivar,
39
- 'integrar': self._integrar,
40
- 'limite': self._limite,
41
- 'sumatoria': self._sumatoria,
42
-
43
- # Algebra
44
- 'simplificar': self._simplificar,
45
- 'expandir': self._expandir,
46
- 'factorizar': self._factorizar,
47
- 'resolver': self._resolver,
48
-
49
- # Statistics
50
- 'media': self._media,
51
- 'mediana': self._mediana,
52
- 'desviacion': self._desviacion,
53
- 'varianza': self._varianza,
54
-
55
- # Finance
56
- 'van': self._van,
57
- 'tir': self._tir,
58
- 'depreciar': self._depreciar,
59
- 'interes_simple': self._interes_simple,
60
- 'interes_compuesto': self._interes_compuesto,
61
-
62
- # Trigonometry aliases
63
- 'seno': sin,
64
- 'coseno': cos,
65
- 'tangente': tan,
66
- 'arcoseno': sp.asin,
67
- 'arcocoseno': sp.acos,
68
- 'arcotangente': sp.atan,
69
- }
70
-
71
- def parse(self, expression: str) -> Any:
72
- """Parse a math expression string into SymPy."""
73
- # Preprocess Spanish functions
74
- expr = self._preprocess(expression)
75
-
76
- # Parse with transformations
77
- transformations = (
78
- standard_transformations +
79
- (implicit_multiplication_application, convert_xor, function_exponentiation)
80
- )
81
-
82
- try:
83
- result = parse_expr(expr, local_dict=self.symbols, transformations=transformations)
84
- return result
85
- except Exception as e:
86
- raise ValueError(f"Parse error: {e}")
87
-
88
- def evaluate(self, expression: str) -> Any:
89
- """Evaluate a math expression and return the result."""
90
- expression = expression.strip()
91
-
92
- if not expression:
93
- return None
94
-
95
- self.history.append(expression)
96
-
97
- # Check for function calls
98
- for func_name, func in self.function_map.items():
99
- if expression.startswith(f'{func_name}('):
100
- result = self._call_function(expression)
101
- self.last_result = result
102
- return result
103
-
104
- # Standard expression evaluation
105
- try:
106
- parsed = self.parse(expression)
107
- result = sp.simplify(parsed)
108
- self.last_result = result
109
- return result
110
- except Exception as e:
111
- raise ValueError(f"Evaluation error: {e}")
112
-
113
- def _preprocess(self, expr: str) -> str:
114
- """Convert Spanish function names and shortcuts."""
115
- # Replace Spanish trig
116
- replacements = {
117
- 'seno': 'sin',
118
- 'coseno': 'cos',
119
- 'tangente': 'tan',
120
- 'arcoseno': 'asin',
121
- 'arcocoseno': 'acos',
122
- 'arcotangente': 'atan',
123
- 'raiz': 'sqrt',
124
- 'absoluto': 'Abs',
125
- 'logaritmo': 'log',
126
- 'exponencial': 'exp',
127
- }
128
-
129
- for es, en in replacements.items():
130
- expr = re.sub(rf'\b{es}\b', en, expr, flags=re.IGNORECASE)
131
-
132
- # Handle ^ as power
133
- expr = expr.replace('^', '**')
134
-
135
- return expr
136
-
137
- def _call_function(self, expression: str) -> Any:
138
- """Call a Spanish function with arguments."""
139
- # Extract function name and args
140
- match = re.match(r'(\w+)\((.*)\)$', expression, re.DOTALL)
141
- if not match:
142
- raise ValueError(f"Invalid function call: {expression}")
143
-
144
- func_name = match.group(1)
145
- args_str = match.group(2)
146
-
147
- if func_name not in self.function_map:
148
- raise ValueError(f"Unknown function: {func_name}")
149
-
150
- func = self.function_map[func_name]
151
-
152
- # Parse arguments
153
- args = self._parse_args(args_str)
154
-
155
- return func(*args)
156
-
157
- def _parse_args(self, args_str: str) -> List[Any]:
158
- """Parse function arguments, handling nested parentheses."""
159
- args = []
160
- current = ""
161
- depth = 0
162
-
163
- for char in args_str:
164
- if char == '(':
165
- depth += 1
166
- current += char
167
- elif char == ')':
168
- depth -= 1
169
- current += char
170
- elif char == ',' and depth == 0:
171
- if current.strip():
172
- args.append(self._parse_single_arg(current.strip()))
173
- current = ""
174
- else:
175
- current += char
176
-
177
- if current.strip():
178
- args.append(self._parse_single_arg(current.strip()))
179
-
180
- return args
181
-
182
- def _parse_single_arg(self, arg: str) -> Any:
183
- """Parse a single argument - could be number, symbol, or expression."""
184
- try:
185
- return float(arg)
186
- except ValueError:
187
- pass
188
-
189
- if arg in self.symbols:
190
- return self.symbols[arg]
191
-
192
- return self.parse(arg)
193
-
194
- # ============ CALCULUS ============
195
-
196
- def _derivar(self, expr, var=None, n=1):
197
- """Derivative: derivar(x^2 + 3x, x) → 2x + 3"""
198
- if var is None:
199
- var = x
200
- return diff(expr, var, n)
201
-
202
- def _integrar(self, expr, var=None, a=None, b=None):
203
- """Integral: integrar(x^2, x) or integrar(x^2, x, 0, 1)"""
204
- if var is None:
205
- var = x
206
- if a is not None and b is not None:
207
- return integrate(expr, (var, a, b))
208
- return integrate(expr, var)
209
-
210
- def _limite(self, expr, var=None, punto=0, direccion=None):
211
- """Limit: limite(sin(x)/x, x, 0) → 1"""
212
- if var is None:
213
- var = x
214
- if direccion:
215
- return limit(expr, var, punto, direccion)
216
- return limit(expr, var, punto)
217
-
218
- def _sumatoria(self, expr, var=None, a=0, b=10):
219
- """Sum: sumatoria(n^2, n, 1, 10) → 385"""
220
- if var is None:
221
- var = n
222
- return summation(expr, (var, a, b))
223
-
224
- # ============ ALGEBRA ============
225
-
226
- def _simplificar(self, expr):
227
- """Simplify: simplificar((x^2-1)/(x-1)) → x+1"""
228
- return simplify(expr)
229
-
230
- def _expandir(self, expr):
231
- """Expand: expandir((x+1)^2) x^2 + 2x + 1"""
232
- return expand(expr)
233
-
234
- def _factorizar(self, expr):
235
- """Factor: factorizar(x^2 - 1) → (x-1)(x+1)"""
236
- return factor(expr)
237
-
238
- def _resolver(self, expr, var=None):
239
- """Solve: resolver(x^2 - 4, x) → [-2, 2]"""
240
- if var is None:
241
- var = x
242
- return solve(expr, var)
243
-
244
- # ============ STATISTICS ============
245
-
246
- def _media(self, *values):
247
- """Mean: media(1, 2, 3, 4, 5) → 3"""
248
- nums = [float(v) for v in values]
249
- return sum(nums) / len(nums)
250
-
251
- def _mediana(self, *values):
252
- """Median: mediana(1, 2, 3, 4, 5) → 3"""
253
- nums = sorted([float(v) for v in values])
254
- n = len(nums)
255
- mid = n // 2
256
- if n % 2 == 0:
257
- return (nums[mid - 1] + nums[mid]) / 2
258
- return nums[mid]
259
-
260
- def _desviacion(self, *values):
261
- """Standard deviation: desviacion(1, 2, 3, 4, 5)"""
262
- nums = [float(v) for v in values]
263
- mean = sum(nums) / len(nums)
264
- variance = sum((x - mean) ** 2 for x in nums) / len(nums)
265
- return variance ** 0.5
266
-
267
- def _varianza(self, *values):
268
- """Variance: varianza(1, 2, 3, 4, 5)"""
269
- nums = [float(v) for v in values]
270
- mean = sum(nums) / len(nums)
271
- return sum((x - mean) ** 2 for x in nums) / len(nums)
272
-
273
- # ============ FINANCE ============
274
-
275
- def _van(self, tasa, *flujos):
276
- """NPV: van(0.10, -1000, 300, 400, 500)"""
277
- r = float(tasa)
278
- result = 0
279
- for i, flujo in enumerate(flujos):
280
- result += float(flujo) / ((1 + r) ** i)
281
- return round(result, 2)
282
-
283
- def _tir(self, *flujos):
284
- """IRR: tir(-1000, 300, 400, 500) using Newton-Raphson"""
285
- flows = [float(f) for f in flujos]
286
-
287
- def npv(r):
288
- return sum(f / ((1 + r) ** i) for i, f in enumerate(flows))
289
-
290
- def npv_deriv(r):
291
- return sum(-i * f / ((1 + r) ** (i + 1)) for i, f in enumerate(flows))
292
-
293
- r = 0.1 # Initial guess
294
- for _ in range(100):
295
- npv_val = npv(r)
296
- if abs(npv_val) < 1e-10:
297
- break
298
- deriv = npv_deriv(r)
299
- if deriv == 0:
300
- break
301
- r = r - npv_val / deriv
302
-
303
- return round(r * 100, 2) # Return as percentage
304
-
305
- def _depreciar(self, costo, residual, años):
306
- """Straight-line depreciation: depreciar(10000, 1000, 5)"""
307
- c, r, n = float(costo), float(residual), int(años)
308
- annual = (c - r) / n
309
- schedule = []
310
- for i in range(n):
311
- schedule.append({
312
- 'año': i + 1,
313
- 'depreciacion': round(annual, 2),
314
- 'acumulado': round(annual * (i + 1), 2),
315
- 'valor_libro': round(c - annual * (i + 1), 2)
316
- })
317
- return schedule
318
-
319
- def _interes_simple(self, capital, tasa, tiempo):
320
- """Simple interest: interes_simple(1000, 0.05, 3)"""
321
- c, r, t = float(capital), float(tasa), float(tiempo)
322
- interest = c * r * t
323
- return {
324
- 'interes': round(interest, 2),
325
- 'monto_final': round(c + interest, 2)
326
- }
327
-
328
- def _interes_compuesto(self, capital, tasa, n, tiempo):
329
- """Compound interest: interes_compuesto(1000, 0.05, 12, 3)"""
330
- c, r, periods, t = float(capital), float(tasa), int(n), float(tiempo)
331
- monto = c * ((1 + r / periods) ** (periods * t))
332
- return {
333
- 'monto_final': round(monto, 2),
334
- 'interes': round(monto - c, 2)
335
- }
336
-
337
-
338
- # Convenience functions for direct import
339
- def derivar(expr, var=None, n=1):
340
- engine = MathEngine()
341
- return engine._derivar(engine.parse(str(expr)), var, n)
342
-
343
- def integrar(expr, var=None, a=None, b=None):
344
- engine = MathEngine()
345
- return engine._integrar(engine.parse(str(expr)), var, a, b)
346
-
347
- def limite(expr, var=None, punto=0):
348
- engine = MathEngine()
349
- return engine._limite(engine.parse(str(expr)), var, punto)
350
-
351
- def sumatoria(expr, var=None, a=0, b=10):
352
- engine = MathEngine()
353
- return engine._sumatoria(engine.parse(str(expr)), var, a, b)
354
-
355
- def simplificar(expr):
356
- engine = MathEngine()
357
- return engine._simplificar(engine.parse(str(expr)))
358
-
359
- def expandir(expr):
360
- engine = MathEngine()
361
- return engine._expandir(engine.parse(str(expr)))
362
-
363
- def factorizar(expr):
364
- engine = MathEngine()
365
- return engine._factorizar(engine.parse(str(expr)))
366
-
367
- def resolver(expr, var=None):
368
- engine = MathEngine()
369
- return engine._resolver(engine.parse(str(expr)), var)
370
-
371
- def van(tasa, *flujos):
372
- engine = MathEngine()
373
- return engine._van(tasa, *flujos)
374
-
375
- def tir(*flujos):
376
- engine = MathEngine()
377
- return engine._tir(*flujos)
378
-
379
- def depreciar(costo, residual, años):
380
- engine = MathEngine()
381
- return engine._depreciar(costo, residual, años)
382
-
383
- def interes_simple(capital, tasa, tiempo):
384
- engine = MathEngine()
385
- return engine._interes_simple(capital, tasa, tiempo)
386
-
387
- def interes_compuesto(capital, tasa, n, tiempo):
388
- engine = MathEngine()
389
- return engine._interes_compuesto(capital, tasa, n, tiempo)
390
-
391
- def media(*values):
392
- engine = MathEngine()
393
- return engine._media(*values)
394
-
395
- def mediana(*values):
396
- engine = MathEngine()
397
- return engine._mediana(*values)
398
-
399
- def desviacion(*values):
400
- engine = MathEngine()
401
- return engine._desviacion(*values)
402
-
403
- def varianza(*values):
404
- engine = MathEngine()
405
- return engine._varianza(*values)
1
+ """
2
+ Binary EquaLab - Math Engine
3
+ Core symbolic computation using SymPy with Spanish function translations.
4
+ """
5
+
6
+ import sympy as sp
7
+ from sympy import (
8
+ Symbol, symbols, sin, cos, tan, sqrt, exp, log, ln, pi, E, I,
9
+ diff, integrate, limit, summation, simplify, expand, factor, solve,
10
+ Abs, factorial, gamma, binomial, floor, ceiling,
11
+ Matrix, det, Transpose
12
+ )
13
+ from sympy.parsing.sympy_parser import (
14
+ parse_expr, standard_transformations, implicit_multiplication_application,
15
+ convert_xor, function_exponentiation
16
+ )
17
+ from typing import Any, Union, List, Optional
18
+ import re
19
+
20
+ from .parser_enhanced import EnhancedParser
21
+ from .sonify import AudioEngine
22
+ from .geometry import GeometryEngine
23
+
24
+ # Symbol shortcuts
25
+ x, y, z, t, n, k = symbols('x y z t n k')
26
+
27
+
28
+ class MathEngine:
29
+ """
30
+ Core math engine with Spanish function support.
31
+ Wraps SymPy with user-friendly Spanish aliases.
32
+ """
33
+
34
+ def __init__(self):
35
+ self.symbols = {'x': x, 'y': y, 'z': z, 't': t, 'n': n, 'k': k}
36
+ self.last_result = None
37
+ self.history: List[str] = []
38
+
39
+ # Spanish → SymPy function mapping
40
+ self.function_map = {
41
+ # Calculus
42
+ 'derivar': self._derivar,
43
+ 'integrar': self._integrar,
44
+ 'limite': self._limite,
45
+ 'sumatoria': self._sumatoria,
46
+
47
+ # Algebra
48
+ 'simplificar': self._simplificar,
49
+ 'expandir': self._expandir,
50
+ 'factorizar': self._factorizar,
51
+ 'resolver': self._resolver,
52
+
53
+ # Statistics
54
+ 'media': self._media,
55
+ 'mediana': self._mediana,
56
+ 'desviacion': self._desviacion,
57
+ 'varianza': self._varianza,
58
+
59
+ # Finance
60
+ 'van': self._van,
61
+ 'tir': self._tir,
62
+ 'depreciar': self._depreciar,
63
+ 'interes_simple': self._interes_simple,
64
+ 'interes_compuesto': self._interes_compuesto,
65
+
66
+ # Audio / Sonification
67
+ 'sonify': self._sonify,
68
+ 'sonificar': self._sonify,
69
+
70
+ # Geometry
71
+ 'distancia': self._distancia,
72
+ 'punto_medio': self._punto_medio,
73
+ 'pendiente': self._pendiente,
74
+ 'recta': self._recta,
75
+ 'circulo': self._circulo,
76
+
77
+ # Trigonometry aliases
78
+ 'seno': sin,
79
+ 'coseno': cos,
80
+ 'tangente': tan,
81
+ 'arcoseno': sp.asin,
82
+ 'arcocoseno': sp.acos,
83
+ 'arcotangente': sp.atan,
84
+ }
85
+
86
+ def parse(self, expression: str) -> Any:
87
+ """Parse a math expression string into SymPy."""
88
+ # Preprocess Spanish functions
89
+ expr = self._preprocess(expression)
90
+
91
+ # Parse with transformations
92
+ transformations = (
93
+ standard_transformations +
94
+ (implicit_multiplication_application, convert_xor, function_exponentiation)
95
+ )
96
+
97
+ try:
98
+ result = parse_expr(expr, local_dict=self.symbols, transformations=transformations)
99
+ return result
100
+ except Exception as e:
101
+ raise ValueError(f"Parse error: {e}")
102
+
103
+ def evaluate(self, expression: str) -> Any:
104
+ """Evaluate a math expression and return the result."""
105
+ expression = expression.strip()
106
+
107
+ if not expression:
108
+ return None
109
+
110
+ self.history.append(expression)
111
+
112
+ if expression.lower().strip() in ["sentimiento", "amor", "error", "feel"]:
113
+ return "Aquí el sentimiento existe. El error, no."
114
+
115
+ # Check for variable assignment (e.g., "a = 5")
116
+ assignment_match = re.match(r'^([a-zA-Z_]\w*)\s*=\s*(.+)$', expression)
117
+ if assignment_match:
118
+ var_name, val_expr = assignment_match.groups()
119
+ try:
120
+ # Calculate value first
121
+ val_result = self.evaluate(val_expr)
122
+ # Store in symbols
123
+ self.symbols[var_name] = val_result
124
+ return val_result
125
+ except Exception as e:
126
+ raise ValueError(f"Assignment error: {e}")
127
+
128
+ # Check for function calls
129
+ for func_name, func in self.function_map.items():
130
+ if expression.startswith(f'{func_name}('):
131
+ result = self._call_function(expression)
132
+ self.last_result = result
133
+ return result
134
+
135
+ # Standard expression evaluation
136
+ try:
137
+ parsed = self.parse(expression)
138
+ result = sp.simplify(parsed)
139
+ self.last_result = result
140
+ return result
141
+ except Exception as e:
142
+ raise ValueError(f"Evaluation error: {e}")
143
+
144
+ def _preprocess(self, expr: str) -> str:
145
+ """Convert Spanish function names and shortcuts."""
146
+ # Replace Spanish trig
147
+ replacements = {
148
+ 'seno': 'sin',
149
+ 'sen': 'sin',
150
+ 'coseno': 'cos',
151
+ 'tangente': 'tan',
152
+ 'arcoseno': 'asin',
153
+ 'arcocoseno': 'acos',
154
+ 'arcotangente': 'atan',
155
+ 'raiz': 'sqrt',
156
+ 'absoluto': 'Abs',
157
+ 'logaritmo': 'log',
158
+ 'exponencial': 'exp',
159
+ }
160
+
161
+ for es, en in replacements.items():
162
+ expr = re.sub(rf'\b{es}\b', en, expr, flags=re.IGNORECASE)
163
+
164
+ # Enhanced Parser Logic (Sugared Syntax)
165
+ expr = EnhancedParser.preprocess(expr)
166
+
167
+ # Handle ^ as power (fallback)
168
+ expr = expr.replace('^', '**')
169
+
170
+ return expr
171
+
172
+ def _call_function(self, expression: str) -> Any:
173
+ """Call a Spanish function with arguments."""
174
+ # Extract function name and args
175
+ match = re.match(r'(\w+)\((.*)\)$', expression, re.DOTALL)
176
+ if not match:
177
+ raise ValueError(f"Invalid function call: {expression}")
178
+
179
+ func_name = match.group(1)
180
+ args_str = match.group(2)
181
+
182
+ if func_name not in self.function_map:
183
+ raise ValueError(f"Unknown function: {func_name}")
184
+
185
+ func = self.function_map[func_name]
186
+
187
+ # Parse arguments
188
+ args = self._parse_args(args_str)
189
+
190
+ return func(*args)
191
+
192
+ def _parse_args(self, args_str: str) -> List[Any]:
193
+ """Parse function arguments, handling nested parentheses."""
194
+ args = []
195
+ current = ""
196
+ depth = 0
197
+
198
+ for char in args_str:
199
+ if char == '(':
200
+ depth += 1
201
+ current += char
202
+ elif char == ')':
203
+ depth -= 1
204
+ current += char
205
+ elif char == ',' and depth == 0:
206
+ if current.strip():
207
+ args.append(self._parse_single_arg(current.strip()))
208
+ current = ""
209
+ else:
210
+ current += char
211
+
212
+ if current.strip():
213
+ args.append(self._parse_single_arg(current.strip()))
214
+
215
+ return args
216
+
217
+ def _parse_single_arg(self, arg: str) -> Any:
218
+ """Parse a single argument - could be number, symbol, or expression."""
219
+ try:
220
+ return float(arg)
221
+ except ValueError:
222
+ pass
223
+
224
+ if arg in self.symbols:
225
+ return self.symbols[arg]
226
+
227
+ return self.parse(arg)
228
+
229
+ # ============ CALCULUS ============
230
+
231
+ def _derivar(self, expr, var=None, n=1):
232
+ """Derivative: derivar(x^2 + 3x, x) → 2x + 3"""
233
+ if var is None:
234
+ var = x
235
+ return diff(expr, var, n)
236
+
237
+ def _integrar(self, expr, var=None, a=None, b=None):
238
+ """Integral: integrar(x^2, x) or integrar(x^2, x, 0, 1)"""
239
+ if var is None:
240
+ var = x
241
+ if a is not None and b is not None:
242
+ return integrate(expr, (var, a, b))
243
+ return integrate(expr, var)
244
+
245
+ def _limite(self, expr, var=None, punto=0, direccion=None):
246
+ """Limit: limite(sin(x)/x, x, 0) → 1"""
247
+ if var is None:
248
+ var = x
249
+ if direccion:
250
+ return limit(expr, var, punto, direccion)
251
+ return limit(expr, var, punto)
252
+
253
+ def _sumatoria(self, expr, var=None, a=0, b=10):
254
+ """Sum: sumatoria(n^2, n, 1, 10) → 385"""
255
+ if var is None:
256
+ var = n
257
+ return summation(expr, (var, a, b))
258
+
259
+ # ============ ALGEBRA ============
260
+
261
+ def _simplificar(self, expr):
262
+ """Simplify: simplificar((x^2-1)/(x-1)) x+1"""
263
+ return simplify(expr)
264
+
265
+ def _expandir(self, expr):
266
+ """Expand: expandir((x+1)^2) → x^2 + 2x + 1"""
267
+ return expand(expr)
268
+
269
+ def _factorizar(self, expr):
270
+ """Factor: factorizar(x^2 - 1) (x-1)(x+1)"""
271
+ return factor(expr)
272
+
273
+ def _resolver(self, expr, var=None):
274
+ """Solve: resolver(x^2 - 4, x) → [-2, 2]"""
275
+ if var is None:
276
+ var = x
277
+ return solve(expr, var)
278
+
279
+ # ============ STATISTICS ============
280
+
281
+ def _media(self, *values):
282
+ """Mean: media(1, 2, 3, 4, 5) → 3"""
283
+ nums = [float(v) for v in values]
284
+ return sum(nums) / len(nums)
285
+
286
+ def _mediana(self, *values):
287
+ """Median: mediana(1, 2, 3, 4, 5) → 3"""
288
+ nums = sorted([float(v) for v in values])
289
+ n = len(nums)
290
+ mid = n // 2
291
+ if n % 2 == 0:
292
+ return (nums[mid - 1] + nums[mid]) / 2
293
+ return nums[mid]
294
+
295
+ def _desviacion(self, *values):
296
+ """Standard deviation: desviacion(1, 2, 3, 4, 5)"""
297
+ nums = [float(v) for v in values]
298
+ mean = sum(nums) / len(nums)
299
+ variance = sum((x - mean) ** 2 for x in nums) / len(nums)
300
+ return variance ** 0.5
301
+
302
+ def _varianza(self, *values):
303
+ """Variance: varianza(1, 2, 3, 4, 5)"""
304
+ nums = [float(v) for v in values]
305
+ mean = sum(nums) / len(nums)
306
+ return sum((x - mean) ** 2 for x in nums) / len(nums)
307
+
308
+ # ============ FINANCE ============
309
+
310
+ def _van(self, tasa, *flujos):
311
+ """NPV: van(0.10, -1000, 300, 400, 500)"""
312
+ r = float(tasa)
313
+ result = 0
314
+ for i, flujo in enumerate(flujos):
315
+ result += float(flujo) / ((1 + r) ** i)
316
+ return round(result, 2)
317
+
318
+ def _tir(self, *flujos):
319
+ """IRR: tir(-1000, 300, 400, 500) using Newton-Raphson"""
320
+ flows = [float(f) for f in flujos]
321
+
322
+ def npv(r):
323
+ return sum(f / ((1 + r) ** i) for i, f in enumerate(flows))
324
+
325
+ def npv_deriv(r):
326
+ return sum(-i * f / ((1 + r) ** (i + 1)) for i, f in enumerate(flows))
327
+
328
+ r = 0.1 # Initial guess
329
+ for _ in range(100):
330
+ npv_val = npv(r)
331
+ if abs(npv_val) < 1e-10:
332
+ break
333
+ deriv = npv_deriv(r)
334
+ if deriv == 0:
335
+ break
336
+ r = r - npv_val / deriv
337
+
338
+ return round(r * 100, 2) # Return as percentage
339
+
340
+ def _depreciar(self, costo, residual, años):
341
+ """Straight-line depreciation: depreciar(10000, 1000, 5)"""
342
+ c, r, n = float(costo), float(residual), int(años)
343
+ annual = (c - r) / n
344
+ schedule = []
345
+ for i in range(n):
346
+ schedule.append({
347
+ 'año': i + 1,
348
+ 'depreciacion': round(annual, 2),
349
+ 'acumulado': round(annual * (i + 1), 2),
350
+ 'valor_libro': round(c - annual * (i + 1), 2)
351
+ })
352
+ return schedule
353
+
354
+ def _interes_simple(self, capital, tasa, tiempo):
355
+ """Simple interest: interes_simple(1000, 0.05, 3)"""
356
+ c, r, t = float(capital), float(tasa), float(tiempo)
357
+ interest = c * r * t
358
+ return {
359
+ 'interes': round(interest, 2),
360
+ 'monto_final': round(c + interest, 2)
361
+ }
362
+
363
+ def _interes_compuesto(self, capital, tasa, n, tiempo):
364
+ """Compound interest: interes_compuesto(1000, 0.05, 12, 3)"""
365
+ c, r, periods, t = float(capital), float(tasa), int(n), float(tiempo)
366
+ monto = c * ((1 + r / periods) ** (periods * t))
367
+ return {
368
+ 'monto_final': round(monto, 2),
369
+ 'interes': round(monto - c, 2)
370
+ }
371
+
372
+ def _distancia(self, p1, p2):
373
+ geo = GeometryEngine()
374
+ return geo.distancia(p1, p2)
375
+
376
+ def _punto_medio(self, p1, p2):
377
+ geo = GeometryEngine()
378
+ return geo.punto_medio(p1, p2)
379
+
380
+ def _pendiente(self, p1, p2):
381
+ geo = GeometryEngine()
382
+ return geo.pendiente(p1, p2)
383
+
384
+ def _recta(self, p1, p2):
385
+ geo = GeometryEngine()
386
+ return geo.recta(p1, p2)
387
+
388
+ def _circulo(self, centro, radio):
389
+ geo = GeometryEngine()
390
+ return geo.circulo(centro, radio)
391
+
392
+ def _sonify(self, expr, duration=3.0, filename="output.wav"):
393
+ """Generate audio from expression: sonify(sin(440*2*pi*t))"""
394
+ engine = AudioEngine()
395
+ return engine.generate(str(expr), float(duration), str(filename))
396
+
397
+ # Convenience functions for direct import
398
+ def derivar(expr, var=None, n=1):
399
+ engine = MathEngine()
400
+ return engine._derivar(engine.parse(str(expr)), var, n)
401
+
402
+ def integrar(expr, var=None, a=None, b=None):
403
+ engine = MathEngine()
404
+ return engine._integrar(engine.parse(str(expr)), var, a, b)
405
+
406
+ def limite(expr, var=None, punto=0):
407
+ engine = MathEngine()
408
+ return engine._limite(engine.parse(str(expr)), var, punto)
409
+
410
+ def sumatoria(expr, var=None, a=0, b=10):
411
+ engine = MathEngine()
412
+ return engine._sumatoria(engine.parse(str(expr)), var, a, b)
413
+
414
+ def simplificar(expr):
415
+ engine = MathEngine()
416
+ return engine._simplificar(engine.parse(str(expr)))
417
+
418
+ def expandir(expr):
419
+ engine = MathEngine()
420
+ return engine._expandir(engine.parse(str(expr)))
421
+
422
+ def factorizar(expr):
423
+ engine = MathEngine()
424
+ return engine._factorizar(engine.parse(str(expr)))
425
+
426
+ def resolver(expr, var=None):
427
+ engine = MathEngine()
428
+ return engine._resolver(engine.parse(str(expr)), var)
429
+
430
+ def van(tasa, *flujos):
431
+ engine = MathEngine()
432
+ return engine._van(tasa, *flujos)
433
+
434
+ def tir(*flujos):
435
+ engine = MathEngine()
436
+ return engine._tir(*flujos)
437
+
438
+ def depreciar(costo, residual, años):
439
+ engine = MathEngine()
440
+ return engine._depreciar(costo, residual, años)
441
+
442
+ def interes_simple(capital, tasa, tiempo):
443
+ engine = MathEngine()
444
+ return engine._interes_simple(capital, tasa, tiempo)
445
+
446
+ def interes_compuesto(capital, tasa, n, tiempo):
447
+ engine = MathEngine()
448
+ return engine._interes_compuesto(capital, tasa, n, tiempo)
449
+
450
+ def media(*values):
451
+ engine = MathEngine()
452
+ return engine._media(*values)
453
+
454
+ def mediana(*values):
455
+ engine = MathEngine()
456
+ return engine._mediana(*values)
457
+
458
+ def desviacion(*values):
459
+ engine = MathEngine()
460
+ return engine._desviacion(*values)
461
+
462
+ def varianza(*values):
463
+ engine = MathEngine()
464
+ return engine._varianza(*values)
465
+
466
+
467
+
468
+ # ... (Global functions) ...
469
+
470
+ def distancia(p1, p2):
471
+ engine = MathEngine()
472
+ return engine._distancia(p1, p2)
473
+
474
+ def punto_medio(p1, p2):
475
+ engine = MathEngine()
476
+ return engine._punto_medio(p1, p2)
477
+
478
+ def pendiente(p1, p2):
479
+ engine = MathEngine()
480
+ return engine._pendiente(p1, p2)
481
+
482
+ def recta(p1, p2):
483
+ engine = MathEngine()
484
+ return engine._recta(p1, p2)
485
+
486
+ def circulo(centro, radio):
487
+ engine = MathEngine()
488
+ return engine._circulo(centro, radio)
489
+
490
+ def sonify(expr, duration=3.0, filename="output.wav"):
491
+ engine = MathEngine()
492
+ return engine._sonify(expr, duration, filename)