from setuptools import setup, find_packages
from distutils.command.build_py import build_py

# Get __version__ from PACKAGE_NAME/__init__.py without importing the package
# __version__ has to be defined in the first line
with open('spint/__init__.py', 'r') as f:
    exec(f.readline())


def _get_requirements_from_files(groups_files):
    groups_reqlist = {}

    for k, v in groups_files.items():
        with open(v, 'r') as f:
            pkg_list = f.read().splitlines()
        groups_reqlist[k] = pkg_list

    return groups_reqlist


def setup_package():
    _groups_files = {
        'base': 'requirements.txt',
        'tests': 'requirements_tests.txt',
        'docs': 'requirements_docs.txt'
    }

    reqs = _get_requirements_from_files(_groups_files)
    install_reqs = reqs.pop('base')
    extras_reqs = reqs

    setup(name='spint',  # name of package
          version=__version__,
          description='SPatial INTeraction models',  # short <80chr description
          url='https://github.com/pysal/spint',  # github repo
          maintainer='Taylor M. Oshan',
          maintainer_email='tayoshan@gmail.com',
          python_requires='>3.5',
          test_suite='nose.collector',
          tests_require=['nose'],
          keywords='spatial statistics',
          classifiers=[
              'Development Status :: 5 - Production/Stable',
              'Intended Audience :: Science/Research',
              'Intended Audience :: Developers',
              'Intended Audience :: Education',
              'Topic :: Scientific/Engineering',
              'Topic :: Scientific/Engineering :: GIS',
              'License :: OSI Approved :: BSD License',
              'Programming Language :: Python',
              'Programming Language :: Python :: 3.6',
              'Programming Language :: Python :: 3.7'
          ],
          license='3-Clause BSD',
          packages=find_packages(),
          install_requires=install_reqs,
          extras_require=extras_reqs,
          zip_safe=False,
          cmdclass={'build.py': build_py})


if __name__ == '__main__':
    setup_package()


# -*- coding: utf-8 -*-
#
# giddy documentation build configuration file, created by
# sphinx-quickstart on Wed Jun  6 15:54:22 2018.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import sys, os
import sphinx_bootstrap_theme


sys.path.insert(0, os.path.abspath('../../'))

#import your package to obtain the version info to display on the docs website
import spint


# -- General configuration ------------------------------------------------

# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [#'sphinx_gallery.gen_gallery',
              'sphinx.ext.autodoc',
              'sphinx.ext.autosummary',
              'sphinx.ext.viewcode',
              'sphinxcontrib.bibtex',
              'sphinx.ext.mathjax',
              'sphinx.ext.doctest',
              'sphinx.ext.intersphinx',
              'numpydoc',
              'matplotlib.sphinxext.plot_directive']


# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']

# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'

# The master toctree document.
master_doc = 'index'

# General information about the project.
project = "spint"  # string of your project name, for example, 'giddy'
copyright = '2018, pysal developers'
author = 'pysal developers'

# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The full version.
version = spint.__version__ #should replace it with your PACKAGE_NAME
release = spint.__version__ #should replace it with your PACKAGE_NAME

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'tests/*']

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'

# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False

# -- Options for HTML output ----------------------------------------------

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
#
# html_theme = 'alabaster'
html_theme = 'bootstrap'
html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
html_title = "%s v%s Manual" % (project, version)

# (Optional) Logo of your package. Should be small enough to fit the navbar (ideally 24x24).
# Path should be relative to the ``_static`` files directory.
#html_logo = "_static/images/package_logo.jpg"

# (Optional) PySAL favicon
html_favicon = "_static/images/pysal_favicon.ico"


# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {

    # Navigation bar title. (Default: ``project`` value)
    'navbar_title': "spint", # string of your project name, for example, 'giddy'

    # Render the next and previous page links in navbar. (Default: true)
    'navbar_sidebarrel': False,

    # Render the current pages TOC in the navbar. (Default: true)
    #'navbar_pagenav': True,
    #'navbar_pagenav': False,

    # No sidebar
    'nosidebar': True,

    # Tab name for the current pages TOC. (Default: "Page")
    #'navbar_pagenav_name': "Page",

    # Global TOC depth for "site" navbar tab. (Default: 1)
    # Switching to -1 shows all levels.
    'globaltoc_depth': 2,

    # Include hidden TOCs in Site navbar?
    #
    # Note: If this is "false", you cannot have mixed ``:hidden:`` and
    # non-hidden ``toctree`` directives in the same page, or else the build
    # will break.
    #
    # Values: "true" (default) or "false"
    'globaltoc_includehidden': "true",

    # HTML navbar class (Default: "navbar") to attach to <div> element.
    # For black navbar, do "navbar navbar-inverse"
    #'navbar_class': "navbar navbar-inverse",

    # Fix navigation bar to top of page?
    # Values: "true" (default) or "false"
    'navbar_fixed_top': "true",


    # Location of link to source.
    # Options are "nav" (default), "footer" or anything else to exclude.
    'source_link_position': 'footer',

    # Bootswatch (http://bootswatch.com/) theme.
    #
    # Options are nothing (default) or the name of a valid theme
    # such as "amelia" or "cosmo", "yeti", "flatly".
    'bootswatch_theme': "yeti",

    # Choose Bootstrap version.
    # Values: "3" (default) or "2" (in quotes)
    'bootstrap_version': "3",

    # Navigation bar menu
    'navbar_links': [
                     ("Installation", "installation"),
                     ("API", "api"),
                     ("References", "references"),
                     ],

}

# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']

# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {'sidebar': ['localtoc.html', 'sourcelink.html', 'searchbox.html']}

# -- Options for HTMLHelp output ------------------------------------------

# Output file base name for HTML help builder.
htmlhelp_basename = 'spint'+'doc'


# -- Options for LaTeX output ---------------------------------------------

latex_elements = {
    # The paper size ('letterpaper' or 'a4paper').
    #
    # 'papersize': 'letterpaper',

    # The font size ('10pt', '11pt' or '12pt').
    #
    # 'pointsize': '10pt',

    # Additional stuff for the LaTeX preamble.
    #
    # 'preamble': '',

    # Latex figure (float) alignment
    #
    # 'figure_align': 'htbp',
}

# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
#  author, documentclass [howto, manual, or own class]).
latex_documents = [
    (master_doc, 'spint.tex', u'spint Documentation',
     u'pysal developers', 'manual'),
]


# -- Options for manual page output ---------------------------------------

# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
    (master_doc, 'spint', u'spint Documentation',
     [author], 1)
]


# -- Options for Texinfo output -------------------------------------------

# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
#  dir menu entry, description, category)
texinfo_documents = [
    (master_doc, 'spint', u'spint Documentation',
     author, 'spint', 'One line description of project.',
     'Miscellaneous'),
]


# -----------------------------------------------------------------------------
# Autosummary
# -----------------------------------------------------------------------------

# Generate the API documentation when building
autosummary_generate = True
numpydoc_show_class_members = True
class_members_toctree = True
numpydoc_show_inherited_class_members = True
numpydoc_use_plots = True

# display the source code for Plot directive
plot_include_source = True

def setup(app):
    app.add_stylesheet("pysal-styles.css")

# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'https://docs.python.org/3.6/': None}



"""
CountModel class for dispatching different types of count models and different
types of estimation technqiues.
"""

__author__ = "Taylor Oshan tayoshan@gmail.com"

import numpy as np
from spglm.glm import GLM
from spglm.family import Poisson, QuasiPoisson


class CountModel(object):
    """
    Base class for variety of count-based models such as Poisson, negative binomial,
    etc. of the exponetial family.

    Parameters
    ----------
    y           : array
                  n x 1; n observations of the depedent variable
    X           : array
                  n x k; design matrix of k explanatory variables
    family      : instance of class 'family'
                  default is Poisson()
    constant    : boolean
                  True if intercept should be estimated and false otherwise.
                  Default is True.


    Attributes
    ----------
    y           : array
                  n x 1; n observations of the depedent variable
    X           : array
                  n x k; design matrix of k explanatory variables
    fitted      : boolean
                  False is model has not been fitted and True if it has been
                  successfully fitted. Deault is False.
    constant    : boolean
                  True if intercept should be estimated and false otherwise.
                  Default is True.

    Example
    -------
    >>> from spint.count_model import CountModel
    >>> import libpysal
    >>> db = libpysal.io.open(libpysal.examples.get_path('columbus.dbf'),'r')
    >>> y =  np.array(db.by_col("HOVAL"))
    >>> y = np.reshape(y, (49,1))
    >>> y = np.round(y).astype(int)
    >>> X = []
    >>> X.append(db.by_col("INC"))
    >>> X.append(db.by_col("CRIME"))
    >>> X = np.array(X).T
    >>> model = CountModel(y, X, family=Poisson())
    >>> results = model.fit('GLM')
    >>> results.params
    array([ 3.92159085,  0.01183491, -0.01371397])

    """

    def __init__(self, y, X, family=Poisson(), constant=True):
        self.y = self._check_counts(y)
        self.X = X
        self.constant = constant

    def _check_counts(self, y):
        if (y.dtype == 'int64') | (y.dtype == 'int32'):
            return y
        else:
            raise TypeError(
                'Dependent variable (y) must be composed of integers')

    def fit(self, framework='GLM', Quasi=False):
        """
        Method that fits a particular count model usign the appropriate
        estimation technique. Models include Poisson GLM, Negative Binomial GLM,
        Quasi-Poisson - at the moment Poisson GLM is the only option.

        TODO: add zero inflated variants and hurdle variants.

        Parameters
        ----------
        framework           : string
                            estimation framework; default is GLM
                             "GLM" | "QUASI" |
        """
        if (framework.lower() == 'glm'):
            if not Quasi:
                results = GLM(
                    self.y,
                    self.X,
                    family=Poisson(),
                    constant=self.constant).fit()
            else:
                results = GLM(
                    self.y,
                    self.X,
                    family=QuasiPoisson(),
                    constant=self.constant).fit()
            return CountModelResults(results)

        else:
            raise NotImplemented(
                'Poisson GLM is the only count model currently implemented')


class CountModelResults(object):
    """
    Results of estimated GLM and diagnostics.

    Parameters
    ----------
        results       : GLM object
                        Pointer to GLMResults object with estimated parameters
                        and diagnostics.

    Attributes
    ----------
        model         : GLM Object
                        Points to model object for which parameters have been
                        estimated. May contain additional diagnostics.
        y             : array
                        n*1, dependent variable.
        X             : array
                        n*k, independent variable, including constant.
        family        : string
                        Model type: 'Gaussian', 'Poisson', 'Logistic'
        n             : integer
                        Number of observations
        k             : integer
                        Number of independent variables
        df_model      : float
                        k-1, where k is the number of variables (including
                        intercept)
        df_residual   : float
                        observations minus variables (n-k)
                        routine.
        params        : array
                        n*k, estimared beta coefficients
        yhat          : array
                        n*1, predicted value of y (i.e., fittedvalues)
        cov_params    : array
                        Variance covariance matrix (kxk) of betas
        std_err       : array
                        k*1, standard errors of betas
        pvalues       : array
                        k*1, two-tailed pvalues of parameters
        tvalues       : array
                        k*1, the tvalues of the standard errors
        deviance      : float
                        value of the deviance function evalued at params;
                        see family.py for distribution-specific deviance
        llf           : float
                        value of the loglikelihood function evalued at params;
                        see family.py for distribution-specific loglikelihoods
        llnull        : float
                        value of the loglikelihood function evaluated with only an
                        intercept; see family.py for distribution-specific
                        loglikelihoods
        AIC           : float
                        Akaike information criterion
        resid         : array
                        response residuals; defined as y-mu

        resid_dev     : array
                        k x 1, residual deviance of model
        D2            : float
                        percentage of explained deviance
        adj_D2        : float

        pseudo_R2       : float
                        McFadden's pseudo R2  (coefficient of determination)
        adj_pseudoR2    : float
                        adjusted McFadden's pseudo R2

    """

    def __init__(self, results):
        self.y = results.y
        self.X = results.X
        self.family = results.family
        self.params = results.params
        self.AIC = results.aic
        self.df_model = results.df_model
        self.df_resid = results.df_resid
        self.llf = results.llf
        self.llnull = results.llnull
        self.yhat = results.mu
        self.deviance = results.deviance
        self.n = results.n
        self.k = results.k
        self.resid = results.resid_response
        self.resid_dev = results.resid_deviance
        self.cov_params = results.cov_params()
        self.std_err = results.bse
        self.pvalues = results.pvalues
        self.tvalues = results.tvalues
        self.D2 = results.D2
        self.adj_D2 = results.adj_D2
        self.pseudoR2 = results.pseudoR2
        self.adj_pseudoR2 = results.adj_pseudoR2
        self.model = results


"""
Various functions to test hypotheses regarding the dispersion of the variance of
a variable.

"""

__author__ = "Taylor Oshan tayoshan@gmail.com"

from spglm.glm import GLM
from spglm.family import Poisson
import numpy as np
import scipy.stats as stats
from types import FunctionType


def phi_disp(model):
    """
    Test the hypothesis that var[y] = mu (equidispersion) against the
    alternative hypothesis (quasi-Poisson) that var[y] = phi * mu  where mu
    is the expected value of y and phi is an estimated overdispersion
    coefficient which is equivalent to 1+alpha in the alternative alpha
    dispersion test.

    phi > 0: overdispersion
    phi = 1: equidispersion
    phi < 0: underdispersion

    Parameters
    ----------
    model       : Model results class
                  function can only be called on a sucessfully fitted model
                  which has a valid response variable, y, and a valid
                  predicted response variable, yhat.
    alt_var     : function
                  specifies an alternative varaince as a function of mu.
                  Function must take a single scalar as input and return a
                  single scalar as output
    Returns
    -------
    array       : [alpha coefficient, tvalue of alpha, pvalue of alpha]

    """
    try:
        y = model.y.reshape((-1, 1))
        yhat = model.yhat.reshape((-1, 1))
        ytest = (((y - yhat)**2 - y) / yhat).reshape((-1, 1))
    except BaseException:
        raise AttributeError(
            "Check that fitted model has valid 'y' and 'yhat' attributes")

    phi = 1 + np.mean(ytest)
    zval = np.sqrt(len(ytest)) * np.mean(ytest) / np.std(ytest, ddof=1)
    pval = stats.norm.sf(zval)

    return np.array([phi, zval, pval])


def alpha_disp(model, alt_var=lambda x: x):
    """
    Test the hypothesis that var[y] = mu (equidispersion) against the
    alternative hypothesis that var[y] = mu + alpha * alt_var(mu) where mu
    is the expected value of y, alpha is an estimated coefficient, and
    alt_var() specifies an alternative variance as a function of mu.
    alt_var=lambda x:x corresponds to an alternative hypothesis of a negative
    binomimal model with a linear variance function and alt_var=lambda
    x:x**2 correspinds to an alternative hypothesis of a negative binomial
    model with a quadratic varaince function.

    alpha > 0: overdispersion
    alpha = 1: equidispersion
    alpha < 0: underdispersion

    Parameters
    ----------
    model       : Model results class
                  function can only be called on a sucessfully fitted model
                  which has a valid response variable, y, and a valid
                  predicted response variable, yhat.
    alt_var     : function
                  specifies an alternative varaince as a function of mu.
                  Function must take a single scalar as input and return a
                  single scalar as output
    Returns
    -------
    array       : [alpha coefficient, tvalue of alpha, pvalue of alpha]

    """
    try:
        y = model.y.reshape((-1, 1))
        yhat = model.yhat.reshape((-1, 1))
        ytest = (((y - yhat)**2 - y) / yhat).reshape((-1, 1))
    except BaseException:
        raise AttributeError(
            "Make sure model passed has been estimated and has a valid 'y' and 'yhat' attribute")

    if isinstance(alt_var, FunctionType):
        X = (alt_var(yhat) / yhat).reshape((-1, 1))
        test_results = GLM(ytest, X, constant=False).fit()
        alpha = test_results.params[0]
        zval = test_results.tvalues[0]
        pval = stats.norm.sf(zval)
    else:
        raise TypeError(
            "The alternative variance function, 'alt_var', must be a valid function'")

    return np.array([alpha, zval, pval])


# coding=utf-8
# 3. FlowAccessibility = Accessibility of flow taking existing destinations
import numpy as np
import pandas as pd
import itertools

#-------------------------------------------------------------------------------------------------------------

def _generate_dummy_flows():
    nodes = ['A','B','C','D','E']
    destination_masses = [60,10,10,30,50]

    all_flow = pd.DataFrame( list(itertools.product(nodes,nodes
                                               )
                             )
                       ).rename(columns = {0:'origin_ID', 1:'destination_ID'
                                          }
                               )

    masses = {'nodes': nodes,'dest_masses': destination_masses
         }

    masses = pd.DataFrame({'nodes': nodes,'dest_masses': destination_masses
                      }, columns = ['nodes','dest_masses'
                                   ]
                     )

    all_flow['volume_in_unipartite'] = [10,10,10,10,10,
                                    10,0,0,10,10,
                                    10,0,0,10,0,
                                    10,10,10,0,10,
                                    10,10,0,10,10]

    all_flow['volume_in_bipartite'] = [0,0,10,10,10,
                          0,0,0,10,10,
                          0,0,0,0,0,
                          0,0,0,0,0,
                          0,0,0,0,0]

    all_flow['distances'] = [0,8,2,5,5,
                          8,0,10,7,4,
                          2,10,0,6,9,
                          5,7,6,0,2,
                          5,4,9,2,0]

    all_flow = all_flow.merge(masses, how = 'left', left_on = 'destination_ID', right_on = 'nodes')

    all_flow['results_all=False'] = [500, 510, 730, 230, 190,
                                     400, 890, 750, 400, 360,
                                     150, 690, 300, 300, 360,
                                     350, 780, 670, 530, 430,
                                     230, 690, 400, 370, 400]
    
    return all_flow


#-------------------------------------------------------------------------------------------------------------



def Accessibility(dest_nodes, distances, weights, masses, all_destinations=False, is_bipartite = False):

    """
    Function to calculate Accessibility for Competing Destination model, given a COMPLETE origin-destination edgelist. 
    See an example notebook to see how to construct the COMPLETE origin-destination edgelist from your data.

    Parameters
    ----------
    dest_nodes      : array of strings or numeric codes
                      n x 1; the DESTINATION column in the origin-destination edgelist 

    distances       : array of numbers
                      n x 1; the distance column in the origin-destination edgelist 

    weights         : array of numbers
                      n x 1; the flow volume column in the origin-destination edgelist 

    masses          : array of numbers
                      n x 1; the DESTINATION masses column in the origin-destination edgelist

    all_destinations: bolean, Deafult is False
                      True to consider all the existing destinations as a competing destinations, 
                      even those where flows does not exist in the data.
                      False to consider only those destinations as a competing destinations, which exists in the data. 
                      This option is only available for flow data that can be represented as unipartite graph.
    is_bipartite    : bolean, Deafult is False
                      True to predefine the flow graph as bipartite: one where origins and destinations are separate 
                      entities and where interaction can happen only in one direction, from origin to destination.
                      False to keep assumption for regular unipartite graph.
"""
    
    # convert numbers to integers
    distances = np.array(distances.astype(int))
    weights = np.array(weights.astype(int))
    masses = np.array(masses.astype(int))
    dest_nodes = np.array(dest_nodes.astype(str))
    
    # define error
    if len(distances) != len(weights) != len(masses) != len(dest_nodes):
        raise ValueError("One of the input array is different length then the others, but they should all be the same length. See notebook example if you are unsure what the input should look like ")
    if all_destinations & is_bipartite:
        raise ValueError("This option has not been implemented yet")
    
    # define number of rows
    nrows= len(dest_nodes)
    uniques = len(np.unique(np.array(dest_nodes)))
    
    # create binary for weight
    v_bin =  np.ones(nrows)
    weights[np.isnan(weights)] = 0
    v_bin[weights <= 0] = 0
    
    # define the base matrices
    distance = distances.reshape(uniques,uniques)
    mass =masses.reshape(uniques,uniques).T
      
    # define the identity array
    idn = np.identity(uniques) 
    idn = np.where((idn==1), 0, 1)
    idn = np.concatenate(uniques * [idn.ravel()], axis = 0
                        ).reshape(uniques,uniques,uniques
                                 ).T

    # multiply the distance by mass
    dm = distance * mass
    
     # combine all matrices for either all or existing destinations
    if is_bipartite:
        exists = v_bin.reshape(uniques,uniques).T
        output = (np.concatenate(uniques * [exists], axis = 0
                                ).reshape(uniques,uniques,uniques
                                         ).T
                 ) * idn * (uniques * [dm]
                           )
    elif all_destinations:
        exists = v_bin.reshape(uniques,uniques)
        output = idn * (nrows * [dm])
    else:
        exists = v_bin.reshape(uniques,uniques)
        output = (np.concatenate(uniques * [exists], axis = 0
                                ).reshape(uniques,uniques,uniques
                                         ).T
                 ) * idn * (uniques * [dm]
                           )
    
    # get the sum and covert to series
    output = (np.sum(output,axis = 1
                    ) 
             ).reshape(nrows
                      ).T
    
    
    return output


# coding=utf-8
"""
 Wilsonian (1967) family of gravity-type spatial interaction models

References
----------

Fotheringham, A. S. and O'Kelly, M. E. (1989). Spatial Interaction Models: Formulations
 and Applications. London: Kluwer Academic Publishers.

Wilson, A. G. (1967). A statistical theory of spatial distribution models.
 Transportation Research, 1, 253–269.

"""

__author__ = "Taylor Oshan tayoshan@gmail.com"

from types import FunctionType
import numpy as np
from scipy import sparse as sp
from spreg import user_output as User
from spreg.utils import sphstack
from spglm.utils import cache_readonly
from .count_model import CountModel
from .utils import sorensen, srmse, spcategorical


class BaseGravity(CountModel):
    """
    Base class to set up gravity-type spatial interaction models and dispatch
    estimaton technqiues.

    Parameters
    ----------
    flows           : array of integers
                      n x 1; observed flows between O origins and D destinations
    origins         : array of strings
                      n x 1; unique identifiers of origins of n flows
    destinations    : array of strings
                      n x 1; unique identifiers of destinations of n flows
    cost            : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cost_func       : string or function that has scalar input and output
                      functional form of the cost function;
                      'exp' | 'pow' | custom function
    o_vars          : array (optional)
                      n x p; p attributes for each origin of  n flows; default
                      is None
    d_vars          : array (optional)
                      n x p; p attributes for each destination of n flows;
                      default is None
    constant        : boolean
                      True to include intercept in model; True by default
    framework       : string
                      estimation technique; currently only 'GLM' is avaialble
    Quasi           : boolean
                      True to estimate QuasiPoisson model; should result in same
                      parameters as Poisson but with altered covariance; default
                      to true which estimates Poisson model
    SF              : array
                      n x 1; eigenvector spatial filter to include in the model;
                      default to None which does not include a filter; not yet
                      implemented
    CD              : array
                      n x 1; competing destination term that accounts for the
                      likelihood that alternative destinations are considered
                      along with each destination under consideration for every
                      OD pair; defaults to None which does not include a CD
                      term; not yet implemented
    Lag             : W object
                      spatial weight for n observations (OD pairs) used to
                      construct a spatial autoregressive model and estimator;
                      defaults to None which does not include an autoregressive
                      term; not yet implemented


    Attributes
    ----------
    f               : array
                      n x 1; observed flows; dependent variable; y
    n               : integer
                      number of observations
    k               : integer
                      number of parameters
    c               : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cf              : function
                      cost function; used to transform cost variable
    ov              : array
                      n x p(o); p attributes for each origin of n flows
    dv              : array
                      n x p(d); p attributes for each destination of n flows
    constant        : boolean
                      True to include intercept in model; True by default
    y               : array
                      n x 1; dependent variable used in estimation including any
                      transformations
    X               : array
                      n x k, design matrix used in estimation
    params          : array
                      n x k, k estimated beta coefficients; k = p(o) + p(d) + 1
    yhat            : array
                      n x 1, predicted value of y (i.e., fittedvalues)
    cov_params      : array
                      Variance covariance matrix (k x k) of betas
    std_err         : array
                      k x 1, standard errors of betas
    pvalues         : array
                      k x 1, two-tailed pvalues of parameters
    tvalues         : array
                      k x 1, the tvalues of the standard errors
    deviance        : float
                      value of the deviance function evalued at params;
                      see family.py for distribution-specific deviance
    resid_dev       : array
                      n x 1, residual deviance of model
    llf             : float
                      value of the loglikelihood function evalued at params;
                      see family.py for distribution-specific loglikelihoods
    llnull          : float
                      value of the loglikelihood function evaluated with only an
                      intercept; see family.py for distribution-specific
                      loglikelihoods
    AIC             : float
                      Akaike information criterion
    D2              : float
                      percentage of explained deviance
    adj_D2          : float
                      adjusted percentage of explained deviance
    pseudo_R2       : float
                      McFadden's pseudo R2  (coefficient of determination)
    adj_pseudoR2    : float
                      adjusted McFadden's pseudo R2
    SRMSE           : float
                      standardized root mean square error
    SSI             : float
                      Sorensen similarity index
    results         : object
                      full results from estimated model. May contain addtional
                      diagnostics
    Example
    -------
    >>> import numpy as np
    >>> import libpysal
    >>> from spint.gravity import BaseGravity
    >>> db = libpysal.io.open(libpysal.examples.get_path('nyc_bikes_ct.csv'))
    >>> cost = np.array(db.by_col('tripduration')).reshape((-1,1))
    >>> flows = np.array(db.by_col('count')).reshape((-1,1))
    >>> model = BaseGravity(flows, cost)
    >>> model.params
    array([17.84839637, -1.68325787])

    """

    def __init__(
            self,
            flows,
            cost,
            cost_func='pow',
            o_vars=None,
            d_vars=None,
            origins=None,
            destinations=None,
            constant=True,
            framework='GLM',
            SF=None,
            CD=None,
            Lag=None,
            Quasi=False):
        n = User.check_arrays(flows, cost)
        #User.check_y(flows, n)
        self.n = n
        self.f = flows
        self.c = cost
        self.ov = o_vars
        self.dv = d_vars
        if isinstance(cost_func, str):
            if cost_func.lower() == 'pow':
                self.cf = np.log
                if (self.c == 0).any():
                    raise ValueError(
                        "Zero values detected: cost function 'pow'"
                        "requires the logarithm of the cost variable which"
                        "is undefined at 0")
            elif cost_func.lower() == 'exp':
                self.cf = lambda x: x * 1.0
        elif (isinstance(cost_func, FunctionType)) | (isinstance(cost_func, np.ufunc)):
            self.cf = cost_func
        else:
            raise ValueError(
                "cost_func must be 'exp', 'pow' or a valid "
                " function that has a scalar as a input and output")

        y = np.reshape(self.f, (-1, 1))
        if isinstance(self, Gravity):
            X = np.empty((self.n, 0))
        else:
            X = sp.csr_matrix((self.n, 1))
        if isinstance(self, Production) | isinstance(self, Doubly):
            o_dummies = spcategorical(origins.flatten())
            if constant:
                o_dummies = o_dummies[:, 1:]
            X = sphstack(X, o_dummies, array_out=False)
        if isinstance(self, Attraction) | isinstance(self, Doubly):
            d_dummies = spcategorical(destinations.flatten())
            if constant | isinstance(self, Doubly):
                d_dummies = d_dummies[:, 1:]
            X = sphstack(X, d_dummies, array_out=False)
        if self.ov is not None:
            if isinstance(self, Gravity):
                for each in range(self.ov.shape[1]):
                    if (self.ov[:, each] == 0).any():
                        raise ValueError(
                            "Zero values detected in column %s "
                            "of origin variables, which are undefined for "
                            "Poisson log-linear spatial interaction models" %
                            each)
                    X = np.hstack(
                        (X, np.log(np.reshape(self.ov[:, each], (-1, 1)))))
            else:
                for each in range(self.ov.shape[1]):
                    if (self.ov[:, each] == 0).any():
                        raise ValueError(
                            "Zero values detected in column %s "
                            "of origin variables, which are undefined for "
                            "Poisson log-linear spatial interaction models" %
                            each)
                    ov = sp.csr_matrix(
                        np.log(np.reshape(self.ov[:, each], ((-1, 1)))))
                    X = sphstack(X, ov, array_out=False)
        if self.dv is not None:
            if isinstance(self, Gravity):
                for each in range(self.dv.shape[1]):
                    if (self.dv[:, each] == 0).any():
                        raise ValueError(
                            "Zero values detected in column %s "
                            "of destination variables, which are undefined for "
                            "Poisson log-linear spatial interaction models" %
                            each)
                    X = np.hstack(
                        (X, np.log(np.reshape(self.dv[:, each], (-1, 1)))))
            else:
                for each in range(self.dv.shape[1]):
                    if (self.dv[:, each] == 0).any():
                        raise ValueError(
                            "Zero values detected in column %s "
                            "of destination variables, which are undefined for "
                            "Poisson log-linear spatial interaction models" %
                            each)
                    dv = sp.csr_matrix(
                        np.log(np.reshape(self.dv[:, each], ((-1, 1)))))
                    X = sphstack(X, dv, array_out=False)
        if isinstance(self, Gravity):
            X = np.hstack((X, self.cf(np.reshape(self.c, (-1, 1)))))
        else:
            c = sp.csr_matrix(self.cf(np.reshape(self.c, (-1, 1))))
            X = sphstack(X, c, array_out=False)
            X = X[:, 1:]  # because empty array instantiated with extra column
        if not isinstance(self, (Gravity, Production, Attraction, Doubly)):
            X = self.cf(np.reshape(self.c, (-1, 1)))
        if SF:
            raise NotImplementedError(
                "Spatial filter model not yet implemented")
        if CD:
            raise NotImplementedError(
                "Competing destination model not yet implemented")
        if Lag:
            raise NotImplementedError(
                "Spatial Lag autoregressive model not yet implemented")

        CountModel.__init__(self, y, X, constant=constant)
        if (framework.lower() == 'glm'):
            if not Quasi:
                results = self.fit(framework='glm')
            else:
                results = self.fit(framework='glm', Quasi=True)
        else:
            raise NotImplementedError('Only GLM is currently implemented')

        self.params = results.params
        self.yhat = results.yhat
        self.cov_params = results.cov_params
        self.std_err = results.std_err
        self.pvalues = results.pvalues
        self.tvalues = results.tvalues
        self.deviance = results.deviance
        self.resid_dev = results.resid_dev
        self.llf = results.llf
        self.llnull = results.llnull
        self.AIC = results.AIC
        self.k = results.k
        self.D2 = results.D2
        self.adj_D2 = results.adj_D2
        self.pseudoR2 = results.pseudoR2
        self.adj_pseudoR2 = results.adj_pseudoR2
        self.results = results
        self._cache = {}

    @cache_readonly
    def SSI(self):
        return sorensen(self)

    @cache_readonly
    def SRMSE(self):
        return srmse(self)

    def reshape(self, array):
        if isinstance(array, np.ndarray):
            return array.reshape((-1, 1))
        elif isinstance(array, list):
            return np.array(array).reshape((-1, 1))
        else:
            raise TypeError(
                "input must be an numpy array or list that can be coerced"
                " into the dimensions n x 1")


class Gravity(BaseGravity):
    """
    Unconstrained (traditional gravity) gravity-type spatial interaction model

    Parameters
    ----------
    flows           : array of integers
                      n x 1; observed flows between O origins and D destinations
    cost            : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cost_func       : string or function that has scalar input and output
                      functional form of the cost function;
                      'exp' | 'pow' | custom function
    o_vars          : array (optional)
                      n x p; p attributes for each origin of  n flows; default
                      is None
    d_vars          : array (optional)
                      n x p; p attributes for each destination of n flows;
                      default is None
    constant        : boolean
                      True to include intercept in model; True by default
    framework       : string
                      estimation technique; currently only 'GLM' is avaialble
    Quasi           : boolean
                      True to estimate QuasiPoisson model; should result in same
                      parameters as Poisson but with altered covariance; default
                      to true which estimates Poisson model
    SF              : array
                      n x 1; eigenvector spatial filter to include in the model;
                      default to None which does not include a filter; not yet
                      implemented
    CD              : array
                      n x 1; competing destination term that accounts for the
                      likelihood that alternative destinations are considered
                      along with each destination under consideration for every
                      OD pair; defaults to None which does not include a CD
                      term; not yet implemented
    Lag             : W object
                      spatial weight for n observations (OD pairs) used to
                      construct a spatial autoregressive model and estimator;
                      defaults to None which does not include an autoregressive
                      term; not yet implemented

    Attributes
    ----------
    f               : array
                      n x 1; observed flows; dependent variable; y
    n               : integer
                      number of observations
    k               : integer
                      number of parameters
    c               : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cf              : function
                      cost function; used to transform cost variable
    ov              : array
                      n x p(o); p attributes for each origin of n flows
    dv              : array
                      n x p(d); p attributes for each destination of n flows
    constant        : boolean
                      True to include intercept in model; True by default
    y               : array
                      n x 1; dependent variable used in estimation including any
                      transformations
    X               : array
                      n x k, design matrix used in estimation
    params          : array
                      n x k, k estimated beta coefficients; k = p(o) + p(d) + 1
    yhat            : array
                      n x 1, predicted value of y (i.e., fittedvalues)
    cov_params      : array
                      Variance covariance matrix (kxk) of betas
    std_err         : array
                      k x 1, standard errors of betas
    pvalues         : array
                      k x 1, two-tailed pvalues of parameters
    tvalues         : array
                      k x 1, the tvalues of the standard errors
    deviance        : float
                      value of the deviance function evalued at params;
                      see family.py for distribution-specific deviance
    resid_dev       : array
                      n x 1, residual deviance of model
    llf             : float
                      value of the loglikelihood function evalued at params;
                      see family.py for distribution-specific loglikelihoods
    llnull          : float
                      value of the loglikelihood function evaluated with only an
                      intercept; see family.py for distribution-specific
                      loglikelihoods
    AIC             : float
                      Akaike information criterion
    D2              : float
                      percentage of explained deviance
    adj_D2          : float
                      adjusted percentage of explained deviance
    pseudo_R2       : float
                      McFadden's pseudo R2  (coefficient of determination)
    adj_pseudoR2    : float
                      adjusted McFadden's pseudo R2
    SRMSE           : float
                      standardized root mean square error
    SSI             : float
                      Sorensen similarity index
    results         : object
                      Full results from estimated model. May contain addtional
                      diagnostics
    Example
    -------
    >>> import numpy as np
    >>> import libpysal
    >>> from spint.gravity import Gravity
    >>> db = libpysal.io.open(libpysal.examples.get_path('nyc_bikes_ct.csv'))
    >>> cost = np.array(db.by_col('tripduration')).reshape((-1,1))
    >>> flows = np.array(db.by_col('count')).reshape((-1,1))
    >>> o_cap = np.array(db.by_col('o_cap')).reshape((-1,1))
    >>> d_cap = np.array(db.by_col('d_cap')).reshape((-1,1))
    >>> model = Gravity(flows, o_cap, d_cap, cost, 'exp')
    >>> model.params
    array([ 3.80050153e+00,  5.54103854e-01,  3.94282921e-01, -2.27091686e-03])

    """

    def __init__(self, flows, o_vars, d_vars, cost,
                 cost_func, constant=True, framework='GLM', SF=None, CD=None,
                 Lag=None, Quasi=False):
        self.f = np.reshape(flows, (-1, 1))
        if len(o_vars.shape) > 1:
            p = o_vars.shape[1]
        else:
            p = 1
        self.ov = np.reshape(o_vars, (-1, p))
        if len(d_vars.shape) > 1:
            p = d_vars.shape[1]
        else:
            p = 1
        self.dv = np.reshape(d_vars, (-1, p))
        self.c = np.reshape(cost, (-1, 1))
        #User.check_arrays(self.f, self.ov, self.dv, self.c)

        BaseGravity.__init__(
            self,
            self.f,
            self.c,
            cost_func=cost_func,
            o_vars=self.ov,
            d_vars=self.dv,
            constant=constant,
            framework=framework,
            SF=SF,
            CD=CD,
            Lag=Lag,
            Quasi=Quasi)

    def local(self, loc_index, locs):
        """
        Calibrate local models for subsets of data from a single location to all
        other locations

        Parameters
        ----------
        loc_index   : n x 1 array of either origin or destination id label for
                      flows; must be explicitly provided for local version of
                      basic gravity model since these are not passed to the
                      global model.

        locs        : iterable of either origin or destination labels for which
                      to calibrate local models; must also be explicitly
                      provided since local gravity models can be calibrated from origins
                      or destinations. If all origins are also destinations and
                      a local model is desired for each location then use
                      np.unique(loc_index)

        Returns
        -------
        results     : dict where keys are names of model outputs and diagnostics
                      and values are lists of location specific values.
        """
        results = {}
        covs = self.ov.shape[1] + self.dv.shape[1] + 1
        results['AIC'] = []
        results['deviance'] = []
        results['pseudoR2'] = []
        results['adj_pseudoR2'] = []
        results['D2'] = []
        results['adj_D2'] = []
        results['SSI'] = []
        results['SRMSE'] = []
        for cov in range(covs):
            results['param' + str(cov)] = []
            results['stde' + str(cov)] = []
            results['pvalue' + str(cov)] = []
            results['tvalue' + str(cov)] = []
        for loc in locs:
            subset = loc_index == loc
            f = self.reshape(self.f[subset])
            o_vars = self.ov[subset.reshape(self.ov.shape[0]), :]
            d_vars = self.dv[subset.reshape(self.dv.shape[0]), :]
            dij = self.reshape(self.c[subset])
            model = Gravity(f, o_vars, d_vars, dij, self.cf,
                            constant=False)
            results['AIC'].append(model.AIC)
            results['deviance'].append(model.deviance)
            results['pseudoR2'].append(model.pseudoR2)
            results['adj_pseudoR2'].append(model.adj_pseudoR2)
            results['D2'].append(model.D2)
            results['adj_D2'].append(model.adj_D2)
            results['SSI'].append(model.SSI)
            results['SRMSE'].append(model.SRMSE)
            for cov in range(covs):
                results['param' + str(cov)].append(model.params[cov])
                results['stde' + str(cov)].append(model.std_err[cov])
                results['pvalue' + str(cov)].append(model.pvalues[cov])
                results['tvalue' + str(cov)].append(model.tvalues[cov])
        return results


class Production(BaseGravity):
    """
    Production-constrained (origin-constrained) gravity-type spatial interaction model

    Parameters
    ----------
    flows           : array of integers
                      n x 1; observed flows between O origins and D destinations
    origins         : array of strings
                      n x 1; unique identifiers of origins of n flows; when
                      there are many origins it will be faster to use integers
                      rather than strings for id labels.
    cost            : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cost_func       : string or function that has scalar input and output
                      functional form of the cost function;
                      'exp' | 'pow' | custom function
    d_vars          : array (optional)
                      n x p; p attributes for each destination of n flows;
                      default is None
    constant        : boolean
                      True to include intercept in model; True by default
    framework       : string
                      estimation technique; currently only 'GLM' is avaialble
    Quasi           : boolean
                      True to estimate QuasiPoisson model; should result in same
                      parameters as Poisson but with altered covariance; default
                      to true which estimates Poisson model
    SF              : array
                      n x 1; eigenvector spatial filter to include in the model;
                      default to None which does not include a filter; not yet
                      implemented
    CD              : array
                      n x 1; competing destination term that accounts for the
                      likelihood that alternative destinations are considered
                      along with each destination under consideration for every
                      OD pair; defaults to None which does not include a CD
                      term; not yet implemented
    Lag             : W object
                      spatial weight for n observations (OD pairs) used to
                      construct a spatial autoregressive model and estimator;
                      defaults to None which does not include an autoregressive
                      term; not yet implemented

    Attributes
    ----------
    f               : array
                      n x 1; observed flows; dependent variable; y
    n               : integer
                      number of observations
    k               : integer
                      number of parameters
    c               : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cf              : function
                      cost function; used to transform cost variable
    o               : array
                      n x 1; index of origin id's
    dv              : array
                      n x p; p attributes for each destination of n flows
    constant        : boolean
                      True to include intercept in model; True by default
    y               : array
                      n x 1; dependent variable used in estimation including any
                      transformations
    X               : array
                      n x k, design matrix used in estimation
    params          : array
                      n x k, k estimated beta coefficients; k = # of origins + p + 1
    yhat            : array
                      n x 1, predicted value of y (i.e., fittedvalues)
    cov_params      : array
                      Variance covariance matrix (kxk) of betas
    std_err         : array
                      k x 1, standard errors of betas
    pvalues         : array
                      k x 1, two-tailed pvalues of parameters
    tvalues         : array
                      k x 1, the tvalues of the standard errors
    deviance        : float
                      value of the deviance function evalued at params;
                      see family.py for distribution-specific deviance
    resid_dev       : array
                      n x 1, residual deviance of model
    llf             : float
                      value of the loglikelihood function evalued at params;
                      see family.py for distribution-specific loglikelihoods
    llnull          : float
                      value of the loglikelihood function evaluated with only an
                      intercept; see family.py for distribution-specific
                      loglikelihoods
    AIC             : float
                      Akaike information criterion
    D2              : float
                      percentage of explained deviance
    adj_D2          : float
                      adjusted percentage of explained deviance
    pseudo_R2       : float
                      McFadden's pseudo R2  (coefficient of determination)
    adj_pseudoR2    : float
                      adjusted McFadden's pseudo R2
    SRMSE           : float
                      standardized root mean square error
    SSI             : float
                      Sorensen similarity index
    results         : object
                      Full results from estimated model. May contain addtional
                      diagnostics
    Example
    -------

    >>> import numpy as np
    >>> import libpysal
    >>> from spint.gravity import Production
    >>> db = libpysal.io.open(libpysal.examples.get_path('nyc_bikes_ct.csv'))
    >>> cost = np.array(db.by_col('tripduration')).reshape((-1,1))
    >>> flows = np.array(db.by_col('count')).reshape((-1,1))
    >>> o = np.array(db.by_col('o_tract')).reshape((-1,1))
    >>> d_cap = np.array(db.by_col('d_cap')).reshape((-1,1))
    >>> model = Production(flows, o, d_cap, cost, 'exp')
    >>> model.params[-4:]
    array([ 1.34721352,  0.96357345,  0.85535775, -0.00227444])

    """

    def __init__(self, flows, origins, d_vars, cost, cost_func, constant=True,
                 framework='GLM', SF=None, CD=None, Lag=None, Quasi=False):
        self.constant = constant
        self.f = self.reshape(flows)
        self.o = self.reshape(origins)

        try:
            if d_vars.shape[1]:
                p = d_vars.shape[1]
        except BaseException:
            p = 1
        self.dv = np.reshape(d_vars, (-1, p))
        self.c = self.reshape(cost)
        #User.check_arrays(self.f, self.o, self.dv, self.c)

        BaseGravity.__init__(
            self,
            self.f,
            self.c,
            cost_func=cost_func,
            d_vars=self.dv,
            origins=self.o,
            constant=constant,
            framework=framework,
            SF=SF,
            CD=CD,
            Lag=Lag,
            Quasi=Quasi)

    def local(self, locs=None):
        """
        Calibrate local models for subsets of data from a single location to all
        other locations

        Parameters
        ----------
        locs        : iterable of location (origins) labels; default is
                      None which calibrates a local model for each origin

        Returns
        -------
        results     : dict where keys are names of model outputs and diagnostics
                      and values are lists of location specific values
        """
        results = {}
        offset = 1
        covs = self.dv.shape[1] + 1
        results['AIC'] = []
        results['deviance'] = []
        results['pseudoR2'] = []
        results['adj_pseudoR2'] = []
        results['D2'] = []
        results['adj_D2'] = []
        results['SSI'] = []
        results['SRMSE'] = []
        for cov in range(covs):
            results['param' + str(cov)] = []
            results['stde' + str(cov)] = []
            results['pvalue' + str(cov)] = []
            results['tvalue' + str(cov)] = []
        if locs is None:
            locs = np.unique(self.o)
        for loc in np.unique(locs):
            subset = self.o == loc
            f = self.reshape(self.f[subset])
            o = self.reshape(self.o[subset])
            d_vars = self.dv[subset.reshape(self.dv.shape[0]), :]
            dij = self.reshape(self.c[subset])
            model = Production(f, o, d_vars, dij, self.cf, constant=False)
            results['AIC'].append(model.AIC)
            results['deviance'].append(model.deviance)
            results['pseudoR2'].append(model.pseudoR2)
            results['adj_pseudoR2'].append(model.adj_pseudoR2)
            results['D2'].append(model.D2)
            results['adj_D2'].append(model.adj_D2)
            results['SSI'].append(model.SSI)
            results['SRMSE'].append(model.SRMSE)
            for cov in range(covs):
                results['param' + str(cov)].append(model.params[offset + cov])
                results['stde' + str(cov)].append(model.std_err[offset + cov])
                results['pvalue' +
                        str(cov)].append(model.pvalues[offset + cov])
                results['tvalue' +
                        str(cov)].append(model.tvalues[offset + cov])
        return results


class Attraction(BaseGravity):
    """
    Attraction-constrained (destination-constrained) gravity-type spatial interaction model

    Parameters
    ----------
    flows           : array of integers
                      n x 1; observed flows between O origins and D destinations
    destinations    : array of strings
                      n x 1; unique identifiers of destinations of n flows; when
                      there are many destinations it will be faster to use
                      integers over strings for id labels.
    cost            : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cost_func       : string or function that has scalar input and output
                      functional form of the cost function;
                      'exp' | 'pow' | custom function
    o_vars          : array (optional)
                      n x p; p attributes for each origin of  n flows; default
                      is None
    constant        : boolean
                      True to include intercept in model; True by default
    y               : array
                      n x 1; dependent variable used in estimation including any
                      transformations
    X               : array
                      n x k, design matrix used in estimation
    framework       : string
                      estimation technique; currently only 'GLM' is avaialble
    Quasi           : boolean
                      True to estimate QuasiPoisson model; should result in same
                      parameters as Poisson but with altered covariance; default
                      to true which estimates Poisson model
    SF              : array
                      n x 1; eigenvector spatial filter to include in the model;
                      default to None which does not include a filter; not yet
                      implemented
    CD              : array
                      n x 1; competing destination term that accounts for the
                      likelihood that alternative destinations are considered
                      along with each destination under consideration for every
                      OD pair; defaults to None which does not include a CD
                      term; not yet implemented
    Lag             : W object
                      spatial weight for n observations (OD pairs) used to
                      construct a spatial autoregressive model and estimator;
                      defaults to None which does not include an autoregressive
                      term; not yet implemented

    Attributes
    ----------
    f               : array
                      n x 1; observed flows; dependent variable; y
    n               : integer
                      number of observations
    k               : integer
                      number of parameters
    c               : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cf              : function
                      cost function; used to transform cost variable
    d               : array
                      n x 1; index of destination id's
    ov              : array
                      n x p; p attributes for each origin of n flows
    constant        : boolean
                      True to include intercept in model; True by default
    params          : array
                      n x k, k estimated beta coefficients; k = # of
                      destinations + p + 1
    yhat            : array
                      n x 1, predicted value of y (i.e., fittedvalues)
    cov_params      : array
                      Variance covariance matrix (kxk) of betas
    std_err         : array
                      k x 1, standard errors of betas
    pvalues         : array
                      k x 1, two-tailed pvalues of parameters
    tvalues         : array
                      k x 1, the tvalues of the standard errors
    deviance        : float
                      value of the deviance function evalued at params;
                      see family.py for distribution-specific deviance
    resid_dev       : array
                      n x 1, residual deviance of model
    llf             : float
                      value of the loglikelihood function evalued at params;
                      see family.py for distribution-specific loglikelihoods
    llnull          : float
                      value of the loglikelihood function evaluated with only an
                      intercept; see family.py for distribution-specific
                      loglikelihoods
    AIC             : float
                      Akaike information criterion
    D2              : float
                      percentage of explained deviance
    adj_D2          : float
                      adjusted percentage of explained deviance
    pseudo_R2       : float
                      McFadden's pseudo R2  (coefficient of determination)
    adj_pseudoR2    : float
                      adjusted McFadden's pseudo R2
    SRMSE           : float
                      standardized root mean square error
    SSI             : float
                      Sorensen similarity index
    results         : object
                      Full results from estimated model. May contain addtional
                      diagnostics
    Example
    -------
    >>> import numpy as np
    >>> import libpysal
    >>> from spint.gravity import Attraction
    >>> nyc_bikes = libpysal.examples.load_example('nyc_bikes')
    >>> db = libpysal.io.open(nyc_bikes.get_path('nyc_bikes_ct.csv'))
    >>> cost = np.array(db.by_col('tripduration')).reshape((-1,1))
    >>> flows = np.array(db.by_col('count')).reshape((-1,1))
    >>> d = np.array(db.by_col('d_tract')).reshape((-1,1))
    >>> o_cap = np.array(db.by_col('o_cap')).reshape((-1,1))
    >>> model = Attraction(flows, d, o_cap, cost, 'exp')
    >>> model.params[-4:]
    array([ 1.21962276,  0.87634028,  0.88290909, -0.00229081])

    """

    def __init__(self, flows, destinations, o_vars, cost, cost_func,
                 constant=True, framework='GLM', SF=None, CD=None, Lag=None,
                 Quasi=False):
        self.f = np.reshape(flows, (-1, 1))
        if len(o_vars.shape) > 1:
            p = o_vars.shape[1]
        else:
            p = 1
        self.ov = np.reshape(o_vars, (-1, p))
        self.d = np.reshape(destinations, (-1, 1))
        self.c = np.reshape(cost, (-1, 1))
        #User.check_arrays(self.f, self.d, self.ov, self.c)

        BaseGravity.__init__(
            self,
            self.f,
            self.c,
            cost_func=cost_func,
            o_vars=self.ov,
            destinations=self.d,
            constant=constant,
            framework=framework,
            SF=SF,
            CD=CD,
            Lag=Lag,
            Quasi=Quasi)

    def local(self, locs=None):
        """
        Calibrate local models for subsets of data from a single location to all
        other locations

        Parameters
        ----------
        locs        : iterable of location (destinations) labels; default is
                      None which calibrates a local model for each destination

        Returns
        -------
        results     : dict where keys are names of model outputs and diagnostics
                      and values are lists of location specific values
        """
        results = {}
        offset = 1
        covs = self.ov.shape[1] + 1
        results['AIC'] = []
        results['deviance'] = []
        results['pseudoR2'] = []
        results['adj_pseudoR2'] = []
        results['D2'] = []
        results['adj_D2'] = []
        results['SSI'] = []
        results['SRMSE'] = []
        for cov in range(covs):
            results['param' + str(cov)] = []
            results['stde' + str(cov)] = []
            results['pvalue' + str(cov)] = []
            results['tvalue' + str(cov)] = []
        if locs is None:
            locs = np.unique(self.d)
        for loc in np.unique(locs):
            subset = self.d == loc
            f = self.reshape(self.f[subset])
            d = self.reshape(self.d[subset])
            o_vars = self.ov[subset.reshape(self.ov.shape[0]), :]
            dij = self.reshape(self.c[subset])
            model = Attraction(f, d, o_vars, dij, self.cf, constant=False)
            results['AIC'].append(model.AIC)
            results['deviance'].append(model.deviance)
            results['pseudoR2'].append(model.pseudoR2)
            results['adj_pseudoR2'].append(model.adj_pseudoR2)
            results['D2'].append(model.D2)
            results['adj_D2'].append(model.adj_D2)
            results['SSI'].append(model.SSI)
            results['SRMSE'].append(model.SRMSE)
            for cov in range(covs):
                results['param' + str(cov)].append(model.params[offset + cov])
                results['stde' + str(cov)].append(model.std_err[offset + cov])
                results['pvalue' +
                        str(cov)].append(model.pvalues[offset + cov])
                results['tvalue' +
                        str(cov)].append(model.tvalues[offset + cov])
        return results


class Doubly(BaseGravity):
    """
    Doubly-constrained gravity-type spatial interaction model

    Parameters
    ----------
    flows           : array of integers
                      n x 1; observed flows between O origins and D destinations
    origins         : array of strings
                      n x 1; unique identifiers of origins of n flows; when
                      there are many origins it will be faster to use integers
                      rather than strings for id labels.
    destinations    : array of strings
                      n x 1; unique identifiers of destinations of n flows; when
                      there are many destinations it will be faster to use
                      integers rather than strings for id labels
    cost            : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cost_func       : string or function that has scalar input and output
                      functional form of the cost function;
                      'exp' | 'pow' | custom function
    constant        : boolean
                      True to include intercept in model; True by default
    y               : array
                      n x 1; dependent variable used in estimation including any
                      transformations
    X               : array
                      n x k, design matrix used in estimation
    framework       : string
                      estimation technique; currently only 'GLM' is avaialble
    Quasi           : boolean
                      True to estimate QuasiPoisson model; should result in same
                      parameters as Poisson but with altered covariance; default
                      to true which estimates Poisson model
    SF              : array
                      n x 1; eigenvector spatial filter to include in the model;
                      default to None which does not include a filter; not yet
                      implemented
    CD              : array
                      n x 1; competing destination term that accounts for the
                      likelihood that alternative destinations are considered
                      along with each destination under consideration for every
                      OD pair; defaults to None which does not include a CD
                      term; not yet implemented
    Lag             : W object
                      spatial weight for n observations (OD pairs) used to
                      construct a spatial autoregressive model and estimator;
                      defaults to None which does not include an autoregressive
                      term; not yet implemented

    Attributes
    ----------
    f               : array
                      n x 1; observed flows; dependent variable; y
    n               : integer
                      number of observations
    k               : integer
                      number of parameters
    c               : array
                      n x 1; cost to overcome separation between each origin and
                      destination associated with a flow; typically distance or time
    cf              : function
                      cost function; used to transform cost variable
    o               : array
                      n x 1; index of origin id's
    d               : array
                      n x 1; index of destination id's
    constant        : boolean
                      True to include intercept in model; True by default
    params          : array
                      n x k, estimated beta coefficients; k = # of origins + #
                      of destinations; the first x-1 values
                      pertain to the x destinations (leaving out the first
                      destination to avoid perfect collinearity; no fixed
                      effect), the next x values pertain to the x origins, and the
                      final value is the distance decay coefficient
    yhat            : array
                      n x 1, predicted value of y (i.e., fittedvalues)
    cov_params      : array
                      Variance covariance matrix (kxk) of betas
    std_err         : array
                      k x 1, standard errors of betas
    pvalues         : array
                      k x 1, two-tailed pvalues of parameters
    tvalues         : array
                      k x 1, the tvalues of the standard errors
    deviance        : float
                      value of the deviance function evalued at params;
                      see family.py for distribution-specific deviance
    resid_dev       : array
                      n x 1, residual deviance of model
    llf             : float
                      value of the loglikelihood function evalued at params;
                      see family.py for distribution-specific loglikelihoods
    llnull          : float
                      value of the loglikelihood function evaluated with only an
                      intercept; see family.py for distribution-specific
                      loglikelihoods
    AIC             : float
                      Akaike information criterion
    D2              : float
                      percentage of explained deviance
    adj_D2          : float
                      adjusted percentage of explained deviance
    pseudo_R2       : float
                      McFadden's pseudo R2  (coefficient of determination)
    adj_pseudoR2    : float
                      adjusted McFadden's pseudo R2
    SRMSE           : float
                      standardized root mean square error
    SSI             : float
                      Sorensen similarity index
    results         : object
                      Full results from estimated model. May contain addtional
                      diagnostics
    Example
    -------
    >>> import numpy as np
    >>> import libpysal
    >>> from spint.gravity import Doubly
    >>> db = libpysal.io.open(libpysal.examples.get_path('nyc_bikes_ct.csv'))
    >>> cost = np.array(db.by_col('tripduration')).reshape((-1,1))
    >>> flows = np.array(db.by_col('count')).reshape((-1,1))
    >>> d = np.array(db.by_col('d_tract')).reshape((-1,1))
    >>> o = np.array(db.by_col('o_tract')).reshape((-1,1))
    >>> model = Doubly(flows, o, d, cost, 'exp')
    >>> model.params[-1:]
    array([-0.00232112])

    """

    def __init__(self, flows, origins, destinations, cost, cost_func,
                 constant=True, framework='GLM', SF=None, CD=None, Lag=None,
                 Quasi=False):

        self.f = np.reshape(flows, (-1, 1))
        self.o = np.reshape(origins, (-1, 1))
        self.d = np.reshape(destinations, (-1, 1))
        self.c = np.reshape(cost, (-1, 1))
        #User.check_arrays(self.f, self.o, self.d, self.c)

        BaseGravity.__init__(
            self,
            self.f,
            self.c,
            cost_func=cost_func,
            origins=self.o,
            destinations=self.d,
            constant=constant,
            framework=framework,
            SF=SF,
            CD=CD,
            Lag=Lag,
            Quasi=Quasi)

    def local(self, locs=None):
        """
        **Not inmplemented for doubly-constrained models** Not possible due to
        insufficient degrees of freedom.

        Calibrate local models for subsets of data from a single location to all
        other locations
        """
        raise NotImplementedError(
            "Local models not possible for"
            " doubly-constrained model due to insufficient degrees of freedom.")


"""
Implementations of universal spatial interaction models: Lenormand's
model, radiation model, and population-weighted opportunities.

References
----------
Lenormand, M., Huet, S., Gargiulo, F., and Deffuant, G. (2012). "A Universal
    Model of Commuting Networks." PLOS One, 7, 10.

Simini, F., Gonzalez, M. C., Maritan, A., Barabasi, A.-L. (2012). "A universal
    model for mobility and migration patterns." Nature, 484, 96-100.

Yan, X.-Y., Zhao, C., Fan, Y., Di, Z., and Wang, W.-X. (2014). "Universal
    predictability of mobility patterns in cities." Journal of the Royal
    Society Interface, 11, 100.
"""

__author__ = 'Tyler Hoffman tylerhoff1@gmail.com'

from abc import ABC, abstractmethod
import numpy as np
import pandas as pd
from scipy.stats import pearsonr


class Universal(ABC):
    """
    Base class for all the universal models as they all have similar
    underlying structures. For backend design purposes, not practical use.

    Parameters
    ----------
    inflows         : array of reals
                      N x 1, observed flows into each location
    outflows        : array of reals
                      M x 1, observed flows out of each location
    dists           : matrix of reals
                      N x M, pairwise distances between each location

    Attributes
    ----------
    N               : integer
                      number of origins
    M               : integer
                      number of destinations
    flowmat         : abstract method
                      estimates flows, implemented by children
    """
    def __init__(self, inflows, outflows, dists):
        self.N = len(outflows)           # number of origins
        self.M = len(inflows)            # number of destinations
        self.outflows = outflows.copy()  # list of origin outflows
        self.inflows = inflows.copy()    # list of destination inflows
        self.dists = dists.copy()        # list of distances

    @abstractmethod
    def flowmat(self): pass


class Lenormand(Universal):
    """
    Universal model based off of Lenormand et al. 2012,
    "A Universal Model of Commuting Networks".

    Parameters
    ----------
    inflows         : array of reals
                      N x 1, observed flows into each location
    outflows        : array of reals
                      M x 1, observed flows out of each location
    dists           : matrix of reals
                      N x M, pairwise distances between each location
    beta            : scalar
                      real, universal parameter for the model
    avg_sa          : scalar
                      real, average surface area of units

    Attributes
    ----------
    N               : integer
                      number of origins
    M               : integer
                      number of destinations
    calibrate       : method
                      calibrates beta using constants from the paper
    flowmat         : method
                      estimates flows via the Lenormand model
    """

    def __init__(self, inflows, outflows, dists, beta=1, avg_sa=None):
        super().__init__(inflows, outflows, dists)
        self.beta = self.calibrate(avg_sa) if avg_sa is not None else beta

    def calibrate(self, avg_sa):
        # Constants from the paper
        nu = 0.177
        alpha = 3.15 * 10**(-4)
        self.beta = alpha*avg_sa**(-nu)

    def flowmat(self):
        # Builds the matrix T from the parameter beta and a matrix of distances
        T = np.zeros((self.N, self.M))

        # Copy class variables so as not to modify
        sIN = self.inflows.copy()
        sOUT = self.outflows.copy()

        # Assembly loop
        while sum(sOUT) > 0:
            # Pick random nonzero sOUT
            idxs, = np.where(sOUT > 0)
            i = np.random.choice(idxs)

            # Compute Pij's (not memoized b/c it changes on iteration)
            Pi = np.multiply(sIN, np.exp(-self.beta*self.dists[i, :])) / \
                np.dot(sIN, np.exp(-self.beta*self.dists[i, :]))

            # Pick random j according to Pij
            j = np.random.choice(range(self.N), p=Pi)

            # Adjust values
            T[i, j] += 1
            sIN[j] -= 1
            sOUT[i] -= 1

        return T


class Radiation(Universal):
    """
    Universal model based off of Simini et al. 2012,
    "A universal model for mobility and migration patterns".
    Requires slightly more data than Lenormand.

    Parameters
    ----------
    inflows         : array of reals
                      N x 1, observed flows into each location
    outflows        : array of reals
                      M x 1, observed flows out of each location
    dists           : matrix of reals
                      N x M, pairwise distances between each location
    ilocs           : array of reals
                      N x 2, inflow node locations
    olocs           : array of reals
                      M x 2, outflow node locations

    Attributes
    ----------
    N               : integer
                      number of origins
    M               : integer
                      number of destinations
    flowmat         : method
                      estimates flows via the Radiation model
    """

    def __init__(self, inflows, outflows, dists, ilocs, olocs):
        super().__init__(inflows, outflows, dists)
        self.ilocs = ilocs.copy()
        self.olocs = olocs.copy()

    def _from_origin(self, idx, total_origins):
        # Sort destinations by distance from origin
        didxs = np.argsort(self.dists[idx, :])
        inflows = self.inflows[didxs]

        # Normalization
        F = 1.0/(1.0 - self.outflows[idx]/total_origins)

        pop_in_radius = 0
        flows = np.zeros((self.M,))
        for j in range(self.M):
            # Use formula from the paper
            flows[j] = F*(self.outflows[idx]*inflows[j]) / \
                       ((self.outflows[idx] + pop_in_radius) *
                        (self.outflows[idx] + inflows[j] + pop_in_radius))

            pop_in_radius += inflows[j]

        # Unsort list
        return flows[didxs.argsort()]

    def flowmat(self):
        # Builds the OD matrix T from the inputted data
        T = np.zeros((self.N, self.M))
        total_origins = sum(self.outflows)

        for i in range(self.N):
            T[i, :] = self._from_origin(i, total_origins)

        return T


class PWO(Universal):
    """
    Population-weighted opportunies (PWO) implements a
    universal model based off of Yan et al. 2014,
    "Universal predictability of mobility patterns in cities".
    Requires slightly more data than Lenormand.

    Parameters
    ----------
    inflows         : array of reals
                      N x 1, observed flows into each location
    outflows        : array of reals
                      M x 1, observed flows out of each location
    dists           : matrix of reals
                      N x M, pairwise distances between each location
    ilocs           : array of reals
                      N x 2, inflow node locations
    olocs           : array of reals
                      M x 2, outflow node locations

    Attributes
    ----------
    N               : integer
                      number of origins
    M               : integer
                      number of destinations
    flowmat         : method
                      estimates flows via the Radiation model
    """

    def __init__(self, inflows, outflows, dists, ilocs, olocs):
        super().__init__(inflows, outflows, dists)
        self.ilocs = ilocs.copy()
        self.olocs = olocs.copy()
        self.total = sum(inflows)  # total population of the system

    def _from_destination(self, jdx):
        # Sort origins by distance from destination
        didxs = np.argsort(self.dists[jdx, :])
        outflows = self.outflows[didxs]
        pop_in_radius = self.inflows[jdx]  # here pop_in_radius includes endpts
        flows = np.zeros((self.N,))

        # Loop over origins
        for i in range(self.N):
            pop_in_radius += outflows[i]  # add other endpt

            # Compute denominator
            denom = 0
            denom_pop_in_radius = outflows[i]
            for k in range(self.M):  # loop over destinations
                denom_pop_in_radius += self.inflows[k]
                if k != i:
                    denom += self.inflows[k] * (1/denom_pop_in_radius -
                                                1/self.total)

            # Use formula from the paper
            flows[i] = self.inflows[jdx]*(1/pop_in_radius - 1/self.total)/denom

        # Unsort list
        return flows[didxs.argsort()]

    def flowmat(self):
        # Builds the OD matrix T from the inputted data
        T = np.zeros((self.N, self.M))

        for j in range(self.M):
            T[:, j] = self._from_destination(j)

        return T


def test():
    # Read data from Austria file
    N = 9
    austria = pd.read_csv('austria.csv')
    modN = austria[austria.index % N == 0]
    outflows = modN['Oi'].values
    inflows = austria['Dj'].head(n=N).values
    locs = np.zeros((N, 2))
    locs[:, 0] = modN['X'].values
    locs[:, 1] = modN['Y'].values
    dists = np.reshape(austria['Dij'].values, (N, N), order='C')
    T_obs = np.reshape(austria['Data'].values, (N, N), order='C')

    # Lenormand paper's model
    model = Lenormand(inflows, outflows, dists)
    T_L = model.flowmat()
    print(pearsonr(T_L.flatten(), T_obs.flatten()))

    # Radiation model -- requires locations of each node
    model = Radiation(inflows, outflows, dists, locs, locs)
    T_R = model.flowmat()
    print(pearsonr(T_R.flatten(), T_obs.flatten()))

    # PWO model
    model = PWO(inflows, outflows, dists, locs, locs)
    T_P = model.flowmat()
    print(pearsonr(T_P.flatten(), T_obs.flatten()))


if __name__ == '__main__':
    test()


"""
Useful functions for analyzing spatial interaction data.
"""

__author__ = "Taylor Oshan tayoshan@gmail.com"

from scipy import sparse as sp
import numpy as np
from collections import defaultdict
from functools import partial
from itertools import count


def CPC(model):
    """
    Common part of commuters based on Sorensen index
    Lenormand et al. 2012
    """
    y = model.y
    try:
        yhat = model.yhat.resahpe((-1, 1))
    except BaseException:
        yhat = model.mu((-1, 1))
    N = model.n
    YYhat = np.hstack([y, yhat])
    NCC = np.sum(np.min(YYhat, axis=1))
    NCY = np.sum(Y)
    NCYhat = np.sum(yhat)
    return (N * NCC) / (NCY + NCYhat)


def sorensen(model):
    """
    Sorensen similarity index

    For use on spatial interaction models; N = sample size
    rather than N = number of locations and normalized by N instead of N**2
    """
    try:
        y = model.y.reshape((-1, 1))
    except BaseException:
        y = model.f.reshape((-1, 1))
    try:
        yhat = model.yhat.reshape((-1, 1))
    except BaseException:
        yhat = model.mu.reshape((-1, 1))
    N = model.n
    YYhat = np.hstack([y, yhat])
    num = 2.0 * np.min(YYhat, axis=1)
    den = yhat + y
    return (1.0 / N) * (np.sum(num.reshape((-1, 1)) / den.reshape((-1, 1))))


def srmse(model):
    """
    Standardized root mean square error
    """
    n = float(model.n)
    try:
        y = model.y.reshape((-1, 1)).astype(float)
    except BaseException:
        y = model.f.reshape((-1, 1)).astype(float)
    try:
        yhat = model.yhat.reshape((-1, 1)).astype(float)
    except BaseException:
        yhat = model.mu.reshape((-1, 1)).astype(float)
    srmse = ((np.sum((y - yhat)**2) / n)**.5) / (np.sum(y) / n)
    return srmse


def spcategorical(index):
    '''
    Returns a dummy matrix given an array of categorical variables.
    Parameters
    ----------
    n_cat_ids    : array
                   A 1d vector of the categorical labels for n observations.

    Returns
    --------
    dummy        : array
                   A sparse matrix of dummy (indicator/binary) variables for the
                   categorical data.

    '''
    if np.squeeze(index).ndim == 1:
        id_set = np.unique(index)
        n = len(index)
        # if index.dtype.type is not np.int_:
        mapper = defaultdict(partial(next, count()))
        [mapper[each] for each in id_set]
        index = [mapper[each] for each in index]
        indptr = np.arange(n + 1, dtype=int)
        return sp.csr_matrix((np.ones(n), index, indptr))
    else:
        raise IndexError("The index %s is not understood" % index)


#old and slow
"""
def spcategorical(n_cat_ids):
    '''
    Returns a dummy matrix given an array of categorical variables.
    Parameters
    ----------
    n_cat_ids    : array
                   A 1d vector of the categorical labels for n observations.

    Returns
    --------
    dummy        : array
                   A sparse matrix of dummy (indicator/binary) variables for the
                   categorical data.

    '''
    if np.squeeze(n_cat_ids).ndim == 1:
        cat_set = np.unique(n_cat_ids)
        n = len(n_cat_ids)
        index = [np.where(cat_set == id)[0].tolist()[0] for id in n_cat_ids]
        indptr = np.arange(n+1, dtype=int)
        return sp.csr_matrix((np.ones(n), index, indptr))
    else:
        raise IndexError("The index %s is not understood" % col)
"""


"""
Classes for statistics for testing hypotheses of spatial autocorrelation amongst
vectors.
"""

_author_ = "Taylor Oshan tayoshan@gmail.com, Levi Wolf levi.john.wolf@gmail.com"

import numpy as np
import scipy.stats as stats
from libpysal.weights.distance import DistanceBand

PERMUTATIONS = 99


class VecMoran:
    """Moran's I Global Autocorrelation Statistic For Vectors

    Parameters
    ----------
    y               : array
                      variable measured across n origin-destination vectors
    w               : W
                      spatial weights instance
    focus           : string
                      denotes whether to calculate the statistic with a focus on
                      spatial proximity between origins or destinations; default
                      is 'origin' but option include:

                      'origin' | 'destination'

    rand            : string
                      denote which randomization technqiue to use for
                      significance testing; default is 'A' but options are:

                      'A': transate entire vector
                      'B': shuffle points and redraw vectors

    permutations    : int
                      number of random permutations for calculation of
                      pseudo-p_values
    two_tailed      : boolean
                      If True (default) analytical p-values for Moran are two
                      tailed, otherwise if False, they are one-tailed.
    Attributes
    ----------
    y               : array
                      original variable
    w               : W obejct
                      original w object
    n               : integer
                      number of vectors
    o               : array
                      n x 2; 2D coordinates of vector origins
    d               : array
                      n x 2: 2D coordinates of vector destinations
    alpha           : scalar
                      distance decay parameter harvested from W object
    binary          : boolean
                      True is all entries in W > 0 are set to 1; False if if they
                      are inverse distance weighted; default is False; attribute is
                      harvested from W object
    build_sp        : boolean
                      True if W object is build using sparse distance matrix and
                      False if it is built using a dense distance matrix; attribute
                      is harvested from W object
    threshold       : scalar
                      all values larger than threshold are set 0 in W object;
                      attribute is harvested from W object
    silent          : boolean
                      True if island warnings are silent and False if they are not;
                      default is False; attribute is harvested from W object
    focus           : string
                      denotes whether to calculate the statistic with a focus on
                      spatial proximity between origins or destinations; default
                      is 'origin' but option include:

                      'origin' | 'destination'

    rand            : string
                      denote which randomization technqiue to use for
                      significance testing; default is 'A' but options are:

                      'A': transate entire vector
                      'B': shuffle points and redraw vectors

    permutations    : int
                      number of permutations
    I               : float
                      value of vector-based Moran's I
    EI              : float
                      expected value under randomization assumption
    VI_rand         : float
                      variance of I under randomization assumption
    seI_rand        : float
                      standard deviation of I under randomization assumption
    z_rand          : float
                      z-value of I under randomization assumption
    p_rand          : float
                      p-value of I under randomization assumption
    two_tailed      : boolean
                      If True p_norm and p_rand are two-tailed, otherwise they
                      are one-tailed.
    sim             : array
                      (if permutations>0)
                      vector of I values for permuted samples
    p_sim           : array
                      (if permutations>0)
                      p-value based on permutations (one-tailed)
                      null: spatial randomness
                      alternative: the observed I is extreme if
                      it is either extremely greater or extremely lower
                      than the values obtained based on permutations
    EI_sim          : float
                      (if permutations>0)
                      average value of I from permutations
    VI_sim          : float
                      (if permutations>0)
                      variance of I from permutations
    seI_sim         : float
                      (if permutations>0)
                      standard deviation of I under permutations.
    z_sim           : float
                      (if permutations>0)
                      standardized I based on permutations
    p_z_sim         : float
                      (if permutations>0)
                      p-value based on standard normal approximation from
                      permutations

    Examples
    --------
    >>> import numpy as np
    >>> np.random.seed(1)
    >>> from libpysal.weights import DistanceBand
    >>> from spint.vec_SA import VecMoran
    >>> vecs = np.array([[1, 55, 60, 100, 500],
    ...                  [2, 60, 55, 105, 501],
    ...                  [3, 500, 55, 155, 500],
    ...                  [4, 505, 60, 160, 500],
    ...                  [5, 105, 950, 105, 500],
    ...                  [6, 155, 950, 155, 499]])
    >>> origins = vecs[:, 1:3]
    >>> dests = vecs[:, 3:5]
    >>> wo = DistanceBand(origins, threshold=9999, alpha=-1.5, binary=False)
    >>> wd = DistanceBand(dests, threshold=9999, alpha=-1.5, binary=False)

    #randomization technique A
    >>> vmo = VecMoran(vecs, wo, focus='origin', rand='A')
    >>> vmd = VecMoran(vecs, wd, focus='destination', rand='A')
    >>> vmo.I
    0.6459445943670211
    >>> vmo.p_z_sim
    0.03898650733809228
    >>> vmd.I
    -0.7646036950223406
    >>> vmd.p_z_sim
    0.11275129553163704

    #randomization technique B
    >>> vmo = VecMoran(vecs, wo, focus='origin', rand='B')
    >>> vmd = VecMoran(vecs, wd, focus='destination', rand='B')
    >>> vmo.I
    0.6459445943670211
    >>> vmo.p_z_sim
    0.05087923006558356
    >>> vmd.I
    -0.7646036950223406
    >>> vmd.p_z_sim
    0.1468368983650693

    """

    def __init__(
            self,
            y,
            w,
            focus='origin',
            rand='A',
            permutations=PERMUTATIONS,
            two_tailed=True):
        self.y = y
        self.o = y[:, 1:3]
        self.d = y[:, 3:5]
        self.focus = focus
        self.rand = rand
        self.permutations = permutations
        self.two_tailed = two_tailed
        if isinstance(w, DistanceBand):
            self.w = w
        else:
            raise TypeError('Spatial weight, W, must be of type DistanceBand')
        try:
            self.threshold = w.threshold
            self.alpha = w.alpha
            self.build_sp = w.build_sp
            self.binary = w.binary
            self.silence_warnings = w.silence_warnings
        except BaseException:
            raise AttributeError('W object missing necessary attributes: '
                                 'threshold, alpha, binary, build_sp, silent')

        self.__moments()
        self.I = self.__calc(self.z)
        self.z_rand = (self.I - self.EI) / self.seI_rand

        if self.z_rand > 0:
            self.p_rand = 1 - stats.norm.cdf(self.z_rand)
        else:
            self.p_rand = stats.norm.cdf(self.z_rand)

        if self.two_tailed:
            self.p_rand *= 2.

        if permutations:
            if self.rand.upper() == 'A':
                sim = self.__rand_vecs_A(self.focus)
            elif self.rand.upper() == 'B':
                sim = self.__rand_vecs_B(self.focus)
            else:
                raise ValueError(
                    "Parameter 'rand' must take a value of either 'A' or 'B'")

            self.sim = sim = np.array(sim)
            above = sim >= self.I
            larger = above.sum()
            if (self.permutations - larger) < larger:
                larger = self.permutations - larger
            self.p_sim = (larger + 1.) / (permutations + 1.)
            self.EI_sim = sim.sum() / permutations
            self.seI_sim = np.array(sim).std()
            self.VI_sim = self.seI_sim ** 2
            self.z_sim = (self.I - self.EI_sim) / self.seI_sim
            if self.z_sim > 0:
                self.p_z_sim = 1 - stats.norm.cdf(self.z_sim)
            else:
                self.p_z_sim = stats.norm.cdf(self.z_sim)

    def __moments(self):
        self.n = len(self.y)
        xObar = self.o[:, 0].mean()
        yObar = self.o[:, 1].mean()
        xDbar = self.d[:, 0].mean()
        yDbar = self.d[:, 1].mean()
        u = (self.y[:, 3] - self.y[:, 1]) - (xDbar - xObar)
        v = (self.y[:, 4] - self.y[:, 2]) - (yDbar - yObar)
        z = np.outer(u, u) + np.outer(v, v)
        self.z = z
        self.uv2ss = np.sum(np.dot(u, u) + np.dot(v, v))
        self.EI = -1. / (self.n - 1)
        n = self.n
        s1 = self.w.s1
        W = self.w.s0
        s2 = self.w.s2

        v_num = n * n * s1 - n * s2 + 3 * W * W
        v_den = (n - 1) * (n + 1) * W * W

        a2 = np.sum(np.dot(u, u)) / n
        b2 = np.sum(np.dot(v, v)) / n
        m2 = a2 + b2
        a4 = np.sum(np.dot(np.dot(u, u), np.dot(u, u))) / n
        b4 = np.sum(np.dot(np.dot(v, u), np.dot(v, v))) / n
        n1 = a2**2 * ((n**2 - 3 * n + 3) * s1 - n * s2 + 3 * W**2)
        n2 = a4 * ((n**2 - n) * s1 - 2 * n * s2 + 6 * W**2)
        n3 = b2**2 * ((n**2 - 3 * n + 3) * s1 - n * s2 + 3 * W**2)
        n4 = b4 * ((n**2 - n) * s1 - 2 * n * s2 + 6 * W**2)
        d = (n - 1) * (n - 2) * (n - 3)
        self.VI_rand = 1 / (W**2 * m2**2) * \
            ((n1 - n2) / d + (n3 - n4) / d) + \
            ((a2 * b2) - m2**2) / (m2**2 * (n - 1)**2)
        self.seI_rand = self.VI_rand ** (1 / 2.)

    def __calc(self, z):
        zl = self._slag(self.w, z)
        inum = np.sum(zl)
        return self.n / self.w.s0 * inum / self.uv2ss

    def _newD(self, oldO, oldD, newO):
        oldOX, oldOY = oldO[:, 0], oldO[:, 1]
        oldDX, oldDY = oldD[:, 0], oldD[:, 1]
        newOX, newOY = newO[:, 0], newO[:, 1]
        deltaX = newOX - oldOX
        deltaY = newOY - oldOY
        newDX = oldDX + deltaX
        newDY = oldDY + deltaY
        return np.hstack([newDX.reshape((-1, 1)), newDY.reshape((-1, 1))])

    def _newO(self, oldO, oldD, newD):
        oldOX, oldOY = oldO[:, 0], oldO[:, 1]
        oldDX, oldDY = oldD[:, 0], oldD[:, 1]
        newDX, newDY = newD[:, 0], newD[:, 1]
        deltaX = newDX - oldDX
        deltaY = newDY - oldDY
        newOX = oldOX + deltaX
        newOY = oldOY + deltaY
        return np.hstack([newOX.reshape((-1, 1)), newOY.reshape((-1, 1))])

    def __rand_vecs_A(self, focus):
        if focus.lower() == 'origin':
            newOs = [
                np.random.permutation(
                    self.o) for i in range(
                    self.permutations)]
            sims = [np.hstack([np.arange(self.n).reshape((-1, 1)), newO,
                               self._newD(self.o, self.d, newO)]) for newO in newOs]
            Ws = [
                DistanceBand(
                    newO,
                    threshold=self.threshold,
                    alpha=self.alpha,
                    binary=self.binary,
                    build_sp=self.build_sp,
                    silence_warnings=self.silence_warnings) for newO in newOs]
        elif focus.lower() == 'destination':
            newDs = [
                np.random.permutation(
                    self.d) for i in range(
                    self.permutations)]
            sims = [np.hstack([np.arange(self.n).reshape(
                (-1, 1)), self._newO(self.o, self.d, newD), newD]) for newD in newDs]
            Ws = [
                DistanceBand(
                    newD,
                    threshold=self.threshold,
                    alpha=self.alpha,
                    binary=self.binary,
                    build_sp=self.build_sp,
                    silence_warnings=self.silence_warnings) for newD in newDs]
        else:
            raise ValueError(
                "Parameter 'focus' must take value of either 'origin' or 'destination.'")

        VMs = [VecMoran(y, Ws[i], permutations=None)
               for i, y in enumerate(sims)]
        sim = [VM.__calc(VM.z) for VM in VMs]
        return sim

    def __rand_vecs_B(self, focus):
        if focus.lower() == 'origin':
            sims = [
                np.hstack(
                    [
                        np.arange(
                            self.n).reshape(
                            (-1,
                             1)),
                        self.o,
                        np.random.permutation(
                            self.d)]) for i in range(
                    self.permutations)]
        elif focus.lower() == 'destination':
            sims = [
                np.hstack(
                    [
                        np.arange(
                            self.n).reshape(
                            (-1,
                             1)),
                        np.random.permutation(
                            self.o),
                        self.d]) for i in range(
                    self.permutations)]
        else:
            raise ValueError(
                "Parameter 'focus' must take value of either 'origin' or 'destination.'")
        sims = [VecMoran(y, self.w, permutations=None) for y in sims]
        sim = [VM.__calc(VM.z) for VM in sims]
        return sim

    def _slag(self, w, y):
        """
        Dense spatial lag operator for.
        If w is row standardized, returns the average of each observation's neighbors;
        if not, returns the weighted sum of each observation's neighbors.
        Parameters
        ----------
        w                   : W
                              object
        y                   : array
                              numpy array with dimensionality conforming to w (see examples)
        Returns
        -------
        wy                  : array
                              array of numeric values for the spatial lag
        """
        return np.array(w.sparse.todense()) * y


__version__ = '1.0.7'

from .gravity import Gravity, Production, Attraction, Doubly
from .utils import CPC, sorensen, srmse
from .vec_SA import VecMoran as Moran_Vector
from .dispersion import phi_disp, alpha_disp
from .flow_accessibility import Accessibility


"""
Tests for Accessibility function.

The correctness of the function is veryfied by matching the output to manually calculated values, using very simple dummy dataset.

"""

__author__ = 'Lenka Hasova haska.lenka@gmail.com'

import unittest
import numpy as np
from ..flow_accessibility import _generate_dummy_flows
from ..flow_accessibility import Accessibility



class AccessibilityTest(unittest.TestCase):
    
    def test_accessibility(self):
        flow = _generate_dummy_flows()
        flow = flow.loc[:,['origin_ID', 'destination_ID','distances', 'volume_in_unipartite','dest_masses','results_all=False']]
        flow['acc_uni'] = Accessibility(nodes = flow['origin_ID'],  distances = flow['distances'], weights = flow['volume_in_unipartite'], masses = flow['dest_masses'], all_destinations=False)
        
        np.testing.testing.assert_array_equal(flow['results_all=False'], flow['acc_uni'])

if __name__ == '__main__':
    unittest.main()

"""
Tests for CountModel class, which includes various linear models designed for
count data

Test data is the Columbus dataset after it has been rounded to integers to act
as count data. Results are verified using corresponding functions in R.

"""

__author__ = 'Taylor Oshan tayoshan@gmail.com'

import unittest
import numpy as np
import libpysal
from spglm.family import Poisson
from ..count_model import CountModel


class TestCountModel(unittest.TestCase):
    """Tests CountModel class"""

    def setUp(self):
        db = libpysal.io.open(libpysal.examples.get_path('columbus.dbf'), 'r')
        y = np.array(db.by_col("HOVAL"))
        y = np.reshape(y, (49, 1))
        self.y = np.round(y).astype(int)
        X = []
        X.append(db.by_col("INC"))
        X.append(db.by_col("CRIME"))
        self.X = np.array(X).T

    def test_PoissonGLM(self):
        model = CountModel(self.y, self.X, family=Poisson())
        results = model.fit('GLM')
        np.testing.assert_allclose(results.params, [3.92159085, 0.01183491,
                                                    -0.01371397], atol=1.0e-8)
        self.assertIsInstance(results.family, Poisson)
        self.assertEqual(results.n, 49)
        self.assertEqual(results.k, 3)
        self.assertEqual(results.df_model, 2)
        self.assertEqual(results.df_resid, 46)
        np.testing.assert_allclose(results.yhat,
                                   [51.26831574,
                                    50.15022766,
                                    40.06142973,
                                    34.13799739,
                                    28.76119226,
                                    42.6836241,
                                    55.64593703,
                                    34.08277997,
                                    40.90389582,
                                    37.19727958,
                                    23.47459217,
                                    26.12384057,
                                    29.78303507,
                                    25.96888223,
                                    29.14073823,
                                    26.04369592,
                                    34.18996367,
                                    32.28924005,
                                    27.42284396,
                                    72.69207879,
                                    33.05316347,
                                    36.52276972,
                                    49.2551479,
                                    35.33439632,
                                    24.07252457,
                                    31.67153709,
                                    27.81699478,
                                    25.38021219,
                                    24.31759259,
                                    23.13586161,
                                    48.40724678,
                                    48.57969818,
                                    31.92596006,
                                    43.3679231,
                                    34.32925819,
                                    51.78908089,
                                    34.49778584,
                                    27.56236198,
                                    48.34273194,
                                    57.50829097,
                                    50.66038226,
                                    54.68701352,
                                    35.77103116,
                                    43.21886784,
                                    40.07615759,
                                    49.98658004,
                                    43.13352883,
                                    40.28520774,
                                    46.28910294])
        np.testing.assert_allclose(results.cov_params,
                                   [[1.70280610e-02, -6.18628383e-04, -2.21386966e-04],
                                    [-6.18628383e-04, 2.61733917e-05, 6.77496445e-06],
                                       [-2.21386966e-04, 6.77496445e-06, 3.75463502e-06]])
        np.testing.assert_allclose(results.std_err, [0.13049161, 0.00511599,
                                                     0.00193769], atol=1.0e-8)
        np.testing.assert_allclose(
            results.pvalues, [
                2.02901657e-198, 2.07052532e-002, 1.46788805e-012])
        np.testing.assert_allclose(results.tvalues, [30.0524361, 2.31331634,
                                                     -7.07748998])
        np.testing.assert_allclose(results.resid, [28.73168426, -
                                                   5.15022766, -
                                                   14.06142973, -
                                                   1.13799739, -
                                                   5.76119226, -
                                                   13.6836241, 19.35406297, 2.91722003, 12.09610418, 58.80272042, -
                                                   3.47459217, -
                                                   6.12384057, 12.21696493, 17.03111777, -
                                                   11.14073823, -
                                                   7.04369592, 7.81003633, 27.71075995, 3.57715604, 8.30792121, -
                                                   13.05316347, -
                                                   6.52276972, -
                                                   1.2551479, 17.66560368, -
                                                   6.07252457, -
                                                   11.67153709, 6.18300522, -
                                                   2.38021219, 7.68240741, -
                                                   1.13586161, -
                                                   16.40724678, -
                                                   8.57969818, -
                                                   7.92596006, -
                                                   15.3679231, -
                                                   7.32925819, -
                                                   15.78908089, 8.50221416, -
                                                   4.56236198, -
                                                   8.34273194, 4.49170903, -
                                                   8.66038226, -
                                                   10.68701352, -
                                                   9.77103116, -
                                                   9.21886784, -
                                                   12.07615759, 26.01341996, -
                                                   1.13352883, -
                                                   13.28520774, -
                                                   10.28910294])
        self.assertAlmostEqual(results.deviance, 230.46013824817649)
        self.assertAlmostEqual(results.llf, -247.42592089969378)
        self.assertAlmostEqual(results.AIC, 500.85184179938756)
        self.assertAlmostEqual(results.D2, 0.388656011675)
        self.assertAlmostEqual(results.adj_D2, 0.36207583826952761)


if __name__ == '__main__':
    unittest.main()



"""
Tests for regressiom based dispersion tests (Cameron & Trivedi, 2013)

Cameron, Colin A. & Trivedi, Pravin K. (2013) Regression Analysis of Count Data.
    Camridge University Press: New York, New York.

"""

__author__ = 'Taylor Oshan tayoshan@gmail.com'

import unittest
import numpy as np
import libpysal
from spglm.family import Poisson
from ..count_model import CountModel
from ..dispersion import phi_disp, alpha_disp


class TestDispersion(unittest.TestCase):

    def setUp(self):
        db = libpysal.io.open(libpysal.examples.get_path('columbus.dbf'), 'r')
        y = np.array(db.by_col("HOVAL"))
        y = np.reshape(y, (49, 1))
        self.y = np.round(y).astype(int)
        X = []
        X.append(db.by_col("INC"))
        X.append(db.by_col("CRIME"))
        self.X = np.array(X).T

    def test_Dispersion(self):
        model = CountModel(self.y, self.X, family=Poisson())
        results = model.fit('GLM')
        phi = phi_disp(results)
        alpha1 = alpha_disp(results)
        alpha2 = alpha_disp(results, lambda x: x**2)
        np.testing.assert_allclose(phi, [5.39968689, 2.3230411, 0.01008847],
                                   atol=1.0e-8)
        np.testing.assert_allclose(alpha1, [4.39968689, 2.3230411,
                                            0.01008847], atol=1.0e-8)
        np.testing.assert_allclose(alpha2, [0.10690133, 2.24709978,
                                            0.01231683], atol=1.0e-8)


if __name__ == '__main__':
    unittest.main()



"""
Tests for gravity-style spatial interaction models

Test data is the Austria migration dataset used in Dennet's (2012) practical primer
on spatial interaction modeling. The data was made avialable through the
following dropbox link: http://dl.dropbox.com/u/8649795/AT_Austria.csv.
The data has been pre-filtered so that there are no intra-zonal flows,

Dennett, A. (2012). Estimating flows between geographical locations:get me
started in spatial interaction modelling (Working Paper No. 184). UCL: Citeseer.

"""

import unittest
import math
import numpy as np
from ..gravity import BaseGravity, Gravity, Production, Attraction, Doubly


class TestGravity(unittest.TestCase):
    """Tests for gravity-type models"""

    def setUp(self):
        self.f = np.array([1131, 1887, 69, 738, 98, 31, 43, 19, 1633,
                           14055, 416, 1276, 1850, 388, 303, 159, 2301, 20164,
                           1080, 1831, 1943, 742, 674, 407, 85, 379, 1597,
                           1608, 328, 317, 469, 114, 762, 1110, 2973, 1252,
                           1081, 622, 425, 262, 196, 2027, 3498, 346, 1332,
                           2144, 821, 274, 49, 378, 1349, 310, 851, 2117,
                           630, 106, 87, 424, 978, 490, 670, 577, 546,
                           569, 33, 128, 643, 154, 328, 199, 112, 587])

        self.o = np.array(['AT11',
                           'AT11',
                           'AT11',
                           'AT11',
                           'AT11',
                           'AT11',
                           'AT11',
                           'AT11',
                           'AT12',
                           'AT12',
                           'AT12',
                           'AT12',
                           'AT12',
                           'AT12',
                           'AT12',
                           'AT12',
                           'AT13',
                           'AT13',
                           'AT13',
                           'AT13',
                           'AT13',
                           'AT13',
                           'AT13',
                           'AT13',
                           'AT21',
                           'AT21',
                           'AT21',
                           'AT21',
                           'AT21',
                           'AT21',
                           'AT21',
                           'AT21',
                           'AT22',
                           'AT22',
                           'AT22',
                           'AT22',
                           'AT22',
                           'AT22',
                           'AT22',
                           'AT22',
                           'AT31',
                           'AT31',
                           'AT31',
                           'AT31',
                           'AT31',
                           'AT31',
                           'AT31',
                           'AT31',
                           'AT32',
                           'AT32',
                           'AT32',
                           'AT32',
                           'AT32',
                           'AT32',
                           'AT32',
                           'AT32',
                           'AT33',
                           'AT33',
                           'AT33',
                           'AT33',
                           'AT33',
                           'AT33',
                           'AT33',
                           'AT33',
                           'AT34',
                           'AT34',
                           'AT34',
                           'AT34',
                           'AT34',
                           'AT34',
                           'AT34',
                           'AT34'])

        self.d = np.array(['AT12',
                           'AT13',
                           'AT21',
                           'AT22',
                           'AT31',
                           'AT32',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT13',
                           'AT21',
                           'AT22',
                           'AT31',
                           'AT32',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT21',
                           'AT22',
                           'AT31',
                           'AT32',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT13',
                           'AT22',
                           'AT31',
                           'AT32',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT13',
                           'AT21',
                           'AT31',
                           'AT32',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT13',
                           'AT21',
                           'AT22',
                           'AT32',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT13',
                           'AT21',
                           'AT22',
                           'AT31',
                           'AT33',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT13',
                           'AT21',
                           'AT22',
                           'AT31',
                           'AT32',
                           'AT34',
                           'AT11',
                           'AT12',
                           'AT13',
                           'AT21',
                           'AT22',
                           'AT31',
                           'AT32',
                           'AT33'])

        self.dij = np.array([103.001845,
                             84.204666,
                             220.811933,
                             132.00748,
                             214.511814,
                             246.933305,
                             390.85611,
                             505.089539,
                             103.001845,
                             45.796272,
                             216.994739,
                             129.878172,
                             140.706671,
                             201.232355,
                             343.50075,
                             453.515594,
                             84.204666,
                             45.796272,
                             249.932874,
                             158.630661,
                             186.420738,
                             244.108305,
                             387.61776,
                             498.407152,
                             220.811933,
                             216.994739,
                             249.932874,
                             92.407958,
                             151.777157,
                             92.894408,
                             194.851669,
                             306.105825,
                             132.00748,
                             129.878172,
                             158.630661,
                             92.407958,
                             124.563096,
                             122.433524,
                             261.893783,
                             376.34667,
                             214.511814,
                             140.706671,
                             186.420738,
                             151.777157,
                             124.563096,
                             81.753652,
                             208.456383,
                             314.793199,
                             246.933305,
                             201.232355,
                             244.108305,
                             92.894408,
                             122.433524,
                             81.753652,
                             145.076472,
                             258.591197,
                             390.85611,
                             343.50075,
                             387.61776,
                             194.851669,
                             261.893783,
                             208.456383,
                             145.076472,
                             114.46325,
                             505.089539,
                             453.515594,
                             498.407152,
                             306.105825,
                             376.34667,
                             314.793199,
                             258.591197,
                             114.46325])

        self.o_var = np.array([4320,
                               4320,
                               4320,
                               4320,
                               4320,
                               4320,
                               4320,
                               4320,
                               21478,
                               21478,
                               21478,
                               21478,
                               21478,
                               21478,
                               21478,
                               21478,
                               30500,
                               30500,
                               30500,
                               30500,
                               30500,
                               30500,
                               30500,
                               30500,
                               5012,
                               5012,
                               5012,
                               5012,
                               5012,
                               5012,
                               5012,
                               5012,
                               8811,
                               8811,
                               8811,
                               8811,
                               8811,
                               8811,
                               8811,
                               8811,
                               11349,
                               11349,
                               11349,
                               11349,
                               11349,
                               11349,
                               11349,
                               11349,
                               6021,
                               6021,
                               6021,
                               6021,
                               6021,
                               6021,
                               6021,
                               6021,
                               4477,
                               4477,
                               4477,
                               4477,
                               4477,
                               4477,
                               4477,
                               4477,
                               2275,
                               2275,
                               2275,
                               2275,
                               2275,
                               2275,
                               2275,
                               2275])

        self.d_var = np.array([27169,
                               28710,
                               4354,
                               9069,
                               8577,
                               4963,
                               3923,
                               2026,
                               5452,
                               28710,
                               4354,
                               9069,
                               8577,
                               4963,
                               3923,
                               2026,
                               5452,
                               27169,
                               4354,
                               9069,
                               8577,
                               4963,
                               3923,
                               2026,
                               5452,
                               27169,
                               28710,
                               9069,
                               8577,
                               4963,
                               3923,
                               2026,
                               5452,
                               27169,
                               28710,
                               4354,
                               8577,
                               4963,
                               3923,
                               2026,
                               5452,
                               27169,
                               28710,
                               4354,
                               9069,
                               4963,
                               3923,
                               2026,
                               5452,
                               27169,
                               28710,
                               4354,
                               9069,
                               8577,
                               3923,
                               2026,
                               5452,
                               27169,
                               28710,
                               4354,
                               9069,
                               8577,
                               4963,
                               2026,
                               5452,
                               27169,
                               28710,
                               4354,
                               9069,
                               8577,
                               4963,
                               3923])

    def test_BaseGravity_exp(self):
        f = np.array(self.f).reshape((-1, 1))
        dij = np.array(self.dij).reshape((-1, 1))
        model = BaseGravity(f, dij, 'exp', constant=False)
        np.testing.assert_allclose(model.params, [0.01641585], atol=.0001)
        self.assertAlmostEqual(model.AIC, 957622.28429746185, delta=.0001)
        np.testing.assert_allclose(model.cov_params, [[1.92096665e-10]])
        self.assertAlmostEqual(model.deviance, 1087408.9707170483, delta=.0001)
        self.assertAlmostEqual(model.llf, -478810.14214873099, delta=.0001)
        self.assertAlmostEqual(model.llnull, -88037.0499629, delta=.0001)
        np.testing.assert_allclose(model.pvalues, [0.])
        np.testing.assert_allclose(model.std_err, [1.38598941e-05])
        np.testing.assert_allclose(model.tvalues, [1184.41355888])
        np.testing.assert_allclose(model.yhat,
                                   [5.42415692e+00, 3.98401807e+00, 3.75177744e+01,
                                    8.73217546e+00, 3.38315236e+01, 5.76055685e+01,
                                    6.11695077e+02, 3.98970414e+03, 5.42415692e+00,
                                    2.12078133e+00, 3.52389616e+01, 8.43222048e+00,
                                    1.00726025e+01, 2.72049640e+01, 2.81140796e+02,
                                    1.71101560e+03, 3.98401807e+00, 2.12078133e+00,
                                    6.05130899e+01, 1.35184658e+01, 2.13329799e+01,
                                    5.49951210e+01, 5.80026424e+02, 3.57519614e+03,
                                    3.75177744e+01, 3.52389616e+01, 6.05130899e+01,
                                    4.55832329e+00, 1.20799918e+01, 4.59486946e+00,
                                    2.44995584e+01, 1.52168163e+02, 8.73217546e+00,
                                    8.43222048e+00, 1.35184658e+01, 4.55832329e+00,
                                    7.72767984e+00, 7.46219749e+00, 7.36414576e+01,
                                    4.82050643e+02, 3.38315236e+01, 1.00726025e+01,
                                    2.13329799e+01, 1.20799918e+01, 7.72767984e+00,
                                    3.82690126e+00, 3.06302472e+01, 1.75492594e+02,
                                    5.76055685e+01, 2.72049640e+01, 5.49951210e+01,
                                    4.59486946e+00, 7.46219749e+00, 3.82690126e+00,
                                    1.08216970e+01, 6.97553001e+01, 6.11695077e+02,
                                    2.81140796e+02, 5.80026424e+02, 2.44995584e+01,
                                    7.36414576e+01, 3.06302472e+01, 1.08216970e+01,
                                    6.54702760e+00, 3.98970414e+03, 1.71101560e+03,
                                    3.57519614e+03, 1.52168163e+02, 4.82050643e+02,
                                    1.75492594e+02, 6.97553001e+01, 6.54702760e+00])

    def test_BaseGravity_pow(self):
        f = np.array(self.f).reshape((-1, 1))
        dij = np.array(self.dij).reshape((-1, 1))
        model = BaseGravity(f, dij, 'pow', constant=False)
        np.testing.assert_allclose(model.params, [1.27223738], atol=.0001)
        self.assertAlmostEqual(model.AIC, 377298.04716333596, delta=.0001)
        np.testing.assert_allclose(model.cov_params, [[4.31955426e-07]])
        self.assertAlmostEqual(model.deviance, 409811.34329065739, delta=.0001)
        self.assertAlmostEqual(model.llf, -188648.02358166798, delta=.0001)
        self.assertAlmostEqual(model.llnull, -88037.0499629, delta=.0001)
        np.testing.assert_allclose(model.pvalues, [0.])
        np.testing.assert_allclose(model.std_err, [0.00065723], atol=.000001)
        np.testing.assert_allclose(model.tvalues, [1935.74740017])
        np.testing.assert_allclose(model.yhat,
                                   [363.76143383,
                                    281.50403714,
                                    959.7388893,
                                    498.77506053,
                                    925.03759732,
                                    1106.44361848,
                                    1984.54428735,
                                    2749.95948574,
                                    363.76143383,
                                    129.70901679,
                                    938.68096943,
                                    488.56203387,
                                    540.96136464,
                                    852.80642651,
                                    1683.84456031,
                                    2397.81642174,
                                    281.50403714,
                                    129.70901679,
                                    1123.57104159,
                                    630.10766251,
                                    773.76239688,
                                    1090.36467516,
                                    1963.64917204,
                                    2703.75625368,
                                    959.7388893,
                                    938.68096943,
                                    1123.57104159,
                                    316.84652033,
                                    595.67905738,
                                    318.9700416,
                                    818.55371165,
                                    1454.18199247,
                                    498.77506053,
                                    488.56203387,
                                    630.10766251,
                                    316.84652033,
                                    463.26843623,
                                    453.2156204,
                                    1192.42000515,
                                    1891.29566175,
                                    925.03759732,
                                    540.96136464,
                                    773.76239688,
                                    595.67905738,
                                    463.26843623,
                                    271.12096396,
                                    891.94447199,
                                    1506.88882976,
                                    1106.44361848,
                                    852.80642651,
                                    1090.36467516,
                                    318.9700416,
                                    453.2156204,
                                    271.12096396,
                                    562.42482847,
                                    1173.32244253,
                                    1984.54428735,
                                    1683.84456031,
                                    1963.64917204,
                                    818.55371165,
                                    1192.42000515,
                                    891.94447199,
                                    562.42482847,
                                    416.01781589,
                                    2749.95948574,
                                    2397.81642174,
                                    2703.75625368,
                                    1454.18199247,
                                    1891.29566175,
                                    1506.88882976,
                                    1173.32244253,
                                    416.01781589])

    def test_QuasiPoisson(self):
        f = np.array(self.f).reshape((-1, 1))
        dij = np.array(self.dij).reshape((-1, 1))
        model = BaseGravity(f, dij, 'exp', constant=False, Quasi=True)
        np.testing.assert_allclose(model.params, [0.01641585], atol=.0001)
        self.assertTrue(math.isnan(model.AIC))
        np.testing.assert_allclose(model.cov_params, [[0.00079749]],
                                   atol=1.0e-8)
        self.assertAlmostEqual(model.deviance, 1087408.9707170483, delta=.0001)
        self.assertTrue(np.isnan(model.llf))
        self.assertTrue(np.isnan(model.llnull))
        np.testing.assert_allclose(model.pvalues, [0.56103881])
        np.testing.assert_allclose(model.std_err, [0.02823993], atol=1.0e-8)
        np.testing.assert_allclose(model.tvalues, [0.58129922])
        np.testing.assert_allclose(model.yhat,
                                   [5.42415692e+00, 3.98401807e+00, 3.75177744e+01,
                                    8.73217546e+00, 3.38315236e+01, 5.76055685e+01,
                                    6.11695077e+02, 3.98970414e+03, 5.42415692e+00,
                                    2.12078133e+00, 3.52389616e+01, 8.43222048e+00,
                                    1.00726025e+01, 2.72049640e+01, 2.81140796e+02,
                                    1.71101560e+03, 3.98401807e+00, 2.12078133e+00,
                                    6.05130899e+01, 1.35184658e+01, 2.13329799e+01,
                                    5.49951210e+01, 5.80026424e+02, 3.57519614e+03,
                                    3.75177744e+01, 3.52389616e+01, 6.05130899e+01,
                                    4.55832329e+00, 1.20799918e+01, 4.59486946e+00,
                                    2.44995584e+01, 1.52168163e+02, 8.73217546e+00,
                                    8.43222048e+00, 1.35184658e+01, 4.55832329e+00,
                                    7.72767984e+00, 7.46219749e+00, 7.36414576e+01,
                                    4.82050643e+02, 3.38315236e+01, 1.00726025e+01,
                                    2.13329799e+01, 1.20799918e+01, 7.72767984e+00,
                                    3.82690126e+00, 3.06302472e+01, 1.75492594e+02,
                                    5.76055685e+01, 2.72049640e+01, 5.49951210e+01,
                                    4.59486946e+00, 7.46219749e+00, 3.82690126e+00,
                                    1.08216970e+01, 6.97553001e+01, 6.11695077e+02,
                                    2.81140796e+02, 5.80026424e+02, 2.44995584e+01,
                                    7.36414576e+01, 3.06302472e+01, 1.08216970e+01,
                                    6.54702760e+00, 3.98970414e+03, 1.71101560e+03,
                                    3.57519614e+03, 1.52168163e+02, 4.82050643e+02,
                                    1.75492594e+02, 6.97553001e+01, 6.54702760e+00])

    def test_Gravity(self):
        model = Gravity(self.f, self.o_var, self.d_var,
                        self.dij, 'exp', constant=True)
        np.testing.assert_allclose(
            model.params, [-7.95447436e+00, 8.63867812e-01, 8.80474585e-01, -6.20544765e-03])
        self.assertAlmostEqual(model.AIC, 20395.085388908723, delta=.0001)
        np.testing.assert_allclose(model.cov_params,
                                   [[5.70906352e-03, -3.00814799e-04, -2.62650384e-04,
                                     -2.40317578e-06],
                                    [-3.00814799e-04, 2.67121974e-05, 3.21466745e-06,
                                     1.16544737e-07],
                                       [-2.62650384e-04, 3.21466745e-06, 2.28600781e-05,
                                        9.94368232e-08],
                                       [-2.40317578e-06, 1.16544737e-07, 9.94368232e-08,
                                        2.68830005e-09]])
        self.assertAlmostEqual(model.deviance, 19806.408696637576, delta=.0001)
        self.assertAlmostEqual(model.llf, -10193.542694454361, delta=.0001)
        self.assertAlmostEqual(model.llnull, -88037.0499629, delta=.0001)
        np.testing.assert_allclose(model.pvalues, [0., 0., 0., 0.])
        np.testing.assert_allclose(
            model.std_err, [
                7.55583451e-02, 5.16838440e-03, 4.78122141e-03, 5.18488192e-05])
        np.testing.assert_allclose(
            model.tvalues, [-105.27592086, 167.14465196, 184.15264854, -119.68349034])
        np.testing.assert_allclose(model.yhat,
                                   [2053.49248374,
                                    2422.40705883,
                                    197.17666947,
                                    652.77645945,
                                    372.46664089,
                                    188.15630595,
                                    62.62225447,
                                    17.22633782,
                                    1995.44179687,
                                    12287.11927555,
                                    806.92929317,
                                    2643.59913196,
                                    2353.33783354,
                                    998.56216427,
                                    335.77135891,
                                    94.81498069,
                                    3035.77484367,
                                    15846.25211871,
                                    890.511914,
                                    2994.19536934,
                                    2399.15053753,
                                    1036.08892279,
                                    345.71715146,
                                    97.15537629,
                                    273.27020389,
                                    1150.87005074,
                                    984.81363732,
                                    948.91636667,
                                    625.0285152,
                                    556.41059801,
                                    240.2714148,
                                    67.32796418,
                                    771.93257863,
                                    3217.0998412,
                                    2825.35215036,
                                    809.66631035,
                                    1204.76218438,
                                    754.13231343,
                                    258.03819482,
                                    70.88540396,
                                    575.70226041,
                                    3743.25042014,
                                    2959.00444172,
                                    697.06556556,
                                    1574.69697708,
                                    1207.94322877,
                                    447.3674688,
                                    129.24387416,
                                    272.27577768,
                                    1487.02882957,
                                    1196.36810195,
                                    580.9635273,
                                    922.83252627,
                                    1130.90519845,
                                    383.40648414,
                                    105.94015788,
                                    86.29277039,
                                    476.14958977,
                                    380.14055538,
                                    238.89720288,
                                    300.687118,
                                    398.84078404,
                                    365.10261002,
                                    200.59513613,
                                    23.66650989,
                                    134.05168303,
                                    106.50884151,
                                    66.7421182,
                                    82.35371404,
                                    114.87900692,
                                    100.58000293,
                                    199.99352826])
        self.assertAlmostEquals(model.D2, 0.88713874099960177)
        self.assertAlmostEquals(model.adj_D2, 0.88215956780840776)
        self.assertAlmostEquals(model.SSI, 0.72706171189789603)
        self.assertAlmostEquals(model.pseudoR2, 0.88421303645743465)
        self.assertAlmostEquals(model.adj_pseudoR2, 0.88416760104130376)
        self.assertAlmostEquals(model.SRMSE, 0.62063116008447083)

    def test_local_Gravity(self):
        model = Gravity(self.f, self.o_var, self.d_var, self.dij, 'exp')
        local = model.local(loc_index=self.o, locs=np.unique(self.o))
        self.assertEqual(list(local.keys()).sort(), ['stde0',
                                                     'stde1',
                                                     'stde2',
                                                     'pvalue2',
                                                     'SRMSE',
                                                     'pvalue0',
                                                     'deviance',
                                                     'adj_pseudoR2',
                                                     'pvalue1',
                                                     'tvalue0',
                                                     'tvalue2',
                                                     'adj_D2',
                                                     'tvalue1',
                                                     'SSI',
                                                     'AIC',
                                                     'param1',
                                                     'param0',
                                                     'D2',
                                                     'pseudoR2',
                                                     'param2'].sort())

    def test_Production(self):
        model = Production(self.f, self.o, self.d_var,
                           self.dij, 'exp', constant=True)
        np.testing.assert_allclose(model.params,
                                   [-1.11700938, 1.68662317, 2.15188689, 0.60300297,
                                    0.88380784, 1.20926104, 0.68938983, 1.15472804,
                                    1.02479968, 0.89278717, -0.00727113], atol=.0001)
        self.assertAlmostEqual(model.AIC, 15882.651018068489, delta=.0001)
        np.testing.assert_allclose(model.cov_params,
                                   [[2.58467540e-03, -3.29423877e-04, -3.27686611e-04,
                                     -3.08689103e-04, -2.97140418e-04, -2.89494010e-04,
                                     -3.24014540e-04, -3.00776842e-04, -2.84393168e-04,
                                     -2.24000219e-04, -9.64855587e-07],
                                    [-3.29423877e-04, 3.03025458e-04, 2.53009591e-04,
                                     2.47263232e-04, 2.49058621e-04, 2.47981815e-04,
                                     2.48504221e-04, 2.42350062e-04, 2.38815483e-04,
                                     7.03380199e-06, 9.21233182e-08],
                                       [-3.27686611e-04, 2.53009591e-04, 2.87125687e-04,
                                        2.47623385e-04, 2.49193103e-04, 2.48208882e-04,
                                        2.48776786e-04, 2.43211814e-04, 2.40006717e-04,
                                        6.93978830e-06, 8.51141937e-08],
                                       [-3.08689103e-04, 2.47263232e-04, 2.47623385e-04,
                                        4.64543893e-04, 2.54358195e-04, 2.56271647e-04,
                                        2.58881194e-04, 2.72166616e-04, 2.79448200e-04,
                                        7.48669925e-06, -1.28025978e-07],
                                       [-2.97140418e-04, 2.49058621e-04, 2.49193103e-04,
                                        2.54358195e-04, 3.69553926e-04, 2.52457119e-04,
                                        2.53895776e-04, 2.59259137e-04, 2.62162787e-04,
                                        5.35894223e-06, -4.43827259e-08],
                                       [-2.89494010e-04, 2.47981815e-04, 2.48208882e-04,
                                        2.56271647e-04, 2.52457119e-04, 3.47667496e-04,
                                        2.55361893e-04, 2.63782581e-04, 2.68393880e-04,
                                        5.00405857e-06, -8.03379392e-08],
                                       [-3.24014540e-04, 2.48504221e-04, 2.48776786e-04,
                                        2.58881194e-04, 2.53895776e-04, 2.55361893e-04,
                                        4.30583201e-04, 2.68391703e-04, 2.74115589e-04,
                                        8.62466197e-06, -9.29399372e-08],
                                       [-3.00776842e-04, 2.42350062e-04, 2.43211814e-04,
                                        2.72166616e-04, 2.59259137e-04, 2.63782581e-04,
                                        2.68391703e-04, 5.29121755e-04, 3.15535312e-04,
                                        8.88670616e-06, -3.18385859e-07],
                                       [-2.84393168e-04, 2.38815483e-04, 2.40006717e-04,
                                        2.79448200e-04, 2.62162787e-04, 2.68393880e-04,
                                        2.74115589e-04, 3.15535312e-04, 7.96308690e-04,
                                        8.69726183e-06, -4.44906514e-07],
                                       [-2.24000219e-04, 7.03380199e-06, 6.93978830e-06,
                                        7.48669925e-06, 5.35894223e-06, 5.00405857e-06,
                                        8.62466197e-06, 8.88670616e-06, 8.69726183e-06,
                                        2.17985878e-05, 6.51339971e-08],
                                       [-9.64855587e-07, 9.21233182e-08, 8.51141937e-08,
                                        -1.28025978e-07, -4.43827259e-08, -8.03379392e-08,
                                        -9.29399372e-08, -3.18385859e-07, -4.44906514e-07,
                                        6.51339971e-08, 2.77308674e-09]])
        self.assertAlmostEqual(model.deviance, 15279.974241770311, delta=.0001)
        self.assertAlmostEqual(model.llf, -7930.3255090342445, delta=.0001)
        self.assertAlmostEqual(model.llnull, -88037.0499629, delta=.0001)
        np.testing.assert_allclose(model.pvalues,
                                   [5.43122293e-107,
                                    0.00000000e+000,
                                    0.00000000e+000,
                                    3.06800447e-172,
                                    0.00000000e+000,
                                    0.00000000e+000,
                                    5.04395549e-242,
                                    0.00000000e+000,
                                    9.03955976e-289,
                                    0.00000000e+000,
                                    0.00000000e+000])
        np.testing.assert_allclose(model.std_err,
                                   [5.08397030e-02,
                                    1.74076264e-02,
                                    1.69447835e-02,
                                    2.15532803e-02,
                                    1.92237854e-02,
                                    1.86458439e-02,
                                    2.07504988e-02,
                                    2.30026467e-02,
                                    2.82189420e-02,
                                    4.66889578e-03,
                                    5.26601057e-05])
        np.testing.assert_allclose(model.tvalues,
                                   [-21.97120187, 96.88989939, 126.99406254, 27.97731759,
                                    45.97470357, 64.85418671, 33.22280753, 50.19979035,
                                    36.31602055, 191.22019711, -138.07670549])
        np.testing.assert_allclose(model.yhat,
                                   [1.40705950e+03, 1.69457663e+03, 1.16508879e+02,
                                    4.27850723e+02, 2.23425179e+02, 1.08301078e+02,
                                    3.08300817e+01, 7.44793331e+00, 1.81162644e+03,
                                    1.21014912e+04, 6.46999802e+02, 2.34696906e+03,
                                    2.06388796e+03, 8.15528209e+02, 2.34966095e+02,
                                    5.85312512e+01, 3.30741049e+03, 1.83446566e+04,
                                    8.10873546e+02, 3.03231168e+03, 2.35717102e+03,
                                    9.50837295e+02, 2.71489717e+02, 6.72496632e+01,
                                    2.60277189e+02, 1.12260001e+03, 9.28118288e+02,
                                    1.04284804e+03, 6.44343295e+02, 6.06652130e+02,
                                    2.34315477e+02, 5.78455649e+01, 6.57379261e+02,
                                    2.80075361e+03, 2.38710037e+03, 7.17245241e+02,
                                    1.03993511e+03, 6.48056270e+02, 1.90566474e+02,
                                    4.59636590e+01, 4.99603238e+02, 3.58445439e+03,
                                    2.70058180e+03, 6.44960859e+02, 1.51347637e+03,
                                    1.20618713e+03, 3.89165529e+02, 9.95706858e+01,
                                    2.34675109e+02, 1.37251483e+03, 1.05563448e+03,
                                    5.88432822e+02, 9.13951678e+02, 1.16884200e+03,
                                    3.66858927e+02, 8.90901579e+01, 1.31244011e+02,
                                    7.76879800e+02, 5.92149430e+02, 4.46507449e+02,
                                    5.27992298e+02, 7.40876898e+02, 7.20725128e+02,
                                    4.04624989e+02, 5.02255240e+01, 3.06563409e+02,
                                    2.32354948e+02, 1.74615053e+02, 2.01734215e+02,
                                    3.00280455e+02, 2.77258060e+02, 6.40968342e+02])
        self.assertAlmostEquals(model.D2, 0.912931356874)
        self.assertAlmostEquals(model.adj_D2, 0.89865780882)
        self.assertAlmostEquals(model.SSI, 0.740619203383)
        self.assertAlmostEquals(model.pseudoR2, 0.909920590111)
        self.assertAlmostEquals(model.adj_pseudoR2, 0.909795642717)
        self.assertAlmostEquals(model.SRMSE, 0.46622685091043831)

    def test_local_Production(self):
        model = Production(self.f, self.o, self.d_var, self.dij, 'exp')
        local = model.local(locs=np.unique(self.o))
        self.assertEqual(list(local.keys()).sort(), ['stde0',
                                                     'stde1',
                                                     'stde2',
                                                     'pvalue2',
                                                     'SRMSE',
                                                     'pvalue0',
                                                     'deviance',
                                                     'adj_pseudoR2',
                                                     'pvalue1',
                                                     'tvalue0',
                                                     'tvalue2',
                                                     'adj_D2',
                                                     'tvalue1',
                                                     'SSI',
                                                     'AIC',
                                                     'param1',
                                                     'param0',
                                                     'D2',
                                                     'pseudoR2',
                                                     'param2'].sort())

    def test_Attraction(self):
        model = Production(self.f, self.d, self.o_var,
                           self.dij, 'exp', constant=True)
        np.testing.assert_allclose(model.params,
                                   [-0.88439723,
                                    1.62180605,
                                    1.92772078,
                                    0.12462001,
                                    0.62378812,
                                    0.69646073,
                                    0.20909411,
                                    0.6856777,
                                    0.48539625,
                                    0.89235874,
                                    -0.00693755],
                                   atol=.001)
        self.assertAlmostEqual(model.AIC, 16275.899321893821, delta=.0001)
        np.testing.assert_allclose(model.cov_params,
                                   [[3.01436996e-03, -2.61742292e-04, -3.18191276e-04,
                                     -2.61736294e-04, -2.53401872e-04, -2.53545012e-04,
                                     -2.81169571e-04, -2.43409544e-04, -2.12802803e-04,
                                     -2.71488782e-04, -1.17108280e-06],
                                    [-2.61742292e-04, 2.36371652e-04, 1.97978106e-04,
                                     1.92565769e-04, 1.94367290e-04, 1.93561823e-04,
                                     1.93929484e-04, 1.87837851e-04, 1.84018218e-04,
                                     5.78923328e-06, 8.62912701e-08],
                                       [-3.18191276e-04, 1.97978106e-04, 2.37130911e-04,
                                        1.95813824e-04, 1.96321084e-04, 1.95974290e-04,
                                        1.97059881e-04, 1.93136341e-04, 1.90444087e-04,
                                        1.16187824e-05, 7.68842070e-08],
                                       [-2.61736294e-04, 1.92565769e-04, 1.95813824e-04,
                                        4.45977428e-04, 1.98639315e-04, 2.00358776e-04,
                                        2.01640218e-04, 2.11745720e-04, 2.17565021e-04,
                                        7.97756072e-06, -9.56753770e-08],
                                       [-2.53401872e-04, 1.94367290e-04, 1.96321084e-04,
                                        1.98639315e-04, 3.12561535e-04, 1.97440629e-04,
                                        1.98271627e-04, 2.01952018e-04, 2.03971780e-04,
                                        6.29181262e-06, -2.57004528e-08],
                                       [-2.53545012e-04, 1.93561823e-04, 1.95974290e-04,
                                        2.00358776e-04, 1.97440629e-04, 3.20607776e-04,
                                        1.99534150e-04, 2.05855338e-04, 2.09446226e-04,
                                        6.66273501e-06, -5.53303117e-08],
                                       [-2.81169571e-04, 1.93929484e-04, 1.97059881e-04,
                                        2.01640218e-04, 1.98271627e-04, 1.99534150e-04,
                                        4.04837719e-04, 2.07747990e-04, 2.11608257e-04,
                                        9.45143925e-06, -5.46040064e-08],
                                       [-2.43409544e-04, 1.87837851e-04, 1.93136341e-04,
                                        2.11745720e-04, 2.01952018e-04, 2.05855338e-04,
                                        2.07747990e-04, 4.85148555e-04, 2.46472592e-04,
                                        8.10725781e-06, -2.60737252e-07],
                                       [-2.12802803e-04, 1.84018218e-04, 1.90444087e-04,
                                        2.17565021e-04, 2.03971780e-04, 2.09446226e-04,
                                        2.11608257e-04, 2.46472592e-04, 7.90692415e-04,
                                        6.52409863e-06, -3.86785704e-07],
                                       [-2.71488782e-04, 5.78923328e-06, 1.16187824e-05,
                                        7.97756072e-06, 6.29181262e-06, 6.66273501e-06,
                                        9.45143925e-06, 8.10725781e-06, 6.52409863e-06,
                                        2.64461183e-05, 8.70031728e-08],
                                       [-1.17108280e-06, 8.62912701e-08, 7.68842070e-08,
                                        -9.56753770e-08, -2.57004528e-08, -5.53303117e-08,
                                        -5.46040064e-08, -2.60737252e-07, -3.86785704e-07,
                                        8.70031728e-08, 2.62593686e-09]])
        self.assertAlmostEqual(model.deviance, 15673.222613627502, delta=.0001)
        self.assertAlmostEqual(model.llf, -8126.9496609469106, delta=.0001)
        self.assertAlmostEqual(model.llnull, -88037.0499629, delta=.0001)
        np.testing.assert_allclose(model.pvalues,
                                   [2.23154436e-058,
                                    0.00000000e+000,
                                    0.00000000e+000,
                                    3.61133996e-009,
                                    1.05877746e-272,
                                    0.00000000e+000,
                                    2.69492058e-025,
                                    9.38664385e-213,
                                    9.08121216e-067,
                                    0.00000000e+000,
                                    0.00000000e+000])
        np.testing.assert_allclose(model.std_err,
                                   [5.49032782e-02,
                                    1.53743830e-02,
                                    1.53990555e-02,
                                    2.11181777e-02,
                                    1.76794099e-02,
                                    1.79055236e-02,
                                    2.01205795e-02,
                                    2.20260880e-02,
                                    2.81192535e-02,
                                    5.14257895e-03,
                                    5.12438958e-05])
        np.testing.assert_allclose(model.tvalues,
                                   [-16.10827734, 105.487554, 125.18435157, 5.90107805,
                                    35.28331128, 38.89641795, 10.39205191, 31.13025333,
                                    17.2620603, 173.52358548, -135.38293645])
        np.testing.assert_allclose(model.yhat,
                                   [1.79502279e+03, 2.77690999e+03, 1.77376340e+02,
                                    5.41058308e+02, 3.28265191e+02, 1.61020145e+02,
                                    9.55492240e+01, 3.54052486e+01, 1.48342439e+03,
                                    1.51642463e+04, 7.61962380e+02, 2.29718733e+03,
                                    2.29156465e+03, 9.24935115e+02, 5.55191561e+02,
                                    2.11833031e+02, 2.31106289e+03, 1.52712766e+04,
                                    8.29095427e+02, 2.57322937e+03, 2.28197035e+03,
                                    9.39377653e+02, 5.59026730e+02, 2.12153271e+02,
                                    1.78795020e+02, 9.29389609e+02, 1.00418629e+03,
                                    8.13086106e+02, 5.79191340e+02, 5.35268359e+02,
                                    4.24969889e+02, 1.60758895e+02, 5.47719688e+02,
                                    2.81394269e+03, 3.12998907e+03, 8.16565623e+02,
                                    1.15732912e+03, 7.21460431e+02, 4.41575377e+02,
                                    1.63374443e+02, 3.87326254e+02, 3.27181524e+03,
                                    3.23528198e+03, 6.77976176e+02, 1.34894643e+03,
                                    1.19916138e+03, 8.01876754e+02, 3.13863001e+02,
                                    1.75685709e+02, 1.22115852e+03, 1.23153422e+03,
                                    5.79386090e+02, 7.77596785e+02, 1.10887286e+03,
                                    7.06986190e+02, 2.63279368e+02, 4.96907636e+01,
                                    3.49378290e+02, 3.49326167e+02, 2.19253703e+02,
                                    2.26850151e+02, 3.53430501e+02, 3.36979293e+02,
                                    5.49332748e+02, 1.22952888e+01, 8.90162551e+01,
                                    8.85260032e+01, 5.53842615e+01, 5.60455225e+01,
                                    9.23759900e+01, 8.37976212e+01, 3.66824277e+02])
        self.assertAlmostEquals(model.D2, .910690541438)
        self.assertAlmostEquals(model.adj_D2, .896049646592)
        self.assertAlmostEquals(model.SSI, .750634498293)
        self.assertAlmostEquals(model.pseudoR2, .90768716507)
        self.assertAlmostEquals(model.adj_pseudoR2, .907562217676)
        self.assertAlmostEquals(model.SRMSE, 0.59478477816884223)

    def test_local_Attraction(self):
        model = Attraction(self.f, self.d, self.o_var, self.dij, 'exp')
        local = model.local(locs=np.unique(self.d))
        self.assertEqual(list(local.keys()).sort(), ['stde0',
                                                     'stde1',
                                                     'stde2',
                                                     'pvalue2',
                                                     'SRMSE',
                                                     'pvalue0',
                                                     'deviance',
                                                     'adj_pseudoR2',
                                                     'pvalue1',
                                                     'tvalue0',
                                                     'tvalue2',
                                                     'adj_D2',
                                                     'tvalue1',
                                                     'SSI',
                                                     'AIC',
                                                     'param1',
                                                     'param0',
                                                     'D2',
                                                     'pseudoR2',
                                                     'param2'].sort())

    def test_Doubly(self):
        model = Doubly(self.f, self.o, self.d,
                       self.dij, 'exp', constant=True)
        np.testing.assert_allclose(model.params,
                                   [6.20471518, 1.5449095, 2.4414292, 0.69924374,
                                    0.94869185, 1.28967637, 0.74270015, 1.19468573,
                                    0.98874193, 1.49709841, 2.18492741, 0.18784818,
                                    0.66434515, 0.74264938, 0.21334535, 0.66765781,
                                    0.39986094, -0.00791533], atol=1e-05)
        np.testing.assert_allclose(model.cov_params,
                                   [[5.01690795e-04, -2.67085869e-04, -2.85861407e-04,
                                     -2.47145002e-04, -2.56344375e-04, -2.47959694e-04,
                                     -2.51858026e-04, -2.34909872e-04, -2.31532205e-04,
                                     -2.12557582e-04, -2.30973877e-04, -1.98360054e-04,
                                     -2.04848380e-04, -1.97315240e-04, -2.04713619e-04,
                                     -1.92147501e-04, -1.90223393e-04, -2.88654296e-07],
                                    [-2.67085869e-04, 3.09818975e-04, 2.38201819e-04,
                                     2.40816440e-04, 2.44508571e-04, 2.41573651e-04,
                                     2.43213918e-04, 2.36475186e-04, 2.35264739e-04,
                                     2.70747190e-05, -4.06434204e-06, 3.54631504e-06,
                                     6.73127801e-06, 3.48881444e-07, 4.84106698e-06,
                                     -1.08816401e-07, -7.82227026e-07, 1.17369687e-07],
                                       [-2.85861407e-04, 2.38201819e-04, 3.15835404e-04,
                                        2.60714112e-04, 2.57317985e-04, 2.58287551e-04,
                                        2.61026738e-04, 2.62571867e-04, 2.63204233e-04,
                                        -1.46703716e-06, 6.08300790e-05, 2.43162304e-05,
                                        2.12302255e-05, 1.90685319e-05, 2.32782320e-05,
                                        2.47736982e-05, 2.53307733e-05, -1.06931968e-08],
                                       [-2.47145002e-04, 2.40816440e-04, 2.60714112e-04,
                                        4.73670174e-04, 2.57806206e-04, 2.62588091e-04,
                                        2.64062738e-04, 2.76642894e-04, 2.84509772e-04,
                                        1.49294364e-06, 2.51567145e-05, 1.84043845e-05,
                                        -2.99190057e-06, 1.28512200e-06, -1.26738274e-05,
                                        -7.38879982e-06, 8.72549111e-08, -1.47073267e-07],
                                       [-2.56344375e-04, 2.44508571e-04, 2.57317985e-04,
                                        2.57806206e-04, 3.74656775e-04, 2.56143467e-04,
                                        2.58077967e-04, 2.64765554e-04, 2.67330191e-04,
                                        5.69526013e-06, 2.17034297e-05, -1.88949992e-06,
                                        2.20220200e-05, 4.13804378e-06, 1.27479768e-06,
                                        6.91280894e-06, 9.28146927e-06, -7.56672892e-08],
                                       [-2.47959694e-04, 2.41573651e-04, 2.58287551e-04,
                                        2.62588091e-04, 2.56143467e-04, 3.54522394e-04,
                                        2.58778390e-04, 2.69502689e-04, 2.75078094e-04,
                                        -1.05126847e-06, 1.97946415e-05, 1.65176617e-06,
                                        3.76490799e-06, 1.56828518e-05, -1.12614285e-05,
                                        -3.07326187e-06, 1.83335365e-06, -1.15030115e-07],
                                       [-2.51858026e-04, 2.43213918e-04, 2.61026738e-04,
                                        2.64062738e-04, 2.58077967e-04, 2.58778390e-04,
                                        4.39566465e-04, 2.72270083e-04, 2.82540973e-04,
                                        1.91670954e-06, 2.37810191e-05, -1.14352620e-05,
                                        1.02053574e-06, -1.13429776e-05, 9.96630546e-06,
                                        -1.99290067e-05, -9.39196494e-06, -9.20519180e-08],
                                       [-2.34909872e-04, 2.36475186e-04, 2.62571867e-04,
                                        2.76642894e-04, 2.64765554e-04, 2.69502689e-04,
                                        2.72270083e-04, 5.48320166e-04, 2.87269673e-04,
                                        -4.65887794e-06, 2.58726435e-05, -8.69859569e-06,
                                        5.47577328e-06, -4.51656124e-06, -2.45399627e-05,
                                        1.73990976e-05, -1.18725973e-04, -2.41709452e-07],
                                       [-2.31532205e-04, 2.35264739e-04, 2.63204233e-04,
                                        2.84509772e-04, 2.67330191e-04, 2.75078094e-04,
                                        2.82540973e-04, 2.87269673e-04, 8.14375093e-04,
                                        -5.91338987e-06, 2.65660547e-05, -1.32176066e-06,
                                        7.75773294e-06, 5.22185282e-07, -1.46830696e-05,
                                        -1.23317429e-04, 1.69481305e-05, -2.70788264e-07],
                                       [-2.12557582e-04, 2.70747190e-05, -1.46703716e-06,
                                        1.49294364e-06, 5.69526013e-06, -1.05126847e-06,
                                        1.91670954e-06, -4.65887794e-06, -5.91338987e-06,
                                        2.42563703e-04, 1.84733809e-04, 1.89259451e-04,
                                        1.91681710e-04, 1.89152965e-04, 1.92231256e-04,
                                        1.87441436e-04, 1.86834624e-04, 1.13843139e-07],
                                       [-2.30973877e-04, -4.06434204e-06, 6.08300790e-05,
                                        2.51567145e-05, 2.17034297e-05, 1.97946415e-05,
                                        2.37810191e-05, 2.58726435e-05, 2.65660547e-05,
                                        1.84733809e-04, 2.63915732e-04, 2.06737361e-04,
                                        2.02941436e-04, 2.03812109e-04, 2.06876793e-04,
                                        2.08793972e-04, 2.09473765e-04, -1.94248549e-08],
                                       [-1.98360054e-04, 3.54631504e-06, 2.43162304e-05,
                                        1.84043845e-05, -1.88949992e-06, 1.65176617e-06,
                                        -1.14352620e-05, -8.69859569e-06, -1.32176066e-06,
                                        1.89259451e-04, 2.06737361e-04, 4.53792323e-04,
                                        2.01217128e-04, 2.05155865e-04, 2.05806138e-04,
                                        2.14332194e-04, 2.21550755e-04, -9.10506514e-08],
                                       [-2.04848380e-04, 6.73127801e-06, 2.12302255e-05,
                                        -2.99190057e-06, 2.20220200e-05, 3.76490799e-06,
                                        1.02053574e-06, 5.47577328e-06, 7.75773294e-06,
                                        1.91681710e-04, 2.02941436e-04, 2.01217128e-04,
                                        3.17290867e-04, 2.00121482e-04, 2.02120689e-04,
                                        2.06522637e-04, 2.08554008e-04, -4.37219119e-08],
                                       [-1.97315240e-04, 3.48881444e-07, 1.90685319e-05,
                                        1.28512200e-06, 4.13804378e-06, 1.56828518e-05,
                                        -1.13429776e-05, -4.51656124e-06, 5.22185282e-07,
                                        1.89152965e-04, 2.03812109e-04, 2.05155865e-04,
                                        2.00121482e-04, 3.26458468e-04, 2.01391450e-04,
                                        2.09628557e-04, 2.14889547e-04, -7.68167253e-08],
                                       [-2.04713619e-04, 4.84106698e-06, 2.32782320e-05,
                                        -1.26738274e-05, 1.27479768e-06, -1.12614285e-05,
                                        9.96630546e-06, -2.45399627e-05, -1.46830696e-05,
                                        1.92231256e-04, 2.06876793e-04, 2.05806138e-04,
                                        2.02120689e-04, 2.01391450e-04, 4.14676504e-04,
                                        2.11496728e-04, 2.21430978e-04, -2.25147281e-08],
                                       [-1.92147501e-04, -1.08816401e-07, 2.47736982e-05,
                                        -7.38879982e-06, 6.91280894e-06, -3.07326187e-06,
                                        -1.99290067e-05, 1.73990976e-05, -1.23317429e-04,
                                        1.87441436e-04, 2.08793972e-04, 2.14332194e-04,
                                        2.06522637e-04, 2.09628557e-04, 2.11496728e-04,
                                        5.06851801e-04, 2.14569472e-04, -1.33183180e-07],
                                       [-1.90223393e-04, -7.82227026e-07, 2.53307733e-05,
                                        8.72549111e-08, 9.28146927e-06, 1.83335365e-06,
                                        -9.39196494e-06, -1.18725973e-04, 1.69481305e-05,
                                        1.86834624e-04, 2.09473765e-04, 2.21550755e-04,
                                        2.08554008e-04, 2.14889547e-04, 2.21430978e-04,
                                        2.14569472e-04, 8.07696796e-04, -1.49419116e-07],
                                       [-2.88654296e-07, 1.17369687e-07, -1.06931968e-08,
                                        -1.47073267e-07, -7.56672892e-08, -1.15030115e-07,
                                        -9.20519180e-08, -2.41709452e-07, -2.70788264e-07,
                                        1.13843139e-07, -1.94248549e-08, -9.10506514e-08,
                                        -4.37219119e-08, -7.68167253e-08, -2.25147281e-08,
                                        -1.33183180e-07, -1.49419116e-07, 2.56252122e-09]])
        self.assertAlmostEqual(model.deviance, 9360.482092561484, delta=.0001)
        self.assertAlmostEqual(model.llf, -4970.5795707251054, delta=.0001)
        self.assertAlmostEqual(model.llnull, -88037.0499629, delta=.0001)
        np.testing.assert_allclose(model.pvalues,
                                   [0.00000000e+000,
                                    0.00000000e+000,
                                    0.00000000e+000,
                                    1.76331083e-226,
                                    0.00000000e+000,
                                    0.00000000e+000,
                                    7.21755436e-275,
                                    0.00000000e+000,
                                    4.88760299e-263,
                                    0.00000000e+000,
                                    0.00000000e+000,
                                    1.16346714e-018,
                                    1.88877600e-304,
                                    0.00000000e+000,
                                    1.10421926e-025,
                                    2.83322217e-193,
                                    5.83172788e-045,
                                    0.00000000e+000])
        np.testing.assert_allclose(model.std_err,
                                   [2.23984552e-02,
                                    1.76016753e-02,
                                    1.77717586e-02,
                                    2.17639650e-02,
                                    1.93560527e-02,
                                    1.88287651e-02,
                                    2.09658404e-02,
                                    2.34162372e-02,
                                    2.85372580e-02,
                                    1.55744567e-02,
                                    1.62454834e-02,
                                    2.13024018e-02,
                                    1.78126603e-02,
                                    1.80681617e-02,
                                    2.03636073e-02,
                                    2.25133694e-02,
                                    2.84200070e-02,
                                    5.06213514e-05])
        np.testing.assert_allclose(model.tvalues,
                                   [277.01531761, 87.77059404, 137.37690538, 32.12850872,
                                    49.0126713, 68.49500564, 35.42429674, 51.01954322,
                                    34.64740487, 96.12524108, 134.49445277, 8.81816885,
                                    37.29623407, 41.10265285, 10.47679558, 29.65605897,
                                    14.06969877, -156.36352821])
        np.testing.assert_allclose(model.yhat,
                                   [9.78988280e+02, 2.26003279e+03, 1.04038742e+02,
                                    3.38382580e+02, 1.90458075e+02, 8.67908467e+01,
                                    4.37554720e+01, 1.35532201e+01, 1.02693176e+03,
                                    1.43579537e+04, 5.02646536e+02, 1.61314478e+03,
                                    1.60124044e+03, 5.84144805e+02, 2.98377549e+02,
                                    9.55604104e+01, 2.92086883e+03, 1.76899160e+04,
                                    9.49267467e+02, 3.14910952e+03, 2.73315395e+03,
                                    1.01972797e+03, 5.15779061e+02, 1.64177257e+02,
                                    1.73496758e+02, 7.99088484e+02, 1.22486311e+03,
                                    9.31563443e+02, 6.29698756e+02, 5.91117070e+02,
                                    4.15424488e+02, 1.31747905e+02, 4.49674437e+02,
                                    2.04361676e+03, 3.23802841e+03, 7.42345992e+02,
                                    1.00234547e+03, 6.00432512e+02, 3.13590596e+02,
                                    9.69658353e+01, 3.29132064e+02, 2.63792996e+03,
                                    3.65458094e+03, 6.52540343e+02, 1.30346098e+03,
                                    1.16517842e+03, 6.73203489e+02, 2.21973821e+02,
                                    1.47356669e+02, 9.45479887e+02, 1.33962391e+03,
                                    6.01828982e+02, 7.67131590e+02, 1.14476805e+03,
                                    6.43385796e+02, 2.00425139e+02, 7.41169755e+01,
                                    4.81822820e+02, 6.76007805e+02, 4.21969575e+02,
                                    3.99722086e+02, 6.59873779e+02, 6.41890452e+02,
                                    9.85596546e+02, 2.44225078e+01, 1.64157859e+02,
                                    2.28909306e+02, 1.42362371e+02, 1.31485029e+02,
                                    2.31461478e+02, 2.12717926e+02, 1.04848355e+03])
        self.assertAlmostEquals(model.D2, .946661920897)
        self.assertAlmostEquals(model.adj_D2, .929870303401)
        self.assertAlmostEquals(model.SSI, .811852110904)
        self.assertAlmostEquals(model.pseudoR2, .943539912198)
        self.assertAlmostEquals(model.adj_pseudoR2, .943335452826)
        self.assertAlmostEquals(model.SRMSE, 0.37925654532618808)


if __name__ == '__main__':
    unittest.main()


"""
Tests for universal spatial interaction models.

Test data is the Austria migration dataset used in Dennet's (2012) practical
primer on spatial interaction modeling. The data was made avialable through the
following dropbox link: http://dl.dropbox.com/u/8649795/AT_Austria.csv.
The data has been pre-filtered so that there are no intra-zonal flows.

Dennett, A. (2012). Estimating flows between geographical locations: get me
    started in spatial interaction modelling (Working Paper No. 184).
    UCL: Citeseer.
"""

__author__ = 'Tyler Hoffman tylerhoff1@gmail.com'

import unittest
import numpy as np
from scipy.stats import pearsonr
from ..universal import Lenormand, Radiation, PWO
np.random.seed(123456)


class TestUniversal(unittest.TestCase):
    """ Tests for universal models """

    def setUp(self):
        self.f = np.array([0,  1131,  1887,    69,   738,    98,
                          31,    43,    19, 1633,     0, 14055,   416,
                          1276,  1850,   388,   303,   159, 2301, 20164,
                          0,  1080,  1831,  1943,   742,   674,   407,
                          85,   379,  1597,     0,  1608,   328,   317,
                          469,   114, 762,  1110,  2973,  1252,     0,
                          1081,   622,   425,   262, 196,  2027,  3498, 346,
                          1332,     0,  2144,   821,   274, 49,   378,  1349,
                          310,   851,  2117,     0,   630,   106, 87,   424,
                          978,   490,   670,   577,   546,     0,   569,
                          33,   128,   643,   154,   328,   199, 112, 587, 0])

        self.o = np.array(['AT11', 'AT11', 'AT11', 'AT11', 'AT11', 'AT11',
                           'AT11', 'AT11', 'AT11', 'AT12', 'AT12', 'AT12',
                           'AT12', 'AT12', 'AT12', 'AT12', 'AT12', 'AT12',
                           'AT13', 'AT13', 'AT13', 'AT13', 'AT13', 'AT13',
                           'AT13', 'AT13', 'AT13', 'AT21', 'AT21', 'AT21',
                           'AT21', 'AT21', 'AT21', 'AT21', 'AT21', 'AT21',
                           'AT22', 'AT22', 'AT22', 'AT22', 'AT22', 'AT22',
                           'AT22', 'AT22', 'AT22', 'AT31', 'AT31', 'AT31',
                           'AT31', 'AT31', 'AT31', 'AT31', 'AT31', 'AT31',
                           'AT32', 'AT32', 'AT32', 'AT32', 'AT32', 'AT32',
                           'AT32', 'AT32', 'AT32', 'AT33', 'AT33', 'AT33',
                           'AT33', 'AT33', 'AT33', 'AT33', 'AT33', 'AT33',
                           'AT34', 'AT34', 'AT34', 'AT34', 'AT34', 'AT34',
                           'AT34', 'AT34', 'AT34'])

        self.d = np.array(['AT11', 'AT12', 'AT13', 'AT21', 'AT22', 'AT31',
                           'AT32', 'AT33', 'AT34', 'AT11', 'AT12', 'AT13',
                           'AT21', 'AT22', 'AT31', 'AT32', 'AT33', 'AT34',
                           'AT11', 'AT12', 'AT13', 'AT21', 'AT22', 'AT31',
                           'AT32', 'AT33', 'AT34', 'AT11', 'AT12', 'AT13',
                           'AT21', 'AT22', 'AT31', 'AT32', 'AT33', 'AT34',
                           'AT11', 'AT12', 'AT13', 'AT21', 'AT22', 'AT31',
                           'AT32', 'AT33', 'AT34', 'AT11', 'AT12', 'AT13',
                           'AT21', 'AT22', 'AT31', 'AT32', 'AT33', 'AT34',
                           'AT11', 'AT12', 'AT13', 'AT21', 'AT22', 'AT31',
                           'AT32', 'AT33', 'AT34', 'AT11', 'AT12', 'AT13',
                           'AT21', 'AT22', 'AT31', 'AT32', 'AT33', 'AT34',
                           'AT11', 'AT12', 'AT13', 'AT21', 'AT22', 'AT31',
                           'AT32', 'AT33', 'AT34'])

        self.dij = np.array([0, 103,  84, 221, 132, 215, 247, 391, 505,
                            103,   0,  46, 217, 130, 141, 201, 344, 454,
                            84,  46,   0, 250, 159, 186, 244, 288, 498,
                            221, 217, 250,   0,  92, 152,  93, 195, 306,
                            132, 130, 159, 92,   0, 125, 122, 262, 376,
                            215, 141, 186, 152, 125,   0,  82, 208, 315,
                            247, 201, 244,  93, 122,  82,   0, 145, 259,
                            391, 344, 388, 195, 262, 208, 145,   0, 114,
                            505, 454, 498, 306, 376, 315, 259, 114,   0])

        self.o_var = np.array([4016,  4016,  4016,  4016,  4016,  4016,
                              4016,  4016,  4016, 20080, 20080, 20080,
                              20080, 20080, 20080, 20080, 20080, 20080,
                              29142, 29142, 29142, 29142, 29142, 29142,
                              29142, 29142, 29142, 4897,  4897,  4897,
                              4897,  4897,  4897,  4897,  4897,  4897,
                              8487,  8487,  8487,  8487,  8487,  8487,
                              8487,  8487,  8487, 10638, 10638, 10638,
                              10638, 10638, 10638, 10638, 10638, 10638,
                              5790,  5790,  5790,  5790,  5790,  5790,
                              5790,  5790,  5790, 4341,  4341,  4341,
                              4341,  4341,  4341,  4341,  4341,  4341,
                              2184,  2184,  2184,  2184,  2184,  2184,
                              2184,  2184,  2184])

        self.d_var = np.array([5146, 25741, 26980,  4117,  8634,  8193,
                              4902,  3952,  1910, 5146, 25741, 26980,  4117,
                              8634,  8193,  4902,  3952,  1910, 5146, 25741,
                              26980,  4117,  8634,  8193,  4902,  3952,  1910,
                              5146, 25741, 26980,  4117,  8634,  8193,  4902,
                              3952,  1910, 5146, 25741, 26980,  4117,  8634,
                              8193,  4902,  3952,  1910, 5146, 25741, 26980,
                              4117,  8634,  8193,  4902,  3952,  1910,
                              5146, 25741, 26980,  4117,  8634,  8193,
                              4902,  3952,  1910, 5146, 25741, 26980,  4117,
                              8634,  8193,  4902,  3952,  1910, 5146, 25741,
                              26980,  4117,  8634,  8193,  4902,  3952,  1910])

        self.xlocs = np.array([47.1537, 48.1081, 48.2082, 46.7222, 47.3593,
                               48.0259, 47.8095, 47.2537, 47.2497])

        self.ylocs = np.array([16.2689, 15.805, 16.3738, 14.1806, 14.47,
                               13.9724, 13.055, 11.6015,  9.9797])

    def ready(self):
        N = 9
        outflows = self.o_var[0::N]
        inflows = self.d_var[0:N]
        locs = np.zeros((N, 2))
        locs[:, 0] = self.xlocs
        locs[:, 1] = self.ylocs
        dists = np.reshape(self.dij, (N, N), order='C')
        T_obs = np.reshape(self.f, (N, N), order='C')

        return outflows, inflows, locs, dists, T_obs

    def test_Lenormand(self):
        outflows, inflows, locs, dists, T_obs = self.ready()

        # Lenormand paper's model
        model = Lenormand(inflows, outflows, dists)
        T_L = model.flowmat()
        np.testing.assert_almost_equal(
            pearsonr(T_L.flatten(), T_obs.flatten()),
            (-0.0728415, 0.5181216)
        )
        
    def test_Radiation(self):
        outflows, inflows, locs, dists, T_obs = self.ready()

        # Radiation model -- requires locations of each node
        model = Radiation(inflows, outflows, dists, locs, locs)
        T_R = model.flowmat()
        np.testing.assert_almost_equal(
            pearsonr(T_R.flatten(), T_obs.flatten()),
            (0.05384603805950201, 0.6330568989373918)
        )

    def test_PWO(self):
        outflows, inflows, locs, dists, T_obs = self.ready()

        # PWO model
        model = PWO(inflows, outflows, dists, locs, locs)
        T_P = model.flowmat()
        np.testing.assert_almost_equal(
            pearsonr(T_P.flatten(), T_obs.flatten()),
            (0.23623562773229048, 0.033734908271368574)
        )


if __name__ == '__main__':
    unittest.main()


"""
Tests for analysis of spatial autocorrelation within vectors

"""

__author__ = 'Taylor Oshan tayoshan@gmail.com'

import unittest
import numpy as np
from libpysal.weights.distance import DistanceBand
from ..vec_SA import VecMoran


class TestVecMoran(unittest.TestCase):
    """Tests VecMoran class"""

    def setUp(self):
        self.vecs = np.array([[1, 55, 60, 100, 500],
                              [2, 60, 55, 105, 501],
                              [3, 500, 55, 155, 500],
                              [4, 505, 60, 160, 500],
                              [5, 105, 950, 105, 500],
                              [6, 155, 950, 155, 499]])
        self.origins = self.vecs[:, 1:3]
        self.dests = self.vecs[:, 3:5]

    def test_origin_focused_A(self):
        wo = DistanceBand(
            self.origins,
            threshold=9999,
            alpha=-1.5,
            binary=False)
        np.random.seed(1)
        vmo = VecMoran(self.vecs, wo, focus='origin', rand='A')
        self.assertAlmostEquals(vmo.I, 0.645944594367)
        self.assertAlmostEquals(vmo.p_z_sim, 0.03898650733809228)

    def test_dest_focused_A(self):
        wd = DistanceBand(self.dests, threshold=9999, alpha=-1.5, binary=False)
        np.random.seed(1)
        vmd = VecMoran(self.vecs, wd, focus='destination', rand='A')
        self.assertAlmostEquals(vmd.I, -0.764603695022)
        self.assertAlmostEquals(vmd.p_z_sim, 0.149472673677)

    def test_origin_focused_B(self):
        wo = DistanceBand(
            self.origins,
            threshold=9999,
            alpha=-1.5,
            binary=False)
        np.random.seed(1)
        vmo = VecMoran(self.vecs, wo, focus='origin', rand='B')
        self.assertAlmostEquals(vmo.I, 0.645944594367)
        self.assertAlmostEquals(vmo.p_z_sim, 0.02944612633233532)

    def test_dest_focused_B(self):
        wd = DistanceBand(self.dests, threshold=9999, alpha=-1.5, binary=False)
        np.random.seed(1)
        vmd = VecMoran(self.vecs, wd, focus='destination', rand='B')
        self.assertAlmostEquals(vmd.I, -0.764603695022)
        self.assertAlmostEquals(vmd.p_z_sim, 0.12411761124197379)


if __name__ == '__main__':
    unittest.main()


