CtrEditor/HydraulicSimulator/TSNet/TSNetINPGenerator.cs

347 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HydraulicSimulator.Models;
namespace CtrEditor.HydraulicSimulator.TSNet
{
/// <summary>
/// Generador de archivos INP para TSNet basado en la red hidráulica
/// Convierte el modelo interno a formato EPANET INP compatible con TSNet
/// </summary>
public class TSNetINPGenerator
{
private readonly HydraulicNetwork _network;
private readonly TSNetConfiguration _config;
public TSNetINPGenerator(HydraulicNetwork network, TSNetConfiguration config)
{
_network = network ?? throw new ArgumentNullException(nameof(network));
_config = config ?? throw new ArgumentNullException(nameof(config));
}
/// <summary>
/// Genera el archivo INP completo
/// </summary>
public async Task GenerateAsync(string filePath)
{
var content = new StringBuilder();
// Header
content.AppendLine("[TITLE]");
content.AppendLine("TSNet Hydraulic Network");
content.AppendLine($"Generated on {DateTime.Now}");
content.AppendLine("CtrEditor TSNet Integration");
content.AppendLine();
// Junctions (nodos libres)
GenerateJunctions(content);
// Reservoirs (nodos de presión fija)
GenerateReservoirs(content);
// Tanks (tanques)
GenerateTanks(content);
// Pipes (tuberías)
GeneratePipes(content);
// Pumps (bombas)
GeneratePumps(content);
// Valves (válvulas)
GenerateValves(content);
// Patterns (patrones de demanda)
GeneratePatterns(content);
// Curves (curvas de bombas)
GenerateCurves(content);
// Quality
GenerateQuality(content);
// Options
GenerateOptions(content);
// Times
GenerateTimes(content);
// Coordinates (opcional, para visualización)
GenerateCoordinates(content);
// End
content.AppendLine("[END]");
// Escribir archivo
await File.WriteAllTextAsync(filePath, content.ToString());
}
private void GenerateJunctions(StringBuilder content)
{
content.AppendLine("[JUNCTIONS]");
content.AppendLine(";ID \tElev \tDemand \tPattern ");
foreach (var node in _network.Nodes.Values.Where(n => !n.FixedP && !IsTank(n)))
{
var elevation = GetNodeElevation(node);
var demand = GetNodeDemand(node);
content.AppendLine($" {node.Name,-15}\t{elevation.ToString("F2", CultureInfo.InvariantCulture)} \t{demand.ToString("F2", CultureInfo.InvariantCulture)} \t;");
}
content.AppendLine();
}
private void GenerateReservoirs(StringBuilder content)
{
content.AppendLine("[RESERVOIRS]");
content.AppendLine(";ID \tHead \tPattern ");
foreach (var node in _network.Nodes.Values.Where(n => n.FixedP && !IsTank(n)))
{
var head = PressureToHead(node.P);
content.AppendLine($" {node.Name,-15}\t{head.ToString("F2", CultureInfo.InvariantCulture)} \t;");
}
content.AppendLine();
}
private void GenerateTanks(StringBuilder content)
{
content.AppendLine("[TANKS]");
content.AppendLine(";ID \tElevation \tInitLevel \tMinLevel \tMaxLevel \tDiameter \tMinVol \tVolCurve");
// Por ahora generar tanques dummy - será expandido cuando integremos osHydTank
var tankNodes = _network.Nodes.Values.Where(IsTank);
foreach (var node in tankNodes)
{
var elevation = GetNodeElevation(node);
content.AppendLine($" {node.Name,-15}\t{elevation:F2} \t1.0 \t0.0 \t2.0 \t1.0 \t0 \t");
}
content.AppendLine();
}
private void GeneratePipes(StringBuilder content)
{
content.AppendLine("[PIPES]");
content.AppendLine(";ID \tNode1 \tNode2 \tLength \tDiameter \tRoughness \tMinorLoss \tStatus");
int pipeId = 1;
foreach (var branch in _network.Branches)
{
foreach (var element in branch.Elements.OfType<Pipe>())
{
var id = $"PIPE{pipeId++}";
var length = element.L; // Usar L en lugar de Length
var diameter = element.D * 1000; // Usar D en lugar de Diameter, convertir a mm
var roughness = element.Rough * 1000; // Usar Rough en lugar de Roughness, convertir a mm
content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\t{length.ToString("F2", CultureInfo.InvariantCulture)} \t{diameter.ToString("F1", CultureInfo.InvariantCulture)} \t{roughness.ToString("F4", CultureInfo.InvariantCulture)} \t0 \tOpen");
}
}
content.AppendLine();
}
private void GeneratePumps(StringBuilder content)
{
content.AppendLine("[PUMPS]");
content.AppendLine(";ID \tNode1 \tNode2 \tParameters");
int pumpId = 1;
foreach (var branch in _network.Branches)
{
foreach (var element in branch.Elements.OfType<PumpHQ>())
{
var id = $"PUMP{pumpId}";
content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\tHEAD CURVE{pumpId}");
pumpId++;
}
}
content.AppendLine();
}
private void GenerateValves(StringBuilder content)
{
content.AppendLine("[VALVES]");
content.AppendLine(";ID \tNode1 \tNode2 \tDiameter \tType\tSetting \tMinorLoss ");
int valveId = 1;
foreach (var branch in _network.Branches)
{
foreach (var element in branch.Elements.OfType<ValveKv>())
{
var id = $"VALVE{valveId++}";
var diameter = EstimateDiameter(element);
content.AppendLine($" {id,-15}\t{branch.N1,-15}\t{branch.N2,-15}\t{diameter:F1} \tFCV \t{element.KvFull:F2} \t0 ");
}
}
content.AppendLine();
}
private void GeneratePatterns(StringBuilder content)
{
content.AppendLine("[PATTERNS]");
content.AppendLine(";ID \tMultipliers");
content.AppendLine(" 1 \t1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0");
content.AppendLine(" 1 \t1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0");
content.AppendLine(" 1 \t1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0");
content.AppendLine();
}
private void GenerateCurves(StringBuilder content)
{
content.AppendLine("[CURVES]");
content.AppendLine(";ID \tX-Value \tY-Value");
// Generar curvas para bombas
int curveId = 1;
foreach (var branch in _network.Branches)
{
foreach (var element in branch.Elements.OfType<PumpHQ>())
{
content.AppendLine($";PUMP CURVE {curveId}");
// Puntos de la curva H-Q (simplificada)
var maxHead = element.H0;
var maxFlow = element.H0 / 10; // Estimación simple
content.AppendLine($" CURVE{curveId} \t0 \t{maxHead:F2}");
content.AppendLine($" CURVE{curveId} \t{maxFlow/2:F2} \t{maxHead*0.8:F2}");
content.AppendLine($" CURVE{curveId} \t{maxFlow:F2} \t{maxHead*0.5:F2}");
curveId++;
}
}
content.AppendLine();
}
private void GenerateQuality(StringBuilder content)
{
content.AppendLine("[QUALITY]");
content.AppendLine(";Node \tInitQual");
foreach (var node in _network.Nodes.Values)
{
content.AppendLine($" {node.Name,-15}\t0.0");
}
content.AppendLine();
}
private void GenerateOptions(StringBuilder content)
{
content.AppendLine("[OPTIONS]");
content.AppendLine(" Units \tLPS"); // Litros por segundo
content.AppendLine(" Headloss \tD-W"); // Darcy-Weisbach
content.AppendLine(" Specific Gravity\t1.0");
content.AppendLine($" Viscosity \t{_network.Fluid.Mu.ToString("E2", CultureInfo.InvariantCulture)}");
content.AppendLine(" Trials \t40");
content.AppendLine(" Accuracy \t0.001");
content.AppendLine(" CHECKFREQ \t2");
content.AppendLine(" MAXCHECK \t10");
content.AppendLine(" DAMPLIMIT \t0");
content.AppendLine(" Unbalanced \tContinue 10");
content.AppendLine(" Pattern \t1");
content.AppendLine(" Demand Multiplier\t1.0");
content.AppendLine(" Emitter Exponent\t0.5");
content.AppendLine(" Quality \tNone mg/L");
content.AppendLine(" Diffusivity \t1.0");
content.AppendLine(" Tolerance \t0.01");
content.AppendLine();
}
private void GenerateTimes(StringBuilder content)
{
content.AppendLine("[TIMES]");
// Convert duration from seconds to H:MM:SS format
var durationTime = TimeSpan.FromSeconds(_config.Duration);
var durationStr = $"{(int)durationTime.TotalHours}:{durationTime.Minutes:00}:{durationTime.Seconds:00}";
// Convert timestep from seconds to H:MM:SS format
var timestepTime = TimeSpan.FromSeconds(_config.TimeStep);
var timestepStr = $"{(int)timestepTime.TotalHours}:{timestepTime.Minutes:00}:{timestepTime.Seconds:00}";
content.AppendLine($" Duration \t{durationStr}");
content.AppendLine($" Hydraulic Timestep\t{timestepStr}");
content.AppendLine(" Quality Timestep\t0:05:00");
content.AppendLine(" Pattern Timestep\t1:00:00");
content.AppendLine(" Pattern Start \t0:00:00");
content.AppendLine(" Report Timestep \t1:00:00");
content.AppendLine(" Report Start \t0:00:00");
content.AppendLine(" Start ClockTime \t12:00:00 AM");
content.AppendLine(" Statistic \tNone");
content.AppendLine();
}
private void GenerateCoordinates(StringBuilder content)
{
content.AppendLine("[COORDINATES]");
content.AppendLine(";Node \tX-Coord \tY-Coord");
// Generar coordenadas simples en grid
int x = 0, y = 0;
foreach (var node in _network.Nodes.Values)
{
content.AppendLine($" {node.Name,-15}\t{x:F2} \t{y:F2}");
x += 1000;
if (x > 5000)
{
x = 0;
y += 1000;
}
}
content.AppendLine();
}
#region Helper Methods
private bool IsTank(Node node)
{
// Determinar si un nodo es un tanque basado en su nombre
return node.Name.ToLower().Contains("tank") || node.Name.ToLower().Contains("tanque");
}
private double GetNodeElevation(Node node)
{
// Por defecto usar elevación de 0, será mejorado con datos reales
return 0.0;
}
private double GetNodeDemand(Node node)
{
// Por defecto sin demanda, será mejorado con datos reales
return 0.0;
}
private double PressureToHead(double pressurePa)
{
// Convertir presión en Pa a altura en metros
// H = P / (ρ * g)
var density = _network.Fluid.Rho; // Usar Rho en lugar de Density
var gravity = 9.81;
return pressurePa / (density * gravity);
}
private double EstimateDiameter(ValveKv valve)
{
// Estimación del diámetro basado en Kv
// Kv = 0.865 * A * sqrt(2*g*H) donde A = π*D²/4
// Esta es una aproximación, se mejorará con datos reales
return Math.Sqrt(valve.KvFull / 100.0) * 0.1; // en metros
}
#endregion
}
}