628 lines
23 KiB
Python
628 lines
23 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
|
||
|
||
|
||
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"):
|
||
self.parser = BracketParser()
|
||
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 = []
|
||
|
||
# Debug mode
|
||
self.debug = False
|
||
# 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(),
|
||
# 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,
|
||
'help': self._help_function,
|
||
'evalf': lambda expr, n=15: expr.evalf(n) if hasattr(expr, 'evalf') else float(expr),
|
||
}
|
||
|
||
# 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)}")
|
||
|
||
def _update_bracket_parser(self):
|
||
"""Actualiza el BracketParser con las clases descubiertas"""
|
||
try:
|
||
discovered_bracket_classes = get_registered_bracket_classes()
|
||
# Combinar con clases existentes del parser
|
||
self.parser.BRACKET_CLASSES = self.parser.BRACKET_CLASSES.union(discovered_bracket_classes)
|
||
|
||
if self.debug:
|
||
print(f"🔧 Bracket classes actualizadas: {self.parser.BRACKET_CLASSES}")
|
||
except Exception as e:
|
||
print(f"⚠️ Error actualizando bracket parser: {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
|
||
"""
|
||
try:
|
||
# 1. Parsear la línea
|
||
parsed_line, parse_info = self.parser.parse_line(line)
|
||
|
||
if self.debug:
|
||
print(f"Parse: '{line}' → '{parsed_line}' ({parse_info})")
|
||
|
||
# 2. Manejar casos especiales
|
||
if parse_info == "comment":
|
||
return EvaluationResult(None, "comment", original_line=line)
|
||
elif parse_info == "equation":
|
||
return self._evaluate_equation_addition(parsed_line, line)
|
||
elif parse_info == "assignment":
|
||
return self._evaluate_assignment(parsed_line, line)
|
||
|
||
# 3. Evaluación SymPy
|
||
return self._evaluate_sympy_expression(parsed_line, parse_info, line)
|
||
|
||
except Exception as e:
|
||
return EvaluationResult(
|
||
None, "error",
|
||
error=str(e),
|
||
original_line=line
|
||
)
|
||
|
||
def _evaluate_assignment(self, parsed_line: str, original_line: str) -> 'EvaluationResult':
|
||
"""Maneja la asignación de variables"""
|
||
try:
|
||
# Ejecutar _assign_variable
|
||
result = self._eval_in_context(parsed_line)
|
||
|
||
# Extraer nombre de variable y valor del resultado
|
||
parts = original_line.split('=', 1)
|
||
var_name = parts[0].strip()
|
||
|
||
# Obtener el valor asignado
|
||
assigned_value = self.symbol_table.get(var_name)
|
||
|
||
return EvaluationResult(
|
||
assigned_value, "assignment",
|
||
symbolic_result=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 es posible
|
||
numeric_result = None
|
||
if hasattr(result, 'evalf'):
|
||
try:
|
||
numeric_eval = result.evalf()
|
||
if numeric_eval != result:
|
||
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'):
|
||
return eval(expression, {"__builtins__": {}}, context)
|
||
else:
|
||
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 _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
|
||
if isinstance(expression, str):
|
||
value = sympify(expression, locals=self._get_full_context())
|
||
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 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()
|
||
|
||
|
||
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 ""
|
||
|
||
|
||
# Funciones de testing
|
||
def test_hybrid_engine_with_types():
|
||
"""Test del motor de evaluación con sistema de tipos"""
|
||
print("🧪 Test HybridEvaluationEngine con Type Registry")
|
||
print("=" * 60)
|
||
|
||
# Crear motor con auto-descubrimiento
|
||
engine = HybridEvaluationEngine(auto_discover_types=True)
|
||
engine.debug = True
|
||
|
||
# Mostrar información de tipos
|
||
types_info = engine.get_available_types()
|
||
print(f"📊 Tipos disponibles: {types_info}")
|
||
print()
|
||
|
||
# Test casos básicos
|
||
test_cases = [
|
||
# Expresiones básicas
|
||
"2 + 3",
|
||
"x + 2",
|
||
"sin(pi/2)",
|
||
|
||
# Tipo Chr si está disponible
|
||
"Chr[A]" if "Chr" in types_info['registered_classes'] else "# Chr no disponible",
|
||
|
||
# Variables y ecuaciones
|
||
"a = 10",
|
||
"x + 2 = 5",
|
||
"solve(x + 2 - 5, x)",
|
||
|
||
# Funciones avanzadas
|
||
"diff(x**2, x)",
|
||
"plot(sin(x), (x, -pi, pi))",
|
||
]
|
||
|
||
print("🔍 Ejecutando casos de prueba:")
|
||
for test in test_cases:
|
||
if test.startswith('#'):
|
||
print(f"⏭️ {test}")
|
||
continue
|
||
|
||
result = engine.evaluate_line(test)
|
||
print(f"✅ '{test}' → {result} (type: {result.result_type})")
|
||
|
||
if result.info:
|
||
print(f" ℹ️ Info: {result.info}")
|
||
|
||
if result.is_error:
|
||
print(f" ❌ Error: {result.error}")
|
||
|
||
print("\n🔄 Test recarga de tipos:")
|
||
engine.reload_types()
|
||
print("✅ Recarga completada")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
test_hybrid_engine_with_types()
|