Calc/app/sympy_Base.py

306 lines
10 KiB
Python

"""
Clase base híbrida que combina SymPy con funcionalidad especializada
"""
import sympy
from sympy import Basic, Symbol, sympify
from typing import Any, Optional, Dict
import re
from .class_base import ClassBase
class SympyClassBase(ClassBase, sympy.Basic):
"""Para clases que necesitan álgebra completa"""
def __new__(cls, *args, **kwargs):
# La subclase (ej. Class_IP4) es responsable de pasar los argumentos correctos
# que sympy.Basic.__new__ espera.
obj = sympy.Basic.__new__(cls, *args, **kwargs)
return obj
def __init__(self, value, original_str=""):
ClassBase.__init__(self, value, original_str)
# La inicialización de sympy.Basic se maneja principalmente a través de __new__.
# Atributos adicionales de sympy como self.args son establecidos por sympy.Basic
# basado en los argumentos pasados a __new__.
def _sympystr(self, printer):
# Las subclases deben implementar esto para una representación SymPy adecuada.
# Por defecto, usa el __str__ de ClassBase, que es str(self.value).
# Si self.value es un objeto Sympy, str(self.value) ya dará una buena representación.
# Si self.value no es un objeto Sympy pero se desea una forma SymPy específica,
# esto debería ser sobreescrito.
return str(self.value)
def _init_specialized(self):
"""Override en subclases para inicialización especializada"""
pass
@property
def value(self):
"""Acceso al valor interno"""
return self._value
@property
def original_str(self):
"""String original de entrada"""
return self._original_str
# Propiedades requeridas por SymPy
@property
def args(self):
"""Argumentos para SymPy - retorna valor como argumento"""
return (sympify(self._value),)
@property
def func(self):
"""Función constructora para SymPy"""
return self.__class__
def _latex(self, printer):
"""Representación LaTeX"""
return self._sympystr(printer)
def __str__(self):
"""Representación string para display"""
return str(self._value)
def __repr__(self):
"""Representación para debugging"""
return f"{self.__class__.__name__}('{self._original_str}')"
def evalf(self, n=15, **options):
"""Evaluación numérica si es posible"""
if isinstance(self._value, (int, float, complex)):
return sympy.Float(self._value)
return self
def _eval_evalf(self, prec):
"""Evaluación numérica para SymPy"""
if isinstance(self._value, (int, float, complex)):
return sympy.Float(self._value, prec)
return None
def __dec__(self):
"""Conversión a decimal para compatibilidad"""
if isinstance(self._value, (int, float, complex)):
return self._value
raise TypeError(f"Cannot convert {self.__class__.__name__} to decimal")
# Métodos requeridos por SymPy para manipulación algebraica
def as_coeff_Mul(self, rational=True):
"""Descomponer como coeficiente * términos"""
if isinstance(self._value, (int, float)):
return sympify(self._value), sympify(1)
return sympify(1), self
def as_coeff_Add(self, rational=True):
"""Descomponer como coeficiente + términos"""
if isinstance(self._value, (int, float)):
return sympify(self._value), sympify(0)
return sympify(0), self
def as_base_exp(self):
"""Descomponer como base^exponente"""
return self, sympify(1)
def as_numer_denom(self):
"""Descomponer como numerador/denominador"""
return self, sympify(1)
# Propiedades de tipo para SymPy
@property
def is_number(self):
"""Indica si es un número"""
return isinstance(self._value, (int, float, complex))
@property
def is_real(self):
"""Indica si es real"""
return isinstance(self._value, (int, float)) and not isinstance(self._value, complex)
@property
def is_integer(self):
"""Indica si es entero"""
return isinstance(self._value, int)
@property
def is_rational(self):
"""Indica si es racional"""
return isinstance(self._value, (int, float))
@property
def is_irrational(self):
"""Indica si es irracional"""
return False
@property
def is_positive(self):
"""Indica si es positivo"""
if isinstance(self._value, (int, float)):
return self._value > 0
return None
@property
def is_negative(self):
"""Indica si es negativo"""
if isinstance(self._value, (int, float)):
return self._value < 0
return None
@property
def is_zero(self):
"""Indica si es cero"""
if isinstance(self._value, (int, float)):
return self._value == 0
return None
@property
def is_finite(self):
"""Indica si es finito"""
return isinstance(self._value, (int, float, complex))
@property
def is_infinite(self):
"""Indica si es infinito"""
return False
@property
def is_commutative(self):
"""Indica si es conmutativo"""
return True
# Operaciones aritméticas simples que retornan el valor numérico para integración con SymPy
def __add__(self, other):
"""Suma que integra con SymPy"""
if isinstance(other, (int, float, complex)):
# Retornar valor numérico para que SymPy maneje la operación
return sympify(self._value + other)
elif hasattr(other, '_value'):
return sympify(self._value + other._value)
else:
# Dejar que SymPy maneje la operación
return sympify(self._value) + sympify(other)
def __radd__(self, other):
"""Suma reversa"""
return sympify(other) + sympify(self._value)
def __mul__(self, other):
"""Multiplicación que integra con SymPy"""
if isinstance(other, (int, float, complex)):
return sympify(self._value * other)
elif hasattr(other, '_value'):
return sympify(self._value * other._value)
else:
return sympify(self._value) * sympify(other)
def __rmul__(self, other):
"""Multiplicación reversa"""
return sympify(other) * sympify(self._value)
def __sub__(self, other):
"""Resta"""
if isinstance(other, (int, float, complex)):
return sympify(self._value - other)
elif hasattr(other, '_value'):
return sympify(self._value - other._value)
else:
return sympify(self._value) - sympify(other)
def __rsub__(self, other):
"""Resta reversa"""
return sympify(other) - sympify(self._value)
def __truediv__(self, other):
"""División"""
if isinstance(other, (int, float, complex)):
if other == 0:
raise ZeroDivisionError("Division by zero")
return sympify(self._value / other)
elif hasattr(other, '_value'):
if other._value == 0:
raise ZeroDivisionError("Division by zero")
return sympify(self._value / other._value)
else:
return sympify(self._value) / sympify(other)
def __rtruediv__(self, other):
"""División reversa"""
if self._value == 0:
raise ZeroDivisionError("Division by zero")
return sympify(other) / sympify(self._value)
def __pow__(self, other):
"""Potencia"""
if isinstance(other, (int, float, complex)):
return sympify(self._value ** other)
elif hasattr(other, '_value'):
return sympify(self._value ** other._value)
else:
return sympify(self._value) ** sympify(other)
def __rpow__(self, other):
"""Potencia reversa"""
return sympify(other) ** sympify(self._value)
@staticmethod
def Helper(input_str):
"""Override en subclases para ayuda contextual"""
return None
# ========== FUNCIÓN DE TOKENIZACIÓN ACTUALIZADA ==========
def preprocess_tokens(expression):
"""
Función de tokenización que usa el registro dinámico de tipos
NUEVA VERSIÓN: Obtiene clases desde custom_types/ automáticamente
"""
# Intentar obtener las clases desde el registro
try:
from type_registry import get_registered_base_context
context = get_registered_base_context()
IntBase = context.get('IntBase')
FourBytes = context.get('FourBytes')
if not IntBase or not FourBytes:
# Fallback: tokenización básica sin clases
return _basic_tokenization(expression)
except ImportError:
# Si el registro no está disponible, usar tokenización básica
return _basic_tokenization(expression)
# Tokenización completa con clases disponibles
return _full_tokenization(expression, IntBase, FourBytes)
def _full_tokenization(expression, IntBase, FourBytes):
"""Tokenización completa usando las clases registradas"""
import re
def replace_intbase(match):
base = match.group(1)
value = match.group(2)
return f'IntBase("{value}", {base})'
def replace_fourbytes(match):
pattern = match.group(1)
return f'FourBytes("{pattern}")'
# PASO 1: IntBase - precedencia MAYOR (patrón más específico)
# Patrón: número#valor (ej: 16#FF, 2#1010)
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', replace_intbase, expression)
# PASO 2: FourBytes - precedencia MENOR (patrón más general)
# Patrón: x.x.x.x donde x puede ser número o símbolo
# Usar \b para boundary, evitar conflictos con acceso a atributos
expression = re.sub(r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b',
replace_fourbytes, expression)
return expression
def _basic_tokenization(expression):
"""Tokenización básica cuando las clases no están disponibles"""
# Solo hacer transformaciones básicas sin objetos
return expression