13 KiB
13 KiB
📖 Documentación - Simulador Hidráulico C# .NET 8
🎯 Descripción General
Esta librería permite simular redes hidráulicas complejas con bombas, tuberías, válvulas y tanques. Está diseñada para calcular flujos y presiones en cada punto de la red, permitiendo posteriormente calcular niveles en tanques basados en los flujos calculados.
🏗️ Arquitectura de la Librería
📁 Estructura del Proyecto
HydraulicSimulator/
├── Models/ # Modelos principales
│ ├── Fluid.cs # Propiedades del fluido
│ ├── Node.cs # Nodos de la red
│ ├── Branch.cs # Ramas que conectan nodos
│ ├── Element.cs # Clase base para elementos
│ ├── Pipe.cs # Tuberías
│ ├── PumpHQ.cs # Bombas con curva H-Q
│ ├── ValveKv.cs # Válvulas con coeficiente Kv
│ ├── MinorLoss.cs # Pérdidas menores
│ └── HydraulicNetwork.cs # Red hidráulica y solver
├── Core/ # Funcionalidades auxiliares
│ ├── TestRunner.cs # Casos de prueba
│ └── InteractiveSimulator.cs # Simulador interactivo
└── Program.cs # Punto de entrada
🧱 Componentes Principales
1. Fluid - Propiedades del Fluido
var fluid = new Fluid(
rho: 1000, // Densidad (kg/m³)
mu: 0.001, // Viscosidad dinámica (Pa·s)
name: "Water", // Nombre del fluido
temp: 20.0, // Temperatura (°C)
pVapor: 2337.0 // Presión de vapor (Pa)
);
// Fluido predefinido
var water = Fluid.Water20C; // Agua a 20°C
2. Node - Nodos de la Red
// Nodo con presión fija (tanques, descargas)
var tank = new Node("TANQUE_01", fixedP: true, p: 0);
// Nodo con presión libre (calculada por el solver)
var junction = new Node("UNION_01", fixedP: false, p: 0);
Propiedades:
Name
: Nombre identificadorFixedP
: Si la presión es fija (true) o calculada (false)P
: Presión en Pascal (Pa)
3. Pipe - Tuberías
var pipe = new Pipe(
length: 100, // Longitud (m)
diameter: 0.1, // Diámetro interno (m)
roughness: 0.0015 // Rugosidad absoluta (m)
);
Características:
- Usa ecuación de Darcy-Weisbach
- Factor de fricción por Swamee-Jain
- Cálculo automático de pérdidas por fricción
4. PumpHQ - Bombas
var pump = new PumpHQ(
h0: 100, // Cabeza a caudal cero (m)
q0: 0.02, // Caudal a cabeza cero (m³/s)
speedRel: 1.0, // Velocidad relativa (1.0 = nominal)
direction: 1 // Dirección (+1 o -1)
);
Curva característica: H = H0 * (1 - (Q/Q0)²)
5. ValveKv - Válvulas
var valve = new ValveKv(
kvFull: 25, // Kv a apertura completa (m³/h/√bar)
opening: 1.0 // Apertura (0.0 a 1.0)
);
6. MinorLoss - Pérdidas Menores
var elbow = new MinorLoss(k: 0.9); // Codo 90°
var tee = new MinorLoss(k: 1.8); // Té
🔧 Uso Básico en tu Aplicación
1. Crear una Red Hidráulica
using HydraulicSimulator.Models;
// 1. Crear la red
var network = new HydraulicNetwork();
// 2. Agregar nodos
network.AddNode("TANQUE_A", 0); // Tanque a presión atmosférica
network.AddNode("UNION_1"); // Nodo intermedio
network.AddNode("PROCESO_1", 50000); // Proceso a 0.5 bar
network.AddNode("DESCARGA", 0); // Descarga a atmósfera
// 3. Crear elementos
var pump = new PumpHQ(h0: 80, q0: 0.01);
var mainPipe = new Pipe(length: 50, diameter: 0.08, roughness: 0.0015);
var valve = new ValveKv(kvFull: 20, opening: 0.8);
var dischargePipe = new Pipe(length: 30, diameter: 0.06, roughness: 0.0015);
// 4. Conectar elementos en ramas
network.AddBranch("TANQUE_A", "UNION_1",
new List<Element> { pump, mainPipe });
network.AddBranch("UNION_1", "PROCESO_1",
new List<Element> { valve });
network.AddBranch("PROCESO_1", "DESCARGA",
new List<Element> { dischargePipe });
// 5. Resolver la red
var result = network.Solve();
if (result.Converged)
{
Console.WriteLine("✅ Simulación exitosa");
network.Report(); // Mostrar resultados
}
2. Obtener Resultados
// Flujos en cada rama (m³/s)
foreach (var branch in network.Branches)
{
Console.WriteLine($"Rama {branch.Name}: {branch.Q:F6} m³/s");
}
// Presiones en cada nodo (Pa)
foreach (var node in network.Nodes)
{
double pressureBar = node.Value.P / 100000.0;
Console.WriteLine($"Nodo {node.Key}: {pressureBar:F2} bar");
}
// Resultados específicos
var flowRate = result.Flows["TANQUE_A->UNION_1"];
var pressure = result.Pressures["UNION_1"];
🎮 Integración con Objetos Gráficos
Mapeo de Objetos Gráficos a Elementos
public class GraphicToHydraulicMapper
{
public HydraulicNetwork CreateNetworkFromGraphics(List<GraphicElement> graphics)
{
var network = new HydraulicNetwork();
// 1. Agregar todos los nodos
foreach (var graphic in graphics.Where(g => g.Type == "Node"))
{
var isFixed = graphic.Properties.ContainsKey("FixedPressure");
var pressure = isFixed ? Convert.ToDouble(graphic.Properties["Pressure"]) : 0;
network.AddNode(graphic.Id, isFixed ? pressure : null);
}
// 2. Crear elementos hidráulicos desde gráficos
foreach (var graphic in graphics.Where(g => g.Type != "Node"))
{
Element element = graphic.Type switch
{
"Pump" => new PumpHQ(
h0: GetProperty<double>(graphic, "Head"),
q0: GetProperty<double>(graphic, "MaxFlow") / 3600.0 // Convertir m³/h a m³/s
),
"Pipe" => new Pipe(
length: GetProperty<double>(graphic, "Length"),
diameter: GetProperty<double>(graphic, "Diameter"),
roughness: GetProperty<double>(graphic, "Roughness", 0.0015)
),
"Valve" => new ValveKv(
kvFull: GetProperty<double>(graphic, "Kv"),
opening: GetProperty<double>(graphic, "Opening", 1.0)
),
_ => throw new NotSupportedException($"Elemento {graphic.Type} no soportado")
};
// 3. Conectar elemento entre nodos
network.AddBranch(
graphic.FromNodeId,
graphic.ToNodeId,
new List<Element> { element },
graphic.Id
);
}
return network;
}
private T GetProperty<T>(GraphicElement graphic, string key, T defaultValue = default)
{
return graphic.Properties.ContainsKey(key)
? (T)Convert.ChangeType(graphic.Properties[key], typeof(T))
: defaultValue;
}
}
🏭 Casos Especiales: Tanques y Descargas
1. Tanque de Suministro
// Tanque con nivel fijo (presión constante)
network.AddNode("TANQUE_SUMINISTRO", 0); // Presión atmosférica
// Tanque con altura (presión hidrostática)
double height = 10; // metros
double pressure = 1000 * 9.81 * height; // ρgh
network.AddNode("TANQUE_ELEVADO", pressure);
2. Tanque de Proceso (Volumen Variable)
// Nodo con presión libre - el nivel se calcula después
network.AddNode("TANQUE_PROCESO"); // Presión calculada por el solver
// Después de la simulación, calcular nivel
public double CalculateLevel(double pressure, double density = 1000)
{
return pressure / (density * 9.81); // h = P/(ρg)
}
3. Descarga a Atmósfera
// Descarga libre (presión atmosférica)
network.AddNode("DESCARGA", 0);
// Descarga con contrapresión
network.AddNode("DESCARGA_PRESURIZADA", 20000); // 0.2 bar
4. Tanque con Cálculo de Nivel Dinámico
public class Tank
{
public string NodeId { get; set; }
public double Area { get; set; } // m² - área del tanque
public double CurrentVolume { get; set; } // m³ - volumen actual
public double Height => CurrentVolume / Area; // m - altura actual
// Actualizar volumen basado en flujos
public void UpdateVolume(Dictionary<string, double> flows, double deltaTime)
{
double netFlow = 0;
// Sumar flujos entrantes, restar salientes
foreach (var flow in flows)
{
if (flow.Key.EndsWith($"->{NodeId}"))
netFlow += flow.Value; // Entrante
else if (flow.Key.StartsWith($"{NodeId}->"))
netFlow -= flow.Value; // Saliente
}
// Actualizar volumen
CurrentVolume += netFlow * deltaTime;
CurrentVolume = Math.Max(0, CurrentVolume); // No puede ser negativo
}
// Calcular presión en el fondo del tanque
public double BottomPressure(double density = 1000)
{
return density * 9.81 * Height; // Presión hidrostática
}
}
📊 Ejemplo Completo: Sistema de Mixers
public class MixerSystemSimulation
{
public void RunSimulation()
{
var network = new HydraulicNetwork();
// Nodos del sistema
network.AddNode("TANQUE_PRINCIPAL", 0); // Suministro
network.AddNode("MIXER_1", 100000); // Proceso 1 (1 bar)
network.AddNode("MIXER_2", 100000); // Proceso 2 (1 bar)
network.AddNode("DESCARGA", 50000); // Descarga (0.5 bar)
// Equipos
var pump1 = new PumpHQ(h0: 120, q0: 0.009);
var pump2 = new PumpHQ(h0: 110, q0: 0.008);
var supply1 = new Pipe(length: 50, diameter: 0.1, roughness: 0.0015);
var supply2 = new Pipe(length: 45, diameter: 0.08, roughness: 0.0015);
var valve1 = new ValveKv(kvFull: 25, opening: 1.0);
var valve2 = new ValveKv(kvFull: 20, opening: 1.0);
var discharge1 = new Pipe(length: 20, diameter: 0.06, roughness: 0.0015);
var discharge2 = new Pipe(length: 25, diameter: 0.06, roughness: 0.0015);
// Conexiones
network.AddBranch("TANQUE_PRINCIPAL", "MIXER_1",
new List<Element> { pump1, supply1, valve1 });
network.AddBranch("TANQUE_PRINCIPAL", "MIXER_2",
new List<Element> { pump2, supply2, valve2 });
network.AddBranch("MIXER_1", "DESCARGA",
new List<Element> { discharge1 });
network.AddBranch("MIXER_2", "DESCARGA",
new List<Element> { discharge2 });
// Simular
var result = network.Solve();
if (result.Converged)
{
// Procesar resultados
ProcessResults(result);
}
else
{
Console.WriteLine($"❌ No convergió después de {result.Iterations} iteraciones");
}
}
private void ProcessResults(SolutionResult result)
{
Console.WriteLine("🎯 RESULTADOS DE SIMULACIÓN:");
foreach (var flow in result.Flows)
{
double flowM3h = flow.Value * 3600; // Convertir a m³/h
Console.WriteLine($" {flow.Key}: {flowM3h:F2} m³/h");
}
foreach (var pressure in result.Pressures)
{
double pressureBar = pressure.Value / 100000.0; // Convertir a bar
Console.WriteLine($" {pressure.Key}: {pressureBar:F2} bar");
}
}
}
🚀 Ejecución desde Línea de Comandos
# Casos de prueba disponibles
dotnet run test simple-pipe --verbose
dotnet run test mixer-system --json
dotnet run test complex-network --flow 25 --pressure 30
# Ayuda
dotnet run help
⚙️ Parámetros de Configuración
Solver Parameters
var result = network.Solve(
maxIterations: 200, // Máximo de iteraciones
tolerance: 1e-4, // Tolerancia de convergencia
relaxationFactor: 0.7, // Factor de relajación
verbose: true // Mostrar progreso
);
Unidades del Sistema
- Presión: Pascal (Pa) - Convertir:
bar = Pa / 100000
- Flujo: m³/s - Convertir:
m³/h = m³/s * 3600
- Longitud: metros (m)
- Diámetro: metros (m)
- Rugosidad: metros (m)
🎯 Recomendaciones para tu Aplicación
- 📋 Mapea tus objetos gráficos a los elementos hidráulicos
- 🔄 Implementa actualización dinámica de niveles de tanques
- ⚡ Usa tolerancias apropiadas según la complejidad de tu red
- 📊 Implementa visualización de resultados en tiempo real
- 💾 Guarda configuraciones para reutilizar casos de simulación
¿Te gustaría que desarrolle alguna sección específica o que agregue ejemplos para casos particulares de tu aplicación?