def _repeated_svd(x, lwork, overwrite_a=False): """Mimic scipy.linalg.svd, avoid lwork and get_lapack_funcs overhead.""" if x.dtype == np.float64: gesdd, gesvd = dgesdd, zgesdd else: assert x.dtype == np.complex128 gesdd, gesvd = zgesdd, zgesvd # this has to use overwrite_a=False in case we need to fall back to gesvd u, s, v, info = gesdd(x, compute_uv=True, lwork=lwork[0], full_matrices=False, overwrite_a=False) if info > 0: # Fall back to slower gesvd, sometimes gesdd fails u, s, v, info = gesvd(x, compute_uv=True, lwork=lwork[1], full_matrices=False, overwrite_a=overwrite_a) if info > 0: raise LinAlgError("SVD did not converge") if info < 0: raise ValueError('illegal value in %d-th argument of internal gesdd' % -info) return u, s, v
def solve(a, b, overwrite_a=False, overwrite_b=False): """ Solve a linear system ``a x = b`` Parameters ---------- a : (N, N) array_like left-hand-side matrix b : (N, NRHS) array_like right-hand-side matrix overwrite_a : bool, optional whether we are allowed to overwrite the matrix `a` overwrite_b : bool, optional whether we are allowed to overwrite the matrix `b` Returns ------- x : (N, NRHS) numpy.ndarray solution matrix """ a1 = atleast_2d(_asarray_validated(a, check_finite=False)) b1 = atleast_1d(_asarray_validated(b, check_finite=False)) n = a1.shape[0] overwrite_a = overwrite_a or _datacopied(a1, a) overwrite_b = overwrite_b or _datacopied(b1, b) if a1.shape[0] != a1.shape[1]: raise ValueError('LHS needs to be a square matrix.') if n != b1.shape[0]: # Last chance to catch 1x1 scalar a and 1D b arrays if not (n == 1 and b1.size != 0): raise ValueError('Input b has to have same number of rows as ' 'input a') # regularize 1D b arrays to 2D if b1.ndim == 1: if n == 1: b1 = b1[None, :] else: b1 = b1[:, None] b_is_1D = True else: b_is_1D = False gesv = get_lapack_funcs('gesv', (a1, b1)) _, _, x, info = gesv(a1, b1, overwrite_a=overwrite_a, overwrite_b=overwrite_b) if info > 0: raise LinAlgError("Singular matrix") elif info < 0: raise ValueError('illegal value in %d-th argument of internal ' 'gesv' % -info) if b_is_1D: return x.ravel() return x
def _check_info(info, driver, positive='did not converge (LAPACK info=%d)'): """Check info return value.""" if info < 0: raise ValueError('illegal value in argument %d of internal %s' % (-info, driver)) if info > 0 and positive: raise LinAlgError(("%s " + positive) % (driver, info,))
def logposterior(self, params): r""" Compute the negative log posterior for a particular set of parameters Computes the negative log posterior (negative marginal log-likelihood minus any prior log-probabilities). This is computed on the root process and then broadcast to all processes. The main computational expense is computing the prior mean and covariance, which only needs to be done once and can be cached. This also requires computing the Cholesky decomposition of the covariance plus model discrepancy. New parameters must be a numpy array of length 3. First parameter is the data/model scaling factor :math:`{\rho}`, second parameter is the model discrepancy covariance, and the third parameter is the model discrepancy correlation length. All parameters are assumed to be on a logarithmic scale to enforce positivity. :param params: New set of parameters (must be a numpy array of length 3) :type params: ndarray :returns: negative log posterior :rtype: float """ self.set_params(params) rho = np.exp(self.params[0]) if self.Cu is None or self.mu is None: self.solve_prior() # compute log-likelihood on root process and broadcast if COMM_WORLD.rank == 0: KCu = rho**2 * self.Cu + self.data.calc_K_plus_sigma( self.params[1:]) try: L = cho_factor(KCu) except LinAlgError: raise LinAlgError( "Error attempting to factorize the covariance matrix " + "in model_loglikelihood") invKCudata = cho_solve(L, self.data.get_data() - rho * self.mu) log_posterior = 0.5 * ( self.data.get_n_obs() * np.log(2. * np.pi) + 2. * np.sum(np.log(np.diag(L[0]))) + np.dot(self.data.get_data() - rho * self.mu, invKCudata)) for i in range(3): if not self.priors[i] is None: log_posterior -= self.priors[i].logp(self.params[i]) else: log_posterior = None log_posterior = COMM_WORLD.bcast(log_posterior, root=0) assert not log_posterior is None, "error in broadcasting the log likelihood" COMM_WORLD.barrier() return log_posterior
def solve_triangular(A, B, trans=False): """ Solve the system Ax=B where A is lower triangular. If `trans` is True then solve the system A'x=B. """ X, info = lapack.dtrtrs(A, B, lower=1, trans=int(trans)) if info != 0: raise LinAlgError('Matrix is singular') return X
def cholesky_inverse(A): """ Compute the inverse of the matrix AA' given its cholesky decomposition A. """ X, info = lapack.dpotri(A, lower=1) if info != 0: raise LinAlgError('Matrix is not invertible') triu = np.triu_indices_from(X, k=1) X[triu] = X.T[triu] return X
def cholesky(A, maxtries=5): """ Compute the cholesky of A. If the matrix is singular make `maxtries` additional attempts to invert it by adding small amounts to the diagonal before giving up. """ L, info = lapack.dpotrf(A, lower=1) if info == 0: return L else: d = A.diagonal() if np.any(d <= 0): raise LinAlgError('Matrix has non-positive diagonal elements') j = d.mean() * 1e-6 for _ in xrange(maxtries): L, info = lapack.dpotrf(add_diagonal(A, j, True), lower=1) if info == 0: message = 'jitter of {:s} required to compute the cholesky' warnings.warn(message.format(str(j)), stacklevel=2) return L else: j *= 10 raise LinAlgError('Matrix is not positive definite, even with jitter')
def f(self, *args, **kwargs): refactor_now = False out = None if method.__name__ == "update": # this will get zeroed if we refactor self.updates += 1 # if average solve time is increasing, then it would # be faster to refactor from scratch slowing_down = (self.average_solve_times[1] > self.average_solve_times[0]) # if update limit is reached, we should refactor to # limit error buildup too_many_updates = self.updates >= self.max_updates if self.mast: refactor_now = (slowing_down or too_many_updates) else: refactor_now = too_many_updates if refactor_now: # update basis indices and factor from scratch self.update_basis(*args, **kwargs) out = self.refactor() # time will be recorded # If refactor_now is True, then self.refactor() is called # We don't want to call method = self.update again here if not refactor_now: # record the time it took to call the method t0 = timer() out = method(self, *args, **kwargs) if isinstance(out, np.ndarray) and np.any(np.isnan(out)): raise LinAlgError("Nans in output") t1 = timer() self.bglu_time += (t1 - t0) # calculate average solve time, # considering all significant method calls if method.__name__ == "solve": self.solves += 1 avg = self.bglu_time / self.solves self.average_solve_times = [self.average_solve_times[1], avg] return out
def solve_posdef(A, b): """ Solve the system :math:`A X = b` for :math:`X` where :math:`A` is a positive semi-definite matrix. This first tries cholesky, and if numerically unstable with solve using a truncated SVD (see solve_posdef_svd). The log-determinant of :math:`A` is also returned since it requires minimal overhead. Parameters ---------- A: ndarray A positive semi-definite matrix. b: ndarray An array or matrix Returns ------- X: ndarray The result of :math:`X = A^-1 b` logdet: float The log-determinant of :math:`A` """ # Try cholesky for speed try: lower = False L = cholesky(A, lower=lower) if any(L.diagonal() < cholthresh): raise LinAlgError("Unstable cholesky factor detected") X = cho_solve((L, lower), b) logdet = cho_log_det(L) # Failed cholesky, use svd to do the inverse except LinAlgError: U, s, V = svd(A) X = svd_solve(U, s, V, b) logdet = svd_log_det(s) return X, logdet
def eigh(a, overwrite_a=False, check_finite=True): """Efficient wrapper for eigh. Parameters ---------- a : ndarray, shape (n_components, n_components) The symmetric array operate on. overwrite_a : bool If True, the contents of a can be overwritten for efficiency. check_finite : bool If True, check that all elements are finite. Returns ------- w : ndarray, shape (n_components,) The N eigenvalues, in ascending order, each repeated according to its multiplicity. v : ndarray, shape (n_components, n_components) The normalized eigenvector corresponding to the eigenvalue ``w[i]`` is the column ``v[:, i]``. """ # We use SYEVD, see https://github.com/scipy/scipy/issues/9212 if check_finite: a = _asarray_validated(a, check_finite=check_finite) if a.dtype == np.float64: evr, driver = dsyevd, 'syevd' else: assert a.dtype == np.complex128 evr, driver = zheevd, 'heevd' w, v, info = evr(a, lower=1, overwrite_a=overwrite_a) if info == 0: return w, v if info < 0: raise ValueError('illegal value in argument %d of internal %s' % (-info, driver)) else: raise LinAlgError("internal fortran routine failed to converge: " "%i off-diagonal elements of an " "intermediate tridiagonal form did not converge" " to zero." % info)
def _repeated_inv(a, lwork, overwrite_a=False): if a.dtype == np.float64: getrf, getri = dgetrf, dgetri else: assert a.dtype == np.complex128 getrf, getri = zgetrf, zgetri lu, piv, info = getrf(a, overwrite_a=overwrite_a) if info == 0: # XXX: the following line fixes curious SEGFAULT when # benchmarking 500x500 matrix inverse. This seems to # be a bug in LAPACK ?getri routine because if lwork is # minimal (when using lwork[0] instead of lwork[1]) then # all tests pass. Further investigation is required if # more such SEGFAULTs occur. lwork = int(1.01 * lwork) inv_a, info = getri(lu, piv, lwork=lwork, overwrite_lu=1) if info > 0: raise LinAlgError("singular matrix") if info < 0: raise ValueError('illegal value in %d-th argument of internal ' 'getrf|getri' % -info) return inv_a
def solve_posterior_generating(self): r""" Solve for the posterior of the generating process This method solves for the posterior of the generating process before looking at the data. The main computational cost is solving for the prior of the covariance, so if this is cached from a previous solve this is a simple calculation. :returns: FEM posterior mean and covariance of the true generating process (as a tuple of numpy arrays) on the root process. Non-root processes return numpy arrays of shape ``(0,)`` (mean) and ``(0, 0)`` (covariance). :rtype: tuple of ndarrays """ # create interpolation matrix if not cached m_eta, C_eta = self.solve_prior_generating() if self.ensemble_comm.rank == 0 and self.G.comm.rank == 0: try: L = cho_factor(self.data.get_unc()**2 * np.eye(self.data.get_n_obs()) + C_eta) except LinAlgError: raise LinAlgError( "Cholesky factorization of the covariance matrix failed") C_etay = cho_solve(L, self.data.get_unc()**2 * C_eta) m_etay = cho_solve( L, np.dot(C_eta, self.data.get_data()) + self.data.get_unc()**2 * m_eta) else: m_etay = np.zeros(0) C_etay = np.zeros((0, 0)) return m_etay, C_etay
def inv(a, overwrite_a=False): """ Inverts a matrix Parameters ---------- a : (N, N) array_like the matrix to be inverted. overwrite_a : bool, optional whether we are allowed to overwrite the matrix `a` Returns ------- x : (N, N) numpy.ndarray The inverted matrix """ a1 = atleast_2d(_asarray_validated(a, check_finite=False)) overwrite_a = overwrite_a or _datacopied(a1, a) if a1.shape[0] != a1.shape[1]: raise ValueError('Input a needs to be a square matrix.') getrf, getri, getri_lwork = get_lapack_funcs( ('getrf', 'getri', 'getri_lwork'), (a1, )) lu, piv, info = getrf(a1, overwrite_a=overwrite_a) if info == 0: lwork = _compute_lwork(getri_lwork, a1.shape[0]) lwork = int(1.01 * lwork) x, info = getri(lu, piv, lwork=lwork, overwrite_lu=True) if info > 0: raise LinAlgError("Singular matrix") elif info < 0: raise ValueError('illegal value in %d-th argument of internal ' 'getrf|getri' % -info) return x
def LSfit(funct, data, X, param0, weights=-1, quiet=True, **kwargs): """ ##### USAGE ##### Least-square minimization algorithm. Parametric least-square fitting. Minimises Xhi2 = sum(weights*(f(X,param) - data)^2) where the sum(.) runs over the coordinates X minimization is performed over the unknown "param" "data" is the noisy observation "f(X,param)" is your model, set by the vector of parameters "param" and evaluated on coordinates X Algorithm is Levenberg-Marquardt, with adaptive damping factor ##### SYNTAX ##### Two syntaxes are possible param = LSfit(funct,data,X,param0) param,status = LSfit(funct,data,X,param0) "param" is the returned solution "status" contains extra information about convergence status = {"xhi2","mu","param"} ##### INPUTS ##### funct - [Function] The function to call data - [Vector] Noisy data X - [Vector] Coordinates where to evaluate the function param0 - [Vector,LSparam] First estimation of parameters param0 can be simply a vector if param0 is a LSparam, you can define bounds, fixed values... see LSparam documentation ##### OPTIONAL INPUTS ##### weights - [Vector] Weights (1/noise^2) on each coordinate X Default : no weighting (weights=1) weights = 1/sigma^2 for a gaussian noise weights = 1/data for a Poisson-like noise quiet - [Boolean] Don't print status of algorithm Default : quiet = True **kwargs - [Dictionary] Use a dictionary to pass some keywords to your function ##### OUTPUT ##### param - [Vector] Best parameters for least-square fitting status - [dictionary] Optional output, contains some information about convergence """ # INITIALIZATION Xarr = array(X) sX = size(Xarr) param = LSparam(param0) param.initParam() mu_cond = 0 Id = eye(param.nb_valid_param) # Identity matrix for conditioning J = zeros((sX, param.nb_param)) # Jacobian h = 1e-9 #step to compute Jacobian bad_cond = 1e10 #conditioning higher than this value is bad status = {"xhi2": [], "mu": [], "param": []} nout = _n_out(offset=3) if nout > 2: raise ValueError( "Number of outpt argument for LSfit can be only 1 or 2") # STOPPING CONDITIONS J_min = 1e-5 * param.nb_param**2 # stopping criteria based on small Jacobian dp_min = 1e-7 #(1e-8) stopping criteria based on small variation of parameters max_iter = 1e4 # stopping criteria based on max number of iteration stop_loop = False # boolean for stopping the loop stop_trace = "LSfit started but not finished correctly" # WEIGHTS if size(weights) == 1 and weights <= 0: weights = zeros(sX) + 1 elif size(weights) > 1 and size(weights) != sX: raise ValueError("WEIGHTS should be a scalar or same size as X") # LEVENBERG-MARQUARDT mu = 1 # Print some information f = funct(Xarr, param.value, **kwargs) Xhi2 = sum(weights * (f - data)**2) status["xhi2"].append(Xhi2) if not quiet: print("[Iter=0] Xhi2 = " + str(Xhi2)) # LOOP iteration = 0 f = funct(Xarr, param.value, **kwargs) while (iteration < max_iter) and not stop_loop: mu_cond = 0 ## Iterate over each parameter to compute derivative for ip in param.validValue: J[:, ip] = weights * (funct(Xarr, param.step(ip, h), **kwargs) - f) / h ## Compute dvalue = -{(transpose(J)*J)^(-1)}*transpose(J)*(weights*(func-data)) ## Reduce the matrix J only on the moving parameters JTJ = dot(J[:, param.validValue].T, J[:, param.validValue]) JTJ += mu * diag(JTJ.diagonal()) ## Try to improve conditioning c = cond(JTJ) if c > bad_cond: mu_cond = _improve_cond(JTJ, bad_cond, Id) ## Try inversion, here we might encounter numerical issues try: param.dvalue[param.validValue] = -dot( dot(inv(JTJ + mu_cond * Id), J[:, param.validValue].T), weights * (f - data)) except LinAlgError as exception_message: _print_info_on_error(JTJ, iteration, mu, param.value) raise LinAlgError(exception_message) except ValueError as exception_message: _print_info_on_error(JTJ, iteration, mu, param.value) raise ValueError(exception_message) ## Xhi square old Xhi2 = sum(weights * (f - data)**2) if nout == 2: status["xhi2"].append(Xhi2) status["mu"].append(mu) status["param"].append(param.value) # Step forward with dvalue param.forward() # New value of f(.) f = funct(Xarr, param.value, **kwargs) ## Levenberg-Marquardt update for mu Xhi2_new = sum(weights * (f - data)**2) if Xhi2_new > Xhi2: mu = min(10 * mu, 1e10) else: mu = max(0.1 * mu, 1e-10) ## Stop loop based on small variation of parameter if param.dvalueNorm() < dp_min * param.valueNorm(): stop_loop = True stop_trace = "Parameter not evolving enough at iter=" + str( iteration) ## Stop loop based on small Jacobian if abs(J[:, param.validValue]).sum() < J_min: stop_loop = True stop_trace = "Jacobian small enough at iter=" + str(iteration) ## Increment Loop iteration += 1 ## END of LOOP and SHOW RESULTS if iteration >= max_iter: stop_trace = "Maximum iteration reached (iter=" + str(iteration) + ")" if not quiet: print("[Iter=" + str(iteration) + "] Xhi2 = " + str(Xhi2)) print(" ") print("Stopping condition: " + stop_trace) print(" ") if nout == 2: status["param"] = array(status["param"]) return param.value, status return param.value
def make_interp_spline(x, y, k=3, t=None, bc_type=None, axis=0, check_finite=True): """Compute the (coefficients of) interpolating B-spline. Parameters ---------- x : array_like, shape (n,) Abscissas. y : array_like, shape (n, ...) Ordinates. k : int, optional B-spline degree. Default is cubic, k=3. t : array_like, shape (nt + k + 1,), optional. Knots. The number of knots needs to agree with the number of datapoints and the number of derivatives at the edges. Specifically, ``nt - n`` must equal ``len(deriv_l) + len(deriv_r)``. bc_type : 2-tuple or None Boundary conditions. Default is None, which means choosing the boundary conditions automatically. Otherwise, it must be a length-two tuple where the first element sets the boundary conditions at ``x[0]`` and the second element sets the boundary conditions at ``x[-1]``. Each of these must be an iterable of pairs ``(order, value)`` which gives the values of derivatives of specified orders at the given edge of the interpolation interval. Alternatively, the following string aliases are recognized: * ``"clamped"``: The first derivatives at the ends are zero. This is equivalent to ``bc_type=([(1, 0.0)], [(1, 0.0)])``. * ``"natural"``: The second derivatives at ends are zero. This is equivalent to ``bc_type=([(2, 0.0)], [(2, 0.0)])``. * ``"not-a-knot"`` (default): The first and second segments are the same polynomial. This is equivalent to having ``bc_type=None``. axis : int, optional Interpolation axis. Default is 0. check_finite : bool, optional Whether to check that the input arrays contain only finite numbers. Disabling may give a performance gain, but may result in problems (crashes, non-termination) if the inputs do contain infinities or NaNs. Default is True. Returns ------- b : a BSpline object of the degree ``k`` and with knots ``t``. Examples -------- Use cubic interpolation on Chebyshev nodes: >>> def cheb_nodes(N): ... jj = 2.*np.arange(N) + 1 ... x = np.cos(np.pi * jj / 2 / N)[::-1] ... return x >>> x = cheb_nodes(20) >>> y = np.sqrt(1 - x**2) >>> from scipy.interpolate import BSpline, make_interp_spline >>> b = make_interp_spline(x, y) >>> np.allclose(b(x), y) True Note that the default is a cubic spline with a not-a-knot boundary condition >>> b.k 3 Here we use a 'natural' spline, with zero 2nd derivatives at edges: >>> l, r = [(2, 0.0)], [(2, 0.0)] >>> b_n = make_interp_spline(x, y, bc_type=(l, r)) # or, bc_type="natural" >>> np.allclose(b_n(x), y) True >>> x0, x1 = x[0], x[-1] >>> np.allclose([b_n(x0, 2), b_n(x1, 2)], [0, 0]) True Interpolation of parametric curves is also supported. As an example, we compute a discretization of a snail curve in polar coordinates >>> phi = np.linspace(0, 2.*np.pi, 40) >>> r = 0.3 + np.cos(phi) >>> x, y = r*np.cos(phi), r*np.sin(phi) # convert to Cartesian coordinates Build an interpolating curve, parameterizing it by the angle >>> from scipy.interpolate import make_interp_spline >>> spl = make_interp_spline(phi, np.c_[x, y]) Evaluate the interpolant on a finer grid (note that we transpose the result to unpack it into a pair of x- and y-arrays) >>> phi_new = np.linspace(0, 2.*np.pi, 100) >>> x_new, y_new = spl(phi_new).T Plot the result >>> import matplotlib.pyplot as plt >>> plt.plot(x, y, 'o') >>> plt.plot(x_new, y_new, '-') >>> plt.show() See Also -------- BSpline : base class representing the B-spline objects CubicSpline : a cubic spline in the polynomial basis make_lsq_spline : a similar factory function for spline fitting UnivariateSpline : a wrapper over FITPACK spline fitting routines splrep : a wrapper over FITPACK spline fitting routines """ # convert string aliases for the boundary conditions if bc_type is None or bc_type == 'not-a-knot': deriv_l, deriv_r = None, None elif isinstance(bc_type, str): deriv_l, deriv_r = bc_type, bc_type else: try: deriv_l, deriv_r = bc_type except TypeError: raise ValueError("Unknown boundary condition: %s" % bc_type) y = np.asarray(y) axis = normalize_axis_index(axis, y.ndim) # special-case k=0 right away if k == 0: if any(_ is not None for _ in (t, deriv_l, deriv_r)): raise ValueError("Too much info for k=0: t and bc_type can only " "be None.") x = _as_float_array(x, check_finite) t = np.r_[x, x[-1]] c = np.asarray(y) c = np.rollaxis(c, axis) c = np.ascontiguousarray(c, dtype=_get_dtype(c.dtype)) return BSpline.construct_fast(t, c, k, axis=axis) # special-case k=1 (e.g., Lyche and Morken, Eq.(2.16)) if k == 1 and t is None: if not (deriv_l is None and deriv_r is None): raise ValueError( "Too much info for k=1: bc_type can only be None.") x = _as_float_array(x, check_finite) t = np.r_[x[0], x, x[-1]] c = np.asarray(y) c = np.rollaxis(c, axis) c = np.ascontiguousarray(c, dtype=_get_dtype(c.dtype)) return BSpline.construct_fast(t, c, k, axis=axis) x = _as_float_array(x, check_finite) y = _as_float_array(y, check_finite) k = operator.index(k) # come up with a sensible knot vector, if needed if t is None: if deriv_l is None and deriv_r is None: if k == 2: # OK, it's a bit ad hoc: Greville sites + omit # 2nd and 2nd-to-last points, a la not-a-knot t = (x[1:] + x[:-1]) / 2. t = np.r_[(x[0], ) * (k + 1), t[1:-1], (x[-1], ) * (k + 1)] else: t = _not_a_knot(x, k) else: t = _augknt(x, k) t = _as_float_array(t, check_finite) y = np.rollaxis(y, axis) # now internally interp axis is zero if x.ndim != 1 or np.any(x[1:] <= x[:-1]): raise ValueError("Expect x to be a 1-D sorted array_like.") if k < 0: raise ValueError("Expect non-negative k.") if t.ndim != 1 or np.any(t[1:] < t[:-1]): raise ValueError("Expect t to be a 1-D sorted array_like.") if x.size != y.shape[0]: raise ValueError('x and y are incompatible.') if t.size < x.size + k + 1: raise ValueError('Got %d knots, need at least %d.' % (t.size, x.size + k + 1)) if (x[0] < t[k]) or (x[-1] > t[-k]): raise ValueError('Out of bounds w/ x = %s.' % x) # Here : deriv_l, r = [(nu, value), ...] deriv_l = _convert_string_aliases(deriv_l, y.shape[1:]) deriv_l_ords, deriv_l_vals = _process_deriv_spec(deriv_l) nleft = deriv_l_ords.shape[0] deriv_r = _convert_string_aliases(deriv_r, y.shape[1:]) deriv_r_ords, deriv_r_vals = _process_deriv_spec(deriv_r) nright = deriv_r_ords.shape[0] # have `n` conditions for `nt` coefficients; need nt-n derivatives n = x.size nt = t.size - k - 1 if nt - n != nleft + nright: raise ValueError("The number of derivatives at boundaries does not " "match: expected %s, got %s+%s" % (nt - n, nleft, nright)) # set up the LHS: the collocation matrix + derivatives at boundaries kl = ku = k ab = np.zeros((2 * kl + ku + 1, nt), dtype=np.float_, order='F') _bspl._colloc(x, t, k, ab, offset=nleft) if nleft > 0: _bspl._handle_lhs_derivatives(t, k, x[0], ab, kl, ku, deriv_l_ords) if nright > 0: _bspl._handle_lhs_derivatives(t, k, x[-1], ab, kl, ku, deriv_r_ords, offset=nt - nright) # set up the RHS: values to interpolate (+ derivative values, if any) extradim = prod(y.shape[1:]) rhs = np.empty((nt, extradim), dtype=y.dtype) if nleft > 0: rhs[:nleft] = deriv_l_vals.reshape(-1, extradim) rhs[nleft:nt - nright] = y.reshape(-1, extradim) if nright > 0: rhs[nt - nright:] = deriv_r_vals.reshape(-1, extradim) # solve Ab @ x = rhs; this is the relevant part of linalg.solve_banded if check_finite: ab, rhs = map(np.asarray_chkfinite, (ab, rhs)) gbsv, = get_lapack_funcs(('gbsv', ), (ab, rhs)) lu, piv, c, info = gbsv(kl, ku, ab, rhs, overwrite_ab=True, overwrite_b=True) if info > 0: raise LinAlgError("Collocation matix is singular.") elif info < 0: raise ValueError('illegal value in %d-th argument of internal gbsv' % -info) c = np.ascontiguousarray(c.reshape((nt, ) + y.shape[1:])) return BSpline.construct_fast(t, c, k, axis=axis)
def inference_nonroot(self, kern, X, Z, likelihood, Y, Y_metadata=None, Lm=None, dL_dKmm=None): num_data, output_dim = Y.shape num_data_total = allReduceArrays([np.int32(num_data)], self.mpi_comm)[0] input_dim = Z.shape[0] uncertain_inputs = isinstance(X, VariationalPosterior) uncertain_outputs = isinstance(Y, VariationalPosterior) beta = 1. / np.fmax(likelihood.variance, 1e-6) psi0, psi2, YRY, psi1, psi1Y, Shalf, psi1S = self.gatherPsiStat( kern, X, Z, Y, beta, uncertain_inputs) flag = np.zeros((1, ), dtype=np.int32) self.mpi_comm.Bcast(flag, root=self.root) if flag[0] == 1: raise LinAlgError('Linalg error!') LmInv, LLInv = np.empty((input_dim, input_dim)).T, np.empty( (input_dim, input_dim)).T broadcastArrays([LmInv, LLInv], self.mpi_comm, self.root) LmLLInv = LLInv.dot(LmInv) b = psi1Y.dot(LmLLInv.T) v = b.dot(LmLLInv) if psi1S is not None: psi1SLLinv = psi1S.dot(LmLLInv.T) bbt_sum = np.square(psi1SLLinv).sum() LLinvPsi1TYYTPsi1LLinvT_sum = tdot(psi1SLLinv.T) reduceArrays([bbt_sum, LLinvPsi1TYYTPsi1LLinvT_sum], self.mpi_comm, self.root) psi1SP = psi1SLLinv.dot(LmLLInv) dL_dpsi2R = np.empty((input_dim, input_dim)) broadcastArrays([dL_dpsi2R], self.mpi_comm, self.root) dL_dpsi0 = -output_dim * (beta * np.ones((num_data, ))) / 2. if uncertain_outputs: m, s = Y.mean, Y.variance dL_dpsi1 = beta * (np.dot(m, v) + Shalf[:, None] * psi1SP) else: dL_dpsi1 = beta * np.dot(Y, v) if uncertain_inputs: dL_dpsi2 = beta * dL_dpsi2R else: dL_dpsi1 += np.dot(psi1, dL_dpsi2R) * 2. dL_dpsi2 = None if uncertain_inputs: grad_dict = { 'dL_dKmm': None, 'dL_dpsi0': dL_dpsi0, 'dL_dpsi1': dL_dpsi1, 'dL_dpsi2': dL_dpsi2, 'dL_dthetaL': None } else: grad_dict = { 'dL_dKmm': None, 'dL_dKdiag': dL_dpsi0, 'dL_dKnm': dL_dpsi1, 'dL_dthetaL': None } if uncertain_outputs: m, s = Y.mean, Y.variance psi1LmiLLi = psi1.dot(LmLLInv.T) LLiLmipsi1Y = b.T grad_dict['dL_dYmean'] = -m * beta + psi1LmiLLi.dot(LLiLmipsi1Y) grad_dict['dL_dYvar'] = beta / -2. + np.square(psi1LmiLLi).sum( axis=1) / 2 return None, 0, grad_dict
def cossin(X, p=None, q=None, separate=False, swap_sign=False, compute_u=True, compute_vh=True): """ Compute the cosine-sine (CS) decomposition of an orthogonal/unitary matrix. X is an ``(m, m)`` orthogonal/unitary matrix, partitioned as the following where upper left block has the shape of ``(p, q)``:: ┌ ┐ │ I 0 0 │ 0 0 0 │ ┌ ┐ ┌ ┐│ 0 C 0 │ 0 -S 0 │┌ ┐* │ X11 │ X12 │ │ U1 │ ││ 0 0 0 │ 0 0 -I ││ V1 │ │ │ ────┼──── │ = │────┼────││─────────┼─────────││────┼────│ │ X21 │ X22 │ │ │ U2 ││ 0 0 0 │ I 0 0 ││ │ V2 │ └ ┘ └ ┘│ 0 S 0 │ 0 C 0 │└ ┘ │ 0 0 I │ 0 0 0 │ └ ┘ ``U1``, ``U2``, ``V1``, ``V2`` are square orthogonal/unitary matrices of dimensions ``(p,p)``, ``(m-p,m-p)``, ``(q,q)``, and ``(m-q,m-q)`` respectively, and ``C`` and ``S`` are ``(r, r)`` nonnegative diagonal matrices satisfying ``C^2 + S^2 = I`` where ``r = min(p, m-p, q, m-q)``. Moreover, the rank of the identity matrices are ``min(p, q) - r``, ``min(p, m - q) - r``, ``min(m - p, q) - r``, and ``min(m - p, m - q) - r`` respectively. X can be supplied either by itself and block specifications p, q or its subblocks in an iterable from which the shapes would be derived. See the examples below. Parameters ---------- X : array_like, iterable complex unitary or real orthogonal matrix to be decomposed, or iterable of subblocks ``X11``, ``X12``, ``X21``, ``X22``, when ``p``, ``q`` are omitted. p : int, optional Number of rows of the upper left block ``X11``, used only when X is given as an array. q : int, optional Number of columns of the upper left block ``X11``, used only when X is given as an array. separate : bool, optional if ``True``, the low level components are returned instead of the matrix factors, i.e. ``(u1,u2)``, ``theta``, ``(v1h,v2h)`` instead of ``u``, ``cs``, ``vh``. swap_sign : bool, optional if ``True``, the ``-S``, ``-I`` block will be the bottom left, otherwise (by default) they will be in the upper right block. compute_u : bool, optional if ``False``, ``u`` won't be computed and an empty array is returned. compute_vh : bool, optional if ``False``, ``vh`` won't be computed and an empty array is returned. Returns ------- u : ndarray When ``compute_u=True``, contains the block diagonal orthogonal/unitary matrix consisting of the blocks ``U1`` (``p`` x ``p``) and ``U2`` (``m-p`` x ``m-p``) orthogonal/unitary matrices. If ``separate=True``, this contains the tuple of ``(U1, U2)``. cs : ndarray The cosine-sine factor with the structure described above. If ``separate=True``, this contains the ``theta`` array containing the angles in radians. vh : ndarray When ``compute_vh=True`, contains the block diagonal orthogonal/unitary matrix consisting of the blocks ``V1H`` (``q`` x ``q``) and ``V2H`` (``m-q`` x ``m-q``) orthogonal/unitary matrices. If ``separate=True``, this contains the tuple of ``(V1H, V2H)``. Examples -------- >>> from scipy.linalg import cossin >>> from scipy.stats import unitary_group >>> x = unitary_group.rvs(4) >>> u, cs, vdh = cossin(x, p=2, q=2) >>> np.allclose(x, u @ cs @ vdh) True Same can be entered via subblocks without the need of ``p`` and ``q``. Also let's skip the computation of ``u`` >>> ue, cs, vdh = cossin((x[:2, :2], x[:2, 2:], x[2:, :2], x[2:, 2:]), ... compute_u=False) >>> print(ue) [] >>> np.allclose(x, u @ cs @ vdh) True References ---------- .. [1] : Brian D. Sutton. Computing the complete CS decomposition. Numer. Algorithms, 50(1):33-65, 2009. """ if p or q: p = 1 if p is None else int(p) q = 1 if q is None else int(q) X = _asarray_validated(X, check_finite=True) if not np.equal(*X.shape): raise ValueError("Cosine Sine decomposition only supports square" " matrices, got {}".format(X.shape)) m = X.shape[0] if p >= m or p <= 0: raise ValueError("invalid p={}, 0<p<{} must hold".format( p, X.shape[0])) if q >= m or q <= 0: raise ValueError("invalid q={}, 0<q<{} must hold".format( q, X.shape[0])) x11, x12, x21, x22 = X[:p, :q], X[:p, q:], X[p:, :q], X[p:, q:] elif not isinstance(X, Iterable): raise ValueError("When p and q are None, X must be an Iterable" " containing the subblocks of X") else: if len(X) != 4: raise ValueError("When p and q are None, exactly four arrays" " should be in X, got {}".format(len(X))) x11, x12, x21, x22 = [np.atleast_2d(x) for x in X] for name, block in zip(["x11", "x12", "x21", "x22"], [x11, x12, x21, x22]): if block.shape[1] == 0: raise ValueError("{} can't be empty".format(name)) p, q = x11.shape mmp, mmq = x22.shape if x12.shape != (p, mmq): raise ValueError("Invalid x12 dimensions: desired {}, " "got {}".format((p, mmq), x12.shape)) if x21.shape != (mmp, q): raise ValueError("Invalid x21 dimensions: desired {}, " "got {}".format((mmp, q), x21.shape)) if p + mmp != q + mmq: raise ValueError("The subblocks have compatible sizes but " "don't form a square array (instead they form a" " {}x{} array). This might be due to missing " "p, q arguments.".format(p + mmp, q + mmq)) m = p + mmp cplx = any([np.iscomplexobj(x) for x in [x11, x12, x21, x22]]) driver = "uncsd" if cplx else "orcsd" csd, csd_lwork = get_lapack_funcs([driver, driver + "_lwork"], [x11, x12, x21, x22]) lwork = _compute_lwork(csd_lwork, m=m, p=p, q=q) lwork_args = ({ 'lwork': lwork[0], 'lrwork': lwork[1] } if cplx else { 'lwork': lwork }) *_, theta, u1, u2, v1h, v2h, info = csd(x11=x11, x12=x12, x21=x21, x22=x22, compute_u1=compute_u, compute_u2=compute_u, compute_v1t=compute_vh, compute_v2t=compute_vh, trans=False, signs=swap_sign, **lwork_args) method_name = csd.typecode + driver if info < 0: raise ValueError('illegal value in argument {} of internal {}'.format( -info, method_name)) if info > 0: raise LinAlgError("{} did not converge: {}".format(method_name, info)) if separate: return (u1, u2), theta, (v1h, v2h) U = block_diag(u1, u2) VDH = block_diag(v1h, v2h) # Construct the middle factor CS c = np.diag(np.cos(theta)) s = np.diag(np.sin(theta)) r = min(p, q, m - p, m - q) n11 = min(p, q) - r n12 = min(p, m - q) - r n21 = min(m - p, q) - r n22 = min(m - p, m - q) - r Id = np.eye(np.max([n11, n12, n21, n22, r]), dtype=theta.dtype) CS = np.zeros((m, m), dtype=theta.dtype) CS[:n11, :n11] = Id[:n11, :n11] xs = n11 + r xe = n11 + r + n12 ys = n11 + n21 + n22 + 2 * r ye = n11 + n21 + n22 + 2 * r + n12 CS[xs:xe, ys:ye] = Id[:n12, :n12] if swap_sign else -Id[:n12, :n12] xs = p + n22 + r xe = p + n22 + r + +n21 ys = n11 + r ye = n11 + r + n21 CS[xs:xe, ys:ye] = -Id[:n21, :n21] if swap_sign else Id[:n21, :n21] CS[p:p + n22, q:q + n22] = Id[:n22, :n22] CS[n11:n11 + r, n11:n11 + r] = c CS[p + n22:p + n22 + r, r + n21 + n22:2 * r + n21 + n22] = c xs = n11 xe = n11 + r ys = n11 + n21 + n22 + r ye = n11 + n21 + n22 + 2 * r CS[xs:xe, ys:ye] = s if swap_sign else -s CS[p + n22:p + n22 + r, n11:n11 + r] = -s if swap_sign else s return U, CS, VDH
def logpost_deriv(self, params): r""" Compute the gradient of the negative log posterior for a particular set of parameters Computes the gradient of the negative log posterior (negative marginal log-likelihood minus any prior log-probabilities). This is computed on the root process and then broadcast to all processes. The main computational expense is computing the prior mean and covariance, which only needs to be done once and can be cached. This also requires computing the Cholesky decomposition of the covariance plus model discrepancy. New parameters must be a numpy array of length 3. First parameter is the data/model scaling factor :math:`{\rho}`, second parameter is the model discrepancy covariance, and the third parameter is the model discrepancy correlation length. All parameters are assumed to be on a logarithmic scale to enforce positivity. The returned log posterior gradient is a numpy array of length 3, with each component corresponding to the derivative of each of the input parameters. :param params: New set of parameters (must be a numpy array of length 3) :type params: ndarray :returns: gradient of the negative log posterior :rtype: ndarray """ self.set_params(params) rho = np.exp(self.params[0]) if self.Cu is None or self.mu is None: self.solve_prior() # compute log-likelihood on root process if COMM_WORLD.rank == 0: KCu = rho**2 * self.Cu + self.data.calc_K_plus_sigma(params[1:]) try: L = cho_factor(KCu) except LinAlgError: raise LinAlgError( "Error attempting to factorize the covariance matrix " + "in model_loglikelihood") invKCudata = cho_solve(L, self.data.get_data() - rho * self.mu) K_deriv = self.data.calc_K_deriv(self.params[1:]) deriv = np.zeros(3) deriv[0] = ( -rho * np.dot(self.mu, invKCudata) - rho**2 * np.linalg.multi_dot([invKCudata, self.Cu, invKCudata]) + rho**2 * np.trace(cho_solve(L, self.Cu))) for i in range(0, 2): deriv[i + 1] = -0.5 * ( np.linalg.multi_dot([invKCudata, K_deriv[i], invKCudata]) - np.trace(cho_solve(L, K_deriv[i]))) for i in range(3): if not self.priors[i] is None: deriv[i] -= self.priors[i].dlogpdtheta(self.params[i]) else: deriv = None deriv = COMM_WORLD.bcast(deriv, root=0) assert not deriv is None, "error in broadcasting the log likelihood derivative" COMM_WORLD.barrier() return deriv
def Discretize(V, **kwargs): """Adapted from :func:`~sklearn.cluster.spectral.discretize`. Copyright please see LICENSE.rst. """ from scipy.sparse import csc_matrix from scipy.linalg import LinAlgError copy = kwargs.pop('copy', False) max_svd_restarts = kwargs.pop('max_svd_restarts', 30) n_iter_max = kwargs.pop('n_iter_max', 20) random_state = kwargs.pop('random_state', None) info = kwargs.pop('info', None) def check_random_state(seed): """Adapted from :func:`~sklearn.utils.validation.check_random_state`.""" if seed is None or seed is np.random: return np.random.mtrand._rand if isinstance(seed, Integral): return np.random.RandomState(seed) if isinstance(seed, np.random.RandomState): return seed raise ValueError('%r cannot be used to seed a numpy.random.RandomState' ' instance' % seed) random_state = check_random_state(random_state) eps = np.finfo(float).eps vectors = np.array(V, dtype=float, copy=copy) n_samples, n_components = vectors.shape # Normalize the eigenvectors to an equal length of a vector of ones. # Reorient the eigenvectors to point in the negative direction with respect # to the first element. This may have to do with constraining the # eigenvectors to lie in a specific quadrant to make the discretization # search easier. norm_ones = np.sqrt(n_samples) for i in range(vectors.shape[1]): vectors[:, i] *= norm_ones if vectors[0, i] != 0: vectors[:, i] = -1 * vectors[:, i] * np.sign(vectors[0, i]) # Normalize the rows of the eigenvectors. Samples should lie on the unit # hypersphere centered at the origin. This transforms the samples in the # embedding space to the space of partition matrices. vectors = vectors / np.sqrt((vectors**2).sum(axis=1))[:, np.newaxis] svd_restarts = 0 has_converged = False # If there is an exception we try to randomize and rerun SVD again # do this max_svd_restarts times. while (svd_restarts < max_svd_restarts) and not has_converged: # Initialize first column of rotation matrix with a row of the # eigenvectors rotation = np.zeros((n_components, n_components)) rotation[:, 0] = vectors[random_state.randint(n_samples), :].T # To initialize the rest of the rotation matrix, find the rows # of the eigenvectors that are as orthogonal to each other as # possible c = np.zeros(n_samples) for j in range(1, n_components): # Accumulate c to ensure row is as orthogonal as possible to # previous picks as well as current one c += np.abs(np.dot(vectors, rotation[:, j - 1])) rotation[:, j] = vectors[c.argmin(), :].T last_objective_value = 0.0 n_iter = 0 while not has_converged: n_iter += 1 t_discrete = np.dot(vectors, rotation) labels = t_discrete.argmax(axis=1) vectors_discrete = csc_matrix( (np.ones(len(labels)), (np.arange(0, n_samples), labels)), shape=(n_samples, n_components)) t_svd = vectors_discrete.T * vectors try: U, S, Vh = np.linalg.svd(t_svd) svd_restarts += 1 except LinAlgError: print("SVD did not converge, randomizing and trying again") break ncut_value = 2.0 * (n_samples - S.sum()) if ((abs(ncut_value - last_objective_value) < eps) or (n_iter > n_iter_max)): has_converged = True else: # otherwise calculate rotation and continue last_objective_value = ncut_value rotation = np.dot(Vh.T, U.T) if info is not None: if not isinstance(info, dict): raise TypeError('info must be a dict') info['indicators'] = t_discrete info['vectors'] = vectors if not has_converged: raise LinAlgError('SVD did not converge') return labels
def predict_covariance(self, coords, unc): r""" Compute the predictive covariance This method computes the predictive covariance of data values at unmeasured locations. It returns the array of predicted sensor value covariances on the root process as numpy array. Unlike the mean, the predictive covariance requires doing two additional sets of covariance solves: one on the new sensor locations (to get the baseline covariance), and one set of solves that interpolates between the predictive points and the original sensor locations. This can be thought of as doing the covariance solves at the new points to get a baseline uncertainty, and then the cross-solves determine if any of the sensor data is close enough to the predictive points to reduce this uncertainty. :param coords: Spatial coordinates at which the mean will be predicted. Must be a 2D Numpy array (or a 1D array, which will assume the second axis has length 1) :type coords: ndarray :param unc: Uncertainty for unmeasured sensor locations (i.e. the statistical error one would expect if these measurements were made). Can be a single non-negative float, or an array of non-negative floats with the same length as the first axis of ``coords``. :type unc: float or ndarray :returns: FEM predictive covariance at specified sensor locations as a numpy array on the root process. All other processes will have a numpy array of shape ``(0, 0)``. :rtype: ndarray """ coords = np.array(coords, dtype=np.float64) if coords.ndim == 1: coords = np.reshape(coords, (-1, 1)) assert coords.ndim == 2, "coords must be a 1d or 2d array" assert coords.shape[1] == self.data.get_n_dim( ), "axis 1 of coords must be the same length as the FEM dimension" if self.Cu is None: self.solve_prior() if self.params is None: raise ValueError("must set parameter values to make predictions") rho = np.exp(self.params[0]) im_coords = InterpolationMatrix(self.G.function_space, coords) if coords.shape[0] > self.data.get_n_obs(): Cucd = interp_covariance_to_data(im_coords, self.G, self.solver, self.im, self.ensemble_comm) else: Cucd = interp_covariance_to_data(self.im, self.G, self.solver, im_coords, self.ensemble_comm).T Cucc = interp_covariance_to_data(im_coords, self.G, self.solver, im_coords, self.ensemble_comm) if self.ensemble_comm.rank == 0 and self.G.comm.rank == 0: try: Ks = self.data.calc_K_plus_sigma(self.params[1:]) LC = cho_factor(Ks + rho**2 * self.Cu) except LinAlgError: raise LinAlgError( "Cholesky factorization of one of the covariance matrices failed" ) # compute predictive covariance Cuy = Cucc - rho**2 * np.dot(Cucd, cho_solve(LC, Cucd.T)) Cuy = ObsData(coords, np.zeros(coords.shape[0]), unc).calc_K_plus_sigma( self.params[1:]) + rho**2 * Cuy else: Cuy = np.zeros((0, 0)) im_coords.destroy() return Cuy
def _cholesky(a, lower=False, overwrite_a=False, clean=True, check_finite=True, full_pivot=False, pivot_tol=-1): """Common code for cholesky() and cho_factor().""" a1 = asarray_chkfinite(a) if check_finite else asarray(a) a1 = atleast_2d(a1) # Dimension check if a1.ndim != 2: raise ValueError('Input array needs to be 2 dimensional but received ' 'a {}d-array.'.format(a1.ndim)) # Squareness check if a1.shape[0] != a1.shape[1]: raise ValueError('Input array is expected to be square but has ' 'the shape: {}.'.format(a1.shape)) # Quick return for square empty array if a1.size == 0: return a1.copy(), lower #if not is_hermitian(): # raise LinAlgError("Expected symmetric or hermitian matrix") overwrite_a = overwrite_a or _datacopied(a1, a) # if the pivot flag is false, return the result if not full_pivot: potrf, = get_lapack_funcs(('potrf', ), (a1, )) c, info = potrf(a1, lower=lower, overwrite_a=overwrite_a, clean=clean) if info > 0: raise LinAlgError( "%d-th leading minor of the array is not positive " "definite" % info) if info < 0: raise ValueError( 'LAPACK reported an illegal value in {}-th argument' 'on entry to "POTRF".'.format(-info)) return c, lower else: # if the pivot flag is true, return the result plus rank and pivot pstrf, = get_lapack_funcs(('pstrf', ), (a1, )) c, pivot, rank, info = pstrf(a1, lower=lower, overwrite_a=overwrite_a, tol=pivot_tol) # if info > 0: if rank == 0: raise LinAlgError( "%d-th leading minor of the array is not positive " "semidefinite" % info) else: raise LinAlgError("The array is rank deficient with " "computed rank %d" % info) if info < 0: raise ValueError( 'LAPACK reported an illegal value in {}-th argument' 'on entry to "PSTRF".'.format(-info)) return c, lower, rank, pivot
def _svdp(A, k, which='LM', irl_mode=True, kmax=None, compute_u=True, compute_v=True, v0=None, full_output=False, tol=0, delta=None, eta=None, anorm=0, cgs=False, elr=True, min_relgap=0.002, shifts=None, maxiter=None, random_state=None): """ Compute the singular value decomposition of a linear operator using PROPACK Parameters ---------- A : array_like, sparse matrix, or LinearOperator Operator for which SVD will be computed. If `A` is a LinearOperator object, it must define both ``matvec`` and ``rmatvec`` methods. k : int Number of singular values/vectors to compute which : {"LM", "SM"} Which singluar triplets to compute: - 'LM': compute triplets corresponding to the `k` largest singular values - 'SM': compute triplets corresponding to the `k` smallest singular values `which='SM'` requires `irl_mode=True`. Computes largest singular values by default. irl_mode : bool, optional If `True`, then compute SVD using IRL (implicitly restarted Lanczos) mode. Default is `True`. kmax : int, optional Maximal number of iterations / maximal dimension of the Krylov subspace. Default is ``10 * k``. compute_u : bool, optional If `True` (default) then compute left singular vectors, `u`. compute_v : bool, optional If `True` (default) then compute right singular vectors, `v`. tol : float, optional The desired relative accuracy for computed singular values. If not specified, it will be set based on machine precision. v0 : array_like, optional Starting vector for iterations: must be of length ``A.shape[0]``. If not specified, PROPACK will generate a starting vector. full_output : bool, optional If `True`, then return sigma_bound. Default is `False`. delta : float, optional Level of orthogonality to maintain between Lanczos vectors. Default is set based on machine precision. eta : float, optional Orthogonality cutoff. During reorthogonalization, vectors with component larger than `eta` along the Lanczos vector will be purged. Default is set based on machine precision. anorm : float, optional Estimate of ``||A||``. Default is `0`. cgs : bool, optional If `True`, reorthogonalization is done using classical Gram-Schmidt. If `False` (default), it is done using modified Gram-Schmidt. elr : bool, optional If `True` (default), then extended local orthogonality is enforced when obtaining singular vectors. min_relgap : float, optional The smallest relative gap allowed between any shift in IRL mode. Default is `0.001`. Accessed only if ``irl_mode=True``. shifts : int, optional Number of shifts per restart in IRL mode. Default is determined to satisfy ``k <= min(kmax-shifts, m, n)``. Must be >= 0, but choosing 0 might lead to performance degredation. Accessed only if ``irl_mode=True``. maxiter : int, optional Maximum number of restarts in IRL mode. Default is `1000`. Accessed only if ``irl_mode=True``. random_state : {None, int, `numpy.random.Generator`, `numpy.random.RandomState`}, optional Pseudorandom number generator state used to generate resamples. If `random_state` is ``None`` (or `np.random`), the `numpy.random.RandomState` singleton is used. If `random_state` is an int, a new ``RandomState`` instance is used, seeded with `random_state`. If `random_state` is already a ``Generator`` or ``RandomState`` instance then that instance is used. Returns ------- u : ndarray The `k` largest (``which="LM"``) or smallest (``which="SM"``) left singular vectors, ``shape == (A.shape[0], 3)``, returned only if ``compute_u=True``. sigma : ndarray The top `k` singular values, ``shape == (k,)`` vt : ndarray The `k` largest (``which="LM"``) or smallest (``which="SM"``) right singular vectors, ``shape == (3, A.shape[1])``, returned only if ``compute_v=True``. sigma_bound : ndarray the error bounds on the singular values sigma, returned only if ``full_output=True``. """ # 32-bit complex PROPACK functions have Fortran LAPACK ABI # incompatibility issues if np.iscomplexobj(A) and (np.intp(0).itemsize < 8): raise TypeError('PROPACK complex-valued SVD methods not available ' 'for 32-bit builds') random_state = check_random_state(random_state) which = which.upper() if which not in {'LM', 'SM'}: raise ValueError("`which` must be either 'LM' or 'SM'") if not irl_mode and which == 'SM': raise ValueError("`which`='SM' requires irl_mode=True") aprod = _AProd(A) typ = aprod.dtype.char try: lansvd_irl = _lansvd_irl_dict[typ] lansvd = _lansvd_dict[typ] except KeyError: # work with non-supported types using native system precision if np.iscomplexobj(np.empty(0, dtype=typ)): typ = np.dtype(complex).char else: typ = np.dtype(float).char lansvd_irl = _lansvd_irl_dict[typ] lansvd = _lansvd_dict[typ] m, n = aprod.shape if (k < 1) or (k > min(m, n)): raise ValueError("k must be positive and not greater than m or n") if kmax is None: kmax = 10 * k if maxiter is None: maxiter = 1000 # guard against unnecessarily large kmax kmax = min(m + 1, n + 1, kmax) if kmax < k: raise ValueError("kmax must be greater than or equal to k, " f"but kmax ({kmax}) < k ({k})") # convert python args to fortran args jobu = 'y' if compute_u else 'n' jobv = 'y' if compute_v else 'n' # these will be the output arrays u = np.zeros((m, kmax + 1), order='F', dtype=typ) v = np.zeros((n, kmax), order='F', dtype=typ) # Specify the starting vector. if v0 is all zero, PROPACK will generate # a random starting vector: the random seed cannot be controlled in that # case, so we'll instead use numpy to generate a random vector if v0 is None: u[:, 0] = random_state.uniform(size=m) if np.iscomplexobj(np.empty(0, dtype=typ)): # complex type u[:, 0] += 1j * random_state.uniform(size=m) else: try: u[:, 0] = v0 except ValueError: raise ValueError(f"v0 must be of length {m}") # process options for the fit if delta is None: delta = np.sqrt(np.finfo(typ).eps) if eta is None: eta = np.finfo(typ).eps**0.75 if irl_mode: doption = np.array((delta, eta, anorm, min_relgap), dtype=typ.lower()) # validate or find default shifts if shifts is None: shifts = kmax - k if k > min(kmax - shifts, m, n): raise ValueError('shifts must satisfy ' 'k <= min(kmax-shifts, m, n)!') elif shifts < 0: raise ValueError('shifts must be >= 0!') else: doption = np.array((delta, eta, anorm), dtype=typ.lower()) ioption = np.array((int(bool(cgs)), int(bool(elr))), dtype='i') # If computing `u` or `v` (left and right singular vectors, # respectively), `blocksize` controls how large a fraction of the # work is done via fast BLAS level 3 operations. A larger blocksize # may lead to faster computation at the expense of greater memory # consumption. `blocksize` must be ``>= 1``. Choosing blocksize # of 16, but docs don't specify; it's almost surely a # power of 2. blocksize = 16 # Determine lwork & liwork: # the required lengths are specified in the PROPACK documentation if compute_u or compute_v: lwork = m + n + 9 * kmax + 5 * kmax * kmax + 4 + max( 3 * kmax * kmax + 4 * kmax + 4, blocksize * max(m, n)) liwork = 8 * kmax else: lwork = m + n + 9 * kmax + 2 * kmax * kmax + 4 + max( m + n, 4 * kmax + 4) liwork = 2 * kmax + 1 work = np.empty(lwork, dtype=typ.lower()) iwork = np.empty(liwork, dtype=np.int32) # dummy arguments: these are passed to aprod, and not used in this wrapper dparm = np.empty(1, dtype=typ.lower()) iparm = np.empty(1, dtype=np.int32) if typ.isupper(): # PROPACK documentation is unclear on the required length of zwork. # Use the same length Julia's wrapper uses # see https://github.com/JuliaSmoothOptimizers/PROPACK.jl/ zwork = np.empty(m + n + 32 * m, dtype=typ) works = work, zwork, iwork else: works = work, iwork if irl_mode: u, sigma, bnd, v, info = lansvd_irl(_which_converter[which], jobu, jobv, m, n, shifts, k, maxiter, aprod, u, v, tol, *works, doption, ioption, dparm, iparm) else: u, sigma, bnd, v, info = lansvd(jobu, jobv, m, n, k, aprod, u, v, tol, *works, doption, ioption, dparm, iparm) if info > 0: raise LinAlgError( f"An invariant subspace of dimension {info} was found.") elif info < 0: raise LinAlgError(f"k={k} singular triplets did not converge within " f"kmax={kmax} iterations") # info == 0: The K largest (or smallest) singular triplets were computed # succesfully! return u[:, :k], sigma, v[:, :k].conj().T, bnd
def solve_posterior_covariance(self, scale_mean=False): r""" Solve posterior FEM and covariance interpolated to the data locations This method solves the posterior FEM and covariance interpolated to the sensor locations. The method returns solution as numpy arrays on the root process (rank 0). Note that unlike the solve done in the meshspace, this uses a return value rather than a Firedrake/PETSc style interface to place the solution in a pre-allocated ``Function``. This is because each process has a different array size, so would require correctly pre-allocating arrays of different lengths on each process. The optional ``scale_mean`` argument determines if the solution is to be re-scaled by the model discrepancy scaling factor. This value is by default ``False``. To re-scale to match the data, pass ``scale_mean=True``. :returns: FEM posterior mean and covariance (as a tuple of numpy arrays) on the root process. Non-root processes return numpy arrays of shape ``(0,)`` (mean) and ``(0, 0)`` (covariance). :param scale_mean: Boolean indicating if the mean should be scaled by the model discrepancy scaling factor. Optional, default is ``False`` :type scale_mean: bool :rtype: tuple of ndarrays """ if not isinstance(bool(scale_mean), bool): raise TypeError("scale_mean argument must be boolean-like") # create interpolation matrix if not cached if self.mu is None or self.Cu is None: self.solve_prior() if self.params is None: raise ValueError("must set parameter values to solve posterior") rho = np.exp(self.params[0]) if scale_mean: scalefact = rho else: scalefact = 1. if self.ensemble_comm.rank == 0 and self.G.comm.rank == 0: try: Ks = self.data.calc_K_plus_sigma(self.params[1:]) LK = cho_factor(Ks) LC = cho_factor(Ks + rho**2 * self.Cu) except LinAlgError: raise LinAlgError( "Cholesky factorization of one of the covariance matrices failed" ) # compute posterior mean muy = rho * np.dot(self.Cu, cho_solve( LK, self.data.get_data())) + self.mu muy_tmp = rho**2 * np.dot(self.Cu, cho_solve(LC, muy)) muy = muy - muy_tmp # compute posterior covariance Cuy = self.Cu - rho**2 * np.dot(self.Cu, cho_solve(LC, self.Cu)) else: muy = np.zeros(0) Cuy = np.zeros((0, 0)) return scalefact * muy, Cuy
def jitchol(a, jit=None, jit_max=1e-3, returns_jit=False, lower=False, overwrite_a=False, check_finite=True): """ Do cholesky decomposition with a bit of diagonal jitter if needs be. Arguments: A: a [NxN] positive definite symmetric matrix to be decomposed as A = L.dot(L.T). lower: Return lower triangular factor, default False (upper). Returns: An upper or lower triangular matrix factor, L, also [NxN]. Also wheter or not a the matrix is lower triangular form, (L, lower). Examples -------- >>> a = np.array([[1, -2j], ... [2j, 5]]) >>> jitchol(a, lower=True) array([[ 1.+0.j, 0.+0.j], [ 0.+2.j, 1.+0.j]]) >>> np.all(a == np.array([[1, -2j], ... [2j, 5]])) True >>> b = np.array([[ 2, -1, 0], ... [-1, 2, -1], ... [ 0, -1, 2]]) >>> U, jit = jitchol(b, returns_jit=True) >>> U.round(2) array([[ 1.41, -0.71, 0. ], [ 0. , 1.22, -0.82], [ 0. , 0. , 1.15]]) >>> jit is None True Should remain unchanged >>> b array([[ 2, -1, 0], [-1, 2, -1], [ 0, -1, 2]]) >>> c = np.array([[1, 2], ... [2, 1]]) >>> jitchol(c) # doctest: +IGNORE_EXCEPTION_DETAIL Traceback (most recent call last): ... LinAlgError: Exceeded maximum jitter limit, yet a is still not positive semidefinite! """ try: chol = cholesky(a, lower=lower, overwrite_a=overwrite_a, check_finite=check_finite) if returns_jit: return chol, jit else: return chol except LinAlgError: if jit is None: jit = 1e-16 if jit > jit_max: raise LinAlgError('Exceeded maximum jitter limit, yet a is still' ' not positive semidefinite!') diag = np.diag(a) diag_mean = diag.mean() diag_delta = jit * diag_mean if overwrite_a: diag_ind = np.diag_indices_from(a) a[diag_ind] += diag_delta return jitchol(a, jit=10 * jit, jit_max=jit_max, returns_jit=returns_jit, lower=lower, overwrite_a=overwrite_a, check_finite=check_finite) return jitchol(a + diag_delta * np.eye(*a.shape), jit=10 * jit, jit_max=jit_max, returns_jit=returns_jit, lower=lower, overwrite_a=overwrite_a, check_finite=check_finite)
def spsolve_triangular(A, b, lower=True, overwrite_A=False, overwrite_b=False): """ Solve the equation `A x = b` for `x`, assuming A is a triangular matrix. Parameters ---------- A : (M, M) sparse matrix A sparse square triangular matrix. Should be in CSR format. b : (M,) or (M, N) array_like Right-hand side matrix in `A x = b` lower : bool, optional Whether `A` is a lower or upper triangular matrix. Default is lower triangular matrix. overwrite_A : bool, optional Allow changing `A`. The indices of `A` are going to be sorted and zero entries are going to be removed. Enabling gives a performance gain. Default is False. overwrite_b : bool, optional Allow overwriting data in `b`. Enabling gives a performance gain. Default is False. If `overwrite_b` is True, it should be ensured that `b` has an appropriate dtype to be able to store the result. Returns ------- x : (M,) or (M, N) ndarray Solution to the system `A x = b`. Shape of return matches shape of `b`. Raises ------ LinAlgError If `A` is singular or not triangular. ValueError If shape of `A` or shape of `b` do not match the requirements. Notes ----- .. versionadded:: 0.19.0 Examples -------- >>> from scipy.sparse import csr_matrix >>> from scipy.sparse.linalg import spsolve_triangular >>> A = csr_matrix([[3, 0, 0], [1, -1, 0], [2, 0, 1]], dtype=float) >>> B = np.array([[2, 0], [-1, 0], [2, 0]], dtype=float) >>> x = spsolve_triangular(A, B) >>> np.allclose(A.dot(x), B) True """ # Check the input for correct type and format. if not isspmatrix_csr(A): warn('CSR matrix format is required. Converting to CSR matrix.', SparseEfficiencyWarning) A = csr_matrix(A) elif not overwrite_A: A = A.copy() if A.shape[0] != A.shape[1]: raise ValueError( 'A must be a square matrix but its shape is {}.'.format(A.shape)) A.eliminate_zeros() A.sort_indices() b = np.asanyarray(b) if b.ndim not in [1, 2]: raise ValueError('b must have 1 or 2 dims but its shape is {}.'.format( b.shape)) if A.shape[0] != b.shape[0]: raise ValueError( 'The size of the dimensions of A must be equal to ' 'the size of the first dimension of b but the shape of A is ' '{} and the shape of b is {}.'.format(A.shape, b.shape)) # Init x as (a copy of) b. x_dtype = np.result_type(A.data, b, np.float) if overwrite_b: if np.can_cast(b.dtype, x_dtype, casting='same_kind'): x = b else: raise ValueError('Cannot overwrite b (dtype {}) with result ' 'of type {}.'.format(b.dtype, x_dtype)) else: x = b.astype(x_dtype, copy=True) # Choose forward or backward order. if lower: row_indices = range(len(b)) else: row_indices = range(len(b) - 1, -1, -1) # Fill x iteratively. for i in row_indices: # Get indices for i-th row. indptr_start = A.indptr[i] indptr_stop = A.indptr[i + 1] if lower: A_diagonal_index_row_i = indptr_stop - 1 A_off_diagonal_indices_row_i = slice(indptr_start, indptr_stop - 1) else: A_diagonal_index_row_i = indptr_start A_off_diagonal_indices_row_i = slice(indptr_start + 1, indptr_stop) # Check regularity and triangularity of A. if indptr_stop <= indptr_start or A.indices[A_diagonal_index_row_i] < i: raise LinAlgError('A is singular: diagonal {} is zero.'.format(i)) if A.indices[A_diagonal_index_row_i] > i: raise LinAlgError('A is not triangular: A[{}, {}] is nonzero.' ''.format(i, A.indices[A_diagonal_index_row_i])) # Incorporate off-diagonal entries. A_column_indices_in_row_i = A.indices[A_off_diagonal_indices_row_i] A_values_in_row_i = A.data[A_off_diagonal_indices_row_i] x[i] -= np.dot(x[A_column_indices_in_row_i].T, A_values_in_row_i) # Compute i-th entry of x. x[i] /= A.data[A_diagonal_index_row_i] return x
def firls(numtaps, bands, desired, weight=None, nyq=None, fs=None): """ FIR filter design using least-squares error minimization. Calculate the filter coefficients for the linear-phase finite impulse response (FIR) filter which has the best approximation to the desired frequency response described by `bands` and `desired` in the least squares sense (i.e., the integral of the weighted mean-squared error within the specified bands is minimized). Parameters ---------- numtaps : int The number of taps in the FIR filter. `numtaps` must be odd. bands : array_like A monotonic nondecreasing sequence containing the band edges in Hz. All elements must be non-negative and less than or equal to the Nyquist frequency given by `nyq`. desired : array_like A sequence the same size as `bands` containing the desired gain at the start and end point of each band. weight : array_like, optional A relative weighting to give to each band region when solving the least squares problem. `weight` has to be half the size of `bands`. nyq : float, optional *Deprecated. Use `fs` instead.* Nyquist frequency. Each frequency in `bands` must be between 0 and `nyq` (inclusive). Default is 1. fs : float, optional The sampling frequency of the signal. Each frequency in `bands` must be between 0 and ``fs/2`` (inclusive). Default is 2. Returns ------- coeffs : ndarray Coefficients of the optimal (in a least squares sense) FIR filter. See also -------- firwin firwin2 minimum_phase remez Notes ----- This implementation follows the algorithm given in [1]_. As noted there, least squares design has multiple advantages: 1. Optimal in a least-squares sense. 2. Simple, non-iterative method. 3. The general solution can obtained by solving a linear system of equations. 4. Allows the use of a frequency dependent weighting function. This function constructs a Type I linear phase FIR filter, which contains an odd number of `coeffs` satisfying for :math:`n < numtaps`: .. math:: coeffs(n) = coeffs(numtaps - 1 - n) The odd number of coefficients and filter symmetry avoid boundary conditions that could otherwise occur at the Nyquist and 0 frequencies (e.g., for Type II, III, or IV variants). .. versionadded:: 0.18 References ---------- .. [1] Ivan Selesnick, Linear-Phase Fir Filter Design By Least Squares. OpenStax CNX. Aug 9, 2005. http://cnx.org/contents/eb1ecb35-03a9-4610-ba87-41cd771c95f2@7 Examples -------- We want to construct a band-pass filter. Note that the behavior in the frequency ranges between our stop bands and pass bands is unspecified, and thus may overshoot depending on the parameters of our filter: >>> from scipy import signal >>> import matplotlib.pyplot as plt >>> fig, axs = plt.subplots(2) >>> fs = 10.0 # Hz >>> desired = (0, 0, 1, 1, 0, 0) >>> for bi, bands in enumerate(((0, 1, 2, 3, 4, 5), (0, 1, 2, 4, 4.5, 5))): ... fir_firls = signal.firls(73, bands, desired, fs=fs) ... fir_remez = signal.remez(73, bands, desired[::2], fs=fs) ... fir_firwin2 = signal.firwin2(73, bands, desired, fs=fs) ... hs = list() ... ax = axs[bi] ... for fir in (fir_firls, fir_remez, fir_firwin2): ... freq, response = signal.freqz(fir) ... hs.append(ax.semilogy(0.5*fs*freq/np.pi, np.abs(response))[0]) ... for band, gains in zip(zip(bands[::2], bands[1::2]), ... zip(desired[::2], desired[1::2])): ... ax.semilogy(band, np.maximum(gains, 1e-7), 'k--', linewidth=2) ... if bi == 0: ... ax.legend(hs, ('firls', 'remez', 'firwin2'), ... loc='lower center', frameon=False) ... else: ... ax.set_xlabel('Frequency (Hz)') ... ax.grid(True) ... ax.set(title='Band-pass %d-%d Hz' % bands[2:4], ylabel='Magnitude') ... >>> fig.tight_layout() >>> plt.show() """ # noqa nyq = 0.5 * _get_fs(fs, nyq) numtaps = int(numtaps) if numtaps % 2 == 0 or numtaps < 1: raise ValueError("numtaps must be odd and >= 1") M = (numtaps-1) // 2 # normalize bands 0->1 and make it 2 columns nyq = float(nyq) if nyq <= 0: raise ValueError('nyq must be positive, got %s <= 0.' % nyq) bands = np.asarray(bands).flatten() / nyq if len(bands) % 2 != 0: raise ValueError("bands must contain frequency pairs.") if (bands < 0).any() or (bands > 1).any(): raise ValueError("bands must be between 0 and 1 relative to Nyquist") bands.shape = (-1, 2) # check remaining params desired = np.asarray(desired).flatten() if bands.size != desired.size: raise ValueError("desired must have one entry per frequency, got %s " "gains for %s frequencies." % (desired.size, bands.size)) desired.shape = (-1, 2) if (np.diff(bands) <= 0).any() or (np.diff(bands[:, 0]) < 0).any(): raise ValueError("bands must be monotonically nondecreasing and have " "width > 0.") if (bands[:-1, 1] > bands[1:, 0]).any(): raise ValueError("bands must not overlap.") if (desired < 0).any(): raise ValueError("desired must be non-negative.") if weight is None: weight = np.ones(len(desired)) weight = np.asarray(weight).flatten() if len(weight) != len(desired): raise ValueError("weight must be the same size as the number of " "band pairs (%s)." % (len(bands),)) if (weight < 0).any(): raise ValueError("weight must be non-negative.") # Set up the linear matrix equation to be solved, Qa = b # We can express Q(k,n) = 0.5 Q1(k,n) + 0.5 Q2(k,n) # where Q1(k,n)=q(k−n) and Q2(k,n)=q(k+n), i.e. a Toeplitz plus Hankel. # We omit the factor of 0.5 above, instead adding it during coefficient # calculation. # We also omit the 1/π from both Q and b equations, as they cancel # during solving. # We have that: # q(n) = 1/π ∫W(ω)cos(nω)dω (over 0->π) # Using our nomalization ω=πf and with a constant weight W over each # interval f1->f2 we get: # q(n) = W∫cos(πnf)df (0->1) = Wf sin(πnf)/πnf # integrated over each f1->f2 pair (i.e., value at f2 - value at f1). n = np.arange(numtaps)[:, np.newaxis, np.newaxis] q = np.dot(np.diff(np.sinc(bands * n) * bands, axis=2)[:, :, 0], weight) # Now we assemble our sum of Toeplitz and Hankel Q1 = toeplitz(q[:M+1]) Q2 = hankel(q[:M+1], q[M:]) Q = Q1 + Q2 # Now for b(n) we have that: # b(n) = 1/π ∫ W(ω)D(ω)cos(nω)dω (over 0->π) # Using our normalization ω=πf and with a constant weight W over each # interval and a linear term for D(ω) we get (over each f1->f2 interval): # b(n) = W ∫ (mf+c)cos(πnf)df # = f(mf+c)sin(πnf)/πnf + mf**2 cos(nπf)/(πnf)**2 # integrated over each f1->f2 pair (i.e., value at f2 - value at f1). n = n[:M + 1] # only need this many coefficients here # Choose m and c such that we are at the start and end weights m = (np.diff(desired, axis=1) / np.diff(bands, axis=1)) c = desired[:, [0]] - bands[:, [0]] * m b = bands * (m*bands + c) * np.sinc(bands * n) # Use L'Hospital's rule here for cos(nπf)/(πnf)**2 @ n=0 b[0] -= m * bands * bands / 2. b[1:] += m * np.cos(n[1:] * np.pi * bands) / (np.pi * n[1:]) ** 2 b = np.dot(np.diff(b, axis=2)[:, :, 0], weight) # Now we can solve the equation try: # try the fast way with warnings.catch_warnings(record=True) as w: warnings.simplefilter('always') a = solve(Q, b, sym_pos=True, check_finite=False) for ww in w: if (ww.category == LinAlgWarning and str(ww.message).startswith('Ill-conditioned matrix')): raise LinAlgError(str(ww.message)) except LinAlgError: # in case Q is rank deficient # This is faster than pinvh, even though we don't explicitly use # the symmetry here. gelsy was faster than gelsd and gelss in # some non-exhaustive tests. a = lstsq(Q, b, lapack_driver='gelsy')[0] # make coefficients symmetric (linear phase) coeffs = np.hstack((a[:0:-1], 2 * a[0], a[1:])) return coeffs
def new_svd(*args, **kwargs): raise LinAlgError()
def spsolve_triangular(A, b, lower=True, overwrite_A=False, overwrite_b=False): """ Solve the equation `A x = b` for `x`, assuming A is a triangular matrix. Parameters ---------- A : (M, M) sparse matrix A sparse square triangular matrix. Should be in CSR format. b : (M,) or (M, N) array_like Right-hand side matrix in `A x = b` lower : bool, optional Whether `A` is a lower or upper triangular matrix. Default is lower triangular matrix. overwrite_A : bool, optional Allow changing `A`. The indices of `A` are going to be sorted and zero entries are going to be removed. Enabling gives a performance gain. Default is False. overwrite_b : bool, optional Allow overwriting data in `b`. Enabling gives a performance gain. Default is False. Returns ------- x : (M,) or (M, N) ndarray Solution to the system `A x = b`. Shape of return matches shape of `b`. Raises ------ LinAlgError If `A` is singular or not triangular. ValueError If shape of `A` or shape of `b` do not match the requirements. Notes ----- .. versionadded:: 0.19.0 """ # Check the input for correct type and format. if not isspmatrix_csr(A): warn('CSR matrix format is required. Converting to CSR matrix.', SparseEfficiencyWarning) A = csr_matrix(A) elif not overwrite_A: A = A.copy() if A.shape[0] != A.shape[1]: raise ValueError( 'A must be a square matrix but its shape is {}.'.format(A.shape)) A.eliminate_zeros() A.sort_indices() b = np.asanyarray(b) if b.ndim not in [1, 2]: raise ValueError('b must have 1 or 2 dims but its shape is {}.'.format( b.shape)) if A.shape[0] != b.shape[0]: raise ValueError( 'The size of the dimensions of A must be equal to ' 'the size of the first dimension of b but the shape of A is ' '{} and the shape of b is {}.'.format(A.shape, b.shape)) # Init x as copy of b. if overwrite_b: x = b else: x = b.copy() # Choose forward or backward order. if lower: row_indices = range(len(b)) else: row_indices = range(len(b) - 1, -1, -1) # Fill x iteratively. for i in row_indices: # Get indices for i-th row. indptr_start = A.indptr[i] indptr_stop = A.indptr[i + 1] if lower: A_diagonal_index_row_i = indptr_stop - 1 A_off_diagonal_indices_row_i = slice(indptr_start, indptr_stop - 1) else: A_diagonal_index_row_i = indptr_start A_off_diagonal_indices_row_i = slice(indptr_start + 1, indptr_stop) # Check regularity and triangularity of A. if indptr_stop <= indptr_start or A.indices[A_diagonal_index_row_i] < i: raise LinAlgError('A is singular: ' '{}th diagonal is zero!'.format(i)) if A.indices[A_diagonal_index_row_i] > i: raise LinAlgError('A is no triangular matrix: entry ' '[{},{}] is not zero!'.format( i, A.indices[A_diagonal_index_row_i])) # Incorporate off-diagonal entries. A_column_indices_in_row_i = A.indices[A_off_diagonal_indices_row_i] A_values_in_row_i = A.data[A_off_diagonal_indices_row_i] x[i] -= np.dot(x[A_column_indices_in_row_i].T, A_values_in_row_i) # Compute i-th entry of x. x[i] /= A.data[A_diagonal_index_row_i] return x
def discretize(vectors, copy=True, max_svd_restarts=30, n_iter_max=20, random_state=None): """Search for a partition matrix (clustering) which is closest to the eigenvector embedding. Parameters ---------- vectors : array-like, shape: (n_samples, n_clusters) The embedding space of the samples. copy : boolean, optional, default: True Whether to copy vectors, or perform in-place normalization. max_svd_restarts : int, optional, default: 30 Maximum number of attempts to restart SVD if convergence fails n_iter_max : int, optional, default: 30 Maximum number of iterations to attempt in rotation and partition matrix search if machine precision convergence is not reached random_state: int seed, RandomState instance, or None (default) A pseudo random number generator used for the initialization of the of the rotation matrix Returns ------- labels : array of integers, shape: n_samples The labels of the clusters. References ---------- - Multiclass spectral clustering, 2003 Stella X. Yu, Jianbo Shi http://www1.icsi.berkeley.edu/~stellayu/publication/doc/2003kwayICCV.pdf Notes ----- The eigenvector embedding is used to iteratively search for the closest discrete partition. First, the eigenvector embedding is normalized to the space of partition matrices. An optimal discrete partition matrix closest to this normalized embedding multiplied by an initial rotation is calculated. Fixing this discrete partition matrix, an optimal rotation matrix is calculated. These two calculations are performed until convergence. The discrete partition matrix is returned as the clustering solution. Used in spectral clustering, this method tends to be faster and more robust to random initialization than k-means. """ from scipy.sparse import csc_matrix from scipy.linalg import LinAlgError random_state = check_random_state(random_state) vectors = as_float_array(vectors, copy=copy) eps = np.finfo(float).eps n_samples, n_components = vectors.shape # Normalize the eigenvectors to an equal length of a vector of ones. # Reorient the eigenvectors to point in the negative direction with respect # to the first element. This may have to do with constraining the # eigenvectors to lie in a specific quadrant to make the discretization # search easier. norm_ones = np.sqrt(n_samples) for i in range(vectors.shape[1]): vectors[:, i] = (vectors[:, i] / norm(vectors[:, i])) \ * norm_ones if vectors[0, i] != 0: vectors[:, i] = -1 * vectors[:, i] * np.sign(vectors[0, i]) # Normalize the rows of the eigenvectors. Samples should lie on the unit # hypersphere centered at the origin. This transforms the samples in the # embedding space to the space of partition matrices. vectors = vectors / np.sqrt((vectors**2).sum(axis=1))[:, np.newaxis] svd_restarts = 0 has_converged = False # If there is an exception we try to randomize and rerun SVD again # do this max_svd_restarts times. while (svd_restarts < max_svd_restarts) and not has_converged: # Initialize first column of rotation matrix with a row of the # eigenvectors rotation = np.zeros((n_components, n_components)) rotation[:, 0] = vectors[random_state.randint(n_samples), :].T # To initialize the rest of the rotation matrix, find the rows # of the eigenvectors that are as orthogonal to each other as # possible c = np.zeros(n_samples) for j in range(1, n_components): # Accumulate c to ensure row is as orthogonal as possible to # previous picks as well as current one c += np.abs(np.dot(vectors, rotation[:, j - 1])) rotation[:, j] = vectors[c.argmin(), :].T last_objective_value = 0.0 n_iter = 0 while not has_converged: n_iter += 1 t_discrete = np.dot(vectors, rotation) labels = t_discrete.argmax(axis=1) vectors_discrete = csc_matrix( (np.ones(len(labels)), (np.arange(0, n_samples), labels)), shape=(n_samples, n_components)) t_svd = vectors_discrete.T * vectors try: U, S, Vh = np.linalg.svd(t_svd) svd_restarts += 1 except LinAlgError: print "SVD did not converge, randomizing and trying again" break ncut_value = 2.0 * (n_samples - S.sum()) if ((abs(ncut_value - last_objective_value) < eps) or (n_iter > n_iter_max)): has_converged = True else: # otherwise calculate rotation and continue last_objective_value = ncut_value rotation = np.dot(Vh.T, U.T) if not has_converged: raise LinAlgError('SVD did not converge') return labels
def solve_posterior(self, x, scale_mean=False): r""" Solve FEM posterior in mesh space Solve for the FEM posterior conditioned on the data on the FEM mesh. The solution is stored in the preallocated Firedrake ``Function``. Note that if an ensemble communicator was used to parallelize the covariance solves, the solution is only stored in the root of the ensemble communicator. The Firedrake ``Function`` on the other processes will not be modified. The optional ``scale_mean`` argument determines if the solution is to be re-scaled by the model discrepancy scaling factor. This value is by default ``False``. To re-scale to match the data, pass ``scale_mean=True``. :param x: Firedrake ``Function`` for holding the solution. This is modified in place by the method. :type x: Firedrake Function :param scale_mean: Boolean indicating if the mean should be scaled by the model discrepancy scaling factor. Optional, default is ``False`` :type scale_mean: bool :returns: None """ if not isinstance(bool(scale_mean), bool): raise TypeError("scale_mean argument must be boolean-like") # create interpolation matrix if not cached if self.Cu is None or self.x is None: self.solve_prior() if self.params is None: raise ValueError("must set parameter values to solve posterior") rho = np.exp(self.params[0]) if scale_mean: scalefact = rho else: scalefact = 1. # remaining solves are just done on ensemble root if self.ensemble_comm.rank == 0: if self.G.comm.rank == 0: Ks = self.data.calc_K_plus_sigma(self.params[1:]) try: LK = cho_factor(Ks) except LinAlgError: raise LinAlgError( "Error attempting to compute the Cholesky factorization " + "of the model discrepancy") tmp_dataspace_1 = cho_solve(LK, self.data.get_data()) else: tmp_dataspace_1 = np.zeros(0) # interpolate to dataspace tmp_meshspace_1 = self.im.interp_data_to_mesh(tmp_dataspace_1) # solve forcing covariance and interpolate to dataspace tmp_meshspace_2 = solve_forcing_covariance( self.G, self.solver, tmp_meshspace_1)._scale(rho) + self.x.vector() tmp_dataspace_1 = self.im.interp_mesh_to_data(tmp_meshspace_2) if self.G.comm.rank == 0: try: L = cho_factor(Ks + rho**2 * self.Cu) except LinAlgError: raise LinAlgError( "Error attempting to compute the Cholesky factorization " + "of the model discrepancy plus forcing covariance") tmp_dataspace_2 = cho_solve(L, tmp_dataspace_1) else: tmp_dataspace_2 = np.zeros(0) tmp_meshspace_1 = self.im.interp_data_to_mesh(tmp_dataspace_2) tmp_meshspace_1 = solve_forcing_covariance( self.G, self.solver, tmp_meshspace_1)._scale(rho**2) x.assign( (tmp_meshspace_2 - tmp_meshspace_1)._scale(scalefact).function)