""" Motor de evaluación híbrida INTEGRADO con el sistema de auto-descubrimiento de tipos VERSIÓN CORREGIDA siguiendo principios de Guía_Base.md """ 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 CORREGIDA siguiendo principios de Guía_Base.md """ 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 según Guía Base self.symbolic_mode = True # Por defecto, mantener forma simbólica self.show_numeric_approximation = True # Mostrar aproximación numérica cuando es útil 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': self._smart_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 completa sobre tipos disponibles""" return self.registered_types_info def get_type_help(self, type_name: str) -> Optional[str]: """Obtiene ayuda para un tipo específico""" # Buscar la clase en el registro classes = self.registered_types_info.get('registered_classes', {}) if type_name in classes: cls = classes[type_name] if hasattr(cls, 'Helper'): return cls.Helper("") return None def _create_plot_placeholder(self, *args, **kwargs): """Crea un resultado interactivo para plot""" return PlotResult("plot", args, kwargs) def _create_plot3d_placeholder(self, *args, **kwargs): """Crea un resultado interactivo para plot3d""" return PlotResult("plot3d", args, kwargs) def _help_function(self, obj=None): """Función de ayuda personalizada""" if obj is None: return "Ayuda general disponible. Usa help(objeto) para ayuda específica." # Si es un tipo personalizado, buscar en helpers obj_type = type(obj).__name__ for helper_func in self.helper_functions: try: help_text = helper_func(obj_type) if help_text and help_text.strip(): return help_text except: continue # Fallback a ayuda de Python import pydoc return pydoc.render_doc(obj, renderer=pydoc.plaintext) def _smart_solve(self, *args, **kwargs): """ Función solve inteligente que usa nuestro sistema de ecuaciones cuando es apropiado, o llama a sympy.solve en otros casos """ try: # Caso 1: solve(variable) → usar nuestro sistema if len(args) == 1 and isinstance(args[0], (sympy.Symbol, str)): var_name = str(args[0]) return self._solve_variable_in_system(var_name) # Caso 2: solve(ecuacion, variable) → usar sympy.solve directamente elif len(args) == 2: return sympy.solve(*args, **kwargs) # Caso 3: solve(lista_ecuaciones, lista_variables) → usar sympy.solve elif len(args) >= 1 and isinstance(args[0], (list, tuple)): return sympy.solve(*args, **kwargs) # Caso 4: solve() sin argumentos → resolver todas las variables del sistema elif len(args) == 0: return self.solve_system() # Otros casos → usar sympy.solve directamente else: return sympy.solve(*args, **kwargs) except Exception as e: if self.debug: print(f"⚠️ Error en _smart_solve: {e}") # Fallback a sympy.solve return sympy.solve(*args, **kwargs) def evaluate_line(self, line: str) -> 'EvaluationResult': """ Evalúa una línea de código y retorna el resultado VERSIÓN CORREGIDA siguiendo principios de Guía_Base.md """ try: # 1. Aplicar tokenización distribuida parsed_line = self.parser.process_expression(line) if self.debug: print(f"Parse: '{line}' → '{parsed_line}'") # 2. NUEVA LÓGICA: Detectar atajo =? if self._is_solve_shortcut(line): return self._evaluate_solve_shortcut(line) # 3. Clasificar tipo de línea según criterios de la Guía Base line_type = self._classify_line(parsed_line, line) if line_type == "comment": return EvaluationResult(None, "comment", original_line=line) elif line_type == "assignment": return self._evaluate_assignment(parsed_line, line) elif line_type == "equation": return self._evaluate_equation_addition(parsed_line, line) # 4. Evaluación SymPy con evaluación numérica automática 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 _is_solve_shortcut(self, line: str) -> bool: """Detecta el atajo variable=? según Guía Base""" # Patrón: variable=? pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?\s*$' return bool(re.match(pattern, line.strip())) def _evaluate_solve_shortcut(self, line: str) -> 'EvaluationResult': """Evalúa el atajo variable=? como solve(variable)""" try: # Extraer variable del patrón variable=? pattern = r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*\?\s*$' match = re.match(pattern, line.strip()) if not match: raise ValueError("Formato inválido para =?") var_name = match.group(1) # Crear llamada a solve solve_expression = f"solve({var_name})" # Evaluar usando _solve_variable_in_system directamente result = self._solve_variable_in_system(var_name) return EvaluationResult( result, "solve_result", symbolic_result=result, info=f"Resolviendo {var_name} en el sistema de ecuaciones", original_line=line ) except Exception as e: return EvaluationResult( None, "error", error=f"Error en solve shortcut: {e}", original_line=line ) def _classify_line(self, parsed_line: str, original_line: str) -> str: """ Clasifica el tipo de línea según criterios de la Guía Base Criterios: - Asignación: variable = expresión (solo variables simples) - Ecuación: contiene = Y NO es asignación simple O contiene operadores de comparación - Comentario: línea vacía o con # - Expresión: todo lo demás """ # Comentarios if not parsed_line or parsed_line.strip().startswith('#'): return "comment" # Verificar si contiene operadores de ecuación has_equals = '=' in parsed_line has_comparison = any(op in parsed_line for op in ['==', '!=', '<=', '>=', '<', '>']) if has_comparison: return "equation" if has_equals: # Verificar si es asignación simple según Guía Base if self._is_simple_assignment(parsed_line): return "assignment" else: # Es una ecuación (estructura algebraica en ambos lados) return "equation" return "expression" def _is_simple_assignment(self, line: str) -> bool: """ Detecta asignaciones simples según Guía Base: - variable = expresión - Solo un = (no ==, !=, etc.) - Lado izquierdo es un identificador válido de Python """ try: # Verificar que solo tiene un = y no es comparación if line.count('=') != 1 or any(op in line for op in ['==', '!=', '<=', '>=']): return False parts = line.split('=', 1) if len(parts) != 2: return False var_part = parts[0].strip() expr_part = parts[1].strip() # Verificar que la parte izquierda sea un identificador válido if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', var_part): return False # Verificar que la parte derecha no esté vacía if not expr_part: return False return True except: return False def _evaluate_assignment(self, parsed_line: str, original_line: str) -> 'EvaluationResult': """ Evalúa asignación de variable según Guía Base - Guarda el valor simbólico - Si contiene variables no definidas, también agrega como ecuación implícita - Proporciona evaluación numérica cuando es útil """ try: if '=' not in parsed_line: raise ValueError(f"Línea de asignación sin '=': {parsed_line}") parts = parsed_line.split('=', 1) var_name = parts[0].strip() expr_part = parts[1].strip() # Evaluar la expresión en el contexto actual result = self._eval_in_context(expr_part) # Guardar en symbol_table self.symbol_table[var_name] = result self.last_result = result # NUEVO: Si la asignación contiene símbolos no definidos, agregarla como ecuación implícita if self._assignment_has_undefined_symbols(var_name, result): try: # Crear ecuación implícita: var_name = result var_symbol = sympy.Symbol(var_name) equation = sympy.Eq(var_symbol, result) self.equations.append(equation) if self.debug: print(f"🔗 Asignación con símbolos agregada como ecuación: {equation}") except Exception as eq_error: if self.debug: print(f"⚠️ No se pudo agregar asignación como ecuación: {eq_error}") # Generar evaluación numérica según Guía Base numeric_result = self._generate_numeric_approximation(result) return EvaluationResult( result, "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 _assignment_has_undefined_symbols(self, var_name: str, result: Any) -> bool: """ Determina si una asignación contiene símbolos no definidos y por tanto debería agregarse como ecuación implícita """ try: # Obtener símbolos libres del resultado if hasattr(result, 'free_symbols'): free_symbols = result.free_symbols # Verificar si hay símbolos que no están definidos en symbol_table # (excluyendo el símbolo de la variable que se está asignando) for symbol in free_symbols: symbol_name = str(symbol) if symbol_name != var_name and symbol_name not in self.symbol_table: return True # También verificar si el símbolo existe pero es solo un Symbol sin valor elif symbol_name in self.symbol_table: symbol_value = self.symbol_table[symbol_name] if isinstance(symbol_value, sympy.Symbol) and str(symbol_value) == symbol_name: return True return False except Exception as e: if self.debug: print(f"⚠️ Error verificando símbolos de asignación: {e}") return False def _generate_numeric_approximation(self, result: Any) -> Optional[Any]: """ Genera aproximación numérica según Guía Base: - Solo mostrar cuando la representación string difiere del resultado algebraico """ if not self.show_numeric_approximation: return None try: # Intentar evaluación numérica if hasattr(result, 'evalf'): numeric_eval = result.evalf() # Solo mostrar si el string es diferente if str(numeric_eval) != str(result): return numeric_eval elif hasattr(result, '__float__'): try: numeric_eval = float(result) if str(numeric_eval) != str(result): return numeric_eval except: pass except Exception as e: if self.debug: print(f"DEBUG: Error en evaluación numérica: {e}") return None def _evaluate_equation_addition(self, parsed_line: str, original_line: str) -> 'EvaluationResult': """ Evalúa y agrega ecuación al sistema según Guía Base - Se agrega automáticamente al sistema de ecuaciones """ try: # Detectar si es ecuación con = o con operadores de comparación if '=' in parsed_line and '==' not in parsed_line: # Ecuación con = → convertir a Eq() parts = parsed_line.split('=', 1) left_expr = parts[0].strip() right_expr = parts[1].strip() # Evaluar ambos lados left_val = self._eval_in_context(left_expr) right_val = self._eval_in_context(right_expr) # Crear ecuación SymPy equation = sympy.Eq(left_val, right_val) else: # Ecuación con operadores de comparación → evaluar directamente equation = self._eval_in_context(parsed_line) # Agregar al sistema self.equations.append(equation) return EvaluationResult( equation, "equation_added", symbolic_result=equation, info=f"Ecuación agregada al sistema: {equation}", 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 con evaluación numérica automática """ 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 # Generar evaluación numérica automática según Guía Base numeric_result = self._generate_numeric_approximation(result) # Determinar tipo de resultado result_type = type(result).__name__ return EvaluationResult( result, result_type, symbolic_result=result, numeric_result=numeric_result, parse_info=parse_info, original_line=original_line ) except Exception as e: # Manejar símbolos no definidos if "undefined" in str(e).lower() or "not defined" in str(e).lower(): return self._handle_undefined_symbols(expression, original_line, 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 con sustituciones automáticas""" context = self.base_context.copy() # NUEVO: Aplicar sustituciones automáticas en expresiones simbólicas substituted_table = self._apply_automatic_substitutions() context.update(substituted_table) context['last'] = self.last_result return context def _apply_automatic_substitutions(self) -> Dict[str, Any]: """ Aplica sustituciones automáticas a expresiones simbólicas usando valores conocidos """ substituted = {} # Separar valores numéricos de expresiones simbólicas numeric_values = {} symbolic_expressions = {} for var_name, value in self.symbol_table.items(): if hasattr(value, 'free_symbols') and value.free_symbols: # Es una expresión simbólica symbolic_expressions[var_name] = value else: # Es un valor numérico o constante numeric_values[var_name] = value substituted[var_name] = value # Crear diccionario de sustituciones con valores numéricos substitutions = {} for var_name, value in numeric_values.items(): if hasattr(value, 'evalf'): # Es un valor SymPy, conservar forma exacta para sustitución substitutions[sympy.Symbol(var_name)] = value elif isinstance(value, (int, float)): # Es un valor numérico Python substitutions[sympy.Symbol(var_name)] = value else: # Otros tipos, intentar convertir a SymPy try: substitutions[sympy.Symbol(var_name)] = sympy.sympify(value) except: substitutions[sympy.Symbol(var_name)] = value # Aplicar sustituciones a expresiones simbólicas for var_name, expr in symbolic_expressions.items(): try: if substitutions: # Aplicar todas las sustituciones conocidas substituted_expr = expr.subs(substitutions) # Si después de las sustituciones no quedan símbolos libres, evaluar numéricamente if hasattr(substituted_expr, 'free_symbols') and not substituted_expr.free_symbols: try: # Evaluar completamente la expresión evaluated = substituted_expr.evalf() if hasattr(substituted_expr, 'evalf') else substituted_expr substituted[var_name] = evaluated if self.debug: print(f"🔄 Sustitución automática: {var_name} = {expr} → {evaluated}") except: # Si falla la evaluación, usar la expresión sustituida substituted[var_name] = substituted_expr else: # Aún hay símbolos libres, usar expresión parcialmente sustituida substituted[var_name] = substituted_expr if self.debug and substituted_expr != expr: print(f"🔄 Sustitución parcial: {var_name} = {expr} → {substituted_expr}") else: # No hay sustituciones disponibles, mantener expresión original substituted[var_name] = expr except Exception as e: if self.debug: print(f"⚠️ Error en sustitución automática para {var_name}: {e}") # En caso de error, mantener expresión original substituted[var_name] = expr return substituted 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 ""