Within Danomics Flows you can add a Python block, such as shown below:

To utilize the Danomics Petrophysics library you will need to import it. This can be done as shown above with the statement on line 2:

from src.main.python import petrophysics

Alternatively, the import can be performed for only specific functions. For example, to import the function for Vclay from Gamma Ray and Vclay from Neutron Density you’d use a statement like:

from src.main.python.petrophysics import calculate_nd_clay, calculate_gr_clay

Regardless of the import style it could then be used in the execute block. For the former, calling the function would look like:

vclay_gr = petrophysics.calculate_gr_clay(gr, gr_clean, gr_clay)
vclay_nd = petrophysics.calculate_nd_clay(rhob, nphi, rhob_matrix, rhob_clean, rhob_clay, nphi_matrix, nphi_clean, nphi_clay)

Similarly, in the latter the same commands would be:

vclay_gr = calculate_gr_clay(gr, gr_clean, gr_clay)
vclay_nd = calculate_nd_clay(rhob, nphi, rhob_matrix, rhob_clean, rhob_clay, nphi_matrix, nphi_clean, nphi_clay)

Notes on usage

Remember that Danomics treats well log curves as numpy arrays, and these functions take advantage of that. This significantly increases the speed and for many operations removes the need to use for loops to iterate through arrays. However, it also requires that you ensure all your arrays are the same length – and although this is the case by default in Danomics if you work with array slices care should be taken. If you use arrays of different lengths as arguments then you’ll receive assertion errors.

Many of these functions take both curves and parameters. The curves, as mentioned above, are arrays. The params you will likely use (at least in this version) will likely be scalar values. This is fine and the library accommodates this. If you do have an array of parameter values, this will work as well, as long as that array has the same length as the input curves.

The code is given below.

Code

"""
Date: May 17th, 2021
Copyright Danomics LLC, 2021. All rights reserved.
Provided for use with Danomics software only.
No authorized for distribution or publication.

@author: CameronSnow
"""

import numpy as np


#################################
###  Helper Conversions       ###
#################################
def degf2degc(value):
    
    """
    Convert value from degrees Farenheit to Celsius.
    """
    return (value-32)*5/9

def mpsi2gpa(value):
    """
    Convert value from degrees milli-pound force per square inch to Gigapascale.
    """
    return value * 6.89475729

def psi2ppg(value, depth_curve):
    """
    Convert value from psi to pounds per gallon.
    """
    return (value / depth_curve)/0.052

def ppg2psi(value, depth_curve):
    """
    Convert value from pounds per gallon to psi.
    """
    return (value*depth_curve)*0.052

def fs2kms(value):
    """
    Convert value from feet/second to km/second.
    """
    return value * 0.000305

def kms2fs(value):
    """
    Convert value from km/second to feet/second.
    """
    return value * 3280.84

def api2gcc(value):
    """
    Convert value from API gravity to g/cm3.
    """
    return 141.5/(value+131.5)

def psi2mpa(value):
    """
    Convert value from psi to megapascale.
    """
    return value*6.894754*0.001

def mpa2psi(value):
    """
    Convert value from megapascale to psi.
    """
    return value/6.894754/0.001

def scfbbl2ll(value):
    """
    Convert value from scf/barrel to liters/liter.
    """
    return value/5.6145

def usf2fs(value):
    """
    Convert value from microseconds/foot to feet/second.
    """
    return 1/value*1000000


#################################
### Clay Volume Functions     ###
#################################
def calculate_single_clay(curve, clean, clay):
    """
    Calculate clay volume by a single clay method

    Parameters
    ----------
    curve : ARRAY
        Curve to use.
    clean : FLOAT
        Clean point for curve.
    clay : FLOAT
        Clay point for curve.

    Returns
    -------
    vclay : ARRAY
        Clay Volume.

    """
    if np.isscalar(clay):
        temp = np.zeros(len(curve))
        temp[:] = clay
        clay = temp
    if np.isscalar(clean):
        temp = np.zeros(len(curve))
        temp[:] = clean
        clean = temp
    assert len(clean) == len(curve), "Input clean array must be of same length as curve." 
    assert len(clay) == len(curve), "Input clay array must be of same length as curve."   
      
    vclay = (curve - clean) / (clay-clean)
    vclay = np.minimum(np.maximum(vclay, 0), 1)
    return vclay

def calculate_gr_clay(gr, gr_clean, gr_clay):
    """
    Calculate clay volume by gamma ray method

    Parameters
    ----------
    gr : ARRAY
        Gamma Ray curve.
    gr_clean : FLOAT
        Clean value for gamma ray curve.
    gr_clay : FLOAT
        Clay value for gamma ray curve.

    Returns
    -------
    vcl_gr : ARRAY
        Clay Volume.

    """
    vcl_gr = calculate_single_clay(gr, gr_clean, gr_clay)
    return vcl_gr

def calculate_gr_clay_by_method(gr, gr_clean, gr_clay, method):
    """
    Calculate clay volume from gamma ray by a specific method.

    Parameters
    ----------
    gr : ARRAY
        Gamma Ray curve.
    gr_clean : TYPE
        Clean value for gamma ray curve.
    gr_clay : FLOAT
        Clay value for gamma ray curve.
    method : STRING
        Options are 'larionov_young', 'larionov_old', 'clavier', 'steiber'.

    Returns
    -------
    vcl : ARRAY
        Clay Volume.

    """
    if method == 'larionov_young':
        vcl = 0.083*(2**(3.7*((gr - gr_clean) / (gr_clay - gr_clean)))-1)
    elif method == 'larionov_old':
        vcl =0.33*(2**(2*((gr - gr_clean) / (gr_clay - gr_clean)))-1)
    elif method == 'clavier':
        vcl = 1.7-(3.38-(((gr - gr_clean) / (gr_clay - gr_clean))+0.7)**2)**0.5
    elif method == 'steiber':
        vcl = 0.5*((gr - gr_clean) / (gr_clay - gr_clean))/(1.5-((gr - gr_clean) / (gr_clay - gr_clean)))

    vcl = np.minimum(np.maximum(vcl, 0), 1)
    return vcl

def calculate_sp_clay(sp, sp_clean, sp_clay):
    """
    Calculate clay volume by gamma ray method

    Parameters
    ----------
    sp : ARRAY
        SP curve.
    sp_clean : FLOAT
        Clean value for SP curve.
    sp_clay : FLOAT
        Clay value for SP curve.

    Returns
    -------
    vcl_sp : ARRAY
        Clay Volume.

    """
    vcl_sp = calculate_single_clay(sp, sp_clean, sp_clay)
    return vcl_sp


def calculate_nphi_clay(nphi, nphi_clean, nphi_clay):
    """
    Calculate clay volume by neutron porosity method

    Parameters
    ----------
    nphi : ARRAY
        Neutron curve.
    nphi_clean : FLOAT
        Neutron curve clean value.
    nphi_clay : FLOAT
        Neutron curve clay value.

    Returns
    -------
    vcl_nphi : ARRAY
        Clay Volume.

    """
    if np.isscalar(nphi_clay):
        temp = np.zeros(len(nphi))
        temp[:] = nphi_clay
        nphi_clay = temp
    if np.isscalar(nphi_clean):
        temp = np.zeros(len(nphi))
        temp[:] = nphi_clean
        nphi_clean = temp
    assert len(nphi_clean) == len(nphi), "Input clean array must be of same length as curve." 
    assert len(nphi_clay) == len(nphi), "Input clay array must be of same length as curve." 
    
    vcl_nphi = np.sqrt((nphi/nphi_clay)*((nphi-nphi_clean)/(nphi_clay-nphi_clean)))
    vcl_nphi = np.minimum(np.maximum(vcl_nphi, 0), 1)
    return vcl_nphi


def calculate_dual_clay(c1, c2, c1cl1, c1cl2, c1clay, c2cl1, c2cl2, c2clay):
    """
    Calculate clay volume by a dual clay method.

    Parameters
    ----------
    c1 : ARRAY
        Curve one.
    c2 : ARRAY
        Curve two.
    c1cl1 : FLOAT
        Matrix point of curve one.
    c1cl2 : FLOAT
        Clean point of curve one.
    c1clay : FLOAT
        Clay point of curve one.
    c2cl1 : FLOAT
        Matrix point of curve two.
    c2cl2 : FLOAT
        Clean point of curve two.
    c2clay : FLOAT
        CLay point of curve two.

    Returns
    -------
    vclay : ARRAY
        Clay volume.

    """
    assert len(c1)== len(c2), "Input curves must be of same length."
    if np.isscalar(c1cl1):
        temp = np.zeros(len(c1))
        temp[:] = c1cl1
        c1cl1 = temp
    if np.isscalar(c1cl2):
        temp = np.zeros(len(c1))
        temp[:] = c1cl2
        c1cl2 = temp
    if np.isscalar(c1clay):
        temp = np.zeros(len(c1))
        temp[:] = c1clay
        c1clay = temp
    if np.isscalar(c2cl1):
        temp = np.zeros(len(c1))
        temp[:] = c2cl1
        c2cl1 = temp
    if np.isscalar(c2cl2):
        temp = np.zeros(len(c1))
        temp[:] = c2cl2
        c2cl2 = temp
    if np.isscalar(c2clay):
        temp = np.zeros(len(c1))
        temp[:] = c2clay
        c2clay = temp        
    assert len(c1cl1) == len(c1), "Input curve one,clean one array must be of same length as curve." 
    assert len(c1cl2) == len(c1), "Input curve one,clean two array must be of same length as curve."
    assert len(c1clay) == len(c1), "Input curve one,clay array must be of same length as curve."
    assert len(c2cl1) == len(c1), "Input curve two,clean one array must be of same length as curve." 
    assert len(c2cl2) == len(c1), "Input curve two,clean two array must be of same length as curve."
    assert len(c2clay) == len(c1), "Input curve two,clay array must be of same length as curve."
    
    vclay = (((c1cl2 - c1cl1)*(c2 - c2cl1) - (c1 - c1cl1)*(c2cl2 - c2cl1)) / 
             ((c1cl2 - c1cl1)*(c2clay - c2cl1)-(c1clay - c1cl1)*(c2cl2 - c2cl1)))
    vclay = np.minimum(np.maximum(vclay, 0),1)
    return vclay

def calculate_nd_clay(rhob, nphi, rhob_matrix, rhob_clean, rhob_clay, nphi_matrix, nphi_clean, nphi_clay):
    """
    Calculate vclay by neutron density method

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    nphi : ARRAY
        Neutron curve.
    rhob_matrix : FLOAT
        Matrix density value.
    rhob_clean : FLOAT
        RhoB clean one point.
    rhob_clay : FLOAT
        Clay density value.
    nphi_matrix : FLOAT
        Neutron matrix value.
    nphi_clean : FLOAT
        Neutron clean one point.
    nphi_clay : FLOAT
        Clay neutron value.

    Returns
    -------
    vcl_nd : ARRAY
        Clay volume.

    """
    vcl_nd = calculate_dual_clay(rhob, nphi, rhob_matrix, rhob_clean, rhob_clay, nphi_matrix, nphi_clean, nphi_clay)
    return vcl_nd


def calculate_sd_clay(rhob, dt, rhob_matrix, rhob_clean, rhob_clay, dt_matrix, dt_clean, dt_clay):
    """
    Calculate vclay by sonic density method

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    dt : ARRAY
        Sonic curve.
    rhob_matrix : FLOAT
        Matrix density value.
    rhob_clean : FLOAT
        RhoB clean one point.
    rhob_clay : FLOAT
        Clay density value.
    dt_matrix : FLOAT
        Sonic matrix value.
    dt_clean : FLOAT
        Sonic clean one point.
    dt_clay : FLOAT
        Clay sonic value.

    Returns
    -------
    vcl_sd : ARRAY
        Clay volume.

    """
    vcl_sd = calculate_dual_clay(rhob, dt, rhob_matrix, rhob_clean, rhob_clay, dt_matrix, dt_clean, dt_clay)
    return vcl_sd


def calculate_ns_clay(nphi, dt, nphi_matrix, nphi_clean, nphi_clay, dt_matrix, dt_clean, dt_clay):
    """
    Calculate vclay by neutron sonic method

    Parameters
    ----------
    nphi : ARRAY
        Neutron curve.
    dt : ARRAY
        Sonic curve.
    nphi_matrix : FLOAT
        Matrix neutron value.
    nphi_clean : FLOAT
        Neutron clean one point.
    nphi_clay : FLOAT
        Clay neutron value.
    dt_matrix : FLOAT
        Sonic matrix value.
    dt_clean : FLOAT
        Sonic clean one point.
    dt_clay : FLOAT
        Clay sonic value.

    Returns
    -------
    vcl_ns : ARRAY
        Clay volume.

    """
    vcl_ns = calculate_dual_clay(nphi, dt, nphi_matrix, nphi_clean, nphi_clay, dt_matrix, dt_clean, dt_clay)
    return vcl_ns


def calculate_vwcl(*args):
    """
    Calculate the minimum from multiple input clay volume curves

    Parameters
    ----------
    *args : ARRAY
        Two or more clay volume curves.

    Returns
    -------
    vwcl : ARRAY
        Volume wet clay.

    """
    if len(args) == 1:
        vwcl = args
        return vwcl
    else:
        for i in range(len(args)-1):
            vwcl = np.minimum(args[i], args[i+1])
        return vwcl

#################################
### TOC Analysis Functions    ###
#################################
def calculate_schmoker_toc(rhob, schmoker_a=154.497, schmoker_b=57.261):
    """
    Calculate TOC wt. % via Schmoker method

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    schmoker_a : FLOAT, optional
        Schmoker a parameter. The default is 154.497.
    schmoker_b : FLOAT, optional
        Schmoker b parameter. The default is 57.261.

    Returns
    -------
    toc : Array
        Total Organic Carbon (wt %).

    """
    if np.isscalar(schmoker_a):
        temp = np.zeros(len(rhob))
        temp[:] = schmoker_a
        schmoker_a = temp
    if np.isscalar(schmoker_b):
        temp = np.zeros(len(rhob))
        temp[:] = schmoker_b
        schmoker_b = temp
    assert len(schmoker_a) == len(rhob), "Input schmoker_a array must be of same length as rhob curve." 
    assert len(schmoker_b) == len(rhob), "Input schmoker_b array must be of same length as rhob curve."
    toc = ((schmoker_a/rhob-schmoker_b)*.01)
    toc = np.minimum(np.maximum(toc, 0),1)
    return toc


def calculate_lom(vre):
    """
    Calculate level of Maturity from vitrinite reflectance

    Parameters
    ----------
    vre : FLOAT
        Vitrinite Reflectance.

    Returns
    -------
    FLOAT
        Level of Maturity.

    """
    return (-8.3180734+(53.367852*vre)-(45.319273*vre**2)+(13.422786*vre**3)) / (1+(1.0895416*vre)-(1.4275785*vre**2)+(0.54231042*vre**3))


def calculate_mod_passey_toc(resistivity, porosity, vre, rwno_clay):
    """
    Calculate TOC wt% via modified Passey method

    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total porosity curve.
    vre : FLOAT
        Vitrinite reflectance.
    rwno_clay : FLOAT
        Resistivity water in non-organic clay interval.

    Returns
    -------
    toc : ARRAY
        Total Organic Carbon (wt %).

    """
    assert len(resistivity) == len(porosity), "Resistivity and porosity arrays must be of same length"
    if np.isscalar(vre):
        temp = np.zeros(len(porosity))
        temp[:] = vre
        vre = temp
    if np.isscalar(rwno_clay):
        temp = np.zeros(len(porosity))
        temp[:] = rwno_clay
        rwno_clay = temp
    assert len(vre) == len(porosity), "Input vre array must be of same length as curves." 
    assert len(rwno_clay) == len(porosity), "Input rwno_clay array must be of same length as curves."
    
    toc_lom = calculate_lom(vre)
    toc_ro = rwno_clay / porosity

    toc = ((np.log10(resistivity) - np.log10(toc_ro)) * 10 ** (2.297-0.1688*toc_lom))/100
    toc = np.minimum(np.maximum(toc, 0),1)
    return toc

def calculate_faust_toc(dt, resistivity, depth_curve, fm=0.21):
    """
    Calculate TOC wt % by Faust method

    Parameters
    ----------
    dt : ARRAY
        Sonic curve.
    resistivity : ARRAY
        Deep Resistivity curve.
    depth_curve : ARRAY
        Depth curve.
    fm : FLOAT, optional
        Faust Modifier. The default is 0.21.

    Returns
    -------
    toc : ARRAY
        Total Organic Carbon (wt %).

    """
    assert len(depth_curve) == len(resistivity) == len(dt), "All curves must be of same length."
    if np.isscalar(fm):
        temp = np.zeros(len(depth_curve))
        temp[:] = fm
        fm = temp
    assert len(depth_curve) == len(fm), "Input fm array must be same length as input curves."
    dt_faust = 513.347/(depth_curve*resistivity)**(1/6)
    first = fm * ((dt - 56) / (189 - 56))
    last = (dt_faust - 56) / (189 - 56)
    toc = first - last
    toc = np.minimum(np.maximum(toc, 0),1)
    return toc

def calculate_passey_toc(resistivity, curve, resistivity_baseline, curve_baseline, vre, method):
    """
    Calculate TOC wt% by designated Passey overlay method

    Parameters
    ----------
    resistivity : ARRAY
        Deep Resistivity curve.
    curve : ARRAY
        Either a sonic, density, or neutron curve.
    resistivity_baseline : FLOAT
        Baseline value for resistivity in an inorganic shale interval.
    curve_baseline : FLOAT
        Baseline value for selected curve in an inorganic shale interval.
    vre : FLOAT
        Vitrinite reflectance.
    method : STRING
        Options are 'dt', 'rhob', or 'nphi'. Should correspond to curve entered.

    Returns
    -------
    toc : ARRAY
        Total Organic Carbon (wt %).

    """
    assert len(resistivity) == len(curve), "All curves must be of same length."
    assert method in ['dt', 'rhob', 'nphi'], "Method not recognized."
    if np.isscalar(resistivity_baseline):
        temp = np.zeros(len(curve))
        temp[:] = resistivity_baseline
        resistivity_baseline = temp
    if np.isscalar(curve_baseline):
        temp = np.zeros(len(curve))
        temp[:] = curve_baseline
        curve_baseline = temp
    if np.isscalar(vre):
        temp = np.zeros(len(curve))
        temp[:] = vre
        vre = temp
    assert len(curve) == len(resistivity_baseline), "Input resistivity_baseline array must be same length as input curves."
    assert len(curve) == len(curve_baseline), "Input curve_baseline array must be same length as input curves."
    assert len(curve) == len(vre), "Input vre array must be same length as input curves."
    
    toc_lom = calculate_lom(vre)
    
    if method == 'dt':
        xlogr = np.log10(resistivity/resistivity_baseline)+0.02 *(curve - curve_baseline)    
    elif method == 'rhob':
        xlogr = np.log10(resistivity/resistivity_baseline)+2.5 *(curve - curve_baseline)
    elif method == 'nphi':
        xlogr = np.log10(resistivity/resistivity_baseline)+4.0 *(curve - curve_baseline)
        
    toc = (xlogr*10**(2.297-0.1688*toc_lom))/100
    toc = np.minimum(np.maximum(toc, 0),1)
    return toc

def calculate_volume_kerogen(toc, ktoc = 0.80, method='slb', matrix_density = 2.65, dens_kerogen = 1.26):
    """
    Convert TOC in wt. % to kerogen vol. %.

    Parameters
    ----------
    toc : ARRAY
        Total organic carbon curve.
    ktoc : FLOAT, optional
        Kerogen to TOC ratio. The default is 0.80.
    method : STRING, optional
        Conversion method. Options are 'slb', 'danomics', 'standard'. The default is 'slb'.
    matrix_density : FLOAT, optional
        Matrix density. The default is 2.65.
    dens_kerogen : FLOAT, optional
        Kergogen density. The default is 1.26.

    Returns
    -------
    v_ker : TYPE
        DESCRIPTION.

    """
    assert method in ['slb', 'danomics', 'standard'], "Method not recognized."
    if np.isscalar(ktoc):
        temp = np.zeros(len(toc))
        temp[:] = ktoc
        ktoc = temp
    if np.isscalar(matrix_density):
        temp = np.zeros(len(toc))
        temp[:] = matrix_density
        matrix_density = temp
    if np.isscalar(dens_kerogen):
        temp = np.zeros(len(toc))
        temp[:] = dens_kerogen
        dens_kerogen = temp
    assert len(toc) == len(ktoc), "Input ktoc array must be of same length as toc curve."
    assert len(toc) == len(matrix_density), "Input matrix_density array must be of same length as toc curve."
    assert len(toc) == len(dens_kerogen), "Input dens_kerogen array must be of same length as toc curve."
        
    if method == 'slb':
        v_ker = (toc * ktoc * matrix_density / dens_kerogen)
    elif method == 'danomics':
        v_ker = ((toc/ktoc)/dens_kerogen)/(((toc/ktoc)/dens_kerogen) + ((1-(toc/ktoc))/matrix_density))
    elif method == "standard":
        v_ker = (toc * matrix_density / dens_kerogen)

    return v_ker


#################################
###   Porosity Functions      ###
#################################
def calculate_density_porosity(rhob, rho_matrix=2.65, rho_fluid=1, phi_max=0.30):
    """
    Calculate density porosity

    Parameters
    ----------
    rhob : ARRAY
        Bulk Density Curve.
    rho_matrix : FLOAT, optional
        Matrix density. The default is 2.65.
    rho_fluid : FLOAT, optional
        Fluid density. The default is 1.
    phi_max : Float, optional
        Maximum allowed porosity. The default is 0.30.

    Returns
    -------
    phit : ARRAY
        Total Porosity.

    """
    if np.isscalar(rho_matrix):
        temp = np.zeros(len(rhob))
        temp[:] = rho_matrix
        rho_matrix = temp
    if np.isscalar(rho_fluid):
        temp = np.zeros(len(rhob))
        temp[:] = rho_fluid
        rho_fluid = temp
    if np.isscalar(phi_max):
        temp = np.zeros(len(rhob))
        temp[:] = phi_max
        phi_max = temp
    assert len(rhob) == len(rho_matrix), "Input rho_matrix array must be of same length as rhob curve."
    assert len(rhob) == len(rho_fluid), "Input rho_fluid array must be of same length as rhob curve."
    assert len(rhob) == len(phi_max), "Input phi_max array must be of same length as rhob curve."
    
    phit = (rho_matrix - rhob) / (rho_matrix - rho_fluid)
    phit = np.maximum(np.minimum(phit, phi_max), 0.0001)
    return phit

def calculate_nd_porosity(rhob, nphi, rho_matrix=2.65, rho_fluid=1, phi_max=0.30):
    """
    Calculate neutron-density crossplot porosity

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    nphi : ARRAY
        Neutron porosity curve.
    rho_matrix : FLOAT, optional
        Matrix density. The default is 2.65.
    rho_fluid : FLOAT, optional
        Fluid density. The default is 1.
    phi_max : TYPE, optional
        Maximum allowed porosity. The default is 0.30.

    Returns
    -------
    phit : ARRAY
        Total porosity.

    """
    assert len(rhob) == len(nphi), "Rhob and Nphi curves must be of same length."
    if np.isscalar(rho_matrix):
        temp = np.zeros(len(rhob))
        temp[:] = rho_matrix
        rho_matrix = temp
    if np.isscalar(rho_fluid):
        temp = np.zeros(len(rhob))
        temp[:] = rho_fluid
        rho_fluid = temp
    if np.isscalar(phi_max):
        temp = np.zeros(len(rhob))
        temp[:] = phi_max
        phi_max = temp
    assert len(rhob) == len(rho_matrix), "Input rho_matrix array must be of same length as rhob curve."
    assert len(rhob) == len(rho_fluid), "Input rho_fluid array must be of same length as rhob curve."
    assert len(rhob) == len(phi_max), "Input phi_max array must be of same length as rhob curve."
    
    dphi = (rho_matrix - rhob) / (rho_matrix - rho_fluid)
    dphi = np.maximum(np.minimum(dphi, phi_max), 0.0001)
    phit = (nphi + dphi)/2
    phit = np.maximum(np.minimum(phit, phi_max), 0.0001)
    return phit


def calculate_sonic_porosity(dt, dt_matrix=56, dt_fluid=189, dt_shale=120, phi_max = 0.30):
    """
    Calculate sonic porosity.

    Parameters
    ----------
    dt : ARRAY
        Compressional sonic curve.
    dt_matrix : ARRAY, optional
        Sonic matrix value. The default is 56.
    dt_fluid : FLOAT, optional
        Sonic fluid value. The default is 189.
    dt_shale : FLOAT, optional
        Sonic value in shale. The default is 120.
    phi_max : FLOAT, optional
        Maximum allowed porosity. The default is 0.30.

    Returns
    -------
    phit : ARRAY
        Total porosity.

    """
    if np.isscalar(dt_matrix):
        temp = np.zeros(len(dt))
        temp[:] = dt_matrix
        dt_matrix = temp
    if np.isscalar(dt_fluid):
        temp = np.zeros(len(dt))
        temp[:] = dt_fluid
        dt_fluid = temp
    if np.isscalar(dt_shale):
        temp = np.zeros(len(dt))
        temp[:] = dt_shale
        dt_shale = temp
    if np.isscalar(phi_max):
        temp = np.zeros(len(dt))
        temp[:] = phi_max
        phi_max = temp
    assert len(dt) == len(dt_matrix), "Input dt_matrix array must be of same length as dt curve."
    assert len(dt) == len(dt_fluid), "Input dt_fluid array must be of same length as dt curve."
    assert len(dt) == len(dt_shale), "Input dt_shale array must be of same length as dt curve."
    assert len(dt) == len(phi_max), "Input phi_max array must be of same length as dt curve."

    phit = ((dt - dt_matrix) / (dt_fluid - dt_matrix) / np.maximum(1, dt_shale/100))
    phit = np.maximum(np.minimum(phit, phi_max), 0.0001)
    return phit
    

def calculate_deterministic_grain_density(rhob, nphi, vclay, rho_matrix=2.65, rho_fluid=1, phi_shale = 0.10):
    """
    Calculate a deterministic grain density.

    Parameters
    ----------
    rhob : ARRAY
        DESCRIPTION.
    nphi : ARRAY
        DESCRIPTION.
    vclay : ARRAY
        DESCRIPTION.
    rho_matrix : FLOAT, optional
        DESCRIPTION. The default is 2.65.
    rho_fluid : FLOAT, optional
        DESCRIPTION. The default is 1.
    phi_shale : FLOAT, optional
        DESCRIPTION. The default is 0.10.

    Returns
    -------
    grain_density : ARRAY
        Deterministic grain_density.

    """
    assert len(rhob) == len(nphi) == len(vclay), "All input curves must be of same length."
    if np.isscalar(rho_matrix):
        temp = np.zeros(len(rhob))
        temp[:] = rho_matrix
        rho_matrix = temp
    if np.isscalar(rho_fluid):
        temp = np.zeros(len(rhob))
        temp[:] = rho_fluid
        rho_fluid = temp
    if np.isscalar(phi_shale):
        temp = np.zeros(len(rhob))
        temp[:] = phi_shale
        phi_shale = temp
    assert len(rhob) == len(rho_matrix), "Input rho_matrix array must be of same length as rhob curve."
    assert len(rhob) == len(rho_fluid), "Input rho_fluid array must be of same length as rhob curve."
    assert len(rhob) == len(phi_shale), "Input phi_max array must be of same length as rhob curve."
    
    dphi = (rho_matrix - rhob) / (rho_matrix - rho_fluid)
    phix = np.sqrt((nphi**2 + dphi**2) / 2) - (vclay * phi_shale)
    grain_density = (rhob-phix)/(1-phix)
    return grain_density


def calculate_grain_density_porosity(rhob, grain_density, rho_fluid=1, phi_max=0.30):
    """
    Calculate density porosity using grain density for rho_matrix.

    Parameters
    ----------
    rhob : ARRAY
        Bulk Density Curve.
    grain_density : ARRAY
        Grain Density Curve.
    rho_fluid : FLOAT, optional
        Fluid density. The default is 1.
    phi_max : Float, optional
        Maximum allowed porosity. The default is 0.30.

    Returns
    -------
    phit : ARRAY
        Total Porosity.

    """
    assert len(rhob) == len(grain_density), "Rhob and Grain Density curves must be of same length."
    if np.isscalar(rho_fluid):
        temp = np.zeros(len(rhob))
        temp[:] = rho_fluid
        rho_fluid = temp
    if np.isscalar(phi_max):
        temp = np.zeros(len(rhob))
        temp[:] = phi_max
        phi_max = temp
    assert len(rhob) == len(rho_fluid), "Input rho_fluid array must be of same length as rhob curve."
    assert len(rhob) == len(phi_max), "Input phi_max array must be of same length as rhob curve."    
    
    phit = (grain_density - rhob) / (grain_density - rho_fluid)
    phit = np.maximum(np.minimum(phit, phi_max), 0.0001)
    return phit

def calculate_phie_porosity(phit, vclay, phi_shale=0.10, phi_max = 0.30):
    """
    Clay correct total porosity to effective porosity.

    Parameters
    ----------
    phit : ARRAY
        Total porosity.
    vclay : ARRAY
        Clay volume.
    phi_shale : FLOAT, optional
        Porosity fraction of shale. The default is 0.10.
    phi_max : FLOAT, optional
        Maximum allowed porosity. The default is 0.30.

    Returns
    -------
    phie : ARRAY
        Effective Porosity.

    """
    assert len(phit) == len(vclay), "Input curves must be of same length."
    if np.isscalar(phi_shale):
        temp = np.zeros(len(phit))
        temp[:] = phi_shale
        phi_shale = temp
    if np.isscalar(phi_max):
        temp = np.zeros(len(phit))
        temp[:] = phi_max
        phi_max = temp
    assert len(phit) == len(phi_max), "Input phi_max array must be of same length as phit curve." 
    assert len(phit) == len(phi_shale), "Input phi_shale array must be of same length as phit curve."
    
    phie = phit - (vclay * phi_shale)
    phie = np.minimum(np.maximum(phie, 0.0001), phi_max)
    return phie
    

#################################
### Water Saturation Functions###
#################################

def calculate_temperature_by_gradient(depth_curve, surf_temp = 55, gradient = 1.1):
    """
    Calculate Temperature Curve from geothermal gradient
    Parameters
    ----------
    depth_curve : ARRAY
        Depth curve in feet.
    surf_temp : FLOAT, optional
        Surface temperature at well location. The default is 55.
    gradient : FLOAT, optional
        Geothermal gradient in degrees Farenheit per 100 ft. The default is 1.1.

    Returns
    -------
    temperature : ARRAY
        TEMPERATURE curve in degrees Farenheit.

    """
    if np.isscalar(surf_temp):
        temp = np.zeros(len(depth_curve))
        temp[:] = surf_temp
        surf_temp = temp
    if np.isscalar(gradient):
        temp = np.zeros(len(depth_curve))
        temp[:] = gradient
        gradient = temp
    assert len(depth_curve) == len(surf_temp), "Input surf_temp array must be of same length as depth curve."
    assert len(depth_curve) == len(gradient), "Input gradient array must be of same length as depth curve."
    
    temperature = (surf_temp + depth_curve * gradient/100)
    return temperature


def calculate_rw_ft(temperature, rw_surf, surf_temp):
    """
    Calculate the formation water resistivity at formation temperature.

    Parameters
    ----------
    rw_surf : FLOAT
        Formation water resistivity at reference surface temperature.
    surf_temp : FLOAT
        Reference surface temperature for Rw.
    temperature : ARRAY
        Temperature curve.

    Returns
    -------
    rw_ft : ARRAY
        Formation water resistivity curve.

    """
    if np.isscalar(surf_temp):
        temp = np.zeros(len(temperature))
        temp[:] = surf_temp
        surf_temp = temp
    if np.isscalar(rw_surf):
        temp = np.zeros(len(temperature))
        temp[:] = rw_surf
        rw_surf = temp        
    assert len(temperature) == len(surf_temp), "Input surf_temp array must be of same length as temperature curve."
    assert len(temperature) == len(rw_surf), "Input rw_surf array must be of same length as temperature curve."
        
    rw_ft = (400000 / temperature / ((400000 / surf_temp) / ((rw_surf)**1.14)))**0.88
    return rw_ft


def calculate_archie_sw(resistivity, porosity, rw_ft, a=1, m=2, n=2, use_variable_m = False):
    """
    Calculate water saturation by Archie's equation.
    
    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total or effective porosity curve.
    rw_ft : ARRAY
        Formation water resitivity at formation temperature.
    a : FLOAT, optional
        Tortuosity factor. The default is 1.
    m : FLOAT, optional
        Cementation exponent. The default is 2.
    n : FLOAT, optional
        Saturation exponent. The default is 2.
    use_variable_m : BOOL, optional
        Use a variable 'm' based on Shell method for cementation exponent. The default is False.

    Returns
    -------
    ARRAY
        Water Saturation Curve.
    """
    assert len(resistivity) == len(porosity) == len(rw_ft), "All input curves must be of same length."
    if np.isscalar(a):
        temp = np.zeros(len(porosity))
        temp[:] = a
        a = temp
    if np.isscalar(m):
        temp = np.zeros(len(porosity))
        temp[:] = m
        m = temp
    if np.isscalar(n):
        temp = np.zeros(len(porosity))
        temp[:] = n
        n = temp
    assert len(porosity) == len(a), "Input a array must have same length as input curves."
    assert len(porosity) == len(m), "Input m array must have same length as input curves."
    assert len(porosity) == len(n), "Input n array must have same length as input curves."

    if use_variable_m is True:
        m_curve = 1.87 + (1.9 * porosity)
    else:
        m_curve = m

    sw = ((a * rw_ft/(resistivity * porosity**m_curve))**(1/n))
    sw = np.minimum(np.maximum(sw, 0),1)
    return sw


def calculate_simandoux_sw(resistivity, porosity, vclay, rw_ft, a=1, m=2, n=2, use_variable_m = False, Rt_clay=4):
    """
    Calculate water saturation via Simandoux method.
    
    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total or effective porosity curve.
    vclay : ARRAY
        Clay volume curve.
    rw_ft: Array
        Formation water resitivity at formation temperature.
    a : FLOAT, optional
        Tortuosity factor. The default is 1.
    m : FLOAT, optional
        Cementation exponent. The default is 2.
    n : FLOAT, optional
        Saturation exponent. The default is 2.
    use_variable_m : BOOL, optional
        Use a variable 'm' based on Shell method for cementation exponent. The default is False.
    Rt_clay : FLOAT, optional
        Clay resistivity. The default is 4.

    Returns
    -------
    ARRAY
        Water Saturation Curve.
    """
    assert len(resistivity) == len(porosity) == len(rw_ft) == len(vclay), "All input curves must be of same length."
    if np.isscalar(a):
        temp = np.zeros(len(porosity))
        temp[:] = a
        a = temp
    if np.isscalar(m):
        temp = np.zeros(len(porosity))
        temp[:] = m
        m = temp
    if np.isscalar(n):
        temp = np.zeros(len(porosity))
        temp[:] = n
        n = temp
    if np.isscalar(Rt_clay):
        temp = np.zeros(len(porosity))
        temp[:] = Rt_clay
        Rt_clay = temp
    assert len(porosity) == len(a), "Input a array must have same length as input curves."
    assert len(porosity) == len(m), "Input m array must have same length as input curves."
    assert len(porosity) == len(n), "Input n array must have same length as input curves."
    assert len(porosity) == len(Rt_clay), "Input Rt_clay array must have same length as input curves."
       
    if use_variable_m is True:
        m_curve = 1.87 + (1.9 * porosity)
    else:
        m_curve = m
    
    sw = (a * rw_ft/(2*porosity**m_curve)) *(np.sqrt(((vclay/Rt_clay)**2) + ((4*porosity**m_curve)/(a * rw_ft* resistivity))) - vclay/Rt_clay)   
    sw = np.minimum(np.maximum(sw, 0),1)
    return sw


def calculate_mod_simandoux_sw(resistivity, porosity, vclay, rw_ft, a=1, m=2, n=2, use_variable_m = False, Rt_clay=4):
    """
    Calculate water saturation via Modified Simandoux method.
    
    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total or effective porosity curve.
    vclay : ARRAY
        Clay volume curve.
    rw_ft: Array
        Formation water resitivity at formation temperature.
    a : FLOAT, optional
        Tortuosity factor. The default is 1.
    m : FLOAT, optional
        Cementation exponent. The default is 2.
    n : FLOAT, optional
        Saturation exponent. The default is 2.
    use_variable_m : BOOL, optional
        Use a variable 'm' based on Shell method for cementation exponent. The default is False.
    Rt_clay : FLOAT, optional
        Clay resistivity. The default is 4.

    Returns
    -------
    ARRAY
        Water Saturation Curve.
    """
    assert len(resistivity) == len(porosity) == len(rw_ft) == len(vclay), "All input curves must be of same length."
    if np.isscalar(a):
        temp = np.zeros(len(porosity))
        temp[:] = a
        a = temp
    if np.isscalar(m):
        temp = np.zeros(len(porosity))
        temp[:] = m
        m = temp
    if np.isscalar(n):
        temp = np.zeros(len(porosity))
        temp[:] = n
        n = temp
    if np.isscalar(Rt_clay):
        temp = np.zeros(len(porosity))
        temp[:] = Rt_clay
        Rt_clay = temp
    assert len(porosity) == len(a), "Input a array must have same length as input curves."
    assert len(porosity) == len(m), "Input m array must have same length as input curves."
    assert len(porosity) == len(n), "Input n array must have same length as input curves."
    assert len(porosity) == len(Rt_clay), "Input Rt_clay array must have same length as input curves."

    if use_variable_m is True:
        m_curve = 1.87 + (1.9 * porosity)
    else:
        m_curve = np.zeros(len(porosity))
        m_curve[:] = m  ###Need to pay attention to this when working with arrays for params
    
    sw = np.empty(len(porosity))
    sw[:] = np.nan
    
    for i in range(len(porosity)):
        if vclay[i] < 1:
            sw[i] = ((((vclay[i]/Rt_clay[i])**2 + 4*porosity[i]**m_curve[i]/(a[i]* rw_ft[i]*(1-vclay[i])*resistivity[i]))**(1/n[i])) - vclay[i]/Rt_clay[i])/(2*porosity[i]**m_curve[i]/(a[i]*rw_ft[i]*(1-vclay[i])))
        else: 
            sw[i] =1
        
    sw = np.minimum(np.maximum(sw, 0),1)
    return sw


def calculate_indonesian_sw(resistivity, porosity, vclay, rw_ft, a=1, m=2, n=2, use_variable_m = False, Rt_clay=4):
    """
    Calculate water saturation via Indonesian method.
    
    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total or effective porosity curve.
    vclay : ARRAY
        Clay volume curve.
    rw_ft: Array
        Formation water resitivity at formation temperature.
    a : FLOAT, optional
        Tortuosity factor. The default is 1.
    m : FLOAT, optional
        Cementation exponent. The default is 2.
    n : FLOAT, optional
        Saturation exponent. The default is 2.
    use_variable_m : BOOL, optional
        Use a variable 'm' based on Shell method for cementation exponent. The default is False.
    Rt_clay : FLOAT, optional
        Clay resistivity. The default is 4.

    Returns
    -------
    ARRAY
        Water Saturation Curve.
    """
    assert len(resistivity) == len(porosity) == len(rw_ft) == len(vclay), "All input curves must be of same length."
    if np.isscalar(a):
        temp = np.zeros(len(porosity))
        temp[:] = a
        a = temp
    if np.isscalar(m):
        temp = np.zeros(len(porosity))
        temp[:] = m
        m = temp
    if np.isscalar(n):
        temp = np.zeros(len(porosity))
        temp[:] = n
        n = temp
    if np.isscalar(Rt_clay):
        temp = np.zeros(len(porosity))
        temp[:] = Rt_clay
        Rt_clay = temp
    assert len(porosity) == len(a), "Input a array must have same length as input curves."
    assert len(porosity) == len(m), "Input m array must have same length as input curves."
    assert len(porosity) == len(n), "Input n array must have same length as input curves."
    assert len(porosity) == len(Rt_clay), "Input Rt_clay array must have same length as input curves."
       
    if use_variable_m is True:
        m_curve = 1.87 + (1.9 * porosity)
    else:
        m_curve = m
    
    sw = (((np.sqrt( (vclay**(2-vclay))/Rt_clay ) + np.sqrt( ((porosity**m_curve)/rw_ft) ))**2)*resistivity)**(-1/n)   
    sw = np.minimum(np.maximum(sw, 0),1)
    return sw



def calculate_dual_water_sw(resistivity, porosity, vclay, rw_ft, rwb_ft, a=1, m=2, n=2, use_variable_m = False, Rt_clay=4, phi_shale=0.10):
    """
    Calculate water saturation via Dual Water method.
    
    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total or effective porosity curve.
    vclay : ARRAY, optional
        Clay volume curve
    rw_ft: Array
        Formation water resitivity at formation temperature.
    rwb_ft: Array
        Formation water resitivity at formation temperature.
    a : FLOAT, optional
        Tortuosity factor. The default is 1.
    m : FLOAT, optional
        Cementation exponent. The default is 2.
    n : FLOAT, optional
        Saturation exponent. The default is 2.
    use_variable_m : BOOL, optional
        Use a variable 'm' based on Shell method for cementation exponent. The default is False.
    Rt_clay : FLOAT, optional
        Clay resistivity. The default is 4.
    phi_shale : FLOAT, optional
        Shale porosity fraction. The default is 0.10.

    Returns
    -------
    ARRAY
        Water Saturation Curve.
    """
    assert len(resistivity) == len(porosity) == len(rw_ft) == len(rwb_ft) == len(vclay), "All input curves must be of same length."
    if np.isscalar(a):
        temp = np.zeros(len(porosity))
        temp[:] = a
        a = temp
    if np.isscalar(m):
        temp = np.zeros(len(porosity))
        temp[:] = m
        m = temp
    if np.isscalar(n):
        temp = np.zeros(len(porosity))
        temp[:] = n
        n = temp
    if np.isscalar(Rt_clay):
        temp = np.zeros(len(porosity))
        temp[:] = Rt_clay
        Rt_clay = temp
    if np.isscalar(phi_shale):
        temp = np.zeros(len(porosity))
        temp[:] = phi_shale
        phi_shale = temp
    assert len(porosity) == len(a), "Input a array must have same length as input curves."
    assert len(porosity) == len(m), "Input m array must have same length as input curves."
    assert len(porosity) == len(n), "Input n array must have same length as input curves."
    assert len(porosity) == len(Rt_clay), "Input Rt_clay array must have same length as input curves."
    assert len(porosity) == len(phi_shale), "Input phi_shale array must have same length as input curves."
       
    if use_variable_m is True:
        m_curve = 1.87 + (1.9 * porosity)
    else:
        m_curve = m
       
    sw = (((a**2*rw_ft**2) / ((porosity**m_curve) * ((vclay * phi_shale/porosity) * a*rw_ft + (1-(vclay * phi_shale/porosity)) * a*rwb_ft)))/resistivity)**(1/n)   
    sw = np.minimum(np.maximum(sw, 0),1)
    return sw


def calculate_waxman_smits_sw(resistivity, porosity, rw_ft, temperature, a=1, m=2, n=2, use_variable_m = False, Qv=0.4):
    """
    
    Parameters
    ----------
    resistivity : ARRAY
        Deep resistivity curve.
    porosity : ARRAY
        Total or effective porosity curve.
    rw_ft: ARRAY
        Formation water resitivity at formation temperature.
    temperature: ARRAY
        Temperature curve in degrees Farenheit.
    a : FLOAT, optional
        Tortuosity factor. The default is 1.
    m : FLOAT, optional
        Cementation exponent. The default is 2.
    n : FLOAT, optional
        Saturation exponent. The default is 2.
    use_variable_m : BOOL, optional
        Use a variable 'm' based on Shell method for cementation exponent. The default is False.
    Qv : FLOAT, optional
        Qv parameter for Waxman Smits. The default is 0.4.

    Returns
    -------
    ARRAY
        Water Saturation Curve.
    """
    assert len(resistivity) == len(porosity) == len(rw_ft), "All input curves must be of same length."
    if np.isscalar(a):
        temp = np.zeros(len(porosity))
        temp[:] = a
        a = temp
    if np.isscalar(m):
        temp = np.zeros(len(porosity))
        temp[:] = m
        m = temp
    if np.isscalar(n):
        temp = np.zeros(len(porosity))
        temp[:] = n
        n = temp
    if np.isscalar(Qv):
        temp = np.zeros(len(porosity))
        temp[:] = Qv
        Qv = temp

    assert len(porosity) == len(a), "Input a array must have same length as input curves."
    assert len(porosity) == len(m), "Input m array must have same length as input curves."
    assert len(porosity) == len(n), "Input n array must have same length as input curves."
    assert len(porosity) == len(Qv), "Input Qv array must have same length as input curves."
       
    if use_variable_m is True:
        m_curve = 1.87 + (1.9 * porosity)
    else:
        m_curve = m
    
    
    sw_start = 0.50
    m_star = (m_curve)+ 0.5*(0.258*(Qv*porosity/(1-porosity))+0.2*(1-np.exp(-16.4*(Qv*porosity/(1-porosity)))))
    b_curve = (-1.28 + 0.225 * degf2degc(temperature) - 0.0004059 * degf2degc(temperature)**2)/(1 + (rw_ft**1.23)*(0.045*degf2degc(temperature)-0.027))
    for i in range(7):
        sw_start = ((a*rw_ft/resistivity) /( 1+b_curve*Qv*rw_ft/sw_start) / (porosity**m_star))**(1/n)
    
    sw = sw_start    
    sw = np.minimum(np.maximum(sw, 0),1)
    return sw


def calculate_BVW(sw, phit, phie):
    """
    Parameters
    ----------
    sw : ARRAY
        Water saturation curve.
    phit : ARRAY
        Total porosity curve.
    phie : ARRAY
        Effective porosity curve.

    Returns
    -------
    ARRAY
        Total Bulk Volume Water.
    ARRAY
        Effective Bulk Volume Water.
    """
    assert len(sw) == len(phit) == len(phie), "All input curves must be of same length."
    bvwt = sw*phit
    bvwe = sw*phie
    
    return bvwt, bvwe

    
def calculate_SWT_SWE(bvwt, bvwe, phit, phie, vclay, phi_shale = 0.10, Sw_Type="total"):
    """
    Parameters
    ----------
    bvwt : ARRAY
        Total Bulk Volume Water.
    bvwe : ARRAY
        Effective Bulk Volume Water.
    phit : ARRAY
        Total porosity curve.
    phie : ARRAY
        Effective porosity curve.
    vclay : ARRAY
        Clay volume curve.
    phi_shale : FLOAT, optional
        Shale porosity fraction. The default is 0.10.
    Sw_Type : STRING, optional
        Type of saturation calculated Options are total or effective. The default is "total".

    Returns
    -------
    SwT : TYPE
        DESCRIPTION.
    SwE : TYPE
        DESCRIPTION.

    """
    assert len(bvwe) == len(bvwt) == len(phit) == len(phie) == len(vclay), "All input curves must be of same length."
    if np.isscalar(phi_shale):
        temp = np.zeros(len(phit))
        temp[:] = phi_shale
        phi_shale = temp
    assert len(phit) == len(phi_shale), "Input phi_shale array must have same length as input curves."    
    
    Sw = bvwt/phit
    if Sw_Type == "total":
        SwT = Sw
        SwE = np.minimum(np.maximum((bvwt-(vclay * phi_shale))/phie,0),1)
    else:
        SwE = Sw
        SwT = np.minimum(np.maximum( (bvwe+(vclay * phi_shale))/phit ,0),1)
    
    return SwT, SwE
    
    
######################################
###   Pore Pressure Functions      ###
######################################

def calculate_hydrostatic_pressure(depth_curve, hpg=0.460):
    """
    Calculate the hydrostatic pressure gradient

    Parameters
    ----------
    depth_curve : ARRAY
        Depth curve in feet.
    hpg : FLOAT, optional
        Hydrostatic pressure gradient in psi/ft. The default is 0.460.

    Returns
    -------
    hp  : ARRAY
        Hydrostatic pressure in psi.

    """
    if np.isscalar(hpg):
        temp = np.zeros(len(depth_curve))
        temp[:] = hpg
        hpg = temp
    assert len(depth_curve) == len(hpg), "Input hpg array must have same length as depth curve."
    hp = depth_curve * hpg
    return hp


def calculate_overburden_pressure(depth_curve, phit, rho_matrix = 2.65, rho_fluid = 1.0, lpg=1.1):
    """
    Calculate the overburden pressure gradient using the porosity.
    Rho_matrix can be a float or grain density array.
    LPG is used for intervals without no porosity measurement available.

    Parameters
    ----------
    depth_curve : ARRAY
        Depth curve.
    phit : ARRAY
        Total porosity curve.
    rho_matrix : FLOAT, optional
        Matrix density. The default is 2.65.
    rho_fluid : FLOAT, optional
        Fluid density. The default is 1.0.
    lpg : FLOAT, optional
        Lithostatic pressure gradient. The default is 1.1.

    Returns
    -------
    obp : ARRAY
        Overburden pressure in psi.

    """
    assert len(phit) == len(depth_curve), "All input curves must be of same length."
    if np.isscalar(rho_matrix):
        temp = np.zeros(len(depth_curve))
        temp[:] = rho_matrix
        rho_matrix = temp
    if np.isscalar(rho_fluid):
        temp = np.zeros(len(depth_curve))
        temp[:] = rho_fluid
        rho_fluid = temp
    if np.isscalar(lpg):
        temp = np.zeros(len(depth_curve))
        temp[:] = lpg
        lpg = temp
        
    step = np.zeros(len(depth_curve))
    for i in range(1,len(depth_curve)):
        step[i] = (depth_curve[i] - depth_curve[i-1])
    step[0] = step[1]
    
    obp = 0.433 *( (1-phit)*rho_matrix*step + phit*rho_fluid*step)
    obp = np.nan_to_num(obp, nan=lpg)
    
    for i in range(1,len(obp)):
        obp[i] = np.nansum([obp[i],obp[i-1]])
       
    return obp

def calculate_eaton_sonic_porepressure(depth_curve, dt, vclay, obp, hp, 
                                 vclay_cutoff = 0.35, 
                                 method="eaton", 
                                 dtn=90, 
                                 dt_exp = 3.0, 
                                 dto = 50.0, 
                                 nctl= -6.0 ):
    """
    Calculate pore pressure via eaton or modified eaton methodology from sonic log.

    Parameters
    ----------
    depth_curve : ARRAY
        Depth curve.
    dt : ARRAY
        Compressional sonic curve in usec/foot.
    vclay : ARRAY
        Clay volume curve.
    obp : ARRAY
        Overburden curve.
    hp : ARRAY
        Hydrostatic pressure curve.
    vclay_cutoff : FLOAT, optional
        Cutoff value to determine if an interval is clay for analysis. The default is 0.35.
    method : STRING, optional
        Options are 'eaton' or 'mod_eaton'. The default is "eaton".
    dtn : FLOAT, optional
        Normal value of sonic transit time. The default is 90.
    dt_exp : FLOAT, optional
        Eaton's exponent. The default is 3.0.
    dto : FLOAT, optional
        DTo value in modified eaton NCTL equation. The default is 50.0.
    nctl: FLOAT, optional
        'b' exponent in modified eaton NCTL equation. The default is -6.0.

    Returns
    -------
    pp : ARRAY
        Pore pressure in psi/ft.

    """
    assert len(depth_curve) == len(dt) == len(vclay) == len(obp) == len(hp), "All input curves must be of same length."
    assert method in ['eaton', 'mod_eaton'], "Input method not recognized."
    if np.isscalar(vclay_cutoff):
        temp = np.zeros(len(depth_curve))
        temp[:] = vclay_cutoff
        vclay_cutoff = temp
    if np.isscalar(dtn):
        temp = np.zeros(len(depth_curve))
        temp[:] = dtn
        dtn = temp
    if np.isscalar(dt_exp):
        temp = np.zeros(len(depth_curve))
        temp[:] = dt_exp
        dt_exp = temp
    if np.isscalar(dto):
        temp = np.zeros(len(depth_curve))
        temp[:] = dto
        dto = temp
    if np.isscalar(nctl):
        temp = np.zeros(len(depth_curve))
        temp[:] = nctl
        nctl= temp
    assert len(depth_curve) == len(vclay_cutoff), "Vclay cutoff array must be same length as curves."
    assert len(depth_curve) == len(dtn), "dtnarray must be same length as curves."
    assert len(depth_curve) == len(dt_exp), "dt_exp array must be same length as curves."
    assert len(depth_curve) == len(dto), "dto array must be same length as curves."
    assert len(depth_curve) == len(nctl), "nctlarray must be same length as curves."
    
    if method == "eaton":
        dt_nctl = dtn
        pp = np.zeros(len(depth_curve))
        for i in range(len(vclay)):
            if vclay[i] > vclay_cutoff:
                pp[i] = (obp[i] - ((obp[i] - hp[i]) * (dt_nctl / dt[i])**dt_exp))
            else:
                pp[i] = hp[i]
    elif method == "mod_eaton":
        dt_nctl = (dto + (185-dto) * np.exp((nctl/10000*depth_curve/3.28)))
        pp = np.zeros(len(depth_curve))
        for i in range(len(vclay)):
            if vclay[i] > vclay_cutoff:
                pp[i] = (obp[i] - ((obp[i] - hp[i]) * (dt_nctl[i] / dt[i])**dt_exp))
            else:
                pp[i] = hp[i]
    
    pp = np.maximum(hp, pp)
    return pp


def calculate_eaton_resistivity_porepressure(depth_curve, resistivity, vclay, obp, hp,
                                             vclay_cutoff = 0.35,
                                             method = 'eaton',
                                             res_n = 3.0,
                                             res_exp = 1.2,
                                             res_a = 1,
                                             nctl = 0.34):
    """
    Calculate pore pressure via eaton or modified eaton methodology from resistivity log.

    Parameters
    ----------
    depth_curve : ARRAY
        Depth curve.
    resistivity : ARRAY
        Deep resistivity curve.
    vclay : ARRAY
        Clay volume curve.
    obp : ARRAY
        Overburden curve.
    hp : ARRAY
        Hydrostatic pressure curve.
    vclay_cutoff : FLOAT, optional
        Cutoff value to determine if an interval is clay for analysis. The default is 0.35.
    method : STRING, optional
        Options are 'eaton' or 'mod_eaton'. The default is "eaton".
    res_n : FLOAT, optional
       Normal value of resistivity. The default is 3.0.
    res_exp : FLOAT, optional
        Eaton exponent. The default is 1.2.
    res_a : FLOAT, optional
        Initial resistivity value in NCTL for modified eaton. The default is 1.
    nctl : FLOAT, optional
        C exponent in modified eaton NCTL.. The default is 0.34.

    Returns
    -------
    pp : TYPE
        DESCRIPTION.

    """
    assert len(depth_curve) == len(resistivity) == len(vclay) == len(obp) == len(hp), "All input curves must be of same length."
    assert method in ['eaton', 'mod_eaton'], "Input method not recognized."
    if np.isscalar(vclay_cutoff):
        temp = np.zeros(len(depth_curve))
        temp[:] = vclay_cutoff
        vclay_cutoff = temp
    if np.isscalar(res_n):
        temp = np.zeros(len(depth_curve))
        temp[:] = res_n
        res_n = temp
    if np.isscalar(res_exp):
        temp = np.zeros(len(depth_curve))
        temp[:] = res_exp
        res_exp = temp
    if np.isscalar(res_a):
        temp = np.zeros(len(depth_curve))
        temp[:] = res_a
        res_a = temp
    if np.isscalar(nctl):
        temp = np.zeros(len(depth_curve))
        temp[:] = nctl
        nctl = temp
    assert len(depth_curve) == len(vclay_cutoff), "Vclay cutoff array must be same length as curves."
    assert len(depth_curve) == len(res_n), "res_n array must be same length as curves."
    assert len(depth_curve) == len(res_exp), "res_exp array must be same length as curves."
    assert len(depth_curve) == len(res_a), "res_a array must be same length as curves."
    assert len(depth_curve) == len(nctl), "nctl array must be same length as curves."
    
    if method == "eaton":
        res_nctl = res_n
        pp = np.zeros(len(depth_curve))
        
        for i in range(len(vclay)):
            if vclay[i] > vclay_cutoff:
                pp[i] = (obp[i] - ((obp[i] - hp[i]) * (resistivity[i] / res_nctl)**res_exp))
            else:
                pp[i] = hp[i]
                
    elif method == "mod_eaton":
        res_nctl = (res_a*np.exp((nctl/10000*depth_curve/3.28)))            

        pp = np.zeros(len(depth_curve))
        
        for i in range(len(vclay)):
            if vclay[i] > vclay_cutoff:
                pp[i] = (obp[i] - ((obp[i] - hp[i]) * (resistivity[i] / res_nctl[i])**res_exp))
            else:
                pp[i] = hp[i]
    
    pp = np.maximum(hp, pp)
    return pp

def calculate_overpressure(pp, hp):
    """
    Calculate overpressure as difference between pore pressure and hydrostatic pressure.

    Parameters
    ----------
    pp : ARRAY
        Pore pressure curve.
    hp : ARRAY
        Hydrostatic pressure curve.

    Returns
    -------
    overpressure : ARRAY
        Overpressure curve.

    """
    assert len(pp) == len(hp), "Input curves must be of same length."
    overpressure =  np.subtract(pp,hp)    
    return overpressure

######################################
### Shear Log Modelling Functions  ###
######################################

def calculate_vp(dt):
    """
    Calculate P-wave velocity.

    Parameters
    ----------
    dt : ARRAY
        Compressional sonic log.

    Returns
    -------
    ARRAY
        P-wave velocity.

    """
    return fs2kms(usf2fs(dt))


def calculate_vs(dts):
    """
    Calculate S-wave velocity

    Parameters
    ----------
    dts : ARRAY
        Shear sonic log.

    Returns
    -------
    ARRAY
        S-wave velocity.

    """
    return fs2kms(usf2fs(dts))


def calculate_gc_ls_shear(vp):
    """
    Calculate Shear log via Greenberg-Castagna limestone end-member model.

    Parameters
    ----------
    vp : ARRAY
        P-wave velocity.

    Returns
    -------
    ARRAY
        Synthetic shear log.

    """
    return usf2fs(kms2fs(-0.05508 * vp**2 + 1.01677 * vp - 1.03049))


def calculate_gc_dol_shear(vp):
    """
    Calculate Shear log via Greenberg-Castagna dolomite end-member model.

    Parameters
    ----------
    vp : ARRAY
        P-wave velocity.

    Returns
    -------
    ARRAY
        Synthetic shear log.

    """
    return usf2fs(kms2fs(0.58321 * vp - 0.07775))


def calculate_gc_shale_shear(vp):
    """
    Calculate Shear log via Greenberg-Castagna shale end-member model.

    Parameters
    ----------
    vp : ARRAY
        P-wave velocity.

    Returns
    -------
    ARRAY
        Synthetic shear log.

    """
    return usf2fs(kms2fs(0.7969 * vp - 0.86735))


def calculate_gc_mudrock_shear(vp):
    """
    Calculate Shear log via Greenberg-Castagna mudrock line end-member model.

    Parameters
    ----------
    vp : ARRAY
        P-wave velocity.

    Returns
    -------
    ARRAY
        Synthetic shear log.

    """
    return usf2fs(kms2fs(0.8621 * vp - 1.1724))


def calculate_gc_ss_shear(vp):
    """
    Calculate Shear log via Greenberg-Castagna sandstone end-member model.

    Parameters
    ----------
    vp : ARRAY
        P-wave velocity.

    Returns
    -------
    ARRAY
        Synthetic shear log.

    """
    return usf2fs(kms2fs(0.80416 * vp - 0.85588))


######################################
###    Geomechanics Functions      ###
######################################

def calculate_youngs_modulus(rhob, dt, dts):
    """
    Calculate the Young's Modulus.

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    dt : ARRAY
        Compressional sonic curve.
    dts : ARRAY
        Shear sonic curve.

    Returns
    -------
    youngs_modulus : ARRAY
        Young's modulus.

    """
    assert len(rhob) == len(dt) == len(dts), "All input curves must be of same length"
    shear_modulus = 13474.45 * (rhob / dts**2)
    bulk_modulus = 13474.45 * (rhob / (dt**2)) - (4/3) * shear_modulus
    
    youngs_modulus = (9 * shear_modulus * bulk_modulus) / (shear_modulus + 3 * bulk_modulus)
    return youngs_modulus

def calculate_bulk_modulus(rhob, dt, dts):
    """
    Calculate the bulk modulus.

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    dt : ARRAY
        Compressional sonic curve.
    dts : ARRAY
        Shear sonic curve.

    Returns
    -------
    bulk_modulus : ARRAY
        Bulk modulus.

    """
    assert len(rhob) == len(dt) == len(dts), "All input curves must be of same length"
    shear_modulus = 13474.45 * (rhob / dts**2)
    
    bulk_modulus = 13474.45 * (rhob / (dt**2)) - (4/3) * shear_modulus
    return bulk_modulus

def calculate_shear_modulus(rhob, dts):
    """
    Calculate the shear modulus

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    dts : ARRAY
        Shear sonic curve.

    Returns
    -------
    shear_modulus : ARRAY
        Shear Modulus.

    """
    assert len(rhob) == len(dts), "All input curves must be of same length"
    shear_modulus = 13474.45 * (rhob / dts**2)
    return shear_modulus

def calculate_poissons_ratio(rhob, dt, dts):
    """
    Calculate poissons ratio.

    Parameters
    ----------
    rhob : ARRAY
        Bulk density curve.
    dt : ARRAY
        Compressional sonic curve.
    dts : ARRAY
        Shear sonic curve.

    Returns
    -------
    poissons_ratio : ARRAY
        Poisson's ratio.

    """
    assert len(rhob) == len(dt) == len(dts), "All input curves must be of same length"
    shear_modulus = 13474.45 * (rhob / dts**2)
    bulk_modulus = 13474.45 * (rhob / (dt**2)) - (4/3) * shear_modulus
    
    poissons_ratio = (3 * bulk_modulus - 2 * shear_modulus) / (6 * bulk_modulus + 2 * shear_modulus)
    return poissons_ratio