Refactorización del motor de evaluación para utilizar un sistema algebraico puro. Se actualizan las configuraciones de la interfaz y se simplifican las funciones de evaluación. Se añaden patrones de tokenización en el registro de tipos y se mejora la gestión de errores en el motor. Se eliminan configuraciones obsoletas y se optimiza el menú de herramientas.

This commit is contained in:
Miguel 2025-06-06 07:00:00 +02:00
parent ece028e837
commit 7d2033c60e
10 changed files with 1051 additions and 242 deletions

View File

@ -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
IP4(10.1.1.2)

View File

@ -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."""

395
main_evaluation_puro.py Normal file
View File

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

View File

@ -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']}")

View File

@ -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)"
}
]
}

View File

@ -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 (<string>, line 1)"
},
{
"index": 1,
"input": "from sympy import symbols, Eq, solve",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 2,
"input": "x, y, z = symbols('x y z')",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 3,
"input": "# Sistema puramente algebraico",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 4,
"input": "eq1 = Eq(x, 5)",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 5,
"input": "eq2 = Eq(y, x + 3)",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 6,
"input": "eq3 = Eq(z, x + y)",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 7,
"input": "sistema = [eq1, eq2, eq3]",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, 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 (<string>, line 1)"
},
{
"index": 10,
"input": "m, t, u = symbols('m t u')",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 11,
"input": "eq4 = Eq(m, t + u * 5)",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 12,
"input": "eq5 = Eq(t, 4)",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 13,
"input": "eq6 = Eq(m, 3)",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 14,
"input": "sistema2 = [eq4, eq5, eq6]",
"output": null,
"result_type": null,
"success": false,
"error": "invalid syntax (<string>, line 1)"
},
{
"index": 15,
"input": "solve(sistema2)",
"output": null,
"result_type": null,
"success": false,
"error": "name 'solve' is not defined"
}
]
}

80
test_motor_puro.json Normal file
View File

@ -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"
}
]
}

View File

@ -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
}
]
}

72
test_sympy_puro.py Normal file
View File

@ -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!")

View File

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