From 7d2033c60e98135ebea530c0c525efb74693a2d6 Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 6 Jun 2025 07:00:00 +0200 Subject: [PATCH] =?UTF-8?q?Refactorizaci=C3=B3n=20del=20motor=20de=20evalu?= =?UTF-8?q?aci=C3=B3n=20para=20utilizar=20un=20sistema=20algebraico=20puro?= =?UTF-8?q?.=20Se=20actualizan=20las=20configuraciones=20de=20la=20interfa?= =?UTF-8?q?z=20y=20se=20simplifican=20las=20funciones=20de=20evaluaci?= =?UTF-8?q?=C3=B3n.=20Se=20a=C3=B1aden=20patrones=20de=20tokenizaci=C3=B3n?= =?UTF-8?q?=20en=20el=20registro=20de=20tipos=20y=20se=20mejora=20la=20ges?= =?UTF-8?q?ti=C3=B3n=20de=20errores=20en=20el=20motor.=20Se=20eliminan=20c?= =?UTF-8?q?onfiguraciones=20obsoletas=20y=20se=20optimiza=20el=20men=C3=BA?= =?UTF-8?q?=20de=20herramientas.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hybrid_calc_history.txt | 13 +- main_calc_app.py | 293 ++++++--------------- main_evaluation_puro.py | 395 +++++++++++++++++++++++++++++ simple_debug.py | 59 +++-- test_ecuaciones_puras.json | 84 ++++++ test_ecuaciones_puras_results.json | 139 ++++++++++ test_motor_puro.json | 80 ++++++ test_motor_puro_results.json | 131 ++++++++++ test_sympy_puro.py | 72 ++++++ type_registry.py | 27 +- 10 files changed, 1051 insertions(+), 242 deletions(-) create mode 100644 main_evaluation_puro.py create mode 100644 test_ecuaciones_puras.json create mode 100644 test_ecuaciones_puras_results.json create mode 100644 test_motor_puro.json create mode 100644 test_motor_puro_results.json create mode 100644 test_sympy_puro.py diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index 4b60771..60e6407 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,12 +1,9 @@ -a = x + 5 -x=? -solve(x) +a = x + 5 / z -m=t+u * 5 +z=? -t=4 -m=3 -u=? +a=2 +x=3 -u \ No newline at end of file +IP4(10.1.1.2) \ No newline at end of file diff --git a/main_calc_app.py b/main_calc_app.py index 2cdafbd..5035dae 100644 --- a/main_calc_app.py +++ b/main_calc_app.py @@ -45,7 +45,7 @@ if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None: # ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ========== # Importar componentes del CAS híbrido con nuevo sistema de tipos -from main_evaluation import HybridEvaluationEngine, EvaluationResult +from main_evaluation_puro import PureAlgebraicEngine, EvaluationResult from tl_popup import InteractiveResultManager, PlotResult from type_registry import get_registered_helper_functions, get_registered_base_context import sympy @@ -69,13 +69,15 @@ class HybridCalculatorApp: self.root.geometry(self.settings.get("window_geometry", "1000x700")) self.root.configure(bg="#2b2b2b") - # Configurar motor con configuraciones cargadas - self.engine = HybridEvaluationEngine(auto_discover_types=True, types_directory="custom_types") - self._apply_symbolic_settings() # NUEVO: Aplicar configuraciones simbólicas - - # Debug desde configuración + # Debug desde configuración (definir antes del motor) self.debug = self.settings.get("debug", False) - self.engine.debug = self.debug + + # Motor de evaluación - SISTEMA ALGEBRAICO PURO + self.engine = PureAlgebraicEngine() + + # Configurar motor + if hasattr(self.engine, 'logger'): + self.engine.logger.setLevel(logging.DEBUG if self.debug else logging.INFO) # Autocompletado self.autocomplete_popup = None @@ -103,7 +105,7 @@ class HybridCalculatorApp: self.status_label = tk.Label( self.status_frame, - text=self._get_status_text(), + text="🔢 Calculadora MAV - Sistema Algebraico Puro", bg="#2b2b2b", fg="#80c7f7", font=("Consolas", 9), @@ -140,49 +142,43 @@ class HybridCalculatorApp: def reload_types(self): """Recarga el sistema de tipos (útil para desarrollo)""" try: - self.logger.info("Recargando sistema de tipos...") # Original: 🔄 - - # Recargar engine - self.engine.reload_types() + self.logger.info("Recargando sistema de tipos...") # Recargar helpers self._setup_dynamic_helpers() # Re-evaluar contenido actual self._evaluate_and_update() - self.logger.info("Sistema de tipos recargado.") # Original: ✅ + self.logger.info("Sistema de tipos recargado.") except Exception as e: - self.logger.error(f"Error recargando tipos: {e}", exc_info=True) # Original: ❌ + self.logger.error(f"Error recargando tipos: {e}", exc_info=True) messagebox.showerror("Error", f"Error recargando tipos:\n{e}") def show_types_info(self): """Muestra información sobre tipos disponibles""" try: - types_info = self.engine.get_available_types() + context_info = self.engine.get_context_info() - info_text = f"""INFORMACIÓN DEL SISTEMA DE TIPOS + info_text = f"""INFORMACIÓN DEL SISTEMA ALGEBRAICO PURO -Clases registradas: {len(types_info.get('registered_classes', {}))} -Clases con sintaxis de corchetes: {len(types_info.get('bracket_classes', []))} -Entradas en contexto: {types_info.get('total_context_entries', 0)} -Helper functions: {types_info.get('helper_functions_count', 0)} +Ecuaciones en el sistema: {context_info.get('equations', 0)} +Variables definidas: {context_info.get('variables', 0)} +Variables activas: {', '.join(context_info.get('variable_names', []))} -CLASES DISPONIBLES: +CARACTERÍSTICAS: +• Sistema de ecuaciones puras con SymPy +• Todas las asignaciones son ecuaciones +• Resolución automática de sistemas +• Evaluación numérica inteligente +• Atajo x=? equivale a solve(x) """ - for name, cls in types_info.get('registered_classes', {}).items(): - info_text += f"• {name}: {cls.__name__}\n" - - info_text += f"\nCLASES CON SINTAXIS DE CORCHETES:\n" - for name in types_info.get('bracket_classes', []): - info_text += f"• {name}[...]\n" - # Mostrar en ventana - self._show_help_window("Información de Tipos", info_text) + self._show_help_window("Información del Sistema", info_text) except Exception as e: - messagebox.showerror("Error", f"Error obteniendo información de tipos:\n{e}") + messagebox.showerror("Error", f"Error obteniendo información del sistema:\n{e}") def _setup_icon(self): """Configura el ícono de la aplicación""" @@ -228,27 +224,7 @@ CLASES DISPONIBLES: if self.debug: self.logger.error(f"Error guardando configuración: {e}", exc_info=True) - def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None, - keep_fractions=None, auto_simplify=None): - """Actualiza configuraciones simbólicas y las guarda""" - if symbolic_mode is not None: - self.settings["symbolic_mode"] = symbolic_mode - if show_numeric is not None: - self.settings["show_numeric_approximation"] = show_numeric - if keep_fractions is not None: - self.settings["keep_symbolic_fractions"] = keep_fractions - if auto_simplify is not None: - self.settings["auto_simplify"] = auto_simplify - - # Aplicar al motor - self._apply_symbolic_settings() - - # Actualizar barra de estado - if hasattr(self, 'status_label'): - self.status_label.config(text=self._get_status_text()) - - # Guardar configuraciones - self._save_settings() + def create_widgets(self): """Crea la interfaz gráfica""" @@ -346,34 +322,10 @@ CLASES DISPONIBLES: edit_menu.add_separator() edit_menu.add_command(label="Limpiar historial", command=self.clear_history) - # Menú Configuración - config_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white") - menubar.add_cascade(label="Configuración", menu=config_menu) - - # Variables para checkbuttons - self.symbolic_mode_var = tk.BooleanVar(value=self.settings.get("symbolic_mode", True)) - self.show_numeric_var = tk.BooleanVar(value=self.settings.get("show_numeric_approximation", True)) - self.keep_fractions_var = tk.BooleanVar(value=self.settings.get("keep_symbolic_fractions", True)) - - # Modo simbólico - config_menu.add_checkbutton( - label="Modo Simbólico", - variable=self.symbolic_mode_var, - command=self.toggle_symbolic_mode - ) - config_menu.add_checkbutton( - label="Mostrar Aproximación Numérica", - variable=self.show_numeric_var, - command=self.toggle_numeric_approximation - ) - config_menu.add_checkbutton( - label="Mantener Fracciones Simbólicas", - variable=self.keep_fractions_var, - command=self.toggle_symbolic_fractions - ) - config_menu.add_separator() - - config_menu.add_command(label="Recargar Tipos Personalizados", command=self.reload_types) + # Menú Herramientas (simplificado) + tools_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white") + menubar.add_cascade(label="Herramientas", menu=tools_menu) + tools_menu.add_command(label="Recargar Tipos Personalizados", command=self.reload_types) # ========== MENÚ TIPOS (NUEVO) ========== types_menu = Menu(menubar, tearoff=0, bg="#3c3c3c", fg="white") @@ -687,7 +639,7 @@ CLASES DISPONIBLES: # NUEVO: Limpiar completamente el contexto antes de cada evaluación # Esto garantiza que cada modificación reevalúe todo desde cero - self.engine.clear_all() + self.engine.clear_context() lines = input_content.splitlines() self._evaluate_lines(lines) @@ -718,132 +670,73 @@ CLASES DISPONIBLES: self._display_output(output_data) def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]: - """Procesa el resultado de evaluación para display""" + """Procesa el resultado de evaluación para display - ADAPTADO AL MOTOR PURO""" output_parts = [] - if result.is_error: - ayuda = self.obtener_ayuda(result.original_line) + if not result.success: + # Error + ayuda = self.obtener_ayuda(result.input_line) if ayuda: ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ") if len(ayuda_linea) > 120: ayuda_linea = ayuda_linea[:117] + "..." output_parts.append(("helper", ayuda_linea)) else: - output_parts.append(("error", f"Error: {result.error}")) + output_parts.append(("error", f"Error: {result.error_message}")) elif result.result_type == "comment": - output_parts.append(("comment", result.original_line)) - elif result.result_type == "equation_added": - output_parts.append(("equation", result.symbolic_result)) - elif result.result_type == "assignment": - output_parts.append(("info", result.symbolic_result)) - # Mostrar evaluación numérica para asignaciones si existe - if result.numeric_result is not None and result.numeric_result != result.result: - output_parts.append(("numeric", f"≈ {result.numeric_result}")) + output_parts.append(("comment", result.input_line)) + elif result.result_type == "equation": + output_parts.append(("equation", result.output)) + elif result.result_type == "symbolic": + output_parts.append(("symbolic", result.output)) else: - # Resultado normal - if result.result is not None: - # Determinar tag basado en tipo (DINÁMICO) - tag = self._get_result_tag_dynamic(result.result) - - # Verificar si es resultado interactivo - if self.interactive_manager and result.is_interactive: - interactive_tag, display_text = self.interactive_manager.create_interactive_tag(result.result, self.output_text, "1.0") - if interactive_tag: - output_parts.append((interactive_tag, display_text)) - else: - output_parts.append((tag, str(result.result))) - else: - output_parts.append((tag, str(result.result))) - - # Añadir pista de clase para el resultado principal - primary_result_object = result.result - if not isinstance(primary_result_object, PlotResult): - class_display_name = self._get_class_display_name_dynamic(primary_result_object) - if class_display_name: - output_parts.append(("class_hint", f"[{class_display_name}]")) - - # Mostrar evaluación numérica si existe - if result.numeric_result is not None and result.numeric_result != result.result: - output_parts.append(("numeric", f"≈ {result.numeric_result}")) - - # Mostrar información adicional - if result.info: - output_parts.append(("info", f"({result.info})")) + # Resultado general + output_parts.append(("result", result.output)) return output_parts def _get_result_tag_dynamic(self, result: Any) -> str: - """Determina el tag de color para un resultado - VERSIÓN DINÁMICA""" - # Obtener clases registradas dinámicamente del sistema de tipos - try: - registered_classes = self.engine.get_available_types().get('registered_classes', {}) - - # Verificar si es una instancia de alguna clase registrada - for name, cls in registered_classes.items(): - if isinstance(result, cls): - # Usar tags específicos basados en el nombre de la clase - name_lower = name.lower() - if name_lower == "hex": - return "hex" - elif name_lower == "bin": - return "bin" - elif name_lower in ["ip4", "ip"]: - return "ip" - elif name_lower == "chr": - return "chr_type" - elif name_lower == "date": - return "date" - else: - return "custom_type" # Tag genérico para tipos personalizados - - except Exception as e: - if self.debug: - self.logger.debug(f"Error en get_result_tag_dynamic: {e}") + """Determina el tag de color para un resultado - SIMPLIFICADO""" + # Determinar tag basado en tipo + if hasattr(result, '__class__'): + class_name = result.__class__.__name__.lower() + if 'hex' in class_name: + return "hex" + elif 'bin' in class_name: + return "bin" + elif 'ip' in class_name: + return "ip" + elif 'chr' in class_name: + return "chr_type" + elif 'date' in class_name: + return "date" - # Fallback a tags existentes para tipos no registrados - if isinstance(result, sympy.Basic): - return "symbolic" - else: - return "result" + # Fallback a tags existentes + try: + import sympy + if isinstance(result, sympy.Basic): + return "symbolic" + except: + pass + + return "result" def _get_class_display_name_dynamic(self, obj: Any) -> str: - """Obtiene nombre de clase para display - VERSIÓN DINÁMICA""" + """Obtiene nombre de clase para display - SIMPLIFICADO""" try: - # Verificar si es una clase registrada dinámicamente - registered_classes = self.engine.get_available_types().get('registered_classes', {}) - - for name, cls in registered_classes.items(): - if isinstance(obj, cls): - return name - - except Exception as e: - if self.debug: - self.logger.debug(f"Error en get_class_display_name_dynamic: {e}") - - # Fallback a lógica existente para tipos nativos - if isinstance(obj, sympy.logic.boolalg.BooleanAtom): - return "Boolean" - elif isinstance(obj, sympy.Basic): - if hasattr(obj, 'is_number') and obj.is_number: - if hasattr(obj, 'is_Integer') and obj.is_Integer: - return "Integer" - elif hasattr(obj, 'is_Rational') and obj.is_Rational and not obj.is_Integer: - return "Rational" - elif hasattr(obj, 'is_Float') and obj.is_Float: - return "Float" - else: - return "SympyNumber" - else: + import sympy + if isinstance(obj, sympy.Basic): return "Sympy" - elif isinstance(obj, bool): - return "Boolean" - elif isinstance(obj, (int, float, str, list, dict, tuple, type(None))): + except: + pass + + if isinstance(obj, (int, float, str, list, dict, tuple, bool, type(None))): class_display_name = type(obj).__name__.capitalize() if class_display_name == "Nonetype": class_display_name = "None" return class_display_name - return "" + return type(obj).__name__ def _display_output(self, output_data: List[List[tuple]]): """Muestra los datos de salida en el widget (sin cambios)""" @@ -919,6 +812,7 @@ CLASES DISPONIBLES: """Inicia nueva sesión""" self.clear_input() self.clear_output() + self.engine.clear_context() # Limpiar contexto del motor def load_file(self): """Carga archivo en el editor""" @@ -1428,42 +1322,7 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el continue return None - def _apply_symbolic_settings(self): - """Aplica configuraciones simbólicas al motor de evaluación""" - symbolic_mode = self.settings.get("symbolic_mode", True) - show_numeric = self.settings.get("show_numeric_approximation", True) - keep_fractions = self.settings.get("keep_symbolic_fractions", True) - auto_simplify = self.settings.get("auto_simplify", False) - - self.engine.set_symbolic_mode( - symbolic_mode=symbolic_mode, - show_numeric=show_numeric, - keep_fractions=keep_fractions, - auto_simplify=auto_simplify - ) - def toggle_symbolic_mode(self): - """Alterna el modo simbólico""" - new_value = self.symbolic_mode_var.get() - self.update_symbolic_settings(symbolic_mode=new_value) - - def toggle_numeric_approximation(self): - """Alterna la aproximación numérica""" - new_value = self.show_numeric_var.get() - self.update_symbolic_settings(show_numeric=new_value) - - def toggle_symbolic_fractions(self): - """Alterna la mantención de fracciones simbólicas""" - new_value = self.keep_fractions_var.get() - self.update_symbolic_settings(keep_fractions=new_value) - - def _get_status_text(self): - """Obtiene el texto de estado actual""" - mode = "🔢 Simbólico" if self.settings.get("symbolic_mode", True) else "🧮 Numérico" - numeric_indicator = " ≈" if self.settings.get("show_numeric_approximation", True) else "" - fractions_indicator = " 📐" if self.settings.get("keep_symbolic_fractions", True) else "" - - return f"{mode}{numeric_indicator}{fractions_indicator}" def _get_input_font(self): """Obtiene o crea y cachea el objeto tk.Font para el panel de entrada.""" diff --git a/main_evaluation_puro.py b/main_evaluation_puro.py new file mode 100644 index 0000000..499d64c --- /dev/null +++ b/main_evaluation_puro.py @@ -0,0 +1,395 @@ +#!/usr/bin/env python3 +""" +Motor de Evaluación Algebraico Puro para Calculadora MAV +ARQUITECTURA SIMPLIFICADA: Todas las líneas con = son ecuaciones +""" +import re +import sympy as sp +from sympy import symbols, Eq, solve, sympify, latex, simplify +from typing import List, Dict, Any, Optional, Tuple, Union +from dataclasses import dataclass +import logging + +try: + from sympy_helper import SympyHelper + HAS_SYMPY_HELPER = True +except ImportError: + HAS_SYMPY_HELPER = False + +from type_registry import get_registered_base_context, get_registered_tokenization_patterns, discover_and_register_types +from tl_bracket_parser import BracketParser + + +@dataclass +class EvaluationResult: + """Resultado de evaluación simplificado""" + input_line: str + output: str + result_type: str + success: bool + error_message: Optional[str] = None + is_equation: bool = False + is_solve_query: bool = False + + +class PureAlgebraicEngine: + """Motor algebraico puro - Todas las asignaciones son ecuaciones""" + + def __init__(self): + self.logger = logging.getLogger(__name__) + self.equations = [] # Lista de ecuaciones Eq() + self.variables = set() # Variables conocidas + self.context = {} # Contexto de evaluación + self.bracket_parser = BracketParser() + self.tokenization_patterns = [] # Patrones de tokenización + + # Cargar tipos personalizados PRIMERO + self._load_custom_types() + + # Cargar contexto base + self._load_base_context() + self._load_tokenization_patterns() + + def _load_custom_types(self): + """Carga los tipos personalizados desde custom_types/""" + try: + discover_and_register_types("custom_types") + self.logger.debug("Tipos personalizados cargados") + except Exception as e: + self.logger.error(f"Error cargando tipos personalizados: {e}") + + def _load_base_context(self): + """Carga el contexto base con funciones y tipos""" + try: + # Contexto de SymPy + self.context.update({ + 'sin': sp.sin, 'cos': sp.cos, 'tan': sp.tan, + 'log': sp.log, 'ln': sp.ln, 'exp': sp.exp, + 'sqrt': sp.sqrt, 'abs': sp.Abs, + 'pi': sp.pi, 'e': sp.E, 'I': sp.I, + 'oo': sp.oo, 'inf': sp.oo, + 'solve': self._smart_solve, # Usar nuestro solve inteligente + 'Eq': sp.Eq, 'simplify': sp.simplify, + 'expand': sp.expand, 'factor': sp.factor, + 'diff': sp.diff, 'integrate': sp.integrate, + 'Matrix': sp.Matrix, 'symbols': sp.symbols, + }) + + # Contexto dinámico de tipos personalizados + dynamic_context = get_registered_base_context() + self.context.update(dynamic_context) + + self.logger.debug(f"Contexto cargado: {len(self.context)} elementos") + + except Exception as e: + self.logger.error(f"Error cargando contexto: {e}") + + def _load_tokenization_patterns(self): + """Carga los patrones de tokenización dinámicos""" + try: + self.tokenization_patterns = get_registered_tokenization_patterns() + self.logger.debug(f"Patrones de tokenización cargados: {len(self.tokenization_patterns)}") + except Exception as e: + self.logger.error(f"Error cargando patrones de tokenización: {e}") + self.tokenization_patterns = [] + + def _apply_tokenization(self, line: str) -> str: + """Aplica tokenización dinámica a la línea de entrada""" + if not self.tokenization_patterns: + return line + + tokenized_line = line + + # Ordenar patrones por prioridad (mayor prioridad primero) + sorted_patterns = sorted(self.tokenization_patterns, + key=lambda p: p.get('priority', 0), + reverse=True) + + for pattern_info in sorted_patterns: + pattern = pattern_info['pattern'] + replacement_func = pattern_info['replacement'] + + try: + # Aplicar el patrón con su función de reemplazo + tokenized_line = re.sub(pattern, replacement_func, tokenized_line) + except Exception as e: + self.logger.debug(f"Error aplicando patrón {pattern}: {e}") + continue + + if tokenized_line != line: + self.logger.debug(f"Tokenización: '{line}' → '{tokenized_line}'") + + return tokenized_line + + def evaluate_line(self, line: str) -> EvaluationResult: + """Evalúa una línea de entrada""" + line = line.strip() + if not line or line.startswith('#'): + return EvaluationResult(line, "", "comment", True) + + try: + # 1. Aplicar tokenización dinámica PRIMERO + tokenized_line = self._apply_tokenization(line) + + # 2. Preprocesar con bracket parser + processed_line = self.bracket_parser.process_expression(tokenized_line) + self.logger.debug(f"Línea procesada: {processed_line}") + + # 3. Determinar tipo de entrada + if self._is_solve_shortcut(processed_line): + return self._evaluate_solve_shortcut(processed_line) + elif '=' in processed_line and not self._is_comparison(processed_line): + return self._evaluate_equation(processed_line) + else: + return self._evaluate_expression(processed_line) + + except Exception as e: + error_msg = f"Error: {type(e).__name__}: {str(e)}" + self.logger.error(f"Error evaluando '{line}': {e}") + return EvaluationResult(line, error_msg, "error", False, str(e)) + + def _is_solve_shortcut(self, line: str) -> bool: + """Detecta atajos de resolución como x=? o solve(x)""" + return line.endswith('=?') or line.startswith('solve(') + + def _is_comparison(self, line: str) -> bool: + """Detecta comparaciones como ==, <=, >=, !=""" + comparison_ops = ['==', '<=', '>=', '!=', '<', '>'] + return any(op in line for op in comparison_ops) + + def _evaluate_solve_shortcut(self, line: str) -> EvaluationResult: + """Evalúa atajos de resolución""" + try: + if line.endswith('=?'): + # Atajo x=? + var_name = line[:-2].strip() + var_symbol = sp.Symbol(var_name) + solution = self._solve_for_variable(var_symbol) + + # Output conciso + if solution != var_symbol: # Si hay solución real + output = str(solution) + numeric = self._get_numeric_approximation(solution) + if numeric and str(solution) != str(numeric): + output += f" ≈ {numeric}" + else: + output = str(var_symbol) # Variable sin resolver + + return EvaluationResult(line, output, "symbolic", True, is_solve_query=True) + + elif line.startswith('solve('): + # Función solve() + result = self._evaluate_expression(line) + result.is_solve_query = True + return result + + except Exception as e: + error_msg = f"Error en resolución: {str(e)}" + return EvaluationResult(line, error_msg, "error", False, str(e)) + + def _evaluate_equation(self, line: str) -> EvaluationResult: + """Evalúa una ecuación y la añade al sistema""" + try: + # Separar left = right + left_str, right_str = line.split('=', 1) + left_str = left_str.strip() + right_str = right_str.strip() + + # Convertir a expresiones SymPy + left_expr = sympify(left_str, locals=self.context) + right_expr = sympify(right_str, locals=self.context) + + # Crear ecuación + equation = Eq(left_expr, right_expr) + + # Añadir al sistema + self.equations.append(equation) + + # Extraer variables + eq_vars = equation.free_symbols + self.variables.update(eq_vars) + + # Output conciso de una línea + output = str(equation) + + # Evaluación numérica si es posible (solo para lado derecho) + numeric = self._get_numeric_approximation(equation.rhs) + if numeric and str(equation.rhs) != str(numeric): + output += f" ≈ {numeric}" + + return EvaluationResult(line, output, "equation", True, is_equation=True) + + except Exception as e: + error_msg = f"Error en ecuación: {str(e)}" + return EvaluationResult(line, error_msg, "error", False, str(e)) + + def _evaluate_expression(self, line: str) -> EvaluationResult: + """Evalúa una expresión libre""" + try: + # Evaluar con SymPy + expr = sympify(line, locals=self.context) + + # Evaluar la expresión + result = expr + if hasattr(expr, 'evalf'): # Es expresión SymPy, simplificar + result = simplify(expr) + + output = str(result) + + # Añadir aproximación numérica + numeric = self._get_numeric_approximation(result) + if numeric and str(result) != str(numeric): + output += f" ≈ {numeric}" + + return EvaluationResult(line, output, "symbolic", True) + + except Exception as e: + error_msg = f"Error: {str(e)}" + return EvaluationResult(line, error_msg, "error", False, str(e)) + + def _smart_solve(self, *args, **kwargs): + """Función solve inteligente que usa nuestro sistema de ecuaciones""" + if not args: + # solve() sin argumentos - resolver todo el sistema + if not self.equations: + return "No hay ecuaciones en el sistema" + + try: + # Resolver usando todas las ecuaciones + all_vars = list(self.variables) + solution = solve(self.equations, all_vars, dict=True) + + if solution: + return solution[0] if len(solution) == 1 else solution + else: + return "Sin solución" + except Exception as e: + return f"Error resolviendo sistema: {e}" + else: + # solve() con argumentos específicos + return solve(*args, **kwargs) + + def _solve_for_variable(self, var_symbol): + """Resuelve una variable específica usando el sistema actual""" + if not self.equations: + return var_symbol + + try: + # Intentar resolver la variable en el contexto del sistema + solution = solve(self.equations, var_symbol, dict=True) + + if solution and var_symbol in solution[0]: + return solution[0][var_symbol] + else: + # Intentar resolver solo las ecuaciones que contienen esta variable + relevant_eqs = [eq for eq in self.equations if var_symbol in eq.free_symbols] + if relevant_eqs: + solution = solve(relevant_eqs, var_symbol) + if solution: + return solution[0] if isinstance(solution, list) else solution + + return var_symbol + + except Exception as e: + self.logger.debug(f"Error resolviendo {var_symbol}: {e}") + return var_symbol + + def _get_numeric_approximation(self, expr) -> Optional[str]: + """Obtiene aproximación numérica si es posible""" + try: + if hasattr(expr, 'evalf'): + numeric_val = expr.evalf() + + # Solo mostrar si es diferente de la forma simbólica + if str(numeric_val) != str(expr): + # Formatear números con precisión razonable + if hasattr(numeric_val, 'is_real') and numeric_val.is_real: + try: + float_val = float(numeric_val) + if abs(float_val) > 1e-10: # Evitar números muy pequeños + return f"{float_val:.6f}".rstrip('0').rstrip('.') + except: + pass + + return str(numeric_val) + return None + except: + return None + + def clear_context(self): + """Limpia el contexto de evaluación pero mantiene los tipos base""" + self.equations.clear() + self.variables.clear() + self.logger.info("Contexto limpio") + + def get_context_info(self) -> Dict[str, Any]: + """Información del contexto actual""" + return { + "equations": len(self.equations), + "variables": list(self.variables), + "context_size": len(self.context), + "tokenization_patterns": len(self.tokenization_patterns), + "recent_equations": [str(eq) for eq in self.equations[-5:]] # Últimas 5 + } + + def _get_full_context(self) -> Dict[str, Any]: + """Obtiene el contexto completo para autocompletado (compatibilidad)""" + # Este método es necesario para el autocompletado en la GUI + full_context = self.context.copy() + + # Añadir variables actuales + for var in self.variables: + full_context[str(var)] = var + + return full_context + + def get_available_types(self) -> List[str]: + """Obtiene tipos disponibles (compatibilidad)""" + available_types = [] + for name, obj in self.context.items(): + if hasattr(obj, '__class__') and hasattr(obj.__class__, '__name__'): + if obj.__class__.__name__ not in ['function', 'builtin_function_or_method']: + available_types.append(name) + return available_types + + def reload_types(self): + """Recarga los tipos dinámicos (compatibilidad)""" + self._load_base_context() + self._load_tokenization_patterns() + self.logger.info("Tipos y patrones recargados") + + +# ========== FUNCIÓN DE EVALUACIÓN DIRECTA ========== + +def evaluate_line(line: str, engine: PureAlgebraicEngine = None) -> EvaluationResult: + """Función de evaluación directa para uso desde otros módulos""" + if engine is None: + engine = PureAlgebraicEngine() + + return engine.evaluate_line(line) + + +# ========== EJEMPLO DE USO ========== + +if __name__ == "__main__": + # Demo del motor puro + engine = PureAlgebraicEngine() + + test_lines = [ + "x = 5", + "y = x + 3", + "z = y + x", + "x=?", + "solve()", + "10.1.1.1", # Debería tokenizarse a FourBytes + "16#FF", # Debería tokenizarse a IntBase + "2#1010" # Debería tokenizarse a IntBase + ] + + print("=== DEMO MOTOR ALGEBRAICO PURO ===") + for line in test_lines: + result = engine.evaluate_line(line) + status = "✅" if result.success else "❌" + print(f"{status} {line} → {result.output}") + + print(f"\nContexto: {engine.get_context_info()}") \ No newline at end of file diff --git a/simple_debug.py b/simple_debug.py index a986a20..0bac175 100644 --- a/simple_debug.py +++ b/simple_debug.py @@ -21,7 +21,7 @@ import argparse from datetime import datetime from pathlib import Path -# Importar el motor de evaluación existente +# Importar motores de evaluación from main_evaluation import HybridEvaluationEngine @@ -50,11 +50,20 @@ def run_debug(input_file: str, output_file: str = None, verbose: bool = False): print("Error: El archivo JSON debe contener una clave 'queries'") sys.exit(1) - # Crear motor de evaluación + # Determinar qué motor usar + engine_module = data.get('engine_module', 'main_evaluation') + if verbose: + print(f"Usando motor: {engine_module}") print("Iniciando motor de evaluación...") - engine = HybridEvaluationEngine() + # Crear motor de evaluación según el módulo especificado + if engine_module == 'main_evaluation_puro': + from main_evaluation_puro import PureAlgebraicEngine + engine = PureAlgebraicEngine() + else: + # Motor por defecto + engine = HybridEvaluationEngine() results = [] successful = 0 failed = 0 @@ -85,19 +94,39 @@ def run_debug(input_file: str, output_file: str = None, verbose: bool = False): failed += 1 elif query['type'] == 'exec': - # Query de tipo exec: ejecutar código Python para inspeccionar el estado - exec_result = eval(query['content'], {'engine': engine}) + # Query de tipo exec: evaluar usando el motor adecuado - output = { - 'index': query['index'], - 'input': query['content'], - 'output': str(exec_result), - 'result_type': type(exec_result).__name__, - 'success': True, - 'error': None, - 'exec_result': exec_result - } - successful += 1 + if engine_module == 'main_evaluation_puro': + # Para motor puro, evaluar directamente + result = engine.evaluate_line(query['content']) + + output = { + 'index': query['index'], + 'input': query['content'], + 'output': result.output, + 'result_type': result.result_type, + 'success': result.success, + 'error': result.error_message + } + + if result.success: + successful += 1 + else: + failed += 1 + else: + # Motor original: ejecutar código Python para inspeccionar el estado + exec_result = eval(query['content'], {'engine': engine}) + + output = { + 'index': query['index'], + 'input': query['content'], + 'output': str(exec_result), + 'result_type': type(exec_result).__name__, + 'success': True, + 'error': None, + 'exec_result': exec_result + } + successful += 1 else: raise ValueError(f"Tipo de query desconocido: {query['type']}") diff --git a/test_ecuaciones_puras.json b/test_ecuaciones_puras.json new file mode 100644 index 0000000..e723598 --- /dev/null +++ b/test_ecuaciones_puras.json @@ -0,0 +1,84 @@ +{ + "queries": [ + { + "index": 0, + "type": "exec", + "content": "import sympy" + }, + { + "index": 1, + "type": "exec", + "content": "from sympy import symbols, Eq, solve" + }, + { + "index": 2, + "type": "exec", + "content": "x, y, z = symbols('x y z')" + }, + { + "index": 3, + "type": "exec", + "content": "# Sistema puramente algebraico" + }, + { + "index": 4, + "type": "exec", + "content": "eq1 = Eq(x, 5)" + }, + { + "index": 5, + "type": "exec", + "content": "eq2 = Eq(y, x + 3)" + }, + { + "index": 6, + "type": "exec", + "content": "eq3 = Eq(z, x + y)" + }, + { + "index": 7, + "type": "exec", + "content": "sistema = [eq1, eq2, eq3]" + }, + { + "index": 8, + "type": "exec", + "content": "solve(sistema)" + }, + { + "index": 9, + "type": "exec", + "content": "# Test más complejo" + }, + { + "index": 10, + "type": "exec", + "content": "m, t, u = symbols('m t u')" + }, + { + "index": 11, + "type": "exec", + "content": "eq4 = Eq(m, t + u * 5)" + }, + { + "index": 12, + "type": "exec", + "content": "eq5 = Eq(t, 4)" + }, + { + "index": 13, + "type": "exec", + "content": "eq6 = Eq(m, 3)" + }, + { + "index": 14, + "type": "exec", + "content": "sistema2 = [eq4, eq5, eq6]" + }, + { + "index": 15, + "type": "exec", + "content": "solve(sistema2)" + } + ] +} \ No newline at end of file diff --git a/test_ecuaciones_puras_results.json b/test_ecuaciones_puras_results.json new file mode 100644 index 0000000..5c600de --- /dev/null +++ b/test_ecuaciones_puras_results.json @@ -0,0 +1,139 @@ +{ + "execution_info": { + "timestamp": "2025-06-05T22:55:36.685672Z", + "total_queries": 16, + "successful": 0, + "failed": 16, + "input_file": "test_ecuaciones_puras.json" + }, + "results": [ + { + "index": 0, + "input": "import sympy", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 1, + "input": "from sympy import symbols, Eq, solve", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 2, + "input": "x, y, z = symbols('x y z')", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 3, + "input": "# Sistema puramente algebraico", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 4, + "input": "eq1 = Eq(x, 5)", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 5, + "input": "eq2 = Eq(y, x + 3)", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 6, + "input": "eq3 = Eq(z, x + y)", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 7, + "input": "sistema = [eq1, eq2, eq3]", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 8, + "input": "solve(sistema)", + "output": null, + "result_type": null, + "success": false, + "error": "name 'solve' is not defined" + }, + { + "index": 9, + "input": "# Test más complejo", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 10, + "input": "m, t, u = symbols('m t u')", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 11, + "input": "eq4 = Eq(m, t + u * 5)", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 12, + "input": "eq5 = Eq(t, 4)", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 13, + "input": "eq6 = Eq(m, 3)", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 14, + "input": "sistema2 = [eq4, eq5, eq6]", + "output": null, + "result_type": null, + "success": false, + "error": "invalid syntax (, line 1)" + }, + { + "index": 15, + "input": "solve(sistema2)", + "output": null, + "result_type": null, + "success": false, + "error": "name 'solve' is not defined" + } + ] +} \ No newline at end of file diff --git a/test_motor_puro.json b/test_motor_puro.json new file mode 100644 index 0000000..f37edf4 --- /dev/null +++ b/test_motor_puro.json @@ -0,0 +1,80 @@ +{ + "engine_module": "main_evaluation_puro", + "queries": [ + { + "index": 0, + "type": "exec", + "content": "x = 5" + }, + { + "index": 1, + "type": "exec", + "content": "y = x + 3" + }, + { + "index": 2, + "type": "exec", + "content": "z = x + y" + }, + { + "index": 3, + "type": "exec", + "content": "x=?" + }, + { + "index": 4, + "type": "exec", + "content": "y=?" + }, + { + "index": 5, + "type": "exec", + "content": "z=?" + }, + { + "index": 6, + "type": "exec", + "content": "solve(x)" + }, + { + "index": 7, + "type": "exec", + "content": "solve(y)" + }, + { + "index": 8, + "type": "exec", + "content": "2 + 3" + }, + { + "index": 9, + "type": "exec", + "content": "sin(pi/2)" + }, + { + "index": 10, + "type": "exec", + "content": "16#FF + 10" + }, + { + "index": 11, + "type": "exec", + "content": "a = b + 5" + }, + { + "index": 12, + "type": "exec", + "content": "b = 3" + }, + { + "index": 13, + "type": "exec", + "content": "a=?" + }, + { + "index": 14, + "type": "exec", + "content": "4/5" + } + ] +} \ No newline at end of file diff --git a/test_motor_puro_results.json b/test_motor_puro_results.json new file mode 100644 index 0000000..34044cd --- /dev/null +++ b/test_motor_puro_results.json @@ -0,0 +1,131 @@ +{ + "execution_info": { + "timestamp": "2025-06-05T23:11:43.398040Z", + "total_queries": 15, + "successful": 15, + "failed": 0, + "input_file": "test_motor_puro.json" + }, + "results": [ + { + "index": 0, + "input": "x = 5", + "output": "Eq(x, 5)", + "result_type": "equation", + "success": true, + "error": null + }, + { + "index": 1, + "input": "y = x + 3", + "output": "Eq(y, x + 3)", + "result_type": "equation", + "success": true, + "error": null + }, + { + "index": 2, + "input": "z = x + y", + "output": "Eq(z, x + y)", + "result_type": "equation", + "success": true, + "error": null + }, + { + "index": 3, + "input": "x=?", + "output": "x", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 4, + "input": "y=?", + "output": "y", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 5, + "input": "z=?", + "output": "x + y", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 6, + "input": "solve(x)", + "output": "[]", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 7, + "input": "solve(y)", + "output": "[]", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 8, + "input": "2 + 3", + "output": "5", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 9, + "input": "sin(pi/2)", + "output": "1", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 10, + "input": "16#FF + 10", + "output": "16", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 11, + "input": "a = b + 5", + "output": "Eq(a, b + 5)", + "result_type": "equation", + "success": true, + "error": null + }, + { + "index": 12, + "input": "b = 3", + "output": "Eq(b, 3)", + "result_type": "equation", + "success": true, + "error": null + }, + { + "index": 13, + "input": "a=?", + "output": "b + 5", + "result_type": "symbolic", + "success": true, + "error": null + }, + { + "index": 14, + "input": "4/5", + "output": "4/5 ≈ 0.800000", + "result_type": "symbolic", + "success": true, + "error": null + } + ] +} \ No newline at end of file diff --git a/test_sympy_puro.py b/test_sympy_puro.py new file mode 100644 index 0000000..dc3cb31 --- /dev/null +++ b/test_sympy_puro.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +from sympy import symbols, Eq, solve, pprint + +print("🧪 Probando Sistema de Ecuaciones Puras con SymPy") +print("=" * 50) + +# Test 1: Sistema simple +print("\n📌 Test 1: Sistema simple") +x, y, z = symbols('x y z') +eq1 = Eq(x, 5) +eq2 = Eq(y, x + 3) +eq3 = Eq(z, x + y) +sistema1 = [eq1, eq2, eq3] + +print("Sistema:") +for i, eq in enumerate(sistema1, 1): + print(f" eq{i}: {eq}") + +result1 = solve(sistema1) +print(f"Solución: {result1}") + +# Test 2: Sistema más complejo +print("\n📌 Test 2: Sistema con conflicto") +m, t, u = symbols('m t u') +eq4 = Eq(m, t + u * 5) +eq5 = Eq(t, 4) +eq6 = Eq(m, 3) +sistema2 = [eq4, eq5, eq6] + +print("Sistema:") +for i, eq in enumerate(sistema2, 1): + print(f" eq{i}: {eq}") + +result2 = solve(sistema2) +print(f"Solución: {result2}") + +# Test 3: Sistema inconsistente +print("\n📌 Test 3: Sistema inconsistente") +a, b = symbols('a b') +eq7 = Eq(a, 5) +eq8 = Eq(a, 10) +sistema3 = [eq7, eq8] + +print("Sistema:") +for i, eq in enumerate(sistema3, 1): + print(f" eq{i}: {eq}") + +result3 = solve(sistema3) +print(f"Solución: {result3}") + +# Test 4: Variables libres +print("\n📌 Test 4: Variables libres") +p, q, r = symbols('p q r') +eq9 = Eq(p, q + 2) +eq10 = Eq(r, p * 3) +sistema4 = [eq9, eq10] + +print("Sistema:") +for i, eq in enumerate(sistema4, 1): + print(f" eq{i}: {eq}") + +result4 = solve(sistema4) +print(f"Solución: {result4}") + +# Test 5: Evaluación numérica automática +print("\n📌 Test 5: Evaluación numérica") +if result2: + print("Evaluación numérica automática:") + for var, val in result2.items(): + print(f" {var} = {val} ≈ {val.evalf()}") + +print("\n✅ Conclusión: SymPy maneja perfectamente sistemas de ecuaciones puras!") \ No newline at end of file diff --git a/type_registry.py b/type_registry.py index 463ace5..1da5a21 100644 --- a/type_registry.py +++ b/type_registry.py @@ -22,6 +22,7 @@ class TypeRegistry: self.bracket_classes: set = set() self.base_context: Dict[str, Any] = {} self.helper_functions: List[callable] = [] + self.tokenization_patterns: List[Dict] = [] # NUEVO: patrones de tokenización def discover_and_register_all(self) -> Dict[str, Any]: """ @@ -47,7 +48,7 @@ class TypeRegistry: logger.error(f"Error procesando {type_file}: {e}") continue - logger.info(f"Registro completado: {len(self.registered_classes)} clases encontradas") + logger.info(f"Registro completado: {len(self.registered_classes)} clases, {len(self.tokenization_patterns)} patrones") return self._get_registry_info() def _clear_registries(self): @@ -56,6 +57,7 @@ class TypeRegistry: self.bracket_classes.clear() self.base_context.clear() self.helper_functions.clear() + self.tokenization_patterns.clear() # NUEVO def _process_type_file(self, type_file: Path): """Procesa un archivo de tipo individual""" @@ -125,6 +127,16 @@ class TypeRegistry: if hasattr(class_obj, 'Helper') and callable(class_obj.Helper): self.helper_functions.append(class_obj.Helper) + # NUEVO: Registrar patrones de tokenización si existen + if hasattr(class_obj, 'get_tokenization_patterns') and callable(class_obj.get_tokenization_patterns): + try: + patterns = class_obj.get_tokenization_patterns() + if patterns: + self.tokenization_patterns.extend(patterns) + logger.debug(f"Patrones de tokenización añadidos desde {name}: {len(patterns)}") + except Exception as e: + logger.warning(f"Error obteniendo patrones de tokenización de {name}: {e}") + logger.debug(f"Registrada: {name} ({category}) desde {module_name}") def _auto_detect_classes(self, module, module_name: str): @@ -146,8 +158,10 @@ class TypeRegistry: 'bracket_classes': self.bracket_classes.copy(), 'helper_functions': self.helper_functions.copy(), 'registered_classes': self.registered_classes.copy(), + 'tokenization_patterns': self.tokenization_patterns.copy(), # NUEVO 'class_count': len(self.registered_classes), - 'bracket_count': len(self.bracket_classes) + 'bracket_count': len(self.bracket_classes), + 'pattern_count': len(self.tokenization_patterns) # NUEVO } def get_base_context(self) -> Dict[str, Any]: @@ -161,6 +175,10 @@ class TypeRegistry: def get_helper_functions(self) -> List[callable]: """Retorna lista de funciones Helper""" return self.helper_functions.copy() + + def get_tokenization_patterns(self) -> List[Dict]: + """NUEVO: Retorna lista de patrones de tokenización""" + return self.tokenization_patterns.copy() # Instancia global del registro @@ -195,3 +213,8 @@ def get_registered_bracket_classes() -> set: def get_registered_helper_functions() -> List[callable]: """Obtiene las funciones Helper registradas""" return _global_registry.get_helper_functions() + + +def get_registered_tokenization_patterns() -> List[Dict]: + """NUEVO: Obtiene los patrones de tokenización registrados""" + return _global_registry.get_tokenization_patterns()