Agregado de Date

This commit is contained in:
Miguel 2025-06-28 15:42:05 +02:00
parent 6ebc81d0b9
commit a85434d492
4 changed files with 550 additions and 6 deletions

View File

@ -1,5 +1,41 @@
valor = 500
k = valor * symbols('Hz') / symbols('mmS')
# Italiano
faltan_italiano = 51
hechas_italiano = 21
total_italiano = faltan_italiano+hechas_italiano
valor_mmS = 100 * symbols('mmS')
speed = k * valor_mmS
# Matematica
faltan_mate = 59
hechas_mate = 6
total_mate = faltan_mate+hechas_mate
# Matematica Libro
faltan_mate2 = 25
hechas_mate2 = 0
total__mate2 = hechas_mate2+faltan_mate2
# Ingles
faltan_english = 48
hechas_english = 3
total__english = hechas_english + faltan_english
# Resumen
total = total_italiano+total_mate+total__mate2+total__english
hecho = hechas_italiano + hechas_mate + hechas_mate2 + hechas_english
faltan = total - hecho
# Estado actual de dias
dias_pasados = 28-6
dias_faltan = 31+31+14+2
# Promedio actual
hecho/dias_pasados
# Minimo para terminar
hacer_por_dia = faltan/dias_faltan
# Minimo para terminar
promedio_ideal = total/(dias_pasados+dias_faltan)
Date('23/4/25')=Date('23/2/25') + x
x=?

View File

@ -407,6 +407,13 @@ class PureAlgebraicEngine:
eval_context = self._get_complete_context()
result_obj = sympify(expression_str, locals=eval_context)
# NUEVA FUNCIONALIDAD: Aplicar simplificación simbólica automática en asignaciones
if hasattr(result_obj, 'free_symbols') and result_obj.free_symbols and self.equations:
simplified_obj = self._apply_symbolic_simplification(result_obj)
if simplified_obj != result_obj:
result_obj = simplified_obj
self.logger.debug(f"Simplificación simbólica aplicada en asignación: {expression_str}{result_obj}")
# 1. ASIGNACIÓN AL CONTEXTO (para evaluación directa)
self.symbol_table[var_name] = result_obj
self.variables.add(var_name)
@ -509,6 +516,13 @@ class PureAlgebraicEngine:
eval_context = self._get_complete_context()
result_obj = sympify(line, locals=eval_context)
# NUEVA FUNCIONALIDAD: Aplicar simplificación simbólica automática
if hasattr(result_obj, 'free_symbols') and result_obj.free_symbols and self.equations:
simplified_obj = self._apply_symbolic_simplification(result_obj)
if simplified_obj != result_obj:
result_obj = simplified_obj
self.logger.debug(f"Simplificación simbólica aplicada: {line}{result_obj}")
# Si es un PlotResult, asegurar que tenga la línea original
if isinstance(result_obj, PlotResult) and not result_obj.original_expression:
result_obj.original_expression = line
@ -600,15 +614,33 @@ class PureAlgebraicEngine:
# Verificar que el resultado no sea problemático
if final_value != var_symbol and not isinstance(final_value, (sp.logic.boolalg.BooleanTrue, sp.logic.boolalg.BooleanFalse)):
# NUEVA FUNCIONALIDAD: Aplicar simplificación simbólica al resultado final
if hasattr(final_value, 'free_symbols') and final_value.free_symbols and self.equations:
simplified_final = self._apply_symbolic_simplification(final_value)
if simplified_final != final_value:
self.logger.debug(f"Simplificación aplicada en resultado final: {final_value}{simplified_final}")
final_value = simplified_final
return Eq(var_symbol, final_value)
else:
# Si hay problema con la resolución, devolver simbólico
# Aplicar simplificación también al valor simbólico
if hasattr(solution_value, 'free_symbols') and solution_value.free_symbols and self.equations:
simplified_solution = self._apply_symbolic_simplification(solution_value)
if simplified_solution != solution_value:
self.logger.debug(f"Simplificación aplicada en solución simbólica: {solution_value}{simplified_solution}")
solution_value = simplified_solution
return Eq(var_symbol, solution_value)
else:
# Hay variables con valores simbólicos, devolver ecuación simbólica
return Eq(var_symbol, solution_value)
else:
# No hay variables sin resolver, es ya un valor final
# NUEVA FUNCIONALIDAD: Aplicar simplificación simbólica al valor final
if hasattr(solution_value, 'free_symbols') and solution_value.free_symbols and self.equations:
simplified_solution = self._apply_symbolic_simplification(solution_value)
if simplified_solution != solution_value:
self.logger.debug(f"Simplificación aplicada en valor final: {solution_value}{simplified_solution}")
solution_value = simplified_solution
return Eq(var_symbol, solution_value)
else:
# Si no hay solución en las ecuaciones, verificar en symbol_table
@ -617,6 +649,12 @@ class PureAlgebraicEngine:
value = self.symbol_table[var_name]
# Si el valor en symbol_table es diferente de la variable, devolverlo
if value != var_symbol:
# NUEVA FUNCIONALIDAD: Aplicar simplificación simbólica también aquí
if hasattr(value, 'free_symbols') and value.free_symbols and self.equations:
simplified_value = self._apply_symbolic_simplification(value)
if simplified_value != value:
self.logger.debug(f"Simplificación aplicada en solve: {value}{simplified_value}")
value = simplified_value
return Eq(var_symbol, value)
# Si no hay información, devolver ecuación con la variable sin resolver
@ -715,6 +753,85 @@ class PureAlgebraicEngine:
self.logger.debug(f"Error resolviendo {var_symbol}: {e}")
return var_symbol
def _apply_symbolic_simplification(self, expression):
"""
Aplica simplificación simbólica automática usando ecuaciones definidas
Esta función sustituye símbolos por sus definiciones de ecuaciones cuando es posible,
permitiendo simplificaciones como: mS = mmS/1000, entonces 50000*Hz*mS/mmS 50*Hz
"""
try:
current_expr = expression
max_iterations = 5 # Evitar bucles infinitos
for iteration in range(max_iterations):
substituted = current_expr
substitution_made = False
# Obtener símbolos libres de la expresión actual
free_symbols = substituted.free_symbols
# Para cada símbolo en la expresión, buscar si hay una ecuación que lo defina
for symbol in free_symbols:
symbol_definition = self._find_symbol_definition(symbol)
if symbol_definition is not None and symbol_definition != symbol:
# Aplicar la sustitución
new_expr = substituted.subs(symbol, symbol_definition)
# Simplificar después de la sustitución
simplified = sp.simplify(new_expr)
if simplified != substituted:
substituted = simplified
substitution_made = True
self.logger.debug(f"Símbolo {symbol} sustituido por {symbol_definition}")
# Si no se hicieron sustituciones, terminar
if not substitution_made:
break
current_expr = substituted
return current_expr
except Exception as e:
self.logger.debug(f"Error en simplificación simbólica: {e}")
return expression
def _find_symbol_definition(self, symbol):
"""
Busca la definición de un símbolo en las ecuaciones del sistema
Returns:
La expresión que define al símbolo, o None si no se encuentra
"""
try:
# Buscar en ecuaciones donde el símbolo aparece solo en el lado izquierdo
for eq in self.equations:
if hasattr(eq, 'lhs') and hasattr(eq, 'rhs'):
# Caso 1: símbolo = expresión
if eq.lhs == symbol:
return eq.rhs
# Caso 2: expresión = símbolo
elif eq.rhs == symbol:
return eq.lhs
# Buscar en symbol_table como respaldo
symbol_name = str(symbol)
if symbol_name in self.symbol_table:
value = self.symbol_table[symbol_name]
# Solo usar si el valor es diferente del símbolo mismo
if value != symbol:
return value
return None
except Exception as e:
self.logger.debug(f"Error buscando definición de {symbol}: {e}")
return None
def _resolve_iteratively(self, expression, max_iterations=10):
"""Resuelve una expresión iterativamente sustituyendo valores conocidos"""
try:

View File

@ -315,10 +315,21 @@ class HybridCalculatorPySide6(QMainWindow):
self.latex_button.setChecked(True)
self.latex_panel_visible = True
# Configurar tamaños
# Configurar tamaños con límites
latex_width = self.settings.get("latex_panel_width", 300)
total_width = self.width()
# CORRECCIÓN: Aplicar límites para evitar crecimiento descontrolado
min_latex_width = 200
max_latex_width = min(600, total_width * 0.5) # Máximo 50% del ancho total
latex_width = max(min_latex_width, min(latex_width, max_latex_width))
main_width = total_width - latex_width - 50 # 50 para el botón
# Asegurar que main_width no sea negativo
if main_width < 200:
main_width = 200
latex_width = total_width - main_width - 50
self.secondary_splitter.setSizes([main_width, latex_width])
# Actualizar panel con ecuaciones pendientes
@ -346,11 +357,17 @@ class HybridCalculatorPySide6(QMainWindow):
def closeEvent(self, event):
"""Maneja el cierre de la aplicación"""
# Guardar ancho del panel LaTeX si está visible
# Guardar ancho del panel LaTeX si está visible con límites de validación
if self.latex_panel_visible and self.secondary_splitter.count() > 1:
sizes = self.secondary_splitter.sizes()
if len(sizes) > 1:
latex_width = sizes[1]
# CORRECCIÓN: Validar y limitar el ancho antes de guardarlo
min_latex_width = 200
max_latex_width = 600
latex_width = max(min_latex_width, min(latex_width, max_latex_width))
self.settings_manager.set_setting("latex_panel_width", latex_width)
# Delegar al settings manager

374
custom_types/date_type.py Normal file
View File

@ -0,0 +1,374 @@
"""
Módulo de manejo de fechas para el sistema de tipos
Permite crear y manipular fechas con aritmética de días y semanas, soporte para
operaciones simbólicas a través de SymPy, y utilidades comunes como cálculo de
fines de semana, días hábiles y diferencias entre fechas.
Ejemplo de uso:
>>> d = Date("10/5/25") # 10 de mayo de 2025
>>> d + 7 # 17/5/25
>>> d.day_of_week() # 'Sábado'
>>> d.is_weekend() # True
>>> d.next_business_day() # 12/5/25
"""
from __future__ import annotations
import datetime
from typing import List, Tuple, Optional, Union
import sympy
from app.class_base import (
ClassBase,
) # noqa: F401 (aún no se usa, pero se importa para coherencia)
from app.sympy_Base import SympyClassBase
__all__ = [
"Class_Date",
"Date", # alias de conveniencia
"Today", # nueva función constructora
]
def _parse_date_str(date_str: str) -> datetime.date:
"""Convierte una cadena a objeto ``datetime.date``.
Acepta los formatos comunes:
- DD/MM/YY (se asume 2000-2099 para YY < 70 y 1900-1999 para el resto)
- DD/MM/YYYY
- YYYY-MM-DD (ISO-8601)
Asimismo se admiten separadores «/», «-» o «.».
"""
date_str = date_str.strip()
# Intentar ISO-8601 directamente
try:
return datetime.date.fromisoformat(date_str)
except ValueError:
pass
# Reemplazar posibles separadores por «/» para simplificar
normalised = date_str.replace("-", "/").replace(".", "/")
parts = normalised.split("/")
if len(parts) != 3:
raise ValueError(f"Formato de fecha desconocido: '{date_str}'")
# Detectar si el primer campo es año (ISO) o día
if len(parts[0]) == 4: # AAAA/MM/DD
year = int(parts[0])
month = int(parts[1])
day = int(parts[2])
else: # DD/MM/AA o DD/MM/YYYY
day = int(parts[0])
month = int(parts[1])
year_part = parts[2]
if len(year_part) == 2:
yy = int(year_part)
year = 2000 + yy if yy < 70 else 1900 + yy
else:
year = int(year_part)
return datetime.date(year, month, day)
class Class_Date(SympyClassBase):
"""Clase de fecha con aritmética de días y semanas.
Internamente la fecha se almacena como el ordinal devuelto por
:py:meth:`datetime.date.toordinal`, lo que facilita la aritmética con
números enteros (p.ej. añadir días).
"""
_op_priority = 20 # preferencia alta en operaciones de SymPy
def __init__(self, value: Union[str, datetime.date, int, sympy.Expr]):
# Manejar expresiones SymPy (p. ej. símbolos o enteros simbólicos)
if isinstance(value, sympy.Expr) and not isinstance(value, sympy.Integer):
# Modo simbólico puro (no intentamos resolver a fecha real)
self._ordinal_expr = value
self._has_symbols = True
date_repr = str(value)
else:
# Convertir todos los casos a «datetime.date» y ordinal entero
if isinstance(value, datetime.date):
date_obj = value
elif isinstance(value, str):
date_obj = _parse_date_str(value)
elif isinstance(value, (int, sympy.Integer)):
# Se interpreta como ordinal absoluto
date_obj = datetime.date.fromordinal(int(value))
else:
raise TypeError(
"value debe ser str, datetime.date o entero (ordinal), "
f"recibido: {type(value)}"
)
self._ordinal = date_obj.toordinal()
self._has_symbols = False
self._date = date_obj
self._ordinal_expr = sympy.Integer(self._ordinal)
date_repr = self._date.isoformat()
super().__init__(self._ordinal_expr, date_repr)
# ------------------------------------------------------------------
# Representación
# ------------------------------------------------------------------
def __repr__(self): # noqa: D401
return f"Date('{self.__str__()}')"
def __str__(self): # noqa: D401
if self._has_symbols:
return str(self._ordinal_expr)
return self._date.strftime("%d/%m/%y")
def _sympystr(self, printer): # pragma: no cover
return str(self)
# ------------------------------------------------------------------
# Conversión
# ------------------------------------------------------------------
def to_datetime(self) -> datetime.date:
"""Devuelve la fecha como objeto ``datetime.date``."""
if self._has_symbols:
raise ValueError(
"La fecha es simbólica y no se puede convertir a 'datetime.date'."
)
return self._date
def to_ordinal(self) -> int:
"""Devuelve el ordinal (días desde 01-01-0001)."""
return int(self._ordinal_expr)
# ------------------------------------------------------------------
# Aritmética
# ------------------------------------------------------------------
def __add__(self, other):
"""Añadir días o expresiones simbólicas: ``fecha + n`` o ``fecha + símbolo``."""
# Soporte para expresiones SymPy (símbolos, enteros simbólicos, etc.)
if isinstance(other, sympy.Expr):
new_expr = self._ordinal_expr + other
return Class_Date(new_expr)
if hasattr(other, "__int__"):
return self.add_days(int(other))
return NotImplemented
__radd__ = __add__
def __sub__(self, other):
"""Resta días o devuelve diferencia: ``fecha - n``, ``fecha - símbolo``, o diferencia entre fechas."""
if isinstance(other, sympy.Expr):
# Restar expresión simbólica -> nueva fecha simbólica
new_expr = self._ordinal_expr - other
return Class_Date(new_expr)
if hasattr(other, "__int__"):
return self.add_days(-int(other))
if isinstance(other, Class_Date):
# Diferencia (puede ser simbólica)
return self._ordinal_expr - other._ordinal_expr
return NotImplemented
def add_days(self, n: int) -> "Class_Date":
"""Devuelve una nueva fecha desplazada *n* días."""
if self._has_symbols:
new_expr = self._ordinal_expr + n
return Class_Date(new_expr)
else:
new_date = self._date + datetime.timedelta(days=n)
return Class_Date(new_date)
def add_weeks(self, n: int) -> "Class_Date":
"""Devuelve una nueva fecha desplazada *n* semanas."""
return self.add_days(n * 7)
def next_day(self) -> "Class_Date":
return self.add_days(1)
def previous_day(self) -> "Class_Date":
return self.add_days(-1)
# ------------------------------------------------------------------
# Información de calendario
# ------------------------------------------------------------------
_WEEKDAY_NAMES_ES = [
"Lunes",
"Martes",
"Miércoles",
"Jueves",
"Viernes",
"Sábado",
"Domingo",
]
def day_of_week(self) -> str:
"""Nombre del día de la semana en español (Lunes-Domingo)."""
if self._has_symbols:
raise ValueError(
"La fecha es simbólica; no se puede determinar el día de la semana."
)
weekday = self._date.weekday() # 0 = lunes
return self._WEEKDAY_NAMES_ES[weekday]
def is_weekend(self) -> bool:
if self._has_symbols:
raise ValueError(
"La fecha es simbólica; no se puede determinar si es fin de semana."
)
return self._date.weekday() >= 5
# ------------------------------------------------------------------
# Días hábiles
# ------------------------------------------------------------------
def is_business_day(self, holidays: Optional[List[datetime.date]] = None) -> bool:
"""Indica si es día laborable (L-V) y no festivo.
Args:
holidays: lista opcional de festivos como ``datetime.date``.
"""
if self._has_symbols:
raise ValueError(
"La fecha es simbólica; no se puede evaluar si es laborable."
)
if self.is_weekend():
return False
if holidays and self._date in holidays:
return False
return True
def next_business_day(
self, holidays: Optional[List[datetime.date]] = None
) -> "Class_Date":
"""Devuelve el siguiente día laborable."""
candidate = self.next_day()
while not candidate.is_business_day(holidays):
candidate = candidate.next_day()
return candidate
def business_days_between(
self, other: "Class_Date", holidays: Optional[List[datetime.date]] = None
) -> int:
"""Cuenta días laborables entre dos fechas (excluye la inicial, incluye la final si procede)."""
if not isinstance(other, Class_Date):
raise TypeError("other debe ser Class_Date")
start_ord = min(self.to_ordinal(), other.to_ordinal()) + 1
end_ord = max(self.to_ordinal(), other.to_ordinal())
count = 0
for ord_val in range(start_ord, end_ord + 1):
d = Class_Date(ord_val)
if d.is_business_day(holidays):
count += 1
return count
# ------------------------------------------------------------------
# Diferencias
# ------------------------------------------------------------------
def days_between(self, other: "Class_Date") -> int:
"""Número absoluto de días entre dos fechas."""
if not isinstance(other, Class_Date):
raise TypeError("other debe ser Class_Date")
return abs(self - other)
def weeks_between(self, other: "Class_Date") -> float:
"""Número de semanas (con decimales) entre dos fechas."""
return self.days_between(other) / 7
# ------------------------------------------------------------------
# Comparaciones y ecuaciones simbólicas
# ------------------------------------------------------------------
def __eq__(self, other): # type: ignore[override]
"""Devuelve una ecuación SymPy en vez de comparar booleanamente cuando se usa con objetos Date o expresiones SymPy."""
if isinstance(other, Class_Date):
return sympy.Eq(self._ordinal_expr, other._ordinal_expr)
if isinstance(other, sympy.Expr):
return sympy.Eq(self._ordinal_expr, other)
return NotImplemented
# ------------------------------------------------------------------
# Helper para autocompletado & paneles GUI
# ------------------------------------------------------------------
@staticmethod
def Helper(input_str): # pragma: no cover
if input_str.strip().lower().startswith("date("):
return (
"Ej: date('10/5/25'), date('2025-05-10')\n" # noqa: E501
"Funciones: add_days(), add_weeks(), next_business_day(), day_of_week(), is_weekend(), is_business_day(), days_between()…"
)
return None
@staticmethod
def PopupFunctionList(): # pragma: no cover
return [
("add_days", "Suma N días"),
("add_weeks", "Suma N semanas"),
("next_day", "Día siguiente"),
("previous_day", "Día anterior"),
("next_business_day", "Siguiente día hábil"),
("is_business_day", "¿Es día laborable?"),
("day_of_week", "Nombre del día de la semana"),
("is_weekend", "¿Es fin de semana?"),
("days_between", "Días entre dos fechas"),
("weeks_between", "Semanas entre dos fechas"),
("to_datetime", "Convertir a datetime.date"),
("to_ordinal", "Ordinal (nº de días)"),
]
def today(cls) -> "Class_Date":
"""Fecha actual (según *datetime.date.today()*)."""
return cls(datetime.date.today())
# Alias estático para consistencia con otras clases
Today = classmethod(today)
# ----------------------------------------------------------------------
# Alias de usuario final — ajustado al sistema de tokenización existente
# ----------------------------------------------------------------------
def Date(value: Union[str, datetime.date, int, sympy.Expr]): # noqa: N802
"""Alias que imita la sintaxis ``date('')`` empleada por el usuario."""
return Class_Date(value)
def Today(): # noqa: N802
"""Construye un objeto Date con la fecha actual (hoy)."""
return Class_Date.today()
# ----------------------------------------------------------------------
# Registro para el sistema de tipos dinámico
# ----------------------------------------------------------------------
def register_classes_in_module(): # pragma: no cover
"""Devuelve la lista de clases para el registro automático."""
return [
(
"Date",
Class_Date,
"SympyClassBase",
{
"add_lowercase": True, # permite usar ``date`` minúscula
"supports_brackets": False,
"description": "Fechas con aritmética y utilidades de calendario",
},
),
(
"Today",
Today,
"function",
{
"add_lowercase": True,
"supports_brackets": False,
"description": "Fecha actual (hoy)",
},
),
]