381 lines
13 KiB
C#
381 lines
13 KiB
C#
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace CtrEditor.HydraulicSimulator.Python
|
|
{
|
|
/// <summary>
|
|
/// Interoperabilidad con Python usando CPython embebido
|
|
/// Permite ejecutar scripts de TSNet desde C#
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Ruta base de la instalación de Python
|
|
/// </summary>
|
|
public static string PythonBasePath { get; set; } = @"D:\Proyectos\VisualStudio\CtrEditor\bin\Debug\net8.0-windows8.0\tsnet";
|
|
|
|
/// <summary>
|
|
/// Ruta al ejecutable de Python
|
|
/// </summary>
|
|
public static string PythonExecutable => Path.Combine(PythonBasePath, "python.exe");
|
|
|
|
/// <summary>
|
|
/// Ruta a la DLL de Python
|
|
/// </summary>
|
|
public static string PythonDll => Path.Combine(PythonBasePath, "python312.dll");
|
|
|
|
/// <summary>
|
|
/// Indica si Python está inicializado
|
|
/// </summary>
|
|
public static bool IsInitialized { get; private set; } = false;
|
|
|
|
#endregion
|
|
|
|
#region Initialization
|
|
|
|
/// <summary>
|
|
/// Inicializa el intérprete de Python embebido
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finaliza el intérprete de Python
|
|
/// </summary>
|
|
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
|
|
|
|
/// <summary>
|
|
/// Ejecuta un comando Python simple
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ejecuta un script Python desde archivo
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ejecuta un script Python con argumentos usando subprocess
|
|
/// Útil para scripts más complejos o cuando necesitamos capturar output
|
|
/// </summary>
|
|
public static async Task<PythonExecutionResult> 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
|
|
|
|
/// <summary>
|
|
/// Verifica si TSNet está disponible
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ejecuta una simulación TSNet básica
|
|
/// </summary>
|
|
public static async Task<PythonExecutionResult> 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
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resultado de la ejecución de un script Python
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
}
|