chemistrykit.photochem#

chemistrykit.photochem: photochemistry.

Jablonski-diagram excited-state kinetics (fluorescence, internal conversion, intersystem crossing, and phosphorescence) modeled as a mass-action reaction network and integrated via chemistrykit.kinetics/chemistrykit.integrators; fluorescence/phosphorescence quantum yields and the photochemical quantum yield via Beer-Lambert (chemistrykit.spectro); Stern-Volmer fluorescence quenching with a static-vs-dynamic diagnostic; photostationary-state kinetics for a two-state photoswitch under simultaneous forward/reverse photolysis; the H2/Cl2 photochemical chain reaction; Stokes shifts and Perrin fluorescence anisotropy; Kasha’s-rule emission yields; Förster and Dexter energy transfer; ferrioxalate actinometry; and Rehm-Weller electron-transfer quenching.

class chemistrykit.photochem.PhotostationaryStateResult(A_pss, B_pss, ratio_B_over_A, k_AB, k_BA)[source]#

Bases: object

Result of a chemistrykit.photochem.systems.photostationary_state.photostationary_state() call.

Parameters:
A_pss: float#

Steady-state concentration (or population fraction) of species A.

Type:

float

B_pss: float#

Steady-state concentration (or population fraction) of species B.

Type:

float

k_AB: float#

Forward (A -> B) pseudo-first-order photolysis rate constant used.

Type:

float

k_BA: float#

Reverse (B -> A) pseudo-first-order photolysis rate constant used.

Type:

float

ratio_B_over_A: float#

\([B]_{pss}/[A]_{pss} = k_{AB}/k_{BA}\).

Type:

float

class chemistrykit.photochem.SternVolmerFit(Ksv, r_squared)[source]#

Bases: object

Result of fitting intensity-ratio-vs-quencher-concentration data to the Stern-Volmer equation.

Parameters:
Ksv: float#

Fitted Stern-Volmer constant.

Type:

float

predict(Q)[source]#

Evaluate the fitted Stern-Volmer line at quencher concentration(s) Q.

Parameters:

Q (float or array-like of float)

Returns:

float or ndarray

r_squared: float#

Coefficient of determination of the linear fit.

Type:

float

chemistrykit.photochem.chain_quantum_yield(k2, kt, H2, I_abs)[source]#

Steady-state quantum yield of the H2/Cl2 chain: \(\Phi = 2k_2[\mathrm{H_2}]/\sqrt{k_t I_{abs}}\).

Parameters:
  • k2 (float) – \(\mathrm{Cl}+\mathrm{H_2}\) propagation rate constant.

  • kt (float) – \(\mathrm{Cl}+\mathrm{Cl}\) termination rate constant.

  • H2 (float or array-like of float) – Hydrogen concentration.

  • I_abs (float or array-like of float) – Absorbed photon rate per unit volume (same concentration/time units as the rate constants).

Returns:

float or ndarray – HCl molecules formed per photon absorbed – not bounded by 1.

Examples

Halving the light intensity by a factor of 4 doubles the quantum yield (the chain lengthens as \(I_{abs}^{-1/2}\)):

>>> phi1 = chain_quantum_yield(k2=10.0, kt=100.0, H2=1.0, I_abs=1e-4)
>>> phi2 = chain_quantum_yield(k2=10.0, kt=100.0, H2=1.0, I_abs=0.25e-4)
>>> round(phi1, 6), round(phi2 / phi1, 6)
(200.0, 2.0)
chemistrykit.photochem.classify_quenching_mechanism(intensity_ratio_slope, lifetime_ratio_slope, rtol=0.1)[source]#

Classify a quenching mechanism as dynamic, static, or mixed from the two Stern-Volmer slopes.

Measuring both the steady-state intensity ratio’s Stern-Volmer slope and the excited-state lifetime ratio’s is the standard diagnostic (Lakowicz, Principles of Fluorescence Spectroscopy, 3rd ed., Ch. 8.2): pure dynamic (collisional) quenching reduces the lifetime and the intensity by the same factor, so the two slopes agree (\((I_0/I)\) and \((\tau_0/\tau)\) are superimposable); pure static quenching (ground-state complexation) removes a fraction of fluorophores from the population entirely without shortening the lifetime of the ones that remain uncomplexed, so \(\tau_0/\tau=1\) (slope 0) while \(I_0/I\) still rises linearly. A slope ratio between these extremes indicates both mechanisms are contributing.

Parameters:
  • intensity_ratio_slope (float) – Fitted Stern-Volmer slope of \(I_0/I\) vs. \([Q]\).

  • lifetime_ratio_slope (float) – Fitted Stern-Volmer slope of \(\tau_0/\tau\) vs. \([Q]\).

  • rtol (float) – Relative tolerance for judging the two slopes “equal” (dynamic) vs. the lifetime slope “zero” (static).

Return type:

str

Returns:

str – One of "dynamic", "static", or "mixed".

Examples

Equal slopes indicate purely dynamic quenching:

>>> classify_quenching_mechanism(intensity_ratio_slope=40.0, lifetime_ratio_slope=40.0)
'dynamic'

A vanishing lifetime slope with a nonzero intensity slope indicates purely static quenching:

>>> classify_quenching_mechanism(intensity_ratio_slope=40.0, lifetime_ratio_slope=0.0)
'static'

Something in between indicates a mixed mechanism:

>>> classify_quenching_mechanism(intensity_ratio_slope=40.0, lifetime_ratio_slope=20.0)
'mixed'
chemistrykit.photochem.dexter_rate(r, K, J, L)[source]#

Dexter exchange energy-transfer rate \(k_{ET} = KJ\exp(-2r/L)\).

Exchange transfer requires the donor’s and acceptor’s electron clouds to overlap, so the rate decays exponentially with separation rather than as the \(r^{-6}\) power law of Förster transfer, and it allows spin-forbidden triplet-triplet transfer that dipole coupling does not (Dexter 1953; Turro et al., Ch. 7).

Parameters:
  • r (float or array-like of float) – Donor-acceptor edge-to-edge distance.

  • K (float) – Pre-exponential factor (orbital interaction strength).

  • J (float) – Normalized spectral overlap integral.

  • L (float) – Effective average van der Waals radius (same unit as r).

Returns:

float or ndarray

Examples

Each increase of \(r\) by \(L/2\) reduces the rate by a factor of \(e\):

>>> k1, k2 = dexter_rate(1.0, K=1.0, J=1.0, L=2.0), dexter_rate(2.0, K=1.0, J=1.0, L=2.0)
>>> round(k1 / k2, 6) == round(float(np.e), 6)
True
chemistrykit.photochem.dynamic_quenching_constant(kq, tau0)[source]#

The Stern-Volmer constant for purely dynamic (collisional) quenching: \(K_{sv} = k_q\tau_0\).

\(k_q\) is the bimolecular quenching rate constant (an encounter/collision frequency, at most diffusion-controlled) and \(\tau_0\) the unquenched excited-state lifetime – a longer- lived excited state has more opportunity to be collisionally quenched before it decays on its own, hence the product form (Lakowicz, Principles of Fluorescence Spectroscopy, 3rd ed., Ch. 8, eq. 8.5).

Parameters:
  • kq (float) – Bimolecular quenching rate constant, in 1/(M*s).

  • tau0 (float) – Unquenched excited-state lifetime, in s.

Return type:

float

Returns:

float – \(K_{sv}\), in 1/M.

Examples

>>> round(dynamic_quenching_constant(kq=1.0e10, tau0=5.0e-9), 4)
50.0
chemistrykit.photochem.ferrioxalate_fe2_moles(absorbance_510, volume_L, path_length_cm=1.0, epsilon_510=11100.0)[source]#

Moles of Fe(II) from the absorbance of its phenanthroline complex at 510 nm.

Beer-Lambert: \(n = A_{510} V/(\varepsilon_{510}\ell)\).

Parameters:
  • absorbance_510 (float or array-like of float) – Absorbance change of the developed solution at 510 nm.

  • volume_L (float) – Volume of the developed solution, in L.

  • path_length_cm (float) – Cuvette path length, in cm.

  • epsilon_510 (float) – Molar absorptivity of Fe(phen)3(2+), in M^-1 cm^-1.

Returns:

float or ndarray – Moles of Fe(II).

Examples

>>> round(ferrioxalate_fe2_moles(1.11, volume_L=0.01) * 1e6, 6)
1.0
chemistrykit.photochem.ferrioxalate_photon_flux(moles_fe2, time_s, absorbance=inf, quantum_yield=1.21)[source]#

Incident photon flux \(q_p = n_{Fe^{2+}}/(\Phi\,t\,(1-10^{-A}))\) from ferrioxalate actinometry.

Parameters:
  • moles_fe2 (float or array-like of float) – Moles of Fe(II) formed (e.g. from ferrioxalate_fe2_moles()).

  • time_s (float or array-like of float) – Irradiation time, in s.

  • absorbance (float) – Actinometer absorbance at the irradiation wavelength (inf means total absorption, the usual operating regime).

  • quantum_yield (float) – Fe(II) quantum yield at the irradiation wavelength (1.21 near 365 nm per Hatchard & Parker).

Returns:

float or ndarray – Incident photon flux, in einstein/s (mol photons/s).

Examples

1.21 micromol Fe(II) in 100 s under total absorption means \(10^{-8}\) einstein/s:

>>> round(ferrioxalate_photon_flux(1.21e-6, 100.0) * 1e8, 6)
1.0
chemistrykit.photochem.fit_stern_volmer(Q, intensity_ratio)[source]#

Fit \(I_0/I\) (or \(\tau_0/\tau\)) vs. \([Q]\) data to the Stern-Volmer equation.

Fits \(I_0/I = 1 + K_{sv}[Q]\) by ordinary least squares, constraining the intercept to 1 exactly (the physically required value at \([Q]=0\)) by fitting the shift \(I_0/I - 1\) through the origin against Q – structurally similar to chemistrykit.kinetics.systems.arrhenius.fit_arrhenius()’s linearized fit, but with a fixed rather than free intercept.

Parameters:
  • Q (array-like of float) – Quencher concentrations (at least 2 distinct values, including a nonzero one).

  • intensity_ratio (array-like of float) – Corresponding measured \(I_0/I\) (or \(\tau_0/\tau\)).

Return type:

SternVolmerFit

Returns:

SternVolmerFit

Examples

Generate exact data from a known \(K_{sv}\) and recover it:

>>> Q = np.array([0.0, 0.005, 0.010, 0.020, 0.040])
>>> ratio = stern_volmer_ratio(Ksv=35.0, Q=Q)
>>> fit = fit_stern_volmer(Q, ratio)
>>> round(fit.Ksv, 6)
35.0
>>> round(fit.r_squared, 6)
1.0
chemistrykit.photochem.fluorescence_quantum_yield(kf, kic, kisc)[source]#

Fluorescence quantum yield \(\Phi_f = k_f/(k_f+k_{ic}+k_{isc})\).

The fraction of excited \(S_1\) molecules that relax by emitting a photon (fluorescence) rather than through either nonradiative decay channel – the ratio of the radiative rate constant to the total \(S_1\) decay rate (Lakowicz, Principles of Fluorescence Spectroscopy, 3rd ed., Ch. 1, eq. 1.1). See chemistrykit.photochem.systems.jablonski for the underlying kinetic model this ratio comes from.

Parameters:
  • kf (float) – Fluorescence (radiative) rate constant.

  • kic (float) – Internal conversion (nonradiative, \(S_1\to S_0\)) rate constant.

  • kisc (float) – Intersystem crossing (\(S_1\to T_1\)) rate constant.

Return type:

float

Returns:

float – In \([0, 1]\).

Examples

With no competing nonradiative pathway, every excited molecule fluoresces:

>>> round(fluorescence_quantum_yield(kf=1.0, kic=0.0, kisc=0.0), 6)
1.0

Adding a nonradiative channel of the same magnitude as kf exactly halves the yield:

>>> round(fluorescence_quantum_yield(kf=1.0, kic=1.0, kisc=0.0), 6)
0.5
chemistrykit.photochem.forster_efficiency(r, R0)[source]#

FRET efficiency \(E = 1/(1+(r/R_0)^6)\).

Parameters:
  • r (float or array-like of float) – Donor-acceptor distance.

  • R0 (float) – Förster distance (same unit as r).

Returns:

float or ndarray – Efficiency in \([0,1]\).

Examples

>>> forster_efficiency(5.0, R0=5.0)
0.5
>>> round(forster_efficiency(10.0, R0=5.0), 5)
0.01538
chemistrykit.photochem.forster_radius(kappa2, n, quantum_yield_donor, overlap_J)[source]#

Förster distance \(R_0 = 0.211\,(\kappa^2 n^{-4} Q_D J)^{1/6}\) Å.

The donor-acceptor distance at which transfer is 50% efficient (Lakowicz, 3rd ed., eq. 13.12), with the spectral overlap integral \(J\) in M-1 cm-1 nm4.

Parameters:
  • kappa2 (float) – Orientation factor \(\kappa^2\) (2/3 for isotropic averaging).

  • n (float) – Refractive index of the medium.

  • quantum_yield_donor (float) – Donor fluorescence quantum yield without acceptor.

  • overlap_J (float) – Spectral overlap integral, in M^-1 cm^-1 nm^4.

Return type:

float

Returns:

float – \(R_0\), in Å.

Examples

A typical dye pair (\(J=10^{15}\), \(Q_D=0.5\), \(n=1.4\)) gives \(R_0\) of about 4.4 nm:

>>> round(forster_radius(2 / 3, 1.4, 0.5, 1.0e15), 1)
44.4
chemistrykit.photochem.forster_rate(r, R0, tau_D)[source]#

Förster transfer rate \(k_T = \tau_D^{-1}(R_0/r)^6\).

Parameters:
  • r (float or array-like of float) – Donor-acceptor distance (same unit as R0).

  • R0 (float) – Förster distance.

  • tau_D (float) – Donor lifetime without acceptor.

Returns:

float or ndarray – Transfer rate constant, in 1/(unit of tau_D).

Examples

At \(r=R_0\) transfer competes equally with donor decay:

>>> forster_rate(5.0, R0=5.0, tau_D=2.0)
0.5
chemistrykit.photochem.hydrogen_chlorine_chain_network(j, k2, k3, kt, H2_0=1.0, Cl2_0=1.0)[source]#

Build Nernst’s photochemical H2/Cl2 atom-chain mechanism as a mass-action network.

Parameters:
  • j (float) – Photolysis rate constant of \(\mathrm{Cl_2}\) (so the absorbed photon rate per volume is \(I_{abs}=j[\mathrm{Cl_2}]\), assuming each absorbed photon dissociates one \(\mathrm{Cl_2}\)).

  • k2 (float) – \(\mathrm{Cl}+\mathrm{H_2}\to\mathrm{HCl}+\mathrm{H}\) rate constant.

  • k3 (float) – \(\mathrm{H}+\mathrm{Cl_2}\to\mathrm{HCl}+\mathrm{Cl}\) rate constant.

  • kt (float) – \(\mathrm{Cl}+\mathrm{Cl}\to\mathrm{Cl_2}\) termination rate constant (mass-action rate \(k_t[\mathrm{Cl}]^2\)).

  • H2_0 (float) – Initial concentrations.

  • Cl2_0 (float) – Initial concentrations.

Return type:

StoichiometricNetwork

Returns:

StoichiometricNetwork – Species ("H2", "Cl2", "Cl", "H", "HCl").

Examples

Hydrogen atoms are conserved between H2, H and HCl:

>>> net = hydrogen_chlorine_chain_network(j=1e-4, k2=10.0, k3=100.0, kt=100.0)
>>> res = net.integrate((0.0, 5.0), dt=1e-3, method="rk4")
>>> H_total = 2 * res.concentration("H2") + res.concentration("H") + res.concentration("HCl")
>>> bool(np.allclose(H_total, 2.0))
True
chemistrykit.photochem.intersystem_crossing_yield(kisc, kf, kic)[source]#

Intersystem-crossing yield \(\Phi_{isc} = k_{isc}/(k_f+k_{ic}+k_{isc})\).

The fraction of excited \(S_1\) population that crosses to the triplet manifold \(T_1\) rather than returning directly to \(S_0\) (Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 5).

Parameters:
  • kisc (float) – Intersystem crossing rate constant.

  • kf (float) – Fluorescence rate constant.

  • kic (float) – Internal conversion rate constant.

Return type:

float

Returns:

float – In \([0, 1]\).

Examples

Together with fluorescence_quantum_yield() and the (implicit) internal-conversion yield, the three \(S_1\) decay-channel yields sum to exactly 1:

>>> kf, kic, kisc = 2.0, 1.0, 0.5
>>> phi_f = fluorescence_quantum_yield(kf, kic, kisc)
>>> phi_isc = intersystem_crossing_yield(kisc, kf, kic)
>>> phi_ic = kic / (kf + kic + kisc)
>>> round(phi_f + phi_isc + phi_ic, 9)
1.0
chemistrykit.photochem.jablonski_network(kf, kic, kisc, kp, kic_T, S1_0=1.0)[source]#

Build the 3-state (\(S_1,T_1,S_0\)) Jablonski excited-state decay network.

Structurally, \(S_1\) decays through two parallel first-order channels (directly to \(S_0\) at combined rate \(k_f+k_{ic}\), and to \(T_1\) at rate \(k_{isc}\)), and \(T_1\) then decays to \(S_0\) at combined rate \(k_p+k_{ic,T}\) – a branching-then-consecutive network, built here as a general StoichiometricNetwork (its three named constructors, parallel/consecutive/ reversible, don’t individually cover this branching topology, but the general mass-action constructor they’re built on does).

Parameters:
  • kf (float) – Fluorescence (\(S_1\to S_0\), radiative) rate constant.

  • kic (float) – Internal conversion (\(S_1\to S_0\), nonradiative) rate constant.

  • kisc (float) – Intersystem crossing (\(S_1\to T_1\)) rate constant.

  • kp (float) – Phosphorescence (\(T_1\to S_0\), radiative) rate constant.

  • kic_T (float) – Triplet nonradiative decay (\(T_1\to S_0\)) rate constant.

  • S1_0 (float) – Initial \(S_1\) population immediately after excitation (e.g. following a short excitation pulse).

Return type:

StoichiometricNetwork

Returns:

StoichiometricNetwork – Species ("S1", "T1", "S0").

Examples

Total population is conserved (S0 is a pure sink, nothing is created or destroyed, just redistributed):

>>> net = jablonski_network(kf=2.0, kic=1.0, kisc=0.5, kp=0.3, kic_T=0.2, S1_0=1.0)
>>> result = net.integrate((0.0, 20.0), dt=1e-3, method="rk4")
>>> total = result.concentration("S1") + result.concentration("T1") + result.concentration("S0")
>>> bool(np.allclose(total, 1.0, atol=1e-6))
True
chemistrykit.photochem.jablonski_populations_analytic(kf, kic, kisc, kp, kic_T, S1_0, t)[source]#

Closed-form \(S_1(t)\), \(T_1(t)\), \(S_0(t)\) for the 3-state Jablonski network.

\(S_1\) decays as a simple single exponential with total rate \(k_{S_1}=k_f+k_{ic}+k_{isc}\):

\[[S_1](t) = [S_1]_0 e^{-k_{S_1}t}\]

\(T_1\) is fed by the fraction \(k_{isc}/k_{S_1}\) of decayed \(S_1\) and itself decays at total rate \(k_{T_1}=k_p+k_{ic,T}\); solving \(d[T_1]/dt=k_{isc}[S_1]-k_{T_1}[T_1]\) gives a scaled version of the same Bateman-equation form as a consecutive \(A\to B\to C\) chain (Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 5; the underlying algebra is identical to chemistrykit.kinetics.systems.networks.consecutive_analytic(), which this function calls directly and rescales by \(k_{isc}/k_{S_1}\) – since only that fraction of decayed \(S_1\), not all of it, feeds \(T_1\)):

\[[T_1](t) = \frac{k_{isc}}{k_{S_1}} \left[\frac{[S_1]_0 k_{S_1}}{k_{T_1}-k_{S_1}} \left(e^{-k_{S_1}t}-e^{-k_{T_1}t}\right)\right]\]

and \([S_0](t) = [S_1]_0 - [S_1](t) - [T_1](t)\) by mass balance. This closed form was checked against direct numerical integration of jablonski_network() (to a relative agreement of better than \(10^{-10}\) across a range of rate constants) as part of developing this module.

Parameters:
Returns:

S1, T1, S0 (float or ndarray)

Examples

Matches jablonski_network()’s numerical integration:

>>> kf, kic, kisc, kp, kic_T, S1_0 = 2.0, 1.0, 0.5, 0.3, 0.2, 1.0
>>> net = jablonski_network(kf, kic, kisc, kp, kic_T, S1_0)
>>> result = net.integrate((0.0, 10.0), dt=1e-3, method="rk4")
>>> S1, T1, S0 = jablonski_populations_analytic(kf, kic, kisc, kp, kic_T, S1_0, result.t)
>>> bool(np.allclose(result.concentration("S1"), S1, atol=1e-6))
True
>>> bool(np.allclose(result.concentration("T1"), T1, atol=1e-6))
True
chemistrykit.photochem.kasha_emission_yields(kf2, k_ic21, kf1, knr1, excite='S2')[source]#

Emission quantum yields from \(S_2\) and \(S_1\) for a four-level (\(S_2,S_1,S_0\)) model.

After excitation into \(S_2\), the molecule either emits directly from \(S_2\) (rate \(k_{f2}\)) or internally converts to \(S_1\) (rate \(k_{ic,21}\)); from \(S_1\) it fluoresces (\(k_{f1}\)) or decays nonradiatively by any other channel (\(k_{nr1}\), lumping \(k_{ic}+k_{isc}\)). Branching ratios give

\[\Phi_{S_2} = \frac{k_{f2}}{k_{f2}+k_{ic,21}}, \qquad \Phi_{S_1} = \frac{k_{ic,21}}{k_{f2}+k_{ic,21}}\cdot \frac{k_{f1}}{k_{f1}+k_{nr1}}.\]

Because \(k_{ic,21}\) (typically \(10^{12}\)-\(10^{14}\) s-1) vastly exceeds \(k_{f2}\) (\(\sim10^8\)-\(10^9\) s-1), \(\Phi_{S_2}\approx0\) and essentially all emission comes from \(S_1\) – Kasha’s rule (M. Kasha, Discuss. Faraday Soc. 9, 14 (1950); Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 4). Excitation directly into \(S_1\) (excite="S1") gives \(\Phi_{S_2}=0\) and \(\Phi_{S_1}=k_{f1}/(k_{f1}+k_{nr1})\).

Parameters:
  • kf2 (float) – Radiative rate constant of \(S_2\to S_0\).

  • k_ic21 (float) – Internal-conversion rate constant \(S_2\to S_1\).

  • kf1 (float) – Radiative rate constant of \(S_1\to S_0\).

  • knr1 (float) – Total nonradiative decay rate constant of \(S_1\).

  • excite (str) – State populated by absorption.

Returns:

phi_S2, phi_S1 (float) – Quantum yields of emission from \(S_2\) and from \(S_1\).

Examples

With realistic rates almost no emission comes from \(S_2\), and the \(S_1\) fluorescence yield barely depends on which state was excited:

>>> phi2, phi1 = kasha_emission_yields(kf2=1e8, k_ic21=1e13, kf1=1e8, knr1=1e8)
>>> phi2 < 1e-4, round(phi1, 4)
(True, 0.5)
>>> kasha_emission_yields(kf2=1e8, k_ic21=1e13, kf1=1e8, knr1=1e8, excite="S1")
(0.0, 0.5)
chemistrykit.photochem.perrin_anisotropy(r0, tau, theta)[source]#

Perrin equation for steady-state fluorescence anisotropy, \(r = r_0/(1+\tau/\theta)\).

A fluorophore excited by polarized light emits polarized light, but rotational diffusion during the excited-state lifetime \(\tau\) scrambles the orientation; for a spherical rotor with rotational correlation time \(\theta\) the time-averaged anisotropy is \(r_0/r = 1+\tau/\theta\) (Perrin 1926; Lakowicz, Ch. 10, eq. 10.45). \(r_0\) (at most 0.4 for one-photon excitation) is the anisotropy in the absence of rotation.

Parameters:
  • r0 (float) – Fundamental (rotation-free) anisotropy.

  • tau (float or array-like of float) – Fluorescence lifetime.

  • theta (float or array-like of float) – Rotational correlation time (same unit as tau).

Returns:

float or ndarray

Examples

When the lifetime equals the correlation time, the anisotropy is halved:

>>> round(perrin_anisotropy(0.4, tau=4.0, theta=4.0), 6)
0.2
chemistrykit.photochem.phosphorescence_quantum_yield(kisc, kf, kic, kp, kic_T)[source]#

Phosphorescence quantum yield \(\Phi_p = \Phi_{isc}\cdot\frac{k_p}{k_p+k_{ic,T}}\).

Phosphorescence requires two successive branching events to succeed: the molecule must first cross to the triplet manifold (probability \(\Phi_{isc}\)), and the resulting triplet must then decay radiatively rather than nonradiatively (probability \(k_p/(k_p+k_{ic,T})\)) – the product of these two independent branching ratios (Lakowicz, Principles of Fluorescence Spectroscopy, 3rd ed., Ch. 1; Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 5).

Parameters:
  • kisc (float) – Intersystem crossing rate constant.

  • kf (float) – Fluorescence rate constant.

  • kic (float) – \(S_1\) internal conversion rate constant.

  • kp (float) – Phosphorescence (radiative \(T_1\to S_0\)) rate constant.

  • kic_T (float) – Triplet nonradiative decay rate constant.

Return type:

float

Returns:

float – In \([0, 1]\).

Examples

With no intersystem crossing at all, no phosphorescence is possible:

>>> round(phosphorescence_quantum_yield(kisc=0.0, kf=1.0, kic=0.0, kp=1.0, kic_T=0.0), 6)
0.0

With every \(S_1\) crossing to the triplet, and every triplet decaying radiatively, the phosphorescence yield is exactly 1:

>>> round(phosphorescence_quantum_yield(kisc=1.0, kf=0.0, kic=0.0, kp=1.0, kic_T=0.0), 6)
1.0
chemistrykit.photochem.photochemical_quantum_yield(moles_product_formed, moles_photons_absorbed)[source]#

Photochemical quantum yield \(\Phi = \frac{\text{moles of product formed}}{\text{moles of photons absorbed}}\).

The photochemistry analogue of the excited-state quantum yields above: the efficiency of converting absorbed photons into chemical product (Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 7). Note on range: for a simple, non-chain photoreaction \(0\le\Phi\le1\) (each absorbed photon produces at most one product molecule), but a radical-chain photoreaction can propagate after the initiating absorption event and give \(\Phi\gg1\) – this function does not itself enforce an upper bound, since chain reactions are a real and common exception, not implemented here.

Parameters:
  • moles_product_formed (float or array-like of float)

  • moles_photons_absorbed (float or array-like of float)

Returns:

float or ndarray

Examples

One photon absorbed producing exactly one product molecule (a typical simple photoisomerization) gives \(\Phi=1\):

>>> round(float(photochemical_quantum_yield(1.0, 1.0)), 6)
1.0
chemistrykit.photochem.photons_absorbed(photon_flux_incident, absorbance)[source]#

Photon flux (or count) actually absorbed by a sample, from the Beer-Lambert absorbance.

\(I_{abs} = I_0(1-10^{-A}) = I_0(1-T)\), using chemistrykit.spectro.systems.beer_lambert.transmittance() for \(T=10^{-A}\) – the fraction of incident light not transmitted is, by energy conservation (neglecting reflection and scattering losses), the fraction absorbed (Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 7).

Parameters:
  • photon_flux_incident (float or array-like of float) – Incident photon flux (or photon count over an exposure time), in any consistent unit (e.g. mol photons / s, or einstein/s).

  • absorbance (float or array-like of float) – Beer-Lambert absorbance of the sample at the excitation wavelength (see chemistrykit.spectro.systems.beer_lambert).

Returns:

float or ndarray – Absorbed photon flux, same unit as photon_flux_incident.

Examples

At zero absorbance, nothing is absorbed:

>>> round(float(photons_absorbed(1.0, absorbance=0.0)), 6)
0.0

At very high absorbance, essentially all incident light is absorbed:

>>> round(float(photons_absorbed(1.0, absorbance=10.0)), 6)
1.0
chemistrykit.photochem.photostationary_ratio(k_AB, k_BA)[source]#

The photostationary-state population ratio \([B]_{pss}/[A]_{pss} = k_{AB}/k_{BA}\).

At the photostationary state, \(d[A]/dt=-k_{AB}[A]+k_{BA}[B]=0\), giving this ratio directly (E. Fischer, J. Phys. Chem. 71, 3704 (1967); Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 7.6) – exactly the long-time limit of chemistrykit.kinetics.systems.networks.reversible_analytic()’s equilibrium-population formula, since a photostationary state is mathematically the same steady state a thermally reversible first-order reaction relaxes to.

Parameters:
  • k_AB (float) – Pseudo-first-order photoisomerization rate constants.

  • k_BA (float) – Pseudo-first-order photoisomerization rate constants.

Return type:

float

Returns:

float

Examples

Equal rate constants give a 1:1 photostationary mixture:

>>> round(photostationary_ratio(k_AB=2.0, k_BA=2.0), 6)
1.0
chemistrykit.photochem.photostationary_state(k_AB, k_BA, total_concentration=1.0)[source]#

Solve for the photostationary-state populations of a two-state photoswitch.

Uses the exact algebraic steady-state solution of \(A\rightleftharpoons B\) (the \(t\to\infty\) limit of chemistrykit.kinetics.systems.networks.reversible_analytic(), with the “forward” rate constant \(k_f=k_{AB}\) and “reverse” \(k_r=k_{BA}\)) rather than integrating photoswitch_network() to long time numerically – both were checked to agree (see this module’s test suite) as part of developing this function.

Parameters:
Return type:

PhotostationaryStateResult

Returns:

PhotostationaryStateResult

Examples

>>> result = photostationary_state(k_AB=1.5, k_BA=0.4, total_concentration=1.0)
>>> round(result.ratio_B_over_A, 3)
3.75
>>> round(result.A_pss + result.B_pss, 9)
1.0
chemistrykit.photochem.photoswitch_network(k_AB, k_BA, A0=1.0)[source]#

Build the \(A\rightleftharpoons B\) photoswitch network as a StoichiometricNetwork.

Directly reuses reversible() – a two-state photoswitch under simultaneous forward/reverse photolysis is, mathematically, the identical reversible first-order reaction network that thermal isomerization would be, just with photochemical rather than Arrhenius rate constants.

Parameters:
Return type:

StoichiometricNetwork

Returns:

StoichiometricNetwork

Examples

>>> net = photoswitch_network(k_AB=1.5, k_BA=0.4, A0=1.0)
>>> result = net.integrate((0.0, 50.0), dt=1e-3, method="rk4")
>>> round(float(result.concentration("B")[-1] / result.concentration("A")[-1]), 3)
3.75
chemistrykit.photochem.photoswitch_rate_constants(phi_AB, eps_A, phi_BA, eps_B, I0=1.0)[source]#

Pseudo-first-order photoisomerization rate constants in the low-optical-density limit.

\(k_{AB}=2.303\,\Phi_{AB}\,\varepsilon_A\,I_0\) and \(k_{BA}=2.303\,\Phi_{BA}\,\varepsilon_B\,I_0\) (Turro, Ramamurthy & Scaiano, Modern Molecular Photochemistry of Organic Molecules, Ch. 7.6) – see the module docstring for the dilute-limit approximation this relies on.

Parameters:
  • phi_AB (float) – Quantum yield of the \(A\to B\) photoreaction.

  • eps_A (float) – Molar absorptivity of \(A\) at the irradiation wavelength, in L mol^-1 cm^-1.

  • phi_BA (float) – Quantum yield of the \(B\to A\) photoreaction.

  • eps_B (float) – Molar absorptivity of \(B\) at the irradiation wavelength.

  • I0 (float) – Incident photon flux (or intensity); the ratio k_AB/k_BA – and hence the photostationary ratio – does not depend on this value.

Return type:

tuple[float, float]

Returns:

k_AB, k_BA (float)

Examples

Equal quantum yields and absorptivities give equal rate constants:

>>> k_AB, k_BA = photoswitch_rate_constants(phi_AB=0.5, eps_A=1000.0, phi_BA=0.5, eps_B=1000.0)
>>> round(k_AB, 6) == round(k_BA, 6)
True
chemistrykit.photochem.rehm_weller_free_energy(E_ox_donor, E_red_acceptor, E00, work_term=0.0)[source]#

Free energy of photoinduced electron transfer, \(\Delta G_{ET}=E_{ox}(D)-E_{red}(A)-E_{00}+w\) (eV).

Parameters:
  • E_ox_donor (float) – Oxidation potential of the donor, in V.

  • E_red_acceptor (float) – Reduction potential of the acceptor, in V (same reference).

  • E00 (float) – Excitation energy of the excited partner, in eV.

  • work_term (float) – Coulombic ion-pair work term, in eV (small in polar solvents).

Return type:

float

Returns:

float – \(\Delta G_{ET}\), in eV (negative means exergonic).

Examples

>>> round(rehm_weller_free_energy(1.2, -1.9, 3.3), 6)
-0.2
chemistrykit.photochem.rehm_weller_quenching_rate(delta_G, k_diff=20000000000.0, ratio=0.25, dG0_dagger=0.104, T=298.15)[source]#

Rehm-Weller electron-transfer quenching rate constant \(k_q(\Delta G_{ET})\).

Parameters:
  • delta_G (float or array-like of float) – Electron-transfer free energy, in eV.

  • k_diff (float) – Diffusion rate constant \(k_d\), in 1/(M*s).

  • ratio (float) – \(k_{-d}/k_e^0\).

  • dG0_dagger (float) – Intrinsic barrier \(\Delta G^\ddagger(0)\), in eV.

  • T (float) – Temperature, in K.

Returns:

float or ndarray – \(k_q\), in 1/(M*s).

Examples

Strongly exergonic transfer approaches the plateau \(k_d/(1+0.25)=1.6\times10^{10}\), while thermoneutral transfer is more than ten times slower:

>>> round(rehm_weller_quenching_rate(-1.5) / 1e10, 2)
1.5
>>> round(rehm_weller_quenching_rate(-50.0) / 1e10, 2)
1.6
>>> round(rehm_weller_quenching_rate(0.0) / 1e10, 3)
0.128
chemistrykit.photochem.rotational_correlation_time(viscosity, volume, T)[source]#

Stokes-Einstein-Debye rotational correlation time \(\theta = \eta V/(k_B T)\).

Parameters:
  • viscosity (float or array-like of float) – Solvent viscosity \(\eta\), in Pa*s.

  • volume (float) – Hydrodynamic volume of the rotating molecule, in m3.

  • T (float or array-like of float) – Temperature, in K.

Returns:

float or ndarray – \(\theta\), in s.

Examples

A ~1 nm3 fluorophore in water (1 mPa*s) at 298 K rotates in about a quarter of a nanosecond:

>>> round(rotational_correlation_time(1.0e-3, 1.0e-27, 298.15) * 1e9, 3)
0.243
chemistrykit.photochem.stern_volmer_ratio(Ksv, Q)[source]#

The Stern-Volmer equation: \(I_0/I = 1 + K_{sv}[Q]\).

A plot of the fluorescence-intensity ratio \(I_0/I\) (unquenched over quenched intensity) against quencher concentration \([Q]\) is linear with slope \(K_{sv}\), the Stern-Volmer constant (Lakowicz, Principles of Fluorescence Spectroscopy, 3rd ed., Ch. 8, eq. 8.4). For purely dynamic (collisional) quenching, \(K_{sv}=k_q\tau_0\) (see dynamic_quenching_constant()) and this same linear relationship also holds for the fluorescence lifetime ratio \(\tau_0/\tau\); for purely static quenching (ground-state complex formation) the intensity ratio is still linear in \([Q]\) (with \(K_{sv}\) reinterpreted as the complex’s formation constant) but the lifetime ratio stays exactly 1 – see classify_quenching_mechanism().

Parameters:
  • Ksv (float) – Stern-Volmer constant, in units inverse to Q’s (e.g. 1/M for Q in mol/L).

  • Q (float or array-like of float) – Quencher concentration.

Returns:

float or ndarray – \(I_0/I\) (or, for a dynamic mechanism, equally \(\tau_0/\tau\)).

Examples

At zero quencher, the ratio is exactly 1 (no quenching):

>>> round(float(stern_volmer_ratio(Ksv=5.0, Q=0.0)), 6)
1.0

The ratio grows linearly with quencher concentration:

>>> Q = np.array([0.0, 0.01, 0.02, 0.03])
>>> ratio = stern_volmer_ratio(Ksv=20.0, Q=Q)
>>> bool(np.allclose(np.diff(ratio), 20.0 * np.diff(Q)))
True
chemistrykit.photochem.stokes_shift(absorption_max_nm, emission_max_nm)[source]#

Stokes shift in wavenumbers, \(\Delta\tilde\nu = 10^7/\lambda_{abs} - 10^7/\lambda_{em}\) (cm-1).

Stokes (1852) observed that fluorescence is emitted at longer wavelength (lower energy) than the light that excites it; the energy difference is lost to vibrational relaxation and solvent reorganization in the excited state before emission (Lakowicz, Ch. 1.4).

Parameters:
  • absorption_max_nm (float or array-like of float) – Wavelength of the absorption maximum, in nm.

  • emission_max_nm (float or array-like of float) – Wavelength of the emission maximum, in nm.

Returns:

float or ndarray – Stokes shift in cm-1 (positive for red-shifted emission).

Examples

Absorption at 400 nm and emission at 500 nm:

>>> round(stokes_shift(400.0, 500.0), 6)
5000.0