@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,201 @@
|
|
|
1
|
+
import { elementBySymbol } from "../atomic/elements.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/formula.ts
|
|
4
|
+
/**
|
|
5
|
+
* Chemical formulae: what is in them, what they weigh, and what they weigh per element.
|
|
6
|
+
*
|
|
7
|
+
* ## One parser, because two would disagree
|
|
8
|
+
*
|
|
9
|
+
* A formula turns up in every quantitative question there is, and each of them needs the same three
|
|
10
|
+
* answers: which atoms, how many, and what that comes to. Writing a small reader for each caller is
|
|
11
|
+
* how a kernel ends up telling two different stories about Ca(NO3)2, so everything reads formulae
|
|
12
|
+
* here.
|
|
13
|
+
*
|
|
14
|
+
* What it understands:
|
|
15
|
+
*
|
|
16
|
+
* Ca(NO3)2 groups in brackets, with a multiplier
|
|
17
|
+
* K4[Fe(CN)6] square brackets, nested
|
|
18
|
+
* CuSO4.5H2O a hydrate, written with a dot and a leading count
|
|
19
|
+
* MgCl2.KCl.6H2O a double salt, as many parts as it takes
|
|
20
|
+
*
|
|
21
|
+
* ## Masses come from the element table, and a formula that names nothing is refused
|
|
22
|
+
*
|
|
23
|
+
* Relative atomic masses are the IUPAC values the atomic engine already holds, so a mass computed
|
|
24
|
+
* here cannot disagree with one computed anywhere else. A symbol that is not an element stops the
|
|
25
|
+
* parse: `Xy2O3` is a typo, and a parser that skipped it would return a mass for a substance that
|
|
26
|
+
* does not exist, which is worse than no answer at all.
|
|
27
|
+
*
|
|
28
|
+
* ## The empirical formula is a search, not a division
|
|
29
|
+
*
|
|
30
|
+
* Dividing by the smallest mole count is only the first half. It lands on whole numbers for some
|
|
31
|
+
* compounds and on 1.5 or 1.33 for others, and the answer is to scale until every ratio is close to
|
|
32
|
+
* a whole number. {@link empiricalFormula} tries multipliers in order and takes the first that
|
|
33
|
+
* works, which is exactly the step a learner is told to do "by inspection" and is the step most
|
|
34
|
+
* often fudged.
|
|
35
|
+
*
|
|
36
|
+
* Pure: no React, no pixels.
|
|
37
|
+
*/
|
|
38
|
+
const isUpper = (c) => c >= "A" && c <= "Z";
|
|
39
|
+
const isLower = (c) => c >= "a" && c <= "z";
|
|
40
|
+
const isDigit = (c) => c >= "0" && c <= "9";
|
|
41
|
+
function readNumber(r) {
|
|
42
|
+
let digits = "";
|
|
43
|
+
while (r.at < r.text.length && isDigit(r.text[r.at])) digits += r.text[r.at++];
|
|
44
|
+
return digits === "" ? 1 : Number(digits);
|
|
45
|
+
}
|
|
46
|
+
const CLOSERS = {
|
|
47
|
+
"(": ")",
|
|
48
|
+
"[": "]"
|
|
49
|
+
};
|
|
50
|
+
/** Parse one group, stopping at a closing bracket or the end of the text. */
|
|
51
|
+
function readGroup(r, closer) {
|
|
52
|
+
const counts = /* @__PURE__ */ new Map();
|
|
53
|
+
const add = (element, n) => void counts.set(element, (counts.get(element) ?? 0) + n);
|
|
54
|
+
while (r.at < r.text.length) {
|
|
55
|
+
const c = r.text[r.at];
|
|
56
|
+
if (closer && c === closer) {
|
|
57
|
+
r.at += 1;
|
|
58
|
+
return counts;
|
|
59
|
+
}
|
|
60
|
+
if (c === "(" || c === "[") {
|
|
61
|
+
r.at += 1;
|
|
62
|
+
const inner = readGroup(r, CLOSERS[c]);
|
|
63
|
+
const multiplier = readNumber(r);
|
|
64
|
+
for (const [element, n] of inner) add(element, n * multiplier);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (c === ")" || c === "]") throw new Error(`cannot read "${r.text}": a closing bracket at position ${r.at} opens nothing`);
|
|
68
|
+
if (!isUpper(c)) throw new Error(`cannot read "${r.text}": expected an element symbol at position ${r.at}, found "${c}"`);
|
|
69
|
+
let symbol = r.text[r.at++];
|
|
70
|
+
if (r.at < r.text.length && isLower(r.text[r.at])) symbol += r.text[r.at++];
|
|
71
|
+
if (!elementBySymbol(symbol)) throw new Error(`cannot read "${r.text}": "${symbol}" is not an element`);
|
|
72
|
+
add(symbol, readNumber(r));
|
|
73
|
+
}
|
|
74
|
+
if (closer) throw new Error(`cannot read "${r.text}": a bracket is never closed`);
|
|
75
|
+
return counts;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The atoms in a formula, in the order they are written.
|
|
79
|
+
*
|
|
80
|
+
* Hydrates and double salts are split on the dot, each part parsed on its own and multiplied by its
|
|
81
|
+
* leading count, then added together. So CuSO4.5H2O has ten hydrogens, nine oxygens, one copper and
|
|
82
|
+
* one sulfur, which is what it weighs as.
|
|
83
|
+
*/
|
|
84
|
+
function parseFormula(input) {
|
|
85
|
+
const text = input.replace(/\s+/g, "");
|
|
86
|
+
if (text === "") throw new Error("an empty formula has no composition");
|
|
87
|
+
const total = /* @__PURE__ */ new Map();
|
|
88
|
+
text.split(".").forEach((part, index) => {
|
|
89
|
+
if (part === "") throw new Error(`cannot read "${input}": an empty part between dots`);
|
|
90
|
+
const r = {
|
|
91
|
+
text: part,
|
|
92
|
+
at: 0
|
|
93
|
+
};
|
|
94
|
+
const multiplier = index === 0 ? 1 : readNumber(r);
|
|
95
|
+
for (const [element, n] of readGroup(r)) total.set(element, (total.get(element) ?? 0) + n * multiplier);
|
|
96
|
+
});
|
|
97
|
+
return [...total].map(([element, count]) => ({
|
|
98
|
+
element,
|
|
99
|
+
count
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Relative molecular mass, from the IUPAC atomic weights the element table holds.
|
|
104
|
+
*
|
|
105
|
+
* Called relative FORMULA mass for anything ionic, since sodium chloride has no molecules, and the
|
|
106
|
+
* arithmetic is identical either way.
|
|
107
|
+
*/
|
|
108
|
+
function molarMass(formula) {
|
|
109
|
+
return parseFormula(formula).reduce((total, { element, count }) => total + elementBySymbol(element).atomicWeight * count, 0);
|
|
110
|
+
}
|
|
111
|
+
/** What fraction of a compound's mass each element accounts for. */
|
|
112
|
+
function percentComposition(formula) {
|
|
113
|
+
const total = molarMass(formula);
|
|
114
|
+
return parseFormula(formula).map(({ element, count }) => {
|
|
115
|
+
const mass = elementBySymbol(element).atomicWeight * count;
|
|
116
|
+
return {
|
|
117
|
+
element,
|
|
118
|
+
mass,
|
|
119
|
+
percent: mass / total * 100
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* How close a scaled ratio has to be to a whole number before it counts as one.
|
|
125
|
+
*
|
|
126
|
+
* Fixed, and deliberately not loosened as the multiplier grows. Scaling the tolerance with the
|
|
127
|
+
* multiplier made every larger multiplier more forgiving than the last, so a composition that fits
|
|
128
|
+
* nothing would always find some multiple that looked close enough, and the refusal below could
|
|
129
|
+
* never fire.
|
|
130
|
+
*/
|
|
131
|
+
const WHOLE_TOLERANCE = .1;
|
|
132
|
+
/** Multipliers tried when the first division does not land on whole numbers. */
|
|
133
|
+
const MULTIPLIERS = [
|
|
134
|
+
1,
|
|
135
|
+
2,
|
|
136
|
+
3,
|
|
137
|
+
4,
|
|
138
|
+
5,
|
|
139
|
+
6,
|
|
140
|
+
8
|
|
141
|
+
];
|
|
142
|
+
/**
|
|
143
|
+
* The empirical formula implied by a composition.
|
|
144
|
+
*
|
|
145
|
+
* Takes percentages by mass, or any set of masses in the same unit: only the ratios matter, so the
|
|
146
|
+
* numbers do not have to add to a hundred and a set of measured masses works directly.
|
|
147
|
+
*
|
|
148
|
+
* The scaling step is the one that is usually waved through. Dividing by the smallest mole count
|
|
149
|
+
* can land on 1.5 or 1.33, and the ratios then have to be multiplied up until they are all close to
|
|
150
|
+
* whole numbers. That search is done here rather than left to inspection, and if no multiplier up
|
|
151
|
+
* to eight works the composition is refused rather than rounded into something tidy.
|
|
152
|
+
*/
|
|
153
|
+
function empiricalFormula(composition) {
|
|
154
|
+
const entries = Object.entries(composition).filter(([, amount]) => amount > 0);
|
|
155
|
+
if (entries.length === 0) throw new Error("a composition with nothing in it has no formula");
|
|
156
|
+
const moles = entries.map(([element, amount]) => {
|
|
157
|
+
const record = elementBySymbol(element);
|
|
158
|
+
if (!record) throw new Error(`"${element}" is not an element`);
|
|
159
|
+
return {
|
|
160
|
+
element,
|
|
161
|
+
moles: amount / record.atomicWeight
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
const smallest = Math.min(...moles.map((m) => m.moles));
|
|
165
|
+
const ratios = moles.map((m) => m.moles / smallest);
|
|
166
|
+
for (const multiplier of MULTIPLIERS) {
|
|
167
|
+
const scaled = ratios.map((r) => r * multiplier);
|
|
168
|
+
if (scaled.every((value) => Math.abs(value - Math.round(value)) <= WHOLE_TOLERANCE)) return moles.map((m, i) => ({
|
|
169
|
+
element: m.element,
|
|
170
|
+
count: Math.round(scaled[i])
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
throw new Error(`this composition does not reduce to a whole-number ratio: ${ratios.map((r, i) => `${moles[i].element} ${r.toFixed(3)}`).join(", ")}. Check the percentages add up and that no element is missing.`);
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* How many empirical units make up the real molecule.
|
|
177
|
+
*
|
|
178
|
+
* Rounded, because the ratio of a measured molar mass to an exact empirical mass never comes out
|
|
179
|
+
* whole. Refuses when it is not close to one, since a value of 2.4 means the molar mass or the
|
|
180
|
+
* composition is wrong and multiplying by 2 would bury that.
|
|
181
|
+
*/
|
|
182
|
+
function unitsInMolecule(empirical, relativeMolecularMass) {
|
|
183
|
+
const empiricalMass = empirical.reduce((total, { element, count }) => total + elementBySymbol(element).atomicWeight * count, 0);
|
|
184
|
+
const ratio = relativeMolecularMass / empiricalMass;
|
|
185
|
+
const whole = Math.round(ratio);
|
|
186
|
+
if (whole < 1 || Math.abs(ratio - whole) > .1) throw new Error(`a relative molecular mass of ${relativeMolecularMass} is ${ratio.toFixed(2)} times the empirical mass of ${empiricalMass.toFixed(1)}, which is not a whole number of units`);
|
|
187
|
+
return whole;
|
|
188
|
+
}
|
|
189
|
+
/** The molecular formula: the empirical formula multiplied up to fit the measured mass. */
|
|
190
|
+
const molecularFormulaFor = (empirical, relativeMolecularMass) => {
|
|
191
|
+
const n = unitsInMolecule(empirical, relativeMolecularMass);
|
|
192
|
+
return empirical.map(({ element, count }) => ({
|
|
193
|
+
element,
|
|
194
|
+
count: count * n
|
|
195
|
+
}));
|
|
196
|
+
};
|
|
197
|
+
/** Write counts back out as a formula, leaving a count of one unwritten. */
|
|
198
|
+
const formatFormula = (counts) => counts.map(({ element, count }) => `${element}${count === 1 ? "" : count}`).join("");
|
|
199
|
+
|
|
200
|
+
//#endregion
|
|
201
|
+
export { empiricalFormula, formatFormula, molarMass, molecularFormulaFor, parseFormula, percentComposition, unitsInMolecule };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Structure } from "./molecule.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/functional-groups.d.ts
|
|
4
|
+
type GroupKind = 'alkene' | 'alkyne' | 'arene' | 'alcohol' | 'halogenoalkane' | 'aldehyde' | 'ketone' | 'carboxylic-acid' | 'ester' | 'acyl-chloride' | 'amide' | 'amine' | 'nitrile';
|
|
5
|
+
interface FoundGroup {
|
|
6
|
+
readonly kind: GroupKind;
|
|
7
|
+
/** The atoms that make up the group, so a figure can highlight exactly these. */
|
|
8
|
+
readonly atoms: readonly number[];
|
|
9
|
+
/**
|
|
10
|
+
* Primary, secondary or tertiary, where the syllabus distinguishes them.
|
|
11
|
+
*
|
|
12
|
+
* Alcohols and halogenoalkanes, because the class decides the mechanism, and amines, because it
|
|
13
|
+
* decides the basicity.
|
|
14
|
+
*/
|
|
15
|
+
readonly klass?: 'primary' | 'secondary' | 'tertiary';
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Every functional group in the structure.
|
|
19
|
+
*
|
|
20
|
+
* An atom may appear in more than one group only when the chemistry really has two: an amino acid
|
|
21
|
+
* genuinely has both a carboxylic acid and an amine, and reporting one would lose the point of the
|
|
22
|
+
* molecule. What never happens is the same atoms being reported twice under different names, which
|
|
23
|
+
* is what the carbonyl classification exists to prevent.
|
|
24
|
+
*/
|
|
25
|
+
declare function functionalGroups(structure: Structure): FoundGroup[];
|
|
26
|
+
/** Does the structure contain this group? The question a reagent test actually asks. */
|
|
27
|
+
declare const hasGroup: (structure: Structure, kind: GroupKind) => boolean;
|
|
28
|
+
/** Group kinds present, each once, in the order they were found. */
|
|
29
|
+
declare const groupKinds: (structure: Structure) => GroupKind[];
|
|
30
|
+
//#endregion
|
|
31
|
+
export { FoundGroup, GroupKind, functionalGroups, groupKinds, hasGroup };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { bondsAt, implicitHydrogens, otherEnd } from "./molecule.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/chem/functional-groups.ts
|
|
4
|
+
/**
|
|
5
|
+
* functional-groups, found in a structure rather than declared beside it.
|
|
6
|
+
*
|
|
7
|
+
* WHAT THIS IS FOR. Every lab that highlights a functional group currently does it by listing vertex
|
|
8
|
+
* indices, which means the highlight and the structure are two independent claims and nothing checks
|
|
9
|
+
* that they agree. Worse, it means a learner is shown "here is the carbonyl" rather than being
|
|
10
|
+
* taught what makes a carbonyl a carbonyl, because the recognition was done by the author.
|
|
11
|
+
*
|
|
12
|
+
* Perception closes both. A group is a small pattern in the graph, and finding it is the same work
|
|
13
|
+
* the learner is being asked to do.
|
|
14
|
+
*
|
|
15
|
+
* ## Why the order matters
|
|
16
|
+
*
|
|
17
|
+
* Several groups share a C=O and are told apart only by what else is on that carbon. An ester, an
|
|
18
|
+
* acid, an amide, an acyl chloride, an aldehyde and a ketone all contain one. So the carbonyl is
|
|
19
|
+
* classified ONCE, by looking at its other neighbours, and the more specific answer wins: a
|
|
20
|
+
* structure with a C=O and an O-H on the same carbon is a carboxylic acid and is not reported as a
|
|
21
|
+
* ketone with an alcohol attached.
|
|
22
|
+
*
|
|
23
|
+
* That ordering is not an implementation detail. It is exactly the reasoning an exam question wants,
|
|
24
|
+
* and stating it as a sequence of tests is what makes it teachable.
|
|
25
|
+
*
|
|
26
|
+
* Pure: no React, no pixels.
|
|
27
|
+
*/
|
|
28
|
+
const isC = (s, id) => s.atoms[id].element === "C";
|
|
29
|
+
/** Carbons attached to this atom. */
|
|
30
|
+
const carbonNeighbours = (s, id) => bondsAt(s, id).map((b) => otherEnd(b, id)).filter((n) => isC(s, n));
|
|
31
|
+
/** An oxygen bonded to exactly one heavy atom, so its other bond is to hydrogen. */
|
|
32
|
+
const isHydroxyl = (s, id) => s.atoms[id].element === "O" && bondsAt(s, id).length === 1 && implicitHydrogens(s, id) === 1;
|
|
33
|
+
/** Primary, secondary or tertiary, from the carbons attached to the carbon bearing the group. */
|
|
34
|
+
function classify(s, carbon, exclude) {
|
|
35
|
+
const others = carbonNeighbours(s, carbon).filter((n) => n !== exclude).length;
|
|
36
|
+
return others <= 1 ? "primary" : others === 2 ? "secondary" : "tertiary";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Classify one carbonyl carbon by what else is attached to it.
|
|
40
|
+
*
|
|
41
|
+
* The whole of the carbonyl family, in the order an exam question works through it: look for the
|
|
42
|
+
* second oxygen first, because that is what separates the acid and the ester from everything else.
|
|
43
|
+
*/
|
|
44
|
+
function carbonylKind(s, carbon, oxygen) {
|
|
45
|
+
const attached = bondsAt(s, carbon).map((b) => otherEnd(b, carbon)).filter((n) => n !== oxygen);
|
|
46
|
+
const hydrogens = implicitHydrogens(s, carbon);
|
|
47
|
+
const hydroxyl = attached.find((n) => isHydroxyl(s, n));
|
|
48
|
+
if (hydroxyl !== void 0) return {
|
|
49
|
+
kind: "carboxylic-acid",
|
|
50
|
+
atoms: [
|
|
51
|
+
carbon,
|
|
52
|
+
oxygen,
|
|
53
|
+
hydroxyl
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
const etherO = attached.find((n) => s.atoms[n].element === "O" && bondsAt(s, n).length === 2);
|
|
57
|
+
if (etherO !== void 0) return {
|
|
58
|
+
kind: "ester",
|
|
59
|
+
atoms: [
|
|
60
|
+
carbon,
|
|
61
|
+
oxygen,
|
|
62
|
+
etherO
|
|
63
|
+
]
|
|
64
|
+
};
|
|
65
|
+
const chlorine = attached.find((n) => s.atoms[n].element === "Cl");
|
|
66
|
+
if (chlorine !== void 0) return {
|
|
67
|
+
kind: "acyl-chloride",
|
|
68
|
+
atoms: [
|
|
69
|
+
carbon,
|
|
70
|
+
oxygen,
|
|
71
|
+
chlorine
|
|
72
|
+
]
|
|
73
|
+
};
|
|
74
|
+
const nitrogen = attached.find((n) => s.atoms[n].element === "N");
|
|
75
|
+
if (nitrogen !== void 0) return {
|
|
76
|
+
kind: "amide",
|
|
77
|
+
atoms: [
|
|
78
|
+
carbon,
|
|
79
|
+
oxygen,
|
|
80
|
+
nitrogen
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
const carbons = attached.filter((n) => isC(s, n)).length;
|
|
84
|
+
if (hydrogens >= 1 || carbons <= 1) return {
|
|
85
|
+
kind: "aldehyde",
|
|
86
|
+
atoms: [carbon, oxygen]
|
|
87
|
+
};
|
|
88
|
+
return {
|
|
89
|
+
kind: "ketone",
|
|
90
|
+
atoms: [carbon, oxygen]
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Every functional group in the structure.
|
|
95
|
+
*
|
|
96
|
+
* An atom may appear in more than one group only when the chemistry really has two: an amino acid
|
|
97
|
+
* genuinely has both a carboxylic acid and an amine, and reporting one would lose the point of the
|
|
98
|
+
* molecule. What never happens is the same atoms being reported twice under different names, which
|
|
99
|
+
* is what the carbonyl classification exists to prevent.
|
|
100
|
+
*/
|
|
101
|
+
function functionalGroups(structure) {
|
|
102
|
+
const found = [];
|
|
103
|
+
const carbonylOxygens = /* @__PURE__ */ new Set();
|
|
104
|
+
const groupedO = /* @__PURE__ */ new Set();
|
|
105
|
+
const groupedN = /* @__PURE__ */ new Set();
|
|
106
|
+
for (const bond of structure.bonds) {
|
|
107
|
+
if (bond.order !== 2 || bond.aromatic) continue;
|
|
108
|
+
const pair = [bond.a, bond.b];
|
|
109
|
+
const carbon = pair.find((n) => isC(structure, n));
|
|
110
|
+
const oxygen = pair.find((n) => structure.atoms[n].element === "O");
|
|
111
|
+
if (carbon === void 0 || oxygen === void 0) continue;
|
|
112
|
+
const group = carbonylKind(structure, carbon, oxygen);
|
|
113
|
+
if (!group) continue;
|
|
114
|
+
found.push(group);
|
|
115
|
+
carbonylOxygens.add(oxygen);
|
|
116
|
+
for (const id of group.atoms) {
|
|
117
|
+
if (structure.atoms[id].element === "O") groupedO.add(id);
|
|
118
|
+
if (structure.atoms[id].element === "N") groupedN.add(id);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const bond of structure.bonds) {
|
|
122
|
+
if (bond.aromatic) continue;
|
|
123
|
+
const bothCarbon = isC(structure, bond.a) && isC(structure, bond.b);
|
|
124
|
+
if (bothCarbon && bond.order === 2) found.push({
|
|
125
|
+
kind: "alkene",
|
|
126
|
+
atoms: [bond.a, bond.b]
|
|
127
|
+
});
|
|
128
|
+
if (bothCarbon && bond.order === 3) found.push({
|
|
129
|
+
kind: "alkyne",
|
|
130
|
+
atoms: [bond.a, bond.b]
|
|
131
|
+
});
|
|
132
|
+
if (bond.order === 3 && !bothCarbon) {
|
|
133
|
+
const carbon = [bond.a, bond.b].find((n) => isC(structure, n));
|
|
134
|
+
const nitrogen = [bond.a, bond.b].find((n) => structure.atoms[n].element === "N");
|
|
135
|
+
if (carbon !== void 0 && nitrogen !== void 0) {
|
|
136
|
+
found.push({
|
|
137
|
+
kind: "nitrile",
|
|
138
|
+
atoms: [carbon, nitrogen]
|
|
139
|
+
});
|
|
140
|
+
groupedN.add(nitrogen);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (structure.atoms.some((a) => a.aromatic)) found.push({
|
|
145
|
+
kind: "arene",
|
|
146
|
+
atoms: structure.atoms.filter((a) => a.aromatic).map((a) => a.id)
|
|
147
|
+
});
|
|
148
|
+
for (const atom of structure.atoms) {
|
|
149
|
+
if (atom.element === "O" && !groupedO.has(atom.id) && isHydroxyl(structure, atom.id)) {
|
|
150
|
+
const carbon = carbonNeighbours(structure, atom.id)[0];
|
|
151
|
+
if (carbon !== void 0) found.push({
|
|
152
|
+
kind: "alcohol",
|
|
153
|
+
atoms: [carbon, atom.id],
|
|
154
|
+
klass: classify(structure, carbon, atom.id)
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if ([
|
|
158
|
+
"F",
|
|
159
|
+
"Cl",
|
|
160
|
+
"Br",
|
|
161
|
+
"I"
|
|
162
|
+
].includes(atom.element)) {
|
|
163
|
+
const carbon = carbonNeighbours(structure, atom.id)[0];
|
|
164
|
+
if (carbon !== void 0 && !found.some((g) => g.kind === "acyl-chloride" && g.atoms.includes(atom.id))) found.push({
|
|
165
|
+
kind: "halogenoalkane",
|
|
166
|
+
atoms: [carbon, atom.id],
|
|
167
|
+
klass: classify(structure, carbon, atom.id)
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (atom.element === "N" && !groupedN.has(atom.id)) {
|
|
171
|
+
const carbons = carbonNeighbours(structure, atom.id);
|
|
172
|
+
if (carbons.length >= 1) found.push({
|
|
173
|
+
kind: "amine",
|
|
174
|
+
atoms: [atom.id, ...carbons],
|
|
175
|
+
klass: carbons.length === 1 ? "primary" : carbons.length === 2 ? "secondary" : "tertiary"
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return found;
|
|
180
|
+
}
|
|
181
|
+
/** Does the structure contain this group? The question a reagent test actually asks. */
|
|
182
|
+
const hasGroup = (structure, kind) => functionalGroups(structure).some((g) => g.kind === kind);
|
|
183
|
+
/** Group kinds present, each once, in the order they were found. */
|
|
184
|
+
const groupKinds = (structure) => [...new Set(functionalGroups(structure).map((g) => g.kind))];
|
|
185
|
+
|
|
186
|
+
//#endregion
|
|
187
|
+
export { functionalGroups, groupKinds, hasGroup };
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
//#region src/chem/hazard.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* hazard, what a reagent does to you and the precaution that follows from it.
|
|
4
|
+
*
|
|
5
|
+
* ## Why this is a kernel and not a sticker on each lab
|
|
6
|
+
*
|
|
7
|
+
* Our labs put learners in front of concentrated ammonia, acidified manganate(VII) and nickel salts
|
|
8
|
+
* and said nothing about any of them. A simulation that teaches a learner to reach for a reagent
|
|
9
|
+
* without ever meeting its hazard has taught half of a practical skill, and the missing half is the
|
|
10
|
+
* half that hurts.
|
|
11
|
+
*
|
|
12
|
+
* Hazard is a property of a SUBSTANCE, so it is declared once here and read by whoever needs it. A
|
|
13
|
+
* warning written into each lab would drift, would be missing wherever someone forgot, and could
|
|
14
|
+
* never be checked.
|
|
15
|
+
*
|
|
16
|
+
* ## The three-part statement, enforced by the type
|
|
17
|
+
*
|
|
18
|
+
* An examiner credits a hazard statement only when it names three things: the substance, its
|
|
19
|
+
* specific hazard, and a precaution that FOLLOWS from that hazard. "Wear safety goggles and a lab
|
|
20
|
+
* coat" is what almost every candidate writes and it scores nothing, because it is true of every
|
|
21
|
+
* experiment ever performed and therefore says nothing about this one.
|
|
22
|
+
*
|
|
23
|
+
* So a `Hazard` cannot be constructed without all three parts, and {@link isGenericPrecaution}
|
|
24
|
+
* exists to refuse the ones that score nothing. A test asserts that no precaution in this file is
|
|
25
|
+
* generic, which means the data cannot quietly rot into a list of goggles.
|
|
26
|
+
*
|
|
27
|
+
* ## Concentration is part of the identity
|
|
28
|
+
*
|
|
29
|
+
* Dilute sulfuric acid is an irritant. Concentrated sulfuric acid is corrosive and reacts violently
|
|
30
|
+
* with water. They are different entries here, because a learner who writes "corrosive" for the
|
|
31
|
+
* dilute acid in a Paper 5 answer has not scored: the statement has to match the concentration that
|
|
32
|
+
* was planned.
|
|
33
|
+
*
|
|
34
|
+
* ## What this is not
|
|
35
|
+
*
|
|
36
|
+
* Teaching data, matched to what a school laboratory actually does and to what a mark scheme
|
|
37
|
+
* credits. It is not a risk assessment, and nothing here replaces the one a real practical needs.
|
|
38
|
+
*
|
|
39
|
+
* Pure: no React, no pixels.
|
|
40
|
+
*/
|
|
41
|
+
/** The GHS classes a school syllabus uses by name. */
|
|
42
|
+
type HazardClass = 'corrosive' | 'irritant' | 'flammable' | 'toxic' | 'harmful' | 'oxidising' | 'explosive' | 'carcinogenic' | 'environmental';
|
|
43
|
+
interface Hazard {
|
|
44
|
+
readonly hazard: HazardClass;
|
|
45
|
+
/** What the substance does, in the words a mark scheme credits. */
|
|
46
|
+
readonly statement: string;
|
|
47
|
+
/**
|
|
48
|
+
* The precaution that follows from THIS hazard.
|
|
49
|
+
*
|
|
50
|
+
* Never a generic one. A precaution that would be true of any experiment is not creditable and,
|
|
51
|
+
* more to the point, does not change what the learner does next.
|
|
52
|
+
*/
|
|
53
|
+
readonly precaution: string;
|
|
54
|
+
}
|
|
55
|
+
/** How the reagent is supplied, because the hazard depends on it. */
|
|
56
|
+
type Strength = 'dilute' | 'concentrated' | 'solid' | 'gas' | 'solution';
|
|
57
|
+
interface Reagent {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
/** As a learner would write it in a method. */
|
|
60
|
+
readonly name: string;
|
|
61
|
+
readonly formula: string;
|
|
62
|
+
readonly strength: Strength;
|
|
63
|
+
readonly hazards: readonly Hazard[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* True when a precaution is one of the generic ones and would score nothing on its own.
|
|
67
|
+
*
|
|
68
|
+
* Note the "on its own": gloves are a real precaution when they follow from a named corrosive
|
|
69
|
+
* substance, which is why the entries below that use them say what they are for.
|
|
70
|
+
*/
|
|
71
|
+
declare const isGenericPrecaution: (text: string) => boolean;
|
|
72
|
+
/**
|
|
73
|
+
* The reagents our chemistry labs actually put in a learner's hand.
|
|
74
|
+
*
|
|
75
|
+
* Added to when a lab needs one, never speculatively: an entry nobody reads is an entry nobody
|
|
76
|
+
* checks.
|
|
77
|
+
*/
|
|
78
|
+
declare const REAGENTS: readonly Reagent[];
|
|
79
|
+
declare const reagentById: (id: string) => Reagent | undefined;
|
|
80
|
+
/** Every reagent carrying a given hazard class, for a lesson that groups by hazard. */
|
|
81
|
+
declare const reagentsWith: (hazard: HazardClass) => Reagent[];
|
|
82
|
+
/**
|
|
83
|
+
* The creditable sentence: substance, hazard, precaution.
|
|
84
|
+
*
|
|
85
|
+
* Built rather than authored, so that every lab and every lesson states a hazard the same way and
|
|
86
|
+
* a learner meets the same three-part shape every time. That repetition is the teaching: the shape
|
|
87
|
+
* of the sentence is what the exam pays for.
|
|
88
|
+
*/
|
|
89
|
+
declare const hazardSentence: (reagent: Reagent, hazard: Hazard) => string;
|
|
90
|
+
/** Every creditable sentence for a reagent. */
|
|
91
|
+
declare const hazardSentences: (reagent: Reagent) => string[];
|
|
92
|
+
declare function worstHazard(reagent: Reagent): Hazard;
|
|
93
|
+
//#endregion
|
|
94
|
+
export { Hazard, HazardClass, REAGENTS, Reagent, Strength, hazardSentence, hazardSentences, isGenericPrecaution, reagentById, reagentsWith, worstHazard };
|