""" 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 # ========== NUEVO: Importar clases base para tokenización ========== try: from sympy_Base import IntBase, FourBytes BASE_CLASSES_AVAILABLE = True except ImportError: BASE_CLASSES_AVAILABLE = False print("⚠️ Clases base IntBase/FourBytes no disponibles") 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 habilitada ========== self.parser = BracketParser(use_type_registry=True) self.parser.use_tokenizer = BASE_CLASSES_AVAILABLE # Habilitar tokenizador si clases disponibles if self.parser.use_tokenizer: self.parser.debug = False # Será habilitado por self.debug más adelante 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 # 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 PARA TOKENIZACIÓN ========== base_classes = {} if BASE_CLASSES_AVAILABLE: base_classes = { 'IntBase': IntBase, 'FourBytes': FourBytes, } if self.debug: print("🔧 Clases base IntBase/FourBytes añadidas al contexto") # 5. COMBINAR TODO EN EL CONTEXTO BASE self.base_context = { **math_functions, **specialized_classes, **utility_functions, **base_classes # ← NUEVO: Incluir clases base } # 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 def _update_bracket_parser(self): """Actualiza el BracketParser con las clases descubiertas""" try: # NUEVO: Llamar al método reload para actualizar dinámicamente self.parser.reload_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 NUEVA LÓGICA: Priorizar asignaciones, intentar ecuaciones silenciosamente """ 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 == "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 parse_info == "equation": return self._evaluate_equation_addition(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) # 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: # 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 _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 ""