Eliminación de archivos innecesarios (icon.png y MaVCalcv2.lnk). Actualización del historial de cálculos con nuevos formatos de salario y tarifas. Refactorización del motor algebraico para mejorar la salida de resultados y la gestión de ecuaciones, implementando un nuevo formato de salida que incluye aproximaciones numéricas. Mejora en la generación de contenido LaTeX para ecuaciones y asignaciones.

This commit is contained in:
Miguel 2025-06-12 10:22:02 +02:00
parent e51e6bf1ae
commit 639081c0d0
6 changed files with 119 additions and 58 deletions

View File

@ -1,12 +1,10 @@
ex = (t * 8) / w
salario=horas*tarifa
salario=8000
tarifa=36
horas=?
var1 = 2
var2 = 4
vatt1 = 4
vatvatt1()

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

View File

@ -361,10 +361,8 @@ class PureAlgebraicEngine:
var_symbol = sp.Symbol(var_content)
solution_result_obj = self._smart_solve(var_symbol)
output = str(solution_result_obj)
numeric = self._get_numeric_approximation(solution_result_obj)
if numeric and str(solution_result_obj) != str(numeric):
output += f"{numeric}"
# Usar nuevo formato de salida
output = self._format_output_with_approximation(solution_result_obj)
current_algebraic_type = type(solution_result_obj).__name__
# Los resultados de solve() generalmente no son plots
@ -383,22 +381,16 @@ class PureAlgebraicEngine:
# Para casos más complejos de solve() o si el regex no coincide,
# usar _evaluate_expression que ya maneja 'last' y los tipos.
# _evaluate_expression se encargará de self.last_result_object.
result = self._evaluate_expression(line) # Llama a la versión ya modificada
result.is_solve_query = True # Mantener esta bandera
result = self._evaluate_expression(line)
return result
# Si no es solve(), podría ser otro tipo de atajo (si se añaden más tarde)
# Por ahora, si no empieza con solve(, lo tratamos como una expresión normal.
return self._evaluate_expression(line)
except Exception as e:
error_msg = f"Error en atajo de resolución '{line}': {type(e).__name__}: {str(e)}"
error_msg = f"Error en solve(): {type(e).__name__}: {str(e)}"
self.logger.error(error_msg)
# No actualizar last_result_object en caso de error
return EvaluationResult(line, error_msg, "error", False, str(e), is_solve_query=True)
def _evaluate_assignment(self, line: str) -> EvaluationResult:
"""Evalúa una asignación CON FUNCIONALIDAD DUAL: asignación al contexto + ecuación a sympy"""
"""Evalúa una asignación y gestiona 'last'"""
try:
var_name, expression_str = line.split('=', 1)
var_name = var_name.strip()
@ -439,7 +431,8 @@ class PureAlgebraicEngine:
# Si falla como ecuación, continuar solo con la asignación
self.logger.warning(f"No se pudo agregar '{line}' como ecuación: {eq_error}")
output = f"{var_name} = {result_obj}"
# Usar nuevo formato de salida consistente
output = self._format_output_with_approximation(Eq(sp.Symbol(var_name), result_obj))
# Devolver el resultado de la asignación
return EvaluationResult(
@ -489,7 +482,9 @@ class PureAlgebraicEngine:
for free_symbol in equation_obj.free_symbols:
self.variables.add(str(free_symbol))
output = str(equation_obj)
# Usar nuevo formato de salida
output = self._format_equation_output(equation_obj)
# No actualizar last_result_object para ecuaciones
return EvaluationResult(
input_line=line,
@ -518,20 +513,12 @@ class PureAlgebraicEngine:
if isinstance(result_obj, PlotResult) and not result_obj.original_expression:
result_obj.original_expression = line
output = str(result_obj)
# Usar nuevo formato de salida que maneja Eq() automáticamente
output = self._format_output_with_approximation(result_obj)
result_type_str = "symbolic" # Tipo por defecto
current_algebraic_type = type(result_obj).__name__
# Manejo especial para tipos de sympy que pueden necesitar aproximación numérica
if hasattr(result_obj, 'evalf') and not isinstance(result_obj, (sp.Matrix, sp.Basic, sp.Expr)):
# Evitar evalf en matrices directamente o en tipos ya específicos como Integer, Float
pass
numeric_approximation = self._get_numeric_approximation(result_obj)
if numeric_approximation and output != numeric_approximation:
output += f"{numeric_approximation}"
# Considerar si esto cambia el result_type a "numeric_approx"
# Determinar si es un objeto de plotting (para no asignarlo a 'last')
is_plot_object = False
if isinstance(result_obj, PlotResult):
@ -825,10 +812,21 @@ class PureAlgebraicEngine:
try:
float_val = float(numeric_val)
if abs(float_val) > 1e-10:
return f"{float_val:.6f}".rstrip('0').rstrip('.')
# Formatear número eliminando ceros innecesarios
if float_val == int(float_val):
# Es un entero, mostrarlo como tal
return str(int(float_val))
else:
# Es decimal, formatear con precisión limitada
formatted = f"{float_val:.10f}".rstrip('0').rstrip('.')
# Si queda muy largo, usar notación más compacta
if len(formatted) > 12:
formatted = f"{float_val:.6g}"
return formatted
except:
pass
# Para otros tipos (complejos, expresiones, etc.)
return str(numeric_val)
return None
except:
@ -872,6 +870,63 @@ class PureAlgebraicEngine:
self._load_tokenization_patterns()
self.logger.info("Tipos y patrones recargados")
def _format_equation_output(self, result_obj) -> str:
"""Convierte Eq(var, valor) a formato legible var = valor"""
try:
if hasattr(result_obj, 'func') and result_obj.func.__name__ == 'Equality':
# Es una ecuación Eq(lhs, rhs)
lhs = result_obj.lhs
rhs = result_obj.rhs
return f"{lhs} = {rhs}"
else:
# No es una ecuación, devolver string normal
return str(result_obj)
except Exception:
return str(result_obj)
def _format_output_with_approximation(self, result_obj) -> str:
"""Formatea salida con aproximación numérica, convirtiendo Eq() a formato legible"""
main_output = self._format_equation_output(result_obj)
# Obtener aproximación numérica
numeric_approximation = self._get_numeric_approximation(result_obj)
if numeric_approximation and main_output != numeric_approximation:
# Para ecuaciones, formatear también la aproximación
if hasattr(result_obj, 'func') and result_obj.func.__name__ == 'Equality':
# Crear aproximación en formato var = valor_aproximado
try:
lhs = result_obj.lhs
rhs_numeric = result_obj.rhs.evalf() if hasattr(result_obj.rhs, 'evalf') else result_obj.rhs
# Formatear la parte numérica con el mismo sistema
rhs_numeric_str = self._get_numeric_approximation(result_obj.rhs)
if rhs_numeric_str is None:
rhs_numeric_str = str(rhs_numeric)
numeric_output = f"{lhs} = {rhs_numeric_str}"
# Comparar si son realmente diferentes
# Extraer solo las partes de valor para comparar
main_value = str(result_obj.rhs)
if main_value != rhs_numeric_str:
return f"{main_output}{numeric_output}"
else:
# Son iguales, no mostrar aproximación
return main_output
except:
return f"{main_output}{numeric_approximation}"
else:
# Para expresiones no-ecuación, comparar directamente
if str(result_obj) != numeric_approximation:
return f"{main_output}{numeric_approximation}"
else:
# Son iguales, no mostrar aproximación
return main_output
return main_output
# ========== FUNCIÓN DE EVALUACIÓN DIRECTA ==========

View File

@ -417,17 +417,22 @@ class LatexProcessor:
main_content = ""
if result.actual_result_object is not None:
try:
# Intentar generar LaTeX de SymPy para el objeto matemático
main_content = sympy.latex(result.actual_result_object)
# Verificar si es una ecuación Eq()
if hasattr(result.actual_result_object, 'func') and result.actual_result_object.func.__name__ == 'Equality':
# Es una ecuación, convertir a formato var = valor
lhs = result.actual_result_object.lhs
rhs = result.actual_result_object.rhs
lhs_latex = sympy.latex(lhs)
rhs_latex = sympy.latex(rhs)
main_content = f"{lhs_latex} = {rhs_latex}"
else:
# Intentar generar LaTeX de SymPy para el objeto matemático
main_content = sympy.latex(result.actual_result_object)
# Para asignaciones, necesitamos agregar el lado izquierdo
if result.is_assignment and result.input_line:
# Extraer la variable del lado izquierdo
if '=' in result.input_line:
left_side = result.input_line.split('=')[0].strip()
# Limpiar posibles símbolos LaTeX del lado izquierdo
left_side = left_side.replace('$$', '').strip()
main_content = f"{left_side} = {main_content}"
# Para asignaciones, el resultado ya viene formateado
if result.is_assignment and not main_content:
# Extraer del output ya formateado
main_content = result.output.split("")[0] if "" in result.output else result.output
except Exception:
# Si falla el LaTeX de SymPy, usar el output textual
@ -446,19 +451,8 @@ class LatexProcessor:
if ";" in approx_value:
approx_value = approx_value.split(";")[0].strip()
# Intentar convertir la aproximación a LaTeX si es una ecuación
try:
if "Eq(" in approx_value:
# Es una ecuación, intentar parserarla para LaTeX
approx_obj = eval(approx_value, {'Eq': sympy.Eq, 'sqrt': sympy.sqrt})
approx_latex = sympy.latex(approx_obj)
latex_parts.append(f"\\approx {approx_latex}")
else:
# Es un valor numérico simple
latex_parts.append(f"\\approx {approx_value}")
except:
# Si falla, usar la aproximación como texto
latex_parts.append(f"\\approx {approx_value}")
# La aproximación ya viene en formato var = valor, mantenerla así
latex_parts.append(f"\\approx {approx_value}")
# PARTE 3: Indicador de tipo (si está en el output)
if result.output and "[" in result.output and "]" in result.output:

View File

@ -3,6 +3,7 @@ Calculadora MAV CAS Híbrida - Aplicación principal PySide6 (Refactorizada)
VERSIÓN MODULAR: Código organizado en módulos especializados
"""
import sys
import os
import logging
from typing import Optional, Dict, Any, List
@ -11,7 +12,7 @@ from PySide6.QtWidgets import (
QSplitter, QStatusBar
)
from PySide6.QtCore import Qt, QTimer
from PySide6.QtGui import QKeySequence, QShortcut
from PySide6.QtGui import QKeySequence, QShortcut, QPixmap, QIcon
# Importar componentes del CAS híbrido
from .evaluation import PureAlgebraicEngine
@ -41,6 +42,9 @@ class HybridCalculatorPySide6(QMainWindow):
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
self.logger = logging.getLogger(__name__)
# Configurar icono de la aplicación
self._setup_application_icon()
# Motor y managers
self.engine = PureAlgebraicEngine()
self.interactive_manager = None
@ -305,6 +309,16 @@ class HybridCalculatorPySide6(QMainWindow):
self.settings_manager.save_all()
event.accept()
def _setup_application_icon(self):
"""Configura el icono de la aplicación"""
# Buscar el icono en el directorio raíz del proyecto
icon_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), ".res", "icon.png")
if os.path.exists(icon_path):
self.setWindowIcon(QIcon(icon_path))
self.logger.debug(f"✅ Icono cargado desde: {icon_path}")
else:
self.logger.warning(f"⚠️ Icono no encontrado en: {icon_path}")
def main():
"""Función principal para ejecutar la aplicación"""