398 lines
13 KiB
Markdown
398 lines
13 KiB
Markdown
# 📖 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
|
||
```csharp
|
||
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
|
||
```csharp
|
||
// 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
|
||
```csharp
|
||
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
|
||
```csharp
|
||
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
|
||
```csharp
|
||
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
|
||
```csharp
|
||
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**
|
||
```csharp
|
||
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**
|
||
```csharp
|
||
// 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
|
||
```csharp
|
||
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**
|
||
```csharp
|
||
// 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)**
|
||
```csharp
|
||
// 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**
|
||
```csharp
|
||
// 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**
|
||
```csharp
|
||
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
|
||
|
||
```csharp
|
||
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
|
||
|
||
```bash
|
||
# 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
|
||
```csharp
|
||
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?
|