unitforge 0.5.9__tar.gz → 0.5.11__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 (115) hide show
  1. {unitforge-0.5.9 → unitforge-0.5.11}/Cargo.lock +2 -2
  2. {unitforge-0.5.9 → unitforge-0.5.11}/Cargo.toml +2 -2
  3. {unitforge-0.5.9 → unitforge-0.5.11}/PKG-INFO +3 -1
  4. {unitforge-0.5.9 → unitforge-0.5.11}/install_hooks.sh +0 -0
  5. {unitforge-0.5.9 → unitforge-0.5.11}/src/lib.rs +6 -2
  6. unitforge-0.5.11/src/quantities/heat_capacity.rs +76 -0
  7. unitforge-0.5.11/src/quantities/thermal_conductance.rs +42 -0
  8. {unitforge-0.5.9 → unitforge-0.5.11}/src/relations.rs +10 -5
  9. {unitforge-0.5.9 → unitforge-0.5.11}/test.sh +0 -0
  10. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantities.rs +64 -0
  11. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/metadata.rs +1 -0
  12. {unitforge-0.5.9 → unitforge-0.5.11}/tests/test_quantities.py +29 -0
  13. unitforge-0.5.11/tests/thermal_transient.rs +226 -0
  14. {unitforge-0.5.9 → unitforge-0.5.11}/tests/typecheck/mypy_smoke.py +22 -0
  15. {unitforge-0.5.9 → unitforge-0.5.11}/unitforge_macros/Cargo.toml +1 -1
  16. unitforge-0.5.9/python/unitforge/__init__.pyi +0 -3413
  17. unitforge-0.5.9/python/unitforge/py.typed +0 -0
  18. {unitforge-0.5.9 → unitforge-0.5.11}/.gitignore +0 -0
  19. {unitforge-0.5.9 → unitforge-0.5.11}/.gitlab-ci.yml +0 -0
  20. {unitforge-0.5.9 → unitforge-0.5.11}/Readme.md +0 -0
  21. {unitforge-0.5.9 → unitforge-0.5.11}/Readme_py.md +0 -0
  22. {unitforge-0.5.9 → unitforge-0.5.11}/pyproject.toml +0 -0
  23. {unitforge-0.5.9 → unitforge-0.5.11}/python/unitforge/__init__.py +0 -0
  24. {unitforge-0.5.9 → unitforge-0.5.11}/scripts/generate_python_stubs.py +0 -0
  25. {unitforge-0.5.9 → unitforge-0.5.11}/src/errors.rs +0 -0
  26. {unitforge-0.5.9 → unitforge-0.5.11}/src/impl_macros.rs +0 -0
  27. {unitforge-0.5.9 → unitforge-0.5.11}/src/internal.rs +0 -0
  28. {unitforge-0.5.9 → unitforge-0.5.11}/src/prelude.rs +0 -0
  29. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/acceleration.rs +0 -0
  30. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/angle.rs +0 -0
  31. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/angular_acceleration.rs +0 -0
  32. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/angular_velocity.rs +0 -0
  33. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/area.rs +0 -0
  34. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/area_of_moment.rs +0 -0
  35. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/charge.rs +0 -0
  36. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/compliance.rs +0 -0
  37. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/density.rs +0 -0
  38. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/density_rate.rs +0 -0
  39. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/density_temperature.rs +0 -0
  40. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/density_temperature_rate.rs +0 -0
  41. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/distance.rs +0 -0
  42. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force.rs +0 -0
  43. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force_area.rs +0 -0
  44. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force_distance.rs +0 -0
  45. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force_div_distance_power_four.rs +0 -0
  46. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force_per_volume.rs +0 -0
  47. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force_stress.rs +0 -0
  48. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/force_volume.rs +0 -0
  49. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/frequency.rs +0 -0
  50. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/heat_flux.rs +0 -0
  51. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/inverse_angle.rs +0 -0
  52. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/inverse_area.rs +0 -0
  53. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/inverse_distance.rs +0 -0
  54. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/inverse_stress.rs +0 -0
  55. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/inverse_temperature.rs +0 -0
  56. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/mass.rs +0 -0
  57. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/mass_per_distance.rs +0 -0
  58. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/molar_mass.rs +0 -0
  59. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/power.rs +0 -0
  60. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/pressure_rate.rs +0 -0
  61. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/rotational_stiffness.rs +0 -0
  62. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/specific_energy.rs +0 -0
  63. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/specific_gas_constant.rs +0 -0
  64. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/specific_heat_capacity.rs +0 -0
  65. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stiffness.rs +0 -0
  66. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/strain.rs +0 -0
  67. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/strain_squared.rs +0 -0
  68. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stress.rs +0 -0
  69. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stress_cubed.rs +0 -0
  70. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stress_per_temperature.rs +0 -0
  71. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stress_rate.rs +0 -0
  72. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stress_rate_per_temperature.rs +0 -0
  73. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/stress_squared.rs +0 -0
  74. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/temperature.rs +0 -0
  75. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/temperature_gradient.rs +0 -0
  76. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/thermal_conductivity.rs +0 -0
  77. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/time.rs +0 -0
  78. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/universal_gas_constant.rs +0 -0
  79. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/velocity.rs +0 -0
  80. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/velocity_squared.rs +0 -0
  81. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/voltage.rs +0 -0
  82. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/volume.rs +0 -0
  83. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/volumetric_energy_density.rs +0 -0
  84. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities/volumetric_heat_capacity.rs +0 -0
  85. {unitforge-0.5.9 → unitforge-0.5.11}/src/quantities.template.rs +0 -0
  86. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/bindings/matrix3_py.template.rs +0 -0
  87. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/bindings/vector3_py.template.rs +0 -0
  88. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/matrix2.rs +0 -0
  89. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/matrix2x3.rs +0 -0
  90. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/matrix3.rs +0 -0
  91. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/matrix3x2.rs +0 -0
  92. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/vector2.rs +0 -0
  93. {unitforge-0.5.9 → unitforge-0.5.11}/src/small_linalg/vector3.rs +0 -0
  94. {unitforge-0.5.9 → unitforge-0.5.11}/tests/common/mod.rs +0 -0
  95. {unitforge-0.5.9 → unitforge-0.5.11}/tests/matrix2.rs +0 -0
  96. {unitforge-0.5.9 → unitforge-0.5.11}/tests/matrix2x3.rs +0 -0
  97. {unitforge-0.5.9 → unitforge-0.5.11}/tests/matrix3.rs +0 -0
  98. {unitforge-0.5.9 → unitforge-0.5.11}/tests/matrix3x2.rs +0 -0
  99. {unitforge-0.5.9 → unitforge-0.5.11}/tests/ndarray_dot.rs +0 -0
  100. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/constants.rs +0 -0
  101. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/construction.rs +0 -0
  102. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/enum_iter.rs +0 -0
  103. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/normalization.rs +0 -0
  104. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/plain_storage.rs +0 -0
  105. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/relations.rs +0 -0
  106. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/runtime_quantity.rs +0 -0
  107. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core/scalar_edges.rs +0 -0
  108. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_core.rs +0 -0
  109. {unitforge-0.5.9 → unitforge-0.5.11}/tests/quantity_serde.rs +0 -0
  110. {unitforge-0.5.9 → unitforge-0.5.11}/tests/small_linalg_edge_cases.rs +0 -0
  111. {unitforge-0.5.9 → unitforge-0.5.11}/tests/small_linalg_interfaces.rs +0 -0
  112. {unitforge-0.5.9 → unitforge-0.5.11}/tests/test_small_linalg.py +0 -0
  113. {unitforge-0.5.9 → unitforge-0.5.11}/tests/vector2.rs +0 -0
  114. {unitforge-0.5.9 → unitforge-0.5.11}/tests/vector3.rs +0 -0
  115. {unitforge-0.5.9 → unitforge-0.5.11}/unitforge_macros/src/lib.rs +0 -0
@@ -351,7 +351,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
351
351
 
352
352
  [[package]]
353
353
  name = "unitforge"
354
- version = "0.5.9"
354
+ version = "0.5.11"
355
355
  dependencies = [
356
356
  "ndarray",
357
357
  "num-traits",
@@ -367,7 +367,7 @@ dependencies = [
367
367
 
368
368
  [[package]]
369
369
  name = "unitforge_macros"
370
- version = "0.5.8"
370
+ version = "0.5.11"
371
371
  dependencies = [
372
372
  "regex",
373
373
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "unitforge"
3
- version = "0.5.9"
3
+ version = "0.5.11"
4
4
  edition = "2021"
5
5
  authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
6
6
  description = "A library for unit and quantity consistent computations in Rust"
@@ -20,7 +20,7 @@ pyo3 = { version = "0.28.3", features = ["extension-module", "auto-initialize",
20
20
  numpy = { version = "0.28.0", optional = true }
21
21
  strum = { version = "0.28.0", optional = true }
22
22
  strum_macros = { version = "0.28.0", optional = true }
23
- unitforge_macros = { version = "0.5.8", path = "unitforge_macros" }
23
+ unitforge_macros = { version = "0.5.11", path = "unitforge_macros" }
24
24
 
25
25
  [features]
26
26
  default = []
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitforge
3
- Version: 0.5.9
3
+ Version: 0.5.11
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -9,7 +9,9 @@ Classifier: Topic :: Scientific/Engineering :: Physics
9
9
  Requires-Dist: numpy>=2.2.0
10
10
  Summary: A library for unit and quantity consistent computations in Rust
11
11
  Keywords: units,quantities,simulation,physics
12
+ Author: Dr.Q team <info@askdrq.com>, Henrik Stromberg <henrik@askdrq.com>
12
13
  Author-email: "Dr.Q team" <info@askdrq.com>, Henrik Stromberg <henrik@askdrq.com>
14
+ License: MIT
13
15
  Requires-Python: >=3.7
14
16
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
15
17
  Project-URL: Repository, https://gitlab.com/henrikjstromberg/unitforge
File without changes
@@ -95,6 +95,8 @@ pub mod quantities {
95
95
  pub use frequency::*;
96
96
  pub mod heat_flux;
97
97
  pub use heat_flux::*;
98
+ pub mod heat_capacity;
99
+ pub use heat_capacity::*;
98
100
  pub mod inverse_distance;
99
101
  pub use inverse_distance::*;
100
102
  pub mod inverse_area;
@@ -107,10 +109,10 @@ pub mod quantities {
107
109
  pub use molar_mass::*;
108
110
  pub mod mass_per_distance;
109
111
  pub use mass_per_distance::*;
110
- pub mod power;
111
- pub use power::*;
112
112
  pub mod pressure_rate;
113
113
  pub use pressure_rate::*;
114
+ pub mod power;
115
+ pub use power::*;
114
116
  pub mod force_distance;
115
117
  pub use force_distance::*;
116
118
  pub mod force_stress;
@@ -145,6 +147,8 @@ pub mod quantities {
145
147
  pub use temperature::*;
146
148
  pub mod temperature_gradient;
147
149
  pub use temperature_gradient::*;
150
+ pub mod thermal_conductance;
151
+ pub use thermal_conductance::*;
148
152
  pub mod inverse_temperature;
149
153
  pub use inverse_temperature::*;
150
154
  pub mod thermal_conductivity;
@@ -0,0 +1,76 @@
1
+ //! Heat capacity of a finite amount of material, in energy per temperature.
2
+ //!
3
+ //! With `ndarray`, use scalar operators in `mapv` to divide by time and
4
+ //! `QuantityDot` for contractions (including borrowed views). Multiplication
5
+ //! through `MulArray1`/`MulArray2` remains elementwise.
6
+ //!
7
+ //! ```
8
+ //! # #[cfg(feature = "ndarray")] {
9
+ //! use ndarray::{arr1, Array1, Array2};
10
+ //! use unitforge::*;
11
+ //!
12
+ //! let volumetric = VolumetricHeatCapacity::new(2.0, VolumetricHeatCapacityUnit::kJ_mcbK);
13
+ //! let volume = Volume::new(0.5, VolumeUnit::mcb);
14
+ //! let mut capacitance = Array2::<HeatCapacity>::zeros((2, 2));
15
+ //! capacitance[(0, 0)] += volumetric * volume;
16
+ //! let temperature = arr1(&[Temperature::new(300.0, TemperatureUnit::K); 2]);
17
+ //! let delta_time = Time::new(2.0, TimeUnit::s);
18
+ //! let conductance: Array2<ThermalConductance> = capacitance.mapv(|c| c / delta_time);
19
+ //! let energy: Array1<Energy> = QuantityDot::dot(&capacitance.view(), &temperature.view());
20
+ //! let power: Array1<Power> = energy.mapv(|e| e / delta_time);
21
+ //! let power_from_conductance: Array1<Power> = QuantityDot::dot(&conductance, &temperature);
22
+ //! assert!(power[0].is_close(&power_from_conductance[0], &Power::new(1e-6, PowerUnit::W)));
23
+ //! # }
24
+ //! ```
25
+ //!
26
+ //! Incompatible quantities cannot be added:
27
+ //! ```compile_fail
28
+ //! use unitforge::*;
29
+ //! let capacity = HeatCapacity::new(1.0, HeatCapacityUnit::J_K);
30
+ //! let conductance = ThermalConductance::new(1.0, ThermalConductanceUnit::W_K);
31
+ //! let _ = capacity + conductance;
32
+ //! ```
33
+ //!
34
+ //! A dimensionless divisor preserves heat capacity; converting to conductance
35
+ //! requires a typed `Time`:
36
+ //! ```compile_fail
37
+ //! use unitforge::*;
38
+ //! let capacity = HeatCapacity::new(1.0, HeatCapacityUnit::J_K);
39
+ //! let conductance: ThermalConductance = capacity / 2.0;
40
+ //! ```
41
+
42
+ use crate::impl_macros::macros::*;
43
+
44
+ #[cfg_attr(feature = "strum", derive(EnumIter))]
45
+ #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
46
+ #[derive(Copy, Clone, PartialEq, Debug, Hash, Eq)]
47
+ #[cfg_attr(feature = "pyo3", pyclass(eq, eq_int, from_py_object))]
48
+ pub enum HeatCapacityUnit {
49
+ J_K,
50
+ kJ_K,
51
+ }
52
+
53
+ impl HeatCapacityUnit {
54
+ pub const fn base_per_x_const(self) -> (f64, i32) {
55
+ match self {
56
+ Self::J_K => (1.0, 0),
57
+ Self::kJ_K => (1.0, 3),
58
+ }
59
+ }
60
+ }
61
+
62
+ impl PhysicsUnit for HeatCapacityUnit {
63
+ fn name(&self) -> &str {
64
+ match self {
65
+ Self::J_K => "J/K",
66
+ Self::kJ_K => "kJ/K",
67
+ }
68
+ }
69
+
70
+ fn base_per_x(&self) -> (f64, i32) {
71
+ (*self).base_per_x_const()
72
+ }
73
+ }
74
+
75
+ impl_quantity!(HeatCapacity, HeatCapacityUnit, [HeatCapacityUnit::J_K]);
76
+ impl_div_with_self_to_f64!(HeatCapacity);
@@ -0,0 +1,42 @@
1
+ use crate::impl_macros::macros::*;
2
+
3
+ #[cfg_attr(feature = "strum", derive(EnumIter))]
4
+ #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
5
+ #[derive(Copy, Clone, PartialEq, Debug, Hash, Eq)]
6
+ #[cfg_attr(feature = "pyo3", pyclass(eq, eq_int, from_py_object))]
7
+ pub enum ThermalConductanceUnit {
8
+ mW_K,
9
+ W_K,
10
+ kW_K,
11
+ }
12
+
13
+ impl ThermalConductanceUnit {
14
+ pub const fn base_per_x_const(self) -> (f64, i32) {
15
+ match self {
16
+ ThermalConductanceUnit::mW_K => (1., -3),
17
+ ThermalConductanceUnit::W_K => (1., 0),
18
+ ThermalConductanceUnit::kW_K => (1., 3),
19
+ }
20
+ }
21
+ }
22
+
23
+ impl PhysicsUnit for ThermalConductanceUnit {
24
+ fn name(&self) -> &str {
25
+ match self {
26
+ ThermalConductanceUnit::mW_K => "mW/K",
27
+ ThermalConductanceUnit::W_K => "W/K",
28
+ ThermalConductanceUnit::kW_K => "kW/K",
29
+ }
30
+ }
31
+
32
+ fn base_per_x(&self) -> (f64, i32) {
33
+ (*self).base_per_x_const()
34
+ }
35
+ }
36
+
37
+ impl_quantity!(
38
+ ThermalConductance,
39
+ ThermalConductanceUnit,
40
+ [ThermalConductanceUnit::W_K]
41
+ );
42
+ impl_div_with_self_to_f64!(ThermalConductance);
@@ -28,6 +28,16 @@ impl_mul_relation_with_other!(Distance, InverseArea, InverseDistance);
28
28
  impl_mul_relation_with_other!(InverseDistance, Area, Distance);
29
29
  impl_mul_relation_with_other!(Velocity, Time, Distance);
30
30
  impl_mul_relation_with_other!(Force, Distance, ForceDistance);
31
+ impl_mul_relation_with_other!(Power, Time, ForceDistance);
32
+ impl_mul_relation_with_other!(Force, Velocity, Power);
33
+ impl_mul_relation_with_other!(HeatFlux, Area, Power);
34
+ impl_mul_relation_with_other!(ThermalConductivity, TemperatureGradient, HeatFlux);
35
+ impl_mul_relation_with_other!(TemperatureGradient, Distance, Temperature);
36
+ impl_mul_relation_with_other!(ThermalConductivity, Distance, ThermalConductance);
37
+ impl_mul_relation_with_other!(ThermalConductance, Temperature, Power);
38
+ impl_mul_relation_with_other!(VolumetricHeatCapacity, Volume, HeatCapacity);
39
+ impl_mul_relation_with_other!(ThermalConductance, Time, HeatCapacity);
40
+ impl_mul_relation_with_other!(HeatCapacity, Temperature, ForceDistance);
31
41
  impl_mul_relation_with_other!(Force, Area, ForceArea);
32
42
  impl_mul_relation_with_other!(ForceArea, Distance, ForceVolume);
33
43
  impl_mul_relation_with_other!(AreaOfMoment, Stress, ForceArea);
@@ -80,12 +90,7 @@ impl_mul_relation_with_self!(Strain, StrainSquared);
80
90
  impl_mul_relation_with_self!(Stress, StressSquared);
81
91
  impl_mul_relation_with_other!(Stress, StressSquared, StressCubed);
82
92
  impl_mul_relation_with_self!(AngularVelocity, AngularAcceleration);
83
- impl_mul_relation_with_other!(ThermalConductivity, TemperatureGradient, HeatFlux);
84
- impl_mul_relation_with_other!(TemperatureGradient, Distance, Temperature);
85
- impl_mul_relation_with_other!(HeatFlux, Area, Power);
86
- impl_mul_relation_with_other!(Power, Time, ForceDistance);
87
93
  impl_mul_relation_with_other!(ForceDistance, Frequency, Power);
88
- impl_mul_relation_with_other!(Force, Velocity, Power);
89
94
  impl_mul_relation_with_other!(Density, SpecificHeatCapacity, VolumetricHeatCapacity);
90
95
  impl_mul_relation_with_other!(VolumetricHeatCapacity, Temperature, VolumetricEnergyDensity);
91
96
  impl_mul_relation_with_other!(SpecificHeatCapacity, Temperature, SpecificEnergy);
File without changes
@@ -361,6 +361,70 @@ mod test_thermo_and_rates {
361
361
  ));
362
362
  }
363
363
 
364
+ #[test]
365
+ fn thermal_quantities_convert_and_relate_to_energy() {
366
+ let conductivity = ThermalConductivity::new(1.0, ThermalConductivityUnit::kW_mK);
367
+ assert!(crate::approx_eq(
368
+ conductivity.to(ThermalConductivityUnit::mW_mK),
369
+ 1_000_000.0,
370
+ 1e-6
371
+ ));
372
+
373
+ let gradient = TemperatureGradient::new(2.0, TemperatureGradientUnit::K_m);
374
+ let flux = ThermalConductivity::new(1.0, ThermalConductivityUnit::kW_mK) * gradient;
375
+ assert!(crate::approx_eq(flux.to(HeatFluxUnit::kW_msq), 2.0, 1e-12));
376
+
377
+ let area = Area::new(0.5, AreaUnit::msq);
378
+ let power = flux * area;
379
+ assert!(crate::approx_eq(power.to(PowerUnit::W), 1000.0, 1e-12));
380
+
381
+ let duration = Time::new(30.0, TimeUnit::s);
382
+ let energy = power * duration;
383
+ assert!(crate::approx_eq(energy.to(EnergyUnit::J), 30_000.0, 1e-12));
384
+ assert!(crate::approx_eq(
385
+ (energy / duration).to(PowerUnit::kW),
386
+ 1.0,
387
+ 1e-12
388
+ ));
389
+ }
390
+
391
+ #[test]
392
+ fn thermal_conductance_relates_conductivity_length_and_power() {
393
+ let conductivity = ThermalConductivity::new(200.0, ThermalConductivityUnit::W_mK);
394
+ let characteristic_length = Distance::new(0.05, DistanceUnit::m);
395
+ let conductance = conductivity * characteristic_length;
396
+ assert!(crate::approx_eq(
397
+ conductance.to(ThermalConductanceUnit::W_K),
398
+ 10.0,
399
+ 1e-12
400
+ ));
401
+
402
+ let temperature_difference = Temperature::new(15.0, TemperatureUnit::K);
403
+ let power = conductance * temperature_difference;
404
+ assert!(crate::approx_eq(power.to(PowerUnit::W), 150.0, 1e-12));
405
+
406
+ assert!(crate::approx_eq(
407
+ (power / conductance).to(TemperatureUnit::K),
408
+ 15.0,
409
+ 1e-12
410
+ ));
411
+ assert!(crate::approx_eq(
412
+ (power / temperature_difference).to(ThermalConductanceUnit::W_K),
413
+ 10.0,
414
+ 1e-12
415
+ ));
416
+ assert!(crate::approx_eq(
417
+ (conductance / conductivity).to(DistanceUnit::m),
418
+ 0.05,
419
+ 1e-12
420
+ ));
421
+ assert!(crate::approx_eq(
422
+ (conductance / characteristic_length).to(ThermalConductivityUnit::W_mK),
423
+ 200.0,
424
+ 1e-12
425
+ ));
426
+ }
427
+
364
428
  #[test]
365
429
  fn specific_and_molar_gas_constants_relate_through_molar_mass() {
366
430
  let molar_mass = MolarMass::new(28.97, MolarMassUnit::g_mol);
@@ -34,6 +34,7 @@ fn quantity_kind_id_is_stored_in_memory_optimal_layout() {
34
34
  #[test]
35
35
  fn quantity_kind_ids_are_unique_and_non_zero() {
36
36
  let ids = [
37
+ <HeatCapacity as QuantityKindId>::KIND_ID,
37
38
  <Acceleration as QuantityKindId>::KIND_ID,
38
39
  <Angle as QuantityKindId>::KIND_ID,
39
40
  <AngularAcceleration as QuantityKindId>::KIND_ID,
@@ -2,6 +2,35 @@ import pytest
2
2
  from unitforge import *
3
3
 
4
4
 
5
+ def test_heat_capacity_thermal_relations():
6
+ volumetric = VolumetricHeatCapacity(2.0, VolumetricHeatCapacityUnit.kJ_mcbK)
7
+ volume = Volume(0.5, VolumeUnit.mcb)
8
+ capacity = volumetric * volume
9
+ assert isinstance(capacity, HeatCapacity)
10
+ assert capacity.to(HeatCapacityUnit.J_K) == pytest.approx(1000.0)
11
+ assert capacity.to(HeatCapacityUnit.kJ_K) == pytest.approx(1.0)
12
+ assert volume * volumetric == capacity
13
+ assert capacity / volume == volumetric
14
+ assert capacity / volumetric == volume
15
+ time = Time(2.0, TimeUnit.s)
16
+ conductance = capacity / time
17
+ assert isinstance(conductance, ThermalConductance)
18
+ assert conductance.to(ThermalConductanceUnit.W_K) == pytest.approx(500.0)
19
+ assert conductance * time == capacity
20
+ assert time * conductance == capacity
21
+ assert capacity / conductance == time
22
+ temperature = Temperature(300.0, TemperatureUnit.K)
23
+ energy = capacity * temperature
24
+ assert isinstance(energy, Energy)
25
+ assert energy.to(EnergyUnit.J) == pytest.approx(300000.0)
26
+ assert temperature * capacity == energy
27
+ assert energy / temperature == capacity
28
+ assert energy / capacity == temperature
29
+ assert (energy / time).to(PowerUnit.W) == pytest.approx(150000.0)
30
+ with pytest.raises(TypeError):
31
+ _ = capacity + conductance
32
+
33
+
5
34
  def test_inverse_stress_from_stress_division():
6
35
  stress = Stress(2.0, StressUnit.Pa)
7
36
  inverse = 1 / stress
@@ -0,0 +1,226 @@
1
+ use num_traits::Zero;
2
+ use unitforge::*;
3
+
4
+ fn close<Q: PhysicsQuantity>(actual: Q, expected: Q) {
5
+ // Match the precision supported by each storage configuration.
6
+ let epsilon = if cfg!(feature = "storage-f32") {
7
+ 1e-5
8
+ } else {
9
+ 1e-10
10
+ };
11
+ let tolerance = expected.abs() * epsilon + Q::from_raw(epsilon);
12
+ assert!(
13
+ actual.is_close(&expected, &tolerance),
14
+ "physical values differ"
15
+ );
16
+ }
17
+
18
+ #[test]
19
+ fn scalar_thermal_relations_in_both_orders_and_inverses() {
20
+ let volumetric = VolumetricHeatCapacity::new(2.0, VolumetricHeatCapacityUnit::kJ_mcbK);
21
+ let volume = Volume::new(0.5, VolumeUnit::mcb);
22
+ let capacity = HeatCapacity::new(1000.0, HeatCapacityUnit::J_K);
23
+ let time = Time::new(2000.0, TimeUnit::ms);
24
+ let conductance = ThermalConductance::new(0.5, ThermalConductanceUnit::kW_K);
25
+ let temperature = Temperature::new(300.0, TemperatureUnit::K);
26
+ let energy = Energy::new(300.0, EnergyUnit::kNm);
27
+
28
+ let assembled: HeatCapacity = volumetric * volume;
29
+ close(assembled, capacity);
30
+ close(volume * volumetric, capacity);
31
+ close(capacity / volume, volumetric);
32
+ close(capacity / volumetric, volume);
33
+ close(conductance * time, capacity);
34
+ close(time * conductance, capacity);
35
+ let rate: ThermalConductance = capacity / time;
36
+ close(rate, conductance);
37
+ close(capacity / conductance, time);
38
+ let stored_energy: Energy = capacity * temperature;
39
+ close(stored_energy, energy);
40
+ close(temperature * capacity, energy);
41
+ close(energy / temperature, capacity);
42
+ close(energy / capacity, temperature);
43
+ let power: Power = stored_energy / time;
44
+ close(power, Power::new(150000.0, PowerUnit::W));
45
+ }
46
+
47
+ #[test]
48
+ fn capacity_units_signed_arithmetic_and_registration() {
49
+ let capacity = HeatCapacity::new(1.0, HeatCapacityUnit::kJ_K);
50
+ close(capacity, HeatCapacity::new(1000.0, HeatCapacityUnit::J_K));
51
+ assert_eq!(capacity.to(HeatCapacityUnit::kJ_K), 1.0);
52
+ assert_eq!(capacity.to(HeatCapacityUnit::J_K), 1000.0);
53
+ let mut accumulated = HeatCapacity::zero();
54
+ accumulated += capacity;
55
+ accumulated -= capacity * 0.25;
56
+ close(accumulated, HeatCapacity::new(750.0, HeatCapacityUnit::J_K));
57
+ close(-capacity + capacity, HeatCapacity::zero());
58
+ close(capacity / 2.0, 0.5 * capacity);
59
+ assert!(capacity > -capacity);
60
+ assert!((capacity / HeatCapacity::new(250.0, HeatCapacityUnit::J_K) - 4.0).abs() < 1e-5);
61
+ assert_ne!(HeatCapacity::KIND_ID, 0);
62
+ assert_ne!(HeatCapacity::KIND_ID, ThermalConductance::KIND_ID);
63
+ let runtime = Unit::HeatCapacityUnit(HeatCapacityUnit::kJ_K).to_quantity(1.0);
64
+ assert_eq!(runtime, Quantity::HeatCapacityQuantity(capacity));
65
+ let result = (runtime / Quantity::TimeQuantity(Time::new(2.0, TimeUnit::s))).unwrap();
66
+ assert_eq!(
67
+ result,
68
+ Quantity::ThermalConductanceQuantity(ThermalConductance::new(
69
+ 500.0,
70
+ ThermalConductanceUnit::W_K
71
+ ))
72
+ );
73
+ }
74
+
75
+ #[test]
76
+ fn exceptional_values_follow_quantity_semantics() {
77
+ let zero = HeatCapacity::zero();
78
+ let time_zero = Time::zero();
79
+ assert!((zero / time_zero).is_nan());
80
+ assert!((zero / zero).is_nan());
81
+ close(
82
+ HeatCapacity::new(-2.0, HeatCapacityUnit::J_K) / Time::new(2.0, TimeUnit::s),
83
+ ThermalConductance::new(-1.0, ThermalConductanceUnit::W_K),
84
+ );
85
+ assert!((HeatCapacity::new(1.0, HeatCapacityUnit::J_K) / time_zero)
86
+ .to(ThermalConductanceUnit::W_K)
87
+ .is_infinite());
88
+ assert!(HeatCapacity::nan().is_nan());
89
+ #[cfg(not(any(feature = "storage-f64", feature = "storage-f32")))]
90
+ {
91
+ assert!(HeatCapacity::from_exponential(1.0, MAX_ABS_QUANTITY_POWER)
92
+ .to(HeatCapacityUnit::J_K)
93
+ .is_infinite());
94
+ close(
95
+ HeatCapacity::from_exponential(1.0, -MAX_ABS_QUANTITY_POWER),
96
+ zero,
97
+ );
98
+ }
99
+ }
100
+
101
+ #[cfg(feature = "serde")]
102
+ #[test]
103
+ fn capacity_serialization_roundtrip() {
104
+ let original = HeatCapacity::new(-0.125, HeatCapacityUnit::kJ_K);
105
+ let restored: HeatCapacity =
106
+ serde_json::from_str(&serde_json::to_string(&original).unwrap()).unwrap();
107
+ close(restored, original);
108
+ let unit: HeatCapacityUnit =
109
+ serde_json::from_str(&serde_json::to_string(&HeatCapacityUnit::kJ_K).unwrap()).unwrap();
110
+ assert_eq!(unit, HeatCapacityUnit::kJ_K);
111
+ }
112
+
113
+ #[cfg(feature = "strum")]
114
+ #[test]
115
+ fn capacity_units_are_registered_for_iteration() {
116
+ use strum::IntoEnumIterator;
117
+ assert_eq!(
118
+ HeatCapacityUnit::iter().collect::<Vec<_>>(),
119
+ vec![HeatCapacityUnit::J_K, HeatCapacityUnit::kJ_K]
120
+ );
121
+ }
122
+
123
+ #[cfg(feature = "ndarray")]
124
+ mod arrays {
125
+ use super::*;
126
+ use ndarray::{arr1, arr2, s, Array1, Array2};
127
+
128
+ fn capacity(value: f64) -> HeatCapacity {
129
+ HeatCapacity::new(value, HeatCapacityUnit::J_K)
130
+ }
131
+
132
+ #[test]
133
+ fn backward_euler_both_routes() {
134
+ let mut c = Array2::<HeatCapacity>::zeros((2, 2));
135
+ let volumetric = VolumetricHeatCapacity::new(2.0, VolumetricHeatCapacityUnit::kJ_mcbK);
136
+ c[(0, 0)] += volumetric * Volume::new(0.001, VolumeUnit::mcb);
137
+ c[(0, 1)] += HeatCapacity::new(0.001, HeatCapacityUnit::kJ_K);
138
+ c[(1, 0)] += capacity(1.0);
139
+ c[(1, 1)] += capacity(3.0);
140
+ let t = arr1(&[300.0, 400.0]).mapv(|v| Temperature::new(v, TemperatureUnit::K));
141
+ let time = Time::new(2.0, TimeUnit::s);
142
+ let k: Array2<ThermalConductance> = c.mapv(|v| v / time);
143
+ assert_eq!(k.dim(), c.dim());
144
+ for (actual, expected) in k.iter().zip([1.0, 0.5, 0.5, 1.5]) {
145
+ close(
146
+ *actual,
147
+ ThermalConductance::new(expected, ThermalConductanceUnit::W_K),
148
+ );
149
+ }
150
+ let energy: Array1<Energy> = QuantityDot::dot(&c, &t);
151
+ close(energy[0], Energy::new(1000.0, EnergyUnit::J));
152
+ close(energy[1], Energy::new(1500.0, EnergyUnit::J));
153
+ let p: Array1<Power> = energy.mapv(|v| v / time);
154
+ let other: Array1<Power> = QuantityDot::dot(&k, &t);
155
+ assert_eq!(p.len(), 2);
156
+ for ((a, b), expected) in p.iter().zip(other.iter()).zip([500.0, 750.0]) {
157
+ close(*a, Power::new(expected, PowerUnit::W));
158
+ close(*a, *b);
159
+ }
160
+ #[cfg(feature = "serde")]
161
+ {
162
+ let restored: Array2<HeatCapacity> =
163
+ serde_json::from_str(&serde_json::to_string(&c).unwrap()).unwrap();
164
+ assert_eq!(restored.dim(), c.dim());
165
+ for (a, b) in restored.iter().zip(c.iter()) {
166
+ close(*a, *b);
167
+ }
168
+ }
169
+ }
170
+
171
+ #[test]
172
+ fn rectangular_strided_views_and_cancellation() {
173
+ let c = arr2(&[[2.0, 99.0, -1.0, 99.0, 0.0], [0.0, 99.0, 3.0, 99.0, -2.0]]).mapv(capacity);
174
+ let c_view = c.slice(s![.., ..;2]);
175
+ let t = arr1(&[300.0, 99.0, 600.0, 99.0, 400.0])
176
+ .mapv(|v| Temperature::new(v, TemperatureUnit::K));
177
+ let t_view = t.slice(s![..;2]);
178
+ let energy: Array1<Energy> = QuantityDot::dot(&c_view, &t_view);
179
+ close(energy[0], Energy::zero());
180
+ close(energy[1], Energy::new(1000.0, EnergyUnit::J));
181
+ let time = Time::new(2.0, TimeUnit::s);
182
+ let power: Array1<Power> = energy.mapv(|v| v / time);
183
+ let k = c_view.mapv(|v| v / time);
184
+ let other: Array1<Power> = QuantityDot::dot(&k.view(), &t_view);
185
+ for (a, b) in power.iter().zip(other.iter()) {
186
+ close(*a, *b);
187
+ }
188
+ let vv: Energy = QuantityDot::dot(&c_view.row(0), &t_view);
189
+ close(vv, Energy::zero());
190
+ let vm: Array1<Energy> = QuantityDot::dot(&t_view, &c_view.t());
191
+ for (a, b) in vm.iter().zip(energy.iter()) {
192
+ close(*a, *b);
193
+ }
194
+ let temperatures = arr2(&[[300.0, 0.0], [600.0, 1.0], [400.0, -1.0]])
195
+ .mapv(|v| Temperature::new(v, TemperatureUnit::K));
196
+ let mm: Array2<Energy> = QuantityDot::dot(&c_view, &temperatures.view());
197
+ assert_eq!(mm.dim(), (2, 2));
198
+ close(mm[(0, 0)], Energy::zero());
199
+ close(mm[(1, 0)], energy[1]);
200
+ close(mm[(0, 1)], Energy::new(-1.0, EnergyUnit::J));
201
+ close(mm[(1, 1)], Energy::new(5.0, EnergyUnit::J));
202
+ }
203
+
204
+ #[test]
205
+ fn contraction_dimensions_must_match() {
206
+ let c = Array2::from_elem((2, 3), capacity(1.0));
207
+ let t = Array1::from_elem(2, Temperature::new(300.0, TemperatureUnit::K));
208
+ let tm = Array2::from_elem((2, 4), Temperature::new(300.0, TemperatureUnit::K));
209
+ assert!(std::panic::catch_unwind(|| QuantityDot::dot(&c.view(), &t.view())).is_err());
210
+ assert!(std::panic::catch_unwind(|| QuantityDot::dot(&c.view(), &tm.view())).is_err());
211
+ assert!(std::panic::catch_unwind(|| QuantityDot::dot(&c.row(0), &t.view())).is_err());
212
+ assert!(std::panic::catch_unwind(|| QuantityDot::dot(&c.row(0), &tm.view())).is_err());
213
+ }
214
+
215
+ #[test]
216
+ fn quantity_array_conversion() {
217
+ let c = Array2::<HeatCapacity>::from_raw(
218
+ arr2(&[[0.001, -0.002]]).view(),
219
+ HeatCapacityUnit::kJ_K,
220
+ );
221
+ close(c[(0, 0)], capacity(1.0));
222
+ close(c[(0, 1)], capacity(-2.0));
223
+ let v = Array1::<HeatCapacity>::from_raw(arr1(&[0.003]).view(), HeatCapacityUnit::kJ_K);
224
+ close(v[0], capacity(3.0));
225
+ }
226
+ }
@@ -11,6 +11,18 @@ from unitforge import (
11
11
  ForceAreaUnit,
12
12
  Force,
13
13
  ForceUnit,
14
+ HeatCapacity,
15
+ HeatCapacityUnit,
16
+ Power,
17
+ Temperature,
18
+ TemperatureUnit,
19
+ ThermalConductance,
20
+ Time,
21
+ TimeUnit,
22
+ Volume,
23
+ VolumeUnit,
24
+ VolumetricHeatCapacity,
25
+ VolumetricHeatCapacityUnit,
14
26
  Matrix3,
15
27
  Moment,
16
28
  MomentUnit,
@@ -25,6 +37,16 @@ force_scaled: Force = force * 2.0
25
37
  force_ratio: float = force / Force(3.0, ForceUnit.N)
26
38
  dynamic_product: Energy = force * distance
27
39
 
40
+ volumetric: VolumetricHeatCapacity = VolumetricHeatCapacity(2.0, VolumetricHeatCapacityUnit.kJ_mcbK)
41
+ volume: Volume = Volume(0.5, VolumeUnit.mcb)
42
+ capacity: HeatCapacity = volumetric * volume
43
+ capacity_in_kj: float = capacity.to(HeatCapacityUnit.kJ_K)
44
+ step: Time = Time(2.0, TimeUnit.s)
45
+ conductance: ThermalConductance = capacity / step
46
+ temperature: Temperature = Temperature(300.0, TemperatureUnit.K)
47
+ thermal_energy: Energy = capacity * temperature
48
+ heat_rate: Power = thermal_energy / step
49
+
28
50
  vector: Vector3 = Vector3.from_list([1.0, 2.0, 3.0], ForceUnit.N)
29
51
  vector_first: Any = vector[0]
30
52
  vector_norm: Any = vector.norm()
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "unitforge_macros"
3
- version = "0.5.8"
3
+ version = "0.5.11"
4
4
  edition = "2021"
5
5
  authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
6
6
  description = "Proc macros for generating units, quantities, and Python bindings in unitforge"