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));
+ }
+ }
}
///