universal-physics-tensor 8.0.0 → 9.0.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 (322) hide show
  1. package/README.md +8 -6
  2. package/data/atlas/atlas.json +21 -21
  3. package/data/atlas/atlas.jsonld +1 -1
  4. package/data/atlas/diffusion.json +5 -5
  5. package/data/atlas/oscillators.json +7 -7
  6. package/data/atlas/waves.json +11 -11
  7. package/data/bridge-catalog.json +490 -338
  8. package/data/bridge-catalog.schema.json +566 -40
  9. package/data/quantities.json +230 -218
  10. package/data/quantities.schema.json +52 -0
  11. package/data/units.json +1149 -0
  12. package/data/units.schema.json +124 -0
  13. package/dist/atlas/association.d.ts +1 -1
  14. package/dist/atlas/benchmark/hybrid-retrieval.d.ts.map +1 -1
  15. package/dist/atlas/benchmark/hybrid-retrieval.js +2 -5
  16. package/dist/atlas/benchmark/hybrid-retrieval.js.map +1 -1
  17. package/dist/atlas/physjs-entries.generated.d.ts +1601 -220
  18. package/dist/atlas/physjs-entries.generated.d.ts.map +1 -1
  19. package/dist/atlas/physjs-entries.generated.js +1854 -345
  20. package/dist/atlas/physjs-entries.generated.js.map +1 -1
  21. package/dist/atlas/physjs-ref.d.ts +89 -89
  22. package/dist/atlas/physjs-ref.d.ts.map +1 -1
  23. package/dist/atlas/physjs-ref.js +128 -176
  24. package/dist/atlas/physjs-ref.js.map +1 -1
  25. package/dist/atlas/proof-target.d.ts +16 -8
  26. package/dist/atlas/proof-target.d.ts.map +1 -1
  27. package/dist/atlas/proof-target.js +34 -31
  28. package/dist/atlas/proof-target.js.map +1 -1
  29. package/dist/bridges/catalog-adapter.d.ts.map +1 -1
  30. package/dist/bridges/catalog-adapter.js +5 -5
  31. package/dist/bridges/catalog-adapter.js.map +1 -1
  32. package/dist/bridges/catalog-load.d.ts +7 -0
  33. package/dist/bridges/catalog-load.d.ts.map +1 -1
  34. package/dist/bridges/catalog-load.js +69 -13
  35. package/dist/bridges/catalog-load.js.map +1 -1
  36. package/dist/bridges/catalog-types.d.ts +51 -6
  37. package/dist/bridges/catalog-types.d.ts.map +1 -1
  38. package/dist/bridges/evaluation-errors.d.ts +76 -0
  39. package/dist/bridges/evaluation-errors.d.ts.map +1 -0
  40. package/dist/bridges/evaluation-errors.js +118 -0
  41. package/dist/bridges/evaluation-errors.js.map +1 -0
  42. package/dist/bridges/evaluator-inputs.d.ts +5 -3
  43. package/dist/bridges/evaluator-inputs.d.ts.map +1 -1
  44. package/dist/bridges/evaluator-inputs.js +24 -24
  45. package/dist/bridges/evaluator-inputs.js.map +1 -1
  46. package/dist/bridges/evaluators.d.ts +32 -7
  47. package/dist/bridges/evaluators.d.ts.map +1 -1
  48. package/dist/bridges/evaluators.js +131 -29
  49. package/dist/bridges/evaluators.js.map +1 -1
  50. package/dist/bridges/expr-parse.d.ts +11 -3
  51. package/dist/bridges/expr-parse.d.ts.map +1 -1
  52. package/dist/bridges/expr-parse.js +46 -153
  53. package/dist/bridges/expr-parse.js.map +1 -1
  54. package/dist/bridges/holds.d.ts +11 -7
  55. package/dist/bridges/holds.d.ts.map +1 -1
  56. package/dist/bridges/holds.js +113 -234
  57. package/dist/bridges/holds.js.map +1 -1
  58. package/dist/bridges/input-contract.d.ts +65 -0
  59. package/dist/bridges/input-contract.d.ts.map +1 -0
  60. package/dist/bridges/input-contract.js +97 -0
  61. package/dist/bridges/input-contract.js.map +1 -0
  62. package/dist/bridges/notices.d.ts +11 -0
  63. package/dist/bridges/notices.d.ts.map +1 -0
  64. package/dist/bridges/notices.js +22 -0
  65. package/dist/bridges/notices.js.map +1 -0
  66. package/dist/canonical/canonical-equation.d.ts +31 -0
  67. package/dist/canonical/canonical-equation.d.ts.map +1 -1
  68. package/dist/canonical/entries/atomic.d.ts.map +1 -1
  69. package/dist/canonical/entries/atomic.js +11 -1
  70. package/dist/canonical/entries/atomic.js.map +1 -1
  71. package/dist/canonical/entries/condensed-matter.d.ts.map +1 -1
  72. package/dist/canonical/entries/condensed-matter.js +3 -0
  73. package/dist/canonical/entries/condensed-matter.js.map +1 -1
  74. package/dist/canonical/entries/dimensional-classics.d.ts.map +1 -1
  75. package/dist/canonical/entries/dimensional-classics.js +48 -0
  76. package/dist/canonical/entries/dimensional-classics.js.map +1 -1
  77. package/dist/canonical/entries/electromagnetism.d.ts.map +1 -1
  78. package/dist/canonical/entries/electromagnetism.js +30 -0
  79. package/dist/canonical/entries/electromagnetism.js.map +1 -1
  80. package/dist/canonical/entries/fluids-waves.d.ts.map +1 -1
  81. package/dist/canonical/entries/fluids-waves.js +34 -0
  82. package/dist/canonical/entries/fluids-waves.js.map +1 -1
  83. package/dist/canonical/entries/mechanics.d.ts.map +1 -1
  84. package/dist/canonical/entries/mechanics.js +24 -0
  85. package/dist/canonical/entries/mechanics.js.map +1 -1
  86. package/dist/canonical/entries/nonmonomial.d.ts.map +1 -1
  87. package/dist/canonical/entries/nonmonomial.js +15 -4
  88. package/dist/canonical/entries/nonmonomial.js.map +1 -1
  89. package/dist/canonical/entries/relativity.d.ts.map +1 -1
  90. package/dist/canonical/entries/relativity.js +6 -0
  91. package/dist/canonical/entries/relativity.js.map +1 -1
  92. package/dist/canonical/entries/statistical-mechanics.d.ts.map +1 -1
  93. package/dist/canonical/entries/statistical-mechanics.js +19 -0
  94. package/dist/canonical/entries/statistical-mechanics.js.map +1 -1
  95. package/dist/canonical/entries/thermo-nuclear-cosmo.d.ts.map +1 -1
  96. package/dist/canonical/entries/thermo-nuclear-cosmo.js +15 -0
  97. package/dist/canonical/entries/thermo-nuclear-cosmo.js.map +1 -1
  98. package/dist/cases/index.d.ts +9 -2
  99. package/dist/cases/index.d.ts.map +1 -1
  100. package/dist/cases/index.js +26 -7
  101. package/dist/cases/index.js.map +1 -1
  102. package/dist/cases/kepler-rv.js +6 -6
  103. package/dist/cases/kepler-rv.js.map +1 -1
  104. package/dist/cli/commands/_discovery-opts.d.ts +2 -1
  105. package/dist/cli/commands/_discovery-opts.d.ts.map +1 -1
  106. package/dist/cli/commands/_discovery-opts.js +7 -12
  107. package/dist/cli/commands/_discovery-opts.js.map +1 -1
  108. package/dist/cli/commands/atlas.d.ts.map +1 -1
  109. package/dist/cli/commands/atlas.js +6 -9
  110. package/dist/cli/commands/atlas.js.map +1 -1
  111. package/dist/cli/commands/audit.d.ts.map +1 -1
  112. package/dist/cli/commands/audit.js +1 -2
  113. package/dist/cli/commands/audit.js.map +1 -1
  114. package/dist/cli/commands/connectors.d.ts.map +1 -1
  115. package/dist/cli/commands/connectors.js +8 -9
  116. package/dist/cli/commands/connectors.js.map +1 -1
  117. package/dist/cli/commands/derive.js +9 -10
  118. package/dist/cli/commands/derive.js.map +1 -1
  119. package/dist/cli/commands/discover.d.ts.map +1 -1
  120. package/dist/cli/commands/discover.js +3 -4
  121. package/dist/cli/commands/discover.js.map +1 -1
  122. package/dist/cli/commands/eval.d.ts.map +1 -1
  123. package/dist/cli/commands/eval.js +17 -17
  124. package/dist/cli/commands/eval.js.map +1 -1
  125. package/dist/cli/commands/evaluate.d.ts +1 -35
  126. package/dist/cli/commands/evaluate.d.ts.map +1 -1
  127. package/dist/cli/commands/evaluate.js +56 -187
  128. package/dist/cli/commands/evaluate.js.map +1 -1
  129. package/dist/cli/commands/explain.d.ts.map +1 -1
  130. package/dist/cli/commands/explain.js +43 -38
  131. package/dist/cli/commands/explain.js.map +1 -1
  132. package/dist/cli/commands/ground.js +1 -1
  133. package/dist/cli/commands/ground.js.map +1 -1
  134. package/dist/cli/commands/map.js +3 -4
  135. package/dist/cli/commands/map.js.map +1 -1
  136. package/dist/cli/commands/metric.d.ts.map +1 -1
  137. package/dist/cli/commands/metric.js +3 -0
  138. package/dist/cli/commands/metric.js.map +1 -1
  139. package/dist/cli/commands/regime.js +1 -1
  140. package/dist/cli/commands/regime.js.map +1 -1
  141. package/dist/cli/commands/symbolic.d.ts.map +1 -1
  142. package/dist/cli/commands/symbolic.js +5 -2
  143. package/dist/cli/commands/symbolic.js.map +1 -1
  144. package/dist/cli/commands/testplan.js +3 -3
  145. package/dist/cli/commands/testplan.js.map +1 -1
  146. package/dist/cli/conventions.d.ts +6 -7
  147. package/dist/cli/conventions.d.ts.map +1 -1
  148. package/dist/cli/conventions.js +19 -28
  149. package/dist/cli/conventions.js.map +1 -1
  150. package/dist/cli/euler-guard.d.ts +13 -2
  151. package/dist/cli/euler-guard.d.ts.map +1 -1
  152. package/dist/cli/euler-guard.js +17 -3
  153. package/dist/cli/euler-guard.js.map +1 -1
  154. package/dist/cli/eval-numbers.d.ts +4 -9
  155. package/dist/cli/eval-numbers.d.ts.map +1 -1
  156. package/dist/cli/eval-numbers.js +11 -41
  157. package/dist/cli/eval-numbers.js.map +1 -1
  158. package/dist/cli/published-url.d.ts +2 -0
  159. package/dist/cli/published-url.d.ts.map +1 -1
  160. package/dist/cli/published-url.js +5 -0
  161. package/dist/cli/published-url.js.map +1 -1
  162. package/dist/cli/record-tables.d.ts +2 -1
  163. package/dist/cli/record-tables.d.ts.map +1 -1
  164. package/dist/cli/record-tables.js +8 -3
  165. package/dist/cli/record-tables.js.map +1 -1
  166. package/dist/cli-api.d.ts +45 -12
  167. package/dist/cli-api.d.ts.map +1 -1
  168. package/dist/cli-api.js +38 -11
  169. package/dist/cli-api.js.map +1 -1
  170. package/dist/composition/adjudication.d.ts +8 -30
  171. package/dist/composition/adjudication.d.ts.map +1 -1
  172. package/dist/composition/adjudication.js +15 -67
  173. package/dist/composition/adjudication.js.map +1 -1
  174. package/dist/composition/aliases.d.ts +1 -1
  175. package/dist/composition/aliases.d.ts.map +1 -1
  176. package/dist/composition/aliases.js +10 -20
  177. package/dist/composition/aliases.js.map +1 -1
  178. package/dist/composition/bridge-analysis.d.ts +4 -0
  179. package/dist/composition/bridge-analysis.d.ts.map +1 -1
  180. package/dist/composition/bridge-analysis.js +13 -14
  181. package/dist/composition/bridge-analysis.js.map +1 -1
  182. package/dist/composition/canonical-graph.d.ts +7 -9
  183. package/dist/composition/canonical-graph.d.ts.map +1 -1
  184. package/dist/composition/canonical-graph.js +35 -19
  185. package/dist/composition/canonical-graph.js.map +1 -1
  186. package/dist/composition/canonical-prefactors.d.ts +10 -29
  187. package/dist/composition/canonical-prefactors.d.ts.map +1 -1
  188. package/dist/composition/canonical-prefactors.js +15 -202
  189. package/dist/composition/canonical-prefactors.js.map +1 -1
  190. package/dist/composition/compose.js +2 -1
  191. package/dist/composition/compose.js.map +1 -1
  192. package/dist/composition/discovery.d.ts +50 -0
  193. package/dist/composition/discovery.d.ts.map +1 -1
  194. package/dist/composition/discovery.js +2 -2
  195. package/dist/composition/discovery.js.map +1 -1
  196. package/dist/composition/edge.d.ts +0 -14
  197. package/dist/composition/edge.d.ts.map +1 -1
  198. package/dist/composition/edge.js +1 -17
  199. package/dist/composition/edge.js.map +1 -1
  200. package/dist/composition/evaluate-relation.d.ts +29 -5
  201. package/dist/composition/evaluate-relation.d.ts.map +1 -1
  202. package/dist/composition/evaluate-relation.js +100 -72
  203. package/dist/composition/evaluate-relation.js.map +1 -1
  204. package/dist/composition/explain.d.ts +5 -0
  205. package/dist/composition/explain.d.ts.map +1 -1
  206. package/dist/composition/explain.js +9 -8
  207. package/dist/composition/explain.js.map +1 -1
  208. package/dist/composition/expr-eval.d.ts.map +1 -1
  209. package/dist/composition/expr-eval.js +2 -1
  210. package/dist/composition/expr-eval.js.map +1 -1
  211. package/dist/composition/index.d.ts +3 -1
  212. package/dist/composition/index.d.ts.map +1 -1
  213. package/dist/composition/index.js +3 -1
  214. package/dist/composition/index.js.map +1 -1
  215. package/dist/composition/proposed-bridges.d.ts.map +1 -1
  216. package/dist/composition/proposed-bridges.js +3 -2
  217. package/dist/composition/proposed-bridges.js.map +1 -1
  218. package/dist/composition/retrodiction.d.ts +8 -0
  219. package/dist/composition/retrodiction.d.ts.map +1 -1
  220. package/dist/composition/retrodiction.js +5 -0
  221. package/dist/composition/retrodiction.js.map +1 -1
  222. package/dist/composition/user-equation.d.ts +11 -7
  223. package/dist/composition/user-equation.d.ts.map +1 -1
  224. package/dist/composition/user-equation.js +24 -26
  225. package/dist/composition/user-equation.js.map +1 -1
  226. package/dist/core/constants.d.ts +27 -4
  227. package/dist/core/constants.d.ts.map +1 -1
  228. package/dist/core/constants.js +38 -5
  229. package/dist/core/constants.js.map +1 -1
  230. package/dist/core/data-file.d.ts +27 -0
  231. package/dist/core/data-file.d.ts.map +1 -0
  232. package/dist/core/data-file.js +48 -0
  233. package/dist/core/data-file.js.map +1 -0
  234. package/dist/core/json-schema.d.ts +27 -0
  235. package/dist/core/json-schema.d.ts.map +1 -0
  236. package/dist/core/json-schema.js +153 -0
  237. package/dist/core/json-schema.js.map +1 -0
  238. package/dist/core/types.d.ts +3 -2
  239. package/dist/core/types.d.ts.map +1 -1
  240. package/dist/core/types.js +16 -14
  241. package/dist/core/types.js.map +1 -1
  242. package/dist/diff/bridge-ast-gradient.d.ts.map +1 -1
  243. package/dist/diff/bridge-ast-gradient.js +6 -17
  244. package/dist/diff/bridge-ast-gradient.js.map +1 -1
  245. package/dist/diff/bridge-specs.d.ts +2 -1
  246. package/dist/diff/bridge-specs.d.ts.map +1 -1
  247. package/dist/diff/bridge-specs.js +1 -1
  248. package/dist/diff/bridge-specs.js.map +1 -1
  249. package/dist/dimensional/bridge-check.d.ts.map +1 -1
  250. package/dist/dimensional/bridge-check.js +3 -5
  251. package/dist/dimensional/bridge-check.js.map +1 -1
  252. package/dist/dimensional/constant-rows.d.ts +54 -0
  253. package/dist/dimensional/constant-rows.d.ts.map +1 -0
  254. package/dist/dimensional/constant-rows.js +111 -0
  255. package/dist/dimensional/constant-rows.js.map +1 -0
  256. package/dist/dimensional/constants.d.ts +4 -13
  257. package/dist/dimensional/constants.d.ts.map +1 -1
  258. package/dist/dimensional/constants.js +11 -19
  259. package/dist/dimensional/constants.js.map +1 -1
  260. package/dist/dimensional/dimension-spec.d.ts +12 -3
  261. package/dist/dimensional/dimension-spec.d.ts.map +1 -1
  262. package/dist/dimensional/dimension-spec.js +114 -150
  263. package/dist/dimensional/dimension-spec.js.map +1 -1
  264. package/dist/dimensional/exact-scale.d.ts +79 -0
  265. package/dist/dimensional/exact-scale.d.ts.map +1 -0
  266. package/dist/dimensional/exact-scale.js +222 -0
  267. package/dist/dimensional/exact-scale.js.map +1 -0
  268. package/dist/dimensional/formula-names.d.ts +13 -12
  269. package/dist/dimensional/formula-names.d.ts.map +1 -1
  270. package/dist/dimensional/formula-names.js +28 -51
  271. package/dist/dimensional/formula-names.js.map +1 -1
  272. package/dist/dimensional/quantity-registry.d.ts +10 -1
  273. package/dist/dimensional/quantity-registry.d.ts.map +1 -1
  274. package/dist/dimensional/quantity-registry.js +45 -16
  275. package/dist/dimensional/quantity-registry.js.map +1 -1
  276. package/dist/dimensional/symbolic-constants.d.ts +63 -19
  277. package/dist/dimensional/symbolic-constants.d.ts.map +1 -1
  278. package/dist/dimensional/symbolic-constants.js +109 -92
  279. package/dist/dimensional/symbolic-constants.js.map +1 -1
  280. package/dist/dimensional/unit-data.d.ts +70 -0
  281. package/dist/dimensional/unit-data.d.ts.map +1 -0
  282. package/dist/dimensional/unit-data.js +135 -0
  283. package/dist/dimensional/unit-data.js.map +1 -0
  284. package/dist/dimensional/units.d.ts +87 -24
  285. package/dist/dimensional/units.d.ts.map +1 -1
  286. package/dist/dimensional/units.js +253 -276
  287. package/dist/dimensional/units.js.map +1 -1
  288. package/dist/index.d.ts +15 -13
  289. package/dist/index.d.ts.map +1 -1
  290. package/dist/index.js +7 -4
  291. package/dist/index.js.map +1 -1
  292. package/dist/numerical/binding-value.d.ts +21 -1
  293. package/dist/numerical/binding-value.d.ts.map +1 -1
  294. package/dist/numerical/binding-value.js +123 -118
  295. package/dist/numerical/binding-value.js.map +1 -1
  296. package/dist/numerical/evaluator-uncertainty.d.ts +34 -0
  297. package/dist/numerical/evaluator-uncertainty.d.ts.map +1 -0
  298. package/dist/numerical/evaluator-uncertainty.js +117 -0
  299. package/dist/numerical/evaluator-uncertainty.js.map +1 -0
  300. package/dist/numerical/formula-contract.d.ts +4 -0
  301. package/dist/numerical/formula-contract.d.ts.map +1 -1
  302. package/dist/numerical/formula-contract.js +7 -0
  303. package/dist/numerical/formula-contract.js.map +1 -1
  304. package/dist/numerical/formula-dimension.d.ts +9 -0
  305. package/dist/numerical/formula-dimension.d.ts.map +1 -1
  306. package/dist/numerical/formula-dimension.js +64 -10
  307. package/dist/numerical/formula-dimension.js.map +1 -1
  308. package/dist/numerical/formula-mathts.d.ts.map +1 -1
  309. package/dist/numerical/formula-mathts.js +6 -2
  310. package/dist/numerical/formula-mathts.js.map +1 -1
  311. package/dist/numerical/gl4-integrator.d.ts.map +1 -1
  312. package/dist/numerical/gl4-integrator.js +3 -3
  313. package/dist/numerical/gl4-integrator.js.map +1 -1
  314. package/dist/numerical/spacetime-metrics.d.ts +4 -0
  315. package/dist/numerical/spacetime-metrics.d.ts.map +1 -1
  316. package/dist/numerical/spacetime-metrics.js +34 -4
  317. package/dist/numerical/spacetime-metrics.js.map +1 -1
  318. package/dist/relations/types.d.ts +8 -1
  319. package/dist/relations/types.d.ts.map +1 -1
  320. package/dist/relations/types.js +10 -0
  321. package/dist/relations/types.js.map +1 -1
  322. package/package.json +1 -1
@@ -30,18 +30,17 @@
30
30
  * splits name each reading and are refused (`mAs` is milliampere·second or
31
31
  * metre·ampere·second). An unknown token that ends in a digit stays unknown
32
32
  * when it is not that product.
33
- * `AU` is the same exact metre count as `au`. `Msun` is `M_SUN_SI` kilograms; `Msun_iau` is
34
- * `GM_SUN_SI / G_SI`. `myr` is a milliyear because `m` is the SI prefix.
35
- * `eV` takes an SI prefix, so `GeV` is 10⁹ eV in joules. `nat` is the
36
- * coherent information unit (scale 1); `bit` is ln 2 nat. Neither takes a
37
- * prefix.
33
+ * The table is data: `data/units.json`, loaded and validated by
34
+ * `unit-data.ts`. Every row, prefix, refused spelling, prefix letter, affine
35
+ * scale and spelling note is a row there, with the reason as its `comment`;
36
+ * this module holds the grammar. `myr` is a milliyear because `m` is the SI
37
+ * prefix, and `eV` takes one, so `GeV` is 10⁹ eV in joules.
38
38
  *
39
39
  * @module dimensional/units
40
40
  */
41
- import { toSiDimensionVector } from '@danielsimonjr/mathts-core';
42
- import { unit } from '@danielsimonjr/mathts-functions';
43
41
  import { equals, format, multiply, power } from './algebra.js';
44
- import { C_SI, E_SI, G_SI, GM_SUN_SI, M_SUN_SI } from '../core/constants.js';
42
+ import { addScales, decimalScale, divideScales, multiplyScales, powerScale, ratioScale, scaleToNumber, solidusSides, UNIT_SCALE, } from './exact-scale.js';
43
+ import { UNIT_DATA } from './unit-data.js';
45
44
  /** Thrown for any input that does not parse as a value with a known unit. @public */
46
45
  export class UnitError extends Error {
47
46
  constructor(message) {
@@ -49,126 +48,93 @@ export class UnitError extends Error {
49
48
  this.name = 'UnitError';
50
49
  }
51
50
  }
51
+ /** A symbol no table row and no prefix spells. @public */
52
+ export class UnknownUnitError extends UnitError {
53
+ symbol;
54
+ constructor(symbol) {
55
+ super(`unknown unit '${symbol}'`);
56
+ this.symbol = symbol;
57
+ this.name = 'UnknownUnitError';
58
+ }
59
+ }
60
+ /**
61
+ * A unit the reader recognizes and refuses: a logarithmic or ratio unit, an
62
+ * ambiguous name such as `ton`, or an affine temperature inside a compound.
63
+ * @public
64
+ */
65
+ export class UnitRefusedError extends UnitError {
66
+ constructor(message) {
67
+ super(message);
68
+ this.name = 'UnitRefusedError';
69
+ }
70
+ }
71
+ /** A unit expression with more than one dimensionally distinct reading. @public */
72
+ export class AmbiguousUnitError extends UnitError {
73
+ text;
74
+ readings;
75
+ constructor(text, readings) {
76
+ super(`'${text}' is ambiguous: ${readings.join(' or ')}`);
77
+ this.text = text;
78
+ this.readings = readings;
79
+ this.name = 'AmbiguousUnitError';
80
+ }
81
+ }
52
82
  const D = (p) => ({ L: 0, M: 0, T: 0, I: 0, Theta: 0, N: 0, J: 0, ...p });
53
- const JOULE = D({ L: 2, M: 1, T: -2 });
54
83
  const DIMENSIONLESS = D({});
55
- /** symbol → [scale to SI base, dimension, takes an SI prefix]. */
56
- const UNITS = new Map([
57
- ['m', [1, D({ L: 1 }), true]],
58
- ['g', [1e-3, D({ M: 1 }), true]],
59
- ['s', [1, D({ T: 1 }), true]],
60
- ['K', [1, D({ Theta: 1 }), true]],
61
- ['A', [1, D({ I: 1 }), true]],
62
- ['mol', [1, D({ N: 1 }), true]],
63
- ['Hz', [1, D({ T: -1 }), true]],
64
- ['N', [1, D({ L: 1, M: 1, T: -2 }), true]],
65
- ['Pa', [1, D({ L: -1, M: 1, T: -2 }), true]],
66
- ['J', [1, JOULE, true]],
67
- ['W', [1, D({ L: 2, M: 1, T: -3 }), true]],
68
- ['C', [1, D({ T: 1, I: 1 }), true]],
69
- ['V', [1, D({ L: 2, M: 1, T: -3, I: -1 }), true]],
70
- ['ohm', [1, D({ L: 2, M: 1, T: -3, I: -2 }), true]],
71
- ['Ω', [1, D({ L: 2, M: 1, T: -3, I: -2 }), true]],
72
- ['S', [1, D({ L: -2, M: -1, T: 3, I: 2 }), true]],
73
- ['F', [1, D({ L: -2, M: -1, T: 4, I: 2 }), true]],
74
- ['eV', [E_SI, JOULE, true]],
75
- // Information. A nat is the coherent dimensionless unit; a bit is ln 2 nat.
76
- // Exact, so neither takes a prefix (`kbit` is not a kilobit).
77
- ['nat', [1, DIMENSIONLESS, false]],
78
- ['bit', [Math.LN2, DIMENSIONLESS, false]],
79
- ['rad', [1, DIMENSIONLESS, true]],
80
- ['deg', [Math.PI / 180, DIMENSIONLESS, false]],
81
- ['min', [60, D({ T: 1 }), false]],
82
- ['h', [3600, D({ T: 1 }), false]],
83
- ['d', [86400, D({ T: 1 }), false]],
84
- // The Julian year, the year the orbital evaluators take.
85
- ['yr', [365.25 * 86400, D({ T: 1 }), true]],
86
- ['au', [149597870700, D({ L: 1 }), false]],
87
- ['AU', [149597870700, D({ L: 1 }), false]],
88
- // The solar mass the evaluators use, not the IAU nominal value.
89
- ['Msun', [M_SUN_SI, D({ M: 1 }), false]],
90
- // GM☉/G, so G × Msun_iau is the IAU solar mass parameter.
91
- ['Msun_iau', [GM_SUN_SI / G_SI, D({ M: 1 }), false]],
92
- // Tesla. An exact `T` still wins over the tera prefix, so `Ts` is a
93
- // terasecond. The prefix flag is what lets `nT` and `uT` parse.
94
- ['T', [1, D({ M: 1, T: -2, I: -1 }), true]],
95
- // Gauss = 10⁻⁴ T. Exact, so `GPa` stays gigapascal (prefix G + Pa) and bare `G` is gauss.
96
- // The spelled name is the same unit. It does not take a prefix (`kgauss` is not a kilogauss).
97
- ['G', [1e-4, D({ M: 1, T: -2, I: -1 }), false]],
98
- ['gauss', [1e-4, D({ M: 1, T: -2, I: -1 }), false]],
99
- ['Gauss', [1e-4, D({ M: 1, T: -2, I: -1 }), false]],
100
- ['bar', [1e5, D({ L: -1, M: 1, T: -2 }), true]],
101
- ['atm', [101325, D({ L: -1, M: 1, T: -2 }), false]],
102
- ['angstrom', [1e-10, D({ L: 1 }), false]],
103
- ['Angstrom', [1e-10, D({ L: 1 }), false]],
104
- ['Å', [1e-10, D({ L: 1 }), false]],
105
- // IAU-style parsec; the prefix applies, so `Mpc` is a megaparsec.
106
- ['pc', [3.0856775814913673e16, D({ L: 1 }), true]],
107
- ['ly', [C_SI * 365.25 * 86400, D({ L: 1 }), false]],
108
- // CGPM 1964: 1 L = 1 dm³ = 10⁻³ m³ exactly. Prefixable, so mL is a millilitre.
109
- // `l` is the same litre. Bare `mL` stays the prefix; `mol/mL` competes with metre·litre.
110
- ['L', [1e-3, D({ L: 3 }), true]],
111
- ['l', [1e-3, D({ L: 3 }), true]],
112
- // Thermochemical calorie: 1 cal_th = 4.184 J exactly (NIST). Not cal_IT = 4.1868 J.
113
- // Prefixable, so kcal is 4184 J.
114
- ['cal', [4.184, JOULE, true]],
115
- // International Steam Table BTU: cal_IT × (lb/g) / (°F per 1.8 °C).
116
- // 4.1868 × 453.59237 / 1.8 J exactly. Not the thermochemical calorie.
117
- ['BTU', [(4.1868 * 453.59237) / 1.8, JOULE, false]],
118
- // psi = lbf/in². lbf = 0.45359237 kg × 9.80665 m/s² (1959 pound × standard gravity).
119
- // inch = 0.0254 m exactly.
120
- ['psi', [(0.45359237 * 9.80665) / (0.0254 * 0.0254), D({ L: -1, M: 1, T: -2 }), false]],
121
- // torr = 1/760 of a standard atmosphere = 101325/760 Pa exactly. Prefixable (mtorr).
122
- ['torr', [101325 / 760, D({ L: -1, M: 1, T: -2 }), true]],
123
- // Conventional millimetre of mercury, 133.322387415 Pa exactly (NIST SP 811).
124
- // Not the torr, and not `mm` × `Hg`.
125
- ['mmHg', [133.322387415, D({ L: -1, M: 1, T: -2 }), false]],
126
- // Poise = 0.1 Pa·s exactly. Prefixable, so cP = 10⁻³ Pa·s. Exact `P` wins over peta.
127
- ['P', [0.1, D({ L: -1, M: 1, T: -1 }), true]],
128
- // Rankine is proportional: K = °R × 5/9. Not affine.
129
- ['degR', [5 / 9, D({ Theta: 1 }), false]],
130
- ['°R', [5 / 9, D({ Theta: 1 }), false]],
131
- // Revolutions per minute = 1/60 Hz. Prefixable, so krpm is a kilorevolution per minute.
132
- ['rpm', [1 / 60, D({ T: -1 }), true]],
133
- // Mechanical horsepower = 550 ft·lbf/s. ft = 0.3048 m.
134
- ['hp', [550 * 0.3048 * 0.45359237 * 9.80665, D({ L: 2, M: 1, T: -3 }), false]],
135
- ]);
136
- const CELSIUS_OFFSET_K = 273.15;
137
- const FAHRENHEIT_SCALE = 5 / 9;
138
- /** Lone spellings. A compound that contains one is refused. */
139
- const AFFINE = [
140
- { id: 'celsius', scale: 1, offset: CELSIUS_OFFSET_K, symbols: ['degC', '°C'] },
141
- {
142
- id: 'fahrenheit',
143
- scale: FAHRENHEIT_SCALE,
144
- offset: CELSIUS_OFFSET_K - 32 * FAHRENHEIT_SCALE,
145
- symbols: ['degF', '°F'],
146
- },
147
- ];
84
+ /** symbol → exact scale to SI base, dimension, prefixability, cycle count. Rows are `data/units.json`. */
85
+ const UNITS = UNIT_DATA.units;
86
+ /** Units that are a ratio or a log scale, and the reason each is not a plain factor. */
87
+ const REFUSED_UNITS = UNIT_DATA.refused;
88
+ /**
89
+ * Letters that are both an SI prefix and a unit of their own. Such a token
90
+ * reads as the prefix reading and not as the unit times the rest: `Gyr` is a
91
+ * gigayear. The file's `prefixLetterUnits.comment` says why `m` is not one.
92
+ */
93
+ const PREFIX_LETTER_UNITS = UNIT_DATA.prefixLetterUnits;
94
+ /** Kelvin at 0 °C, exactly. */
95
+ const CELSIUS_OFFSET_K = UNIT_DATA.iceKelvin;
96
+ /**
97
+ * Lone spellings. A compound that contains one is refused. The schema's `id`
98
+ * enum is {@link AffineTemperature}, held equal by a test: the public type
99
+ * promises callers these ids, so a new affine scale is an API change.
100
+ */
101
+ const AFFINE = UNIT_DATA.affine;
102
+ /** Kelvin at a reading of zero, exactly: 273.15 − icePoint × scale. */
103
+ function affineOffset(row) {
104
+ return addScales(CELSIUS_OFFSET_K, multiplyScales(ratioScale(-1, 1), row.icePoint, row.scale));
105
+ }
106
+ const fractionText = (s) => (s.den === 1n ? String(s.num) : `${s.num}/${s.den}`);
107
+ /**
108
+ * How a reading on an affine scale becomes kelvin, from the row's scale and
109
+ * ice point: `absolute: (degF − 32) × 5/9 + 273.15`, `a difference: × 5/9, no offset`.
110
+ * @internal
111
+ */
112
+ export function affineReadingNote(affine, reading) {
113
+ const row = AFFINE.find((candidate) => candidate.id === affine);
114
+ const unitScale = row.scale.num === row.scale.den;
115
+ if (reading === 'difference')
116
+ return unitScale ? 'a difference: no offset' : `a difference: × ${fractionText(row.scale)}, no offset`;
117
+ const kelvin = String(scaleToNumber(CELSIUS_OFFSET_K));
118
+ if (unitScale && row.icePoint.num === 0n)
119
+ return `absolute: + ${kelvin}`;
120
+ const shifted = row.icePoint.num === 0n ? row.symbols[0] : `(${row.symbols[0]} − ${fractionText(row.icePoint)})`;
121
+ return `absolute: ${unitScale ? shifted : `${shifted} × ${fractionText(row.scale)}`} + ${kelvin}`;
122
+ }
148
123
  function affineRow(text) {
149
- return AFFINE.find((row) => row.symbols.includes(text));
150
- }
151
- const PREFIXES = new Map([
152
- ['Y', 1e24], ['Z', 1e21], ['E', 1e18], ['P', 1e15], ['T', 1e12], ['G', 1e9], ['M', 1e6], ['k', 1e3],
153
- ['h', 1e2], ['da', 1e1], ['d', 1e-1], ['c', 1e-2], ['m', 1e-3], ['u', 1e-6], ['µ', 1e-6], ['μ', 1e-6],
154
- ['n', 1e-9], ['p', 1e-12], ['f', 1e-15], ['a', 1e-18], ['z', 1e-21], ['y', 1e-24],
155
- ]);
156
- function parseSymbol(sym) {
157
- const parsed = trySymbol(sym);
158
- if (parsed !== null)
159
- return parsed;
160
- throw new UnitError(`unknown unit '${sym}'`);
124
+ return AFFINE.find((candidate) => candidate.symbols.includes(text));
161
125
  }
126
+ const PREFIXES = UNIT_DATA.prefixes;
162
127
  function trySymbol(sym) {
163
128
  const exact = UNITS.get(sym);
164
129
  if (exact !== undefined)
165
- return [exact[0], exact[1]];
130
+ return { scale: exact.scale, dim: exact.dim, cycles: exact.cycles === true ? 1 : 0 };
166
131
  for (const [p, f] of PREFIXES) {
167
132
  if (!sym.startsWith(p) || sym.length === p.length)
168
133
  continue;
169
134
  const base = UNITS.get(sym.slice(p.length));
170
- if (base !== undefined && base[2])
171
- return [f * base[0], base[1]];
135
+ if (base !== undefined && base.prefixable) {
136
+ return { scale: multiplyScales(f, base.scale), dim: base.dim, cycles: base.cycles === true ? 1 : 0 };
137
+ }
172
138
  }
173
139
  return null;
174
140
  }
@@ -199,7 +165,7 @@ function tryOneFactor(token) {
199
165
  const parsed = trySymbol(base);
200
166
  if (parsed === null || !Number.isFinite(exp))
201
167
  return null;
202
- return { base, exp, scale: parsed[0], dim: parsed[1] };
168
+ return { base, exp, scale: parsed.scale, dim: parsed.dim, cycles: parsed.cycles };
203
169
  }
204
170
  function formatReading(factors) {
205
171
  return factors.map((factor) => (factor.exp === 1 ? factor.base : `${factor.base}^${factor.exp}`)).join('·');
@@ -212,8 +178,8 @@ function attachedUnit(symbol) {
212
178
  if (!symbol.startsWith(prefix) || symbol.length === prefix.length)
213
179
  continue;
214
180
  const base = symbol.slice(prefix.length);
215
- const row = UNITS.get(base);
216
- if (row !== undefined && row[2])
181
+ const unitRow = UNITS.get(base);
182
+ if (unitRow !== undefined && unitRow.prefixable)
217
183
  return base;
218
184
  }
219
185
  return null;
@@ -254,11 +220,21 @@ function segmentations(token) {
254
220
  * does not also split it. Inside a compound, a prefixed factor also competes
255
221
  * with each heterogeneous product. A homogeneous power does not.
256
222
  */
257
- function tokenWays(token, compete) {
223
+ function tokenWays(token, compete, side) {
224
+ const refused = REFUSED_UNITS.get(token);
225
+ if (refused !== undefined)
226
+ throw new UnitRefusedError(refused);
258
227
  const single = tryOneFactor(token);
259
228
  if (single !== null && !compete)
260
229
  return [[single]];
261
- const products = segmentations(token).filter((way) => way.length > 1);
230
+ // A prefixed numerator token is the prefix reading: `mN/m` is millinewton per metre.
231
+ // A denominator keeps the product readings (`W/mK`), and a target dimension chooses.
232
+ if (single !== null && side === 'numerator')
233
+ return [[single]];
234
+ const prefixWins = single !== null && PREFIX_LETTER_UNITS.has(token[0]) && attachedUnit(single.base) !== null;
235
+ const products = segmentations(token)
236
+ .filter((way) => way.length > 1)
237
+ .filter(() => !prefixWins);
262
238
  const extra = single === null ? products : products.filter((way) => !homogeneousPower(single, way));
263
239
  const ways = [];
264
240
  if (single !== null)
@@ -270,7 +246,7 @@ function tokenWays(token, compete) {
270
246
  unique.set(formatReading(way), way);
271
247
  const list = [...unique.values()];
272
248
  if (list.length === 0)
273
- throw new UnitError(`unknown unit '${token}'`);
249
+ throw new UnknownUnitError(token);
274
250
  return list;
275
251
  }
276
252
  function cartesian(lists) {
@@ -285,7 +261,7 @@ function cartesian(lists) {
285
261
  return acc;
286
262
  }
287
263
  function sideTokens(text) {
288
- return text.split(/[*·\s]+/).filter((part) => part.length > 0);
264
+ return text.split(/[*·\s]+|(?<=[A-Za-zµμΩ])\.(?=[A-Za-zµμΩ])/).filter((part) => part.length > 0);
289
265
  }
290
266
  function formatSide(tokens) {
291
267
  return tokens.map((factors) => formatReading(factors)).join('·');
@@ -298,42 +274,61 @@ function formatExpression(num, den) {
298
274
  return denominator.includes('·') ? `${numerator}/(${denominator})` : `${numerator}/${denominator}`;
299
275
  }
300
276
  function accumulate(tokens, sign) {
301
- let scale = 1;
277
+ let scale = UNIT_SCALE;
302
278
  let dim = DIMENSIONLESS;
279
+ let cycles = 0;
303
280
  for (const factors of tokens) {
304
281
  for (const factor of factors) {
305
282
  const n = sign * factor.exp;
306
- scale *= factor.scale ** n;
283
+ scale = multiplyScales(scale, powerScale(factor.scale, n));
307
284
  dim = multiply(dim, power(factor.dim, n));
285
+ cycles += factor.cycles * n;
308
286
  }
309
287
  }
310
- return { scale, dim };
288
+ return { scale, dim, cycles };
289
+ }
290
+ function reading(exact, dim, label, cycles, affine) {
291
+ return {
292
+ scale: scaleToNumber(exact),
293
+ exact,
294
+ dim,
295
+ label,
296
+ cycles,
297
+ ...(affine === undefined ? {} : { affine }),
298
+ };
311
299
  }
312
- /** Every dimensionally distinct spelling of one unit expression. */
300
+ /**
301
+ * Every dimensionally distinct reading of one unit expression.
302
+ *
303
+ * The unit grammar is not the formula grammar, and the difference is
304
+ * deliberate: a unit symbol is not an identifier (a prefix, a glued product
305
+ * or a glued power lives inside one token: `mN`, `m2K`, `Vs`, `K²`, `°C`,
306
+ * `%`), and everything after the first `/` is the denominator, the
307
+ * single-solidus convention of ISO 80000-1 (`W/m*K` is W·m⁻¹·K⁻¹), where the
308
+ * formula grammar would read `(W/m)·K`.
309
+ * @internal
310
+ */
313
311
  function unitReadings(text) {
314
312
  const t = text.trim();
315
313
  if (t === '' || t === '1')
316
- return [{ scale: 1, dim: DIMENSIONLESS, label: '1' }];
314
+ return [reading(UNIT_SCALE, DIMENSIONLESS, '1', 0)];
317
315
  const affine = affineRow(t);
318
- if (affine !== undefined) {
319
- return [{ scale: affine.scale, dim: D({ Theta: 1 }), affine: affine.id, label: t }];
320
- }
321
- if (AFFINE.some((row) => row.symbols.some((symbol) => t.includes(symbol))) ||
316
+ if (affine !== undefined)
317
+ return [reading(affine.scale, D({ Theta: 1 }), t, 0, affine.id)];
318
+ if (AFFINE.some((candidate) => candidate.symbols.some((symbol) => t.includes(symbol))) ||
322
319
  /deg\s+[CFcf]|°\s*[CFcf]/.test(t)) {
323
- throw new UnitError('an affine temperature cannot be part of a compound unit; give K');
320
+ throw new UnitRefusedError('an affine temperature cannot be part of a compound unit; give K');
324
321
  }
325
- const parts = t.split('/');
326
- if (parts.length > 2)
327
- throw new UnitError(`'${t}' has more than one '/'; put the whole denominator after one '/'`);
322
+ // A chain is one denominator: `km/s/Mpc` is km/(s·Mpc), the way the Hubble constant is written.
323
+ const sides = solidusSides(t);
328
324
  const compete = /[*·/\s]/.test(t);
329
- const numerator = sideTokens(parts[0]);
330
- const denominatorText = parts.length === 1 ? '' : parts[1].replace(/^\((.*)\)$/, '$1');
331
- const denominator = denominatorText === '' ? [] : sideTokens(denominatorText);
325
+ const numerator = sideTokens(sides.numerator);
326
+ const denominator = sides.denominator === '' ? [] : sideTokens(sides.denominator);
332
327
  // `/s` is a dimensionless numerator over seconds. A bare `/` is not a unit.
333
328
  if (numerator.length === 0 && denominator.length === 0)
334
- throw new UnitError(`unknown unit '${t}'`);
335
- const numWays = numerator.map((token) => tokenWays(token, compete));
336
- const denWays = denominator.map((token) => tokenWays(token, compete));
329
+ throw new UnknownUnitError(t);
330
+ const numWays = numerator.map((token) => tokenWays(token, compete, 'numerator'));
331
+ const denWays = denominator.map((token) => tokenWays(token, compete, 'denominator'));
337
332
  const numCombos = numerator.length === 0 ? [[]] : cartesian(numWays);
338
333
  const denCombos = denominator.length === 0 ? [[]] : cartesian(denWays);
339
334
  const unique = new Map();
@@ -342,180 +337,162 @@ function unitReadings(text) {
342
337
  const n = accumulate(num, 1);
343
338
  const d = accumulate(den, -1);
344
339
  const label = formatExpression(num, den);
345
- unique.set(label, { scale: n.scale * d.scale, dim: multiply(n.dim, d.dim), label });
340
+ unique.set(label, reading(multiplyScales(n.scale, d.scale), multiply(n.dim, d.dim), label, n.cycles + d.cycles));
346
341
  }
347
342
  }
348
343
  return [...unique.values()];
349
344
  }
350
- function ambiguousMessage(text, readings) {
351
- return `'${text}' is ambiguous: ${readings.map((reading) => reading.label).join(' or ')}`;
352
- }
353
345
  const SUPERSCRIPT = { '¹': 1, '²': 2, '³': 3 };
354
346
  /**
355
- * Spellings that convert correctly and still mean something else to a reader.
356
- * `myr` is a milliyear, `Msun` is `M_SUN_SI`, bare `G` is the gauss, bare `T`
357
- * is the tesla, and bare `A` is the ampere rather than the angstrom.
347
+ * Spellings that convert correctly and still mean something else to a reader:
348
+ * the `spellingNotes` of `data/units.json` for each symbol in `given`, in file
349
+ * order. `myr` is a milliyear, bare `G` is the gauss, bare `A` the ampere.
358
350
  * @internal
359
351
  */
360
352
  export function unitConventionNotes(given) {
361
- const notes = [];
362
- const symbols = given.split(/[*·/\s^0-9()+-]+/).filter((s) => s.length > 0);
363
- if (symbols.includes('myr')) {
364
- notes.push('myr is a milliyear (the SI prefix m on yr = 0.001 yr), not a million years; a million years is Myr');
365
- }
366
- if (symbols.includes('Msun')) {
367
- notes.push(`Msun is ${M_SUN_SI} kg (M_SUN_SI), not GM☉/G; G×Msun is about 3.0e-4 high versus the IAU GM☉. Use the unit Msun_iau, or the eval name GM_sun, for GM_SUN_SI`);
368
- }
369
- if (symbols.includes('G'))
370
- notes.push('bare G is the gauss (1e-4 T); GPa is still a gigapascal');
371
- if (symbols.includes('T'))
372
- notes.push('bare T is the tesla; Ts is a terasecond');
373
- if (symbols.includes('A'))
374
- notes.push('bare A is the ampere, not the angstrom; write angstrom or Å for 10^-10 m');
375
- if (symbols.includes('P'))
376
- notes.push('bare P is the poise (0.1 Pa·s); PV is still a petavolt');
377
- return notes;
353
+ const symbols = new Set(given.split(/[*·/\s^0-9()+-]+/).filter((s) => s.length > 0));
354
+ return [...UNIT_DATA.spellingNotes].filter(([spelling]) => symbols.has(spelling)).map(([, note]) => note);
378
355
  }
379
- /** Parse a unit expression; the empty string is dimensionless. @public */
380
- export function parseUnit(text) {
356
+ /** The one reading of a unit expression; more than one is refused. @internal */
357
+ export function readUnit(text) {
381
358
  const t = text.trim();
382
359
  const readings = unitReadings(t);
383
360
  if (readings.length !== 1)
384
- throw new UnitError(ambiguousMessage(t, readings));
385
- const reading = readings[0];
386
- return {
387
- scale: reading.scale,
388
- dim: reading.dim,
389
- ...(reading.affine !== undefined ? { affine: reading.affine } : {}),
390
- };
361
+ throw new AmbiguousUnitError(t, readings.map((r) => r.label));
362
+ return readings[0];
391
363
  }
392
- /** Kelvin added when this affine unit is an absolute point. @internal */
393
- export function affineAbsoluteOffsetK(id) {
394
- const row = AFFINE.find((candidate) => candidate.id === id);
395
- if (row === undefined)
396
- throw new UnitError(`unknown affine temperature '${id}'`);
397
- return row.offset;
364
+ /** Parse a unit expression; the empty string is dimensionless. @public */
365
+ export function parseUnit(text) {
366
+ const r = readUnit(text);
367
+ return { scale: r.scale, dim: r.dim, ...(r.affine !== undefined ? { affine: r.affine } : {}) };
398
368
  }
399
- /** Kelvin from an affine or proportional reading. A difference drops the offset. */
400
- function kelvinFrom(value, unit, reading) {
369
+ /** The SI magnitude of `magnitude` in `unit`, exactly. An absolute affine reading adds its offset. */
370
+ function siMagnitude(magnitude, unit, temperature) {
371
+ const scaled = multiplyScales(magnitude, unit.exact);
401
372
  const row = unit.affine === undefined ? undefined : AFFINE.find((candidate) => candidate.id === unit.affine);
402
- const offset = row !== undefined && reading === 'absolute' ? row.offset : 0;
403
- return value * unit.scale + offset;
373
+ return row !== undefined && temperature === 'absolute' ? addScales(scaled, affineOffset(row)) : scaled;
374
+ }
375
+ const FRACTION = /^\s*([+-]?(?:\d+\.?\d*|\.\d+))\s*\/\s*((?:\d+\.?\d*|\.\d+))\s*$/;
376
+ const NUMBER_THEN_REST = /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(.*?)\s*$/;
377
+ /**
378
+ * `raw` split into an exact decimal magnitude and the unit text after it, or
379
+ * null when `raw` is not a number followed by an optional unit. A plain
380
+ * fraction (`1/3`) is a number. Text that begins with an operator (`*h`,
381
+ * `·c`) is an expression, not a unit.
382
+ * @internal
383
+ */
384
+ function splitQuantityLiteral(raw) {
385
+ const fraction = FRACTION.exec(raw);
386
+ if (fraction !== null) {
387
+ const den = decimalScale(fraction[2]);
388
+ if (den.num !== 0n)
389
+ return { magnitude: divideScales(decimalScale(fraction[1]), den), unit: '' };
390
+ }
391
+ const m = NUMBER_THEN_REST.exec(raw);
392
+ if (m === null)
393
+ return null;
394
+ const unit = m[2];
395
+ if (unit.startsWith('*') || unit.startsWith('·'))
396
+ return null;
397
+ const magnitude = decimalScale(m[1]);
398
+ return magnitude === null ? null : { magnitude, unit };
399
+ }
400
+ function finiteLiteral(raw, value) {
401
+ if (!Number.isFinite(value))
402
+ throw new UnitError(`'${raw}' is not a finite number`);
403
+ return value;
404
+ }
405
+ /**
406
+ * Read `raw` as one number with an optional unit, in SI. Null when `raw` is
407
+ * not that form or names a unit no row spells, so the caller can read it as
408
+ * an expression. A refused unit and an ambiguous one throw. The value may be
409
+ * non-finite (`1e999`); the caller owns that refusal and its error class.
410
+ * @internal
411
+ */
412
+ export function readQuantityLiteral(raw, temperature = 'absolute') {
413
+ const literal = splitQuantityLiteral(raw);
414
+ if (literal === null)
415
+ return null;
416
+ if (literal.unit === '') {
417
+ return { value: scaleToNumber(literal.magnitude), dim: DIMENSIONLESS, unit: '', cycles: 0 };
418
+ }
419
+ let unit;
420
+ try {
421
+ unit = readUnit(literal.unit);
422
+ }
423
+ catch (error) {
424
+ if (error instanceof UnknownUnitError)
425
+ return null;
426
+ throw error;
427
+ }
428
+ return {
429
+ value: scaleToNumber(siMagnitude(literal.magnitude, unit, temperature)),
430
+ dim: unit.dim,
431
+ unit: literal.unit,
432
+ cycles: unit.cycles,
433
+ ...(unit.affine === undefined ? {} : { affine: unit.affine }),
434
+ };
404
435
  }
405
436
  /**
406
437
  * Convert `raw` (`<number>[unit]`) into `target` (a unit expression). A bare
407
438
  * number is taken to be in `target` already. The dimensions must agree.
408
439
  *
440
+ * The magnitude, the unit scales and the target scale are multiplied as
441
+ * exact rationals and rounded once, so `1 g/cm^3` in `kg/m^3` is 1000.
442
+ *
409
443
  * @returns the value in `target`, and the unit the user gave (`''` for none).
410
444
  * @public
411
445
  */
412
- export function convertValue(raw, target, reading = 'absolute') {
413
- const m = /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(.*?)\s*$/.exec(raw);
414
- if (m === null)
446
+ export function convertValue(raw, target, temperature = 'absolute') {
447
+ const literal = splitQuantityLiteral(raw);
448
+ if (literal === null)
415
449
  throw new UnitError(`'${raw}' is not a number with an optional unit`);
416
- const v = Number(m[1]);
417
- const given = m[2];
418
- if (!Number.isFinite(v))
419
- throw new UnitError(`'${raw}' is not a finite number`);
420
- if (given === '')
421
- return { value: v, given };
450
+ if (literal.unit === '')
451
+ return { value: finiteLiteral(raw, scaleToNumber(literal.magnitude)), given: '' };
452
+ const given = literal.unit;
422
453
  const readings = unitReadings(given);
423
- const to = parseUnit(target);
454
+ const to = readUnit(target);
424
455
  if (to.affine !== undefined)
425
456
  throw new UnitError(`a declared unit cannot be affine ('${target}')`);
426
457
  const matched = readings.filter((candidate) => equals(candidate.dim, to.dim));
427
458
  if (matched.length !== 1) {
428
459
  if (readings.length > 1)
429
- throw new UnitError(ambiguousMessage(given, readings));
460
+ throw new AmbiguousUnitError(given, readings.map((r) => r.label));
430
461
  const from = readings[0];
431
462
  if (from === undefined)
432
- throw new UnitError(`unknown unit '${given}'`);
463
+ throw new UnknownUnitError(given);
433
464
  throw new UnitError(`'${given}' is ${format(from.dim)}, but this input is ${format(to.dim)} (${target || 'dimensionless'})`);
434
465
  }
435
466
  const from = matched[0];
436
- const local = kelvinFrom(v, from, reading) / to.scale;
437
- // MathTS `unit` + `toSI` is the conversion when it reads the same quantity.
438
- // A temperature difference must not take the absolute offset `toSI` adds.
439
- // `bit` is ln 2 nat here; MathTS reads `bit` as 1. Symbols MathTS does not
440
- // have (a solar mass, a Julian year, the gauss) stay on the table above.
441
- const via = mathTsRatio(v, given, target);
442
- if (via !== undefined && sameQuantity(via, local))
443
- return { value: via, given };
444
- return { value: local, given };
445
- }
446
- /** Spellings MathTS's unit parser accepts for the same UPT symbol. */
447
- function mathTsSpelling(text) {
448
- return text
449
- .replaceAll('µ', 'u')
450
- .replaceAll('μ', 'u')
451
- .replaceAll('Ω', 'ohm')
452
- .replaceAll('Å', 'angstrom')
453
- .replaceAll('°C', 'degC');
454
- }
455
- /** The 7-base record for a MathTS length-10 exponent vector. Angle, bit, and solid angle are dropped. */
456
- function dimensionFromUnitVector(vector) {
457
- const v = toSiDimensionVector(vector, { ignoreExtra: true });
458
- return { L: v[0], M: v[1], T: v[2], I: v[3], Theta: v[4], N: v[5], J: v[6] };
459
- }
460
- function mathTsReading(value, unitText) {
461
- // `unit` is a typed-function; its declared return is `unknown`.
462
- const created = unit(value, mathTsSpelling(unitText));
463
- const n = Number(created.toSI().value);
464
- if (!Number.isFinite(n))
465
- throw new Error('non-finite SI magnitude');
466
- return { si: n, dim: dimensionFromUnitVector(created.dimensions) };
467
- }
468
- function mathTsRatio(value, given, target) {
469
- try {
470
- const from = mathTsReading(value, given);
471
- const to = mathTsReading(1, target);
472
- if (to.si === 0)
473
- return undefined;
474
- // A scale match is not enough when the 7-base dimensions differ.
475
- if (!equals(from.dim, parseUnit(given).dim))
476
- return undefined;
477
- if (!equals(to.dim, parseUnit(target).dim))
478
- return undefined;
479
- return from.si / to.si;
480
- }
481
- catch {
482
- return undefined;
483
- }
484
- }
485
- /**
486
- * MathTS's SI value for `magnitude` of `unitText`, when that value and the
487
- * 7-base dimension agree with `local`. Affine °C stays on the local offset.
488
- * A symbol MathTS lacks, or a scale it disagrees with (`bit` is 1 there and
489
- * ln 2 here; a solar mass, a Julian year, and the gauss are absent), returns
490
- * undefined so the caller keeps the local table.
491
- *
492
- * @internal
493
- */
494
- export function mathTsAgreedQuantity(magnitude, unitText, local) {
495
- if (local.affine !== undefined)
496
- return undefined;
497
- try {
498
- const read = mathTsReading(magnitude, unitText);
499
- if (!sameQuantity(read.si, magnitude * local.scale))
500
- return undefined;
501
- if (!equals(read.dim, local.dim))
502
- return undefined;
503
- return { value: read.si, dim: read.dim };
504
- }
505
- catch {
506
- return undefined;
507
- }
508
- }
509
- function sameQuantity(got, expected) {
510
- const scale = Math.max(Math.abs(got), Math.abs(expected));
511
- return Math.abs(got - expected) <= 1e-9 * scale;
467
+ const value = scaleToNumber(divideScales(siMagnitude(literal.magnitude, from, temperature), to.exact));
468
+ return {
469
+ value: finiteLiteral(raw, value),
470
+ given,
471
+ ...(from.cycles === 0 ? {} : { cycles: from.cycles }),
472
+ ...(from.affine === undefined ? {} : { affine: from.affine }),
473
+ };
512
474
  }
513
475
  /** The dimension of a declared unit expression. @internal */
514
476
  export function unitDimension(unit) {
515
477
  return parseUnit(unit).dim;
516
478
  }
517
- /** The tables conversion reads, for the CLI record's fingerprint (`src/cli/record-tables.ts`). @internal */
479
+ /** The symbols the unit table spells, with their exact scales, for the agreement test against MathTS. @internal */
480
+ export function unitRows() {
481
+ return UNITS;
482
+ }
483
+ /**
484
+ * The tables conversion reads, for the CLI record's fingerprint (`src/cli/record-tables.ts`):
485
+ * each row's scale, dimension, prefixability and `cycles` flag (which multiplies an angular
486
+ * input by 2π), the prefixes, the Celsius offset, and the SHA-256 of `data/units.json`, so a
487
+ * refused spelling, a prefix letter, an affine row or a note that changes changes the fingerprint too.
488
+ * @internal
489
+ */
518
490
  export function unitTables() {
519
- return { units: UNITS, prefixes: PREFIXES, celsiusOffsetK: CELSIUS_OFFSET_K };
491
+ return {
492
+ units: new Map([...UNITS].map(([symbol, r]) => [symbol, [scaleToNumber(r.scale), r.dim, r.prefixable, r.cycles === true]])),
493
+ prefixes: new Map([...PREFIXES].map(([prefix, f]) => [prefix, scaleToNumber(f)])),
494
+ celsiusOffsetK: scaleToNumber(CELSIUS_OFFSET_K),
495
+ sourceSha256: UNIT_DATA.sha256,
496
+ };
520
497
  }
521
498
  //# sourceMappingURL=units.js.map