""" Linear Algebra solvers and other helpers """ import numpy as np __all__ = ["logdet_symm", "stationary_solve", "transf_constraints", "matrix_sqrt"] def logdet_symm(m, check_symm=False): """ Return log(det(m)) asserting positive definiteness of m. Parameters ---------- m : array_like 2d array that is positive-definite (and symmetric) Returns ------- logdet : float The log-determinant of m. """ from scipy import linalg if check_symm: if not np.all(m == m.T): # would be nice to short-circuit check raise ValueError("m is not symmetric.") c, _ = linalg.cho_factor(m, lower=True) return 2*np.sum(np.log(c.diagonal())) def stationary_solve(r, b): """ Solve a linear system for a Toeplitz correlation matrix. A Toeplitz correlation matrix represents the covariance of a stationary series with unit variance. Parameters ---------- r : array_like A vector describing the coefficient matrix. r[0] is the first band next to the diagonal, r[1] is the second band, etc. b : array_like The right-hand side for which we are solving, i.e. we solve Tx = b and return b, where T is the Toeplitz coefficient matrix. Returns ------- The solution to the linear system. """ db = r[0:1] dim = b.ndim if b.ndim == 1: b = b[:, None] x = b[0:1, :] for j in range(1, len(b)): rf = r[0:j][::-1] a = (b[j, :] - np.dot(rf, x)) / (1 - np.dot(rf, db[::-1])) z = x - np.outer(db[::-1], a) x = np.concatenate((z, a[None, :]), axis=0) if j == len(b) - 1: break rn = r[j] a = (rn - np.dot(rf, db)) / (1 - np.dot(rf, db[::-1])) z = db - a*db[::-1] db = np.concatenate((z, np.r_[a])) if dim == 1: x = x[:, 0] return x def transf_constraints(constraints): """use QR to get transformation matrix to impose constraint Parameters ---------- constraints : ndarray, 2-D restriction matrix with one constraints in rows Returns ------- transf : ndarray transformation matrix to reparameterize so that constraint is imposed Notes ----- This is currently and internal helper function for GAM. API not stable and will most likely change. The code for this function was taken from patsy spline handling, and corresponds to the reparameterization used by Wood in R's mgcv package. See Also -------- statsmodels.base._constraints.TransformRestriction : class to impose constraints by reparameterization used by `_fit_constrained`. """ from scipy import linalg m = constraints.shape[0] q, _ = linalg.qr(np.transpose(constraints)) transf = q[:, m:] return transf def matrix_sqrt(mat, inverse=False, full=False, nullspace=False, threshold=1e-15): """matrix square root for symmetric matrices Usage is for decomposing a covariance function S into a square root R such that R' R = S if inverse is False, or R' R = pinv(S) if inverse is True Parameters ---------- mat : array_like, 2-d square symmetric square matrix for which square root or inverse square root is computed. There is no checking for whether the matrix is symmetric. A warning is issued if some singular values are negative, i.e. below the negative of the threshold. inverse : bool If False (default), then the matrix square root is returned. If inverse is True, then the matrix square root of the inverse matrix is returned. full : bool If full is False (default, then the square root has reduce number of rows if the matrix is singular, i.e. has singular values below the threshold. nullspace : bool If nullspace is true, then the matrix square root of the null space of the matrix is returned. threshold : float Singular values below the threshold are dropped. Returns ------- msqrt : ndarray matrix square root or square root of inverse matrix. """ # see also scipy.linalg null_space u, s, v = np.linalg.svd(mat) if np.any(s < -threshold): import warnings warnings.warn('some singular values are negative') if not nullspace: mask = s > threshold s[s < threshold] = 0 else: mask = s < threshold s[s > threshold] = 0 sqrt_s = np.sqrt(s[mask]) if inverse: sqrt_s = 1 / np.sqrt(s[mask]) if full: b = np.dot(u[:, mask], np.dot(np.diag(sqrt_s), v[mask])) else: b = np.dot(np.diag(sqrt_s), v[mask]) return b