From baed392d90aa14c3caba14f077351cd5b68c566e Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 12 Sep 2025 02:58:39 +0200 Subject: [PATCH] Enhance TSNet simulation: optimize tank configurations, improve time settings, and extract dynamic tank levels. Update INP generator to utilize real tank data and add methods for tank adapter retrieval. --- HydraulicSimulator/Python/PythonInterop.cs | 85 ++++++++++++++++++- .../TSNet/Components/TSNetTankAdapter.cs | 5 ++ HydraulicSimulator/TSNet/TSNetINPGenerator.cs | 22 ++++- .../TSNet/TSNetSimulationManager.cs | 69 ++++++++++++++- ObjetosSim/HydraulicComponents/osHydTank.cs | 21 ++++- 5 files changed, 189 insertions(+), 13 deletions(-) diff --git a/HydraulicSimulator/Python/PythonInterop.cs b/HydraulicSimulator/Python/PythonInterop.cs index fb3f2b6..f0240ae 100644 --- a/HydraulicSimulator/Python/PythonInterop.cs +++ b/HydraulicSimulator/Python/PythonInterop.cs @@ -318,7 +318,7 @@ try: # Configurar WNTR options para headloss formula wn.options.hydraulic.headloss = 'D-W' # Darcy-Weisbach - wn.options.time.duration = 10.0 # 10 segundos de duración + wn.options.time.duration = 60.0 # 60 segundos de duración para ver transferencia real wn.options.time.hydraulic_timestep = 0.01 # Paso de tiempo de 0.01 segundos wn.options.time.report_timestep = 0.01 # Reportar cada 0.01 segundos @@ -330,8 +330,8 @@ try: print('TSNet: simulation_period inicial =', getattr(tm, 'simulation_period', 'N/A')) print('TSNet: time_step inicial =', getattr(tm, 'time_step', 'N/A')) - # Configurar tiempo usando API oficial de TSNet - simulation_time = 1.0 # 1 segundo por defecto + # Configurar tiempo usando API oficial de TSNet - TIEMPO OPTIMIZADO para co-simulación + simulation_time = 1.0 # 1 segundo para ver transferencia sin extremos if hasattr(tm, 'simulation_period') and tm.simulation_period > 0: simulation_time = tm.simulation_period @@ -471,6 +471,48 @@ try: tm = tsnet.simulation.Initializer(tm, t0, engine) print('TSNet: Inicialización completada') + # PASO 1.5: CONVERTIR TANQUES EPANET A SURGE TANKS DINÁMICOS CON CONFIGURACIONES REALISTAS + print('TSNet: Convirtiendo tanques a surge tanks dinámicos...') + tank_nodes_converted = [] + + # Buscar todos los tanques en el modelo + if hasattr(tm, 'tank_name_list') and tm.tank_name_list: + for tank_name in tm.tank_name_list: + try: + # Obtener información del tanque original + tank_obj = tm.get_node(tank_name) + + # Usar configuraciones reales del tanque EPANET + if hasattr(tank_obj, 'init_level') and hasattr(tank_obj, 'max_level'): + # CONFIGURACIONES REALISTAS basadas en ejemplo oficial TSNet + tank_area = 5.0 # m² - área realista (ejemplo oficial usa 10 m²) + tank_height = float(tank_obj.max_level - tank_obj.min_level) if hasattr(tank_obj, 'min_level') else 5.0 + water_height = float(tank_obj.init_level) if hasattr(tank_obj, 'init_level') else 1.0 + + # VALIDACIÓN DE UNIDADES: Asegurar que estén en metros + if tank_height < 1.0: # Si height < 1m, probablemente está en unidades incorrectas + tank_height = 5.0 # Default a 5m como en ejemplo oficial + + # CRÍTICO: Asegurar que water_height esté dentro de límites válidos + if water_height > tank_height: + water_height = tank_height * 0.8 # 80% de la altura máxima + elif water_height < 0.1: + water_height = 0.5 # Mínimo 50cm + + # USAR CLOSED SURGE TANKS para presurización como indica el usuario + tm.add_surge_tank(tank_name, [tank_area, tank_height, water_height], 'closed') + tank_nodes_converted.append(tank_name) + print('TSNet: Surge tank agregado: ' + tank_name + ' (área=' + str(tank_area) + 'm², altura=' + str(tank_height) + 'm, nivel_inicial=' + str(water_height) + 'm, tipo=closed)') + + except Exception as e: + print('TSNet: Error convirtiendo tanque ' + tank_name + ': ' + str(e)) + pass + + if tank_nodes_converted: + print('TSNet: ' + str(len(tank_nodes_converted)) + ' tanques convertidos a surge tanks dinámicos') + else: + print('TSNet: No se encontraron tanques para convertir') + # PASO 2: SIMULACIÓN TRANSITORIA (como en ejemplos oficiales) print('TSNet: Ejecutando simulación transitoria...') results_obj = 'ctreditor_results' # nombre para guardar resultados @@ -489,6 +531,8 @@ try: # Extraer resultados de nodos usando API oficial node_results = {{}} + tank_levels = {{}} # Específico para niveles de tanques + if hasattr(tm, 'node_name_list'): for node_name in tm.node_name_list: try: @@ -516,6 +560,38 @@ try: print('TSNet: Nodo ' + node_name + ' - sin atributos head disponibles') node_results[node_name] = final_head + + # EXTRACCIÓN ESPECÍFICA DE NIVELES DE TANQUES + # Los tanques en TSNet tienen propiedades especiales para niveles + if hasattr(node_obj, '_level'): + try: + if hasattr(node_obj._level, '__len__') and len(node_obj._level) > 1: + # Nivel dinámico calculado por TSNet (último valor) + tank_level = float(node_obj._level[-1]) + tank_levels[node_name] = tank_level + print('TSNet: TANQUE ' + node_name + ' _level[-1]: ' + str(tank_level) + ' m (DINÁMICO)') + else: + tank_level = float(node_obj._level) + tank_levels[node_name] = tank_level + print('TSNet: TANQUE ' + node_name + ' _level: ' + str(tank_level) + ' m') + except Exception as tank_error: + print('TSNet: Error extrayendo nivel de tanque ' + node_name + ': ' + str(tank_error)) + elif hasattr(node_obj, 'level'): + try: + if hasattr(node_obj.level, '__len__') and len(node_obj.level) > 1: + tank_level = float(node_obj.level[-1]) + tank_levels[node_name] = tank_level + print('TSNet: TANQUE ' + node_name + ' level[-1]: ' + str(tank_level) + ' m (DINÁMICO)') + else: + tank_level = float(node_obj.level) + tank_levels[node_name] = tank_level + print('TSNet: TANQUE ' + node_name + ' level: ' + str(tank_level) + ' m') + except Exception as tank_error: + print('TSNet: Error extrayendo nivel de tanque ' + node_name + ': ' + str(tank_error)) + + # También verificar si es un tanque por tipo de nodo + if hasattr(node_obj, 'node_type') and 'Tank' in str(node_obj.node_type): + print('TSNet: ' + node_name + ' identificado como TANQUE por node_type') except Exception as e: print('TSNet: Error extrayendo resultados de nodo ' + node_name + ': ' + str(e)) @@ -552,6 +628,7 @@ try: results_data = {{ 'nodes': node_results, 'pipes': pipe_results, + 'tank_levels': tank_levels, # Niveles específicos de tanques calculados por TSNet 'simulation_time': tm.simulation_period, 'time_step': tm.time_step }} @@ -561,7 +638,7 @@ try: json.dump(results_data, f, indent=2) print('TSNet: Resultados guardados en: ' + results_file) - print('TSNet: ' + str(len(node_results)) + ' nodos, ' + str(len(pipe_results)) + ' tuberías procesadas') + print('TSNet: ' + str(len(node_results)) + ' nodos, ' + str(len(pipe_results)) + ' tuberías, ' + str(len(tank_levels)) + ' tanques procesados') # Guardar resultados si es necesario print('Directorio de resultados: ' + r'{outputDir}') diff --git a/HydraulicSimulator/TSNet/Components/TSNetTankAdapter.cs b/HydraulicSimulator/TSNet/Components/TSNetTankAdapter.cs index b8dc896..18a03ea 100644 --- a/HydraulicSimulator/TSNet/Components/TSNetTankAdapter.cs +++ b/HydraulicSimulator/TSNet/Components/TSNetTankAdapter.cs @@ -18,6 +18,11 @@ namespace CtrEditor.HydraulicSimulator.TSNet.Components // Resultados calculados por TSNet (SOLO escritura desde TSNet) public TSNetTankResults Results { get; private set; } + + /// + /// Acceso al objeto tanque hidráulico subyacente + /// + public osHydTank Tank => tank; public TSNetTankAdapter(osHydTank tank) { diff --git a/HydraulicSimulator/TSNet/TSNetINPGenerator.cs b/HydraulicSimulator/TSNet/TSNetINPGenerator.cs index 100da12..107ab7b 100644 --- a/HydraulicSimulator/TSNet/TSNetINPGenerator.cs +++ b/HydraulicSimulator/TSNet/TSNetINPGenerator.cs @@ -17,11 +17,13 @@ namespace CtrEditor.HydraulicSimulator.TSNet { private readonly HydraulicNetwork _network; private readonly TSNetConfiguration _config; + private readonly TSNetSimulationManager _simulationManager; - public TSNetINPGenerator(HydraulicNetwork network, TSNetConfiguration config) + public TSNetINPGenerator(HydraulicNetwork network, TSNetConfiguration config, TSNetSimulationManager simulationManager = null) { _network = network ?? throw new ArgumentNullException(nameof(network)); _config = config ?? throw new ArgumentNullException(nameof(config)); + _simulationManager = simulationManager; // Puede ser null para compatibilidad hacia atrás } /// @@ -117,13 +119,27 @@ namespace CtrEditor.HydraulicSimulator.TSNet content.AppendLine("[TANKS]"); content.AppendLine(";ID \tElevation \tInitLevel \tMinLevel \tMaxLevel \tDiameter \tMinVol \tVolCurve"); - // Por ahora generar tanques dummy - será expandido cuando integremos osHydTank + // Usar configuración real de los TSNetTankAdapter en lugar de valores dummy var tankNodes = _network.Nodes.Values.Where(IsTank); foreach (var node in tankNodes) { var elevation = GetNodeElevation(node); var sanitizedName = SanitizeNodeName(node.Name); - content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t"); + + // Buscar el adapter correspondiente en el simulation manager + var tankAdapter = _simulationManager?.GetTankAdapterByNodeName(node.Name); + + if (tankAdapter?.Configuration != null) + { + // Usar configuración real del tanque + var config = tankAdapter.Configuration; + content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t{config.InitialLevelM.ToString("F2", CultureInfo.InvariantCulture)} \t{config.MinLevelM.ToString("F2", CultureInfo.InvariantCulture)} \t{config.MaxLevelM.ToString("F2", CultureInfo.InvariantCulture)} \t{config.DiameterM.ToString("F2", CultureInfo.InvariantCulture)} \t0 \t"); + } + else + { + // Fallback a valores por defecto si no hay configuración + content.AppendLine($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t"); + } } content.AppendLine(); diff --git a/HydraulicSimulator/TSNet/TSNetSimulationManager.cs b/HydraulicSimulator/TSNet/TSNetSimulationManager.cs index e15bcb6..4c7d048 100644 --- a/HydraulicSimulator/TSNet/TSNetSimulationManager.cs +++ b/HydraulicSimulator/TSNet/TSNetSimulationManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using System.Diagnostics; using System.Windows; @@ -101,8 +102,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet // Configuración por defecto Configuration = new TSNetConfiguration { - Duration = 10.0, - TimeStep = 0.01, + Duration = 1.0, // 1 segundo para ver transferencia sin extremos + TimeStep = 0.01, // 0.01 segundos para precisión OutputDirectory = Path.Combine(Path.GetTempPath(), "TSNet") }; @@ -316,6 +317,21 @@ namespace CtrEditor.HydraulicSimulator.TSNet return _tankAdapters.TryGetValue(objectId, out var adapter) ? adapter : null; } + /// + /// Obtiene el adaptador de tanque por nombre de nodo de la red hidráulica + /// + public TSNetTankAdapter GetTankAdapterByNodeName(string nodeName) + { + foreach (var adapter in _tankAdapters.Values) + { + if (adapter?.Tank?.Nombre == nodeName) + { + return adapter; + } + } + return null; + } + /// /// Obtiene el adaptador de bomba por ID /// @@ -527,7 +543,7 @@ namespace CtrEditor.HydraulicSimulator.TSNet try { var inpPath = Path.Combine(WorkingDirectory, $"network_{DateTime.Now:yyyyMMdd_HHmmss}.inp"); - var inpGenerator = new TSNetINPGenerator(Network, Configuration); + var inpGenerator = new TSNetINPGenerator(Network, Configuration, this); await inpGenerator.GenerateAsync(inpPath); CurrentInpFile = inpPath; @@ -648,6 +664,7 @@ namespace CtrEditor.HydraulicSimulator.TSNet var flows = new Dictionary(); var pressures = new Dictionary(); + var tankLevels = new Dictionary(); // Niveles específicos de tanques if (File.Exists(resultsFilePath)) { @@ -668,6 +685,18 @@ namespace CtrEditor.HydraulicSimulator.TSNet } } + // Extraer NIVELES ESPECÍFICOS DE TANQUES (calculados por TSNet) + if (resultsData.ContainsKey("tank_levels")) + { + var tankLevelsJson = resultsData["tank_levels"].ToString(); + var tankResults = System.Text.Json.JsonSerializer.Deserialize>(tankLevelsJson); + foreach (var kvp in tankResults) + { + tankLevels[kvp.Key] = kvp.Value; + Debug.WriteLine($"TSNet: TANQUE {kvp.Key} NIVEL = {kvp.Value:F3} m (DINÁMICO CALCULADO)"); + } + } + // Extraer flujos de tuberías if (resultsData.ContainsKey("pipes")) { @@ -680,7 +709,7 @@ namespace CtrEditor.HydraulicSimulator.TSNet } } - Debug.WriteLine($"TSNet: Cargados {pressures.Count} presiones y {flows.Count} flujos"); + Debug.WriteLine($"TSNet: Cargados {pressures.Count} presiones, {tankLevels.Count} niveles de tanques y {flows.Count} flujos"); } catch (Exception ex) { @@ -711,7 +740,39 @@ namespace CtrEditor.HydraulicSimulator.TSNet { try { + // Aplicar resultados hidráulicos estándar (flujos y presiones) hydraulicComponent.ApplyHydraulicResults(flows, pressures); + + // APLICAR NIVELES ESPECÍFICOS DE TANQUES CALCULADOS POR TSNET + if (obj is osHydTank tank) + { + // Buscar en tankLevels usando el nombre original del tanque + var originalName = tank.Nombre; + + // Sanitizar nombre (lógica simple inline) + var sanitizedName = originalName?.Replace(" ", "_") ?? "UnknownNode"; + + if (tankLevels.ContainsKey(originalName)) + { + var calculatedLevel = tankLevels[originalName]; + Debug.WriteLine($"TSNet: Aplicando NIVEL DINÁMICO calculado a tanque {originalName}: {calculatedLevel:F3} m"); + tank.CurrentLevelM = calculatedLevel; + Debug.WriteLine($"TSNet: Tanque {originalName} actualizado - Nivel: {tank.CurrentLevelM:F3}m, Fill%: {tank.FillPercentage:F1}%"); + } + else if (tankLevels.ContainsKey(sanitizedName)) + { + var calculatedLevel = tankLevels[sanitizedName]; + Debug.WriteLine($"TSNet: Aplicando NIVEL DINÁMICO calculado a tanque {originalName} (usando nombre sanitizado {sanitizedName}): {calculatedLevel:F3} m"); + tank.CurrentLevelM = calculatedLevel; + Debug.WriteLine($"TSNet: Tanque {originalName} actualizado - Nivel: {tank.CurrentLevelM:F3}m, Fill%: {tank.FillPercentage:F1}%"); + } + else + { + Debug.WriteLine($"TSNet: WARNING - No se encontró nivel calculado para tanque '{originalName}' ni '{sanitizedName}' en tankLevels"); + Debug.WriteLine($"TSNet: Claves disponibles en tankLevels: {string.Join(", ", tankLevels.Keys)}"); + } + } + objectsProcessed++; Debug.WriteLine($"TSNet: Resultados aplicados a {obj}"); } diff --git a/ObjetosSim/HydraulicComponents/osHydTank.cs b/ObjetosSim/HydraulicComponents/osHydTank.cs index b30f142..c915f6b 100644 --- a/ObjetosSim/HydraulicComponents/osHydTank.cs +++ b/ObjetosSim/HydraulicComponents/osHydTank.cs @@ -201,7 +201,15 @@ namespace CtrEditor.ObjetosSim public double CurrentLevel { get => _currentLevel; - private set => SetProperty(ref _currentLevel, value); + private set + { + if (SetProperty(ref _currentLevel, value)) + { + // Notificar que las propiedades dependientes también cambiaron + OnPropertyChanged(nameof(FillPercentage)); + OnPropertyChanged(nameof(CurrentVolume)); + } + } } [Category("📊 Estado Actual")] @@ -259,7 +267,16 @@ namespace CtrEditor.ObjetosSim public double CurrentLevelM { get => _currentLevel; - set => SetProperty(ref _currentLevel, value); + set + { + if (SetProperty(ref _currentLevel, value)) + { + // Notificar que las propiedades dependientes también cambiaron + OnPropertyChanged(nameof(FillPercentage)); + OnPropertyChanged(nameof(CurrentVolume)); + OnPropertyChanged(nameof(CurrentLevel)); + } + } } ///