Calc/main_evaluation.py

628 lines
23 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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()