Functions.Localization_Methods

View Source
__docformat__ = "google"
# build doc : pdoc D:\Dropbox\Run4_transitoire\Functions\Feflow_Utils
# pdoc -o D:\Dropbox\Run4_transitoire\Documentation D:\Dropbox\Run4_transitoire\Functions


"""
    This module holds the code to create a weighting localization around each observation point
"""

from typing import Union
import numpy as np
import pandas as pd



def get_ellipse_bb(major : float, 
                    minor : float, 
                    angle_rad : float) -> Union[float, float, float, float]:
    
    """
    Compute tight bounding box around ellipse of range major / minor


    Args:
        major (float): ellipse range along long axis, positive value
        minor (float): ellipse range along short axis, positive value
        angle_rad (float): radian trigonometrical rotation, 0 along x, increase counter clockwise

    Returns:
        Union[float, float, float, float]: x_min, y_min, x_max, y_max
    """	

    xp = np.cos(angle_rad) * major
    yp = np.sin(angle_rad) * major
    xq = np.cos(angle_rad - np.pi/2) * minor
    yq = np.sin(angle_rad - np.pi/2) * minor


    return np.floor(-np.sqrt(xp**2 + xq**2)), np.floor(-np.sqrt(yp**2 + yq**2)),\
				np.ceil(np.sqrt(xp**2 + xq**2)), np.ceil(np.sqrt(yp**2 + yq**2))



def validity_variogram(s : float, 
            n : float, 
            r : float) -> None:
    """
    Compute numerical validity of variogram model. Return an error if any fails

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value

    Raises:
        ValueError: Nugget effect, sill or range is negative, impossible
        ValueError: Nugget effect should be lower than sill
    """

    if (n < 0 or s < 0 or r < 0):
        raise ValueError("Nugget effect, sill or range is negative, impossible")

    if (n > s):
        raise ValueError("Nugget effect should be lower than sill")



def gaussian(s : float, 
            n : float, 
            r : float, 
            h : float) -> float:
    """
    Compute variance on a gaussian variogram model given the sill, range, nugget and distance of observation.   
    Formula defined by Chiles and Delfiner 1999.

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value
        h (float): distance, could be single value or a vector of several point to compute) 
    
    Returns:
        float: variance, single value or vector of values in function of the type of h
    """

    validity_variogram(s, n, r)

    return (s-n)*(1-np.exp((-h**2)/(r**2*(1/3)))) + n



def exponential(s : float, 
            n : float, 
            r : float, 
            h : float) -> float:
    """
    Compute variance on an exponential variogram model given the sill, range, nugget and distance of observation.   
    Formula defined by Chiles and Delfiner 1999.

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value
        h (float): distance, could be single value or a vector of several point to compute) 
    
    Returns:
        float: variance, single value or vector of values in function of the type of h
    """

    validity_variogram(s, n, r)

    return (s - n)*(1 - np.exp(-abs(h) / (r/3) )) + n



def spherical(s : float, 
            n : float, 
            r : float, 
            h : float) -> float:

    """
    Compute variance on a spherical variogram model given the sill, range, nugget and distance of observation.   
    Formula defined by Chiles and Delfiner 1999.

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value
        h (float): distance, could be single value or a vector of several point to compute) 
    
    Returns:
        float: variance, single value or vector of values in function of the type of h
    """

    validity_variogram(s, n, r)

    temp = np.zeros(len(h))

    for i in range(len(h)):
        if h[i] < r : 
            temp[i] = (s - n) * ( ( (3 * abs(h[i])) / (2*r) ) - (abs(h[i])**3) / (2 * (abs(r)**3) ) ) + n
        else :
            temp[i] = s

    return temp



def get_elements_ellipsoid(data: pd.DataFrame, 
            max_range : float, 
            med_range : float, 
            min_range : float, 
            dip : float, 
            azimuth : float, 
            x_c : float, 
            y_c : float, 
            z_c : float, 
            sill : float, 
            nugget : float, 
            model : str) -> Union[int,float]:

    """
    Computes an observation weight for each point in a ellispoid around
    a given center and ellipse geometry. Weight equal at 1 on observation point, decaying
    following the given model, to the nugget on the ellispe boundary and outside. 
    
    It is used has localization method to improve numerical stability in further inversion and 
    reduces spurrious correlations. Take 3 vectors x, y, z containing coordinate of Feflow elements. 
    Combined with ellipse geometry, it returned index of element inside the ellipse and the weight 
    associated with every Feflow element.

    Numerical implementation required to pass by a local coordinate system centered on observation
    point, and rotation along dip and azimuth to use ellispoid axis as system axis.

    Args:
        data (pd.DataFrame): containing the coordinates X,Y,Z and the ElementId as 
        columns of Dataframe.
        max_range (float): maximum range of observation weight along the ellipsoid axis
        med_range (float): medium range of observation weight along the ellipsoid axis
        min_range (float): minimum range of observation weight along the ellipsoid axis
        dip (float): Angle in radian of max_range on an vertical plane, with 0 correpsonding to no dip,
            ranging from 0 to -pi/2, negative value to the center of earth
        azimuth (float): Angle in radian of max_range on an horizontal plane, with 0 pointing to north,
            ranging from 0 to 2pi clockwise
        x_c (float): x cell coordinate center of the observed cell
        y_c (float): y cell coordinate center of the observed cell
        z_c (float): z cell coordinate center of the observed cell
        sill (float): Equivalent to the sill in variogram, giving a weight to points around observation 
            point
        nugget (float): Equivalent to the nugget in variogram, giving a random noise weight to points around 
            observation point
        model (str): Variogram model type used to compute the weight decay in the ellipsoid. "gaussian", 
            "exponential" or "spherical". Models defined by Chiles and Delfiner 1999.

    Returns:
        index (int): Indexes of Feflow element inside the research ellispoid, 
            values (float): Observation weight associated to elements in index
    """

    x = data.X.values
    y = data.Y.values
    z = data.Z.values

    # Centering every Feflow elements around the observation point
    coords_local = np.matrix([(x - x_c),(y - y_c),(z - z_c)])

    # Rotation_along_y to remove dip effect
    mat = np.matrix([[np.cos(dip), 0, np.sin(dip)],
         [0, 1, 0],
         [-np.sin(dip), 0, np.cos(dip)]])

    coords_local = np.matmul(mat,coords_local)


    # Rotation_along_z to remove azimuth effect
    mat_ = np.matrix([[np.cos(-azimuth), -np.sin(-azimuth),0],
     [np.sin(-azimuth), np.cos(-azimuth), 0],
     [0, 0, 1]])

    coords_local = np.matmul(mat_,coords_local)

    # Transformation in a np.array to insure working element acces
    coords_local = np.array(coords_local)

    # Elements contained in a given ellispoid will respect a^2 + b^2 + c^2 <= 1
    a = np.square(coords_local[0]/max_range)
    b = np.square(coords_local[1]/med_range)
    c = np.square(coords_local[2]/min_range)

    temp = a+b+c 

    # Retaining only elements contained in ellipsoid, with a trick to allow a point
    # Localized on the observation point, numerically impossible (division by 0)
    values = np.zeros(len(np.where(temp == 0)[0]) + len(np.where(temp <= 1)[0]))
    index = np.zeros(len(np.where(temp == 0)[0]) + len(np.where(temp <= 1)[0]))

    values[0:len(np.where(temp == 0)[0])] = sill - nugget
    index[0:len(np.where(temp == 0)[0])] = np.where(temp == 0)[0]

    # Weighting computed as asked by the final user with respective model
    if model == "gaussian":

        values[-len(np.where(temp <= 1)[0]):] = (sill - (a[np.where(temp <= 1)[0]] * gaussian(sill,nugget,max_range,coords_local[0][np.where(temp <= 1)[0]]) + 
                     b[np.where(temp <= 1)[0]] * gaussian(sill,nugget,med_range,coords_local[1][np.where(temp <= 1)[0]]) +
                     c[np.where(temp <= 1)[0]] * gaussian(sill,nugget,min_range,coords_local[2][np.where(temp <= 1)[0]])))
        index[-len(np.where(temp <= 1)[0]):] = np.where(temp <= 1)[0]

    if model == "spherical":

        values[-len(np.where(temp <= 1)[0]):] = (sill - (a[np.where(temp <= 1)[0]] * spherical(sill,nugget,max_range,coords_local[0][np.where(temp <= 1)[0]]) +
                     b[np.where(temp <= 1)[0]] * spherical(sill,nugget,med_range,coords_local[1][np.where(temp <= 1)[0]]) +
                     c[np.where(temp <= 1)[0]] * spherical(sill,nugget,min_range,coords_local[2][np.where(temp <= 1)[0]])))
        index[-len(np.where(temp <= 1)[0]):] = np.where(temp <= 1)[0]

    if model == "exponential":

        values[-len(np.where(temp <= 1)[0]):] = (sill - (a[np.where(temp <= 1)[0]] * exponential(sill,nugget,max_range,coords_local[0][np.where(temp <= 1)[0]]) +
                     b[np.where(temp <= 1)[0]] * exponential(sill,nugget,med_range,coords_local[1][np.where(temp <= 1)[0]]) +
                     c[np.where(temp <= 1)[0]] * exponential(sill,nugget,min_range,coords_local[2][np.where(temp <= 1)[0]])))
        index[-len(np.where(temp <= 1)[0]):] = np.where(temp <= 1)[0]


    index = index.astype(int)

    # Indexs of elements in Feflow, weight associated with each element.

    return data.iloc[index,0].values, values


def extract_feflow_from_ellipsoid(data: pd.DataFrame,
                                  x_obs: float,
                                  y_obs: float,
                                  z_obs: float,
                                  max_range: float,
                                  med_range: float,
                                  min_range: float,
                                  azimuth: float,
                                  dip: float,
                                  sill: float,
                                  nugget: float,
                                  model: str) -> Union[int,float]:
                            
    """
    Computes an observation weight for each elements in a Feflow model. Weight is equal to 1 
    on observation point, decaying following the range and type of  variogram model. Outside 
    of the range, the weight is equal to the nugget. 
    
    It is used has localization method to improve numerical stability in further inversion and 
    reduces spurrious correlations. Take 4 vectors x, y, z and elementID of Feflow elements. 
    Combined with variogram geomtry, it returned index of elements inside the range and the weight 
    associated with every Feflow element.

    Args:
        data (pd.DataFrame): containing reindex from 0 to n_element, the coordinates X,Y,Z and 
        the ElementId as columns of Dataframe. Columns name : index, X, Y, Z, ElementId
        x_obs (float): x coordinate of observed point in Feflow model reference coordinate
        y_obs (float): y coordinate of observed point in Feflow model reference coordinate
        z_obs (float): z coordinate of observed point in Feflow model reference coordinate
        max_range (float): maximum range of observation weight along the ellipsoid axis
        med_range (float): medium range of observation weight along the ellipsoid axis
        min_range (float): minimum range of observation weight along the ellipsoid axis
        azimuth (float): Angle in degree of max_range on an horizontal plane with the north,
            0 degree is a pure northern azimut. Ranging from 0 to 360 degrees clockwise
            / ! \ Degrees increases opposite to trigonometric way. Real world map convention
        dip (float): Angle in degree of max_range on an vertical plane, with 0 correpsonding to no dip,
            ranging from 0 to -90, -90 is pointing to the earth center
        sill (float): Equivalent to the sill in variogram, giving a weight to points around observation 
            point
        nugget (float): Equivalent to the nugget in variogram, giving a random noise weight to points around 
            observation point
        model (str): Variogram model type used to compute the weight decay in the ellipsoid. "gaussian", 
            "exponential" or "spherical". Models defined by Chiles and Delfiner 1999.

    Raises:
        ValueError: Azimuth should be in radian between 0 and 2 pi
        ValueError: Dip should be in radian between 0 and -pi/2
        ValueError: Wrong variogram model, should be gaussian, spherical or exponential
        ValueError: Dataframe coordinates columns should be named exactly X,Y,Z

    Returns:
        index (int): Indexes of Feflow element inside the research ellispoid, 
            values (float): Observation weight associated to elements in index

    """

    if (azimuth < 0 or azimuth >= 360):
        raise ValueError("Azimuth should be in degree between 0 and 359.9")

    if (dip > 0 or dip < -90):
        raise ValueError("Dip should be in degree between 0 and -90")


    # Converts degree angle to radian angle, as degree are more commonly used in field measure
    azimuth = azimuth * np.pi/180
    dip = dip * np.pi/180

    if not (model == "gaussian" or model == "spherical" or model == "exponential"):
        raise ValueError("Wrong variogram model, should be gaussian, spherical or exponential")   

    if not np.all(data.columns[0:5]==["index","X","Y","Z","ElementID"]):
        raise ValueError("Dataframe coordinates columns should be named exactly index, X, Y, Z, ElementID")   

    # Transforming azimuth with 0 to the north to the classical trignonometric 0 on the east axis
    azimuth = -azimuth - np.pi/2

    
    #SpeedUp by doing a smaller subset of element in a bounding box.
    #Factor ranging from 0.9 to 15 as the ellipsoid search reduces around observation point
    x_min, y_min, x_max, y_max = get_ellipse_bb(max_range, med_range, azimuth)
    _, z_min, _, z_max = get_ellipse_bb(med_range, min_range, dip)
 
    data = data[np.logical_and(data.X.values > x_min + x_obs, data.X.values < x_max + x_obs)]
    data = data[np.logical_and(data.Y.values > y_min + y_obs, data.Y.values < y_max + y_obs)]
    data = data[np.logical_and(data.Z.values > z_min + z_obs, data.Z.values < z_max + z_obs)]
    

    index,values = get_elements_ellipsoid(data,
                                    max_range,
                                    med_range,
                                    min_range,
                                    dip,
                                    azimuth,
                                    x_obs,
                                    y_obs,
                                    z_obs,
                                    sill,
                                    nugget,
                                    model)

    return index, values
#   def get_ellipse_bb(major: float, minor: float, angle_rad: float) -> float:
View Source
def get_ellipse_bb(major : float, 
                    minor : float, 
                    angle_rad : float) -> Union[float, float, float, float]:
    
    """
    Compute tight bounding box around ellipse of range major / minor


    Args:
        major (float): ellipse range along long axis, positive value
        minor (float): ellipse range along short axis, positive value
        angle_rad (float): radian trigonometrical rotation, 0 along x, increase counter clockwise

    Returns:
        Union[float, float, float, float]: x_min, y_min, x_max, y_max
    """	

    xp = np.cos(angle_rad) * major
    yp = np.sin(angle_rad) * major
    xq = np.cos(angle_rad - np.pi/2) * minor
    yq = np.sin(angle_rad - np.pi/2) * minor


    return np.floor(-np.sqrt(xp**2 + xq**2)), np.floor(-np.sqrt(yp**2 + yq**2)),\
				np.ceil(np.sqrt(xp**2 + xq**2)), np.ceil(np.sqrt(yp**2 + yq**2))

Compute tight bounding box around ellipse of range major / minor

Args
  • major (float): ellipse range along long axis, positive value
  • minor (float): ellipse range along short axis, positive value
  • angle_rad (float): radian trigonometrical rotation, 0 along x, increase counter clockwise
Returns

Union[float, float, float, float]: x_min, y_min, x_max, y_max

#   def validity_variogram(s: float, n: float, r: float) -> None:
View Source
def validity_variogram(s : float, 
            n : float, 
            r : float) -> None:
    """
    Compute numerical validity of variogram model. Return an error if any fails

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value

    Raises:
        ValueError: Nugget effect, sill or range is negative, impossible
        ValueError: Nugget effect should be lower than sill
    """

    if (n < 0 or s < 0 or r < 0):
        raise ValueError("Nugget effect, sill or range is negative, impossible")

    if (n > s):
        raise ValueError("Nugget effect should be lower than sill")

Compute numerical validity of variogram model. Return an error if any fails

Args
  • s (float): sill, positive value
  • n (float): nugget, positive value
  • r (float): range, positive value
Raises
  • ValueError: Nugget effect, sill or range is negative, impossible
  • ValueError: Nugget effect should be lower than sill
#   def gaussian(s: float, n: float, r: float, h: float) -> float:
View Source
def gaussian(s : float, 
            n : float, 
            r : float, 
            h : float) -> float:
    """
    Compute variance on a gaussian variogram model given the sill, range, nugget and distance of observation.   
    Formula defined by Chiles and Delfiner 1999.

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value
        h (float): distance, could be single value or a vector of several point to compute) 
    
    Returns:
        float: variance, single value or vector of values in function of the type of h
    """

    validity_variogram(s, n, r)

    return (s-n)*(1-np.exp((-h**2)/(r**2*(1/3)))) + n

Compute variance on a gaussian variogram model given the sill, range, nugget and distance of observation.
Formula defined by Chiles and Delfiner 1999.

Args
  • s (float): sill, positive value
  • n (float): nugget, positive value
  • r (float): range, positive value
  • h (float): distance, could be single value or a vector of several point to compute)
Returns

float: variance, single value or vector of values in function of the type of h

#   def exponential(s: float, n: float, r: float, h: float) -> float:
View Source
def exponential(s : float, 
            n : float, 
            r : float, 
            h : float) -> float:
    """
    Compute variance on an exponential variogram model given the sill, range, nugget and distance of observation.   
    Formula defined by Chiles and Delfiner 1999.

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value
        h (float): distance, could be single value or a vector of several point to compute) 
    
    Returns:
        float: variance, single value or vector of values in function of the type of h
    """

    validity_variogram(s, n, r)

    return (s - n)*(1 - np.exp(-abs(h) / (r/3) )) + n

Compute variance on an exponential variogram model given the sill, range, nugget and distance of observation.
Formula defined by Chiles and Delfiner 1999.

Args
  • s (float): sill, positive value
  • n (float): nugget, positive value
  • r (float): range, positive value
  • h (float): distance, could be single value or a vector of several point to compute)
Returns

float: variance, single value or vector of values in function of the type of h

#   def spherical(s: float, n: float, r: float, h: float) -> float:
View Source
def spherical(s : float, 
            n : float, 
            r : float, 
            h : float) -> float:

    """
    Compute variance on a spherical variogram model given the sill, range, nugget and distance of observation.   
    Formula defined by Chiles and Delfiner 1999.

    Args:
        s (float): sill, positive value
        n (float): nugget, positive value
        r (float): range, positive value
        h (float): distance, could be single value or a vector of several point to compute) 
    
    Returns:
        float: variance, single value or vector of values in function of the type of h
    """

    validity_variogram(s, n, r)

    temp = np.zeros(len(h))

    for i in range(len(h)):
        if h[i] < r : 
            temp[i] = (s - n) * ( ( (3 * abs(h[i])) / (2*r) ) - (abs(h[i])**3) / (2 * (abs(r)**3) ) ) + n
        else :
            temp[i] = s

    return temp

Compute variance on a spherical variogram model given the sill, range, nugget and distance of observation.
Formula defined by Chiles and Delfiner 1999.

Args
  • s (float): sill, positive value
  • n (float): nugget, positive value
  • r (float): range, positive value
  • h (float): distance, could be single value or a vector of several point to compute)
Returns

float: variance, single value or vector of values in function of the type of h

#   def get_elements_ellipsoid( data: pandas.core.frame.DataFrame, max_range: float, med_range: float, min_range: float, dip: float, azimuth: float, x_c: float, y_c: float, z_c: float, sill: float, nugget: float, model: str ) -> Union[int, float]:
View Source
def get_elements_ellipsoid(data: pd.DataFrame, 
            max_range : float, 
            med_range : float, 
            min_range : float, 
            dip : float, 
            azimuth : float, 
            x_c : float, 
            y_c : float, 
            z_c : float, 
            sill : float, 
            nugget : float, 
            model : str) -> Union[int,float]:

    """
    Computes an observation weight for each point in a ellispoid around
    a given center and ellipse geometry. Weight equal at 1 on observation point, decaying
    following the given model, to the nugget on the ellispe boundary and outside. 
    
    It is used has localization method to improve numerical stability in further inversion and 
    reduces spurrious correlations. Take 3 vectors x, y, z containing coordinate of Feflow elements. 
    Combined with ellipse geometry, it returned index of element inside the ellipse and the weight 
    associated with every Feflow element.

    Numerical implementation required to pass by a local coordinate system centered on observation
    point, and rotation along dip and azimuth to use ellispoid axis as system axis.

    Args:
        data (pd.DataFrame): containing the coordinates X,Y,Z and the ElementId as 
        columns of Dataframe.
        max_range (float): maximum range of observation weight along the ellipsoid axis
        med_range (float): medium range of observation weight along the ellipsoid axis
        min_range (float): minimum range of observation weight along the ellipsoid axis
        dip (float): Angle in radian of max_range on an vertical plane, with 0 correpsonding to no dip,
            ranging from 0 to -pi/2, negative value to the center of earth
        azimuth (float): Angle in radian of max_range on an horizontal plane, with 0 pointing to north,
            ranging from 0 to 2pi clockwise
        x_c (float): x cell coordinate center of the observed cell
        y_c (float): y cell coordinate center of the observed cell
        z_c (float): z cell coordinate center of the observed cell
        sill (float): Equivalent to the sill in variogram, giving a weight to points around observation 
            point
        nugget (float): Equivalent to the nugget in variogram, giving a random noise weight to points around 
            observation point
        model (str): Variogram model type used to compute the weight decay in the ellipsoid. "gaussian", 
            "exponential" or "spherical". Models defined by Chiles and Delfiner 1999.

    Returns:
        index (int): Indexes of Feflow element inside the research ellispoid, 
            values (float): Observation weight associated to elements in index
    """

    x = data.X.values
    y = data.Y.values
    z = data.Z.values

    # Centering every Feflow elements around the observation point
    coords_local = np.matrix([(x - x_c),(y - y_c),(z - z_c)])

    # Rotation_along_y to remove dip effect
    mat = np.matrix([[np.cos(dip), 0, np.sin(dip)],
         [0, 1, 0],
         [-np.sin(dip), 0, np.cos(dip)]])

    coords_local = np.matmul(mat,coords_local)


    # Rotation_along_z to remove azimuth effect
    mat_ = np.matrix([[np.cos(-azimuth), -np.sin(-azimuth),0],
     [np.sin(-azimuth), np.cos(-azimuth), 0],
     [0, 0, 1]])

    coords_local = np.matmul(mat_,coords_local)

    # Transformation in a np.array to insure working element acces
    coords_local = np.array(coords_local)

    # Elements contained in a given ellispoid will respect a^2 + b^2 + c^2 <= 1
    a = np.square(coords_local[0]/max_range)
    b = np.square(coords_local[1]/med_range)
    c = np.square(coords_local[2]/min_range)

    temp = a+b+c 

    # Retaining only elements contained in ellipsoid, with a trick to allow a point
    # Localized on the observation point, numerically impossible (division by 0)
    values = np.zeros(len(np.where(temp == 0)[0]) + len(np.where(temp <= 1)[0]))
    index = np.zeros(len(np.where(temp == 0)[0]) + len(np.where(temp <= 1)[0]))

    values[0:len(np.where(temp == 0)[0])] = sill - nugget
    index[0:len(np.where(temp == 0)[0])] = np.where(temp == 0)[0]

    # Weighting computed as asked by the final user with respective model
    if model == "gaussian":

        values[-len(np.where(temp <= 1)[0]):] = (sill - (a[np.where(temp <= 1)[0]] * gaussian(sill,nugget,max_range,coords_local[0][np.where(temp <= 1)[0]]) + 
                     b[np.where(temp <= 1)[0]] * gaussian(sill,nugget,med_range,coords_local[1][np.where(temp <= 1)[0]]) +
                     c[np.where(temp <= 1)[0]] * gaussian(sill,nugget,min_range,coords_local[2][np.where(temp <= 1)[0]])))
        index[-len(np.where(temp <= 1)[0]):] = np.where(temp <= 1)[0]

    if model == "spherical":

        values[-len(np.where(temp <= 1)[0]):] = (sill - (a[np.where(temp <= 1)[0]] * spherical(sill,nugget,max_range,coords_local[0][np.where(temp <= 1)[0]]) +
                     b[np.where(temp <= 1)[0]] * spherical(sill,nugget,med_range,coords_local[1][np.where(temp <= 1)[0]]) +
                     c[np.where(temp <= 1)[0]] * spherical(sill,nugget,min_range,coords_local[2][np.where(temp <= 1)[0]])))
        index[-len(np.where(temp <= 1)[0]):] = np.where(temp <= 1)[0]

    if model == "exponential":

        values[-len(np.where(temp <= 1)[0]):] = (sill - (a[np.where(temp <= 1)[0]] * exponential(sill,nugget,max_range,coords_local[0][np.where(temp <= 1)[0]]) +
                     b[np.where(temp <= 1)[0]] * exponential(sill,nugget,med_range,coords_local[1][np.where(temp <= 1)[0]]) +
                     c[np.where(temp <= 1)[0]] * exponential(sill,nugget,min_range,coords_local[2][np.where(temp <= 1)[0]])))
        index[-len(np.where(temp <= 1)[0]):] = np.where(temp <= 1)[0]


    index = index.astype(int)

    # Indexs of elements in Feflow, weight associated with each element.

    return data.iloc[index,0].values, values

Computes an observation weight for each point in a ellispoid around a given center and ellipse geometry. Weight equal at 1 on observation point, decaying following the given model, to the nugget on the ellispe boundary and outside.

It is used has localization method to improve numerical stability in further inversion and reduces spurrious correlations. Take 3 vectors x, y, z containing coordinate of Feflow elements. Combined with ellipse geometry, it returned index of element inside the ellipse and the weight associated with every Feflow element.

Numerical implementation required to pass by a local coordinate system centered on observation point, and rotation along dip and azimuth to use ellispoid axis as system axis.

Args
  • data (pd.DataFrame): containing the coordinates X,Y,Z and the ElementId as
  • columns of Dataframe.
  • max_range (float): maximum range of observation weight along the ellipsoid axis
  • med_range (float): medium range of observation weight along the ellipsoid axis
  • min_range (float): minimum range of observation weight along the ellipsoid axis
  • dip (float): Angle in radian of max_range on an vertical plane, with 0 correpsonding to no dip, ranging from 0 to -pi/2, negative value to the center of earth
  • azimuth (float): Angle in radian of max_range on an horizontal plane, with 0 pointing to north, ranging from 0 to 2pi clockwise
  • x_c (float): x cell coordinate center of the observed cell
  • y_c (float): y cell coordinate center of the observed cell
  • z_c (float): z cell coordinate center of the observed cell
  • sill (float): Equivalent to the sill in variogram, giving a weight to points around observation point
  • nugget (float): Equivalent to the nugget in variogram, giving a random noise weight to points around observation point
  • model (str): Variogram model type used to compute the weight decay in the ellipsoid. "gaussian", "exponential" or "spherical". Models defined by Chiles and Delfiner 1999.
Returns

index (int): Indexes of Feflow element inside the research ellispoid, values (float): Observation weight associated to elements in index

#   def extract_feflow_from_ellipsoid( data: pandas.core.frame.DataFrame, x_obs: float, y_obs: float, z_obs: float, max_range: float, med_range: float, min_range: float, azimuth: float, dip: float, sill: float, nugget: float, model: str ) -> Union[int, float]:
View Source
def extract_feflow_from_ellipsoid(data: pd.DataFrame,
                                  x_obs: float,
                                  y_obs: float,
                                  z_obs: float,
                                  max_range: float,
                                  med_range: float,
                                  min_range: float,
                                  azimuth: float,
                                  dip: float,
                                  sill: float,
                                  nugget: float,
                                  model: str) -> Union[int,float]:
                            
    """
    Computes an observation weight for each elements in a Feflow model. Weight is equal to 1 
    on observation point, decaying following the range and type of  variogram model. Outside 
    of the range, the weight is equal to the nugget. 
    
    It is used has localization method to improve numerical stability in further inversion and 
    reduces spurrious correlations. Take 4 vectors x, y, z and elementID of Feflow elements. 
    Combined with variogram geomtry, it returned index of elements inside the range and the weight 
    associated with every Feflow element.

    Args:
        data (pd.DataFrame): containing reindex from 0 to n_element, the coordinates X,Y,Z and 
        the ElementId as columns of Dataframe. Columns name : index, X, Y, Z, ElementId
        x_obs (float): x coordinate of observed point in Feflow model reference coordinate
        y_obs (float): y coordinate of observed point in Feflow model reference coordinate
        z_obs (float): z coordinate of observed point in Feflow model reference coordinate
        max_range (float): maximum range of observation weight along the ellipsoid axis
        med_range (float): medium range of observation weight along the ellipsoid axis
        min_range (float): minimum range of observation weight along the ellipsoid axis
        azimuth (float): Angle in degree of max_range on an horizontal plane with the north,
            0 degree is a pure northern azimut. Ranging from 0 to 360 degrees clockwise
            / ! \ Degrees increases opposite to trigonometric way. Real world map convention
        dip (float): Angle in degree of max_range on an vertical plane, with 0 correpsonding to no dip,
            ranging from 0 to -90, -90 is pointing to the earth center
        sill (float): Equivalent to the sill in variogram, giving a weight to points around observation 
            point
        nugget (float): Equivalent to the nugget in variogram, giving a random noise weight to points around 
            observation point
        model (str): Variogram model type used to compute the weight decay in the ellipsoid. "gaussian", 
            "exponential" or "spherical". Models defined by Chiles and Delfiner 1999.

    Raises:
        ValueError: Azimuth should be in radian between 0 and 2 pi
        ValueError: Dip should be in radian between 0 and -pi/2
        ValueError: Wrong variogram model, should be gaussian, spherical or exponential
        ValueError: Dataframe coordinates columns should be named exactly X,Y,Z

    Returns:
        index (int): Indexes of Feflow element inside the research ellispoid, 
            values (float): Observation weight associated to elements in index

    """

    if (azimuth < 0 or azimuth >= 360):
        raise ValueError("Azimuth should be in degree between 0 and 359.9")

    if (dip > 0 or dip < -90):
        raise ValueError("Dip should be in degree between 0 and -90")


    # Converts degree angle to radian angle, as degree are more commonly used in field measure
    azimuth = azimuth * np.pi/180
    dip = dip * np.pi/180

    if not (model == "gaussian" or model == "spherical" or model == "exponential"):
        raise ValueError("Wrong variogram model, should be gaussian, spherical or exponential")   

    if not np.all(data.columns[0:5]==["index","X","Y","Z","ElementID"]):
        raise ValueError("Dataframe coordinates columns should be named exactly index, X, Y, Z, ElementID")   

    # Transforming azimuth with 0 to the north to the classical trignonometric 0 on the east axis
    azimuth = -azimuth - np.pi/2

    
    #SpeedUp by doing a smaller subset of element in a bounding box.
    #Factor ranging from 0.9 to 15 as the ellipsoid search reduces around observation point
    x_min, y_min, x_max, y_max = get_ellipse_bb(max_range, med_range, azimuth)
    _, z_min, _, z_max = get_ellipse_bb(med_range, min_range, dip)
 
    data = data[np.logical_and(data.X.values > x_min + x_obs, data.X.values < x_max + x_obs)]
    data = data[np.logical_and(data.Y.values > y_min + y_obs, data.Y.values < y_max + y_obs)]
    data = data[np.logical_and(data.Z.values > z_min + z_obs, data.Z.values < z_max + z_obs)]
    

    index,values = get_elements_ellipsoid(data,
                                    max_range,
                                    med_range,
                                    min_range,
                                    dip,
                                    azimuth,
                                    x_obs,
                                    y_obs,
                                    z_obs,
                                    sill,
                                    nugget,
                                    model)

    return index, values

Computes an observation weight for each elements in a Feflow model. Weight is equal to 1 on observation point, decaying following the range and type of variogram model. Outside of the range, the weight is equal to the nugget.

It is used has localization method to improve numerical stability in further inversion and reduces spurrious correlations. Take 4 vectors x, y, z and elementID of Feflow elements. Combined with variogram geomtry, it returned index of elements inside the range and the weight associated with every Feflow element.

Args
  • data (pd.DataFrame): containing reindex from 0 to n_element, the coordinates X,Y,Z and
  • the ElementId as columns of Dataframe. Columns name : index, X, Y, Z, ElementId
  • x_obs (float): x coordinate of observed point in Feflow model reference coordinate
  • y_obs (float): y coordinate of observed point in Feflow model reference coordinate
  • z_obs (float): z coordinate of observed point in Feflow model reference coordinate
  • max_range (float): maximum range of observation weight along the ellipsoid axis
  • med_range (float): medium range of observation weight along the ellipsoid axis
  • min_range (float): minimum range of observation weight along the ellipsoid axis
  • azimuth (float): Angle in degree of max_range on an horizontal plane with the north, 0 degree is a pure northern azimut. Ranging from 0 to 360 degrees clockwise / ! \ Degrees increases opposite to trigonometric way. Real world map convention
  • dip (float): Angle in degree of max_range on an vertical plane, with 0 correpsonding to no dip, ranging from 0 to -90, -90 is pointing to the earth center
  • sill (float): Equivalent to the sill in variogram, giving a weight to points around observation point
  • nugget (float): Equivalent to the nugget in variogram, giving a random noise weight to points around observation point
  • model (str): Variogram model type used to compute the weight decay in the ellipsoid. "gaussian", "exponential" or "spherical". Models defined by Chiles and Delfiner 1999.
Raises
  • ValueError: Azimuth should be in radian between 0 and 2 pi
  • ValueError: Dip should be in radian between 0 and -pi/2
  • ValueError: Wrong variogram model, should be gaussian, spherical or exponential
  • ValueError: Dataframe coordinates columns should be named exactly X,Y,Z
Returns

index (int): Indexes of Feflow element inside the research ellispoid, values (float): Observation weight associated to elements in index