|
| 1 | +package neqsim.process.processmodel.graph; |
| 2 | + |
| 3 | +import static org.junit.jupiter.api.Assertions.assertEquals; |
| 4 | +import static org.junit.jupiter.api.Assertions.assertFalse; |
| 5 | +import static org.junit.jupiter.api.Assertions.assertTrue; |
| 6 | +import org.junit.jupiter.api.BeforeEach; |
| 7 | +import org.junit.jupiter.api.Test; |
| 8 | +import neqsim.process.equipment.compressor.Compressor; |
| 9 | +import neqsim.process.equipment.heatexchanger.Cooler; |
| 10 | +import neqsim.process.equipment.heatexchanger.Heater; |
| 11 | +import neqsim.process.equipment.mixer.Mixer; |
| 12 | +import neqsim.process.equipment.separator.Separator; |
| 13 | +import neqsim.process.equipment.splitter.Splitter; |
| 14 | +import neqsim.process.equipment.stream.Stream; |
| 15 | +import neqsim.process.equipment.valve.ThrottlingValve; |
| 16 | +import neqsim.process.processmodel.ProcessSystem; |
| 17 | +import neqsim.thermo.system.SystemInterface; |
| 18 | +import neqsim.thermo.system.SystemSrkEos; |
| 19 | + |
| 20 | +/** |
| 21 | + * Test class to verify that graph-based execution produces identical results to sequential |
| 22 | + * execution. |
| 23 | + * |
| 24 | + * <p> |
| 25 | + * These tests ensure that the graph-based parallel and optimized execution strategies don't |
| 26 | + * introduce numerical differences compared to the traditional sequential execution. |
| 27 | + * </p> |
| 28 | + */ |
| 29 | +public class GraphVsSequentialExecutionTest { |
| 30 | + |
| 31 | + private SystemInterface testFluid; |
| 32 | + |
| 33 | + @BeforeEach |
| 34 | + void setUp() { |
| 35 | + testFluid = new SystemSrkEos(298.0, 50.0); |
| 36 | + testFluid.addComponent("methane", 0.85); |
| 37 | + testFluid.addComponent("ethane", 0.10); |
| 38 | + testFluid.addComponent("propane", 0.05); |
| 39 | + testFluid.setMixingRule("classic"); |
| 40 | + } |
| 41 | + |
| 42 | + /** |
| 43 | + * Test that removeUnit() properly invalidates the graph cache. |
| 44 | + */ |
| 45 | + @Test |
| 46 | + void testRemoveUnitInvalidatesGraph() { |
| 47 | + ProcessSystem system = new ProcessSystem("GraphInvalidation Test"); |
| 48 | + |
| 49 | + Stream feed = new Stream("feed", testFluid.clone()); |
| 50 | + feed.setFlowRate(1000.0, "kg/hr"); |
| 51 | + system.add(feed); |
| 52 | + |
| 53 | + Heater heater = new Heater("heater", feed); |
| 54 | + heater.setOutTemperature(350.0, "K"); |
| 55 | + system.add(heater); |
| 56 | + |
| 57 | + Separator separator = new Separator("separator", heater.getOutletStream()); |
| 58 | + system.add(separator); |
| 59 | + |
| 60 | + // Build graph first |
| 61 | + ProcessGraph graph1 = system.buildGraph(); |
| 62 | + assertEquals(3, graph1.getNodeCount()); |
| 63 | + |
| 64 | + // Remove a unit |
| 65 | + system.removeUnit("heater"); |
| 66 | + |
| 67 | + // Build graph again - should be rebuilt with 2 nodes |
| 68 | + ProcessGraph graph2 = system.buildGraph(); |
| 69 | + assertEquals(2, graph2.getNodeCount()); |
| 70 | + } |
| 71 | + |
| 72 | + /** |
| 73 | + * Test that clear() properly invalidates the graph cache. |
| 74 | + */ |
| 75 | + @Test |
| 76 | + void testClearInvalidatesGraph() { |
| 77 | + ProcessSystem system = new ProcessSystem("Clear Test"); |
| 78 | + |
| 79 | + Stream feed = new Stream("feed", testFluid.clone()); |
| 80 | + feed.setFlowRate(1000.0, "kg/hr"); |
| 81 | + system.add(feed); |
| 82 | + |
| 83 | + Heater heater = new Heater("heater", feed); |
| 84 | + system.add(heater); |
| 85 | + |
| 86 | + // Build graph first |
| 87 | + ProcessGraph graph1 = system.buildGraph(); |
| 88 | + assertEquals(2, graph1.getNodeCount()); |
| 89 | + |
| 90 | + // Clear the system |
| 91 | + system.clear(); |
| 92 | + assertEquals(0, system.size()); |
| 93 | + |
| 94 | + // Add new units |
| 95 | + Stream newFeed = new Stream("newFeed", testFluid.clone()); |
| 96 | + newFeed.setFlowRate(500.0, "kg/hr"); |
| 97 | + system.add(newFeed); |
| 98 | + |
| 99 | + // Build graph again - should reflect new structure |
| 100 | + ProcessGraph graph2 = system.buildGraph(); |
| 101 | + assertEquals(1, graph2.getNodeCount()); |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * Test simple linear process gives identical results with sequential and graph-based execution. |
| 106 | + */ |
| 107 | + @Test |
| 108 | + void testLinearProcessSequentialVsGraphBased() { |
| 109 | + // Create process with sequential execution |
| 110 | + ProcessSystem seqSystem = new ProcessSystem("Sequential Linear"); |
| 111 | + seqSystem.setUseOptimizedExecution(false); |
| 112 | + |
| 113 | + Stream seqFeed = new Stream("feed", testFluid.clone()); |
| 114 | + seqFeed.setFlowRate(1000.0, "kg/hr"); |
| 115 | + seqFeed.setTemperature(25.0, "C"); |
| 116 | + seqFeed.setPressure(50.0, "bara"); |
| 117 | + seqSystem.add(seqFeed); |
| 118 | + |
| 119 | + Heater seqHeater = new Heater("heater", seqFeed); |
| 120 | + seqHeater.setOutTemperature(350.0, "K"); |
| 121 | + seqSystem.add(seqHeater); |
| 122 | + |
| 123 | + Separator seqSep = new Separator("separator", seqHeater.getOutletStream()); |
| 124 | + seqSystem.add(seqSep); |
| 125 | + |
| 126 | + seqSystem.run(); |
| 127 | + |
| 128 | + double seqGasFlow = seqSep.getGasOutStream().getFlowRate("kg/hr"); |
| 129 | + double seqLiqFlow = seqSep.getLiquidOutStream().getFlowRate("kg/hr"); |
| 130 | + double seqOutTemp = seqSep.getGasOutStream().getTemperature("K"); |
| 131 | + |
| 132 | + // Create identical process with graph-based execution |
| 133 | + ProcessSystem graphSystem = new ProcessSystem("Graph Linear"); |
| 134 | + graphSystem.setUseGraphBasedExecution(true); |
| 135 | + |
| 136 | + Stream graphFeed = new Stream("feed", testFluid.clone()); |
| 137 | + graphFeed.setFlowRate(1000.0, "kg/hr"); |
| 138 | + graphFeed.setTemperature(25.0, "C"); |
| 139 | + graphFeed.setPressure(50.0, "bara"); |
| 140 | + graphSystem.add(graphFeed); |
| 141 | + |
| 142 | + Heater graphHeater = new Heater("heater", graphFeed); |
| 143 | + graphHeater.setOutTemperature(350.0, "K"); |
| 144 | + graphSystem.add(graphHeater); |
| 145 | + |
| 146 | + Separator graphSep = new Separator("separator", graphHeater.getOutletStream()); |
| 147 | + graphSystem.add(graphSep); |
| 148 | + |
| 149 | + graphSystem.run(); |
| 150 | + |
| 151 | + double graphGasFlow = graphSep.getGasOutStream().getFlowRate("kg/hr"); |
| 152 | + double graphLiqFlow = graphSep.getLiquidOutStream().getFlowRate("kg/hr"); |
| 153 | + double graphOutTemp = graphSep.getGasOutStream().getTemperature("K"); |
| 154 | + |
| 155 | + // Verify results match |
| 156 | + assertEquals(seqGasFlow, graphGasFlow, 1e-6, "Gas flow should match"); |
| 157 | + assertEquals(seqLiqFlow, graphLiqFlow, 1e-6, "Liquid flow should match"); |
| 158 | + assertEquals(seqOutTemp, graphOutTemp, 1e-6, "Temperature should match"); |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * Test branching process (splitter) gives identical results. |
| 163 | + */ |
| 164 | + @Test |
| 165 | + void testBranchingProcessSequentialVsParallel() throws InterruptedException { |
| 166 | + // Create process with sequential execution |
| 167 | + ProcessSystem seqSystem = new ProcessSystem("Sequential Branching"); |
| 168 | + seqSystem.setUseOptimizedExecution(false); |
| 169 | + |
| 170 | + Stream seqFeed = new Stream("feed", testFluid.clone()); |
| 171 | + seqFeed.setFlowRate(1000.0, "kg/hr"); |
| 172 | + seqFeed.setTemperature(25.0, "C"); |
| 173 | + seqFeed.setPressure(50.0, "bara"); |
| 174 | + seqSystem.add(seqFeed); |
| 175 | + |
| 176 | + Splitter seqSplitter = new Splitter("splitter", seqFeed); |
| 177 | + seqSplitter.setSplitFactors(new double[] {0.6, 0.4}); |
| 178 | + seqSystem.add(seqSplitter); |
| 179 | + |
| 180 | + Heater seqHeater1 = new Heater("heater1", seqSplitter.getSplitStream(0)); |
| 181 | + seqHeater1.setOutTemperature(350.0, "K"); |
| 182 | + seqSystem.add(seqHeater1); |
| 183 | + |
| 184 | + Cooler seqCooler = new Cooler("cooler", seqSplitter.getSplitStream(1)); |
| 185 | + seqCooler.setOutTemperature(280.0, "K"); |
| 186 | + seqSystem.add(seqCooler); |
| 187 | + |
| 188 | + seqSystem.run(); |
| 189 | + |
| 190 | + double seqHeater1Flow = seqHeater1.getOutletStream().getFlowRate("kg/hr"); |
| 191 | + double seqHeater1Temp = seqHeater1.getOutletStream().getTemperature("K"); |
| 192 | + double seqCoolerFlow = seqCooler.getOutletStream().getFlowRate("kg/hr"); |
| 193 | + double seqCoolerTemp = seqCooler.getOutletStream().getTemperature("K"); |
| 194 | + |
| 195 | + // Create identical process with parallel execution |
| 196 | + ProcessSystem parSystem = new ProcessSystem("Parallel Branching"); |
| 197 | + |
| 198 | + Stream parFeed = new Stream("feed", testFluid.clone()); |
| 199 | + parFeed.setFlowRate(1000.0, "kg/hr"); |
| 200 | + parFeed.setTemperature(25.0, "C"); |
| 201 | + parFeed.setPressure(50.0, "bara"); |
| 202 | + parSystem.add(parFeed); |
| 203 | + |
| 204 | + Splitter parSplitter = new Splitter("splitter", parFeed); |
| 205 | + parSplitter.setSplitFactors(new double[] {0.6, 0.4}); |
| 206 | + parSystem.add(parSplitter); |
| 207 | + |
| 208 | + Heater parHeater1 = new Heater("heater1", parSplitter.getSplitStream(0)); |
| 209 | + parHeater1.setOutTemperature(350.0, "K"); |
| 210 | + parSystem.add(parHeater1); |
| 211 | + |
| 212 | + Cooler parCooler = new Cooler("cooler", parSplitter.getSplitStream(1)); |
| 213 | + parCooler.setOutTemperature(280.0, "K"); |
| 214 | + parSystem.add(parCooler); |
| 215 | + |
| 216 | + parSystem.runParallel(); |
| 217 | + |
| 218 | + double parHeater1Flow = parHeater1.getOutletStream().getFlowRate("kg/hr"); |
| 219 | + double parHeater1Temp = parHeater1.getOutletStream().getTemperature("K"); |
| 220 | + double parCoolerFlow = parCooler.getOutletStream().getFlowRate("kg/hr"); |
| 221 | + double parCoolerTemp = parCooler.getOutletStream().getTemperature("K"); |
| 222 | + |
| 223 | + // Verify results match |
| 224 | + assertEquals(seqHeater1Flow, parHeater1Flow, 1e-6, "Heater1 flow should match"); |
| 225 | + assertEquals(seqHeater1Temp, parHeater1Temp, 1e-6, "Heater1 temp should match"); |
| 226 | + assertEquals(seqCoolerFlow, parCoolerFlow, 1e-6, "Cooler flow should match"); |
| 227 | + assertEquals(seqCoolerTemp, parCoolerTemp, 1e-6, "Cooler temp should match"); |
| 228 | + } |
| 229 | + |
| 230 | + /** |
| 231 | + * Test that runOptimized correctly detects multi-input equipment. |
| 232 | + */ |
| 233 | + @Test |
| 234 | + void testRunOptimizedDetectsMultiInputEquipment() { |
| 235 | + ProcessSystem system = new ProcessSystem("Multi-input Test"); |
| 236 | + |
| 237 | + Stream feed1 = new Stream("feed1", testFluid.clone()); |
| 238 | + feed1.setFlowRate(500.0, "kg/hr"); |
| 239 | + system.add(feed1); |
| 240 | + |
| 241 | + Stream feed2 = new Stream("feed2", testFluid.clone()); |
| 242 | + feed2.setFlowRate(500.0, "kg/hr"); |
| 243 | + system.add(feed2); |
| 244 | + |
| 245 | + Mixer mixer = new Mixer("mixer"); |
| 246 | + mixer.addStream(feed1); |
| 247 | + mixer.addStream(feed2); |
| 248 | + system.add(mixer); |
| 249 | + |
| 250 | + // Should detect multi-input equipment and NOT recommend parallel execution |
| 251 | + assertTrue(system.hasMultiInputEquipment(), "Should detect mixer as multi-input"); |
| 252 | + assertFalse(system.isParallelExecutionBeneficial(), |
| 253 | + "Parallel should not be beneficial with mixer"); |
| 254 | + } |
| 255 | + |
| 256 | + /** |
| 257 | + * Test complex process with valve and compressor. |
| 258 | + */ |
| 259 | + @Test |
| 260 | + void testComplexProcessSequentialVsOptimized() { |
| 261 | + // Create process with sequential execution |
| 262 | + ProcessSystem seqSystem = new ProcessSystem("Sequential Complex"); |
| 263 | + seqSystem.setUseOptimizedExecution(false); |
| 264 | + |
| 265 | + Stream seqFeed = new Stream("feed", testFluid.clone()); |
| 266 | + seqFeed.setFlowRate(1000.0, "kg/hr"); |
| 267 | + seqFeed.setTemperature(25.0, "C"); |
| 268 | + seqFeed.setPressure(50.0, "bara"); |
| 269 | + seqSystem.add(seqFeed); |
| 270 | + |
| 271 | + ThrottlingValve seqValve = new ThrottlingValve("valve", seqFeed); |
| 272 | + seqValve.setOutletPressure(30.0); |
| 273 | + seqSystem.add(seqValve); |
| 274 | + |
| 275 | + Heater seqHeater = new Heater("heater", seqValve.getOutletStream()); |
| 276 | + seqHeater.setOutTemperature(320.0, "K"); |
| 277 | + seqSystem.add(seqHeater); |
| 278 | + |
| 279 | + Compressor seqCompressor = new Compressor("compressor", seqHeater.getOutletStream()); |
| 280 | + seqCompressor.setOutletPressure(80.0); |
| 281 | + seqSystem.add(seqCompressor); |
| 282 | + |
| 283 | + Cooler seqCooler = new Cooler("cooler", seqCompressor.getOutletStream()); |
| 284 | + seqCooler.setOutTemperature(300.0, "K"); |
| 285 | + seqSystem.add(seqCooler); |
| 286 | + |
| 287 | + seqSystem.run(); |
| 288 | + |
| 289 | + double seqOutPressure = seqCooler.getOutletStream().getPressure("bara"); |
| 290 | + double seqOutTemp = seqCooler.getOutletStream().getTemperature("K"); |
| 291 | + double seqOutFlow = seqCooler.getOutletStream().getFlowRate("kg/hr"); |
| 292 | + double seqCompPower = seqCompressor.getPower("kW"); |
| 293 | + |
| 294 | + // Create identical process with optimized execution |
| 295 | + ProcessSystem optSystem = new ProcessSystem("Optimized Complex"); |
| 296 | + |
| 297 | + Stream optFeed = new Stream("feed", testFluid.clone()); |
| 298 | + optFeed.setFlowRate(1000.0, "kg/hr"); |
| 299 | + optFeed.setTemperature(25.0, "C"); |
| 300 | + optFeed.setPressure(50.0, "bara"); |
| 301 | + optSystem.add(optFeed); |
| 302 | + |
| 303 | + ThrottlingValve optValve = new ThrottlingValve("valve", optFeed); |
| 304 | + optValve.setOutletPressure(30.0); |
| 305 | + optSystem.add(optValve); |
| 306 | + |
| 307 | + Heater optHeater = new Heater("heater", optValve.getOutletStream()); |
| 308 | + optHeater.setOutTemperature(320.0, "K"); |
| 309 | + optSystem.add(optHeater); |
| 310 | + |
| 311 | + Compressor optCompressor = new Compressor("compressor", optHeater.getOutletStream()); |
| 312 | + optCompressor.setOutletPressure(80.0); |
| 313 | + optSystem.add(optCompressor); |
| 314 | + |
| 315 | + Cooler optCooler = new Cooler("cooler", optCompressor.getOutletStream()); |
| 316 | + optCooler.setOutTemperature(300.0, "K"); |
| 317 | + optSystem.add(optCooler); |
| 318 | + |
| 319 | + optSystem.runOptimized(); |
| 320 | + |
| 321 | + double optOutPressure = optCooler.getOutletStream().getPressure("bara"); |
| 322 | + double optOutTemp = optCooler.getOutletStream().getTemperature("K"); |
| 323 | + double optOutFlow = optCooler.getOutletStream().getFlowRate("kg/hr"); |
| 324 | + double optCompPower = optCompressor.getPower("kW"); |
| 325 | + |
| 326 | + // Verify results match |
| 327 | + assertEquals(seqOutPressure, optOutPressure, 1e-6, "Outlet pressure should match"); |
| 328 | + assertEquals(seqOutTemp, optOutTemp, 1e-6, "Outlet temperature should match"); |
| 329 | + assertEquals(seqOutFlow, optOutFlow, 1e-6, "Outlet flow should match"); |
| 330 | + assertEquals(seqCompPower, optCompPower, 1e-6, "Compressor power should match"); |
| 331 | + } |
| 332 | + |
| 333 | + /** |
| 334 | + * Test that multiple runs produce consistent results with graph-based execution. |
| 335 | + */ |
| 336 | + @Test |
| 337 | + void testMultipleRunsConsistency() throws InterruptedException { |
| 338 | + ProcessSystem system = new ProcessSystem("Consistency Test"); |
| 339 | + |
| 340 | + Stream feed1 = new Stream("feed1", testFluid.clone()); |
| 341 | + feed1.setFlowRate(500.0, "kg/hr"); |
| 342 | + feed1.setTemperature(25.0, "C"); |
| 343 | + feed1.setPressure(50.0, "bara"); |
| 344 | + system.add(feed1); |
| 345 | + |
| 346 | + Stream feed2 = new Stream("feed2", testFluid.clone()); |
| 347 | + feed2.setFlowRate(500.0, "kg/hr"); |
| 348 | + feed2.setTemperature(30.0, "C"); |
| 349 | + feed2.setPressure(50.0, "bara"); |
| 350 | + system.add(feed2); |
| 351 | + |
| 352 | + Heater heater1 = new Heater("heater1", feed1); |
| 353 | + heater1.setOutTemperature(350.0, "K"); |
| 354 | + system.add(heater1); |
| 355 | + |
| 356 | + Heater heater2 = new Heater("heater2", feed2); |
| 357 | + heater2.setOutTemperature(340.0, "K"); |
| 358 | + system.add(heater2); |
| 359 | + |
| 360 | + // Run multiple times and verify consistency |
| 361 | + double[] results = new double[5]; |
| 362 | + for (int i = 0; i < 5; i++) { |
| 363 | + system.runParallel(); |
| 364 | + results[i] = heater1.getOutletStream().getFlowRate("kg/hr") |
| 365 | + + heater2.getOutletStream().getFlowRate("kg/hr"); |
| 366 | + } |
| 367 | + |
| 368 | + // All results should be identical |
| 369 | + for (int i = 1; i < 5; i++) { |
| 370 | + assertEquals(results[0], results[i], 1e-10, |
| 371 | + "Run " + i + " should produce same result as run 0"); |
| 372 | + } |
| 373 | + } |
| 374 | +} |
0 commit comments