# -*- coding: utf-8 -*-

from __future__ import division

import numpy as np

from .library import specvol_SSO_0_p
from .constants import P0, db2Pascal, cp0
from ..utilities import match_args_return

__all__ = ['alpha',
           'alpha_on_beta',
           'alpha_on_beta_CT',
           'beta',
           'dynamic_enthalpy',
           'enthalpy',
           'enthalpy_diff',
           'internal_energy',
           'rho',
           'rho_alpha_beta',
           'SA_from_rho',
           'sigma0',
           'sigma1',
           'sigma2',
           'sigma3',
           'sigma4',
           'sound_speed',
           'specvol',
           'specvol_anom']


# NOTE: these are from gsw_rho_alpha_beta.m
v01 = 9.998420897506056e+2
v02 = 2.839940833161907
v03 = -3.147759265588511e-2
v04 = 1.181805545074306e-3
v05 = -6.698001071123802
v06 = -2.986498947203215e-2
v07 = 2.327859407479162e-4
v08 = -3.988822378968490e-2
v09 = 5.095422573880500e-4
v10 = -1.426984671633621e-5
v11 = 1.645039373682922e-7
v12 = -2.233269627352527e-2
v13 = -3.436090079851880e-4
v14 = 3.726050720345733e-6
v15 = -1.806789763745328e-4
v16 = 6.876837219536232e-7
v17 = -3.087032500374211e-7
v18 = -1.988366587925593e-8
v19 = -1.061519070296458e-11
v20 = 1.550932729220080e-10
v21 = 1.0
v22 = 2.775927747785646e-3
v23 = -2.349607444135925e-5
v24 = 1.119513357486743e-6
v25 = 6.743689325042773e-10
v26 = -7.521448093615448e-3
v27 = -2.764306979894411e-5
v28 = 1.262937315098546e-7
v29 = 9.527875081696435e-10
v30 = -1.811147201949891e-11
v31 = -3.303308871386421e-5
v32 = 3.801564588876298e-7
v33 = -7.672876869259043e-9
v34 = -4.634182341116144e-11
v35 = 2.681097235569143e-12
v36 = 5.419326551148740e-6
v37 = -2.742185394906099e-5
v38 = -3.212746477974189e-7
v39 = 3.191413910561627e-9
v40 = -1.931012931541776e-12
v41 = -1.105097577149576e-7
v42 = 6.211426728363857e-10
v43 = -1.119011592875110e-10
v44 = -1.941660213148725e-11
v45 = -1.864826425365600e-14
v46 = 1.119522344879478e-14
v47 = -1.200507748551599e-15
v48 = 6.057902487546866e-17

a01 = 2.839940833161907
a02 = -6.295518531177023e-2
a03 = 3.545416635222918e-3
a04 = -2.986498947203215e-2
a05 = 4.655718814958324e-4
a06 = 5.095422573880500e-4
a07 = -2.853969343267241e-5
a08 = 4.935118121048767e-7
a09 = -3.436090079851880e-4
a10 = 7.452101440691467e-6
a11 = 6.876837219536232e-7
a12 = -1.988366587925593e-8
a13 = -2.123038140592916e-11

a14 = 2.775927747785646e-3
a15 = -4.699214888271850e-5
a16 = 3.358540072460230e-6
a17 = 2.697475730017109e-9
a18 = -2.764306979894411e-5
a19 = 2.525874630197091e-7
a20 = 2.858362524508931e-9
a21 = -7.244588807799565e-11
a22 = 3.801564588876298e-7
a23 = -1.534575373851809e-8
a24 = -1.390254702334843e-10
a25 = 1.072438894227657e-11
a26 = -3.212746477974189e-7
a27 = 6.382827821123254e-9
a28 = -5.793038794625329e-12
a29 = 6.211426728363857e-10
a30 = -1.941660213148725e-11
a31 = -3.729652850731201e-14
a32 = 1.119522344879478e-14
a33 = 6.057902487546866e-17

b01 = -6.698001071123802
b02 = -2.986498947203215e-2
b03 = 2.327859407479162e-4
b04 = -5.983233568452735e-2
b05 = 7.643133860820750e-4
b06 = -2.140477007450431e-5
b07 = 2.467559060524383e-7
b08 = -1.806789763745328e-4
b09 = 6.876837219536232e-7
b10 = 1.550932729220080e-10
b11 = -7.521448093615448e-3
b12 = -2.764306979894411e-5
b13 = 1.262937315098546e-7
b14 = 9.527875081696435e-10
b15 = -1.811147201949891e-11
b16 = -4.954963307079632e-5
b17 = 5.702346883314446e-7
b18 = -1.150931530388857e-8
b19 = -6.951273511674217e-11
b20 = 4.021645853353715e-12
b21 = 1.083865310229748e-5
b22 = -1.105097577149576e-7
b23 = 6.211426728363857e-10
b24 = 1.119522344879478e-14

c01 = -2.233269627352527e-2
c02 = -3.436090079851880e-4
c03 = 3.726050720345733e-6
c04 = -1.806789763745328e-4
c05 = 6.876837219536232e-7
c06 = -6.174065000748422e-7
c07 = -3.976733175851186e-8
c08 = -2.123038140592916e-11
c09 = 3.101865458440160e-10
c10 = -2.742185394906099e-5
c11 = -3.212746477974189e-7
c12 = 3.191413910561627e-9
c13 = -1.931012931541776e-12
c14 = -1.105097577149576e-7
c15 = 6.211426728363857e-10
c16 = -2.238023185750219e-10
c17 = -3.883320426297450e-11
c18 = -3.729652850731201e-14
c19 = 2.239044689758956e-14
c20 = -3.601523245654798e-15
c21 = 1.817370746264060e-16

# Helper functions that are used by more than one user function.
# FIXME: arrange all these things so that inputs to these
# functions are done with ordinary ndarrays with nans, not
# masked arrays.  We might need a different decorator for this.


def v_hat_denominator(SA, CT, p, sqrtSA):
    return (v01 + CT * (v02 + CT * (v03 + v04*CT)) +
            SA *
            (v05 + CT * (v06 + v07 * CT) + sqrtSA *
             (v08 + CT * (v09 + CT * (v10 + v11 * CT)))) +
            p *
            (v12 + CT * (v13 + v14 * CT) + SA * (v15 + v16 * CT) +
             p * (v17 + CT * (v18 + v19 * CT) + v20 * SA)))


def v_hat_numerator(SA, CT, p, sqrtSA):
    return (v21 + CT * (v22 + CT * (v23 + CT * (v24 + v25 * CT))) +
            SA *
            (v26 + CT * (v27 + CT * (v28 + CT * (v29 + v30 * CT))) +
             v36 * SA + sqrtSA *
             (v31 + CT * (v32 + CT * (v33 + CT * (v34 + v35 * CT))))) +
            p *
            (v37 + CT * (v38 + CT * (v39 + v40 * CT)) + SA *
             (v41 + v42 * CT) + p *
             (v43 + CT * (v44 + v45 * CT + v46 * SA) + p * (v47 + v48 * CT))))


# Next 4 are from alpha on beta:
def dvhatden_dCT(SA, CT, p, sqrtSA):
    return (a01 + CT * (a02 + a03 * CT) + SA *
            (a04 + a05 * CT + sqrtSA * (a06 + CT * (a07 + a08 * CT))) +
            p * (a09 + a10 * CT + a11 * SA + p * (a12 + a13 * CT)))


def dvhatnum_dCT(SA, CT, p, sqrtSA):
    return (a14 + CT * (a15 + CT * (a16 + a17 * CT)) + SA *
            (a18 + CT * (a19 + CT * (a20 + a21 * CT)) + sqrtSA *
             (a22 + CT * (a23 + CT * (a24 + a25 * CT)))) + p *
            (a26 + CT * (a27 + a28 * CT) + a29 * SA + p *
             (a30 + a31 * CT + a32 * SA + a33 * p)))


def dvhatden_dSA(SA, CT, p, sqrtSA):
    return (b01 + CT * (b02 + b03 * CT) + sqrtSA *
            (b04 + CT * (b05 + CT * (b06 + b07 * CT))) +
            p * (b08 + b09 * CT + b10 * p))


def dvhatnum_dSA(SA, CT, p, sqrtSA):
    return(b11 + CT * (b12 + CT * (b13 + CT * (b14 + b15*CT)))
           + sqrtSA * (b16 + CT * (b17 + CT * (b18 + CT * (b19 + b20*CT))))
           + b21*SA
           + p * (b22 + CT * (b23 + b24*p)))


@match_args_return
def alpha_on_beta(SA, CT, p):
    """
     gsw_alpha_on_beta                           alpha/beta (48-term equation)
    ==========================================================================

     USAGE:
     alpha_on_beta = gsw_alpha_on_beta(SA,CT,p)

     DESCRIPTION:
      Calculates alpha divided by beta, where alpha is the thermal expansion
      coefficient and beta is the saline contraction coefficient of seawater
      from Absolute Salinity and Conservative Temperature.  This function uses
      the computationally-efficient 48-term expression for density in terms of
      SA, CT and p (IOC et al., 2010).

      Note that the 48-term equation has been fitted in a restricted range of
      parameter space, and is most accurate inside the "oceanographic funnel"
      described in IOC et al. (2010).  The GSW library function
      "gsw_infunnel(SA,CT,p)" is avaialble to be used if one wants to test if
      some of one's data lies outside this "funnel".

     INPUT:
      SA  =  Absolute Salinity                                        [ g/kg ]
      CT  =  Conservative Temperature (ITS-90)                       [ deg C ]
      p   =  sea pressure                                             [ dbar ]
             ( i.e. absolute pressure - 10.1325 dbar )

      SA & CT need to have the same dimensions.
      p may have dimensions 1x1 or Mx1 or 1xN or MxN, where SA & CT are MxN.

     OUTPUT:
      alpha_on_beta  =  thermal expansion coefficient with respect to
                        Conservative Temperature divided by the saline
                        contraction coefficient at constant Conservative
                        Temperature                           [ kg g^-1 K^-1 ]

     AUTHOR:
      Paul Barker and Trevor McDougall                    [ help@teos-10.org ]

     VERSION NUMBER: 3.03 (10th May, 2013)

     REFERENCES:
      IOC, SCOR and IAPSO, 2010: The international thermodynamic equation of
      seawater - 2010: Calculation and use of thermodynamic properties.
      Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
      UNESCO (English), 196 pp.  Available from http://www.TEOS-10.org
    """

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    num = (dvhatnum_dCT(*args) * v_hat_denominator(*args) -
           dvhatden_dCT(*args) * v_hat_numerator(*args))
    denom = (dvhatden_dSA(*args) * v_hat_numerator(*args) -
             dvhatnum_dSA(*args) * v_hat_denominator(*args))
    return num / denom

alpha_on_beta_CT = alpha_on_beta


@match_args_return
def alpha(SA, CT, p):
    """
    Calculates the thermal expansion coefficient of seawater with respect
    to Conservative Temperature using the computationally-efficient 48-term
    expression for density in terms of SA, CT and p (McDougall et al., 2011)


    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    alpha : array_like
            thermal expansion coefficient [K :math:`-1`]
            with respect to Conservative Temperature

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel"
    described in McDougall et al. (2011).  The GSW library function
    "infunnel(SA, CT, p)" is available to be used if one wants to test if
    some of one's data lies outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqn. (2.18.3).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    spec_vol = v_hat_numerator(*args) / v_hat_denominator(*args)

    return ((dvhatnum_dCT(*args) - dvhatden_dCT(*args) * spec_vol) /
            v_hat_numerator(*args))


@match_args_return
def beta(SA, CT, p):
    """
    Calculates the saline (i.e. haline) contraction coefficient of seawater
    at constant Conservative Temperature using the computationally-efficient
    48-term expression for density in terms of SA, CT and p (McDougall et al.,
    2011).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    beta : array_like
           saline contraction coefficient [kg g :math:`-1`]
           at constant Conservative Temperature.

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel"
    described in McDougall et al. (2011).  The GSW library function
    "infunnel(SA, CT, p)" is available to be used if one wants to test if some
    of one's data lies outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqn. (2.19.3).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    spec_vol = v_hat_numerator(*args) / v_hat_denominator(*args)

    return ((dvhatden_dSA(*args) * spec_vol - dvhatnum_dSA(*args)) /
            v_hat_numerator(*args))


@match_args_return
def dynamic_enthalpy(SA, CT, p):
    """
    Calculates dynamic enthalpy of seawater using the computationally-
    efficient 48-term expression for density in terms of SA, CT and p
    (McDougall et al., 2011).  Dynamic enthalpy is defined as enthalpy minus
    potential enthalpy (Young, 2010).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    dynamic_enthalpy : array_like
                       dynamic enthalpy [J/kg]

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA, CT, p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See section 3.2

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.

    .. [3] Young, W.R., 2010: Dynamic enthalpy, Conservative Temperature, and
       the seawater Boussinesq approximation. Journal of Physical Oceanography,
       40, 394-400.
    """

    SA = np.maximum(SA, 0)

    sqrtSA = np.sqrt(SA)

    a0 = (v21 + CT * (v22 + CT * (v23 + CT * (v24 + v25 * CT))) + SA *
          (v26 + CT * (v27 + CT * (v28 + CT * (v29 + v30 * CT))) + v36 * SA +
           sqrtSA * (v31 + CT * (v32 + CT * (v33 + CT * (v34 + v35 * CT))))))

    a1 = v37 + CT * (v38 + CT * (v39 + v40 * CT)) + SA * (v41 + v42 * CT)

    a2 = v43 + CT * (v44 + v45 * CT + v46 * SA)

    a3 = v47 + v48 * CT

    b0 = (v01 + CT * (v02 + CT * (v03 + v04 * CT)) + SA * (v05 + CT * (v06 +
          v07 * CT) + sqrtSA * (v08 + CT * (v09 + CT * (v10 + v11 * CT)))))

    b1 = 0.5 * (v12 + CT * (v13 + v14 * CT) + SA * (v15 + v16 * CT))

    b2 = v17 + CT * (v18 + v19 * CT) + v20 * SA

    b1sq = b1 * b1

    sqrt_disc = np.sqrt(b1sq - b0 * b2)

    N = a0 + (2 * a3 * b0 * b1 / b2 - a2 * b0) / b2
    M = a1 + (4 * a3 * b1sq / b2 - a3 * b0 - 2 * a2 * b1) / b2

    A = b1 - sqrt_disc
    B = b1 + sqrt_disc

    part = (N * b2 - M * b1) / (b2 * (B - A))

    """This function calculates dynamic_enthalpy using the computationally-
    efficient 48-term expression for density in terms of SA, CT and p.  If one
    wanted to compute dynamic_enthalpy from SA, CT, and p with the full TEOS-10
    Gibbs function, the following lines of code will enable this.

    dynamic_enthalpy = dynamic_enthalpy_CT_exact(SA, CT, p)
    """

    return db2Pascal * (p * (a2 - 2 * a3 * b1 / b2 + 0.5 * a3 * p) / b2 +
                        (M / (2 * b2)) *
                        np.log(1 + p * (2 * b1 + b2 * p) / b0) + part *
                        np.log(1 + (b2 * p * (B - A)) / (A * (B + b2 * p))))


@match_args_return
def enthalpy(SA, CT, p):
    """
    Calculates specific enthalpy of seawater using the computationally-
    efficient 48-term expression for density in terms of SA, CT and p
    (McDougall et al., 2011)

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    enthalpy : array_like
               specific enthalpy [J/kg]

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA, CT, p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqn. (A.30.6).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)

    sqrtSA = np.sqrt(SA)

    a0 = (v21 + CT * (v22 + CT * (v23 + CT * (v24 + v25 * CT))) + SA *
          (v26 + CT * (v27 + CT * (v28 + CT * (v29 + v30 * CT))) + v36 * SA +
           sqrtSA * (v31 + CT * (v32 + CT * (v33 + CT * (v34 + v35 * CT))))))

    a1 = v37 + CT * (v38 + CT * (v39 + v40 * CT)) + SA * (v41 + v42 * CT)

    a2 = v43 + CT * (v44 + v45 * CT + v46 * SA)

    a3 = v47 + v48 * CT

    b0 = (v01 + CT * (v02 + CT * (v03 + v04 * CT)) + SA * (v05 + CT * (v06 +
          v07 * CT) + sqrtSA * (v08 + CT * (v09 + CT * (v10 + v11 * CT)))))

    b1 = 0.5 * (v12 + CT * (v13 + v14 * CT) + SA * (v15 + v16 * CT))

    b2 = v17 + CT * (v18 + v19 * CT) + v20 * SA

    b1sq = b1 * b1

    sqrt_disc = np.sqrt(b1sq - b0 * b2)

    N = a0 + (2 * a3 * b0 * b1 / b2 - a2 * b0) / b2
    M = a1 + (4 * a3 * b1sq / b2 - a3 * b0 - 2 * a2 * b1) / b2

    A = b1 - sqrt_disc
    B = b1 + sqrt_disc

    part = (N * b2 - M * b1) / (b2 * (B - A))

    # This function calculates enthalpy using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p.  If one wanted to
    # compute enthalpy from SA, CT, and p with the full TEOS-10 Gibbs function,
    # the following lines of code will enable this.

    # pt = pt_from_CT(SA, CT)
    # t = pt_from_t(SA, pt, 0, p)
    # enthalpy = enthalpy_t_exact(SA, t, p)

    # or call the following, it is identical to the lines above.

    # enthalpy = enthalpy_CT_exact(SA, CT, p)

    return (cp0 * CT + db2Pascal *
            (p * (a2 - 2 * a3 * b1 / b2 + 0.5 * a3 * p) / b2 + (M / (2 * b2)) *
             np.log(1 + p * (2 * b1 + b2 * p) / b0) + part *
             np.log(1 + (b2 * p * (B - A)) / (A * (B + b2 * p)))))


@match_args_return
def enthalpy_diff(SA, CT, p_shallow, p_deep):
    """
    Calculates the difference of the specific enthalpy of seawater between
    two different pressures, p_deep (the deeper pressure) and p_shallow (the
    shallower pressure), at the same values of SA and CT.  This function uses
    the computationally-efficient 48-term expression for density in terms of
    SA, CT and p (McDougall et al., 2011).  The output (enthalpy_diff_CT) is
    the specific enthalpy evaluated at (SA, CT, p_deep) minus the specific
    enthalpy at (SA, CT, p_shallow).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p_shallow : array_like
                lower sea pressure [dbar]
    p_deep : array_like
             upper sea pressure [dbar]

    Returns
    -------
    enthalpy_diff : array_like
                    difference of specific enthalpy [J/kg]
                    (deep minus shallow)

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA, CT, p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqns. (3.32.2) and (A.30.6).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)

    sqrtSA = np.sqrt(SA)

    a0 = (v21 + CT * (v22 + CT * (v23 + CT * (v24 + v25 * CT))) + SA *
          (v26 + CT * (v27 + CT * (v28 + CT * (v29 + v30 * CT))) + v36 * SA +
           sqrtSA * (v31 + CT * (v32 + CT * (v33 + CT * (v34 + v35 * CT))))))

    a1 = v37 + CT * (v38 + CT * (v39 + v40 * CT)) + SA * (v41 + v42 * CT)

    a2 = v43 + CT * (v44 + v45 * CT + v46 * SA)

    a3 = v47 + v48 * CT

    b0 = (v01 + CT * (v02 + CT * (v03 + v04 * CT)) + SA * (v05 + CT * (v06 +
          v07 * CT) + sqrtSA * (v08 + CT * (v09 + CT * (v10 + v11 * CT)))))

    b1 = 0.5 * (v12 + CT * (v13 + v14 * CT) + SA * (v15 + v16 * CT))

    b2 = v17 + CT * (v18 + v19 * CT) + v20 * SA

    b1sq = b1 * b1

    sqrt_disc = np.sqrt(b1sq - b0 * b2)

    N = a0 + (2 * a3 * b0 * b1 / b2 - a2 * b0) / b2
    M = a1 + (4 * a3 * b1sq / b2 - a3 * b0 - 2 * a2 * b1) / b2

    A = b1 - sqrt_disc
    B = b1 + sqrt_disc

    delta_p = p_deep - p_shallow
    p_sum = p_deep + p_shallow

    part1 = b0 + p_shallow * (2 * b1 + b2 * p_shallow)
    part2 = (B + b2 * p_deep) * (A + b2 * p_shallow)
    part3 = (N * b2 - M * b1) / (b2 * (B - A))

    # This function calculates enthalpy_diff using the computationally
    # efficient 48-term expression for density in terms of SA, CT and p.  If one
    # wanted to compute the enthalpy difference using the full TEOS-10 Gibbs
    # function, the following lines of code will enable this.

    # pt = pt_from_CT(SA, CT)
    # t_shallow = pt_from_t(SA, pt, 0, p_shallow)
    # t_deep = pt_from_t(SA, pt, 0, p_deep)
    # enthalpy_diff = (enthalpy_t_exact(SA, t_deep, p_deep) -
    #                  enthalpy_t_exact(SA, t_shallow, p_shallow))

    # or call the following, it is identical to the lines above.

    # enthalpy_diff = enthalpy_diff_CT_exact(SA, CT, p_shallow, p_deep)

    return db2Pascal * (delta_p * (a2 - 2 * a3 * b1 / b2 + 0.5 * a3 * p_sum) /
                        b2 + (M / (2 * b2)) *
                        np.log(1 + delta_p * (2 * b1 + b2 * p_sum) / part1) +
                        part3 * np.log(1 + delta_p * b2 * (B - A) / part2))


@match_args_return
def internal_energy(SA, CT, p):
    """
    Calculates specific internal energy of seawater using the
    computationally-efficient 48-term expression for density in terms of SA,
    CT and p (McDougall et al., 2011).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    internal_energy : array_like
                      specific internal energy [J/kg]

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel"
    described in McDougall et al. (2011).  The GSW library function
    "infunnel(SA, CT, p)" is available to be used if one wants to test if
    some of one's data lies outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp.

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)

    # This function calculates enthalpy using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p. If one wanted to
    # compute enthalpy from SA, CT, and p with the full TEOS-10 Gibbs function,
    # the following line of code will enable this.

    # internal_energy = internal_energy_CT_exact(SA, CT, p)

    return (enthalpy(SA, CT, p) - (P0 + db2Pascal * p) * specvol(SA, CT, p))


@match_args_return
def rho(SA, CT, p):
    """
    Calculates in-situ density from Absolute Salinity and Conservative
    Temperature, using the computationally-efficient 48-term expression for
    density in terms of SA, CT and p (McDougall et al., 2011).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    rho : array_like
          in-situ density [kg/m**3]

    Notes
    -----
    The potential density with respect to reference pressure, pr, is obtained
    by calling this function with the pressure argument being pr (i.e.
    "rho(SA,CT,pr)").

    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA,CT,p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See appendix A.20 and appendix K.

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    # This function calculates rho using the computationally-efficient 48-term
    # expression for density in terms of SA, CT and p. If one wanted to compute
    # rho from SA, CT, and p with the full TEOS-10 Gibbs function, the following
    # lines of code will enable this.
    #
    # pt0 = pt_from_CT(SA, CT)
    # t = pt_from_t(SA, pt0, 0, p)
    # rho = rho_t_exact(SA, t, p)
    #
    # or call the following, it is identical to the lines above.
    #
    # rho = rho_CT_exact(SA, CT, p)
    #
    # or call the following, it is identical to the lines above.
    #
    # rho,_ ,_ = rho_alpha_beta_CT_exact(SA, CT, p)

    return v_hat_denominator(*args) / v_hat_numerator(*args)


def rho_alpha_beta(SA, CT, p):
    """
    Calculates in-situ density, the appropriate thermal expansion
    coefficient and the appropriate saline contraction coefficient of seawater
    from Absolute Salinity and Conservative Temperature.  This function uses
    the computationally-efficient 48-term expression for density in terms of
    SA, CT and p (McDougall et al., 2011).

    The potential density (pot_rho) with respect to reference pressure p_ref is
    obtained by calling this function with the pressure argument being p_ref as
    in pot_rho, _, _] = rho_alpha_beta(SA, CT, p_ref).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    rho : array_like
          in-situ density [kg/m**3]
    alpha : array_like
            thermal expansion coefficient [K :math:`-1`]
            with respect to Conservative Temperature
    beta : array_like
           saline contraction coefficient [kg g :math:`-1`]
           at constant Conservative Temperature.

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA, CT, p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See appendix A.20 and appendix K.

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
      computationally efficient 48-term expression for the density of
      seawater in terms of Conservative Temperature, and related properties
      of seawater.
    """

    return rho(SA, CT, p), alpha(SA, CT, p), beta(SA, CT, p)


@match_args_return
def SA_from_rho(rho, CT, p):
    """
    Calculates the Absolute Salinity of a seawater sample, for given values
    of its density, Conservative Temperature and sea pressure (in dbar).  This
    function uses the computationally-efficient 48-term expression for density
    in terms of SA, CT and p (McDougall et al., 2011).

    Parameters
    ----------
    rho : array_like
          density of a seawater sample [kg/m**3]
          This input has not had 1000 kg/m^3 subtracted from it
          (e.g. 1026 kg m**-3), that is, it is density, NOT density anomaly.
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    SA : array_like
         Absolute Salinity  [g/kg]

    Notes
    -----
    This is expressed on the Reference-Composition Salinity Scale of
    Millero et al. (2008).

    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA, CT, p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See section 2.5

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.

    .. [3] Millero, F. J., R. Feistel, D. G. Wright, and T. J. McDougall, 2008:
       The composition of Standard Seawater and the definition of the
       Reference-Composition Salinity Scale, Deep-Sea Res. I, 55, 50-72.
    """

    v_lab = 1. / rho
    v_0 = specvol(np.zeros_like(rho), CT, p)
    v_50 = specvol(50 * np.ones_like(rho), CT, p)

    SA = 50 * (v_lab - v_0) / (v_50 - v_0)  # Initial estimate of SA.

    SA[np.logical_or(SA < 0, SA > 50)] = np.ma.masked

    v_SA = (v_50 - v_0) / 50.  # Initial v_SA estimate (SA derivative of v).

    # Begin the modified Newton-Raphson iterative procedure.
    for Number_of_iterations in range(0, 3):
        SA_old = SA
        delta_v = specvol(SA_old, CT, p) - v_lab
        # Half way the mod. N-R method (McDougall and Wotherspoon, 2012)
        SA = SA_old - delta_v / v_SA  # Half way through the mod. N-R method.
        SA_mean = 0.5 * (SA + SA_old)
        rho, alpha, beta = rho_alpha_beta(SA_mean, CT, p)
        v_SA = -beta / rho
        SA = SA_old - delta_v / v_SA
        SA[np.logical_or(SA < 0, SA > 50)] = np.ma.masked

    # After two iterations of this modified Newton-Raphson iteration,
    # the error in SA is no larger than 8x10^-13 g kg^-1, which
    # is machine precision for this calculation.
    return SA


def sigma0(SA, CT):
    """
    Calculates potential density anomaly with reference pressure of 0 dbar,
    this being this particular potential density minus 1000 kg/m^3.  This
    function has inputs of Absolute Salinity and Conservative Temperature.
    This function uses the computationally-efficient 48-term expression for
    density in terms of SA, CT and p (McDougall et al., 2011).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]

    Returns
    -------
    sigma0 : array_like
             potential density anomaly with [kg/m**3]
             respect to a reference pressure of 0 dbar
    """

    # This function calculates sigma0 using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p.  If one wanted
    # to compute sigma0 with the full TEOS-10 Gibbs function expression for
    # density, the following lines of code will enable this.
    #
    # sigma0 = rho_CT_exact(SA, CT, 0) - 1000

    return rho(SA, CT, 0.) - 1000


def sigma1(SA, CT):
    """
    Calculates potential density anomaly with reference pressure of 1000
    dbar, this being this particular potential density minus 1000 kg/m^3.
    This function has inputs of Absolute Salinity and Conservative Temperature.

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]

    Returns
    -------
    sigma1 : array_like
             potential density anomaly with [kg/m**3]
             respect to a reference pressure of 1000 dbar
    """

    # This function calculates sigma1 using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p.  If one wanted
    # to compute sigma1 with the full TEOS-10 Gibbs function expression for
    # density, the following lines of code will enable this.
    #
    # rho1 = rho_CT_exact(SA, CT, 1000)

    return rho(SA, CT, 1000.) - 1000


def sigma2(SA, CT):
    """
    Calculates potential density anomaly with reference pressure of 2000
    dbar, this being this particular potential density minus 1000 kg/m^3.
    This function has inputs of Absolute Salinity and Conservative Temperature.

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]

    Returns
    -------
    sigma2 : array_like
             potential density anomaly with [kg/m**3]
             respect to a reference pressure of 2000 dbar
    """

    # This function calculates sigma2 using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p.  If one wanted
    # to compute sigma2 with the full TEOS-10 Gibbs function expression for
    # density, the following lines of code will enable this.
    #
    # rho2 = rho_CT_exact(SA, CT, 2000.)

    return rho(SA, CT, 2000.) - 1000


def sigma3(SA, CT):
    """
    Calculates potential density anomaly with reference pressure of 3000
    dbar, this being this particular potential density minus 1000 kg/m^3.
    This function has inputs of Absolute Salinity and Conservative Temperature.

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]

    Returns
    -------
    sigma1 : array_like
             potential density anomaly with [kg/m**3]
             respect to a reference pressure of 3000 dbar
    """

    # This function calculates sigma3 using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p.  If one wanted
    # to compute sigma3 with the full TEOS-10 Gibbs function expression for
    # density, the following lines of code will enable this.
    #
    # rho3 = rho_CT_exact(SA, CT, 3000.)

    return rho(SA, CT, 3000.) - 1000


def sigma4(SA, CT):
    """
    Calculates potential density anomaly with reference pressure of 4000
    dbar, this being this particular potential density minus 1000 kg/m^3.
    This function has inputs of Absolute Salinity and Conservative Temperature.

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]

    Returns
    -------
    sigma1 : array_like
             potential density anomaly with [kg/m**3]
             respect to a reference pressure of 4000 dbar
    """

    # This function calculates sigma3 using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p.  If one wanted
    # to compute sigma3 with the full TEOS-10 Gibbs function expression for
    # density, the following lines of code will enable this.
    #
    # rho4 = rho_CT_exact(SA, CT, 4000.)

    return rho(SA, CT, 4000.) - 1000


@match_args_return
def sound_speed(SA, CT, p):
    """
    Calculates the speed of sound in seawater.  This function has inputs of
    Absolute Salinity and Conservative Temperature.  This function uses the
    computationally-efficient 48-term expression for density in terms of SA,
    CT and p (McDougall et al., 2011).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    sound_speed : array_like
                  speed of sound in seawater [m/s]

    Notes
    -----
    Approximate with a r.m.s. of 6.7 cm s^-1.

    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel"
    described in McDougall et al. (2011).  The GSW library function
    "infunnel(SA, CT, p)" is available to be used if one wants to test if
    some of one's data lies outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqn. (2.17.1).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
      computationally efficient 48-term expression for the density of
      seawater in terms of Conservative Temperature, and related properties
      of seawater.
    """

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    dvden_dp = (c01 + CT * (c02 + c03 * CT) + SA * (c04 + c05 * CT) + p *
                (c06 + CT * (c07 + c08 * CT) + c09 * SA))

    dvnum_dp = (c10 + CT * (c11 + CT * (c12 + c13 * CT)) + SA *
                (c14 + c15 * CT) + p *
                (c16 + CT * (c17 + c18 * CT + c19 * SA) + p *
                 (c20 + c21 * CT)))

    drho_dp = ((dvden_dp * v_hat_numerator(*args) - dvnum_dp *
               v_hat_denominator(*args)) / (v_hat_numerator(*args) *
               v_hat_numerator(*args)))

    return 100 * np.sqrt(1. / drho_dp)


@match_args_return
def specvol(SA, CT, p):
    """
    Calculates specific volume from Absolute Salinity, Conservative
    Temperature and pressure, using the computationally-efficient 48-term
    expression for density (McDougall et al., 2011).

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    specvol : array_like
              specific volume [m**3/kg]

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA,CT,p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqn. (2.7.2).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)

    # This function calculates specvol using the computationally-efficient
    # 48-term expression for density in terms of SA, CT and p. If one wanted to
    # compute specvol from SA, CT, and p with the full TEOS-10 Gibbs function,
    # the following lines of code will enable this.
    #
    # pt = pt_from_CT(SA, CT)
    # t = pt_from_t(SA, pt, 0, p)
    # specvol = specvol_t_exact(SA, t, p)
    #
    # or call the following, it is identical to the lines above.
    #
    # specvol = specvol_CT_exact(SA, CT, p)

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    return v_hat_numerator(*args) / v_hat_denominator(*args)


@match_args_return
def specvol_anom(SA, CT, p):
    """
    Calculates specific volume anomaly from Absolute Salinity, Conservative
    Temperature and pressure.  It uses the computationally-efficient 48-term
    expression for density as a function of SA, CT and p (McDougall et al.,
    2011).  The reference value of Absolute Salinity is SSO and the reference
    value of Conservative Temperature is equal to 0 degrees C.

    Parameters
    ----------
    SA : array_like
         Absolute Salinity  [g/kg]
    CT : array_like
         Conservative Temperature [:math:`^\circ` C (ITS-90)]
    p : array_like
        sea pressure [dbar]

    Returns
    -------
    specvol_anom : array_like
                   specific volume anomaly [m**3/kg]

    Notes
    -----
    The 48-term equation has been fitted in a restricted range of parameter
    space, and is most accurate inside the "oceanographic funnel" described in
    McDougall et al. (2011).  The GSW library function "infunnel(SA,CT,p)" is
    available to be used if one wants to test if some of one's data lies
    outside this "funnel".

    Examples
    --------
    TODO

    References
    ----------
    .. [1] IOC, SCOR and IAPSO, 2010: The international thermodynamic equation
       of seawater - 2010: Calculation and use of thermodynamic properties.
       Intergovernmental Oceanographic Commission, Manuals and Guides No. 56,
       UNESCO (English), 196 pp. See Eqn. (3.7.3).

    .. [2] McDougall T.J., P.M. Barker, R. Feistel and D.R. Jackett, 2011:  A
       computationally efficient 48-term expression for the density of
       seawater in terms of Conservative Temperature, and related properties
       of seawater.
    """

    SA = np.maximum(SA, 0)
    sqrtSA = np.sqrt(SA)
    args = SA, CT, p, sqrtSA

    # This function calculates specvol_anom using the computationally-
    # efficient 48-term expression for density in terms of SA, CT and p.  If
    # one wanted to compute specvol_anom from SA, CT, and p with the full
    # TEOS-10 Gibbs function, the following lines of code will enable this.
    #
    # pt = pt_from_CT(SA, CT)
    # t = pt_from_t(SA, pt, 0, p)
    # specvol_anom = specvol_anom_t_exact(SA, t, p)
    #
    # or call the following, it is identical to the lines above.
    #
    # specvol_anom = specvol_anom_CT_exact(SA, CT, p)

    return (v_hat_numerator(*args) / v_hat_denominator(*args) -
            specvol_SSO_0_p(p))


if __name__ == '__main__':
    import doctest
    doctest.testmod()
