@classytic/stage 0.2.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/LICENSE +1 -1
- package/README.md +5 -1
- package/dist/_virtual/_rolldown/runtime.mjs +18 -0
- package/dist/assets/index.mjs +0 -1
- 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/builder/Palette.mjs +50 -89
- package/dist/builder/SceneBuilder.mjs +15 -73
- 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 +4 -1
- package/dist/core/index.mjs +4 -1
- package/dist/core/math.d.mts +26 -0
- package/dist/core/math.mjs +37 -0
- 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 +75 -0
- package/dist/finance/bizsim.mjs +296 -0
- package/dist/finance/index.d.mts +95 -0
- package/dist/finance/index.mjs +145 -0
- 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 +9 -8
- package/dist/index.mjs +5 -5
- package/dist/interaction/MovableDot.mjs +19 -0
- package/dist/interaction/useDraggable.mjs +24 -4
- 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 +17 -1
- package/dist/math/calculus.mjs +365 -2
- 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 +9 -23
- package/dist/math/index.mjs +9 -38
- package/dist/math/latex.mjs +10 -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/primitives/Dot.d.mts +2 -15
- package/dist/primitives/Dot.mjs +6 -4
- package/dist/primitives/Grid.d.mts +33 -17
- package/dist/primitives/Grid.mjs +89 -17
- package/dist/primitives/Label.d.mts +1 -14
- package/dist/primitives/Label.mjs +3 -2
- package/dist/primitives/Lines.d.mts +4 -32
- package/dist/primitives/Lines.mjs +10 -8
- package/dist/primitives/Shapes.d.mts +5 -43
- package/dist/primitives/Shapes.mjs +12 -10
- package/dist/primitives/index.d.mts +2 -2
- package/dist/primitives/index.mjs +2 -2
- package/dist/primitives/props.mjs +31 -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/Scene.d.mts +6 -1
- package/dist/scene/Scene.mjs +15 -40
- 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/view/Stage.mjs +4 -11
- 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 +103 -23
- package/styles.css +125 -8
- package/dist/assets/kit/index.mjs +0 -4
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { elementBySymbol } from "../atomic/elements.mjs";
|
|
2
|
+
import { bondsAt, implicitHydrogens, otherEnd } from "./molecule.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/chem/stereo.ts
|
|
5
|
+
/**
|
|
6
|
+
* Stereochemistry: which carbons have a handedness, and which way round each one is.
|
|
7
|
+
*
|
|
8
|
+
* ## The question, and why ranking is the whole of it
|
|
9
|
+
*
|
|
10
|
+
* Two molecules can have the same atoms joined in the same order and still be different substances,
|
|
11
|
+
* because four different groups around one carbon can be arranged two ways that are mirror images
|
|
12
|
+
* and cannot be superimposed. Telling those two apart needs a rule for which group outranks which,
|
|
13
|
+
* and that rule is the Cahn-Ingold-Prelog priority.
|
|
14
|
+
*
|
|
15
|
+
* Almost every treatment gives the rule as "highest atomic number first, and if there is a tie,
|
|
16
|
+
* look further out". The second half is where all the difficulty lives and it is where textbooks
|
|
17
|
+
* stop. Looking further out means comparing two whole branches, and the comparison has a definite
|
|
18
|
+
* shape: gather the atoms at distance one from each branch, sort them heaviest first, compare the
|
|
19
|
+
* two lists; if they match, gather distance two, and so on. It is a breadth-first comparison of
|
|
20
|
+
* trees, and written that way it is neither vague nor hard.
|
|
21
|
+
*
|
|
22
|
+
* {@link comparePriority} does exactly that, so a learner can be shown the sphere at which two
|
|
23
|
+
* branches finally differed rather than being told the answer.
|
|
24
|
+
*
|
|
25
|
+
* ## Double bonds are duplicated, which is the part that looks like a trick
|
|
26
|
+
*
|
|
27
|
+
* A doubly bonded oxygen counts twice. That is not a convention invented to break ties: the rule is
|
|
28
|
+
* that an atom joined by a double bond is treated as joined to two of that atom, because a double
|
|
29
|
+
* bond is two connections. So the carbon of an aldehyde carries (O, O, H) and the carbon of an
|
|
30
|
+
* alcohol carries (O, H, H), and the aldehyde outranks it for a reason rather than by decree.
|
|
31
|
+
*
|
|
32
|
+
* ## What this covers, and what it refuses
|
|
33
|
+
*
|
|
34
|
+
* Tetrahedral carbon centres, and double bonds for E/Z. The priority comparison explores outward
|
|
35
|
+
* through the whole molecule, so it settles the cases that matter at this level.
|
|
36
|
+
*
|
|
37
|
+
* It does NOT implement the auxiliary descriptors CIP needs for rings that are symmetric until
|
|
38
|
+
* several spheres out, or for deciding between two branches that are identical in constitution and
|
|
39
|
+
* differ only in their own stereochemistry. Those exist, they are genuinely intricate, and a
|
|
40
|
+
* kernel that guessed at them would be wrong quietly. {@link comparePriority} returns undefined
|
|
41
|
+
* where it cannot separate two branches, and everything above it reports that rather than picking.
|
|
42
|
+
*
|
|
43
|
+
* Pure: no React, no pixels.
|
|
44
|
+
*/
|
|
45
|
+
/** Proton number, which is what "higher atomic number wins" is comparing. */
|
|
46
|
+
const protons = (element) => elementBySymbol(element)?.Z ?? 0;
|
|
47
|
+
/**
|
|
48
|
+
* The atoms one step out from `atom`, coming from `cameFrom`, heaviest first.
|
|
49
|
+
*
|
|
50
|
+
* Doubly and triply bonded neighbours appear as many times as the bond order, which is the
|
|
51
|
+
* duplication rule. Implicit hydrogens are included, because a hydrogen is a substituent and
|
|
52
|
+
* leaving it out would make a CH2 look like a CH.
|
|
53
|
+
*/
|
|
54
|
+
function neighbours(structure, atom, cameFrom) {
|
|
55
|
+
const out = [];
|
|
56
|
+
for (const bond of bondsAt(structure, atom)) {
|
|
57
|
+
const other = otherEnd(bond, atom);
|
|
58
|
+
const element = structure.atoms[other].element;
|
|
59
|
+
const order = bond.aromatic ? 2 : bond.order;
|
|
60
|
+
if (other !== cameFrom) out.push({
|
|
61
|
+
element,
|
|
62
|
+
id: other
|
|
63
|
+
});
|
|
64
|
+
for (let k = 1; k < order; k += 1) out.push({
|
|
65
|
+
element,
|
|
66
|
+
id: void 0
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
for (let k = 0; k < implicitHydrogens(structure, atom); k += 1) out.push({
|
|
70
|
+
element: "H",
|
|
71
|
+
id: void 0
|
|
72
|
+
});
|
|
73
|
+
return out.sort((a, b) => protons(b.element) - protons(a.element));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Compare two branches by CIP priority.
|
|
77
|
+
*
|
|
78
|
+
* Returns a negative number when the first outranks the second, positive when the second does, and
|
|
79
|
+
* `undefined` when they cannot be separated within the spheres explored, which for a real molecule
|
|
80
|
+
* means they are the same branch.
|
|
81
|
+
*
|
|
82
|
+
* The comparison walks outward one sphere at a time. At each sphere both branches offer a list of
|
|
83
|
+
* atoms sorted heaviest first; the lists are compared position by position, and the first
|
|
84
|
+
* difference decides it. Only if they are identical does the walk go further out.
|
|
85
|
+
*/
|
|
86
|
+
function comparePriority(structure, centre, firstAtom, secondAtom, maxSpheres = 12) {
|
|
87
|
+
return compareBranches(structure, firstAtom, centre, secondAtom, centre, maxSpheres);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The same comparison, for two branches hanging off DIFFERENT atoms.
|
|
91
|
+
*
|
|
92
|
+
* Needed by the meso check, where the two branches being weighed against each other belong to the
|
|
93
|
+
* two ends of the molecule rather than to one centre. Passing both to {@link comparePriority} with
|
|
94
|
+
* a single centre let each walk escape backwards through the other's parent, so two branches that
|
|
95
|
+
* are plainly the same came out different and no meso form was ever found.
|
|
96
|
+
*/
|
|
97
|
+
function compareBranches(structure, firstAtom, firstFrom, secondAtom, secondFrom, maxSpheres = 12) {
|
|
98
|
+
const first = protons(structure.atoms[firstAtom].element);
|
|
99
|
+
const second = protons(structure.atoms[secondAtom].element);
|
|
100
|
+
if (first !== second) return second - first;
|
|
101
|
+
let frontA = [{
|
|
102
|
+
atom: firstAtom,
|
|
103
|
+
cameFrom: firstFrom
|
|
104
|
+
}];
|
|
105
|
+
let frontB = [{
|
|
106
|
+
atom: secondAtom,
|
|
107
|
+
cameFrom: secondFrom
|
|
108
|
+
}];
|
|
109
|
+
for (let sphere = 0; sphere < maxSpheres; sphere += 1) {
|
|
110
|
+
if (frontA.length === 0 && frontB.length === 0) return void 0;
|
|
111
|
+
const expand = (front) => {
|
|
112
|
+
const list = [];
|
|
113
|
+
const next = [];
|
|
114
|
+
for (const node of front) for (const n of neighbours(structure, node.atom, node.cameFrom)) {
|
|
115
|
+
list.push(n.element);
|
|
116
|
+
if (n.id !== void 0) next.push({
|
|
117
|
+
atom: n.id,
|
|
118
|
+
cameFrom: node.atom
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
list,
|
|
123
|
+
next
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
const a = expand(frontA);
|
|
127
|
+
const b = expand(frontB);
|
|
128
|
+
const sortedA = [...a.list].sort((x, y) => protons(y) - protons(x));
|
|
129
|
+
const sortedB = [...b.list].sort((x, y) => protons(y) - protons(x));
|
|
130
|
+
const length = Math.max(sortedA.length, sortedB.length);
|
|
131
|
+
for (let k = 0; k < length; k += 1) {
|
|
132
|
+
const pa = protons(sortedA[k] ?? "");
|
|
133
|
+
const pb = protons(sortedB[k] ?? "");
|
|
134
|
+
if (pa !== pb) return pb - pa;
|
|
135
|
+
}
|
|
136
|
+
frontA = a.next;
|
|
137
|
+
frontB = b.next;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The substituents of an atom in priority order, highest first.
|
|
142
|
+
*
|
|
143
|
+
* Returns undefined when two of them cannot be separated, which is the honest answer: a carbon with
|
|
144
|
+
* two identical groups on it has no handedness, and saying so is more useful than an arbitrary
|
|
145
|
+
* ordering that would then produce a confident R or S for a molecule that is neither.
|
|
146
|
+
*/
|
|
147
|
+
function rankSubstituents(structure, centre) {
|
|
148
|
+
const attached = bondsAt(structure, centre).map((bond) => otherEnd(bond, centre));
|
|
149
|
+
const hydrogens = implicitHydrogens(structure, centre);
|
|
150
|
+
if (attached.length + hydrogens !== 4) return void 0;
|
|
151
|
+
const sorted = [...attached];
|
|
152
|
+
for (let i = 0; i < sorted.length; i += 1) for (let j = i + 1; j < sorted.length; j += 1) {
|
|
153
|
+
const order = comparePriority(structure, centre, sorted[i], sorted[j]);
|
|
154
|
+
if (order === void 0) return void 0;
|
|
155
|
+
if (order > 0) [sorted[i], sorted[j]] = [sorted[j], sorted[i]];
|
|
156
|
+
}
|
|
157
|
+
const ranked = sorted.map((atom, index) => ({
|
|
158
|
+
atom,
|
|
159
|
+
element: structure.atoms[atom].element,
|
|
160
|
+
rank: index + 1
|
|
161
|
+
}));
|
|
162
|
+
if (hydrogens === 1) return ranked;
|
|
163
|
+
if (hydrogens === 0) return ranked;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Every carbon with four different groups on it.
|
|
167
|
+
*
|
|
168
|
+
* The definition, applied rather than paraphrased. A carbon qualifies when the four things attached
|
|
169
|
+
* to it can all be told apart by the priority rules, which is a stronger statement than "four
|
|
170
|
+
* different atoms" and is why 3-methylhexane has a stereocentre and 3-methylpentane does not: in
|
|
171
|
+
* the second, two of the branches are both ethyl.
|
|
172
|
+
*/
|
|
173
|
+
function stereocentres(structure) {
|
|
174
|
+
const found = [];
|
|
175
|
+
for (const atom of structure.atoms) {
|
|
176
|
+
if (atom.element !== "C" || atom.aromatic) continue;
|
|
177
|
+
const substituents = rankSubstituents(structure, atom.id);
|
|
178
|
+
if (substituents) found.push({
|
|
179
|
+
atom: atom.id,
|
|
180
|
+
substituents
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return found;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* How many stereoisomers a molecule has, from its stereocentre count.
|
|
187
|
+
*
|
|
188
|
+
* Two arrangements at each centre, independently, so 2^n. This is the upper bound: see
|
|
189
|
+
* {@link stereoisomerCount} for the number a molecule with an internal mirror plane actually has.
|
|
190
|
+
*/
|
|
191
|
+
const maximumStereoisomers = (centres) => 2 ** centres;
|
|
192
|
+
/**
|
|
193
|
+
* Whether two stereocentres are mirror images of each other through the middle of the molecule.
|
|
194
|
+
*
|
|
195
|
+
* True when the branches leading away from each centre are the same on both sides, which is what
|
|
196
|
+
* makes the molecule superimposable on its own reflection.
|
|
197
|
+
*/
|
|
198
|
+
function centresArePaired(structure, first, second) {
|
|
199
|
+
const branchesOf = (centre, away) => bondsAt(structure, centre).map((bond) => otherEnd(bond, centre)).filter((id) => id !== away);
|
|
200
|
+
const a = branchesOf(first, second);
|
|
201
|
+
const b = branchesOf(second, first);
|
|
202
|
+
if (a.length !== b.length) return false;
|
|
203
|
+
const spare = [...b];
|
|
204
|
+
for (const branch of a) {
|
|
205
|
+
const match = spare.findIndex((other) => compareBranches(structure, branch, first, other, second) === void 0);
|
|
206
|
+
if (match === -1) return false;
|
|
207
|
+
spare.splice(match, 1);
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* How many stereoisomers a molecule really has.
|
|
213
|
+
*
|
|
214
|
+
* The rule of 2^n over-counts whenever the molecule has an internal mirror plane. Tartaric acid has
|
|
215
|
+
* two stereocentres and three isomers, not four: the arrangement with one centre left-handed and
|
|
216
|
+
* the other right-handed is superimposable on its own reflection, so the two that 2^n counts
|
|
217
|
+
* separately are one substance, and that substance is not optically active at all.
|
|
218
|
+
*
|
|
219
|
+
* Detected by asking whether the centres are mirror images of one another, which is checkable with
|
|
220
|
+
* the machinery already here: the branches leading away from each centre have to be
|
|
221
|
+
* indistinguishable, and indistinguishable is exactly what {@link comparePriority} reports when it
|
|
222
|
+
* cannot separate two branches.
|
|
223
|
+
*
|
|
224
|
+
* Restricted to the two-centre case, which is the one syllabuses set. A molecule with more centres
|
|
225
|
+
* and partial symmetry needs a full search over configurations, and this reports the maximum for
|
|
226
|
+
* those rather than guessing.
|
|
227
|
+
*/
|
|
228
|
+
function stereoisomerCount(structure) {
|
|
229
|
+
const centres = stereocentres(structure);
|
|
230
|
+
const maximum = maximumStereoisomers(centres.length);
|
|
231
|
+
if (centres.length !== 2) return {
|
|
232
|
+
centres: centres.length,
|
|
233
|
+
maximum,
|
|
234
|
+
actual: maximum,
|
|
235
|
+
hasMeso: false,
|
|
236
|
+
because: centres.length < 2 ? "with fewer than two stereocentres there is no internal mirror plane to lose an isomer to" : "more than two centres are not searched for internal symmetry here, so this is the upper bound"
|
|
237
|
+
};
|
|
238
|
+
const paired = centresArePaired(structure, centres[0].atom, centres[1].atom);
|
|
239
|
+
return {
|
|
240
|
+
centres: 2,
|
|
241
|
+
maximum,
|
|
242
|
+
actual: paired ? maximum - 1 : maximum,
|
|
243
|
+
hasMeso: paired,
|
|
244
|
+
because: paired ? "the two centres carry the same groups, so the arrangement with one of each handedness is superimposable on its own mirror image: that meso form is one substance rather than two, and it is not optically active" : "the two centres carry different groups, so every one of the four arrangements is a distinct substance"
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Double bonds that can show E/Z, with the higher-priority group on each end identified.
|
|
249
|
+
*
|
|
250
|
+
* A double bond has a shape worth naming only when BOTH its carbons carry two different groups. If
|
|
251
|
+
* either end has two of the same thing, swapping them changes nothing and there is only one
|
|
252
|
+
* compound, which is why 1,1-dichloroethene has no isomers and 1,2-dichloroethene has two.
|
|
253
|
+
*
|
|
254
|
+
* Which arrangement is E and which is Z cannot be read off the connection table: it depends on
|
|
255
|
+
* where the groups actually are in space, which a graph does not record. So this reports WHICH
|
|
256
|
+
* group wins on each end, and the geometry has to say the rest.
|
|
257
|
+
*/
|
|
258
|
+
function stereogenicDoubleBonds(structure) {
|
|
259
|
+
const found = [];
|
|
260
|
+
for (const bond of structure.bonds) {
|
|
261
|
+
if (bond.order !== 2 || bond.aromatic) continue;
|
|
262
|
+
const higher = (end, far) => {
|
|
263
|
+
const groups = bondsAt(structure, end).map((b) => otherEnd(b, end)).filter((id) => id !== far);
|
|
264
|
+
const hydrogens = implicitHydrogens(structure, end);
|
|
265
|
+
if (groups.length + hydrogens !== 2) return void 0;
|
|
266
|
+
if (groups.length === 1 && hydrogens === 1) return groups[0];
|
|
267
|
+
if (groups.length !== 2) return void 0;
|
|
268
|
+
const order = comparePriority(structure, end, groups[0], groups[1]);
|
|
269
|
+
if (order === void 0) return void 0;
|
|
270
|
+
return order < 0 ? groups[0] : groups[1];
|
|
271
|
+
};
|
|
272
|
+
const higherOnA = higher(bond.a, bond.b);
|
|
273
|
+
const higherOnB = higher(bond.b, bond.a);
|
|
274
|
+
if (higherOnA === void 0 || higherOnB === void 0) continue;
|
|
275
|
+
found.push({
|
|
276
|
+
a: bond.a,
|
|
277
|
+
b: bond.b,
|
|
278
|
+
higherOnA,
|
|
279
|
+
higherOnB
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
return found;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
//#endregion
|
|
286
|
+
export { compareBranches, comparePriority, maximumStereoisomers, rankSubstituents, stereocentres, stereogenicDoubleBonds, stereoisomerCount };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { BalancedEquation } from "./equation.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/stoichiometry.d.ts
|
|
4
|
+
/** How much of a substance is present, in whichever way it was measured. */
|
|
5
|
+
interface Amount {
|
|
6
|
+
/** Grams. */
|
|
7
|
+
readonly mass?: number;
|
|
8
|
+
/** Moles, if they are known directly. */
|
|
9
|
+
readonly moles?: number;
|
|
10
|
+
/** Volume in cubic decimetres, for something in solution. */
|
|
11
|
+
readonly volumeDm3?: number;
|
|
12
|
+
/** Concentration in mol per cubic decimetre, used with the volume. */
|
|
13
|
+
readonly concentration?: number;
|
|
14
|
+
}
|
|
15
|
+
/** Moles of a substance from however it was given. */
|
|
16
|
+
declare function molesOf(formula: string, amount: Amount): number;
|
|
17
|
+
interface ReactantState {
|
|
18
|
+
readonly formula: string;
|
|
19
|
+
readonly coefficient: number;
|
|
20
|
+
readonly moles: number;
|
|
21
|
+
/**
|
|
22
|
+
* Moles available divided by the coefficient: how many times the equation can run on this one.
|
|
23
|
+
*
|
|
24
|
+
* The number the whole calculation turns on, and the one a mass comparison never reaches.
|
|
25
|
+
*/
|
|
26
|
+
readonly equationRuns: number;
|
|
27
|
+
readonly limiting: boolean;
|
|
28
|
+
/** Moles of this reactant left when the limiting one is spent. */
|
|
29
|
+
readonly leftOver: number;
|
|
30
|
+
}
|
|
31
|
+
interface ProductState {
|
|
32
|
+
readonly formula: string;
|
|
33
|
+
readonly coefficient: number;
|
|
34
|
+
readonly moles: number;
|
|
35
|
+
readonly mass: number;
|
|
36
|
+
}
|
|
37
|
+
interface Reaction {
|
|
38
|
+
readonly equation: BalancedEquation;
|
|
39
|
+
/** How many times the equation as written can proceed. */
|
|
40
|
+
readonly runs: number;
|
|
41
|
+
readonly reactants: readonly ReactantState[];
|
|
42
|
+
readonly products: readonly ProductState[];
|
|
43
|
+
readonly limiting: ReactantState;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Work a reaction out from the amounts given.
|
|
47
|
+
*
|
|
48
|
+
* The equation is balanced here rather than taken on trust, because every mole ratio below depends
|
|
49
|
+
* on the coefficients and an unbalanced equation would make all of them confidently wrong.
|
|
50
|
+
*/
|
|
51
|
+
declare function react(equation: string, amounts: Readonly<Record<string, Amount>>): Reaction;
|
|
52
|
+
/** The most of a product the equation allows, in grams. */
|
|
53
|
+
declare function theoreticalYield(reaction: Reaction, product: string): number;
|
|
54
|
+
interface YieldReport {
|
|
55
|
+
/** Measured over theoretical, as a percentage. Reported even when it is impossible. */
|
|
56
|
+
readonly percent: number;
|
|
57
|
+
/** Why this figure cannot be a yield, when it cannot be. */
|
|
58
|
+
readonly impossible?: string;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The measured mass as a percentage of the theoretical one, whatever it comes to.
|
|
62
|
+
*
|
|
63
|
+
* Reports rather than refuses, because a figure above a hundred per cent is a real thing that comes
|
|
64
|
+
* out of a real balance and is the single most useful measurement in the topic: it is the one that
|
|
65
|
+
* tells you something is wrong with the sample. A lab teaching that has to be able to SHOW 112 per
|
|
66
|
+
* cent before asking what it means.
|
|
67
|
+
*
|
|
68
|
+
* What it must not do is call that a yield. So the number comes back with the reason it is not one
|
|
69
|
+
* attached, and {@link percentageYield} is the strict form built on this for the places where the
|
|
70
|
+
* value has to be legitimate.
|
|
71
|
+
*/
|
|
72
|
+
declare function apparentYield(theoretical: number, measured: number): YieldReport;
|
|
73
|
+
/**
|
|
74
|
+
* The fraction of the theoretical yield actually obtained, as a percentage.
|
|
75
|
+
*
|
|
76
|
+
* Refuses anything above a hundred. A flask cannot produce more than the equation allows, so a
|
|
77
|
+
* figure above it is evidence about the sample rather than about the reaction, and reporting it as
|
|
78
|
+
* a yield hides the one piece of information the number actually carries. Use {@link apparentYield}
|
|
79
|
+
* where that figure needs to be shown rather than accepted.
|
|
80
|
+
*/
|
|
81
|
+
declare function percentageYield(theoretical: number, measured: number): number;
|
|
82
|
+
/**
|
|
83
|
+
* The mass of one reactant needed to use up a given amount of another exactly.
|
|
84
|
+
*
|
|
85
|
+
* What a preparation asks: how much of this do I weigh out so nothing is wasted.
|
|
86
|
+
*/
|
|
87
|
+
declare function amountNeeded(equation: string, have: {
|
|
88
|
+
formula: string;
|
|
89
|
+
amount: Amount;
|
|
90
|
+
}, want: string): {
|
|
91
|
+
moles: number;
|
|
92
|
+
mass: number;
|
|
93
|
+
};
|
|
94
|
+
//#endregion
|
|
95
|
+
export { Amount, ProductState, ReactantState, Reaction, YieldReport, amountNeeded, apparentYield, molesOf, percentageYield, react, theoreticalYield };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { molarMass } from "./formula.mjs";
|
|
2
|
+
import { balance } from "./equation.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/chem/stoichiometry.ts
|
|
5
|
+
/**
|
|
6
|
+
* How much of what: limiting reagent, theoretical yield, and what is left over.
|
|
7
|
+
*
|
|
8
|
+
* ## The whole subject is one division
|
|
9
|
+
*
|
|
10
|
+
* Every quantitative question reduces to the same move. Convert what you have into moles, divide
|
|
11
|
+
* each by its coefficient, and whichever comes out smallest runs out first. That ratio, the moles
|
|
12
|
+
* available per unit of equation, is the only number that matters: it says how many times the
|
|
13
|
+
* reaction as written can proceed, and everything else is that number multiplied by a coefficient.
|
|
14
|
+
*
|
|
15
|
+
* So {@link react} computes it once and reads every answer off it. There is no separate method for
|
|
16
|
+
* limiting reagent, for yield, for excess: they are three questions about one quantity.
|
|
17
|
+
*
|
|
18
|
+
* ## The limiting reagent is not the one there is least of
|
|
19
|
+
*
|
|
20
|
+
* The most common mistake in the topic, and it is a mistake about what "least" means. Ten grams of
|
|
21
|
+
* hydrogen and twenty-five of oxygen: there is less hydrogen by mass and far more by moles, and the
|
|
22
|
+
* oxygen runs out first because the equation needs two hydrogens for every oxygen. Comparing masses
|
|
23
|
+
* answers a question nobody asked, and comparing moles without dividing by the coefficients answers
|
|
24
|
+
* a different wrong one.
|
|
25
|
+
*
|
|
26
|
+
* ## Yield is measured, and the theoretical figure is not a prediction of it
|
|
27
|
+
*
|
|
28
|
+
* The theoretical yield is what the equation permits. What comes out of a flask is always less:
|
|
29
|
+
* some product stays in solution, some is lost in transfer, the reaction may not finish. So
|
|
30
|
+
* {@link percentageYield} takes a measured mass and compares, and a yield above a hundred per cent
|
|
31
|
+
* is refused rather than reported, because it means the product is wet or impure and the useful
|
|
32
|
+
* answer is to say so.
|
|
33
|
+
*
|
|
34
|
+
* Pure: no React, no pixels.
|
|
35
|
+
*/
|
|
36
|
+
/** Moles of a substance from however it was given. */
|
|
37
|
+
function molesOf(formula, amount) {
|
|
38
|
+
if (amount.moles !== void 0) return amount.moles;
|
|
39
|
+
if (amount.mass !== void 0) return amount.mass / molarMass(formula);
|
|
40
|
+
if (amount.volumeDm3 !== void 0 && amount.concentration !== void 0) return amount.volumeDm3 * amount.concentration;
|
|
41
|
+
throw new Error(`not enough to work out the moles of ${formula}: give a mass, a number of moles, or a volume together with a concentration`);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Work a reaction out from the amounts given.
|
|
45
|
+
*
|
|
46
|
+
* The equation is balanced here rather than taken on trust, because every mole ratio below depends
|
|
47
|
+
* on the coefficients and an unbalanced equation would make all of them confidently wrong.
|
|
48
|
+
*/
|
|
49
|
+
function react(equation, amounts) {
|
|
50
|
+
const balanced = balance(equation);
|
|
51
|
+
const named = new Set(Object.keys(amounts));
|
|
52
|
+
const missing = balanced.reactants.filter((r) => !named.has(r.formula));
|
|
53
|
+
if (missing.length > 0) throw new Error(`no amount given for ${missing.map((r) => r.formula).join(" or ")}: every reactant needs one, since the limiting one cannot be found without them all`);
|
|
54
|
+
const withRuns = balanced.reactants.map((term) => {
|
|
55
|
+
const moles = molesOf(term.formula, amounts[term.formula]);
|
|
56
|
+
return {
|
|
57
|
+
term,
|
|
58
|
+
moles,
|
|
59
|
+
equationRuns: moles / term.coefficient
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
const runs = Math.min(...withRuns.map((r) => r.equationRuns));
|
|
63
|
+
const limitingRuns = runs;
|
|
64
|
+
const reactants = withRuns.map(({ term, moles, equationRuns }) => ({
|
|
65
|
+
formula: term.formula,
|
|
66
|
+
coefficient: term.coefficient,
|
|
67
|
+
moles,
|
|
68
|
+
equationRuns,
|
|
69
|
+
limiting: equationRuns === limitingRuns,
|
|
70
|
+
leftOver: moles - limitingRuns * term.coefficient
|
|
71
|
+
}));
|
|
72
|
+
return {
|
|
73
|
+
equation: balanced,
|
|
74
|
+
runs,
|
|
75
|
+
reactants,
|
|
76
|
+
products: balanced.products.map((term) => ({
|
|
77
|
+
formula: term.formula,
|
|
78
|
+
coefficient: term.coefficient,
|
|
79
|
+
moles: runs * term.coefficient,
|
|
80
|
+
mass: runs * term.coefficient * molarMass(term.formula)
|
|
81
|
+
})),
|
|
82
|
+
limiting: reactants.find((r) => r.limiting)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** The most of a product the equation allows, in grams. */
|
|
86
|
+
function theoreticalYield(reaction, product) {
|
|
87
|
+
const found = reaction.products.find((p) => p.formula === product);
|
|
88
|
+
if (!found) throw new Error(`${product} is not a product of this reaction: it makes ${reaction.products.map((p) => p.formula).join(", ")}`);
|
|
89
|
+
return found.mass;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The measured mass as a percentage of the theoretical one, whatever it comes to.
|
|
93
|
+
*
|
|
94
|
+
* Reports rather than refuses, because a figure above a hundred per cent is a real thing that comes
|
|
95
|
+
* out of a real balance and is the single most useful measurement in the topic: it is the one that
|
|
96
|
+
* tells you something is wrong with the sample. A lab teaching that has to be able to SHOW 112 per
|
|
97
|
+
* cent before asking what it means.
|
|
98
|
+
*
|
|
99
|
+
* What it must not do is call that a yield. So the number comes back with the reason it is not one
|
|
100
|
+
* attached, and {@link percentageYield} is the strict form built on this for the places where the
|
|
101
|
+
* value has to be legitimate.
|
|
102
|
+
*/
|
|
103
|
+
function apparentYield(theoretical, measured) {
|
|
104
|
+
if (theoretical <= 0) throw new Error("a theoretical yield of zero has no percentage");
|
|
105
|
+
if (measured < 0) throw new Error("a measured mass cannot be negative");
|
|
106
|
+
const percent = measured / theoretical * 100;
|
|
107
|
+
if (percent > 100.0001) return {
|
|
108
|
+
percent,
|
|
109
|
+
impossible: `${measured} g is ${percent.toFixed(1)} per cent of the ${theoretical.toFixed(3)} g the equation allows. A reaction cannot make more than its theoretical maximum, so the product is wet or impure, or the balance is wrong.`
|
|
110
|
+
};
|
|
111
|
+
return { percent };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The fraction of the theoretical yield actually obtained, as a percentage.
|
|
115
|
+
*
|
|
116
|
+
* Refuses anything above a hundred. A flask cannot produce more than the equation allows, so a
|
|
117
|
+
* figure above it is evidence about the sample rather than about the reaction, and reporting it as
|
|
118
|
+
* a yield hides the one piece of information the number actually carries. Use {@link apparentYield}
|
|
119
|
+
* where that figure needs to be shown rather than accepted.
|
|
120
|
+
*/
|
|
121
|
+
function percentageYield(theoretical, measured) {
|
|
122
|
+
const report = apparentYield(theoretical, measured);
|
|
123
|
+
if (report.impossible) throw new Error(report.impossible);
|
|
124
|
+
return report.percent;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The mass of one reactant needed to use up a given amount of another exactly.
|
|
128
|
+
*
|
|
129
|
+
* What a preparation asks: how much of this do I weigh out so nothing is wasted.
|
|
130
|
+
*/
|
|
131
|
+
function amountNeeded(equation, have, want) {
|
|
132
|
+
const balanced = balance(equation);
|
|
133
|
+
const from = balanced.reactants.find((r) => r.formula === have.formula);
|
|
134
|
+
const to = balanced.reactants.find((r) => r.formula === want);
|
|
135
|
+
if (!from) throw new Error(`${have.formula} is not a reactant in this equation`);
|
|
136
|
+
if (!to) throw new Error(`${want} is not a reactant in this equation`);
|
|
137
|
+
const moles = molesOf(have.formula, have.amount) / from.coefficient * to.coefficient;
|
|
138
|
+
return {
|
|
139
|
+
moles,
|
|
140
|
+
mass: moles * molarMass(want)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//#endregion
|
|
145
|
+
export { amountNeeded, apparentYield, molesOf, percentageYield, react, theoreticalYield };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
//#region src/chem/synthesis.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* synthesis, the functional-group map: every conversion as an edge, and a route as a path.
|
|
4
|
+
*
|
|
5
|
+
* WHAT THIS IS FOR. Every synthesis question in the AS and A2 papers has one form: convert X into
|
|
6
|
+
* Y, giving the reagents and conditions for each step. A learner who has memorised the reactions as
|
|
7
|
+
* a list still cannot answer it, because the question is not "what does an alcohol do" but "which
|
|
8
|
+
* sequence of things that alcohols do gets me to an ester". That is a path-finding problem, and the
|
|
9
|
+
* reason it feels hard is that a list is the wrong shape for it.
|
|
10
|
+
*
|
|
11
|
+
* Drawn as a graph the answer is computable: the groups are places, the reagents are roads, and a
|
|
12
|
+
* synthesis is a route. {@link findRoute} returns one, with the reagent and the condition for each
|
|
13
|
+
* step, so a lesson can state a route and have it CHECKED rather than asserted.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the edges carry conditions and never omit them
|
|
16
|
+
*
|
|
17
|
+
* A route is not worth marks without them. `halogenoalkane -> alcohol` and
|
|
18
|
+
* `halogenoalkane -> alkene` are the same reagent, sodium hydroxide, and differ only in the
|
|
19
|
+
* solvent. An edge naming the reagent alone would make those two roads identical, which is exactly
|
|
20
|
+
* the confusion the elimination lesson exists to prevent. So `condition` is not optional.
|
|
21
|
+
*
|
|
22
|
+
* ## Why this is in stage and not in the lab that drew it
|
|
23
|
+
*
|
|
24
|
+
* It was written inside `labs/domains/chem/synthesis-route`, which meant the only thing that could
|
|
25
|
+
* ask a question of it was the figure sitting on top of it. A course could not verify a route it
|
|
26
|
+
* claimed, a second lab could not reuse the graph without copying it, and the most useful fact in
|
|
27
|
+
* the topic, that only two reactions change the number of carbon atoms, was computable but
|
|
28
|
+
* unreachable. Dependency direction decides where a shared function lives, and everything here
|
|
29
|
+
* points inward: the graph needs nothing from the lab, and the lab needs the graph.
|
|
30
|
+
*
|
|
31
|
+
* What stayed behind is presentation. Where a group sits on the map is a layout decision made by
|
|
32
|
+
* eye for legibility, and it is not chemistry.
|
|
33
|
+
*
|
|
34
|
+
* Pure: no React, no pixels.
|
|
35
|
+
*/
|
|
36
|
+
/** The functional groups an AS and A2 synthesis question moves between. */
|
|
37
|
+
type GroupNode = 'alkane' | 'alkene' | 'halogenoalkane' | 'alcohol' | 'nitrile' | 'aldehyde' | 'ketone' | 'acid' | 'ester' | 'acyl-chloride' | 'arene' | 'nitroarene' | 'arylamine' | 'amine' | 'amide';
|
|
38
|
+
/** What a learner calls the group. The name is chemistry; where it sits on a map is not. */
|
|
39
|
+
declare const GROUP_LABELS: Readonly<Record<GroupNode, string>>;
|
|
40
|
+
interface RouteEdge {
|
|
41
|
+
readonly from: GroupNode;
|
|
42
|
+
readonly to: GroupNode;
|
|
43
|
+
readonly reagent: string;
|
|
44
|
+
/** Never optional: the solvent and the temperature are the marks. */
|
|
45
|
+
readonly condition: string;
|
|
46
|
+
/** Does the carbon chain change length? Only two reactions here do. */
|
|
47
|
+
readonly changesChain?: boolean;
|
|
48
|
+
/** Which class of the starting group this needs, where it matters. */
|
|
49
|
+
readonly needs?: string;
|
|
50
|
+
}
|
|
51
|
+
declare const ROUTE_EDGES: readonly RouteEdge[];
|
|
52
|
+
/** Every group the map knows, in the order the type declares them. */
|
|
53
|
+
declare const GROUP_NODES: GroupNode[];
|
|
54
|
+
/** Roads leaving a group. */
|
|
55
|
+
declare const edgesFrom: (id: GroupNode) => readonly RouteEdge[];
|
|
56
|
+
/** The road between two groups, if there is one. */
|
|
57
|
+
declare const edgeBetween: (from: GroupNode, to: GroupNode) => RouteEdge | undefined;
|
|
58
|
+
/** The roads along a path of groups, so a route can be read back as an answer. */
|
|
59
|
+
declare function edgesAlong(path: readonly GroupNode[]): RouteEdge[];
|
|
60
|
+
/**
|
|
61
|
+
* Shortest path length between two groups, by breadth-first search.
|
|
62
|
+
*
|
|
63
|
+
* `undefined` when no route exists, which is a real answer rather than a failure: a learner
|
|
64
|
+
* hunting for a reaction that does not exist is exactly what a route map is for.
|
|
65
|
+
*/
|
|
66
|
+
declare function shortestSteps(from: GroupNode, to: GroupNode): number | undefined;
|
|
67
|
+
/** A synthesis, as a learner would have to write it out. */
|
|
68
|
+
interface Route {
|
|
69
|
+
readonly path: readonly GroupNode[];
|
|
70
|
+
readonly steps: readonly RouteEdge[];
|
|
71
|
+
/** Does this route pass through a reaction that changes the number of carbon atoms? */
|
|
72
|
+
readonly changesChain: boolean;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One shortest route, with the reagent and condition for every step.
|
|
76
|
+
*
|
|
77
|
+
* This is the difference between a distance and an answer. A lesson claiming "alkene to carboxylic
|
|
78
|
+
* acid in three steps" can now be checked against the route the map actually gives, and the steps
|
|
79
|
+
* it returns are the marks.
|
|
80
|
+
*
|
|
81
|
+
* ## Why a plain breadth-first search was not enough
|
|
82
|
+
*
|
|
83
|
+
* Alkene to carboxylic acid has TWO routes of three steps. One hydrates and oxidises twice and
|
|
84
|
+
* keeps the carbon skeleton. The other goes through the nitrile, and a nitrile road adds a carbon,
|
|
85
|
+
* so it arrives at an acid ONE CARBON LONGER than the target. A search that returned whichever it
|
|
86
|
+
* reached first would hand a learner the wrong molecule half the time, and would do it silently,
|
|
87
|
+
* because both routes are three steps and both are real chemistry.
|
|
88
|
+
*
|
|
89
|
+
* So among equally short routes the chain-preserving one wins. A question that wants the longer
|
|
90
|
+
* chain says so, and {@link allRoutes} still has both.
|
|
91
|
+
*/
|
|
92
|
+
declare function findRoute(from: GroupNode, to: GroupNode): Route | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Every route from one group to another within a step budget, shortest first.
|
|
95
|
+
*
|
|
96
|
+
* Exists because the shortest route is not always the best one, which is the single most arguable
|
|
97
|
+
* point in the topic. Esterifying an acid directly is one step and an equilibrium that stops well
|
|
98
|
+
* short; going via the acyl chloride is two steps and goes to completion. Both routes have to be
|
|
99
|
+
* on the table for that to be a judgement rather than a rule.
|
|
100
|
+
*
|
|
101
|
+
* `maxSteps` is capped at six, because the map's diameter is smaller than that and an uncapped
|
|
102
|
+
* enumeration on a cyclic graph does not terminate usefully.
|
|
103
|
+
*/
|
|
104
|
+
declare function allRoutes(from: GroupNode, to: GroupNode, maxSteps?: number): Route[];
|
|
105
|
+
/**
|
|
106
|
+
* The reactions that change the number of carbon atoms.
|
|
107
|
+
*
|
|
108
|
+
* The most useful fact in the topic and the one that is invisible in a list: to reach a target with
|
|
109
|
+
* more carbons than the starting material, a route MUST pass through one of these.
|
|
110
|
+
*/
|
|
111
|
+
declare const chainChangingEdges: () => readonly RouteEdge[];
|
|
112
|
+
//#endregion
|
|
113
|
+
export { GROUP_LABELS, GROUP_NODES, GroupNode, ROUTE_EDGES, Route, RouteEdge, allRoutes, chainChangingEdges, edgeBetween, edgesAlong, edgesFrom, findRoute, shortestSteps };
|