@classytic/stage 0.3.0 → 0.4.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 (431) hide show
  1. package/dist/_virtual/_rolldown/runtime.mjs +18 -0
  2. package/dist/atomic/aufbau.d.mts +144 -0
  3. package/dist/atomic/aufbau.mjs +384 -0
  4. package/dist/atomic/elements.d.mts +67 -0
  5. package/dist/atomic/elements.mjs +785 -0
  6. package/dist/atomic/emission.d.mts +112 -0
  7. package/dist/atomic/emission.mjs +179 -0
  8. package/dist/atomic/index.d.mts +8 -0
  9. package/dist/atomic/index.mjs +9 -0
  10. package/dist/atomic/periodic.d.mts +82 -0
  11. package/dist/atomic/periodic.mjs +231 -0
  12. package/dist/atomic/properties.d.mts +100 -0
  13. package/dist/atomic/properties.mjs +574 -0
  14. package/dist/atomic/shells.d.mts +137 -0
  15. package/dist/atomic/shells.mjs +170 -0
  16. package/dist/atomic/trends.d.mts +148 -0
  17. package/dist/atomic/trends.mjs +225 -0
  18. package/dist/chem/chromatography.d.mts +66 -0
  19. package/dist/chem/chromatography.mjs +63 -0
  20. package/dist/chem/complex.d.mts +114 -0
  21. package/dist/chem/complex.mjs +234 -0
  22. package/dist/chem/cycle.d.mts +75 -0
  23. package/dist/chem/cycle.mjs +145 -0
  24. package/dist/chem/equation.d.mts +86 -0
  25. package/dist/chem/equation.mjs +168 -0
  26. package/dist/chem/formula.d.mts +90 -0
  27. package/dist/chem/formula.mjs +201 -0
  28. package/dist/chem/functional-groups.d.mts +31 -0
  29. package/dist/chem/functional-groups.mjs +187 -0
  30. package/dist/chem/hazard.d.mts +94 -0
  31. package/dist/chem/hazard.mjs +329 -0
  32. package/dist/chem/index.d.mts +34 -33
  33. package/dist/chem/index.mjs +34 -28
  34. package/dist/chem/ionic.d.mts +98 -0
  35. package/dist/chem/ionic.mjs +156 -0
  36. package/dist/chem/layout.d.mts +46 -0
  37. package/dist/chem/layout.mjs +342 -0
  38. package/dist/chem/lewis.d.mts +79 -0
  39. package/dist/chem/lewis.mjs +163 -0
  40. package/dist/chem/molecular-orbital.d.mts +114 -0
  41. package/dist/chem/molecular-orbital.mjs +201 -0
  42. package/dist/chem/molecule.d.mts +154 -0
  43. package/dist/chem/molecule.mjs +282 -0
  44. package/dist/chem/nmr.d.mts +153 -0
  45. package/dist/chem/nmr.mjs +165 -0
  46. package/dist/chem/polymer.d.mts +65 -0
  47. package/dist/chem/polymer.mjs +179 -0
  48. package/dist/chem/redox.d.mts +138 -0
  49. package/dist/chem/redox.mjs +328 -0
  50. package/dist/chem/relations.d.mts +183 -0
  51. package/dist/chem/relations.mjs +299 -0
  52. package/dist/chem/skeleton.d.mts +51 -0
  53. package/dist/chem/skeleton.mjs +186 -0
  54. package/dist/chem/solution.d.mts +116 -0
  55. package/dist/chem/solution.mjs +186 -0
  56. package/dist/chem/species.d.mts +47 -0
  57. package/dist/chem/species.mjs +128 -0
  58. package/dist/chem/stereo.d.mts +108 -0
  59. package/dist/chem/stereo.mjs +286 -0
  60. package/dist/chem/stoichiometry.d.mts +95 -0
  61. package/dist/chem/stoichiometry.mjs +145 -0
  62. package/dist/chem/synthesis.d.mts +113 -0
  63. package/dist/chem/synthesis.mjs +281 -0
  64. package/dist/chem/vsepr.d.mts +80 -0
  65. package/dist/chem/vsepr.mjs +247 -0
  66. package/dist/circuit/ac.d.mts +163 -0
  67. package/dist/circuit/ac.mjs +452 -0
  68. package/dist/circuit/dc.d.mts +92 -0
  69. package/dist/circuit/dc.mjs +333 -0
  70. package/dist/circuit/index.d.mts +5 -92
  71. package/dist/circuit/index.mjs +5 -332
  72. package/dist/circuit/network.d.mts +99 -0
  73. package/dist/circuit/network.mjs +440 -0
  74. package/dist/circuit/topology.d.mts +102 -0
  75. package/dist/circuit/topology.mjs +143 -0
  76. package/dist/colour/absorption.d.mts +79 -0
  77. package/dist/colour/absorption.mjs +137 -0
  78. package/dist/colour/index.d.mts +6 -0
  79. package/dist/colour/index.mjs +7 -0
  80. package/dist/colour/name.d.mts +14 -0
  81. package/dist/colour/name.mjs +76 -0
  82. package/dist/colour/observer.d.mts +31 -0
  83. package/dist/colour/observer.mjs +46 -0
  84. package/dist/colour/spectrum.d.mts +61 -0
  85. package/dist/colour/spectrum.mjs +108 -0
  86. package/dist/colour/srgb.d.mts +49 -0
  87. package/dist/colour/srgb.mjs +108 -0
  88. package/dist/core/constants.d.mts +138 -0
  89. package/dist/core/constants.mjs +139 -0
  90. package/dist/core/index.d.mts +3 -1
  91. package/dist/core/index.mjs +3 -1
  92. package/dist/core/vec3.d.mts +94 -0
  93. package/dist/core/vec3.mjs +123 -0
  94. package/dist/core/worked.d.mts +35 -0
  95. package/dist/core/worked.mjs +44 -0
  96. package/dist/electro/cell.d.mts +102 -0
  97. package/dist/electro/cell.mjs +170 -0
  98. package/dist/electro/conductivity.d.mts +157 -0
  99. package/dist/electro/conductivity.mjs +156 -0
  100. package/dist/electro/couples.d.mts +114 -0
  101. package/dist/electro/couples.mjs +334 -0
  102. package/dist/electro/electrolysis.d.mts +96 -0
  103. package/dist/electro/electrolysis.mjs +274 -0
  104. package/dist/electro/faraday.d.mts +150 -0
  105. package/dist/electro/faraday.mjs +118 -0
  106. package/dist/electro/index.d.mts +6 -0
  107. package/dist/electro/index.mjs +7 -0
  108. package/dist/finance/accounting/adjustments.d.mts +17 -0
  109. package/dist/finance/accounting/adjustments.mjs +202 -0
  110. package/dist/finance/accounting/analysis.d.mts +8 -0
  111. package/dist/finance/accounting/analysis.mjs +205 -0
  112. package/dist/finance/accounting/clubs.d.mts +21 -0
  113. package/dist/finance/accounting/clubs.mjs +217 -0
  114. package/dist/finance/accounting/company-statements.d.mts +7 -0
  115. package/dist/finance/accounting/company-statements.mjs +150 -0
  116. package/dist/finance/accounting/harness.d.mts +7 -0
  117. package/dist/finance/accounting/harness.mjs +45 -0
  118. package/dist/finance/accounting/index.d.mts +18 -0
  119. package/dist/finance/accounting/index.mjs +18 -0
  120. package/dist/finance/accounting/journalize.d.mts +16 -0
  121. package/dist/finance/accounting/journalize.mjs +128 -0
  122. package/dist/finance/accounting/manufacturing.d.mts +8 -0
  123. package/dist/finance/accounting/manufacturing.mjs +236 -0
  124. package/dist/finance/accounting/partnership.d.mts +7 -0
  125. package/dist/finance/accounting/partnership.mjs +152 -0
  126. package/dist/finance/accounting/period-case.d.mts +6 -0
  127. package/dist/finance/accounting/period-case.mjs +71 -0
  128. package/dist/finance/accounting/post.d.mts +9 -0
  129. package/dist/finance/accounting/post.mjs +84 -0
  130. package/dist/finance/accounting/precision.d.mts +9 -0
  131. package/dist/finance/accounting/precision.mjs +15 -0
  132. package/dist/finance/accounting/rules.d.mts +21 -0
  133. package/dist/finance/accounting/rules.mjs +37 -0
  134. package/dist/finance/accounting/solve.d.mts +28 -0
  135. package/dist/finance/accounting/solve.mjs +92 -0
  136. package/dist/finance/accounting/statements.d.mts +7 -0
  137. package/dist/finance/accounting/statements.mjs +142 -0
  138. package/dist/finance/accounting/types.d.mts +649 -0
  139. package/dist/finance/accounting/validate.d.mts +7 -0
  140. package/dist/finance/accounting/validate.mjs +67 -0
  141. package/dist/finance/accounting/verification.d.mts +24 -0
  142. package/dist/finance/accounting/verification.mjs +386 -0
  143. package/dist/finance/bizsim.d.mts +21 -39
  144. package/dist/finance/bizsim.mjs +276 -97
  145. package/dist/finance/index.d.mts +20 -43
  146. package/dist/finance/index.mjs +19 -77
  147. package/dist/finance/investment-math.d.mts +7 -0
  148. package/dist/finance/investment-math.mjs +18 -0
  149. package/dist/finance/management-accounting/budgeting.d.mts +7 -0
  150. package/dist/finance/management-accounting/budgeting.mjs +189 -0
  151. package/dist/finance/management-accounting/cash-budget.d.mts +7 -0
  152. package/dist/finance/management-accounting/cash-budget.mjs +154 -0
  153. package/dist/finance/management-accounting/costing.d.mts +8 -0
  154. package/dist/finance/management-accounting/costing.mjs +175 -0
  155. package/dist/finance/management-accounting/decisions.d.mts +8 -0
  156. package/dist/finance/management-accounting/decisions.mjs +164 -0
  157. package/dist/finance/management-accounting/index.d.mts +13 -0
  158. package/dist/finance/management-accounting/index.mjs +13 -0
  159. package/dist/finance/management-accounting/integrated-budget.d.mts +8 -0
  160. package/dist/finance/management-accounting/integrated-budget.mjs +67 -0
  161. package/dist/finance/management-accounting/inventory-costing.d.mts +7 -0
  162. package/dist/finance/management-accounting/inventory-costing.mjs +173 -0
  163. package/dist/finance/management-accounting/investment-appraisal.d.mts +7 -0
  164. package/dist/finance/management-accounting/investment-appraisal.mjs +148 -0
  165. package/dist/finance/management-accounting/management-case.d.mts +8 -0
  166. package/dist/finance/management-accounting/management-case.mjs +91 -0
  167. package/dist/finance/management-accounting/master-budget.d.mts +7 -0
  168. package/dist/finance/management-accounting/master-budget.mjs +137 -0
  169. package/dist/finance/management-accounting/production-costing.d.mts +8 -0
  170. package/dist/finance/management-accounting/production-costing.mjs +214 -0
  171. package/dist/finance/management-accounting/types.d.mts +644 -0
  172. package/dist/finance/management-accounting/variances.d.mts +7 -0
  173. package/dist/finance/management-accounting/variances.mjs +150 -0
  174. package/dist/index.d.mts +6 -6
  175. package/dist/index.mjs +1 -1
  176. package/dist/math/analysis.d.mts +61 -0
  177. package/dist/math/analysis.mjs +187 -0
  178. package/dist/math/answer-check.d.mts +50 -0
  179. package/dist/math/answer-check.mjs +106 -0
  180. package/dist/math/calculus.d.mts +4 -9
  181. package/dist/math/calculus.mjs +216 -7
  182. package/dist/math/complex.d.mts +93 -0
  183. package/dist/math/complex.mjs +239 -0
  184. package/dist/math/expr.d.mts +23 -0
  185. package/dist/math/expr.mjs +36 -0
  186. package/dist/math/index.d.mts +8 -22
  187. package/dist/math/index.mjs +8 -37
  188. package/dist/math/latex.mjs +2 -1
  189. package/dist/math/parse.mjs +1 -1
  190. package/dist/math/poly.d.mts +55 -0
  191. package/dist/math/poly.mjs +273 -0
  192. package/dist/math/rearrange.d.mts +14 -0
  193. package/dist/math/rearrange.mjs +125 -0
  194. package/dist/math/solve.d.mts +21 -0
  195. package/dist/math/solve.mjs +75 -0
  196. package/dist/networking/addressing.d.mts +26 -0
  197. package/dist/networking/addressing.mjs +33 -0
  198. package/dist/networking/cloud/access.d.mts +36 -0
  199. package/dist/networking/cloud/access.mjs +95 -0
  200. package/dist/networking/cloud/containers.d.mts +43 -0
  201. package/dist/networking/cloud/containers.mjs +54 -0
  202. package/dist/networking/cloud/edge.d.mts +17 -0
  203. package/dist/networking/cloud/edge.mjs +31 -0
  204. package/dist/networking/cloud/failure-domains.d.mts +19 -0
  205. package/dist/networking/cloud/failure-domains.mjs +22 -0
  206. package/dist/networking/cloud/geo-routing.d.mts +22 -0
  207. package/dist/networking/cloud/geo-routing.mjs +64 -0
  208. package/dist/networking/cloud/index.d.mts +18 -0
  209. package/dist/networking/cloud/index.mjs +19 -0
  210. package/dist/networking/cloud/inference-reliability.d.mts +30 -0
  211. package/dist/networking/cloud/inference-reliability.mjs +76 -0
  212. package/dist/networking/cloud/inference.d.mts +118 -0
  213. package/dist/networking/cloud/inference.mjs +179 -0
  214. package/dist/networking/cloud/infrastructure.d.mts +45 -0
  215. package/dist/networking/cloud/infrastructure.mjs +96 -0
  216. package/dist/networking/cloud/observability.d.mts +24 -0
  217. package/dist/networking/cloud/observability.mjs +34 -0
  218. package/dist/networking/cloud/orchestration.d.mts +32 -0
  219. package/dist/networking/cloud/orchestration.mjs +38 -0
  220. package/dist/networking/cloud/recovery.d.mts +21 -0
  221. package/dist/networking/cloud/recovery.mjs +27 -0
  222. package/dist/networking/cloud/releases.d.mts +42 -0
  223. package/dist/networking/cloud/releases.mjs +135 -0
  224. package/dist/networking/cloud/scaling.d.mts +25 -0
  225. package/dist/networking/cloud/scaling.mjs +65 -0
  226. package/dist/networking/cloud/secrets.d.mts +39 -0
  227. package/dist/networking/cloud/secrets.mjs +68 -0
  228. package/dist/networking/cloud/server-deployment.d.mts +29 -0
  229. package/dist/networking/cloud/server-deployment.mjs +72 -0
  230. package/dist/networking/cloud/storage.d.mts +16 -0
  231. package/dist/networking/cloud/storage.mjs +58 -0
  232. package/dist/networking/cloud/vpc.d.mts +18 -0
  233. package/dist/networking/cloud/vpc.mjs +100 -0
  234. package/dist/networking/delivery.d.mts +34 -0
  235. package/dist/networking/delivery.mjs +52 -0
  236. package/dist/networking/index.d.mts +47 -0
  237. package/dist/networking/index.mjs +47 -0
  238. package/dist/networking/ipv6.d.mts +28 -0
  239. package/dist/networking/ipv6.mjs +17 -0
  240. package/dist/networking/journeys.d.mts +83 -0
  241. package/dist/networking/journeys.mjs +161 -0
  242. package/dist/networking/models/airtime.d.mts +87 -0
  243. package/dist/networking/models/airtime.mjs +130 -0
  244. package/dist/networking/models/cellular.d.mts +157 -0
  245. package/dist/networking/models/cellular.mjs +252 -0
  246. package/dist/networking/models/encapsulation.d.mts +98 -0
  247. package/dist/networking/models/encapsulation.mjs +132 -0
  248. package/dist/networking/models/handover.d.mts +116 -0
  249. package/dist/networking/models/handover.mjs +209 -0
  250. package/dist/networking/models/handshake.d.mts +90 -0
  251. package/dist/networking/models/handshake.mjs +122 -0
  252. package/dist/networking/models/index.d.mts +14 -0
  253. package/dist/networking/models/index.mjs +15 -0
  254. package/dist/networking/models/media.d.mts +82 -0
  255. package/dist/networking/models/media.mjs +188 -0
  256. package/dist/networking/models/satellite.d.mts +86 -0
  257. package/dist/networking/models/satellite.mjs +121 -0
  258. package/dist/networking/models/spectrum.d.mts +83 -0
  259. package/dist/networking/models/spectrum.mjs +235 -0
  260. package/dist/networking/models/switch.d.mts +80 -0
  261. package/dist/networking/models/switch.mjs +155 -0
  262. package/dist/networking/models/vlan.d.mts +62 -0
  263. package/dist/networking/models/vlan.mjs +100 -0
  264. package/dist/networking/models/vpn.d.mts +66 -0
  265. package/dist/networking/models/vpn.mjs +125 -0
  266. package/dist/networking/models/wifi-privacy.d.mts +82 -0
  267. package/dist/networking/models/wifi-privacy.mjs +130 -0
  268. package/dist/networking/models/wifi.d.mts +240 -0
  269. package/dist/networking/models/wifi.mjs +562 -0
  270. package/dist/networking/nat-traversal.d.mts +24 -0
  271. package/dist/networking/nat-traversal.mjs +38 -0
  272. package/dist/networking/protocols.d.mts +17 -0
  273. package/dist/networking/protocols.mjs +57 -0
  274. package/dist/networking/queue.d.mts +12 -0
  275. package/dist/networking/queue.mjs +18 -0
  276. package/dist/networking/route-learning.d.mts +23 -0
  277. package/dist/networking/route-learning.mjs +43 -0
  278. package/dist/networking/routing.d.mts +11 -0
  279. package/dist/networking/routing.mjs +25 -0
  280. package/dist/networking/signals.d.mts +26 -0
  281. package/dist/networking/signals.mjs +35 -0
  282. package/dist/networking/subnet.d.mts +49 -0
  283. package/dist/networking/subnet.mjs +89 -0
  284. package/dist/networking/tcp-reliability.d.mts +24 -0
  285. package/dist/networking/tcp-reliability.mjs +38 -0
  286. package/dist/networking/topology/contract.d.mts +123 -0
  287. package/dist/networking/topology/index.d.mts +4 -0
  288. package/dist/networking/topology/index.mjs +4 -0
  289. package/dist/networking/topology/presets.d.mts +7 -0
  290. package/dist/networking/topology/presets.mjs +302 -0
  291. package/dist/networking/topology/simulation.d.mts +8 -0
  292. package/dist/networking/topology/simulation.mjs +179 -0
  293. package/dist/networking/wireless.d.mts +11 -0
  294. package/dist/networking/wireless.mjs +17 -0
  295. package/dist/optics/imaging.d.mts +218 -0
  296. package/dist/optics/imaging.mjs +276 -0
  297. package/dist/optics/index.d.mts +7 -0
  298. package/dist/optics/index.mjs +8 -0
  299. package/dist/optics/laws.d.mts +70 -0
  300. package/dist/optics/laws.mjs +195 -0
  301. package/dist/optics/prism.d.mts +148 -0
  302. package/dist/optics/prism.mjs +197 -0
  303. package/dist/optics/refraction.d.mts +201 -0
  304. package/dist/optics/refraction.mjs +270 -0
  305. package/dist/optics/wave-optics.d.mts +171 -0
  306. package/dist/optics/wave-optics.mjs +259 -0
  307. package/dist/physics/ac/index.d.mts +183 -0
  308. package/dist/physics/ac/index.mjs +203 -0
  309. package/dist/physics/dynamics/forces.d.mts +98 -0
  310. package/dist/physics/dynamics/forces.mjs +153 -0
  311. package/dist/physics/dynamics/friction.d.mts +76 -0
  312. package/dist/physics/dynamics/friction.mjs +119 -0
  313. package/dist/physics/dynamics/system.d.mts +68 -0
  314. package/dist/physics/dynamics/system.mjs +125 -0
  315. package/dist/physics/electrostatics/index.d.mts +169 -0
  316. package/dist/physics/electrostatics/index.mjs +249 -0
  317. package/dist/physics/energy/centre-of-mass.d.mts +115 -0
  318. package/dist/physics/energy/centre-of-mass.mjs +180 -0
  319. package/dist/physics/energy/conservation.d.mts +81 -0
  320. package/dist/physics/energy/conservation.mjs +129 -0
  321. package/dist/physics/energy/power.d.mts +90 -0
  322. package/dist/physics/energy/power.mjs +167 -0
  323. package/dist/physics/energy/spring.d.mts +73 -0
  324. package/dist/physics/energy/spring.mjs +129 -0
  325. package/dist/physics/energy/store.d.mts +63 -0
  326. package/dist/physics/energy/store.mjs +108 -0
  327. package/dist/physics/energy/theorem.d.mts +65 -0
  328. package/dist/physics/energy/theorem.mjs +113 -0
  329. package/dist/physics/energy/work.d.mts +114 -0
  330. package/dist/physics/energy/work.mjs +219 -0
  331. package/dist/physics/gravitation/body.d.mts +85 -0
  332. package/dist/physics/gravitation/body.mjs +168 -0
  333. package/dist/physics/gravitation/kepler.d.mts +127 -0
  334. package/dist/physics/gravitation/kepler.mjs +216 -0
  335. package/dist/physics/gravitation/law.d.mts +117 -0
  336. package/dist/physics/gravitation/law.mjs +175 -0
  337. package/dist/physics/gravitation/orbit.d.mts +119 -0
  338. package/dist/physics/gravitation/orbit.mjs +202 -0
  339. package/dist/physics/gravitation/potential.d.mts +114 -0
  340. package/dist/physics/gravitation/potential.mjs +202 -0
  341. package/dist/physics/gravitation/variation.d.mts +97 -0
  342. package/dist/physics/gravitation/variation.mjs +175 -0
  343. package/dist/physics/index.d.mts +35 -0
  344. package/dist/physics/index.mjs +36 -0
  345. package/dist/physics/integral-sweep.d.mts +63 -0
  346. package/dist/physics/integral-sweep.mjs +126 -0
  347. package/dist/physics/kinematics/equations.d.mts +47 -0
  348. package/dist/physics/kinematics/equations.mjs +126 -0
  349. package/dist/physics/kinematics/free-fall.d.mts +53 -0
  350. package/dist/physics/kinematics/free-fall.mjs +71 -0
  351. package/dist/physics/kinematics/given.d.mts +16 -0
  352. package/dist/physics/kinematics/given.mjs +55 -0
  353. package/dist/physics/kinematics/roots.d.mts +41 -0
  354. package/dist/physics/kinematics/roots.mjs +72 -0
  355. package/dist/physics/kinematics/solve.d.mts +52 -0
  356. package/dist/physics/kinematics/solve.mjs +172 -0
  357. package/dist/physics/magnetism/biot-savart.d.mts +161 -0
  358. package/dist/physics/magnetism/biot-savart.mjs +353 -0
  359. package/dist/physics/magnetism/flux.d.mts +94 -0
  360. package/dist/physics/magnetism/flux.mjs +313 -0
  361. package/dist/physics/magnetism/geomagnetism.d.mts +151 -0
  362. package/dist/physics/magnetism/geomagnetism.mjs +184 -0
  363. package/dist/physics/magnetism/index.d.mts +12 -0
  364. package/dist/physics/magnetism/index.mjs +74 -0
  365. package/dist/physics/magnetism/lorentz.d.mts +28 -0
  366. package/dist/physics/magnetism/lorentz.mjs +88 -0
  367. package/dist/physics/magnetism/magnetic-force.d.mts +102 -0
  368. package/dist/physics/magnetism/magnetic-force.mjs +169 -0
  369. package/dist/physics/momentum/collide.d.mts +77 -0
  370. package/dist/physics/momentum/collide.mjs +115 -0
  371. package/dist/physics/momentum/impulse.d.mts +89 -0
  372. package/dist/physics/momentum/impulse.mjs +159 -0
  373. package/dist/physics/momentum/planar.d.mts +60 -0
  374. package/dist/physics/momentum/planar.mjs +110 -0
  375. package/dist/physics/projectile.d.mts +107 -0
  376. package/dist/physics/projectile.mjs +185 -0
  377. package/dist/physics/rotation/angular.d.mts +85 -0
  378. package/dist/physics/rotation/angular.mjs +130 -0
  379. package/dist/physics/rotation/circular.d.mts +79 -0
  380. package/dist/physics/rotation/circular.mjs +131 -0
  381. package/dist/physics/rotation/inertia.d.mts +164 -0
  382. package/dist/physics/rotation/inertia.mjs +332 -0
  383. package/dist/physics/rotation/torque.d.mts +65 -0
  384. package/dist/physics/rotation/torque.mjs +134 -0
  385. package/dist/physics/vectors.d.mts +64 -0
  386. package/dist/physics/vectors.mjs +114 -0
  387. package/dist/quantity/index.d.mts +119 -0
  388. package/dist/quantity/index.mjs +417 -0
  389. package/dist/relation/derive.d.mts +74 -0
  390. package/dist/relation/derive.mjs +73 -0
  391. package/dist/relation/index.d.mts +56 -0
  392. package/dist/relation/index.mjs +234 -0
  393. package/dist/scene/evaluators.mjs +1 -1
  394. package/dist/sim/thermal.mjs +45 -20
  395. package/dist/thermo/calorimetry.d.mts +207 -0
  396. package/dist/thermo/calorimetry.mjs +345 -0
  397. package/dist/thermo/cycles.d.mts +142 -0
  398. package/dist/thermo/cycles.mjs +256 -0
  399. package/dist/thermo/entropy.d.mts +87 -0
  400. package/dist/thermo/entropy.mjs +140 -0
  401. package/dist/thermo/expansion.d.mts +130 -0
  402. package/dist/thermo/expansion.mjs +178 -0
  403. package/dist/thermo/gas.d.mts +197 -0
  404. package/dist/thermo/gas.mjs +279 -0
  405. package/dist/thermo/heat-transfer.d.mts +164 -0
  406. package/dist/thermo/heat-transfer.mjs +217 -0
  407. package/dist/thermo/index.d.mts +9 -82
  408. package/dist/thermo/index.mjs +9 -132
  409. package/dist/thermo/processes.d.mts +95 -0
  410. package/dist/thermo/processes.mjs +154 -0
  411. package/dist/thermo/temperature.d.mts +174 -0
  412. package/dist/thermo/temperature.mjs +236 -0
  413. package/dist/waves/damping.d.mts +224 -0
  414. package/dist/waves/damping.mjs +317 -0
  415. package/dist/waves/doppler.d.mts +72 -0
  416. package/dist/waves/doppler.mjs +125 -0
  417. package/dist/waves/index.d.mts +10 -0
  418. package/dist/waves/index.mjs +10 -0
  419. package/dist/waves/pendulum.d.mts +130 -0
  420. package/dist/waves/pendulum.mjs +211 -0
  421. package/dist/waves/pitch.d.mts +125 -0
  422. package/dist/waves/pitch.mjs +158 -0
  423. package/dist/waves/shm.d.mts +170 -0
  424. package/dist/waves/shm.mjs +279 -0
  425. package/dist/waves/springs.d.mts +103 -0
  426. package/dist/waves/springs.mjs +176 -0
  427. package/dist/waves/superposition.d.mts +124 -0
  428. package/dist/waves/superposition.mjs +196 -0
  429. package/dist/waves/wave.d.mts +154 -0
  430. package/dist/waves/wave.mjs +178 -0
  431. package/package.json +74 -2
@@ -0,0 +1,282 @@
1
+ //#region src/chem/molecule.ts
2
+ /** Bonds a valence shell can make, before any is used. */
3
+ const VALENCE = {
4
+ C: 4,
5
+ N: 3,
6
+ O: 2,
7
+ S: 2,
8
+ P: 3,
9
+ F: 1,
10
+ Cl: 1,
11
+ Br: 1,
12
+ I: 1,
13
+ H: 1
14
+ };
15
+ /** Relative atomic masses, to the precision a data booklet prints. */
16
+ const ATOMIC_MASS = {
17
+ H: 1,
18
+ C: 12,
19
+ N: 14,
20
+ O: 16,
21
+ F: 19,
22
+ P: 31,
23
+ S: 32.1,
24
+ Cl: 35.5,
25
+ Br: 79.9,
26
+ I: 126.9
27
+ };
28
+ const TWO_LETTER = new Set(["Cl", "Br"]);
29
+ /**
30
+ * Parse the SMILES subset A level needs.
31
+ *
32
+ * `CCO` ethanol, `CC(=O)C` propanone, `c1ccccc1` benzene, `CC(=O)OCC` ethyl ethanoate.
33
+ *
34
+ * Refuses anything it does not fully understand rather than guessing, because a parser that
35
+ * silently drops a feature produces a molecule that is not the one the author wrote, and every
36
+ * number computed from it afterwards is then confidently wrong.
37
+ */
38
+ function parseSmiles(smiles) {
39
+ const atoms = [];
40
+ const bonds = [];
41
+ const branchStack = [];
42
+ const ringOpen = /* @__PURE__ */ new Map();
43
+ let previous;
44
+ let pendingOrder;
45
+ let pendingAromatic = false;
46
+ const addAtom = (element, aromatic, extra = {}) => {
47
+ const id = atoms.length;
48
+ atoms.push({
49
+ id,
50
+ element,
51
+ aromatic,
52
+ ...extra
53
+ });
54
+ if (previous !== void 0) {
55
+ const order = pendingOrder ?? 1;
56
+ const arom = pendingAromatic || aromatic && atoms[previous].aromatic && pendingOrder === void 0;
57
+ bonds.push({
58
+ a: previous,
59
+ b: id,
60
+ order,
61
+ aromatic: arom
62
+ });
63
+ }
64
+ previous = id;
65
+ pendingOrder = void 0;
66
+ pendingAromatic = false;
67
+ return id;
68
+ };
69
+ let i = 0;
70
+ while (i < smiles.length) {
71
+ const ch = smiles[i];
72
+ if (ch === "[") {
73
+ const close = smiles.indexOf("]", i);
74
+ if (close === -1) throw new Error(`"${smiles}": a bracket atom is never closed.`);
75
+ const inside = smiles.slice(i + 1, close);
76
+ const match = /^([A-Za-z][a-z]?)(?:H(\d*))?(?:(\+|-)(\d*))?$/.exec(inside);
77
+ if (!match) throw new Error(`"${smiles}": cannot read the bracket atom "[${inside}]". Write it as [element], [elementH count] or [elementH count charge], for example [NH4+] or [O-].`);
78
+ const symbol = match[1];
79
+ const aromatic = symbol[0] === symbol[0].toLowerCase() && symbol.length === 1;
80
+ const element = aromatic ? symbol.toUpperCase() : symbol;
81
+ if (!(element in VALENCE)) throw new Error(`"${smiles}": "${element}" is not an element this parser knows.`);
82
+ const explicitHydrogens = match[2] === void 0 ? 0 : match[2] === "" ? 1 : Number(match[2]);
83
+ const size = match[4] ? Number(match[4]) : 1;
84
+ const charge = match[3] === void 0 ? 0 : match[3] === "-" ? -size : size;
85
+ addAtom(element, aromatic, {
86
+ explicitHydrogens,
87
+ ...charge === 0 ? {} : { charge }
88
+ });
89
+ i = close + 1;
90
+ continue;
91
+ }
92
+ if (ch === "(") {
93
+ if (previous === void 0) throw new Error(`"${smiles}": a branch cannot open the string.`);
94
+ branchStack.push(previous);
95
+ i += 1;
96
+ continue;
97
+ }
98
+ if (ch === ")") {
99
+ const back = branchStack.pop();
100
+ if (back === void 0) throw new Error(`"${smiles}": a branch was closed that never opened.`);
101
+ previous = back;
102
+ i += 1;
103
+ continue;
104
+ }
105
+ if (ch === "-" || ch === "=" || ch === "#" || ch === ":") {
106
+ pendingOrder = ch === "=" ? 2 : ch === "#" ? 3 : 1;
107
+ pendingAromatic = ch === ":";
108
+ i += 1;
109
+ continue;
110
+ }
111
+ if (ch >= "1" && ch <= "9") {
112
+ if (previous === void 0) throw new Error(`"${smiles}": a ring closure needs an atom first.`);
113
+ const open = ringOpen.get(ch);
114
+ if (open) {
115
+ ringOpen.delete(ch);
116
+ const order = pendingOrder ?? open.order;
117
+ const arom = pendingAromatic || open.aromatic || atoms[open.atom].aromatic && atoms[previous].aromatic;
118
+ bonds.push({
119
+ a: open.atom,
120
+ b: previous,
121
+ order,
122
+ aromatic: arom
123
+ });
124
+ } else ringOpen.set(ch, {
125
+ atom: previous,
126
+ order: pendingOrder ?? 1,
127
+ aromatic: pendingAromatic
128
+ });
129
+ pendingOrder = void 0;
130
+ pendingAromatic = false;
131
+ i += 1;
132
+ continue;
133
+ }
134
+ const two = smiles.slice(i, i + 2);
135
+ if (TWO_LETTER.has(two)) {
136
+ addAtom(two, false);
137
+ i += 2;
138
+ continue;
139
+ }
140
+ const upper = ch.toUpperCase();
141
+ if (upper in VALENCE) {
142
+ addAtom(upper, ch === ch.toLowerCase() && ch !== ch.toUpperCase());
143
+ i += 1;
144
+ continue;
145
+ }
146
+ throw new Error(`"${smiles}": "${ch}" is outside the SMILES subset this kernel parses.`);
147
+ }
148
+ if (branchStack.length) throw new Error(`"${smiles}": a branch was opened and never closed.`);
149
+ if (ringOpen.size) throw new Error(`"${smiles}": a ring was opened and never closed.`);
150
+ return {
151
+ atoms,
152
+ bonds
153
+ };
154
+ }
155
+ /** Bonds touching an atom. */
156
+ const bondsAt = (structure, id) => structure.bonds.filter((b) => b.a === id || b.b === id);
157
+ /** The atom on the other end of a bond. */
158
+ const otherEnd = (bond, id) => bond.a === id ? bond.b : bond.a;
159
+ /** Atoms directly bonded to this one, excluding any explicit hydrogens. */
160
+ const neighbours = (structure, id) => bondsAt(structure, id).map((b) => structure.atoms[otherEnd(b, id)]);
161
+ /** How many bonds an atom has used, counting an aromatic bond as one and a half. */
162
+ const bondOrderSum = (structure, id) => bondsAt(structure, id).reduce((n, b) => n + (b.aromatic ? 1.5 : b.order), 0);
163
+ /**
164
+ * Hydrogens on an atom, from what its valence has left over.
165
+ *
166
+ * Never declared, which is what stops a structure disagreeing with its own formula. An aromatic
167
+ * carbon in benzene has two aromatic bonds worth 1.5 each, leaving one hydrogen, which is right.
168
+ */
169
+ /**
170
+ * Bonds an atom can make, once its charge is taken into account.
171
+ *
172
+ * The two directions are not symmetrical, and the reason is what the charge did to the electrons.
173
+ *
174
+ * An atom with a lone pair to spare, nitrogen or oxygen or sulfur, makes an EXTRA bond when it
175
+ * loses an electron, because the lone pair becomes a bonding pair: ammonium's nitrogen makes four
176
+ * bonds, not three. Gaining one costs it a bond instead, which is why a carboxylate oxygen makes
177
+ * only one.
178
+ *
179
+ * Carbon has no lone pair to donate, so it loses a bond whichever way it is charged. A carbocation
180
+ * and a carbanion both make three.
181
+ */
182
+ function chargedValence(element, charge = 0) {
183
+ const base = VALENCE[element];
184
+ if (charge === 0) return base;
185
+ if (element === "C") return base - Math.abs(charge);
186
+ return base + charge;
187
+ }
188
+ function implicitHydrogens(structure, id) {
189
+ const atom = structure.atoms[id];
190
+ if (!atom) throw new Error(`No atom ${id} in this structure.`);
191
+ if (atom.element === "H") return 0;
192
+ if (atom.explicitHydrogens !== void 0) return atom.explicitHydrogens;
193
+ const used = bondOrderSum(structure, id) + (atom.openValence ?? 0);
194
+ const valence = chargedValence(atom.element, atom.charge);
195
+ const left = valence - used;
196
+ if (left < -.01) throw new Error(`Atom ${id} (${atom.element}${atom.charge ? atom.charge > 0 ? "+" : "-" : ""}) has ${used} bonds but a valence of ${valence}.`);
197
+ return Math.max(0, Math.round(left));
198
+ }
199
+ /** Total formal charge on a structure: the charge of the ion it represents. */
200
+ const totalCharge = (structure) => structure.atoms.reduce((sum, atom) => sum + (atom.charge ?? 0), 0);
201
+ /** Every element in the structure, counted, hydrogens included. */
202
+ function atomCounts(structure) {
203
+ const counts = {};
204
+ for (const atom of structure.atoms) {
205
+ counts[atom.element] = (counts[atom.element] ?? 0) + 1;
206
+ const h = implicitHydrogens(structure, atom.id);
207
+ if (h > 0) counts.H = (counts.H ?? 0) + h;
208
+ }
209
+ return counts;
210
+ }
211
+ /**
212
+ * The molecular formula, in Hill order: carbon, then hydrogen, then everything else alphabetically.
213
+ *
214
+ * Hill order is what a database and a mark scheme both use, so C2H6O and never H6C2O or OC2H6.
215
+ */
216
+ function formatCounts(counts) {
217
+ const rest = Object.keys(counts).filter((e) => e !== "C" && e !== "H" && counts[e] > 0).sort();
218
+ return [
219
+ ...counts.C ? ["C"] : [],
220
+ ...counts.H ? ["H"] : [],
221
+ ...rest
222
+ ].map((e) => `${e}${counts[e] > 1 ? counts[e] : ""}`).join("");
223
+ }
224
+ function molecularFormula(structure) {
225
+ return formatCounts(atomCounts(structure));
226
+ }
227
+ /** Relative molecular mass, from the same counts. */
228
+ const relativeMass = (structure) => {
229
+ const counts = atomCounts(structure);
230
+ const total = Object.entries(counts).reduce((sum, [element, n]) => sum + ATOMIC_MASS[element] * n, 0);
231
+ return Math.round(total * 10) / 10;
232
+ };
233
+ /**
234
+ * Degrees of unsaturation: how many rings plus double-bond equivalents the formula demands.
235
+ *
236
+ * Worth computing because it is the first thing to do with a molecular formula in a structure
237
+ * determination, and because it is the check that catches a proposed structure that cannot exist:
238
+ * a formula with zero degrees cannot contain a C=O however well the rest of the evidence fits.
239
+ *
240
+ * Oxygen does not enter the count, which surprises people every time, and the reason is that adding
241
+ * an oxygen into a chain uses two bonds and supplies two.
242
+ */
243
+ function degreesOfUnsaturation(structure) {
244
+ const c = atomCounts(structure);
245
+ const carbon = c.C ?? 0;
246
+ const hydrogen = c.H ?? 0;
247
+ const nitrogen = c.N ?? 0;
248
+ const halogen = (c.F ?? 0) + (c.Cl ?? 0) + (c.Br ?? 0) + (c.I ?? 0);
249
+ return (2 * carbon + 2 + nitrogen - hydrogen - halogen) / 2;
250
+ }
251
+ /** Atoms in the structure that are carbon. */
252
+ const carbonCount = (structure) => structure.atoms.filter((a) => a.element === "C").length;
253
+ /**
254
+ * Rings, by the cycle rank of the graph: bonds minus atoms plus connected pieces.
255
+ *
256
+ * Counts independent rings rather than every closed loop, which is the number a chemist means by
257
+ * "how many rings", and naphthalene comes out as two rather than three.
258
+ */
259
+ function ringCount(structure) {
260
+ const seen = /* @__PURE__ */ new Set();
261
+ let pieces = 0;
262
+ for (const atom of structure.atoms) {
263
+ if (seen.has(atom.id)) continue;
264
+ pieces += 1;
265
+ const stack = [atom.id];
266
+ seen.add(atom.id);
267
+ while (stack.length) {
268
+ const at = stack.pop();
269
+ for (const b of bondsAt(structure, at)) {
270
+ const next = otherEnd(b, at);
271
+ if (!seen.has(next)) {
272
+ seen.add(next);
273
+ stack.push(next);
274
+ }
275
+ }
276
+ }
277
+ }
278
+ return structure.bonds.length - structure.atoms.length + pieces;
279
+ }
280
+
281
+ //#endregion
282
+ export { ATOMIC_MASS, atomCounts, bondOrderSum, bondsAt, carbonCount, chargedValence, degreesOfUnsaturation, formatCounts, implicitHydrogens, molecularFormula, neighbours, otherEnd, parseSmiles, relativeMass, ringCount, totalCharge };
@@ -0,0 +1,153 @@
1
+ //#region src/chem/nmr.d.ts
2
+ /**
3
+ * nmr, a proton spectrum predicted from the environments in a molecule.
4
+ *
5
+ * WHAT THIS IS FOR. An NMR question is four questions wearing one coat, and a learner who has been
6
+ * shown a finished spectrum cannot take it apart again:
7
+ *
8
+ * how many signals? one per proton ENVIRONMENT
9
+ * where is each one? its chemical shift, from what the protons are attached to
10
+ * how tall is each one? the integration, in proportion to the number of protons
11
+ * how many lines in it? n + 1, where n counts the protons on the neighbouring carbons
12
+ *
13
+ * All four are computed here from the same declaration, so a lesson can change one environment and
14
+ * watch the right things move. A spectrum drawn by hand can only assert the answer.
15
+ *
16
+ * ## Why the environments are declared and not derived
17
+ *
18
+ * Working out which protons are equivalent means perceiving the symmetry of a structure, which is a
19
+ * real problem and not a 9701 one. What IS 9701, and what is done here, is everything that follows
20
+ * once the environments are known. Declaring them is also how a question is actually set: "the three
21
+ * environments in ethanol" is the given, and the spectrum is the answer.
22
+ *
23
+ * The one thing a declaration must not do is let the two halves disagree, so {@link predictNmr}
24
+ * refuses a molecule whose neighbour counts are impossible for the environments it lists.
25
+ *
26
+ * ## Splitting is Pascal's triangle, and that is worth computing
27
+ *
28
+ * n + 1 gives the number of lines. Their relative heights are the binomial coefficients, so a
29
+ * quartet is 1:3:3:1 and not four equal lines. Drawing four equal lines is the commonest way a
30
+ * textbook figure is wrong, and it is wrong in a way that matters: the outer lines of a large
31
+ * multiplet vanish into the baseline, which is why a septet is often reported as a quintet.
32
+ *
33
+ * Pure: no React, no pixels.
34
+ */
35
+ /** Where a proton environment absorbs, from the ranges a data booklet prints. */
36
+ interface ShiftRange {
37
+ readonly from: number;
38
+ readonly to: number;
39
+ /** What the protons are attached to, as the booklet describes it. */
40
+ readonly environment: string;
41
+ }
42
+ /**
43
+ * Chemical shift ranges, in ppm, as the 9701 data booklet gives them.
44
+ *
45
+ * Ranges, never single values, because that is what the booklet prints and because a learner who
46
+ * memorises "2.2" for a ketone cannot place a shift of 2.4. The question is always which RANGE a
47
+ * peak falls in.
48
+ */
49
+ declare const SHIFT_RANGES: Readonly<Record<string, ShiftRange>>;
50
+ type ShiftKey = keyof typeof SHIFT_RANGES;
51
+ /** One set of protons that the machine cannot tell apart. */
52
+ interface ProtonEnvironment {
53
+ /** What a learner would point at, e.g. 'the CH3 of the ethyl group'. */
54
+ readonly label: string;
55
+ /** How many protons are in this environment. Sets the integration. */
56
+ readonly protons: number;
57
+ /** Protons on the ADJACENT carbons. Sets the splitting, through n + 1. */
58
+ readonly neighbours: number;
59
+ /** Which range of the data booklet this environment falls in. */
60
+ readonly shift: ShiftKey;
61
+ /**
62
+ * Where in that range the peak actually sits, in ppm.
63
+ *
64
+ * Given rather than guessed, because a range is what the booklet prints and a spectrum needs a
65
+ * position. {@link predictNmr} refuses a value outside its own declared range.
66
+ */
67
+ readonly ppm: number;
68
+ /**
69
+ * Does this proton exchange with D2O?
70
+ *
71
+ * The O-H and N-H protons do, which is the whole point of the D2O experiment: the peak vanishes.
72
+ * It is also why they are not split and do not split their neighbours.
73
+ */
74
+ readonly exchangeable?: boolean;
75
+ }
76
+ interface NmrMolecule {
77
+ readonly name: string;
78
+ readonly formula: string;
79
+ readonly environments: readonly ProtonEnvironment[];
80
+ }
81
+ /** The name for a number of lines. Beyond five, a spectrum is reported as a multiplet. */
82
+ declare const MULTIPLICITY_NAMES: readonly ["singlet", "doublet", "triplet", "quartet", "quintet", "sextet", "septet"];
83
+ type Multiplicity = (typeof MULTIPLICITY_NAMES)[number] | 'multiplet';
84
+ /**
85
+ * The n + 1 rule.
86
+ *
87
+ * An exchangeable proton is always a singlet and never splits anything, because it swaps between
88
+ * molecules faster than the machine can see it.
89
+ */
90
+ declare function multiplicityOf(neighbours: number, exchangeable?: boolean): Multiplicity;
91
+ /**
92
+ * Relative heights of the lines in a multiplet: a row of Pascal's triangle.
93
+ *
94
+ * A quartet is 1:3:3:1, not four equal lines. It matters because the outer lines of a large
95
+ * multiplet sink into the baseline, which is why a septet is so often reported as a quintet.
96
+ */
97
+ declare function splittingPattern(lines: number): number[];
98
+ /**
99
+ * Integration as the smallest whole-number ratio.
100
+ *
101
+ * A spectrum reports 2:3, not 4:6, because the integration gives the RATIO of protons and never
102
+ * their number. That distinction is the one an exam question turns on: a 2:3 ratio fits both
103
+ * C2H5-OH and C4H10O2, and the molecular formula is what settles it.
104
+ */
105
+ declare function integrationRatio(environments: readonly ProtonEnvironment[]): number[];
106
+ /** One signal in the predicted spectrum. */
107
+ interface NmrPeak {
108
+ readonly label: string;
109
+ readonly ppm: number;
110
+ readonly protons: number;
111
+ /** This peak's share of the total integration, as a whole number. */
112
+ readonly integration: number;
113
+ readonly multiplicity: Multiplicity;
114
+ /** Number of lines, which is n + 1. */
115
+ readonly lines: number;
116
+ /** Relative height of each line, from Pascal's triangle. */
117
+ readonly pattern: readonly number[];
118
+ readonly range: ShiftRange;
119
+ readonly exchangeable: boolean;
120
+ }
121
+ interface NmrSpectrum {
122
+ readonly molecule: NmrMolecule;
123
+ /** Downfield first, which is how a spectrum is printed: high ppm on the left. */
124
+ readonly peaks: readonly NmrPeak[];
125
+ /** Total protons, which must match the molecular formula. */
126
+ readonly totalProtons: number;
127
+ }
128
+ /**
129
+ * Predict the spectrum.
130
+ *
131
+ * Refuses a declaration that contradicts itself rather than drawing a spectrum nobody could have
132
+ * measured: a peak outside the range it claims, or an exchangeable proton given neighbours to
133
+ * couple to. Both are ways a hand-authored figure goes quietly wrong.
134
+ */
135
+ declare function predictNmr(molecule: NmrMolecule): NmrSpectrum;
136
+ /**
137
+ * The spectrum after a drop of D2O, with every exchangeable peak gone.
138
+ *
139
+ * The experiment exists to answer one question: is that broad singlet an O-H? Running it is how a
140
+ * learner tells an alcohol from an ether without any other evidence, and doing it as a computation
141
+ * rather than a second hand-drawn figure means the two spectra cannot disagree.
142
+ */
143
+ declare const afterD2O: (spectrum: NmrSpectrum) => NmrSpectrum;
144
+ /**
145
+ * The line positions of one peak, in ppm, given the coupling constant.
146
+ *
147
+ * `J` is in hertz and a spectrum is in ppm, so the conversion needs the spectrometer frequency.
148
+ * That is not a detail to hide: it is why the same molecule gives the same splitting PATTERN on
149
+ * every machine while the lines sit closer together on a stronger one.
150
+ */
151
+ declare function linePositions(peak: NmrPeak, jHz?: number, spectrometerMHz?: number): number[];
152
+ //#endregion
153
+ export { MULTIPLICITY_NAMES, Multiplicity, NmrMolecule, NmrPeak, NmrSpectrum, ProtonEnvironment, SHIFT_RANGES, ShiftKey, ShiftRange, afterD2O, integrationRatio, linePositions, multiplicityOf, predictNmr, splittingPattern };
@@ -0,0 +1,165 @@
1
+ //#region src/chem/nmr.ts
2
+ /**
3
+ * Chemical shift ranges, in ppm, as the 9701 data booklet gives them.
4
+ *
5
+ * Ranges, never single values, because that is what the booklet prints and because a learner who
6
+ * memorises "2.2" for a ketone cannot place a shift of 2.4. The question is always which RANGE a
7
+ * peak falls in.
8
+ */
9
+ const SHIFT_RANGES = {
10
+ alkane: {
11
+ from: .9,
12
+ to: 1.7,
13
+ environment: "R-CH3, R-CH2-R, R3CH"
14
+ },
15
+ "alkyl-next-to-carbonyl": {
16
+ from: 2,
17
+ to: 2.9,
18
+ environment: "CH3-CO-, -CH2-CO-"
19
+ },
20
+ "alkyl-next-to-halogen": {
21
+ from: 3.2,
22
+ to: 4,
23
+ environment: "R-CH2-Cl or R-CH2-Br"
24
+ },
25
+ "alkyl-next-to-oxygen": {
26
+ from: 3.3,
27
+ to: 4.3,
28
+ environment: "R-CH2-O-"
29
+ },
30
+ alcohol: {
31
+ from: .5,
32
+ to: 6,
33
+ environment: "R-O-H"
34
+ },
35
+ amine: {
36
+ from: 1,
37
+ to: 5,
38
+ environment: "R-N-H"
39
+ },
40
+ alkene: {
41
+ from: 4.5,
42
+ to: 6,
43
+ environment: "R2C=CH2"
44
+ },
45
+ arene: {
46
+ from: 6,
47
+ to: 9,
48
+ environment: "aromatic H"
49
+ },
50
+ aldehyde: {
51
+ from: 9.3,
52
+ to: 10.5,
53
+ environment: "R-CHO"
54
+ },
55
+ "carboxylic-acid": {
56
+ from: 9,
57
+ to: 13,
58
+ environment: "R-COOH"
59
+ }
60
+ };
61
+ /** The name for a number of lines. Beyond five, a spectrum is reported as a multiplet. */
62
+ const MULTIPLICITY_NAMES = [
63
+ "singlet",
64
+ "doublet",
65
+ "triplet",
66
+ "quartet",
67
+ "quintet",
68
+ "sextet",
69
+ "septet"
70
+ ];
71
+ /**
72
+ * The n + 1 rule.
73
+ *
74
+ * An exchangeable proton is always a singlet and never splits anything, because it swaps between
75
+ * molecules faster than the machine can see it.
76
+ */
77
+ function multiplicityOf(neighbours, exchangeable = false) {
78
+ if (exchangeable) return "singlet";
79
+ return MULTIPLICITY_NAMES[neighbours + 1 - 1] ?? "multiplet";
80
+ }
81
+ /**
82
+ * Relative heights of the lines in a multiplet: a row of Pascal's triangle.
83
+ *
84
+ * A quartet is 1:3:3:1, not four equal lines. It matters because the outer lines of a large
85
+ * multiplet sink into the baseline, which is why a septet is so often reported as a quintet.
86
+ */
87
+ function splittingPattern(lines) {
88
+ const row = [1];
89
+ for (let i = 1; i < lines; i += 1) row.push(row[i - 1] * (lines - i) / i);
90
+ return row;
91
+ }
92
+ const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
93
+ /**
94
+ * Integration as the smallest whole-number ratio.
95
+ *
96
+ * A spectrum reports 2:3, not 4:6, because the integration gives the RATIO of protons and never
97
+ * their number. That distinction is the one an exam question turns on: a 2:3 ratio fits both
98
+ * C2H5-OH and C4H10O2, and the molecular formula is what settles it.
99
+ */
100
+ function integrationRatio(environments) {
101
+ const counts = environments.map((e) => e.protons);
102
+ if (counts.length === 0) return [];
103
+ const divisor = counts.reduce((a, b) => gcd(a, b));
104
+ return counts.map((n) => n / divisor);
105
+ }
106
+ /**
107
+ * Predict the spectrum.
108
+ *
109
+ * Refuses a declaration that contradicts itself rather than drawing a spectrum nobody could have
110
+ * measured: a peak outside the range it claims, or an exchangeable proton given neighbours to
111
+ * couple to. Both are ways a hand-authored figure goes quietly wrong.
112
+ */
113
+ function predictNmr(molecule) {
114
+ const ratio = integrationRatio(molecule.environments);
115
+ return {
116
+ molecule,
117
+ peaks: [...molecule.environments.map((env, i) => {
118
+ const range = SHIFT_RANGES[env.shift];
119
+ if (!range) throw new Error(`${env.label}: "${env.shift}" is not a shift range in the booklet.`);
120
+ if (env.ppm < range.from || env.ppm > range.to) throw new Error(`${env.label}: ${env.ppm} ppm is outside the ${range.from} to ${range.to} range for ${env.shift}.`);
121
+ if (env.exchangeable && env.neighbours > 0) throw new Error(`${env.label}: an exchangeable proton cannot be coupled, so it must have 0 neighbours.`);
122
+ const multiplicity = multiplicityOf(env.neighbours, env.exchangeable);
123
+ const lines = env.exchangeable ? 1 : env.neighbours + 1;
124
+ return {
125
+ label: env.label,
126
+ ppm: env.ppm,
127
+ protons: env.protons,
128
+ integration: ratio[i],
129
+ multiplicity,
130
+ lines,
131
+ pattern: splittingPattern(lines),
132
+ range,
133
+ exchangeable: env.exchangeable === true
134
+ };
135
+ })].sort((a, b) => b.ppm - a.ppm),
136
+ totalProtons: molecule.environments.reduce((n, e) => n + e.protons, 0)
137
+ };
138
+ }
139
+ /**
140
+ * The spectrum after a drop of D2O, with every exchangeable peak gone.
141
+ *
142
+ * The experiment exists to answer one question: is that broad singlet an O-H? Running it is how a
143
+ * learner tells an alcohol from an ether without any other evidence, and doing it as a computation
144
+ * rather than a second hand-drawn figure means the two spectra cannot disagree.
145
+ */
146
+ const afterD2O = (spectrum) => ({
147
+ ...spectrum,
148
+ peaks: spectrum.peaks.filter((p) => !p.exchangeable),
149
+ totalProtons: spectrum.peaks.filter((p) => !p.exchangeable).reduce((n, p) => n + p.protons, 0)
150
+ });
151
+ /**
152
+ * The line positions of one peak, in ppm, given the coupling constant.
153
+ *
154
+ * `J` is in hertz and a spectrum is in ppm, so the conversion needs the spectrometer frequency.
155
+ * That is not a detail to hide: it is why the same molecule gives the same splitting PATTERN on
156
+ * every machine while the lines sit closer together on a stronger one.
157
+ */
158
+ function linePositions(peak, jHz = 7, spectrometerMHz = 100) {
159
+ const spacing = jHz / spectrometerMHz;
160
+ const first = peak.ppm - spacing * (peak.lines - 1) / 2;
161
+ return Array.from({ length: peak.lines }, (_, i) => first + i * spacing);
162
+ }
163
+
164
+ //#endregion
165
+ export { MULTIPLICITY_NAMES, SHIFT_RANGES, afterD2O, integrationRatio, linePositions, multiplicityOf, predictNmr, splittingPattern };
@@ -0,0 +1,65 @@
1
+ import { Structure } from "./molecule.mjs";
2
+
3
+ //#region src/chem/polymer.d.ts
4
+ type PolymerKind = 'addition' | 'condensation';
5
+ /** What the two link points are: an ester link, an amide link, or a plain carbon-carbon bond. */
6
+ type LinkKind = 'carbon-carbon' | 'ester' | 'amide';
7
+ interface RepeatUnit {
8
+ readonly kind: PolymerKind;
9
+ readonly link: LinkKind;
10
+ /** The unit itself, with the two atoms that bond onto the neighbouring units. */
11
+ readonly structure: Structure;
12
+ /** Atom ids in `structure` that join to the previous and the next unit. */
13
+ readonly linkAtoms: readonly [number, number];
14
+ /** The molecular formula of one repeat unit. */
15
+ readonly formula: string;
16
+ readonly mass: number;
17
+ /** The small molecule lost per link, for a condensation. `undefined` for addition. */
18
+ readonly lost?: {
19
+ readonly formula: string;
20
+ readonly mass: number;
21
+ };
22
+ /** Why this monomer or pair polymerises the way it does, in the words a mark scheme uses. */
23
+ readonly reason: string;
24
+ }
25
+ /**
26
+ * Open the double bond of an alkene to give the addition repeat unit.
27
+ *
28
+ * Refuses a monomer with no C=C, because "it must be addition" is a guess a learner makes and a
29
+ * kernel should not. The formula is unchanged by construction, and the test asserts it.
30
+ */
31
+ declare function addition(monomer: Structure): RepeatUnit;
32
+ /**
33
+ * Can this monomer sustain a chain?
34
+ *
35
+ * Two reactive groups, or the chain stops after one join. Ethanoic acid has one and is therefore a
36
+ * dead end; hexanedioic acid has two and is not. This is the check most treatments leave out and
37
+ * the reason a learner cannot predict whether a new monomer will polymerise.
38
+ */
39
+ declare const canSustainChain: (monomer: Structure) => boolean;
40
+ /**
41
+ * Join two monomers and eliminate water, giving the condensation repeat unit.
42
+ *
43
+ * The pairing is decided by the groups that are actually present, not by an argument the caller
44
+ * passes: an acid with an alcohol gives an ester link and an acid with an amine gives an amide link,
45
+ * and those are the only two the syllabus needs. A pair that cannot make either is refused with the
46
+ * reason, because "nothing happened" is not a useful answer to a learner.
47
+ */
48
+ declare function condensation(first: Structure, second: Structure): RepeatUnit;
49
+ /**
50
+ * The mass of a chain of `n` repeat units.
51
+ *
52
+ * For an addition polymer it is n times the monomer. For a condensation polymer the water has
53
+ * already gone from the repeat unit, so the same multiplication works, and the difference between
54
+ * the two shows up as a mass that is not a whole multiple of the monomers.
55
+ */
56
+ declare const chainMass: (unit: RepeatUnit, n: number) => number;
57
+ /**
58
+ * How many repeat units a chain of this mass holds, to the nearest whole unit.
59
+ *
60
+ * The question an exam asks the other way round: a poly(ethene) sample with a relative molecular
61
+ * mass of 28 000 contains 1000 repeat units, and that number is what "long chain" means.
62
+ */
63
+ declare const unitsInChain: (unit: RepeatUnit, chainMr: number) => number;
64
+ //#endregion
65
+ export { LinkKind, PolymerKind, RepeatUnit, addition, canSustainChain, chainMass, condensation, unitsInChain };