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; } = 100; public double Tolerance { get; set; } = 1e-3; public double RelaxationFactor { get; set; } = 0.1; public bool VerboseOutput { get; set; } = false; /// /// 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"); } /// /// 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(); } else if (VerboseOutput) { Debug.WriteLine($"Simulación hidráulica no convergió: {LastSolutionResult.ErrorMessage}"); } } 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() { 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() { foreach (var obj in HydraulicObjects) { // Aplicar caudales y presiones calculados al objeto ApplyResultsToObject(obj); } } /// /// 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 } }