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/__init__.py +23 -23
- binary_equalab/cli.py +210 -180
- binary_equalab/engine.py +492 -405
- binary_equalab/functions.py +18 -18
- binary_equalab/geometry.py +69 -0
- binary_equalab/giac_poc.py +74 -0
- binary_equalab/parser_enhanced.py +66 -0
- binary_equalab/shell_setup.py +128 -0
- binary_equalab/sonify.py +91 -0
- {binary_equalab-1.0.0.dist-info → binary_equalab-2.0.0.dist-info}/METADATA +21 -6
- binary_equalab-2.0.0.dist-info/RECORD +13 -0
- {binary_equalab-1.0.0.dist-info → binary_equalab-2.0.0.dist-info}/entry_points.txt +1 -0
- binary_equalab-1.0.0.dist-info/RECORD +0 -8
- {binary_equalab-1.0.0.dist-info → binary_equalab-2.0.0.dist-info}/WHEEL +0 -0
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
self.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
'
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
'
|
|
64
|
-
'
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
for
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
raise ValueError(f"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
n
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
engine
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
engine
|
|
405
|
-
|
|
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)
|