# Path: econtools/metrics/locallinear.py
# def llr(y: np.ndarray, x: np.ndarray,
#         x0: Optional[ArrayLike]=None,
#         N: Optional[int]=None, h: Optional[Union[str, float]]=None,
#         degree: int=1, kernel: str='epan', ci: bool=False):
#     """Local-linear Regression
# 
#     Args:
#         y (array): Dependent variable
#         x (array): Independent variable
# 
#     Keyword Args:
#         x0 (float or array-like): Default ``None``. Values at which to
#             caluculate regression. If ``None``, these values will be calculated
#             automatically. Default length of `x0` is min([len(x), 50]).  At
#             least one of ``x0`` and ``N`` must be ``None``.
#         N (int): Default ``None``. Number of ``x0`` values to calculate if
#             ``x0`` is not specified. At least one of ``x0`` and ``N`` must be
#             ``None``.
#         h (str or float): Defaults to None (Silverman's rule of thumb).
#             Bandwidth for kernel. May pass a float or any of the following for
#             Silverman's rule of thumb: ``'silverman'``, ``'thumb'``, ``'rot'``.
#         kernel (str): Type of kernel to be used. Options
#             are:
#                 - ``'epan'``, Epanechnikov (default)
#                 - ``'unif'``, Uniform
#                 - ``'tria'``, Triangle
#         degree (int): Defaults to 1. Degree of polynomial to use in local
#             regression.
#         ci (bool): Defaults to False. If True, also return confidence interval
#             for each point.
# 
#     Returns:
#         Stuff.
#     """
#     try:
#         assert len(y) == len(x)
#     except AssertionError:
#         raise ValueError("Vectors `y` and `x` must be same size.")
# 
#     # Set model parameters
#     kernel_obj = kernel_parser(kernel)
#     x0 = _set_x0(x, x0, N)      # TODO passed `x0` overwritten?
# 
#     if type(h) is str:
#         return_only = 'return' in cast(str, h)
#     else:
#         return_only = False
#     h_val = set_bandwidth(y, x, h, degree, kernel_obj, return_only=return_only)
#     if return_only:
#         return h_val
# 
#     # Loop over domain values
#     xG = model_loop(y, x, x0, h_val, degree, kernel_obj)
# 
#     est_stats = {
#         'h': h_val,
#         'kernel': kernel_obj.name,
#         'degree': degree
#     }
# 
#     if ci:
#         raise NotImplementedError   # TODO, see `NonParametrics2`, sec 3.11
#     else:
#         return xG, est_stats        # TODO, separate x0 and yhat


import numpy as np
import scipy.linalg as la
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, cdist
from scipy.optimize import minimize
from econtools.metrics.locallinear import llr
    return xG, h, sqdiff, est_stats


# MLE Driver
def variogram_mle(X, y, model, mle_args):
    """ Driver for MLE. """
    D = cdist(X, X)
    y_demeaned = y - y.mean()
    N = len(y)
    # Extract MLE args
    param0 = mle_args.get('param0')
    method = mle_args.get('method', 'BFGS')
    # Estimate
    mle = minimize(_likelihood, param0, args=(D, y_demeaned, N, model),
                   method=method)
    return mle, D

def _likelihood(ca, D, y_demeaned, n, model):
    R = model(D, ca)
    R_inv = la.inv(R)
    L = np.log(la.det(R)) + y_demeaned.dot(R_inv).dot(y_demeaned)
    return L


# Kernel reg of empirical data
def llr_gamma(X, y, maxd=None, scat=False, plot=False, ret_raw=False,
              **npregargs):
    """ Estimate variogram using local linear regression. """
    dist, sqdiff = empirical_gamma(X, y, maxd=maxd)
    # plot w/ kernel reg

# Path: econtools/metrics/locallinear.py
# def llr(y: np.ndarray, x: np.ndarray,
#         x0: Optional[ArrayLike]=None,
#         N: Optional[int]=None, h: Optional[Union[str, float]]=None,
#         degree: int=1, kernel: str='epan', ci: bool=False):
#     """Local-linear Regression
# 
#     Args:
#         y (array): Dependent variable
#         x (array): Independent variable
# 
#     Keyword Args:
#         x0 (float or array-like): Default ``None``. Values at which to
#             caluculate regression. If ``None``, these values will be calculated
#             automatically. Default length of `x0` is min([len(x), 50]).  At
#             least one of ``x0`` and ``N`` must be ``None``.
#         N (int): Default ``None``. Number of ``x0`` values to calculate if
#             ``x0`` is not specified. At least one of ``x0`` and ``N`` must be
#             ``None``.
#         h (str or float): Defaults to None (Silverman's rule of thumb).
#             Bandwidth for kernel. May pass a float or any of the following for
#             Silverman's rule of thumb: ``'silverman'``, ``'thumb'``, ``'rot'``.
#         kernel (str): Type of kernel to be used. Options
#             are:
#                 - ``'epan'``, Epanechnikov (default)
#                 - ``'unif'``, Uniform
#                 - ``'tria'``, Triangle
#         degree (int): Defaults to 1. Degree of polynomial to use in local
#             regression.
#         ci (bool): Defaults to False. If True, also return confidence interval
#             for each point.
# 
#     Returns:
#         Stuff.
#     """
#     try:
#         assert len(y) == len(x)
#     except AssertionError:
#         raise ValueError("Vectors `y` and `x` must be same size.")
# 
#     # Set model parameters
#     kernel_obj = kernel_parser(kernel)
#     x0 = _set_x0(x, x0, N)      # TODO passed `x0` overwritten?
# 
#     if type(h) is str:
#         return_only = 'return' in cast(str, h)
#     else:
#         return_only = False
#     h_val = set_bandwidth(y, x, h, degree, kernel_obj, return_only=return_only)
#     if return_only:
#         return h_val
# 
#     # Loop over domain values
#     xG = model_loop(y, x, x0, h_val, degree, kernel_obj)
# 
#     est_stats = {
#         'h': h_val,
#         'kernel': kernel_obj.name,
#         'degree': degree
#     }
# 
#     if ci:
#         raise NotImplementedError   # TODO, see `NonParametrics2`, sec 3.11
#     else:
#         return xG, est_stats        # TODO, separate x0 and yhat


# Path: econtools/geo/krig.py
import numpy as np
import scipy.linalg as la
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, cdist
from scipy.optimize import minimize
from econtools.metrics.locallinear import llr

    return xG, h, sqdiff, est_stats


# MLE Driver
def variogram_mle(X, y, model, mle_args):
    """ Driver for MLE. """
    D = cdist(X, X)
    y_demeaned = y - y.mean()
    N = len(y)
    # Extract MLE args
    param0 = mle_args.get('param0')
    method = mle_args.get('method', 'BFGS')
    # Estimate
    mle = minimize(_likelihood, param0, args=(D, y_demeaned, N, model),
                   method=method)
    return mle, D

def _likelihood(ca, D, y_demeaned, n, model):
    R = model(D, ca)
    R_inv = la.inv(R)
    L = np.log(la.det(R)) + y_demeaned.dot(R_inv).dot(y_demeaned)
    return L


# Kernel reg of empirical data
def llr_gamma(X, y, maxd=None, scat=False, plot=False, ret_raw=False,
              **npregargs):
    """ Estimate variogram using local linear regression. """
    dist, sqdiff = empirical_gamma(X, y, maxd=maxd)
    # plot w/ kernel reg

    xG, est_stats = llr(sqdiff, dist, **npregargs)