using System; namespace HydraulicSimulator.Models { /// /// 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 /// 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); } } /// /// Calcula el NPSH disponible basado en la presión de succión /// 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 } /// /// Verifica si la bomba puede operar sin cavitación /// public bool CanOperateWithoutCavitation(double suctionPressure, Fluid fluid) { var npshAvailable = CalculateNPSHAvailable(suctionPressure, fluid); return npshAvailable >= NPSHRequired; } /// /// Calcula el factor de reducción por cavitación (0 = cavitación total, 1 = sin cavitación) /// 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 } /// /// Verifica si la bomba puede superar la presión de descarga /// 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; } /// /// Versión mejorada de Dp que considera presiones de succión y descarga /// 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; } /// /// Versión mejorada de DdpDq que considera cavitación /// 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; } } /// /// Extensión de PumpHQ que considera verificaciones de NPSH durante la simulación /// public class PumpHQWithSuctionCheck : PumpHQ { private readonly Dictionary _pressures; private bool _npshCheckEnabled; public PumpHQWithSuctionCheck(double h0, double q0, double speedRel = 1.0, int direction = 1, Dictionary pressures = null, bool enableNpshCheck = true) : base(h0, q0, speedRel, direction) { _pressures = pressures ?? new Dictionary(); _npshCheckEnabled = enableNpshCheck; } public void UpdatePressures(Dictionary 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); } } }