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.

This commit is contained in:
Miguel 2025-09-12 02:58:39 +02:00
parent 88c8eea35e
commit baed392d90
5 changed files with 189 additions and 13 deletions

View File

@ -318,7 +318,7 @@ try:
# Configurar WNTR options para headloss formula # Configurar WNTR options para headloss formula
wn.options.hydraulic.headloss = 'D-W' # Darcy-Weisbach 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.hydraulic_timestep = 0.01 # Paso de tiempo de 0.01 segundos
wn.options.time.report_timestep = 0.01 # Reportar cada 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: simulation_period inicial =', getattr(tm, 'simulation_period', 'N/A'))
print('TSNet: time_step inicial =', getattr(tm, 'time_step', 'N/A')) print('TSNet: time_step inicial =', getattr(tm, 'time_step', 'N/A'))
# Configurar tiempo usando API oficial de TSNet # Configurar tiempo usando API oficial de TSNet - TIEMPO OPTIMIZADO para co-simulación
simulation_time = 1.0 # 1 segundo por defecto simulation_time = 1.0 # 1 segundo para ver transferencia sin extremos
if hasattr(tm, 'simulation_period') and tm.simulation_period > 0: if hasattr(tm, 'simulation_period') and tm.simulation_period > 0:
simulation_time = tm.simulation_period simulation_time = tm.simulation_period
@ -471,6 +471,48 @@ try:
tm = tsnet.simulation.Initializer(tm, t0, engine) tm = tsnet.simulation.Initializer(tm, t0, engine)
print('TSNet: Inicialización completada') 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) # PASO 2: SIMULACIÓN TRANSITORIA (como en ejemplos oficiales)
print('TSNet: Ejecutando simulación transitoria...') print('TSNet: Ejecutando simulación transitoria...')
results_obj = 'ctreditor_results' # nombre para guardar resultados results_obj = 'ctreditor_results' # nombre para guardar resultados
@ -489,6 +531,8 @@ try:
# Extraer resultados de nodos usando API oficial # Extraer resultados de nodos usando API oficial
node_results = {{}} node_results = {{}}
tank_levels = {{}} # Específico para niveles de tanques
if hasattr(tm, 'node_name_list'): if hasattr(tm, 'node_name_list'):
for node_name in tm.node_name_list: for node_name in tm.node_name_list:
try: try:
@ -517,6 +561,38 @@ try:
node_results[node_name] = final_head 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: except Exception as e:
print('TSNet: Error extrayendo resultados de nodo ' + node_name + ': ' + str(e)) print('TSNet: Error extrayendo resultados de nodo ' + node_name + ': ' + str(e))
node_results[node_name] = 0.0 node_results[node_name] = 0.0
@ -552,6 +628,7 @@ try:
results_data = {{ results_data = {{
'nodes': node_results, 'nodes': node_results,
'pipes': pipe_results, 'pipes': pipe_results,
'tank_levels': tank_levels, # Niveles específicos de tanques calculados por TSNet
'simulation_time': tm.simulation_period, 'simulation_time': tm.simulation_period,
'time_step': tm.time_step 'time_step': tm.time_step
}} }}
@ -561,7 +638,7 @@ try:
json.dump(results_data, f, indent=2) json.dump(results_data, f, indent=2)
print('TSNet: Resultados guardados en: ' + results_file) 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 # Guardar resultados si es necesario
print('Directorio de resultados: ' + r'{outputDir}') print('Directorio de resultados: ' + r'{outputDir}')

View File

@ -19,6 +19,11 @@ namespace CtrEditor.HydraulicSimulator.TSNet.Components
// Resultados calculados por TSNet (SOLO escritura desde TSNet) // Resultados calculados por TSNet (SOLO escritura desde TSNet)
public TSNetTankResults Results { get; private set; } public TSNetTankResults Results { get; private set; }
/// <summary>
/// Acceso al objeto tanque hidráulico subyacente
/// </summary>
public osHydTank Tank => tank;
public TSNetTankAdapter(osHydTank tank) public TSNetTankAdapter(osHydTank tank)
{ {
this.tank = tank ?? throw new ArgumentNullException(nameof(tank)); this.tank = tank ?? throw new ArgumentNullException(nameof(tank));

View File

@ -17,11 +17,13 @@ namespace CtrEditor.HydraulicSimulator.TSNet
{ {
private readonly HydraulicNetwork _network; private readonly HydraulicNetwork _network;
private readonly TSNetConfiguration _config; 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)); _network = network ?? throw new ArgumentNullException(nameof(network));
_config = config ?? throw new ArgumentNullException(nameof(config)); _config = config ?? throw new ArgumentNullException(nameof(config));
_simulationManager = simulationManager; // Puede ser null para compatibilidad hacia atrás
} }
/// <summary> /// <summary>
@ -117,14 +119,28 @@ namespace CtrEditor.HydraulicSimulator.TSNet
content.AppendLine("[TANKS]"); content.AppendLine("[TANKS]");
content.AppendLine(";ID \tElevation \tInitLevel \tMinLevel \tMaxLevel \tDiameter \tMinVol \tVolCurve"); 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); var tankNodes = _network.Nodes.Values.Where(IsTank);
foreach (var node in tankNodes) foreach (var node in tankNodes)
{ {
var elevation = GetNodeElevation(node); var elevation = GetNodeElevation(node);
var sanitizedName = SanitizeNodeName(node.Name); var sanitizedName = SanitizeNodeName(node.Name);
// 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($" {sanitizedName,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t");
} }
}
content.AppendLine(); content.AppendLine();
} }

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Diagnostics; using System.Diagnostics;
using System.Windows; using System.Windows;
@ -101,8 +102,8 @@ namespace CtrEditor.HydraulicSimulator.TSNet
// Configuración por defecto // Configuración por defecto
Configuration = new TSNetConfiguration Configuration = new TSNetConfiguration
{ {
Duration = 10.0, Duration = 1.0, // 1 segundo para ver transferencia sin extremos
TimeStep = 0.01, TimeStep = 0.01, // 0.01 segundos para precisión
OutputDirectory = Path.Combine(Path.GetTempPath(), "TSNet") OutputDirectory = Path.Combine(Path.GetTempPath(), "TSNet")
}; };
@ -316,6 +317,21 @@ namespace CtrEditor.HydraulicSimulator.TSNet
return _tankAdapters.TryGetValue(objectId, out var adapter) ? adapter : null; return _tankAdapters.TryGetValue(objectId, out var adapter) ? adapter : null;
} }
/// <summary>
/// Obtiene el adaptador de tanque por nombre de nodo de la red hidráulica
/// </summary>
public TSNetTankAdapter GetTankAdapterByNodeName(string nodeName)
{
foreach (var adapter in _tankAdapters.Values)
{
if (adapter?.Tank?.Nombre == nodeName)
{
return adapter;
}
}
return null;
}
/// <summary> /// <summary>
/// Obtiene el adaptador de bomba por ID /// Obtiene el adaptador de bomba por ID
/// </summary> /// </summary>
@ -527,7 +543,7 @@ namespace CtrEditor.HydraulicSimulator.TSNet
try try
{ {
var inpPath = Path.Combine(WorkingDirectory, $"network_{DateTime.Now:yyyyMMdd_HHmmss}.inp"); 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); await inpGenerator.GenerateAsync(inpPath);
CurrentInpFile = inpPath; CurrentInpFile = inpPath;
@ -648,6 +664,7 @@ namespace CtrEditor.HydraulicSimulator.TSNet
var flows = new Dictionary<string, double>(); var flows = new Dictionary<string, double>();
var pressures = new Dictionary<string, double>(); var pressures = new Dictionary<string, double>();
var tankLevels = new Dictionary<string, double>(); // Niveles específicos de tanques
if (File.Exists(resultsFilePath)) 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<Dictionary<string, double>>(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 // Extraer flujos de tuberías
if (resultsData.ContainsKey("pipes")) 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) catch (Exception ex)
{ {
@ -711,7 +740,39 @@ namespace CtrEditor.HydraulicSimulator.TSNet
{ {
try try
{ {
// Aplicar resultados hidráulicos estándar (flujos y presiones)
hydraulicComponent.ApplyHydraulicResults(flows, pressures); 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++; objectsProcessed++;
Debug.WriteLine($"TSNet: Resultados aplicados a {obj}"); Debug.WriteLine($"TSNet: Resultados aplicados a {obj}");
} }

View File

@ -201,7 +201,15 @@ namespace CtrEditor.ObjetosSim
public double CurrentLevel public double CurrentLevel
{ {
get => _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")] [Category("📊 Estado Actual")]
@ -259,7 +267,16 @@ namespace CtrEditor.ObjetosSim
public double CurrentLevelM public double CurrentLevelM
{ {
get => _currentLevel; 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));
}
}
} }
/// <summary> /// <summary>