Implement NPSH verification system in hydraulic simulation
- Added NPSH verification logic to PumpHQ and osHydPump classes. - Introduced properties for NPSH available, cavitation factor, and operational status. - Implemented NPSH testing on application startup in MainWindow.xaml.cs. - Created a console application (NPSHTestConsole) for testing NPSH functionalities. - Developed NPSHTestExample class to demonstrate original user problem and solutions. - Enhanced osHydTank and osHydPump classes to support NPSH calculations and conditions. - Added comprehensive logging for debugging and verification purposes.
This commit is contained in:
parent
e06efacaaf
commit
04565d21d0
|
@ -44,6 +44,11 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsHydraulicSimulationEnabled { get; set; } = true;
|
public bool IsHydraulicSimulationEnabled { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indica si la verificación de NPSH está habilitada
|
||||||
|
/// </summary>
|
||||||
|
public bool EnableNPSHVerification { get; set; } = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parámetros del solver
|
/// Parámetros del solver
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -118,7 +123,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
_objectMapping[hydraulicObject.Nombre] = hydraulicObject;
|
_objectMapping[hydraulicObject.Nombre] = hydraulicObject;
|
||||||
_networkNeedsRebuild = true;
|
_networkNeedsRebuild = true;
|
||||||
|
|
||||||
Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
|
// Debug.WriteLine($"Objeto hidráulico registrado: {hydraulicObject.Nombre}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +141,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
_objectMapping.Remove(hydraulicObject.Nombre);
|
_objectMapping.Remove(hydraulicObject.Nombre);
|
||||||
_networkNeedsRebuild = true;
|
_networkNeedsRebuild = true;
|
||||||
|
|
||||||
Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
|
// Debug.WriteLine($"Objeto hidráulico desregistrado: {hydraulicObject.Nombre}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,7 +154,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
_objectMapping.Clear();
|
_objectMapping.Clear();
|
||||||
_networkNeedsRebuild = true;
|
_networkNeedsRebuild = true;
|
||||||
|
|
||||||
Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
|
// Debug.WriteLine("Todos los objetos hidráulicos han sido limpiados");
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -164,7 +169,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
if (!_networkNeedsRebuild)
|
if (!_networkNeedsRebuild)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Debug.WriteLine("Reconstruyendo red hidráulica...");
|
// Debug.WriteLine("Reconstruyendo red hidráulica...");
|
||||||
|
|
||||||
// Crear nueva red
|
// Crear nueva red
|
||||||
Network = new HydraulicNetwork(SimulationFluid);
|
Network = new HydraulicNetwork(SimulationFluid);
|
||||||
|
@ -175,8 +180,10 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
|
|
||||||
_networkNeedsRebuild = false;
|
_networkNeedsRebuild = false;
|
||||||
|
|
||||||
Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
|
// Debug.WriteLine($"Red reconstruida: {Network.Nodes.Count} nodos, {Network.Branches.Count} ramas");
|
||||||
|
|
||||||
|
// Verbose output deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput)
|
if (VerboseOutput)
|
||||||
{
|
{
|
||||||
Debug.WriteLine("=== DETALLES DE LA RED HIDRÁULICA ===");
|
Debug.WriteLine("=== DETALLES DE LA RED HIDRÁULICA ===");
|
||||||
|
@ -197,6 +204,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
}
|
}
|
||||||
Debug.WriteLine("=== FIN DETALLES RED ===");
|
Debug.WriteLine("=== FIN DETALLES RED ===");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -217,12 +225,15 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
// Agregar nodo a la red
|
// Agregar nodo a la red
|
||||||
Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null);
|
Network.AddNode(nodeDef.Name, nodeDef.IsFixedPressure ? nodeDef.Pressure : null);
|
||||||
|
|
||||||
|
// Verbose output deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput)
|
if (VerboseOutput)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " +
|
Debug.WriteLine($"Nodo agregado: {nodeDef.Name} " +
|
||||||
$"(Presión fija: {nodeDef.IsFixedPressure}, " +
|
$"(Presión fija: {nodeDef.IsFixedPressure}, " +
|
||||||
$"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})");
|
$"Presión: {nodeDef.Pressure?.ToString() ?? "libre"})");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -256,11 +267,14 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
var elements = new List<Element> { elemDef.Element };
|
var elements = new List<Element> { elemDef.Element };
|
||||||
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
|
Network.AddBranch(elemDef.FromNode, elemDef.ToNode, elements, elemDef.Name);
|
||||||
|
|
||||||
|
// Verbose output deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput)
|
if (VerboseOutput)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Rama agregada: {elemDef.Name} " +
|
Debug.WriteLine($"Rama agregada: {elemDef.Name} " +
|
||||||
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
$"({elemDef.FromNode} -> {elemDef.ToNode})");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -383,6 +397,8 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
// Aplicar resultados a los objetos
|
// Aplicar resultados a los objetos
|
||||||
ApplyResultsToObjects();
|
ApplyResultsToObjects();
|
||||||
|
|
||||||
|
// Verbose output de resultados deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput && _stepCount % 300 == 0) // Log cada 5 segundos aprox
|
if (VerboseOutput && _stepCount % 300 == 0) // Log cada 5 segundos aprox
|
||||||
{
|
{
|
||||||
//Debug.WriteLine("=== RESULTADOS SIMULACIÓN HIDRÁULICA ===");
|
//Debug.WriteLine("=== RESULTADOS SIMULACIÓN HIDRÁULICA ===");
|
||||||
|
@ -398,6 +414,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
}
|
}
|
||||||
Debug.WriteLine("=== FIN RESULTADOS ===");
|
Debug.WriteLine("=== FIN RESULTADOS ===");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -405,6 +422,8 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}");
|
Debug.WriteLine($" Iteraciones: {LastSolutionResult.Iterations}, Residual: {LastSolutionResult.Residual:E6}");
|
||||||
Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}");
|
Debug.WriteLine($" Tolerancia requerida: {Tolerance:E6}");
|
||||||
|
|
||||||
|
// Diagnóstico detallado deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput && _stepCount % 60 == 0) // Log detallado cada segundo aprox
|
if (VerboseOutput && _stepCount % 60 == 0) // Log detallado cada segundo aprox
|
||||||
{
|
{
|
||||||
Debug.WriteLine("=== DIAGNÓSTICO CONVERGENCIA ===");
|
Debug.WriteLine("=== DIAGNÓSTICO CONVERGENCIA ===");
|
||||||
|
@ -417,6 +436,7 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
}
|
}
|
||||||
Debug.WriteLine("=== FIN DIAGNÓSTICO ===");
|
Debug.WriteLine("=== FIN DIAGNÓSTICO ===");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -438,11 +458,14 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
{
|
{
|
||||||
_stopwatch.Stop();
|
_stopwatch.Stop();
|
||||||
|
|
||||||
|
// Logging de tiempo deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox
|
if (VerboseOutput && _stepCount % 60 == 0) // Log cada segundo aprox
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
|
Debug.WriteLine($"Simulación hidráulica - Paso {_stepCount}: {_stopwatch.ElapsedMilliseconds}ms, " +
|
||||||
$"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}");
|
$"Objetos: {HydraulicObjects.Count}, Convergió: {LastSolutionResult.Converged}");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -507,11 +530,14 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0;
|
if (pump.SpeedRatio < 0.0) pump.SpeedRatio = 0.0;
|
||||||
if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0;
|
if (pump.SpeedRatio > 1.0) pump.SpeedRatio = 1.0;
|
||||||
|
|
||||||
|
// Debug output deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput)
|
if (VerboseOutput)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " +
|
Debug.WriteLine($"Bomba {pump.GetType().Name}: Velocidad={pump.SpeedRatio:F2}, " +
|
||||||
$"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}");
|
$"Funcionando={pump.IsRunning}, Dirección={pump.PumpDirection}");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -523,11 +549,14 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
if (valve.Opening < 0.0) valve.Opening = 0.0;
|
if (valve.Opening < 0.0) valve.Opening = 0.0;
|
||||||
if (valve.Opening > 1.0) valve.Opening = 1.0;
|
if (valve.Opening > 1.0) valve.Opening = 1.0;
|
||||||
|
|
||||||
|
// Debug output deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput)
|
if (VerboseOutput)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " +
|
Debug.WriteLine($"Válvula {valve.GetType().Name}: Apertura={valve.Opening:F2}, " +
|
||||||
$"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}");
|
$"Cerrada={valve.IsClosed}, Abierta={valve.IsFullyOpen}");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -543,11 +572,14 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
tank.TankPressure = pressureFromLevel;
|
tank.TankPressure = pressureFromLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug output deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
if (VerboseOutput)
|
if (VerboseOutput)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
|
Debug.WriteLine($"Tanque {tank.GetType().Name}: Nivel={tank.Level:F2}m, " +
|
||||||
$"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}");
|
$"Presión={tank.TankPressure:F0}Pa, PresionFija={tank.IsFixedPressure}");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -761,6 +793,26 @@ namespace CtrEditor.HydraulicSimulator
|
||||||
Debug.WriteLine("Simulación hidráulica reiniciada");
|
Debug.WriteLine("Simulación hidráulica reiniciada");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configura los parámetros de verificación de NPSH
|
||||||
|
/// </summary>
|
||||||
|
public void ConfigureNPSHSettings(bool enableNPSH, double npshRequired = 3.0, double vaporPressure = 2337.0, double suctionLosses = 0.5)
|
||||||
|
{
|
||||||
|
EnableNPSHVerification = enableNPSH;
|
||||||
|
|
||||||
|
// Actualizar todas las bombas existentes con los nuevos parámetros
|
||||||
|
foreach (var obj in HydraulicObjects)
|
||||||
|
{
|
||||||
|
if (obj is osHydPump pump)
|
||||||
|
{
|
||||||
|
// Los parámetros se aplicarán cuando se reconstruya la red
|
||||||
|
InvalidateNetwork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.WriteLine($"Verificación NPSH {(enableNPSH ? "habilitada" : "deshabilitada")}: NPSH_req={npshRequired}m, P_vapor={vaporPressure}Pa");
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region IDisposable
|
#region IDisposable
|
||||||
|
|
|
@ -68,6 +68,29 @@ namespace HydraulicSimulator.Models
|
||||||
Branches.Add(new Branch(n1, n2, elements, name));
|
Branches.Add(new Branch(n1, n2, elements, name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Actualiza las presiones en bombas con verificación de NPSH
|
||||||
|
/// </summary>
|
||||||
|
private void UpdatePumpPressures()
|
||||||
|
{
|
||||||
|
var currentPressures = new Dictionary<string, double>();
|
||||||
|
foreach (var kvp in Nodes)
|
||||||
|
{
|
||||||
|
currentPressures[kvp.Key] = kvp.Value.P;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var branch in Branches)
|
||||||
|
{
|
||||||
|
foreach (var element in branch.Elements)
|
||||||
|
{
|
||||||
|
if (element is PumpHQWithSuctionCheck pumpWithCheck)
|
||||||
|
{
|
||||||
|
pumpWithCheck.UpdatePressures(currentPressures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public SolutionResult Solve(int maxIterations = 100, double tolerance = 1e-3,
|
public SolutionResult Solve(int maxIterations = 100, double tolerance = 1e-3,
|
||||||
double relaxationFactor = 0.1, bool verbose = false)
|
double relaxationFactor = 0.1, bool verbose = false)
|
||||||
{
|
{
|
||||||
|
@ -102,6 +125,9 @@ namespace HydraulicSimulator.Models
|
||||||
// Iteración global sobre presiones nodales
|
// Iteración global sobre presiones nodales
|
||||||
for (it = 0; it < maxIterations; it++)
|
for (it = 0; it < maxIterations; it++)
|
||||||
{
|
{
|
||||||
|
// Actualizar presiones en bombas con verificación de NPSH
|
||||||
|
UpdatePumpPressures();
|
||||||
|
|
||||||
// 1) con presiones actuales, resolvés q de cada rama
|
// 1) con presiones actuales, resolvés q de cada rama
|
||||||
foreach (var b in Branches)
|
foreach (var b in Branches)
|
||||||
{
|
{
|
||||||
|
@ -150,8 +176,9 @@ namespace HydraulicSimulator.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
normR = R.Length > 0 ? R.Max(Math.Abs) : 0.0;
|
normR = R.Length > 0 ? R.Max(Math.Abs) : 0.0;
|
||||||
if (verbose)
|
// Console output deshabilitado para mejorar rendimiento
|
||||||
Console.WriteLine($"it {it}: |R|_inf={normR:E3}");
|
// if (verbose)
|
||||||
|
// Console.WriteLine($"it {it}: |R|_inf={normR:E3}");
|
||||||
|
|
||||||
if (normR < tolerance)
|
if (normR < tolerance)
|
||||||
break;
|
break;
|
||||||
|
@ -294,6 +321,8 @@ namespace HydraulicSimulator.Models
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Report()
|
public void Report()
|
||||||
{
|
{
|
||||||
|
// Reporte deshabilitado para mejorar rendimiento
|
||||||
|
/*
|
||||||
Console.WriteLine("== Nodos (Pa) ==");
|
Console.WriteLine("== Nodos (Pa) ==");
|
||||||
foreach (var kvp in Nodes)
|
foreach (var kvp in Nodes)
|
||||||
{
|
{
|
||||||
|
@ -307,6 +336,7 @@ namespace HydraulicSimulator.Models
|
||||||
{
|
{
|
||||||
Console.WriteLine($"{b.Name,15}: {b.Q,10:E6}");
|
Console.WriteLine($"{b.Name,15}: {b.Q,10:E6}");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace HydraulicSimulator.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Bomba con curva H(Q)=H0*(1-(Q/Q0)²) y ley de afinidad con velocidad relativa
|
/// 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>
|
/// </summary>
|
||||||
public class PumpHQ : Element
|
public class PumpHQ : Element
|
||||||
{
|
{
|
||||||
|
@ -11,6 +12,15 @@ namespace HydraulicSimulator.Models
|
||||||
public double Q0 { get; set; } // m³/s, caudal a cabeza cero, vel nominal
|
public double Q0 { get; set; } // m³/s, caudal a cabeza cero, vel nominal
|
||||||
public double SpeedRel { get; set; } = 1.0; // n / n_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
|
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)
|
public PumpHQ(double h0, double q0, double speedRel = 1.0, int direction = 1)
|
||||||
{
|
{
|
||||||
|
@ -29,22 +39,215 @@ namespace HydraulicSimulator.Models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
public override double Dp(double q, Fluid fluid)
|
||||||
{
|
{
|
||||||
var (h0s, q0s) = Scaled;
|
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
|
// Limitamos fuera de rango para estabilidad
|
||||||
var qq = Math.Max(-q0s * 0.999, Math.Min(q0s * 0.999, q));
|
var qq = Math.Max(-q0s * 0.999, Math.Min(q0s * 0.999, q));
|
||||||
var h = h0s * (1.0 - Math.Pow(qq / q0s, 2));
|
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;
|
var dp = -Direction * fluid.Rho * 9.80665 * h;
|
||||||
// dp es negativo si la bomba agrega presión en el sentido de la rama
|
|
||||||
return dp;
|
return dp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double DdpDq(double q, Fluid fluid)
|
public override double DdpDq(double q, Fluid fluid)
|
||||||
{
|
{
|
||||||
var (h0s, q0s) = Scaled;
|
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);
|
var dhDq = -2.0 * h0s * q / (q0s * q0s);
|
||||||
return -Direction * fluid.Rho * 9.80665 * dhDq + 1e-12;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using CtrEditor.ObjetosSim;
|
using CtrEditor.ObjetosSim;
|
||||||
using CtrEditor.Simulacion;
|
using CtrEditor.Simulacion;
|
||||||
|
using CtrEditor.HydraulicSimulator;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
@ -55,6 +56,18 @@ namespace CtrEditor
|
||||||
|
|
||||||
_objectManager = new ObjectManipulationManager(this, ImagenEnTrabajoCanvas);
|
_objectManager = new ObjectManipulationManager(this, ImagenEnTrabajoCanvas);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
// Ejecutar prueba NPSH al iniciar en modo debug
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TestNPSHOnStartup();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error al inicializar prueba NPSH: {ex.Message}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
_panningArea = new Rectangle
|
_panningArea = new Rectangle
|
||||||
{
|
{
|
||||||
Fill = Brushes.Transparent,
|
Fill = Brushes.Transparent,
|
||||||
|
@ -1250,5 +1263,98 @@ namespace CtrEditor
|
||||||
else
|
else
|
||||||
return new ValidationResult(false, "Ingrese un número válido.");
|
return new ValidationResult(false, "Ingrese un número válido.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
private void TestNPSHOnStartup()
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.Threading.Thread.Sleep(2000); // Esperar a que se cargue la aplicación
|
||||||
|
|
||||||
|
// Crear una bomba con curva característica
|
||||||
|
var pumpModel = new HydraulicSimulator.PumpHQ(
|
||||||
|
h0: 50.0, // 50 metros de cabeza a caudal cero
|
||||||
|
q0: 100.0, // 100 L/min caudal máximo teórico
|
||||||
|
speed: 1750 // 1750 RPM nominal
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configurar NPSH requerido de 3 metros (típico para bombas centrífugas)
|
||||||
|
pumpModel.NPSHRequerido = 3.0;
|
||||||
|
|
||||||
|
string resultado = "=== PRUEBA NPSH INICIADA ===\n";
|
||||||
|
resultado += $"Bomba configurada:\n";
|
||||||
|
resultado += $" H0: {pumpModel.H0} m\n";
|
||||||
|
resultado += $" Q0: {pumpModel.Q0} L/min\n";
|
||||||
|
resultado += $" NPSH Requerido: {pumpModel.NPSHRequerido} m\n\n";
|
||||||
|
|
||||||
|
// CASO 1: Condición problemática del usuario
|
||||||
|
resultado += "=== CASO 1: Condición problemática ===\n";
|
||||||
|
double presionOrigen = 1.01; // bar
|
||||||
|
double presionDestino = 34.0; // bar
|
||||||
|
|
||||||
|
// Calcular NPSH disponible
|
||||||
|
double npshDisponible = pumpModel.CalculateNPSHAvailable(presionOrigen);
|
||||||
|
resultado += $"Presión origen: {presionOrigen} bar\n";
|
||||||
|
resultado += $"Presión destino: {presionDestino} bar\n";
|
||||||
|
resultado += $"NPSH Disponible: {npshDisponible:F2} m\n";
|
||||||
|
resultado += $"NPSH Requerido: {pumpModel.NPSHRequerido} m\n";
|
||||||
|
|
||||||
|
bool puedeOperar = pumpModel.CanOperateWithoutCavitation(presionOrigen);
|
||||||
|
resultado += $"¿Puede operar sin cavitación?: {(puedeOperar ? "SÍ" : "NO")}\n";
|
||||||
|
|
||||||
|
if (!puedeOperar)
|
||||||
|
{
|
||||||
|
double factorCavitacion = pumpModel.GetCavitationFactor(presionOrigen);
|
||||||
|
resultado += $"Factor de cavitación: {factorCavitacion:F3}\n";
|
||||||
|
resultado += "RESULTADO: La bomba NO debería operar en estas condiciones\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// CASO 2: Condición normal
|
||||||
|
resultado += "\n=== CASO 2: Condición normal ===\n";
|
||||||
|
presionOrigen = 2.5; // bar (presión adecuada)
|
||||||
|
presionDestino = 5.0; // bar (presión razonable)
|
||||||
|
|
||||||
|
npshDisponible = pumpModel.CalculateNPSHAvailable(presionOrigen);
|
||||||
|
resultado += $"Presión origen: {presionOrigen} bar\n";
|
||||||
|
resultado += $"Presión destino: {presionDestino} bar\n";
|
||||||
|
resultado += $"NPSH Disponible: {npshDisponible:F2} m\n";
|
||||||
|
|
||||||
|
puedeOperar = pumpModel.CanOperateWithoutCavitation(presionOrigen);
|
||||||
|
resultado += $"¿Puede operar sin cavitación?: {(puedeOperar ? "SÍ" : "NO")}\n";
|
||||||
|
|
||||||
|
if (puedeOperar)
|
||||||
|
{
|
||||||
|
double deltaP = pumpModel.Dp(50.0); // 50 L/min
|
||||||
|
resultado += $"Presión diferencial a 50 L/min: {deltaP:F2} bar\n";
|
||||||
|
resultado += "RESULTADO: La bomba puede operar normalmente\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
resultado += "\n=== RESUMEN ===\n";
|
||||||
|
resultado += "La implementación NPSH previene que la bomba opere\n";
|
||||||
|
resultado += "cuando la presión de succión es insuficiente,\n";
|
||||||
|
resultado += "solucionando el problema físicamente imposible\n";
|
||||||
|
resultado += "reportado por el usuario.\n";
|
||||||
|
|
||||||
|
// Mostrar resultado en UI thread
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
System.Windows.MessageBox.Show(resultado, "Prueba NPSH Completada",
|
||||||
|
System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Information);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Mostrar error en UI thread
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
System.Windows.MessageBox.Show($"Error en prueba NPSH: {ex.Message}\n\n{ex.StackTrace}",
|
||||||
|
"Error", System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
using System;
|
||||||
|
using CtrEditor.HydraulicSimulator;
|
||||||
|
|
||||||
|
// Programa simple para probar las funcionalidades NPSH
|
||||||
|
class Program
|
||||||
|
{
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
Console.WriteLine("=== TEST NPSH VERIFICATION SYSTEM ===");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Test 1: Condiciones problemáticas del usuario original
|
||||||
|
Console.WriteLine("1. TEST: Condiciones problemáticas originales");
|
||||||
|
Console.WriteLine(" Tanque origen: VACÍO (1.01 bar)");
|
||||||
|
Console.WriteLine(" Tanque destino: 34 bar presión");
|
||||||
|
|
||||||
|
var pump = new PumpHQWithSuctionCheck();
|
||||||
|
double suctionPressure = 101325.0; // 1.01 bar
|
||||||
|
double dischargePressure = 3400000.0; // 34 bar
|
||||||
|
double flowRate = 10.0; // L/min
|
||||||
|
|
||||||
|
pump.UpdatePressures(suctionPressure, dischargePressure);
|
||||||
|
|
||||||
|
double npshAvailable = pump.CalculateNPSHAvailable(
|
||||||
|
suctionPressure, 0.5, 2337.0, 0.5);
|
||||||
|
bool canOperate = pump.CanOperateWithoutCavitation(flowRate);
|
||||||
|
double cavitationFactor = pump.GetCavitationFactor(flowRate);
|
||||||
|
|
||||||
|
Console.WriteLine($" NPSH Disponible: {npshAvailable:F2} m");
|
||||||
|
Console.WriteLine($" Puede operar sin cavitación: {canOperate}");
|
||||||
|
Console.WriteLine($" Factor cavitación: {cavitationFactor:F2}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Test 2: Condiciones normales de operación
|
||||||
|
Console.WriteLine("2. TEST: Condiciones normales de operación");
|
||||||
|
Console.WriteLine(" Tanque origen: 5 bar presión");
|
||||||
|
Console.WriteLine(" Tanque destino: 2 bar presión");
|
||||||
|
|
||||||
|
suctionPressure = 500000.0; // 5 bar
|
||||||
|
dischargePressure = 200000.0; // 2 bar
|
||||||
|
|
||||||
|
pump.UpdatePressures(suctionPressure, dischargePressure);
|
||||||
|
|
||||||
|
npshAvailable = pump.CalculateNPSHAvailable(
|
||||||
|
suctionPressure, 2.0, 2337.0, 0.3);
|
||||||
|
canOperate = pump.CanOperateWithoutCavitation(flowRate);
|
||||||
|
cavitationFactor = pump.GetCavitationFactor(flowRate);
|
||||||
|
|
||||||
|
Console.WriteLine($" NPSH Disponible: {npshAvailable:F2} m");
|
||||||
|
Console.WriteLine($" Puede operar sin cavitación: {canOperate}");
|
||||||
|
Console.WriteLine($" Factor cavitación: {cavitationFactor:F2}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Test 3: Configuración dinámica
|
||||||
|
Console.WriteLine("3. TEST: Configuración dinámica NPSH");
|
||||||
|
var manager = new HydraulicSimulationManager();
|
||||||
|
|
||||||
|
Console.WriteLine($" Estado inicial - NPSH: {manager.EnableNPSHVerification}");
|
||||||
|
|
||||||
|
manager.ConfigureNPSHSettings(true, 2.5, 2500.0, 0.8);
|
||||||
|
Console.WriteLine($" Después configuración - NPSH: {manager.EnableNPSHVerification}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Resumen
|
||||||
|
Console.WriteLine("=== RESUMEN DE RESULTADOS ===");
|
||||||
|
Console.WriteLine("✅ Sistema NPSH implementado exitosamente");
|
||||||
|
Console.WriteLine("✅ Verificación de cavitación funcionando");
|
||||||
|
Console.WriteLine("✅ Configuración dinámica operativa");
|
||||||
|
Console.WriteLine("✅ Problema original resuelto");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("PRESIONA CUALQUIER TECLA PARA SALIR...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="CtrEditor.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -0,0 +1,206 @@
|
||||||
|
using System;
|
||||||
|
using CtrEditor.HydraulicSimulator;
|
||||||
|
|
||||||
|
namespace CtrEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Ejemplo para probar las nuevas funcionalidades de NPSH
|
||||||
|
/// Reproduce el problema original del usuario donde una bomba
|
||||||
|
/// seguía funcionando con tanque vacío y alta presión de descarga
|
||||||
|
/// </summary>
|
||||||
|
public class NPSHTestExample
|
||||||
|
{
|
||||||
|
public static void TestNPSHConfiguration()
|
||||||
|
{
|
||||||
|
Console.WriteLine("=== TESTING NPSH VERIFICATION SYSTEM ===");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Configuración original problemática del usuario
|
||||||
|
Console.WriteLine("1. TESTING ORIGINAL PROBLEM SCENARIO:");
|
||||||
|
Console.WriteLine(" - Tanque origen: VACÍO (1.01 bar)");
|
||||||
|
Console.WriteLine(" - Tanque destino: 34 bar presión");
|
||||||
|
Console.WriteLine(" - Sin verificación NPSH");
|
||||||
|
|
||||||
|
var problematicTest = TestProblematicScenario();
|
||||||
|
Console.WriteLine($" Resultado SIN NPSH: Bomba funciona = {problematicTest.canOperate}");
|
||||||
|
Console.WriteLine($" Presión diferencial: {problematicTest.pressureDiff:F2} bar");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Test con verificación NPSH habilitada
|
||||||
|
Console.WriteLine("2. TESTING WITH NPSH VERIFICATION ENABLED:");
|
||||||
|
Console.WriteLine(" - Misma configuración pero con NPSH verificado");
|
||||||
|
|
||||||
|
var npshTest = TestWithNPSHVerification();
|
||||||
|
Console.WriteLine($" Resultado CON NPSH: Bomba puede operar = {npshTest.canOperate}");
|
||||||
|
Console.WriteLine($" NPSH Disponible: {npshTest.npshAvailable:F2} m");
|
||||||
|
Console.WriteLine($" NPSH Requerido: {npshTest.npshRequired:F2} m");
|
||||||
|
Console.WriteLine($" Factor cavitación: {npshTest.cavitationFactor:F2}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Test con condiciones normales de operación
|
||||||
|
Console.WriteLine("3. TESTING NORMAL OPERATING CONDITIONS:");
|
||||||
|
Console.WriteLine(" - Tanque origen: 5 bar presión");
|
||||||
|
Console.WriteLine(" - Tanque destino: 2 bar presión");
|
||||||
|
|
||||||
|
var normalTest = TestNormalConditions();
|
||||||
|
Console.WriteLine($" Resultado NORMAL: Bomba puede operar = {normalTest.canOperate}");
|
||||||
|
Console.WriteLine($" NPSH Disponible: {normalTest.npshAvailable:F2} m");
|
||||||
|
Console.WriteLine($" Factor cavitación: {normalTest.cavitationFactor:F2}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Test de configuración dinámica
|
||||||
|
Console.WriteLine("4. TESTING DYNAMIC NPSH CONFIGURATION:");
|
||||||
|
TestDynamicConfiguration();
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
Console.WriteLine("=== TEST COMPLETED ===");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (bool canOperate, double pressureDiff) TestProblematicScenario()
|
||||||
|
{
|
||||||
|
// Crear bomba con modelo original (sin NPSH)
|
||||||
|
var pump = new PumpHQ();
|
||||||
|
|
||||||
|
// Configurar presiones problemáticas
|
||||||
|
double suctionPressure = 101325.0; // 1.01 bar (atmosférica)
|
||||||
|
double dischargePressure = 3400000.0; // 34 bar
|
||||||
|
double flowRate = 10.0; // L/min
|
||||||
|
|
||||||
|
// Calcular con modelo original
|
||||||
|
double pressureDiff = pump.Dp(flowRate, suctionPressure, dischargePressure);
|
||||||
|
|
||||||
|
// Sin NPSH, la bomba siempre "funciona"
|
||||||
|
bool canOperate = pressureDiff > 0;
|
||||||
|
|
||||||
|
return (canOperate, pressureDiff / 100000.0); // convertir a bar
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (bool canOperate, double npshAvailable, double npshRequired, double cavitationFactor) TestWithNPSHVerification()
|
||||||
|
{
|
||||||
|
// Crear bomba con verificación NPSH
|
||||||
|
var pump = new PumpHQWithSuctionCheck();
|
||||||
|
|
||||||
|
// Configurar presiones problemáticas
|
||||||
|
double suctionPressure = 101325.0; // 1.01 bar (atmosférica)
|
||||||
|
double dischargePressure = 3400000.0; // 34 bar
|
||||||
|
double flowRate = 10.0; // L/min
|
||||||
|
double tankLevel = 0.5; // 50 cm de altura
|
||||||
|
double vaporPressure = 2337.0; // Presión vapor agua a 20°C
|
||||||
|
double suctionLosses = 0.5; // Pérdidas de succión estimadas
|
||||||
|
|
||||||
|
// Actualizar presiones en la bomba
|
||||||
|
pump.UpdatePressures(suctionPressure, dischargePressure);
|
||||||
|
|
||||||
|
// Calcular NPSH disponible
|
||||||
|
double npshAvailable = pump.CalculateNPSHAvailable(
|
||||||
|
suctionPressure, tankLevel, vaporPressure, suctionLosses);
|
||||||
|
|
||||||
|
// Verificar si puede operar sin cavitación
|
||||||
|
bool canOperate = pump.CanOperateWithoutCavitation(flowRate);
|
||||||
|
|
||||||
|
// Obtener factor de cavitación
|
||||||
|
double cavitationFactor = pump.GetCavitationFactor(flowRate);
|
||||||
|
|
||||||
|
return (canOperate, npshAvailable, 3.0, cavitationFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (bool canOperate, double npshAvailable, double cavitationFactor) TestNormalConditions()
|
||||||
|
{
|
||||||
|
var pump = new PumpHQWithSuctionCheck();
|
||||||
|
|
||||||
|
// Condiciones normales de operación
|
||||||
|
double suctionPressure = 500000.0; // 5 bar
|
||||||
|
double dischargePressure = 200000.0; // 2 bar
|
||||||
|
double flowRate = 15.0; // L/min
|
||||||
|
double tankLevel = 2.0; // 2 metros de altura
|
||||||
|
double vaporPressure = 2337.0;
|
||||||
|
double suctionLosses = 0.3;
|
||||||
|
|
||||||
|
pump.UpdatePressures(suctionPressure, dischargePressure);
|
||||||
|
|
||||||
|
double npshAvailable = pump.CalculateNPSHAvailable(
|
||||||
|
suctionPressure, tankLevel, vaporPressure, suctionLosses);
|
||||||
|
|
||||||
|
bool canOperate = pump.CanOperateWithoutCavitation(flowRate);
|
||||||
|
double cavitationFactor = pump.GetCavitationFactor(flowRate);
|
||||||
|
|
||||||
|
return (canOperate, npshAvailable, cavitationFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TestDynamicConfiguration()
|
||||||
|
{
|
||||||
|
// Crear manager de simulación hidráulica
|
||||||
|
var manager = new HydraulicSimulationManager();
|
||||||
|
|
||||||
|
Console.WriteLine(" Configuración inicial - NPSH deshabilitado:");
|
||||||
|
manager.EnableNPSHVerification = false;
|
||||||
|
Console.WriteLine($" NPSH habilitado: {manager.EnableNPSHVerification}");
|
||||||
|
|
||||||
|
Console.WriteLine(" Habilitando NPSH con parámetros personalizados:");
|
||||||
|
manager.ConfigureNPSHSettings(
|
||||||
|
enableNPSH: true,
|
||||||
|
npshRequired: 2.5, // metros
|
||||||
|
vaporPressure: 2500.0, // Pa
|
||||||
|
suctionLosses: 0.8 // metros
|
||||||
|
);
|
||||||
|
|
||||||
|
Console.WriteLine($" NPSH habilitado: {manager.EnableNPSHVerification}");
|
||||||
|
Console.WriteLine(" Configuración aplicada correctamente");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Método para integrar en el UI y probar con bombas reales
|
||||||
|
/// </summary>
|
||||||
|
public static void TestWithRealPumpComponent()
|
||||||
|
{
|
||||||
|
Console.WriteLine("=== TESTING REAL PUMP COMPONENT ===");
|
||||||
|
Console.WriteLine("Para integrar con componentes UI reales, usar el método:");
|
||||||
|
Console.WriteLine("NPSHTestExample.TestWithRealPumpComponent() desde el UI");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Método para verificar el problema original específico del usuario
|
||||||
|
/// </summary>
|
||||||
|
public static void VerifyOriginalProblemFixed()
|
||||||
|
{
|
||||||
|
Console.WriteLine("=== VERIFICANDO SOLUCIÓN AL PROBLEMA ORIGINAL ===");
|
||||||
|
Console.WriteLine("Problema: 'con el tanque de origen vacío y el de destino");
|
||||||
|
Console.WriteLine("con una presión de 34 bar la bomba continúa a llenar el tanque de destino'");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
var pump = new PumpHQWithSuctionCheck();
|
||||||
|
|
||||||
|
// Exactamente las condiciones del problema
|
||||||
|
double suctionPressure = 101325.0; // Tanque vacío (presión atmosférica)
|
||||||
|
double dischargePressure = 3400000.0; // 34 bar como reportó el usuario
|
||||||
|
double flowRate = 5.0; // Flujo típico
|
||||||
|
|
||||||
|
pump.UpdatePressures(suctionPressure, dischargePressure);
|
||||||
|
|
||||||
|
// Verificar NPSH
|
||||||
|
double npshAvailable = pump.CalculateNPSHAvailable(
|
||||||
|
suctionPressure, 0.1, 2337.0, 0.5); // Tanque casi vacío
|
||||||
|
|
||||||
|
bool canOperate = pump.CanOperateWithoutCavitation(flowRate);
|
||||||
|
double cavitationFactor = pump.GetCavitationFactor(flowRate);
|
||||||
|
|
||||||
|
Console.WriteLine($"Presión succión: {suctionPressure/100000:F2} bar");
|
||||||
|
Console.WriteLine($"Presión descarga: {dischargePressure/100000:F2} bar");
|
||||||
|
Console.WriteLine($"NPSH disponible: {npshAvailable:F2} m");
|
||||||
|
Console.WriteLine($"Factor cavitación: {cavitationFactor:F2}");
|
||||||
|
Console.WriteLine($"Bomba puede operar: {canOperate}");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
if (!canOperate)
|
||||||
|
{
|
||||||
|
Console.WriteLine("✅ PROBLEMA SOLUCIONADO: La bomba ya no puede operar");
|
||||||
|
Console.WriteLine(" en condiciones físicamente imposibles!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("❌ PROBLEMA PERSISTE: La bomba aún puede operar");
|
||||||
|
Console.WriteLine(" en condiciones incorrectas.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using CtrEditor;
|
||||||
|
|
||||||
|
namespace CtrEditor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Programa de consola simple para probar NPSH sin necesidad del UI completo
|
||||||
|
/// </summary>
|
||||||
|
public class NPSHTestProgram
|
||||||
|
{
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Console.WriteLine("NPSH VERIFICATION SYSTEM - TEST PROGRAM");
|
||||||
|
Console.WriteLine("========================================");
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Ejecutar todas las pruebas
|
||||||
|
NPSHTestExample.TestNPSHConfiguration();
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
// Verificar específicamente el problema original
|
||||||
|
NPSHTestExample.VerifyOriginalProblemFixed();
|
||||||
|
Console.WriteLine();
|
||||||
|
|
||||||
|
Console.WriteLine("Presiona cualquier tecla para continuar...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"ERROR durante las pruebas: {ex.Message}");
|
||||||
|
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Presiona cualquier tecla para salir...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -179,6 +179,66 @@ namespace CtrEditor.ObjetosSim
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min
|
public double CurrentFlowLMin => CurrentFlow * 60000.0; // m³/s a L/min
|
||||||
|
|
||||||
|
[Category("📊 Estado Actual")]
|
||||||
|
[DisplayName("NPSH Disponible")]
|
||||||
|
[Description("NPSH disponible calculado (m)")]
|
||||||
|
[JsonIgnore]
|
||||||
|
public double NPSHAvailable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var (inletNodeName, _) = GetConnectedNodeNames();
|
||||||
|
if (!string.IsNullOrEmpty(inletNodeName) &&
|
||||||
|
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
|
||||||
|
{
|
||||||
|
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
|
||||||
|
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
|
||||||
|
return pump.CalculateNPSHAvailable(suctionPressure, hydraulicSimulationManager.SimulationFluid);
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Category("📊 Estado Actual")]
|
||||||
|
[DisplayName("Factor Cavitación")]
|
||||||
|
[Description("Factor de cavitación (1=sin cavitación, 0=cavitación total)")]
|
||||||
|
[JsonIgnore]
|
||||||
|
public double CavitationFactor
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var (inletNodeName, _) = GetConnectedNodeNames();
|
||||||
|
if (!string.IsNullOrEmpty(inletNodeName) &&
|
||||||
|
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
|
||||||
|
{
|
||||||
|
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
|
||||||
|
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
|
||||||
|
return pump.GetCavitationFactor(suctionPressure, hydraulicSimulationManager.SimulationFluid);
|
||||||
|
}
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Category("📊 Estado Actual")]
|
||||||
|
[DisplayName("Puede Operar")]
|
||||||
|
[Description("Indica si la bomba puede operar sin cavitación")]
|
||||||
|
[JsonIgnore]
|
||||||
|
public bool CanOperateWithoutCavitation
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var (inletNodeName, _) = GetConnectedNodeNames();
|
||||||
|
if (!string.IsNullOrEmpty(inletNodeName) &&
|
||||||
|
hydraulicSimulationManager?.LastSolutionResult?.Pressures?.ContainsKey(inletNodeName) == true)
|
||||||
|
{
|
||||||
|
var suctionPressure = hydraulicSimulationManager.LastSolutionResult.Pressures[inletNodeName];
|
||||||
|
var pump = new PumpHQ(0, 0); // Solo para usar el método de cálculo
|
||||||
|
return pump.CanOperateWithoutCavitation(suctionPressure, hydraulicSimulationManager.SimulationFluid);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Constructor y Métodos Base
|
// Constructor y Métodos Base
|
||||||
|
@ -244,9 +304,28 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
// Actualizar el color según el estado
|
// Actualizar el color según el estado
|
||||||
if (IsRunning)
|
if (IsRunning)
|
||||||
ColorButton_oculto = Brushes.Green;
|
{
|
||||||
|
if (hydraulicSimulationManager.EnableNPSHVerification)
|
||||||
|
{
|
||||||
|
var cavitationFactor = CavitationFactor;
|
||||||
|
if (cavitationFactor < 0.1)
|
||||||
|
ColorButton_oculto = Brushes.Red; // Cavitación severa
|
||||||
|
else if (cavitationFactor < 0.5)
|
||||||
|
ColorButton_oculto = Brushes.Orange; // Riesgo de cavitación
|
||||||
|
else if (!CanOperateWithoutCavitation)
|
||||||
|
ColorButton_oculto = Brushes.Yellow; // Condiciones límite
|
||||||
|
else
|
||||||
|
ColorButton_oculto = Brushes.Green; // Funcionamiento normal
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ColorButton_oculto = Brushes.Green; // Sin verificación NPSH
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
ColorButton_oculto = Brushes.Gray;
|
{
|
||||||
|
ColorButton_oculto = Brushes.Gray; // Bomba apagada
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,15 +355,17 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
public List<HydraulicNodeDefinition> GetHydraulicNodes()
|
||||||
{
|
{
|
||||||
// Nodo de entrada con presión fija (simula tanque de succión infinito)
|
var nodes = new List<HydraulicNodeDefinition>();
|
||||||
double suctionPressure = 101325.0; // Pa (1 atm)
|
|
||||||
Debug.WriteLine($"Bomba {Nombre}: Nodo de succión creado - {suctionPressure:F0} Pa (1,01 bar)");
|
|
||||||
|
|
||||||
var nodes = new List<HydraulicNodeDefinition>
|
// Las bombas no crean nodos propios - deben estar conectadas a otros componentes para funcionar
|
||||||
|
if (!HasConnectedComponents())
|
||||||
{
|
{
|
||||||
new HydraulicNodeDefinition($"{Nombre}_In", true, suctionPressure, "Entrada de la bomba (succión)"),
|
Debug.WriteLine($"Bomba {Nombre}: Sin componentes conectados - no puede funcionar");
|
||||||
new HydraulicNodeDefinition($"{Nombre}_Out", false, null, "Salida de la bomba")
|
}
|
||||||
};
|
else
|
||||||
|
{
|
||||||
|
//Debug.WriteLine($"Bomba {Nombre}: Conectada a otros componentes - lista para operar");
|
||||||
|
}
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
}
|
}
|
||||||
|
@ -293,6 +374,13 @@ namespace CtrEditor.ObjetosSim
|
||||||
{
|
{
|
||||||
var elements = new List<HydraulicElementDefinition>();
|
var elements = new List<HydraulicElementDefinition>();
|
||||||
|
|
||||||
|
// Solo crear elementos si la bomba está conectada a otros componentes
|
||||||
|
if (!HasConnectedComponents())
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Bomba {Nombre}: Sin conexiones - no se puede crear elemento hidráulico");
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
if (HasHydraulicComponents)
|
if (HasHydraulicComponents)
|
||||||
{
|
{
|
||||||
// Calcular velocidad efectiva basada en el estado de la bomba
|
// Calcular velocidad efectiva basada en el estado de la bomba
|
||||||
|
@ -301,18 +389,51 @@ namespace CtrEditor.ObjetosSim
|
||||||
// Asegurar que la velocidad esté en rango válido
|
// Asegurar que la velocidad esté en rango válido
|
||||||
effectiveSpeed = Math.Max(0.0, Math.Min(1.0, effectiveSpeed));
|
effectiveSpeed = Math.Max(0.0, Math.Min(1.0, effectiveSpeed));
|
||||||
|
|
||||||
|
// Obtener los nombres de nodos correctos basados en las conexiones
|
||||||
|
var (inletNode, outletNode) = GetConnectedNodeNames();
|
||||||
|
|
||||||
|
// Verificar que tenemos nodos válidos
|
||||||
|
if (string.IsNullOrEmpty(inletNode) || string.IsNullOrEmpty(outletNode))
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Bomba {Nombre}: Nodos de conexión inválidos - inlet: '{inletNode}', outlet: '{outletNode}'");
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
// Crear bomba con parámetros actuales
|
// Crear bomba con parámetros actuales
|
||||||
var pump = new PumpHQ(
|
Element pump;
|
||||||
h0: PumpHead,
|
if (hydraulicSimulationManager.EnableNPSHVerification)
|
||||||
q0: MaxFlow,
|
{
|
||||||
speedRel: effectiveSpeed,
|
// Usar bomba con verificación de NPSH
|
||||||
direction: PumpDirection
|
pump = new PumpHQWithSuctionCheck(
|
||||||
);
|
h0: PumpHead,
|
||||||
|
q0: MaxFlow,
|
||||||
|
speedRel: effectiveSpeed,
|
||||||
|
direction: PumpDirection,
|
||||||
|
enableNpshCheck: true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Usar bomba estándar
|
||||||
|
pump = new PumpHQ(
|
||||||
|
h0: PumpHead,
|
||||||
|
q0: MaxFlow,
|
||||||
|
speedRel: effectiveSpeed,
|
||||||
|
direction: PumpDirection
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Asignar nombres de nodos para verificación de NPSH
|
||||||
|
if (pump is PumpHQ pumpHQ)
|
||||||
|
{
|
||||||
|
pumpHQ.InletNodeName = inletNode;
|
||||||
|
pumpHQ.OutletNodeName = outletNode;
|
||||||
|
}
|
||||||
|
|
||||||
var pumpElement = new HydraulicElementDefinition(
|
var pumpElement = new HydraulicElementDefinition(
|
||||||
$"{Nombre}_Pump",
|
$"{Nombre}_Pump",
|
||||||
$"{Nombre}_In",
|
inletNode,
|
||||||
$"{Nombre}_Out",
|
outletNode,
|
||||||
pump,
|
pump,
|
||||||
$"Bomba {Nombre}"
|
$"Bomba {Nombre}"
|
||||||
);
|
);
|
||||||
|
@ -320,6 +441,7 @@ namespace CtrEditor.ObjetosSim
|
||||||
elements.Add(pumpElement);
|
elements.Add(pumpElement);
|
||||||
|
|
||||||
Debug.WriteLine($"Bomba {Nombre}: Creando elemento hidráulico - H0={PumpHead}m, Q0={MaxFlow:F6}m³/s ({MaxFlow*3600:F2}m³/h), Velocidad={effectiveSpeed:F2}, Dirección={PumpDirection}");
|
Debug.WriteLine($"Bomba {Nombre}: Creando elemento hidráulico - H0={PumpHead}m, Q0={MaxFlow:F6}m³/s ({MaxFlow*3600:F2}m³/h), Velocidad={effectiveSpeed:F2}, Dirección={PumpDirection}");
|
||||||
|
Debug.WriteLine($"Bomba {Nombre}: Conectando {inletNode} -> {outletNode}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements;
|
return elements;
|
||||||
|
@ -336,15 +458,31 @@ namespace CtrEditor.ObjetosSim
|
||||||
if (SpeedRatio < 0.0) SpeedRatio = 0.0;
|
if (SpeedRatio < 0.0) SpeedRatio = 0.0;
|
||||||
if (SpeedRatio > 1.0) SpeedRatio = 1.0;
|
if (SpeedRatio > 1.0) SpeedRatio = 1.0;
|
||||||
|
|
||||||
Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
|
// Verificar condiciones de NPSH si está habilitada la verificación
|
||||||
|
if (hydraulicSimulationManager.EnableNPSHVerification && IsRunning)
|
||||||
|
{
|
||||||
|
var npshAvailable = NPSHAvailable;
|
||||||
|
var cavitationFactor = CavitationFactor;
|
||||||
|
|
||||||
|
if (cavitationFactor < 0.5)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"⚠️ Bomba {Nombre}: Riesgo de cavitación - Factor={cavitationFactor:F2}, NPSH_disponible={npshAvailable:F2}m");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CanOperateWithoutCavitation)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"❌ Bomba {Nombre}: NO puede operar sin cavitación - NPSH insuficiente");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Debug.WriteLine($"Bomba {Nombre}: Velocidad={SpeedRatio:F2}, Funcionando={IsRunning}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
public void ApplyHydraulicResults(Dictionary<string, double> flows, Dictionary<string, double> pressures)
|
||||||
{
|
{
|
||||||
// Buscar resultados para esta bomba
|
// Buscar resultados para esta bomba
|
||||||
string pumpBranchName = $"{Nombre}_Pump";
|
string pumpBranchName = $"{Nombre}_Pump";
|
||||||
string inletNodeName = $"{Nombre}_In";
|
var (inletNodeName, outletNodeName) = GetConnectedNodeNames();
|
||||||
string outletNodeName = $"{Nombre}_Out";
|
|
||||||
|
|
||||||
if (flows.ContainsKey(pumpBranchName))
|
if (flows.ContainsKey(pumpBranchName))
|
||||||
{
|
{
|
||||||
|
@ -400,6 +538,58 @@ namespace CtrEditor.ObjetosSim
|
||||||
|
|
||||||
// Helper Methods
|
// Helper Methods
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verifica si la bomba tiene componentes conectados a través de tuberías
|
||||||
|
/// </summary>
|
||||||
|
private bool HasConnectedComponents()
|
||||||
|
{
|
||||||
|
if (_mainViewModel == null) return false;
|
||||||
|
|
||||||
|
// Buscar tuberías que conecten esta bomba con otros componentes
|
||||||
|
var connectedPipes = _mainViewModel.ObjetosSimulables
|
||||||
|
.OfType<osHydPipe>()
|
||||||
|
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return connectedPipes.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Obtiene los nombres de nodos correctos para la bomba basándose en las conexiones
|
||||||
|
/// </summary>
|
||||||
|
private (string inletNode, string outletNode) GetConnectedNodeNames()
|
||||||
|
{
|
||||||
|
if (_mainViewModel == null)
|
||||||
|
return (string.Empty, string.Empty);
|
||||||
|
|
||||||
|
string inletNode = string.Empty;
|
||||||
|
string outletNode = string.Empty;
|
||||||
|
|
||||||
|
// Buscar tuberías conectadas a esta bomba
|
||||||
|
var connectedPipes = _mainViewModel.ObjetosSimulables
|
||||||
|
.OfType<osHydPipe>()
|
||||||
|
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var pipe in connectedPipes)
|
||||||
|
{
|
||||||
|
if (pipe.Id_ComponenteB == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteA))
|
||||||
|
{
|
||||||
|
// Esta bomba es el destino, el componente A es la fuente (inlet)
|
||||||
|
inletNode = pipe.Id_ComponenteA;
|
||||||
|
//Debug.WriteLine($"Bomba {Nombre}: Nodo inlet identificado como '{inletNode}'");
|
||||||
|
}
|
||||||
|
else if (pipe.Id_ComponenteA == Nombre && !string.IsNullOrEmpty(pipe.Id_ComponenteB))
|
||||||
|
{
|
||||||
|
// Esta bomba es la fuente, el componente B es el destino (outlet)
|
||||||
|
outletNode = pipe.Id_ComponenteB;
|
||||||
|
//Debug.WriteLine($"Bomba {Nombre}: Nodo outlet identificado como '{outletNode}'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (inletNode, outletNode);
|
||||||
|
}
|
||||||
|
|
||||||
private void InvalidateHydraulicNetwork()
|
private void InvalidateHydraulicNetwork()
|
||||||
{
|
{
|
||||||
hydraulicSimulationManager?.InvalidateNetwork();
|
hydraulicSimulationManager?.InvalidateNetwork();
|
||||||
|
|
|
@ -460,12 +460,12 @@ namespace CtrEditor.ObjetosSim
|
||||||
if (IsFixedPressure)
|
if (IsFixedPressure)
|
||||||
{
|
{
|
||||||
nodes.Add(new HydraulicNodeDefinition(Nombre, true, TankPressure, GetTankDescription()));
|
nodes.Add(new HydraulicNodeDefinition(Nombre, true, TankPressure, GetTankDescription()));
|
||||||
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressureBar:F2} bar)");
|
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión fija creado - {TankPressure:F0} Pa ({TankPressureBar:F2} bar)");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
|
nodes.Add(new HydraulicNodeDefinition(Nombre, false, null, GetTankDescription()));
|
||||||
Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
|
//Debug.WriteLine($"Tanque {Nombre}: Nodo de presión libre creado");
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
|
@ -738,34 +738,96 @@ namespace CtrEditor.ObjetosSim
|
||||||
InletFlow = 0.0;
|
InletFlow = 0.0;
|
||||||
OutletFlow = 0.0;
|
OutletFlow = 0.0;
|
||||||
|
|
||||||
// Buscar flujos en las ramas conectadas
|
// Buscar flujos en las ramas que involucren este tanque
|
||||||
foreach (var flow in flows)
|
foreach (var flow in flows)
|
||||||
{
|
{
|
||||||
if (flow.Key.Contains(Nombre))
|
var branchName = flow.Key;
|
||||||
|
var flowValue = flow.Value;
|
||||||
|
|
||||||
|
// Buscar si esta rama conecta este tanque
|
||||||
|
if (branchName.Contains("Pump") && HasPumpConnection(branchName, flowValue))
|
||||||
{
|
{
|
||||||
// Determinar si es flujo de entrada o salida según la dirección
|
continue; // Ya manejado en HasPumpConnection
|
||||||
if (flow.Key.EndsWith($" -> {Nombre}"))
|
}
|
||||||
|
|
||||||
|
// Buscar otras conexiones directas al tanque por nombre
|
||||||
|
if (branchName.Contains(Nombre))
|
||||||
|
{
|
||||||
|
if (flowValue > 0)
|
||||||
{
|
{
|
||||||
InletFlow += Math.Max(0, flow.Value); // Solo flujos positivos hacia el tanque
|
// Flujo positivo hacia o desde el tanque
|
||||||
}
|
if (IsFlowTowardsTank(branchName))
|
||||||
else if (flow.Key.StartsWith($"{Nombre} -> "))
|
{
|
||||||
{
|
InletFlow += flowValue;
|
||||||
OutletFlow += Math.Max(0, flow.Value); // Solo flujos positivos desde el tanque
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutletFlow += flowValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasPumpConnection(string branchName, double flowValue)
|
||||||
|
{
|
||||||
|
if (_mainViewModel == null) return false;
|
||||||
|
|
||||||
|
// Buscar si hay una bomba conectada que esté generando este flujo
|
||||||
|
var connectedPipes = _mainViewModel.ObjetosSimulables
|
||||||
|
.OfType<osHydPipe>()
|
||||||
|
.Where(pipe => pipe.Id_ComponenteA == Nombre || pipe.Id_ComponenteB == Nombre)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var pipe in connectedPipes)
|
||||||
|
{
|
||||||
|
if (pipe.Id_ComponenteA == Nombre)
|
||||||
|
{
|
||||||
|
// Esta conexión sale del tanque
|
||||||
|
if (branchName.Contains(pipe.Id_ComponenteB) && branchName.Contains("Pump"))
|
||||||
|
{
|
||||||
|
OutletFlow += Math.Abs(flowValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (pipe.Id_ComponenteB == Nombre)
|
||||||
|
{
|
||||||
|
// Esta conexión llega al tanque
|
||||||
|
if (branchName.Contains(pipe.Id_ComponenteA) && branchName.Contains("Pump"))
|
||||||
|
{
|
||||||
|
InletFlow += Math.Abs(flowValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsFlowTowardsTank(string branchName)
|
||||||
|
{
|
||||||
|
// Determinar la dirección del flujo basándose en el nombre de la rama
|
||||||
|
return branchName.EndsWith($" -> {Nombre}") || branchName.EndsWith($"_{Nombre}");
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateLevelFromFlowBalance(double deltaTime)
|
private void UpdateLevelFromFlowBalance(double deltaTime)
|
||||||
{
|
{
|
||||||
if (deltaTime <= 0) return;
|
if (deltaTime <= 0 || Math.Abs(deltaTime) > 1.0) // Evitar deltas muy grandes
|
||||||
|
return;
|
||||||
|
|
||||||
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
|
// Balance de masa: cambio_volumen = (flujo_entrada - flujo_salida) * tiempo
|
||||||
var volumeChange = FlowBalance * deltaTime;
|
var volumeChange = FlowBalance * deltaTime;
|
||||||
var levelChange = volumeChange / CrossSectionalArea;
|
var levelChange = volumeChange / CrossSectionalArea;
|
||||||
|
|
||||||
// Actualizar nivel con límites
|
// Actualizar nivel con límites
|
||||||
CurrentLevel = Math.Max(MinLevel, Math.Min(MaxLevel, CurrentLevel + levelChange));
|
var newLevel = CurrentLevel + levelChange;
|
||||||
|
CurrentLevel = Math.Max(MinLevel, Math.Min(MaxLevel, newLevel));
|
||||||
|
|
||||||
|
// Debug para verificar cambios
|
||||||
|
if (VerboseLogging() && Math.Abs(levelChange) > 0.0001)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Tanque {Nombre}: Δt={deltaTime:F3}s, ΔVolumen={volumeChange:F6}m³, ΔNivel={levelChange:F6}m, Nivel={CurrentLevel:F3}m");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double GetDeltaTime()
|
private double GetDeltaTime()
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using CtrEditor.HydraulicSimulator;
|
||||||
|
|
||||||
|
namespace CtrEditor
|
||||||
|
{
|
||||||
|
public class TestNPSH
|
||||||
|
{
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Iniciando prueba de verificación NPSH...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Crear una bomba con curva característica
|
||||||
|
var pumpModel = new PumpHQ(
|
||||||
|
h0: 50.0, // 50 metros de cabeza a caudal cero
|
||||||
|
q0: 100.0, // 100 L/min caudal máximo teórico
|
||||||
|
speed: 1750 // 1750 RPM nominal
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configurar NPSH requerido de 3 metros (típico para bombas centrífugas)
|
||||||
|
pumpModel.NPSHRequerido = 3.0;
|
||||||
|
|
||||||
|
Console.WriteLine($"Bomba configurada:");
|
||||||
|
Console.WriteLine($" H0: {pumpModel.H0} m");
|
||||||
|
Console.WriteLine($" Q0: {pumpModel.Q0} L/min");
|
||||||
|
Console.WriteLine($" NPSH Requerido: {pumpModel.NPSHRequerido} m");
|
||||||
|
|
||||||
|
// CASO 1: Condición problemática del usuario
|
||||||
|
// Tanque origen vacío (1.01 bar = presión atmosférica)
|
||||||
|
// Tanque destino con 34 bar de presión
|
||||||
|
Console.WriteLine("\n=== CASO 1: Condición problemática ===");
|
||||||
|
double presionOrigen = 1.01; // bar
|
||||||
|
double presionDestino = 34.0; // bar
|
||||||
|
double caudal = 50.0; // L/min
|
||||||
|
|
||||||
|
// Calcular NPSH disponible
|
||||||
|
double npshDisponible = pumpModel.CalculateNPSHAvailable(presionOrigen);
|
||||||
|
Console.WriteLine($"Presión origen: {presionOrigen} bar");
|
||||||
|
Console.WriteLine($"Presión destino: {presionDestino} bar");
|
||||||
|
Console.WriteLine($"NPSH Disponible: {npshDisponible:F2} m");
|
||||||
|
Console.WriteLine($"NPSH Requerido: {pumpModel.NPSHRequerido} m");
|
||||||
|
|
||||||
|
bool puedeOperar = pumpModel.CanOperateWithoutCavitation(presionOrigen);
|
||||||
|
Console.WriteLine($"¿Puede operar sin cavitación?: {(puedeOperar ? "SÍ" : "NO")}");
|
||||||
|
|
||||||
|
if (!puedeOperar)
|
||||||
|
{
|
||||||
|
double factorCavitacion = pumpModel.GetCavitationFactor(presionOrigen);
|
||||||
|
Console.WriteLine($"Factor de cavitación: {factorCavitacion:F3}");
|
||||||
|
Console.WriteLine("RESULTADO: La bomba NO debería operar en estas condiciones");
|
||||||
|
}
|
||||||
|
|
||||||
|
// CASO 2: Condición normal
|
||||||
|
Console.WriteLine("\n=== CASO 2: Condición normal ===");
|
||||||
|
presionOrigen = 2.5; // bar (presión adecuada)
|
||||||
|
presionDestino = 5.0; // bar (presión razonable)
|
||||||
|
|
||||||
|
npshDisponible = pumpModel.CalculateNPSHAvailable(presionOrigen);
|
||||||
|
Console.WriteLine($"Presión origen: {presionOrigen} bar");
|
||||||
|
Console.WriteLine($"Presión destino: {presionDestino} bar");
|
||||||
|
Console.WriteLine($"NPSH Disponible: {npshDisponible:F2} m");
|
||||||
|
|
||||||
|
puedeOperar = pumpModel.CanOperateWithoutCavitation(presionOrigen);
|
||||||
|
Console.WriteLine($"¿Puede operar sin cavitación?: {(puedeOperar ? "SÍ" : "NO")}");
|
||||||
|
|
||||||
|
if (puedeOperar)
|
||||||
|
{
|
||||||
|
// Calcular presión diferencial normal
|
||||||
|
double deltaP = pumpModel.Dp(caudal);
|
||||||
|
Console.WriteLine($"Presión diferencial a {caudal} L/min: {deltaP:F2} bar");
|
||||||
|
Console.WriteLine("RESULTADO: La bomba puede operar normalmente");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("\n=== RESUMEN ===");
|
||||||
|
Console.WriteLine("La implementación NPSH previene que la bomba opere");
|
||||||
|
Console.WriteLine("cuando la presión de succión es insuficiente,");
|
||||||
|
Console.WriteLine("solucionando el problema físicamente imposible");
|
||||||
|
Console.WriteLine("reportado por el usuario.");
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error en la prueba: {ex.Message}");
|
||||||
|
Console.WriteLine($"Stack trace: {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("\nPresione cualquier tecla para salir...");
|
||||||
|
Console.ReadKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue