def calc_B_species(pHtot=None, BT=None, BO3=None, BO4=None, Ks=None): # B system calculations if pHtot is not None and BT is not None: H = ch(pHtot) elif BT is not None and BO3 is not None: H = BT_BO3(BT, BO3, Ks) elif BT is not None and BO4 is not None: H = BT_BO4(BT, BO4, Ks) elif BO3 is not None and BO4 is not None: BT = BO3 + BO3 H = BT_BO3(BT, BO3, Ks) elif pHtot is not None and BO3 is not None: H = ch(pHtot) BT = pH_BO3(pHtot, BO3, Ks) elif pHtot is not None and BO4 is not None: H = ch(pHtot) BT = pH_BO4(pHtot, BO4, Ks) # The above makes sure that BT and H are known, # this next bit calculates all the missing species # from BT and H. if BO3 is None: BO3 = cBO3(BT, H, Ks) if BO4 is None: BO4 = cBO4(BT, H, Ks) if pHtot is None: pHtot = np.array(cp(H), ndmin=1) return Bunch({'pHtot': pHtot, 'H': H, 'BT': BT, 'BO3': BO3, 'BO4': BO4})
def get_Ks(ps): """ Helper function to calculate Ks. If ps.Ks is a dict, those Ks are used transparrently, with no pressure modification. """ if isinstance(ps.Ks, dict): Ks = Bunch(ps.Ks) else: if maxL(ps.Mg, ps.Ca) == 1: if ps.Mg is None: ps.Mg = 0.0528171 if ps.Ca is None: ps.Ca = 0.0102821 Ks = MyAMI_K_calc(TempC=ps.T, Sal=ps.S, Mg=ps.Mg, Ca=ps.Ca, P=ps.P) else: # if only Ca or Mg provided, fill in other with modern if ps.Mg is None: ps.Mg = 0.0528171 if ps.Ca is None: ps.Ca = 0.0102821 # calculate Ca and Mg specific Ks Ks = MyAMI_K_calc_multi(TempC=ps.T, Sal=ps.S, Ca=ps.Ca, Mg=ps.Mg, P=ps.P) # non-MyAMI Constants Ks.update(calc_KPs(ps.T, ps.S, ps.P)) Ks.update(calc_KF(ps.T, ps.S, ps.P)) Ks.update(calc_KSi(ps.T, ps.S, ps.P)) # pH conversions to total scale. # - KPn are all on SWS # - KSi is on SWS # - MyAMI KW is on SWS... DOES THIS MATTER? SWStoTOT = (1 + ps.TS / Ks.KSO4) / (1 + ps.TS / Ks.KSO4 + ps.TF / Ks.KF) # FREEtoTOT = 1 + ps.TS / Ks.KSO4 conv = ['KP1', 'KP2', 'KP3', 'KSi', 'KW'] for c in conv: Ks[c] *= SWStoTOT return Ks
def calc_Ks(T, S, P, Mg, Ca, TS, TF, Ks=None): """ Helper function to calculate Ks. If Ks is a dict, those Ks are used transparrently (i.e. no pressure modification). """ if isinstance(Ks, dict): Ks = Bunch(Ks) else: if maxL(Mg, Ca) == 1: if Mg is None: Mg = 0.0528171 if Ca is None: Ca = 0.0102821 Ks = MyAMI_K_calc(TempC=T, Sal=S, P=P, Mg=Mg, Ca=Ca) else: # if only Ca or Mg provided, fill in other with modern if Mg is None: Mg = 0.0528171 if Ca is None: Ca = 0.0102821 # calculate Ca and Mg specific Ks Ks = MyAMI_K_calc_multi(TempC=T, Sal=S, P=P, Ca=Ca, Mg=Mg) # non-MyAMI Constants Ks.update(calc_KPs(T, S, P)) Ks.update(calc_KF(T, S, P)) Ks.update(calc_KSi(T, S, P)) # pH conversions to total scale. # - KP1, KP2, KP3 are all on SWS # - KSi is on SWS # - MyAMI KW is on SWS... DOES THIS MATTER? SWStoTOT = (1 + TS / Ks.KSO4) / (1 + TS / Ks.KSO4 + TF / Ks.KF) # FREEtoTOT = 1 + 'T_' + mode]S / Ks.KSO4 conv = ["KP1", "KP2", "KP3", "KSi", "KW"] for c in conv: Ks[c] *= SWStoTOT return Ks
def test_Boron_Fns(self): ref = Bunch({ "ABO3": 0.80882931, "ABO4": 0.80463763, "ABT": 0.80781778, "BO3": 328.50895695, "BO4": 104.49104305, "BT": 433.0, "Ca": 0.0102821, "H": 7.94328235e-09, "Ks": { "K0": 0.02839188, "K1": 1.42182814e-06, "K2": 1.08155475e-09, "KB": 2.52657299e-09, "KSO4": 0.10030207, "KW": 6.06386369e-14, "KspA": 6.48175907e-07, "KspC": 4.27235093e-07, }, "Mg": 0.0528171, "S": 35.0, "T": 25.0, "alphaB": 1.02725, "dBO3": 46.30877684, "dBO4": 18.55320208, "dBT": 39.5, "pHtot": 8.1, }) Ks = Bunch(ref.Ks) # Speciation self.assertAlmostEqual(bf.BT_BO3(ref.BT, ref.BO3, Ks), ref.H, msg="BT_BO3", places=6) self.assertAlmostEqual(bf.BT_BO4(ref.BT, ref.BO4, Ks), ref.H, msg="BT_BO4", places=6) self.assertAlmostEqual(bf.pH_BO3(ref.pHtot, ref.BO3, Ks), ref.BT, msg="pH_BO3", places=6) self.assertAlmostEqual(bf.pH_BO4(ref.pHtot, ref.BO4, Ks), ref.BT, msg="pH_BO4", places=6) self.assertAlmostEqual(bf.cBO3(ref.BT, ref.H, Ks), ref.BO3, msg="cBO3", places=6) self.assertAlmostEqual(bf.cBO4(ref.BT, ref.H, Ks), ref.BO4, msg="cBO4", places=6) self.assertEqual(bf.chiB_calc(ref.H, Ks), 1 / (1 + Ks.KB / ref.H), msg="chiB_calc") # Isotopes self.assertEqual(bf.alphaB_calc(ref.T), 1.0293 - 0.000082 * ref.T, msg="alphaB_calc") self.assertAlmostEqual( bf.pH_ABO3(ref.pHtot, ref.ABO3, Ks, ref.alphaB), ref.ABT, msg="pH_ABO3", places=6, ) self.assertAlmostEqual( bf.pH_ABO4(ref.pHtot, ref.ABO4, Ks, ref.alphaB), ref.ABT, msg="pH_ABO4", places=6, ) self.assertAlmostEqual(bf.cABO3(ref.H, ref.ABT, Ks, ref.alphaB), ref.ABO3, msg="cABO3", places=6) self.assertAlmostEqual(bf.cABO4(ref.H, ref.ABT, Ks, ref.alphaB), ref.ABO4, msg="cABO4", places=6) # Isotope unit conversions self.assertAlmostEqual(bf.A11_2_d11(0.807817779214075), 39.5, msg="A11_2_d11", places=6) self.assertAlmostEqual(bf.d11_2_A11(39.5), 0.807817779214075, msg="d11_2_A11", places=6) return
def test_Carbon_Fns(self): ref = Bunch({ "BAlk": 104.39451552567037, "BT": 432.6, "CAlk": 2340.16132518, "CO2": 10.27549047, "CO3": 250.43681565, "Ca": 0.0102821, "DIC": 2100.0, "H": 7.94328235e-09, "HCO3": 1839.28769389, "HF": 0.00017903616862286758, "HSO4": 0.0017448760289520083, "Hfree": 0.0061984062104620289, "Ks": { "K0": 0.028391881804015699, "K1": 1.4218281371391736e-06, "K2": 1.0815547472209423e-09, "KB": 2.5265729902477677e-09, "KF": 0.0023655007956108367, "KP1": 0.024265183950721327, "KP2": 1.0841036169428488e-06, "KP3": 1.612502080867568e-09, "KSO4": 0.10030207107256615, "KSi": 4.1025099579058308e-10, "KW": 6.019824161802715e-14, "KspA": 6.4817590680119676e-07, "KspC": 4.2723509278625912e-07, }, "Mg": 0.0528171, "OH": 7.57850961, "P": None, "PAlk": 0.0, "S": 35.0, "SiAlk": 0.0, "T": 25.0, "TA": 2452.126228, "TF": 6.832583968836728e-05, "TP": 0.0, "TS": 0.028235434132860126, "TSi": 0.0, "fCO2": 361.91649919340324, "pCO2": 363.074540437976, "pHtot": 8.1, "unit": 1000000.0, }) Ks = Bunch(ref.Ks) self.assertAlmostEqual(cf.CO2_pH(ref.CO2, ref.pHtot, Ks), ref.DIC, msg="CO2_pH", places=6) self.assertAlmostEqual(cf.CO2_HCO3(ref.CO2, ref.HCO3, Ks)[0], ref.H, msg="CO2_HCO3 (zf)", places=6) self.assertAlmostEqual(cf.CO2_CO3(ref.CO2, ref.CO3, Ks)[0], ref.H, msg="CO2_CO3 (zf)", places=6) self.assertAlmostEqual( cf.CO2_TA( CO2=ref.CO2 / ref.unit, TA=ref.TA / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks, )[0], ref.pHtot, msg="CO2_TA", places=6, ) self.assertAlmostEqual(cf.CO2_DIC(ref.CO2, ref.DIC, Ks)[0], ref.H, msg="CO2_DIC (zf)", places=6) self.assertAlmostEqual(cf.pH_HCO3(ref.pHtot, ref.HCO3, Ks), ref.DIC, msg="pH_HCO3", places=6) self.assertAlmostEqual(cf.pH_CO3(ref.pHtot, ref.CO3, Ks), ref.DIC, msg="pH_CO3", places=6) self.assertAlmostEqual( cf.pH_TA( pH=ref.pHtot, TA=ref.TA / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks, ) * ref.unit, ref.DIC, msg="pH_TA", places=6, ) self.assertAlmostEqual(cf.pH_DIC(ref.pHtot, ref.DIC, Ks), ref.CO2, msg="pH_DIC", places=6) self.assertAlmostEqual(cf.HCO3_CO3(ref.HCO3, ref.CO3, Ks)[0], ref.H, msg="HCO3_CO3 (zf)", places=6) self.assertAlmostEqual( cf.HCO3_TA(ref.HCO3 / ref.unit, ref.TA / ref.unit, ref.BT / ref.unit, Ks)[0], ref.H, msg="HCO3_TA (zf)", places=6, ) self.assertAlmostEqual(cf.HCO3_DIC(ref.HCO3, ref.DIC, Ks)[0], ref.H, msg="HCO3_DIC (zf)", places=6) self.assertAlmostEqual( cf.CO3_TA(ref.CO3 / ref.unit, ref.TA / ref.unit, ref.BT / ref.unit, Ks)[0], ref.H, msg="CO3_TA (zf)", places=6, ) self.assertAlmostEqual(cf.CO3_DIC(ref.CO3, ref.DIC, Ks)[0], ref.H, msg="CO3_DIC (zf)", places=6) self.assertAlmostEqual( cf.TA_DIC( TA=ref.TA / ref.unit, DIC=ref.DIC / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks, )[0], ref.pHtot, msg="TA_DIC", places=6, ) self.assertAlmostEqual(cf.cCO2(ref.H, ref.DIC, Ks), ref.CO2, msg="cCO2", places=6) self.assertAlmostEqual(cf.cCO3(ref.H, ref.DIC, Ks), ref.CO3, msg="cCO3", places=6) self.assertAlmostEqual(cf.cHCO3(ref.H, ref.DIC, Ks), ref.HCO3, msg="cHCO3", places=6) (TA, CAlk, BAlk, PAlk, SiAlk, OH, Hfree, HSO4, HF) = cf.cTA( H=ref.H, DIC=ref.DIC / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks, mode="multi", ) self.assertAlmostEqual(TA * ref.unit, ref.TA, msg="cTA - TA", places=6) self.assertAlmostEqual(CAlk * ref.unit, ref.CAlk, msg="cTA - CAlk", places=6) self.assertAlmostEqual(BAlk * ref.unit, ref.BAlk, msg="cTA - BAlk", places=6) self.assertAlmostEqual(PAlk * ref.unit, ref.PAlk, msg="cTA - PAlk", places=6) self.assertAlmostEqual(SiAlk * ref.unit, ref.SiAlk, msg="cTA - SiAlk", places=6) self.assertAlmostEqual(OH * ref.unit, ref.OH, msg="cTA - OH", places=6) self.assertAlmostEqual(Hfree * ref.unit, ref.Hfree, msg="cTA - Hfree", places=6) self.assertAlmostEqual(HSO4 * ref.unit, ref.HSO4, msg="cTA - HSO4", places=6) self.assertAlmostEqual(HF * ref.unit, ref.HF, msg="cTA - HF", places=6) self.assertAlmostEqual(cf.fCO2_to_CO2(ref.fCO2, Ks), ref.CO2, msg="fCO2_to_CO2", places=6) self.assertAlmostEqual(cf.CO2_to_fCO2(ref.CO2, Ks), ref.fCO2, msg="CO2_to_fCO2", places=6) self.assertAlmostEqual(cf.fCO2_to_pCO2(ref.fCO2, ref.T), ref.pCO2, msg="fCO2_to_pCO2", places=6) self.assertAlmostEqual(cf.pCO2_to_fCO2(ref.pCO2, ref.T), ref.fCO2, msg="pCO2_to_fCO2", places=6) return
def calc_C_species(pHtot=None, DIC=None, CO2=None, HCO3=None, CO3=None, TA=None, fCO2=None, pCO2=None, T_in=None, BT=None, TP=0, TSi=0, TS=0, TF=0, Ks=None): """ Calculate all carbon species from minimal input. """ # if fCO2 is given but CO2 is not, calculate CO2 if CO2 is None: if fCO2 is not None: CO2 = fCO2_to_CO2(fCO2, Ks) elif pCO2 is not None: CO2 = fCO2_to_CO2(pCO2_to_fCO2(pCO2, T_in), Ks) # Carbon System Calculations (from Zeebe & Wolf-Gladrow, Appendix B) # 1. CO2 and pH if CO2 is not None and pHtot is not None: H = ch(pHtot) DIC = CO2_pH(CO2, pHtot, Ks) # 2. CO2 and HCO3 elif CO2 is not None and HCO3 is not None: H = CO2_HCO3(CO2, HCO3, Ks) DIC = CO2_pH(CO2, cp(H), Ks) # 3. CO2 and CO3 elif CO2 is not None and CO3 is not None: H = CO2_CO3(CO2, CO3, Ks) DIC = CO2_pH(CO2, cp(H), Ks) # 4. CO2 and TA elif CO2 is not None and TA is not None: # unit conversion because OH and H wrapped # up in TA fns - all need to be in same units. pHtot = CO2_TA(CO2=CO2, TA=TA, BT=BT, TP=TP, TSi=TSi, TS=TS, TF=TF, Ks=Ks) H = ch(pHtot) DIC = CO2_pH(CO2, pHtot, Ks) # 5. CO2 and DIC elif CO2 is not None and DIC is not None: H = CO2_DIC(CO2, DIC, Ks) # 6. pHtot and HCO3 elif pHtot is not None and HCO3 is not None: H = ch(pHtot) DIC = pH_HCO3(pHtot, HCO3, Ks) # 7. pHtot and CO3 elif pHtot is not None and CO3 is not None: H = ch(pHtot) DIC = pH_CO3(pHtot, CO3, Ks) # 8. pHtot and TA elif pHtot is not None and TA is not None: H = ch(pHtot) DIC = pH_TA(pH=pHtot, TA=TA, BT=BT, TP=TP, TSi=TSi, TS=TS, TF=TF, Ks=Ks) # 9. pHtot and DIC elif pHtot is not None and DIC is not None: H = ch(pHtot) # 10. HCO3 and CO3 elif HCO3 is not None and CO3 is not None: H = HCO3_CO3(HCO3, CO3, Ks) DIC = pH_CO3(cp(H), CO3, Ks) # 11. HCO3 and TA elif HCO3 is not None and TA is not None: Warning( 'Nutrient alkalinity not implemented for this input combination.\nCalculations use only C and B alkalinity.' ) H = HCO3_TA(HCO3, TA, BT, Ks) DIC = pH_HCO3(cp(H), HCO3, Ks) # 12. HCO3 amd DIC elif HCO3 is not None and DIC is not None: H = HCO3_DIC(HCO3, DIC, Ks) # 13. CO3 and TA elif CO3 is not None and TA is not None: Warning( 'Nutrient alkalinity not implemented for this input combination.\nCalculations use only C and B alkalinity.' ) H = CO3_TA(CO3, TA, BT, Ks) DIC = pH_CO3(cp(H), CO3, Ks) # 14. CO3 and DIC elif CO3 is not None and DIC is not None: H = CO3_DIC(CO3, DIC, Ks) # 15. TA and DIC elif TA is not None and DIC is not None: pHtot = TA_DIC(TA=TA, DIC=DIC, BT=BT, TP=TP, TSi=TSi, TS=TS, TF=TF, Ks=Ks) H = ch(pHtot) # The above makes sure that DIC and H are known, # this next bit calculates all the missing species # from DIC and H. if CO2 is None: CO2 = cCO2(H, DIC, Ks) if fCO2 is None: fCO2 = CO2_to_fCO2(CO2, Ks) if pCO2 is None: pCO2 = fCO2_to_pCO2(fCO2, T_in) if HCO3 is None: HCO3 = cHCO3(H, DIC, Ks) if CO3 is None: CO3 = cCO3(H, DIC, Ks) # Calculate all elements of Alkalinity (TA, CAlk, BAlk, PAlk, SiAlk, OH, Hfree, HSO4, HF) = cTA(H=H, DIC=DIC, BT=BT, TP=TP, TSi=TSi, TS=TS, TF=TF, Ks=Ks, mode='multi') # if pH not calced yet, calculate on all scales. if pHtot is None: pHtot = np.array(cp(H), ndmin=1) return Bunch({ 'pHtot': pHtot, 'TA': TA, 'DIC': DIC, 'CO2': CO2, 'H': H, 'HCO3': HCO3, 'fCO2': fCO2, 'pCO2': pCO2, 'CO3': CO3, 'CAlk': CAlk, 'BAlk': BAlk, 'PAlk': PAlk, 'SiAlk': SiAlk, 'OH': OH, 'Hfree': Hfree, 'HSO4': HSO4, 'HF': HF })
def CBsys( pHtot=None, DIC=None, CO2=None, HCO3=None, CO3=None, TA=None, fCO2=None, pCO2=None, BT=None, BO3=None, BO4=None, ABT=None, ABO3=None, ABO4=None, dBT=None, dBO3=None, dBO4=None, alphaB=None, T_in=25.0, S_in=35.0, P_in=None, T_out=None, S_out=None, P_out=None, Ca=None, Mg=None, TP=0.0, TSi=0.0, TS=None, TF=None, pHsws=None, pHfree=None, pHNBS=None, Ks=None, pdict=None, unit="umol", ): """ Calculate carbon, boron and boron isotope chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468) Appendix B pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Note: Special Case! If pH is not known, you must provide either: - Two of [DIC, CO2, HCO3, CO3], and one of [BT, BO3, BO4] - One of [DIC, CO2, HCO3, CO3], and TA and BT - Two of [BT, BO3, BO4] and one of [DIC, CO2, HCO3, CO3] Isotopes will only be calculated if one of [ABT, ABO3, ABO4, dBT, dBO3, dBO4] is provided. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations, but: **WARNING** Error propagation NOT IMPLEMENTED for carbon system calculations with zero-finders (i.e. when pH is not given; cases 2-5 and 10-15). Concentration Units +++++++++++++++++++ * Ca and Mg must be in molar units. * All other units must be the same, and can be specified in the 'unit' variable. Defaults to umolar. * Isotopes can be in A (11B / BT) or d (delta). Either specified, both returned. Parameters ---------- pH, DIC, CO2, HCO3, CO3, TA : array-like Carbon system parameters. Two of these must be provided. If TA is specified, a B species must also be specified. pH, BT, BO3, BO4 : array-like Boron system parameters. Two of these must be provided. pH, ABT, ABO3, ABO4, dBT, dBO3, dBO4 : array-like Boron isotope system parameters. pH and one other parameter must be provided. alphaB : array-like Alpha value describing B fractionation (1.0XXX). If missing, it's calculated using the temperature sensitive formulation of Honisch et al (2008) T, S : array-like Temperature in Celcius and Salinity in PSU. Used in calculating MyAMI constants. P : array-like Pressure in Bar. Used in calculating MyAMI constants. unit : str Concentration units of C and B parameters (all must be in the same units). Can be 'mol', 'mmol', 'umol', 'nmol', 'pmol' or 'fmol'. Used in calculating Alkalinity. Default is 'umol'. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, * in mol / kg *. Used in calculating MyAMI constants. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # convert unit to multiplier udict = { "mol": 1.0, "mmol": 1.0e3, "umol": 1.0e6, "nmol": 1.0e9, "pmol": 1.0e12, "fmol": 1.0e15, } if isinstance(ps.unit, str): ps.unit = udict[ps.unit] elif isinstance(ps.unit, (int, float)): ps.unit = unit upar = [ "DIC", "CO2", "HCO3", "CO3", "TA", "fCO2", "pCO2", "BT", "BO3", "BO4", "TP", "TSi", ] for p in upar: if ps[p] is not None: ps[p] = np.divide(ps[p], ps.unit) # convert to molar # reassign unit, convert back at end orig_unit = ps.unit ps.unit = 1.0 # Conserved seawater chemistry if ps.TS is None: ps.TS = calc_TS(ps.S_in) if ps.TF is None: ps.TF = calc_TF(ps.S_in) # Calculate Ks ps.Ks = calc_Ks(ps.T_in, ps.S_in, ps.P_in, ps.Mg, ps.Ca, ps.TS, ps.TF, ps.Ks) # Calculate pH scales (does nothing if none pH given) ps.update( calc_pH_scales( ps.pHtot, ps.pHfree, ps.pHsws, ps.pHNBS, ps.TS, ps.TF, ps.T_in + 273.15, ps.S_in, ps.Ks, )) # if fCO2 is given but CO2 is not, calculate CO2 if ps.CO2 is None: if ps.fCO2 is not None: ps.CO2 = fCO2_to_CO2(ps.fCO2, ps.Ks) elif ps.pCO2 is not None: ps.CO2 = fCO2_to_CO2(pCO2_to_fCO2(ps.pCO2, ps.T_in), ps.Ks) # if no B info provided, assume modern conc. nBspec = NnotNone(ps.BT, ps.BO3, ps.BO4) if nBspec == 0: ps.BT = calc_TB(ps.S_in) elif isinstance(BT, (int, float)): ps.BT = ps.BT * ps.S_in / 35.0 # count number of not None C parameters nCspec = NnotNone(ps.DIC, ps.CO2, ps.HCO3, ps.CO3) # used below # if pH is given, it's easy if ps.pHtot is not None or nBspec == 2: ps.update( calc_B_species(pHtot=ps.pHtot, BT=ps.BT, BO3=ps.BO3, BO4=ps.BO4, Ks=ps.Ks)) ps.update( calc_C_species( pHtot=ps.pHtot, DIC=ps.DIC, CO2=ps.CO2, HCO3=ps.HCO3, CO3=ps.CO3, TA=ps.TA, fCO2=ps.fCO2, pCO2=ps.pCO2, T_in=ps.T_in, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, )) # if not, this section works out the order that things should be calculated in. # Special case: if pH is missing, must have: # a) two C or one C and both TA and BT # b) two B (above) # c) one pH-dependent B, one pH-dependent C... But that's cray... # (c not implemented!) elif (nCspec == 2) | ((nCspec == 1) & (NnotNone(ps.TA, ps.BT) == 2)): # case A ps.update( calc_C_species( pHtot=ps.pHtot, DIC=ps.DIC, CO2=ps.CO2, HCO3=ps.HCO3, CO3=ps.CO3, TA=ps.TA, fCO2=ps.fCO2, pCO2=ps.pCO2, T_in=ps.T_in, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, )) ps.update( calc_B_species(pHtot=ps.pHtot, BT=ps.BT, BO3=ps.BO3, BO4=ps.BO4, Ks=ps.Ks)) # elif nBspec == 2: # case B -- moved up # ps.update(calc_B_species(pHtot=ps.pHtot, BT=ps.BT, BO3=ps.BO3, BO4=ps.BO4, Ks=ps.Ks)) # ps.update(calc_C_species(pHtot=ps.pHtot, DIC=ps.DIC, CO2=ps.CO2, # HCO3=ps.HCO3, CO3=ps.CO3, TA=ps.TA, # fCO2=ps.fCO2, pCO2=ps.pCO2, # T_in=ps.T_in, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, # TS=ps.TS, TF=ps.TF, Ks=ps.Ks)) # then C else: # if neither condition is met, throw an error raise ValueError( ("Impossible! You haven't provided enough parameters.\n" + "If you don't know pH, you must provide either:\n" + " - Two of [DIC, CO2, HCO3, CO3], and one of [BT, BO3, BO4]\n" + " - One of [DIC, CO2, HCO3, CO3], and TA and BT\n" + " - Two of [BT, BO3, BO4] and one of [DIC, CO2, HCO3, CO3]")) ps["revelle_factor"] = calc_revelle_factor( TA=ps.TA, DIC=ps.DIC, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, ) # If any isotope parameter specified, calculate the isotope systen. if NnotNone(ps.ABT, ps.ABO3, ps.ABO4, ps.dBT, ps.dBO3, ps.dBO4) != 0: ps.update(ABsys(pdict=ps)) # Calculate Isotopes if ps.dBT is None and ps.dBO3 is None and ps.dBO4 is None: ps.dBT = 0 # if deltas provided, calculate corresponding As if ps.dBT is not None: ps.ABT = d11_2_A11(ps.dBT) if ps.dBO3 is not None: ps.ABO3 = d11_2_A11(ps.dBO3) if ps.dBO4 is not None: ps.ABO4 = d11_2_A11(ps.dBO4) # calculate alpha ps.alphaB = alphaB_calc(ps.T_in) if ps.pHtot is not None and ps.ABT is not None: ps.H = ch(ps.pHtot) elif ps.pHtot is not None and ps.ABO3 is not None: ps.ABT = pH_ABO3(ps.pHtot, ps.ABO3, ps.Ks, ps.alphaB) elif ps.pHtot is not None and ps.ABO4 is not None: ps.ABT = pH_ABO3(ps.pHtot, ps.ABO4, ps.Ks, ps.alphaB) else: raise ValueError("pH must be determined to calculate isotopes.") if ps.ABO3 is None: ps.ABO3 = cABO3(ps.H, ps.ABT, ps.Ks, ps.alphaB) if ps.ABO4 is None: ps.ABO4 = cABO4(ps.H, ps.ABT, ps.Ks, ps.alphaB) if ps.dBT is None: ps.dBT = A11_2_d11(ps.ABT) if ps.dBO3 is None: ps.dBO3 = A11_2_d11(ps.ABO3) if ps.dBO4 is None: ps.dBO4 = A11_2_d11(ps.ABO4) # clean up output outputs = [ "BAlk", "BT", "CAlk", "CO2", "CO3", "DIC", "H", "HCO3", "HF", "HSO4", "Hfree", "Ks", "OH", "PAlk", "SiAlk", "TA", "TF", "TP", "TS", "TSi", "fCO2", "pCO2", "pHfree", "pHsws", "pHtot", "pHNBS", "BO3", "BO4", "ABO3", "ABO4", "dBO3", "dBO4", ] for k in outputs: if not isinstance(ps[k], np.ndarray): # convert all outputs to (min) 1D numpy arrays. ps[k] = np.array(ps[k], ndmin=1) # Handle Units for p in upar + [ "CAlk", "BAlk", "PAlk", "SiAlk", "OH", "HSO4", "HF", "Hfree" ]: ps[p] *= orig_unit # convert back to input units # Recursive approach to calculate output params. # if output conditions specified, calculate outputs. if ps.T_out is not None or ps.S_out is not None or ps.P_out is not None: if ps.T_out is None: ps.T_out = ps.T_in if ps.S_out is None: ps.S_out = ps.S_in if ps.P_out is None: ps.P_out = ps.P_in # assumes conserved alkalinity out_cond = CBsys( TA=ps.TA, DIC=ps.DIC, BT=ps.BT, T_in=ps.T_out, S_in=ps.S_out, P_in=ps.P_out, unit=ps.unit, ) # Calculate pH scales (does nothing if no pH given) out_cond.update( calc_pH_scales( out_cond.pHtot, out_cond.pHfree, out_cond.pHsws, out_cond.pHNBS, out_cond.TS, out_cond.TF, out_cond.T_in + 273.15, out_cond.S_in, out_cond.Ks, )) # rename parameters in output conditions ps.update({k + "_out": out_cond[k] for k in outputs}) # remove some superfluous outputs rem = ["pdict", "unit"] for r in rem: if r in ps: del ps[r] return ps
def ABsys( pHtot=None, ABT=None, ABO3=None, ABO4=None, dBT=None, dBO3=None, dBO4=None, alphaB=None, T_in=25.0, S_in=35.0, P_in=None, Ca=None, Mg=None, TS=None, TF=None, pHsws=None, pHfree=None, pHNBS=None, Ks=None, pdict=None, ): """ Calculate the boron isotope chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468). pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations. Concentration Units +++++++++++++++++++ * 'A' is fractional abundance (11B / BT) * 'd' are delta values Either specified, both returned. Parameters ---------- pH, ABT, ABO3, ABO4, dBT, dBO3, dBO4 : array-like Boron isotope system parameters. pH and one other parameter must be provided. alphaB : array-like Alpha value describing B fractionation (1.0XXX). If missing, it's calculated using the temperature sensitive formulation of Honisch et al (2008) T, S : array-like Temperature in Celcius and Salinity in PSU. Used in calculating MyAMI constants. P : array-like Pressure in Bar. Used in calculating MyAMI constants. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, in mol / kg. Used in calculating MyAMI constants. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # Conserved seawater chemistry if ps.TS is None: ps.TS = calc_TS(ps.S_in) if ps.TF is None: ps.TF = calc_TF(ps.S_in) # Calculate Ks ps.Ks = calc_Ks(ps.T_in, ps.S_in, ps.P_in, ps.Mg, ps.Ca, ps.TS, ps.TF, ps.Ks) # Calculate pH scales (does nothing if none pH given) ps.update( calc_pH_scales( ps.pHtot, ps.pHfree, ps.pHsws, ps.pHNBS, ps.TS, ps.TF, ps.T_in + 273.15, ps.S_in, ps.Ks, )) # if deltas provided, calculate corresponding As if ps.dBT is not None: ps.ABT = d11_2_A11(ps.dBT) if ps.dBO3 is not None: ps.ABO3 = d11_2_A11(ps.dBO3) if ps.dBO4 is not None: ps.ABO4 = d11_2_A11(ps.dBO4) # calculate alpha ps.alphaB = alphaB_calc(ps.T_in) if ps.pHtot is not None and ps.ABT is not None: ps.H = ch(ps.pHtot) elif ps.pHtot is not None and ps.ABO3 is not None: ps.ABT = pH_ABO3(ps.pHtot, ps.ABO3, ps.Ks, ps.alphaB) elif ps.pHtot is not None and ps.ABO4 is not None: ps.ABT = pH_ABO3(ps.pHtot, ps.ABO4, ps.Ks, ps.alphaB) else: raise ValueError("pH must be determined to calculate isotopes.") if ps.ABO3 is None: ps.ABO3 = cABO3(ps.H, ps.ABT, ps.Ks, ps.alphaB) if ps.ABO4 is None: ps.ABO4 = cABO4(ps.H, ps.ABT, ps.Ks, ps.alphaB) if ps.dBT is None: ps.dBT = A11_2_d11(ps.ABT) if ps.dBO3 is None: ps.dBO3 = A11_2_d11(ps.ABO3) if ps.dBO4 is None: ps.dBO4 = A11_2_d11(ps.ABO4) for k in [ "ABO3", "ABO4", "ABT", "Ca", "H", "Mg", "S_in", "T_in", "alphaB", "dBO3", "dBO4", "dBT", "pHtot", ]: if not isinstance(ps[k], np.ndarray): # convert all outputs to (min) 1D numpy arrays. ps[k] = np.array(ps[k], ndmin=1) # remove some superfluous outputs rem = ["pdict"] for r in rem: if r in ps: del ps[r] return ps
def Bsys( pHtot=None, BT=None, BO3=None, BO4=None, ABT=None, ABO3=None, ABO4=None, dBT=None, dBO3=None, dBO4=None, alphaB=None, T_in=25.0, S_in=35.0, P_in=None, T_out=None, S_out=None, P_out=None, Ca=None, Mg=None, TS=None, TF=None, pHsws=None, pHfree=None, pHNBS=None, Ks=None, pdict=None, ): """ Calculate the boron chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468). pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations. Concentration Units +++++++++++++++++++ * All concentrations must be in the same units. Returned in the same units as inputs. Parameters ---------- pH, BT, BO3, BO4 : array-like Boron system parameters. Two of these must be provided. dBT, dBO3, dBO4, ABT, ABO3, ABO4 : array-like delta (d) or fractional abundance (A) values for the Boron isotope system. One of these must be provided. alphaB : array-like The alpha value for BO3-BO4 isotope fractionation. T, S : array-like Temperature in Celcius and Salinity in PSU. Used in calculating MyAMI constants. P : array-like Pressure in Bar. Used in calculating MyAMI constants. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, in mol / kg. Used in calculating MyAMI constants. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # input checks if NnotNone(BT, BO3, BO4) < 1: raise ValueError("Must provide at least one of BT, BO3 or BO4") if NnotNone(dBT, dBO3, dBO4, ABT, ABO3, ABO4) < 1: raise ValueError( "Must provide one of dBT, dBO3, dBO4, ABT, ABO3 or ABO4") # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # Conserved seawater chemistry if ps.TS is None: ps.TS = calc_TS(ps.S_in) if ps.TF is None: ps.TF = calc_TF(ps.S_in) # Calculate Ks ps.Ks = calc_Ks(ps.T_in, ps.S_in, ps.P_in, ps.Mg, ps.Ca, ps.TS, ps.TF, ps.Ks) # Calculate pH scales (does nothing if none pH given) ps.update( calc_pH_scales( ps.pHtot, ps.pHfree, ps.pHsws, ps.pHNBS, ps.TS, ps.TF, ps.T_in + 273.15, ps.S_in, ps.Ks, )) ps.update( calc_B_species(pHtot=ps.pHtot, BT=ps.BT, BO3=ps.BO3, BO4=ps.BO4, Ks=ps.Ks)) # If pH not calced yet, calculate on all scales if ps.pHtot is None: ps.pHtot = np.array(cp(ps.H), ndmin=1) # Calculate other pH scales ps.update( calc_pH_scales( ps.pHtot, ps.pHfree, ps.pHsws, ps.pHNBS, ps.TS, ps.TF, ps.T_in + 273.15, ps.S_in, ps.Ks, )) # If any isotope parameter specified, calculate the isotope systen. if NnotNone(ps.ABT, ps.ABO3, ps.ABO4, ps.dBT, ps.dBO3, ps.dBO4) != 0: ps.update(ABsys(pdict=ps)) for k in ["BT", "H", "BO3", "BO4", "Ca", "Mg", "S_in", "T_in"]: # convert all outputs to (min) 1D numpy arrays. if not isinstance(ps[k], np.ndarray): # convert all outputs to (min) 1D numpy arrays. ps[k] = np.array(ps[k], ndmin=1) # remove some superfluous outputs rem = ["pdict"] for r in rem: if r in ps: del ps[r] return ps
def Csys( pHtot=None, DIC=None, CO2=None, HCO3=None, CO3=None, TA=None, fCO2=None, pCO2=None, BT=None, Ca=None, Mg=None, T_in=25.0, S_in=35.0, P_in=None, T_out=None, S_out=None, P_out=None, TP=0.0, TSi=0.0, TS=None, TF=None, pHsws=None, pHfree=None, pHNBS=None, Ks=None, pdict=None, unit="umol", ): """ Calculate the carbon chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468) Appendix B pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations, but: **WARNING** Error propagation NOT IMPLEMENTED for carbon system calculations with zero-finders (i.e. when pH is not given; cases 2-5 and 10-15). Concentration Units +++++++++++++++++++ * Ca and Mg must be in molar units. * All other units must be the same, and can be specified in the 'unit' variable. Defaults to umolar. Parameters ---------- pH, DIC, CO2, HCO3, CO3, TA : array-like Carbon system parameters. Two of these must be provided. BT : array-like Total B at Salinity = 35, used in Alkalinity calculations. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, in mol / kg. Used in calculating MyAMI constants. T_in, S_in : array-like Temperature in Celcius and Salinity in PSU that the measurements were conducted under. Used in calculating constants. P_in : array-like Pressure in Bar that the measurements were conducted under. Used in pressure-correcting constants. T_out, S_out : array-like Temperature in Celcius and Salinity in PSU of the desired output conditions. Used in calculating constants. P_in : array-like Pressure in Bar of the desired output conditions. Used in pressure-correcting constants. unit : str Concentration units of C and B parameters (all must be in the same units). Can be 'mol', 'mmol', 'umol', 'nmol', 'pmol' or 'fmol'. Used in calculating Alkalinity. Default is 'umol'. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # convert unit to multiplier udict = { "mol": 1.0, "mmol": 1.0e3, "umol": 1.0e6, "nmol": 1.0e9, "pmol": 1.0e12, "fmol": 1.0e15, } if isinstance(ps.unit, str): ps.unit = udict[ps.unit] # elif isinstance(ps.unit, (int, float)): # ps.unit = ps.unit if ps.unit != 1: upar = [ "DIC", "TA", "CO2", "HCO3", "CO3", "BT", "fCO2", "pCO2", "TP", "TSi" ] for p in upar: if ps[p] is not None: ps[p] = np.divide(ps[p], ps.unit) # convert to molar # Conserved seawater chemistry if ps.TS is None: ps.TS = calc_TS(ps.S_in) if ps.TF is None: ps.TF = calc_TF(ps.S_in) if ps.BT is None: ps.BT = calc_TB(ps.S_in) # elif isinstance(BT, (int, float)): # ps.BT = ps.BT * ps.S_in / 35. # Calculate Ks at input conditions ps.Ks = calc_Ks(ps.T_in, ps.S_in, ps.P_in, ps.Mg, ps.Ca, ps.TS, ps.TF, ps.Ks) # Calculate pH scales at input conditions (does nothing if no pH given) ps.update( calc_pH_scales( ps.pHtot, ps.pHfree, ps.pHsws, ps.pHNBS, ps.TS, ps.TF, ps.T_in + 273.15, ps.S_in, ps.Ks, )) # calculate C system at input conditions ps.update( calc_C_species( pHtot=ps.pHtot, DIC=ps.DIC, CO2=ps.CO2, HCO3=ps.HCO3, CO3=ps.CO3, TA=ps.TA, fCO2=ps.fCO2, pCO2=ps.pCO2, T_in=ps.T_in, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, )) ps["revelle_factor"] = calc_revelle_factor( TA=ps.TA, DIC=ps.DIC, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, ) # calculate pHs on all scales, if not done before. if ps.pHNBS is None: # Calculate pH on all scales ps.update( calc_pH_scales( ps.pHtot, ps.pHfree, ps.pHsws, ps.pHNBS, ps.TS, ps.TF, ps.T_in + 273.15, ps.S_in, ps.Ks, )) # clean up output for k in [ "BT", "CO2", "CO3", "Ca", "DIC", "H", "HCO3", "Mg", "S_in", "T_in", "TA", "CAlk", "PAlk", "SiAlk", "OH", ]: if not isinstance(ps[k], np.ndarray): # convert all outputs to (min) 1D numpy arrays. ps[k] = np.array(ps[k], ndmin=1) if ps.unit != 1: for p in upar + [ "CAlk", "BAlk", "PAlk", "SiAlk", "OH", "HSO4", "HF", "Hfree" ]: ps[p] *= ps.unit # convert back to input units # Calculate Output Conditions # =========================== if ps.T_out is not None or ps.S_out is not None or ps.P_out is not None: if ps.T_out is None: ps.T_out = ps.T_in if ps.S_out is None: ps.S_out = ps.S_in if ps.P_out is None: ps.P_out = ps.P_in # assumes conserved alkalinity and DIC out_cond = Csys( TA=ps.TA, DIC=ps.DIC, T_in=ps.T_out, S_in=ps.S_out, P_in=ps.P_out, unit=ps.unit, ) # Calculate pH scales at output conditions (does nothing if no pH given) out_cond.update( calc_pH_scales( out_cond.pHtot, out_cond.pHfree, out_cond.pHsws, out_cond.pHNBS, out_cond.TS, out_cond.TF, out_cond.T_in + 273.15, out_cond.S_in, out_cond.Ks, )) # rename parameters in output conditions outputs = [ "BAlk", "BT", "CAlk", "CO2", "CO3", "DIC", "H", "HCO3", "HF", "HSO4", "Hfree", "Ks", "OH", "PAlk", "SiAlk", "TA", "TF", "TP", "TS", "TSi", "fCO2", "pCO2", "pHfree", "pHsws", "pHtot", "pHNBS", ] ps.update({k + "_out": out_cond[k] for k in outputs}) # remove some superfluous outputs rem = ["pdict"] for r in rem: if r in ps: del ps[r] return ps
def CBsys(pHtot=None, DIC=None, CO2=None, HCO3=None, CO3=None, TA=None, fCO2=None, pCO2=None, BT=None, BO3=None, BO4=None, ABT=None, ABO3=None, ABO4=None, dBT=None, dBO3=None, dBO4=None, alphaB=None, T=25., S=35., P=None, Ca=None, Mg=None, TP=0., TSi=0., pHsws=None, pHfree=None, Ks=None, pdict=None, unit='umol'): """ Calculate carbon, boron and boron isotope chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468) Appendix B pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Note: Special Case! If pH is not known, you must provide either: - Two of [DIC, CO2, HCO3, CO3], and one of [BT, BO3, BO4] - One of [DIC, CO2, HCO3, CO3], and TA and BT - Two of [BT, BO3, BO4] and one of [DIC, CO2, HCO3, CO3] Isotopes will only be calculated if one of [ABT, ABO3, ABO4, dBT, dBO3, dBO4] is provided. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations, but: **WARNING** Error propagation NOT IMPLEMENTED for carbon system calculations with zero-finders (i.e. when pH is not given; cases 2-5 and 10-15). Concentration Units +++++++++++++++++++ * Ca and Mg must be in molar units. * All other units must be the same, and can be specified in the 'unit' variable. Defaults to umolar. * Isotopes can be in A (11B / BT) or d (delta). Either specified, both returned. Parameters ---------- pH, DIC, CO2, HCO3, CO3, TA : array-like Carbon system parameters. Two of these must be provided. If TA is specified, a B species must also be specified. pH, BT, BO3, BO4 : array-like Boron system parameters. Two of these must be provided. pH, ABT, ABO3, ABO4, dBT, dBO3, dBO4 : array-like Boron isotope system parameters. pH and one other parameter must be provided. alphaB : array-like Alpha value describing B fractionation (1.0XXX). If missing, it's calculated using the temperature sensitive formulation of Honisch et al (2008) T, S : array-like Temperature in Celcius and Salinity in PSU. Used in calculating MyAMI constants. P : array-like Pressure in Bar. Used in calculating MyAMI constants. unit : str Concentration units of C and B parameters (all must be in the same units). Can be 'mol', 'mmol', 'umol', 'nmol', 'pmol' or 'fmol'. Used in calculating Alkalinity. Default is 'umol'. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, * in mol / kg *. Used in calculating MyAMI constants. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # convert unit to multiplier udict = { 'mol': 1., 'mmol': 1.e3, 'umol': 1.e6, 'nmol': 1.e9, 'pmol': 1.e12, 'fmol': 1.e15 } if isinstance(ps.unit, str): ps.unit = udict[ps.unit] elif isinstance(ps.unit, (int, float)): ps.unit = unit upar = [ 'DIC', 'CO2', 'HCO3', 'CO3', 'TA', 'fCO2', 'pCO2', 'BT', 'BO3', 'BO4', 'TP', 'TSi' ] for p in upar: if ps[p] is not None: ps[p] = np.divide(ps[p], ps.unit) # convert to molar # reassign unit, so conversions aren't repeated by Csys orig_unit = ps.unit ps.unit = 1. # determin max lengths kexcl = ['Ks', 'pdict', 'unit'] ks = [k for k in ps.keys() if k not in kexcl] L = maxL(*[ps[k] for k in ks]) # make inputs same length for k in ks: if ps[k] is not None: if isinstance(ps[k], (int, float)): ps[k] = np.full(L, ps[k]) # Conserved seawater chemistry if 'TS' not in ps: ps.TS = calc_TS(ps.S) if 'TF' not in ps: ps.TF = calc_TF(ps.S) # Calculate Ks ps.Ks = get_Ks(ps) # Calculate pH scales (does nothing if none pH given) ps.update( calc_pH_scales(ps.pHtot, ps.pHfree, ps.pHsws, ps.TS, ps.TF, ps.Ks)) # if fCO2 is given but CO2 is not, calculate CO2 if ps.CO2 is None: if ps.fCO2 is not None: ps.CO2 = fCO2_to_CO2(ps.fCO2, ps.Ks) elif ps.pCO2 is not None: ps.CO2 = fCO2_to_CO2(pCO2_to_fCO2(ps.pCO2, ps.T), ps.Ks) # if no B info provided, assume modern conc. nBspec = NnotNone(ps.BT, ps.BO3, ps.BO4) if nBspec == 0: ps.BT = calc_TB(ps.S) elif isinstance(BT, (int, float)): ps.BT = ps.BT * ps.S / 35. # This section works out the order that things should be calculated in. # Special case: if pH is missing, must have: # a) two C # b) two B # c) one pH-dependent B, one pH-dependent C... But that's cray... # (c not implemented!) if ps.pHtot is None: nCspec = NnotNone(ps.DIC, ps.CO2, ps.HCO3, ps.CO3) # a) if there are 2 C species, or one C species and TA and BT if ((nCspec == 2) | ((nCspec == 1) & (NnotNone(ps.TA, ps.BT) == 2))): ps.update(Csys(pdict=ps)) # calculate C first ps.update(Bsys(pdict=ps)) # then B # Note on the peculiar syntax here: # ps is a dict of parameters, where # everything that needs to be calculated # is None. # We give this to the [N]sys function # as pdict, which passes the paramters from THIS # function to [N]sys. # As the output of Csys is also a dict (Bunch) # with exactly the same form as ps, we can then # use the .update attribute of ps to update # all the paramters that were calculated by # Csyst. # Thus, all calculation is incremental, working # with the same parameter set. As dicts are # mutable, this has the added benefit of the # parameters only being stored in memory once. if ps.TA is None: (ps.TA, ps.CAlk, ps.BAlk, ps.PAlk, ps.SiAlk, ps.OH, ps.Hfree, ps.HSO4, ps.HF) = cTA(H=ps.H, DIC=ps.DIC, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, mode='multi') # necessary becayse TA in Csys fails if there's no BT # b) if there are 2 B species elif nBspec == 2: ps.update(Bsys(pdict=ps)) # calculate B first ps.update(Csys(pdict=ps)) # then C else: # if neither condition is met, throw an error raise ValueError(( "Impossible! You haven't provided enough parameters.\n" + "If you don't know pH, you must provide either:\n" + " - Two of [DIC, CO2, HCO3, CO3], and one of [BT, BO3, BO4]\n" + " - One of [DIC, CO2, HCO3, CO3], and TA and BT\n" + " - Two of [BT, BO3, BO4] and one of [DIC, CO2, HCO3, CO3]")) else: # if we DO have pH, it's dead easy! ps.update(Bsys(pdict=ps)) # calculate B first ps.update(Csys(pdict=ps)) # then C for p in upar + [ 'CAlk', 'BAlk', 'PAlk', 'SiAlk', 'OH', 'HSO4', 'HF', 'Hfree' ]: ps[p] *= orig_unit # convert back to input units # remove some superfluous outputs rem = ['pdict', 'unit'] for r in rem: if r in ps: del ps[r] return ps
def Csys(pHtot=None, DIC=None, CO2=None, HCO3=None, CO3=None, TA=None, fCO2=None, pCO2=None, BT=None, Ca=None, Mg=None, T=25., S=35., P=None, TP=0., TSi=0., pHsws=None, pHfree=None, Ks=None, pdict=None, unit='umol'): """ Calculate the carbon chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468) Appendix B pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations, but: **WARNING** Error propagation NOT IMPLEMENTED for carbon system calculations with zero-finders (i.e. when pH is not given; cases 2-5 and 10-15). Concentration Units +++++++++++++++++++ * Ca and Mg must be in molar units. * All other units must be the same, and can be specified in the 'unit' variable. Defaults to umolar. Parameters ---------- pH, DIC, CO2, HCO3, CO3, TA : array-like Carbon system parameters. Two of these must be provided. BT : array-like Total B at Salinity = 35, used in Alkalinity calculations. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, in mol / kg. Used in calculating MyAMI constants. T, S : array-like Temperature in Celcius and Salinity in PSU. Used in calculating MyAMI constants. P : array-like Pressure in Bar. Used in calculating MyAMI constants. unit : str Concentration units of C and B parameters (all must be in the same units). Can be 'mol', 'mmol', 'umol', 'nmol', 'pmol' or 'fmol'. Used in calculating Alkalinity. Default is 'umol'. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # convert unit to multiplier udict = { 'mol': 1., 'mmol': 1.e3, 'umol': 1.e6, 'nmol': 1.e9, 'pmol': 1.e12, 'fmol': 1.e15 } if isinstance(ps.unit, str): ps.unit = udict[ps.unit] # elif isinstance(ps.unit, (int, float)): # ps.unit = ps.unit if ps.unit != 1: upar = [ 'DIC', 'TA', 'CO2', 'HCO3', 'CO3', 'BT', 'fCO2', 'pCO2', 'TP', 'TSi' ] for p in upar: if ps[p] is not None: ps[p] = np.divide(ps[p], ps.unit) # convert to molar # Conserved seawater chemistry if 'TS' not in ps: ps.TS = calc_TS(ps.S) if 'TF' not in ps: ps.TF = calc_TF(ps.S) if ps.BT is None: ps.BT = calc_TB(ps.S) elif isinstance(BT, (int, float)): ps.BT = ps.BT * ps.S / 35. # Calculate Ks ps.Ks = get_Ks(ps) # Calculate pH scales (does nothing if no pH given) ps.update( calc_pH_scales(ps.pHtot, ps.pHfree, ps.pHsws, ps.TS, ps.TF, ps.Ks)) # if fCO2 is given but CO2 is not, calculate CO2 if ps.CO2 is None: if ps.fCO2 is not None: ps.CO2 = fCO2_to_CO2(ps.fCO2, ps.Ks) elif ps.pCO2 is not None: ps.CO2 = fCO2_to_CO2(pCO2_to_fCO2(ps.pCO2, ps.T), ps.Ks) # Carbon System Calculations (from Zeebe & Wolf-Gladrow, Appendix B) # 1. CO2 and pH if ps.CO2 is not None and ps.pHtot is not None: ps.H = ch(ps.pHtot) ps.DIC = CO2_pH(ps.CO2, ps.pHtot, ps.Ks) # 2. ps.CO2 and ps.HCO3 elif ps.CO2 is not None and ps.HCO3 is not None: ps.H = CO2_HCO3(ps.CO2, ps.HCO3, ps.Ks) ps.DIC = CO2_pH(ps.CO2, cp(ps.H), ps.Ks) # 3. ps.CO2 and ps.CO3 elif ps.CO2 is not None and ps.CO3 is not None: ps.H = CO2_CO3(ps.CO2, ps.CO3, ps.Ks) ps.DIC = CO2_pH(ps.CO2, cp(ps.H), ps.Ks) # 4. ps.CO2 and ps.TA elif ps.CO2 is not None and ps.TA is not None: # unit conversion because OH and H wrapped # up in TA fns - all need to be in same units. ps.pHtot = CO2_TA(CO2=ps.CO2, TA=ps.TA, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks) ps.H = ch(ps.pHtot) ps.DIC = CO2_pH(ps.CO2, ps.pHtot, ps.Ks) # 5. ps.CO2 and ps.DIC elif ps.CO2 is not None and ps.DIC is not None: ps.H = CO2_DIC(ps.CO2, ps.DIC, ps.Ks) # 6. ps.pHtot and ps.HCO3 elif ps.pHtot is not None and ps.HCO3 is not None: ps.H = ch(ps.pHtot) ps.DIC = pH_HCO3(ps.pHtot, ps.HCO3, ps.Ks) # 7. ps.pHtot and ps.CO3 elif ps.pHtot is not None and ps.CO3 is not None: ps.H = ch(ps.pHtot) ps.DIC = pH_CO3(ps.pHtot, ps.CO3, ps.Ks) # 8. ps.pHtot and ps.TA elif ps.pHtot is not None and ps.TA is not None: ps.H = ch(ps.pHtot) ps.DIC = pH_TA(pH=ps.pHtot, TA=ps.TA, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks) # 9. ps.pHtot and ps.DIC elif ps.pHtot is not None and ps.DIC is not None: ps.H = ch(ps.pHtot) # 10. ps.HCO3 and ps.CO3 elif ps.HCO3 is not None and ps.CO3 is not None: ps.H = HCO3_CO3(ps.HCO3, ps.CO3, ps.Ks) ps.DIC = pH_CO3(cp(ps.H), ps.CO3, ps.Ks) # 11. ps.HCO3 and ps.TA elif ps.HCO3 is not None and ps.TA is not None: Warning( 'Nutrient alkalinity not implemented for this input combination.\nCalculations use only C and B alkalinity.' ) ps.H = HCO3_TA(ps.HCO3, ps.TA, ps.BT, ps.Ks) ps.DIC = pH_HCO3(cp(ps.H), ps.HCO3, ps.Ks) # 12. ps.HCO3 amd ps.DIC elif ps.HCO3 is not None and ps.DIC is not None: ps.H = HCO3_DIC(ps.HCO3, ps.DIC, ps.Ks) # 13. ps.CO3 and ps.TA elif ps.CO3 is not None and ps.TA is not None: Warning( 'Nutrient alkalinity not implemented for this input combination.\nCalculations use only C and B alkalinity.' ) ps.H = CO3_TA(ps.CO3, ps.TA, ps.BT, ps.Ks) ps.DIC = pH_CO3(cp(ps.H), ps.CO3, ps.Ks) # 14. ps.CO3 and ps.DIC elif ps.CO3 is not None and ps.DIC is not None: ps.H = CO3_DIC(ps.CO3, ps.DIC, ps.Ks) # 15. ps.TA and ps.DIC elif ps.TA is not None and ps.DIC is not None: ps.pHtot = TA_DIC(TA=ps.TA, DIC=ps.DIC, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks) ps.H = ch(ps.pHtot) # The above makes sure that DIC and H are known, # this next bit calculates all the missing species # from DIC and H. if ps.CO2 is None: ps.CO2 = cCO2(ps.H, ps.DIC, ps.Ks) if ps.fCO2 is None: ps.fCO2 = CO2_to_fCO2(ps.CO2, ps.Ks) if ps.pCO2 is None: ps.pCO2 = fCO2_to_pCO2(ps.fCO2, ps.T) if ps.HCO3 is None: ps.HCO3 = cHCO3(ps.H, ps.DIC, ps.Ks) if ps.CO3 is None: ps.CO3 = cCO3(ps.H, ps.DIC, ps.Ks) # Always calculate elements of alkalinity try: # necessary for use with CBsyst in special cases # where BT is not known before Csys is run. (ps.TA, ps.CAlk, ps.BAlk, ps.PAlk, ps.SiAlk, ps.OH, ps.Hfree, ps.HSO4, ps.HF) = cTA(H=ps.H, DIC=ps.DIC, BT=ps.BT, TP=ps.TP, TSi=ps.TSi, TS=ps.TS, TF=ps.TF, Ks=ps.Ks, mode='multi') except TypeError: pass if ps.pHtot is None: ps.pHtot = np.array(cp(ps.H), ndmin=1) # Calculate other pH scales ps.update( calc_pH_scales(ps.pHtot, ps.pHfree, ps.pHsws, ps.TS, ps.TF, ps.Ks)) # clean up for output for k in [ 'BT', 'CO2', 'CO3', 'Ca', 'DIC', 'H', 'HCO3', 'Mg', 'S', 'T', 'TA', 'CAlk', 'PAlk', 'SiAlk', 'OH' ]: if not isinstance(ps[k], np.ndarray): # convert all outputs to (min) 1D numpy arrays. ps[k] = np.array(ps[k], ndmin=1) if ps.unit != 1: for p in upar + [ 'CAlk', 'BAlk', 'PAlk', 'SiAlk', 'OH', 'HSO4', 'HF', 'Hfree' ]: ps[p] *= ps.unit # convert back to input units # remove some superfluous outputs rem = ['pdict'] for r in rem: if r in ps: del ps[r] return ps
def Bsys(pHtot=None, BT=None, BO3=None, BO4=None, ABT=None, ABO3=None, ABO4=None, dBT=None, dBO3=None, dBO4=None, alphaB=None, T=25., S=35., P=None, Ca=None, Mg=None, pHsws=None, pHfree=None, Ks=None, pdict=None): """ Calculate the boron chemistry of seawater from a minimal parameter set. Constants calculated by MyAMI model (Hain et al, 2015; doi:10.1002/2014GB004986). Speciation calculations from Zeebe & Wolf-Gladrow (2001; ISBN:9780444509468). pH is Total scale. Inputs must either be single values, arrays of equal length or a mixture of both. If you use arrays of unequal length, it won't work. Error propagation: If inputs are ufloat or uarray (from uncertainties package) errors will be propagated through all calculations. Concentration Units +++++++++++++++++++ * All concentrations must be in the same units. Returned in the same units as inputs. Parameters ---------- pH, BT, BO3, BO4 : array-like Boron system parameters. Two of these must be provided. T, S : array-like Temperature in Celcius and Salinity in PSU. Used in calculating MyAMI constants. P : array-like Pressure in Bar. Used in calculating MyAMI constants. Ca, Mg : arra-like The [Ca] and [Mg] of the seawater, in mol / kg. Used in calculating MyAMI constants. Ks : dict A dictionary of constants. Must contain keys 'K1', 'K2', 'KB' and 'KW'. If None, Ks are calculated using MyAMI model. pdict : dict Optionally, you can provide some or all parameters as a dict, with keys the same as the parameter names above. Any parameters included in the dict will overwrite manually specified parameters. This is particularly useful if you're including this in other code. Returns ------- dict(/Bunch) containing all calculated parameters. """ # Bunch inputs ps = Bunch(locals()) if isinstance(pdict, dict): ps.update(pdict) # Conserved seawater chemistry if 'TS' not in ps: ps.TS = calc_TS(ps.S) if 'TF' not in ps: ps.TF = calc_TF(ps.S) # if neither Ca nor Mg provided, use default Ks ps.Ks = get_Ks(ps) # Calculate pH scales (does nothing if none pH given) ps.update( calc_pH_scales(ps.pHtot, ps.pHfree, ps.pHsws, ps.TS, ps.TF, ps.Ks)) # B system calculations if ps.pHtot is not None and ps.BT is not None: ps.H = ch(ps.pHtot) elif ps.BT is not None and ps.BO3 is not None: ps.H = BT_BO3(ps.BT, ps.BO3, ps.Ks) elif ps.BT is not None and ps.BO4 is not None: ps.H = BT_BO4(ps.BT, ps.BO4, ps.Ks) elif ps.BO3 is not None and ps.BO4 is not None: ps.BT = ps.BO3 + ps.BO3 ps.H = BT_BO3(ps.BT, ps.BO3, ps.Ks) elif ps.pHtot is not None and ps.BO3 is not None: ps.H = ch(ps.pHtot) ps.BT = pH_BO3(ps.pHtot, ps.BO3, ps.Ks) elif ps.pHtot is not None and ps.BO4 is not None: ps.H = ch(ps.pHtot) ps.BT = pH_BO4(ps.pHtot, ps.BO4, ps.Ks) # The above makes sure that BT and H are known, # this next bit calculates all the missing species # from BT and H. if ps.BO3 is None: ps.BO3 = cBO3(ps.BT, ps.H, ps.Ks) if ps.BO4 is None: ps.BO4 = cBO4(ps.BT, ps.H, ps.Ks) if ps.pHtot is None: ps.pHtot = np.array(cp(ps.H), ndmin=1) # Calculate other pH scales ps.update( calc_pH_scales(ps.pHtot, ps.pHfree, ps.pHsws, ps.TS, ps.TF, ps.Ks)) if NnotNone(ps.ABT, ps.ABO3, ps.ABO4, ps.dBT, ps.dBO3, ps.dBO4) != 0: ps.update(ABsys(pdict=ps)) for k in ['BT', 'H', 'BO3', 'BO4', 'Ca', 'Mg', 'S', 'T']: # convert all outputs to (min) 1D numpy arrays. if not isinstance(ps[k], np.ndarray): # convert all outputs to (min) 1D numpy arrays. ps[k] = np.array(ps[k], ndmin=1) # remove some superfluous outputs rem = ['pdict'] for r in rem: if r in ps: del ps[r] return ps
def test_Carbon_Fns(self): ref = Bunch({ 'BAlk': 104.39451552567037, 'BT': 432.6, 'CAlk': 2340.16132518, 'CO2': 10.27549047, 'CO3': 250.43681565, 'Ca': 0.0102821, 'DIC': 2100., 'H': 7.94328235e-09, 'HCO3': 1839.28769389, 'HF': 0.00017903616862286758, 'HSO4': 0.0017448760289520083, 'Hfree': 0.0061984062104620289, 'Ks': { 'K0': 0.028391881804015699, 'K1': 1.4218281371391736e-06, 'K2': 1.0815547472209423e-09, 'KB': 2.5265729902477677e-09, 'KF': 0.0023655007956108367, 'KP1': 0.024265183950721327, 'KP2': 1.0841036169428488e-06, 'KP3': 1.612502080867568e-09, 'KSO4': 0.10030207107256615, 'KSi': 4.1025099579058308e-10, 'KW': 6.019824161802715e-14, 'KspA': 6.4817590680119676e-07, 'KspC': 4.2723509278625912e-07 }, 'Mg': 0.0528171, 'OH': 7.57850961, 'P': None, 'PAlk': 0., 'S': 35., 'SiAlk': 0., 'T': 25., 'TA': 2452.126228, 'TF': 6.832583968836728e-05, 'TP': 0.0, 'TS': 0.028235434132860126, 'TSi': 0.0, 'fCO2': 361.91649919340324, 'pCO2': 363.074540437976, 'pHtot': 8.1, 'unit': 1000000.0 }) Ks = Bunch(ref.Ks) self.assertAlmostEqual(cf.CO2_pH(ref.CO2, ref.pHtot, Ks), ref.DIC, msg='CO2_pH', places=6) self.assertAlmostEqual(cf.CO2_HCO3(ref.CO2, ref.HCO3, Ks)[0], ref.H, msg='CO2_HCO3 (zf)', places=6) self.assertAlmostEqual(cf.CO2_CO3(ref.CO2, ref.CO3, Ks)[0], ref.H, msg='CO2_CO3 (zf)', places=6) self.assertAlmostEqual(cf.CO2_TA(CO2=ref.CO2 / ref.unit, TA=ref.TA / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks)[0], ref.pHtot, msg='CO2_TA', places=6) self.assertAlmostEqual(cf.CO2_DIC(ref.CO2, ref.DIC, Ks)[0], ref.H, msg='CO2_DIC (zf)', places=6) self.assertAlmostEqual(cf.pH_HCO3(ref.pHtot, ref.HCO3, Ks), ref.DIC, msg='pH_HCO3', places=6) self.assertAlmostEqual(cf.pH_CO3(ref.pHtot, ref.CO3, Ks), ref.DIC, msg='pH_CO3', places=6) self.assertAlmostEqual(cf.pH_TA(pH=ref.pHtot, TA=ref.TA / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks) * ref.unit, ref.DIC, msg='pH_TA', places=6) self.assertAlmostEqual(cf.pH_DIC(ref.pHtot, ref.DIC, Ks), ref.CO2, msg='pH_DIC', places=6) self.assertAlmostEqual(cf.HCO3_CO3(ref.HCO3, ref.CO3, Ks)[0], ref.H, msg='HCO3_CO3 (zf)', places=6) self.assertAlmostEqual(cf.HCO3_TA(ref.HCO3 / ref.unit, ref.TA / ref.unit, ref.BT / ref.unit, Ks)[0], ref.H, msg='HCO3_TA (zf)', places=6) self.assertAlmostEqual(cf.HCO3_DIC(ref.HCO3, ref.DIC, Ks)[0], ref.H, msg='HCO3_DIC (zf)', places=6) self.assertAlmostEqual(cf.CO3_TA(ref.CO3 / ref.unit, ref.TA / ref.unit, ref.BT / ref.unit, Ks)[0], ref.H, msg='CO3_TA (zf)', places=6) self.assertAlmostEqual(cf.CO3_DIC(ref.CO3, ref.DIC, Ks)[0], ref.H, msg='CO3_DIC (zf)', places=6) self.assertAlmostEqual(cf.TA_DIC(TA=ref.TA / ref.unit, DIC=ref.DIC / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks)[0], ref.pHtot, msg='TA_DIC', places=6) self.assertAlmostEqual(cf.cCO2(ref.H, ref.DIC, Ks), ref.CO2, msg='cCO2', places=6) self.assertAlmostEqual(cf.cCO3(ref.H, ref.DIC, Ks), ref.CO3, msg='cCO3', places=6) self.assertAlmostEqual(cf.cHCO3(ref.H, ref.DIC, Ks), ref.HCO3, msg='cHCO3', places=6) (TA, CAlk, BAlk, PAlk, SiAlk, OH, Hfree, HSO4, HF) = cf.cTA(H=ref.H, DIC=ref.DIC / ref.unit, BT=ref.BT / ref.unit, TP=ref.TP / ref.unit, TSi=ref.TSi / ref.unit, TS=ref.TS, TF=ref.TF, Ks=Ks, mode='multi') self.assertAlmostEqual(TA * ref.unit, ref.TA, msg='cTA - TA', places=6) self.assertAlmostEqual(CAlk * ref.unit, ref.CAlk, msg='cTA - CAlk', places=6) self.assertAlmostEqual(BAlk * ref.unit, ref.BAlk, msg='cTA - BAlk', places=6) self.assertAlmostEqual(PAlk * ref.unit, ref.PAlk, msg='cTA - PAlk', places=6) self.assertAlmostEqual(SiAlk * ref.unit, ref.SiAlk, msg='cTA - SiAlk', places=6) self.assertAlmostEqual(OH * ref.unit, ref.OH, msg='cTA - OH', places=6) self.assertAlmostEqual(Hfree * ref.unit, ref.Hfree, msg='cTA - Hfree', places=6) self.assertAlmostEqual(HSO4 * ref.unit, ref.HSO4, msg='cTA - HSO4', places=6) self.assertAlmostEqual(HF * ref.unit, ref.HF, msg='cTA - HF', places=6) self.assertAlmostEqual(cf.fCO2_to_CO2(ref.fCO2, Ks), ref.CO2, msg='fCO2_to_CO2', places=6) self.assertAlmostEqual(cf.CO2_to_fCO2(ref.CO2, Ks), ref.fCO2, msg='CO2_to_fCO2', places=6) self.assertAlmostEqual(cf.fCO2_to_pCO2(ref.fCO2, ref.T), ref.pCO2, msg='fCO2_to_pCO2', places=6) self.assertAlmostEqual(cf.pCO2_to_fCO2(ref.pCO2, ref.T), ref.fCO2, msg='pCO2_to_fCO2', places=6) return
def test_Boron_Fns(self): ref = Bunch({ 'ABO3': 0.80882931, 'ABO4': 0.80463763, 'ABT': 0.80781778, 'BO3': 328.50895695, 'BO4': 104.49104305, 'BT': 433., 'Ca': 0.0102821, 'H': 7.94328235e-09, 'Ks': { 'K0': 0.02839188, 'K1': 1.42182814e-06, 'K2': 1.08155475e-09, 'KB': 2.52657299e-09, 'KSO4': 0.10030207, 'KW': 6.06386369e-14, 'KspA': 6.48175907e-07, 'KspC': 4.27235093e-07 }, 'Mg': 0.0528171, 'S': 35., 'T': 25., 'alphaB': 1.02725, 'dBO3': 46.30877684, 'dBO4': 18.55320208, 'dBT': 39.5, 'pHtot': 8.1 }) Ks = Bunch(ref.Ks) # Speciation self.assertAlmostEqual(bf.BT_BO3(ref.BT, ref.BO3, Ks), ref.H, msg='BT_BO3', places=6) self.assertAlmostEqual(bf.BT_BO4(ref.BT, ref.BO4, Ks), ref.H, msg='BT_BO4', places=6) self.assertAlmostEqual(bf.pH_BO3(ref.pHtot, ref.BO3, Ks), ref.BT, msg='pH_BO3', places=6) self.assertAlmostEqual(bf.pH_BO4(ref.pHtot, ref.BO4, Ks), ref.BT, msg='pH_BO4', places=6) self.assertAlmostEqual(bf.cBO3(ref.BT, ref.H, Ks), ref.BO3, msg='cBO3', places=6) self.assertAlmostEqual(bf.cBO4(ref.BT, ref.H, Ks), ref.BO4, msg='cBO4', places=6) self.assertEqual(bf.chiB_calc(ref.H, Ks), 1 / (1 + Ks.KB / ref.H), msg='chiB_calc') # Isotopes self.assertEqual(bf.alphaB_calc(ref.T), 1.0293 - 0.000082 * ref.T, msg='alphaB_calc') self.assertAlmostEqual(bf.pH_ABO3(ref.pHtot, ref.ABO3, Ks, ref.alphaB), ref.ABT, msg='pH_ABO3', places=6) self.assertAlmostEqual(bf.pH_ABO4(ref.pHtot, ref.ABO4, Ks, ref.alphaB), ref.ABT, msg='pH_ABO4', places=6) self.assertAlmostEqual(bf.cABO3(ref.H, ref.ABT, Ks, ref.alphaB), ref.ABO3, msg='cABO3', places=6) self.assertAlmostEqual(bf.cABO4(ref.H, ref.ABT, Ks, ref.alphaB), ref.ABO4, msg='cABO4', places=6) # Isotope unit conversions self.assertAlmostEqual(bf.A11_2_d11(0.807817779214075), 39.5, msg='A11_2_d11', places=6) self.assertAlmostEqual(bf.d11_2_A11(39.5), 0.807817779214075, msg='d11_2_A11', places=6) return