254 lines
9.9 KiB
C#
254 lines
9.9 KiB
C#
using System;
|
||
|
||
namespace HydraulicSimulator.Models
|
||
{
|
||
/// <summary>
|
||
/// Bomba con curva H(Q)=H0*(1-(Q/Q0)²) y ley de afinidad con velocidad relativa
|
||
/// Incluye verificación de NPSH y condiciones de succión
|
||
/// </summary>
|
||
public class PumpHQ : Element
|
||
{
|
||
public double H0 { get; set; } // m, a velocidad nominal (shutoff head)
|
||
public double Q0 { get; set; } // m³/s, caudal a cabeza cero, vel nominal
|
||
public double SpeedRel { get; set; } = 1.0; // n / n_nominal
|
||
public int Direction { get; set; } = 1; // +1 si impulsa de i->j, -1 si al revés
|
||
|
||
// Propiedades para verificación de NPSH
|
||
public double NPSHRequired { get; set; } = 3.0; // m, NPSH requerido típico
|
||
public double VaporPressure { get; set; } = 2337.0; // Pa, presión de vapor del agua a 20°C
|
||
public double SuctionLosses { get; set; } = 0.5; // m, pérdidas en la succión
|
||
|
||
// Referencias a las presiones de los nodos para verificación
|
||
public string InletNodeName { get; set; }
|
||
public string OutletNodeName { get; set; }
|
||
|
||
public PumpHQ(double h0, double q0, double speedRel = 1.0, int direction = 1)
|
||
{
|
||
H0 = h0;
|
||
Q0 = q0;
|
||
SpeedRel = speedRel;
|
||
Direction = direction;
|
||
}
|
||
|
||
private (double H0s, double Q0s) Scaled
|
||
{
|
||
get
|
||
{
|
||
var s = Math.Max(1e-3, SpeedRel);
|
||
return (H0 * (s * s), Q0 * s);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Calcula el NPSH disponible basado en la presión de succión
|
||
/// </summary>
|
||
public double CalculateNPSHAvailable(double suctionPressure, Fluid fluid)
|
||
{
|
||
// NPSH disponible = (Presión absoluta de succión - Presión de vapor) / (ρ * g) - Pérdidas
|
||
var npshAvailable = (suctionPressure - VaporPressure) / (fluid.Rho * 9.80665) - SuctionLosses;
|
||
return Math.Max(0, npshAvailable); // No puede ser negativo
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifica si la bomba puede operar sin cavitación
|
||
/// </summary>
|
||
public bool CanOperateWithoutCavitation(double suctionPressure, Fluid fluid)
|
||
{
|
||
var npshAvailable = CalculateNPSHAvailable(suctionPressure, fluid);
|
||
return npshAvailable >= NPSHRequired;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Calcula el factor de reducción por cavitación (0 = cavitación total, 1 = sin cavitación)
|
||
/// </summary>
|
||
public double GetCavitationFactor(double suctionPressure, Fluid fluid)
|
||
{
|
||
var npshAvailable = CalculateNPSHAvailable(suctionPressure, fluid);
|
||
var ratio = npshAvailable / NPSHRequired;
|
||
|
||
if (ratio >= 1.0) return 1.0; // Sin cavitación
|
||
if (ratio <= 0.1) return 0.0; // Cavitación severa
|
||
|
||
// Transición suave entre 0.1 y 1.0
|
||
return Math.Pow(ratio, 2); // Curva cuadrática para transición suave
|
||
}
|
||
|
||
/// <summary>
|
||
/// Verifica si la bomba puede superar la presión de descarga
|
||
/// </summary>
|
||
public bool CanOvercomeDischargePressure(double suctionPressure, double dischargePressure, Fluid fluid)
|
||
{
|
||
var (h0s, _) = Scaled;
|
||
var maxPressureRise = h0s * fluid.Rho * 9.80665; // Máxima presión que puede agregar la bomba
|
||
var requiredPressureRise = dischargePressure - suctionPressure;
|
||
|
||
return maxPressureRise >= requiredPressureRise;
|
||
}
|
||
|
||
public override double Dp(double q, Fluid fluid)
|
||
{
|
||
var (h0s, q0s) = Scaled;
|
||
|
||
// Si la velocidad es muy baja o la bomba está apagada, no genera presión
|
||
if (SpeedRel < 0.01)
|
||
return 0.0;
|
||
|
||
// Limitamos fuera de rango para estabilidad
|
||
var qq = Math.Max(-q0s * 0.999, Math.Min(q0s * 0.999, q));
|
||
var h = h0s * (1.0 - Math.Pow(qq / q0s, 2));
|
||
|
||
// Calcular presión diferencial base
|
||
var dpBase = -Direction * fluid.Rho * 9.80665 * h;
|
||
|
||
// Aplicar factor de cavitación si tenemos información de presión de succión
|
||
// Nota: Esto requiere que el simulador pase las presiones de los nodos
|
||
// Por ahora, asumimos operación normal, pero el factor se aplicará en el simulador
|
||
|
||
return dpBase;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Versión mejorada de Dp que considera presiones de succión y descarga
|
||
/// </summary>
|
||
public double DpWithSuctionCheck(double q, Fluid fluid, double suctionPressure, double dischargePressure)
|
||
{
|
||
var (h0s, q0s) = Scaled;
|
||
|
||
// Si la velocidad es muy baja o la bomba está apagada, no genera presión
|
||
if (SpeedRel < 0.01)
|
||
return 0.0;
|
||
|
||
// Verificar si puede superar la presión de descarga
|
||
if (!CanOvercomeDischargePressure(suctionPressure, dischargePressure, fluid))
|
||
{
|
||
// La bomba no puede vencer la presión de descarga, flujo cero o negativo
|
||
return 0.0;
|
||
}
|
||
|
||
// Verificar NPSH y aplicar factor de cavitación
|
||
var cavitationFactor = GetCavitationFactor(suctionPressure, fluid);
|
||
|
||
if (cavitationFactor < 0.1)
|
||
{
|
||
// Cavitación severa, bomba no puede operar efectivamente
|
||
return 0.0;
|
||
}
|
||
|
||
// Limitamos fuera de rango para estabilidad
|
||
var qq = Math.Max(-q0s * 0.999, Math.Min(q0s * 0.999, q));
|
||
var h = h0s * (1.0 - Math.Pow(qq / q0s, 2));
|
||
|
||
// Aplicar factor de cavitación
|
||
h *= cavitationFactor;
|
||
|
||
var dp = -Direction * fluid.Rho * 9.80665 * h;
|
||
|
||
return dp;
|
||
}
|
||
|
||
public override double DdpDq(double q, Fluid fluid)
|
||
{
|
||
var (h0s, q0s) = Scaled;
|
||
|
||
// Si la velocidad es muy baja, derivada es cero
|
||
if (SpeedRel < 0.01)
|
||
return 1e-12;
|
||
|
||
var dhDq = -2.0 * h0s * q / (q0s * q0s);
|
||
return -Direction * fluid.Rho * 9.80665 * dhDq + 1e-12;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Versión mejorada de DdpDq que considera cavitación
|
||
/// </summary>
|
||
public double DdpDqWithSuctionCheck(double q, Fluid fluid, double suctionPressure, double dischargePressure)
|
||
{
|
||
var (h0s, q0s) = Scaled;
|
||
|
||
// Si la velocidad es muy baja, derivada es cero
|
||
if (SpeedRel < 0.01)
|
||
return 1e-12;
|
||
|
||
// Verificar condiciones de operación
|
||
if (!CanOvercomeDischargePressure(suctionPressure, dischargePressure, fluid))
|
||
return 1e-12;
|
||
|
||
var cavitationFactor = GetCavitationFactor(suctionPressure, fluid);
|
||
if (cavitationFactor < 0.1)
|
||
return 1e-12;
|
||
|
||
var dhDq = -2.0 * h0s * q / (q0s * q0s);
|
||
dhDq *= cavitationFactor; // Aplicar factor de cavitación
|
||
|
||
return -Direction * fluid.Rho * 9.80665 * dhDq + 1e-12;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Extensión de PumpHQ que considera verificaciones de NPSH durante la simulación
|
||
/// </summary>
|
||
public class PumpHQWithSuctionCheck : PumpHQ
|
||
{
|
||
private readonly Dictionary<string, double> _pressures;
|
||
private bool _npshCheckEnabled;
|
||
|
||
public PumpHQWithSuctionCheck(double h0, double q0, double speedRel = 1.0, int direction = 1,
|
||
Dictionary<string, double> pressures = null, bool enableNpshCheck = true)
|
||
: base(h0, q0, speedRel, direction)
|
||
{
|
||
_pressures = pressures ?? new Dictionary<string, double>();
|
||
_npshCheckEnabled = enableNpshCheck;
|
||
}
|
||
|
||
public void UpdatePressures(Dictionary<string, double> pressures)
|
||
{
|
||
_pressures.Clear();
|
||
if (pressures != null)
|
||
{
|
||
foreach (var kvp in pressures)
|
||
{
|
||
_pressures[kvp.Key] = kvp.Value;
|
||
}
|
||
}
|
||
}
|
||
|
||
public override double Dp(double q, Fluid fluid)
|
||
{
|
||
// Si no hay verificación de NPSH habilitada, usar el comportamiento base
|
||
if (!_npshCheckEnabled || _pressures == null || string.IsNullOrEmpty(InletNodeName) || string.IsNullOrEmpty(OutletNodeName))
|
||
{
|
||
return base.Dp(q, fluid);
|
||
}
|
||
|
||
// Obtener presiones de succión y descarga
|
||
if (_pressures.TryGetValue(InletNodeName, out double suctionPressure) &&
|
||
_pressures.TryGetValue(OutletNodeName, out double dischargePressure))
|
||
{
|
||
return DpWithSuctionCheck(q, fluid, suctionPressure, dischargePressure);
|
||
}
|
||
|
||
// Si no tenemos presiones, usar comportamiento base
|
||
return base.Dp(q, fluid);
|
||
}
|
||
|
||
public override double DdpDq(double q, Fluid fluid)
|
||
{
|
||
// Si no hay verificación de NPSH habilitada, usar el comportamiento base
|
||
if (!_npshCheckEnabled || _pressures == null || string.IsNullOrEmpty(InletNodeName) || string.IsNullOrEmpty(OutletNodeName))
|
||
{
|
||
return base.DdpDq(q, fluid);
|
||
}
|
||
|
||
// Obtener presiones de succión y descarga
|
||
if (_pressures.TryGetValue(InletNodeName, out double suctionPressure) &&
|
||
_pressures.TryGetValue(OutletNodeName, out double dischargePressure))
|
||
{
|
||
return DdpDqWithSuctionCheck(q, fluid, suctionPressure, dischargePressure);
|
||
}
|
||
|
||
// Si no tenemos presiones, usar comportamiento base
|
||
return base.DdpDq(q, fluid);
|
||
}
|
||
}
|
||
}
|