Source code for burnman.optimize.linear_fitting

# This file is part of BurnMan - a thermoelastic and thermodynamic toolkit for
# the Earth and Planetary Sciences
# Copyright (C) 2012 - 2021 by the BurnMan team, released under the GNU
# GPL v2 or later.


import importlib
import numpy as np
from scipy.linalg import sqrtm
import warnings

try:
    cp = importlib.import_module("cvxpy")
except ImportError as err:
    print(
        f"Warning: {err}. " "For full functionality of BurnMan, please install cvxpy."
    )


[docs] def weighted_constrained_least_squares( A, b, Cov_b=None, equality_constraints=None, inequality_constraints=None, allow_rank_deficient=False, ): """ Solves a weighted, constrained least squares problem using cvxpy. The objective function is to minimize the following: sum_squares(Cov_b^(-1/2).A.x - Cov_b^(-1/2).b)) subject to C.x == c D.x <= d :param A: An array defining matrix A in the objective function above. :type A: 2D numpy array :param b: An array defining vector b in the objective function above. :type b: numpy array :param Cov_b: A covariance matrix associated with b :type Cov_b: 2D numpy array :param equality_constraints: A list containing the matrices C and c in the objective function above. :type equality_constraints: list containing a 2D array and 1D array :param inequality_constraints: A list containing the matrices D and d in the objective function above. :type inequality_constraints: list containing a 2D array and 1D array :param allow_rank_deficient: If True, allows the problem to be solved even if the design matrix is rank-deficient. :type allow_rank_deficient: bool :returns: Tuple containing the optimized phase amounts (1D numpy.array), a covariance matrix corresponding to the optimized phase amounts (2D numpy.array), and the weighted residual of the fitting procedure (a float). :rtype: tuple """ if Cov_b is None: Cov_b = np.eye(len(b)) # Create the standard weighted least squares objective function # (https://stats.stackexchange.com/a/333551) n_vars = A.shape[1] M = np.linalg.pinv(sqrtm(Cov_b)) MA = M @ A Mb = M @ b x = cp.Variable(n_vars) objective = cp.Minimize(cp.sum_squares(MA @ x - Mb)) # Add a check for rank deficiency rank_MA = np.linalg.matrix_rank(MA) if not allow_rank_deficient and rank_MA < n_vars: raise Exception( f"The weighted design matrix is rank-deficient " f"(Cov_b^(-1/2).A={rank_MA} < n_vars={n_vars}). " "This might mean: " "\n(a) that there are insufficient independent " "constraints to yield a unique solution to the problem or " "\n(b) that the covariance matrix is poorly conditioned " "(e.g. a zero element along the diagonal). " "\nEither modify the problem (recommended), or " "set allow_rank_deficient=True in the function call." ) constraints = [] if equality_constraints is not None: n_eq_csts = len(equality_constraints[0]) constraints = [ equality_constraints[0][i] @ x == equality_constraints[1][i] for i in range(n_eq_csts) ] if inequality_constraints is not None: n_ineq_csts = len(inequality_constraints[0]) constraints.extend( [ inequality_constraints[0][i] @ x <= inequality_constraints[1][i] for i in range(n_ineq_csts) ] ) # Set up the problem and solve it warns = [] if len(constraints) > 0: prob = cp.Problem(objective, constraints) else: prob = cp.Problem(objective) try: with warnings.catch_warnings(record=True) as w: res = prob.solve() popt = np.array([x.value[i] for i in range(len(A.T))]) warns.extend(w) except Exception: try: with warnings.catch_warnings(record=True) as w: res = prob.solve(solver=cp.ECOS) popt = np.array([x.value[i] for i in range(len(A.T))]) warns.extend(w) except Exception as e: raise Exception(e) # Calculate the covariance matrix # (also from https://stats.stackexchange.com/a/333551) inv_Cov_b = np.linalg.pinv(Cov_b) pcov = np.linalg.pinv(A.T.dot(inv_Cov_b.dot(A))) return (popt, pcov, res)