Getting Started¶
Prerequisites¶
- Python >= 3.10
- Rust toolchain (for building from source)
- maturin (
uv tool install maturin)
Installation¶
equiconc is a Rust library with Python bindings built via maturin. Install from source:
git clone <repo-url>
cd equiconc
python -m venv .venv
source .venv/bin/activate
maturin develop --release
Or with uv:
Quick start¶
Simple dimerization¶
Compute equilibrium concentrations for A + B ⇌ AB:
import equiconc
eq = (
equiconc.System()
.monomer("A", 100e-9) # 100 nM total
.monomer("B", 100e-9) # 100 nM total
.complex("AB", [("A", 1), ("B", 1)], dg_st=-10.0)
.equilibrium()
)
for name, conc in eq.items():
print(f"{name}: {conc:.3e} M")
Temperature¶
The default temperature is 25 °C. Set it explicitly in Celsius or Kelvin:
sys = equiconc.System() # 25 C (default)
sys = equiconc.System(temperature_C=37) # 37 C
sys = equiconc.System(temperature_K=310.15) # 310.15 K = 37 C
Energy specifications¶
You can specify complex energies in three ways:
Standard free energy (\(\Delta G^\circ\) in kcal/mol):
Dimensionless free energy (\(\Delta G / RT\), unitless). When all complexes use this form, temperature is not required:
Enthalpy and entropy (\(\Delta H\) in kcal/mol, \(\Delta S\) in kcal/(mol·K)). The free energy \(\Delta G = \Delta H - T \Delta S\) is computed at solve time, so changing temperature shifts the equilibrium:
Builder pattern¶
The API uses a fluent builder pattern. Chain calls to monomer() and
complex(), then call equilibrium() to solve:
sys = equiconc.System(temperature_C=37)
sys = sys.monomer("A", 1e-6)
sys = sys.monomer("B", 1e-6)
sys = sys.complex("AB", [("A", 1), ("B", 1)], dg_st=-10.0)
eq = sys.equilibrium()
Working with results¶
The Equilibrium object supports dict-like access:
# Bracket access
conc_ab = eq["AB"]
# Membership test
assert "AB" in eq
# Iteration
for name in eq:
print(name, eq[name])
# Convert to dict
d = eq.to_dict()
# Properties
eq.monomer_names # ["A", "B"]
eq.complex_names # ["AB"]
eq.free_monomer_concentrations # [float, float]
eq.complex_concentrations # [float]
Multi-complex systems¶
Define as many complexes as needed. The solver scales with the number of monomer species (not complexes):
eq = (
equiconc.System()
.monomer("A", 1e-6)
.monomer("B", 1e-6)
.monomer("C", 1e-6)
.complex("AB", [("A", 1), ("B", 1)], dg_st=-10.0)
.complex("AC", [("A", 1), ("C", 1)], dg_st=-8.0)
.complex("ABC", [("A", 1), ("B", 1), ("C", 1)], dg_st=-15.0)
.equilibrium()
)
Conventions¶
- Concentrations are in molar (mol/L).
- Temperature defaults to 25 °C (298.15 K). Specify via
temperature_C(Celsius) ortemperature_K(Kelvin). - Free energies (
dg_st) are in kcal/mol with a 1 M standard state (\(u_0 = 1 \text{M}\)). - Enthalpy (
dh_st) is in kcal/mol; entropy (ds_st) is in kcal/(mol·K). - Dimensionless energies (
delta_g_over_rt) are unitless \(\Delta G / RT\) values.
Symmetry corrections
Homodimer and higher homo-oligomer symmetry corrections are not applied automatically: equiconc has no way of knowing whether the complex is actually symmetric. If your complex contains identical strands, include the symmetry correction in the \(\Delta G^\circ\) value you provide (e.g., add \(+RT \ln \sigma\) where \(\sigma\) is the symmetry number).
Error handling¶
Invalid inputs raise ValueError:
import equiconc
# No monomers
try:
equiconc.System().equilibrium()
except ValueError as e:
print(e) # "system has no monomers"
# Negative concentration
try:
equiconc.System().monomer("A", -1e-9).equilibrium()
except ValueError as e:
print(e) # "invalid concentration: -0.000000001 ..."
# Missing energy specification
try:
equiconc.System().monomer("A", 1e-9).complex("AB", [("A", 1)])
except ValueError as e:
print(e) # "must specify energy: dg_st, delta_g_over_rt, ..."