processpi 0.2.0.2__tar.gz → 0.2.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. {processpi-0.2.0.2 → processpi-0.2.0.4}/PKG-INFO +1 -1
  2. processpi-0.2.0.4/processpi/equipment/__init__.py +21 -0
  3. processpi-0.2.0.4/processpi/equipment/absorber.py +67 -0
  4. processpi-0.2.0.4/processpi/equipment/base.py +57 -0
  5. processpi-0.2.0.4/processpi/equipment/batch.py +39 -0
  6. processpi-0.2.0.4/processpi/equipment/compressor.py +80 -0
  7. processpi-0.2.0.4/processpi/equipment/cstr.py +41 -0
  8. processpi-0.2.0.4/processpi/equipment/decanter.py +61 -0
  9. processpi-0.2.0.4/processpi/equipment/distillation.py +82 -0
  10. processpi-0.2.0.4/processpi/equipment/flash.py +84 -0
  11. processpi-0.2.0.4/processpi/equipment/heater_cooler.py +103 -0
  12. processpi-0.2.0.4/processpi/equipment/heatexchangers/__init__.py +7 -0
  13. processpi-0.2.0.4/processpi/equipment/heatexchangers/base.py +129 -0
  14. processpi-0.2.0.4/processpi/equipment/heatexchangers/examples.py +116 -0
  15. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/__init__.py +53 -0
  16. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/basic_params.py +83 -0
  17. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/bell_method.py +101 -0
  18. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/classification.py +66 -0
  19. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/condenser.py +262 -0
  20. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/double_pipe.py +633 -0
  21. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/evaporator.py +263 -0
  22. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/kern_method.py +112 -0
  23. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/reboiler.py +288 -0
  24. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical/shell_tube.py +686 -0
  25. processpi-0.2.0.4/processpi/equipment/heatexchangers/mechanical.py +20 -0
  26. processpi-0.2.0.4/processpi/equipment/heatexchangers/simulation.py +186 -0
  27. processpi-0.2.0.4/processpi/equipment/heatexchangers/standards.py +118 -0
  28. processpi-0.2.0.4/processpi/equipment/heatexchangers/utils.py +1 -0
  29. processpi-0.2.0.4/processpi/equipment/heatexchangers_.py +155 -0
  30. processpi-0.2.0.4/processpi/equipment/mixer.py +54 -0
  31. processpi-0.2.0.4/processpi/equipment/pfr.py +44 -0
  32. processpi-0.2.0.4/processpi/equipment/pumps.py +92 -0
  33. processpi-0.2.0.4/processpi/equipment/splitter.py +41 -0
  34. processpi-0.2.0.4/processpi/equipment/stripper.py +67 -0
  35. processpi-0.2.0.4/processpi/equipment/turbine.py +81 -0
  36. processpi-0.2.0.4/processpi/equipment/valves.py +112 -0
  37. processpi-0.2.0.4/processpi/integration/__init__.py +5 -0
  38. processpi-0.2.0.4/processpi/integration/flowsheet.py +150 -0
  39. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi.egg-info/PKG-INFO +1 -1
  40. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi.egg-info/SOURCES.txt +37 -0
  41. {processpi-0.2.0.2 → processpi-0.2.0.4}/pyproject.toml +3 -1
  42. {processpi-0.2.0.2 → processpi-0.2.0.4}/LICENSE +0 -0
  43. {processpi-0.2.0.2 → processpi-0.2.0.4}/README.md +0 -0
  44. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/__init__.py +0 -0
  45. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/__init__.py +0 -0
  46. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/base.py +0 -0
  47. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/engine.py +0 -0
  48. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/__init__.py +0 -0
  49. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/flow_type.py +0 -0
  50. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/friction_factor_colebrookwhite.py +0 -0
  51. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/optimium_pipe_dia.py +0 -0
  52. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/pressure_drop_darcy.py +0 -0
  53. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/pressure_drop_fanning.py +0 -0
  54. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/pressure_drop_hazen_williams.py +0 -0
  55. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/pump_power.py +0 -0
  56. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/reynolds_number.py +0 -0
  57. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/fluids/velocity.py +0 -0
  58. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/__init__.py +0 -0
  59. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/biot.py +0 -0
  60. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/boiling_chen.py +0 -0
  61. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/combined_modes.py +0 -0
  62. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/condensation.py +0 -0
  63. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/condensation_dropwise.py +0 -0
  64. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/condensation_nusselt.py +0 -0
  65. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/conduction_heat_loss.py +0 -0
  66. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/convection_heat_loss.py +0 -0
  67. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/crossflow_tube.py +0 -0
  68. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/fourier.py +0 -0
  69. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/fourierlaw.py +0 -0
  70. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/grashof.py +0 -0
  71. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/heat_exchanger_area.py +0 -0
  72. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/lmtd.py +0 -0
  73. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/newton_cooling.py +0 -0
  74. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/ntu.py +0 -0
  75. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/nusselt.py +0 -0
  76. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/overall_u.py +0 -0
  77. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/peclet.py +0 -0
  78. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/prandtl.py +0 -0
  79. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/radial_cylinder.py +0 -0
  80. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/radiation_blackbody.py +0 -0
  81. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/radiation_exchange.py +0 -0
  82. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/radiation_greybody.py +0 -0
  83. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/radiation_viewfactor.py +0 -0
  84. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/resistance.py +0 -0
  85. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/reyleigh.py +0 -0
  86. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/rohsenow_boiling.py +0 -0
  87. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/heat_transfer/stefan_boltzmann.py +0 -0
  88. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/mass_transfer/__init__.py +0 -0
  89. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/mass_transfer/distillation_stage_count.py +0 -0
  90. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/mass_transfer/drying_rate.py +0 -0
  91. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/reaction_engineering/__init__.py +0 -0
  92. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/reaction_engineering/catalyst_activity.py +0 -0
  93. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/reaction_engineering/reaction_rate.py +0 -0
  94. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/reaction_engineering/residence_time.py +0 -0
  95. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/thermodynamics/__init__.py +0 -0
  96. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/thermodynamics/enthalpy_change.py +0 -0
  97. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/thermodynamics/entropy_change.py +0 -0
  98. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/calculations/thermodynamics/heat_of_vaporization.py +0 -0
  99. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/__init__.py +0 -0
  100. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/aceticacid.py +0 -0
  101. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/acetone.py +0 -0
  102. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/acrylicacid.py +0 -0
  103. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/air.py +0 -0
  104. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/ammonia.py +0 -0
  105. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/base.py +0 -0
  106. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/benzene.py +0 -0
  107. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/benzoicacid.py +0 -0
  108. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/bromine.py +0 -0
  109. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/butane.py +0 -0
  110. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/carbondioxide.py +0 -0
  111. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/carbonmonoxide.py +0 -0
  112. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/carbontetrachloride.py +0 -0
  113. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/chlorine.py +0 -0
  114. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/chlorobenzene.py +0 -0
  115. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/chloroform.py +0 -0
  116. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/chloromethane.py +0 -0
  117. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/constants.py +0 -0
  118. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/cyanogen.py +0 -0
  119. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/cyclohexene.py +0 -0
  120. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/ethane.py +0 -0
  121. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/ethanol.py +0 -0
  122. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/ethylacetate.py +0 -0
  123. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/ethylene.py +0 -0
  124. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/fluorine.py +0 -0
  125. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/fluorobenzene.py +0 -0
  126. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/formicacid.py +0 -0
  127. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/gas.py +0 -0
  128. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/inorganic_liquid.py +0 -0
  129. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/methanol.py +0 -0
  130. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/oil.py +0 -0
  131. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/organic_liquid.py +0 -0
  132. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/steam.py +0 -0
  133. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/toluene.py +0 -0
  134. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/vapor.py +0 -0
  135. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/components/water.py +0 -0
  136. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/constants.py +0 -0
  137. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/__init__.py +0 -0
  138. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/base.py +0 -0
  139. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/design_rules.py +0 -0
  140. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/engine.py +0 -0
  141. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/equipment.py +0 -0
  142. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/fittings.py +0 -0
  143. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/insulation.py +0 -0
  144. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/materials.py +0 -0
  145. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/network.py +0 -0
  146. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/nozzle.py +0 -0
  147. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/pipelineresults.py +0 -0
  148. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/pipes.py +0 -0
  149. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/piping_costs.py +0 -0
  150. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/pumps.py +0 -0
  151. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/selection.py +0 -0
  152. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/standards.py +0 -0
  153. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/pipelines/vessel.py +0 -0
  154. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/streams/__init__.py +0 -0
  155. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/streams/energy.py +0 -0
  156. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/streams/material.py +0 -0
  157. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/__init__.py +0 -0
  158. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/area.py +0 -0
  159. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/base.py +0 -0
  160. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/density.py +0 -0
  161. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/diameter.py +0 -0
  162. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/dimensionless.py +0 -0
  163. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/heat_flow.py +0 -0
  164. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/heat_flux.py +0 -0
  165. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/heat_of_vaporization.py +0 -0
  166. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/heat_transfer_coefficient.py +0 -0
  167. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/length.py +0 -0
  168. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/mass.py +0 -0
  169. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/mass_flowrate.py +0 -0
  170. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/molar_flowrate.py +0 -0
  171. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/power.py +0 -0
  172. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/pressure.py +0 -0
  173. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/specific_heat.py +0 -0
  174. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/strings.py +0 -0
  175. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/temperature.py +0 -0
  176. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/thermal_conductivity.py +0 -0
  177. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/thermal_resistance.py +0 -0
  178. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/velocity.py +0 -0
  179. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/viscosity.py +0 -0
  180. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/volume.py +0 -0
  181. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi/units/volumetric_flowrate.py +0 -0
  182. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi.egg-info/dependency_links.txt +0 -0
  183. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi.egg-info/requires.txt +0 -0
  184. {processpi-0.2.0.2 → processpi-0.2.0.4}/processpi.egg-info/top_level.txt +0 -0
  185. {processpi-0.2.0.2 → processpi-0.2.0.4}/setup.cfg +0 -0
  186. {processpi-0.2.0.2 → processpi-0.2.0.4}/setup.py +0 -0
  187. {processpi-0.2.0.2 → processpi-0.2.0.4}/tests/test_engine_network.py +0 -0
  188. {processpi-0.2.0.2 → processpi-0.2.0.4}/tests/test_network.py +0 -0
  189. {processpi-0.2.0.2 → processpi-0.2.0.4}/tests/test_pipeline_network.py +0 -0
  190. {processpi-0.2.0.2 → processpi-0.2.0.4}/tests/test_variables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: processpi
3
- Version: 0.2.0.2
3
+ Version: 0.2.0.4
4
4
  Summary: Python toolkit for chemical engineering simulations, equipment design, and unit conversions
5
5
  Home-page: https://processpi.org
6
6
  Author: Raviteja Varma Nadimpalli
@@ -0,0 +1,21 @@
1
+ """Equipment models and utilities for ProcessPI."""
2
+
3
+ __all__ = [
4
+ "absorber",
5
+ "base",
6
+ "batch",
7
+ "compressor",
8
+ "cstr",
9
+ "decanter",
10
+ "distillation",
11
+ "flash",
12
+ "heater_cooler",
13
+ "heatexchangers",
14
+ "mixer",
15
+ "pfr",
16
+ "pumps",
17
+ "splitter",
18
+ "stripper",
19
+ "turbine",
20
+ "valves",
21
+ ]
@@ -0,0 +1,67 @@
1
+ # processpi/equipment/absorber.py
2
+
3
+ from __future__ import annotations
4
+ from typing import Optional, Dict
5
+ from .base import Equipment
6
+ from ..streams import MaterialStream
7
+
8
+
9
+ class Absorber(Equipment):
10
+ """
11
+ Absorber Column.
12
+
13
+ Removes solute from gas stream into a liquid solvent.
14
+
15
+ Assumptions (initial version):
16
+ - Steady state
17
+ - Counter-current contact
18
+ - Uses overall K-removal efficiency (fraction of solute absorbed)
19
+ - Single major solute for simplicity (extendable to multicomponent)
20
+ """
21
+
22
+ def __init__(self, name: str, stages: int, removal_efficiency: float):
23
+ super().__init__(name)
24
+ self.stages = stages
25
+ self.removal_efficiency = removal_efficiency # 0–1 fraction
26
+
27
+ # Streams
28
+ self.gas_in: Optional[MaterialStream] = None
29
+ self.gas_out: Optional[MaterialStream] = None
30
+ self.liquid_in: Optional[MaterialStream] = None
31
+ self.liquid_out: Optional[MaterialStream] = None
32
+
33
+ def simulate(self) -> Dict:
34
+ if (self.gas_in is None or self.gas_out is None or
35
+ self.liquid_in is None or self.liquid_out is None):
36
+ raise ValueError(f"Absorber {self.name} must have gas_in, gas_out, liquid_in, liquid_out streams.")
37
+
38
+ F_gas = self.gas_in.flow_rate
39
+ F_liq = self.liquid_in.flow_rate
40
+
41
+ z_gas = self.gas_in.components.copy()
42
+ z_liq = self.liquid_in.components.copy()
43
+
44
+ # Pick solute (for demo: assume first component in gas is solute)
45
+ solute = list(z_gas.keys())[0]
46
+
47
+ # Apply removal efficiency
48
+ solute_in_gas = z_gas[solute] * F_gas
49
+ absorbed = solute_in_gas * self.removal_efficiency
50
+ solute_out_gas = solute_in_gas - absorbed
51
+
52
+ # Update outlet streams
53
+ self.gas_out.copy(self.gas_in)
54
+ self.liquid_out.copy(self.liquid_in)
55
+
56
+ self.gas_out.flow_rate = F_gas
57
+ self.liquid_out.flow_rate = F_liq + absorbed # solute added to solvent
58
+
59
+ # Adjust compositions
60
+ self.gas_out.components[solute] = solute_out_gas / F_gas
61
+ self.liquid_out.components[solute] = absorbed / self.liquid_out.flow_rate
62
+
63
+ return {
64
+ "absorbed": absorbed,
65
+ "gas_out_comp": self.gas_out.components,
66
+ "liq_out_comp": self.liquid_out.components,
67
+ }
@@ -0,0 +1,57 @@
1
+ # processpi/equipment/base.py
2
+ from typing import List, Optional, Literal
3
+ from ..streams import MaterialStream
4
+
5
+
6
+ class Equipment:
7
+ """
8
+ Base class for all equipment units.
9
+ Manages inlet/outlet MaterialStreams and ensures consistent graph connections.
10
+ """
11
+
12
+ def __init__(self, name: str, inlet_ports: int = 0, outlet_ports: int = 0):
13
+ self.name = name
14
+ self.inlets: List[Optional[MaterialStream]] = [None] * inlet_ports
15
+ self.outlets: List[Optional[MaterialStream]] = [None] * outlet_ports
16
+
17
+ def attach_stream(self, stream: MaterialStream, port: Literal["inlet", "outlet"], index: int = 0):
18
+ """
19
+ Attach a MaterialStream to an inlet or outlet port.
20
+ Enforces port registration and ensures stream ↔ equipment consistency.
21
+
22
+ Parameters
23
+ ----------
24
+ stream : MaterialStream
25
+ The stream to connect
26
+ port : "inlet" or "outlet"
27
+ Whether this is an inlet or outlet connection
28
+ index : int
29
+ Port index (for multi-port equipment like mixers/splitters)
30
+ """
31
+ if not isinstance(stream, MaterialStream):
32
+ raise TypeError(f"Expected MaterialStream, got {type(stream).__name__}")
33
+
34
+ if port == "inlet":
35
+ if index >= len(self.inlets) or index < 0:
36
+ raise IndexError(f"Inlet port {index} out of range (max={len(self.inlets)-1})")
37
+ if self.inlets[index] is not None:
38
+ raise ValueError(f"Inlet port {index} of {self.name} already occupied")
39
+ self.inlets[index] = stream
40
+ #stream.connect_outlet(self) # stream → equipment consistency
41
+
42
+ elif port == "outlet":
43
+ if index >= len(self.outlets) or index < 0:
44
+ raise IndexError(f"Outlet port {index} out of range (max={len(self.outlets)-1})")
45
+ if self.outlets[index] is not None:
46
+ raise ValueError(f"Outlet port {index} of {self.name} already occupied")
47
+ self.outlets[index] = stream
48
+ #stream.connect_inlet(self) # stream ← equipment consistency
49
+
50
+ else:
51
+ raise ValueError("port must be 'inlet' or 'outlet'")
52
+
53
+ def __repr__(self) -> str:
54
+ return (
55
+ f"<Equipment {self.name}, "
56
+ f"inlets={len(self.inlets)}, outlets={len(self.outlets)}>"
57
+ )
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+ from typing import Optional, Dict, Any
3
+ from .base import Equipment
4
+ from ..streams import MaterialStream
5
+
6
+
7
+ class BatchReactor(Equipment):
8
+ """
9
+ Batch Reactor.
10
+
11
+ Assumptions:
12
+ - Closed system (no inlet/outlet during reaction).
13
+ - Time-dependent simulation.
14
+ - Reaction integrated over given time.
15
+ """
16
+
17
+ def __init__(self, name: str, volume: float, rate_fn: Optional[callable] = None):
18
+ super().__init__(name)
19
+ self.volume = volume
20
+ self.rate_fn = rate_fn
21
+
22
+ def simulate(self, time: float = 1.0, steps: int = 10) -> Dict[str, Any]:
23
+ stream: MaterialStream = self.inlet # batch uses "inlet" as initial contents
24
+
25
+ if stream is None:
26
+ raise ValueError(f"BatchReactor {self.name} requires a feed MaterialStream as initial contents.")
27
+
28
+ if self.rate_fn:
29
+ dt = time / steps
30
+ for _ in range(steps):
31
+ rates = self.rate_fn(stream, stream.temperature, stream.pressure)
32
+ stream.apply_reaction(rates, residence_time=dt)
33
+
34
+ return {
35
+ "type": "BatchReactor",
36
+ "final_T": stream.temperature,
37
+ "final_F": stream.mass_flow,
38
+ "time": time
39
+ }
@@ -0,0 +1,80 @@
1
+ # processpi/equipment/compressor.py
2
+
3
+ from __future__ import annotations
4
+ from typing import Optional, Dict, Any
5
+ from .base import Equipment
6
+ from ..streams import MaterialStream
7
+ from ..units import Power
8
+
9
+
10
+ class Compressor(Equipment):
11
+ """
12
+ Gas-phase compressor.
13
+
14
+ Assumptions:
15
+ - Single stage.
16
+ - Isentropic relation with efficiency correction.
17
+ - No heat exchange (adiabatic).
18
+ """
19
+
20
+ def __init__(self, name: str,
21
+ isentropic_efficiency: float = 0.75,
22
+ outlet_pressure: Optional[float] = None,
23
+ pressure_ratio: Optional[float] = None):
24
+ super().__init__(name, inlet_ports=1, outlet_ports=1)
25
+ self.eta = isentropic_efficiency
26
+ self.outlet_pressure = outlet_pressure
27
+ self.pressure_ratio = pressure_ratio
28
+
29
+ def simulate(self) -> Dict[str, Any]:
30
+ inlet: MaterialStream = self.inlets[0]
31
+ outlet: MaterialStream = self.outlets[0]
32
+
33
+ if inlet is None or outlet is None:
34
+ raise ValueError(f"{self.name}: Must have inlet and outlet streams connected.")
35
+
36
+ if inlet.phase != "vapor":
37
+ raise ValueError(f"{self.name}: Compressor requires vapor phase at inlet.")
38
+
39
+ P1 = inlet.pressure.to("Pa").value
40
+ T1 = inlet.temperature.to("K").value
41
+ cp = inlet.get_property("cp") # J/kg-K
42
+ gamma = inlet.get_property("gamma") # cp/cv
43
+ m_dot = inlet.mass_flow().to("kg/s").value
44
+
45
+ # Determine outlet pressure
46
+ if self.outlet_pressure is not None:
47
+ P2 = self.outlet_pressure.to("Pa").value
48
+ elif self.pressure_ratio is not None:
49
+ P2 = P1 * self.pressure_ratio
50
+ else:
51
+ raise ValueError("Must specify outlet_pressure or pressure_ratio.")
52
+
53
+ if P2 <= P1:
54
+ raise ValueError("Compressor must increase pressure (P2 > P1).")
55
+
56
+ # Ideal (isentropic) outlet temperature
57
+ T2s = T1 * (P2 / P1) ** ((gamma - 1) / gamma)
58
+
59
+ # Actual outlet temperature with efficiency
60
+ T2 = T1 + (T2s - T1) / self.eta
61
+
62
+ # Work requirement (W = m_dot * cp * ΔT)
63
+ w_dot = m_dot * cp * (T2 - T1)
64
+ power = Power(w_dot, "W")
65
+
66
+ # Update outlet stream
67
+ outlet.copy_from(inlet)
68
+ outlet.pressure = P2
69
+ outlet.temperature.value = T2
70
+ # Phase remains vapor
71
+
72
+ return {
73
+ "inlet_P": P1,
74
+ "outlet_P": P2,
75
+ "Tin": T1,
76
+ "Tout": T2,
77
+ "m_dot": m_dot,
78
+ "power": power,
79
+ "efficiency": self.eta
80
+ }
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+ from typing import Optional, Dict, Any
3
+ from .base import Equipment
4
+ from ..streams import MaterialStream
5
+
6
+
7
+ class CSTR(Equipment):
8
+ """
9
+ Continuous Stirred Tank Reactor (CSTR).
10
+
11
+ Assumptions:
12
+ - Perfect mixing (outlet composition = reactor composition).
13
+ - Steady-state operation.
14
+ - Reaction kinetics placeholder (rate function can be injected).
15
+ """
16
+
17
+ def __init__(self, name: str, volume: float, rate_fn: Optional[callable] = None):
18
+ super().__init__(name)
19
+ self.volume = volume
20
+ self.rate_fn = rate_fn
21
+
22
+ def simulate(self) -> Dict[str, Any]:
23
+ inlet: MaterialStream = self.inlet
24
+ outlet: MaterialStream = self.outlet
25
+
26
+ if inlet is None or outlet is None:
27
+ raise ValueError(f"CSTR {self.name} must have inlet and outlet streams.")
28
+
29
+ outlet.copy_from(inlet)
30
+
31
+ if self.rate_fn:
32
+ rates = self.rate_fn(outlet, outlet.temperature, outlet.pressure)
33
+ outlet.apply_reaction(rates, residence_time=self.volume / inlet.volumetric_flow)
34
+
35
+ return {
36
+ "type": "CSTR",
37
+ "inlet_T": inlet.temperature,
38
+ "outlet_T": outlet.temperature,
39
+ "inlet_F": inlet.mass_flow,
40
+ "outlet_F": outlet.mass_flow
41
+ }
@@ -0,0 +1,61 @@
1
+ # processpi/equipment/decanter.py
2
+
3
+ from __future__ import annotations
4
+ from typing import Optional, Dict
5
+ from .base import Equipment
6
+ from ..streams import MaterialStream
7
+
8
+
9
+ class Decanter(Equipment):
10
+ """
11
+ Decanter (liquid–liquid phase separator).
12
+
13
+ Assumptions:
14
+ - Two immiscible liquid phases (e.g., water-rich and organic-rich).
15
+ - Separation based on density: heavy phase → bottom, light phase → top.
16
+ - User specifies split fractions OR component affinity (partitioning).
17
+ - For now: simple split fraction for light/heavy phase.
18
+ """
19
+
20
+ def __init__(self, name: str, light_fraction: float = 0.5):
21
+ super().__init__(name, inlet_ports=1, outlet_ports=2)
22
+ self.light_fraction = light_fraction # fraction of flow to light phase (0–1)
23
+
24
+ def simulate(self) -> Dict:
25
+ inlet: MaterialStream = self.inlets[0]
26
+ if inlet is None:
27
+ raise ValueError(f"Decanter {self.name} must have an inlet stream.")
28
+
29
+ outlet_light: MaterialStream = self.outlets[0]
30
+ outlet_heavy: MaterialStream = self.outlets[1]
31
+
32
+ if outlet_light is None or outlet_heavy is None:
33
+ raise ValueError(f"Decanter {self.name} must have two outlet streams attached.")
34
+
35
+ F_total = inlet.flow
36
+ z = inlet.composition.copy()
37
+
38
+ # Flow split
39
+ F_light = F_total * self.light_fraction
40
+ F_heavy = F_total - F_light
41
+
42
+ # For now: assume components split proportional to flow
43
+ # (extend later with partition coefficients)
44
+ outlet_light.copy_from(inlet)
45
+ outlet_heavy.copy_from(inlet)
46
+
47
+ outlet_light.flow = F_light
48
+ outlet_heavy.flow = F_heavy
49
+
50
+ # Normalize compositions (still same, just different flow basis)
51
+ outlet_light.composition = {k: v for k, v in z.items()}
52
+ outlet_heavy.composition = {k: v for k, v in z.items()}
53
+
54
+ return {
55
+ "F_in": F_total,
56
+ "F_light": F_light,
57
+ "F_heavy": F_heavy,
58
+ "z_in": z,
59
+ "z_light": outlet_light.composition,
60
+ "z_heavy": outlet_heavy.composition,
61
+ }
@@ -0,0 +1,82 @@
1
+ # processpi/equipment/distillation.py
2
+
3
+ from __future__ import annotations
4
+ from typing import Optional, Dict
5
+ from .base import Equipment
6
+ from ..streams import MaterialStream
7
+
8
+
9
+ class DistillationColumn(Equipment):
10
+ """
11
+ Simplified Distillation Column (binary or multicomponent).
12
+
13
+ Assumptions (initial version):
14
+ - Steady-state.
15
+ - Total condenser (distillate is liquid).
16
+ - Partial reboiler (bottoms is liquid).
17
+ - Uses constant relative volatility shortcut (Fenske-Underwood-Gilliland).
18
+ - Feed at specified stage.
19
+ - No rigorous stage-by-stage solving (yet).
20
+ """
21
+
22
+ def __init__(self, name: str, n_stages: int, feed_stage: int,
23
+ reflux_ratio: Optional[float] = None,
24
+ distillate_rate: Optional[float] = None):
25
+ super().__init__(name)
26
+ self.n_stages = n_stages
27
+ self.feed_stage = feed_stage
28
+ self.reflux_ratio = reflux_ratio
29
+ self.distillate_rate = distillate_rate
30
+
31
+ # Streams
32
+ self.feed: Optional[MaterialStream] = None
33
+ self.distillate: Optional[MaterialStream] = None
34
+ self.bottoms: Optional[MaterialStream] = None
35
+
36
+ def simulate(self) -> Dict:
37
+ if self.feed is None or self.distillate is None or self.bottoms is None:
38
+ raise ValueError(f"DistillationColumn {self.name} must have feed, distillate, and bottoms streams attached.")
39
+
40
+ # --- Step 1: Material balance ---
41
+ F = self.feed.flow
42
+ if self.distillate_rate is not None:
43
+ D = self.distillate_rate
44
+ B = F - D
45
+ else:
46
+ raise ValueError("distillate_rate must be specified (initial version).")
47
+
48
+ # --- Step 2: Split compositions using shortcut method ---
49
+ z_feed = self.feed.composition # dict: {component: mole fraction}
50
+ xD, xB = {}, {}
51
+
52
+ for comp, z in z_feed.items():
53
+ # Simple split rule: more volatile → enriched in distillate
54
+ if comp.relative_volatility >= 1.5: # crude threshold
55
+ xD[comp] = min(1.0, z * 1.5)
56
+ xB[comp] = max(0.0, z - (xD[comp] * D / F))
57
+ else:
58
+ xB[comp] = min(1.0, z * 1.5)
59
+ xD[comp] = max(0.0, z - (xB[comp] * B / F))
60
+
61
+ # Normalize
62
+ sum_xD = sum(xD.values())
63
+ sum_xB = sum(xB.values())
64
+ for comp in xD: xD[comp] /= sum_xD
65
+ for comp in xB: xB[comp] /= sum_xB
66
+
67
+ # --- Step 3: Copy to outlet streams ---
68
+ self.distillate.copy_from(self.feed)
69
+ self.distillate.flow = D
70
+ self.distillate.composition = xD
71
+
72
+ self.bottoms.copy_from(self.feed)
73
+ self.bottoms.flow = B
74
+ self.bottoms.composition = xB
75
+
76
+ return {
77
+ "feed_flow": F,
78
+ "distillate_flow": D,
79
+ "bottoms_flow": B,
80
+ "distillate_composition": xD,
81
+ "bottoms_composition": xB
82
+ }
@@ -0,0 +1,84 @@
1
+ # processpi/equipment/flash.py
2
+
3
+ from __future__ import annotations
4
+ from typing import Optional, Dict, Any
5
+ from .base import Equipment
6
+ from ..streams import MaterialStream
7
+ from ..thermo.vle import flash_equilibrium # placeholder VLE calc function
8
+
9
+ class FlashDrum(Equipment):
10
+ """
11
+ Flash Drum for vapor-liquid separation.
12
+
13
+ Modes:
14
+ - T, P specified (isothermal flash)
15
+ - P, Q specified (adiabatic flash with duty)
16
+ """
17
+
18
+ def __init__(self, name: str):
19
+ super().__init__(name, inlet_ports=1, outlet_ports=2)
20
+ self.vapor_out: Optional[MaterialStream] = None
21
+ self.liquid_out: Optional[MaterialStream] = None
22
+
23
+ def attach_stream(self, stream: MaterialStream, port: str):
24
+ if port == "in":
25
+ self.inlets[0] = stream
26
+ stream.connect_outlet(self)
27
+ elif port == "vapor":
28
+ self.vapor_out = stream
29
+ stream.connect_inlet(self)
30
+ elif port == "liquid":
31
+ self.liquid_out = stream
32
+ stream.connect_inlet(self)
33
+ else:
34
+ raise ValueError(f"Invalid port: {port}")
35
+
36
+ def simulate(self,
37
+ pressure: Optional[float] = None,
38
+ temperature: Optional[float] = None,
39
+ duty: Optional[float] = None) -> Dict[str, Any]:
40
+ """
41
+ Perform flash separation.
42
+
43
+ Parameters:
44
+ - pressure: operating P [Pa]
45
+ - temperature: operating T [K]
46
+ - duty: optional heat duty (W)
47
+
48
+ Returns:
49
+ - dict with vapor_fraction, liquid_fraction, compositions, enthalpies
50
+ """
51
+ feed = self.inlets[0]
52
+ if feed is None or self.vapor_out is None or self.liquid_out is None:
53
+ raise ValueError(f"{self.name}: must attach feed, vapor, and liquid streams")
54
+
55
+ # Use feed enthalpy/composition
56
+ z = feed.components
57
+ P = pressure or feed.pressure.value
58
+ T = temperature or feed.temperature.value
59
+
60
+ # Call thermo package (placeholder)
61
+ flash_results = flash_equilibrium(z, T, P, duty=duty)
62
+
63
+ Vfrac = flash_results["vapor_fraction"]
64
+ Lfrac = 1 - Vfrac
65
+
66
+ # Update outlet streams
67
+ self.vapor_out.set_composition(flash_results["y"], basis="mole")
68
+ self.vapor_out.pressure = feed.pressure
69
+ self.vapor_out.temperature = feed.temperature
70
+ self.vapor_out.phase = "vapor"
71
+
72
+ self.liquid_out.set_composition(flash_results["x"], basis="mole")
73
+ self.liquid_out.pressure = feed.pressure
74
+ self.liquid_out.temperature = feed.temperature
75
+ self.liquid_out.phase = "liquid"
76
+
77
+ return {
78
+ "Vfrac": Vfrac,
79
+ "Lfrac": Lfrac,
80
+ "y": flash_results["y"],
81
+ "x": flash_results["x"],
82
+ "T": T,
83
+ "P": P
84
+ }
@@ -0,0 +1,103 @@
1
+ from typing import Optional, Dict, Any
2
+ from ..streams import MaterialStream, EnergyStream
3
+ from .base import Equipment
4
+
5
+
6
+ class HeatTransferUnit(Equipment):
7
+ """
8
+ Base class for heater/cooler type units.
9
+ Supports sensible (+ latent, via MaterialStream auto phase handling).
10
+ Energy balance:
11
+ Q = m * cp * ΔT [+/- m * Hvap if phase change occurs]
12
+ """
13
+
14
+ def __init__(self, name: str, mode: str = "heater", energy_stream: Optional[EnergyStream] = None):
15
+ super().__init__(name, inlet_ports=1, outlet_ports=1)
16
+ self.mode = mode # "heater" or "cooler"
17
+ self.energy_stream = energy_stream
18
+
19
+ # Auto-bind energy stream if provided
20
+ if self.energy_stream:
21
+ self.energy_stream.bind_equipment(self)
22
+
23
+ def simulate(
24
+ self,
25
+ heat_duty: Optional[float] = None,
26
+ outlet_temperature: Optional[float] = None
27
+ ) -> Dict[str, Any]:
28
+ """
29
+ Simulate heater/cooler.
30
+
31
+ Parameters:
32
+ - heat_duty (W) [optional]
33
+ - outlet_temperature (float) [optional]
34
+
35
+ Returns:
36
+ - dict with results: Q (W), Tin, Tout, phase_in, phase_out
37
+ """
38
+ inlet: MaterialStream = self.inlets[0]
39
+ outlet: MaterialStream = self.outlets[0]
40
+
41
+ if inlet is None or outlet is None:
42
+ raise ValueError(f"{self.name}: Must have both inlet and outlet connected.")
43
+
44
+ m_dot = inlet.mass_flow().to("kg/s").value
45
+ cp = inlet.get_property("cp") # J/kg-K
46
+
47
+ Tin = inlet.temperature.value
48
+ Tout = outlet.temperature.value if outlet.temperature else None
49
+ Qin = 0.0
50
+
51
+ # -------------------------
52
+ # Case 1: Duty specified
53
+ # -------------------------
54
+ if heat_duty is not None:
55
+ dT = heat_duty / (m_dot * cp)
56
+ Tout = Tin + dT
57
+ Qin = heat_duty
58
+
59
+ # -------------------------
60
+ # Case 2: Outlet T specified
61
+ # -------------------------
62
+ elif outlet_temperature is not None:
63
+ Tout = outlet_temperature
64
+ Qin = m_dot * cp * (Tout - Tin)
65
+
66
+ else:
67
+ raise ValueError("Must specify either heat_duty or outlet_temperature.")
68
+
69
+ # Update outlet stream — phase auto-updates inside MaterialStream
70
+ outlet.temperature.value = Tout
71
+ outlet.P = inlet.P
72
+ outlet.flow_rate = inlet.flow_rate
73
+
74
+ # Flip sign for cooler
75
+ if self.mode == "cooler" and Qin > 0:
76
+ Qin *= -1
77
+
78
+ # -------------------------
79
+ # EnergyStream logging
80
+ # -------------------------
81
+ if self.energy_stream:
82
+ tag = "Q_in" if Qin > 0 else "Q_out"
83
+ self.energy_stream.record(abs(Qin), tag=tag, equipment=self.name)
84
+
85
+ return {
86
+ "Q": Qin,
87
+ "Tin": Tin,
88
+ "Tout": Tout,
89
+ "phase_in": inlet.phase,
90
+ "phase_out": outlet.phase, # comes directly from MaterialStream auto-detection
91
+ "cp": cp,
92
+ "m_dot": m_dot,
93
+ }
94
+
95
+
96
+ class Heater(HeatTransferUnit):
97
+ def __init__(self, name: str, energy_stream: Optional[EnergyStream] = None):
98
+ super().__init__(name, mode="heater", energy_stream=energy_stream)
99
+
100
+
101
+ class Cooler(HeatTransferUnit):
102
+ def __init__(self, name: str, energy_stream: Optional[EnergyStream] = None):
103
+ super().__init__(name, mode="cooler", energy_stream=energy_stream)
@@ -0,0 +1,7 @@
1
+ from .base import HeatExchanger
2
+ from .mechanical import run_mechanical_design
3
+
4
+ __all__ = [
5
+ "HeatExchanger",
6
+ "run_mechanical_design",
7
+ ]