@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.
- package/dist/_virtual/_rolldown/runtime.mjs +18 -0
- package/dist/atomic/aufbau.d.mts +144 -0
- package/dist/atomic/aufbau.mjs +384 -0
- package/dist/atomic/elements.d.mts +67 -0
- package/dist/atomic/elements.mjs +785 -0
- package/dist/atomic/emission.d.mts +112 -0
- package/dist/atomic/emission.mjs +179 -0
- package/dist/atomic/index.d.mts +8 -0
- package/dist/atomic/index.mjs +9 -0
- package/dist/atomic/periodic.d.mts +82 -0
- package/dist/atomic/periodic.mjs +231 -0
- package/dist/atomic/properties.d.mts +100 -0
- package/dist/atomic/properties.mjs +574 -0
- package/dist/atomic/shells.d.mts +137 -0
- package/dist/atomic/shells.mjs +170 -0
- package/dist/atomic/trends.d.mts +148 -0
- package/dist/atomic/trends.mjs +225 -0
- package/dist/chem/chromatography.d.mts +66 -0
- package/dist/chem/chromatography.mjs +63 -0
- package/dist/chem/complex.d.mts +114 -0
- package/dist/chem/complex.mjs +234 -0
- package/dist/chem/cycle.d.mts +75 -0
- package/dist/chem/cycle.mjs +145 -0
- package/dist/chem/equation.d.mts +86 -0
- package/dist/chem/equation.mjs +168 -0
- package/dist/chem/formula.d.mts +90 -0
- package/dist/chem/formula.mjs +201 -0
- package/dist/chem/functional-groups.d.mts +31 -0
- package/dist/chem/functional-groups.mjs +187 -0
- package/dist/chem/hazard.d.mts +94 -0
- package/dist/chem/hazard.mjs +329 -0
- package/dist/chem/index.d.mts +34 -33
- package/dist/chem/index.mjs +34 -28
- package/dist/chem/ionic.d.mts +98 -0
- package/dist/chem/ionic.mjs +156 -0
- package/dist/chem/layout.d.mts +46 -0
- package/dist/chem/layout.mjs +342 -0
- package/dist/chem/lewis.d.mts +79 -0
- package/dist/chem/lewis.mjs +163 -0
- package/dist/chem/molecular-orbital.d.mts +114 -0
- package/dist/chem/molecular-orbital.mjs +201 -0
- package/dist/chem/molecule.d.mts +154 -0
- package/dist/chem/molecule.mjs +282 -0
- package/dist/chem/nmr.d.mts +153 -0
- package/dist/chem/nmr.mjs +165 -0
- package/dist/chem/polymer.d.mts +65 -0
- package/dist/chem/polymer.mjs +179 -0
- package/dist/chem/redox.d.mts +138 -0
- package/dist/chem/redox.mjs +328 -0
- package/dist/chem/relations.d.mts +183 -0
- package/dist/chem/relations.mjs +299 -0
- package/dist/chem/skeleton.d.mts +51 -0
- package/dist/chem/skeleton.mjs +186 -0
- package/dist/chem/solution.d.mts +116 -0
- package/dist/chem/solution.mjs +186 -0
- package/dist/chem/species.d.mts +47 -0
- package/dist/chem/species.mjs +128 -0
- package/dist/chem/stereo.d.mts +108 -0
- package/dist/chem/stereo.mjs +286 -0
- package/dist/chem/stoichiometry.d.mts +95 -0
- package/dist/chem/stoichiometry.mjs +145 -0
- package/dist/chem/synthesis.d.mts +113 -0
- package/dist/chem/synthesis.mjs +281 -0
- package/dist/chem/vsepr.d.mts +80 -0
- package/dist/chem/vsepr.mjs +247 -0
- package/dist/circuit/ac.d.mts +163 -0
- package/dist/circuit/ac.mjs +452 -0
- package/dist/circuit/dc.d.mts +92 -0
- package/dist/circuit/dc.mjs +333 -0
- package/dist/circuit/index.d.mts +5 -92
- package/dist/circuit/index.mjs +5 -332
- package/dist/circuit/network.d.mts +99 -0
- package/dist/circuit/network.mjs +440 -0
- package/dist/circuit/topology.d.mts +102 -0
- package/dist/circuit/topology.mjs +143 -0
- package/dist/colour/absorption.d.mts +79 -0
- package/dist/colour/absorption.mjs +137 -0
- package/dist/colour/index.d.mts +6 -0
- package/dist/colour/index.mjs +7 -0
- package/dist/colour/name.d.mts +14 -0
- package/dist/colour/name.mjs +76 -0
- package/dist/colour/observer.d.mts +31 -0
- package/dist/colour/observer.mjs +46 -0
- package/dist/colour/spectrum.d.mts +61 -0
- package/dist/colour/spectrum.mjs +108 -0
- package/dist/colour/srgb.d.mts +49 -0
- package/dist/colour/srgb.mjs +108 -0
- package/dist/core/constants.d.mts +138 -0
- package/dist/core/constants.mjs +139 -0
- package/dist/core/index.d.mts +3 -1
- package/dist/core/index.mjs +3 -1
- package/dist/core/vec3.d.mts +94 -0
- package/dist/core/vec3.mjs +123 -0
- package/dist/core/worked.d.mts +35 -0
- package/dist/core/worked.mjs +44 -0
- package/dist/electro/cell.d.mts +102 -0
- package/dist/electro/cell.mjs +170 -0
- package/dist/electro/conductivity.d.mts +157 -0
- package/dist/electro/conductivity.mjs +156 -0
- package/dist/electro/couples.d.mts +114 -0
- package/dist/electro/couples.mjs +334 -0
- package/dist/electro/electrolysis.d.mts +96 -0
- package/dist/electro/electrolysis.mjs +274 -0
- package/dist/electro/faraday.d.mts +150 -0
- package/dist/electro/faraday.mjs +118 -0
- package/dist/electro/index.d.mts +6 -0
- package/dist/electro/index.mjs +7 -0
- package/dist/finance/accounting/adjustments.d.mts +17 -0
- package/dist/finance/accounting/adjustments.mjs +202 -0
- package/dist/finance/accounting/analysis.d.mts +8 -0
- package/dist/finance/accounting/analysis.mjs +205 -0
- package/dist/finance/accounting/clubs.d.mts +21 -0
- package/dist/finance/accounting/clubs.mjs +217 -0
- package/dist/finance/accounting/company-statements.d.mts +7 -0
- package/dist/finance/accounting/company-statements.mjs +150 -0
- package/dist/finance/accounting/harness.d.mts +7 -0
- package/dist/finance/accounting/harness.mjs +45 -0
- package/dist/finance/accounting/index.d.mts +18 -0
- package/dist/finance/accounting/index.mjs +18 -0
- package/dist/finance/accounting/journalize.d.mts +16 -0
- package/dist/finance/accounting/journalize.mjs +128 -0
- package/dist/finance/accounting/manufacturing.d.mts +8 -0
- package/dist/finance/accounting/manufacturing.mjs +236 -0
- package/dist/finance/accounting/partnership.d.mts +7 -0
- package/dist/finance/accounting/partnership.mjs +152 -0
- package/dist/finance/accounting/period-case.d.mts +6 -0
- package/dist/finance/accounting/period-case.mjs +71 -0
- package/dist/finance/accounting/post.d.mts +9 -0
- package/dist/finance/accounting/post.mjs +84 -0
- package/dist/finance/accounting/precision.d.mts +9 -0
- package/dist/finance/accounting/precision.mjs +15 -0
- package/dist/finance/accounting/rules.d.mts +21 -0
- package/dist/finance/accounting/rules.mjs +37 -0
- package/dist/finance/accounting/solve.d.mts +28 -0
- package/dist/finance/accounting/solve.mjs +92 -0
- package/dist/finance/accounting/statements.d.mts +7 -0
- package/dist/finance/accounting/statements.mjs +142 -0
- package/dist/finance/accounting/types.d.mts +649 -0
- package/dist/finance/accounting/validate.d.mts +7 -0
- package/dist/finance/accounting/validate.mjs +67 -0
- package/dist/finance/accounting/verification.d.mts +24 -0
- package/dist/finance/accounting/verification.mjs +386 -0
- package/dist/finance/bizsim.d.mts +21 -39
- package/dist/finance/bizsim.mjs +276 -97
- package/dist/finance/index.d.mts +20 -43
- package/dist/finance/index.mjs +19 -77
- package/dist/finance/investment-math.d.mts +7 -0
- package/dist/finance/investment-math.mjs +18 -0
- package/dist/finance/management-accounting/budgeting.d.mts +7 -0
- package/dist/finance/management-accounting/budgeting.mjs +189 -0
- package/dist/finance/management-accounting/cash-budget.d.mts +7 -0
- package/dist/finance/management-accounting/cash-budget.mjs +154 -0
- package/dist/finance/management-accounting/costing.d.mts +8 -0
- package/dist/finance/management-accounting/costing.mjs +175 -0
- package/dist/finance/management-accounting/decisions.d.mts +8 -0
- package/dist/finance/management-accounting/decisions.mjs +164 -0
- package/dist/finance/management-accounting/index.d.mts +13 -0
- package/dist/finance/management-accounting/index.mjs +13 -0
- package/dist/finance/management-accounting/integrated-budget.d.mts +8 -0
- package/dist/finance/management-accounting/integrated-budget.mjs +67 -0
- package/dist/finance/management-accounting/inventory-costing.d.mts +7 -0
- package/dist/finance/management-accounting/inventory-costing.mjs +173 -0
- package/dist/finance/management-accounting/investment-appraisal.d.mts +7 -0
- package/dist/finance/management-accounting/investment-appraisal.mjs +148 -0
- package/dist/finance/management-accounting/management-case.d.mts +8 -0
- package/dist/finance/management-accounting/management-case.mjs +91 -0
- package/dist/finance/management-accounting/master-budget.d.mts +7 -0
- package/dist/finance/management-accounting/master-budget.mjs +137 -0
- package/dist/finance/management-accounting/production-costing.d.mts +8 -0
- package/dist/finance/management-accounting/production-costing.mjs +214 -0
- package/dist/finance/management-accounting/types.d.mts +644 -0
- package/dist/finance/management-accounting/variances.d.mts +7 -0
- package/dist/finance/management-accounting/variances.mjs +150 -0
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +1 -1
- package/dist/math/analysis.d.mts +61 -0
- package/dist/math/analysis.mjs +187 -0
- package/dist/math/answer-check.d.mts +50 -0
- package/dist/math/answer-check.mjs +106 -0
- package/dist/math/calculus.d.mts +4 -9
- package/dist/math/calculus.mjs +216 -7
- package/dist/math/complex.d.mts +93 -0
- package/dist/math/complex.mjs +239 -0
- package/dist/math/expr.d.mts +23 -0
- package/dist/math/expr.mjs +36 -0
- package/dist/math/index.d.mts +8 -22
- package/dist/math/index.mjs +8 -37
- package/dist/math/latex.mjs +2 -1
- package/dist/math/parse.mjs +1 -1
- package/dist/math/poly.d.mts +55 -0
- package/dist/math/poly.mjs +273 -0
- package/dist/math/rearrange.d.mts +14 -0
- package/dist/math/rearrange.mjs +125 -0
- package/dist/math/solve.d.mts +21 -0
- package/dist/math/solve.mjs +75 -0
- package/dist/networking/addressing.d.mts +26 -0
- package/dist/networking/addressing.mjs +33 -0
- package/dist/networking/cloud/access.d.mts +36 -0
- package/dist/networking/cloud/access.mjs +95 -0
- package/dist/networking/cloud/containers.d.mts +43 -0
- package/dist/networking/cloud/containers.mjs +54 -0
- package/dist/networking/cloud/edge.d.mts +17 -0
- package/dist/networking/cloud/edge.mjs +31 -0
- package/dist/networking/cloud/failure-domains.d.mts +19 -0
- package/dist/networking/cloud/failure-domains.mjs +22 -0
- package/dist/networking/cloud/geo-routing.d.mts +22 -0
- package/dist/networking/cloud/geo-routing.mjs +64 -0
- package/dist/networking/cloud/index.d.mts +18 -0
- package/dist/networking/cloud/index.mjs +19 -0
- package/dist/networking/cloud/inference-reliability.d.mts +30 -0
- package/dist/networking/cloud/inference-reliability.mjs +76 -0
- package/dist/networking/cloud/inference.d.mts +118 -0
- package/dist/networking/cloud/inference.mjs +179 -0
- package/dist/networking/cloud/infrastructure.d.mts +45 -0
- package/dist/networking/cloud/infrastructure.mjs +96 -0
- package/dist/networking/cloud/observability.d.mts +24 -0
- package/dist/networking/cloud/observability.mjs +34 -0
- package/dist/networking/cloud/orchestration.d.mts +32 -0
- package/dist/networking/cloud/orchestration.mjs +38 -0
- package/dist/networking/cloud/recovery.d.mts +21 -0
- package/dist/networking/cloud/recovery.mjs +27 -0
- package/dist/networking/cloud/releases.d.mts +42 -0
- package/dist/networking/cloud/releases.mjs +135 -0
- package/dist/networking/cloud/scaling.d.mts +25 -0
- package/dist/networking/cloud/scaling.mjs +65 -0
- package/dist/networking/cloud/secrets.d.mts +39 -0
- package/dist/networking/cloud/secrets.mjs +68 -0
- package/dist/networking/cloud/server-deployment.d.mts +29 -0
- package/dist/networking/cloud/server-deployment.mjs +72 -0
- package/dist/networking/cloud/storage.d.mts +16 -0
- package/dist/networking/cloud/storage.mjs +58 -0
- package/dist/networking/cloud/vpc.d.mts +18 -0
- package/dist/networking/cloud/vpc.mjs +100 -0
- package/dist/networking/delivery.d.mts +34 -0
- package/dist/networking/delivery.mjs +52 -0
- package/dist/networking/index.d.mts +47 -0
- package/dist/networking/index.mjs +47 -0
- package/dist/networking/ipv6.d.mts +28 -0
- package/dist/networking/ipv6.mjs +17 -0
- package/dist/networking/journeys.d.mts +83 -0
- package/dist/networking/journeys.mjs +161 -0
- package/dist/networking/models/airtime.d.mts +87 -0
- package/dist/networking/models/airtime.mjs +130 -0
- package/dist/networking/models/cellular.d.mts +157 -0
- package/dist/networking/models/cellular.mjs +252 -0
- package/dist/networking/models/encapsulation.d.mts +98 -0
- package/dist/networking/models/encapsulation.mjs +132 -0
- package/dist/networking/models/handover.d.mts +116 -0
- package/dist/networking/models/handover.mjs +209 -0
- package/dist/networking/models/handshake.d.mts +90 -0
- package/dist/networking/models/handshake.mjs +122 -0
- package/dist/networking/models/index.d.mts +14 -0
- package/dist/networking/models/index.mjs +15 -0
- package/dist/networking/models/media.d.mts +82 -0
- package/dist/networking/models/media.mjs +188 -0
- package/dist/networking/models/satellite.d.mts +86 -0
- package/dist/networking/models/satellite.mjs +121 -0
- package/dist/networking/models/spectrum.d.mts +83 -0
- package/dist/networking/models/spectrum.mjs +235 -0
- package/dist/networking/models/switch.d.mts +80 -0
- package/dist/networking/models/switch.mjs +155 -0
- package/dist/networking/models/vlan.d.mts +62 -0
- package/dist/networking/models/vlan.mjs +100 -0
- package/dist/networking/models/vpn.d.mts +66 -0
- package/dist/networking/models/vpn.mjs +125 -0
- package/dist/networking/models/wifi-privacy.d.mts +82 -0
- package/dist/networking/models/wifi-privacy.mjs +130 -0
- package/dist/networking/models/wifi.d.mts +240 -0
- package/dist/networking/models/wifi.mjs +562 -0
- package/dist/networking/nat-traversal.d.mts +24 -0
- package/dist/networking/nat-traversal.mjs +38 -0
- package/dist/networking/protocols.d.mts +17 -0
- package/dist/networking/protocols.mjs +57 -0
- package/dist/networking/queue.d.mts +12 -0
- package/dist/networking/queue.mjs +18 -0
- package/dist/networking/route-learning.d.mts +23 -0
- package/dist/networking/route-learning.mjs +43 -0
- package/dist/networking/routing.d.mts +11 -0
- package/dist/networking/routing.mjs +25 -0
- package/dist/networking/signals.d.mts +26 -0
- package/dist/networking/signals.mjs +35 -0
- package/dist/networking/subnet.d.mts +49 -0
- package/dist/networking/subnet.mjs +89 -0
- package/dist/networking/tcp-reliability.d.mts +24 -0
- package/dist/networking/tcp-reliability.mjs +38 -0
- package/dist/networking/topology/contract.d.mts +123 -0
- package/dist/networking/topology/index.d.mts +4 -0
- package/dist/networking/topology/index.mjs +4 -0
- package/dist/networking/topology/presets.d.mts +7 -0
- package/dist/networking/topology/presets.mjs +302 -0
- package/dist/networking/topology/simulation.d.mts +8 -0
- package/dist/networking/topology/simulation.mjs +179 -0
- package/dist/networking/wireless.d.mts +11 -0
- package/dist/networking/wireless.mjs +17 -0
- package/dist/optics/imaging.d.mts +218 -0
- package/dist/optics/imaging.mjs +276 -0
- package/dist/optics/index.d.mts +7 -0
- package/dist/optics/index.mjs +8 -0
- package/dist/optics/laws.d.mts +70 -0
- package/dist/optics/laws.mjs +195 -0
- package/dist/optics/prism.d.mts +148 -0
- package/dist/optics/prism.mjs +197 -0
- package/dist/optics/refraction.d.mts +201 -0
- package/dist/optics/refraction.mjs +270 -0
- package/dist/optics/wave-optics.d.mts +171 -0
- package/dist/optics/wave-optics.mjs +259 -0
- package/dist/physics/ac/index.d.mts +183 -0
- package/dist/physics/ac/index.mjs +203 -0
- package/dist/physics/dynamics/forces.d.mts +98 -0
- package/dist/physics/dynamics/forces.mjs +153 -0
- package/dist/physics/dynamics/friction.d.mts +76 -0
- package/dist/physics/dynamics/friction.mjs +119 -0
- package/dist/physics/dynamics/system.d.mts +68 -0
- package/dist/physics/dynamics/system.mjs +125 -0
- package/dist/physics/electrostatics/index.d.mts +169 -0
- package/dist/physics/electrostatics/index.mjs +249 -0
- package/dist/physics/energy/centre-of-mass.d.mts +115 -0
- package/dist/physics/energy/centre-of-mass.mjs +180 -0
- package/dist/physics/energy/conservation.d.mts +81 -0
- package/dist/physics/energy/conservation.mjs +129 -0
- package/dist/physics/energy/power.d.mts +90 -0
- package/dist/physics/energy/power.mjs +167 -0
- package/dist/physics/energy/spring.d.mts +73 -0
- package/dist/physics/energy/spring.mjs +129 -0
- package/dist/physics/energy/store.d.mts +63 -0
- package/dist/physics/energy/store.mjs +108 -0
- package/dist/physics/energy/theorem.d.mts +65 -0
- package/dist/physics/energy/theorem.mjs +113 -0
- package/dist/physics/energy/work.d.mts +114 -0
- package/dist/physics/energy/work.mjs +219 -0
- package/dist/physics/gravitation/body.d.mts +85 -0
- package/dist/physics/gravitation/body.mjs +168 -0
- package/dist/physics/gravitation/kepler.d.mts +127 -0
- package/dist/physics/gravitation/kepler.mjs +216 -0
- package/dist/physics/gravitation/law.d.mts +117 -0
- package/dist/physics/gravitation/law.mjs +175 -0
- package/dist/physics/gravitation/orbit.d.mts +119 -0
- package/dist/physics/gravitation/orbit.mjs +202 -0
- package/dist/physics/gravitation/potential.d.mts +114 -0
- package/dist/physics/gravitation/potential.mjs +202 -0
- package/dist/physics/gravitation/variation.d.mts +97 -0
- package/dist/physics/gravitation/variation.mjs +175 -0
- package/dist/physics/index.d.mts +35 -0
- package/dist/physics/index.mjs +36 -0
- package/dist/physics/integral-sweep.d.mts +63 -0
- package/dist/physics/integral-sweep.mjs +126 -0
- package/dist/physics/kinematics/equations.d.mts +47 -0
- package/dist/physics/kinematics/equations.mjs +126 -0
- package/dist/physics/kinematics/free-fall.d.mts +53 -0
- package/dist/physics/kinematics/free-fall.mjs +71 -0
- package/dist/physics/kinematics/given.d.mts +16 -0
- package/dist/physics/kinematics/given.mjs +55 -0
- package/dist/physics/kinematics/roots.d.mts +41 -0
- package/dist/physics/kinematics/roots.mjs +72 -0
- package/dist/physics/kinematics/solve.d.mts +52 -0
- package/dist/physics/kinematics/solve.mjs +172 -0
- package/dist/physics/magnetism/biot-savart.d.mts +161 -0
- package/dist/physics/magnetism/biot-savart.mjs +353 -0
- package/dist/physics/magnetism/flux.d.mts +94 -0
- package/dist/physics/magnetism/flux.mjs +313 -0
- package/dist/physics/magnetism/geomagnetism.d.mts +151 -0
- package/dist/physics/magnetism/geomagnetism.mjs +184 -0
- package/dist/physics/magnetism/index.d.mts +12 -0
- package/dist/physics/magnetism/index.mjs +74 -0
- package/dist/physics/magnetism/lorentz.d.mts +28 -0
- package/dist/physics/magnetism/lorentz.mjs +88 -0
- package/dist/physics/magnetism/magnetic-force.d.mts +102 -0
- package/dist/physics/magnetism/magnetic-force.mjs +169 -0
- package/dist/physics/momentum/collide.d.mts +77 -0
- package/dist/physics/momentum/collide.mjs +115 -0
- package/dist/physics/momentum/impulse.d.mts +89 -0
- package/dist/physics/momentum/impulse.mjs +159 -0
- package/dist/physics/momentum/planar.d.mts +60 -0
- package/dist/physics/momentum/planar.mjs +110 -0
- package/dist/physics/projectile.d.mts +107 -0
- package/dist/physics/projectile.mjs +185 -0
- package/dist/physics/rotation/angular.d.mts +85 -0
- package/dist/physics/rotation/angular.mjs +130 -0
- package/dist/physics/rotation/circular.d.mts +79 -0
- package/dist/physics/rotation/circular.mjs +131 -0
- package/dist/physics/rotation/inertia.d.mts +164 -0
- package/dist/physics/rotation/inertia.mjs +332 -0
- package/dist/physics/rotation/torque.d.mts +65 -0
- package/dist/physics/rotation/torque.mjs +134 -0
- package/dist/physics/vectors.d.mts +64 -0
- package/dist/physics/vectors.mjs +114 -0
- package/dist/quantity/index.d.mts +119 -0
- package/dist/quantity/index.mjs +417 -0
- package/dist/relation/derive.d.mts +74 -0
- package/dist/relation/derive.mjs +73 -0
- package/dist/relation/index.d.mts +56 -0
- package/dist/relation/index.mjs +234 -0
- package/dist/scene/evaluators.mjs +1 -1
- package/dist/sim/thermal.mjs +45 -20
- package/dist/thermo/calorimetry.d.mts +207 -0
- package/dist/thermo/calorimetry.mjs +345 -0
- package/dist/thermo/cycles.d.mts +142 -0
- package/dist/thermo/cycles.mjs +256 -0
- package/dist/thermo/entropy.d.mts +87 -0
- package/dist/thermo/entropy.mjs +140 -0
- package/dist/thermo/expansion.d.mts +130 -0
- package/dist/thermo/expansion.mjs +178 -0
- package/dist/thermo/gas.d.mts +197 -0
- package/dist/thermo/gas.mjs +279 -0
- package/dist/thermo/heat-transfer.d.mts +164 -0
- package/dist/thermo/heat-transfer.mjs +217 -0
- package/dist/thermo/index.d.mts +9 -82
- package/dist/thermo/index.mjs +9 -132
- package/dist/thermo/processes.d.mts +95 -0
- package/dist/thermo/processes.mjs +154 -0
- package/dist/thermo/temperature.d.mts +174 -0
- package/dist/thermo/temperature.mjs +236 -0
- package/dist/waves/damping.d.mts +224 -0
- package/dist/waves/damping.mjs +317 -0
- package/dist/waves/doppler.d.mts +72 -0
- package/dist/waves/doppler.mjs +125 -0
- package/dist/waves/index.d.mts +10 -0
- package/dist/waves/index.mjs +10 -0
- package/dist/waves/pendulum.d.mts +130 -0
- package/dist/waves/pendulum.mjs +211 -0
- package/dist/waves/pitch.d.mts +125 -0
- package/dist/waves/pitch.mjs +158 -0
- package/dist/waves/shm.d.mts +170 -0
- package/dist/waves/shm.mjs +279 -0
- package/dist/waves/springs.d.mts +103 -0
- package/dist/waves/springs.mjs +176 -0
- package/dist/waves/superposition.d.mts +124 -0
- package/dist/waves/superposition.mjs +196 -0
- package/dist/waves/wave.d.mts +154 -0
- package/dist/waves/wave.mjs +178 -0
- package/package.json +74 -2
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { bondsAt, otherEnd } from "./molecule.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/skeleton.ts
|
|
4
|
+
/**
|
|
5
|
+
* skeleton, the backbone of a structure and what hangs off it.
|
|
6
|
+
*
|
|
7
|
+
* WHAT THIS IS FOR. Two jobs need the same answer. Drawing a molecule needs to know which atoms form
|
|
8
|
+
* the chain that runs across the page and which are substituents hanging off it. Naming a molecule
|
|
9
|
+
* needs exactly the same thing, because an IUPAC name is the longest chain plus its branches. So the
|
|
10
|
+
* perception is done once, here, and returns atom ids rather than coordinates or syllables.
|
|
11
|
+
*
|
|
12
|
+
* ## Why the longest chain, and what "longest" means
|
|
13
|
+
*
|
|
14
|
+
* The backbone is the longest path through the skeleton, which is every carbon plus the heteroatoms
|
|
15
|
+
* the chain runs THROUGH: the oxygen in an ester link, the nitrogen in an amide. A carbonyl oxygen
|
|
16
|
+
* or a hydroxyl hangs off the side and is a substituent. Found by the standard trick for a tree:
|
|
17
|
+
* walk from any atom to the furthest one, then walk again from there, and the second walk traces the
|
|
18
|
+
* diameter.
|
|
19
|
+
*
|
|
20
|
+
* ## Rings come first
|
|
21
|
+
*
|
|
22
|
+
* A ring is not a chain and cannot be drawn as one, so a structure containing a ring reports the
|
|
23
|
+
* ring as its backbone and everything else as substituents. That is also how a chemist draws it:
|
|
24
|
+
* benzene-1,4-dicarboxylic acid is a hexagon with two groups on it, not a chain with a hexagon in
|
|
25
|
+
* the middle.
|
|
26
|
+
*
|
|
27
|
+
* ## The subset this handles, stated rather than implied
|
|
28
|
+
*
|
|
29
|
+
* One ring, and substituents that are single atoms or small terminal groups. That covers every
|
|
30
|
+
* molecule in AS and A2 organic chemistry. A fused polycyclic, or a long branch off a long branch,
|
|
31
|
+
* returns a backbone that is correct and a drawing that would be cramped, and the honest answer
|
|
32
|
+
* there is a hand-placed structure rather than a computed one.
|
|
33
|
+
*
|
|
34
|
+
* Pure: no React, no pixels.
|
|
35
|
+
*/
|
|
36
|
+
/** Heavy atoms bonded to this one. */
|
|
37
|
+
const linked = (structure, id) => bondsAt(structure, id).map((b) => otherEnd(b, id));
|
|
38
|
+
/**
|
|
39
|
+
* Atoms that lie on a ring, as one list per ring.
|
|
40
|
+
*
|
|
41
|
+
* Found by removing terminal atoms repeatedly: whatever survives is the ring system, because only a
|
|
42
|
+
* ring has no free end to peel. Simple, and right for the one-ring molecules this subset covers.
|
|
43
|
+
*/
|
|
44
|
+
function ringSystems(structure) {
|
|
45
|
+
const alive = new Set(structure.atoms.map((a) => a.id));
|
|
46
|
+
let peeled = true;
|
|
47
|
+
while (peeled) {
|
|
48
|
+
peeled = false;
|
|
49
|
+
for (const id of [...alive]) if (linked(structure, id).filter((n) => alive.has(n)).length <= 1) {
|
|
50
|
+
alive.delete(id);
|
|
51
|
+
peeled = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (alive.size === 0) return [];
|
|
55
|
+
const systems = [];
|
|
56
|
+
const seen = /* @__PURE__ */ new Set();
|
|
57
|
+
for (const id of alive) {
|
|
58
|
+
if (seen.has(id)) continue;
|
|
59
|
+
const piece = [];
|
|
60
|
+
const stack = [id];
|
|
61
|
+
seen.add(id);
|
|
62
|
+
while (stack.length) {
|
|
63
|
+
const at = stack.pop();
|
|
64
|
+
piece.push(at);
|
|
65
|
+
for (const n of linked(structure, at)) if (alive.has(n) && !seen.has(n)) {
|
|
66
|
+
seen.add(n);
|
|
67
|
+
stack.push(n);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
systems.push(piece);
|
|
71
|
+
}
|
|
72
|
+
return systems;
|
|
73
|
+
}
|
|
74
|
+
/** Walk outward from an atom, returning the furthest one and the path to it. */
|
|
75
|
+
function furthest(structure, from, only) {
|
|
76
|
+
const previous = /* @__PURE__ */ new Map();
|
|
77
|
+
const seen = new Set([from]);
|
|
78
|
+
let frontier = [from];
|
|
79
|
+
let last = from;
|
|
80
|
+
while (frontier.length) {
|
|
81
|
+
const next = [];
|
|
82
|
+
for (const at of frontier) {
|
|
83
|
+
last = at;
|
|
84
|
+
for (const n of linked(structure, at)) {
|
|
85
|
+
if (!only.has(n) || seen.has(n)) continue;
|
|
86
|
+
seen.add(n);
|
|
87
|
+
previous.set(n, at);
|
|
88
|
+
next.push(n);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (next.length) frontier = next;
|
|
92
|
+
else break;
|
|
93
|
+
}
|
|
94
|
+
const path = [last];
|
|
95
|
+
let cursor = last;
|
|
96
|
+
while (previous.has(cursor)) {
|
|
97
|
+
cursor = previous.get(cursor);
|
|
98
|
+
path.unshift(cursor);
|
|
99
|
+
}
|
|
100
|
+
return path;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The longest chain through the structure, as atom ids in order.
|
|
104
|
+
*
|
|
105
|
+
* ## Which atoms count as backbone
|
|
106
|
+
*
|
|
107
|
+
* Every carbon, plus any heteroatom the chain RUNS THROUGH. The ester oxygen and the amide nitrogen
|
|
108
|
+
* are in the middle of a chain and are drawn in it; a carbonyl oxygen, a hydroxyl, an amine or a
|
|
109
|
+
* halogen hangs off the side and is drawn as a substituent.
|
|
110
|
+
*
|
|
111
|
+
* The test is how many bonds the atom has out into the skeleton, counting an open valence as one,
|
|
112
|
+
* because an open valence is where the chain continues into the next repeat unit. Two or more means
|
|
113
|
+
* the chain passes through.
|
|
114
|
+
*
|
|
115
|
+
* Restricting the walk to CARBON was the first version and it broke the thing this exists for: a
|
|
116
|
+
* nylon repeat unit is a chain of six carbons, an amide nitrogen and six more carbons, and a walk
|
|
117
|
+
* that could not cross the nitrogen reported half the molecule as one enormous substituent, which
|
|
118
|
+
* the drawing then collapsed into the label "C6H29N2".
|
|
119
|
+
*
|
|
120
|
+
* Ring atoms are excluded, because a ring is reported separately by {@link backboneOf}.
|
|
121
|
+
*/
|
|
122
|
+
function longestChain(structure) {
|
|
123
|
+
const inRing = new Set(ringSystems(structure).flat());
|
|
124
|
+
const eligible = new Set(structure.atoms.filter((a) => {
|
|
125
|
+
if (inRing.has(a.id)) return false;
|
|
126
|
+
if (a.element === "C") return true;
|
|
127
|
+
return linked(structure, a.id).length + (a.openValence ?? 0) >= 2;
|
|
128
|
+
}).map((a) => a.id));
|
|
129
|
+
if (eligible.size === 0) return [];
|
|
130
|
+
const start = [...eligible][0];
|
|
131
|
+
const firstPath = furthest(structure, start, eligible);
|
|
132
|
+
const end = firstPath[firstPath.length - 1];
|
|
133
|
+
return furthest(structure, end, eligible);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The backbone and its branches.
|
|
137
|
+
*
|
|
138
|
+
* A branch is every atom reachable from a backbone atom without going back through the backbone, so
|
|
139
|
+
* a carboxyl group on a ring comes back as three atoms attached at one vertex rather than as three
|
|
140
|
+
* unrelated neighbours.
|
|
141
|
+
*/
|
|
142
|
+
function backboneOf(structure) {
|
|
143
|
+
const ring = ringSystems(structure)[0];
|
|
144
|
+
const atoms = ring && ring.length >= 3 ? orderRing(structure, ring) : longestChain(structure);
|
|
145
|
+
const onBackbone = new Set(atoms);
|
|
146
|
+
const branches = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const id of atoms) for (const start of linked(structure, id)) {
|
|
148
|
+
if (onBackbone.has(start)) continue;
|
|
149
|
+
const group = [];
|
|
150
|
+
const seen = new Set([start, ...onBackbone]);
|
|
151
|
+
const stack = [start];
|
|
152
|
+
while (stack.length) {
|
|
153
|
+
const at = stack.pop();
|
|
154
|
+
group.push(at);
|
|
155
|
+
for (const n of linked(structure, at)) {
|
|
156
|
+
if (seen.has(n)) continue;
|
|
157
|
+
seen.add(n);
|
|
158
|
+
stack.push(n);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
branches.set(id, [...branches.get(id) ?? [], ...group]);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
atoms,
|
|
165
|
+
ring: Boolean(ring && ring.length >= 3),
|
|
166
|
+
aromatic: Boolean(ring?.every((id) => structure.atoms[id].aromatic)),
|
|
167
|
+
branches
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Put a ring's atoms in the order they are bonded, so a polygon can be walked round. */
|
|
171
|
+
function orderRing(structure, ring) {
|
|
172
|
+
const inRing = new Set(ring);
|
|
173
|
+
const ordered = [ring[0]];
|
|
174
|
+
const seen = new Set(ordered);
|
|
175
|
+
while (ordered.length < ring.length) {
|
|
176
|
+
const at = ordered[ordered.length - 1];
|
|
177
|
+
const next = linked(structure, at).find((n) => inRing.has(n) && !seen.has(n));
|
|
178
|
+
if (next === void 0) break;
|
|
179
|
+
seen.add(next);
|
|
180
|
+
ordered.push(next);
|
|
181
|
+
}
|
|
182
|
+
return ordered;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
export { backboneOf, longestChain, ringSystems };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//#region src/chem/solution.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Sparingly soluble salts: how much dissolves, and when something comes out of solution.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a solubility product exists at all
|
|
6
|
+
*
|
|
7
|
+
* A salt that barely dissolves sits in equilibrium with its own ions. Once the solid is there, more
|
|
8
|
+
* solid changes nothing about the solution, so the equilibrium constant contains only the ions:
|
|
9
|
+
*
|
|
10
|
+
* MpAq(s) ⇌ p M^q+(aq) + q A^p-(aq) Ksp = [M]^p [A]^q
|
|
11
|
+
*
|
|
12
|
+
* That is the whole definition, and everything else here follows from it plus counting.
|
|
13
|
+
*
|
|
14
|
+
* ## Why Ksp values cannot be compared directly, which is the standard trap
|
|
15
|
+
*
|
|
16
|
+
* Silver chloride has a Ksp of 1.8e-10 and silver chromate 1.1e-12, so the chromate looks a hundred
|
|
17
|
+
* times less soluble. It is roughly seven times MORE soluble. The numbers are not comparable
|
|
18
|
+
* because they are not the same kind of number: one is a product of two concentrations and the
|
|
19
|
+
* other of three, so they have different units and different powers.
|
|
20
|
+
*
|
|
21
|
+
* The only honest comparison is between SOLUBILITIES, which is what {@link solubilityFromKsp}
|
|
22
|
+
* computes, and it is computed rather than tabulated so the comparison can be made for any pair.
|
|
23
|
+
*
|
|
24
|
+
* ## Precipitation is a comparison, not a rule
|
|
25
|
+
*
|
|
26
|
+
* Mixing two solutions does not ask whether a salt is "insoluble". It asks whether the ions that
|
|
27
|
+
* are now present exceed what the solution can hold: form the same product from the ACTUAL
|
|
28
|
+
* concentrations and compare it with Ksp. Above it, solid appears until the product falls back;
|
|
29
|
+
* below it, nothing happens however insoluble the salt is said to be.
|
|
30
|
+
*
|
|
31
|
+
* Pure: no React, no pixels.
|
|
32
|
+
*/
|
|
33
|
+
/** A salt as its stoichiometry: p cations of charge q+, q anions of charge p-. */
|
|
34
|
+
interface SaltFormula {
|
|
35
|
+
readonly formula: string;
|
|
36
|
+
/** Cations per formula unit. */
|
|
37
|
+
readonly cations: number;
|
|
38
|
+
/** Anions per formula unit. */
|
|
39
|
+
readonly anions: number;
|
|
40
|
+
}
|
|
41
|
+
interface SparinglySoluble extends SaltFormula {
|
|
42
|
+
/** Solubility product at 298 K. Units depend on the stoichiometry, which is the point. */
|
|
43
|
+
readonly ksp: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Solubility products at 298 K, from standard tables.
|
|
47
|
+
*
|
|
48
|
+
* Deliberately a mixed set of stoichiometries. A table of nothing but 1:1 salts would let the
|
|
49
|
+
* comparison trap below stay invisible, which is the one thing this data is most useful for.
|
|
50
|
+
*/
|
|
51
|
+
declare const SOLUBILITY_PRODUCTS: readonly SparinglySoluble[];
|
|
52
|
+
declare const saltByFormula: (formula: string) => SparinglySoluble | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* The solubility product a given solubility implies.
|
|
55
|
+
*
|
|
56
|
+
* Dissolving S moles of MpAq gives pS of the cation and qS of the anion, so the product is
|
|
57
|
+
* (pS)^p (qS)^q. The powers and the multipliers are both there, and dropping either is the usual
|
|
58
|
+
* way this goes wrong: silver chromate gives (2S)²(S), not S³ and not (2S)(S).
|
|
59
|
+
*/
|
|
60
|
+
declare function kspFromSolubility(salt: SaltFormula, solubility: number): number;
|
|
61
|
+
/**
|
|
62
|
+
* The solubility a solubility product implies, in mol per cubic decimetre.
|
|
63
|
+
*
|
|
64
|
+
* The inverse of the above, solved rather than rearranged by hand, because the rearrangement is a
|
|
65
|
+
* different expression for every stoichiometry:
|
|
66
|
+
*
|
|
67
|
+
* Ksp = p^p q^q S^(p+q) so S = (Ksp / (p^p q^q))^(1/(p+q))
|
|
68
|
+
*/
|
|
69
|
+
declare function solubilityFromKsp(salt: SparinglySoluble): number;
|
|
70
|
+
/**
|
|
71
|
+
* The ionic product for concentrations that are actually present.
|
|
72
|
+
*
|
|
73
|
+
* The same expression as Ksp and a different quantity: Ksp is what a SATURATED solution has, this
|
|
74
|
+
* is what THIS solution has. Comparing them is the whole of precipitation prediction, and calling
|
|
75
|
+
* both of them Ksp is why that comparison so often gets skipped.
|
|
76
|
+
*/
|
|
77
|
+
declare function ionicProduct(salt: SaltFormula, cation: number, anion: number): number;
|
|
78
|
+
type PrecipitationVerdict = 'precipitate' | 'saturated' | 'stays dissolved';
|
|
79
|
+
interface PrecipitationCheck {
|
|
80
|
+
readonly ionicProduct: number;
|
|
81
|
+
readonly ksp: number;
|
|
82
|
+
readonly verdict: PrecipitationVerdict;
|
|
83
|
+
readonly because: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Whether mixing these concentrations produces a solid.
|
|
87
|
+
*
|
|
88
|
+
* Returns the reasoning, because "will it precipitate" is answered by a comparison and a learner
|
|
89
|
+
* who is handed only the verdict never sees which two numbers were compared.
|
|
90
|
+
*/
|
|
91
|
+
declare function willPrecipitate(salt: SparinglySoluble, cation: number, anion: number): PrecipitationCheck;
|
|
92
|
+
/**
|
|
93
|
+
* Solubility when one of the ions is already in the water: the common ion effect.
|
|
94
|
+
*
|
|
95
|
+
* Adding chloride to a saturated silver chloride solution pushes the equilibrium back and less
|
|
96
|
+
* silver chloride dissolves. The equilibrium itself has not changed; there is simply less room.
|
|
97
|
+
*
|
|
98
|
+
* Solved numerically rather than algebraically. With a common ion the equation is
|
|
99
|
+
* (pS + existing)^p (qS)^q = Ksp for a common cation, which is a polynomial of degree p+q with no
|
|
100
|
+
* tidy closed form beyond the simplest salts. It is monotonic in S, so bisection finds the root
|
|
101
|
+
* exactly and works for every stoichiometry rather than for the two that factorise.
|
|
102
|
+
*/
|
|
103
|
+
declare function solubilityWithCommonIon(salt: SparinglySoluble, existing: number, ion?: 'cation' | 'anion'): number;
|
|
104
|
+
/**
|
|
105
|
+
* Compare two salts by how much of each actually dissolves.
|
|
106
|
+
*
|
|
107
|
+
* The reason this exists rather than a sort on Ksp: silver chloride's 1.8e-10 against silver
|
|
108
|
+
* chromate's 1.1e-12 says the chromate is a hundred times less soluble, and it is about seven times
|
|
109
|
+
* MORE soluble. The two numbers are products of different numbers of concentrations, so they are
|
|
110
|
+
* not the same kind of quantity and comparing them is meaningless.
|
|
111
|
+
*/
|
|
112
|
+
declare function moreSoluble(a: SparinglySoluble, b: SparinglySoluble): SparinglySoluble;
|
|
113
|
+
/** Whether comparing two salts by Ksp alone would give the wrong answer. */
|
|
114
|
+
declare const kspComparisonMisleads: (a: SparinglySoluble, b: SparinglySoluble) => boolean;
|
|
115
|
+
//#endregion
|
|
116
|
+
export { PrecipitationCheck, PrecipitationVerdict, SOLUBILITY_PRODUCTS, SaltFormula, SparinglySoluble, ionicProduct, kspComparisonMisleads, kspFromSolubility, moreSoluble, saltByFormula, solubilityFromKsp, solubilityWithCommonIon, willPrecipitate };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//#region src/chem/solution.ts
|
|
2
|
+
/**
|
|
3
|
+
* Solubility products at 298 K, from standard tables.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately a mixed set of stoichiometries. A table of nothing but 1:1 salts would let the
|
|
6
|
+
* comparison trap below stay invisible, which is the one thing this data is most useful for.
|
|
7
|
+
*/
|
|
8
|
+
const SOLUBILITY_PRODUCTS = [
|
|
9
|
+
{
|
|
10
|
+
formula: "AgCl",
|
|
11
|
+
cations: 1,
|
|
12
|
+
anions: 1,
|
|
13
|
+
ksp: 18e-11
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
formula: "AgBr",
|
|
17
|
+
cations: 1,
|
|
18
|
+
anions: 1,
|
|
19
|
+
ksp: 54e-14
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
formula: "AgI",
|
|
23
|
+
cations: 1,
|
|
24
|
+
anions: 1,
|
|
25
|
+
ksp: 85e-18
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
formula: "BaSO4",
|
|
29
|
+
cations: 1,
|
|
30
|
+
anions: 1,
|
|
31
|
+
ksp: 11e-11
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
formula: "CaSO4",
|
|
35
|
+
cations: 1,
|
|
36
|
+
anions: 1,
|
|
37
|
+
ksp: 49e-6
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
formula: "CaCO3",
|
|
41
|
+
cations: 1,
|
|
42
|
+
anions: 1,
|
|
43
|
+
ksp: 3.3e-9
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
formula: "PbSO4",
|
|
47
|
+
cations: 1,
|
|
48
|
+
anions: 1,
|
|
49
|
+
ksp: 25e-9
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
formula: "Ag2CrO4",
|
|
53
|
+
cations: 2,
|
|
54
|
+
anions: 1,
|
|
55
|
+
ksp: 11e-13
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
formula: "PbCl2",
|
|
59
|
+
cations: 1,
|
|
60
|
+
anions: 2,
|
|
61
|
+
ksp: 17e-6
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
formula: "PbI2",
|
|
65
|
+
cations: 1,
|
|
66
|
+
anions: 2,
|
|
67
|
+
ksp: 9.8e-9
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
formula: "Mg(OH)2",
|
|
71
|
+
cations: 1,
|
|
72
|
+
anions: 2,
|
|
73
|
+
ksp: 56e-13
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
formula: "Ca(OH)2",
|
|
77
|
+
cations: 1,
|
|
78
|
+
anions: 2,
|
|
79
|
+
ksp: 55e-7
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
formula: "Fe(OH)3",
|
|
83
|
+
cations: 1,
|
|
84
|
+
anions: 3,
|
|
85
|
+
ksp: 26e-40
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
const saltByFormula = (formula) => SOLUBILITY_PRODUCTS.find((s) => s.formula === formula);
|
|
89
|
+
/**
|
|
90
|
+
* The solubility product a given solubility implies.
|
|
91
|
+
*
|
|
92
|
+
* Dissolving S moles of MpAq gives pS of the cation and qS of the anion, so the product is
|
|
93
|
+
* (pS)^p (qS)^q. The powers and the multipliers are both there, and dropping either is the usual
|
|
94
|
+
* way this goes wrong: silver chromate gives (2S)²(S), not S³ and not (2S)(S).
|
|
95
|
+
*/
|
|
96
|
+
function kspFromSolubility(salt, solubility) {
|
|
97
|
+
if (solubility < 0) throw new Error("a solubility cannot be negative");
|
|
98
|
+
const { cations: p, anions: q } = salt;
|
|
99
|
+
return (p * solubility) ** p * (q * solubility) ** q;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* The solubility a solubility product implies, in mol per cubic decimetre.
|
|
103
|
+
*
|
|
104
|
+
* The inverse of the above, solved rather than rearranged by hand, because the rearrangement is a
|
|
105
|
+
* different expression for every stoichiometry:
|
|
106
|
+
*
|
|
107
|
+
* Ksp = p^p q^q S^(p+q) so S = (Ksp / (p^p q^q))^(1/(p+q))
|
|
108
|
+
*/
|
|
109
|
+
function solubilityFromKsp(salt) {
|
|
110
|
+
const { cations: p, anions: q, ksp } = salt;
|
|
111
|
+
return (ksp / (p ** p * q ** q)) ** (1 / (p + q));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The ionic product for concentrations that are actually present.
|
|
115
|
+
*
|
|
116
|
+
* The same expression as Ksp and a different quantity: Ksp is what a SATURATED solution has, this
|
|
117
|
+
* is what THIS solution has. Comparing them is the whole of precipitation prediction, and calling
|
|
118
|
+
* both of them Ksp is why that comparison so often gets skipped.
|
|
119
|
+
*/
|
|
120
|
+
function ionicProduct(salt, cation, anion) {
|
|
121
|
+
if (cation < 0 || anion < 0) throw new Error("a concentration cannot be negative");
|
|
122
|
+
return cation ** salt.cations * anion ** salt.anions;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Whether mixing these concentrations produces a solid.
|
|
126
|
+
*
|
|
127
|
+
* Returns the reasoning, because "will it precipitate" is answered by a comparison and a learner
|
|
128
|
+
* who is handed only the verdict never sees which two numbers were compared.
|
|
129
|
+
*/
|
|
130
|
+
function willPrecipitate(salt, cation, anion) {
|
|
131
|
+
const product = ionicProduct(salt, cation, anion);
|
|
132
|
+
const ratio = product / salt.ksp;
|
|
133
|
+
const verdict = ratio > 1.0001 ? "precipitate" : ratio < .9999 ? "stays dissolved" : "saturated";
|
|
134
|
+
const because = verdict === "precipitate" ? `the ions present give an ionic product of ${product.toExponential(2)}, above the ${salt.ksp.toExponential(2)} the solution can hold, so solid ${salt.formula} appears until the product falls back to it` : verdict === "stays dissolved" ? `the ions present give an ionic product of ${product.toExponential(2)}, below the ${salt.ksp.toExponential(2)} the solution can hold, so nothing comes out however insoluble ${salt.formula} is said to be` : `the ionic product has reached ${salt.ksp.toExponential(2)} exactly, so the solution is saturated and on the point of depositing solid`;
|
|
135
|
+
return {
|
|
136
|
+
ionicProduct: product,
|
|
137
|
+
ksp: salt.ksp,
|
|
138
|
+
verdict,
|
|
139
|
+
because
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Solubility when one of the ions is already in the water: the common ion effect.
|
|
144
|
+
*
|
|
145
|
+
* Adding chloride to a saturated silver chloride solution pushes the equilibrium back and less
|
|
146
|
+
* silver chloride dissolves. The equilibrium itself has not changed; there is simply less room.
|
|
147
|
+
*
|
|
148
|
+
* Solved numerically rather than algebraically. With a common ion the equation is
|
|
149
|
+
* (pS + existing)^p (qS)^q = Ksp for a common cation, which is a polynomial of degree p+q with no
|
|
150
|
+
* tidy closed form beyond the simplest salts. It is monotonic in S, so bisection finds the root
|
|
151
|
+
* exactly and works for every stoichiometry rather than for the two that factorise.
|
|
152
|
+
*/
|
|
153
|
+
function solubilityWithCommonIon(salt, existing, ion = "anion") {
|
|
154
|
+
if (existing < 0) throw new Error("an existing concentration cannot be negative");
|
|
155
|
+
if (existing === 0) return solubilityFromKsp(salt);
|
|
156
|
+
const { cations: p, anions: q, ksp } = salt;
|
|
157
|
+
const productAt = (s) => {
|
|
158
|
+
const cation = p * s + (ion === "cation" ? existing : 0);
|
|
159
|
+
const anion = q * s + (ion === "anion" ? existing : 0);
|
|
160
|
+
return cation ** p * anion ** q;
|
|
161
|
+
};
|
|
162
|
+
let low = 0;
|
|
163
|
+
let high = solubilityFromKsp(salt);
|
|
164
|
+
for (let i = 0; i < 200; i += 1) {
|
|
165
|
+
const mid = (low + high) / 2;
|
|
166
|
+
if (productAt(mid) > ksp) high = mid;
|
|
167
|
+
else low = mid;
|
|
168
|
+
}
|
|
169
|
+
return (low + high) / 2;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Compare two salts by how much of each actually dissolves.
|
|
173
|
+
*
|
|
174
|
+
* The reason this exists rather than a sort on Ksp: silver chloride's 1.8e-10 against silver
|
|
175
|
+
* chromate's 1.1e-12 says the chromate is a hundred times less soluble, and it is about seven times
|
|
176
|
+
* MORE soluble. The two numbers are products of different numbers of concentrations, so they are
|
|
177
|
+
* not the same kind of quantity and comparing them is meaningless.
|
|
178
|
+
*/
|
|
179
|
+
function moreSoluble(a, b) {
|
|
180
|
+
return solubilityFromKsp(a) >= solubilityFromKsp(b) ? a : b;
|
|
181
|
+
}
|
|
182
|
+
/** Whether comparing two salts by Ksp alone would give the wrong answer. */
|
|
183
|
+
const kspComparisonMisleads = (a, b) => a.cations + a.anions !== b.cations + b.anions && a.ksp < b.ksp !== solubilityFromKsp(a) < solubilityFromKsp(b);
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
export { SOLUBILITY_PRODUCTS, ionicProduct, kspComparisonMisleads, kspFromSolubility, moreSoluble, saltByFormula, solubilityFromKsp, solubilityWithCommonIon, willPrecipitate };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parseFormula } from "./formula.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/species.d.ts
|
|
4
|
+
interface Bonded {
|
|
5
|
+
readonly element: string;
|
|
6
|
+
readonly count: number;
|
|
7
|
+
}
|
|
8
|
+
/** A central atom and everything bonded to it: the unit a shape question is asked about. */
|
|
9
|
+
interface Centre {
|
|
10
|
+
/** As written, without the charge. */
|
|
11
|
+
readonly formula: string;
|
|
12
|
+
readonly central: string;
|
|
13
|
+
readonly bonded: readonly Bonded[];
|
|
14
|
+
/** Overall charge: +1 for an ammonium ion, -2 for a sulfate ion. */
|
|
15
|
+
readonly charge: number;
|
|
16
|
+
/** Total number of atoms bonded to the central atom. */
|
|
17
|
+
readonly bondedCount: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A formula split into its atom counts and its charge.
|
|
21
|
+
*
|
|
22
|
+
* The step before deciding anything about structure, shared by the shape engine, which wants a
|
|
23
|
+
* central atom, and the molecular orbital engine, which wants two atoms and no centre at all.
|
|
24
|
+
*/
|
|
25
|
+
declare const readFormula: (input: string) => {
|
|
26
|
+
counts: {
|
|
27
|
+
element: string;
|
|
28
|
+
count: number;
|
|
29
|
+
}[];
|
|
30
|
+
charge: number;
|
|
31
|
+
};
|
|
32
|
+
interface CentreOptions {
|
|
33
|
+
/** Name the central atom instead of letting it be inferred. */
|
|
34
|
+
readonly central?: string;
|
|
35
|
+
/** Overall charge, if it is not written into the formula. */
|
|
36
|
+
readonly charge?: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Read a formula into a central atom and its ligands.
|
|
40
|
+
*
|
|
41
|
+
* Accepts the charge written in ("NH4+", "SO4^2-", "SO4 2-") or passed separately.
|
|
42
|
+
*/
|
|
43
|
+
declare function parseCentre(input: string, options?: CentreOptions): Centre;
|
|
44
|
+
/** The species written out again, charge included: useful for a label. */
|
|
45
|
+
declare function formatCentre(centre: Centre): string;
|
|
46
|
+
//#endregion
|
|
47
|
+
export { Bonded, Centre, CentreOptions, formatCentre, parseCentre, readFormula };
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { parseFormula } from "./formula.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/species.ts
|
|
4
|
+
/**
|
|
5
|
+
* A species written as a formula, parsed into a central atom and what surrounds it.
|
|
6
|
+
*
|
|
7
|
+
* ## Why a formula and not a graph
|
|
8
|
+
*
|
|
9
|
+
* The molecule engine takes SMILES and builds a graph, which is right for organic chemistry where
|
|
10
|
+
* the skeleton is the question. Shape questions are not like that. They are asked about a single
|
|
11
|
+
* central atom with things around it, written as a formula: what shape is XeF4, what is the bond
|
|
12
|
+
* angle in NH3, why is SF4 not tetrahedral. Making an author write a graph for those would be
|
|
13
|
+
* ceremony, and the formula already contains everything the answer needs.
|
|
14
|
+
*
|
|
15
|
+
* So this parses the formula directly, and the two front ends stay separate rather than one being
|
|
16
|
+
* forced through the other.
|
|
17
|
+
*
|
|
18
|
+
* ## Choosing the central atom
|
|
19
|
+
*
|
|
20
|
+
* The rule used here is the one that works in practice: the central atom is the non-hydrogen element
|
|
21
|
+
* that appears fewest times, and where two tie, the one written first. That picks O out of H2O, S
|
|
22
|
+
* out of SO4, Xe out of XeF4 and Cl out of ClF3. A caller who disagrees can name the central atom
|
|
23
|
+
* instead of having it inferred.
|
|
24
|
+
*
|
|
25
|
+
* ## What this refuses, and why refusing is the right answer
|
|
26
|
+
*
|
|
27
|
+
* Hydrogen alongside other ligands. In H2SO4 the hydrogens are bonded to oxygens, not to the
|
|
28
|
+
* sulfur, so treating them as ligands on the central atom would produce a confident answer to a
|
|
29
|
+
* question nobody asked. The formula alone cannot say where a hydrogen sits, so the parse stops and
|
|
30
|
+
* says so rather than guessing. H2O, NH3 and NH4+ are unaffected: there, hydrogen is the only
|
|
31
|
+
* ligand and there is nowhere else for it to be.
|
|
32
|
+
*
|
|
33
|
+
* Pure: no React, no pixels.
|
|
34
|
+
*/
|
|
35
|
+
/**
|
|
36
|
+
* An element bonded to the centre, and how many of it there are.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately not called a ligand: `Ligand` in this kernel already means a named donor in a
|
|
39
|
+
* coordination complex, which carries a charge and a denticity and an IUPAC prefix. This is a
|
|
40
|
+
* plainer thing, one element bonded to a central atom, and giving the two the same name would
|
|
41
|
+
* suggest they were interchangeable.
|
|
42
|
+
*/
|
|
43
|
+
/** An explicit charge: separated from the formula by a caret or a space, so its digits are its own. */
|
|
44
|
+
const EXPLICIT_CHARGE = /(?:\^|\s+)(\d*)\s*([+-])\s*$/;
|
|
45
|
+
/** A bare sign at the end, which is a charge of one. */
|
|
46
|
+
const BARE_CHARGE = /([+-])\s*$/;
|
|
47
|
+
/**
|
|
48
|
+
* Split a trailing charge off a formula.
|
|
49
|
+
*
|
|
50
|
+
* A number before the sign is ambiguous and cannot be resolved by reading alone: the 4 in NH4+ is a
|
|
51
|
+
* subscript on the hydrogen and the 3 in Fe3+ is the charge, and nothing in the text distinguishes
|
|
52
|
+
* them. Guessing produced an ammonium ion with a charge of four.
|
|
53
|
+
*
|
|
54
|
+
* So the contract is explicit rather than clever. A bare sign means one: "NH4+" is the ammonium ion.
|
|
55
|
+
* Any other size is written with a caret or a space: "SO4^2-" or "SO4 2-". A charge can also be
|
|
56
|
+
* passed separately, which is the unambiguous form for anything generated rather than typed.
|
|
57
|
+
*/
|
|
58
|
+
function splitCharge(input) {
|
|
59
|
+
const text = input.trim();
|
|
60
|
+
const explicit = EXPLICIT_CHARGE.exec(text);
|
|
61
|
+
if (explicit) {
|
|
62
|
+
const size = explicit[1] ? Number(explicit[1]) : 1;
|
|
63
|
+
return {
|
|
64
|
+
body: text.slice(0, explicit.index).trim(),
|
|
65
|
+
charge: explicit[2] === "-" ? -size : size
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const bare = BARE_CHARGE.exec(text);
|
|
69
|
+
if (bare) return {
|
|
70
|
+
body: text.slice(0, bare.index).trim(),
|
|
71
|
+
charge: bare[1] === "-" ? -1 : 1
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
body: text.replace(/\s+/g, ""),
|
|
75
|
+
charge: 0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A formula split into its atom counts and its charge.
|
|
80
|
+
*
|
|
81
|
+
* The step before deciding anything about structure, shared by the shape engine, which wants a
|
|
82
|
+
* central atom, and the molecular orbital engine, which wants two atoms and no centre at all.
|
|
83
|
+
*/
|
|
84
|
+
const readFormula = (input) => {
|
|
85
|
+
const { body, charge } = splitCharge(input);
|
|
86
|
+
return {
|
|
87
|
+
counts: parseFormula(body),
|
|
88
|
+
charge
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Read a formula into a central atom and its ligands.
|
|
93
|
+
*
|
|
94
|
+
* Accepts the charge written in ("NH4+", "SO4^2-", "SO4 2-") or passed separately.
|
|
95
|
+
*/
|
|
96
|
+
function parseCentre(input, options = {}) {
|
|
97
|
+
const { body, charge: written } = splitCharge(input);
|
|
98
|
+
const charge = options.charge ?? written;
|
|
99
|
+
const counts = parseFormula(body);
|
|
100
|
+
const heavy = counts.filter((c) => c.element !== "H");
|
|
101
|
+
if (heavy.length === 0) throw new Error(`"${body}" is hydrogen only and has no central atom`);
|
|
102
|
+
const central = options.central ?? heavy.reduce((best, c) => c.count < best.count ? c : best, heavy[0]).element;
|
|
103
|
+
if (!counts.some((c) => c.element === central)) throw new Error(`"${central}" does not appear in ${body}`);
|
|
104
|
+
const centralCount = counts.find((c) => c.element === central).count;
|
|
105
|
+
if (centralCount !== 1) throw new Error(`${body} has ${centralCount} ${central} atoms, so it has no single central atom: this engine describes one centre at a time`);
|
|
106
|
+
const bonded = counts.filter((c) => c.element !== central).map((c) => ({
|
|
107
|
+
element: c.element,
|
|
108
|
+
count: c.count
|
|
109
|
+
}));
|
|
110
|
+
if (bonded.length === 0) throw new Error(`${body} is a single atom and has no shape`);
|
|
111
|
+
if (bonded.some((l) => l.element === "H") && bonded.length > 1) throw new Error(`${body} has hydrogen alongside other ligands, and a formula cannot say which atom the hydrogen is bonded to. In an oxoacid it is bonded to an oxygen, not to the central atom. Name the central atom and give the ligands directly if you mean something else.`);
|
|
112
|
+
return {
|
|
113
|
+
formula: body,
|
|
114
|
+
central,
|
|
115
|
+
bonded,
|
|
116
|
+
charge,
|
|
117
|
+
bondedCount: bonded.reduce((total, l) => total + l.count, 0)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** The species written out again, charge included: useful for a label. */
|
|
121
|
+
function formatCentre(centre) {
|
|
122
|
+
if (centre.charge === 0) return centre.formula;
|
|
123
|
+
const size = Math.abs(centre.charge) === 1 ? "" : String(Math.abs(centre.charge));
|
|
124
|
+
return `${centre.formula}${size}${centre.charge < 0 ? "-" : "+"}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
//#endregion
|
|
128
|
+
export { formatCentre, parseCentre, readFormula };
|