474 lines
18 KiB
C#
474 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using HydraulicSimulator.Models;
|
|
using CtrEditor.ObjetosSim;
|
|
|
|
namespace CtrEditor.HydraulicSimulator
|
|
{
|
|
/// <summary>
|
|
/// Gestiona la simulación hidráulica de manera unificada, similar a SimulationManagerBEPU
|
|
/// Utiliza solo el patrón osBase + IHydraulicComponent + ApplyHydraulicResults
|
|
/// </summary>
|
|
public class HydraulicSimulationManager : IDisposable
|
|
{
|
|
#region Properties and Fields
|
|
|
|
/// <summary>
|
|
/// Red hidráulica principal que contiene todos los nodos y ramas
|
|
/// </summary>
|
|
public HydraulicNetwork Network { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Lista de objetos simulables que tienen componentes hidráulicos
|
|
/// </summary>
|
|
public List<osBase> HydraulicObjects { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Fluido utilizado en la simulación (agua por defecto)
|
|
/// </summary>
|
|
public Fluid SimulationFluid { get; set; }
|
|
|
|
/// <summary>
|
|
/// Resultado de la última simulación
|
|
/// </summary>
|
|
public SolutionResult LastSolutionResult { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Tiempo global de la simulación hidráulica
|
|
/// </summary>
|
|
public float GlobalTime { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Indica si la simulación hidráulica está habilitada
|
|
/// </summary>
|
|
public bool IsHydraulicSimulationEnabled { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Indica si la verificación de NPSH está habilitada
|
|
/// </summary>
|
|
public bool EnableNPSHVerification { get; set; } = true;
|
|
|
|
/// <summary>
|
|
/// Parámetros del solver
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Contador de pasos de simulación para optimizaciones
|
|
/// </summary>
|
|
private int _stepCount = 0;
|
|
|
|
/// <summary>
|
|
/// Cronómetro para medir tiempo de ejecución
|
|
/// </summary>
|
|
private Stopwatch _stopwatch;
|
|
|
|
/// <summary>
|
|
/// Diccionario para mapear IDs de objetos a elementos hidráulicos
|
|
/// </summary>
|
|
private Dictionary<string, osBase> _objectMapping;
|
|
|
|
/// <summary>
|
|
/// Indica si hay cambios pendientes en la red que requieren reconstrucción
|
|
/// </summary>
|
|
private bool _networkNeedsRebuild = true;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Constructor del gestor de simulación hidráulica
|
|
/// </summary>
|
|
public HydraulicSimulationManager()
|
|
{
|
|
// Inicializar componentes
|
|
SimulationFluid = Fluid.Water20C; // Agua a 20°C por defecto
|
|
Network = new HydraulicNetwork(SimulationFluid);
|
|
HydraulicObjects = new List<osBase>();
|
|
_objectMapping = new Dictionary<string, osBase>();
|
|
_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
|
|
|
|
/// <summary>
|
|
/// Registra un objeto simulable que tiene componentes hidráulicos
|
|
/// </summary>
|
|
/// <param name="hydraulicObject">Objeto con componentes hidráulicos</param>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Desregistra un objeto simulable hidráulico
|
|
/// </summary>
|
|
/// <param name="hydraulicObject">Objeto a desregistrar</param>
|
|
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}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Limpia todos los objetos hidráulicos registrados
|
|
/// </summary>
|
|
public void ClearHydraulicObjects()
|
|
{
|
|
HydraulicObjects.Clear();
|
|
_objectMapping.Clear();
|
|
_networkNeedsRebuild = true;
|
|
|
|
Trace.WriteLine("Todos los objetos hidráulicos han sido limpiados");
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Network Building
|
|
|
|
/// <summary>
|
|
/// Reconstruye la red hidráulica basada en los objetos registrados
|
|
/// </summary>
|
|
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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construye los nodos de la red basándose en los objetos hidráulicos
|
|
/// </summary>
|
|
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"})");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Construye las ramas (conexiones) entre nodos
|
|
/// </summary>
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
// Validar que los nodos existan antes de agregar la rama
|
|
if (!Network.Nodes.ContainsKey(elemDef.FromNode))
|
|
{
|
|
Debug.WriteLine($"ERROR: Nodo '{elemDef.FromNode}' no existe. Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
|
continue;
|
|
}
|
|
|
|
if (!Network.Nodes.ContainsKey(elemDef.ToNode))
|
|
{
|
|
Debug.WriteLine($"ERROR: Nodo '{elemDef.ToNode}' no existe. Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
|
continue;
|
|
}
|
|
|
|
// Crear rama con el elemento
|
|
var elements = new List<Element> { elemDef.Element };
|
|
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
|
|
|
|
if (VerboseOutput)
|
|
{
|
|
Trace.WriteLine($"Rama agregada: {elemDef.Name} " +
|
|
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.WriteLine($"Error agregando rama {elemDef.Name}: {ex.Message}");
|
|
Debug.WriteLine($" FromNode: '{elemDef.FromNode}', ToNode: '{elemDef.ToNode}'");
|
|
Debug.WriteLine($" Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Simulation Step
|
|
|
|
/// <summary>
|
|
/// Ejecuta un paso de simulación hidráulica (equivalente al Step de BEPU)
|
|
/// </summary>
|
|
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}");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actualiza las propiedades de los objetos antes de resolver la red
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Fuerza la reconstrucción de la red en el próximo paso
|
|
/// </summary>
|
|
public void InvalidateNetwork()
|
|
{
|
|
_networkNeedsRebuild = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Obtiene estadísticas de la simulación
|
|
/// </summary>
|
|
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}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reinicia la simulación
|
|
/// </summary>
|
|
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");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Configura los parámetros de verificación de NPSH
|
|
/// </summary>
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Libera recursos del gestor de simulación
|
|
/// </summary>
|
|
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
|
|
}
|
|
}
|