CtrEditor/HydraulicSimulator/TSNet/TSNetSimulationManager.cs

849 lines
31 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Diagnostics;
using CtrEditor.HydraulicSimulator.Python;
using CtrEditor.HydraulicSimulator.TSNet.Components;
using HydraulicSimulator.Models;
using CtrEditor.ObjetosSim;
namespace CtrEditor.HydraulicSimulator.TSNet
{
/// <summary>
/// Gestor de simulaciones TSNet que reemplaza o complementa al HydraulicSimulationManager
/// </summary>
public class TSNetSimulationManager : IDisposable
{
#region Properties and Fields
/// <summary>
/// Red hidráulica actual
/// </summary>
public HydraulicNetwork Network { get; private set; }
/// <summary>
/// Lista de objetos hidráulicos registrados
/// </summary>
public List<osBase> HydraulicObjects { get; private set; }
/// <summary>
/// Directorio de trabajo para archivos INP y resultados
/// </summary>
public string WorkingDirectory { get; set; }
/// <summary>
/// Ruta del archivo INP actual
/// </summary>
public string CurrentInpFile { get; private set; }
/// <summary>
/// Configuración de la simulación transitoria
/// </summary>
public TSNetConfiguration Configuration { get; set; }
/// <summary>
/// Resultado de la última simulación
/// </summary>
public TSNetResult LastResult { get; private set; }
/// <summary>
/// Indica si la simulación está ejecutándose
/// </summary>
public bool IsRunning { get; private set; }
/// <summary>
/// Indica si la red necesita ser reconstruida
/// </summary>
public bool NetworkNeedsRebuild { get; set; } = true;
/// <summary>
/// Evento disparado cuando se completa una simulación
/// </summary>
public event EventHandler<TSNetResult> SimulationCompleted;
/// <summary>
/// Mapeo de objetos por ID
/// </summary>
private Dictionary<string, osBase> _objectMapping;
/// <summary>
/// Adaptadores para tanques registrados
/// </summary>
private Dictionary<string, TSNetTankAdapter> _tankAdapters;
/// <summary>
/// Adaptadores para bombas registradas
/// </summary>
private Dictionary<string, TSNetPumpAdapter> _pumpAdapters;
/// <summary>
/// Adaptadores para tuberías registradas
/// </summary>
private Dictionary<string, TSNetPipeAdapter> _pipeAdapters;
#endregion
#region Constructor
public TSNetSimulationManager()
{
Network = new HydraulicNetwork();
HydraulicObjects = new List<osBase>();
_objectMapping = new Dictionary<string, osBase>();
// Inicializar adaptadores
_tankAdapters = new Dictionary<string, TSNetTankAdapter>();
_pumpAdapters = new Dictionary<string, TSNetPumpAdapter>();
_pipeAdapters = new Dictionary<string, TSNetPipeAdapter>();
// Configuración por defecto
Configuration = new TSNetConfiguration
{
Duration = 10.0,
TimeStep = 0.01,
OutputDirectory = Path.Combine(Path.GetTempPath(), "TSNet")
};
WorkingDirectory = Configuration.OutputDirectory;
EnsureWorkingDirectory();
// Inicializar Python si no está ya
if (!PythonInterop.Initialize())
{
Debug.WriteLine("Warning: No se pudo inicializar Python para TSNet");
}
}
#endregion
#region Network Management
/// <summary>
/// Registra un objeto hidráulico en el sistema TSNet
/// IMPORTANTE: NO se realizan cálculos locales - todo se delega a TSNet
/// </summary>
public void RegisterHydraulicObject(osBase hydraulicObject)
{
if (hydraulicObject == null) return;
// Asegurar que el objeto tenga un Id válido
hydraulicObject.CheckData();
if (!HydraulicObjects.Contains(hydraulicObject))
{
HydraulicObjects.Add(hydraulicObject);
var objectId = hydraulicObject.Id.Value.ToString();
_objectMapping[objectId] = hydraulicObject;
// Crear adaptador específico según el tipo de objeto
CreateAdapterForObject(hydraulicObject);
Debug.WriteLine($"TSNet: Objeto hidráulico registrado: {hydraulicObject.Nombre} (ID: {objectId})");
}
}
/// <summary>
/// Crea el adaptador apropiado para el objeto hidráulico
/// </summary>
private void CreateAdapterForObject(osBase hydraulicObject)
{
var objectId = hydraulicObject.Id.Value.ToString();
switch (hydraulicObject)
{
case osHydTank tank:
try
{
var tankAdapter = new TSNetTankAdapter(tank);
tankAdapter.CaptureConfigurationForSimulation(); // Capturar configuración inmediatamente
_tankAdapters[objectId] = tankAdapter;
Debug.WriteLine($"TSNet: Adaptador de tanque creado para {tank.Nombre}");
}
catch (Exception ex)
{
Debug.WriteLine($"TSNet ERROR: Error creando adaptador de tanque para {tank.Nombre}: {ex.Message}");
}
break;
case osHydPump pump:
try
{
var pumpAdapter = new TSNetPumpAdapter(pump);
pumpAdapter.CaptureConfigurationForSimulation(); // Capturar configuración inmediatamente
_pumpAdapters[objectId] = pumpAdapter;
Debug.WriteLine($"TSNet: Adaptador de bomba creado para {pump.Nombre}");
}
catch (Exception ex)
{
Debug.WriteLine($"TSNet ERROR: Error creando adaptador de bomba para {pump.Nombre}: {ex.Message}");
}
break;
case osHydPipe pipe:
try
{
var pipeAdapter = new TSNetPipeAdapter(pipe);
pipeAdapter.CaptureConfigurationForSimulation(); // Capturar configuración inmediatamente
_pipeAdapters[objectId] = pipeAdapter;
Debug.WriteLine($"TSNet: Adaptador de tubería creado para {pipe.Nombre}");
}
catch (Exception ex)
{
Debug.WriteLine($"TSNet ERROR: Error creando adaptador de tubería para {pipe.Nombre}: {ex.Message}");
}
break;
default:
Debug.WriteLine($"TSNet: Tipo de objeto no soportado: {hydraulicObject.GetType().Name}");
break;
}
}
/// <summary>
/// Desregistra un objeto hidráulico y su adaptador
/// </summary>
public void UnregisterHydraulicObject(osBase hydraulicObject)
{
if (hydraulicObject == null) return;
var objectId = hydraulicObject.Id.Value.ToString();
HydraulicObjects.Remove(hydraulicObject);
_objectMapping.Remove(objectId);
// Remover adaptadores específicos
_tankAdapters.Remove(objectId);
_pumpAdapters.Remove(objectId);
_pipeAdapters.Remove(objectId);
Debug.WriteLine($"TSNet: Objeto hidráulico desregistrado: {hydraulicObject.Nombre} (ID: {objectId})");
}
/// <summary>
/// Reconstruye la red hidráulica a partir de los objetos registrados
/// </summary>
public void RebuildNetwork()
{
try
{
Network = new HydraulicNetwork();
var hydraulicComponents = HydraulicObjects.OfType<IHydraulicComponent>().ToList();
// Primera pasada: Agregar TODOS los nodos
Debug.WriteLine("TSNet: Primera pasada - agregando todos los nodos...");
foreach (var component in hydraulicComponents)
{
try
{
var nodes = component.GetHydraulicNodes();
foreach (var nodeDefinition in nodes)
{
if (nodeDefinition.IsFixedPressure)
{
Network.AddNode(nodeDefinition.Name, nodeDefinition.Pressure);
}
else
{
Network.AddNode(nodeDefinition.Name);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error agregando nodos del componente {component}: {ex.Message}");
}
}
Debug.WriteLine($"TSNet: Nodos agregados - Total: {Network.Nodes.Count}");
Debug.WriteLine($"TSNet: Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
// Segunda pasada: Agregar TODOS los elementos
Debug.WriteLine("TSNet: Segunda pasada - agregando todos los elementos...");
foreach (var component in hydraulicComponents)
{
try
{
var elements = component.GetHydraulicElements();
foreach (var elementDefinition in elements)
{
try
{
// Verificar que los nodos existan antes de agregar el elemento
if (Network.Nodes.ContainsKey(elementDefinition.FromNode) &&
Network.Nodes.ContainsKey(elementDefinition.ToNode))
{
Network.AddElement(elementDefinition.Element,
elementDefinition.FromNode,
elementDefinition.ToNode,
elementDefinition.Name);
Debug.WriteLine($"Rama agregada: {elementDefinition.Name} ({elementDefinition.FromNode} -> {elementDefinition.ToNode})");
}
else
{
Debug.WriteLine($"ERROR: Nodo '{elementDefinition.FromNode}' o '{elementDefinition.ToNode}' no existe. " +
$"Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
}
}
catch (Exception elementEx)
{
Debug.WriteLine($"Error agregando elemento {elementDefinition.Name}: {elementEx.Message}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error obteniendo elementos del componente {component}: {ex.Message}");
}
}
Debug.WriteLine($"TSNet: Red reconstruida - {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
}
catch (Exception ex)
{
Debug.WriteLine($"Error reconstruyendo red TSNet: {ex.Message}");
}
}
/// <summary>
/// Obtiene el adaptador de tanque por ID
/// </summary>
public TSNetTankAdapter GetTankAdapter(string objectId)
{
return _tankAdapters.TryGetValue(objectId, out var adapter) ? adapter : null;
}
/// <summary>
/// Obtiene el adaptador de bomba por ID
/// </summary>
public TSNetPumpAdapter GetPumpAdapter(string objectId)
{
return _pumpAdapters.TryGetValue(objectId, out var adapter) ? adapter : null;
}
/// <summary>
/// Obtiene el adaptador de tubería por ID
/// </summary>
public TSNetPipeAdapter GetPipeAdapter(string objectId)
{
return _pipeAdapters.TryGetValue(objectId, out var adapter) ? adapter : null;
}
/// <summary>
/// Resetea todos los valores calculados en todos los objetos
/// IMPORTANTE: Solo resetea valores calculados por TSNet, NO configuraciones del usuario
/// </summary>
public void ResetAllCalculatedValues()
{
// Resetear adaptadores de tanques
foreach (var tankAdapter in _tankAdapters.Values)
{
tankAdapter.ResetCalculatedValues();
}
// Resetear adaptadores de bombas
foreach (var pumpAdapter in _pumpAdapters.Values)
{
pumpAdapter.ResetCalculatedValues();
}
// Resetear adaptadores de tuberías
foreach (var pipeAdapter in _pipeAdapters.Values)
{
pipeAdapter.ResetCalculatedValues();
}
Debug.WriteLine("TSNet: Todos los valores calculados han sido reseteados");
}
/// <summary>
/// Captura la configuración de TODOS los adaptadores al INICIO de simulación
/// Las configuraciones quedan CONGELADAS hasta que se detenga y reinicie la simulación
/// </summary>
private void CaptureAllConfigurations()
{
Debug.WriteLine("TSNet: Capturando configuraciones de todos los objetos...");
// Capturar configuración de tanques
foreach (var tankAdapter in _tankAdapters.Values)
{
tankAdapter.CaptureConfigurationForSimulation();
}
// Capturar configuración de bombas
foreach (var pumpAdapter in _pumpAdapters.Values)
{
pumpAdapter.CaptureConfigurationForSimulation();
}
// Capturar configuración de tuberías
foreach (var pipeAdapter in _pipeAdapters.Values)
{
pipeAdapter.CaptureConfigurationForSimulation();
}
Debug.WriteLine($"TSNet: Configuraciones capturadas - Tanques: {_tankAdapters.Count}, Bombas: {_pumpAdapters.Count}, Tuberías: {_pipeAdapters.Count}");
}
/// <summary>
/// Aplica los resultados de TSNet a todos los objetos registrados
/// IMPORTANTE: Esta es la ÚNICA forma de actualizar propiedades calculadas
/// </summary>
public void ApplyTSNetResults(Dictionary<string, Dictionary<string, object>> allResults)
{
if (allResults == null) return;
try
{
// Aplicar resultados a tanques
foreach (var kvp in _tankAdapters)
{
var objectId = kvp.Key;
var adapter = kvp.Value;
// Crear diccionarios vacíos para flows y pressures si no existen
var flows = new Dictionary<string, double>();
var pressures = new Dictionary<string, double>();
adapter.ApplyTSNetResults(flows, pressures);
}
// Aplicar resultados a bombas
foreach (var kvp in _pumpAdapters)
{
var objectId = kvp.Key;
var adapter = kvp.Value;
var flows = new Dictionary<string, double>();
var pressures = new Dictionary<string, double>();
adapter.ApplyTSNetResults(flows, pressures);
}
// Aplicar resultados a tuberías
foreach (var kvp in _pipeAdapters)
{
var objectId = kvp.Key;
var adapter = kvp.Value;
var flows = new Dictionary<string, double>();
var pressures = new Dictionary<string, double>();
adapter.ApplyTSNetResults(flows, pressures);
}
Debug.WriteLine($"TSNet: Resultados aplicados a {_tankAdapters.Count} tanques, {_pumpAdapters.Count} bombas, {_pipeAdapters.Count} tuberías");
}
catch (Exception ex)
{
Debug.WriteLine($"Error aplicando resultados TSNet: {ex.Message}");
throw;
}
}
/// <summary>
/// Valida la configuración de todos los adaptadores
/// </summary>
public List<string> ValidateAllConfigurations()
{
var allErrors = new List<string>();
// Verificar que los diccionarios estén inicializados
if (_tankAdapters == null || _pumpAdapters == null || _pipeAdapters == null)
{
allErrors.Add("Error interno: Adaptadores no inicializados correctamente");
return allErrors;
}
// Validar tanques
foreach (var adapter in _tankAdapters.Values)
{
if (adapter != null)
{
var errors = adapter.ValidateConfiguration();
if (errors != null)
allErrors.AddRange(errors);
}
else
{
allErrors.Add("Error interno: Adaptador de tanque nulo detectado");
}
}
// Validar bombas
foreach (var adapter in _pumpAdapters.Values)
{
if (adapter != null)
{
var errors = adapter.ValidateConfiguration();
if (errors != null)
allErrors.AddRange(errors);
}
else
{
allErrors.Add("Error interno: Adaptador de bomba nulo detectado");
}
}
// Validar tuberías
foreach (var adapter in _pipeAdapters.Values)
{
if (adapter != null)
{
var errors = adapter.ValidateConfiguration();
if (errors != null)
allErrors.AddRange(errors);
}
else
{
allErrors.Add("Error interno: Adaptador de tubería nulo detectado");
}
}
if (allErrors.Count > 0)
{
Debug.WriteLine($"TSNet: Se encontraron {allErrors.Count} errores de configuración");
foreach (var error in allErrors)
{
Debug.WriteLine($" - {error}");
}
}
return allErrors;
}
#endregion
#region INP File Generation
/// <summary>
/// Genera el archivo INP para TSNet
/// </summary>
public async Task<string> GenerateINPFileAsync()
{
try
{
var inpPath = Path.Combine(WorkingDirectory, $"network_{DateTime.Now:yyyyMMdd_HHmmss}.inp");
var inpGenerator = new TSNetINPGenerator(Network, Configuration);
await inpGenerator.GenerateAsync(inpPath);
CurrentInpFile = inpPath;
Debug.WriteLine($"TSNet: Archivo INP generado: {inpPath}");
return inpPath;
}
catch (Exception ex)
{
Debug.WriteLine($"Error generando archivo INP: {ex.Message}");
throw;
}
}
#endregion
#region Simulation
/// <summary>
/// Ejecuta una simulación TSNet asíncrona
/// </summary>
public async Task<TSNetResult> RunSimulationAsync()
{
try
{
if (IsRunning)
{
Debug.WriteLine("TSNet: Simulación ya en ejecución");
return LastResult;
}
IsRunning = true;
// PASO 1: CAPTURAR configuración de todos los adaptadores al INICIO
CaptureAllConfigurations();
// Reconstruir red si es necesario
RebuildNetwork();
// Generar archivo INP
var inpFile = await GenerateINPFileAsync();
// Ejecutar simulación TSNet
var result = await PythonInterop.RunTSNetSimulationAsync(inpFile, Configuration.OutputDirectory);
// Procesar resultados
LastResult = new TSNetResult
{
Success = result.Success,
Message = result.Success ? "Simulación completada" : result.Error,
OutputDirectory = Configuration.OutputDirectory,
PythonOutput = result.Output,
PythonError = result.Error
};
// Aplicar resultados a objetos hidráulicos
if (LastResult.Success)
{
await ApplyResultsToObjectsAsync();
}
// Disparar evento
SimulationCompleted?.Invoke(this, LastResult);
return LastResult;
}
catch (Exception ex)
{
LastResult = new TSNetResult
{
Success = false,
Message = $"Error en simulación: {ex.Message}"
};
Debug.WriteLine($"Error en simulación TSNet: {ex.Message}");
return LastResult;
}
finally
{
IsRunning = false;
}
}
/// <summary>
/// Ejecuta una simulación continua (para tiempo real)
/// </summary>
public async Task StartContinuousSimulationAsync(int intervalMs = 1000)
{
// TODO: Implementar simulación continua
// Esta será la clave para integración en tiempo real
await Task.Delay(intervalMs);
}
#endregion
#region Results Processing
/// <summary>
/// Aplica los resultados de TSNet a los objetos hidráulicos
/// </summary>
private async Task ApplyResultsToObjectsAsync()
{
try
{
// TODO: Leer archivos de resultados de TSNet y aplicar a objetos
// Por ahora, aplicamos resultados dummy
var flows = new Dictionary<string, double>();
var pressures = new Dictionary<string, double>();
foreach (var obj in HydraulicObjects)
{
if (obj is IHydraulicComponent hydraulicComponent)
{
hydraulicComponent.ApplyHydraulicResults(flows, pressures);
}
}
Debug.WriteLine("TSNet: Resultados aplicados a objetos hidráulicos");
}
catch (Exception ex)
{
Debug.WriteLine($"Error aplicando resultados: {ex.Message}");
}
}
#endregion
#region Private Methods
/// <summary>
/// Procesa un componente hidráulico para crear nodos y elementos
/// </summary>
private void ProcessHydraulicComponent(IHydraulicComponent component)
{
try
{
// Obtener nodos del componente
var nodes = component.GetHydraulicNodes();
foreach (var nodeDefinition in nodes)
{
if (nodeDefinition.IsFixedPressure)
{
Network.AddNode(nodeDefinition.Name, nodeDefinition.Pressure);
}
else
{
Network.AddNode(nodeDefinition.Name);
}
}
// Obtener elementos del componente
var elements = component.GetHydraulicElements();
foreach (var elementDefinition in elements)
{
try
{
// Verificar que los nodos existan antes de agregar el elemento
if (Network.Nodes.ContainsKey(elementDefinition.FromNode) &&
Network.Nodes.ContainsKey(elementDefinition.ToNode))
{
Network.AddElement(elementDefinition.Element,
elementDefinition.FromNode,
elementDefinition.ToNode,
elementDefinition.Name);
Debug.WriteLine($"Rama agregada: {elementDefinition.Name} ({elementDefinition.FromNode} -> {elementDefinition.ToNode})");
}
else
{
Debug.WriteLine($"ERROR: Nodo '{elementDefinition.FromNode}' o '{elementDefinition.ToNode}' no existe. " +
$"Nodos disponibles: {string.Join(", ", Network.Nodes.Keys)}");
}
}
catch (Exception elementEx)
{
Debug.WriteLine($"Error agregando elemento {elementDefinition.Name}: {elementEx.Message}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error procesando componente hidráulico {component}: {ex.Message}");
}
}
/// <summary>
/// Asegura que el directorio de trabajo existe
/// </summary>
private void EnsureWorkingDirectory()
{
try
{
if (!Directory.Exists(WorkingDirectory))
{
Directory.CreateDirectory(WorkingDirectory);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error creando directorio de trabajo: {ex.Message}");
}
}
#endregion
#region Additional Methods for Compatibility
/// <summary>
/// Reinicia el simulador hidráulico (equivalente a Reset del HydraulicSimulationManager)
/// </summary>
public void Reset()
{
ResetAllCalculatedValues();
Debug.WriteLine("TSNet: Simulador reiniciado");
}
/// <summary>
/// Obtiene estadísticas de la simulación
/// </summary>
public string GetSimulationStats()
{
return $"TSNet - Objetos: {HydraulicObjects.Count}, Tanques: {_tankAdapters.Count}, Bombas: {_pumpAdapters.Count}, Tuberías: {_pipeAdapters.Count}";
}
/// <summary>
/// Propiedad para habilitar/deshabilitar la simulación hidráulica
/// </summary>
public bool IsHydraulicSimulationEnabled { get; set; } = true;
/// <summary>
/// Limpia todos los objetos hidráulicos
/// </summary>
public void ClearHydraulicObjects()
{
HydraulicObjects.Clear();
_objectMapping.Clear();
_tankAdapters.Clear();
_pumpAdapters.Clear();
_pipeAdapters.Clear();
Network = new HydraulicNetwork();
Debug.WriteLine("TSNet: Todos los objetos hidráulicos limpiados");
}
/// <summary>
/// Invalida la red para forzar reconstrucción
/// </summary>
public void InvalidateNetwork()
{
NetworkNeedsRebuild = true;
Debug.WriteLine("TSNet: Red invalidada y marcada para reconstrucción");
}
#endregion
#region IDisposable
public void Dispose()
{
try
{
HydraulicObjects?.Clear();
_objectMapping?.Clear();
// Limpiar archivos temporales si es necesario
// Note: No finalizamos Python aquí porque puede ser usado por otros
}
catch (Exception ex)
{
Debug.WriteLine($"Error en dispose de TSNetSimulationManager: {ex.Message}");
}
}
#endregion
}
#region Supporting Classes
/// <summary>
/// Configuración para simulaciones TSNet
/// </summary>
public class TSNetConfiguration
{
/// <summary>
/// Duración de la simulación en segundos
/// </summary>
public double Duration { get; set; } = 10.0;
/// <summary>
/// Paso de tiempo en segundos
/// </summary>
public double TimeStep { get; set; } = 0.01;
/// <summary>
/// Directorio de salida para resultados
/// </summary>
public string OutputDirectory { get; set; }
/// <summary>
/// Configuración de válvulas/bombas
/// </summary>
public Dictionary<string, object> DeviceSettings { get; set; } = new Dictionary<string, object>();
}
/// <summary>
/// Resultado de una simulación TSNet
/// </summary>
public class TSNetResult
{
public bool Success { get; set; }
public string Message { get; set; }
public string OutputDirectory { get; set; }
public string PythonOutput { get; set; }
public string PythonError { get; set; }
public DateTime Timestamp { get; set; } = DateTime.Now;
}
#endregion
}