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
@@ -0,0 +1,58 @@
1
+ from .base import Variable
2
+
3
+ class Diameter(Variable):
4
+ """
5
+ Represents a Diameter quantity.
6
+ Default SI unit: meters (m).
7
+
8
+ Example:
9
+ d1 = Diameter(100, "mm")
10
+ d2 = Diameter(4, "in")
11
+ """
12
+
13
+ _conversion = {
14
+ "m": 1,
15
+ "cm": 0.01,
16
+ "mm": 0.001,
17
+ "in": 0.0254,
18
+ "ft": 0.3048
19
+ }
20
+
21
+ def __init__(self, value, units="m"):
22
+ if value < 0:
23
+ raise ValueError("Diameter must be non-negative")
24
+ if units not in self._conversion:
25
+ raise TypeError(f"{units} is not a valid unit for Diameter")
26
+ base_value = round(value * self._conversion[units], 6)
27
+ super().__init__(base_value, "m")
28
+ self.original_value = value
29
+ self.original_unit = units
30
+
31
+ def to_base(self) -> float:
32
+ """Return the base (SI) value in meters."""
33
+ return self.value
34
+
35
+ def to(self, target_unit):
36
+ if target_unit not in self._conversion:
37
+ raise TypeError(f"{target_unit} is not a valid unit for Diameter")
38
+ converted_value = self.value / self._conversion[target_unit]
39
+ return Diameter(round(converted_value, 6), target_unit)
40
+
41
+ def __add__(self, other):
42
+ if not isinstance(other, Diameter):
43
+ raise TypeError("Addition only supported between Diameter instances")
44
+ total = self.value + other.value
45
+ return Diameter(round(total, 6), "m")
46
+
47
+ def __eq__(self, other):
48
+ return (
49
+ isinstance(other, self.__class__) and
50
+ self.to_base() == other.to_base()
51
+ )
52
+
53
+ def __hash__(self):
54
+ # hash on immutable tuple
55
+ return hash((self.__class__.__name__, round(self.to_base(), 9)))
56
+
57
+ def __repr__(self):
58
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,42 @@
1
+ from .base import Variable
2
+
3
+ class Dimensionless(Variable):
4
+ """
5
+ Represents a dimensionless quantity (e.g., Reynolds number, friction factor).
6
+ Default SI unit: none (unitless).
7
+
8
+ Example:
9
+ Re = Dimensionless(5000) # Reynolds number
10
+ f = Dimensionless(0.02) # Friction factor
11
+ """
12
+
13
+ def __init__(self, value):
14
+ if not isinstance(value, (int, float)):
15
+ raise TypeError("Dimensionless value must be a number")
16
+ super().__init__(float(value), "")
17
+ self.original_value = float(value)
18
+
19
+ def to(self, target_unit=None):
20
+ """
21
+ Conversion is not applicable, but provided for interface consistency.
22
+ """
23
+ return Dimensionless(self.value)
24
+
25
+ def __add__(self, other):
26
+ if not isinstance(other, Dimensionless):
27
+ raise TypeError("Addition only supported between Dimensionless instances")
28
+ return Dimensionless(round(self.value + other.value, 6))
29
+
30
+ def __mul__(self, other):
31
+ if isinstance(other, Dimensionless):
32
+ return Dimensionless(round(self.value * other.value, 6))
33
+ elif isinstance(other, (int, float)):
34
+ return Dimensionless(round(self.value * other, 6))
35
+ else:
36
+ raise TypeError("Multiplication only supported with Dimensionless or numeric types")
37
+
38
+ def __eq__(self, other):
39
+ return isinstance(other, Dimensionless) and self.value == other.value
40
+
41
+ def __repr__(self):
42
+ return f"{self.original_value} (dimensionless)"
@@ -0,0 +1,46 @@
1
+ from .base import Variable
2
+
3
+ class HeatFlux(Variable):
4
+ """
5
+ Represents Heat Flux (heat transfer rate per unit area).
6
+ Default SI unit: W/m2 (Watts per square meter)
7
+
8
+ Example:
9
+ q = HeatFlux(300, "W/m²")
10
+ """
11
+
12
+ _conversion = {
13
+ "W/m2": 1,
14
+ "kW/m2": 1000,
15
+ "W/cm2": 10000,
16
+ "BTU/hft2": 3.1546, # 1 BTU/hr·ft² ≈ 3.1546 W/m²
17
+ "cal/scm2": 41840, # 1 cal/s·cm² ≈ 41840 W/m²
18
+ }
19
+
20
+ def __init__(self, value, units="W/m2"):
21
+ if value < 0:
22
+ raise ValueError("Heat flux cannot be negative.")
23
+ if units not in self._conversion:
24
+ raise TypeError(f"{units} is not a valid unit for HeatFlux")
25
+ base_value = round(value * self._conversion[units], 6)
26
+ super().__init__(base_value, "W/m2")
27
+ self.original_value = value
28
+ self.original_unit = units
29
+
30
+ def to(self, target_unit):
31
+ if target_unit not in self._conversion:
32
+ raise TypeError(f"{target_unit} is not a valid unit for HeatFlux")
33
+ converted_value = self.value / self._conversion[target_unit]
34
+ return HeatFlux(round(converted_value, 6), target_unit)
35
+
36
+ def __add__(self, other):
37
+ if not isinstance(other, HeatFlux):
38
+ raise TypeError("Addition only supported between HeatFlux instances")
39
+ total = self.value + other.value
40
+ return HeatFlux(round(total, 6), "W/m2")
41
+
42
+ def __eq__(self, other):
43
+ return isinstance(other, HeatFlux) and self.value == other.value
44
+
45
+ def __repr__(self):
46
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,47 @@
1
+ from .base import Variable
2
+
3
+ class HeatOfVaporization(Variable):
4
+ """
5
+ Represents Heat of Vaporization.
6
+ Default SI unit: J/kg (Joules per kilogram)
7
+
8
+ Example:
9
+ hv1 = HeatOfVaporization(2257000) # 2.257 MJ/kg
10
+ hv2 = HeatOfVaporization(540, "cal/g") # 540 cal/g = ~2.259 MJ/kg
11
+ """
12
+
13
+ _conversion = {
14
+ "J/kg": 1,
15
+ "kJ/kg": 1e3,
16
+ "MJ/kg": 1e6,
17
+ "cal/g": 4184, # 1 cal/g = 4184 J/kg
18
+ "BTU/lb": 2326 # 1 BTU/lb ≈ 2326 J/kg
19
+ }
20
+
21
+ def __init__(self, value, units="J/kg"):
22
+ if value < 0:
23
+ raise ValueError("Heat of Vaporization must be a non-negative value")
24
+ if units not in self._conversion:
25
+ raise ValueError(f"{units} is not a valid unit for Heat of Vaporization")
26
+ base_value = round(value * self._conversion[units], 10)
27
+ super().__init__(base_value, "J/kg")
28
+ self.original_value = value
29
+ self.original_unit = units
30
+
31
+ def to(self, target_unit):
32
+ if target_unit not in self._conversion:
33
+ raise ValueError(f"{target_unit} is not a valid unit for Heat of Vaporization")
34
+ converted_value = self.value / self._conversion[target_unit]
35
+ return HeatOfVaporization(converted_value, target_unit)
36
+
37
+ def __add__(self, other):
38
+ if not isinstance(other, HeatOfVaporization):
39
+ raise TypeError("Addition only allowed between HeatOfVaporization objects")
40
+ result_in_base = self.value + other.value
41
+ return HeatOfVaporization(result_in_base / self._conversion[self.original_unit], self.original_unit)
42
+
43
+ def __eq__(self, other):
44
+ return isinstance(other, HeatOfVaporization) and self.value == other.value
45
+
46
+ def __repr__(self):
47
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,45 @@
1
+ from .base import Variable
2
+
3
+ class HeatTransferCoefficient(Variable):
4
+ """
5
+ Represents Heat Transfer Coefficient.
6
+ Default SI unit: W/m2K (Watts per square meter-Kelvin)
7
+
8
+ Example:
9
+ h = HeatTransferCoefficient(200, "W/m2K")
10
+ """
11
+
12
+ _conversion = {
13
+ "W/m2K": 1,
14
+ "kW/m2K": 1000,
15
+ "cal/scm2C": 41840, # 1 cal/s·cm²·°C ≈ 41840 W/m²·K
16
+ "BTU/hft2F": 5.678263, # 1 BTU/hr·ft²·°F ≈ 5.678263 W/m²·K
17
+ }
18
+
19
+ def __init__(self, value, units="W/m2K"):
20
+ if value < 0:
21
+ raise ValueError("Heat transfer coefficient cannot be negative.")
22
+ if units not in self._conversion:
23
+ raise TypeError(f"{units} is not a valid unit for HeatTransferCoefficient")
24
+ base_value = round(value * self._conversion[units], 6)
25
+ super().__init__(base_value, "W/m2K")
26
+ self.original_value = value
27
+ self.original_unit = units
28
+
29
+ def to(self, target_unit):
30
+ if target_unit not in self._conversion:
31
+ raise TypeError(f"{target_unit} is not a valid unit for HeatTransferCoefficient")
32
+ converted_value = self.value / self._conversion[target_unit]
33
+ return HeatTransferCoefficient(round(converted_value, 6), target_unit)
34
+
35
+ def __add__(self, other):
36
+ if not isinstance(other, HeatTransferCoefficient):
37
+ raise TypeError("Addition only supported between HeatTransferCoefficient instances")
38
+ total = self.value + other.value
39
+ return HeatTransferCoefficient(round(total, 6), "W/m2K")
40
+
41
+ def __eq__(self, other):
42
+ return isinstance(other, HeatTransferCoefficient) and self.value == other.value
43
+
44
+ def __repr__(self):
45
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,47 @@
1
+ from .base import Variable
2
+
3
+ class Length(Variable):
4
+ """
5
+ Represents a Length quantity Variable.
6
+ Default SI unit: Meters (m).
7
+
8
+ Example:
9
+ length = Length(30) # 30 meters
10
+ length = Length(30, "in") # 30 inches
11
+ """
12
+
13
+ _conversion = {
14
+ "m": 1,
15
+ "cm": 0.01,
16
+ "mm": 0.001,
17
+ "in": 0.0254,
18
+ "ft": 0.3048
19
+ }
20
+
21
+ def __init__(self, value, units="m"):
22
+ if value < 0:
23
+ raise ValueError("Length must be positive")
24
+ elif units not in self._conversion:
25
+ raise ValueError(f"{units} is not a valid unit for Length")
26
+ base_value = round(value * self._conversion[units], 6)
27
+ super().__init__(base_value, "m") # Always store as base unit (meters)
28
+ self.original_value = value
29
+ self.original_unit = units
30
+
31
+ def to(self, target_unit):
32
+ if target_unit not in self._conversion:
33
+ raise ValueError(f"{target_unit} is not a valid unit for Length")
34
+ converted_value = self.value / self._conversion[target_unit]
35
+ return Length(converted_value, target_unit)
36
+
37
+ def __add__(self, other):
38
+ if not isinstance(other, Length):
39
+ raise TypeError("Addition only allowed between Length objects")
40
+ result_in_base = self.value + other.value
41
+ return Length(result_in_base / self._conversion[self.original_unit], self.original_unit)
42
+
43
+ def __eq__(self, other):
44
+ return isinstance(other, Length) and self.value == other.value
45
+
46
+ def __repr__(self):
47
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,50 @@
1
+ from .base import Variable
2
+
3
+ class Mass(Variable):
4
+ """
5
+ Represents a Mass quantity.
6
+ Default SI unit: Kilogram (kg).
7
+
8
+ Example:
9
+ m1 = Mass(500, "g")
10
+ m2 = Mass(2, "kg")
11
+ """
12
+
13
+ _conversion = {
14
+ "kg": 1,
15
+ "g": 0.001,
16
+ "mg": 0.000001,
17
+ "ton": 1000,
18
+ "lb": 0.453592,
19
+ "oz": 0.0283495,
20
+ "mt": 1000, # metric ton
21
+ "ug": 1e-9
22
+ }
23
+
24
+ def __init__(self, value, units="kg"):
25
+ if value < 0:
26
+ raise ValueError("Mass must be non-negative")
27
+ if units not in self._conversion:
28
+ raise TypeError(f"{units} is not a valid unit for Mass")
29
+ base_value = round(value * self._conversion[units], 6)
30
+ super().__init__(base_value, "kg")
31
+ self.original_value = value
32
+ self.original_unit = units
33
+
34
+ def to(self, target_unit):
35
+ if target_unit not in self._conversion:
36
+ raise TypeError(f"{target_unit} is not a valid unit for Mass")
37
+ converted_value = self.value / self._conversion[target_unit]
38
+ return Mass(round(converted_value, 6), target_unit)
39
+
40
+ def __add__(self, other):
41
+ if not isinstance(other, Mass):
42
+ raise TypeError("Addition only supported between Mass instances")
43
+ total = self.value + other.value
44
+ return Mass(round(total, 6), "kg")
45
+
46
+ def __eq__(self, other):
47
+ return isinstance(other, Mass) and self.value == other.value
48
+
49
+ def __repr__(self):
50
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,50 @@
1
+ from .base import Variable
2
+
3
+ class MassFlowRate(Variable):
4
+ """
5
+ Represents a Mass Flow Rate quantity.
6
+ Default SI unit: kilograms per second (kg/s).
7
+
8
+ Example:
9
+ m1 = MassFlowRate(100, "kg/h")
10
+ m2 = MassFlowRate(0.5, "lb/s")
11
+ """
12
+
13
+ _conversion = {
14
+ "kg/s": 1,
15
+ "kg/h": 1 / 3600,
16
+ "g/s": 0.001,
17
+ "g/min": 0.001 / 60,
18
+ "g/h": 0.001 / 3600,
19
+ "lb/s": 0.453592,
20
+ "lb/min": 0.453592 / 60,
21
+ "lb/h": 0.453592 / 3600
22
+ }
23
+
24
+ def __init__(self, value, units="kg/s"):
25
+ if value < 0:
26
+ raise ValueError("Mass flow rate must be non-negative")
27
+ if units not in self._conversion:
28
+ raise TypeError(f"{units} is not a valid unit for MassFlowRate")
29
+ base_value = round(value * self._conversion[units], 6)
30
+ super().__init__(base_value, "kg/s")
31
+ self.original_value = value
32
+ self.original_unit = units
33
+
34
+ def to(self, target_unit):
35
+ if target_unit not in self._conversion:
36
+ raise TypeError(f"{target_unit} is not a valid unit for MassFlowRate")
37
+ converted_value = self.value / self._conversion[target_unit]
38
+ return MassFlowRate(round(converted_value, 6), target_unit)
39
+
40
+ def __add__(self, other):
41
+ if not isinstance(other, MassFlowRate):
42
+ raise TypeError("Addition only supported between MassFlowRate instances")
43
+ total = self.value + other.value
44
+ return MassFlowRate(round(total, 6), "kg/s")
45
+
46
+ def __eq__(self, other):
47
+ return isinstance(other, MassFlowRate) and self.value == other.value
48
+
49
+ def __repr__(self):
50
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,57 @@
1
+ from .base import Variable
2
+
3
+ class Pressure(Variable):
4
+ """
5
+ Represents a Pressure quantity.
6
+ Default SI unit: Pascal (Pa).
7
+
8
+ Example:
9
+ p1 = Pressure(1, "atm")
10
+ p2 = Pressure(101325, "Pa")
11
+ """
12
+
13
+ _conversion = {
14
+ "Pa": 1,
15
+ "kPa": 1_000,
16
+ "MPa": 1_000_000,
17
+ "bar": 100_000,
18
+ "atm": 101_325,
19
+ "psi": 6_894.76,
20
+ "mmHg": 133.322,
21
+ "torr": 133.322
22
+ }
23
+
24
+ def __init__(self, value, units="Pa"):
25
+ if value < 0:
26
+ raise ValueError("Pressure must be non-negative")
27
+ if units not in self._conversion:
28
+ raise TypeError(f"{units} is not a valid unit for Pressure")
29
+ super().__init__(value, units)
30
+
31
+ def to_base(self):
32
+ """Convert to base SI unit (Pa)."""
33
+ return self.value * self._conversion[self.units]
34
+
35
+ def from_base(self, base_value: float, target_units: str):
36
+ """Convert from Pa to target units."""
37
+ if target_units not in self._conversion:
38
+ raise TypeError(f"{target_units} is not a valid unit for Pressure")
39
+ return base_value / self._conversion[target_units]
40
+
41
+ def to(self, target_unit):
42
+ """Return new Pressure in target units."""
43
+ base_value = self.to_base()
44
+ converted_value = self.from_base(base_value, target_unit)
45
+ return Pressure(round(converted_value, 4), target_unit)
46
+
47
+ def __add__(self, other):
48
+ if not isinstance(other, Pressure):
49
+ raise TypeError("Addition only supported between Pressure instances")
50
+ total_base = self.to_base() + other.to_base()
51
+ return Pressure(self.from_base(total_base, "Pa"), "Pa")
52
+
53
+ def __eq__(self, other):
54
+ return isinstance(other, Pressure) and self.to_base() == other.to_base()
55
+
56
+ def __repr__(self):
57
+ return f"{self.value} {self.units}"
@@ -0,0 +1,47 @@
1
+ from .base import Variable
2
+
3
+ class SpecificHeat(Variable):
4
+ """
5
+ Represents Specific Heat Capacity.
6
+ Default SI unit: kJ/kgK
7
+
8
+ Example:
9
+ cp1 = SpecificHeat(4.186, "kJ/kgK")
10
+ cp2 = SpecificHeat(1.0, "cal/gK")
11
+ """
12
+
13
+ _conversion = {
14
+ "kJ/kgK": 1,
15
+ "J/kgK": 0.001,
16
+ "cal/gK": 4.1868 * 0.001, # 1 cal/g.K = 4.1868 kJ/kg.K
17
+ "BTU/lbF": 4.1868 * 2.326, # approx conversion to kJ/kg.K
18
+ "kcal/kgK": 4.1868
19
+ }
20
+
21
+ def __init__(self, value, units="kJ/kgK"):
22
+ if value < 0:
23
+ raise ValueError("Specific heat must be positive.")
24
+ if units not in self._conversion:
25
+ raise TypeError(f"{units} is not a valid unit for SpecificHeat")
26
+ base_value = round(value * self._conversion[units], 6)
27
+ super().__init__(base_value, "kJ/kgK")
28
+ self.original_value = value
29
+ self.original_unit = units
30
+
31
+ def to(self, target_unit):
32
+ if target_unit not in self._conversion:
33
+ raise TypeError(f"{target_unit} is not a valid unit for SpecificHeat")
34
+ converted_value = self.value / self._conversion[target_unit]
35
+ return SpecificHeat(round(converted_value, 6), target_unit)
36
+
37
+ def __add__(self, other):
38
+ if not isinstance(other, SpecificHeat):
39
+ raise TypeError("Addition only supported between SpecificHeat instances")
40
+ total = self.value + other.value
41
+ return SpecificHeat(round(total, 6), "kJ/kgK")
42
+
43
+ def __eq__(self, other):
44
+ return isinstance(other, SpecificHeat) and self.value == other.value
45
+
46
+ def __repr__(self):
47
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,31 @@
1
+ from .base import Variable
2
+
3
+ class StringUnit(Variable):
4
+ """
5
+ Represents a string-based categorical quantity (e.g., flow type, phase, material category).
6
+ Default unit: none (string).
7
+
8
+ Example:
9
+ flow = StringUnit("Laminar", "flow_type")
10
+ phase = StringUnit("Gas", "phase_state")
11
+ """
12
+
13
+ def __init__(self, value: str, category: str = "string"):
14
+ if not isinstance(value, str):
15
+ raise TypeError("StringUnit value must be a string")
16
+ super().__init__(value, category)
17
+ self.original_value = value
18
+ self.category = category
19
+
20
+ def to(self, target_unit=None):
21
+ """
22
+ Conversion is not applicable for string-based units.
23
+ Provided only for interface consistency.
24
+ """
25
+ return StringUnit(self.value, self.category)
26
+
27
+ def __eq__(self, other):
28
+ return isinstance(other, StringUnit) and self.value == other.value
29
+
30
+ def __repr__(self):
31
+ return f"{self.original_value} ({self.category})"
@@ -0,0 +1,51 @@
1
+ from .base import Variable
2
+
3
+ class Temperature(Variable):
4
+ """
5
+ Represents a Temperature quantity.
6
+ Default SI unit: Kelvin (K).
7
+
8
+ Example:
9
+ t1 = Temperature(100, "C") # 100°C
10
+ t2 = Temperature(373.15) # 373.15 K
11
+ """
12
+
13
+ _conversion = {
14
+ "K": lambda x: x,
15
+ "C": lambda x: x + 273.15,
16
+ "F": lambda x: (x - 32) * 5 / 9 + 273.15
17
+ }
18
+
19
+ _reverse_conversion = {
20
+ "K": lambda x: x,
21
+ "C": lambda x: x - 273.15,
22
+ "F": lambda x: (x - 273.15) * 9 / 5 + 32
23
+ }
24
+
25
+ def __init__(self, value, units="K"):
26
+ if units not in self._conversion:
27
+ raise ValueError(f"{units} is not a valid unit for Temperature")
28
+ base_value = round(self._conversion[units](value), 4)
29
+ super().__init__(base_value, "K")
30
+ self.original_value = value
31
+ self.original_unit = units
32
+
33
+ def to(self, target_unit):
34
+ if target_unit not in self._reverse_conversion:
35
+ raise ValueError(f"{target_unit} is not a valid target unit for Temperature")
36
+ converted_value = self._reverse_conversion[target_unit](self.value)
37
+ return Temperature(round(converted_value, 4), target_unit)
38
+
39
+ def __add__(self, other):
40
+ raise TypeError("Direct addition of two temperatures is not physically meaningful")
41
+
42
+ def __sub__(self, other):
43
+ if not isinstance(other, Temperature):
44
+ raise TypeError("Subtraction only allowed between Temperature objects")
45
+ return self.value - other.value # Difference in Kelvin
46
+
47
+ def __eq__(self, other):
48
+ return isinstance(other, Temperature) and self.value == other.value
49
+
50
+ def __repr__(self):
51
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,45 @@
1
+ from .base import Variable
2
+
3
+ class ThermalConductivity(Variable):
4
+ """
5
+ Represents Thermal Conductivity of a material.
6
+ Default SI unit: W/mK (Watts per meter-Kelvin)
7
+
8
+ Example:
9
+ k = ThermalConductivity(0.5, "W/mK")
10
+ """
11
+
12
+ _conversion = {
13
+ "W/mK": 1,
14
+ "kW/mK": 1000,
15
+ "cal/scmC": 418.4, # 1 cal/s·cm·°C ≈ 418.4 W/m·K
16
+ "BTU/hrftF": 1.730735, # 1 BTU/hr·ft·°F ≈ 1.730735 W/m·K
17
+ }
18
+
19
+ def __init__(self, value, units="W/mK"):
20
+ if value < 0:
21
+ raise ValueError("Thermal conductivity cannot be negative.")
22
+ if units not in self._conversion:
23
+ raise TypeError(f"{units} is not a valid unit for ThermalConductivity")
24
+ base_value = round(value * self._conversion[units], 6)
25
+ super().__init__(base_value, "W/mK")
26
+ self.original_value = value
27
+ self.original_unit = units
28
+
29
+ def to(self, target_unit):
30
+ if target_unit not in self._conversion:
31
+ raise TypeError(f"{target_unit} is not a valid unit for ThermalConductivity")
32
+ converted_value = self.value / self._conversion[target_unit]
33
+ return ThermalConductivity(round(converted_value, 6), target_unit)
34
+
35
+ def __add__(self, other):
36
+ if not isinstance(other, ThermalConductivity):
37
+ raise TypeError("Addition only supported between ThermalConductivity instances")
38
+ total = self.value + other.value
39
+ return ThermalConductivity(round(total, 6), "W/mK")
40
+
41
+ def __eq__(self, other):
42
+ return isinstance(other, ThermalConductivity) and self.value == other.value
43
+
44
+ def __repr__(self):
45
+ return f"{self.original_value} {self.original_unit}"
@@ -0,0 +1,46 @@
1
+ from .base import Variable
2
+
3
+ class ThermalResistance(Variable):
4
+ """
5
+ Represents Thermal Resistance.
6
+ Default SI unit: K/W (Kelvin per Watt)
7
+
8
+ Example:
9
+ r1 = ThermalResistance(0.5, "K/W")
10
+ r2 = ThermalResistance(2, "C/W")
11
+ """
12
+
13
+ _conversion = {
14
+ "K/W": 1,
15
+ "C/W": 1, # °C/W is numerically equivalent to K/W
16
+ "hrft2F/BTU": 0.1761, # 1 hr·ft²·°F/BTU ≈ 0.1761 K/W
17
+ "m2K/W": 1, # often used in building materials
18
+ }
19
+
20
+ def __init__(self, value, units="K/W"):
21
+ if value <= 0:
22
+ raise ValueError("Thermal resistance must be positive.")
23
+ if units not in self._conversion:
24
+ raise TypeError(f"{units} is not a valid unit for ThermalResistance")
25
+ base_value = round(value * self._conversion[units], 6)
26
+ super().__init__(base_value, "K/W")
27
+ self.original_value = value
28
+ self.original_unit = units
29
+
30
+ def to(self, target_unit):
31
+ if target_unit not in self._conversion:
32
+ raise TypeError(f"{target_unit} is not a valid unit for ThermalResistance")
33
+ converted_value = self.value / self._conversion[target_unit]
34
+ return ThermalResistance(round(converted_value, 6), target_unit)
35
+
36
+ def __add__(self, other):
37
+ if not isinstance(other, ThermalResistance):
38
+ raise TypeError("Addition only supported between ThermalResistance instances")
39
+ total = self.value + other.value
40
+ return ThermalResistance(round(total, 6), "K/W")
41
+
42
+ def __eq__(self, other):
43
+ return isinstance(other, ThermalResistance) and self.value == other.value
44
+
45
+ def __repr__(self):
46
+ return f"{self.original_value} {self.original_unit}"