processpi 0.0.0__py3-none-any.whl

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 (96) hide show
  1. processpi/__init__.py +1 -0
  2. processpi/calculations/__init__.py +3 -0
  3. processpi/calculations/base.py +55 -0
  4. processpi/calculations/engine.py +69 -0
  5. processpi/calculations/fluids/__init__.py +13 -0
  6. processpi/calculations/fluids/cool_brook_white.py +49 -0
  7. processpi/calculations/fluids/flow_type.py +37 -0
  8. processpi/calculations/fluids/optimium_pipe_dia.py +45 -0
  9. processpi/calculations/fluids/pressure_drop_darcy.py +25 -0
  10. processpi/calculations/fluids/pressure_drop_fanning.py +25 -0
  11. processpi/calculations/fluids/pump_power.py +34 -0
  12. processpi/calculations/fluids/reynolds_number.py +24 -0
  13. processpi/calculations/fluids/velocity.py +36 -0
  14. processpi/calculations/heat_transfer/__init__.py +4 -0
  15. processpi/calculations/heat_transfer/conduction_heat_loss.py +26 -0
  16. processpi/calculations/heat_transfer/convection_heat_loss.py +23 -0
  17. processpi/calculations/heat_transfer/heat_exchanger_area.py +26 -0
  18. processpi/calculations/mass_transfer/__init__.py +3 -0
  19. processpi/calculations/mass_transfer/distillation_stage_count.py +346 -0
  20. processpi/calculations/mass_transfer/drying_rate.py +132 -0
  21. processpi/calculations/reaction_engineering/__init__.py +4 -0
  22. processpi/calculations/reaction_engineering/catalyst_activity.py +109 -0
  23. processpi/calculations/reaction_engineering/reaction_rate.py +143 -0
  24. processpi/calculations/reaction_engineering/residence_time.py +80 -0
  25. processpi/calculations/thermodynamics/__init__.py +4 -0
  26. processpi/calculations/thermodynamics/enthalpy_change.py +23 -0
  27. processpi/calculations/thermodynamics/entropy_change.py +25 -0
  28. processpi/calculations/thermodynamics/heat_of_vaporization.py +20 -0
  29. processpi/components/__init__.py +5 -0
  30. processpi/components/aceticacid.py +19 -0
  31. processpi/components/acetone.py +20 -0
  32. processpi/components/acrylicacid.py +19 -0
  33. processpi/components/air.py +20 -0
  34. processpi/components/ammonia.py +19 -0
  35. processpi/components/base.py +85 -0
  36. processpi/components/benzene.py +40 -0
  37. processpi/components/benzoicacid.py +19 -0
  38. processpi/components/bromine.py +19 -0
  39. processpi/components/butane.py +19 -0
  40. processpi/components/carbondioxide.py +19 -0
  41. processpi/components/carbonmonoxide.py +17 -0
  42. processpi/components/carbontetrachloride.py +17 -0
  43. processpi/components/chlorine.py +17 -0
  44. processpi/components/chlorobenzene.py +17 -0
  45. processpi/components/chloroform.py +17 -0
  46. processpi/components/chloromethane.py +17 -0
  47. processpi/components/cyanogen.py +17 -0
  48. processpi/components/cyclohexene.py +17 -0
  49. processpi/components/ethane.py +17 -0
  50. processpi/components/ethanol.py +20 -0
  51. processpi/components/ethylacetate.py +17 -0
  52. processpi/components/methanol.py +20 -0
  53. processpi/components/toluene.py +22 -0
  54. processpi/components/water.py +24 -0
  55. processpi/pipelines/__init__.py +0 -0
  56. processpi/pipelines/base.py +76 -0
  57. processpi/pipelines/design_rules.py +91 -0
  58. processpi/pipelines/engine.py +718 -0
  59. processpi/pipelines/equipment.py +32 -0
  60. processpi/pipelines/fittings.py +76 -0
  61. processpi/pipelines/insulation.py +0 -0
  62. processpi/pipelines/materials.py +102 -0
  63. processpi/pipelines/network.py +277 -0
  64. processpi/pipelines/pipelineresults.py +95 -0
  65. processpi/pipelines/pipes.py +84 -0
  66. processpi/pipelines/pumps.py +83 -0
  67. processpi/pipelines/selection.py +89 -0
  68. processpi/pipelines/standards.py +279 -0
  69. processpi/pipelines/vessel.py +34 -0
  70. processpi/units/__init__.py +38 -0
  71. processpi/units/area.py +51 -0
  72. processpi/units/base.py +57 -0
  73. processpi/units/density.py +47 -0
  74. processpi/units/diameter.py +58 -0
  75. processpi/units/dimensionless.py +42 -0
  76. processpi/units/heat_flux.py +46 -0
  77. processpi/units/heat_of_vaporization.py +47 -0
  78. processpi/units/heat_transfer_coefficient.py +45 -0
  79. processpi/units/length.py +47 -0
  80. processpi/units/mass.py +50 -0
  81. processpi/units/mass_flowrate.py +50 -0
  82. processpi/units/pressure.py +57 -0
  83. processpi/units/specific_heat.py +47 -0
  84. processpi/units/strings.py +31 -0
  85. processpi/units/temperature.py +51 -0
  86. processpi/units/thermal_conductivity.py +45 -0
  87. processpi/units/thermal_resistance.py +46 -0
  88. processpi/units/velocity.py +45 -0
  89. processpi/units/viscosity.py +78 -0
  90. processpi/units/volume.py +50 -0
  91. processpi/units/volumetric_flowrate.py +52 -0
  92. processpi-0.0.0.dist-info/METADATA +5 -0
  93. processpi-0.0.0.dist-info/RECORD +96 -0
  94. processpi-0.0.0.dist-info/WHEEL +5 -0
  95. processpi-0.0.0.dist-info/licenses/LICENSE +21 -0
  96. processpi-0.0.0.dist-info/top_level.txt +1 -0
processpi/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,3 @@
1
+ from .engine import CalculationEngine
2
+ from .base import CalculationBase
3
+ __all__ = ["CalculationEngine", "CalculationBase"]
@@ -0,0 +1,55 @@
1
+ # base.py
2
+ from abc import ABC, abstractmethod
3
+
4
+ class CalculationBase(ABC):
5
+ """
6
+ Abstract base for all calculation modules in processpi.
7
+ """
8
+
9
+ def __init__(self, **kwargs):
10
+ """
11
+ Store all input parameters as attributes.
12
+ """
13
+ self.inputs = kwargs
14
+ self.validate_inputs()
15
+
16
+ @abstractmethod
17
+ def validate_inputs(self):
18
+ """
19
+ Validate inputs and raise ValueError if invalid.
20
+ """
21
+ pass
22
+
23
+ @abstractmethod
24
+ def calculate(self):
25
+ """
26
+ Perform the calculation and return results as a dict.
27
+ """
28
+ pass
29
+
30
+ def get_inputs(self):
31
+ """
32
+ Return a dictionary of original inputs.
33
+ """
34
+ return self.inputs
35
+
36
+ def to_dict(self):
37
+ """
38
+ Return a dictionary with both inputs and results.
39
+ """
40
+ result = self.calculate()
41
+ return {
42
+ "inputs": self.inputs,
43
+ "results": result
44
+ }
45
+ @staticmethod
46
+ def _get_value(x, name):
47
+ """Return numeric value if x is numeric or x.value if it's a Variable-like object."""
48
+ if hasattr(x, "value"):
49
+ return x.value
50
+ try:
51
+ # accept numpy/scalar numbers
52
+ return float(x)
53
+ except Exception:
54
+ raise TypeError(f"Could not interpret {name} value: {x!r}")
55
+
@@ -0,0 +1,69 @@
1
+ """
2
+ engine.py
3
+ ---------
4
+ This module will act as the central engine for performing calculations in ProcessPI.
5
+
6
+ Responsibilities:
7
+ 1. Receive input parameters for a calculation.
8
+ 2. Select the correct calculation class/function based on the requested operation.
9
+ 3. Handle validation and error reporting.
10
+ 4. Return results in a standardized format.
11
+ """
12
+
13
+ from typing import Any, Dict, Type
14
+ from processpi.units import * # Import all unit classes
15
+ from processpi.calculations import * # Import all calculation classes
16
+
17
+ # Import calculation classes here
18
+ # Example: from processpi.calculations.heat_transfer import HeatTransferCalculator
19
+ # We'll add them as we implement each calculation
20
+ # For now, I'll just put placeholders for demonstration
21
+ # ----------------------------------------------------------------------
22
+
23
+ class CalculationEngine:
24
+ """
25
+ Central hub for all ProcessPI calculations.
26
+ """
27
+
28
+ def __init__(self):
29
+ """
30
+ Initializes the engine with a registry of available calculations.
31
+ The registry maps calculation names (strings) to their handler classes.
32
+ """
33
+ self.registry: Dict[str, Type] = {}
34
+
35
+ def register_calculation(self, name: str, calc_class: Type):
36
+ """
37
+ Register a new calculation class to the engine.
38
+
39
+ Args:
40
+ name (str): A unique string key for the calculation.
41
+ calc_class (Type): The class that implements the calculation logic.
42
+
43
+ Example:
44
+ engine.register_calculation("heat_transfer", HeatTransferCalculator)
45
+ """
46
+ self.registry[name] = calc_class
47
+
48
+ def calculate(self, name: str, **kwargs) -> Any:
49
+ """
50
+ Executes a calculation by name.
51
+
52
+ Args:
53
+ name (str): The registered name of the calculation.
54
+ **kwargs: All input parameters required for the calculation.
55
+
56
+ Returns:
57
+ Any: The result of the calculation.
58
+
59
+ Raises:
60
+ ValueError: If the calculation name is not registered.
61
+ """
62
+ if name not in self.registry:
63
+ raise ValueError(f"Calculation '{name}' not found in registry.")
64
+
65
+ calc_class = self.registry[name]
66
+ calc_instance = calc_class(**kwargs) # Pass all inputs to the class constructor
67
+ return calc_instance.calculate() # All calculation classes must have a `.calculate()` method
68
+
69
+
@@ -0,0 +1,13 @@
1
+ """ Init module for Process PI calculation engine."""
2
+
3
+ # Implementation will be added here.
4
+ from .pressure_drop_darcy import PressureDropDarcy
5
+ from .pressure_drop_fanning import PressureDropFanning
6
+ from .pump_power import PumpPower
7
+ from .reynolds_number import ReynoldsNumber
8
+ from .optimium_pipe_dia import OptimumPipeDiameter
9
+ from .velocity import FluidVelocity
10
+ from .cool_brook_white import ColebrookWhite
11
+ from .flow_type import TypeOfFlow
12
+
13
+ __all__ = ["PressureDropDarcy", "PressureDropFanning", "PumpPower", "ReynoldsNumber", "OptimumPipeDiameter", "FluidVelocity", "ColebrookWhite", "TypeOfFlow"]
@@ -0,0 +1,49 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ import math
5
+
6
+
7
+ class ColebrookWhite(CalculationBase):
8
+ """
9
+ Calculate Darcy friction factor using the Colebrook–White equation.
10
+
11
+ Formula (implicit):
12
+ 1/sqrt(f) = -2.0 * log10( (ε/D)/3.7 + 2.51/(Re*sqrt(f)) )
13
+
14
+ Notes:
15
+ - For laminar flow (Re < 2000), f = 64/Re.
16
+ - Solved iteratively for turbulent flow.
17
+ """
18
+
19
+ def validate_inputs(self):
20
+ required = ["reynolds_number", "diameter", "roughness"]
21
+ for key in required:
22
+ if key not in self.inputs:
23
+ raise ValueError(f"Missing required input: {key}")
24
+
25
+ def calculate(self):
26
+ Re = self._get_value(self.inputs["reynolds_number"], "reynolds_number")
27
+ D = self._get_value(self.inputs["diameter"], "diameter") / 1000 # m
28
+ eps = self._get_value(self.inputs["roughness"], "roughness") # m
29
+
30
+ # Laminar flow check
31
+ if Re < 2000:
32
+ f = 64.0 / Re
33
+ return Dimensionless(f)
34
+
35
+ # Initial guess
36
+ f = 0.02
37
+ tol = 1e-6
38
+ max_iter = 100
39
+
40
+ for _ in range(max_iter):
41
+ lhs = 1.0 / math.sqrt(f)
42
+ rhs = -2.0 * math.log10((eps / (3.7 * D)) + (2.51 / (Re * math.sqrt(f))))
43
+ new_f = 1.0 / (rhs**2)
44
+
45
+ if abs(new_f - f) < tol:
46
+ return Dimensionless(new_f)
47
+ f = new_f
48
+
49
+ return Dimensionless(f)
@@ -0,0 +1,37 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+
5
+ class TypeOfFlow(CalculationBase):
6
+ """
7
+ Determine the type of flow based on Reynolds number.
8
+
9
+ Criteria:
10
+ - Laminar: Re < 2000
11
+ - Transitional: 2000 ≤ Re ≤ 4000
12
+ - Turbulent: Re > 4000
13
+
14
+ Inputs:
15
+ - reynolds_number (Re) [dimensionless]
16
+
17
+ Output:
18
+ - Flow type as a StringUnit
19
+ """
20
+
21
+ def validate_inputs(self):
22
+ required = ["reynolds_number"]
23
+ for key in required:
24
+ if key not in self.inputs:
25
+ raise ValueError(f"Missing required input: {key}")
26
+
27
+ def calculate(self):
28
+ Re = self._get_value(self.inputs["reynolds_number"], "reynolds_number")
29
+
30
+ if Re < 2000:
31
+ flow_type = "Laminar"
32
+ elif 2000 <= Re <= 4000:
33
+ flow_type = "Transitional"
34
+ else:
35
+ flow_type = "Turbulent"
36
+
37
+ return StringUnit(flow_type, "flow_type")
@@ -0,0 +1,45 @@
1
+ # processpi/calculations/fluids/optimum_pipe_diameter.py
2
+ from ..base import CalculationBase
3
+ from ...units import *
4
+ from ...pipelines.standards import get_nearest_diameter
5
+
6
+ class OptimumPipeDiameter(CalculationBase):
7
+ """
8
+ Calculate the optimum pipe diameter using empirical formula and map to nearest standard:
9
+
10
+ D_opt = 293 * (Q^0.53) * (ρ^-0.37)
11
+
12
+ Where:
13
+ - Q = Volumetric flow rate [m³/s]
14
+ - ρ = Fluid density [kg/m³]
15
+ - D_opt = Optimum diameter [mm]
16
+
17
+ Inputs:
18
+ - flow_rate (Q) [m³/s]
19
+ - density (ρ) [kg/m³]
20
+
21
+ Output:
22
+ - Nearest standard diameter [Diameter object, mm]
23
+ """
24
+
25
+ def validate_inputs(self):
26
+ required = ["flow_rate", "density"]
27
+ for key in required:
28
+ if key not in self.inputs:
29
+ raise ValueError(f"Missing required input: {key}")
30
+
31
+ def calculate(self):
32
+ # Get inputs
33
+ Q_volumetric = self._get_value(self.inputs["flow_rate"], "flow_rate") # m³/s
34
+ rho = self._get_value(self.inputs["density"], "density") # kg/m³
35
+
36
+ # Mass flow for formula
37
+ Q_mass = Q_volumetric * rho # kg/s
38
+
39
+ # Empirical formula for optimum diameter
40
+ D_opt = 293 * (Q_mass ** 0.53) * (rho ** -0.37) # mm
41
+ D_opt = Diameter(D_opt, "mm")
42
+
43
+ # Map to nearest standard diameter
44
+ nearest_std = get_nearest_diameter(D_opt)
45
+ return nearest_std
@@ -0,0 +1,25 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class PressureDropDarcy(CalculationBase):
5
+ """
6
+ Calculate pressure drop using Darcy–Weisbach equation.
7
+ Formula:
8
+ ΔP = f * (L/D) * (ρ * v² / 2)
9
+ """
10
+
11
+ def validate_inputs(self):
12
+ required = ["friction_factor", "length", "diameter", "density", "velocity"]
13
+ for key in required:
14
+ if key not in self.inputs:
15
+ raise ValueError(f"Missing required input: {key}")
16
+
17
+ def calculate(self):
18
+ f = self._get_value(self.inputs["friction_factor"], "friction_factor")
19
+ L = self._get_value(self.inputs["length"], "length") # m
20
+ D = self._get_value(self.inputs["diameter"], "diameter") # m
21
+ rho = self._get_value(self.inputs["density"], "density") # kg/m³
22
+ v = self._get_value(self.inputs["velocity"], "velocity") # m/s
23
+
24
+ delta_P = f * (L / D) * (rho * v**2 / 2)
25
+ return Pressure(delta_P, "Pa") # Return result as Pressure object
@@ -0,0 +1,25 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class PressureDropFanning(CalculationBase):
5
+ """
6
+ Calculate pressure drop using Fanning equation.
7
+ Formula:
8
+ ΔP = 4 * f * (L/D) * (ρ * v² / 2)
9
+ """
10
+
11
+ def validate_inputs(self):
12
+ required = ["friction_factor", "length", "diameter", "density", "velocity"]
13
+ for key in required:
14
+ if key not in self.inputs:
15
+ raise ValueError(f"Missing required input: {key}")
16
+
17
+ def calculate(self):
18
+ f = self._get_value(self.inputs["friction_factor"], "friction_factor")
19
+ L = self._get_value(self.inputs["length"], "length") # m
20
+ D = self._get_value(self.inputs["diameter"], "diameter") # m
21
+ rho = self._get_value(self.inputs["density"], "density") # kg/m³
22
+ v = self._get_value(self.inputs["velocity"], "velocity") # m/s
23
+
24
+ delta_P = 4 * f * (L / D) * (rho * v**2 / 2)
25
+ return Pressure(delta_P, "Pa") # Return result as Pressure object
@@ -0,0 +1,34 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class PumpPower(CalculationBase):
5
+ """
6
+ Calculate pump power from flow rate, head, density, and efficiency.
7
+ """
8
+
9
+ def validate_inputs(self):
10
+ # Ensure required inputs are present and valid
11
+ required = ["flow_rate", "head", "density", "efficiency"]
12
+ for key in required:
13
+ if key not in self.inputs:
14
+ raise ValueError(f"Missing required input: {key}")
15
+ if not (0 < self.inputs["efficiency"] <= 1):
16
+ raise ValueError("Efficiency must be between 0 and 1")
17
+
18
+ def calculate(self):
19
+ g = 9.81 # m/s²
20
+ flow_rate = self._get_value(self.inputs["flow_rate"], "flow_rate") # m³/s
21
+ head = self._get_value(self.inputs["head"], "head") # m
22
+ density = self._get_value(self.inputs["density"], "density") # kg/m³
23
+ efficiency = self._get_value(self.inputs["efficiency"], "efficiency") # decimal
24
+
25
+ # Hydraulic Power (Watts)
26
+ hydraulic_power = density * g * flow_rate * head
27
+
28
+ # Shaft Power (Watts)
29
+ shaft_power = hydraulic_power / efficiency
30
+
31
+ return {
32
+ "hydraulic_power_W": hydraulic_power,
33
+ "shaft_power_W": shaft_power
34
+ }
@@ -0,0 +1,24 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class ReynoldsNumber(CalculationBase):
5
+ """
6
+ Calculate Reynolds number for pipe flow.
7
+ Formula:
8
+ Re = (ρ * v * D) / μ
9
+ """
10
+
11
+ def validate_inputs(self):
12
+ required = ["density", "velocity", "diameter", "viscosity"]
13
+ for key in required:
14
+ if key not in self.inputs:
15
+ raise ValueError(f"Missing required input: {key}")
16
+
17
+ def calculate(self):
18
+ rho = self._get_value(self.inputs["density"], "density") # kg/m³
19
+ v = self._get_value(self.inputs["velocity"], "velocity") # m/s
20
+ D = self._get_value(self.inputs["diameter"], "diameter") # m
21
+ mu = self._get_value(self.inputs["viscosity"], "viscosity") # Pa·s
22
+
23
+ Re = rho * v * D / mu
24
+ return Dimensionless(Re)
@@ -0,0 +1,36 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ import math
5
+
6
+
7
+ class FluidVelocity(CalculationBase):
8
+ """
9
+ Calculate fluid velocity in a circular pipe.
10
+
11
+ Formula:
12
+ v = Q / A
13
+ = 4Q / (πD²)
14
+
15
+ Inputs:
16
+ - volumetric_flow_rate (Q) [m³/s]
17
+ - diameter (D) [m]
18
+
19
+ Output:
20
+ - velocity (v) [m/s]
21
+ """
22
+
23
+ def validate_inputs(self):
24
+ required = ["volumetric_flow_rate", "diameter"]
25
+ for key in required:
26
+ if key not in self.inputs:
27
+ raise ValueError(f"Missing required input: {key}")
28
+
29
+ def calculate(self):
30
+ Q = self._get_value(self.inputs["volumetric_flow_rate"], "volumetric_flow_rate") # m³/s
31
+ D = self._get_value(self.inputs["diameter"], "diameter") # m
32
+
33
+ A = math.pi * (D**2) / 4.0 # cross-sectional area [m²]
34
+ v = Q / A # velocity [m/s]
35
+
36
+ return Velocity(v, "m/s")
@@ -0,0 +1,4 @@
1
+ from .conduction_heat_loss import ConductionHeatLoss
2
+ from .convection_heat_loss import ConvectionHeatLoss
3
+ from .heat_exchanger_area import HeatExchangerArea
4
+ __all__ = ["ConductionHeatLoss", "ConvectionHeatLoss", "HeatExchangerArea"]
@@ -0,0 +1,26 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class ConductionHeatLoss(CalculationBase):
5
+ """
6
+ Calculate steady-state conduction heat loss through a wall.
7
+ Formula:
8
+ Q = k * A * ΔT / L
9
+ """
10
+
11
+ def validate_inputs(self):
12
+ required = ["thermal_conductivity", "area", "temp_difference", "thickness"]
13
+ for key in required:
14
+ if key not in self.inputs:
15
+ raise ValueError(f"Missing required input: {key}")
16
+ if self.inputs["thickness"] <= 0:
17
+ raise ValueError("Wall thickness must be greater than 0")
18
+
19
+ def calculate(self):
20
+ k = self._get_value(self.inputs["thermal_conductivity"], "thermal_conductivity") # W/m·K
21
+ A = self._get_value(self.inputs["area"], "area") # m²
22
+ ΔT = self._get_value(self.inputs["temp_difference"], "temp_difference") # K
23
+ L = self._get_value(self.inputs["thickness"], "thickness") # m
24
+
25
+ Q = k * A * ΔT / L
26
+ return HeatFlux(Q, "W")
@@ -0,0 +1,23 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class ConvectionHeatLoss(CalculationBase):
5
+ """
6
+ Calculate convection heat loss.
7
+ Formula:
8
+ Q = h * A * ΔT
9
+ """
10
+
11
+ def validate_inputs(self):
12
+ required = ["heat_transfer_coeff", "area", "temp_difference"]
13
+ for key in required:
14
+ if key not in self.inputs:
15
+ raise ValueError(f"Missing required input: {key}")
16
+
17
+ def calculate(self):
18
+ h = self._get_value(self.inputs["heat_transfer_coeff"], "heat_transfer_coeff") # W/m²·K
19
+ A = self._get_value(self.inputs["area"], "area") # m²
20
+ ΔT = self._get_value(self.inputs["temp_difference"], "temp_difference") # K
21
+
22
+ Q = h * A * ΔT
23
+ return HeatFlux(Q, "W")
@@ -0,0 +1,26 @@
1
+ from ..base import CalculationBase
2
+ from ...units import *
3
+
4
+ class HeatExchangerArea(CalculationBase):
5
+ """
6
+ Calculate required heat exchanger area.
7
+ Formula:
8
+ Q = U * A * ΔTlm
9
+ => A = Q / (U * ΔTlm)
10
+ """
11
+
12
+ def validate_inputs(self):
13
+ required = ["heat_duty", "overall_heat_transfer_coeff", "log_mean_temp_diff"]
14
+ for key in required:
15
+ if key not in self.inputs:
16
+ raise ValueError(f"Missing required input: {key}")
17
+ if self.inputs["log_mean_temp_diff"] <= 0:
18
+ raise ValueError("Log mean temperature difference must be positive")
19
+
20
+ def calculate(self):
21
+ Q = self._get_value(self.inputs["heat_duty"], "heat_duty") # W
22
+ U = self._get_value(self.inputs["overall_heat_transfer_coeff"], "overall_heat_transfer_coeff") # W/m²·K
23
+ ΔTlm = self._get_value(self.inputs["log_mean_temp_diff"], "log_mean_temp_diff") # K
24
+
25
+ A = Q / (U * ΔTlm)
26
+ return Area(A, "m2")
@@ -0,0 +1,3 @@
1
+ from .distillation_stage_count import DistillationStageCount
2
+ from .drying_rate import DryingRate
3
+ __all__ = ["DistillationStageCount","DryingRate"]