1119 lines
50 KiB
Python
1119 lines
50 KiB
Python
"""
|
|
Motor de evaluación híbrida INTEGRADO con el sistema de auto-descubrimiento de tipos
|
|
"""
|
|
import sympy
|
|
from sympy import symbols, Symbol, sympify, solve, Eq, simplify
|
|
from typing import Dict, Any, Optional, Tuple, List, Union
|
|
import ast
|
|
import re
|
|
from contextlib import contextmanager
|
|
|
|
# Importaciones del sistema de tipos
|
|
from type_registry import (
|
|
discover_and_register_types,
|
|
get_registered_base_context,
|
|
get_registered_bracket_classes,
|
|
get_registered_helper_functions
|
|
)
|
|
|
|
# Importaciones existentes
|
|
from tl_bracket_parser import BracketParser
|
|
from tl_popup import PlotResult
|
|
|
|
# ========== ELIMINADO: Las clases base ahora se obtienen del registro ==========
|
|
# Las clases IntBase y FourBytes se cargan dinámicamente desde el registro de tipos
|
|
|
|
|
|
class HybridEvaluationEngine:
|
|
"""
|
|
Motor de evaluación híbrida que combina SymPy con clases especializadas
|
|
VERSIÓN INTEGRADA con auto-descubrimiento de tipos
|
|
"""
|
|
|
|
def __init__(self, auto_discover_types: bool = True, types_directory: str = "custom_types"):
|
|
# ========== NUEVO: Inicializar parser con tokenización distribuida ==========
|
|
self.parser = BracketParser(enable_tokenization=True, debug=False)
|
|
|
|
# Debug mode (configurar antes que otros módulos)
|
|
self.debug = False
|
|
|
|
self.symbol_table: Dict[str, Any] = {}
|
|
self.equations: List[sympy.Eq] = []
|
|
self.last_result = None
|
|
|
|
# Configuración del sistema de tipos
|
|
self.types_directory = types_directory
|
|
self.auto_discover_enabled = auto_discover_types
|
|
|
|
# Información de tipos registrados
|
|
self.registered_types_info = {}
|
|
self.helper_functions = []
|
|
|
|
# NUEVA CONFIGURACIÓN: Modo simbólico
|
|
self.symbolic_mode = True # Por defecto, mantener forma simbólica
|
|
self.show_numeric_approximation = True # Mostrar aproximación numérica
|
|
self.keep_symbolic_fractions = True # Mantener fracciones como 4/5
|
|
self.auto_simplify = False # No simplificar automáticamente
|
|
|
|
# Configurar contexto base
|
|
self._setup_base_context()
|
|
|
|
|
|
|
|
def _setup_base_context(self):
|
|
"""Configura el contexto base con funciones matemáticas y clases"""
|
|
|
|
# 1. DESCOBRIR Y REGISTRAR TIPOS AUTOMÁTICAMENTE
|
|
if self.auto_discover_enabled:
|
|
try:
|
|
self.registered_types_info = discover_and_register_types(self.types_directory)
|
|
if self.debug:
|
|
print(f"🔍 Tipos descubiertos: {self.registered_types_info['class_count']} clases")
|
|
except Exception as e:
|
|
print(f"⚠️ Error en auto-descubrimiento: {e}")
|
|
self.registered_types_info = {
|
|
'base_context': {},
|
|
'bracket_classes': set(),
|
|
'helper_functions': []
|
|
}
|
|
|
|
# 2. FUNCIONES MATEMÁTICAS DE SYMPY (BASE)
|
|
math_functions = {
|
|
'pi': sympy.pi,
|
|
'e': sympy.E,
|
|
'I': sympy.I,
|
|
'oo': sympy.oo,
|
|
'sin': sympy.sin,
|
|
'cos': sympy.cos,
|
|
'tan': sympy.tan,
|
|
'asin': sympy.asin,
|
|
'acos': sympy.acos,
|
|
'atan': sympy.atan,
|
|
'sinh': sympy.sinh,
|
|
'cosh': sympy.cosh,
|
|
'tanh': sympy.tanh,
|
|
'exp': sympy.exp,
|
|
'log': sympy.log,
|
|
'ln': sympy.log,
|
|
'sqrt': sympy.sqrt,
|
|
'abs': sympy.Abs,
|
|
'sign': sympy.sign,
|
|
'floor': sympy.floor,
|
|
'ceiling': sympy.ceiling,
|
|
'factorial': sympy.factorial,
|
|
# Funciones de cálculo
|
|
'diff': sympy.diff,
|
|
'integrate': sympy.integrate,
|
|
'limit': sympy.limit,
|
|
'series': sympy.series,
|
|
'solve': sympy.solve,
|
|
'simplify': sympy.simplify,
|
|
'expand': sympy.expand,
|
|
'factor': sympy.factor,
|
|
'collect': sympy.collect,
|
|
'cancel': sympy.cancel,
|
|
'apart': sympy.apart,
|
|
'together': sympy.together,
|
|
# Álgebra lineal
|
|
'Matrix': sympy.Matrix,
|
|
'det': lambda m: m.det() if hasattr(m, 'det') else sympy.det(m),
|
|
'inv': lambda m: m.inv() if hasattr(m, 'inv') else sympy.Matrix(m).inv(),
|
|
# Printing
|
|
'latex': sympy.latex, # NUEVO: función latex global
|
|
# Plotting (será manejado por resultados interactivos)
|
|
'plot': self._create_plot_placeholder,
|
|
'plot3d': self._create_plot3d_placeholder,
|
|
}
|
|
|
|
# 3. CLASES ESPECIALIZADAS (DESDE AUTO-DESCUBRIMIENTO)
|
|
specialized_classes = self.registered_types_info.get('base_context', {})
|
|
|
|
# 4. FUNCIONES DE UTILIDAD
|
|
utility_functions = {
|
|
'_add_equation': self._add_equation,
|
|
'_assign_variable': self._assign_variable,
|
|
'_solve_variable_in_system': self._solve_variable_in_system,
|
|
'clear': self.clear,
|
|
'clearContext': self.clearContext,
|
|
'help': self._help_function,
|
|
'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr),
|
|
}
|
|
|
|
# ========== NUEVO: 4.5. CLASES BASE YA ESTÁN EN SPECIALIZED_CLASSES ==========
|
|
# Las clases IntBase y FourBytes ya están en specialized_classes desde el registro
|
|
# No necesitamos añadirlas por separado
|
|
|
|
# 5. COMBINAR TODO EN EL CONTEXTO BASE
|
|
self.base_context = {
|
|
**math_functions,
|
|
**specialized_classes,
|
|
**utility_functions
|
|
}
|
|
|
|
# 6. ACTUALIZAR HELPER FUNCTIONS
|
|
self.helper_functions = get_registered_helper_functions()
|
|
|
|
# 7. ACTUALIZAR BRACKET PARSER CON CLASES DESCUBIERTAS
|
|
self._update_bracket_parser()
|
|
|
|
if self.debug:
|
|
print(f"📋 Contexto base configurado: {len(self.base_context)} entradas")
|
|
print(f"🆘 Helper functions: {len(self.helper_functions)}")
|
|
|
|
# ========== NUEVO: Sincronizar debug con parser ==========
|
|
if hasattr(self.parser, 'debug'):
|
|
self.parser.debug = self.debug
|
|
|
|
# ========== NUEVO: Sincronizar debug con tokenizer ==========
|
|
if hasattr(self.parser, 'tokenizer') and hasattr(self.parser.tokenizer, 'debug'):
|
|
self.parser.tokenizer.debug = self.debug
|
|
|
|
def _update_bracket_parser(self):
|
|
"""Actualiza el BracketParser con las clases descubiertas"""
|
|
try:
|
|
# NUEVO: Recargar reglas de tokenización del sistema distribuido
|
|
if hasattr(self.parser, 'reload_tokenization_rules'):
|
|
self.parser.reload_tokenization_rules()
|
|
|
|
if self.debug:
|
|
tokenization_info = self.parser.get_tokenization_info()
|
|
print(f"🔧 Reglas de tokenización actualizadas: {len(tokenization_info['rules'])} reglas")
|
|
for rule in tokenization_info['rules']:
|
|
print(f" {rule['priority']:2d}: {rule['class']} - {rule['description']}")
|
|
else:
|
|
if self.debug:
|
|
print("⚠️ Parser sin soporte para tokenización distribuida")
|
|
|
|
except Exception as e:
|
|
print(f"⚠️ Error actualizando tokenización: {e}")
|
|
|
|
def reload_types(self):
|
|
"""Recarga todos los tipos del directorio (útil para desarrollo)"""
|
|
if self.debug:
|
|
print("🔄 Recargando tipos...")
|
|
|
|
self._setup_base_context()
|
|
|
|
if self.debug:
|
|
print("✅ Tipos recargados")
|
|
|
|
def get_available_types(self) -> Dict[str, Any]:
|
|
"""Retorna información sobre los tipos disponibles"""
|
|
return {
|
|
'registered_classes': self.registered_types_info.get('registered_classes', {}),
|
|
'bracket_classes': list(self.registered_types_info.get('bracket_classes', set())),
|
|
'total_context_entries': len(self.base_context),
|
|
'helper_functions_count': len(self.helper_functions)
|
|
}
|
|
|
|
def get_type_help(self, type_name: str) -> Optional[str]:
|
|
"""Obtiene ayuda para un tipo específico"""
|
|
# Buscar en clases registradas
|
|
registered_classes = self.registered_types_info.get('registered_classes', {})
|
|
if type_name in registered_classes:
|
|
cls = registered_classes[type_name]
|
|
if hasattr(cls, 'Helper'):
|
|
return cls.Helper(type_name)
|
|
|
|
return None
|
|
|
|
def _create_plot_placeholder(self, *args, **kwargs):
|
|
"""Crear placeholder para plots que será manejado por resultados interactivos"""
|
|
if self.debug:
|
|
print(f"🎯 Creando PlotResult con args: {args}, kwargs: {kwargs}")
|
|
return PlotResult('plot', args, kwargs)
|
|
|
|
def _create_plot3d_placeholder(self, *args, **kwargs):
|
|
"""Crear placeholder para plots 3D"""
|
|
if self.debug:
|
|
print(f"🎯 Creando PlotResult 3D con args: {args}, kwargs: {kwargs}")
|
|
return PlotResult('plot3d', args, kwargs)
|
|
|
|
def _help_function(self, obj=None):
|
|
"""Función de ayuda integrada que usa el sistema de helpers"""
|
|
if obj is None:
|
|
return "Ayuda disponible. Use help(función) para ayuda específica."
|
|
|
|
# Primero intentar con el objeto directamente
|
|
if hasattr(obj, '__doc__') and obj.__doc__:
|
|
return obj.__doc__
|
|
elif hasattr(obj, 'Helper'):
|
|
return obj.Helper("")
|
|
|
|
# Luego buscar en helpers registrados
|
|
obj_name = getattr(obj, '__name__', str(obj))
|
|
for helper_func in self.helper_functions:
|
|
try:
|
|
help_result = helper_func(obj_name)
|
|
if help_result:
|
|
return help_result
|
|
except:
|
|
continue
|
|
|
|
return f"No hay ayuda disponible para {obj}"
|
|
|
|
# ========== RESTO DE MÉTODOS EXISTENTES ==========
|
|
# (Los métodos de evaluación permanecen igual)
|
|
|
|
def evaluate_line(self, line: str) -> 'EvaluationResult':
|
|
"""
|
|
Evalúa una línea de código y retorna el resultado
|
|
NUEVA LÓGICA: Priorizar asignaciones, intentar ecuaciones silenciosamente
|
|
"""
|
|
try:
|
|
# 1. Aplicar tokenización distribuida
|
|
parsed_line = self.parser.process_expression(line)
|
|
|
|
if self.debug:
|
|
print(f"Parse: '{line}' → '{parsed_line}'")
|
|
|
|
# 2. Clasificar tipo de línea
|
|
line_type = self._classify_line(parsed_line)
|
|
|
|
if line_type == "comment":
|
|
return EvaluationResult(None, "comment", original_line=line)
|
|
elif line_type == "assignment":
|
|
# NUEVA LÓGICA: Para asignaciones, también intentar agregar como ecuación silenciosamente
|
|
assignment_result = self._evaluate_assignment(parsed_line, line)
|
|
|
|
# Intentar agregar como ecuación silenciosamente (sin mostrar errores)
|
|
if not assignment_result.is_error:
|
|
try:
|
|
self._add_equation_silently(line)
|
|
except:
|
|
pass # Ignorar errores de ecuación
|
|
|
|
return assignment_result
|
|
elif line_type == "equation":
|
|
return self._evaluate_equation_addition(parsed_line, line)
|
|
|
|
# 3. Evaluación SymPy
|
|
return self._evaluate_sympy_expression(parsed_line, line_type, line)
|
|
|
|
except Exception as e:
|
|
return EvaluationResult(
|
|
None, "error",
|
|
error=str(e),
|
|
original_line=line
|
|
)
|
|
|
|
def _classify_line(self, parsed_line: str) -> str:
|
|
"""Clasifica el tipo de línea después del parsing"""
|
|
|
|
# Simplificado: priorizar asignaciones, ser menos estricto
|
|
if self._is_assignment(parsed_line):
|
|
return "assignment"
|
|
elif self._is_standalone_equation(parsed_line):
|
|
return "equation"
|
|
elif not parsed_line or parsed_line.strip().startswith('#'):
|
|
return "comment"
|
|
else:
|
|
return "expression"
|
|
|
|
def _is_assignment(self, line: str) -> bool:
|
|
"""
|
|
Detecta si una línea es una asignación de variable
|
|
NUEVA LÓGICA: Priorizar asignaciones, ser menos estricto
|
|
"""
|
|
try:
|
|
# Pattern: variable = expresión (que no sea comparación)
|
|
if '=' in line and not any(op in line for op in ['==', '!=', '<=', '>=']):
|
|
# Verificar que sea una asignación válida de Python
|
|
parts = line.split('=', 1)
|
|
if len(parts) == 2:
|
|
var_part = parts[0].strip()
|
|
expr_part = parts[1].strip()
|
|
|
|
# Verificar que la parte izquierda sea un identificador válido
|
|
if re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part) and expr_part:
|
|
return True
|
|
|
|
return False
|
|
except:
|
|
return False
|
|
|
|
def _is_standalone_equation(self, line: str) -> bool:
|
|
"""
|
|
Determina si una línea es una ecuación standalone
|
|
NUEVA LÓGICA: Solo ecuaciones matemáticas obvias, no asignaciones
|
|
"""
|
|
try:
|
|
# Primero verificar si contiene '=' simple
|
|
if '=' not in line or any(op in line for op in ['==', '!=', '<=', '>=']):
|
|
return False
|
|
|
|
# NUEVA LÓGICA: Si ya fue clasificada como asignación, NO es ecuación
|
|
if self._is_assignment(line):
|
|
return False
|
|
|
|
# Solo considerar como ecuación si tiene estructura matemática compleja
|
|
# Por ejemplo: expressions con funciones matemáticas, símbolos algebraicos, etc.
|
|
if any(pattern in line for pattern in ['sin(', 'cos(', 'log(', 'sqrt(', 'diff(', 'integrate(']):
|
|
return True
|
|
|
|
# O si contiene múltiples variables en un formato algebraico
|
|
# Básicamente, ecuaciones que NO son asignaciones simples
|
|
return False
|
|
|
|
except:
|
|
return False
|
|
|
|
def _evaluate_assignment(self, parsed_line: str, original_line: str) -> 'EvaluationResult':
|
|
"""Maneja la asignación de variables"""
|
|
try:
|
|
# ARREGLO: Transformar asignación en llamada a _assign_variable
|
|
# parsed_line es algo como: mask=FourBytes("255.240.0.3")
|
|
# Necesitamos convertirlo en: _assign_variable("mask", FourBytes("255.240.0.3"))
|
|
|
|
if '=' in parsed_line:
|
|
parts = parsed_line.split('=', 1)
|
|
var_name = parts[0].strip()
|
|
expr_part = parts[1].strip()
|
|
|
|
# Crear llamada a _assign_variable
|
|
assign_call = f'_assign_variable("{var_name}", {expr_part})'
|
|
|
|
if self.debug:
|
|
print(f"🔧 Transformando asignación: '{parsed_line}' → '{assign_call}'")
|
|
|
|
# Ejecutar la asignación transformada
|
|
result = self._eval_in_context(assign_call)
|
|
|
|
# Obtener el valor asignado
|
|
assigned_value = self.symbol_table.get(var_name)
|
|
else:
|
|
# Fallback: si no hay '=' algo está mal
|
|
raise ValueError(f"Línea de asignación sin '=': {parsed_line}")
|
|
|
|
|
|
# Generar evaluación numérica si está configurado para mostrarla
|
|
numeric_result = None
|
|
if self.show_numeric_approximation and hasattr(assigned_value, 'evalf'):
|
|
try:
|
|
numeric_eval = assigned_value.evalf()
|
|
# MEJORADO: Solo mostrar aproximación si es realmente útil
|
|
if hasattr(assigned_value, 'is_Rational') and assigned_value.is_Rational:
|
|
# Es una fracción racional, mostrar aproximación decimal
|
|
numeric_result = numeric_eval
|
|
elif hasattr(assigned_value, 'is_Integer') and assigned_value.is_Integer:
|
|
# Es un entero SymPy, no mostrar aproximación
|
|
numeric_result = None
|
|
elif hasattr(assigned_value, 'is_number') and assigned_value.is_number:
|
|
# Es un número, verificar si la aproximación es diferente significativamente
|
|
try:
|
|
if abs(float(numeric_eval) - float(assigned_value)) > 1e-10:
|
|
numeric_result = numeric_eval
|
|
except:
|
|
# Si no se puede comparar, mostrar solo si el string es diferente
|
|
if str(numeric_eval) != str(assigned_value):
|
|
numeric_result = numeric_eval
|
|
elif numeric_eval != assigned_value:
|
|
# Para otros casos, mostrar si son diferentes
|
|
try:
|
|
# Intentar comparación numérica más robusta
|
|
if abs(float(numeric_eval) - float(assigned_value)) > 1e-10:
|
|
numeric_result = numeric_eval
|
|
except:
|
|
# Si la comparación falla, asumir que son diferentes solo si el string es diferente
|
|
if str(numeric_eval) != str(assigned_value):
|
|
numeric_result = numeric_eval
|
|
except Exception as e:
|
|
if self.debug:
|
|
print(f"DEBUG: Error en evaluación numérica: {e}")
|
|
pass
|
|
|
|
return EvaluationResult(
|
|
assigned_value, "assignment",
|
|
symbolic_result=result,
|
|
numeric_result=numeric_result,
|
|
original_line=original_line
|
|
)
|
|
except Exception as e:
|
|
return EvaluationResult(
|
|
None, "error",
|
|
error=f"Error en asignación: {e}",
|
|
original_line=original_line
|
|
)
|
|
|
|
def _evaluate_equation_addition(self, parsed_line: str, original_line: str) -> 'EvaluationResult':
|
|
"""Maneja la adición de ecuaciones al sistema"""
|
|
try:
|
|
# Ejecutar _add_equation
|
|
result = self._eval_in_context(parsed_line)
|
|
return EvaluationResult(
|
|
result, "equation_added",
|
|
symbolic_result=f"Ecuación agregada: {original_line}",
|
|
original_line=original_line
|
|
)
|
|
except Exception as e:
|
|
return EvaluationResult(
|
|
None, "error",
|
|
error=f"Error agregando ecuación: {e}",
|
|
original_line=original_line
|
|
)
|
|
|
|
def _evaluate_sympy_expression(self, expression: str, parse_info: str, original_line: str) -> 'EvaluationResult':
|
|
"""Evalúa una expresión usando SymPy"""
|
|
try:
|
|
# Evaluar en contexto SymPy
|
|
result = self._eval_in_context(expression)
|
|
|
|
if self.debug:
|
|
print(f"🔍 Resultado evaluación: {result} (tipo: {type(result)})")
|
|
if isinstance(result, PlotResult):
|
|
print(f" 📊 Es PlotResult: plot_type={result.plot_type}")
|
|
|
|
# Actualizar last_result
|
|
self.last_result = result
|
|
|
|
# Intentar evaluación numérica si está configurado para mostrarla
|
|
numeric_result = None
|
|
if self.show_numeric_approximation and hasattr(result, 'evalf'):
|
|
try:
|
|
numeric_eval = result.evalf()
|
|
# MEJORADO: Solo mostrar evaluación numérica si es realmente útil
|
|
if hasattr(result, 'is_Integer') and result.is_Integer:
|
|
# Es un entero SymPy, no mostrar aproximación
|
|
numeric_result = None
|
|
elif hasattr(result, 'is_Rational') and result.is_Rational:
|
|
# Es una fracción racional, mostrar aproximación decimal
|
|
numeric_result = numeric_eval
|
|
elif hasattr(result, 'is_number') and result.is_number:
|
|
# Es un número, verificar si la aproximación es diferente significativamente
|
|
try:
|
|
if abs(float(numeric_eval) - float(result)) > 1e-10:
|
|
numeric_result = numeric_eval
|
|
except:
|
|
# Si no se puede comparar, mostrar solo si el string es diferente
|
|
if str(numeric_eval) != str(result):
|
|
numeric_result = numeric_eval
|
|
elif (str(numeric_eval) != str(result) and numeric_eval != result and
|
|
not (isinstance(result, (int, float)) or
|
|
(hasattr(result, 'is_number') and result.is_number and
|
|
hasattr(result, 'is_Integer') and result.is_Integer))):
|
|
numeric_result = numeric_eval
|
|
except:
|
|
pass
|
|
|
|
return EvaluationResult(
|
|
result, "expression",
|
|
symbolic_result=result,
|
|
numeric_result=numeric_result,
|
|
parse_info=parse_info,
|
|
original_line=original_line
|
|
)
|
|
|
|
except NameError as e:
|
|
# Intentar crear símbolos automáticamente
|
|
return self._handle_undefined_symbols(expression, original_line, e)
|
|
except Exception as e:
|
|
return EvaluationResult(
|
|
None, "error",
|
|
error=str(e),
|
|
original_line=original_line
|
|
)
|
|
|
|
def _handle_undefined_symbols(self, expression: str, original_line: str, error: Exception) -> 'EvaluationResult':
|
|
"""Maneja símbolos no definidos creándolos automáticamente"""
|
|
try:
|
|
# Extraer nombres de variables de la expresión
|
|
var_names = self._extract_variable_names(expression)
|
|
|
|
# Crear símbolos automáticamente
|
|
new_symbols = {}
|
|
for name in var_names:
|
|
if name not in self.symbol_table and name not in self.base_context:
|
|
new_symbols[name] = Symbol(name)
|
|
self.symbol_table[name] = Symbol(name)
|
|
|
|
if new_symbols:
|
|
# Reintentar evaluación
|
|
result = self._eval_in_context(expression)
|
|
|
|
symbol_names = list(new_symbols.keys())
|
|
info_msg = f"Símbolos creados: {', '.join(symbol_names)}"
|
|
|
|
return EvaluationResult(
|
|
result, "symbolic_with_new_vars",
|
|
symbolic_result=result,
|
|
info=info_msg,
|
|
original_line=original_line
|
|
)
|
|
else:
|
|
raise error
|
|
|
|
except Exception as e:
|
|
return EvaluationResult(
|
|
None, "error",
|
|
error=str(e),
|
|
original_line=original_line
|
|
)
|
|
|
|
def _extract_variable_names(self, expression: str) -> List[str]:
|
|
"""Extrae nombres de variables de una expresión"""
|
|
try:
|
|
# Usar SymPy para extraer símbolos
|
|
expr = sympify(expression, locals=self._get_full_context())
|
|
return [str(symbol) for symbol in expr.free_symbols]
|
|
except:
|
|
# Fallback: usar regex
|
|
pattern = r'\b[a-zA-Z_][a-zA-Z0-9_]*\b'
|
|
names = re.findall(pattern, expression)
|
|
# Filtrar funciones conocidas
|
|
return [name for name in names if name not in self.base_context]
|
|
|
|
def _eval_in_context(self, expression: str) -> Any:
|
|
"""Evalúa una expresión en el contexto completo"""
|
|
context = self._get_full_context()
|
|
|
|
# Casos especiales para funciones del sistema
|
|
if expression.strip().startswith('_add_equation'):
|
|
return eval(expression, {"__builtins__": {}}, context)
|
|
elif expression.strip().startswith('_assign_variable'):
|
|
# NUEVA LÓGICA: Manejar asignaciones en modo simbólico
|
|
# Extraer la expresión de la llamada _assign_variable("var", expresión)
|
|
import re
|
|
match = re.match(r'_assign_variable\("([^"]+)",\s*(.+)\)', expression.strip())
|
|
if match:
|
|
var_name = match.group(1)
|
|
expr_to_evaluate = match.group(2).strip()
|
|
|
|
# Evaluar la expresión usando la lógica simbólica
|
|
if self.symbolic_mode:
|
|
try:
|
|
value = sympify(expr_to_evaluate, locals=context, rational=self.keep_symbolic_fractions)
|
|
if self.auto_simplify and hasattr(value, 'simplify'):
|
|
value = value.simplify()
|
|
except:
|
|
# Si falla SymPy, usar eval como fallback
|
|
value = eval(expr_to_evaluate, {"__builtins__": {}}, context)
|
|
else:
|
|
# En modo numérico, usar eval
|
|
value = eval(expr_to_evaluate, {"__builtins__": {}}, context)
|
|
|
|
# Asignar directamente usando los valores evaluados
|
|
self.symbol_table[var_name] = value
|
|
return f"{var_name} = {value}"
|
|
else:
|
|
# Si no se puede parsear, usar eval como fallback
|
|
return eval(expression, {"__builtins__": {}}, context)
|
|
else:
|
|
# NUEVA LÓGICA: Priorizar SymPy en modo simbólico
|
|
if self.symbolic_mode:
|
|
try:
|
|
# INTERCEPTAR: Detectar operaciones aritméticas con objetos nativos
|
|
intercepted_result = self._intercept_native_operations(expression, context)
|
|
if intercepted_result is not None:
|
|
return intercepted_result
|
|
|
|
# Primero intentar con SymPy para mantener formas simbólicas
|
|
result = sympify(expression, locals=context, rational=self.keep_symbolic_fractions)
|
|
|
|
# Si auto_simplify está activado, simplificar
|
|
if self.auto_simplify and hasattr(result, 'simplify'):
|
|
result = result.simplify()
|
|
|
|
return result
|
|
|
|
except (SyntaxError, TypeError, ValueError) as sympy_error:
|
|
# Si SymPy falla, intentar con eval para objetos especializados
|
|
try:
|
|
result = eval(expression, {"__builtins__": {}}, context)
|
|
|
|
# Si el resultado es un objeto híbrido, retornarlo
|
|
if hasattr(result, '_sympystr'): # SympyClassBase
|
|
return result
|
|
elif isinstance(result, PlotResult):
|
|
if self.debug:
|
|
print(f" 📊 PlotResult detectado en eval: {result}")
|
|
return result
|
|
elif hasattr(result, '__iter__') and not isinstance(result, str):
|
|
return result
|
|
else:
|
|
# Convertir resultado de eval a SymPy si es posible
|
|
try:
|
|
return sympify(result, rational=self.keep_symbolic_fractions)
|
|
except:
|
|
return result
|
|
|
|
except Exception as eval_error:
|
|
# Si ambos fallan, re-lanzar el error más informativo
|
|
if "invalid syntax" in str(sympy_error):
|
|
raise eval_error
|
|
else:
|
|
raise sympy_error
|
|
else:
|
|
# MODO NO SIMBÓLICO: usar lógica original
|
|
try:
|
|
# Primero intentar evaluación directa para objetos especializados
|
|
try:
|
|
result = eval(expression, {"__builtins__": {}}, context)
|
|
|
|
# Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario
|
|
if hasattr(result, '_sympystr'): # SympyClassBase
|
|
return result
|
|
elif isinstance(result, PlotResult):
|
|
if self.debug:
|
|
print(f" 📊 PlotResult detectado en eval: {result}")
|
|
return result
|
|
elif hasattr(result, '__iter__') and not isinstance(result, str):
|
|
# Si es una lista/tupla, verificar si contiene objetos híbridos
|
|
return result
|
|
else:
|
|
return result
|
|
|
|
except (NameError, TypeError) as eval_error:
|
|
# Si eval falla, intentar con SymPy
|
|
try:
|
|
result = sympify(expression, locals=context)
|
|
return result
|
|
except:
|
|
# Si ambos fallan, re-lanzar el error original de eval
|
|
raise eval_error
|
|
|
|
except SyntaxError as syntax_error:
|
|
# Para errores de sintaxis, intentar SymPy directamente
|
|
try:
|
|
result = sympify(expression, locals=context)
|
|
return result
|
|
except:
|
|
raise syntax_error
|
|
|
|
def _intercept_native_operations(self, expression: str, context: Dict[str, Any]) -> Any:
|
|
"""
|
|
Intercepta operaciones aritméticas con objetos nativos para evitar
|
|
que SymPy las convierta prematuramente
|
|
"""
|
|
import re
|
|
|
|
# Detectar patrones de aritmética con FourBytes, IntBase, etc.
|
|
# Patrones como: FourBytes(...) + número, número + FourBytes(...), etc.
|
|
|
|
# Patrón 1: Constructor de tipo + operador + número/expresión
|
|
pattern1 = r'(\w+\([^)]+\))\s*([+\-*/])\s*(\d+|\w+)'
|
|
|
|
# Patrón 2: número/expresión + operador + Constructor de tipo
|
|
pattern2 = r'(\d+|\w+)\s*([+\-*/])\s*(\w+\([^)]+\))'
|
|
|
|
for pattern in [pattern1, pattern2]:
|
|
match = re.match(pattern, expression.strip())
|
|
if match:
|
|
try:
|
|
# Evaluar usando eval para que los objetos manejen sus propias operaciones
|
|
result = eval(expression, {"__builtins__": {}}, context)
|
|
|
|
# Verificar si el resultado es un objeto nativo que debe mantenerse
|
|
if hasattr(result, '__class__') and result.__class__.__module__ != 'sympy':
|
|
# Es un objeto de nuestras clases personalizadas
|
|
if hasattr(result, 'original') or hasattr(result, '_numeric_value'):
|
|
if self.debug:
|
|
print(f"🔧 Interceptando operación nativa: {expression} → {result} ({type(result).__name__})")
|
|
return result
|
|
|
|
except Exception as e:
|
|
if self.debug:
|
|
print(f"🔧 Error en intercepción: {e}")
|
|
pass
|
|
|
|
# Si no se detecta patrón específico, retornar None para continuar con SymPy
|
|
return None
|
|
|
|
def _get_full_context(self) -> Dict[str, Any]:
|
|
"""Obtiene el contexto completo para evaluación"""
|
|
context = self.base_context.copy()
|
|
context.update(self.symbol_table)
|
|
context['last'] = self.last_result
|
|
return context
|
|
|
|
def _assign_variable(self, var_name: str, expression) -> str:
|
|
"""Asigna un valor a una variable"""
|
|
try:
|
|
# Evaluar la expresión usando el contexto completo y configuraciones simbólicas
|
|
if isinstance(expression, str):
|
|
value = self._eval_in_context(expression)
|
|
else:
|
|
value = expression
|
|
|
|
# Asignar al contexto
|
|
self.symbol_table[var_name] = value
|
|
|
|
return f"{var_name} = {value}"
|
|
|
|
except Exception as e:
|
|
raise ValueError(f"Error asignando variable '{var_name}': {e}")
|
|
|
|
def _add_equation(self, equation_str: str) -> str:
|
|
"""Agrega una ecuación al sistema"""
|
|
try:
|
|
# Parsear ecuación
|
|
if '=' in equation_str and '==' not in equation_str:
|
|
# Ecuación simple: convertir a igualdad SymPy
|
|
left, right = equation_str.split('=', 1)
|
|
left_expr = sympify(left.strip(), locals=self._get_full_context())
|
|
right_expr = sympify(right.strip(), locals=self._get_full_context())
|
|
equation = Eq(left_expr, right_expr)
|
|
else:
|
|
# Ya es una comparación válida de SymPy
|
|
equation = sympify(equation_str, locals=self._get_full_context())
|
|
|
|
self.equations.append(equation)
|
|
return f"Ecuación {len(self.equations)}: {equation}"
|
|
|
|
except Exception as e:
|
|
raise ValueError(f"Error parseando ecuación '{equation_str}': {e}")
|
|
|
|
def _add_equation_silently(self, equation_str: str) -> bool:
|
|
"""
|
|
Intenta agregar una ecuación al sistema silenciosamente
|
|
Retorna True si tuvo éxito, False si falló
|
|
NUEVA FUNCIÓN: Para agregar ecuaciones sin mostrar errores
|
|
"""
|
|
try:
|
|
# Parsear ecuación
|
|
if '=' in equation_str and '==' not in equation_str:
|
|
# Ecuación simple: convertir a igualdad SymPy
|
|
left, right = equation_str.split('=', 1)
|
|
left_expr = sympify(left.strip(), locals=self._get_full_context())
|
|
right_expr = sympify(right.strip(), locals=self._get_full_context())
|
|
equation = Eq(left_expr, right_expr)
|
|
else:
|
|
# Ya es una comparación válida de SymPy
|
|
equation = sympify(equation_str, locals=self._get_full_context())
|
|
|
|
self.equations.append(equation)
|
|
if self.debug:
|
|
print(f"🔍 Ecuación agregada silenciosamente: {equation}")
|
|
return True
|
|
|
|
except Exception as e:
|
|
if self.debug:
|
|
print(f"🔍 No se pudo agregar como ecuación: {e}")
|
|
return False
|
|
|
|
def solve_system(self, variables: Optional[List[str]] = None) -> Dict[str, Any]:
|
|
"""Resuelve el sistema de ecuaciones"""
|
|
if not self.equations:
|
|
raise ValueError("No hay ecuaciones en el sistema")
|
|
|
|
if variables is None:
|
|
# Obtener todas las variables libres
|
|
all_symbols = set()
|
|
for eq in self.equations:
|
|
all_symbols.update(eq.free_symbols)
|
|
variables = [str(s) for s in all_symbols]
|
|
|
|
# Convertir nombres a símbolos
|
|
symbol_vars = []
|
|
for var_name in variables:
|
|
if var_name in self.symbol_table:
|
|
symbol_vars.append(self.symbol_table[var_name])
|
|
else:
|
|
symbol_vars.append(Symbol(var_name))
|
|
|
|
# Resolver sistema
|
|
solutions = solve(self.equations, symbol_vars)
|
|
|
|
# Convertir resultado a diccionario con nombres de variables
|
|
if isinstance(solutions, dict):
|
|
result = {}
|
|
for symbol, value in solutions.items():
|
|
result[str(symbol)] = value
|
|
# Actualizar tabla de símbolos
|
|
self.symbol_table[str(symbol)] = value
|
|
return result
|
|
elif isinstance(solutions, list):
|
|
# Múltiples soluciones
|
|
return {"solutions": solutions}
|
|
else:
|
|
return {"result": solutions}
|
|
|
|
def assign_variable(self, name: str, value: Any):
|
|
"""Asigna un valor a una variable"""
|
|
self.symbol_table[name] = value
|
|
|
|
def get_variable(self, name: str) -> Optional[Any]:
|
|
"""Obtiene el valor de una variable"""
|
|
return self.symbol_table.get(name)
|
|
|
|
def clear_equations(self):
|
|
"""Limpia todas las ecuaciones"""
|
|
self.equations.clear()
|
|
|
|
def clear_variables(self):
|
|
"""Limpia todas las variables"""
|
|
self.symbol_table.clear()
|
|
|
|
def clear_all(self):
|
|
"""Limpia ecuaciones y variables"""
|
|
self.clear_equations()
|
|
self.clear_variables()
|
|
|
|
def set_symbolic_mode(self, symbolic_mode: bool = True,
|
|
show_numeric: bool = True,
|
|
keep_fractions: bool = True,
|
|
auto_simplify: bool = False):
|
|
"""Configura el modo de evaluación simbólica"""
|
|
self.symbolic_mode = symbolic_mode
|
|
self.show_numeric_approximation = show_numeric
|
|
self.keep_symbolic_fractions = keep_fractions
|
|
self.auto_simplify = auto_simplify
|
|
|
|
def clear(self, *variables) -> str:
|
|
"""
|
|
Función flexible para limpiar variables
|
|
|
|
Sin argumentos: Limpia TODAS las variables
|
|
Con argumentos: Limpia solo las variables especificadas
|
|
|
|
Examples:
|
|
clear() # Limpia todas las variables
|
|
clear("x", "y") # Limpia solo x e y
|
|
clear("a") # Limpia solo a
|
|
"""
|
|
if not variables:
|
|
# Sin argumentos: limpiar todas las variables
|
|
cleared_count = len(self.symbol_table)
|
|
self.symbol_table.clear()
|
|
return f"Limpiadas {cleared_count} variables del contexto"
|
|
else:
|
|
# Con argumentos: limpiar variables específicas
|
|
cleared_vars = []
|
|
not_found_vars = []
|
|
|
|
for var_name in variables:
|
|
if var_name in self.symbol_table:
|
|
del self.symbol_table[var_name]
|
|
cleared_vars.append(var_name)
|
|
else:
|
|
not_found_vars.append(var_name)
|
|
|
|
result_parts = []
|
|
if cleared_vars:
|
|
result_parts.append(f"Variables limpiadas: {', '.join(cleared_vars)}")
|
|
if not_found_vars:
|
|
result_parts.append(f"Variables no encontradas: {', '.join(not_found_vars)}")
|
|
|
|
return " | ".join(result_parts) if result_parts else "No hay variables para limpiar"
|
|
|
|
def clearContext(self) -> str:
|
|
"""
|
|
Limpia completamente el contexto: variables Y ecuaciones
|
|
|
|
Equivale a hacer clear() + clear_equations()
|
|
"""
|
|
var_count = len(self.symbol_table)
|
|
eq_count = len(self.equations)
|
|
|
|
self.symbol_table.clear()
|
|
self.equations.clear()
|
|
|
|
return f"Contexto limpiado: {var_count} variables y {eq_count} ecuaciones eliminadas"
|
|
|
|
def _solve_variable_in_system(self, var_name: str) -> Any:
|
|
"""
|
|
Resuelve una variable específica usando las ecuaciones del sistema
|
|
Esta función reemplaza el comportamiento de solve(variable) para el atajo =?
|
|
|
|
Implementa mejores prácticas según la documentación de SymPy:
|
|
- Usa dict=True para formato consistente
|
|
- Especifica explícitamente la variable a resolver
|
|
- Manejo robusto de diferentes tipos de resultado
|
|
- Manejo especial para sistemas de múltiples variables
|
|
- Manejo mejorado de sistemas subdeterminados
|
|
"""
|
|
# 1. Buscar la variable en el contexto o crearla
|
|
var_symbol = self.symbol_table.get(var_name)
|
|
if var_symbol is None:
|
|
var_symbol = Symbol(var_name)
|
|
self.symbol_table[var_name] = var_symbol
|
|
|
|
# 2. Buscar ecuaciones que contengan esta variable
|
|
relevant_equations = []
|
|
for eq in self.equations:
|
|
if var_symbol in eq.free_symbols:
|
|
relevant_equations.append(eq)
|
|
|
|
if not relevant_equations:
|
|
# Si no hay ecuaciones, mostrar el valor actual de la variable
|
|
if var_name in self.symbol_table:
|
|
current_value = self.symbol_table[var_name]
|
|
if isinstance(current_value, Symbol):
|
|
return f"'{var_name}' es un símbolo sin valor definido. No hay ecuaciones para resolverlo."
|
|
else:
|
|
return current_value
|
|
else:
|
|
return f"No se encontraron ecuaciones para '{var_name}' y la variable no está definida"
|
|
|
|
# 3. Resolver las ecuaciones para esta variable usando mejores prácticas de SymPy
|
|
try:
|
|
if self.debug:
|
|
print(f"🔍 Resolviendo '{var_name}' usando ecuaciones: {relevant_equations}")
|
|
|
|
# MEJORA: Detectar si es un sistema de múltiples variables
|
|
all_variables = set()
|
|
for eq in relevant_equations:
|
|
all_variables.update(eq.free_symbols)
|
|
|
|
if len(all_variables) > 1:
|
|
# Sistema de múltiples variables: resolver todo el sistema y extraer la variable deseada
|
|
if self.debug:
|
|
print(f"🔍 Sistema detectado con variables: {all_variables}")
|
|
|
|
# ESTRATEGIA MEJORADA: Intentar primero resolver solo la variable deseada
|
|
# Si falla, resolver el sistema completo
|
|
|
|
# Intento 1: Resolver solo la variable específica
|
|
try:
|
|
solutions_specific = solve(relevant_equations, var_symbol, dict=True)
|
|
if self.debug:
|
|
print(f"🔍 Soluciones específicas para {var_name}: {solutions_specific}")
|
|
|
|
if solutions_specific and len(solutions_specific) > 0:
|
|
# Éxito con resolución específica
|
|
if len(solutions_specific) == 1:
|
|
solution_dict = solutions_specific[0]
|
|
if var_symbol in solution_dict:
|
|
solution = solution_dict[var_symbol]
|
|
self.symbol_table[var_name] = solution
|
|
return solution
|
|
else:
|
|
# Múltiples soluciones específicas
|
|
solution_values = []
|
|
for sol_dict in solutions_specific:
|
|
if var_symbol in sol_dict:
|
|
solution_values.append(sol_dict[var_symbol])
|
|
if solution_values:
|
|
# MEJORA: Asignar la lista completa a la variable para permitir t[0], t[1], etc.
|
|
self.symbol_table[var_name] = solution_values
|
|
return solution_values
|
|
|
|
except:
|
|
if self.debug:
|
|
print(f"🔍 Resolución específica falló, probando sistema completo")
|
|
|
|
# Intento 2: Resolver el sistema completo y extraer
|
|
all_vars_list = list(all_variables)
|
|
solutions = solve(relevant_equations, all_vars_list, dict=True)
|
|
|
|
if self.debug:
|
|
print(f"🔍 Soluciones del sistema: {solutions}")
|
|
|
|
# Extraer la variable específica del resultado
|
|
if isinstance(solutions, list) and len(solutions) > 0:
|
|
solution_dict = solutions[0] # Tomar la primera solución
|
|
if var_symbol in solution_dict:
|
|
solution = solution_dict[var_symbol]
|
|
# Actualizar la variable en el sistema
|
|
self.symbol_table[var_name] = solution
|
|
# También actualizar las otras variables resueltas
|
|
for sym, val in solution_dict.items():
|
|
self.symbol_table[str(sym)] = val
|
|
return solution
|
|
else:
|
|
# NUEVO: Variable no en solución directa, intentar despeje algebraico
|
|
if self.debug:
|
|
print(f"🔍 '{var_name}' no en solución directa, intentando despeje")
|
|
|
|
# Buscar una ecuación simple que podamos despejar
|
|
for eq in relevant_equations:
|
|
if len(eq.free_symbols) == 2 and var_symbol in eq.free_symbols:
|
|
# Ecuación con 2 variables que incluye la deseada
|
|
try:
|
|
# Intentar resolver esta ecuación específica para nuestra variable
|
|
simple_solution = solve(eq, var_symbol)
|
|
if simple_solution:
|
|
result = simple_solution[0] if len(simple_solution) == 1 else simple_solution
|
|
self.symbol_table[var_name] = result
|
|
return result
|
|
except:
|
|
continue
|
|
|
|
return f"Variable '{var_name}' no se pudo resolver directamente. Solución del sistema: {solution_dict}"
|
|
else:
|
|
return f"No se pudo resolver el sistema para '{var_name}'"
|
|
|
|
else:
|
|
# Variable única: usar el método original
|
|
solutions = solve(relevant_equations, var_symbol, dict=True)
|
|
|
|
if self.debug:
|
|
print(f"🔍 Soluciones obtenidas: {solutions}")
|
|
|
|
# Manejar diferentes tipos de resultado según la documentación
|
|
if isinstance(solutions, list):
|
|
if len(solutions) == 0:
|
|
return f"No se encontraron soluciones para '{var_name}'"
|
|
elif len(solutions) == 1:
|
|
# Una solución única - extraer del diccionario
|
|
solution_dict = solutions[0]
|
|
if var_symbol in solution_dict:
|
|
solution = solution_dict[var_symbol]
|
|
# Actualizar la variable en el sistema
|
|
self.symbol_table[var_name] = solution
|
|
return solution
|
|
else:
|
|
return f"Solución encontrada pero no contiene '{var_name}': {solution_dict}"
|
|
else:
|
|
# Múltiples soluciones - extraer valores del diccionario
|
|
solution_values = []
|
|
for sol_dict in solutions:
|
|
if var_symbol in sol_dict:
|
|
solution_values.append(sol_dict[var_symbol])
|
|
|
|
if solution_values:
|
|
# MEJORA: Asignar la lista completa a la variable para permitir t[0], t[1], etc.
|
|
self.symbol_table[var_name] = solution_values
|
|
return solution_values
|
|
else:
|
|
return f"Múltiples soluciones encontradas pero ninguna contiene '{var_name}': {solutions}"
|
|
else:
|
|
# Resultado no esperado - mostrar tal como está
|
|
return solutions
|
|
|
|
except Exception as e:
|
|
# Información de error más detallada
|
|
error_msg = f"Error resolviendo '{var_name}': {e}"
|
|
if self.debug:
|
|
print(f"🚨 {error_msg}")
|
|
print(f"🚨 Ecuaciones que causaron el error: {relevant_equations}")
|
|
return error_msg
|
|
|
|
|
|
class EvaluationResult:
|
|
"""Resultado de evaluación con información contextual"""
|
|
|
|
def __init__(self,
|
|
result: Any,
|
|
result_type: str,
|
|
symbolic_result: Any = None,
|
|
numeric_result: Any = None,
|
|
error: Optional[str] = None,
|
|
info: Optional[str] = None,
|
|
parse_info: Optional[str] = None,
|
|
original_line: Optional[str] = None):
|
|
self.result = result
|
|
self.result_type = result_type
|
|
self.symbolic_result = symbolic_result or result
|
|
self.numeric_result = numeric_result
|
|
self.error = error
|
|
self.info = info
|
|
self.parse_info = parse_info
|
|
self.original_line = original_line
|
|
|
|
@property
|
|
def is_error(self) -> bool:
|
|
return self.result_type == "error"
|
|
|
|
@property
|
|
def is_interactive(self) -> bool:
|
|
"""Determina si el resultado requiere interactividad"""
|
|
return isinstance(self.result, (PlotResult, sympy.Matrix)) or \
|
|
(isinstance(self.result, list) and len(self.result) > 3)
|
|
|
|
def __str__(self):
|
|
if self.is_error:
|
|
return f"Error: {self.error}"
|
|
elif self.result is not None:
|
|
return str(self.result)
|
|
return ""
|