chemistrykit 0.1.0__py3-none-any.whl
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.
- chemistrykit/__init__.py +69 -0
- chemistrykit/analytical/__init__.py +61 -0
- chemistrykit/analytical/core/__init__.py +0 -0
- chemistrykit/analytical/core/base_system.py +118 -0
- chemistrykit/analytical/systems/__init__.py +0 -0
- chemistrykit/analytical/systems/calibration.py +131 -0
- chemistrykit/analytical/systems/chromatography.py +317 -0
- chemistrykit/analytical/systems/qtest.py +128 -0
- chemistrykit/analytical/systems/titration.py +240 -0
- chemistrykit/analytical/systems/uncertainty.py +189 -0
- chemistrykit/analytical/tests/__init__.py +0 -0
- chemistrykit/analytical/tests/test_calibration.py +49 -0
- chemistrykit/analytical/tests/test_chromatography.py +77 -0
- chemistrykit/analytical/tests/test_qtest.py +66 -0
- chemistrykit/analytical/tests/test_titration.py +83 -0
- chemistrykit/analytical/tests/test_uncertainty.py +54 -0
- chemistrykit/analytical/tests/test_visualizers.py +56 -0
- chemistrykit/analytical/utils/__init__.py +0 -0
- chemistrykit/analytical/utils/regression.py +67 -0
- chemistrykit/analytical/visualizers/__init__.py +0 -0
- chemistrykit/analytical/visualizers/analytical_plots.py +129 -0
- chemistrykit/constants.py +208 -0
- chemistrykit/crystal/__init__.py +50 -0
- chemistrykit/crystal/core/__init__.py +0 -0
- chemistrykit/crystal/core/base_system.py +165 -0
- chemistrykit/crystal/systems/__init__.py +0 -0
- chemistrykit/crystal/systems/crystal_systems.py +158 -0
- chemistrykit/crystal/systems/defects.py +125 -0
- chemistrykit/crystal/systems/lattice_energy.py +150 -0
- chemistrykit/crystal/systems/madelung.py +96 -0
- chemistrykit/crystal/systems/packing.py +166 -0
- chemistrykit/crystal/systems/xrd.py +222 -0
- chemistrykit/crystal/tests/__init__.py +0 -0
- chemistrykit/crystal/tests/test_crystal_systems.py +39 -0
- chemistrykit/crystal/tests/test_defects.py +54 -0
- chemistrykit/crystal/tests/test_lattice_energy.py +54 -0
- chemistrykit/crystal/tests/test_madelung.py +41 -0
- chemistrykit/crystal/tests/test_packing.py +73 -0
- chemistrykit/crystal/tests/test_visualizers.py +43 -0
- chemistrykit/crystal/tests/test_xrd.py +90 -0
- chemistrykit/crystal/utils/__init__.py +0 -0
- chemistrykit/crystal/utils/lattice_sums.py +89 -0
- chemistrykit/crystal/utils/reference_data.py +84 -0
- chemistrykit/crystal/visualizers/__init__.py +0 -0
- chemistrykit/crystal/visualizers/crystal_plots.py +118 -0
- chemistrykit/electrochem/__init__.py +76 -0
- chemistrykit/electrochem/core/__init__.py +0 -0
- chemistrykit/electrochem/core/base_system.py +117 -0
- chemistrykit/electrochem/systems/__init__.py +0 -0
- chemistrykit/electrochem/systems/battery.py +183 -0
- chemistrykit/electrochem/systems/butler_volmer.py +306 -0
- chemistrykit/electrochem/systems/electrolysis.py +214 -0
- chemistrykit/electrochem/systems/nernst.py +268 -0
- chemistrykit/electrochem/systems/standard_potentials.py +221 -0
- chemistrykit/electrochem/tests/__init__.py +0 -0
- chemistrykit/electrochem/tests/test_battery.py +68 -0
- chemistrykit/electrochem/tests/test_butler_volmer.py +84 -0
- chemistrykit/electrochem/tests/test_electrolysis.py +61 -0
- chemistrykit/electrochem/tests/test_nernst.py +83 -0
- chemistrykit/electrochem/tests/test_standard_potentials.py +58 -0
- chemistrykit/electrochem/tests/test_visualizers.py +42 -0
- chemistrykit/electrochem/utils/__init__.py +0 -0
- chemistrykit/electrochem/utils/regression.py +57 -0
- chemistrykit/electrochem/visualizers/__init__.py +0 -0
- chemistrykit/electrochem/visualizers/electrochem_plots.py +121 -0
- chemistrykit/integrators/__init__.py +55 -0
- chemistrykit/integrators/adaptive.py +201 -0
- chemistrykit/integrators/fixed_step.py +352 -0
- chemistrykit/integrators/tests/__init__.py +0 -0
- chemistrykit/integrators/tests/test_integrators.py +114 -0
- chemistrykit/kinetics/__init__.py +55 -0
- chemistrykit/kinetics/core/__init__.py +0 -0
- chemistrykit/kinetics/core/base_system.py +247 -0
- chemistrykit/kinetics/core/integrators.py +28 -0
- chemistrykit/kinetics/systems/__init__.py +0 -0
- chemistrykit/kinetics/systems/arrhenius.py +120 -0
- chemistrykit/kinetics/systems/enzyme.py +269 -0
- chemistrykit/kinetics/systems/networks.py +362 -0
- chemistrykit/kinetics/systems/oscillators.py +131 -0
- chemistrykit/kinetics/systems/rate_laws.py +192 -0
- chemistrykit/kinetics/tests/__init__.py +0 -0
- chemistrykit/kinetics/tests/test_arrhenius.py +48 -0
- chemistrykit/kinetics/tests/test_enzyme.py +89 -0
- chemistrykit/kinetics/tests/test_networks.py +102 -0
- chemistrykit/kinetics/tests/test_oscillators.py +48 -0
- chemistrykit/kinetics/tests/test_rate_laws.py +69 -0
- chemistrykit/kinetics/tests/test_visualizers.py +57 -0
- chemistrykit/kinetics/utils/__init__.py +0 -0
- chemistrykit/kinetics/utils/linear_regression.py +56 -0
- chemistrykit/kinetics/visualizers/__init__.py +0 -0
- chemistrykit/kinetics/visualizers/kinetics_plots.py +147 -0
- chemistrykit/md/__init__.py +57 -0
- chemistrykit/md/core/__init__.py +1 -0
- chemistrykit/md/core/base_system.py +363 -0
- chemistrykit/md/systems/__init__.py +2 -0
- chemistrykit/md/systems/lj_fluid.py +466 -0
- chemistrykit/md/systems/pair_potentials.py +550 -0
- chemistrykit/md/systems/thermostats.py +172 -0
- chemistrykit/md/tests/__init__.py +0 -0
- chemistrykit/md/tests/test_lj_fluid.py +108 -0
- chemistrykit/md/tests/test_pair_potentials.py +198 -0
- chemistrykit/md/tests/test_pbc.py +77 -0
- chemistrykit/md/tests/test_thermostats.py +55 -0
- chemistrykit/md/tests/test_visualizers.py +47 -0
- chemistrykit/md/utils/__init__.py +1 -0
- chemistrykit/md/utils/neighbor_list.py +123 -0
- chemistrykit/md/utils/pbc.py +88 -0
- chemistrykit/md/visualizers/__init__.py +2 -0
- chemistrykit/md/visualizers/md_plots.py +146 -0
- chemistrykit/periodic_table.py +341 -0
- chemistrykit/photochem/__init__.py +58 -0
- chemistrykit/photochem/core/__init__.py +0 -0
- chemistrykit/photochem/core/base_system.py +54 -0
- chemistrykit/photochem/systems/__init__.py +0 -0
- chemistrykit/photochem/systems/jablonski.py +157 -0
- chemistrykit/photochem/systems/photostationary_state.py +186 -0
- chemistrykit/photochem/systems/quantum_yield.py +226 -0
- chemistrykit/photochem/systems/stern_volmer.py +224 -0
- chemistrykit/photochem/tests/__init__.py +0 -0
- chemistrykit/photochem/tests/test_jablonski.py +56 -0
- chemistrykit/photochem/tests/test_photostationary_state.py +75 -0
- chemistrykit/photochem/tests/test_quantum_yield.py +69 -0
- chemistrykit/photochem/tests/test_stern_volmer.py +71 -0
- chemistrykit/photochem/tests/test_visualizers.py +41 -0
- chemistrykit/photochem/utils/__init__.py +0 -0
- chemistrykit/photochem/utils/regression.py +56 -0
- chemistrykit/photochem/visualizers/__init__.py +0 -0
- chemistrykit/photochem/visualizers/photochem_plots.py +109 -0
- chemistrykit/polymer/__init__.py +70 -0
- chemistrykit/polymer/core/__init__.py +0 -0
- chemistrykit/polymer/core/base_system.py +106 -0
- chemistrykit/polymer/systems/__init__.py +0 -0
- chemistrykit/polymer/systems/chain_growth.py +253 -0
- chemistrykit/polymer/systems/chain_statistics.py +222 -0
- chemistrykit/polymer/systems/molecular_weight_distribution.py +293 -0
- chemistrykit/polymer/systems/step_growth.py +145 -0
- chemistrykit/polymer/tests/__init__.py +0 -0
- chemistrykit/polymer/tests/test_chain_growth.py +90 -0
- chemistrykit/polymer/tests/test_chain_statistics.py +97 -0
- chemistrykit/polymer/tests/test_molecular_weight_distribution.py +94 -0
- chemistrykit/polymer/tests/test_step_growth.py +36 -0
- chemistrykit/polymer/tests/test_visualizers.py +47 -0
- chemistrykit/polymer/utils/__init__.py +0 -0
- chemistrykit/polymer/utils/moments.py +84 -0
- chemistrykit/polymer/visualizers/__init__.py +0 -0
- chemistrykit/polymer/visualizers/polymer_plots.py +134 -0
- chemistrykit/quantum/__init__.py +72 -0
- chemistrykit/quantum/core/__init__.py +0 -0
- chemistrykit/quantum/core/base_system.py +194 -0
- chemistrykit/quantum/systems/__init__.py +0 -0
- chemistrykit/quantum/systems/harmonic_oscillator.py +281 -0
- chemistrykit/quantum/systems/hartree_fock.py +242 -0
- chemistrykit/quantum/systems/huckel.py +342 -0
- chemistrykit/quantum/systems/hydrogenlike.py +260 -0
- chemistrykit/quantum/systems/particle_in_box.py +287 -0
- chemistrykit/quantum/systems/perturbation.py +258 -0
- chemistrykit/quantum/systems/rigid_rotor.py +146 -0
- chemistrykit/quantum/tests/__init__.py +0 -0
- chemistrykit/quantum/tests/test_harmonic_oscillator.py +99 -0
- chemistrykit/quantum/tests/test_hartree_fock.py +78 -0
- chemistrykit/quantum/tests/test_huckel.py +112 -0
- chemistrykit/quantum/tests/test_hydrogenlike.py +98 -0
- chemistrykit/quantum/tests/test_particle_in_box.py +109 -0
- chemistrykit/quantum/tests/test_perturbation.py +105 -0
- chemistrykit/quantum/tests/test_rigid_rotor.py +53 -0
- chemistrykit/quantum/tests/test_secular_equation.py +63 -0
- chemistrykit/quantum/tests/test_visualizers.py +56 -0
- chemistrykit/quantum/utils/__init__.py +0 -0
- chemistrykit/quantum/utils/basis_sets.py +223 -0
- chemistrykit/quantum/utils/secular_equation.py +116 -0
- chemistrykit/quantum/visualizers/__init__.py +0 -0
- chemistrykit/quantum/visualizers/quantum_plots.py +188 -0
- chemistrykit/solutions/__init__.py +65 -0
- chemistrykit/solutions/core/__init__.py +0 -0
- chemistrykit/solutions/core/base_system.py +154 -0
- chemistrykit/solutions/systems/__init__.py +0 -0
- chemistrykit/solutions/systems/acid_base.py +324 -0
- chemistrykit/solutions/systems/activity.py +152 -0
- chemistrykit/solutions/systems/solubility.py +155 -0
- chemistrykit/solutions/systems/titration.py +219 -0
- chemistrykit/solutions/tests/__init__.py +0 -0
- chemistrykit/solutions/tests/test_acid_base.py +93 -0
- chemistrykit/solutions/tests/test_activity.py +61 -0
- chemistrykit/solutions/tests/test_solubility.py +68 -0
- chemistrykit/solutions/tests/test_titration.py +86 -0
- chemistrykit/solutions/tests/test_visualizers.py +38 -0
- chemistrykit/solutions/utils/__init__.py +0 -0
- chemistrykit/solutions/utils/rootfinding.py +117 -0
- chemistrykit/solutions/visualizers/__init__.py +0 -0
- chemistrykit/solutions/visualizers/solutions_plots.py +93 -0
- chemistrykit/spectro/__init__.py +70 -0
- chemistrykit/spectro/core/__init__.py +0 -0
- chemistrykit/spectro/core/base_system.py +106 -0
- chemistrykit/spectro/systems/__init__.py +0 -0
- chemistrykit/spectro/systems/beer_lambert.py +174 -0
- chemistrykit/spectro/systems/electronic.py +183 -0
- chemistrykit/spectro/systems/nmr.py +197 -0
- chemistrykit/spectro/systems/rotational.py +192 -0
- chemistrykit/spectro/systems/vibrational.py +419 -0
- chemistrykit/spectro/tests/__init__.py +0 -0
- chemistrykit/spectro/tests/test_beer_lambert.py +66 -0
- chemistrykit/spectro/tests/test_electronic.py +71 -0
- chemistrykit/spectro/tests/test_lineshapes.py +75 -0
- chemistrykit/spectro/tests/test_nmr.py +73 -0
- chemistrykit/spectro/tests/test_rotational.py +72 -0
- chemistrykit/spectro/tests/test_spectrum.py +31 -0
- chemistrykit/spectro/tests/test_vibrational.py +122 -0
- chemistrykit/spectro/tests/test_visualizers.py +40 -0
- chemistrykit/spectro/utils/__init__.py +0 -0
- chemistrykit/spectro/utils/lineshapes.py +214 -0
- chemistrykit/spectro/visualizers/__init__.py +0 -0
- chemistrykit/spectro/visualizers/spectro_plots.py +101 -0
- chemistrykit/statmech/__init__.py +51 -0
- chemistrykit/statmech/core/__init__.py +1 -0
- chemistrykit/statmech/core/base_system.py +151 -0
- chemistrykit/statmech/systems/__init__.py +3 -0
- chemistrykit/statmech/systems/lattice_gas.py +151 -0
- chemistrykit/statmech/systems/maxwell_boltzmann.py +211 -0
- chemistrykit/statmech/systems/partition_functions.py +371 -0
- chemistrykit/statmech/tests/__init__.py +0 -0
- chemistrykit/statmech/tests/test_lattice_gas.py +58 -0
- chemistrykit/statmech/tests/test_maxwell_boltzmann.py +95 -0
- chemistrykit/statmech/tests/test_partition_functions.py +137 -0
- chemistrykit/statmech/tests/test_visualizers.py +38 -0
- chemistrykit/statmech/utils/__init__.py +2 -0
- chemistrykit/statmech/utils/combinatorics.py +78 -0
- chemistrykit/statmech/utils/thermal_wavelength.py +53 -0
- chemistrykit/statmech/visualizers/__init__.py +2 -0
- chemistrykit/statmech/visualizers/statmech_plots.py +110 -0
- chemistrykit/structure/__init__.py +49 -0
- chemistrykit/structure/core/__init__.py +0 -0
- chemistrykit/structure/core/base_system.py +278 -0
- chemistrykit/structure/systems/__init__.py +0 -0
- chemistrykit/structure/systems/bonding.py +193 -0
- chemistrykit/structure/systems/lewis.py +180 -0
- chemistrykit/structure/systems/point_group.py +498 -0
- chemistrykit/structure/systems/vsepr.py +296 -0
- chemistrykit/structure/tests/__init__.py +0 -0
- chemistrykit/structure/tests/test_bonding.py +65 -0
- chemistrykit/structure/tests/test_lewis.py +62 -0
- chemistrykit/structure/tests/test_molecule.py +89 -0
- chemistrykit/structure/tests/test_point_group.py +165 -0
- chemistrykit/structure/tests/test_visualizers.py +33 -0
- chemistrykit/structure/tests/test_vsepr.py +103 -0
- chemistrykit/structure/utils/__init__.py +0 -0
- chemistrykit/structure/utils/symmetry_ops.py +243 -0
- chemistrykit/structure/visualizers/__init__.py +0 -0
- chemistrykit/structure/visualizers/structure_plots.py +124 -0
- chemistrykit/surface/__init__.py +45 -0
- chemistrykit/surface/core/__init__.py +0 -0
- chemistrykit/surface/core/base_system.py +83 -0
- chemistrykit/surface/systems/__init__.py +0 -0
- chemistrykit/surface/systems/bet.py +220 -0
- chemistrykit/surface/systems/catalysis.py +169 -0
- chemistrykit/surface/systems/freundlich.py +175 -0
- chemistrykit/surface/systems/langmuir.py +195 -0
- chemistrykit/surface/systems/langmuir_hinshelwood.py +145 -0
- chemistrykit/surface/tests/__init__.py +0 -0
- chemistrykit/surface/tests/test_bet.py +65 -0
- chemistrykit/surface/tests/test_catalysis.py +35 -0
- chemistrykit/surface/tests/test_freundlich.py +45 -0
- chemistrykit/surface/tests/test_langmuir.py +61 -0
- chemistrykit/surface/tests/test_langmuir_hinshelwood.py +49 -0
- chemistrykit/surface/tests/test_visualizers.py +40 -0
- chemistrykit/surface/utils/__init__.py +0 -0
- chemistrykit/surface/utils/regression.py +57 -0
- chemistrykit/surface/visualizers/__init__.py +0 -0
- chemistrykit/surface/visualizers/surface_plots.py +104 -0
- chemistrykit/tests/__init__.py +0 -0
- chemistrykit/tests/test_package.py +62 -0
- chemistrykit/thermo/__init__.py +61 -0
- chemistrykit/thermo/core/__init__.py +0 -0
- chemistrykit/thermo/core/base_system.py +106 -0
- chemistrykit/thermo/systems/__init__.py +0 -0
- chemistrykit/thermo/systems/equations_of_state.py +303 -0
- chemistrykit/thermo/systems/equilibrium.py +433 -0
- chemistrykit/thermo/systems/mixtures.py +273 -0
- chemistrykit/thermo/systems/phase_equilibria.py +187 -0
- chemistrykit/thermo/tests/__init__.py +0 -0
- chemistrykit/thermo/tests/test_equations_of_state.py +95 -0
- chemistrykit/thermo/tests/test_equilibrium.py +112 -0
- chemistrykit/thermo/tests/test_mixtures.py +68 -0
- chemistrykit/thermo/tests/test_phase_equilibria.py +56 -0
- chemistrykit/thermo/tests/test_visualizers.py +53 -0
- chemistrykit/thermo/utils/__init__.py +0 -0
- chemistrykit/thermo/utils/cubic_roots.py +58 -0
- chemistrykit/thermo/utils/regression.py +57 -0
- chemistrykit/thermo/visualizers/__init__.py +0 -0
- chemistrykit/thermo/visualizers/thermo_plots.py +157 -0
- chemistrykit-0.1.0.dist-info/METADATA +200 -0
- chemistrykit-0.1.0.dist-info/RECORD +294 -0
- chemistrykit-0.1.0.dist-info/WHEEL +5 -0
- chemistrykit-0.1.0.dist-info/licenses/LICENSE +21 -0
- chemistrykit-0.1.0.dist-info/top_level.txt +1 -0
chemistrykit/__init__.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""chemistrykit: unified numerical toolkit for computational chemistry.
|
|
2
|
+
|
|
3
|
+
Import as ``ck`` by convention::
|
|
4
|
+
|
|
5
|
+
import chemistrykit as ck
|
|
6
|
+
ck.kinetics.FirstOrder(k=0.1, C0=1.0)
|
|
7
|
+
ck.kinetics.StoichiometricNetwork.consecutive(k1=1.0, k2=0.3)
|
|
8
|
+
ck.thermo.VanDerWaals(a=0.1448, b=3.913e-5)
|
|
9
|
+
ck.solutions.WeakAcid(Ca=0.1, Ka=1.8e-5).pH()
|
|
10
|
+
ck.md.LJFluid.from_lattice(n_per_side=4, density=0.6, temperature=1.0)
|
|
11
|
+
ck.statmech.MaxwellBoltzmannSpeedDistribution(mass=6.63e-26, temperature=298.15)
|
|
12
|
+
ck.structure.determine_point_group(water_molecule)
|
|
13
|
+
ck.spectro.rotational_spectrum(rotor, J_max=10, temperature=300.0)
|
|
14
|
+
ck.electrochem.nernst_potential(E_standard=0.34, n=2, Q=0.01)
|
|
15
|
+
ck.photochem.jablonski_network(kf=2.0, kic=1.0, kisc=0.5, kp=0.3, kic_T=0.2)
|
|
16
|
+
ck.surface.LangmuirIsotherm(K=2.0, qmax=5.0).loading(P=1.0)
|
|
17
|
+
ck.polymer.IdealChain().end_to_end_distance(n=1000, b=0.5)
|
|
18
|
+
ck.crystal.FaceCenteredCubicPacking().packing_fraction()
|
|
19
|
+
ck.analytical.fit_calibration([0, 1, 2, 3], [0.1, 1.0, 2.1, 2.9]).lod()
|
|
20
|
+
ck.constants.R
|
|
21
|
+
ck.integrators.rk4_integrate(...)
|
|
22
|
+
|
|
23
|
+
chemistrykit mirrors the architecture of the sibling project physicskit
|
|
24
|
+
(pk): one subpackage per chemistry domain, sharing common ODE integrators
|
|
25
|
+
(:mod:`chemistrykit.integrators`) and physical/chemical constants
|
|
26
|
+
(:mod:`chemistrykit.constants`). All 14 domains from
|
|
27
|
+
``chemistrykit-spec.md``'s build plan are implemented; see ``__all__``
|
|
28
|
+
below for the full list.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from chemistrykit import (
|
|
32
|
+
analytical,
|
|
33
|
+
constants,
|
|
34
|
+
crystal,
|
|
35
|
+
electrochem,
|
|
36
|
+
integrators,
|
|
37
|
+
kinetics,
|
|
38
|
+
md,
|
|
39
|
+
photochem,
|
|
40
|
+
polymer,
|
|
41
|
+
quantum,
|
|
42
|
+
solutions,
|
|
43
|
+
spectro,
|
|
44
|
+
statmech,
|
|
45
|
+
structure,
|
|
46
|
+
surface,
|
|
47
|
+
thermo,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__version__ = "0.1.0"
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"constants",
|
|
54
|
+
"integrators",
|
|
55
|
+
"kinetics",
|
|
56
|
+
"thermo",
|
|
57
|
+
"solutions",
|
|
58
|
+
"md",
|
|
59
|
+
"statmech",
|
|
60
|
+
"quantum",
|
|
61
|
+
"spectro",
|
|
62
|
+
"structure",
|
|
63
|
+
"electrochem",
|
|
64
|
+
"photochem",
|
|
65
|
+
"surface",
|
|
66
|
+
"polymer",
|
|
67
|
+
"crystal",
|
|
68
|
+
"analytical",
|
|
69
|
+
]
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""chemistrykit.analytical: analytical chemistry.
|
|
2
|
+
|
|
3
|
+
Acid-base titration curves are provided by
|
|
4
|
+
:mod:`chemistrykit.solutions.systems.titration`; this subpackage adds
|
|
5
|
+
redox and complexometric (EDTA) titration-curve simulation with
|
|
6
|
+
equivalence-point detection; chromatographic plate theory and the van
|
|
7
|
+
Deemter equation (resolution, selectivity); linear-regression calibration
|
|
8
|
+
curves with IUPAC-convention limits of detection/quantitation; and
|
|
9
|
+
propagation-of-uncertainty formulas plus Dixon's Q-test for outlier
|
|
10
|
+
rejection.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__version__ = "0.1.0"
|
|
14
|
+
|
|
15
|
+
from chemistrykit.analytical.core.base_system import TitrationCurve, TitrationCurveResult
|
|
16
|
+
from chemistrykit.analytical.systems.calibration import LinearCalibration, fit_calibration
|
|
17
|
+
from chemistrykit.analytical.systems.chromatography import (
|
|
18
|
+
minimum_plate_height,
|
|
19
|
+
optimum_flow_velocity,
|
|
20
|
+
plate_height,
|
|
21
|
+
resolution,
|
|
22
|
+
retention_factor,
|
|
23
|
+
selectivity_factor,
|
|
24
|
+
simulate_chromatogram,
|
|
25
|
+
theoretical_plates,
|
|
26
|
+
van_deemter_H,
|
|
27
|
+
)
|
|
28
|
+
from chemistrykit.analytical.systems.qtest import Q_CRITICAL_TABLE, QTestResult, dixon_q_test
|
|
29
|
+
from chemistrykit.analytical.systems.titration import EDTATitration, RedoxTitration
|
|
30
|
+
from chemistrykit.analytical.systems.uncertainty import (
|
|
31
|
+
propagate_power,
|
|
32
|
+
propagate_product,
|
|
33
|
+
propagate_sum,
|
|
34
|
+
propagate_uncertainty,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
"__version__",
|
|
39
|
+
"TitrationCurve",
|
|
40
|
+
"TitrationCurveResult",
|
|
41
|
+
"RedoxTitration",
|
|
42
|
+
"EDTATitration",
|
|
43
|
+
"theoretical_plates",
|
|
44
|
+
"plate_height",
|
|
45
|
+
"van_deemter_H",
|
|
46
|
+
"optimum_flow_velocity",
|
|
47
|
+
"minimum_plate_height",
|
|
48
|
+
"retention_factor",
|
|
49
|
+
"selectivity_factor",
|
|
50
|
+
"resolution",
|
|
51
|
+
"simulate_chromatogram",
|
|
52
|
+
"LinearCalibration",
|
|
53
|
+
"fit_calibration",
|
|
54
|
+
"propagate_sum",
|
|
55
|
+
"propagate_product",
|
|
56
|
+
"propagate_power",
|
|
57
|
+
"propagate_uncertainty",
|
|
58
|
+
"Q_CRITICAL_TABLE",
|
|
59
|
+
"QTestResult",
|
|
60
|
+
"dixon_q_test",
|
|
61
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
r"""Abstract base class for potentiometric-style titration-curve models, and a note on scope.
|
|
2
|
+
|
|
3
|
+
:class:`TitrationCurve` captures the one genuinely polymorphic shape in
|
|
4
|
+
this domain: several titration *types* (redox, complexometric --
|
|
5
|
+
:mod:`chemistrykit.analytical.systems.titration`) that all reduce to
|
|
6
|
+
"compute some scalar response as a function of titrant volume, then find
|
|
7
|
+
the equivalence point as the point of steepest response change" -- the
|
|
8
|
+
same shape that :class:`chemistrykit.solutions.core.base_system.Titration`
|
|
9
|
+
already captures for acid-base titrations (pH vs. volume).
|
|
10
|
+
|
|
11
|
+
**Why a new ABC here, rather than reusing** :class:`chemistrykit.solutions.core.base_system.Titration`:
|
|
12
|
+
that class's abstract method is named and documented specifically as
|
|
13
|
+
``pH_at`` (a *hydrogen-ion* activity readout), which is the wrong
|
|
14
|
+
abstraction for a redox titration's electrode potential `E` or a
|
|
15
|
+
complexometric titration's `pM`. Rather than force those into a
|
|
16
|
+
pH-flavored interface (or reach across a domain boundary the way
|
|
17
|
+
``chemistrykit.photochem`` reaches into ``chemistrykit.kinetics`` for
|
|
18
|
+
substantial shared machinery -- not warranted here, since the shared
|
|
19
|
+
logic is a handful of lines), :class:`TitrationCurve` reimplements the
|
|
20
|
+
same small pattern (curve, steepest-ascent-or-descent equivalence-point
|
|
21
|
+
detection) with a response-type-neutral abstract method name, exactly
|
|
22
|
+
the way :mod:`chemistrykit.surface.utils.regression`,
|
|
23
|
+
:mod:`chemistrykit.electrochem.utils.regression`, and
|
|
24
|
+
:mod:`chemistrykit.photochem.utils.regression` each keep their own small
|
|
25
|
+
``linear_fit`` rather than importing one another's. Acid-base titration
|
|
26
|
+
curves are *not* reimplemented here at all -- :mod:`chemistrykit.analytical`'s
|
|
27
|
+
examples and tests use
|
|
28
|
+
:mod:`chemistrykit.solutions.systems.titration`'s classes directly for
|
|
29
|
+
that case, side by side with the new redox/complexometric models below.
|
|
30
|
+
|
|
31
|
+
Chromatography (:mod:`chemistrykit.analytical.systems.chromatography`),
|
|
32
|
+
calibration curves (:mod:`chemistrykit.analytical.systems.calibration`),
|
|
33
|
+
uncertainty propagation (:mod:`chemistrykit.analytical.systems.uncertainty`),
|
|
34
|
+
and the Q-test (:mod:`chemistrykit.analytical.systems.qtest`) are each a
|
|
35
|
+
self-contained set of formulas with no swappable sibling, so -- following
|
|
36
|
+
``chemistrykit.electrochem``/``chemistrykit.photochem``/``chemistrykit.surface``'s
|
|
37
|
+
precedent -- they stay as plain functions (plus small result dataclasses)
|
|
38
|
+
in their own ``systems/`` modules.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
from abc import ABC, abstractmethod
|
|
44
|
+
from dataclasses import dataclass
|
|
45
|
+
|
|
46
|
+
import numpy as np
|
|
47
|
+
|
|
48
|
+
__all__ = ["TitrationCurveResult", "TitrationCurve"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class TitrationCurveResult:
|
|
53
|
+
"""Container for the output of a :meth:`TitrationCurve.curve` call."""
|
|
54
|
+
|
|
55
|
+
V: np.ndarray
|
|
56
|
+
"""ndarray: Volume(s) of titrant added, in L (or any consistent volume unit)."""
|
|
57
|
+
|
|
58
|
+
response: np.ndarray
|
|
59
|
+
"""ndarray: The titration's response variable at each volume in `V`
|
|
60
|
+
(e.g. electrode potential `E` in volts, or `pM`)."""
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class TitrationCurve(ABC):
|
|
64
|
+
"""Common base for a potentiometric titration-curve model whose response is not pH.
|
|
65
|
+
|
|
66
|
+
Concrete subclasses implement :meth:`response_at`; :meth:`curve` and
|
|
67
|
+
:meth:`find_equivalence_point` are then available for free, mirroring
|
|
68
|
+
:meth:`chemistrykit.solutions.core.base_system.Titration.curve`/
|
|
69
|
+
:meth:`~chemistrykit.solutions.core.base_system.Titration.find_equivalence_point`.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def response_at(self, V: np.ndarray) -> np.ndarray:
|
|
74
|
+
"""Return the titration's response variable at each titrant volume in `V`.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
V : ndarray
|
|
79
|
+
Volume(s) of titrant added, in L.
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
ndarray
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def curve(self, V) -> TitrationCurveResult:
|
|
87
|
+
"""Compute the full titration curve over a range of titrant volumes.
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
V : array-like of float
|
|
92
|
+
Volumes of titrant added, in L.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
TitrationCurveResult
|
|
97
|
+
"""
|
|
98
|
+
V = np.atleast_1d(np.asarray(V, dtype=np.float64))
|
|
99
|
+
response = np.asarray(self.response_at(V), dtype=np.float64)
|
|
100
|
+
return TitrationCurveResult(V=V, response=response)
|
|
101
|
+
|
|
102
|
+
def find_equivalence_point(self, V) -> float:
|
|
103
|
+
r"""Numerically locate the equivalence point as the point of steepest response change.
|
|
104
|
+
|
|
105
|
+
Parameters
|
|
106
|
+
----------
|
|
107
|
+
V : array-like of float
|
|
108
|
+
A sufficiently fine grid of titrant volumes spanning the
|
|
109
|
+
equivalence point, in L.
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
float
|
|
114
|
+
"""
|
|
115
|
+
result = self.curve(V)
|
|
116
|
+
d_response_dV = np.gradient(result.response, result.V)
|
|
117
|
+
idx = int(np.argmax(np.abs(d_response_dV)))
|
|
118
|
+
return float(result.V[idx])
|
|
File without changes
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
r"""Linear-regression calibration curves, and IUPAC-convention limits of detection/quantitation.
|
|
2
|
+
|
|
3
|
+
See Harris, *Quantitative Chemical Analysis*, 9th ed., Ch. 4.5 ("Method
|
|
4
|
+
Validation") and Ch. 5, or the IUPAC recommendation (G. L. Long & J. D.
|
|
5
|
+
Winefordner, *Anal. Chem.* 55, 712A (1983)), for the standard :math:`3.3
|
|
6
|
+
\sigma/m` (LOD) and :math:`10\sigma/m` (LOQ) convention, with `sigma` the
|
|
7
|
+
calibration curve's residual standard error and `m` its slope.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from chemistrykit.analytical.utils.regression import linear_fit
|
|
17
|
+
|
|
18
|
+
__all__ = ["LinearCalibration", "fit_calibration"]
|
|
19
|
+
|
|
20
|
+
#: float: IUPAC LOD multiplier (Long & Winefordner, 1983).
|
|
21
|
+
_LOD_MULTIPLIER = 3.3
|
|
22
|
+
|
|
23
|
+
#: float: IUPAC LOQ multiplier (Long & Winefordner, 1983).
|
|
24
|
+
_LOQ_MULTIPLIER = 10.0
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class LinearCalibration:
|
|
29
|
+
"""A fitted instrument-response-vs-concentration calibration curve, ``signal = slope*conc + intercept``."""
|
|
30
|
+
|
|
31
|
+
slope: float
|
|
32
|
+
"""float: Sensitivity, signal units per concentration unit."""
|
|
33
|
+
|
|
34
|
+
intercept: float
|
|
35
|
+
"""float: Signal at zero concentration (ideally the blank signal)."""
|
|
36
|
+
|
|
37
|
+
r_squared: float
|
|
38
|
+
"""float: Coefficient of determination of the fit."""
|
|
39
|
+
|
|
40
|
+
residual_std_error: float
|
|
41
|
+
"""float: :math:`s_{y/x}`, the residual standard error about the fit
|
|
42
|
+
(Harris, *Quantitative Chemical Analysis*, 9th ed., Ch. 4.5)."""
|
|
43
|
+
|
|
44
|
+
def predict_signal(self, concentration):
|
|
45
|
+
"""Predict the instrument signal at given concentration(s).
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
concentration : float or array-like of float
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
float or ndarray
|
|
54
|
+
"""
|
|
55
|
+
c = np.asarray(concentration, dtype=np.float64)
|
|
56
|
+
result = self.slope * c + self.intercept
|
|
57
|
+
return float(result) if result.ndim == 0 else result
|
|
58
|
+
|
|
59
|
+
def predict_concentration(self, signal):
|
|
60
|
+
r"""Invert the calibration to estimate concentration from a measured signal.
|
|
61
|
+
|
|
62
|
+
.. math::
|
|
63
|
+
|
|
64
|
+
\hat c = (y_{measured} - b)/m
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
signal : float or array-like of float
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
float or ndarray
|
|
73
|
+
"""
|
|
74
|
+
y = np.asarray(signal, dtype=np.float64)
|
|
75
|
+
result = (y - self.intercept) / self.slope
|
|
76
|
+
return float(result) if result.ndim == 0 else result
|
|
77
|
+
|
|
78
|
+
def lod(self) -> float:
|
|
79
|
+
r"""Limit of detection, :math:`\text{LOD}=3.3\,s_{y/x}/|m|` (IUPAC convention).
|
|
80
|
+
|
|
81
|
+
Returns
|
|
82
|
+
-------
|
|
83
|
+
float
|
|
84
|
+
"""
|
|
85
|
+
return _LOD_MULTIPLIER * self.residual_std_error / abs(self.slope)
|
|
86
|
+
|
|
87
|
+
def loq(self) -> float:
|
|
88
|
+
r"""Limit of quantitation, :math:`\text{LOQ}=10\,s_{y/x}/|m|` (IUPAC convention).
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
float
|
|
93
|
+
"""
|
|
94
|
+
return _LOQ_MULTIPLIER * self.residual_std_error / abs(self.slope)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def fit_calibration(concentration, signal) -> LinearCalibration:
|
|
98
|
+
r"""Fit a linear calibration curve (signal vs. concentration) by ordinary least squares.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
concentration : array-like of float
|
|
103
|
+
Known standard concentrations (at least 3 distinct values, so the
|
|
104
|
+
residual standard error is defined).
|
|
105
|
+
signal : array-like of float
|
|
106
|
+
Corresponding measured instrument signals.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
LinearCalibration
|
|
111
|
+
|
|
112
|
+
Examples
|
|
113
|
+
--------
|
|
114
|
+
LOQ is always exactly :math:`10/3.3` times LOD, by definition,
|
|
115
|
+
regardless of the data:
|
|
116
|
+
|
|
117
|
+
>>> conc = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]
|
|
118
|
+
>>> signal = [0.02, 1.05, 1.98, 3.10, 3.95, 5.08]
|
|
119
|
+
>>> cal = fit_calibration(conc, signal)
|
|
120
|
+
>>> round(cal.loq() / cal.lod(), 6) == round(10.0 / 3.3, 6)
|
|
121
|
+
True
|
|
122
|
+
|
|
123
|
+
A perfect (noiseless) calibration has zero residual error and hence
|
|
124
|
+
zero LOD/LOQ:
|
|
125
|
+
|
|
126
|
+
>>> cal_perfect = fit_calibration([0.0, 1.0, 2.0, 3.0], [1.0, 3.0, 5.0, 7.0])
|
|
127
|
+
>>> cal_perfect.lod() < 1e-9
|
|
128
|
+
True
|
|
129
|
+
"""
|
|
130
|
+
fit = linear_fit(concentration, signal)
|
|
131
|
+
return LinearCalibration(slope=fit.slope, intercept=fit.intercept, r_squared=fit.r_squared, residual_std_error=fit.residual_std_error)
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
r"""Chromatographic plate theory: theoretical plates, the van Deemter equation, resolution, and selectivity.
|
|
2
|
+
|
|
3
|
+
See Harris, *Quantitative Chemical Analysis*, 9th ed., Ch. 23 ("An
|
|
4
|
+
Introduction to Chromatographic Separations"), or Skoog, West, Holler &
|
|
5
|
+
Crouch, *Fundamentals of Analytical Chemistry*, 9th ed., Ch. 26,
|
|
6
|
+
throughout.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from chemistrykit.spectro.utils.lineshapes import gaussian
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"theoretical_plates",
|
|
17
|
+
"plate_height",
|
|
18
|
+
"van_deemter_H",
|
|
19
|
+
"optimum_flow_velocity",
|
|
20
|
+
"minimum_plate_height",
|
|
21
|
+
"retention_factor",
|
|
22
|
+
"selectivity_factor",
|
|
23
|
+
"resolution",
|
|
24
|
+
"simulate_chromatogram",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def theoretical_plates(retention_time: float, peak_width: float, width_type: str = "base"):
|
|
29
|
+
r"""Number of theoretical plates `N` from a peak's retention time and width.
|
|
30
|
+
|
|
31
|
+
For a Gaussian elution peak (Harris, *Quantitative Chemical
|
|
32
|
+
Analysis*, 9th ed., eq. 23.19-23.20):
|
|
33
|
+
|
|
34
|
+
.. math::
|
|
35
|
+
|
|
36
|
+
N = 16\left(\frac{t_R}{w_{base}}\right)^2 = 5.545\left(\frac{t_R}{w_{1/2}}\right)^2
|
|
37
|
+
|
|
38
|
+
where :math:`w_{base}` is the width at the peak base (tangents to the
|
|
39
|
+
inflection points) and :math:`w_{1/2}` is the full width at half
|
|
40
|
+
maximum -- the numeric prefactor differs only because a Gaussian's
|
|
41
|
+
base width (4 standard deviations) and FWHM
|
|
42
|
+
(:math:`2\sqrt{2\ln2}\,\sigma`) are different multiples of `sigma`.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
retention_time : float or array-like of float
|
|
47
|
+
Peak retention time :math:`t_R`.
|
|
48
|
+
peak_width : float or array-like of float
|
|
49
|
+
Peak width, in the same units as `retention_time`, of the type
|
|
50
|
+
selected by `width_type`.
|
|
51
|
+
width_type : {"base", "half_height"}, default "base"
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
float or ndarray
|
|
56
|
+
|
|
57
|
+
Examples
|
|
58
|
+
--------
|
|
59
|
+
A peak eluting at 10.0 min with a 0.5 min base width:
|
|
60
|
+
|
|
61
|
+
>>> round(float(theoretical_plates(10.0, 0.5, width_type="base")), 1)
|
|
62
|
+
6400.0
|
|
63
|
+
|
|
64
|
+
The two width conventions must agree for a Gaussian peak of
|
|
65
|
+
consistent shape (base width = 4 sigma, FWHM =
|
|
66
|
+
:math:`2\sqrt{2\ln2}\,\sigma\approx2.3548\sigma`):
|
|
67
|
+
|
|
68
|
+
>>> sigma = 0.125
|
|
69
|
+
>>> N_base = theoretical_plates(10.0, 4.0 * sigma, width_type="base")
|
|
70
|
+
>>> N_half = theoretical_plates(10.0, 2.3548 * sigma, width_type="half_height")
|
|
71
|
+
>>> bool(abs(N_base - N_half) / N_base < 1e-3)
|
|
72
|
+
True
|
|
73
|
+
"""
|
|
74
|
+
tR = np.asarray(retention_time, dtype=np.float64)
|
|
75
|
+
w = np.asarray(peak_width, dtype=np.float64)
|
|
76
|
+
if width_type == "base":
|
|
77
|
+
result = 16.0 * (tR / w) ** 2
|
|
78
|
+
elif width_type == "half_height":
|
|
79
|
+
result = 5.545 * (tR / w) ** 2
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError('width_type must be "base" or "half_height"')
|
|
82
|
+
return float(result) if result.ndim == 0 else result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def plate_height(column_length: float, N):
|
|
86
|
+
r"""Plate height (HETP) :math:`H=L/N`, the column length per theoretical plate.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
column_length : float
|
|
91
|
+
Column length `L`, e.g. in cm.
|
|
92
|
+
N : float or array-like of float
|
|
93
|
+
Number of theoretical plates.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
float or ndarray
|
|
98
|
+
|
|
99
|
+
Examples
|
|
100
|
+
--------
|
|
101
|
+
>>> round(float(plate_height(column_length=25.0, N=6400.0)), 6)
|
|
102
|
+
0.003906
|
|
103
|
+
"""
|
|
104
|
+
N = np.asarray(N, dtype=np.float64)
|
|
105
|
+
result = column_length / N
|
|
106
|
+
return float(result) if result.ndim == 0 else result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def van_deemter_H(u, A: float, B: float, C: float):
|
|
110
|
+
r"""The van Deemter equation: plate height `H` as a function of mobile-phase linear velocity `u`.
|
|
111
|
+
|
|
112
|
+
.. math::
|
|
113
|
+
|
|
114
|
+
H = A + \frac{B}{u} + Cu
|
|
115
|
+
|
|
116
|
+
with `A` the eddy-diffusion term (velocity-independent band
|
|
117
|
+
broadening from unequal flow paths), `B` the longitudinal-molecular-
|
|
118
|
+
diffusion term (dominant at low `u`), and `C` the mass-transfer-
|
|
119
|
+
resistance term (dominant at high `u`) (J. J. van Deemter, F. J.
|
|
120
|
+
Zuiderweg, A. Klinkenberg, *Chem. Eng. Sci.* 5, 271 (1956); Harris,
|
|
121
|
+
*Quantitative Chemical Analysis*, 9th ed., Ch. 23.4).
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
u : float or array-like of float
|
|
126
|
+
Mobile-phase linear velocity.
|
|
127
|
+
A, B, C : float
|
|
128
|
+
Van Deemter coefficients (all non-negative for a physical column).
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
float or ndarray
|
|
133
|
+
|
|
134
|
+
Examples
|
|
135
|
+
--------
|
|
136
|
+
>>> round(float(van_deemter_H(u=2.0, A=1.0, B=2.0, C=0.05)), 4)
|
|
137
|
+
2.1
|
|
138
|
+
"""
|
|
139
|
+
u = np.asarray(u, dtype=np.float64)
|
|
140
|
+
result = A + B / u + C * u
|
|
141
|
+
return float(result) if result.ndim == 0 else result
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def optimum_flow_velocity(B: float, C: float) -> float:
|
|
145
|
+
r"""The flow velocity :math:`u_{opt}=\sqrt{B/C}` minimizing the van Deemter equation.
|
|
146
|
+
|
|
147
|
+
Found by :math:`dH/du=-B/u^2+C=0` (Harris, *Quantitative Chemical
|
|
148
|
+
Analysis*, 9th ed., Ch. 23.4).
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
B, C : float
|
|
153
|
+
Van Deemter longitudinal-diffusion and mass-transfer coefficients.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
float
|
|
158
|
+
|
|
159
|
+
Examples
|
|
160
|
+
--------
|
|
161
|
+
>>> round(optimum_flow_velocity(B=2.0, C=0.05), 6)
|
|
162
|
+
6.324555
|
|
163
|
+
"""
|
|
164
|
+
return float(np.sqrt(B / C))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def minimum_plate_height(A: float, B: float, C: float) -> float:
|
|
168
|
+
r"""The minimum plate height :math:`H_{min}=A+2\sqrt{BC}`, at the optimum flow velocity.
|
|
169
|
+
|
|
170
|
+
Substituting :func:`optimum_flow_velocity` into
|
|
171
|
+
:func:`van_deemter_H` (Harris, *Quantitative Chemical Analysis*, 9th
|
|
172
|
+
ed., Ch. 23.4).
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
A, B, C : float
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
float
|
|
181
|
+
|
|
182
|
+
Examples
|
|
183
|
+
--------
|
|
184
|
+
Matches direct evaluation of :func:`van_deemter_H` at the optimum
|
|
185
|
+
velocity from :func:`optimum_flow_velocity`:
|
|
186
|
+
|
|
187
|
+
>>> A, B, C = 1.0, 2.0, 0.05
|
|
188
|
+
>>> u_opt = optimum_flow_velocity(B, C)
|
|
189
|
+
>>> bool(round(minimum_plate_height(A, B, C), 6) == round(float(van_deemter_H(u_opt, A, B, C)), 6))
|
|
190
|
+
True
|
|
191
|
+
"""
|
|
192
|
+
return A + 2.0 * np.sqrt(B * C)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def retention_factor(retention_time: float, dead_time: float):
|
|
196
|
+
r"""The retention (capacity) factor :math:`k=(t_R-t_0)/t_0`.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
retention_time : float or array-like of float
|
|
201
|
+
Analyte retention time :math:`t_R`.
|
|
202
|
+
dead_time : float
|
|
203
|
+
Column dead time (retention time of an unretained species) :math:`t_0`.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
float or ndarray
|
|
208
|
+
|
|
209
|
+
Examples
|
|
210
|
+
--------
|
|
211
|
+
>>> round(float(retention_factor(retention_time=12.0, dead_time=2.0)), 6)
|
|
212
|
+
5.0
|
|
213
|
+
"""
|
|
214
|
+
tR = np.asarray(retention_time, dtype=np.float64)
|
|
215
|
+
result = (tR - dead_time) / dead_time
|
|
216
|
+
return float(result) if result.ndim == 0 else result
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def selectivity_factor(k1: float, k2: float) -> float:
|
|
220
|
+
r"""The selectivity (relative retention) factor :math:`\alpha=k_2/k_1` (:math:`k_2\ge k_1` by convention).
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
k1, k2 : float
|
|
225
|
+
Retention factors of the earlier- and later-eluting peaks.
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
float
|
|
230
|
+
|
|
231
|
+
Examples
|
|
232
|
+
--------
|
|
233
|
+
>>> round(selectivity_factor(k1=2.0, k2=5.0), 6)
|
|
234
|
+
2.5
|
|
235
|
+
"""
|
|
236
|
+
return k2 / k1
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def resolution(tR1: float, tR2: float, w1: float, w2: float) -> float:
|
|
240
|
+
r"""Chromatographic resolution :math:`R_s=2(t_{R,2}-t_{R,1})/(w_1+w_2)` between two adjacent peaks.
|
|
241
|
+
|
|
242
|
+
:math:`R_s\ge1.5` is the conventional criterion for baseline
|
|
243
|
+
separation (Harris, *Quantitative Chemical Analysis*, 9th ed., eq.
|
|
244
|
+
23.21).
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
tR1, tR2 : float
|
|
249
|
+
Retention times of the earlier- and later-eluting peaks.
|
|
250
|
+
w1, w2 : float
|
|
251
|
+
Base widths of the two peaks, same time units.
|
|
252
|
+
|
|
253
|
+
Returns
|
|
254
|
+
-------
|
|
255
|
+
float
|
|
256
|
+
|
|
257
|
+
Examples
|
|
258
|
+
--------
|
|
259
|
+
>>> round(resolution(tR1=9.0, tR2=10.0, w1=0.5, w2=0.5), 6)
|
|
260
|
+
2.0
|
|
261
|
+
"""
|
|
262
|
+
return 2.0 * (tR2 - tR1) / (w1 + w2)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def simulate_chromatogram(t, centers, retention_times=None, N: float = 10000.0, amplitudes=None):
|
|
266
|
+
r"""Simulate a chromatogram as a sum of Gaussian elution peaks of plate-count-consistent width.
|
|
267
|
+
|
|
268
|
+
Reuses :func:`chemistrykit.spectro.utils.lineshapes.gaussian` (each
|
|
269
|
+
elution peak is, to a good approximation, Gaussian -- the same
|
|
270
|
+
plate-theory result that underlies :func:`theoretical_plates`), with
|
|
271
|
+
each peak's FWHM set by :math:`w_{1/2}=t_R\sqrt{8\ln2/N}` (inverting
|
|
272
|
+
:func:`theoretical_plates`'s half-height form,
|
|
273
|
+
:math:`N=5.545(t_R/w_{1/2})^2` with :math:`5.545\approx8\ln2`).
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
t : array-like of float
|
|
278
|
+
Time grid to evaluate the chromatogram on.
|
|
279
|
+
centers : array-like of float
|
|
280
|
+
Retention time of each peak (alias for `retention_times`, kept
|
|
281
|
+
for a natural call signature with :func:`chemistrykit.spectro.utils.lineshapes.broaden_stick_spectrum`).
|
|
282
|
+
retention_times : array-like of float, optional
|
|
283
|
+
If given, overrides `centers`.
|
|
284
|
+
N : float, default 10000.0
|
|
285
|
+
Number of theoretical plates (assumed equal for every peak, the
|
|
286
|
+
common simplifying assumption for a single column/method).
|
|
287
|
+
amplitudes : array-like of float, optional
|
|
288
|
+
Relative peak heights; defaults to 1.0 for every peak.
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
ndarray, shape matching `t`
|
|
293
|
+
|
|
294
|
+
Examples
|
|
295
|
+
--------
|
|
296
|
+
A single simulated peak's apparent plate count (recovered from its
|
|
297
|
+
numerically-measured FWHM) matches the `N` it was built from:
|
|
298
|
+
|
|
299
|
+
>>> import numpy as np
|
|
300
|
+
>>> t = np.linspace(8.0, 12.0, 200001)
|
|
301
|
+
>>> N_true = 10000.0
|
|
302
|
+
>>> chrom = simulate_chromatogram(t, centers=[10.0], N=N_true)
|
|
303
|
+
>>> half_max = chrom.max() / 2.0
|
|
304
|
+
>>> above = t[chrom >= half_max]
|
|
305
|
+
>>> fwhm = above.max() - above.min()
|
|
306
|
+
>>> N_recovered = theoretical_plates(10.0, fwhm, width_type="half_height")
|
|
307
|
+
>>> bool(abs(N_recovered - N_true) / N_true < 0.01)
|
|
308
|
+
True
|
|
309
|
+
"""
|
|
310
|
+
t = np.asarray(t, dtype=np.float64)
|
|
311
|
+
centers = np.asarray(retention_times if retention_times is not None else centers, dtype=np.float64)
|
|
312
|
+
amps = np.ones_like(centers) if amplitudes is None else np.asarray(amplitudes, dtype=np.float64)
|
|
313
|
+
chrom = np.zeros_like(t)
|
|
314
|
+
for tR, amp in zip(centers, amps, strict=True):
|
|
315
|
+
fwhm = tR * np.sqrt(8.0 * np.log(2.0) / N)
|
|
316
|
+
chrom += amp * gaussian(t, tR, fwhm)
|
|
317
|
+
return chrom
|