From 6cd2f4c8cbfd322d0b0a4c69fe6aa08c310bb1c3 Mon Sep 17 00:00:00 2001 From: Even Solbraa <41290109+EvenSol@users.noreply.github.com> Date: Sat, 4 Jan 2025 11:51:44 +0100 Subject: [PATCH] add multi stream HX (#1230) * add multi stream HX * update * update * update model --- .../MultiStreamHeatExchanger.java | 655 ++++++++++++++++++ .../MultiStreamHeatExchangerInterface.java | 296 ++++++++ .../MultiStreamHeatExchangerTest.java | 69 ++ 3 files changed, 1020 insertions(+) create mode 100644 src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchanger.java create mode 100644 src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerInterface.java create mode 100644 src/test/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerTest.java diff --git a/src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchanger.java b/src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchanger.java new file mode 100644 index 0000000000..cb63acee2c --- /dev/null +++ b/src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchanger.java @@ -0,0 +1,655 @@ +/* + * MultiStreamHeatExchanger.java + * + * Created on [Date] + */ + +package neqsim.process.equipment.heatexchanger; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import neqsim.process.conditionmonitor.ConditionMonitorSpecifications; +import neqsim.process.equipment.ProcessEquipmentInterface; +import neqsim.process.equipment.stream.StreamInterface; +import neqsim.thermo.system.SystemInterface; +import neqsim.thermodynamicoperations.ThermodynamicOperations; +import neqsim.util.ExcludeFromJacocoGeneratedReport; + +/** + *

+ * MultiStreamHeatExchanger class. + *

+ * + * Extends the Heater class to support multiple input and output streams, enabling the simulation of + * complex heat exchange processes such as those found in LNG heat exchangers. + * + * @author [Your Name] + * @version 1.0 + */ +public class MultiStreamHeatExchanger extends Heater implements MultiStreamHeatExchangerInterface { + private static final long serialVersionUID = 1001; + static Logger logger = LogManager.getLogger(MultiStreamHeatExchanger.class); + + private boolean setTemperature = false; + private List outStreams = new ArrayList<>(); + private List inStreams = new ArrayList<>(); + private SystemInterface system; + private double NTU; + protected double temperatureOut = 0; + + protected double dT = 0.0; + private double temperatureApproach = 0.0; + + + private double UAvalue = 500.0; // Overall heat transfer coefficient times area + private double duty = 0.0; + private double hotColdDutyBalance = 1.0; + private boolean firstTime = true; + private double guessOutTemperature = 273.15 + 130.0; // Default guess in K + private String guessOutTemperatureUnit = "K"; + private int outStreamSpecificationNumber = 0; + private double thermalEffectiveness = 0.0; + private String flowArrangement = "counterflow"; // Default arrangement + private boolean useDeltaT = false; + private double deltaT = 1.0; + + /** + * Constructor for MultiStreamHeatExchanger. + * + * @param name Name of the heat exchanger + */ + public MultiStreamHeatExchanger(String name) { + super(name); + } + + /** + * Constructor for MultiStreamHeatExchanger with initial input streams. + * + * @param name Name of the heat exchanger + * @param inStreams Initial list of input streams + */ + public MultiStreamHeatExchanger(String name, List inStreams) { + this(name); + this.inStreams.addAll(inStreams); + // Initialize outStreams as clones of inStreams + for (StreamInterface inStream : inStreams) { + StreamInterface outStream = inStream.clone(); + outStream.setName(name + "_Sout" + (outStreams.size() + 1)); + this.outStreams.add(outStream); + } + setName(name); + } + + /** + * Adds an inlet stream to the heat exchanger. + * + * @param inStream Input stream to add + */ + public void addInStream(StreamInterface inStream) { + this.inStreams.add(inStream); + StreamInterface outStream = inStream.clone(); + outStream.setName(getName() + "_Sout" + (outStreams.size() + 1)); + this.outStreams.add(outStream); + } + + /** + * Sets the feed stream at a specific index. + * + * @param index Index of the stream to set + * @param inStream Input stream to set + */ + public void setFeedStream(int index, StreamInterface inStream) { + if (index < inStreams.size()) { + this.inStreams.set(index, inStream); + this.outStreams.set(index, inStream.clone()); + setName(getName()); + } else { + throw new IndexOutOfBoundsException("Stream index out of bounds."); + } + } + + /** {@inheritDoc} */ + @Override + public void setName(String name) { + super.setName(name); + for (int i = 0; i < outStreams.size(); i++) { + outStreams.get(i).setName(name + "_Sout" + (i + 1)); + } + } + + /** {@inheritDoc} */ + @Override + public void setdT(double dT) { + this.dT = dT; + } + + /** {@inheritDoc} */ + @Override + public StreamInterface getOutStream(int i) { + return outStreams.get(i); + } + + /** {@inheritDoc} */ + @Override + public StreamInterface getInStream(int i) { + return inStreams.get(i); + } + + /** {@inheritDoc} */ + @Override + public void setOutTemperature(double temperature) { + this.temperatureOut = temperature; + } + + /** + * Gets the output temperature of a specific stream. + * + * @param i Index of the stream + * @return Temperature of the output stream + */ + public double getOutTemperature(int i) { + return outStreams.get(i).getThermoSystem().getTemperature(); + } + + /** + * Gets the input temperature of a specific stream. + * + * @param i Index of the stream + * @return Temperature of the input stream + */ + public double getInTemperature(int i) { + return inStreams.get(i).getThermoSystem().getTemperature(); + } + + /** {@inheritDoc} */ + @Override + public double getDuty() { + return duty; + } + + /** {@inheritDoc} */ + @Override + @ExcludeFromJacocoGeneratedReport + public void displayResult() { + for (StreamInterface outStream : outStreams) { + outStream.displayResult(); + } + } + + /** + * Gets the overall UA value. + * + * @return UA value + */ + public double getUAvalue() { + return UAvalue; + } + + /** + * Sets the overall UA value. + * + * @param UAvalue UA value to set + */ + public void setUAvalue(double UAvalue) { + this.UAvalue = UAvalue; + } + + /** + * Gets the guessed outlet temperature. + * + * @return Guessed outlet temperature + */ + public double getGuessOutTemperature() { + return guessOutTemperature; + } + + /** + * Sets the guessed outlet temperature in Kelvin. + * + * @param guessOutTemperature Guessed outlet temperature + */ + public void setGuessOutTemperature(double guessOutTemperature) { + this.guessOutTemperature = guessOutTemperature; + this.guessOutTemperatureUnit = "K"; + } + + /** + * Sets the guessed outlet temperature with specified unit. + * + * @param guessOutTemperature Guessed outlet temperature + * @param unit Unit of the temperature + */ + public void setGuessOutTemperature(double guessOutTemperature, String unit) { + this.guessOutTemperature = guessOutTemperature; + this.guessOutTemperatureUnit = unit; + } + + /** {@inheritDoc} */ + @Override + public double getEntropyProduction(String unit) { + double entropyProduction = 0.0; + + for (int i = 0; i < inStreams.size(); i++) { + UUID id = UUID.randomUUID(); + inStreams.get(i).run(id); + inStreams.get(i).getFluid().init(3); + outStreams.get(i).run(id); + outStreams.get(i).getFluid().init(3); + entropyProduction += outStreams.get(i).getThermoSystem().getEntropy(unit) + - inStreams.get(i).getThermoSystem().getEntropy(unit); + } + + // Additional entropy production due to heat transfer + // Assuming the first stream is the hot stream and the last is cold + if (inStreams.size() >= 2) { + int hotStream = 0; + int coldStream = inStreams.size() - 1; + double heatTransferEntropyProd = + Math.abs(getDuty()) * (1.0 / inStreams.get(coldStream).getTemperature() + - 1.0 / inStreams.get(hotStream).getTemperature()); + entropyProduction += heatTransferEntropyProd; + } + + return entropyProduction; + } + + /** {@inheritDoc} */ + @Override + public double getMassBalance(String unit) { + double massBalance = 0.0; + + for (int i = 0; i < inStreams.size(); i++) { + inStreams.get(i).run(); + inStreams.get(i).getFluid().init(3); + outStreams.get(i).run(); + outStreams.get(i).getFluid().init(3); + massBalance += outStreams.get(i).getThermoSystem().getFlowRate(unit) + - inStreams.get(i).getThermoSystem().getFlowRate(unit); + } + return massBalance; + } + + /** {@inheritDoc} */ + @Override + public void runConditionAnalysis(ProcessEquipmentInterface refExchanger) { + double heatBalanceError = 0.0; + conditionAnalysisMessage += getName() + " condition analysis started/"; + MultiStreamHeatExchanger refEx = (MultiStreamHeatExchanger) refExchanger; + + for (int i = 0; i < inStreams.size(); i++) { + inStreams.get(i).getFluid().initProperties(); + outStreams.get(i).getFluid().initProperties(); + heatBalanceError += outStreams.get(i).getThermoSystem().getEnthalpy() + - inStreams.get(i).getThermoSystem().getEnthalpy(); + + if (Math.abs(refEx.getInStream(i).getTemperature("C") + - getInStream(i).getTemperature("C")) > ConditionMonitorSpecifications.HXmaxDeltaT) { + conditionAnalysisMessage += ConditionMonitorSpecifications.HXmaxDeltaT_ErrorMsg; + } else if (Math.abs(refEx.getOutStream(i).getTemperature("C") + - getOutStream(i).getTemperature("C")) > ConditionMonitorSpecifications.HXmaxDeltaT) { + conditionAnalysisMessage += ConditionMonitorSpecifications.HXmaxDeltaT_ErrorMsg; + } + } + + heatBalanceError = heatBalanceError / (outStreams.get(0).getThermoSystem().getEnthalpy() + - inStreams.get(0).getThermoSystem().getEnthalpy()) * 100.0; + + if (Math.abs(heatBalanceError) > 10.0) { + String error = "Heat balance not fulfilled. Error: " + heatBalanceError + " "; + conditionAnalysisMessage += error; + } else { + String message = "Heat balance ok. Enthalpy balance deviation: " + heatBalanceError + " %"; + conditionAnalysisMessage += message; + } + + conditionAnalysisMessage += getName() + "/analysis ended/"; + + // Calculate thermal effectiveness and duty + double totalDuty = 0.0; + for (int i = 0; i < inStreams.size(); i++) { + double dutyStream = Math.abs(outStreams.get(i).getThermoSystem().getEnthalpy() + - inStreams.get(i).getThermoSystem().getEnthalpy()); + totalDuty += dutyStream; + } + + double referenceDuty = Math.abs(((MultiStreamHeatExchanger) refExchanger).getDuty()); + thermalEffectiveness = ((MultiStreamHeatExchanger) refExchanger).getThermalEffectiveness() + * (totalDuty) / referenceDuty; + + // Optionally, calculate duty balance among streams + // This can be customized based on specific requirements + } + + /** + * Runs condition analysis by comparing the exchanger with itself. + */ + public void runConditionAnalysis() { + runConditionAnalysis(this); + } + + /** + * Gets the thermal effectiveness of the heat exchanger. + * + * @return Thermal effectiveness + */ + public double getThermalEffectiveness() { + return thermalEffectiveness; + } + + /** + * Sets the thermal effectiveness of the heat exchanger. + * + * @param thermalEffectiveness Thermal effectiveness to set + */ + public void setThermalEffectiveness(double thermalEffectiveness) { + this.thermalEffectiveness = thermalEffectiveness; + } + + /** + * Gets the flow arrangement of the heat exchanger. + * + * @return Flow arrangement + */ + public String getFlowArrangement() { + return flowArrangement; + } + + /** + * Sets the flow arrangement of the heat exchanger. + * + * @param flowArrangement Name of the flow arrangement + */ + public void setFlowArrangement(String flowArrangement) { + this.flowArrangement = flowArrangement; + } + + /** + * Calculates the thermal effectiveness based on NTU and capacity ratio. + * + * @param NTU NTU value + * @param Cr Capacity ratio (Cmin/Cmax) + * @return Thermal effectiveness + */ + public double calcThermalEffectiveness(double NTU, double Cr) { + switch (flowArrangement.toLowerCase()) { + case "counterflow": + if (Cr == 1.0) { + return NTU / (1.0 + NTU); + } else { + return (1.0 - Math.exp(-NTU * (1 - Cr))) / (1.0 - Cr * Math.exp(-NTU * (1 - Cr))); + } + case "parallelflow": + return (1.0 - Math.exp(-NTU * (1 + Cr))) / (1.0 + Cr); + case "crossflow": + // Simplified model for crossflow; more complex models can be implemented + return 1 - Math.exp(-NTU * Math.pow(1 + Cr, 0.22)); + default: + // Default to counterflow if arrangement is unrecognized + if (Cr == 1.0) { + return NTU / (1.0 + NTU); + } else { + return (1.0 - Math.exp(-NTU * (1 - Cr))) / (1.0 - Cr * Math.exp(-NTU * (1 - Cr))); + } + } + } + + /** + * Gets the hot and cold duty balance. + * + * @return Hot and cold duty balance + */ + public double getHotColdDutyBalance() { + return hotColdDutyBalance; + } + + /** + * Sets the hot and cold duty balance. + * + * @param hotColdDutyBalance Hot and cold duty balance to set + */ + public void setHotColdDutyBalance(double hotColdDutyBalance) { + this.hotColdDutyBalance = hotColdDutyBalance; + } + + /** {@inheritDoc} */ + @Override + public String toJson() { + // return new GsonBuilder().serializeSpecialFloatingPointValues().create() + // .toJson(new HXResponse(this)); + return super.toJson(); + } + + /** + * Sets whether to use delta T for the calculations. + * + * @param useDeltaT Boolean flag to use delta T + */ + public void setUseDeltaT(boolean useDeltaT) { + this.useDeltaT = useDeltaT; + } + + /** + * Gets the delta T value. + * + * @return Delta T + */ + public double getDeltaT() { + return deltaT; + } + + /** + * Sets the delta T value and enables its usage. + * + * @param deltaT Delta T to set + */ + public void setDeltaT(double deltaT) { + this.useDeltaT = true; + this.deltaT = deltaT; + } + + /** + * Runs the heat exchanger simulation considering multiple streams. + * + * @param id Unique identifier for the run + */ + @Override + public void run(UUID id) { + if (firstTime) { + firstTime = false; + + // 1. Identify the hottest and coldest inlet streams + double hottestTemperature = Double.NEGATIVE_INFINITY; + double coldestTemperature = Double.POSITIVE_INFINITY; + int hottestIndex = -1; + int coldestIndex = -1; + + for (int i = 0; i < inStreams.size(); i++) { + StreamInterface inStream = inStreams.get(i); + // Ensure the inlet stream is run to get the latest temperature + inStream.run(); + double currentTemp = inStream.getThermoSystem().getTemperature("K"); + + if (currentTemp > hottestTemperature) { + hottestTemperature = currentTemp; + hottestIndex = i; + } + + if (currentTemp < coldestTemperature) { + coldestTemperature = currentTemp; + coldestIndex = i; + } + } + + // Check if valid indices were found + if (hottestIndex == -1 || coldestIndex == -1) { + throw new IllegalStateException("Unable to determine hottest or coldest inlet streams."); + } + + // 2. Set the outlet temperatures accordingly + for (int i = 0; i < outStreams.size(); i++) { + StreamInterface outStream = outStreams.get(i); + SystemInterface systemOut = inStreams.get(i).getThermoSystem().clone(); + outStream.setThermoSystem(systemOut); + + if (i == hottestIndex) { + // Set the outlet temperature of the hottest inlet stream to the coldest inlet temperature + outStream.getThermoSystem().setTemperature(coldestTemperature + temperatureApproach, "K"); + } else if (i == coldestIndex) { + // Set the outlet temperature of the coldest inlet stream to the hottest inlet temperature + outStream.getThermoSystem().setTemperature(hottestTemperature - temperatureApproach, "K"); + } else { + // Set the outlet temperature of other streams to the hottest inlet temperature + outStream.getThermoSystem().setTemperature(hottestTemperature - temperatureApproach, "K"); + } + + // Run the outlet stream with the given ID + outStream.run(id); + } + + // Finalize the setup + run(); + return; + } + + else { + // Run all input and output streams to ensure they are up-to-date + for (StreamInterface inStream : inStreams) { + inStream.run(id); + } + for (StreamInterface outStream : outStreams) { + outStream.run(id); + } + + // Identify heated and cooled streams + List heatedStreamIndices = new ArrayList<>(); + List cooledStreamIndices = new ArrayList<>(); + double totalHeatGained = 0.0; // Total Q for heated streams + double totalHeatLost = 0.0; // Total Q for cooled streams + + for (int i = 0; i < inStreams.size(); i++) { + double enthalpyIn = inStreams.get(i).getThermoSystem().getEnthalpy(); + double enthalpyOut = outStreams.get(i).getThermoSystem().getEnthalpy(); + double deltaH = enthalpyOut - enthalpyIn; + + if (deltaH > 0) { + // Stream is being heated + heatedStreamIndices.add(i); + totalHeatGained += deltaH; + } else if (deltaH < 0) { + // Stream is being cooled + cooledStreamIndices.add(i); + totalHeatLost += Math.abs(deltaH); + } + // Streams with deltaH == 0 are neither heated nor cooled + } + + logger.debug(": Total Heat Gained = " + totalHeatGained + " J"); + logger.debug(": Total Heat Lost = " + totalHeatLost + " J"); + + // Determine the limiting side + double limitingHeat; + boolean heatingIsLimiting; + + if (totalHeatGained < totalHeatLost) { + limitingHeat = totalHeatGained; + heatingIsLimiting = true; + logger.debug("Limiting side: Heating"); + } else { + limitingHeat = totalHeatLost; + heatingIsLimiting = false; + logger.debug("Limiting side: Cooling"); + } + + // Calculate scaling factors for each side + double scalingFactor = 1.0; + + if (heatingIsLimiting) { + // Scale down the heat lost by cooled streams + scalingFactor = limitingHeat / totalHeatLost; + logger.debug("Scaling factor for cooled streams: " + scalingFactor); + } else { + // Scale down the heat gained by heated streams + scalingFactor = limitingHeat / totalHeatGained; + logger.debug("Scaling factor for heated streams: " + scalingFactor); + } + + // Apply scaling factors to adjust outlet enthalpies + double maxTemperatureChange = 0.0; + + for (int i : cooledStreamIndices) { + StreamInterface inStream = inStreams.get(i); + StreamInterface outStream = outStreams.get(i); + + double enthalpyIn = inStream.getThermoSystem().getEnthalpy(); + double targetDeltaH = + -(outStream.getThermoSystem().getEnthalpy() - enthalpyIn) * scalingFactor; + + // Adjust the outlet enthalpy + double adjustedEnthalpyOut = enthalpyIn - (Math.abs(targetDeltaH)); + ThermodynamicOperations ops = new ThermodynamicOperations(outStream.getThermoSystem()); + ops.PHflash(adjustedEnthalpyOut); + + // Calculate temperature change for convergence check + double oldTemp = outStream.getThermoSystem().getTemperature("K"); + outStream.run(id); // Re-run to update temperature based on adjusted enthalpy + double newTemp = outStream.getThermoSystem().getTemperature("K"); + double tempChange = Math.abs(newTemp - oldTemp); + if (tempChange > maxTemperatureChange) { + maxTemperatureChange = tempChange; + } + + logger.debug("Adjusted cooled stream " + i + ": ΔH = " + targetDeltaH); + } + + scalingFactor = 1.0; + for (int i : heatedStreamIndices) { + StreamInterface inStream = inStreams.get(i); + StreamInterface outStream = outStreams.get(i); + + double enthalpyIn = inStream.getThermoSystem().getEnthalpy(); + double targetDeltaH = + (outStream.getThermoSystem().getEnthalpy() - enthalpyIn) * scalingFactor; + + // Adjust the outlet enthalpy + double adjustedEnthalpyOut = enthalpyIn + (Math.abs(targetDeltaH)); + ThermodynamicOperations ops = new ThermodynamicOperations(outStream.getThermoSystem()); + ops.PHflash(adjustedEnthalpyOut); + + // Calculate temperature change for convergence check + double oldTemp = outStream.getThermoSystem().getTemperature("K"); + outStream.run(id); // Re-run to update temperature based on adjusted enthalpy + double newTemp = outStream.getThermoSystem().getTemperature("K"); + double tempChange = Math.abs(newTemp - oldTemp); + if (tempChange > maxTemperatureChange) { + maxTemperatureChange = tempChange; + } + + logger.debug("Adjusted heated stream " + i + ": ΔH = " + targetDeltaH); + } + } + setCalculationIdentifier(id); + } + + + /** + * Runs the heat exchanger simulation using a specified stream approach. + * + * @param id Unique identifier for the run + */ + public void runSpecifiedStream(UUID id) { + // Implementation similar to the two-stream case but generalized for multiple streams + // This method needs to be defined based on specific requirements + } + + + public double getTemperatureApproach() { + return temperatureApproach; + } + + public void setTemperatureApproach(double temperatureApproach) { + this.temperatureApproach = temperatureApproach; + } +} diff --git a/src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerInterface.java b/src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerInterface.java new file mode 100644 index 0000000000..4c61fbf5e8 --- /dev/null +++ b/src/main/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerInterface.java @@ -0,0 +1,296 @@ +/* + * MultiStreamHeatExchangerInterface.java + * + * Created on [Date] + */ + +package neqsim.process.equipment.heatexchanger; + +import java.util.UUID; +import neqsim.process.equipment.ProcessEquipmentInterface; +import neqsim.process.equipment.stream.StreamInterface; + +/** + *

+ * MultiStreamHeatExchangerInterface interface. + *

+ * + * Defines the contract for a multi-stream heat exchanger, enabling the simulation and management of + * multiple input and output streams. This interface extends the {@link ProcessEquipmentInterface} + * to integrate with the broader NeqSim process simulation framework. + * + *

+ * Implementations of this interface should handle the addition and management of multiple streams, + * perform energy and mass balance calculations, and provide methods to analyze the performance and + * condition of the heat exchanger. + *

+ * + * @author + * @version 1.0 + */ +public interface MultiStreamHeatExchangerInterface extends ProcessEquipmentInterface { + + // ================================ + // Stream Management Methods + // ================================ + + /** + * Adds an inlet stream to the heat exchanger. + * + * @param inStream Input stream to be added + */ + void addInStream(StreamInterface inStream); + + /** + * Sets the feed stream at a specific index. + * + * @param index Index of the stream to set + * @param inStream Input stream to set at the specified index + * @throws IndexOutOfBoundsException if the index is out of range + */ + void setFeedStream(int index, StreamInterface inStream); + + /** + * Retrieves the output stream at the specified index. + * + * @param index Index of the output stream + * @return The output {@link StreamInterface} at the given index + * @throws IndexOutOfBoundsException if the index is out of range + */ + StreamInterface getOutStream(int index); + + /** + * Retrieves the input stream at the specified index. + * + * @param index Index of the input stream + * @return The input {@link StreamInterface} at the given index + * @throws IndexOutOfBoundsException if the index is out of range + */ + StreamInterface getInStream(int index); + + // ================================ + // Temperature Control Methods + // ================================ + + /** + * Sets the outlet temperature for the heat exchanger. + * + * @param temperature Desired outlet temperature + */ + void setOutTemperature(double temperature); + + /** + * Gets the outlet temperature of a specific output stream. + * + * @param index Index of the output stream + * @return Outlet temperature in Kelvin + */ + double getOutTemperature(int index); + + /** + * Gets the inlet temperature of a specific input stream. + * + * @param index Index of the input stream + * @return Inlet temperature in Kelvin + */ + double getInTemperature(int index); + + /** + * Sets the temperature difference (ΔT) for the heat exchanger calculations. + * + * @param dT Temperature difference to set + */ + void setdT(double dT); + + /** + * Enables or disables the use of a fixed temperature difference (ΔT) in calculations. + * + * @param useDeltaT True to use ΔT, false otherwise + */ + void setUseDeltaT(boolean useDeltaT); + + /** + * Sets the fixed temperature difference (ΔT) for calculations and enables its usage. + * + * @param deltaT Fixed temperature difference to set + */ + void setDeltaT(double deltaT); + + /** + * Retrieves the fixed temperature difference (ΔT) used in calculations. + * + * @return Temperature difference ΔT + */ + double getDeltaT(); + + // ================================ + // Thermodynamic and Energy Methods + // ================================ + + /** + * Calculates and retrieves the thermal effectiveness of the heat exchanger. + * + * @return Thermal effectiveness + */ + double getThermalEffectiveness(); + + /** + * Sets the thermal effectiveness of the heat exchanger. + * + * @param thermalEffectiveness Thermal effectiveness to set + */ + void setThermalEffectiveness(double thermalEffectiveness); + + /** + * Calculates the thermal effectiveness based on the Number of Transfer Units (NTU) and the + * capacity ratio (Cr). + * + * @param NTU Number of Transfer Units + * @param Cr Capacity ratio (Cmin/Cmax) + * @return Calculated thermal effectiveness + */ + double calcThermalEffectiveness(double NTU, double Cr); + + /** + * Retrieves the overall heat transfer coefficient times area (UA value). + * + * @return UA value + */ + double getUAvalue(); + + /** + * Sets the overall heat transfer coefficient times area (UA value). + * + * @param UAvalue UA value to set + */ + void setUAvalue(double UAvalue); + + /** + * Retrieves the hot and cold duty balance of the heat exchanger. + * + * @return Hot and cold duty balance + */ + double getHotColdDutyBalance(); + + /** + * Sets the hot and cold duty balance of the heat exchanger. + * + * @param hotColdDutyBalance Hot and cold duty balance to set + */ + void setHotColdDutyBalance(double hotColdDutyBalance); + + // ================================ + // Duty and Balance Methods + // ================================ + + /** + * Retrieves the duty (heat transfer) of the heat exchanger. + * + * @return Duty in appropriate units + */ + double getDuty(); + + /** + * Retrieves the mass balance of the heat exchanger. + * + * @param unit Unit of mass flow rate (e.g., "kg/sec") + * @return Mass balance value + */ + double getMassBalance(String unit); + + /** + * Retrieves the entropy production of the heat exchanger. + * + * @param unit Unit of entropy (e.g., "J/(kg*K)") + * @return Entropy production value + */ + double getEntropyProduction(String unit); + + // ================================ + // Flow Arrangement Methods + // ================================ + + /** + * Retrieves the flow arrangement of the heat exchanger. + * + * @return Flow arrangement as a String (e.g., "counterflow", "parallelflow") + */ + String getFlowArrangement(); + + /** + * Sets the flow arrangement of the heat exchanger. + * + * @param flowArrangement Name of the flow arrangement + */ + void setFlowArrangement(String flowArrangement); + + // ================================ + // Condition Analysis Methods + // ================================ + + /** + * Runs a condition analysis by comparing the current heat exchanger with a reference exchanger. + * + * @param refExchanger Reference {@link ProcessEquipmentInterface} heat exchanger for comparison + */ + void runConditionAnalysis(ProcessEquipmentInterface refExchanger); + + /** + * Runs a condition analysis using the current heat exchanger as the reference. + */ + void runConditionAnalysis(); + + // ================================ + // Guess Temperature Methods + // ================================ + + /** + * Retrieves the guessed outlet temperature used during initialization. + * + * @return Guessed outlet temperature + */ + double getGuessOutTemperature(); + + /** + * Sets the guessed outlet temperature in Kelvin. + * + * @param guessOutTemperature Guessed outlet temperature + */ + void setGuessOutTemperature(double guessOutTemperature); + + /** + * Sets the guessed outlet temperature with a specified unit. + * + * @param guessOutTemperature Guessed outlet temperature + * @param unit Unit of the temperature (e.g., "K", "C") + */ + void setGuessOutTemperature(double guessOutTemperature, String unit); + + // ================================ + // JSON Serialization Methods + // ================================ + + /** + * Serializes the heat exchanger's state to a JSON string. + * + * @return JSON representation of the heat exchanger + */ + String toJson(); + + // ================================ + // Additional Methods + // ================================ + + /** + * Runs the heat exchanger simulation with a unique identifier. + * + * @param id Unique identifier for the simulation run + */ + void run(UUID id); + + /** + * Displays the results of the heat exchanger simulation. Typically outputs temperatures, + * pressures, flow rates, etc. + */ + void displayResult(); +} diff --git a/src/test/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerTest.java b/src/test/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerTest.java new file mode 100644 index 0000000000..6648eaddb8 --- /dev/null +++ b/src/test/java/neqsim/process/equipment/heatexchanger/MultiStreamHeatExchangerTest.java @@ -0,0 +1,69 @@ +package neqsim.process.equipment.heatexchanger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import neqsim.process.equipment.stream.Stream; +import neqsim.thermodynamicoperations.ThermodynamicOperations; + +public class MultiStreamHeatExchangerTest { + static Logger logger = LogManager.getLogger(MultiStreamHeatExchangerTest.class); + + static neqsim.thermo.system.SystemInterface testSystem; + Stream gasStream; + + @BeforeEach + void setUp() { + testSystem = new neqsim.thermo.system.SystemSrkEos((273.15 + 60.0), 20.00); + testSystem.addComponent("methane", 120.00); + testSystem.addComponent("ethane", 120.0); + testSystem.addComponent("n-heptane", 3.0); + testSystem.createDatabase(true); + testSystem.setMixingRule(2); + ThermodynamicOperations testOps = new ThermodynamicOperations(testSystem); + testOps.TPflash(); + } + + + @Test + void testRun1() { + Stream stream_Hot = new Stream("Stream1", testSystem); + stream_Hot.setTemperature(100.0, "C"); + stream_Hot.setFlowRate(1000.0, "kg/hr"); + + Stream stream_Cold = new Stream("Stream2", testSystem.clone()); + stream_Cold.setTemperature(20.0, "C"); + stream_Cold.setFlowRate(310.0, "kg/hr"); + + Stream stream_Cold2 = new Stream("Stream3", testSystem.clone()); + stream_Cold2.setTemperature(0.0, "C"); + stream_Cold2.setFlowRate(50.0, "kg/hr"); + + MultiStreamHeatExchanger heatEx = new MultiStreamHeatExchanger("heatEx"); + heatEx.addInStream(stream_Hot); + heatEx.addInStream(stream_Cold); + heatEx.addInStream(stream_Cold2); + heatEx.setTemperatureApproach(5.0); + + + neqsim.process.processmodel.ProcessSystem operations = + new neqsim.process.processmodel.ProcessSystem(); + operations.add(stream_Hot); + operations.add(stream_Cold); + operations.add(stream_Cold2); + operations.add(heatEx); + + operations.run(); + + assertEquals(95, heatEx.getOutStream(1).getTemperature("C"), 1e-3); + assertEquals(95, heatEx.getOutStream(2).getTemperature("C"), 1e-3); + assertEquals(70.5921794735, heatEx.getOutStream(0).getTemperature("C"), 1e-3); + + } + + + +} +