346 lines
14 KiB
C#
346 lines
14 KiB
C#
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}");
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|
||
}
|