Calc/hybrid_base_types.py

607 lines
20 KiB
Python

"""
Clases base híbridas que combinan SymPy con funcionalidad especializada
"""
import sympy
from sympy import Basic, Symbol, sympify
from typing import Any, Optional, Dict
import re
class HybridCalcType(Basic):
"""
Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora
Todas las clases especializadas deben heredar de esta
"""
def __new__(cls, *args, **kwargs):
"""Crear objeto SymPy válido"""
obj = Basic.__new__(cls)
return obj
def __init__(self, value, original_str=""):
"""Inicialización de funcionalidad especializada"""
self._value = value
self._original_str = original_str
self._init_specialized()
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 _sympystr(self, printer):
"""Representación SymPy string"""
return f"{self.__class__.__name__}({self._original_str})"
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
class HybridHex(HybridCalcType):
"""Clase híbrida para números hexadecimales"""
def __new__(cls, value_input):
obj = HybridCalcType.__new__(cls)
return obj
def __init__(self, value_input):
processed_value = None
original_str = str(value_input)
if isinstance(value_input, HybridCalcType):
processed_value = int(value_input.__dec__())
elif isinstance(value_input, (int, float)):
processed_value = int(value_input)
else:
# String input
str_input = str(value_input).lower()
if str_input.startswith("0x"):
processed_value = int(str_input, 16)
else:
processed_value = int(str_input, 16)
super().__init__(processed_value, original_str)
def __str__(self):
return f"0x{self._value:X}"
def _sympystr(self, printer):
return f"Hex({self._value})"
@staticmethod
def Helper(input_str):
if re.match(r"^\s*Hex(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE):
return 'Ej: Hex[FF] o Hex[255]'
return None
def toDecimal(self):
"""Convertir a decimal"""
return self._value
class HybridBin(HybridCalcType):
"""Clase híbrida para números binarios"""
def __new__(cls, value_input):
obj = HybridCalcType.__new__(cls)
return obj
def __init__(self, value_input):
processed_value = None
original_str = str(value_input)
if isinstance(value_input, HybridCalcType):
processed_value = int(value_input.__dec__())
elif isinstance(value_input, (int, float)):
processed_value = int(value_input)
else:
# String input
str_input = str(value_input).lower()
if str_input.startswith("0b"):
processed_value = int(str_input, 2)
else:
processed_value = int(str_input, 2)
super().__init__(processed_value, original_str)
def __str__(self):
return f"0b{bin(self._value)[2:]}"
def _sympystr(self, printer):
return f"Bin({self._value})"
@staticmethod
def Helper(input_str):
if re.match(r"^\s*Bin(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE):
return 'Ej: Bin[1011] o Bin[11]'
return None
class HybridDec(HybridCalcType):
"""Clase híbrida para números decimales"""
def __new__(cls, value_input):
obj = HybridCalcType.__new__(cls)
return obj
def __init__(self, value_input):
processed_value = None
original_str = str(value_input)
if isinstance(value_input, HybridCalcType):
processed_value = float(value_input.__dec__())
elif isinstance(value_input, (int, float)):
processed_value = float(value_input)
else:
try:
processed_value = float(str(value_input))
except (ValueError, TypeError) as e:
raise ValueError(f"Invalid decimal value: '{value_input}'") from e
super().__init__(processed_value, original_str)
def __str__(self):
if self._value == int(self._value):
return str(int(self._value))
return str(self._value)
def _sympystr(self, printer):
return f"Dec({self._value})"
@staticmethod
def Helper(input_str):
if re.match(r"^\s*Dec(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE):
return 'Ej: Dec[10.5] o Dec[10]'
return None
class HybridIP4(HybridCalcType):
"""Clase híbrida para direcciones IPv4"""
def __new__(cls, *args):
obj = HybridCalcType.__new__(cls)
return obj
def __init__(self, *args):
import socket
import struct
if not args:
raise ValueError("IP4() constructor requires at least one argument.")
ip_str = None
prefix = None
if len(args) == 1:
arg = args[0]
if not isinstance(arg, str):
raise ValueError(f"IP4 single argument must be a string, got '{arg}'")
if "/" in arg: # CIDR notation
parts = arg.split('/', 1)
ip_str = parts[0]
if len(parts) == 2 and parts[1].isdigit():
prefix_val = int(parts[1])
if not self._is_valid_prefix(prefix_val):
raise ValueError(f"Invalid CIDR prefix '{parts[1]}' in '{arg}'")
prefix = prefix_val
else:
raise ValueError(f"Invalid CIDR format: '{arg}'")
else:
ip_str = arg
elif len(args) == 2:
ip_arg, mask_arg = args[0], args[1]
if not isinstance(ip_arg, str):
raise ValueError(f"IP4 first argument must be an IP string")
ip_str = ip_arg
if isinstance(mask_arg, int):
if not self._is_valid_prefix(mask_arg):
raise ValueError(f"Invalid prefix length: {mask_arg}")
prefix = mask_arg
elif isinstance(mask_arg, str):
if mask_arg.isdigit():
prefix_val = int(mask_arg)
if self._is_valid_prefix(prefix_val):
prefix = prefix_val
else:
parsed_prefix = self._netmask_str_to_prefix(mask_arg)
if parsed_prefix is None:
raise ValueError(f"Invalid mask string: '{mask_arg}'")
prefix = parsed_prefix
else:
parsed_prefix = self._netmask_str_to_prefix(mask_arg)
if parsed_prefix is None:
raise ValueError(f"Invalid netmask string: '{mask_arg}'")
prefix = parsed_prefix
else:
raise ValueError(f"IP4() constructor takes 1 or 2 arguments, got {len(args)}")
if not self._is_valid_ip_string(ip_str):
raise ValueError(f"Invalid IP address string: '{ip_str}'")
self.ip_address = ip_str
self.prefix = prefix
# Valor para SymPy - usar representación numérica de la IP
ip_as_int = struct.unpack("!I", socket.inet_aton(ip_str))[0]
canonical_repr = f"{ip_str}/{prefix}" if prefix is not None else ip_str
super().__init__(ip_as_int, canonical_repr)
def _is_valid_ip_string(self, ip_str: str) -> bool:
"""Verifica si es una dirección IP válida"""
import socket
try:
socket.inet_aton(ip_str)
parts = ip_str.split(".")
return len(parts) == 4 and all(0 <= int(part) <= 255 for part in parts)
except (socket.error, ValueError, TypeError):
return False
def _is_valid_prefix(self, prefix: int) -> bool:
"""Verifica si es un prefijo CIDR válido"""
return isinstance(prefix, int) and 0 <= prefix <= 32
def _netmask_str_to_prefix(self, netmask_str: str) -> Optional[int]:
"""Convierte máscara decimal a prefijo CIDR"""
import socket
import struct
if not self._is_valid_ip_string(netmask_str):
return None
try:
mask_int = struct.unpack("!I", socket.inet_aton(netmask_str))[0]
except socket.error:
return None
binary_mask = bin(mask_int)[2:].zfill(32)
if '0' in binary_mask and '1' in binary_mask[binary_mask.find('0'):]:
return None
return binary_mask.count('1')
def __str__(self):
if self.prefix is not None:
return f"{self.ip_address}/{self.prefix}"
return self.ip_address
def _sympystr(self, printer):
return f"IP4({self.__str__()})"
@staticmethod
def Helper(input_str):
if re.match(r"^\s*IP4(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE):
return 'Ej: IP4[192.168.1.1/24] o IP4[10.0.0.1]'
return None
def NetworkAddress(self):
"""Retorna la dirección de red"""
import socket
import struct
if self.prefix is None:
raise ValueError("Mask not set, cannot calculate network address.")
ip_int = struct.unpack("!I", socket.inet_aton(self.ip_address))[0]
mask_int = (0xFFFFFFFF << (32 - self.prefix)) & 0xFFFFFFFF if self.prefix > 0 else 0
net_addr_int = ip_int & mask_int
net_addr_str = socket.inet_ntoa(struct.pack("!I", net_addr_int))
return HybridIP4(net_addr_str, self.prefix)
def BroadcastAddress(self):
"""Retorna la dirección de broadcast"""
import socket
import struct
if self.prefix is None:
raise ValueError("Mask not set, cannot calculate broadcast address.")
net_addr_obj = self.NetworkAddress()
net_addr_int = struct.unpack("!I", socket.inet_aton(net_addr_obj.ip_address))[0]
wildcard_mask = ((1 << (32 - self.prefix)) - 1) if self.prefix < 32 else 0
bcast_addr_int = net_addr_int | wildcard_mask
bcast_addr_str = socket.inet_ntoa(struct.pack("!I", bcast_addr_int))
return HybridIP4(bcast_addr_str, self.prefix)
def Nodes(self):
"""Retorna el número de hosts disponibles"""
if self.prefix is None:
raise ValueError("Mask not set, cannot calculate number of nodes.")
host_bits = 32 - self.prefix
if host_bits < 2:
return 0
return (1 << host_bits) - 2
class HybridChr(HybridCalcType):
"""Clase híbrida para caracteres ASCII"""
def __new__(cls, str_input):
obj = HybridCalcType.__new__(cls)
return obj
def __init__(self, str_input: str):
if not isinstance(str_input, str):
raise TypeError(f"Chr input must be a string, got {type(str_input).__name__}")
if not str_input:
raise ValueError("Chr input string cannot be empty.")
if len(str_input) == 1:
processed_value = ord(str_input)
else:
processed_value = [ord(c) for c in str_input]
super().__init__(processed_value, str_input)
def __str__(self):
if isinstance(self._value, int):
return chr(self._value)
elif isinstance(self._value, list):
return "".join(map(chr, self._value))
return super().__str__()
def _sympystr(self, printer):
return f"Chr({self._original_str})"
@staticmethod
def Helper(input_str):
if re.match(r"^\s*Chr(\b|\s*[\[\(].*)?", input_str, re.IGNORECASE):
return 'Ej: Chr[A] o Chr[Hello]. Representa caracteres ASCII.'
return None
# Alias para compatibilidad
Hex = HybridHex
Bin = HybridBin
Dec = HybridDec
IP4 = HybridIP4
Chr = HybridChr
# Funciones de testing
def test_hybrid_classes():
"""Test de las clases híbridas"""
print("=== Test Clases Híbridas ===")
# Test Hex
h = Hex("FF")
print(f"Hex('FF'): {h} (type: {type(h)})")
print(f" SymPy str: {h._sympystr(None)}")
print(f" Is SymPy Basic: {isinstance(h, sympy.Basic)}")
print(f" Value: {h._value}")
# Test operación simple
try:
result = h + 1
print(f" Hex + 1: {result} (type: {type(result)})")
except Exception as e:
print(f" Error en Hex + 1: {e}")
# Test IP4
ip = IP4("192.168.1.100/24")
print(f"IP4('192.168.1.100/24'): {ip}")
print(f" NetworkAddress: {ip.NetworkAddress()}")
print(f" Is SymPy Basic: {isinstance(ip, sympy.Basic)}")
# Test Chr
c = Chr("A")
print(f"Chr('A'): {c}")
print(f" ASCII value: {c._value}")
print(f" Is SymPy Basic: {isinstance(c, sympy.Basic)}")
if __name__ == "__main__":
test_hybrid_classes()