chemistrykit.electrochem#

chemistrykit.electrochem: electrochemistry.

The Nernst equation for standard and concentration cells (with an activity-coefficient-corrected variant built on chemistrykit.solutions.systems.activity); a curated table of standard reduction potentials with redox-couple balancing and cell-potential combination; Butler-Volmer electrode kinetics, exchange current density, and Tafel-plot linearization; galvanic vs. electrolytic cells and Faraday’s laws of electrolysis; and a simplified constant- current battery discharge/capacity model with Peukert’s-law rate dependence; Kohlrausch’s conductivity laws; fuel-cell thermodynamic limits; and the diffusion-limited currents of electroanalysis (Cottrell, Ilkovič, Heyrovský-Ilkovič wave, Randles-Ševčík).

class chemistrykit.electrochem.BatteryDischargeModel[source]#

Bases: ABC

Common interface for a simplified constant-current battery discharge model.

Simplified model, flagged explicitly: a real battery’s discharge behavior involves a nonlinear open-circuit-voltage-vs-state-of-charge curve, temperature dependence, cycle aging, and relaxation effects under load – none of that is modeled here. This captures only the two textbook-level effects usually taught alongside each other: a constant ohmic (IR) voltage drop under load, and the empirical Peukert’s-law dependence of effective capacity on discharge rate (W. Peukert, Elektrotechnische Zeitschrift 20, 20 (1897); see Linden & Reddy, Handbook of Batteries, 3rd ed., Ch. 3.3).

Concrete subclasses implement state_of_charge() and terminal_voltage(); discharge_curve() is then available on every subclass for free, mirroring how chemistrykit.solutions.core.base_system.Titration.curve() is shared machinery built once atop each subclass’s pH_at.

discharge_curve(t)[source]#

Compute the full discharge curve over a range of elapsed times.

Parameters:

t (array-like of float) – Elapsed discharge times to evaluate at.

Return type:

DischargeResult

Returns:

DischargeResult

abstractmethod state_of_charge(t)[source]#

Return the fraction of rated capacity remaining at time(s) t.

Parameters:

t (float or array-like of float) – Elapsed discharge time.

Returns:

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

abstractmethod terminal_voltage(t)[source]#

Return the terminal voltage at time(s) t.

Parameters:

t (float or array-like of float) – Elapsed discharge time.

Returns:

float or ndarray – Terminal voltage, in V (0 once the battery is exhausted).

class chemistrykit.electrochem.ConstantCurrentBattery(capacity_peukert, current, v_nominal, internal_resistance=0.0, k=1.0)[source]#

Bases: BatteryDischargeModel

A battery discharged at constant current, with a Peukert-law-limited runtime and an ohmic voltage sag.

Simplified model (see the module and BatteryDischargeModel docstrings): the open-circuit voltage is treated as constant at v_nominal until the Peukert-law discharge time is reached, at which point the battery is treated as instantaneously exhausted (voltage drops to 0); the only voltage variation modeled during discharge is a constant ohmic sag \(IR_{internal}\), not the smoothly declining open-circuit-voltage-vs-state-of-charge curve of a real cell.

Parameters:
  • capacity_peukert (float) – Peukert capacity constant, in \(A^k\cdot h\).

  • current (float) – Constant discharge current, in A.

  • v_nominal (float) – Open-circuit (fully charged) terminal voltage, in V.

  • internal_resistance (float) – Internal (ohmic) resistance, in ohms.

  • k (float) – Peukert exponent.

Examples

>>> battery = ConstantCurrentBattery(capacity_peukert=2.0, current=1.0, v_nominal=3.7, internal_resistance=0.05)
>>> round(battery.discharge_time(), 4)
2.0
>>> round(float(battery.terminal_voltage(1.0)), 4)
3.65
>>> round(float(battery.terminal_voltage(3.0)), 4)
0.0
discharge_time()[source]#

Total discharge time (runtime until exhausted), in h.

Return type:

float

Returns:

float

state_of_charge(t)[source]#

Return the fraction of rated capacity remaining at time(s) t.

Parameters:

t (float or array-like of float) – Elapsed discharge time.

Returns:

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

terminal_voltage(t)[source]#

Return the terminal voltage at time(s) t.

Parameters:

t (float or array-like of float) – Elapsed discharge time.

Returns:

float or ndarray – Terminal voltage, in V (0 once the battery is exhausted).

class chemistrykit.electrochem.DischargeResult(t, voltage, state_of_charge)[source]#

Bases: object

Container for the output of a BatteryDischargeModel.discharge_curve() call.

Mirrors chemistrykit.solutions.core.base_system.TitrationResult (a stable, dataclass return type for a curve traced out over an independent variable – there, titrant volume; here, elapsed time).

Parameters:
state_of_charge: ndarray#

Fraction of rated capacity remaining at each time in t, in [0, 1].

Type:

ndarray

t: ndarray#

Elapsed discharge time, in the same time unit as the model’s rate constants (h if current is in A and capacity in Ah).

Type:

ndarray

voltage: ndarray#

Terminal voltage at each time in t, in V.

Type:

ndarray

class chemistrykit.electrochem.HalfReaction(name, n, E_standard)[source]#

Bases: object

A single tabulated standard reduction half-reaction.

Parameters:
  • name (str) – Short label, e.g. "Cu2+/Cu" for \(Cu^{2+} + 2e^- o Cu\).

  • n (int) – Number of electrons transferred in the half-reaction as written (in the reduction direction).

  • E_standard (float) – Standard reduction potential, in V vs. SHE.

E_standard: float#
n: int#
name: str#
class chemistrykit.electrochem.KohlrauschFit(limiting_molar_conductivity, kohlrausch_coefficient, r_squared)[source]#

Bases: object

Result of fitting \(\Lambda_m\) vs. \(\sqrt{c}\) data to Kohlrausch’s square-root law.

Parameters:
  • limiting_molar_conductivity (float)

  • kohlrausch_coefficient (float)

  • r_squared (float)

kohlrausch_coefficient: float#

Fitted coefficient K (minus the slope).

Type:

float

limiting_molar_conductivity: float#

Fitted \(\Lambda_m^\circ\) (the intercept at \(c\to0\)), in S m^2 mol^-1.

Type:

float

r_squared: float#

Coefficient of determination of the linear fit.

Type:

float

class chemistrykit.electrochem.TafelFit(tafel_slope, exchange_current_density, r_squared)[source]#

Bases: object

Result of fitting \(\log_{10}|i|\) vs. \(\eta\) data to a Tafel line.

Since \(\eta = b\log_{10}i - b\log_{10}i_0\), a plot of eta against \(\log_{10}|i|\) is a straight line of slope b (the Tafel slope) and intercept \(-b\log_{10}i_0\), from which the exchange current density \(i_0=10^{-\text{intercept}/b}\) follows.

Parameters:
exchange_current_density: float#

Fitted exchange current density \(i_0\).

Type:

float

predict(i)[source]#

Evaluate the fitted Tafel line’s overpotential at current density(-ies) i.

Parameters:

i (float or array-like of float)

Returns:

float or ndarray

r_squared: float#

Coefficient of determination of the linear fit.

Type:

float

tafel_slope: float#

Fitted Tafel slope b, in V/decade.

Type:

float

chemistrykit.electrochem.activity_corrected_reaction_quotient(concentrations, charges, stoich_coeffs, T=298.15, extended=True, Ba=1.0)[source]#

Reaction quotient built from Debye-Huckel activity-corrected concentrations rather than raw concentrations.

\(Q = \prod_i a_i^{\nu_i} = \prod_i (\gamma_i c_i / c^\circ)^{\nu_i}\) (with \(c^\circ = 1\) mol/L implicit throughout, matching the convention of chemistrykit.thermo.systems.equilibrium.reaction_quotient()), where every ion’s activity coefficient \(\gamma_i\) is evaluated at the mixture’s ionic strength via chemistrykit.solutions.systems.activity.ionic_strength() and chemistrykit.solutions.systems.activity.activity_coefficient_debye_huckel_extended() – reusing this package’s existing Debye-Huckel machinery rather than assuming unit activity coefficients, per Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 2.1.3. Only ionic species (\(z\neq0\)) get a nontrivial activity coefficient; a neutral species or a pure solid/liquid passed with charges=0 is treated as ideal (\(\gamma=1\)), the usual convention for a species at unit activity by definition (e.g. the solid electrode itself).

Parameters:
  • concentrations (array-like of float) – Concentration of each species, in mol/L.

  • charges (array-like of float) – Charge number of each species (0 for a neutral species / pure solid or liquid).

  • stoich_coeffs (array-like of float) – Signed net stoichiometric coefficient of each species (positive for products, negative for reactants), same order as concentrations.

  • T (float) – Absolute temperature; only used insofar as chemistrykit.solutions.systems.activity.DEBYE_HUCKEL_A_25C is itself a 25 degC value – this function does not otherwise adjust A for T, an approximation reasonable near room temperature only.

  • extended (bool) – Use the extended Debye-Huckel law (valid to higher ionic strength) rather than the limiting law.

  • Ba (float) – Ion-size parameter for the extended law; see chemistrykit.solutions.systems.activity.activity_coefficient_debye_huckel_extended().

Return type:

float

Returns:

float

Examples

At vanishing ionic strength (dilute limit), activity coefficients are all 1 and this reduces to the raw-concentration reaction quotient:

>>> c = [1e-9, 1e-12]
>>> z = [1, -2]
>>> nu = [-1.0, 1.0]
>>> round(activity_corrected_reaction_quotient(c, z, nu), 6)
0.001

At higher ionic strength the two differently-charged species’ activity coefficients diverge from each other and from 1, so the activity-corrected quotient deviates from the raw-concentration ratio (here \(c_1/c_0=0.1\)):

>>> Q_raw = 0.1
>>> Q_corrected = activity_corrected_reaction_quotient([0.1, 0.1], [1, -2], nu)
>>> bool(abs(Q_corrected - Q_raw) > 1e-3)
True
chemistrykit.electrochem.balance_redox_reaction(cathode, anode)[source]#

Electron-balancing multiples for combining two half-reactions into one overall redox reaction.

Half-reaction potentials combine as-is (see cell_potential()), but mass-balancing the overall reaction – and therefore computing e.g. how much of each species is consumed per mole of overall reaction via Faraday’s laws (chemistrykit.electrochem.systems.electrolysis) – requires each half-reaction to transfer the same number of electrons. The standard recipe (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 6.9) multiplies the cathode half-reaction by \(n_{anode}/\gcd(n_{cathode},n_{anode})\) and the anode half-reaction by \(n_{cathode}/\gcd(n_{cathode},n_{anode})\), so both sides transfer \(\mathrm{lcm}(n_{cathode},n_{anode})\) electrons.

Parameters:
Return type:

tuple[int, int, int]

Returns:

cathode_multiple, anode_multiple, n_total (int) – Multiples applied to the cathode and anode half-reactions, and the resulting total number of electrons transferred by the balanced overall reaction.

Examples

\(Zn + Cu^{2+} \to Zn^{2+} + Cu\) needs no rebalancing (both transfer 2 electrons already):

>>> from chemistrykit.electrochem.systems.standard_potentials import STANDARD_REDUCTION_POTENTIALS as T
>>> balance_redox_reaction(T["Cu2+/Cu"], T["Zn2+/Zn"])
(1, 1, 2)

\(MnO_4^- + 5Fe^{2+} \to \dots\) needs Fe balanced 5-fold against Mn’s single 5-electron step:

>>> balance_redox_reaction(T["MnO4-/Mn2+"], T["Fe3+/Fe2+"])
(1, 5, 5)
chemistrykit.electrochem.butler_volmer_current_density(i0, eta, alpha=0.5, n=1, T=298.15, R_gas=8.31446261815324, F=96485.33212331001)[source]#

The Butler-Volmer equation for net electrode current density.

\[i = i_0\left[\exp\!\left(\frac{\alpha n F\eta}{RT}\right) -\exp\!\left(-\frac{(1-\alpha)nF\eta}{RT}\right)\right]\]

where \(\eta = E - E_{eq}\) is the overpotential, \(i_0\) the exchange current density, and \(\alpha\) the (cathodic) charge transfer coefficient (Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 3.3, eq. 3.3.11). The first term is the anodic (oxidation) partial current, the second the cathodic (reduction) partial current; at \(\eta=0\) they exactly cancel, so no net current flows at equilibrium even though both partial reactions are still occurring (a genuinely dynamic equilibrium, unlike a thermodynamic “nothing is happening”).

Parameters:
  • i0 (float) – Exchange current density, in A/m^2 (or any consistent current- density unit; the same unit is returned).

  • eta (float or array-like of float) – Overpotential, in V.

  • alpha (float) – Charge transfer (symmetry) coefficient, in \((0, 1)\); 0.5 is the common approximation for a simple, symmetric one-electron step.

  • n (int) – Number of electrons transferred in the rate-determining step.

  • T (float) – Absolute temperature, in K.

  • R_gas (float) – Gas constant, in J mol^-1 K^-1.

  • F (float) – Faraday constant, in C/mol.

Returns:

float or ndarray

Examples

At zero overpotential the net current is exactly zero:

>>> round(float(butler_volmer_current_density(i0=1e-6, eta=0.0)), 12)
0.0

A positive overpotential drives net anodic (positive) current:

>>> bool(butler_volmer_current_density(1e-6, eta=0.1) > 0)
True
chemistrykit.electrochem.cell_potential(cathode, anode)[source]#

Standard cell potential from two half-reactions: \(E^\circ_{cell} = E^\circ_{cathode} - E^\circ_{anode}\).

Both potentials are used exactly as tabulated reduction potentials – the anode’s is not negated by hand and neither is scaled by the number of electrons it transfers, because electrode potential is an intensive quantity (a per-electron driving force), unaffected by how many electrons the balanced overall reaction happens to require (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 6.9). See balance_redox_reaction() for the electron-balancing arithmetic this potential calculation deliberately does not need.

Parameters:
  • cathode (HalfReaction) – The half-reaction that runs as a reduction (gains electrons).

  • anode (HalfReaction) – The half-reaction that runs as an oxidation (loses electrons); its tabulated reduction potential is passed in unchanged.

Return type:

float

Returns:

float – Standard cell potential, in V. Positive means the reaction as paired (cathode reduction + anode oxidation) is spontaneous.

Examples

The classic Daniell cell, Cu cathode / Zn anode:

>>> from chemistrykit.electrochem.systems.standard_potentials import STANDARD_REDUCTION_POTENTIALS as T
>>> round(cell_potential(T["Cu2+/Cu"], T["Zn2+/Zn"]), 2)
1.1
chemistrykit.electrochem.charge_from_current(current, time)[source]#

Charge passed at constant current: \(Q = It\).

Parameters:
  • current (float or array-like of float) – Current, in A.

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

Returns:

float or ndarray – Charge, in C.

Examples

>>> round(float(charge_from_current(current=2.0, time=3600.0)), 2)
7200.0
chemistrykit.electrochem.concentration_cell_potential(n, C_cathode, C_anode, T=298.15, R_gas=8.31446261815324, F=96485.33212331001)[source]#

Cell potential of a concentration cell: \(E = \frac{RT}{nF}\ln(C_{cathode}/C_{anode})\).

A concentration cell pairs two half-cells built from the same electrode material and redox couple, differing only in the concentration of the dissolved species – so \(E^\circ = 0\) identically (both half-reactions have the same standard potential) and the entire driving force comes from the Nernst equation’s activity term (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 6.10). By convention the more concentrated compartment is the cathode (reduction is favored there, consuming ions and diluting that side, driving the system toward equalized concentrations).

Parameters:
  • n (int) – Number of electrons transferred per formula unit of the redox couple (e.g. 2 for \(M^{2+} + 2e^- \to M\)).

  • C_cathode (float or array-like of float) – Ion concentrations in the cathode and anode compartments, in the same units (an activity-coefficient-corrected value is more accurate at high concentration – see nernst_potential_with_activity()).

  • C_anode (float or array-like of float) – Ion concentrations in the cathode and anode compartments, in the same units (an activity-coefficient-corrected value is more accurate at high concentration – see nernst_potential_with_activity()).

  • T (float) – Absolute temperature, in K.

  • R_gas (float) – Gas constant, in J mol^-1 K^-1.

  • F (float) – Faraday constant, in C/mol.

Returns:

float or ndarray – Cell potential, in V. Positive when C_cathode > C_anode, zero at equal concentrations (no net driving force between two identical half-cells).

Examples

Equal concentrations give exactly zero cell potential:

>>> round(float(concentration_cell_potential(n=2, C_cathode=0.10, C_anode=0.10)), 9)
0.0

A tenfold concentration difference in a 1-electron couple at 25 degC gives the textbook ~59 mV per decade:

>>> round(float(concentration_cell_potential(n=1, C_cathode=0.10, C_anode=0.010)), 4)
0.0592
chemistrykit.electrochem.cottrell_current(t, n, A, C, D, F=96485.33212331001)[source]#

The Cottrell equation: \(i(t) = nFAC\sqrt{D/(\pi t)}\).

Diffusion-limited current at a planar electrode after a potential step that drives the surface concentration of the electroactive species to zero (Bard & Faulkner, Electrochemical Methods, 2nd ed., eq. 5.2.11).

Parameters:
  • t (float or array-like of float) – Time since the potential step, in s (must be positive).

  • n (int) – Electrons transferred per molecule.

  • A (float) – Electrode area, in m^2.

  • C (float) – Bulk concentration, in mol/m^3.

  • D (float) – Diffusion coefficient, in m^2/s.

  • F (float)

Returns:

float or ndarray – Current, in A.

Examples

The Cottrell signature – \(i\sqrt{t}\) is constant in time:

>>> a = cottrell_current(1.0, n=1, A=1e-6, C=1.0, D=1e-9)
>>> b = cottrell_current(4.0, n=1, A=1e-6, C=1.0, D=1e-9)
>>> round(a / b, 12)
2.0
chemistrykit.electrochem.effective_capacity(capacity_peukert, current, k=1.0)[source]#

Effective delivered capacity at a given discharge current: \(C_{eff}(I) = I\,t(I) = C_p I^{1-k}\).

At the ideal Peukert exponent \(k=1\), this is exactly \(C_p\) regardless of current – a battery that delivers its full rated capacity no matter how fast it is discharged. Real batteries (\(k>1\)) deliver progressively less effective capacity as the discharge current increases, the practical meaning of “capacity fade with rate” (Linden & Reddy, Handbook of Batteries, 3rd ed., Ch. 3.3).

Parameters:
  • capacity_peukert (float) – Peukert capacity constant, in \(A^k\cdot h\).

  • current (float) – Constant discharge current, in A.

  • k (float) – Peukert exponent.

Returns:

float or ndarray – Effective capacity, in Ah.

Examples

At k=1, effective capacity is independent of current (the ideal, rate-independent battery):

>>> c_low = effective_capacity(10.0, current=1.0, k=1.0)
>>> c_high = effective_capacity(10.0, current=5.0, k=1.0)
>>> round(c_low, 6), round(c_high, 6)
(10.0, 10.0)

At k>1, effective capacity decreases as discharge current increases – the real-battery “capacity fade at high rate” effect:

>>> c_low = effective_capacity(10.0, current=1.0, k=1.2)
>>> c_high = effective_capacity(10.0, current=5.0, k=1.2)
>>> bool(c_high < c_low)
True
chemistrykit.electrochem.exchange_current_density(k0, C_ox, C_red, n=1, alpha=0.5, F=96485.33212331001)[source]#

Exchange current density from a standard heterogeneous rate constant and bulk concentrations.

\[i_0 = nFk^0 C_{ox}^{1-\alpha}C_{red}^{\alpha}\]

(Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 3.4, eq. 3.4.6) – the current density that flows equally in both directions at equilibrium, before any net overpotential is applied.

Parameters:
  • k0 (float) – Standard heterogeneous electron-transfer rate constant, in m/s (or a consistent length/time unit).

  • C_ox (float) – Bulk concentrations of the oxidized and reduced forms, in mol/m^3 (consistent with k0’s length unit).

  • C_red (float) – Bulk concentrations of the oxidized and reduced forms, in mol/m^3 (consistent with k0’s length unit).

  • n (int) – Electrons transferred.

  • alpha (float) – Charge transfer coefficient.

  • F (float) – Faraday constant, in C/mol.

Return type:

float

Returns:

float – Exchange current density, in A/m^2.

Examples

Symmetric concentrations and alpha=0.5 give a simple, order-1-free-of-composition result:

>>> i0 = exchange_current_density(k0=1e-5, C_ox=1.0, C_red=1.0, n=1)
>>> round(i0, 6)
0.964853
chemistrykit.electrochem.faradays_law_mass(current, time, molar_mass, n, efficiency=1.0, F=96485.33212331001)[source]#

Mass deposited/consumed by electrolysis at constant current: \(m = ItM\eta/(nF)\).

Combines charge_from_current() and mass_from_charge() (Faraday’s laws of electrolysis together, Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 1.3.3), with an optional current (faradaic) efficiency \(\eta\in(0,1]\) for the common real-world case where a side reaction (e.g. competing gas evolution) consumes some of the current without depositing the desired product.

Parameters:
  • current (float) – Applied current, in A.

  • time (float) – Duration, in s.

  • molar_mass (float) – Molar mass of the deposited/consumed species, in g/mol.

  • n (int) – Electrons transferred per formula unit.

  • efficiency (float) – Current (faradaic) efficiency, in \((0, 1]\); 1.0 is the ideal, no-side-reaction case.

  • F (float) – Faraday constant, in C/mol.

Returns:

float – Mass, in g.

Raises:

ValueError – If efficiency is not in \((0, 1]\).

Examples

Copper electrorefining, \(Cu^{2+}+2e^-\to Cu\) (M=63.546 g/mol, n=2), run at 10 A for 1 hour: mass scales linearly with both current and time, the direct experimental test of Faraday’s first law.

>>> m1 = faradays_law_mass(current=10.0, time=3600.0, molar_mass=63.546, n=2)
>>> m2 = faradays_law_mass(current=20.0, time=3600.0, molar_mass=63.546, n=2)
>>> round(m2 / m1, 6)
2.0
>>> round(m1, 4)
11.8549

Reduced current efficiency proportionally reduces the mass deposited:

>>> m_ideal = faradays_law_mass(10.0, 3600.0, 63.546, n=2, efficiency=1.0)
>>> m_90pct = faradays_law_mass(10.0, 3600.0, 63.546, n=2, efficiency=0.9)
>>> round(m_90pct / m_ideal, 6)
0.9
chemistrykit.electrochem.fit_kohlrausch_law(c, Lambda_m)[source]#

Extrapolate measured molar conductivities to infinite dilution.

Fits \(\Lambda_m\) against \(\sqrt{c}\) by ordinary least squares; the intercept is \(\Lambda_m^\circ\) and minus the slope is K – exactly Kohlrausch’s graphical extrapolation.

Parameters:
  • c (array-like of float) – Molar concentrations, in mol/L.

  • Lambda_m (array-like of float) – Measured molar conductivities, in S m^2 mol^-1.

Return type:

KohlrauschFit

Returns:

KohlrauschFit

Examples

>>> import numpy as np
>>> c = np.array([1e-4, 1e-3, 5e-3, 1e-2])
>>> fit = fit_kohlrausch_law(c, kohlrausch_molar_conductivity(c, 0.0126, 0.0089))
>>> round(fit.limiting_molar_conductivity, 6), round(fit.kohlrausch_coefficient, 6)
(0.0126, 0.0089)
chemistrykit.electrochem.fit_tafel_plot(eta, i)[source]#

Fit high-overpotential (eta, i) data to a Tafel line and recover b and \(i_0\).

Linearizes \(\eta = b\log_{10}i - b\log_{10}i_0\) and fits by ordinary least squares against \(x=\log_{10}|i|\) – the experimental Tafel-plot analysis (Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 3.4), structurally identical to chemistrykit.kinetics.systems.arrhenius.fit_arrhenius()’s Arrhenius plot.

Parameters:
  • eta (array-like of float) – Overpotentials, in V (should be restricted to the high-|eta| Tafel regime for the linear approximation to hold).

  • i (array-like of float) – Corresponding current densities.

Return type:

TafelFit

Returns:

TafelFit

Examples

Generate exact high-overpotential data from a known \((b, i_0)\) and recover both:

>>> import numpy as np
>>> i0_true, alpha, n = 2e-6, 0.5, 1
>>> eta = np.linspace(0.2, 0.4, 10)
>>> i = butler_volmer_current_density(i0_true, eta, alpha=alpha, n=n)
>>> fit = fit_tafel_plot(eta, i)
>>> round(fit.exchange_current_density / i0_true, 2)
1.0
chemistrykit.electrochem.fuel_cell_efficiency_limit(delta_G, delta_H)[source]#

Maximum (thermodynamic) efficiency of a fuel cell: \(\eta_{max} = \Delta G/\Delta H\).

The fraction of the fuel’s heat of combustion that a reversible cell can deliver as electrical work – not bounded by the Carnot factor of a heat engine (Larminie & Dicks, Fuel Cell Systems Explained, 2nd ed., Ch. 2.3).

Parameters:
  • delta_G (float) – Reaction Gibbs energy and enthalpy (same units, same sign).

  • delta_H (float) – Reaction Gibbs energy and enthalpy (same units, same sign).

Return type:

float

Returns:

float

Examples

>>> round(fuel_cell_efficiency_limit(-237.13e3, -285.83e3), 3)
0.83
chemistrykit.electrochem.ilkovic_diffusion_current(n, D, m, t_drop, C, average=False)[source]#

The Ilkovič equation for the polarographic diffusion current at a dropping mercury electrode.

\(i_d = k\,n D^{1/2} m^{2/3} t^{1/6} C\) with \(k=708\) for the maximum current at the end of each drop’s life and \(k=607\) for the drop-averaged current (Bard & Faulkner, Electrochemical Methods, 2nd ed., eqs. 7.1.6-7.1.7). Traditional polarographic units are used here, not SI, because the numerical constants are defined in them.

Parameters:
  • n (int) – Electrons transferred per molecule.

  • D (float) – Diffusion coefficient, in cm^2/s.

  • m (float) – Mercury flow rate, in mg/s.

  • t_drop (float) – Drop time, in s.

  • C (float) – Bulk concentration, in mmol/L.

  • average (bool) – Return the drop-averaged (607) rather than the maximum (708) current.

Return type:

float

Returns:

float – Diffusion current, in microamperes.

Examples

>>> round(ilkovic_diffusion_current(n=2, D=1e-5, m=2.0, t_drop=4.0, C=1.0), 2)
8.96

The diffusion current is strictly proportional to concentration – the basis of quantitative polarography:

>>> round(ilkovic_diffusion_current(2, 1e-5, 2.0, 4.0, C=3.0) / ilkovic_diffusion_current(2, 1e-5, 2.0, 4.0, C=1.0), 12)
3.0
chemistrykit.electrochem.is_spontaneous(E_cell)[source]#

Whether a cell reaction is spontaneous as written, from its cell potential.

\(E_{cell} > 0 \iff \Delta G = -nFE_{cell} < 0\), the spontaneity criterion (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 6.9). A spontaneous cell reaction discharges galvanically (delivers electrical work); a non-spontaneous one must be driven electrolytically by an externally applied voltage exceeding \(|E_{cell}|\) in the opposing sense – see chemistrykit.electrochem.systems.electrolysis.

Parameters:

E_cell (float) – Cell potential (standard or Nernst-corrected), in V.

Return type:

bool

Returns:

bool

Examples

>>> is_spontaneous(1.10)
True
>>> is_spontaneous(-0.76)
False
chemistrykit.electrochem.kohlrausch_molar_conductivity(c, Lambda0, K)[source]#

Kohlrausch’s square-root law: \(\Lambda_m = \Lambda_m^\circ - K\sqrt{c}\).

Empirically valid for strong electrolytes at low concentration (Atkins & de Paula, Physical Chemistry, 11th ed., Topic 16C.1(b)); Debye, Hückel and Onsager later derived the coefficient K from ion-atmosphere theory.

Parameters:
  • c (float or array-like of float) – Molar concentration, in mol/L.

  • Lambda0 (float) – Limiting molar conductivity, in S m^2 mol^-1.

  • K (float) – Kohlrausch coefficient, in S m^2 mol^-1 (mol/L)^-1/2.

Returns:

float or ndarray – Molar conductivity, in S m^2 mol^-1.

Examples

>>> round(kohlrausch_molar_conductivity(0.0, Lambda0=0.0126, K=0.0089), 6)
0.0126
>>> round(float(kohlrausch_molar_conductivity(0.01, Lambda0=0.0126, K=0.0089)), 6)
0.01171
chemistrykit.electrochem.limiting_molar_conductivity(ions, table=None)[source]#

Kohlrausch’s law of independent migration: \(\Lambda_m^\circ = \sum_i \nu_i\lambda_i^\circ\).

At infinite dilution every ion migrates independently of its counter-ions, so an electrolyte’s limiting molar conductivity is the stoichiometry-weighted sum of per-ion contributions (Atkins & de Paula, Physical Chemistry, 11th ed., Topic 16C.1(b)).

Parameters:
  • ions (dict[str, float]) – Map from ion name (a key of table) to the number of those ions per formula unit, e.g. {"Mg2+": 1, "Cl-": 2} for MgCl2. Negative multiples are allowed, which is how Kohlrausch’s law is used to combine measured electrolytes.

  • table (dict[str, float] | None) – Limiting ionic conductivities, in S m^2 mol^-1; defaults to LIMITING_IONIC_CONDUCTIVITIES.

Return type:

float

Returns:

float – Limiting molar conductivity, in S m^2 mol^-1.

Examples

>>> round(limiting_molar_conductivity({"Na+": 1, "Cl-": 1}) * 1e4, 1)  # S cm^2/mol
126.4

Independent migration means the difference between two salts with a common anion depends only on the cations:

>>> kcl = limiting_molar_conductivity({"K+": 1, "Cl-": 1})
>>> kno3 = limiting_molar_conductivity({"K+": 1, "NO3-": 1})
>>> nacl = limiting_molar_conductivity({"Na+": 1, "Cl-": 1})
>>> nano3 = limiting_molar_conductivity({"Na+": 1, "NO3-": 1})
>>> round(kcl - nacl, 8) == round(kno3 - nano3, 8)
True
chemistrykit.electrochem.mass_from_charge(charge, molar_mass, n, F=96485.33212331001)[source]#

Mass of substance deposited/consumed: \(m = QM/(nF)\).

Parameters:
  • charge (float or array-like of float) – Charge passed, in C.

  • molar_mass (float) – Molar mass of the species, in g/mol.

  • n (int) – Electrons transferred per formula unit.

  • F (float) – Faraday constant, in C/mol.

Returns:

float or ndarray – Mass, in g.

Examples

Silver electroplating, \(Ag^+ + e^- \to Ag\) (n=1, M=107.87 g/mol): depositing 1 mol of electrons’ worth of charge deposits exactly one mole (107.87 g) of silver:

>>> round(mass_from_charge(charge=96485.332, molar_mass=107.87, n=1), 2)
107.87
chemistrykit.electrochem.minimum_applied_voltage_electrolytic(E_cell)[source]#

Minimum externally applied voltage to drive a non-spontaneous (electrolytic) cell reaction.

A cell reaction with \(E_{cell}<0\) is non-spontaneous as written and will not run galvanically; forcing it to run (electrolysis) requires an externally applied voltage of at least \(|E_{cell}|\), opposing the cell’s natural (spontaneous reverse) direction (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 6.10). Approximation flagged: this is the thermodynamic minimum only – a real electrolysis cell also needs to overcome activation overpotential at each electrode (see chemistrykit.electrochem.systems.butler_volmer) and ohmic (solution-resistance) losses, so the practically applied voltage is always somewhat larger than this minimum.

Parameters:

E_cell (float) – Cell potential of the reaction as written, in V (should be negative – i.e. the reaction is non-spontaneous – for electrolysis to be the relevant regime; see chemistrykit.electrochem.systems.standard_potentials.is_spontaneous()).

Return type:

float

Returns:

float – Minimum applied voltage magnitude, in V.

Examples

Electrolyzing molten NaCl (reversing the spontaneous Na/Cl2 cell reaction, \(E_{cell}=-4.07\) V as written for Na deposition):

>>> round(minimum_applied_voltage_electrolytic(-4.07), 2)
4.07
chemistrykit.electrochem.moles_from_charge(charge, n, F=96485.33212331001)[source]#

Faraday’s first law: moles of substance transformed \(= Q/(nF)\).

One mole of electrons (\(F\) coulombs) transforms exactly \(1/n\) mole of a species undergoing an n-electron electrode reaction – charge passed is directly proportional to the amount of chemical change, the historically first-discovered electrochemical stoichiometry relationship (Faraday, 1834; Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 1.3.3).

Parameters:
  • charge (float or array-like of float) – Charge passed, in C.

  • n (int) – Electrons transferred per formula unit of the species.

  • F (float) – Faraday constant, in C/mol.

Returns:

float or ndarray – Amount transformed, in mol.

Examples

Doubling the charge exactly doubles the moles transformed (Faraday’s first law’s defining linearity):

>>> moles_from_charge(2.0 * 96485.332, n=1) == 2.0 * moles_from_charge(96485.332, n=1)
True
chemistrykit.electrochem.nernst_potential(E_standard, n, Q, T=298.15, R_gas=8.31446261815324, F=96485.33212331001)[source]#

The Nernst equation: \(E = E^\circ - \frac{RT}{nF}\ln Q\).

Relates a cell’s potential under arbitrary (non-standard-state) conditions to its standard potential and the reaction quotient Q of the overall cell reaction (Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 2.1, eq. 2.1.13).

Parameters:
  • E_standard (float) – Standard cell potential \(E^\circ\), in V.

  • n (int) – Number of electrons transferred in the balanced overall cell reaction (see chemistrykit.electrochem.systems.standard_potentials.balance_redox_reaction()).

  • Q (float or array-like of float) – Reaction quotient of the overall cell reaction (activities of products over reactants, each raised to its stoichiometric coefficient).

  • T (float) – Absolute temperature, in K.

  • R_gas (float) – Gas constant, in J mol^-1 K^-1.

  • F (float) – Faraday constant, in C/mol.

Returns:

float or ndarray

Examples

At \(Q = 1\) (unit activity of every species – the standard state by definition), the Nernst equation reduces exactly to \(E^\circ\):

>>> round(float(nernst_potential(E_standard=1.10, n=2, Q=1.0)), 6)
1.1

Increasing the product-side activity (Q > 1) lowers the cell potential below standard, per Le Chatelier’s principle applied to the driving reaction:

>>> bool(nernst_potential(1.10, 2, Q=10.0) < 1.10)
True
chemistrykit.electrochem.nernst_potential_with_activity(E_standard, n, concentrations, charges, stoich_coeffs, T=298.15, extended=True, Ba=1.0)[source]#

The Nernst equation with Q built from Debye-Huckel activity-corrected concentrations.

Combines activity_corrected_reaction_quotient() with nernst_potential() – the “reuse chemistrykit.solutions’s activity-coefficient machinery” version of the Nernst equation, as opposed to nernst_potential()’s assumption of ideal (unit activity coefficient) behavior. Real electrochemical cells, like real solution equilibria, deviate from ideal Nernstian behavior at concentrations much above dilute (Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 2.1.3).

Parameters:
Return type:

float

Returns:

float

Examples

In the dilute limit, this recovers the ideal Nernst potential to high accuracy:

>>> c = [1e-6, 1e-12]
>>> z = [2, 0]
>>> nu = [-1.0, 1.0]
>>> Q_raw = c[1] / c[0]
>>> ideal = nernst_potential(0.34, n=2, Q=Q_raw)
>>> corrected = nernst_potential_with_activity(0.34, n=2, concentrations=c, charges=z, stoich_coeffs=nu)
>>> bool(abs(ideal - corrected) < 1e-4)
True
chemistrykit.electrochem.peukert_discharge_time(capacity_peukert, current, k=1.0)[source]#

Peukert’s-law discharge time at constant current: \(t = C_p / I^k\).

\(C_p\) (the “Peukert capacity”, in \(A^k\cdot h\)) and the Peukert exponent \(k\) are empirical constants of a given battery chemistry/construction, fitted from discharge tests at several currents (Linden & Reddy, Handbook of Batteries, 3rd ed., Ch. 3.3, eq. 3.5). \(k=1\) is the ideal case (discharge time inversely proportional to current, i.e. a current-independent delivered capacity, see effective_capacity()); real batteries typically have \(k>1\) (lead-acid: \(k\approx1.1-1.3\)), reflecting the fact that a higher discharge rate delivers less total capacity than a lower one before the same cutoff voltage is reached.

Parameters:
  • capacity_peukert (float) – Peukert capacity constant \(C_p\), in \(A^k\cdot h\) (numerically equal to the rated capacity in Ah only when k=1).

  • current (float) – Constant discharge current, in A.

  • k (float) – Peukert exponent, \(\geq1\).

Return type:

float

Returns:

float or ndarray – Discharge time, in h.

Examples

At k=1 (the ideal case), discharge time is exactly inversely proportional to current:

>>> round(float(peukert_discharge_time(capacity_peukert=10.0, current=2.0, k=1.0)), 4)
5.0
>>> round(float(peukert_discharge_time(capacity_peukert=10.0, current=4.0, k=1.0)), 4)
2.5

At k>1, doubling the current more than halves the discharge time:

>>> t1 = peukert_discharge_time(10.0, current=2.0, k=1.2)
>>> t2 = peukert_discharge_time(10.0, current=4.0, k=1.2)
>>> bool(t2 < t1 / 2.0)
True
chemistrykit.electrochem.polarographic_wave_current(E, E_half, i_d, n, T=298.15, R_gas=8.31446261815324, F=96485.33212331001)[source]#

Heyrovský-Ilkovič reversible wave: \(E = E_{1/2} + \frac{RT}{nF}\ln\frac{i_d-i}{i}\).

Solved for the (cathodic) current, \(i = i_d/[1+\exp(nF(E-E_{1/2})/RT)]\): a sigmoidal wave rising from 0 to the diffusion plateau \(i_d\) as the potential is swept negative through the half-wave potential (Bard & Faulkner, Electrochemical Methods, 2nd ed., eq. 5.4.22).

Parameters:
  • E (float or array-like of float) – Electrode potential, in V.

  • E_half (float) – Half-wave potential, in V (characteristic of the species).

  • i_d (float) – Limiting diffusion current (any unit; the result has the same unit).

  • n (int) – Electrons transferred.

  • T (float)

  • R_gas (float)

  • F (float)

Returns:

float or ndarray – Current, in the unit of i_d.

Examples

At the half-wave potential the current is exactly half the plateau:

>>> polarographic_wave_current(-0.40, E_half=-0.40, i_d=8.0, n=2)
4.0
chemistrykit.electrochem.randles_sevcik_peak_current(v, n, A, C, D, T=298.15, R_gas=8.31446261815324, F=96485.33212331001)[source]#

The Randles-Ševčík equation: \(i_p = 0.4463\,nFAC\sqrt{nFvD/(RT)}\).

Peak current of a reversible, diffusion-controlled linear-sweep or cyclic voltammogram at a planar electrode (Bard & Faulkner, Electrochemical Methods, 2nd ed., eq. 6.2.19). At 25 degC this is the familiar \(i_p = (2.69\times10^5)\,n^{3/2}AD^{1/2}Cv^{1/2}\) in cm/mol/s units.

Parameters:
  • v (float or array-like of float) – Scan rate, in V/s.

  • n (int) – Electrons transferred.

  • A (float) – Electrode area, in m^2.

  • C (float) – Bulk concentration, in mol/m^3.

  • D (float) – Diffusion coefficient, in m^2/s.

  • T (float)

  • R_gas (float)

  • F (float)

Returns:

float or ndarray – Peak current, in A.

Examples

1 mM ferrocene-like couple (n=1, D=1e-9 m^2/s) at a 3 mm diameter disc, 100 mV/s:

>>> import math
>>> A = math.pi * (1.5e-3) ** 2
>>> round(randles_sevcik_peak_current(0.1, n=1, A=A, C=1.0, D=1e-9) * 1e6, 1)  # microamps
19.0

Peak current grows as the square root of scan rate:

>>> round(randles_sevcik_peak_current(0.4, 1, A, 1.0, 1e-9) / randles_sevcik_peak_current(0.1, 1, A, 1.0, 1e-9), 12)
2.0
chemistrykit.electrochem.reversible_cell_voltage(delta_G, n, F=96485.33212331001)[source]#

Reversible (open-circuit) voltage from the reaction Gibbs energy: \(E = -\Delta G/(nF)\).

Parameters:
  • delta_G (float) – Gibbs energy of the overall cell reaction, in J/mol.

  • n (int) – Electrons transferred per mole of reaction.

  • F (float)

Return type:

float

Returns:

float – Reversible cell voltage, in V.

Examples

Hydrogen-oxygen cell, \(H_2 + \tfrac{1}{2}O_2 \to H_2O(l)\), \(\Delta G^\circ = -237.13\) kJ/mol, n=2:

>>> round(reversible_cell_voltage(-237.13e3, n=2), 3)
1.229
chemistrykit.electrochem.reversible_cell_voltage_at_temperature(delta_H, delta_S, n, T, F=96485.33212331001)[source]#

Reversible voltage at temperature T: \(E(T) = -(\Delta H - T\Delta S)/(nF)\).

Assumes \(\Delta H\) and \(\Delta S\) independent of temperature (a standard first approximation over modest ranges).

Parameters:
  • delta_H (float) – Reaction enthalpy, in J/mol.

  • delta_S (float) – Reaction entropy, in J/(mol K).

  • n (int) – Electrons transferred per mole of reaction.

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

  • F (float)

Returns:

float or ndarray – Reversible cell voltage, in V.

Examples

The voltage falls with temperature when \(\Delta S<0\), with slope exactly \(\Delta S/(nF)\):

>>> e1 = reversible_cell_voltage_at_temperature(-285.83e3, -163.3, 2, 300.0)
>>> e2 = reversible_cell_voltage_at_temperature(-285.83e3, -163.3, 2, 301.0)
>>> round((e2 - e1) * 2 * 96485.33212 / -163.3, 6)
1.0
chemistrykit.electrochem.standard_cell_potential(cathode_name, anode_name)[source]#

Look up two half-reactions by name in STANDARD_REDUCTION_POTENTIALS and combine them.

Parameters:
  • cathode_name (str) – Keys into STANDARD_REDUCTION_POTENTIALS.

  • anode_name (str) – Keys into STANDARD_REDUCTION_POTENTIALS.

Return type:

float

Returns:

float – Standard cell potential, in V.

Examples

>>> round(standard_cell_potential("Cu2+/Cu", "Zn2+/Zn"), 2)
1.1
chemistrykit.electrochem.tafel_overpotential(i, i0, alpha=0.5, n=1, T=298.15, R_gas=8.31446261815324, F=96485.33212331001, branch='anodic')[source]#

The linearized Tafel-equation overpotential at high \(|\eta|\).

\(\eta = b\log_{10}(i/i_0)\), dropping the Butler-Volmer equation’s back-reaction exponential term (valid once \(|\eta|\gtrsim100/n\) mV, where that term is negligible compared to the dominant one – Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 3.4). Approximation flagged: this is the high-overpotential limit of butler_volmer_current_density(), not the exact equation – it is inaccurate near \(\eta=0\) where both exponential terms matter (see the module examples gallery for a numerical comparison across overpotential).

Parameters:
  • i (float or array-like of float) – Current density (same sign/branch as branch), in the same units as i0.

  • i0 (float) – Exchange current density.

  • alpha (float)

  • n (int)

  • T (float)

  • R_gas (float)

  • F (float)

  • branch (str)

Returns:

float or ndarray – Overpotential, in V.

Examples

At high overpotential the Tafel approximation agrees closely with the full Butler-Volmer equation (checked here to better than 0.5% at 300 mV):

>>> i0, alpha, n = 1e-6, 0.5, 1
>>> eta_true = 0.30
>>> i_full = butler_volmer_current_density(i0, eta_true, alpha=alpha, n=n)
>>> eta_tafel = tafel_overpotential(i_full, i0, alpha=alpha, n=n, branch="anodic")
>>> bool(abs(eta_tafel - eta_true) / eta_true < 0.005)
True
chemistrykit.electrochem.tafel_slope(alpha, n=1, T=298.15, R_gas=8.31446261815324, F=96485.33212331001, branch='anodic')[source]#

The Tafel slope \(b\), in V per decade of current.

From linearizing the dominant (far-from-equilibrium) exponential term of the Butler-Volmer equation and rewriting in terms of \(\log_{10}i\) (Bard & Faulkner, Electrochemical Methods, 2nd ed., Ch. 3.4, eq. 3.4.11-3.4.13):

\[b_{anodic} = \frac{2.303RT}{\alpha n F}, \qquad b_{cathodic} = \frac{2.303RT}{(1-\alpha) n F}\]
Parameters:
  • alpha (float) – Charge transfer coefficient.

  • n (int) – Electrons transferred.

  • T (float)

  • R_gas (float)

  • F (float)

  • branch (str) – Which high-overpotential branch’s slope to return.

Return type:

float

Returns:

float – Tafel slope, in V/decade (positive for the anodic branch, negative for the cathodic one, matching the sign convention that \(\eta\) increases with \(\log_{10}i\) on the anodic branch and decreases with it on the cathodic one).

Examples

With alpha=0.5 and a one-electron step at 25 degC, the anodic and cathodic slopes have equal magnitude (the symmetric-barrier case):

>>> b_a = tafel_slope(alpha=0.5, n=1, branch="anodic")
>>> b_c = tafel_slope(alpha=0.5, n=1, branch="cathodic")
>>> round(b_a, 4), round(b_c, 4)
(0.1183, -0.1183)