Calc/sympy_Base.py

654 lines
23 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
# ========== CLASES BASE UNIVERSALES PARA TOKENIZACIÓN ==========
class IntBase(SympyClassBase):
"""
Representación universal de números en cualquier base
con soporte algebraico completo y operaciones aritméticas
"""
def __init__(self, value_str, base=10):
self.value_str = str(value_str)
self.base = int(base)
# Detección mejorada de símbolos: considerar la base
self.has_symbols = self._has_symbols_for_base()
if self.has_symbols:
# Modo algebraico: mantener como expresión simbólica
self._symbolic_value = self._parse_symbolic_base()
self._numeric_value = None # No hay valor numérico
super().__init__(self._symbolic_value, f"{self.base}#{self.value_str}")
else:
# Modo numérico: convertir a entero
self._numeric_value = int(self.value_str, self.base)
self._symbolic_value = None # No hay valor simbólico
super().__init__(self._numeric_value, f"{self.base}#{self.value_str}")
def _has_symbols_for_base(self):
"""Detecta si hay símbolos considerando la base numérica"""
valid_digits = "0123456789ABCDEF"[:self.base]
for char in self.value_str.upper():
if char not in valid_digits:
# Es un símbolo (no un dígito válido para esta base)
return True
return False
def _parse_symbolic_base(self):
"""Convierte valor con símbolos a expresión SymPy"""
# Ejemplo: "x0" base 16 → x*16^1 + 0*16^0
# Ejemplo: "101x" base 2 → 1*2^3 + 0*2^2 + 1*2^1 + x*2^0
result = 0
digits = list(self.value_str)
for i, digit in enumerate(reversed(digits)):
power = self.base ** i
if digit.isdigit():
coefficient = int(digit)
elif digit in 'ABCDEF':
coefficient = ord(digit) - ord('A') + 10
elif digit in 'abcdef':
coefficient = ord(digit) - ord('a') + 10
else:
# Es un símbolo
coefficient = sympy.Symbol(digit)
result += coefficient * power
return result
def to_base(self, new_base):
"""Convierte a nueva base"""
if self.has_symbols:
# En modo algebraico, mantener expresión
return IntBase(f"({self._symbolic_value})_base{new_base}", new_base)
else:
# En modo numérico, convertir directamente
new_value_str = self._convert_to_base_string(self._numeric_value, new_base)
return IntBase(new_value_str, new_base)
@staticmethod
def _convert_to_base_string(value, base):
"""Convierte entero a string en base específica"""
if value == 0:
return "0"
digits = "0123456789ABCDEF"
result = ""
while value:
result = digits[value % base] + result
value //= base
return result
@property
def value(self):
"""Propiedad para acceder al valor (numérico o simbólico)"""
if self.has_symbols:
return self._symbolic_value
else:
return self._numeric_value
# ========== OPERADORES ARITMÉTICOS ==========
def __add__(self, other):
"""Suma: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__add__(other) # Delegar a SymPy
if isinstance(other, IntBase):
if other.has_symbols:
return super().__add__(other)
result_value = self._numeric_value + other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value + other
else:
return super().__add__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __sub__(self, other):
"""Resta: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__sub__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__sub__(other)
result_value = self._numeric_value - other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value - other
else:
return super().__sub__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __mul__(self, other):
"""Multiplicación: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__mul__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__mul__(other)
result_value = self._numeric_value * other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value * other
else:
return super().__mul__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __truediv__(self, other):
"""División: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__truediv__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__truediv__(other)
result_value = self._numeric_value // other._numeric_value # División entera
elif isinstance(other, int):
result_value = self._numeric_value // other
else:
return super().__truediv__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
def __mod__(self, other):
"""Módulo: mantiene la base del operando izquierdo"""
if self.has_symbols:
return super().__mod__(other)
if isinstance(other, IntBase):
if other.has_symbols:
return super().__mod__(other)
result_value = self._numeric_value % other._numeric_value
elif isinstance(other, int):
result_value = self._numeric_value % other
else:
return super().__mod__(other)
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
# Operadores reversos para int + IntBase
def __radd__(self, other):
return self.__add__(other)
def __rsub__(self, other):
if isinstance(other, int):
result_value = other - self._numeric_value
result_str = self._convert_to_base_string(result_value, self.base)
return IntBase(result_str, self.base)
return super().__rsub__(other)
def __rmul__(self, other):
return self.__mul__(other)
def __str__(self):
"""Representación string"""
return f"{self.base}#{self.value_str}"
def __repr__(self):
return f"IntBase('{self.value_str}', {self.base})"
class FourBytes(SympyClassBase):
"""
Representación universal de patrones de 4 elementos
con soporte algebraico completo y operaciones aritméticas
"""
def __init__(self, dotted_string):
self.original = str(dotted_string)
self.elements = self.original.split('.')
if len(self.elements) != 4:
raise ValueError(f"FourBytes requiere exactamente 4 elementos: {dotted_string}")
self.has_symbols = any(not elem.isdigit() for elem in self.elements)
if self.has_symbols:
# Modo algebraico: crear expresión simbólica
self._symbolic_elements = []
for elem in self.elements:
if elem.isdigit():
self._symbolic_elements.append(int(elem))
else:
self._symbolic_elements.append(sympy.Symbol(elem))
# CORREGIDO: Crear expresión escalar en lugar de Matrix para compatibilidad con IP4
# Convertir a representación de entero de 32 bits simbólico
self._symbolic_value = (self._symbolic_elements[0] * 2**24 +
self._symbolic_elements[1] * 2**16 +
self._symbolic_elements[2] * 2**8 +
self._symbolic_elements[3])
super().__init__(self._symbolic_value, dotted_string)
else:
# Modo numérico: validar rangos y convertir
self._numeric_elements = [int(elem) for elem in self.elements]
# Crear valor como entero de 32 bits (para IPs)
self._numeric_value = (self._numeric_elements[0] << 24 |
self._numeric_elements[1] << 16 |
self._numeric_elements[2] << 8 |
self._numeric_elements[3])
super().__init__(self._numeric_value, dotted_string)
def is_valid_ip_range(self):
"""Verifica si todos los elementos numéricos están en rango IP"""
if self.has_symbols:
return None # No se puede validar con símbolos
return all(0 <= x <= 255 for x in self._numeric_elements)
def substitute(self, **kwargs):
"""Sustituye símbolos por valores"""
if not self.has_symbols:
return self
new_elements = []
for elem in self.elements:
if elem in kwargs:
new_elements.append(str(kwargs[elem]))
else:
new_elements.append(elem)
return FourBytes('.'.join(new_elements))
def __getitem__(self, index):
"""Acceso a elementos individuales"""
if self.has_symbols:
return self._symbolic_elements[index]
else:
return self._numeric_elements[index]
def to_ip_int(self):
"""Convierte a entero de 32 bits (para IPs)"""
if self.has_symbols:
# CORREGIDO: Ya tenemos la expresión escalar directamente
return self._symbolic_value
else:
return self._numeric_value
@staticmethod
def _int_to_fourbytes(value):
"""Convierte entero de 32 bits a formato x.y.z.w"""
w = value & 0xFF
z = (value >> 8) & 0xFF
y = (value >> 16) & 0xFF
x = (value >> 24) & 0xFF
return f"{x}.{y}.{z}.{w}"
# ========== OPERADORES ARITMÉTICOS ==========
def __add__(self, other):
"""Suma: convierte a int, opera, reconvierte a FourBytes"""
if self.has_symbols:
return super().__add__(other) # Delegar a SymPy
if isinstance(other, FourBytes):
if other.has_symbols:
return super().__add__(other)
result_int = self._numeric_value + other._numeric_value
elif isinstance(other, int):
result_int = self._numeric_value + other
else:
return super().__add__(other)
# Mantener en rango de 32 bits
result_int = result_int & 0xFFFFFFFF
result_str = self._int_to_fourbytes(result_int)
return FourBytes(result_str)
def __sub__(self, other):
"""Resta: convierte a int, opera, reconvierte a FourBytes"""
if self.has_symbols:
return super().__sub__(other)
if isinstance(other, FourBytes):
if other.has_symbols:
return super().__sub__(other)
result_int = self._numeric_value - other._numeric_value
elif isinstance(other, int):
result_int = self._numeric_value - other
else:
return super().__sub__(other)
# Mantener en rango de 32 bits (underflow se convierte en valor alto)
result_int = result_int & 0xFFFFFFFF
result_str = self._int_to_fourbytes(result_int)
return FourBytes(result_str)
# ========== CONVERSIONES DE BASE ==========
def ToBase(self, base):
"""
Convierte cada elemento a la base especificada
Retorna expresión con cada elemento convertido
"""
if self.has_symbols:
# Para elementos simbólicos, retornar expresión algebraica
converted_elements = []
for elem in self.elements:
if elem.isdigit():
int_val = int(elem)
converted = IntBase._convert_to_base_string(int_val, base)
converted_elements.append(f"{base}#{converted}")
else:
# Símbolo: expresar como conversión algebraica
converted_elements.append(f"{base}#{elem}")
return '.'.join(converted_elements)
else:
# Para elementos numéricos, conversión directa
converted_elements = []
for elem_val in self._numeric_elements:
converted = IntBase._convert_to_base_string(elem_val, base)
converted_elements.append(f"{base}#{converted}")
return '.'.join(converted_elements)
def ToCIDR(self, prefix_length):
"""Convierte a notación CIDR"""
return f"{self.original}/{prefix_length}"
def ToHex(self):
"""Convierte cada elemento a hexadecimal"""
return self.ToBase(16)
def ToBinary(self):
"""Convierte cada elemento a binario"""
return self.ToBase(2)
def ToOctal(self):
"""Convierte cada elemento a octal"""
return self.ToBase(8)
def __str__(self):
"""Representación string"""
return self.original
def __repr__(self):
return f"FourBytes('{self.original}')"
# ========== TOKENIZADOR ALGEBRAICO ==========
def preprocess_tokens(expression):
"""
Tokenizador que convierte patrones específicos en objetos tipados
con precedencia correcta: IntBase (más específico) antes que FourBytes
"""
# 1. MAYOR PRECEDENCIA: IntBase (patrón más específico)
# Pattern: base#valor
expression = re.sub(r'(\d+)#([0-9A-Fa-fx]+)', r'IntBase("\2", \1)', expression)
# 2. MENOR PRECEDENCIA: FourBytes (patrón más general)
# Pattern: x.x.x.x pero evitar casos como obj.method
# Solo tokenizar si hay exactamente 4 elementos separados por puntos
# y no es parte de una cadena de métodos
pattern = r'\b([a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+)\b'
def replace_fourbytes(match):
candidate = match.group(1)
# Verificar que realmente tiene 4 elementos
parts = candidate.split('.')
if len(parts) == 4:
return f'FourBytes("{candidate}")'
return candidate
expression = re.sub(pattern, replace_fourbytes, expression)
return expression