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 similar a SimulationManagerBEPU /// 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; /// /// Parámetros del solver /// public int MaxIterations { get; set; } = 300; public double Tolerance { get; set; } = 1e-3; // Tolerancia más relajada para convergencia inicial public double RelaxationFactor { get; set; } = 0.8; public bool VerboseOutput { get; set; } = false; // Activado para debugging /// /// 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; Debug.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; Debug.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; Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}"); } } /// /// Limpia todos los objetos hidráulicos registrados /// public void ClearHydraulicObjects() { HydraulicObjects.Clear(); _objectMapping.Clear(); _networkNeedsRebuild = true; Debug.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; Debug.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; Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas"); if (VerboseOutput) { Debug.WriteLine("=== DETALLES DE LA RED HIDRÁULICA ==="); Debug.WriteLine("Nodos:"); foreach (var node in Network.Nodes) { string pressureInfo = node.Value.FixedP ? $"Presión fija: {node.Value.P:F0} Pa" : "Presión libre"; Debug.WriteLine($" - {node.Key}: {pressureInfo}"); } Debug.WriteLine("Ramas:"); foreach (var branch in Network.Branches) { Debug.WriteLine($" - {branch.Name}: {branch.N1} -> {branch.N2} ({branch.Elements.Count} elementos)"); foreach (var element in branch.Elements) { Debug.WriteLine($" * {element.GetType().Name}"); } } Debug.WriteLine("=== FIN DETALLES RED ==="); } } /// /// 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) { Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " + $"(Presión fija: {nodeDef.IsFixedPressure}, " + $"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})"); } } } else { // Objeto sin interfaz hidráulica - crear nodos básicos por compatibilidad string nodeName = $"Node_{obj.Nombre}"; bool isFixedPressure = DetermineIfFixedPressure(obj); double? pressure = isFixedPressure ? GetObjectPressure(obj) : null; Network.AddNode(nodeName, pressure); } } } /// /// 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) { Debug.WriteLine($"Rama agregada: {elemDef.Name} " + $"({elemDef.FromNode} -> {elemDef.ToNode})"); } } } else { // Objeto sin interfaz hidráulica - crear elementos básicos por compatibilidad var elements = CreateHydraulicElementsFromObject(obj); if (elements.Any()) { string fromNode = GetSourceNode(obj); string toNode = GetTargetNode(obj); if (!string.IsNullOrEmpty(fromNode) && !string.IsNullOrEmpty(toNode)) { Network.AddBranch(fromNode, toNode, elements, $"Branch_{obj.Nombre}"); } } } } } #endregion #region Helper Methods for Object Analysis /// /// Determina si un objeto debe tener presión fija /// private bool DetermineIfFixedPressure(osBase obj) { // Implementación que será extendida con tipos específicos // Por ejemplo: tanques, descargas atmosféricas = presión fija // Uniones, conexiones = presión libre return false; // Por defecto, presión libre } /// /// Obtiene la presión de un objeto (si tiene presión fija) /// private double GetObjectPressure(osBase obj) { // Implementación que será extendida return 0.0; // Por defecto, presión atmosférica } /// /// Crea elementos hidráulicos basándose en un objeto /// private List CreateHydraulicElementsFromObject(osBase obj) { var elements = new List(); // Implementación que será extendida con tipos específicos // Por ejemplo: // - Si es una bomba: crear PumpHQ // - Si es una tubería: crear Pipe // - Si es una válvula: crear ValveKv return elements; } /// /// Obtiene el nodo fuente de un objeto /// private string GetSourceNode(osBase obj) { // Implementación que será extendida return $"Node_{obj.Nombre}_In"; } /// /// Obtiene el nodo destino de un objeto /// private string GetTargetNode(osBase obj) { // Implementación que será extendida return $"Node_{obj.Nombre}_Out"; } #endregion #region Simulation Step /// /// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU) /// public void Step(float deltaTime = 0.016f) // ~60 FPS por defecto { 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 ApplyResultsToObjects(); if (VerboseOutput && _stepCount % 300 == 0) // Log cada 5 segundos aprox { //Debug.WriteLine("=== RESULTADOS SIMULACIÓN HIDRÁULICA ==="); //Debug.WriteLine("Flujos:"); foreach (var flow in LastSolutionResult.Flows) { Debug.WriteLine($" {flow.Key}: {flow.Value:F6} m³/s ({flow.Value * 3600:F2} m³/h)"); } Debug.WriteLine("Presiones:"); foreach (var pressure in LastSolutionResult.Pressures) { Debug.WriteLine($" {pressure.Key}: {pressure.Value:F0} Pa ({pressure.Value / 100000:F2} bar)"); } Debug.WriteLine("=== FIN RESULTADOS ==="); } } else { Debug.WriteLine($"❌ Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}"); Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}"); if (VerboseOutput && _stepCount % 60 == 0) // Log detallado cada segundo aprox { Debug.WriteLine("=== DIAGNÓSTICO CONVERGENCIA ==="); Debug.WriteLine($"Nodos en red: {Network.Nodes.Count}"); Debug.WriteLine($"Ramas en red: {Network.Branches.Count}"); foreach (var node in Network.Nodes) { string info = node.Value.FixedP ? $"FIJA={node.Value.P:F0}Pa" : "LIBRE"; Debug.WriteLine($" Nodo {node.Key}: {info}"); } Debug.WriteLine("=== FIN DIAGNÓSTICO ==="); } } } 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}" }; Debug.WriteLine($"Error en HydraulicSimulationManager.Step: {ex}"); } finally { _stopwatch.Stop(); if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox { Debug.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 específicos foreach (var simObj in HydraulicSimObjects) { simObj.UpdateProperties(); } // Actualizar objetos hidráulicos generales (legacy) foreach (var obj in HydraulicObjects) { // Aquí se implementará la actualización de propiedades específicas // Por ejemplo: estado de válvulas, velocidad de bombas, etc. UpdateObjectHydraulicProperties(obj); } } /// /// Actualiza las propiedades hidráulicas específicas de un objeto /// private void UpdateObjectHydraulicProperties(osBase obj) { // Actualizar propiedades basándose en las interfaces implementadas if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { // Llamar al método de actualización del objeto hydraulicComponent.UpdateHydraulicProperties(); } // Actualización específica por tipo de objeto if (obj is IHydraulicPump pump) { // Verificar estado de la bomba, velocidad, etc. UpdatePumpProperties(pump); } else if (obj is IHydraulicValve valve) { // Verificar apertura de la válvula UpdateValveProperties(valve); } else if (obj is IHydraulicTank tank) { // Actualizar nivel y presión del tanque UpdateTankProperties(tank); } } /// /// Actualiza propiedades específicas de bombas /// private void UpdatePumpProperties(IHydraulicPump pump) { // Aquí se pueden hacer validaciones y ajustes específicos de bombas // Por ejemplo, limitar velocidad, verificar estado de operación, etc. if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0; if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0; if (VerboseOutput) { Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " + $"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}"); } } /// /// Actualiza propiedades específicas de válvulas /// private void UpdateValveProperties(IHydraulicValve valve) { // Validar apertura de válvula if (valve.Opening < 0.0) valve.Opening = 0.0; if (valve.Opening > 1.0) valve.Opening = 1.0; if (VerboseOutput) { Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " + $"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}"); } } /// /// Actualiza propiedades específicas de tanques /// private void UpdateTankProperties(IHydraulicTank tank) { // Calcular presión basada en nivel si no es presión fija if (!tank.IsFixedPressure) { // P = ρgh + P_atmosferica double pressureFromLevel = SimulationFluid.Rho * 9.80665 * tank.Level; // + presión atmosférica tank.TankPressure = pressureFromLevel; } if (VerboseOutput) { Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " + $"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}"); } } /// /// Aplica los resultados de la simulación a los objetos /// private void ApplyResultsToObjects() { // Aplicar resultados a objetos hidráulicos específicos foreach (var simObj in HydraulicSimObjects) { ApplyResultsToSimObject(simObj); simObj.ApplySimulationResults(); } // Aplicar resultados a objetos hidráulicos generales (legacy) foreach (var obj in HydraulicObjects) { // Aplicar caudales y presiones calculados al objeto ApplyResultsToObject(obj); } } /// /// Aplica los resultados de la simulación a un objeto específico /// private void ApplyResultsToSimObject(simHydraulicBase simObj) { // Aplicar resultados específicos según el tipo de objeto if (simObj is simHydraulicPump pump) { // Buscar flujo y presión de la bomba en los resultados string pumpBranchName = $"{pump.Nombre}_Pump"; if (LastSolutionResult.Flows.TryGetValue(pumpBranchName, out double flow)) { pump.CurrentFlow = flow; } string inletNodeName = $"{pump.Nombre}_In"; if (LastSolutionResult.Pressures.TryGetValue(inletNodeName, out double pressure)) { pump.CurrentPressure = pressure; } } else if (simObj is simHydraulicTank tank) { // Buscar flujos de entrada y salida del tanque // Esto requeriría mapeo de tuberías conectadas // Por ahora, aplicar presión del tanque string tankNodeName = $"{tank.Nombre}_Tank"; if (LastSolutionResult.Pressures.TryGetValue(tankNodeName, out double pressure)) { tank.CurrentPressure = pressure; } } else if (simObj is simHydraulicPipe pipe) { // Buscar flujo a través de la tubería string pipeBranchName = $"{pipe.Nombre}_Pipe"; if (LastSolutionResult.Flows.TryGetValue(pipeBranchName, out double flow)) { pipe.CurrentFlow = flow; } // Calcular pérdida de presión string fromNode = pipe.ComponenteAId; string toNode = pipe.ComponenteBId; if (LastSolutionResult.Pressures.TryGetValue(fromNode, out double pressureA) && LastSolutionResult.Pressures.TryGetValue(toNode, out double pressureB)) { pipe.PressureDrop = pressureA - pressureB; } } } /// /// Aplica los resultados a un objeto específico /// private void ApplyResultsToObject(osBase obj) { // Aplicar resultados usando las interfaces hidráulicas if (obj is IHydraulicComponent hydraulicComponent && hydraulicComponent.HasHydraulicComponents) { // Llamar al método de aplicación de resultados del objeto hydraulicComponent.ApplyHydraulicResults(LastSolutionResult.Flows, LastSolutionResult.Pressures); } // Aplicación específica por tipo de interfaz if (obj is IHydraulicFlowReceiver flowReceiver) { // Buscar caudal asociado al objeto string branchName = FindBranchNameForObject(obj); if (!string.IsNullOrEmpty(branchName) && LastSolutionResult.Flows.ContainsKey(branchName)) { double flow = LastSolutionResult.Flows[branchName]; flowReceiver.SetFlow(flow); } } if (obj is IHydraulicPressureReceiver pressureReceiver) { // Buscar presión asociada al objeto string nodeName = FindNodeNameForObject(obj); if (!string.IsNullOrEmpty(nodeName) && LastSolutionResult.Pressures.ContainsKey(nodeName)) { double pressure = LastSolutionResult.Pressures[nodeName]; pressureReceiver.SetPressure(pressure); } } } /// /// Encuentra el nombre de rama asociado a un objeto /// private string FindBranchNameForObject(osBase obj) { // Buscar en las ramas de la red foreach (var branch in Network.Branches) { if (branch.Name.Contains(obj.Nombre)) { return branch.Name; } } // Fallback a nombre por defecto return $"Branch_{obj.Nombre}"; } /// /// Encuentra el nombre de nodo asociado a un objeto /// private string FindNodeNameForObject(osBase obj) { // Buscar en los nodos de la red foreach (var nodeKvp in Network.Nodes) { if (nodeKvp.Key.Contains(obj.Nombre)) { return nodeKvp.Key; } } // Fallback a nombre por defecto return $"Node_{obj.Nombre}"; } /// /// Establece el caudal en un objeto /// private void SetObjectFlow(osBase obj, double flow) { if (obj is IHydraulicFlowReceiver flowReceiver) { flowReceiver.SetFlow(flow); } } /// /// Establece la presión en un objeto /// private void SetObjectPressure(osBase obj, double pressure) { if (obj is IHydraulicPressureReceiver pressureReceiver) { pressureReceiver.SetPressure(pressure); } } #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" }; Debug.WriteLine("Simulación hidráulica reiniciada"); } #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(); Debug.WriteLine("HydraulicSimulationManager disposed"); } _disposed = true; } } #endregion #region Hydraulic Object Creation /// /// Lista de objetos hidráulicos específicos del simulador /// public List HydraulicSimObjects { get; private set; } = new List(); /// /// Crea una bomba hidráulica en la simulación /// public simHydraulicPump AddPump(double pumpHead, double maxFlow, double speedRatio, bool isRunning, int direction) { var pump = new simHydraulicPump(this) { PumpHead = pumpHead, MaxFlow = maxFlow, SpeedRatio = speedRatio, IsRunning = isRunning, PumpDirection = direction }; HydraulicSimObjects.Add(pump); _networkNeedsRebuild = true; Debug.WriteLine($"Bomba hidráulica creada: H={pumpHead}m, Q={maxFlow}m³/s"); return pump; } /// /// Crea un tanque hidráulico en la simulación /// public simHydraulicTank AddTank(double pressure, double currentLevel, double maxLevel, double minLevel, double crossSectionalArea, bool isFixedPressure) { var tank = new simHydraulicTank(this) { TankPressure = pressure, CurrentLevel = currentLevel, MaxLevel = maxLevel, MinLevel = minLevel, CrossSectionalArea = crossSectionalArea, IsFixedPressure = isFixedPressure }; HydraulicSimObjects.Add(tank); _networkNeedsRebuild = true; Debug.WriteLine($"Tanque hidráulico creado: P={pressure}Pa, Nivel={currentLevel}m"); return tank; } /// /// Crea una tubería hidráulica en la simulación /// public simHydraulicPipe AddPipe(double length, double diameter, double roughness, string componenteAId, string componenteBId) { var pipe = new simHydraulicPipe(this) { Length = length, Diameter = diameter, Roughness = roughness, ComponenteAId = componenteAId, ComponenteBId = componenteBId }; HydraulicSimObjects.Add(pipe); _networkNeedsRebuild = true; Debug.WriteLine($"Tubería hidráulica creada: L={length}m, D={diameter}m"); return pipe; } /// /// Remueve un objeto hidráulico de la simulación /// public void Remove(simHydraulicBase hydraulicObject) { if (hydraulicObject != null && HydraulicSimObjects.Contains(hydraulicObject)) { HydraulicSimObjects.Remove(hydraulicObject); _networkNeedsRebuild = true; Debug.WriteLine($"Objeto hidráulico removido: {hydraulicObject.SimObjectType}"); } } /// /// Limpia todos los objetos hidráulicos específicos /// public void ClearHydraulicSimObjects() { HydraulicSimObjects.Clear(); _networkNeedsRebuild = true; Debug.WriteLine("Todos los objetos hidráulicos específicos han sido limpiados"); } #endregion } }