# -*- coding: utf-8 -*-
"""
Created on Sun May 10 12:39:33 2015

Author: Josef Perktold
License: BSD-3
"""

import warnings

import pytest
import numpy as np
from numpy.testing import assert_allclose, assert_equal
from statsmodels.discrete.discrete_model import Poisson, Logit, Probit
from statsmodels.genmod.generalized_linear_model import GLM
from statsmodels.genmod.families import family
from statsmodels.sandbox.regression.penalized import TheilGLS
from statsmodels.base._penalized import PenalizedMixin
import statsmodels.base._penalties as smpen


class PoissonPenalized(PenalizedMixin, Poisson):
    pass


class LogitPenalized(PenalizedMixin, Logit):
    pass


class ProbitPenalized(PenalizedMixin, Probit):
    pass


class GLMPenalized(PenalizedMixin, GLM):
    pass


class CheckPenalizedPoisson(object):

    @classmethod
    def setup_class(cls):
        # simulate data
        np.random.seed(987865)

        nobs, k_vars = 500, 10
        k_nonzero = 4
        x = ((np.random.rand(nobs, k_vars) +
              0.5* (np.random.rand(nobs, 1) - 0.5)) * 2 - 1)
        x *= 1.2
        x[:, 0] = 1
        beta = np.zeros(k_vars)
        beta[:k_nonzero] = 1. / np.arange(1, k_nonzero + 1)
        linpred = x.dot(beta)
        y = cls._generate_endog(linpred)

        cls.k_nonzero = k_nonzero
        cls.x = x
        cls.y = y

        # defaults to be overwritten by subclasses
        cls.rtol = 1e-4
        cls.atol = 1e-6
        cls.exog_index = slice(None, None, None)
        cls.k_params = k_vars
        cls.skip_hessian = False  # can be overwritten in _initialize
        cls.penalty = smpen.SCADSmoothed(0.1, c0=0.0001)  # default for tests
        cls._initialize()

    @classmethod
    def _generate_endog(self, linpred):
        mu = np.exp(linpred)
        np.random.seed(999)
        y = np.random.poisson(mu)
        return y

    def test_params_table(self):
        res1 = self.res1
        res2 = self.res2
        assert_equal((res1.params != 0).sum(), self.k_params)
        assert_allclose(res1.params[self.exog_index], res2.params,
                        rtol=self.rtol, atol=self.atol)
        assert_allclose(res1.bse[self.exog_index], res2.bse, rtol=self.rtol,
                        atol=self.atol)
        with warnings.catch_warnings():
            # silence scipy distribution warnigns becaus of zero bse
            warnings.simplefilter('ignore', RuntimeWarning)
            assert_allclose(res1.pvalues[self.exog_index], res2.pvalues,
                            rtol=self.rtol, atol=self.atol)
        assert_allclose(res1.predict(), res2.predict(), rtol=0.05)

    @pytest.mark.smoke
    def test_summary(self):
        self.res1.summary()

    @pytest.mark.smoke
    def test_summary2(self):
        summ = self.res1.summary2()
        assert isinstance(summ.as_latex(), str)
        assert isinstance(summ.as_html(), str)
        assert isinstance(summ.as_text(), str)

    def test_numdiff(self):
        res1 = self.res1

        # avoid checking score at MLE, score close to zero
        p = res1.params * 0.98
        # GLM concentrates out scale which affects derivatives, see #4616
        kwds = {'scale': 1} if isinstance(res1.model, GLM) else {}

        assert_allclose(res1.model.score(p, **kwds)[self.exog_index],
                        res1.model.score_numdiff(p, **kwds)[self.exog_index],
                        rtol=0.025)

        if not self.skip_hessian:
            if isinstance(self.exog_index, slice):
                idx1 = idx2 = self.exog_index
            else:
                idx1 = self.exog_index[:, None]
                idx2 = self.exog_index

            h1 = res1.model.hessian(res1.params, **kwds)[idx1, idx2]
            h2 = res1.model.hessian_numdiff(res1.params, **kwds)[idx1, idx2]
            assert_allclose(h1, h2, rtol=0.02)


class TestPenalizedPoissonNonePenal(CheckPenalizedPoisson):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x

        modp = Poisson(y, x)
        cls.res2 = modp.fit(disp=0)

        mod = PoissonPenalized(y, x)  # default no penalty
        mod.pen_weight = 0
        cls.res1 = mod.fit(method='bfgs', maxiter=100, disp=0)

        cls.atol = 5e-6


class TestPenalizedPoissonNoPenal(CheckPenalizedPoisson):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x

        modp = Poisson(y, x)
        cls.res2 = modp.fit(disp=0)

        mod = PoissonPenalized(y, x)
        mod.pen_weight = 0
        cls.res1 = mod.fit(method='bfgs', maxiter=100, disp=0)

        cls.atol = 5e-6


class TestPenalizedGLMPoissonNoPenal(CheckPenalizedPoisson):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x

        modp = GLM(y, x, family=family.Poisson())
        cls.res2 = modp.fit()

        mod = GLMPenalized(y, x, family=family.Poisson(), penal=cls.penalty)
        mod.pen_weight = 0
        cls.res1 = mod.fit(method='bfgs', maxiter=100, disp=0)

        cls.atol = 5e-6


class TestPenalizedPoissonOracle(CheckPenalizedPoisson):
    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = Poisson(y, x[:, :cls.k_nonzero])
        cls.res2 = modp.fit(disp=0)

        mod = PoissonPenalized(y, x, penal=cls.penalty)
        mod.pen_weight *= 1.5
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-3


class TestPenalizedGLMPoissonOracle(CheckPenalizedPoisson):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Poisson())
        cls.res2 = modp.fit()

        mod = GLMPenalized(y, x, family=family.Poisson(), penal=cls.penalty)
        mod.pen_weight *= 1.5  # same as discrete Poisson
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-3


class TestPenalizedPoissonOracleHC(CheckPenalizedPoisson):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        cov_type = 'HC0'
        modp = Poisson(y, x[:, :cls.k_nonzero])
        cls.res2 = modp.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                            disp=0)

        mod = PoissonPenalized(y, x, penal=cls.penalty)
        mod.pen_weight *= 1.5
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-3

    def test_cov_type(self):
        res1 = self.res1
        res2 = self.res2

        assert_equal(self.res1.cov_type, 'HC0')
        cov_kwds = {'description': 'Standard Errors are heteroscedasticity '
                    'robust (HC0)',
                    'adjust_df': False, 'use_t': False, 'scaling_factor': None}
        assert_equal(self.res1.cov_kwds, cov_kwds)
        # numbers are regression test using bfgs
        params = np.array([0.96817787574701109, 0.43674374940137434,
                           0.33096260487556745, 0.27415680046693747])
        bse = np.array([0.028126650444581985, 0.033099984564283147,
                        0.033184585514904545, 0.034282504130503301])
        assert_allclose(res2.params[:self.k_nonzero], params, atol=1e-5)
        assert_allclose(res2.bse[:self.k_nonzero], bse, rtol=1e-6)
        assert_allclose(res1.params[:self.k_nonzero], params, atol=self.atol)
        assert_allclose(res1.bse[:self.k_nonzero], bse, rtol=0.02)


class TestPenalizedGLMPoissonOracleHC(CheckPenalizedPoisson):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        cov_type = 'HC0'
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Poisson())
        cls.res2 = modp.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                            disp=0)

        mod = GLMPenalized(y, x, family=family.Poisson(), penal=cls.penalty)
        mod.pen_weight *= 1.5  # same as ddiscrete Poisson
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-3


class TestPenalizedPoissonGLMOracleHC(CheckPenalizedPoisson):
    # compare discrete Poisson and GLM-Poisson

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        cov_type = 'HC0'
        modp = PoissonPenalized(y, x, penal=cls.penalty)
        modp.pen_weight *= 1.5  # same as discrete Poisson 1.5
        modp.penal.tau = 0.05
        cls.res2 = modp.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                            disp=0)

        mod = GLMPenalized(y, x, family=family.Poisson(), penal=cls.penalty)
        mod.pen_weight *= 1.5  # same as discrete Poisson 1.5
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0)

        cls.exog_index = slice(None, None, None)

        cls.atol = 1e-4


class TestPenalizedPoissonOraclePenalized(CheckPenalizedPoisson):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = PoissonPenalized(y, x[:, :cls.k_nonzero], penal=cls.penalty)
        cls.res2 = modp.fit(method='bfgs', maxiter=100, disp=0)

        mod = PoissonPenalized(y, x, penal=cls.penalty)
        # mod.pen_weight *= 1.5
        # mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100, trim=False, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 1e-3


class TestPenalizedPoissonOraclePenalized2(CheckPenalizedPoisson):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = PoissonPenalized(y, x[:, :cls.k_nonzero], penal=cls.penalty)
        modp.pen_weight *= 10  # need to penalize more to get oracle selection
        modp.penal.tau = 0.05
        sp2 = np.array([0.96817921, 0.43673551, 0.33096011, 0.27416614])
        cls.res2 = modp.fit(start_params=sp2 * 0.5, method='bfgs',
                            maxiter=100, disp=0)

        params_notrim = np.array([
            9.68178874e-01, 4.36744981e-01, 3.30965041e-01, 2.74161883e-01,
            -2.58988461e-06, -1.24352640e-06, 4.48584458e-08, -2.46876149e-06,
            -1.02471074e-05, -4.39248098e-06])

        mod = PoissonPenalized(y, x, penal=cls.penalty)
        mod.pen_weight *= 10  # need to penalize more to get oracle selection
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(start_params=params_notrim * 0.5,
                           method='bfgs', maxiter=100, trim=True, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 1e-8
        cls.k_params = cls.k_nonzero

    def test_zeros(self):

        # first test for trimmed result
        assert_equal(self.res1.params[self.k_nonzero:], 0)
        # we also set bse to zero, TODO: check fit_regularized
        assert_equal(self.res1.bse[self.k_nonzero:], 0)


class TestPenalizedPoissonOraclePenalized2HC(CheckPenalizedPoisson):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        cov_type = 'HC0'
        modp = PoissonPenalized(y, x[:, :cls.k_nonzero], penal=cls.penalty)
        modp.pen_weight *= 10  # need to penalize more to get oracle selection
        modp.penal.tau = 0.05
        sp2 = np.array([0.96817921, 0.43673551, 0.33096011, 0.27416614])
        cls.res2 = modp.fit(start_params=sp2 * 0.5, cov_type=cov_type,
                            method='bfgs', maxiter=100, disp=0)

        params_notrim = np.array([
            9.68178874e-01, 4.36744981e-01, 3.30965041e-01, 2.74161883e-01,
            -2.58988461e-06, -1.24352640e-06, 4.48584458e-08, -2.46876149e-06,
            -1.02471074e-05, -4.39248098e-06])

        mod = PoissonPenalized(y, x, penal=cls.penalty)
        mod.pen_weight *= 10  # need to penalize more to get oracle selection
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(start_params=params_notrim * 0.5,cov_type=cov_type,
                           method='bfgs', maxiter=100, trim=True, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)
        cls.atol = 1e-12
        cls.k_params = cls.k_nonzero

    def test_cov_type(self):
        res1 = self.res1
        res2 = self.res2

        assert_equal(self.res1.cov_type, 'HC0')
        assert_equal(self.res1.results_constrained.cov_type, 'HC0')
        cov_kwds = {'description': 'Standard Errors are heteroscedasticity '
                                   'robust (HC0)',
                    'adjust_df': False, 'use_t': False, 'scaling_factor': None}
        assert_equal(self.res1.cov_kwds, cov_kwds)
        assert_equal(self.res1.cov_kwds, self.res1.results_constrained.cov_kwds)

        # numbers are regression test using bfgs
        params = np.array([0.96817787574701109, 0.43674374940137434,
                           0.33096260487556745, 0.27415680046693747])
        bse = np.array([0.028126650444581985, 0.033099984564283147,
                        0.033184585514904545, 0.034282504130503301])
        assert_allclose(res2.params[:self.k_nonzero], params, atol=1e-5)
        assert_allclose(res2.bse[:self.k_nonzero], bse, rtol=5e-6)
        assert_allclose(res1.params[:self.k_nonzero], params, atol=1e-5)
        assert_allclose(res1.bse[:self.k_nonzero], bse, rtol=5e-6)


# the following classes are copies of Poisson with model adjustments

class CheckPenalizedLogit(CheckPenalizedPoisson):

    @classmethod
    def _generate_endog(self, linpred):
        mu = 1 / (1 + np.exp(-linpred + linpred.mean() - 0.5))
        np.random.seed(999)
        y = np.random.rand(len(mu)) < mu
        return y


class TestPenalizedLogitNoPenal(CheckPenalizedLogit):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x

        modp = Logit(y, x)
        cls.res2 = modp.fit(disp=0)

        mod = LogitPenalized(y, x, penal=cls.penalty)
        mod.pen_weight = 0
        cls.res1 = mod.fit(disp=0)

        cls.atol = 1e-4  # why not closer ?


class TestPenalizedLogitOracle(CheckPenalizedLogit):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = Logit(y, x[:, :cls.k_nonzero])
        cls.res2 = modp.fit(disp=0)

        mod = LogitPenalized(y, x, penal=cls.penalty)
        mod.pen_weight *= .5
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-3


class TestPenalizedGLMLogitOracle(CheckPenalizedLogit):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Binomial())
        cls.res2 = modp.fit(disp=0)

        mod = GLMPenalized(y, x, family=family.Binomial(), penal=cls.penalty)
        mod.pen_weight *= .5
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-3


class TestPenalizedLogitOraclePenalized(CheckPenalizedLogit):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = LogitPenalized(y, x[:, :cls.k_nonzero], penal=cls.penalty)
        cls.res2 = modp.fit(method='bfgs', maxiter=100, disp=0)

        mod = LogitPenalized(y, x, penal=cls.penalty)
        # mod.pen_weight *= 1.5
        # mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100, trim=False, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 1e-3


class TestPenalizedLogitOraclePenalized2(CheckPenalizedLogit):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        modp = LogitPenalized(y, x[:, :cls.k_nonzero], penal=cls.penalty)
        modp.pen_weight *= 0.5
        modp.penal.tau = 0.05
        cls.res2 = modp.fit(method='bfgs', maxiter=100, disp=0)

        mod = LogitPenalized(y, x, penal=cls.penalty)
        mod.pen_weight *= 0.5
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(method='bfgs', maxiter=100, trim=True, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 1e-8
        cls.k_params = cls.k_nonzero

    def test_zeros(self):

        # test for trimmed result
        assert_equal(self.res1.params[self.k_nonzero:], 0)
        # we also set bse to zero
        assert_equal(self.res1.bse[self.k_nonzero:], 0)


# the following classes are copies of Poisson with model adjustments
class CheckPenalizedBinomCount(CheckPenalizedPoisson):

    @classmethod
    def _generate_endog(self, linpred):
        mu = 1 / (1 + np.exp(-linpred + linpred.mean() - 0.5))
        np.random.seed(999)
        n_trials = 5 * np.ones(len(mu), int)
        n_trials[:len(mu)//2] += 5
        y = np.random.binomial(n_trials, mu)
        return np.column_stack((y, n_trials - y))


class TestPenalizedGLMBinomCountNoPenal(CheckPenalizedBinomCount):
    # TODO: check, adjust cov_type

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        x = x[:, :4]
        offset = -0.25 * np.ones(len(y))  # also check offset
        modp = GLM(y, x, family=family.Binomial(), offset=offset)
        cls.res2 = modp.fit(method='bfgs', max_start_irls=100)

        mod = GLMPenalized(y, x, family=family.Binomial(), offset=offset,
                           penal=cls.penalty)
        mod.pen_weight = 0
        cls.res1 = mod.fit(method='bfgs', max_start_irls=3, maxiter=100, disp=0,
                           start_params=cls.res2.params*0.9)

        cls.atol = 1e-10
        cls.k_params = 4

    def test_deriv(self):
        res1 = self.res1
        res2 = self.res2
        assert_allclose(res1.model.score(res2.params * 0.98),
                        res2.model.score(res2.params * 0.98), rtol=1e-10)
        assert_allclose(res1.model.score_obs(res2.params * 0.98),
                        res2.model.score_obs(res2.params * 0.98), rtol=1e-10)


class TestPenalizedGLMBinomCountOracleHC(CheckPenalizedBinomCount):
    # TODO: There are still problems with this case
    # using the standard optimization, I get convergence failures and
    # different estimates depending on details, e.g. small changes in pen_weight
    # most likely convexity fails with SCAD in this case

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        offset = -0.25 * np.ones(len(y))  # also check offset
        cov_type = 'HC0'
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Binomial(),
                   offset=offset)
        cls.res2 = modp.fit(cov_type=cov_type, method='newton', maxiter=1000,
                            disp=0)

        mod = GLMPenalized(y, x, family=family.Binomial(), offset=offset,
                           penal=cls.penalty)
        mod.pen_weight *= 1  # lower than in other cases
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', max_start_irls=0,
                           maxiter=100, disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 1e-3


class TestPenalizedGLMBinomCountOracleHC2(CheckPenalizedBinomCount):
    # TODO: There are still problems with this case, see other class
    # with trimming of small parameters, needs larger trim threshold

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        offset = -0.25 * np.ones(len(y))  # also check offset
        cov_type = 'HC0'
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Binomial(),
                   offset=offset)
        cls.res2 = modp.fit(cov_type=cov_type, method='newton', maxiter=1000,
                            disp=0)

        mod = GLMPenalized(y, x, family=family.Binomial(), offset=offset,
                           penal=cls.penalty)
        mod.pen_weight *= 1  # lower than in other cases
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', max_start_irls=0,
                           maxiter=100, disp=0, trim=0.001)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 1e-3
        cls.k_params = cls.k_nonzero


# the following classes are copies of Poisson with model adjustments
class CheckPenalizedGaussian(CheckPenalizedPoisson):

    @classmethod
    def _generate_endog(self, linpred):
        sig_e = np.sqrt(0.1)
        np.random.seed(999)
        y = linpred + sig_e * np.random.rand(len(linpred))
        return y


class TestPenalizedGLMGaussianOracleHC(CheckPenalizedGaussian):

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        # adding 10 to avoid strict rtol at predicted values close to zero
        y = y + 10
        cov_type = 'HC0'
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Gaussian())
        cls.res2 = modp.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                            disp=0)

        mod = GLMPenalized(y, x, family=family.Gaussian(), penal=cls.penalty)
        mod.pen_weight *= 1.5  # same as discrete Poisson
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0)

        cls.exog_index = slice(None, cls.k_nonzero, None)

        cls.atol = 5e-6
        cls.rtol = 1e-6


class TestPenalizedGLMGaussianOracleHC2(CheckPenalizedGaussian):
    # with trimming

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        # adding 10 to avoid strict rtol at predicted values close to zero
        y = y + 10
        cov_type = 'HC0'
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Gaussian())
        cls.res2 = modp.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                            disp=0)

        mod = GLMPenalized(y, x, family=family.Gaussian(), penal=cls.penalty)
        mod.pen_weight *= 1.5  # same as discrete Poisson
        mod.penal.tau = 0.05
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0, trim=True)

        cls.exog_index = slice(None, cls.k_nonzero, None)
        cls.k_params = cls.k_nonzero
        cls.atol = 1e-5
        cls.rtol = 1e-5


class TestPenalizedGLMGaussianL2(CheckPenalizedGaussian):
    # L2 penalty on redundant exog

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        # adding 10 to avoid strict rtol at predicted values close to zero
        y = y + 10
        cov_type = 'HC0'
        modp = GLM(y, x[:, :cls.k_nonzero], family=family.Gaussian())
        cls.res2 = modp.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                            disp=0)

        weights = (np.arange(x.shape[1]) >= 4).astype(float)
        mod = GLMPenalized(y, x, family=family.Gaussian(),
                           penal=smpen.L2ConstraintsPenalty(weights=weights))
        # make pen_weight large to force redundant to close to zero
        mod.pen_weight *= 500
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0, trim=False)

        cls.exog_index = slice(None, cls.k_nonzero, None)
        cls.k_params = x.shape[1]
        cls.atol = 1e-5
        cls.rtol = 1e-5


class TestPenalizedGLMGaussianL2Theil(CheckPenalizedGaussian):
    # L2 penalty on redundant exog

    @classmethod
    def _initialize(cls):
        y, x = cls.y, cls.x
        # adding 10 to avoid strict rtol at predicted values close to zero
        y = y + 10
        k = x.shape[1]
        cov_type = 'HC0'
        restriction = np.eye(k)[2:]
        modp = TheilGLS(y, x, r_matrix=restriction)
        # the corresponding Theil penweight seems to be 2 * nobs / sigma2_e
        cls.res2 = modp.fit(pen_weight=120.74564413221599 * 1000, use_t=False)

        pen = smpen.L2ConstraintsPenalty(restriction=restriction)
        mod = GLMPenalized(y, x, family=family.Gaussian(),
                           penal=pen)
        # use default weight for GLMPenalized
        mod.pen_weight *= 1
        cls.res1 = mod.fit(cov_type=cov_type, method='bfgs', maxiter=100,
                           disp=0, trim=False)

        cls.k_nonzero = k
        cls.exog_index = slice(None, cls.k_nonzero, None)
        cls.k_params = x.shape[1]
        cls.atol = 1e-5
        cls.rtol = 1e-5

    def test_params_table(self):
        # override inherited because match is not good except for params and predict
        # The cov_type in GLMPenalized and in TheilGLS are not the same
        # both use sandwiches but TheilGLS sandwich is not HC
        # relative difference in bse up to 7%
        res1 = self.res1
        res2 = self.res2
        assert_equal((res1.params != 0).sum(), self.k_params)
        assert_allclose(res1.params, res2.params, rtol=self.rtol,
                        atol=self.atol)

        exog_index = slice(None, None, None)
        assert_allclose(res1.bse[exog_index], res2.bse[exog_index],
                        rtol=0.1, atol=self.atol)
        assert_allclose(res1.tvalues[exog_index], res2.tvalues[exog_index],
                        rtol=0.08, atol=5e-3)
        assert_allclose(res1.pvalues[exog_index], res2.pvalues[exog_index],
                        rtol=0.1, atol=5e-3)
        assert_allclose(res1.predict(), res2.predict(), rtol=1e-5)
