CtrEditor/Documentation/HydraulicSimulator DOC.md

13 KiB
Raw Blame History

📖 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 identificador
  • FixedP: 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

  1. 📋 Mapea tus objetos gráficos a los elementos hidráulicos
  2. 🔄 Implementa actualización dinámica de niveles de tanques
  3. Usa tolerancias apropiadas según la complejidad de tu red
  4. 📊 Implementa visualización de resultados en tiempo real
  5. 💾 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?