Skip to content

Commit 5421126

Browse files
authored
Fix bugs process solver (#1837)
* update * update
1 parent 9471c25 commit 5421126

File tree

2 files changed

+377
-0
lines changed

2 files changed

+377
-0
lines changed

src/main/java/neqsim/process/processmodel/ProcessSystem.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,7 @@ public void removeUnit(String name) {
489489
for (int i = 0; i < unitOperations.size(); i++) {
490490
if (unitOperations.get(i).getName().equals(name)) {
491491
unitOperations.remove(i);
492+
graphDirty = true; // Invalidate graph when structure changes
492493
}
493494
}
494495
}
@@ -500,6 +501,7 @@ public void removeUnit(String name) {
500501
*/
501502
public void clearAll() {
502503
unitOperations.clear();
504+
graphDirty = true; // Invalidate graph when structure changes
503505
}
504506

505507
/**
@@ -509,6 +511,7 @@ public void clearAll() {
509511
*/
510512
public void clear() {
511513
unitOperations = new ArrayList<ProcessEquipmentInterface>(0);
514+
graphDirty = true; // Invalidate graph when structure changes
512515
}
513516

514517
/**
Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
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

Comments
 (0)