builds the GAM block-diagonal penalty matrix in quadratic form
        out of penalty matrices specified for each feature.

        each feature penalty matrix is multiplied by a lambda for that feature.

        so for m features:
        P = block_diag[lam0 * P0, lam1 * P1, lam2 * P2, ... , lamm * Pm]


        Parameters
        ---------
        None

        Returns
        -------
        P : sparse CSC matrix containing the model penalties in quadratic form

def build_penalties(self, verbose=False):
        """
        builds the GAM block-diagonal penalty matrix in quadratic form
        out of penalty matrices specified for each feature.

        each feature penalty matrix is multiplied by a lambda for that feature.

        so for m features:
        P = block_diag[lam0 * P0, lam1 * P1, lam2 * P2, ... , lamm * Pm]


        Parameters
        ---------
        None

        Returns
        -------
        P : sparse CSC matrix containing the model penalties in quadratic form
        """
        if self.isintercept:
            return np.array([[0.]])

        Ps = []
        for penalty, lam in zip(self.penalties, self.lam):
            if penalty == 'auto':
                if self.dtype == 'numerical':
                    if self._name == 'spline_term':
                        if self.basis in ['cp']:
                            penalty = 'periodic'
                        else:
                            penalty = 'derivative'
                    else:
                        penalty = 'l2'
                if self.dtype == 'categorical':
                    penalty = 'l2'
            if penalty is None:
                penalty = 'none'
            if penalty in PENALTIES:
                penalty = PENALTIES[penalty]

            P = penalty(self.n_coefs, coef=None) # penalties dont need coef
            Ps.append(np.multiply(P, lam))
        return np.sum(Ps)