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 { /// /// Gestor de simulaciones TSNet que reemplaza o complementa al HydraulicSimulationManager /// public class TSNetSimulationManager : IDisposable { #region Properties and Fields /// /// Red hidráulica actual /// public HydraulicNetwork Network { get; private set; } /// /// Lista de objetos hidráulicos registrados /// public List HydraulicObjects { get; private set; } /// /// Directorio de trabajo para archivos INP y resultados /// public string WorkingDirectory { get; set; } /// /// Ruta del archivo INP actual /// public string CurrentInpFile { get; private set; } /// /// Configuración de la simulación transitoria /// public TSNetConfiguration Configuration { get; set; } /// /// Resultado de la última simulación /// public TSNetResult LastResult { get; private set; } /// /// Indica si la simulación está ejecutándose /// public bool IsRunning { get; private set; } /// /// Indica si la red necesita ser reconstruida /// public bool NetworkNeedsRebuild { get; set; } = true; /// /// Evento disparado cuando se completa una simulación /// public event EventHandler SimulationCompleted; /// /// Mapeo de objetos por ID /// private Dictionary _objectMapping; /// /// Adaptadores para tanques registrados /// private Dictionary _tankAdapters; /// /// Adaptadores para bombas registradas /// private Dictionary _pumpAdapters; /// /// Adaptadores para tuberías registradas /// private Dictionary _pipeAdapters; #endregion #region Constructor public TSNetSimulationManager() { Network = new HydraulicNetwork(); HydraulicObjects = new List(); _objectMapping = new Dictionary(); // Inicializar adaptadores _tankAdapters = new Dictionary(); _pumpAdapters = new Dictionary(); _pipeAdapters = new Dictionary(); // 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 /// /// Registra un objeto hidráulico en el sistema TSNet /// IMPORTANTE: NO se realizan cálculos locales - todo se delega a TSNet /// 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})"); } } /// /// Crea el adaptador apropiado para el objeto hidráulico /// 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; } } /// /// Desregistra un objeto hidráulico y su adaptador /// 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})"); } /// /// Reconstruye la red hidráulica a partir de los objetos registrados /// public void RebuildNetwork() { try { Network = new HydraulicNetwork(); var hydraulicComponents = HydraulicObjects.OfType().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}"); } } /// /// Obtiene el adaptador de tanque por ID /// public TSNetTankAdapter GetTankAdapter(string objectId) { return _tankAdapters.TryGetValue(objectId, out var adapter) ? adapter : null; } /// /// Obtiene el adaptador de bomba por ID /// public TSNetPumpAdapter GetPumpAdapter(string objectId) { return _pumpAdapters.TryGetValue(objectId, out var adapter) ? adapter : null; } /// /// Obtiene el adaptador de tubería por ID /// public TSNetPipeAdapter GetPipeAdapter(string objectId) { return _pipeAdapters.TryGetValue(objectId, out var adapter) ? adapter : null; } /// /// Resetea todos los valores calculados en todos los objetos /// IMPORTANTE: Solo resetea valores calculados por TSNet, NO configuraciones del usuario /// 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"); } /// /// 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 /// 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}"); } /// /// Aplica los resultados de TSNet a todos los objetos registrados /// IMPORTANTE: Esta es la ÚNICA forma de actualizar propiedades calculadas /// public void ApplyTSNetResults(Dictionary> 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(); var pressures = new Dictionary(); 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(); var pressures = new Dictionary(); 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(); var pressures = new Dictionary(); 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; } } /// /// Valida la configuración de todos los adaptadores /// public List ValidateAllConfigurations() { var allErrors = new List(); // 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 /// /// Genera el archivo INP para TSNet /// public async Task 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 /// /// Ejecuta una simulación TSNet asíncrona /// public async Task 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; } } /// /// Ejecuta una simulación continua (para tiempo real) /// 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 /// /// Aplica los resultados de TSNet a los objetos hidráulicos /// 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(); var pressures = new Dictionary(); 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 /// /// Procesa un componente hidráulico para crear nodos y elementos /// 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}"); } } /// /// Asegura que el directorio de trabajo existe /// 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 /// /// Reinicia el simulador hidráulico (equivalente a Reset del HydraulicSimulationManager) /// public void Reset() { ResetAllCalculatedValues(); Debug.WriteLine("TSNet: Simulador reiniciado"); } /// /// Obtiene estadísticas de la simulación /// public string GetSimulationStats() { return $"TSNet - Objetos: {HydraulicObjects.Count}, Tanques: {_tankAdapters.Count}, Bombas: {_pumpAdapters.Count}, Tuberías: {_pipeAdapters.Count}"; } /// /// Propiedad para habilitar/deshabilitar la simulación hidráulica /// public bool IsHydraulicSimulationEnabled { get; set; } = true; /// /// Limpia todos los objetos hidráulicos /// 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"); } /// /// Invalida la red para forzar reconstrucción /// 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 /// /// Configuración para simulaciones TSNet /// public class TSNetConfiguration { /// /// Duración de la simulación en segundos /// public double Duration { get; set; } = 10.0; /// /// Paso de tiempo en segundos /// public double TimeStep { get; set; } = 0.01; /// /// Directorio de salida para resultados /// public string OutputDirectory { get; set; } /// /// Configuración de válvulas/bombas /// public Dictionary DeviceSettings { get; set; } = new Dictionary(); } /// /// Resultado de una simulación TSNet /// 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 }