CtrEditor/HydraulicSimulator/Models/PumpHQ.cs

254 lines
9.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}