Domain: Chemistry — Physical Chemistry

Problem:
Write the code to compute \([\mathrm{CO_3^{2-}}]\) in mM, rounded to three decimals.

Given (25 °C, aqueous, density ≈ 1 kg L\(^{-1}\)):

* Open gas: \(p_{\mathrm{CO_2}}=0.050\) atm.
* Background electrolyte: NaCl = 0.700 M (fully dissociated; contributes to ionic strength).
* Totals: \(C_{\mathrm N}=[\mathrm{NH_3}]+[\mathrm{NH_4^+}]=0.00500\) M; \(C_{\mathrm B}=[\mathrm{B(OH)_3}]+[\mathrm{B(OH)_4^-}]=0.0100\) M.

Nonideality (activities):

* Davies (25 °C, \(A=0.509)\): \(\displaystyle \log_{10}\gamma_i=-A z_i^2\left(\frac{\sqrt I}{1+\sqrt I}-0.3I\right)\); neutrals: \(\gamma=1\).
* Ionic strength: \(\displaystyle I=\tfrac12\left(0.700+0.700+[\mathrm{H^+}]+[\mathrm{OH^-}]+[\mathrm{HCO_3^-}]+4[\mathrm{CO_3^{2-}}]+[\mathrm{NH_4^+}]+[\mathrm{B(OH)_4^-}]\right)\).

CO₂ dissolution (Henry + salting-out):

* \([\mathrm{H_2CO_3^*}]=K_H^{\mathrm{eff}},p_{\mathrm{CO_2}},\quad K_H^{\mathrm{eff}}=K_H^0,10^{-k_s[{\rm NaCl}]}\).
* Constants: \(K_H^0=0.0339\ \mathrm{M\ atm^{-1}},\ k_s=0.12\ \mathrm{L\ mol^{-1}}\).

Equilibria (activity-based constants at infinite dilution):

* Carbonic: \(K_1=10^{-6.35},\ K_2=10^{-10.33}\).
* Ammonium: \(K_{a,\mathrm{NH_4^+}}=10^{-9.25}\).
* Borate: \(K_{a,\mathrm B}=10^{-9.24}\).
* Water: \(K_w=10^{-14.00}\).

Balances/constraints (use activities \(a_i=\gamma_i[i])\):

* Mass: \([\mathrm{NH_3}]+[\mathrm{NH_4^+}]=C_{\mathrm N}), ([\mathrm{B(OH)_3}]+[\mathrm{B(OH)*4^-}]=C*{\mathrm B}\).
* Charge: \([\mathrm{H^+}]+[\mathrm{NH_4^+}]=[\mathrm{OH^-}]+[\mathrm{HCO_3^-}]+2[\mathrm{CO_3^{2-}}]+[\mathrm{B(OH)_4^-}]\).

Answer: 0.003

Solution code:
import math

def solution():
    # ---- Constants (25 °C) ----
    pCO2 = 0.050  # atm
    KH0 = 0.0339  # M/atm
    ks = 0.12     # L/mol
    NaCl = 0.700  # M (fully dissociated)
    KH_eff = KH0 * (10 ** (-ks * NaCl))
    H2CO3_star = KH_eff * pCO2  # M (neutral)

    # Totals (M)
    C_N = 0.00500  # NH3 + NH4+
    C_B = 0.0100   # B(OH)3 + B(OH)4-

    # Equilibrium constants (infinite-dilution)
    K1  = 10 ** (-6.35)
    K2  = 10 ** (-10.33)
    KaN = 10 ** (-9.25)
    KaB = 10 ** (-9.24)
    Kw  = 10 ** (-14.00)

    # Davies
    A_davies = 0.509
    I_CAP, MIN_I = 3.0, 1e-12          # cap I for stability (Davies validity)
    MAX_CONC = 10.0                    # physical upper cap (M) to avoid blow-ups

    # Precompute logs
    log10_K1H2   = math.log10(K1 * H2CO3_star)
    log10_K1K2H2 = math.log10(K1 * K2 * H2CO3_star)
    log10_Kw     = math.log10(Kw)
    log10_MAXC   = math.log10(MAX_CONC)

    def pow10_clipped(x, cap=MAX_CONC):
        if x > log10_MAXC:
            return cap
        if x < -308:                   # avoid subnormal underflow
            return 0.0
        return 10 ** x

    def gamma_log10_from_I(I):
        I = max(min(I, I_CAP), MIN_I)
        sI = math.sqrt(I)
        bracket = (sI / (1.0 + sI)) - 0.3 * I
        logg = {}
        for name, z in (("H+", +1), ("OH-", -1), ("HCO3-", -1), ("CO3--", -2), ("NH4+", +1), ("B(OH)4-", -1)):
            logg[name] = -A_davies * (z*z) * bracket  # log10(gamma)
        return logg

    def species_given_pH(pH):
        H = 10 ** (-pH)
        I = 0.5 * (NaCl + NaCl)  # start from NaCl only (1.4/2 = 0.7)
        for _ in range(200):
            logg = gamma_log10_from_I(I)
            log10_H = -pH
            log10_gamma_H = logg["H+"]

            # Carbonate species (log domain)
            log10_HCO3 = log10_K1H2   - log10_H - (log10_gamma_H + logg["HCO3-"])
            HCO3 = pow10_clipped(log10_HCO3)

            log10_CO3  = log10_K1K2H2 - 2*log10_H - (2*log10_gamma_H + logg["CO3--"])
            CO3 = pow10_clipped(log10_CO3)

            # Water
            log10_OH = log10_Kw - (logg["H+"] + logg["OH-"]) - log10_H
            OH = pow10_clipped(log10_OH)

            # Ammonia buffer (bounded by total)
            denom_N = 1.0 + KaN * (10 ** (logg["NH4+"] - logg["H+"] - log10_H))
            NH4 = C_N / denom_N
            NH4 = min(max(NH4, 0.0), C_N)

            # Borate buffer (bounded by total)
            denom_B = 1.0 + (10 ** (logg["H+"] + logg["B(OH)4-"] + log10_H)) / KaB
            B4 = C_B / denom_B
            B4 = min(max(B4, 0.0), C_B)

            # Ionic strength update (cap for Davies)
            I_new = 0.5 * (NaCl + NaCl + H + OH + HCO3 + 4.0*CO3 + NH4 + B4)
            I_new = min(max(I_new, MIN_I), I_CAP)

            if abs(I_new - I) < 1e-10:
                I = I_new
                break
            I = 0.5 * (I + I_new)

        return {"H+": H, "OH-": OH, "HCO3-": HCO3, "CO3--": CO3, "NH4+": NH4, "B(OH)4-": B4}, I

    def charge_balance(pH):
        sp, _ = species_given_pH(pH)
        return (sp["H+"] + sp["NH4+"]) - (sp["OH-"] + sp["HCO3-"] + 2.0*sp["CO3--"] + sp["B(OH)4-"])

    # ---- pH bracketing (safe range) ----
    grid = [4.0 + 0.25*i for i in range(int((11.5-4.0)/0.25)+1)]
    vals = [charge_balance(pH) for pH in grid]
    lo = hi = None
    for i in range(len(grid)-1):
        if vals[i] == 0.0:
            lo = hi = grid[i]
            break
        if vals[i] * vals[i+1] < 0:
            lo, hi = grid[i], grid[i+1]
            break
    if lo is None:
        j = min(range(len(vals)), key=lambda k: abs(vals[k]))
        lo = max(4.0, grid[max(0, j-1)])
        hi = min(11.5, grid[min(len(grid)-1, j+1)])
        if lo >= hi:
            lo, hi = 6.0, 9.0

    f_lo = charge_balance(lo)
    f_hi = charge_balance(hi)
    if f_lo * f_hi > 0:
        lo, hi = 5.0, 10.5
        f_lo = charge_balance(lo)
        f_hi = charge_balance(hi)

    # ---- Bisection ----
    for _ in range(200):
        mid = 0.5 * (lo + hi)
        f_mid = charge_balance(mid)
        if abs(f_mid) < 1e-14 or (hi - lo) < 1e-9:
            pH_star = mid
            break
        if f_lo * f_mid <= 0:
            hi, f_hi = mid, f_mid
        else:
            lo, f_lo = mid, f_mid
    else:
        pH_star = 0.5 * (lo + hi)

    sp_final, _ = species_given_pH(pH_star)
    CO3_mM = sp_final["CO3--"] * 1000.0
    return float(round(CO3_mM, 3))

print (solution())