pyedl (formerly pycapacitance) is a Python package for modeling the physics of Electric Double Layers (EDL) in electrochemical systems. It implements semianalytical approximations for steric models (Carnahan-Starling and Liu) to efficiently calculate properties like electrode charge density, differential capacitance, and grand potential energies, particularly in regimes of high electrode potential and high electrolyte concentration where traditional dilute solution models fail.
This codebase accompanies the manuscript:
Semianalytical approximation of Ion Adsorption Layers and Capacitance in Carnahan–Starling-like steric models
Dagmawi B. Tadesse and Drew F. Parsons
Electrochimica Acta, 531, 146266 (2025).
DOI: 10.1016/j.electacta.2025.146266
URL: ScienceDirect
- Steric Models: Implements the Carnahan-Starling (CS) and Liu equations of state for hard-sphere fluids within mean-field theory.
- Composite Diffuse Layer (CDL): Implements a high-potential analytical approximation to the Bikerman model with a fully capped counterion concentration in the steric layer.
- Double-Electrode Cells: Compute full-cell voltage splits, capacitance, energies, and profiles from charge neutrality rather than user-supplied electrode potentials.
- Semianalytical Approximation: Uses a linear concentration profile approximation to solve the Poisson-Boltzmann equations analytically in the steric layer, providing rapid convergence to full numerical solutions at high potentials (>0.2 V) and concentrations (>1 M).
-
EDL Properties:
-
Charge Density: Calculate electrode surface charge density (
$\sigma$ ). -
Differential Capacitance: Calculate differential capacitance (
$C_d$ ) with potential-dependent dielectric effects. - Profiles: Generate concentration, electric field, and potential profiles within the double layer.
-
Charge Density: Calculate electrode surface charge density (
-
Thermodynamics:
-
Grand Potential Energy: Calculate the total energy stored in the EDL, decomposed into:
- Entropic Energy: Ideal ion configuration entropy.
- Electrostatic Energy: Energy stored in the electric field.
- Steric Energy: Excess free energy due to finite ion size (excluded volume).
-
Grand Potential Energy: Calculate the total energy stored in the EDL, decomposed into:
- Material Database: Includes extensible databases for common ions (alkali metals, halides, ionic liquids) and solvents (water, organic solvents).
The package requires Python 3.12+.
# Clone the repository
git clone https://github.com/daggbt/pyedl.git
cd pyedl
# Run examples directly
uv run examples/capacitance.py
uv run examples/energy.py
uv run examples/fitting.py
uv run examples/double_electrode.pypip install .from pyedl import ion_database, solvent_database
from pyedl import ElectrochemicalSystem, StericModel
# Define the system: NaF in Water
system = ElectrochemicalSystem(
cation=ion_database['Na+_hydrated'],
anion=ion_database['F-_hydrated'],
solvent=solvent_database['water'],
concentration=1.0, # mol/L
temperature=298.15, # K
n_hydration_cation=3.5,
n_hydration_anion=2.7
)
# Initialize the model (Carnahan-Starling)
model = StericModel(system, steric_model='cs')
# Calculate capacitance at 1.0 V
potential = 1.0 # V
capacitance = model.analytical_capacitance(potential)
print(f"Capacitance at {potential}V: {capacitance:.2f} μF/cm²")Calculate the components of the Grand Potential Energy stored in the EDL.
from pyedl.materials import Ion, Solvent
from pyedl import ElectrochemicalSystem, StericModel
# Define custom materials (e.g., LiPF6 in Propylene Carbonate)
pc_solvent = Solvent(name='Propylene Carbonate', dielectricConstant=66.14, solventPolarizability=6.0)
li_ion = Ion(name='Li+', charge=1, radiusAng=2.82, dispersionB=0.0, ionPolarizability=0.03)
pf6_ion = Ion(name='PF6-', charge=-1, radiusAng=2.54, dispersionB=0.0, ionPolarizability=4.0)
system = ElectrochemicalSystem(
cation=li_ion,
anion=pf6_ion,
solvent=pc_solvent,
concentration=1.0
)
model = StericModel(system, steric_model='cs')
# Calculate energy components at 1.0 V
phi = 1.0
entropic = model.get_entropic_energy(phi)
electrostatic = model.get_electrostatic_energy(phi)
steric = model.get_steric_free_energy(phi)
total = model.get_total_energy(phi)
print(f"Total Energy: {total:.4e} J/m²")
print(f" - Entropic: {entropic:.4e} J/m²")
print(f" - Electrostatic: {electrostatic:.4e} J/m²")
print(f" - Steric: {steric:.4e} J/m²")The Phase 0 plotting API lives in pyedl.plotting, with the legacy root-level pyedl.plot_capacitance_vs_potential kept as a compatibility wrapper.
from pyedl.plotting import (
plot_capacitance_vs_potential,
plot_energy_components_vs_potential,
sample_profiles,
)
# Plot capacitance vs potential and keep the sampled data
fig, ax, capacitance_data = plot_capacitance_vs_potential(
system=system,
potential_range=(-1.5, 1.5),
save_path='capacitance_curve.png',
)
# Plot grand-potential components with the same public API style
fig, ax, energy_data = plot_energy_components_vs_potential(
system=system,
potential_range=(0.1, 1.0),
save_path='energy_components.png',
)
# Sample steric-layer profiles without creating a figure
profile_data = sample_profiles(system=system, potential=0.8)
print(profile_data['steric_layer_thickness'])If you need the older two-array return shape, pyedl.plot_capacitance_vs_potential(...) still returns (potentials, capacitance).
For complete runnable examples, see examples/capacitance.py and examples/energy.py.
For inversion or optimization workflows, the JIT sweep path can evaluate an ordered capacitance curve efficiently while scanning candidate parameters.
import numpy as np
from pyedl import ion_database, solvent_database
from pyedl import ElectrochemicalSystem, StericModel, fit_counterion_permittivity_curve
system = ElectrochemicalSystem(
cation=ion_database['Na+_hydrated'],
anion=ion_database['F-_hydrated'],
solvent=solvent_database['water'],
concentration=1.0,
temperature=298.15,
)
potentials = np.linspace(0.02, 1.0, 251)
target_model = StericModel(system, steric_model='cs')
fit_model = StericModel(system, steric_model='cs')
# Synthetic target curve for a positive-potential sweep.
target_epsilon = 4.75
target_model.ion_permitivities[1] = target_epsilon
target_model.invalidate_caches()
target_capacitance = target_model.analytical_capacitance_sweep_jit(potentials)
fit_result = fit_counterion_permittivity_curve(
fit_model,
potentials,
target_capacitance,
epsilon_bounds=(1.0, 10.0),
use_jit_sweep=True,
)
print(f"Recovered permittivity: {fit_result.fitted_permittivity:.3f}")
print(f"Curve-fit RMSE: {fit_result.rmse:.3e} μF/cm²")For a complete runnable example, see examples/fitting.py.
The CDLModel provides the analytical composite diffuse layer approximation: below the steric threshold it follows the Gouy-Chapman branch, and above the threshold it uses a capped counterion layer matched to a diffuse tail.
from pyedl import CDLModel
cdl_model = CDLModel(system)
phi = 1.0
print(cdl_model.get_steric_layer_thickness(phi))
print(cdl_model.charge_density(phi))
print(cdl_model.analytical_capacitance(phi))
print(cdl_model.get_total_energy(phi))For a complete comparison against the Carnahan-Starling model, see examples/cdl.py.
For a double-electrode cell, provide the full-cell voltage. The left and right electrode potentials are computed automatically from charge neutrality, so they should not be supplied by the user.
from pyedl import CDLModel, DoubleElectrodeCell
single_interface_model = CDLModel(system)
cell = DoubleElectrodeCell(single_interface_model)
cell_voltage = 1.0
split = cell.get_potential_split(cell_voltage)
print(split.left_potential, split.right_potential)
print(split.left_charge_density + split.right_charge_density)
print(cell.analytical_capacitance(cell_voltage))
print(cell.get_energy_components(cell_voltage))DoubleElectrodeCell works as a wrapper around single-interface models. It uses the analytical CDL split when available and otherwise solves the charge-neutrality equation directly, which also supports the semianalytical CS/Liu models.
For a complete runnable example, see examples/double_electrode.py.
pyedl.cells: Full-cell wrappers and potential-split helpers.DoubleElectrodeCell: Computes double-electrode voltage splits and combines single-interface observables.PotentialSplit: Dataclass containing left/right electrode potentials and charge-neutrality residuals.
pyedl.models: Core physics implementation.StericModel: Implementation of Carnahan-Starling and Liu models.CDLModel: Composite Diffuse Layer approximation with a capped counterion steric layer.ElectrochemicalSystem: Container for system properties.
pyedl.materials: Chemical property definitions.Ion: Properties like radius, charge, polarizability.Solvent: Dielectric constant, polarizability.
pyedl.fitting: Inversion helpers for fitting counterion permittivities to capacitance curves.pyedl.plotting: Sampling and plotting helpers for capacitance, energy components, and steric-layer profiles.pyedl.utils: Data export helpers and compatibility wrappers for older plotting imports.
If you use this code in your research, please cite the following paper:
@article{TADESSE2025146266,
title = {Semianalytical approximation of Ion Adsorption Layers and Capacitance in Carnahan–Starling-like steric models},
journal = {Electrochimica Acta},
volume = {531},
pages = {146266},
year = {2025},
issn = {0013-4686},
doi = {10.1016/j.electacta.2025.146266},
url = {https://www.sciencedirect.com/science/article/pii/S0013468625006279},
author = {Dagmawi B. Tadesse and Drew F. Parsons},
keywords = {Carnahan–Starling equation, Steric forces, Semianalytical Carnahan–Starling approximations, Electric double layers, Electric double layer capacitors},
abstract = {The Carnahan–Starling (CS) steric model is the best description of hard-sphere fluids within the mean-field theory. Here we introduce an approximation of the near-linear adsorption concentration profile of a counterion near an electrode for a CS model and derive the subsequent electric field and electrostatic potential profile in a double layer. This enables the derivation of a semianalytical approximation of the electrode charge density, differential capacitance, and total energies (grand potentials) of an electric double-layer capacitor. These semianalytical equations are valid for electrode potentials between 0.2–4 V and converge to the full numerical solutions of the CS model at high potentials of 1V and bulk concentration of 1M with relative errors less than 2% for the electrode charge densities, and less than 5% for the capacitance and total energies. We find the steric contribution comprises approximately one-quarter of the total energy at high electrode potentials, while the contribution from ideal ion entropies becomes insignificant. The model shows very good agreement with experimental measurements of an aqueous electrolyte, and good agreement at high potentials with computer simulations of an ionic liquid. These semianalytical approximations are effective for applications with concentrated solutions or ionic liquids at high applied voltages where the full numerical solution is computationally expensive or in some cases impossible.}
}MIT License