siderust-js 0.1.0

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 (272) hide show
  1. package/.github/workflows/ci.yml +166 -0
  2. package/.gitmodules +9 -0
  3. package/CHANGELOG.md +26 -0
  4. package/LICENSE +661 -0
  5. package/README.md +138 -0
  6. package/package.json +12 -0
  7. package/qtty-js/.github/workflows/ci.yml +151 -0
  8. package/qtty-js/.gitmodules +3 -0
  9. package/qtty-js/CHANGELOG.md +31 -0
  10. package/qtty-js/LICENSE +661 -0
  11. package/qtty-js/README.md +132 -0
  12. package/qtty-js/package.json +20 -0
  13. package/qtty-js/qtty/.github/workflows/ci.yml +155 -0
  14. package/qtty-js/qtty/CHANGELOG.md +120 -0
  15. package/qtty-js/qtty/Cargo.lock +1462 -0
  16. package/qtty-js/qtty/Cargo.toml +12 -0
  17. package/qtty-js/qtty/LICENSE +661 -0
  18. package/qtty-js/qtty/README.md +9 -0
  19. package/qtty-js/qtty/qtty/Cargo.toml +41 -0
  20. package/qtty-js/qtty/qtty/README.md +8 -0
  21. package/qtty-js/qtty/qtty/examples/angles.rs +14 -0
  22. package/qtty-js/qtty/qtty/examples/astronomy.rs +17 -0
  23. package/qtty-js/qtty/qtty/examples/dimensional_arithmetic.rs +83 -0
  24. package/qtty-js/qtty/qtty/examples/python_integration.rs +61 -0
  25. package/qtty-js/qtty/qtty/examples/quickstart.rs +15 -0
  26. package/qtty-js/qtty/qtty/examples/ratios.rs +12 -0
  27. package/qtty-js/qtty/qtty/examples/serde_with_unit.rs +234 -0
  28. package/qtty-js/qtty/qtty/examples/serialization.rs +141 -0
  29. package/qtty-js/qtty/qtty/examples/serialization_advanced.rs +155 -0
  30. package/qtty-js/qtty/qtty/src/f32.rs +108 -0
  31. package/qtty-js/qtty/qtty/src/f64.rs +30 -0
  32. package/qtty-js/qtty/qtty/src/i128.rs +111 -0
  33. package/qtty-js/qtty/qtty/src/i16.rs +111 -0
  34. package/qtty-js/qtty/qtty/src/i32.rs +111 -0
  35. package/qtty-js/qtty/qtty/src/i64.rs +111 -0
  36. package/qtty-js/qtty/qtty/src/i8.rs +111 -0
  37. package/qtty-js/qtty/qtty/src/lib.rs +238 -0
  38. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.lock +83 -0
  39. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/Cargo.toml +10 -0
  40. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std/src/lib.rs +7 -0
  41. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.lock +83 -0
  42. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/Cargo.toml +10 -0
  43. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-no-std-alloc/src/lib.rs +7 -0
  44. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.lock +83 -0
  45. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/Cargo.toml +10 -0
  46. package/qtty-js/qtty/qtty/tests/fixtures/qtty-vec-std/src/lib.rs +5 -0
  47. package/qtty-js/qtty/qtty/tests/integration_tests.rs +529 -0
  48. package/qtty-js/qtty/qtty/tests/qtty_vec_feature_matrix.rs +58 -0
  49. package/qtty-js/qtty/qtty-core/Cargo.toml +41 -0
  50. package/qtty-js/qtty/qtty-core/README.md +8 -0
  51. package/qtty-js/qtty/qtty-core/examples/diesel_integration.rs +145 -0
  52. package/qtty-js/qtty/qtty-core/examples/quantity_db_serde.rs +215 -0
  53. package/qtty-js/qtty/qtty-core/src/dimension.rs +249 -0
  54. package/qtty-js/qtty/qtty-core/src/feature_diesel.rs +318 -0
  55. package/qtty-js/qtty/qtty-core/src/feature_pyo3.rs +27 -0
  56. package/qtty-js/qtty/qtty-core/src/feature_serde.rs +203 -0
  57. package/qtty-js/qtty/qtty-core/src/feature_tiberius.rs +28 -0
  58. package/qtty-js/qtty/qtty-core/src/lib.rs +744 -0
  59. package/qtty-js/qtty/qtty-core/src/macros.rs +93 -0
  60. package/qtty-js/qtty/qtty-core/src/quantity.rs +810 -0
  61. package/qtty-js/qtty/qtty-core/src/scalar.rs +1742 -0
  62. package/qtty-js/qtty/qtty-core/src/unit.rs +332 -0
  63. package/qtty-js/qtty/qtty-core/src/units/angular.rs +1228 -0
  64. package/qtty-js/qtty/qtty-core/src/units/area.rs +243 -0
  65. package/qtty-js/qtty/qtty-core/src/units/frequency.rs +179 -0
  66. package/qtty-js/qtty/qtty-core/src/units/length.rs +1270 -0
  67. package/qtty-js/qtty/qtty-core/src/units/mass.rs +488 -0
  68. package/qtty-js/qtty/qtty-core/src/units/mod.rs +26 -0
  69. package/qtty-js/qtty/qtty-core/src/units/power.rs +324 -0
  70. package/qtty-js/qtty/qtty-core/src/units/time.rs +667 -0
  71. package/qtty-js/qtty/qtty-core/src/units/unitless.rs +212 -0
  72. package/qtty-js/qtty/qtty-core/src/units/velocity.rs +210 -0
  73. package/qtty-js/qtty/qtty-core/src/units/volume.rs +269 -0
  74. package/qtty-js/qtty/qtty-core/tests/core.rs +628 -0
  75. package/qtty-js/qtty/qtty-core/tests/diesel.rs +461 -0
  76. package/qtty-js/qtty/qtty-core/tests/integers.rs +632 -0
  77. package/qtty-js/qtty/qtty-core/tests/no_cross_unit_ops.rs +35 -0
  78. package/qtty-js/qtty/qtty-core/tests/pyo3.rs +334 -0
  79. package/qtty-js/qtty/qtty-core/tests/quantity_f32.rs +276 -0
  80. package/qtty-js/qtty/qtty-core/tests/scalar_decimal.rs +258 -0
  81. package/qtty-js/qtty/qtty-core/tests/scalar_f32.rs +286 -0
  82. package/qtty-js/qtty/qtty-core/tests/scalar_f64_real.rs +287 -0
  83. package/qtty-js/qtty/qtty-core/tests/scalar_rational.rs +260 -0
  84. package/qtty-js/qtty/qtty-core/tests/serde.rs +256 -0
  85. package/qtty-js/qtty/qtty-core/tests/tiberius.rs +208 -0
  86. package/qtty-js/qtty/qtty-derive/Cargo.toml +23 -0
  87. package/qtty-js/qtty/qtty-derive/README.md +8 -0
  88. package/qtty-js/qtty/qtty-derive/src/lib.rs +340 -0
  89. package/qtty-js/qtty/qtty-ffi/ARCHITECTURE.md +3 -0
  90. package/qtty-js/qtty/qtty-ffi/Cargo.toml +31 -0
  91. package/qtty-js/qtty/qtty-ffi/README.md +9 -0
  92. package/qtty-js/qtty/qtty-ffi/build.rs +326 -0
  93. package/qtty-js/qtty/qtty-ffi/cbindgen.toml +105 -0
  94. package/qtty-js/qtty/qtty-ffi/include/qtty_ffi.h +1126 -0
  95. package/qtty-js/qtty/qtty-ffi/src/ffi.rs +1251 -0
  96. package/qtty-js/qtty/qtty-ffi/src/ffi_serde.rs +294 -0
  97. package/qtty-js/qtty/qtty-ffi/src/helpers.rs +310 -0
  98. package/qtty-js/qtty/qtty-ffi/src/lib.rs +229 -0
  99. package/qtty-js/qtty/qtty-ffi/src/macros.rs +121 -0
  100. package/qtty-js/qtty/qtty-ffi/src/registry.rs +274 -0
  101. package/qtty-js/qtty/qtty-ffi/src/types.rs +620 -0
  102. package/qtty-js/qtty/qtty-ffi/tests/integration_tests.rs +842 -0
  103. package/qtty-js/qtty/qtty-ffi/units.csv +156 -0
  104. package/qtty-js/qtty/qtty-ffi/units.csv.md +3 -0
  105. package/qtty-js/qtty-node/.prettierignore +6 -0
  106. package/qtty-js/qtty-node/.prettierrc.json +6 -0
  107. package/qtty-js/qtty-node/README.md +250 -0
  108. package/qtty-js/qtty-node/c8.config.json +11 -0
  109. package/qtty-js/qtty-node/eslint.config.js +31 -0
  110. package/qtty-js/qtty-node/examples/arithmetic.mjs +64 -0
  111. package/qtty-js/qtty-node/examples/astronomy.mjs +90 -0
  112. package/qtty-js/qtty-node/examples/quickstart.mjs +36 -0
  113. package/qtty-js/qtty-node/examples/serialization.mjs +125 -0
  114. package/qtty-js/qtty-node/examples/unit_factories.mjs +74 -0
  115. package/qtty-js/qtty-node/index.d.ts +219 -0
  116. package/qtty-js/qtty-node/index.js +323 -0
  117. package/qtty-js/qtty-node/lib/DerivedQuantity.js +122 -0
  118. package/qtty-js/qtty-node/lib/Quantity.js +151 -0
  119. package/qtty-js/qtty-node/lib/backend.js +25 -0
  120. package/qtty-js/qtty-node/native.cjs +306 -0
  121. package/qtty-js/qtty-node/package-lock.json +3223 -0
  122. package/qtty-js/qtty-node/package.json +70 -0
  123. package/qtty-js/qtty-node/units.d.ts +299 -0
  124. package/qtty-js/qtty-node/units.js +210 -0
  125. package/qtty-js/qtty-web/Cargo.lock +767 -0
  126. package/qtty-js/qtty-web/Cargo.toml +21 -0
  127. package/qtty-js/qtty-web/index.d.ts +140 -0
  128. package/qtty-js/qtty-web/index.js +20 -0
  129. package/qtty-js/qtty-web/lib/DerivedQuantity.js +58 -0
  130. package/qtty-js/qtty-web/lib/Quantity.js +75 -0
  131. package/qtty-js/qtty-web/lib/backend.js +80 -0
  132. package/qtty-js/qtty-web/package.json +45 -0
  133. package/qtty-js/qtty-web/src/lib.rs +111 -0
  134. package/qtty-js/scripts/ci.sh +73 -0
  135. package/scripts/ci.sh +123 -0
  136. package/siderust-core/Cargo.lock +787 -0
  137. package/siderust-core/Cargo.toml +18 -0
  138. package/siderust-core/DEDUPLICATION.md +124 -0
  139. package/siderust-core/src/body.rs +120 -0
  140. package/siderust-core/src/events.rs +184 -0
  141. package/siderust-core/src/lib.rs +20 -0
  142. package/siderust-core/src/observer.rs +55 -0
  143. package/siderust-core/src/position.rs +213 -0
  144. package/siderust-node/.prettierignore +7 -0
  145. package/siderust-node/.prettierrc.json +6 -0
  146. package/siderust-node/Cargo.lock +906 -0
  147. package/siderust-node/Cargo.toml +29 -0
  148. package/siderust-node/README.md +109 -0
  149. package/siderust-node/__test__/index.test.mjs +248 -0
  150. package/siderust-node/build.rs +5 -0
  151. package/siderust-node/c8.config.json +3 -0
  152. package/siderust-node/eslint.config.js +31 -0
  153. package/siderust-node/examples/01_basic_coordinates.mjs +24 -0
  154. package/siderust-node/examples/02_coordinate_transformations.mjs +25 -0
  155. package/siderust-node/examples/03_all_frames_conversions.mjs +26 -0
  156. package/siderust-node/examples/04_all_center_conversions.mjs +24 -0
  157. package/siderust-node/examples/05_target_tracking.mjs +22 -0
  158. package/siderust-node/examples/06_night_events.mjs +18 -0
  159. package/siderust-node/examples/07_moon_properties.mjs +21 -0
  160. package/siderust-node/examples/08_solar_system.mjs +19 -0
  161. package/siderust-node/examples/09_star_observability.mjs +22 -0
  162. package/siderust-node/examples/10_time_periods.mjs +9 -0
  163. package/siderust-node/examples/11_serialization.mjs +31 -0
  164. package/siderust-node/examples/12_runtime_ephemeris.mjs +27 -0
  165. package/siderust-node/examples/13_coordinate_operations.mjs +20 -0
  166. package/siderust-node/index.d.ts +623 -0
  167. package/siderust-node/index.js +79 -0
  168. package/siderust-node/lib/Observer.js +112 -0
  169. package/siderust-node/lib/Star.js +118 -0
  170. package/siderust-node/lib/backend.js +63 -0
  171. package/siderust-node/lib/wrappers.js +566 -0
  172. package/siderust-node/main.js +20 -0
  173. package/siderust-node/native.cjs +360 -0
  174. package/siderust-node/package-lock.json +3261 -0
  175. package/siderust-node/package.json +71 -0
  176. package/siderust-node/src/body.rs +74 -0
  177. package/siderust-node/src/coordinates.rs +372 -0
  178. package/siderust-node/src/ephemeris.rs +462 -0
  179. package/siderust-node/src/events.rs +577 -0
  180. package/siderust-node/src/lib.rs +43 -0
  181. package/siderust-node/src/observer.rs +132 -0
  182. package/siderust-node/src/phase.rs +218 -0
  183. package/siderust-node/src/position.rs +292 -0
  184. package/siderust-node/src/star.rs +200 -0
  185. package/siderust-web/Cargo.lock +855 -0
  186. package/siderust-web/Cargo.toml +34 -0
  187. package/siderust-web/README.md +100 -0
  188. package/siderust-web/__test__/index.test.mjs +118 -0
  189. package/siderust-web/examples/github-pages/README.md +31 -0
  190. package/siderust-web/examples/github-pages/index.html +135 -0
  191. package/siderust-web/index.d.ts +311 -0
  192. package/siderust-web/index.js +66 -0
  193. package/siderust-web/lib/Observer.js +103 -0
  194. package/siderust-web/lib/Star.js +116 -0
  195. package/siderust-web/lib/backend.js +400 -0
  196. package/siderust-web/lib/wrappers.js +512 -0
  197. package/siderust-web/package.json +55 -0
  198. package/siderust-web/src/body.rs +69 -0
  199. package/siderust-web/src/coordinates.rs +302 -0
  200. package/siderust-web/src/ephemeris.rs +456 -0
  201. package/siderust-web/src/events.rs +520 -0
  202. package/siderust-web/src/lib.rs +51 -0
  203. package/siderust-web/src/observer.rs +117 -0
  204. package/siderust-web/src/phase.rs +190 -0
  205. package/siderust-web/src/position.rs +291 -0
  206. package/siderust-web/src/star.rs +178 -0
  207. package/tempoch-js/.github/workflows/ci.yml +142 -0
  208. package/tempoch-js/.gitmodules +3 -0
  209. package/tempoch-js/CHANGELOG.md +25 -0
  210. package/tempoch-js/LICENSE +661 -0
  211. package/tempoch-js/README.md +126 -0
  212. package/tempoch-js/package.json +20 -0
  213. package/tempoch-js/scripts/ci.sh +73 -0
  214. package/tempoch-js/tempoch/.github/workflows/ci.yml +113 -0
  215. package/tempoch-js/tempoch/CHANGELOG.md +82 -0
  216. package/tempoch-js/tempoch/Cargo.lock +947 -0
  217. package/tempoch-js/tempoch/Cargo.toml +3 -0
  218. package/tempoch-js/tempoch/LICENSE +661 -0
  219. package/tempoch-js/tempoch/README.md +76 -0
  220. package/tempoch-js/tempoch/tempoch/Cargo.toml +27 -0
  221. package/tempoch-js/tempoch/tempoch/examples/periods.rs +45 -0
  222. package/tempoch-js/tempoch/tempoch/examples/quickstart.rs +13 -0
  223. package/tempoch-js/tempoch/tempoch/src/lib.rs +49 -0
  224. package/tempoch-js/tempoch/tempoch/tests/integration.rs +57 -0
  225. package/tempoch-js/tempoch/tempoch-core/Cargo.toml +24 -0
  226. package/tempoch-js/tempoch/tempoch-core/src/delta_t.rs +345 -0
  227. package/tempoch-js/tempoch/tempoch-core/src/instant.rs +811 -0
  228. package/tempoch-js/tempoch/tempoch-core/src/julian_date_ext.rs +142 -0
  229. package/tempoch-js/tempoch/tempoch-core/src/lib.rs +81 -0
  230. package/tempoch-js/tempoch/tempoch-core/src/period.rs +1168 -0
  231. package/tempoch-js/tempoch/tempoch-core/src/scales.rs +779 -0
  232. package/tempoch-js/tempoch/tempoch-ffi/Cargo.lock +889 -0
  233. package/tempoch-js/tempoch/tempoch-ffi/Cargo.toml +26 -0
  234. package/tempoch-js/tempoch/tempoch-ffi/build.rs +24 -0
  235. package/tempoch-js/tempoch/tempoch-ffi/cbindgen.toml +30 -0
  236. package/tempoch-js/tempoch/tempoch-ffi/src/error.rs +19 -0
  237. package/tempoch-js/tempoch/tempoch-ffi/src/lib.rs +82 -0
  238. package/tempoch-js/tempoch/tempoch-ffi/src/period.rs +101 -0
  239. package/tempoch-js/tempoch/tempoch-ffi/src/time.rs +711 -0
  240. package/tempoch-js/tempoch/tempoch-ffi/tests/ffi.rs +265 -0
  241. package/tempoch-js/tempoch-node/.prettierignore +6 -0
  242. package/tempoch-js/tempoch-node/.prettierrc.json +6 -0
  243. package/tempoch-js/tempoch-node/Cargo.lock +496 -0
  244. package/tempoch-js/tempoch-node/Cargo.toml +29 -0
  245. package/tempoch-js/tempoch-node/README.md +265 -0
  246. package/tempoch-js/tempoch-node/__test__/index.test.mjs +598 -0
  247. package/tempoch-js/tempoch-node/build.rs +5 -0
  248. package/tempoch-js/tempoch-node/c8.config.json +3 -0
  249. package/tempoch-js/tempoch-node/eslint.config.js +31 -0
  250. package/tempoch-js/tempoch-node/examples/periods.mjs +79 -0
  251. package/tempoch-js/tempoch-node/examples/quickstart.mjs +71 -0
  252. package/tempoch-js/tempoch-node/examples/timescales.mjs +92 -0
  253. package/tempoch-js/tempoch-node/index.d.ts +280 -0
  254. package/tempoch-js/tempoch-node/index.js +32 -0
  255. package/tempoch-js/tempoch-node/lib/JulianDate.js +176 -0
  256. package/tempoch-js/tempoch-node/lib/ModifiedJulianDate.js +156 -0
  257. package/tempoch-js/tempoch-node/lib/Period.js +133 -0
  258. package/tempoch-js/tempoch-node/lib/backend.js +38 -0
  259. package/tempoch-js/tempoch-node/lib/qttyCompat.js +92 -0
  260. package/tempoch-js/tempoch-node/native.cjs +317 -0
  261. package/tempoch-js/tempoch-node/package-lock.json +3223 -0
  262. package/tempoch-js/tempoch-node/package.json +56 -0
  263. package/tempoch-js/tempoch-node/src/lib.rs +573 -0
  264. package/tempoch-js/tempoch-web/Cargo.toml +23 -0
  265. package/tempoch-js/tempoch-web/index.d.ts +95 -0
  266. package/tempoch-js/tempoch-web/index.js +27 -0
  267. package/tempoch-js/tempoch-web/lib/JulianDate.js +170 -0
  268. package/tempoch-js/tempoch-web/lib/ModifiedJulianDate.js +145 -0
  269. package/tempoch-js/tempoch-web/lib/Period.js +121 -0
  270. package/tempoch-js/tempoch-web/lib/backend.js +118 -0
  271. package/tempoch-js/tempoch-web/package.json +46 -0
  272. package/tempoch-js/tempoch-web/src/lib.rs +184 -0
@@ -0,0 +1,332 @@
1
+ //! Unit types and traits.
2
+
3
+ use crate::dimension::{DimDiv, DimMul, Dimension, Dimensionless};
4
+ use crate::scalar::Scalar;
5
+ use crate::Quantity;
6
+ use core::fmt::{Debug, Display, Formatter, LowerExp, Result, UpperExp};
7
+ use core::marker::PhantomData;
8
+
9
+ /// Trait implemented by every **unit** type.
10
+ ///
11
+ /// * `RATIO` is the conversion factor from this unit to the *canonical scaling unit* of the same dimension.
12
+ /// Example: if metres are canonical (`Meter::RATIO == 1.0`), then kilometres use `Kilometer::RATIO == 1000.0`
13
+ /// because `1 km = 1000 m`.
14
+ ///
15
+ /// * `SYMBOL` is the printable string (e.g. `"m"` or `"km"`).
16
+ ///
17
+ /// * `Dim` ties the unit to its underlying [`Dimension`].
18
+ ///
19
+ /// # Invariants
20
+ ///
21
+ /// - Implementations should be zero-sized marker types (this crate's built-in units are unit structs with no fields).
22
+ /// - `RATIO` should be finite and non-zero.
23
+ pub trait Unit: Copy + PartialEq + Debug + 'static {
24
+ /// Unit-to-canonical conversion factor.
25
+ const RATIO: f64;
26
+
27
+ /// Dimension to which this unit belongs.
28
+ type Dim: Dimension;
29
+
30
+ /// Printable symbol, shown by [`core::fmt::Display`].
31
+ const SYMBOL: &'static str;
32
+ }
33
+
34
+ /// Unit representing the division of two other units.
35
+ ///
36
+ /// `Per<N, D>` corresponds to `N / D` and carries both the
37
+ /// dimensional information and the scaling ratio between the
38
+ /// constituent units. It is generic over any numerator and
39
+ /// denominator units, which allows implementing arithmetic
40
+ /// generically for all pairs without bespoke macros.
41
+ #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
42
+ pub struct Per<N: Unit, D: Unit>(PhantomData<(N, D)>);
43
+
44
+ impl<N: Unit, D: Unit> Unit for Per<N, D>
45
+ where
46
+ N::Dim: DimDiv<D::Dim>,
47
+ <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
48
+ {
49
+ const RATIO: f64 = N::RATIO / D::RATIO;
50
+ type Dim = <N::Dim as DimDiv<D::Dim>>::Output;
51
+ const SYMBOL: &'static str = "";
52
+ }
53
+
54
+ impl<N: Unit, D: Unit, S: Scalar + Display> Display for Quantity<Per<N, D>, S>
55
+ where
56
+ N::Dim: DimDiv<D::Dim>,
57
+ <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
58
+ {
59
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
60
+ Display::fmt(&self.value(), f)?;
61
+ write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
62
+ }
63
+ }
64
+
65
+ impl<N: Unit, D: Unit, S: Scalar + LowerExp> LowerExp for Quantity<Per<N, D>, S>
66
+ where
67
+ N::Dim: DimDiv<D::Dim>,
68
+ <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
69
+ {
70
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
71
+ LowerExp::fmt(&self.value(), f)?;
72
+ write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
73
+ }
74
+ }
75
+
76
+ impl<N: Unit, D: Unit, S: Scalar + UpperExp> UpperExp for Quantity<Per<N, D>, S>
77
+ where
78
+ N::Dim: DimDiv<D::Dim>,
79
+ <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
80
+ {
81
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
82
+ UpperExp::fmt(&self.value(), f)?;
83
+ write!(f, " {}/{}", N::SYMBOL, D::SYMBOL)
84
+ }
85
+ }
86
+
87
+ /// Unit representing the product of two other units.
88
+ ///
89
+ /// `Prod<A, B>` corresponds to `A * B` and carries both the
90
+ /// dimensional information and the scaling ratio between the
91
+ /// constituent units.
92
+ #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
93
+ pub struct Prod<A: Unit, B: Unit>(PhantomData<(A, B)>);
94
+
95
+ impl<A: Unit, B: Unit> Unit for Prod<A, B>
96
+ where
97
+ A::Dim: DimMul<B::Dim>,
98
+ <A::Dim as DimMul<B::Dim>>::Output: Dimension,
99
+ {
100
+ const RATIO: f64 = A::RATIO * B::RATIO;
101
+ type Dim = <A::Dim as DimMul<B::Dim>>::Output;
102
+ const SYMBOL: &'static str = "";
103
+ }
104
+
105
+ impl<A: Unit, B: Unit, S: Scalar + Display> Display for Quantity<Prod<A, B>, S>
106
+ where
107
+ A::Dim: DimMul<B::Dim>,
108
+ <A::Dim as DimMul<B::Dim>>::Output: Dimension,
109
+ {
110
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
111
+ Display::fmt(&self.value(), f)?;
112
+ write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
113
+ }
114
+ }
115
+
116
+ impl<A: Unit, B: Unit, S: Scalar + LowerExp> LowerExp for Quantity<Prod<A, B>, S>
117
+ where
118
+ A::Dim: DimMul<B::Dim>,
119
+ <A::Dim as DimMul<B::Dim>>::Output: Dimension,
120
+ {
121
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
122
+ LowerExp::fmt(&self.value(), f)?;
123
+ write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
124
+ }
125
+ }
126
+
127
+ impl<A: Unit, B: Unit, S: Scalar + UpperExp> UpperExp for Quantity<Prod<A, B>, S>
128
+ where
129
+ A::Dim: DimMul<B::Dim>,
130
+ <A::Dim as DimMul<B::Dim>>::Output: Dimension,
131
+ {
132
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
133
+ UpperExp::fmt(&self.value(), f)?;
134
+ write!(f, " {}·{}", A::SYMBOL, B::SYMBOL)
135
+ }
136
+ }
137
+
138
+ /// Zero-sized marker type for dimensionless quantities.
139
+ ///
140
+ /// `Unitless` represents a dimensionless unit with a conversion ratio of 1.0
141
+ /// and an empty symbol. It is used to model the result of simplifying same-unit
142
+ /// ratios (e.g., `Meters / Meters`) into a plain "number-like" `Quantity<Unitless>`.
143
+ ///
144
+ /// Unlike a type alias to `f64`, this is a proper zero-sized type, which ensures
145
+ /// that only explicitly constructed `Quantity<Unitless>` values are treated as
146
+ /// dimensionless, not bare `f64` primitives.
147
+ #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
148
+ pub struct Unitless;
149
+
150
+ impl Unit for Unitless {
151
+ const RATIO: f64 = 1.0;
152
+ type Dim = Dimensionless;
153
+ const SYMBOL: &'static str = "";
154
+ }
155
+
156
+ impl<S: Scalar + Display> Display for Quantity<Unitless, S> {
157
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
158
+ Display::fmt(&self.value(), f)
159
+ }
160
+ }
161
+
162
+ impl<S: Scalar + LowerExp> LowerExp for Quantity<Unitless, S> {
163
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
164
+ LowerExp::fmt(&self.value(), f)
165
+ }
166
+ }
167
+
168
+ impl<S: Scalar + UpperExp> UpperExp for Quantity<Unitless, S> {
169
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
170
+ UpperExp::fmt(&self.value(), f)
171
+ }
172
+ }
173
+
174
+ /// Trait for simplifying composite unit types.
175
+ ///
176
+ /// This allows reducing complex unit expressions to simpler forms,
177
+ /// such as `Per<U, U>` to `Unitless` or `Per<N, Per<N, D>>` to `D`.
178
+ pub trait Simplify {
179
+ /// The scalar type of this quantity.
180
+ type Scalar: Scalar;
181
+ /// The simplified unit type.
182
+ type Out: Unit;
183
+ /// Convert this quantity to its simplified unit.
184
+ fn simplify(self) -> Quantity<Self::Out, Self::Scalar>;
185
+ }
186
+
187
+ impl<U: Unit, S: Scalar> Simplify for Quantity<Per<U, U>, S>
188
+ where
189
+ U::Dim: DimDiv<U::Dim>,
190
+ <U::Dim as DimDiv<U::Dim>>::Output: Dimension,
191
+ {
192
+ type Scalar = S;
193
+ type Out = Unitless;
194
+ /// ```rust
195
+ /// use qtty_core::length::Meters;
196
+ /// use qtty_core::{Quantity, Simplify, Unitless};
197
+ ///
198
+ /// let ratio = Meters::new(1.0) / Meters::new(2.0);
199
+ /// let unitless: Quantity<Unitless> = ratio.simplify();
200
+ /// assert!((unitless.value() - 0.5).abs() < 1e-12);
201
+ /// ```
202
+ fn simplify(self) -> Quantity<Unitless, S> {
203
+ Quantity::new(self.value())
204
+ }
205
+ }
206
+
207
+ impl<N: Unit, D: Unit, S: Scalar> Simplify for Quantity<Per<N, Per<N, D>>, S>
208
+ where
209
+ N::Dim: DimDiv<D::Dim>,
210
+ <N::Dim as DimDiv<D::Dim>>::Output: Dimension,
211
+ N::Dim: DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>,
212
+ <N::Dim as DimDiv<<N::Dim as DimDiv<D::Dim>>::Output>>::Output: Dimension,
213
+ {
214
+ type Scalar = S;
215
+ type Out = D;
216
+ fn simplify(self) -> Quantity<D, S> {
217
+ Quantity::new(self.value())
218
+ }
219
+ }
220
+
221
+ #[cfg(test)]
222
+ mod tests {
223
+ use super::*;
224
+ use crate::units::length::{Kilometer, Meter, Meters};
225
+ use crate::units::time::{Hour, Second};
226
+ use crate::Quantity;
227
+
228
+ // ── Per: Display, LowerExp, UpperExp ──────────────────────────────────────
229
+
230
+ #[test]
231
+ fn per_display_formats_value_and_symbol() {
232
+ // 10 m/s with Display
233
+ let qty: Quantity<Per<Meter, Second>> = Quantity::new(10.0);
234
+ let s = format!("{qty}");
235
+ assert_eq!(s, "10 m/s");
236
+ }
237
+
238
+ #[test]
239
+ fn per_display_with_precision() {
240
+ let qty: Quantity<Per<Meter, Second>> = Quantity::new(1.5);
241
+ let s = format!("{qty:.2}");
242
+ assert_eq!(s, "1.50 m/s");
243
+ }
244
+
245
+ #[test]
246
+ fn per_lower_exp_formats_correctly() {
247
+ let qty: Quantity<Per<Meter, Second>> = Quantity::new(1000.0);
248
+ let s = format!("{qty:e}");
249
+ assert!(s.contains("e"), "Expected scientific notation, got: {s}");
250
+ assert!(s.ends_with("m/s"), "Expected 'm/s' suffix, got: {s}");
251
+ }
252
+
253
+ #[test]
254
+ fn per_upper_exp_formats_correctly() {
255
+ let qty: Quantity<Per<Meter, Second>> = Quantity::new(1000.0);
256
+ let s = format!("{qty:E}");
257
+ assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
258
+ assert!(s.ends_with("m/s"), "Expected 'm/s' suffix, got: {s}");
259
+ }
260
+
261
+ // ── Prod: Display, LowerExp, UpperExp ─────────────────────────────────────
262
+
263
+ #[test]
264
+ fn prod_display_formats_value_and_symbol() {
265
+ let qty: Quantity<Prod<Meter, Second>> = Quantity::new(3.0);
266
+ let s = format!("{qty}");
267
+ assert_eq!(s, "3 m·s");
268
+ }
269
+
270
+ #[test]
271
+ fn prod_display_with_precision() {
272
+ let qty: Quantity<Prod<Meter, Second>> = Quantity::new(2.5);
273
+ let s = format!("{qty:.3}");
274
+ assert_eq!(s, "2.500 m·s");
275
+ }
276
+
277
+ #[test]
278
+ fn prod_lower_exp_formats_correctly() {
279
+ let qty: Quantity<Prod<Kilometer, Second>> = Quantity::new(5000.0);
280
+ let s = format!("{qty:.2e}");
281
+ assert!(s.contains("e"), "Expected scientific notation, got: {s}");
282
+ assert!(s.ends_with("km·s"), "Expected 'km·s' suffix, got: {s}");
283
+ }
284
+
285
+ #[test]
286
+ fn prod_upper_exp_formats_correctly() {
287
+ let qty: Quantity<Prod<Kilometer, Second>> = Quantity::new(5000.0);
288
+ let s = format!("{qty:.2E}");
289
+ assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
290
+ assert!(s.ends_with("km·s"), "Expected 'km·s' suffix, got: {s}");
291
+ }
292
+
293
+ // ── Unitless: LowerExp, UpperExp ──────────────────────────────────────────
294
+
295
+ #[test]
296
+ fn unitless_lower_exp_formats_correctly() {
297
+ let qty: Quantity<Unitless> = Quantity::new(0.5);
298
+ let s = format!("{qty:e}");
299
+ assert!(s.contains("e"), "Expected scientific notation, got: {s}");
300
+ // No unit symbol for Unitless
301
+ assert!(
302
+ !s.contains(' '),
303
+ "Unitless should not have a space, got: {s}"
304
+ );
305
+ }
306
+
307
+ #[test]
308
+ fn unitless_upper_exp_formats_correctly() {
309
+ let qty: Quantity<Unitless> = Quantity::new(0.5);
310
+ let s = format!("{qty:E}");
311
+ assert!(s.contains("E"), "Expected uppercase-E notation, got: {s}");
312
+ }
313
+
314
+ // ── Simplify: Per<U, U> → Unitless ────────────────────────────────────────
315
+
316
+ #[test]
317
+ fn simplify_per_u_u_gives_unitless() {
318
+ let ratio = Meters::new(3.0) / Meters::new(6.0);
319
+ let unitless: Quantity<Unitless> = ratio.simplify();
320
+ assert!((unitless.value() - 0.5).abs() < 1e-12);
321
+ }
322
+
323
+ // ── Simplify: Per<N, Per<N, D>> → D ──────────────────────────────────────
324
+
325
+ #[test]
326
+ fn simplify_per_n_per_n_d_gives_d() {
327
+ // Quantity<Per<Meter, Per<Meter, Hour>>> should simplify to Quantity<Hour>
328
+ let q: Quantity<Per<Meter, Per<Meter, Hour>>> = Quantity::new(42.0);
329
+ let simplified: Quantity<Hour> = q.simplify();
330
+ assert!((simplified.value() - 42.0).abs() < 1e-12);
331
+ }
332
+ }