コード例 #1
0
ファイル: generation.py プロジェクト: uxvrob/penaltymodel
def generate_ising(graph, feasible_configurations, decision_variables,
                   linear_energy_ranges, quadratic_energy_ranges,
                   smt_solver_name):
    """Generates the Ising model that induces the given feasible configurations.

    Args:
        graph (nx.Graph): The target graph on which the Ising model is to be built.
        feasible_configurations (dict): The set of feasible configurations
            of the decision variables. The key is a feasible configuration
            as a tuple of spins, the values are the associated energy.
        decision_variables (list/tuple): Which variables in the graph are
            assigned as decision variables.
        linear_energy_ranges (dict, optional): A dict of the form
            {v: (min, max, ...} where min and max are the range
            of values allowed to v.
        quadratic_energy_ranges (dict): A dict of the form
            {(u, v): (min, max), ...} where min and max are the range
            of values allowed to (u, v).
        smt_solver_name (str/None): The name of the smt solver. Must
            be a solver available to pysmt. If None, uses the pysmt default.

    Returns:
        tuple: A 4-tuple contiaing:

            dict: The linear biases of the Ising problem.

            dict: The quadratic biases of the Ising problem.

            float: The ground energy of the Ising problem.

            float: The classical energy gap between ground and the first
            excited state.

    Raises:
        ImpossiblePenaltyModel: If the penalty model cannot be built. Normally due
            to a non-zero infeasible gap.

    """
    # we need to build a Table. The table encodes all of the information used by the smt solver
    table = Table(graph, decision_variables, linear_energy_ranges, quadratic_energy_ranges)

    # iterate over every possible configuration of the decision variables.
    for config in itertools.product((-1, 1), repeat=len(decision_variables)):

        # determine the spin associated with each varaible in decision variables.
        spins = dict(zip(decision_variables, config))

        if config in feasible_configurations:
            # if the configuration is feasible, we require that the mininum energy over all
            # possible aux variable settings be exactly its target energy (given by the value)
            table.set_energy(spins, feasible_configurations[config])
        else:
            # if the configuration is infeasible, we simply want its minimum energy over all
            # possible aux variable settings to be an upper bound on the classical gap.
            table.set_energy_upperbound(spins)

    # now we just need to get a solver
    with Solver(smt_solver_name) as solver:

        # add all of the assertions from the table to the solver
        for assertion in table.assertions:
            solver.add_assertion(assertion)

        # check if the model is feasible at all.
        if solver.solve():

            # we want to increase the gap until we have found the max classical gap
            gmin = 0
            gmax = sum(max(abs(r) for r in linear_energy_ranges[v]) for v in graph)
            gmax += sum(max(abs(r) for r in quadratic_energy_ranges[(u, v)])
                        for (u, v) in graph.edges)

            # 2 is a good target gap
            g = 2.

            while abs(gmax - gmin) >= .01:
                solver.push()

                gap_assertion = table.gap_bound_assertion(g)
                solver.add_assertion(gap_assertion)

                if solver.solve():
                    model = solver.get_model()
                    gmin = float(model.get_py_value(table.gap).limit_denominator())
                else:
                    solver.pop()
                    gmax = g

                g = min(gmin + .1, (gmax + gmin) / 2)

        else:
            raise pm.ImpossiblePenaltyModel("Model cannot be built")

    # finally we need to convert our values back into python floats.
    # we use limit_denominator to deal with some of the rounding
    # issues.
    theta = table.theta
    linear = {v: float(model.get_py_value(bias).limit_denominator())
              for v, bias in iteritems(theta.linear)}
    quadratic = {(u, v): float(model.get_py_value(bias).limit_denominator())
                 for (u, v), bias in iteritems(theta.quadratic)}
    ground_energy = -float(model.get_py_value(theta.offset).limit_denominator())
    classical_gap = float(model.get_py_value(table.gap).limit_denominator())

    return linear, quadratic, ground_energy, classical_gap
コード例 #2
0
ファイル: generation.py プロジェクト: cnxtech/penaltymodel
def _generate_ising(graph, table, decision, min_classical_gap, linear_energy_ranges,
                    quadratic_energy_ranges):

    if not table:
        # if there are no feasible configurations then the gap is 0 and the model is empty
        h = {v: 0.0 for v in graph.nodes}
        J = {edge: 0.0 for edge in graph.edges}
        offset = 0.0
        gap = 0.0
        return h, J, offset, gap, {}

    auxiliary = [v for v in graph if v not in decision]
    variables = decision + auxiliary

    solver = pywraplp.Solver('SolveIntegerProblem', pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)

    h = {v: solver.NumVar(linear_energy_ranges[v][0], linear_energy_ranges[v][1], 'h_%s' % v)
         for v in graph.nodes}

    J = {}
    for u, v in graph.edges:
        if (u, v) in quadratic_energy_ranges:
            low, high = quadratic_energy_ranges[(u, v)]
        else:
            low, high = quadratic_energy_ranges[(v, u)]
        J[(u, v)] = solver.NumVar(low, high, 'J_%s,%s' % (u, v))

    offset = solver.NumVar(-solver.infinity(), solver.infinity(), 'offset')

    gap = solver.NumVar(min_classical_gap, solver.infinity(), 'classical_gap')

    # Let x, a be the decision, auxiliary variables respectively
    # Let E(x, a) be the energy of x and a
    # Let F be the feasible configurations of x
    # Let g be the classical gap
    # Let a*(x) be argmin_a E(x, a) - the config of aux variables that minimizes the energy with x fixed

    # We want:
    #   E(x, a) >= target_energy  forall x in F, forall a
    #   E(x, a) - g >= highest_target_energy  forall x not in F, forall a
    highest_target_energy = max(table.values()) if isinstance(table, dict) else 0

    for config in itertools.product((-1, 1), repeat=len(variables)):
        spins = dict(zip(variables, config))

        decision_config = tuple(spins[v] for v in decision)

        target_energy = table.get(decision_config, highest_target_energy)

        # the E(x, a) term
        coefficients = {bias: spins[v] for v, bias in h.items()}
        coefficients.update({bias: spins[u] * spins[v] for (u, v), bias in J.items()})
        coefficients[offset] = 1

        if decision_config not in table:
            # we want energy greater than gap for decision configs not in feasible
            coefficients[gap] = -1

        const = solver.Constraint(target_energy, solver.infinity())
        for var, coef in coefficients.items():
            const.SetCoefficient(var, coef)

    if not auxiliary:
        # We have no auxiliary variables. We want:
        #   E(x) <= target_energy forall x in F
        for decision_config, target_energy in table.items():
            spins = dict(zip(decision, decision_config))

            # the E(x, a) term
            coefficients = {bias: spins[v] for v, bias in h.items()}
            coefficients.update({bias: spins[u] * spins[v] for (u, v), bias in J.items()})
            coefficients[offset] = 1

            const = solver.Constraint(-solver.infinity(), target_energy)
            for var, coef in coefficients.items():
                const.SetCoefficient(var, coef)

    else:
        # We have auxiliary variables. So that each feasible config has at least one ground we want:
        #   E(x, a) - 100*|| a - a*(x) || <= target_energy  forall x in F, forall a

        # we need a*(x) forall x in F
        a_star = {config: {v: solver.IntVar(0, 1, 'a*(%s)_%s' % (config, v)) for v in auxiliary} for config in table}

        for decision_config, target_energy in table.items():

            for aux_config in itertools.product((-1, 1), repeat=len(variables) - len(decision)):
                spins = dict(zip(variables, decision_config+aux_config))

                ub = target_energy

                # the E(x, a) term
                coefficients = {bias: spins[v] for v, bias in h.items()}
                coefficients.update({bias: spins[u] * spins[v] for (u, v), bias in J.items()})
                coefficients[offset] = 1

                # # the -100*|| a - a*(x) || term
                for v in auxiliary:
                    # we don't have absolute value, so we check what a is and order the subtraction accordingly
                    if spins[v] == -1:
                        # a*(x)_v - a_v
                        coefficients[a_star[decision_config][v]] = -200
                    else:
                        # a_v - a*(x)_v
                        assert spins[v] == 1  # sanity check
                        coefficients[a_star[decision_config][v]] = +200
                        ub += 200

                const = solver.Constraint(-solver.infinity(), ub)
                for var, coef in coefficients.items():
                    const.SetCoefficient(var, coef)

        # without loss of generality we can fix the auxiliary variables associated with
        # one of the feasible configurations. Do so randomly.
        for var in next(iter(a_star.values())).values():
            val = random.randint(0, 1)
            const = solver.Constraint(val, val)  # equality constraint
            const.SetCoefficient(var, 1)

    if auxiliary or len(table) != 2**len(decision):
        objective = solver.Objective()
        objective.SetCoefficient(gap, 1)
        objective.SetMaximization()
        _inf_gap = False
    else:
        _inf_gap = True

    # run solver
    result_status = solver.Solve()

    if result_status not in [solver.OPTIMAL, solver.FEASIBLE]:
        raise pm.ImpossiblePenaltyModel("No solution was found")

    # read everything back into floats
    h = {v: bias.solution_value() for v, bias in h.items()}
    J = {(u, v): bias.solution_value() for (u, v), bias in J.items()}
    offset = offset.solution_value()
    gap = float('inf') if _inf_gap else gap.solution_value()

    if not gap:
        raise pm.ImpossiblePenaltyModel("No positive gap can be found for the given model")

    if auxiliary:
        aux_configs = {config: {v: val.solution_value()*2 - 1 for v, val in a_star[config].items()}
                       for config in table}
    else:
        aux_configs = {config: dict() for config in table}

    return h, J, offset, gap, aux_configs