using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using HydraulicSimulator.Models; using CtrEditor.ObjetosSim; namespace CtrEditor.HydraulicSimulator { /// /// Gestiona la simulación hidráulica de manera unificada, similar a SimulationManagerBEPU /// Utiliza solo el patrón osBase + IHydraulicComponent + ApplyHydraulicResults /// public class HydraulicSimulationManager : IDisposable { #region Properties and Fields /// /// Red hidráulica principal que contiene todos los nodos y ramas /// public HydraulicNetwork Network { get; private set; } /// /// Lista de objetos simulables que tienen componentes hidráulicos /// public List HydraulicObjects { get; private set; } /// /// Fluido utilizado en la simulación (agua por defecto) /// public Fluid SimulationFluid { get; set; } /// /// Resultado de la última simulación /// public SolutionResult LastSolutionResult { get; private set; } /// /// Tiempo global de la simulación hidráulica /// public float GlobalTime { get; private set; } /// /// Indica si la simulación hidráulica está habilitada /// public bool IsHydraulicSimulationEnabled { get; set; } = true; /// /// Indica si la verificación de NPSH está habilitada /// public bool EnableNPSHVerification { get; set; } = true; /// /// Parámetros del solver /// public int MaxIterations { get; set; } = 300; public double Tolerance { get; set; } = 1e-3; public double RelaxationFactor { get; set; } = 0.8; public bool VerboseOutput { get; set; } = true; /// /// Contador de pasos de simulación para optimizaciones /// private int _stepCount = 0; /// /// Cronómetro para medir tiempo de ejecución /// private Stopwatch _stopwatch; /// /// Diccionario para mapear IDs de objetos a elementos hidráulicos /// private Dictionary _objectMapping; /// /// Indica si hay cambios pendientes en la red que requieren reconstrucción /// private bool _networkNeedsRebuild = true; #endregion #region Constructor /// /// Constructor del gestor de simulación hidráulica /// public HydraulicSimulationManager() { // Inicializar componentes SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto Network = new HydraulicNetwork(SimulationFluid); HydraulicObjects = new List(); _objectMapping = new Dictionary(); _stopwatch = new Stopwatch(); // Inicializar resultado vacío LastSolutionResult = new SolutionResult(false) { ErrorMessage = "Simulación no ejecutada" }; GlobalTime = 0.0f; Trace.WriteLine("HydraulicSimulationManager inicializado"); } #endregion #region Object Management /// /// Registra un objeto simulable que tiene componentes hidráulicos /// /// Objeto con componentes hidráulicos public void RegisterHydraulicObject(osBase hydraulicObject) { if (hydraulicObject == null) return; if (!HydraulicObjects.Contains(hydraulicObject)) { HydraulicObjects.Add(hydraulicObject); _objectMapping[hydraulicObject.Nombre] = hydraulicObject; _networkNeedsRebuild = true; Trace.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}"); } } /// /// Desregistra un objeto simulable hidráulico /// /// Objeto a desregistrar public void UnregisterHydraulicObject(osBase hydraulicObject) { if (hydraulicObject == null) return; if (HydraulicObjects.Remove(hydraulicObject)) { _objectMapping.Remove(hydraulicObject.Nombre); _networkNeedsRebuild = true; Trace.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}"); } } /// /// Limpia todos los objetos hidráulicos registrados /// public void ClearHydraulicObjects() { HydraulicObjects.Clear(); _objectMapping.Clear(); _networkNeedsRebuild = true; Trace.WriteLine("Todos los objetos hidráulicos han sido limpiados"); } #endregion #region Network Building /// /// Reconstruye la red hidráulica basada en los objetos registrados /// private void RebuildNetwork() { if (!_networkNeedsRebuild) return; Trace.WriteLine("Reconstruyendo red hidráulica..."); // Crear nueva red Network = new HydraulicNetwork(SimulationFluid); // Agregar nodos y elementos basados en los objetos hidráulicos BuildNodesFromObjects(); BuildBranchesFromObjects(); _networkNeedsRebuild = false; Trace.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas"); } /// /// Construye los nodos de la red basándose en los objetos hidráulicos /// private void BuildNodesFromObjects() { foreach (var obj in HydraulicObjects) { // Verificar si el objeto implementa la interfaz hidráulica if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { // Obtener nodos definidos por el objeto var nodeDefinitions = hydraulicComponent.GetHydraulicNodes(); foreach (var nodeDef in nodeDefinitions) { // Agregar nodo a la red Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null); if (VerboseOutput) { Trace.WriteLine($"Nodo agregado: {nodeDef.Name} " + $"(Presión fija: {nodeDef.IsFixedPressure}, " + $"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})"); } } } } } /// /// Construye las ramas (conexiones) entre nodos /// private void BuildBranchesFromObjects() { foreach (var obj in HydraulicObjects) { // Verificar si el objeto implementa la interfaz hidráulica if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { // Obtener elementos definidos por el objeto var elementDefinitions = hydraulicComponent.GetHydraulicElements(); foreach (var elemDef in elementDefinitions) { // Crear rama con el elemento var elements = new List { elemDef.Element }; Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name); if (VerboseOutput) { Trace.WriteLine($"Rama agregada: {elemDef.Name} " + $"({elemDef.FromNode} -> {elemDef.ToNode})"); } } } } } #endregion #region Simulation Step /// /// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU) /// public void Step(float deltaTime = 0.01f) // 10ms por defecto (100 FPS hidráulico) { if (!IsHydraulicSimulationEnabled) return; _stopwatch.Restart(); try { // Actualizar tiempo global GlobalTime += deltaTime; _stepCount++; // Reconstruir red si es necesario RebuildNetwork(); // Solo resolver si tenemos objetos hidráulicos if (HydraulicObjects.Count > 0 && Network.Branches.Count > 0) { // Actualizar propiedades de objetos antes de resolver UpdateObjectProperties(); // Resolver la red hidráulica LastSolutionResult = Network.Solve( MaxIterations, Tolerance, RelaxationFactor, VerboseOutput ); if (LastSolutionResult.Converged) { // Aplicar resultados a los objetos usando ApplyHydraulicResults ApplyResultsToObjects(); if (VerboseOutput && _stepCount % 100 == 0) // Log cada segundo aprox { Trace.WriteLine($"Simulación hidráulica - Paso {_stepCount}: Convergió exitosamente"); } } else { Trace.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); Trace.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}"); Trace.WriteLine($" Tolerancia requerida: {Tolerance:E6}"); } } else { // Sin objetos hidráulicos, resultado exitoso pero vacío LastSolutionResult = new SolutionResult(true); } } catch (Exception ex) { LastSolutionResult = new SolutionResult(false) { ErrorMessage = $"Error en simulación hidráulica: {ex.Message}" }; Trace.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}"); } finally { _stopwatch.Stop(); if (VerboseOutput && _stepCount % 100 == 0) // Log cada segundo aprox { Trace.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " + $"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}"); } } } /// /// Actualiza las propiedades de los objetos antes de resolver la red /// private void UpdateObjectProperties() { // Actualizar objetos hidráulicos antes de la simulación foreach (var obj in HydraulicObjects) { if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { hydraulicComponent.UpdateHydraulicProperties(); } } } /// /// Aplica los resultados de la simulación a los objetos usando ApplyHydraulicResults /// Este es el único punto donde se actualizan los resultados, siguiendo el patrón BEPU /// private void ApplyResultsToObjects() { // Aplicar resultados a todos los objetos hidráulicos usando ApplyHydraulicResults foreach (var obj in HydraulicObjects) { if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures); } } } #endregion #region Public Methods /// /// Fuerza la reconstrucción de la red en el próximo paso /// public void InvalidateNetwork() { _networkNeedsRebuild = true; } /// /// Obtiene estadísticas de la simulación /// public string GetSimulationStats() { return $"Objetos hidráulicos: {HydraulicObjects.Count}\n" + $"Nodos: {Network.Nodes.Count}\n" + $"Ramas: {Network.Branches.Count}\n" + $"Última simulación convergió: {LastSolutionResult.Converged}\n" + $"Iteraciones: {LastSolutionResult.Iterations}\n" + $"Residual: {LastSolutionResult.Residual:E3}\n" + $"Tiempo global: {GlobalTime:F2}s\n" + $"Pasos: {_stepCount}"; } /// /// Reinicia la simulación /// public void Reset() { GlobalTime = 0.0f; _stepCount = 0; _networkNeedsRebuild = true; // Reinicializar resultado LastSolutionResult = new SolutionResult(false) { ErrorMessage = "Simulación reiniciada" }; Trace.WriteLine("Simulación hidráulica reiniciada"); } /// /// Configura los parámetros de verificación de NPSH /// public void ConfigureNPSHSettings(bool enableNPSH, double npshRequired = 3.0, double vaporPressure = 2337.0, double suctionLosses = 0.5) { EnableNPSHVerification = enableNPSH; // Invalidar la red para que se reconstruya con los nuevos parámetros InvalidateNetwork(); Trace.WriteLine($"Verificación NPSH {(enableNPSH ? "habilitada" : "deshabilitada")}: NPSH_req={npshRequired}m, P_vapor={vaporPressure}Pa"); } #endregion #region IDisposable private bool _disposed = false; /// /// Libera recursos del gestor de simulación /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!_disposed) { if (disposing) { // Limpiar recursos gestionados HydraulicObjects?.Clear(); _objectMapping?.Clear(); Network = null; _stopwatch?.Stop(); Trace.WriteLine("HydraulicSimulationManager disposed"); } _disposed = true; } } #endregion } }