From bb3b668f17dfef54fb4511a34ec0a5a961ea2b79 Mon Sep 17 00:00:00 2001 From: Even Solbraa <41290109+EvenSol@users.noreply.github.com> Date: Sun, 19 Jan 2025 19:31:38 +0000 Subject: [PATCH] added mechanical design of valves --- .../equipment/valve/ThrottlingValve.java | 392 ++++--------- .../equipment/valve/ValveInterface.java | 19 + .../valve/ControlValveSizing.java | 555 ++++++++++++++++++ .../valve/ValveMechanicalDesign.java | 47 +- .../equipment/valve/ThrottlingValveTest.java | 97 ++- .../valve/ControlValveSizingTest.java | 82 +++ .../ProcessSystemRunTransientTest.java | 8 +- 7 files changed, 902 insertions(+), 298 deletions(-) create mode 100644 src/main/java/neqsim/process/mechanicaldesign/valve/ControlValveSizing.java create mode 100644 src/test/java/neqsim/process/mechanicaldesign/valve/ControlValveSizingTest.java diff --git a/src/main/java/neqsim/process/equipment/valve/ThrottlingValve.java b/src/main/java/neqsim/process/equipment/valve/ThrottlingValve.java index 54f28437d9..73e161cdcf 100644 --- a/src/main/java/neqsim/process/equipment/valve/ThrottlingValve.java +++ b/src/main/java/neqsim/process/equipment/valve/ThrottlingValve.java @@ -1,5 +1,6 @@ package neqsim.process.equipment.valve; +import java.util.Map; import java.util.UUID; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -7,6 +8,7 @@ import neqsim.physicalproperties.PhysicalPropertyType; import neqsim.process.equipment.TwoPortEquipment; import neqsim.process.equipment.stream.StreamInterface; +import neqsim.process.mechanicaldesign.valve.ControlValveSizing; import neqsim.process.mechanicaldesign.valve.ValveMechanicalDesign; import neqsim.process.util.monitor.ValveResponse; import neqsim.thermo.phase.PhaseType; @@ -155,6 +157,33 @@ && getOutletPressure() == getOutletStream().getPressure()) { } } + public void calcCv(SystemInterface fluid) { + double xT = .137; + double D1 = 8 * 0.0254; + double D2 = 8 * 0.0254; + double d = D1; + double FL = 1.0; + double FD = 1.0; + + if (fluid.hasPhaseType(PhaseType.GAS)) { + Map result = neqsim.process.mechanicaldesign.valve.ControlValveSizing + .sizeControlValveGas(fluid.getTemperature("K"), fluid.getMolarMass("gr/mol"), + fluid.getViscosity("kg/msec"), fluid.getGamma2(), fluid.getZ(), + getInletPressure() * 1e5, getOutletPressure() * 1e5, fluid.getFlowRate("Sm3/sec"), D1, + D2, d, FL, FD, xT, true, true, true); + this.Cv = (double) result.get("Cv"); + + } else { + Map result = neqsim.process.mechanicaldesign.valve.ControlValveSizing + .sizeControlValveLiquid(fluid.getDensity("kg/m3"), 1.0 * 1e5, + fluid.getPhase(0).getPseudoCriticalPressure() * 1e5, fluid.getViscosity("kg/msec"), + getInletPressure() * 1e5, getOutletPressure() * 1e5, + fluid.getFlowRate("kg/sec") / fluid.getDensity("kg/m3"), null, null, null, 1.0, 1.0, + true, true, true); + this.Cv = (double) result.get("Cv"); + } + } + /** * Adjusts the flow coefficient (Cv) based on the percentage valve opening. * @@ -166,74 +195,6 @@ private double adjustCv(double Cv, double percentValveOpening) { return Cv * (percentValveOpening / 100); } - /** - * Calculates the mass flow rate through a control valve for a liquid based on the given - * parameters. - * - * @param P1 Upstream pressure in bar. - * @param P2 Downstream pressure in bar. - * @param rho Density of the fluid in kilograms per cubic meter (kg/m³). - * @param Cv Flow coefficient in US gallons per minute (USG/min). - * @param Fp Piping geometry factor (dimensionless). - * @param percentValveOpening Percentage valve opening (0 to 100). - * @return Mass flow rate in kilograms per hour (kg/h). - */ - public double liquidValveMassFlow(double P1, double P2, double rho, double Cv, double Fp, - double percentValveOpening) { - // Equation unit conversion constant - final double N1 = 0.0865; - - // Convert pressures from bar to Pascals directly in the code - double P1Pa = P1 * 100000; - double P2Pa = P2 * 100000; - - // Adjust Cv based on the percentage valve opening - double adjustedCv = adjustCv(Cv, percentValveOpening); - - // Clip Cv value to be non-negative - double clippedCv = Math.max(adjustedCv, 0); - // Calculate pressure difference and clip to be non-negative - double deltaP = Math.max(P1Pa - P2Pa, 0); - // Calculate mass flow rate - double massFlowRate = clippedCv * N1 * Fp * Math.sqrt(deltaP * rho); - - return massFlowRate; - } - - /** - * Calculates the percent valve opening given the mass flow rate through a valve, upstream - * pressure (P1), downstream pressure (P2), fluid density (rho), flow coefficient (Cv), and piping - * geometry factor (Fp). - * - * @param massFlowRate The mass flow rate through the valve in kg/hr. - * @param P1 The upstream pressure in bar. - * @param P2 The downstream pressure in bar. - * @param rho The density of the fluid in kilograms per cubic meter (kg/m³). - * @param Cv The flow coefficient of the valve in US gallons per minute (USG/min). - * @param Fp The piping geometry factor (dimensionless). - * @return The percent valve opening. - */ - public double calcPercentValveOpeningLiquid(double massFlowRate, double P1, double P2, double rho, - double Cv, double Fp) { - // Equation unit conversion constant - final double N1 = 0.0865; - - // Convert pressures from bar to Pascals directly in the code - double P1Pa = P1 * 100000; - double P2Pa = P2 * 100000; - - // Calculate pressure difference and clip to be non-negative - double deltaP = Math.max(P1Pa - P2Pa, 0); - - // Calculate the denominator part of the equation - double denominator = Cv * N1 * Fp * Math.sqrt(deltaP * rho); - - // Calculate percent valve opening - double percentValveOpening = (massFlowRate / denominator) * 100; - - return percentValveOpening; - } - /** * Calculates the downstream pressure (P2) through a control valve for a liquid based on the given * parameters. @@ -271,167 +232,16 @@ public double liquidValvePout(double P1, double m, double rho, double Cv, double return P2Pa / 100000; } - /** - * Calculates the flow coefficient (Cv) of a control valve for a liquid based on the given - * parameters. - * - * @param P1 Upstream pressure in bar. - * @param P2 Downstream pressure in bar. - * @param rho Density of the fluid in kilograms per cubic meter (kg/m³). - * @param m Mass flow rate in kilograms per hour (kg/h). - * @param Fp Piping geometry factor (dimensionless). - * @param percentValveOpening Percentage valve opening (0 to 100). - * @return Flow coefficient (Cv) in US gallons per minute (USG/min). - */ - public double liquidValveCv(double P1, double P2, double rho, double m, double Fp, - double percentValveOpening) { - // Equation unit conversion constant - final double N1 = 0.0865; - - // Convert pressures from bar to Pascals directly in the code - double P1Pa = P1 * 100000; - double P2Pa = P2 * 100000; - - // Calculate pressure difference and clip to be non-negative - double deltaP = Math.max(P1Pa - P2Pa, 0); - // Calculate flow coefficient - double Cv = m / (N1 * Fp * Math.sqrt(deltaP * rho)); - - // Adjust Cv based on the percentage valve opening - return Cv / (percentValveOpening / 100); - } - - - /** - * Calculate the mass flow rate through a throttling valve. - * - * @param Pus Upstream pressure (in bar). - * @param Pds Downstream pressure (in bar). - * @param rhous Upstream density (in kg/m^3). - * @param Cv Flow coefficient for the valve. - * @param percentValveOpening Percentage of valve opening (0-100%). - * @param isGasService Flag indicating if the service is gas (true) or liquid (false). - * @return The calculated mass flow rate (in kg/s). - */ - public double calcmassflow(double Pus, double Pds, double rhous, double Cv, - double percentValveOpening, boolean isGasService) { - - double Cl = 30.0; // Define Cl value - double sineFactor; - - if (isGasService) { - // Gas service calculations - sineFactor = Math.sin(Math.toRadians(3417 / Cl)); - double Cg = Cv * Cl; // Convert Cv to Cg - // Calculate the mass flow rate using Cg - double massFlowRate = 0.0457 * Math.sqrt(Pus * 100.0 * rhous) * sineFactor - * Math.sqrt((Pus - Pds) / Pus) * Cg * percentValveOpening / 100.0; - return massFlowRate; - } else { - // Liquid service calculations - sineFactor = Math.sin(Math.toRadians(3417 / Cl)); - // Calculate the mass flow rate using Cv - double massFlowRate = 0.0457 * Math.sqrt(Pus * 100.0 * rhous) * sineFactor - * Math.sqrt((Pus - Pds) / Pus) * Cv * percentValveOpening / 100.0; - return massFlowRate; - } - } - - /** - * Calculates the downstream pressure (Pds) of a valve given the upstream pressure (Pus), fluid - * density (rhous), flow coefficient (Cv), mass flow rate, and the percent valve opening. - * - * The calculation is based on the formula for mass flow through a valve, rearranged to solve for - * Pds. - * - * @param Pus The upstream pressure (Pus) in bara. - * @param rhous The density of the fluid upstream of the valve in kg/m^3. - * @param Cv The flow coefficient of the valve. - * @param massFlowRate The mass flow rate through the valve in kg/hr. - * @param percentValveOpening Opening of valve in % - * @return The downstream pressure (Pds) in bara. - */ - public double calcValvePout(double Pus, double rhous, double Cv, double massFlowRate, - double percentValveOpening) { - // Sine of 3417 / 30.0 - double sineFactor = Math.sin(3417 / 30.0); - - // Calculate the term that involves the mass flow rate, Cv, and percent valve - // opening - double flowTerm = (massFlowRate / (0.0457 * Math.sqrt(Pus * 100.0 * rhous) * sineFactor * Cv - * (percentValveOpening / 100.0))); - - // Square the flowTerm to eliminate the square root - double flowTermSquared = flowTerm * flowTerm; - - // Calculate Pds - double Pds = Pus * (1 - flowTermSquared); - - return Pds; - } - - /** - * Calculates the valve flow coefficient (Cv) for a throttling valve. - * - * @param Pus Upstream pressure (in bar). - * @param Pds Downstream pressure (in bar). - * @param rhous Upstream fluid density (in kg/m³). - * @param massFlowRate Mass flow rate through the valve (in kg/h). - * @param percentValveOpening Percentage of valve opening (0-100%). - * @return The calculated valve flow coefficient (Cv). - */ - public double calcCv(double Pus, double Pds, double rhous, double massFlowRate, - double percentValveOpening) { - // Sine of 3417 / 30.0 - double sineFactor = Math.sin(3417 / 30.0); - - // Calculate Cv - double Cv = massFlowRate / (0.0457 * Math.sqrt(Pus * 100.0 * rhous) * sineFactor - * Math.sqrt((Pus - Pds) / Pus) * percentValveOpening / 100.0); - - return Cv; - } - - /** - * Calculates the percent valve opening given the upstream pressure (Pus), downstream pressure - * (Pds), fluid density (rhous), flow coefficient (Cv), and mass flow rate. - * - * The calculation is based on the formula for mass flow through a valve, rearranged to solve for - * percent valve opening. - * - * @param Pus The upstream pressure (Pus) in bara. - * @param Pds The downstream pressure (Pds) in bara. - * @param rhous The density of the fluid upstream of the valve in kg/m^3. - * @param Cv The flow coefficient of the valve. - * @param massFlowRate The mass flow rate through the valve in kg/hr. - * @return The percent valve opening. - */ - public double calcPercentValveOpening(double Pus, double Pds, double rhous, double Cv, - double massFlowRate) { - // Sine of 3417 / 30.0 - double sineFactor = Math.sin(3417 / 30.0); - - // Calculate the term that involves the mass flow rate, Pus, rhous, and Cv - double term = massFlowRate / (0.0457 * Math.sqrt(Pus * 100 * rhous) * sineFactor * Cv); - - // Calculate the percent valve opening - double percentValveOpening = term / Math.sqrt(1 - (Pds / Pus)) * 100.0; - - return percentValveOpening; - } - /** {@inheritDoc} */ @Override public void run(UUID id) { - // System.out.println("valve running.."); - // outStream.setSpecification(inletStream.getSpecification()); + if (getInletStream().getThermoSystem() != null) { thermoSystem = getInletStream().getThermoSystem().clone(); } else { logger.error("Inlet stream thermo system is null"); return; } - ThermodynamicOperations thermoOps = new ThermodynamicOperations(thermoSystem); thermoSystem.init(3); double enthalpy = thermoSystem.getEnthalpy(); inStream.getThermoSystem().initPhysicalProperties(PhysicalPropertyType.MASS_DENSITY); @@ -449,17 +259,30 @@ public void run(UUID id) { if (valveCvSet && isCalcPressure) { if (gasValve) { - outp = calcValvePout(inStream.getThermoSystem().getPressure(), - inStream.getThermoSystem().getDensity("kg/m3"), Cv, inStream.getFlowRate("kg/hr"), - percentValveOpening); - } else { - outp = - liquidValvePout(inStream.getThermoSystem().getPressure(), inStream.getFlowRate("kg/hr"), - inStream.getThermoSystem().getDensity("kg/m3"), Cv, Fp, percentValveOpening); + + outp = ControlValveSizing.findOutletPressureForFixedCvGas(inStream.getTemperature(), + inStream.getFluid().getMolarMass("gr/mol"), inStream.getFluid().getViscosity("kg/msec"), + inStream.getFluid().getGamma2(), inStream.getFluid().getZ(), + inStream.getThermoSystem().getPressure("Pa"), inStream.getFlowRate("Sm3/sec"), Cv, + 0.137, true) / 1e5; + + } else + + { + outp = ControlValveSizing.findOutletPressureForFixedCvLiquid( + inStream.getFluid().getDensity("kg/m3"), 1e5, + inStream.getFluid().getPhase(0).getPseudoCriticalPressure() * 1e5, + inStream.getFluid().getViscosity("kg/msec"), + inStream.getThermoSystem().getPressure("Pa"), + inStream.getFlowRate("kg/sec") / inStream.getFluid().getDensity("kg/m3"), Cv, 1.0, 1.0, + true, true) / 1e5; } + setOutletPressure(outp); } - if (deltaPressure != 0) { + if (deltaPressure != 0) + + { thermoSystem.setPressure(thermoSystem.getPressure(pressureUnit) - deltaPressure, pressureUnit); setOutletPressure(thermoSystem.getPressure()); @@ -476,10 +299,10 @@ public void run(UUID id) { if (getSpecification().equals("out stream")) { thermoSystem.setPressure(outStream.getPressure(), pressureUnit); } - // System.out.println("enthalpy inn.." + enthalpy); - // thermoOps.PHflash(enthalpy, 0); + + ThermodynamicOperations thermoOps = new ThermodynamicOperations(thermoSystem); if (isIsoThermal() || Math.abs(pressure - inStream.getThermoSystem().getPressure()) < 1e-6 - || thermoSystem.getNumberOfMoles() < 1e-12 || pressure == 0) { + || thermoSystem.getTotalNumberOfMoles() < 1e-12 || pressure == 0) { thermoOps.TPflash(); } else { thermoOps.PHflash(enthalpy, 0); @@ -494,59 +317,33 @@ public void run(UUID id) { // inletStream.getThermoSystem().getDensity()); if (!valveCvSet) { - // If valve CV is not set, calculate it from inletstream flow, percent opening - // and - // differential pressure over valve. - if (gasValve) { - Cv = calcCv(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), - inStream.getFlowRate("kg/hr"), percentValveOpening); - } else { - Cv = liquidValveCv(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), - inStream.getFlowRate("kg/hr"), Fp, percentValveOpening); - } + calcCv(thermoSystem); valveCvSet = true; } - if (gasValve) { - percentValveOpening = calcPercentValveOpening(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), Cv, - inStream.getFlowRate("kg/hr")); - } else { - percentValveOpening = calcPercentValveOpeningLiquid(inStream.getFlowRate("kg/hr"), - inStream.getThermoSystem().getPressure(), outStream.getThermoSystem().getPressure(), - inStream.getFluid().getDensity("kg/m3"), Cv, Fp); - } - - if (gasValve) { - molarFlow = calcmassflow(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), Cv, - percentValveOpening, true) / 3600.0 / inStream.getFluid().getMolarMass("kg/mol"); - } else { - molarFlow = liquidValveMassFlow(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), Cv, - Fp, percentValveOpening) / 3600.0 / inStream.getFluid().getMolarMass("kg/mol"); - } - if (Math.abs(pressure - inStream.getThermoSystem().getPressure()) < 1e-6) { - molarFlow = inStream.getThermoSystem().getTotalNumberOfMoles(); - } - try { - inStream.getThermoSystem().setTotalNumberOfMoles(molarFlow); - inStream.getThermoSystem().init(3); - } catch (Exception ex) { - logger.error(ex.getMessage()); + if (isGasValve()) { + double xT = .137; + double Q = inStream.getFlowRate("Sm3/sec"); + percentValveOpening = ControlValveSizing.calculateValveOpeningFromFlowRateGas(Q, Cv, + getInletStream().getTemperature(), getInletStream().getFluid().getMolarMass("gr/mol"), + getInletStream().getFluid().getViscosity("kg/msec"), + getInletStream().getFluid().getGamma2(), getInletStream().getFluid().getZ(), + inStream.getThermoSystem().getPressure("Pa"), + outStream.getThermoSystem().getPressure("Pa"), 1.0, xT, true); + } else { + double Q = inStream.getFlowRate("kg/sec") / inStream.getFluid().getDensity("kg/m3"); + percentValveOpening = ControlValveSizing.calculateValveOpeningFromFlowRateLiquid(Q, Cv, + inStream.getThermoSystem().getDensity("kg/m3"), 1.0e5, + inStream.getThermoSystem().getPhase(0).getPseudoCriticalPressure() * 1e5, + inStream.getThermoSystem().getViscosity("kg/msec"), + inStream.getThermoSystem().getPressure("Pa"), + outStream.getThermoSystem().getPressure("Pa"), 1.0, 1.0, true); } - // inletStream.run(id); - - outStream.setThermoSystem(thermoSystem.clone()); - outStream.getThermoSystem().setTotalNumberOfMoles(molarFlow); - outStream.getThermoSystem().init(3); - outStream.setCalculationIdentifier(id); setCalculationIdentifier(id); } + /** {@inheritDoc} */ /** * This annotation is used to exclude the method from Jacoco code coverage reports. @@ -589,29 +386,40 @@ public void runTransient(double dt, UUID id) { outStream.setThermoSystem(thermoSystem); if (gasValve) { - molarFlow = calcmassflow(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), Cv, - percentValveOpening, true) / 3600.0 / inStream.getFluid().getMolarMass("kg/mol"); + molarFlow = ControlValveSizing.calculateFlowRateFromCvAndValveOpeningGas(Cv, + percentValveOpening, inStream.getThermoSystem().getTemperature(), + inStream.getFluid().getMolarMass("gr/mol"), inStream.getFluid().getViscosity("kg/msec"), + inStream.getFluid().getGamma2(), inStream.getFluid().getZ(), + inStream.getThermoSystem().getPressure() * 1e5, + outStream.getThermoSystem().getPressure() * 1e5, 1.0, 0.137, true) * 101325.0 / 8.314 + / 288.15; } else { - molarFlow = liquidValveMassFlow(inStream.getThermoSystem().getPressure(), - outStream.getThermoSystem().getPressure(), inStream.getFluid().getDensity("kg/m3"), Cv, - Fp, percentValveOpening) / 3600.0 / inStream.getFluid().getMolarMass("kg/mol"); + double oldFLow = inStream.getFlowRate("mole/sec"); + molarFlow = ControlValveSizing.calculateFlowRateFromValveOpeningLiquid(percentValveOpening, + Cv, inStream.getThermoSystem().getDensity("kg/m3"), 1.0e5, + inStream.getThermoSystem().getPhase(0).getPseudoCriticalPressure() * 1e5, + inStream.getThermoSystem().getViscosity("kg/msec"), + inStream.getThermoSystem().getPressure("Pa"), + outStream.getThermoSystem().getPressure("Pa"), 1.0, 1.0, true) + * inStream.getThermoSystem().getDensity("kg/m3") + / inStream.getThermoSystem().getMolarMass("kg/mol"); } try { inStream.getThermoSystem().setTotalNumberOfMoles(molarFlow); - inStream.getThermoSystem().init(1); + inStream.getFluid().init(1); inStream.run(id); } catch (Exception ex) { logger.error(ex.getMessage()); } try { - outStream.getThermoSystem().setTotalNumberOfMoles(molarFlow); - outStream.getThermoSystem().init(1); + outStream.getFluid().setTotalNumberOfMoles(molarFlow); + outStream.getFluid().init(1); outStream.run(id); } catch (Exception ex) { logger.error(ex.getMessage()); } + setCalculationIdentifier(id); } @@ -686,6 +494,20 @@ public void setCv(double cv, String unit) { valveCvSet = true; } + /** {@inheritDoc} */ + @Override + public void setCg(double cg) { + double Cl = 30.0; + this.setCv(cg / Cl); + } + + /** {@inheritDoc} */ + @Override + public double getCg() { + double Cl = 30.0; + return getCv() * Cl; + } + /** {@inheritDoc} */ @Override public double getPercentValveOpening() { diff --git a/src/main/java/neqsim/process/equipment/valve/ValveInterface.java b/src/main/java/neqsim/process/equipment/valve/ValveInterface.java index 9f4c18482f..0a409da572 100644 --- a/src/main/java/neqsim/process/equipment/valve/ValveInterface.java +++ b/src/main/java/neqsim/process/equipment/valve/ValveInterface.java @@ -64,6 +64,15 @@ public interface ValveInterface extends ProcessEquipmentInterface, TwoPortInterf */ public double getCv(); + /** + *

+ * getCg. + *

+ * + * @return a double + */ + public double getCg(); + /** *

* getCv. @@ -74,6 +83,7 @@ public interface ValveInterface extends ProcessEquipmentInterface, TwoPortInterf */ public double getCv(String unit); + /** *

* setCv. @@ -83,6 +93,15 @@ public interface ValveInterface extends ProcessEquipmentInterface, TwoPortInterf */ public void setCv(double Cv); + /** + *

+ * setCv. + *

+ * + * @param Cg a double + */ + public void setCg(double Cg); + /** *

* setCv. diff --git a/src/main/java/neqsim/process/mechanicaldesign/valve/ControlValveSizing.java b/src/main/java/neqsim/process/mechanicaldesign/valve/ControlValveSizing.java new file mode 100644 index 0000000000..490cc040b1 --- /dev/null +++ b/src/main/java/neqsim/process/mechanicaldesign/valve/ControlValveSizing.java @@ -0,0 +1,555 @@ +package neqsim.process.mechanicaldesign.valve; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class provides methods for sizing control valves for both liquid and gas fluids. + */ +public class ControlValveSizing { + + // Constants + private static final double N1 = 0.1; // Constant for liquids (m^3/hr, kPa) + private static final double N9 = 0.03; // Constant for gases (m^3/hr, kPa) + private static final double rho0 = 999.10329075702327; // Water density at 288.15 K + private static final double R = 8.314; // Gas constant [J/(mol*K)] + private String method = "API"; + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + // Enum for fluid type + /** + * Enum representing the type of fluid. + */ + public enum FluidType { + LIQUID, GAS + } + + /** + * Sizes a control valve based on the provided parameters. + * + * @param type the type of fluid (LIQUID or GAS) + * @param rhoOrT density for liquid or temperature for gas + * @param MW molecular weight of the fluid + * @param mu dynamic viscosity of the fluid + * @param gammaOrPsat specific heat ratio for gas or saturation pressure for liquid + * @param ZOrPc compressibility factor for gas or critical pressure for liquid + * @param P1 upstream pressure + * @param P2 downstream pressure + * @param Q flow rate + * @param D1 upstream pipe diameter + * @param D2 downstream pipe diameter + * @param d valve diameter + * @param FL liquid pressure recovery factor + * @param Fd valve style modifier + * @param xTOrNone pressure drop ratio factor for gas + * @param allowChoked whether to allow choked flow + * @param allowLaminar whether to allow laminar flow + * @param fullOutput whether to return full output + * @return a map containing the sizing results + */ + public static Map sizeControlValve(FluidType type, double rhoOrT, double MW, + double mu, double gammaOrPsat, double ZOrPc, double P1, double P2, double Q, Double D1, + Double D2, Double d, double FL, double Fd, double xTOrNone, boolean allowChoked, + boolean allowLaminar, boolean fullOutput) { + + Map result = fullOutput ? new HashMap<>() : null; + + if (type == FluidType.LIQUID) { + return sizeControlValveLiquid(rhoOrT, gammaOrPsat, ZOrPc, mu, P1, P2, Q, D1, D2, d, FL, Fd, + allowChoked, allowLaminar, fullOutput); + } else if (type == FluidType.GAS) { + return sizeControlValveGas(rhoOrT, MW, mu, gammaOrPsat, ZOrPc, P1, P2, Q, D1, D2, d, FL, Fd, + xTOrNone, allowChoked, allowLaminar, fullOutput); + } else { + throw new IllegalArgumentException("Invalid fluid type"); + } + } + + /** + * Sizes a control valve for liquid based on the provided parameters. + * + * @param rho density of the liquid + * @param Psat saturation pressure of the liquid + * @param Pc critical pressure of the liquid + * @param mu dynamic viscosity of the liquid + * @param P1 upstream pressure + * @param P2 downstream pressure + * @param Q flow rate + * @param D1 upstream pipe diameter + * @param D2 downstream pipe diameter + * @param d valve diameter + * @param FL liquid pressure recovery factor + * @param Fd valve style modifier + * @param allowChoked whether to allow choked flow + * @param allowLaminar whether to allow laminar flow + * @param fullOutput whether to return full output + * @return a map containing the sizing results + */ + public static Map sizeControlValveLiquid(double rho, double Psat, double Pc, + double mu, double P1, double P2, double Q, Double D1, Double D2, Double d, double FL, + double Fd, boolean allowChoked, boolean allowLaminar, boolean fullOutput) { + + Map ans = fullOutput ? new HashMap<>() : null; + + double locP1 = P1 / 1000.0; + double locP2 = P2 / 1000.0; + double locPsat = Psat / 1000.0; + double locPc = Pc / 1000.0; + double Qloc = Q * 3600.0; + + double nu = mu / rho; // Kinematic viscosity + double dP = locP1 - locP2; + + // Calculate choked flow + double FF = ffCriticalPressureRatioL(locPsat, locPc); + boolean choked = isChokedTurbulentL(dP, locP1, locPsat, FF, FL); + double C = + choked && allowChoked ? Qloc / N1 / FL * Math.sqrt(rho / rho0 / (locP1 - FF * locPsat)) + : Qloc / N1 * Math.sqrt(rho / rho0 / dP); + + if (fullOutput) { + ans.put("FF", FF); + ans.put("choked", choked); + ans.put("Kv", C); + ans.put("Cv", Kv_to_Cv(C)); + } + return ans; + } + + /** + * Calculates the flow rate for a control valve based on the valve opening and given Cv. + * + * @param valveOpening percentage of valve opening (0 to 100) + * @param Cv flow coefficient (at 100% opening) + * @param rho density of the liquid (kg/m^3) + * @param Psat saturation pressure of the liquid (Pa) + * @param Pc critical pressure of the liquid (Pa) + * @param mu dynamic viscosity of the liquid (Pa·s) + * @param P1 upstream pressure (Pa) + * @param P2 downstream pressure (Pa) + * @param FL liquid pressure recovery factor + * @param Fd valve style modifier + * @param allowChoked whether to allow choked flow + * @return the calculated flow rate in m^3/s + */ + public static double calculateFlowRateFromValveOpeningLiquid(double valveOpening, double Cv, + double rho, double Psat, double Pc, double mu, double P1, double P2, double FL, double Fd, + boolean allowChoked) { + + // Validate input for valve opening + if (valveOpening < 0 || valveOpening > 100) { + throw new IllegalArgumentException("Valve opening must be between 0 and 100%"); + } + + // Convert pressures to bar + double locP1 = P1 / 1000.0; + double locP2 = P2 / 1000.0; + double locPsat = Psat / 1000.0; + double locPc = Pc / 1000.0; + + // Differential pressure + double dP = locP1 - locP2; + + // Calculate the effective Cv based on valve opening + double effectiveCv = Cv * (valveOpening / 100.0); + + double effectiveKv = Cv_to_Kv(effectiveCv); + // Calculate choked flow condition + double FF = ffCriticalPressureRatioL(locPsat, locPc); + boolean choked = isChokedTurbulentL(dP, locP1, locPsat, FF, FL); + + // Calculate flow rate + double Qloc; // Flow rate in m^3/h + if (choked && allowChoked) { + Qloc = effectiveKv * N1 * FL * Math.sqrt((locP1 - FF * locPsat) * rho0 / rho); + } else { + Qloc = effectiveKv * N1 / Math.sqrt(rho / rho0 / dP); + } + + // Convert flow rate from m^3/h to m^3/s + return Qloc / 3600.0; + } + + /** + * Calculates the valve opening percentage based on the flow rate and Cv. + * + * @param Q desired flow rate in m^3/s + * @param Cv full flow coefficient (at 100% opening) + * @param rho density of the liquid (kg/m^3) + * @param Psat saturation pressure of the liquid (Pa) + * @param Pc critical pressure of the liquid (Pa) + * @param mu dynamic viscosity of the liquid (Pa·s) + * @param P1 upstream pressure (Pa) + * @param P2 downstream pressure (Pa) + * @param FL liquid pressure recovery factor + * @param Fd valve style modifier + * @param allowChoked whether to allow choked flow + * @return the valve opening percentage (0 to 100%) + */ + public static double calculateValveOpeningFromFlowRateLiquid(double Q, double Cv, double rho, + double Psat, double Pc, double mu, double P1, double P2, double FL, double Fd, + boolean allowChoked) { + + // Constants + double N1 = 0.865; // Flow coefficient constant for liquids + double rho0 = 1000.0; // Reference density (kg/m^3) + + // Convert pressures to bar + double locP1 = P1 / 1000.0; + double locP2 = P2 / 1000.0; + double locPsat = Psat / 1000.0; + double locPc = Pc / 1000.0; + + // Differential pressure + double dP = locP1 - locP2; + + // Convert flow rate from m^3/s to m^3/h + double Qloc = Q * 3600.0; + + // Calculate choked flow condition + double FF = ffCriticalPressureRatioL(locPsat, locPc); + boolean choked = isChokedTurbulentL(dP, locP1, locPsat, FF, FL); + + // Calculate effective Cv required for the given flow rate + double effectiveCv; + if (choked && allowChoked) { + effectiveCv = Qloc / (N1 * FL * Math.sqrt((locP1 - FF * locPsat) * rho0 / rho)); + } else { + effectiveCv = Qloc / (N1 * Math.sqrt(rho0 / rho / dP)); + } + // Calculate valve opening percentage + double valveOpening = (effectiveCv / Cv) * 100.0; + + // Ensure the valve opening percentage is within valid bounds + if (valveOpening < 0.0) { + valveOpening = 0.0; + } + if (valveOpening > 100.0) { + valveOpening = 100.0; + } + + return valveOpening; + } + + /** + * Finds the outlet pressure for a given flow rate Q and a fixed (actual) Cv in a liquid valve, by + * iterating around your existing sizeControlValveLiquid(...) method. + * + * @param rho density of the liquid [kg/m^3] + * @param Psat saturation pressure [Pa] + * @param Pc critical pressure [Pa] + * @param mu dynamic viscosity [Pa.s] + * @param P1 upstream pressure [Pa] + * @param Q flow rate [m^3/s] + * @param actualCv the actual installed valve's Cv + * @param FL liquid pressure recovery factor + * @param Fd valve style modifier + * @param allowChoked whether to allow choked flow + * @param allowLaminar whether to allow laminar flow + * @return outlet pressure P2 [Pa] + */ + public static double findOutletPressureForFixedCvLiquid(double rho, double Psat, double Pc, + double mu, double P1, double Q, double actualCv, double FL, double Fd, boolean allowChoked, + boolean allowLaminar) { + // Convert upstream pressure to bar + double locP1 = P1 / 1e3; // [bar] + + // Set bisection bounds for P2 in bar + // (e.g. from near vacuum 0.001 bar up to just below P1) + double locP2_low = 0.001; + double locP2_high = locP1 - 1e-4; + double locP2mid = 0.5 * (locP2_low + locP2_high); + + // Convergence settings + double tolerance = 1e-6; + int maxIter = 50; + + for (int i = 0; i < maxIter; i++) { + // Call sizeControlValveLiquid(...) for the guess of locP2mid + double guessP2_pa = locP2mid * 1e3; // convert bar -> Pa + + // fullOutput = true => we get "Cv" in result map + Map result = + sizeControlValveLiquid(rho, Psat, Pc, mu, P1, guessP2_pa, Q, 1.0, 1.0, 1.0, // D1, D2, + FL, Fd, allowChoked, allowLaminar, true // fullOutput + ); + + // The required Cv for that guessed P2 + double requiredCv = (double) result.get("Cv"); + + // Compare required Cv vs. actualCv: + if (requiredCv < actualCv) { + // The valve "demands" more Cv than we actually have + // => means the guess dP is too large for that flow (or the flow is too big), + // => to reduce requiredCv, we must raise P2 => reduce deltaP + locP2_low = locP2mid; + } else { + // requiredCv <= actualCv => we can pass that flow + // => we might be able to drop P2 further + locP2_high = locP2mid; + } + + // Next iteration's midpoint + double oldMid = locP2mid; + locP2mid = 0.5 * (locP2_low + locP2_high); + + // Optional: print iteration info + // System.out.printf("iter=%d: P2mid=%.6f bar, requiredCv=%.4f, actualCv=%.4f %n", i, + // locP2mid, + // requiredCv, actualCv); + + // Check convergence + if (Math.abs(locP2mid - oldMid) < tolerance) { + break; + } + } + + // Return final guess in Pa + return locP2mid * 1e3; + } + + /** + * Sizes a control valve for gas based on the provided parameters. + * + * @param T temperature of the gas + * @param MW molecular weight of the gas + * @param mu dynamic viscosity of the gas + * @param gamma specific heat ratio of the gas + * @param Z compressibility factor of the gas + * @param P1 upstream pressure + * @param P2 downstream pressure + * @param Q flow rate + * @param D1 upstream pipe diameter + * @param D2 downstream pipe diameter + * @param d valve diameter + * @param FL liquid pressure recovery factor + * @param Fd valve style modifier + * @param xT pressure drop ratio factor for gas + * @param allowChoked whether to allow choked flow + * @param allowLaminar whether to allow laminar flow + * @param fullOutput whether to return full output + * @return a map containing the sizing results + */ + public static Map sizeControlValveGas(double T, double MW, double mu, + double gamma, double Z, double P1, double P2, double Q, Double D1, Double D2, Double d, + double FL, double Fd, double xT, boolean allowChoked, boolean allowLaminar, + boolean fullOutput) { + + Map ans = fullOutput ? new HashMap<>() : null; + + // Convert units + double locP1 = P1 / 1000.0; + double locP2 = P2 / 1000.0; + double Qloc = Q * 3600.0; + + // Gas properties + double Vm = Z * R * T / (locP1 * 1000); + double rho = MW * 1e-3 / Vm; + double nu = mu / rho; + double dP = P1 - P2; + + // Choked flow check + double Fgamma = gamma / 1.40; + double x = dP / P1; + double Y = Math.max(1 - x / (3 * Fgamma * xT), 2.0 / 3.0); + boolean choked = isChokedTurbulentG(x, Fgamma, xT); + + double C = choked && allowChoked ? Qloc / (N9 * P1 * Y) * Math.sqrt(MW * T * Z / xT / Fgamma) + : Qloc / (N9 * P1 * Y) * Math.sqrt(MW * T * Z / x); + if (fullOutput) { + ans.put("choked", choked); + ans.put("Y", Y); + ans.put("Kv", C); + ans.put("Cv", Kv_to_Cv(C)); + ans.put("Cg", Kv_to_Cv(C) * 30.0); + } + return ans; + } + + /** + * Calculates the flow rate for gas based on Cv and valve opening percentage. + * + * @param Cv full flow coefficient (at 100% opening) + * @param valveOpening valve opening percentage (0 to 100%) + * @param T temperature of the gas (K) + * @param MW molecular weight of the gas (g/mol) + * @param mu dynamic viscosity of the gas (Pa·s) + * @param gamma specific heat ratio of the gas + * @param Z compressibility factor of the gas + * @param P1 upstream pressure (Pa) + * @param P2 downstream pressure (Pa) + * @param FL liquid pressure recovery factor + * @param xT pressure drop ratio factor for gas + * @param allowChoked whether to allow choked flow + * @return the calculated flow rate in m^3/s + */ + public static double calculateFlowRateFromCvAndValveOpeningGas(double Cv, double valveOpening, + double T, double MW, double mu, double gamma, double Z, double P1, double P2, double FL, + double xT, boolean allowChoked) { + + // Validate input for valve opening + if (valveOpening < 0 || valveOpening > 100) { + throw new IllegalArgumentException("Valve opening must be between 0 and 100%"); + } + + // Convert pressures to bar + double locP1 = P1 / 1000.0; + double locP2 = P2 / 1000.0; + + // Calculate effective Cv based on valve opening percentage + double effectiveCv = Cv * (valveOpening / 100.0); + effectiveCv = Cv_to_Kv(effectiveCv); + // Gas properties + double Vm = Z * R * T / (locP1 * 1000.0); // Molar volume (m^3/kmol) + double rho = MW * 1e-3 / Vm; // Gas density (kg/m^3) + double dP = locP1 - locP2; // Pressure difference (bar) + + // Choked flow check + double Fgamma = gamma / 1.40; + double x = dP / locP1; + double Y = Math.max(1 - x / (3 * Fgamma * xT), 2.0 / 3.0); // Expansion factor + boolean choked = isChokedTurbulentG(x, Fgamma, xT); + + // Calculate flow rate in m^3/h + double Qloc; + if (choked && allowChoked) { + Qloc = effectiveCv * N9 * P1 * Y / Math.sqrt(MW * T * Z / xT / Fgamma); + } else { + Qloc = effectiveCv * N9 * P1 * Y / Math.sqrt(MW * T * Z / x); + } + + // Convert flow rate from m^3/h to m^3/s + return Qloc / 3600.0; + } + + /** + * Calculates the valve opening percentage for gas based on the flow rate and Cv. + * + * @param Q desired flow rate in m^3/s + * @param Cv full flow coefficient (at 100% opening) + * @param T temperature of the gas (K) + * @param MW molecular weight of the gas (g/mol) + * @param mu dynamic viscosity of the gas (Pa·s) + * @param gamma specific heat ratio of the gas + * @param Z compressibility factor of the gas + * @param P1 upstream pressure (Pa) + * @param P2 downstream pressure (Pa) + * @param FL liquid pressure recovery factor + * @param xT pressure drop ratio factor for gas + * @param allowChoked whether to allow choked flow + * @return the valve opening percentage (0 to 100%) + */ + public static double calculateValveOpeningFromFlowRateGas(double Q, double Cv, double T, + double MW, double mu, double gamma, double Z, double P1, double P2, double FL, double xT, + boolean allowChoked) { + // Convert pressures to bar + double locP1 = P1 / 1000.0; + double locP2 = P2 / 1000.0; + + // Convert flow rate from m^3/s to m^3/h + double Qloc = Q * 3600.0; + + // Gas properties + double Vm = Z * R * T / (locP1 * 1000.0); // Molar volume (m^3/kmol) + double rho = MW * 1e-3 / Vm; // Gas density (kg/m^3) + double dP = locP1 - locP2; // Pressure difference (bar) + + // Choked flow check + double Fgamma = gamma / 1.40; + double x = dP / locP1; + double Y = Math.max(1 - x / (3 * Fgamma * xT), 2.0 / 3.0); // Expansion factor + boolean choked = isChokedTurbulentG(x, Fgamma, xT); + + // Calculate the effective Cv required for the given flow rate + double effectiveCv; + if (choked && allowChoked) { + effectiveCv = Qloc / (N9 * P1 * Y) * Math.sqrt(MW * T * Z / xT / Fgamma); + } else { + effectiveCv = Qloc / (N9 * P1 * Y) * Math.sqrt(MW * T * Z / x); + } + + effectiveCv = Kv_to_Cv(effectiveCv); + + + // Calculate valve opening percentage + double valveOpening = (effectiveCv / Cv) * 100.0; + + // Ensure the valve opening percentage is within valid bounds + if (valveOpening < 0.0) + valveOpening = 0.0; + if (valveOpening > 100.0) + valveOpening = 100.0; + + return valveOpening; + } + + public static double findOutletPressureForFixedCvGas(double T, double MW, double mu, double gamma, + double Z, double P1, double Q, // known upstream pressure & desired flow + double actualCv, // the actual installed valve's Cv + double xT, boolean allowChoked) { + double locP1 = P1 / 1e3; // bar + double locP2_low = 0.001; // lower bound ~ near vacuum [bar] + double locP2_high = locP1 - 1e-4; // just below P1 + double tolerance = 1e-6; + double locP2mid = 0.5 * (locP2_low + locP2_high); + + for (int i = 0; i < 50; i++) { + // call sizeControlValveGas for guess of P2mid + Map result = sizeControlValveGas(T, MW, mu, gamma, Z, + // P1 in Pa, P2 guess in Pa + P1, locP2mid * 1e3, Q, 0.1, 0.1, 0.1, 1.0, 1.0, xT, allowChoked, false, true); + double requiredCv = (double) result.get("Cv"); + + // see how it compares to our actual Cv + if (requiredCv < actualCv) { + // we need more Cv than we have => flow is too big => + // or we must reduce deltaP => raise P2 + locP2_low = locP2mid; + } else { + // requiredCv <= actualCv => we can pass that flow with our valve => + // maybe we can drop P2 further + locP2_high = locP2mid; + } + double oldMid = locP2mid; + locP2mid = 0.5 * (locP2_low + locP2_high); + // System.out.println("P2mid: " + locP2mid + " Cv " + requiredCv + " actual Cv " + actualCv); + if (Math.abs(locP2mid - oldMid) < tolerance) + break; + } + + // return final guess of P2 in Pa + return locP2mid * 1e3; + } + + + private static boolean isChokedTurbulentL(double dP, double P1, double Psat, double FF, + double FL) { + return dP >= FL * FL * (P1 - FF * Psat); + } + + private static boolean isChokedTurbulentG(double x, double Fgamma, double xT) { + return x >= Fgamma * xT; + } + + private static double ffCriticalPressureRatioL(double Psat, double Pc) { + return 0.96 - 0.28 * Math.sqrt(Psat / Pc); + } + + // Kv to Cv Conversion + private static double Kv_to_Cv(double Kv) { + return 1.156 * Kv; + } + + // Cv to Kv Conversion + private static double Cv_to_Kv(double Cv) { + return 0.864 * Cv; + } +} + diff --git a/src/main/java/neqsim/process/mechanicaldesign/valve/ValveMechanicalDesign.java b/src/main/java/neqsim/process/mechanicaldesign/valve/ValveMechanicalDesign.java index 53c37827dd..873ed969c4 100644 --- a/src/main/java/neqsim/process/mechanicaldesign/valve/ValveMechanicalDesign.java +++ b/src/main/java/neqsim/process/mechanicaldesign/valve/ValveMechanicalDesign.java @@ -2,14 +2,18 @@ import java.awt.BorderLayout; import java.awt.Container; +import java.util.Map; import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.JTable; import neqsim.process.costestimation.valve.ValveCostEstimate; import neqsim.process.equipment.ProcessEquipmentInterface; import neqsim.process.equipment.valve.ThrottlingValve; +import neqsim.process.equipment.valve.ValveInterface; import neqsim.process.mechanicaldesign.MechanicalDesign; import neqsim.process.mechanicaldesign.designstandards.ValveDesignStandard; +import neqsim.thermo.phase.PhaseType; +import neqsim.thermo.system.SystemInterface; import neqsim.util.ExcludeFromJacocoGeneratedReport; /** @@ -23,12 +27,22 @@ public class ValveMechanicalDesign extends MechanicalDesign { /** Serialization version UID. */ private static final long serialVersionUID = 1000; - + neqsim.process.mechanicaldesign.valve.ControlValveSizing controlValveSizing = + new neqsim.process.mechanicaldesign.valve.ControlValveSizing(); double valveCvMax = 1.0; double valveWeight = 100.0; double inletPressure = 0.0; double outletPressure = 0.0; double dP = 0.0; + double diameter = 0.1; + double diameterInlet = 0.1; + double diameterOutlet = 0.1; + double xT = 0.137; + double FL = 1.0; + double FD = 1.0; + boolean allowChoked = true; + boolean allowLaminar = true; + boolean fullOutput = true; /** *

@@ -40,6 +54,7 @@ public class ValveMechanicalDesign extends MechanicalDesign { public ValveMechanicalDesign(ProcessEquipmentInterface equipment) { super(equipment); costEstimate = new ValveCostEstimate(this); + controlValveSizing.setMethod("API"); // IEC / ANSI / / API } /** {@inheritDoc} */ @@ -65,13 +80,32 @@ public void calcDesign() { inletPressure = valve1.getInletPressure(); outletPressure = valve1.getOutletPressure(); dP = inletPressure - outletPressure; - - valveCvMax = valve1.getThermoSystem().getFlowRate("m3/hr") - * Math.sqrt(valve1.getThermoSystem().getDensity("kg/m3") / 1000.0 / dP); + SystemInterface fluid = getProcessEquipment().getFluid(); + if (getProcessEquipment().getFluid().hasPhaseType(PhaseType.GAS)) { + Map result = ControlValveSizing.sizeControlValveGas(fluid.getTemperature("K"), + fluid.getMolarMass("gr/mol"), fluid.getViscosity("kg/msec"), fluid.getGamma2(), + fluid.getZ(), ((ValveInterface) getProcessEquipment()).getInletPressure() * 1e5, + ((ValveInterface) getProcessEquipment()).getOutletPressure() * 1e5, + fluid.getFlowRate("Sm3/sec"), diameterInlet, diameterOutlet, diameter, FL, FD, xT, + allowChoked, allowLaminar, fullOutput); + this.valveCvMax = (double) result.get("Cv"); + } else { + Map result = ControlValveSizing.sizeControlValveLiquid( + fluid.getDensity("kg/m3"), 1.0 * 1e5, fluid.getPC() * 1e5, fluid.getViscosity("kg/msec"), + ((ValveInterface) getProcessEquipment()).getInletPressure() * 1e5, + ((ValveInterface) getProcessEquipment()).getOutletPressure() * 1e5, + fluid.getFlowRate("kg/hr") / fluid.getDensity("kg/m3"), diameterInlet, diameterOutlet, + diameter, FL, FD, allowChoked, allowLaminar, fullOutput); + this.valveCvMax = (double) result.get("Cv"); + } valveWeight = valveCvMax * 100.0; setWeightTotal(valveWeight); } + public ControlValveSizing getControlValveSizing() { + return new ControlValveSizing(); + } + /** {@inheritDoc} */ @Override @ExcludeFromJacocoGeneratedReport @@ -107,4 +141,9 @@ public void displayResults() { // dialog.pack(); dialog.setVisible(true); } + + public void setControlValveSizing( + neqsim.process.mechanicaldesign.valve.ControlValveSizing controlValveSizing) { + this.controlValveSizing = controlValveSizing; + } } diff --git a/src/test/java/neqsim/process/equipment/valve/ThrottlingValveTest.java b/src/test/java/neqsim/process/equipment/valve/ThrottlingValveTest.java index 3974ba01cb..c8987a51c8 100644 --- a/src/test/java/neqsim/process/equipment/valve/ThrottlingValveTest.java +++ b/src/test/java/neqsim/process/equipment/valve/ThrottlingValveTest.java @@ -34,8 +34,83 @@ void testCalcCvGas() { valve1.setPercentValveOpening(100); valve1.run(); - assertEquals(2649.761258, valve1.getCv("SI"), 1e-2); - assertEquals(48.2652323991, valve1.getCv("US"), 1e-2); + assertEquals(82.276059, valve1.getCv(), 1e-2); + assertEquals(2468.2817776, valve1.getCg(), 1e-2); + assertEquals(82.276059256, valve1.getCv("SI"), 1e-2); + assertEquals(100.0, valve1.getPercentValveOpening(), 1e-2); + + valve1.setCalculateSteadyState(false); + valve1.runTransient(0.1); + assertEquals(7000.0, valve1.getOutletStream().getFlowRate("Sm3/hr"), 7000 / 100); + valve1.setPercentValveOpening(80); + valve1.runTransient(0.1); + assertEquals(5582.5183549, valve1.getOutletStream().getFlowRate("Sm3/hr"), 7000 / 100); + + valve1.setIsCalcOutPressure(true); + valve1.run(); + assertEquals(9.537350370, valve1.getOutletStream().getPressure("bara"), 0.01); // choked flow up + // to 9.5 bar? + + + } + + /** + * Test method for calculating the flow coefficient (Cv) of a gas through a throttling valve. + *

+ * This test sets up a thermodynamic system using the SRK EOS model with methane as the component. + * It creates a stream with specified flow rate, pressure, and temperature, and then passes it + * through a throttling valve. The outlet pressure and valve opening percentage are set, and the + * valve is run. The test asserts that the calculated Cv values in both US and SI units are as + * expected. + *

+ */ + @Test + void testCalcCvGas2() { + neqsim.thermo.system.SystemInterface testSystem2 = + new neqsim.thermo.system.SystemSrkEos((273.15 + 25.0), 10.00); + testSystem2.addComponent("methane", 1.0); + testSystem2.setMixingRule(2); + + Stream stream1 = new Stream("Stream1", testSystem2); + stream1.setFlowRate(47.2, "Sm3/sec"); + stream1.setPressure(14.8, "bara"); + stream1.setTemperature(16.0, "C"); + stream1.run(); + + ThrottlingValve valve1 = new ThrottlingValve("valve_1", stream1); + valve1.setOutletPressure(4.46); + valve1.setPercentValveOpening(100); + valve1.run(); + + assertEquals(1248.72092647, valve1.getCv(), 1e-2); + assertEquals(1248.72092647 * 30, valve1.getCg(), 1e-2); + assertEquals(1248.720926478, valve1.getCv("SI"), 1e-2); + assertEquals(1248.720926478 / 54.9, valve1.getCv("US"), 1e-2); + } + + @Test + void testCalcCvLiquid2() { + neqsim.thermo.system.SystemInterface testSystem2 = + new neqsim.thermo.system.SystemSrkEos((273.15 + 25.0), 10.00); + testSystem2.addComponent("water", 1.0); + testSystem2.setMixingRule(2); + + Stream stream1 = new Stream("Stream1", testSystem2); + stream1.setFlowRate(100.0, "Am3/hr"); + stream1.setPressure(14.8, "bara"); + stream1.setTemperature(15.0, "C"); + stream1.run(); + + ThrottlingValve valve1 = new ThrottlingValve("valve_1", stream1); + valve1.setOutletPressure(4.46); + valve1.setPercentValveOpening(100); + valve1.run(); + + assertEquals(0.318792242, valve1.getCv(), 1e-2); + assertEquals(0.318792242, valve1.getCv("SI"), 1e-2); + assertEquals(0.318792242 / 54.9, valve1.getCv("US"), 1e-2); + + } @Test @@ -56,11 +131,23 @@ void testCalcCvLiquid() { valve1.setPercentValveOpening(100); valve1.run(); - assertEquals(0.4515327970, stream1.getFlowRate("gallons/min"), 1e-2); - assertEquals(0.0165567743765, valve1.getCv("SI"), 1e-2); + + // assertEquals(0.451532797, stream1.getFlowRate("gallons/min"), 1e-2); + assertEquals(0.0165632088268, valve1.getCv("SI"), 1e-2); assertEquals(100.0, valve1.getPercentValveOpening(), 1e-2); - assertEquals(100, stream1.getFlowRate("kg/hr"), 1e-2); + assertEquals(100.0, stream1.getFlowRate("kg/hr"), 1e-2); assertEquals(3.015805897362369E-4, valve1.getCv("US"), 1e-2); + + valve1.setCalculateSteadyState(false); + valve1.runTransient(0.1); + valve1.setOutletPressure(51.0); + valve1.runTransient(0.1); + assertEquals(100.0, valve1.getInletStream().getFlowRate("kg/hr"), 1e-2); + + + valve1.setIsCalcOutPressure(true); + valve1.run(); + assertEquals(50.00000000, valve1.getOutletStream().getPressure("bara"), 0.01); } @Test diff --git a/src/test/java/neqsim/process/mechanicaldesign/valve/ControlValveSizingTest.java b/src/test/java/neqsim/process/mechanicaldesign/valve/ControlValveSizingTest.java new file mode 100644 index 0000000000..9e653ba551 --- /dev/null +++ b/src/test/java/neqsim/process/mechanicaldesign/valve/ControlValveSizingTest.java @@ -0,0 +1,82 @@ +package neqsim.process.mechanicaldesign.valve; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.Map; +import org.junit.jupiter.api.Test; + + + +public class ControlValveSizingTest { + + @Test + public void testSizeControlValveLiquid() { + Map result = ControlValveSizing.sizeControlValve( + ControlValveSizing.FluidType.LIQUID, 1000.0, 0.0, 1.0, 100.0, 200.0, 500000.0, 400000.0, + 10.0, null, null, null, 0.9, 0.8, 0.0, true, true, true); + + assertNotNull(result); + assertTrue(result.containsKey("FF")); + assertTrue(result.containsKey("choked")); + assertTrue(result.containsKey("Kv")); + } + + @Test + public void testSizeControlValveGas() { + Map result = + ControlValveSizing.sizeControlValve(ControlValveSizing.FluidType.GAS, 300.0, 28.97, 0.01, + 1.4, 0.9, 500000.0, 400000.0, 10.0, null, null, null, 0.9, 0.8, 0.7, true, true, true); + + assertNotNull(result); + assertTrue(result.containsKey("choked")); + assertTrue(result.containsKey("Y")); + assertTrue(result.containsKey("Kv")); + } + + @Test + public void testInvalidFluidType() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + ControlValveSizing.sizeControlValve(null, 300.0, 28.97, 0.01, 1.4, 0.9, 500000.0, 400000.0, + 10.0, null, null, null, 0.9, 0.8, 0.7, true, true, true); + }); + + assertEquals("Invalid fluid type", exception.getMessage()); + } + + @Test + public void testSizeControlValveGasFullOutput() { + Map result = + ControlValveSizing.sizeControlValve(ControlValveSizing.FluidType.GAS, 300.0, 28.97, 0.01, + 1.4, 0.9, 500000.0, 400000.0, 10.0, null, null, null, 0.9, 0.8, 0.7, true, true, true); + + assertNotNull(result); + assertTrue(result.containsKey("choked")); + assertTrue(result.containsKey("Y")); + assertTrue(result.containsKey("Kv")); + assertTrue(result.containsKey("Cv")); + } + + @Test + public void testSizeControlValveGasChokedFlow() { + Map result = + ControlValveSizing.sizeControlValve(ControlValveSizing.FluidType.GAS, 300.0, 28.97, 0.01, + 1.4, 0.9, 500000.0, 100000.0, 10.0, null, null, null, 0.9, 0.8, 0.7, true, true, true); + + assertNotNull(result); + assertTrue(result.containsKey("choked")); + assertTrue((boolean) result.get("choked")); + } + + @Test + public void testSizeControlValveGasNonChokedFlow() { + Map result = + ControlValveSizing.sizeControlValve(ControlValveSizing.FluidType.GAS, 300.0, 28.97, 0.01, + 1.4, 0.9, 500000.0, 490000.0, 10.0, null, null, null, 0.9, 0.8, 0.7, true, true, true); + + assertNotNull(result); + assertTrue(result.containsKey("choked")); + assertTrue(!(boolean) result.get("choked")); + } +} diff --git a/src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java b/src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java index 26ed0b6870..4dff9f7e23 100644 --- a/src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java +++ b/src/test/java/neqsim/process/processmodel/ProcessSystemRunTransientTest.java @@ -113,7 +113,7 @@ public void testDynamicCalculation() { assertEquals(sim.getCalculationIdentifier(), p.getCalculationIdentifier()); } } - assertEquals(73.5, flowTransmitter.getMeasuredValue(), 1.0); + assertEquals(50.147984, flowTransmitter.getMeasuredValue(), 1.0); } @Test @@ -524,7 +524,7 @@ public void testDynamicCompressorSpeedControl() { assertEquals(102.7, compressor1.getOutletStream().getPressure(), 2.01); assertEquals(50.0, separator1.getGasOutStream().getPressure(), 0.01); // System.out.println("speed " + compressor1.getSpeed()); - p.setTimeStep(10.0); + p.setTimeStep(1.0); p.runTransient(); /* * System.out.println(" speed " + compressor1.getSpeed() + "feed flow " + @@ -812,10 +812,10 @@ public void testValveRegulator() { p.runTransient(1.0); double compPower2 = compressor1.getPower("kW"); - assertEquals(1.7776105238, compPower1 - compPower2, 0.0001); + assertEquals(2.0261603269, compPower1 - compPower2, 0.0001); p.runTransient(12.0); compPower2 = compressor1.getPower("kW"); - assertEquals(1.7776105238, compPower1 - compPower2, 0.0001); + assertEquals(2.0261603269, compPower1 - compPower2, 0.0001); } }