Skip to content

Commit 0d59c43

Browse files
committed
update
1 parent 45c5f98 commit 0d59c43

File tree

1 file changed

+202
-1
lines changed

1 file changed

+202
-1
lines changed

src/neqsim/process/processTools.py

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,8 +406,51 @@ def __init__(self, name: str = ""):
406406
self._name = name
407407

408408
def _get_outlet(self, ref: Union[str, Any]) -> Any:
409-
"""Get outlet stream from equipment reference (name or object)."""
409+
"""
410+
Get outlet stream from equipment reference (name or object).
411+
412+
Supports dot notation for selecting specific outlets from separators:
413+
- 'separator.gas' or 'separator.vapor' - gas/vapor outlet
414+
- 'separator.liquid' - liquid outlet (2-phase separator)
415+
- 'separator.oil' - oil outlet (3-phase separator)
416+
- 'separator.water' or 'separator.aqueous' - water outlet (3-phase separator)
417+
418+
Examples:
419+
>>> builder.add_compressor('comp', 'sep.gas', pressure=100)
420+
>>> builder.add_pump('pump', 'sep.oil', pressure=50)
421+
"""
410422
if isinstance(ref, str):
423+
# Check for dot notation (e.g., 'separator.gas')
424+
if '.' in ref:
425+
parts = ref.split('.', 1)
426+
equip_name = parts[0]
427+
outlet_type = parts[1].lower()
428+
429+
equip = self.equipment.get(equip_name)
430+
if equip is None:
431+
raise ValueError(f"Equipment '{equip_name}' not found")
432+
433+
# Map outlet type to method
434+
outlet_methods = {
435+
'gas': ['getGasOutStream', 'getOutletStream'],
436+
'vapor': ['getGasOutStream', 'getOutletStream'],
437+
'liquid': ['getLiquidOutStream', 'getOilOutStream'],
438+
'oil': ['getOilOutStream', 'getLiquidOutStream'],
439+
'water': ['getWaterOutStream', 'getAqueousOutStream'],
440+
'aqueous': ['getWaterOutStream', 'getAqueousOutStream'],
441+
}
442+
443+
if outlet_type not in outlet_methods:
444+
raise ValueError(f"Unknown outlet type '{outlet_type}'. "
445+
f"Valid types: {list(outlet_methods.keys())}")
446+
447+
for method_name in outlet_methods[outlet_type]:
448+
if hasattr(equip, method_name):
449+
return getattr(equip, method_name)()
450+
451+
raise ValueError(f"Equipment '{equip_name}' does not have a '{outlet_type}' outlet")
452+
453+
# Standard lookup without dot notation
411454
equip = self.equipment.get(ref)
412455
if equip is None:
413456
raise ValueError(f"Equipment '{ref}' not found")
@@ -773,6 +816,164 @@ def get(self, name: str) -> Any:
773816
def get_process(self) -> Any:
774817
"""Get the underlying ProcessSystem object."""
775818
return self.process
819+
820+
def results_json(self) -> Dict[str, Any]:
821+
"""
822+
Get simulation results as a JSON-compatible dictionary.
823+
824+
Returns:
825+
Dictionary with all process results.
826+
827+
Example:
828+
>>> process = ProcessBuilder("Test").add_stream(...).run()
829+
>>> results = process.results_json()
830+
>>> print(json.dumps(results, indent=2))
831+
"""
832+
json_report = str(self.process.getReport_json())
833+
return json.loads(json_report)
834+
835+
def results_dataframe(self) -> pd.DataFrame:
836+
"""
837+
Get simulation results as a pandas DataFrame.
838+
839+
Returns:
840+
DataFrame with equipment results including temperatures,
841+
pressures, flow rates, power, and duties.
842+
843+
Example:
844+
>>> process = ProcessBuilder("Test").add_stream(...).run()
845+
>>> df = process.results_dataframe()
846+
>>> print(df)
847+
"""
848+
rows = []
849+
for name, eq in self.equipment.items():
850+
row = {'Equipment': name}
851+
852+
# Get outlet stream properties
853+
out_stream = None
854+
if hasattr(eq, 'getOutletStream'):
855+
out_stream = eq.getOutletStream()
856+
elif hasattr(eq, 'getOutStream'):
857+
out_stream = eq.getOutStream()
858+
elif hasattr(eq, 'getGasOutStream'):
859+
out_stream = eq.getGasOutStream()
860+
861+
if out_stream:
862+
try:
863+
row['T_out (°C)'] = round(out_stream.getTemperature() - 273.15, 2)
864+
except:
865+
pass
866+
try:
867+
row['P_out (bara)'] = round(out_stream.getPressure(), 2)
868+
except:
869+
pass
870+
try:
871+
row['Flow (kg/hr)'] = round(out_stream.getFlowRate('kg/hr'), 1)
872+
except:
873+
pass
874+
875+
# Get power/duty
876+
if hasattr(eq, 'getPower'):
877+
try:
878+
row['Power (kW)'] = round(eq.getPower() / 1e3, 2)
879+
except:
880+
pass
881+
if hasattr(eq, 'getDuty'):
882+
try:
883+
row['Duty (kW)'] = round(eq.getDuty() / 1e3, 2)
884+
except:
885+
pass
886+
887+
rows.append(row)
888+
889+
return pd.DataFrame(rows)
890+
891+
def print_results(self) -> 'ProcessBuilder':
892+
"""
893+
Print a formatted summary of simulation results.
894+
895+
Returns:
896+
Self for method chaining.
897+
898+
Example:
899+
>>> (ProcessBuilder("Test")
900+
... .add_stream('inlet', feed)
901+
... .add_compressor('comp1', 'inlet', pressure=100)
902+
... .run()
903+
... .print_results())
904+
"""
905+
print(f"\n{'='*60}")
906+
print(f"Process Results: {self._name}")
907+
print(f"{'='*60}\n")
908+
909+
for name, eq in self.equipment.items():
910+
print(f"📦 {name}")
911+
912+
# Get outlet stream properties
913+
out_stream = None
914+
if hasattr(eq, 'getOutletStream'):
915+
out_stream = eq.getOutletStream()
916+
elif hasattr(eq, 'getOutStream'):
917+
out_stream = eq.getOutStream()
918+
elif hasattr(eq, 'getGasOutStream'):
919+
out_stream = eq.getGasOutStream()
920+
921+
if out_stream:
922+
try:
923+
print(f" Temperature: {out_stream.getTemperature() - 273.15:.1f} °C")
924+
except:
925+
pass
926+
try:
927+
print(f" Pressure: {out_stream.getPressure():.1f} bara")
928+
except:
929+
pass
930+
try:
931+
print(f" Flow rate: {out_stream.getFlowRate('kg/hr'):.0f} kg/hr")
932+
except:
933+
pass
934+
935+
if hasattr(eq, 'getPower'):
936+
try:
937+
print(f" Power: {eq.getPower()/1e3:.2f} kW")
938+
except:
939+
pass
940+
if hasattr(eq, 'getDuty'):
941+
try:
942+
print(f" Duty: {eq.getDuty()/1e3:.2f} kW")
943+
except:
944+
pass
945+
print()
946+
947+
return self
948+
949+
def save_results(self, filename: str, format: str = 'json') -> 'ProcessBuilder':
950+
"""
951+
Save simulation results to a file.
952+
953+
Args:
954+
filename: Output file path.
955+
format: Output format - 'json', 'csv', or 'excel'.
956+
957+
Returns:
958+
Self for method chaining.
959+
960+
Example:
961+
>>> process.run().save_results('results.json')
962+
>>> process.save_results('results.csv', format='csv')
963+
>>> process.save_results('results.xlsx', format='excel')
964+
"""
965+
if format == 'json':
966+
with open(filename, 'w') as f:
967+
json.dump(self.results_json(), f, indent=2)
968+
elif format == 'csv':
969+
self.results_dataframe().to_csv(filename, index=False)
970+
elif format == 'excel':
971+
self.results_dataframe().to_excel(filename, index=False)
972+
else:
973+
raise ValueError(f"Unknown format: {format}. Use 'json', 'csv', or 'excel'.")
974+
975+
print(f"Results saved to {filename}")
976+
return self
776977

777978

778979
def _add_to_process(equipment: Any, process: Any = None) -> None:

0 commit comments

Comments
 (0)