Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
def TA_DIC(TA, DIC, BT, TP, TSi, TS, TF, Ks):
    """
    Returns pH

    Taken directly from MATLAB CO2SYS.
    """
    L = maxL(TA, DIC, BT, TP, TSi, TS, TF, Ks.K1)
    pHguess = 7.0
    pHtol = 0.00000001
    pHx = np.full(L, pHguess)
    deltapH = np.array(pHtol + 1, ndmin=1)
    ln10 = np.log(10)

    while any(abs(deltapH) > pHtol):
        H = 10**-pHx
        # negative
        Denom = H**2 + Ks.K1 * H + Ks.K1 * Ks.K2
        CAlk = DIC * Ks.K1 * (H + 2 * Ks.K2) / Denom
        BAlk = BT * Ks.KB / (Ks.KB + H)
        OH = Ks.KW / H
        PhosTop = Ks.KP1 * Ks.KP2 * H + 2 * Ks.KP1 * Ks.KP2 * Ks.KP3 - H**3
        PhosBot = (H**3 + Ks.KP1 * H**2 + Ks.KP1 * Ks.KP2 * H +
                   Ks.KP1 * Ks.KP2 * Ks.KP3)
        PAlk = TP * PhosTop / PhosBot
        SiAlk = TSi * Ks.KSi / (Ks.KSi + H)
        # positive
        Hfree = H / (1 + TS / Ks.KSO4)
        HSO4 = TS / (1 + Ks.KSO4 / Hfree)
        HF = TF / (1 + Ks.KF / Hfree)

        Residual = TA - CAlk - BAlk - OH - PAlk - SiAlk + Hfree + HSO4 + HF

        Slope = ln10 * (
            DIC * Ks.K1 * H *
            (H**2 + Ks.K1 * Ks.K2 + 4 * H * Ks.K2) / Denom / Denom + BAlk * H /
            (Ks.KB + H) + OH + H)
        deltapH = Residual / Slope

        while any(abs(deltapH) > 1):
            FF = abs(deltapH) > 1
            deltapH[FF] = deltapH[FF] / 2

        pHx += deltapH

    return pHx
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
def CO2_TA(CO2, TA, BT, TP, TSi, TS, TF, Ks):
    """
    Returns pH

    Taken from matlab CO2SYS
    """
    fCO2 = CO2 / Ks.K0
    L = maxL(TA, CO2, BT, TP, TSi, TS, TF, Ks.K1)
    pHguess = 8.0
    pHtol = 0.0000001
    pHx = np.full(L, pHguess)
    deltapH = np.array(pHtol + 1, ndmin=1)
    ln10 = np.log(10)

    while any(abs(deltapH) > pHtol):
        H = 10**-pHx
        HCO3 = Ks.K0 * Ks.K1 * fCO2 / H
        CO3 = Ks.K0 * Ks.K1 * Ks.K2 * fCO2 / H**2
        CAlk = HCO3 + 2 * CO3
        BAlk = BT * Ks.KB / (Ks.KB + H)
        OH = Ks.KW / H
        PhosTop = Ks.KP1 * Ks.KP2 * H + 2 * Ks.KP1 * Ks.KP2 * Ks.KP3 - H**3
        PhosBot = (H**3 + Ks.KP1 * H**2 + Ks.KP1 * Ks.KP2 * H +
                   Ks.KP1 * Ks.KP2 * Ks.KP3)
        PAlk = TP * PhosTop / PhosBot
        SiAlk = TSi * Ks.KSi / (Ks.KSi + H)
        # positive
        Hfree = H / (1 + TS / Ks.KSO4)
        HSO4 = TS / (1 + Ks.KSO4 / Hfree)
        HF = TF / (1 + Ks.KF / Hfree)

        Residual = TA - CAlk - BAlk - OH - PAlk - SiAlk + Hfree + HSO4 + HF
        Slope = ln10 * (HCO3 + 4.0 * CO3 + BAlk * H / (Ks.KB + H) + OH + H)
        deltapH = Residual / Slope

        while any(abs(deltapH) > 1):
            FF = abs(deltapH) > 1
            deltapH[FF] = deltapH[FF] / 2

        pHx += deltapH

    return pHx
Ejemplo n.º 5
0
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