Пример #1
0
 def test_initial2(self):
     op = OptimizationProblem()
     op.variables.add(names=['x1', 'x2', 'x3'], types='B' * 3)
     c = op.quadratic_constraints.add(lin_expr=SparsePair(ind=['x1', 'x3'],
                                                          val=[1.0, -1.0]),
                                      quad_expr=SparseTriple(
                                          ind1=['x1', 'x2'],
                                          ind2=['x2', 'x3'],
                                          val=[1.0, -1.0]),
                                      sense='E',
                                      rhs=1.0)
     quad = op.quadratic_constraints
     self.assertEqual(quad.get_num(), 1)
     self.assertListEqual(quad.get_names(), ['q0'])
     self.assertListEqual(quad.get_rhs(), [1.0])
     self.assertListEqual(quad.get_senses(), ['E'])
     self.assertListEqual(quad.get_linear_num_nonzeros(), [2])
     self.assertListEqual(quad.get_quad_num_nonzeros(), [2])
     l = quad.get_linear_components()
     self.assertEqual(len(l), 1)
     self.assertListEqual(l[0].ind, [0, 2])
     self.assertListEqual(l[0].val, [1.0, -1.0])
     q = quad.get_quadratic_components()
     self.assertEqual(len(q), 1)
     self.assertListEqual(q[0].ind1, [1, 2])
     self.assertListEqual(q[0].ind2, [0, 1])
     self.assertListEqual(q[0].val, [1.0, -1.0])
Пример #2
0
 def test_add(self):
     op = OptimizationProblem()
     op.variables.add(names=['x', 'y'])
     l = SparsePair(ind=['x'], val=[1.0])
     q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0])
     self.assertEqual(
         op.quadratic_constraints.add(name='my quad',
                                      lin_expr=l,
                                      quad_expr=q,
                                      rhs=1.0,
                                      sense='G'), 0)
Пример #3
0
 def test_get_num(self):
     op = OptimizationProblem()
     op.variables.add(names=['x', 'y'])
     l = SparsePair(ind=['x'], val=[1.0])
     q = SparseTriple(ind1=['x'], ind2=['y'], val=[1.0])
     n = 10
     for i in range(n):
         self.assertEqual(
             op.quadratic_constraints.add(name=str(i),
                                          lin_expr=l,
                                          quad_expr=q), i)
     self.assertEqual(op.quadratic_constraints.get_num(), n)
Пример #4
0
    def test_cobyla_optimizer_with_quadratic_constraint(self):
        """ Cobyla Optimizer Test """

        # load optimization problem
        problem = OptimizationProblem()
        problem.variables.add(lb=[0, 0], ub=[1, 1], types='CC')
        problem.objective.set_linear([(0, 1), (1, 1)])

        qc = problem.quadratic_constraints
        linear = SparsePair(ind=[0, 1], val=[-1, -1])
        quadratic = SparseTriple(ind1=[0, 1], ind2=[0, 1], val=[1, 1])
        qc.add(name='qc', lin_expr=linear, quad_expr=quadratic, rhs=-1/2)

        # solve problem with cobyla
        result = self.cobyla_optimizer.solve(problem)

        # analyze results
        self.assertAlmostEqual(result.fval, 1.0, places=2)
Пример #5
0
    def add_quadratic_constraint(self, lin, quad, sense, rhs):
        """
        Args:
            lin (list[(int, float)]): lin
            quad (list[(int, int, float)]): quad
            sense (str): sense
            rhs (float): rhs
        """
        ind, val = self._convert_coefficients(lin)
        ind1 = [e[0] for e in quad]
        ind2 = [e[1] for e in quad]
        val2 = [e[2] for e in quad]
        sense = self._convert_sense(sense)
        c = {'lin_expr': SparsePair(ind, val),
             'quad_expr': SparseTriple(ind1, ind2, val2),
             'sense': sense,
             'rhs': rhs,
             'name': 'q' + str(self._model.quadratic_constraints.get_num())
             }

        self._model.quadratic_constraints.add(**c)
Пример #6
0
    def add_quadratic_constraint(self, lin, quad, sense, rhs):
        """
        :type lin: list[(int, float)]
        :type quad: list[(int, int, float)]
        :type sense: string
        :type rhs: float
        :rtype: None
        """
        ind, val = self._convert_coefficients(lin)
        ind1 = [e[0] for e in quad]
        ind2 = [e[1] for e in quad]
        val2 = [e[2] for e in quad]
        sense = self._convert_sense(sense)
        c = {'lin_expr': SparsePair(ind, val),
             'quad_expr': SparseTriple(ind1, ind2, val2),
             'sense': sense,
             'rhs': rhs,
             'name': 'q' + str(self._model.quadratic_constraints.get_num())
             }

        self._model.quadratic_constraints.add(**c)
Пример #7
0
    def Quadratic_constraint(self):
        """Adds Quadratic constraint to the model's Gurobi/Cplex Interface.
        (x-mu).T @ inv(cov) @ (x-mu) <= chi-square
        Note: This one creates one ellipsoidal constraint for all the metabolites that has non zero or non 'nan' formation energy, irrespective of the magnitude of variance. if the model is infeasible after adding this constraint, refer to util_func.py, find_correlated metabolites to add different ellipsoidal constraints to high variance and normal compounds to avoid possible numerical issues.

        Unable to retrieve quadratic constraints in Gurobi model, can see the QC when printed.

        :raises NotImplementedError: Implemented only for Gurobi/Cplex interfaces.
        :return: [description]
        :rtype: [type]
        """

        # Pick indices of components present in the current model
        model_component_indices = [
            i for i in range(self.compound_vector_matrix.shape[1])
            if np.any(self.compound_vector_matrix[:, i])
        ]

        # Reduced the compound_vector to contain only the non zero entries
        model_compound_vector = self.compound_vector_matrix[:,
                                                            model_component_indices]

        # Now extract the sub covariance matrix containing only the components present in the model
        component_model_covariance = covariance[:, model_component_indices][
            model_component_indices, :]

        # Now separate the compounds that have variance > 1000 and others to avoid numerical issues
        high_variance_indices = np.where(
            np.diag(component_model_covariance) > 1000)[0]
        low_variance_indices = np.where(
            np.diag(component_model_covariance) < 1000)[0]

        # Calculate cholesky matrix for two different covariance matrices
        if len(low_variance_indices) > 0:
            small_component_covariance = component_model_covariance[:, low_variance_indices][
                low_variance_indices, :]
            cholesky_small_variance = matrix_decomposition(
                small_component_covariance)
            chi2_value_small = stats.chi2.isf(
                q=0.05, df=cholesky_small_variance.shape[1]
            )  # Chi-square value to map confidence interval

            for i in high_variance_indices:
                zeros_axis = np.zeros((cholesky_small_variance.shape[1], ))
                cholesky_small_variance = np.insert(cholesky_small_variance,
                                                    i,
                                                    zeros_axis,
                                                    axis=0)

            metabolite_sphere_small = (
                model_compound_vector @ cholesky_small_variance
            )  # This is a fixed term compound_vector @ cholesky

        if len(high_variance_indices) > 0:
            large_component_covariance = component_model_covariance[:, high_variance_indices][
                high_variance_indices, :]  # Covariance matrix for the high variance components

            cholesky_large_variance = matrix_decomposition(
                large_component_covariance)
            chi2_value_high = stats.chi2.isf(
                q=0.05, df=cholesky_large_variance.shape[1])

            # Insert empty rows for the low_variance_components
            for i in low_variance_indices:
                zeros_axis = np.zeros((cholesky_large_variance.shape[1], ))
                cholesky_large_variance = np.insert(cholesky_large_variance,
                                                    i,
                                                    zeros_axis,
                                                    axis=0)
            metabolite_sphere_large = (
                model_compound_vector @ cholesky_large_variance
            )  # This is a fixed term compound_vector @ cholesky

        proton_indices = [
            self.metabolites.index(metabolite)
            for metabolite in self.metabolites
            if metabolite.equilibrator_accession is not None
            if metabolite.equilibrator_accession.inchi_key == PROTON_INCHI_KEY
        ]  # Get indices of protons in metabolite list to avoid double correcting them for concentrations

        if self.solver.__class__.__module__ == "optlang.cplex_interface":

            from cplex import Cplex, SparsePair, SparseTriple

            # Instantiate Cplex model
            cplex_model = Cplex()

            rand_str = "".join(
                choices(string.ascii_lowercase + string.digits, k=6))
            # write cplex model to mps file in random directory and re read
            with tempfile.TemporaryDirectory() as td:
                temp_filename = os.path.join(td, rand_str + ".mps")
                self.solver.problem.write(temp_filename)
                cplex_model.read(temp_filename)

            # Stop printing output in cplex
            cplex_model.set_log_stream(None)
            cplex_model.set_error_stream(None)
            cplex_model.set_warning_stream(None)
            cplex_model.set_results_stream(None)

            # Remove the unnecessary variables and constraints
            remove_vars = [
                var for var in cplex_model.variables.get_names()
                if var.startswith("component_") or var.startswith("dG_err_")
            ]  # Remove error variables

            remove_constrs = [
                cons for cons in cplex_model.linear_constraints.get_names()
                if cons.startswith("delG_") or cons.startswith("std_dev_")
            ]  # Remove delG constraint and re-add with component variables

            cplex_model.linear_constraints.delete(
                remove_constrs)  # Removing constr
            cplex_model.variables.delete(remove_vars)  # Removing Vars

            # QC for small variance components
            if len(low_variance_indices) > 0:
                indices_sphere1 = cplex_model.variables.add(
                    names=[
                        "Sphere1_{}".format(i)
                        for i in range(cholesky_small_variance.shape[1])
                    ],
                    lb=[-1] * cholesky_small_variance.shape[1],
                    ub=[1] * cholesky_small_variance.shape[1],
                )  # Adding independent component variables to the model, store the variable indices

                # Add the Sphere constraint
                cplex_model.quadratic_constraints.add(
                    quad_expr=SparseTriple(
                        ind1=indices_sphere1,
                        ind2=indices_sphere1,
                        val=len(indices_sphere1) * [1],
                    ),
                    sense="L",
                    rhs=1,
                    name="unit_normal_small_variance",
                )
            else:
                indices_sphere1 = [
                ]  # Just to adjust the matrix dimensions later

            # QC for large variance components
            if len(high_variance_indices) > 0:
                indices_sphere2 = cplex_model.variables.add(
                    names=[
                        "Sphere2_{}".format(i)
                        for i in range(cholesky_large_variance.shape[1])
                    ],
                    lb=[-1] * cholesky_large_variance.shape[1],
                    ub=[1] * cholesky_large_variance.shape[1],
                )  # Independent large variance components

                cplex_model.quadratic_constraints.add(
                    quad_expr=SparseTriple(
                        ind1=indices_sphere2,
                        ind2=indices_sphere2,
                        val=len(indices_sphere2) * [1],
                    ),
                    rhs=1,
                    sense="L",
                    name="unit_normal_high_variance",
                )
            else:
                indices_sphere2 = []  # Balancing matrix dimensions

            concentration_variables = [
                "lnc_{}".format(metabolite.id)
                for metabolite in self.metabolites
            ]

            # Add the delG constraints
            for reaction in self.reactions:
                if reaction.id in self.Exclude_reactions:
                    continue
                rxn_stoichiometry = reaction.cal_stoichiometric_matrix()
                rxn_stoichiometry = rxn_stoichiometry[np.newaxis, :]

                if len(low_variance_indices) > 0:
                    coefficient_matrix_small_variance = (
                        np.sqrt(chi2_value_small) *
                        rxn_stoichiometry @ metabolite_sphere_small
                    )  # Coefficient array for small variance ellipsoid
                else:
                    coefficient_matrix_small_variance = np.array(())

                if len(high_variance_indices) > 0:
                    coefficient_matrix_large_variance = (
                        np.sqrt(chi2_value_high) *
                        rxn_stoichiometry @ metabolite_sphere_large
                    )  # Coefficient array for large variance ellipsoid
                else:
                    coefficient_matrix_large_variance = np.array(())

                concentration_coefficients = RT * rxn_stoichiometry
                concentration_coefficients[0, proton_indices] = 0

                coefficients_forward = np.hstack((
                    np.array((1)),
                    -1 * concentration_coefficients.flatten(),
                    -1 * coefficient_matrix_small_variance.flatten(),
                    -1 * coefficient_matrix_large_variance.flatten(),
                ))

                coefficients_reverse = np.hstack((
                    np.array((1)),
                    concentration_coefficients.flatten(),
                    coefficient_matrix_small_variance.flatten(),
                    coefficient_matrix_large_variance.flatten(),
                ))

                variable_order_forward = (
                    ["dG_{}".format(reaction.forward_variable.name)] +
                    concentration_variables + list(indices_sphere1) +
                    list(indices_sphere2))
                variable_order_reverse = (
                    ["dG_{}".format(reaction.reverse_variable.name)] +
                    concentration_variables + list(indices_sphere1) +
                    list(indices_sphere2))

                rhs = reaction.delG_prime + reaction.delG_transport

                cplex_model.linear_constraints.add(
                    lin_expr=[
                        SparsePair(
                            ind=variable_order_forward,
                            val=coefficients_forward.tolist(),
                        )
                    ],
                    senses=["E"],
                    rhs=[rhs],
                    names=["delG_{}".format(reaction.forward_variable.name)],
                )  # delG constraint for forward reaction

                cplex_model.linear_constraints.add(
                    lin_expr=[
                        SparsePair(
                            ind=variable_order_reverse,
                            val=coefficients_reverse.tolist(),
                        )
                    ],
                    senses=["E"],
                    rhs=[-rhs],
                    names=["delG_{}".format(reaction.reverse_variable.name)],
                )  # delG constraint for reverse reaction

            return cplex_model

        elif self.solver.__class__.__module__ == "optlang.gurobi_interface":
            from gurobipy import GRB, LinExpr

            gurobi_model = self.solver.problem.copy()

            # Remove unnecessary variables and constraints and rebuild  appropriate ones
            remove_vars = [
                var for var in gurobi_model.getVars()
                if var.VarName.startswith("component_")
                or var.VarName.startswith("dG_err_")
            ]

            remove_constrs = [
                cons for cons in gurobi_model.getConstrs()
                if cons.ConstrName.startswith("delG_")
                or cons.ConstrName.startswith("std_dev_")
            ]

            gurobi_model.remove(remove_constrs + remove_vars)

            # Add sphere variables for smaller set and larger set separately
            if len(low_variance_indices) > 0:
                for i in range(cholesky_small_variance.shape[1]):
                    gurobi_model.addVar(lb=-1,
                                        ub=1,
                                        name="Sphere1_{}".format(i))

                gurobi_model.update()
                sphere1_variables = [
                    var for var in gurobi_model.getVars()
                    if var.VarName.startswith("Sphere1_")
                ]

                gurobi_model.addQConstr(
                    np.sum(np.square(np.array(sphere1_variables))) <= 1,
                    name="unit_normal_small_variance",
                )
                gurobi_model.update()
            else:
                sphere1_variables = []

            # QC for large variance components
            if len(high_variance_indices) > 0:
                for i in range(cholesky_large_variance.shape[1]):
                    gurobi_model.addVar(lb=-1,
                                        ub=1,
                                        name="Sphere2_{}".format(i))

                gurobi_model.update()
                sphere2_variables = [
                    var for var in gurobi_model.getVars()
                    if var.VarName.startswith("Sphere2_")
                ]

                gurobi_model.addQConstr(
                    np.sum(np.square(np.array(sphere2_variables))) <= 1,
                    name="unit_normal_high_variance",
                )
                gurobi_model.update()
            else:
                sphere2_variables = []

            # Create a list of metabolite concentration variables
            concentration_variables = []
            for metabolite in self.metabolites:
                varname = "lnc_{}".format(metabolite.id)
                conc_var = gurobi_model.getVarByName(varname)
                concentration_variables.append(conc_var)

            # Add the delG constraints
            for reaction in self.reactions:
                if reaction.id in self.Exclude_reactions:
                    continue
                rxn_stoichiometry = reaction.cal_stoichiometric_matrix()
                rxn_stoichiometry = rxn_stoichiometry[np.newaxis, :]

                if len(low_variance_indices) > 0:
                    coefficient_matrix_small_variance = (
                        np.sqrt(chi2_value_small) *
                        rxn_stoichiometry @ metabolite_sphere_small
                    )  # Coefficient array for small variance ellipsoid
                else:
                    coefficient_matrix_small_variance = np.array(())

                if len(high_variance_indices) > 0:
                    coefficient_matrix_large_variance = (
                        np.sqrt(chi2_value_high) *
                        rxn_stoichiometry @ metabolite_sphere_large
                    )  # Coefficient array for large variance ellipsoid
                else:
                    coefficient_matrix_large_variance = np.array(())

                concentration_coefficients = RT * rxn_stoichiometry
                concentration_coefficients[0, proton_indices] = 0

                coefficients_forward = np.hstack((
                    -1 * concentration_coefficients.flatten(),
                    -1 * coefficient_matrix_small_variance.flatten(),
                    -1 * coefficient_matrix_large_variance.flatten(),
                ))

                coefficients_reverse = np.hstack((
                    concentration_coefficients.flatten(),
                    coefficient_matrix_small_variance.flatten(),
                    coefficient_matrix_large_variance.flatten(),
                ))

                variable_order = (concentration_variables + sphere1_variables +
                                  sphere2_variables)

                delG_err_forward = LinExpr(coefficients_forward.tolist(),
                                           variable_order)
                delG_err_reverse = LinExpr(coefficients_reverse.tolist(),
                                           variable_order)

                delG_for_var = gurobi_model.getVarByName("dG_{}".format(
                    reaction.forward_variable.name))
                delG_rev_var = gurobi_model.getVarByName("dG_{}".format(
                    reaction.reverse_variable.name))
                rhs = reaction.delG_prime + reaction.delG_transport

                gurobi_model.addConstr(
                    delG_for_var + delG_err_forward,
                    GRB.EQUAL,
                    rhs,
                    name="delG_{}".format(reaction.forward_variable.name),
                )

                gurobi_model.addConstr(
                    delG_rev_var + delG_err_reverse,
                    GRB.EQUAL,
                    -rhs,
                    name="delG_{}".format(reaction.reverse_variable.name),
                )

            gurobi_model.update()

            return gurobi_model

        else:
            raise NotImplementedError("Current solver doesn't support QC")
            logging.error(
                "Current solver doesnt support problesm of type MIQC")
Пример #8
0
    def solve(self, problem: OptimizationProblem) -> OptimizationResult:
        """Tries to solves the given problem using the recursive optimizer.

        Runs the optimizer to try to solve the optimization problem.

        Args:
            problem: The problem to be solved.

        Returns:
            The result of the optimizer applied to the problem.

        """

        # convert problem to QUBO
        qubo_converter = OptimizationProblemToQubo()
        problem_ = qubo_converter.encode(problem)
        problem_ref = deepcopy(problem_)

        # run recursive optimization until the resulting problem is small enough
        replacements = {}
        while problem_.variables.get_num() > self._min_num_vars:

            # solve current problem with optimizer
            result = self._min_eigen_optimizer.solve(problem_)
            samples = result.samples

            # analyze results to get strongest correlation
            states = [v[0] for v in samples]
            probs = [v[2] for v in samples]
            correlations = self._construct_correlations(states, probs)
            i, j = self._find_strongest_correlation(correlations)

            x_i = problem_.variables.get_names(i)
            x_j = problem_.variables.get_names(j)
            if correlations[i, j] > 0:
                # set x_i = x_j
                problem_ = problem_.substitute_variables(
                    variables=SparseTriple([i], [j], [1]))
                replacements[x_i] = (x_j, 1)
            else:
                # set x_i = 1 - x_j, this is done in two steps:
                # 1. set x_i = 1 + x_i
                # 2. set x_i = -x_j

                # 1a. get additional offset
                offset = problem_.objective.get_offset()
                offset += problem_.objective.get_quadratic_coefficients(i,
                                                                        i) / 2
                offset += problem_.objective.get_linear(i)
                problem_.objective.set_offset(offset)

                # 1b. get additional linear part
                for k in range(problem_.variables.get_num()):
                    coeff = problem_.objective.get_quadratic_coefficients(i, k)
                    if np.abs(coeff) > 1e-10:
                        coeff += problem_.objective.get_linear(k)
                        problem_.objective.set_linear(k, coeff)

                # 2. replace x_i by -x_j
                problem_ = problem_.substitute_variables(
                    variables=SparseTriple([i], [j], [-1]))
                replacements[x_i] = (x_j, -1)

        # solve remaining problem
        result = self._min_num_vars_optimizer.solve(problem_)

        # unroll replacements
        var_values = {}
        for i, name in enumerate(problem_.variables.get_names()):
            var_values[name] = result.x[i]

        def find_value(x, replacements, var_values):
            if x in var_values:
                # if value for variable is known, return it
                return var_values[x]
            elif x in replacements:
                # get replacement for variable
                (y, sgn) = replacements[x]
                # find details for replacing variable
                value = find_value(y, replacements, var_values)
                # construct, set, and return new value
                var_values[x] = value if sgn == 1 else 1 - value
                return var_values[x]
            else:
                raise QiskitOptimizationError('Invalid values!')

        # loop over all variables to set their values
        for x_i in problem_ref.variables.get_names():
            if x_i not in var_values:
                find_value(x_i, replacements, var_values)

        # construct result
        x = [var_values[name] for name in problem_ref.variables.get_names()]
        fval = result.fval
        results = OptimizationResult(x, fval, (replacements, qubo_converter))
        results = qubo_converter.decode(results)
        return results