@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,179 @@
|
|
|
1
|
+
import { ATOMIC_MASS, molecularFormula, relativeMass } from "./molecule.mjs";
|
|
2
|
+
import { functionalGroups } from "./functional-groups.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/chem/polymer.ts
|
|
5
|
+
/**
|
|
6
|
+
* polymer, the repeat unit worked out from the monomer rather than drawn beside it.
|
|
7
|
+
*
|
|
8
|
+
* WHAT THIS IS FOR. Polymerisation is taught as two pictures to copy: an alkene with its double bond
|
|
9
|
+
* opened, and two monomers with a water molecule falling out between them. Copied, they are
|
|
10
|
+
* indistinguishable, and a learner who has copied both still cannot say which one a new monomer will
|
|
11
|
+
* do. The question an exam actually asks is "draw the repeat unit", and that is a transformation of
|
|
12
|
+
* a structure, so it can be performed.
|
|
13
|
+
*
|
|
14
|
+
* ## Addition: the double bond becomes the link
|
|
15
|
+
*
|
|
16
|
+
* An alkene's C=C opens. One bond stays holding the two carbons together, and the other becomes the
|
|
17
|
+
* two bonds that join this unit to the next. So the repeat unit has the SAME molecular formula as
|
|
18
|
+
* the monomer, which is the fact that tells addition from condensation without looking at anything
|
|
19
|
+
* else.
|
|
20
|
+
*
|
|
21
|
+
* ## Condensation: two groups meet and lose a small molecule
|
|
22
|
+
*
|
|
23
|
+
* A carboxylic acid meets an alcohol and loses water, leaving an ester link. It meets an amine and
|
|
24
|
+
* loses water, leaving an amide link, which is also the peptide link in a protein. The repeat unit
|
|
25
|
+
* is therefore LIGHTER than the monomers by the mass of what left, and each monomer needs TWO
|
|
26
|
+
* reactive groups or the chain stops after one join.
|
|
27
|
+
*
|
|
28
|
+
* That last point is the one usually left out, and it is what makes the difference between ethanoic
|
|
29
|
+
* acid, which cannot polymerise, and hexanedioic acid, which can.
|
|
30
|
+
*
|
|
31
|
+
* Pure: no React, no pixels.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Open the double bond of an alkene to give the addition repeat unit.
|
|
35
|
+
*
|
|
36
|
+
* Refuses a monomer with no C=C, because "it must be addition" is a guess a learner makes and a
|
|
37
|
+
* kernel should not. The formula is unchanged by construction, and the test asserts it.
|
|
38
|
+
*/
|
|
39
|
+
function addition(monomer) {
|
|
40
|
+
const alkene = functionalGroups(monomer).find((g) => g.kind === "alkene");
|
|
41
|
+
if (!alkene) throw new Error(`${molecularFormula(monomer)} has no C=C, so it cannot polymerise by addition. Addition polymerisation needs a double bond to open.`);
|
|
42
|
+
const [a, b] = alkene.atoms;
|
|
43
|
+
const bonds = monomer.bonds.map((bond) => bond.a === a && bond.b === b || bond.a === b && bond.b === a ? {
|
|
44
|
+
...bond,
|
|
45
|
+
order: 1
|
|
46
|
+
} : bond);
|
|
47
|
+
const structure = {
|
|
48
|
+
atoms: monomer.atoms.map((atom) => atom.id === a || atom.id === b ? {
|
|
49
|
+
...atom,
|
|
50
|
+
openValence: (atom.openValence ?? 0) + 1
|
|
51
|
+
} : atom),
|
|
52
|
+
bonds
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
kind: "addition",
|
|
56
|
+
link: "carbon-carbon",
|
|
57
|
+
structure,
|
|
58
|
+
linkAtoms: [a, b],
|
|
59
|
+
formula: molecularFormula(structure),
|
|
60
|
+
mass: relativeMass(structure),
|
|
61
|
+
reason: "The C=C opens. One of its two bonds holds the carbons together and the other becomes the bonds to the units on either side, so nothing is lost and the repeat unit has the same formula as the monomer."
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Groups that can form a link, and what they form it with. */
|
|
65
|
+
const REACTIVE = [
|
|
66
|
+
"carboxylic-acid",
|
|
67
|
+
"alcohol",
|
|
68
|
+
"amine"
|
|
69
|
+
];
|
|
70
|
+
const reactiveGroups = (s) => functionalGroups(s).filter((g) => REACTIVE.includes(g.kind));
|
|
71
|
+
/**
|
|
72
|
+
* Can this monomer sustain a chain?
|
|
73
|
+
*
|
|
74
|
+
* Two reactive groups, or the chain stops after one join. Ethanoic acid has one and is therefore a
|
|
75
|
+
* dead end; hexanedioic acid has two and is not. This is the check most treatments leave out and
|
|
76
|
+
* the reason a learner cannot predict whether a new monomer will polymerise.
|
|
77
|
+
*/
|
|
78
|
+
const canSustainChain = (monomer) => reactiveGroups(monomer).length >= 2;
|
|
79
|
+
const WATER = {
|
|
80
|
+
formula: "H2O",
|
|
81
|
+
mass: ATOMIC_MASS.H * 2 + ATOMIC_MASS.O
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* Join two monomers and eliminate water, giving the condensation repeat unit.
|
|
85
|
+
*
|
|
86
|
+
* The pairing is decided by the groups that are actually present, not by an argument the caller
|
|
87
|
+
* passes: an acid with an alcohol gives an ester link and an acid with an amine gives an amide link,
|
|
88
|
+
* and those are the only two the syllabus needs. A pair that cannot make either is refused with the
|
|
89
|
+
* reason, because "nothing happened" is not a useful answer to a learner.
|
|
90
|
+
*/
|
|
91
|
+
function condensation(first, second) {
|
|
92
|
+
const a = reactiveGroups(first);
|
|
93
|
+
const b = reactiveGroups(second);
|
|
94
|
+
const kindsA = new Set(a.map((g) => g.kind));
|
|
95
|
+
const kindsB = new Set(b.map((g) => g.kind));
|
|
96
|
+
const pairing = [[
|
|
97
|
+
"carboxylic-acid",
|
|
98
|
+
"alcohol",
|
|
99
|
+
"ester"
|
|
100
|
+
], [
|
|
101
|
+
"carboxylic-acid",
|
|
102
|
+
"amine",
|
|
103
|
+
"amide"
|
|
104
|
+
]].find(([x, y]) => kindsA.has(x) && kindsB.has(y) || kindsA.has(y) && kindsB.has(x));
|
|
105
|
+
if (!pairing) throw new Error(`${molecularFormula(first)} and ${molecularFormula(second)} have no pair of groups that can condense. An ester link needs an acid and an alcohol; an amide link needs an acid and an amine.`);
|
|
106
|
+
const [, , link] = pairing;
|
|
107
|
+
if (!canSustainChain(first) || !canSustainChain(second)) throw new Error(`A condensation polymer needs TWO reactive groups on each monomer. ${canSustainChain(first) ? molecularFormula(second) : molecularFormula(first)} has only one, so the chain would stop after a single join.`);
|
|
108
|
+
const acids = a.filter((g) => g.kind === "carboxylic-acid");
|
|
109
|
+
const partners = b.filter((g) => g.kind === (link === "ester" ? "alcohol" : "amine"));
|
|
110
|
+
if (acids.length < 2 || partners.length < 2) throw new Error("A repeat unit needs two acid groups and two partner groups, one pair to link and one pair to continue the chain.");
|
|
111
|
+
/** The atom that carries the new bond: the oxygen of an alcohol, or the nitrogen of an amine. */
|
|
112
|
+
const partnerAtom = (group) => group.kind === "alcohol" ? group.atoms[1] : group.atoms[0];
|
|
113
|
+
const shift = first.atoms.length;
|
|
114
|
+
const dropped = new Set([acids[0].atoms[2], acids[1].atoms[2]]);
|
|
115
|
+
const opened = new Map([[acids[1].atoms[0], 1], [partnerAtom(partners[1]) + shift, 1]]);
|
|
116
|
+
const kept = [...first.atoms.filter((atom) => !dropped.has(atom.id)), ...second.atoms.map((atom) => ({
|
|
117
|
+
...atom,
|
|
118
|
+
id: atom.id + shift
|
|
119
|
+
}))];
|
|
120
|
+
const renumber = new Map(kept.map((atom, i) => [atom.id, i]));
|
|
121
|
+
const merged = {
|
|
122
|
+
atoms: kept.map((atom, i) => ({
|
|
123
|
+
...atom,
|
|
124
|
+
id: i,
|
|
125
|
+
...opened.has(atom.id) ? { openValence: (atom.openValence ?? 0) + opened.get(atom.id) } : {}
|
|
126
|
+
})),
|
|
127
|
+
bonds: [
|
|
128
|
+
...first.bonds.filter((bond) => !dropped.has(bond.a) && !dropped.has(bond.b)),
|
|
129
|
+
...second.bonds.map((bond) => ({
|
|
130
|
+
...bond,
|
|
131
|
+
a: bond.a + shift,
|
|
132
|
+
b: bond.b + shift
|
|
133
|
+
})),
|
|
134
|
+
{
|
|
135
|
+
a: acids[0].atoms[0],
|
|
136
|
+
b: partnerAtom(partners[0]) + shift,
|
|
137
|
+
order: 1,
|
|
138
|
+
aromatic: false
|
|
139
|
+
}
|
|
140
|
+
].map((bond) => ({
|
|
141
|
+
...bond,
|
|
142
|
+
a: renumber.get(bond.a),
|
|
143
|
+
b: renumber.get(bond.b)
|
|
144
|
+
}))
|
|
145
|
+
};
|
|
146
|
+
const mass = relativeMass(merged);
|
|
147
|
+
const formula = molecularFormula(merged);
|
|
148
|
+
return {
|
|
149
|
+
kind: "condensation",
|
|
150
|
+
link,
|
|
151
|
+
structure: merged,
|
|
152
|
+
linkAtoms: [renumber.get(acids[1].atoms[0]), renumber.get(partnerAtom(partners[1]) + shift)],
|
|
153
|
+
formula,
|
|
154
|
+
mass,
|
|
155
|
+
lost: {
|
|
156
|
+
formula: WATER.formula,
|
|
157
|
+
mass: Math.round(WATER.mass * 10) / 10
|
|
158
|
+
},
|
|
159
|
+
reason: link === "ester" ? "The O-H of the acid and the O-H of the alcohol meet, water is eliminated and an ester link is left. Each monomer needs two groups, so the chain can continue at both ends." : "The O-H of the acid and an N-H of the amine meet, water is eliminated and an amide link is left. That is the same link that joins amino acids into a protein, where it is called a peptide link."
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The mass of a chain of `n` repeat units.
|
|
164
|
+
*
|
|
165
|
+
* For an addition polymer it is n times the monomer. For a condensation polymer the water has
|
|
166
|
+
* already gone from the repeat unit, so the same multiplication works, and the difference between
|
|
167
|
+
* the two shows up as a mass that is not a whole multiple of the monomers.
|
|
168
|
+
*/
|
|
169
|
+
const chainMass = (unit, n) => Math.round(unit.mass * n * 10) / 10;
|
|
170
|
+
/**
|
|
171
|
+
* How many repeat units a chain of this mass holds, to the nearest whole unit.
|
|
172
|
+
*
|
|
173
|
+
* The question an exam asks the other way round: a poly(ethene) sample with a relative molecular
|
|
174
|
+
* mass of 28 000 contains 1000 repeat units, and that number is what "long chain" means.
|
|
175
|
+
*/
|
|
176
|
+
const unitsInChain = (unit, chainMr) => Math.round(chainMr / unit.mass);
|
|
177
|
+
|
|
178
|
+
//#endregion
|
|
179
|
+
export { addition, canSustainChain, chainMass, condensation, unitsInChain };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//#region src/chem/redox.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* redox: oxidation numbers, half-equations, and the ionic equation they combine into.
|
|
4
|
+
*
|
|
5
|
+
* Three things the syllabus treats as three topics and which are one procedure applied at
|
|
6
|
+
* different scales. A learner who balances a half-equation in alkali by first balancing it in acid
|
|
7
|
+
* has understood that; one who memorises two procedures has not, which is why `medium` is an
|
|
8
|
+
* ARGUMENT here rather than a second function.
|
|
9
|
+
*
|
|
10
|
+
* ## Writing a species, and why the caret is required
|
|
11
|
+
*
|
|
12
|
+
* `'MnO4-'`, `'Cr2O7^2-'`, `'Fe^3+'`, `'H2O'`.
|
|
13
|
+
*
|
|
14
|
+
* A charge of one is a bare sign. **Any larger charge needs a caret**, because plain ASCII cannot
|
|
15
|
+
* distinguish the two readings of a digit before a sign and no syntactic rule can:
|
|
16
|
+
*
|
|
17
|
+
* `MnO4-` the 4 is a subscript, and the charge is −1
|
|
18
|
+
* `Mn2+` the 2 is the charge, and there is one manganese
|
|
19
|
+
*
|
|
20
|
+
* Both end in a letter, a digit and a sign. Only chemistry tells them apart, so an implementation
|
|
21
|
+
* that guesses is wrong roughly half the time and silently. Writing `Mn^2+` removes the question,
|
|
22
|
+
* and a digit before a bare sign is refused with a message saying so.
|
|
23
|
+
*
|
|
24
|
+
* `speciesToUnicode` renders `Mn^2+` as `Mn²⁺` for display, so the caret never reaches a learner.
|
|
25
|
+
*
|
|
26
|
+
* ## Oxidation numbers
|
|
27
|
+
*
|
|
28
|
+
* The rules are applied in order of how rarely they are broken: F is always −1, group 1 always +1,
|
|
29
|
+
* group 2 always +2, H is +1 except in a metal hydride, O is −2 except in a peroxide. Everything
|
|
30
|
+
* else is then fixed by the requirement that the oxidation numbers sum to the charge.
|
|
31
|
+
*
|
|
32
|
+
* That last line is the whole method, and it is why only ONE element may be unknown. A species with
|
|
33
|
+
* two unknowns is refused rather than guessed at, because the answer would not be determined.
|
|
34
|
+
*
|
|
35
|
+
* ## Balancing
|
|
36
|
+
*
|
|
37
|
+
* The five steps, in the order a mark scheme wants them:
|
|
38
|
+
*
|
|
39
|
+
* 1. balance the element being oxidised or reduced,
|
|
40
|
+
* 2. balance oxygen with H₂O,
|
|
41
|
+
* 3. balance hydrogen with H⁺,
|
|
42
|
+
* 4. balance the charge with electrons,
|
|
43
|
+
* 5. in ALKALI only, add OH⁻ to both sides to mop up the H⁺.
|
|
44
|
+
*
|
|
45
|
+
* Step 5 is what makes alkaline balancing one procedure rather than two: balance in acid, then
|
|
46
|
+
* neutralise. Every H⁺ meets an OH⁻ and becomes water, and the excess water cancels.
|
|
47
|
+
*
|
|
48
|
+
* Pure: no React, no pixels.
|
|
49
|
+
*/
|
|
50
|
+
type Medium = 'acid' | 'alkali';
|
|
51
|
+
interface Species {
|
|
52
|
+
/** The formula as written, without its coefficient. */
|
|
53
|
+
readonly formula: string;
|
|
54
|
+
/** Element symbol to count. */
|
|
55
|
+
readonly elements: Readonly<Record<string, number>>;
|
|
56
|
+
/** Net charge. 0 for a neutral species. */
|
|
57
|
+
readonly charge: number;
|
|
58
|
+
}
|
|
59
|
+
interface Term {
|
|
60
|
+
readonly coeff: number;
|
|
61
|
+
readonly species: Species;
|
|
62
|
+
}
|
|
63
|
+
interface HalfEquation {
|
|
64
|
+
readonly left: readonly Term[];
|
|
65
|
+
readonly right: readonly Term[];
|
|
66
|
+
/** Electrons, on the side they appear. Always positive. */
|
|
67
|
+
readonly electrons: number;
|
|
68
|
+
/** 'left' for a reduction (electrons are gained), 'right' for an oxidation. */
|
|
69
|
+
readonly electronSide: 'left' | 'right';
|
|
70
|
+
readonly medium: Medium;
|
|
71
|
+
}
|
|
72
|
+
/** Parse `'Cr2O7^2-'` into its element counts and charge. See the docblock on the caret rule. */
|
|
73
|
+
declare function parseSpecies(input: string): Species;
|
|
74
|
+
/**
|
|
75
|
+
* Oxidation number of every element in a species.
|
|
76
|
+
*
|
|
77
|
+
* `peroxide` forces O to −1, for H₂O₂ and its salts. It is an argument rather than a detection
|
|
78
|
+
* rule, because H₂O₂ and H₂O differ only in a subscript and guessing from the formula would make
|
|
79
|
+
* the kernel wrong in exactly the case the syllabus asks about.
|
|
80
|
+
*/
|
|
81
|
+
declare function oxidationNumbers(input: string | Species, opts?: {
|
|
82
|
+
peroxide?: boolean;
|
|
83
|
+
hydride?: boolean;
|
|
84
|
+
}): Record<string, number>;
|
|
85
|
+
/**
|
|
86
|
+
* `'Cr2O7^2-'` → `'Cr₂O₇²⁻'`, for display.
|
|
87
|
+
*
|
|
88
|
+
* The caret is an input convention forced by ASCII ambiguity, and a learner should never see it.
|
|
89
|
+
*
|
|
90
|
+
* ## The one place the caret is not needed
|
|
91
|
+
*
|
|
92
|
+
* After a closing bracket. `[Cu(H2O)6]2+` has no ambiguity at all, because a digit cannot be the
|
|
93
|
+
* subscript of a `]`, and that is the form `complexFormula` emits. Without this case a complex ion
|
|
94
|
+
* came back as `[Cu(H₂O)₆]₂⁺`, with its charge written as a subscript: the same class of defect as a
|
|
95
|
+
* formatter producing a string its sibling parser cannot read.
|
|
96
|
+
*/
|
|
97
|
+
declare function speciesToUnicode(formula: string): string;
|
|
98
|
+
/** Positive when oxidised, negative when reduced, for `element` between two species. */
|
|
99
|
+
declare function oxidationChange(element: string, from: string, to: string): number;
|
|
100
|
+
/**
|
|
101
|
+
* Balance one half-equation, in acid or in alkali.
|
|
102
|
+
*
|
|
103
|
+
* `balanceHalf('MnO4-', 'Mn2+')` → MnO₄⁻ + 8H⁺ + 5e⁻ → Mn²⁺ + 4H₂O.
|
|
104
|
+
*
|
|
105
|
+
* Alkali is not a second procedure: the equation is balanced in acid and then neutralised, which is
|
|
106
|
+
* exactly what a mark scheme wants written.
|
|
107
|
+
*/
|
|
108
|
+
declare function balanceHalf(from: string, to: string, medium?: Medium): HalfEquation;
|
|
109
|
+
/**
|
|
110
|
+
* "MnO₄⁻ + 8H⁺ + 5e⁻ → Mn²⁺ + 4H₂O", for display.
|
|
111
|
+
*
|
|
112
|
+
* Built from the TERMS, not by running `speciesToUnicode` over the finished string. That was tried
|
|
113
|
+
* and is wrong: a coefficient is a normal-size number and a separator is a plus sign, so formatting
|
|
114
|
+
* the whole line turns `2Cr^3+ + 7H2O` into subscripted coefficients and superscripted plus signs.
|
|
115
|
+
* Chemistry typography is per-species, so the formatter has to be too.
|
|
116
|
+
*/
|
|
117
|
+
declare function halfToUnicode(half: HalfEquation): string;
|
|
118
|
+
/** "MnO₄⁻ + 8H⁺ + 5Fe²⁺ → Mn²⁺ + 4H₂O + 5Fe³⁺", for display. */
|
|
119
|
+
declare function ionicToUnicode(equation: IonicEquation): string;
|
|
120
|
+
/** "MnO4- + 8H+ + 5e- -> Mn2+ + 4H2O" */
|
|
121
|
+
declare function halfToString(half: HalfEquation): string;
|
|
122
|
+
interface IonicEquation {
|
|
123
|
+
readonly left: readonly Term[];
|
|
124
|
+
readonly right: readonly Term[];
|
|
125
|
+
/** How many electrons were transferred once the two halves were scaled to match. */
|
|
126
|
+
readonly transferred: number;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Combine a reduction and an oxidation into the full ionic equation.
|
|
130
|
+
*
|
|
131
|
+
* Each half is scaled so the electrons cancel exactly, which is the step the whole topic exists to
|
|
132
|
+
* teach: electrons are not created, so the number lost must equal the number gained.
|
|
133
|
+
*/
|
|
134
|
+
declare function combineHalves(a: HalfEquation, b: HalfEquation): IonicEquation;
|
|
135
|
+
/** "2MnO4- + 16H+ + 10Fe2+ -> 2Mn2+ + 8H2O + 10Fe3+" */
|
|
136
|
+
declare function ionicToString(equation: IonicEquation): string;
|
|
137
|
+
//#endregion
|
|
138
|
+
export { HalfEquation, IonicEquation, Medium, Species, Term, balanceHalf, combineHalves, halfToString, halfToUnicode, ionicToString, ionicToUnicode, oxidationChange, oxidationNumbers, parseSpecies, speciesToUnicode };
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
//#region src/chem/redox.ts
|
|
2
|
+
/** Parse `'Cr2O7^2-'` into its element counts and charge. See the docblock on the caret rule. */
|
|
3
|
+
function parseSpecies(input) {
|
|
4
|
+
const formula = input.trim();
|
|
5
|
+
if (!formula) throw new Error("Empty species");
|
|
6
|
+
let body = formula;
|
|
7
|
+
let charge = 0;
|
|
8
|
+
const caret = body.lastIndexOf("^");
|
|
9
|
+
if (caret >= 0) {
|
|
10
|
+
const tail = body.slice(caret + 1);
|
|
11
|
+
const m = /^(\d*)([+-])$/.exec(tail);
|
|
12
|
+
if (!m) throw new Error(`Cannot read the charge "${tail}" in "${formula}". Write it as ^2- or ^3+.`);
|
|
13
|
+
charge = (m[1] ? Number(m[1]) : 1) * (m[2] === "+" ? 1 : -1);
|
|
14
|
+
body = body.slice(0, caret);
|
|
15
|
+
} else {
|
|
16
|
+
const m = /([+-])$/.exec(body);
|
|
17
|
+
if (m) {
|
|
18
|
+
charge = m[1] === "+" ? 1 : -1;
|
|
19
|
+
body = body.slice(0, body.length - 1);
|
|
20
|
+
if (/^[A-Z][a-z]?\d+$/.test(body)) {
|
|
21
|
+
const split = /^([A-Z][a-z]?)(\d+)$/.exec(body);
|
|
22
|
+
throw new Error(`"${formula}" is ambiguous: the ${split[2]} could be a subscript or the charge. Write it as "${split[1]}^${split[2]}${m[1]}".`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const elements = {};
|
|
27
|
+
const add = (symbol, n) => {
|
|
28
|
+
elements[symbol] = (elements[symbol] ?? 0) + n;
|
|
29
|
+
};
|
|
30
|
+
const walk = (text, multiplier) => {
|
|
31
|
+
let i = 0;
|
|
32
|
+
while (i < text.length) {
|
|
33
|
+
if (text[i] === "(") {
|
|
34
|
+
let depth = 1;
|
|
35
|
+
let j = i + 1;
|
|
36
|
+
while (j < text.length && depth > 0) {
|
|
37
|
+
if (text[j] === "(") depth++;
|
|
38
|
+
if (text[j] === ")") depth--;
|
|
39
|
+
j++;
|
|
40
|
+
}
|
|
41
|
+
if (depth !== 0) throw new Error(`Unclosed bracket in "${formula}"`);
|
|
42
|
+
const inner = text.slice(i + 1, j - 1);
|
|
43
|
+
const digits = /^\d+/.exec(text.slice(j));
|
|
44
|
+
walk(inner, multiplier * (digits ? Number(digits[0]) : 1));
|
|
45
|
+
i = j + (digits ? digits[0].length : 0);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const sym = /^[A-Z][a-z]?/.exec(text.slice(i));
|
|
49
|
+
if (!sym) throw new Error(`Cannot read "${text.slice(i)}" in "${formula}"`);
|
|
50
|
+
i += sym[0].length;
|
|
51
|
+
const digits = /^\d+/.exec(text.slice(i));
|
|
52
|
+
const count = digits ? Number(digits[0]) : 1;
|
|
53
|
+
i += digits ? digits[0].length : 0;
|
|
54
|
+
add(sym[0], count * multiplier);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
walk(body, 1);
|
|
58
|
+
return {
|
|
59
|
+
formula,
|
|
60
|
+
elements,
|
|
61
|
+
charge
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const ALWAYS = {
|
|
65
|
+
F: -1,
|
|
66
|
+
Li: 1,
|
|
67
|
+
Na: 1,
|
|
68
|
+
K: 1,
|
|
69
|
+
Rb: 1,
|
|
70
|
+
Cs: 1,
|
|
71
|
+
Be: 2,
|
|
72
|
+
Mg: 2,
|
|
73
|
+
Ca: 2,
|
|
74
|
+
Sr: 2,
|
|
75
|
+
Ba: 2
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Oxidation number of every element in a species.
|
|
79
|
+
*
|
|
80
|
+
* `peroxide` forces O to −1, for H₂O₂ and its salts. It is an argument rather than a detection
|
|
81
|
+
* rule, because H₂O₂ and H₂O differ only in a subscript and guessing from the formula would make
|
|
82
|
+
* the kernel wrong in exactly the case the syllabus asks about.
|
|
83
|
+
*/
|
|
84
|
+
function oxidationNumbers(input, opts = {}) {
|
|
85
|
+
const species = typeof input === "string" ? parseSpecies(input) : input;
|
|
86
|
+
const symbols = Object.keys(species.elements);
|
|
87
|
+
if (symbols.length === 1 && species.charge === 0) return { [symbols[0]]: 0 };
|
|
88
|
+
if (symbols.length === 1 && species.elements[symbols[0]] === 1) return { [symbols[0]]: species.charge };
|
|
89
|
+
const known = {};
|
|
90
|
+
const unknown = [];
|
|
91
|
+
for (const symbol of symbols) if (symbol in ALWAYS) known[symbol] = ALWAYS[symbol];
|
|
92
|
+
else if (symbol === "O") known.O = opts.peroxide ? -1 : -2;
|
|
93
|
+
else if (symbol === "H") known.H = opts.hydride ? -1 : 1;
|
|
94
|
+
else unknown.push(symbol);
|
|
95
|
+
if (unknown.length === 0) return known;
|
|
96
|
+
if (unknown.length > 1) throw new Error(`"${species.formula}" has ${unknown.length} elements with no fixed oxidation number (${unknown.join(", ")}), so none of them is determined. Split the species or state one.`);
|
|
97
|
+
const target = unknown[0];
|
|
98
|
+
let sum = 0;
|
|
99
|
+
for (const [symbol, value] of Object.entries(known)) sum += value * species.elements[symbol];
|
|
100
|
+
known[target] = (species.charge - sum) / species.elements[target];
|
|
101
|
+
return known;
|
|
102
|
+
}
|
|
103
|
+
const SUPER = {
|
|
104
|
+
"0": "⁰",
|
|
105
|
+
"1": "¹",
|
|
106
|
+
"2": "²",
|
|
107
|
+
"3": "³",
|
|
108
|
+
"4": "⁴",
|
|
109
|
+
"5": "⁵",
|
|
110
|
+
"6": "⁶",
|
|
111
|
+
"7": "⁷",
|
|
112
|
+
"8": "⁸",
|
|
113
|
+
"9": "⁹",
|
|
114
|
+
"+": "⁺",
|
|
115
|
+
"-": "⁻"
|
|
116
|
+
};
|
|
117
|
+
const SUB = {
|
|
118
|
+
"0": "₀",
|
|
119
|
+
"1": "₁",
|
|
120
|
+
"2": "₂",
|
|
121
|
+
"3": "₃",
|
|
122
|
+
"4": "₄",
|
|
123
|
+
"5": "₅",
|
|
124
|
+
"6": "₆",
|
|
125
|
+
"7": "₇",
|
|
126
|
+
"8": "₈",
|
|
127
|
+
"9": "₉"
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* `'Cr2O7^2-'` → `'Cr₂O₇²⁻'`, for display.
|
|
131
|
+
*
|
|
132
|
+
* The caret is an input convention forced by ASCII ambiguity, and a learner should never see it.
|
|
133
|
+
*
|
|
134
|
+
* ## The one place the caret is not needed
|
|
135
|
+
*
|
|
136
|
+
* After a closing bracket. `[Cu(H2O)6]2+` has no ambiguity at all, because a digit cannot be the
|
|
137
|
+
* subscript of a `]`, and that is the form `complexFormula` emits. Without this case a complex ion
|
|
138
|
+
* came back as `[Cu(H₂O)₆]₂⁺`, with its charge written as a subscript: the same class of defect as a
|
|
139
|
+
* formatter producing a string its sibling parser cannot read.
|
|
140
|
+
*/
|
|
141
|
+
function speciesToUnicode(formula) {
|
|
142
|
+
const bracketed = /^(.*\])(\d+)([+-])$/.exec(formula);
|
|
143
|
+
if (bracketed) return bracketed[1].replace(/\d/g, (d) => SUB[d]) + ([...bracketed[2]].map((d) => SUPER[d]).join("") + SUPER[bracketed[3]]);
|
|
144
|
+
const caret = formula.lastIndexOf("^");
|
|
145
|
+
const body = caret >= 0 ? formula.slice(0, caret) : formula.replace(/[+-]+$/, "");
|
|
146
|
+
const tail = caret >= 0 ? formula.slice(caret + 1) : /[+-]+$/.exec(formula)?.[0] ?? "";
|
|
147
|
+
return body.replace(/\d/g, (d) => SUB[d]) + [...tail].map((c) => SUPER[c] ?? c).join("");
|
|
148
|
+
}
|
|
149
|
+
/** Positive when oxidised, negative when reduced, for `element` between two species. */
|
|
150
|
+
function oxidationChange(element, from, to) {
|
|
151
|
+
const a = oxidationNumbers(from)[element];
|
|
152
|
+
const b = oxidationNumbers(to)[element];
|
|
153
|
+
if (a === void 0 || b === void 0) throw new Error(`"${element}" is not in both ${from} and ${to}`);
|
|
154
|
+
return b - a;
|
|
155
|
+
}
|
|
156
|
+
const gcd = (a, b) => b === 0 ? Math.abs(a) : gcd(b, a % b);
|
|
157
|
+
const WATER = parseSpecies("H2O");
|
|
158
|
+
const PROTON = parseSpecies("H+");
|
|
159
|
+
const HYDROXIDE = parseSpecies("OH-");
|
|
160
|
+
const term = (coeff, species) => ({
|
|
161
|
+
coeff,
|
|
162
|
+
species
|
|
163
|
+
});
|
|
164
|
+
function countIn(terms, element) {
|
|
165
|
+
return terms.reduce((n, t) => n + t.coeff * (t.species.elements[element] ?? 0), 0);
|
|
166
|
+
}
|
|
167
|
+
const chargeOf = (terms) => terms.reduce((q, t) => q + t.coeff * t.species.charge, 0);
|
|
168
|
+
/**
|
|
169
|
+
* Balance one half-equation, in acid or in alkali.
|
|
170
|
+
*
|
|
171
|
+
* `balanceHalf('MnO4-', 'Mn2+')` → MnO₄⁻ + 8H⁺ + 5e⁻ → Mn²⁺ + 4H₂O.
|
|
172
|
+
*
|
|
173
|
+
* Alkali is not a second procedure: the equation is balanced in acid and then neutralised, which is
|
|
174
|
+
* exactly what a mark scheme wants written.
|
|
175
|
+
*/
|
|
176
|
+
function balanceHalf(from, to, medium = "acid") {
|
|
177
|
+
const a = parseSpecies(from);
|
|
178
|
+
const b = parseSpecies(to);
|
|
179
|
+
const key = Object.keys(a.elements).filter((s) => s !== "O" && s !== "H");
|
|
180
|
+
let coeffA = 1;
|
|
181
|
+
let coeffB = 1;
|
|
182
|
+
for (const element of key) {
|
|
183
|
+
const inA = a.elements[element] ?? 0;
|
|
184
|
+
const inB = b.elements[element] ?? 0;
|
|
185
|
+
if (inA === 0 || inB === 0) continue;
|
|
186
|
+
const g = gcd(inA, inB);
|
|
187
|
+
coeffA = Math.max(coeffA, inB / g);
|
|
188
|
+
coeffB = Math.max(coeffB, inA / g);
|
|
189
|
+
}
|
|
190
|
+
let left = [term(coeffA, a)];
|
|
191
|
+
let right = [term(coeffB, b)];
|
|
192
|
+
const dO = countIn(left, "O") - countIn(right, "O");
|
|
193
|
+
if (dO > 0) right.push(term(dO, WATER));
|
|
194
|
+
else if (dO < 0) left.push(term(-dO, WATER));
|
|
195
|
+
const dH = countIn(left, "H") - countIn(right, "H");
|
|
196
|
+
if (dH > 0) right.push(term(dH, PROTON));
|
|
197
|
+
else if (dH < 0) left.push(term(-dH, PROTON));
|
|
198
|
+
const dQ = chargeOf(left) - chargeOf(right);
|
|
199
|
+
const electrons = Math.abs(dQ);
|
|
200
|
+
const electronSide = dQ > 0 ? "left" : "right";
|
|
201
|
+
if (medium === "alkali") {
|
|
202
|
+
const protons = [...left, ...right].find((t) => t.species.formula === "H+");
|
|
203
|
+
if (protons) {
|
|
204
|
+
const n = protons.coeff;
|
|
205
|
+
const protonsOnLeft = left.includes(protons);
|
|
206
|
+
left = left.filter((t) => t !== protons);
|
|
207
|
+
right = right.filter((t) => t !== protons);
|
|
208
|
+
if (protonsOnLeft) {
|
|
209
|
+
left.push(term(n, WATER));
|
|
210
|
+
right.push(term(n, HYDROXIDE));
|
|
211
|
+
} else {
|
|
212
|
+
right.push(term(n, WATER));
|
|
213
|
+
left.push(term(n, HYDROXIDE));
|
|
214
|
+
}
|
|
215
|
+
left = mergeWater(left);
|
|
216
|
+
right = mergeWater(right);
|
|
217
|
+
cancelWater(left, right);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
left: left.filter((t) => t.coeff > 0),
|
|
222
|
+
right: right.filter((t) => t.coeff > 0),
|
|
223
|
+
electrons,
|
|
224
|
+
electronSide,
|
|
225
|
+
medium
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function mergeWater(terms) {
|
|
229
|
+
const water = terms.filter((t) => t.species.formula === "H2O");
|
|
230
|
+
if (water.length < 2) return terms;
|
|
231
|
+
const total = water.reduce((n, t) => n + t.coeff, 0);
|
|
232
|
+
return [...terms.filter((t) => t.species.formula !== "H2O"), term(total, WATER)];
|
|
233
|
+
}
|
|
234
|
+
/** Water on both sides cancels, which is the tidying step a mark scheme expects. */
|
|
235
|
+
function cancelWater(left, right) {
|
|
236
|
+
const l = left.findIndex((t) => t.species.formula === "H2O");
|
|
237
|
+
const r = right.findIndex((t) => t.species.formula === "H2O");
|
|
238
|
+
if (l < 0 || r < 0) return;
|
|
239
|
+
const shared = Math.min(left[l].coeff, right[r].coeff);
|
|
240
|
+
left[l] = term(left[l].coeff - shared, WATER);
|
|
241
|
+
right[r] = term(right[r].coeff - shared, WATER);
|
|
242
|
+
if (left[l].coeff === 0) left.splice(l, 1);
|
|
243
|
+
const r2 = right.findIndex((t) => t.species.formula === "H2O" && t.coeff === 0);
|
|
244
|
+
if (r2 >= 0) right.splice(r2, 1);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* "MnO₄⁻ + 8H⁺ + 5e⁻ → Mn²⁺ + 4H₂O", for display.
|
|
248
|
+
*
|
|
249
|
+
* Built from the TERMS, not by running `speciesToUnicode` over the finished string. That was tried
|
|
250
|
+
* and is wrong: a coefficient is a normal-size number and a separator is a plus sign, so formatting
|
|
251
|
+
* the whole line turns `2Cr^3+ + 7H2O` into subscripted coefficients and superscripted plus signs.
|
|
252
|
+
* Chemistry typography is per-species, so the formatter has to be too.
|
|
253
|
+
*/
|
|
254
|
+
function halfToUnicode(half) {
|
|
255
|
+
const side = (terms, withElectrons) => {
|
|
256
|
+
const parts = terms.map((t) => (t.coeff === 1 ? "" : String(t.coeff)) + speciesToUnicode(t.species.formula));
|
|
257
|
+
if (withElectrons && half.electrons > 0) parts.push((half.electrons === 1 ? "" : String(half.electrons)) + "e⁻");
|
|
258
|
+
return parts.join(" + ");
|
|
259
|
+
};
|
|
260
|
+
return `${side(half.left, half.electronSide === "left")} → ${side(half.right, half.electronSide === "right")}`;
|
|
261
|
+
}
|
|
262
|
+
/** "MnO₄⁻ + 8H⁺ + 5Fe²⁺ → Mn²⁺ + 4H₂O + 5Fe³⁺", for display. */
|
|
263
|
+
function ionicToUnicode(equation) {
|
|
264
|
+
const side = (terms) => terms.map((t) => (t.coeff === 1 ? "" : String(t.coeff)) + speciesToUnicode(t.species.formula)).join(" + ");
|
|
265
|
+
return `${side(equation.left)} → ${side(equation.right)}`;
|
|
266
|
+
}
|
|
267
|
+
/** "MnO4- + 8H+ + 5e- -> Mn2+ + 4H2O" */
|
|
268
|
+
function halfToString(half) {
|
|
269
|
+
const side = (terms, withElectrons) => {
|
|
270
|
+
const parts = terms.map((t) => t.coeff === 1 ? t.species.formula : `${t.coeff}${t.species.formula}`);
|
|
271
|
+
if (withElectrons && half.electrons > 0) parts.push(half.electrons === 1 ? "e-" : `${half.electrons}e-`);
|
|
272
|
+
return parts.join(" + ");
|
|
273
|
+
};
|
|
274
|
+
return `${side(half.left, half.electronSide === "left")} -> ${side(half.right, half.electronSide === "right")}`;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Combine a reduction and an oxidation into the full ionic equation.
|
|
278
|
+
*
|
|
279
|
+
* Each half is scaled so the electrons cancel exactly, which is the step the whole topic exists to
|
|
280
|
+
* teach: electrons are not created, so the number lost must equal the number gained.
|
|
281
|
+
*/
|
|
282
|
+
function combineHalves(a, b) {
|
|
283
|
+
const reduction = a.electronSide === "left" ? a : b;
|
|
284
|
+
const oxidation = a.electronSide === "left" ? b : a;
|
|
285
|
+
if (reduction.electronSide !== "left" || oxidation.electronSide !== "right") throw new Error("combineHalves needs one reduction and one oxidation, not two of the same.");
|
|
286
|
+
const g = gcd(reduction.electrons, oxidation.electrons) || 1;
|
|
287
|
+
const lcm = reduction.electrons * oxidation.electrons / g;
|
|
288
|
+
const scaleR = lcm / reduction.electrons;
|
|
289
|
+
const scaleO = lcm / oxidation.electrons;
|
|
290
|
+
const scale = (terms, by) => terms.map((t) => term(t.coeff * by, t.species));
|
|
291
|
+
const left = collect([...scale(reduction.left, scaleR), ...scale(oxidation.left, scaleO)]);
|
|
292
|
+
const right = collect([...scale(reduction.right, scaleR), ...scale(oxidation.right, scaleO)]);
|
|
293
|
+
cancelCommon(left, right);
|
|
294
|
+
return {
|
|
295
|
+
left,
|
|
296
|
+
right,
|
|
297
|
+
transferred: lcm
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function collect(terms) {
|
|
301
|
+
const out = [];
|
|
302
|
+
for (const t of terms) {
|
|
303
|
+
const found = out.findIndex((u) => u.species.formula === t.species.formula);
|
|
304
|
+
if (found >= 0) out[found] = term(out[found].coeff + t.coeff, t.species);
|
|
305
|
+
else out.push(t);
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
/** A species on both sides is a spectator of the combination and is cancelled. */
|
|
310
|
+
function cancelCommon(left, right) {
|
|
311
|
+
for (let i = left.length - 1; i >= 0; i--) {
|
|
312
|
+
const j = right.findIndex((t) => t.species.formula === left[i].species.formula);
|
|
313
|
+
if (j < 0) continue;
|
|
314
|
+
const shared = Math.min(left[i].coeff, right[j].coeff);
|
|
315
|
+
left[i] = term(left[i].coeff - shared, left[i].species);
|
|
316
|
+
right[j] = term(right[j].coeff - shared, right[j].species);
|
|
317
|
+
if (right[j].coeff === 0) right.splice(j, 1);
|
|
318
|
+
if (left[i].coeff === 0) left.splice(i, 1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** "2MnO4- + 16H+ + 10Fe2+ -> 2Mn2+ + 8H2O + 10Fe3+" */
|
|
322
|
+
function ionicToString(equation) {
|
|
323
|
+
const side = (terms) => terms.map((t) => t.coeff === 1 ? t.species.formula : `${t.coeff}${t.species.formula}`).join(" + ");
|
|
324
|
+
return `${side(equation.left)} -> ${side(equation.right)}`;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
export { balanceHalf, combineHalves, halfToString, halfToUnicode, ionicToString, ionicToUnicode, oxidationChange, oxidationNumbers, parseSpecies, speciesToUnicode };
|