using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace CtrEditor.HydraulicSimulator.Python
{
///
/// Interoperabilidad con Python usando CPython embebido
/// Permite ejecutar scripts de TSNet desde C#
///
public static class PythonInterop
{
#region Python DLL Imports
[DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int Py_Initialize();
[DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int Py_Finalize();
[DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int PyRun_SimpleString(string command);
[DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr PyRun_String(string str, int start, IntPtr globals, IntPtr locals);
[DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr PyImport_ImportModule(string name);
[DllImport("python312.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int Py_IsInitialized();
#endregion
#region Configuration
///
/// Ruta base de la instalación de Python
///
public static string PythonBasePath { get; set; } = @"D:\Proyectos\VisualStudio\CtrEditor\bin\Debug\net8.0-windows8.0\tsnet";
///
/// Ruta al ejecutable de Python
///
public static string PythonExecutable => Path.Combine(PythonBasePath, "python.exe");
///
/// Ruta a la DLL de Python
///
public static string PythonDll => Path.Combine(PythonBasePath, "python312.dll");
///
/// Indica si Python está inicializado
///
public static bool IsInitialized { get; private set; } = false;
#endregion
#region Initialization
///
/// Inicializa el intérprete de Python embebido
///
public static bool Initialize()
{
try
{
if (IsInitialized)
return true;
// Verificar que los archivos existan
if (!File.Exists(PythonDll))
{
Debug.WriteLine($"Error: No se encuentra la DLL de Python en: {PythonDll}");
return false;
}
// Configurar el path de Python
Environment.SetEnvironmentVariable("PYTHONPATH",
$"{PythonBasePath};{Path.Combine(PythonBasePath, "Lib")};{Path.Combine(PythonBasePath, "site-packages")}");
// Inicializar Python
var result = Py_Initialize();
IsInitialized = Py_IsInitialized() != 0;
if (IsInitialized)
{
// Configurar paths de Python
var pathSetup = $@"
import sys
sys.path.insert(0, r'{PythonBasePath}')
sys.path.insert(0, r'{Path.Combine(PythonBasePath, "Lib")}')
sys.path.insert(0, r'{Path.Combine(PythonBasePath, "site-packages")}')
";
PyRun_SimpleString(pathSetup);
Debug.WriteLine("Python inicializado correctamente");
Debug.WriteLine($"PYTHONPATH: {Environment.GetEnvironmentVariable("PYTHONPATH")}");
}
else
{
Debug.WriteLine("Error al inicializar Python");
}
return IsInitialized;
}
catch (Exception ex)
{
Debug.WriteLine($"Error al inicializar Python: {ex.Message}");
return false;
}
}
///
/// Finaliza el intérprete de Python
///
public static void Finalize()
{
try
{
if (IsInitialized)
{
Py_Finalize();
IsInitialized = false;
Debug.WriteLine("Python finalizado");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error al finalizar Python: {ex.Message}");
}
}
#endregion
#region Script Execution
///
/// Ejecuta un comando Python simple
///
public static bool ExecuteCommand(string command)
{
try
{
if (!IsInitialized && !Initialize())
return false;
var result = PyRun_SimpleString(command);
return result == 0;
}
catch (Exception ex)
{
Debug.WriteLine($"Error ejecutando comando Python: {ex.Message}");
return false;
}
}
///
/// Ejecuta un script Python desde archivo
///
public static bool ExecuteScript(string scriptPath)
{
try
{
if (!File.Exists(scriptPath))
{
Debug.WriteLine($"Error: Script no encontrado: {scriptPath}");
return false;
}
var scriptContent = File.ReadAllText(scriptPath);
return ExecuteCommand(scriptContent);
}
catch (Exception ex)
{
Debug.WriteLine($"Error ejecutando script: {ex.Message}");
return false;
}
}
///
/// Ejecuta un script Python con argumentos usando subprocess
/// Útil para scripts más complejos o cuando necesitamos capturar output
///
public static async Task ExecuteScriptAsync(string scriptPath, string arguments = "")
{
try
{
// Try using cmd.exe wrapper to force proper stream handling
var startInfo = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = $"/c \"cd /d \"{PythonBasePath}\" && python.exe \"{scriptPath}\" {arguments}\"",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8
};
// Add debugging
Debug.WriteLine($"[PythonInterop] Using cmd.exe wrapper for stream capture");
Debug.WriteLine($"[PythonInterop] Command: {startInfo.Arguments}");
using var process = new Process { StartInfo = startInfo };
process.Start();
Debug.WriteLine($"[PythonInterop] Process started, PID: {process.Id}");
// Read streams synchronously
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
var output = await outputTask;
var error = await errorTask;
Debug.WriteLine($"[PythonInterop] Process completed");
Debug.WriteLine($"[PythonInterop] Exit Code: {process.ExitCode}");
Debug.WriteLine($"[PythonInterop] Output Length: {output?.Length ?? 0}");
Debug.WriteLine($"[PythonInterop] Error Length: {error?.Length ?? 0}");
Debug.WriteLine($"[PythonInterop] Raw Output: '{output}'");
Debug.WriteLine($"[PythonInterop] Raw Error: '{error}'");
return new PythonExecutionResult
{
Success = process.ExitCode == 0,
Output = output,
Error = error,
ExitCode = process.ExitCode
};
}
catch (Exception ex)
{
Debug.WriteLine($"[PythonInterop] Exception: {ex.Message}");
Debug.WriteLine($"[PythonInterop] Stack trace: {ex.StackTrace}");
return new PythonExecutionResult
{
Success = false,
Error = ex.Message,
ExitCode = -1
};
}
}
#endregion
#region TSNet Specific Methods
///
/// Verifica si TSNet está disponible
///
public static bool IsTSNetAvailable()
{
try
{
if (!IsInitialized && !Initialize())
return false;
var command = @"
try:
import tsnet
print('TSNet version:', tsnet.__version__)
result = True
except ImportError as e:
print('TSNet not available:', e)
result = False
";
return ExecuteCommand(command);
}
catch (Exception ex)
{
Debug.WriteLine($"Error verificando TSNet: {ex.Message}");
return false;
}
}
///
/// Ejecuta una simulación TSNet básica
///
public static async Task RunTSNetSimulationAsync(string inpFilePath, string outputDir)
{
var scriptContent = $@"
import tsnet
import wntr
import os
try:
# Crear directorio de salida si no existe
os.makedirs(r'{outputDir}', exist_ok=True)
# Cargar el modelo usando WNTR (TSNet usa WNTR internamente)
wn = wntr.network.WaterNetworkModel(r'{inpFilePath}')
# 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.hydraulic_timestep = 0.01 # Paso de tiempo de 0.01 segundos
wn.options.time.report_timestep = 0.01 # Reportar cada 0.01 segundos
# CORRECCIÓN: TSNet.TransientModel necesita el archivo INP, no el objeto WaterNetworkModel
# Convertir a modelo transient de TSNet usando el archivo INP directamente
tm = tsnet.network.TransientModel(r'{inpFilePath}')
# Ejecutar simulación usando la API correcta de TSNet
try:
# Método correcto de TSNet
results = tsnet.simulation.MOCSimulator(tm, results_obj='results', friction='steady')
print('Simulación TSNet completada exitosamente')
print(f'Resultados generados en modelo transient')
# Guardar resultados si es necesario
print('Directorio de resultados: ' + r'{outputDir}')
except Exception as tsnet_error:
print('Error en TSNet: ' + str(tsnet_error))
# Fallback a simulación básica con WNTR
try:
import wntr.sim
sim = wntr.sim.EpanetSimulator(wn)
results = sim.run_sim()
print('Ejecutada simulación básica WNTR (fallback)')
except Exception as wntr_error:
print('Error en WNTR fallback: ' + str(wntr_error))
raise tsnet_error
print('Simulación completada exitosamente')
print('Resultados guardados en: ' + r'{outputDir}')
except Exception as e:
print('Error en simulación TSNet: ' + str(e))
raise
";
var tempScript = Path.Combine(Path.GetTempPath(), "tsnet_simulation.py");
await File.WriteAllTextAsync(tempScript, scriptContent);
try
{
return await ExecuteScriptAsync(tempScript);
}
finally
{
if (File.Exists(tempScript))
File.Delete(tempScript);
}
}
#endregion
}
///
/// Resultado de la ejecución de un script Python
///
public class PythonExecutionResult
{
public bool Success { get; set; }
public string Output { get; set; } = "";
public string Error { get; set; } = "";
public int ExitCode { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine($"Success: {Success}");
sb.AppendLine($"ExitCode: {ExitCode}");
if (!string.IsNullOrEmpty(Output))
sb.AppendLine($"Output: {Output}");
if (!string.IsNullOrEmpty(Error))
sb.AppendLine($"Error: {Error}");
return sb.ToString();
}
}
}