Exemplo n.º 1
0
 def convert_to_ising(self):
     """Transform a QUBO problem into an Ising problem.  Return the new
     Ising problem."""
     if not self.qubo:
         raise TypeError("Can convert only QUBO problems to Ising problems")
     new_obj = copy.deepcopy(self)
     qmatrix = {(q, q): w for q, w in new_obj.weights.items()}
     qmatrix.update(new_obj.strengths)
     hvals, new_obj.strengths, qoffset = qubo_to_ising(qmatrix)
     new_obj.strengths = qmasm.canonicalize_strengths(new_obj.strengths)
     new_obj.weights = {i: hvals[i] for i in range(len(hvals))}
     new_obj.offset = qoffset
     new_obj.qubo = False
     return new_obj
Exemplo n.º 2
0
 def convert_to_ising(self):
     """Transform a QUBO problem into an Ising problem.  Return the new
     Ising problem."""
     if not self.qubo:
         raise TypeError("Can convert only QUBO problems to Ising problems")
     new_obj = copy.deepcopy(self)
     qmatrix = {(q, q): w for q, w in new_obj.weights.items()}
     qmatrix.update(new_obj.strengths)
     hvals, new_obj.strengths, qoffset = qubo_to_ising(qmatrix)
     new_obj.strengths = qmasm.canonicalize_strengths(new_obj.strengths)
     new_obj.weights = {i: hvals[i] for i in range(len(hvals))}
     new_obj.offset = qoffset
     new_obj.qubo = False
     return new_obj
Exemplo n.º 3
0
 def convert_to_qubo(self):
     """Transform an Ising problem into a QUBO problem.  Return the new
     QUBO problem."""
     if self.qubo:
         raise TypeError("Can convert only Ising problems to QUBO problems")
     new_obj = copy.deepcopy(self)
     qmatrix, qoffset = ising_to_qubo(qmasm.dict_to_list(self.weights), self.strengths)
     new_obj.offset = qoffset
     new_obj.weights = defaultdict(lambda: 0.0,
                                   {q1: wt
                                    for (q1, q2), wt in qmatrix.items()
                                    if q1 == q2})
     new_obj.strengths = qmasm.canonicalize_strengths({(q1, q2): wt
                                                       for (q1, q2), wt in qmatrix.items()
                                                       if q1 != q2})
     new_obj.qubo = True
     return new_obj
Exemplo n.º 4
0
 def convert_to_qubo(self):
     """Transform an Ising problem into a QUBO problem.  Return the new
     QUBO problem."""
     if self.qubo:
         raise TypeError("Can convert only Ising problems to QUBO problems")
     new_obj = copy.deepcopy(self)
     qmatrix, qoffset = ising_to_qubo(qmasm.dict_to_list(self.weights),
                                      self.strengths)
     new_obj.offset = qoffset
     new_obj.weights = defaultdict(
         lambda: 0.0,
         {q1: wt
          for (q1, q2), wt in qmatrix.items() if q1 == q2})
     new_obj.strengths = qmasm.canonicalize_strengths({
         (q1, q2): wt
         for (q1, q2), wt in qmatrix.items() if q1 != q2
     })
     new_obj.qubo = True
     return new_obj
Exemplo n.º 5
0
def output_bqpjson(outfile, as_qubo, problem):
    "Output weights and strengths in bqpjson format, either Ising or QUBO."
    # Prepare the "easy" fields.
    bqp = {}
    bqp["version"] = "1.0.0"
    bqp["id"] = random.randint(2**20, 2**60)
    bqp["scale"] = 1.0
    bqp["offset"] = 0.0
    if as_qubo:
        bqp["variable_domain"] = "boolean"
    else:
        bqp["variable_domain"] = "spin"

    # Prepare the list of all variables.
    var_ids = set(problem.weights.keys())
    for q1, q2 in problem.strengths.keys():
        var_ids.add(q1)
        var_ids.add(q2)
    bqp["variable_ids"] = sorted(var_ids)

    # Prepare the linear terms.
    lin_terms = []
    for q, wt in sorted(problem.weights.items()):
        lin_terms.append({
            "id": q,
            "coeff": wt})
    bqp["linear_terms"] = lin_terms

    # Prepare the quadratic terms.
    quad_terms = []
    strengths = qmasm.canonicalize_strengths(problem.strengths)
    for (q1, q2), wt in sorted(strengths.items()):
        quad_terms.append({
            "id_tail": q1,
            "id_head": q2,
            "coeff": wt})
    bqp["quadratic_terms"] = quad_terms

    # Prepare some metadata.
    metadata = {}
    if as_qubo:
        metadata["description"] = "QUBO problem compiled by QMASM (https://github.com/lanl/qmasm)"
    else:
        metadata["description"] = "Ising problem compiled by QMASM (https://github.com/lanl/qmasm)"
    metadata["command_line"] = qmasm.get_command_line()
    metadata["generated"] = datetime.datetime.utcnow().isoformat()
    if hasattr(problem, "embedding"):
        # Physical problem
        def attempt_assign(key, func):
            "Try assigning a key, but don't complain if we can't."
            try:
                metadata[key] = func()
            except KeyError:
                pass
        attempt_assign("dw_url", lambda: os.environ["DW_INTERNAL__HTTPLINK"])
        attempt_assign("dw_solver_name", lambda: qmasm.solver_name)
        props = qmasm.solver.properties
        attempt_assign("dw_chip_id", lambda: props["chip_id"])
        L, M, N = qmasm.chimera_topology(qmasm.solver)
        metadata["chimera_cell_size"] = L*2
        metadata["chimera_degree"] = max(M, N)
        metadata["equivalent_ids"] = sorted(problem.chains)
        metadata["variable_names"] = {s: problem.embedding[n]
                                      for s, n in qmasm.sym_map.symbol_number_items()}
    else:
        metadata["variable_names"] = {s: [n]
                                      for s, n in qmasm.sym_map.symbol_number_items()}
    bqp["metadata"] = metadata

    # Output the problem in JSON format.
    outfile.write(json.dumps(bqp, indent=2, sort_keys=True) + "\n")
Exemplo n.º 6
0
def simplify_problem(logical, verbosity):
    """Try to find spins that can be removed from the problem because their
    value is known a priori."""
    # SAPI's fix_variables function works only on QUBOs so we have to convert.
    # We directly use SAPI's ising_to_qubo function instead of our own
    # convert_to_qubo because the QUBO has to be in matrix form.
    hs = qmasm.dict_to_list(logical.weights)
    Js = logical.strengths
    Q, qubo_offset = ising_to_qubo(hs, Js)

    # Simplify the problem if possible.
    simple = fix_variables(Q, method="optimized")
    fixed_vars = simple["fixed_variables"]

    # At high verbosity levels, list all of the known symbols and their value.
    if verbosity >= 2:
        # Map each logical qubit to one or more symbols.
        num2syms = [[] for _ in range(len(qmasm.sym2num))]
        max_sym_name_len = 7
        for q, n in qmasm.sym2num.items():
            num2syms[n].append(q)
            max_sym_name_len = max(max_sym_name_len,
                                   len(repr(num2syms[n])) - 1)

        # Output a table of know values
        sys.stderr.write(
            "Elided qubits whose low-energy value can be determined a priori:\n\n"
        )
        if len(fixed_vars) > 0:
            sys.stderr.write("    Logical  %-*s  Value\n" %
                             (max_sym_name_len, "Name(s)"))
            sys.stderr.write("    -------  %s  -----\n" %
                             ("-" * max_sym_name_len))
            truval = {0: "False", +1: "True"}
            for q, b in sorted(fixed_vars.items()):
                if num2syms[q] == []:
                    continue
                name_list = " ".join(sorted(num2syms[q]))
                sys.stderr.write("    %7d  %-*s  %-s\n" %
                                 (q, max_sym_name_len, name_list, truval[b]))
            sys.stderr.write("\n")

    # Return the original problem if no qubits could be elided.
    if verbosity >= 2:
        sys.stderr.write("  %6d logical qubits before elision\n" %
                         (qmasm.next_sym_num + 1))
    if len(fixed_vars) == 0:
        if verbosity >= 2:
            sys.stderr.write("  %6d logical qubits after elision\n\n" %
                             (qmasm.next_sym_num + 1))
        return logical

    # Construct a simplified problem, renumbering so as to compact qubit
    # numbers.
    new_obj = copy.deepcopy(logical)
    new_obj.known_values = {
        s: 2 * fixed_vars[n] - 1
        for s, n in qmasm.sym2num.items() if n in fixed_vars
    }
    new_obj.simple_offset = simple["offset"]
    hs, Js, ising_offset = qubo_to_ising(simple["new_Q"])
    qubits_used = set([i for i in range(len(hs)) if hs[i] != 0.0])
    for q1, q2 in Js.keys():
        qubits_used.add(q1)
        qubits_used.add(q2)
    qmap = dict(zip(sorted(qubits_used), range(len(qubits_used))))
    new_obj.chains = {(qmap[q1], qmap[q2]): None
                      for q1, q2 in new_obj.chains.keys()
                      if q1 in qmap and q2 in qmap}
    new_obj.weights = defaultdict(
        lambda: 0.0, {qmap[i]: hs[i]
                      for i in range(len(hs)) if hs[i] != 0.0})
    new_obj.strengths = qmasm.canonicalize_strengths({
        (qmap[q1], qmap[q2]): wt
        for (q1, q2), wt in Js.items()
    })
    new_obj.pinned = [(qmap[q], b) for q, b in new_obj.pinned if q in qmap]
    qmasm.sym2num = {s: qmap[q] for s, q in qmasm.sym2num.items() if q in qmap}
    try:
        qmasm.next_sym_num = max(qmasm.sym2num.values())
    except ValueError:
        qmasm.next_sym_num = -1
    if verbosity >= 2:
        sys.stderr.write("  %6d logical qubits after elision\n\n" %
                         (qmasm.next_sym_num + 1))
    return new_obj
Exemplo n.º 7
0
    def convert_chains_to_aliases(self):
        "Replace user-specified chains with aliases."

        # Group qubits that can be aliased.
        num2alias = {
        }  # Map from a qubit number to a disjoint set (which maps to a qubit number)
        for q1, q2 in self.chains:
            if q1 not in num2alias:
                num2alias[q1] = DisjointSet(q1)
            if q2 not in num2alias:
                num2alias[q2] = DisjointSet(q2)
            num2alias[q1].union(num2alias[q2])

        # Regenerate our chains, discarding any that have been merged into a
        # single qubit.
        new_chains = set()
        for q1, q2 in self.chains:
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_chains.add((new_q1, new_q2))
        self.chains = new_chains

        # Regenerate our anti-chains, discarding any that have been merged into
        # a single qubit.
        new_antichains = set()
        for q1, q2 in self.antichains:
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_antichains.add((new_q1, new_q2))
        self.antichains = new_antichains

        # Regenerate our weights.
        new_weights = defaultdict(lambda: 0.0)
        for q, wt in self.weights.items():
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_weights[new_q] += wt
        self.weights = new_weights

        # Regenerate our strengths.
        new_strengths = defaultdict(lambda: 0.0)
        for (q1, q2), wt in self.strengths.items():
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_strengths[(new_q1, new_q2)] += wt
        self.strengths = new_strengths

        # Regenerate our pinned values.
        new_pinned = {}
        for q, b in self.pinned:
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_pinned[new_q] = b
        self.pinned = sorted(new_pinned.items())

        # Regenerate the global symbol table.
        new_sym2num = {}
        for s, q in qmasm.sym_map.symbol_number_items():
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_sym2num[s] = new_q
        qmasm.sym_map.overwrite_with(new_sym2num)

        # Renumber all of the above to compact the qubit numbers.
        qubits_used = set([q.find().contents for q in num2alias.values()])
        qubits_used.update(self.weights.keys())
        for q1, q2 in self.strengths.keys():
            qubits_used.add(q1)
            qubits_used.add(q2)
        qmap = dict(zip(sorted(qubits_used), range(len(qubits_used))))
        self.chains = set([(qmap[q1], qmap[q2]) for q1, q2 in self.chains])
        self.antichains = set([(qmap[q1], qmap[q2])
                               for q1, q2 in self.antichains])
        self.weights = defaultdict(
            lambda: 0.0, {qmap[q]: wt
                          for q, wt in self.weights.items()})
        self.strengths = qmasm.canonicalize_strengths({
            (qmap[q1], qmap[q2]): wt
            for (q1, q2), wt in self.strengths.items()
        })
        self.pinned = [(qmap[q], b) for q, b in self.pinned]
        qmasm.sym_map.overwrite_with(
            {s: qmap[q]
             for s, q in qmasm.sym_map.symbol_number_items()})
Exemplo n.º 8
0
    def convert_chains_to_aliases(self):
        """Convert chains to aliases where possible.  A chain is
        convertible if the qubits on either end have the same point weight
        applied to them."""

        # Group qubits that can be aliased.
        num2alias = {
        }  # Map from a qubit number to a disjoint set (which maps to a qubit number)
        for q1, q2 in self.chains:
            if self.weights[q1] == self.weights[q2]:
                if q1 not in num2alias:
                    num2alias[q1] = DisjointSet(q1)
                if q2 not in num2alias:
                    num2alias[q2] = DisjointSet(q2)
                num2alias[q1].union(num2alias[q2])

        # Regenerate our chains, discarding any that have been merged into a
        # single qubit.
        new_chains = {}
        for q1, q2 in self.chains:
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_chains[(new_q1, new_q2)] = None
        self.chains = new_chains

        # Regenerate our weights.
        new_weights = defaultdict(lambda: 0.0)
        for q, wt in self.weights.items():
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_weights[new_q] += wt
        self.weights = new_weights

        # Regenerate our strengths.
        new_strengths = defaultdict(lambda: 0.0)
        for (q1, q2), wt in self.strengths.items():
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_strengths[(new_q1, new_q2)] += wt
        self.strengths = new_strengths

        # Regenerate our pinned values.
        new_pinned = {}
        for q, b in self.pinned:
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_pinned[new_q] = b
        self.pinned = sorted(new_pinned.items())

        # Regenerate the global symbol table.
        new_sym2num = {}
        for s, q in qmasm.sym2num.items():
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_sym2num[s] = new_q
        qmasm.sym2num = new_sym2num

        # Renumber all of the above to compact the qubit numbers.
        qubits_used = set(self.weights.keys())
        for q1, q2 in self.strengths.keys():
            qubits_used.add(q1)
            qubits_used.add(q2)
        qmap = dict(zip(sorted(qubits_used), range(len(qubits_used))))
        self.chains = {(qmap[q1], qmap[q2]): None
                       for q1, q2 in self.chains.keys()}
        self.weights = defaultdict(
            lambda: 0.0, {qmap[q]: wt
                          for q, wt in self.weights.items()})
        self.strengths = qmasm.canonicalize_strengths({
            (qmap[q1], qmap[q2]): wt
            for (q1, q2), wt in self.strengths.items()
        })
        self.pinned = [(qmap[q], b) for q, b in self.pinned]
        qmasm.sym2num = {s: qmap[q] for s, q in qmasm.sym2num.items()}
        qmasm.next_sym_num = max(qmasm.sym2num.values())
Exemplo n.º 9
0
def output_bqpjson(outfile, as_qubo, problem):
    "Output weights and strengths in bqpjson format, either Ising or QUBO."
    # Prepare the "easy" fields.
    bqp = {}
    bqp["version"] = "1.0.0"
    bqp["id"] = random.randint(2**20, 2**60)
    bqp["scale"] = 1.0
    bqp["offset"] = 0.0
    if as_qubo:
        bqp["variable_domain"] = "boolean"
    else:
        bqp["variable_domain"] = "spin"

    # Prepare the list of all variables.
    var_ids = set(problem.weights.keys())
    for q1, q2 in problem.strengths.keys():
        var_ids.add(q1)
        var_ids.add(q2)
    bqp["variable_ids"] = sorted(var_ids)

    # Prepare the linear terms.
    lin_terms = []
    for q, wt in sorted(problem.weights.items()):
        lin_terms.append({
            "id": q,
            "coeff": wt})
    bqp["linear_terms"] = lin_terms

    # Prepare the quadratic terms.
    quad_terms = []
    strengths = qmasm.canonicalize_strengths(problem.strengths)
    for (q1, q2), wt in sorted(strengths.items()):
        quad_terms.append({
            "id_tail": q1,
            "id_head": q2,
            "coeff": wt})
    bqp["quadratic_terms"] = quad_terms

    # Prepare some metadata.
    metadata = {}
    if as_qubo:
        metadata["description"] = "QUBO problem compiled by QMASM (https://github.com/lanl/qmasm)"
    else:
        metadata["description"] = "Ising problem compiled by QMASM (https://github.com/lanl/qmasm)"
    metadata["command_line"] = qmasm.get_command_line()
    metadata["generated"] = datetime.datetime.utcnow().isoformat()
    if hasattr(problem, "embedding"):
        # Physical problem
        def attempt_assign(key, func):
            "Try assigning a key, but don't complain if we can't."
            try:
                metadata[key] = func()
            except KeyError:
                pass
        attempt_assign("dw_url", lambda: os.environ["DW_INTERNAL__HTTPLINK"])
        attempt_assign("dw_solver_name", lambda: qmasm.solver_name)
        props = qmasm.solver.properties
        attempt_assign("dw_chip_id", lambda: props["chip_id"])
        L, M, N = qmasm.chimera_topology(qmasm.solver)
        metadata["chimera_cell_size"] = L*2
        metadata["chimera_degree"] = max(M, N)
        metadata["equivalent_ids"] = sorted(problem.chains)
        metadata["variable_names"] = {s: problem.embedding[n]
                                      for s, n in qmasm.sym_map.symbol_number_items()}
    else:
        metadata["variable_names"] = {s: [n]
                                      for s, n in qmasm.sym_map.symbol_number_items()}
    bqp["metadata"] = metadata

    # Output the problem in JSON format.
    outfile.write(json.dumps(bqp, indent=2, sort_keys=True) + "\n")
Exemplo n.º 10
0
    def convert_chains_to_aliases(self):
        "Replace user-specified chains with aliases."

        # Group qubits that can be aliased.
        num2alias = {}  # Map from a qubit number to a disjoint set (which maps to a qubit number)
        for q1, q2 in self.chains:
            if q1 not in num2alias:
                num2alias[q1] = DisjointSet(q1)
            if q2 not in num2alias:
                num2alias[q2] = DisjointSet(q2)
            num2alias[q1].union(num2alias[q2])

        # Regenerate our chains, discarding any that have been merged into a
        # single qubit.
        new_chains = set()
        for q1, q2 in self.chains:
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_chains.add((new_q1, new_q2))
        self.chains = new_chains

        # Regenerate our anti-chains, discarding any that have been merged into
        # a single qubit.
        new_antichains = set()
        for q1, q2 in self.antichains:
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_antichains.add((new_q1, new_q2))
        self.antichains = new_antichains

        # Regenerate our weights.
        new_weights = defaultdict(lambda: 0.0)
        for q, wt in self.weights.items():
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_weights[new_q] += wt
        self.weights = new_weights

        # Regenerate our strengths.
        new_strengths = defaultdict(lambda: 0.0)
        for (q1, q2), wt in self.strengths.items():
            try:
                new_q1 = num2alias[q1].find().contents
            except KeyError:
                new_q1 = q1
            try:
                new_q2 = num2alias[q2].find().contents
            except KeyError:
                new_q2 = q2
            if new_q1 == new_q2:
                continue
            if new_q1 > new_q2:
                new_q1, new_q2 = new_q2, new_q1
            new_strengths[(new_q1, new_q2)] += wt
        self.strengths = new_strengths

        # Regenerate our pinned values.
        new_pinned = {}
        for q, b in self.pinned:
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_pinned[new_q] = b
        self.pinned = sorted(new_pinned.items())

        # Regenerate the global symbol table.
        new_sym2num = {}
        for s, q in qmasm.sym_map.symbol_number_items():
            try:
                new_q = num2alias[q].find().contents
            except KeyError:
                new_q = q
            new_sym2num[s] = new_q
        qmasm.sym_map.overwrite_with(new_sym2num)

        # Renumber all of the above to compact the qubit numbers.
        qubits_used = set([q.find().contents for q in num2alias.values()])
        qubits_used.update(self.weights.keys())
        for q1, q2 in self.strengths.keys():
            qubits_used.add(q1)
            qubits_used.add(q2)
        qmap = dict(zip(sorted(qubits_used), range(len(qubits_used))))
        self.chains = set([(qmap[q1], qmap[q2]) for q1, q2 in self.chains])
        self.antichains = set([(qmap[q1], qmap[q2]) for q1, q2 in self.antichains])
        self.weights = defaultdict(lambda: 0.0,
                                   {qmap[q]: wt for q, wt in self.weights.items()})
        self.strengths = qmasm.canonicalize_strengths({(qmap[q1], qmap[q2]): wt for (q1, q2), wt in self.strengths.items()})
        self.pinned = [(qmap[q], b) for q, b in self.pinned]
        qmasm.sym_map.overwrite_with({s: qmap[q] for s, q in qmasm.sym_map.symbol_number_items()})
Exemplo n.º 11
0
def simplify_problem(logical, verbosity):
    """Try to find spins that can be removed from the problem because their
    value is known a priori."""
    # SAPI's fix_variables function works only on QUBOs so we have to convert.
    # We directly use SAPI's ising_to_qubo function instead of our own
    # convert_to_qubo because the QUBO has to be in matrix form.
    hs = qmasm.dict_to_list(logical.weights)
    Js = logical.strengths
    Q, qubo_offset = ising_to_qubo(hs, Js)

    # Simplify the problem if possible.
    simple = fix_variables(Q, method="standard")
    fixed_vars = simple["fixed_variables"]
    if verbosity >= 2:
        # Also determine if we could get rid of more qubits if we care about
        # only *a* solution rather than *all* solutions.
        alt_simple = fix_variables(Q, method="optimized")
        all_gone = len(alt_simple["new_Q"]) == 0

    # At high verbosity levels, list all of the known symbols and their value.
    if verbosity >= 2:
        # Map each logical qubit to one or more symbols.
        num2syms = [[] for _ in range(qmasm.sym_map.max_number() + 1)]
        max_sym_name_len = 7
        for q, n in qmasm.sym_map.symbol_number_items():
            num2syms[n].append(q)
            max_sym_name_len = max(max_sym_name_len, len(repr(num2syms[n])) - 1)

        # Output a table of know values
        sys.stderr.write("Elided qubits whose low-energy value can be determined a priori:\n\n")
        if len(fixed_vars) > 0:
            sys.stderr.write("    Logical  %-*s  Value\n" % (max_sym_name_len, "Name(s)"))
            sys.stderr.write("    -------  %s  -----\n" % ("-" * max_sym_name_len))
            truval = {0: "False", +1: "True"}
            for q, b in sorted(fixed_vars.items()):
                try:
                    syms = qmasm.sym_map.to_symbols(q)
                except KeyError:
                    continue
                name_list = " ".join(sorted(syms))
                sys.stderr.write("    %7d  %-*s  %-s\n" % (q, max_sym_name_len, name_list, truval[b]))
            sys.stderr.write("\n")

    # Return the original problem if no qubits could be elided.
    if verbosity >= 2:
        sys.stderr.write("  %6d logical qubits before elision\n" % (qmasm.sym_map.max_number() + 1))
    if len(fixed_vars) == 0:
        if verbosity >= 2:
            sys.stderr.write("  %6d logical qubits after elision\n\n" % (qmasm.sym_map.max_number() + 1))
            if all_gone:
                sys.stderr.write("    Note: A complete solution can be found classically using roof duality and strongly connected components.\n\n")
        return logical

    # Construct a simplified problem, renumbering so as to compact qubit
    # numbers.
    new_obj = copy.deepcopy(logical)
    new_obj.known_values = {s: 2*fixed_vars[n] - 1
                            for s, n in qmasm.sym_map.symbol_number_items()
                            if n in fixed_vars}
    new_obj.simple_offset = simple["offset"]
    hs, Js, ising_offset = qubo_to_ising(simple["new_Q"])
    qubits_used = set([i for i in range(len(hs)) if hs[i] != 0.0])
    for q1, q2 in Js.keys():
        qubits_used.add(q1)
        qubits_used.add(q2)
    qmap = dict(zip(sorted(qubits_used), range(len(qubits_used))))
    new_obj.chains = set([(qmap[q1], qmap[q2])
                          for q1, q2 in new_obj.chains
                          if q1 in qmap and q2 in qmap])
    new_obj.weights = defaultdict(lambda: 0.0,
                                  {qmap[i]: hs[i]
                                   for i in range(len(hs))
                                   if hs[i] != 0.0})
    new_obj.strengths = qmasm.canonicalize_strengths({(qmap[q1], qmap[q2]): wt
                                                      for (q1, q2), wt in Js.items()})
    new_obj.pinned = [(qmap[q], b)
                      for q, b in new_obj.pinned
                      if q in qmap]
    qmasm.sym_map.overwrite_with({s: qmap[q]
                                  for s, q in qmasm.sym_map.symbol_number_items()
                                  if q in qmap})
    if verbosity >= 2:
        # Report the number of logical qubits that remain, but compute the
        # number that could be removed if only a single solution were required.
        sys.stderr.write("  %6d logical qubits after elision\n\n" % (qmasm.sym_map.max_number() + 1))
        if all_gone:
            sys.stderr.write("    Note: A complete solution can be found classically using roof duality and strongly connected components.\n\n")
    return new_obj
Exemplo n.º 12
0
def simplify_problem(logical, verbosity):
    """Try to find spins that can be removed from the problem because their
    value is known a priori."""
    # SAPI's fix_variables function works only on QUBOs so we have to convert.
    # We directly use SAPI's ising_to_qubo function instead of our own
    # convert_to_qubo because the QUBO has to be in matrix form.
    hs = qmasm.dict_to_list(logical.weights)
    Js = logical.strengths
    Q, qubo_offset = ising_to_qubo(hs, Js)

    # Simplify the problem if possible.
    simple = fix_variables(Q, method="standard")
    new_Q = simple["new_Q"]
    fixed_vars = simple["fixed_variables"]
    if verbosity >= 2:
        # Also determine if we could get rid of more qubits if we care about
        # only *a* solution rather than *all* solutions.
        alt_simple = fix_variables(Q, method="optimized")
        all_gone = len(alt_simple["new_Q"]) == 0

    # Work around the rare case in which fix_variables drops a variable
    # entirely, leaving it neither in new_Q nor in fixed_variables.  If this
    # happenes, we explicitly re-add the variable from Q to new_Q and
    # transitively everything it touches (removing from fixed_vars if a
    # variable appears there).
    old_vars = qubo_vars(Q)
    new_vars = qubo_vars(new_Q)
    new_vars.update(fixed_vars)
    missing_vars = sorted(old_vars.difference(new_vars))
    while len(missing_vars) > 0:
        q = missing_vars.pop()
        for (q1, q2), val in Q.items():
            if q1 == q or q2 == q:
                new_Q[(q1, q2)] = val
                fixed_vars.pop(q1, None)
                fixed_vars.pop(q2, None)
                if q1 == q and q2 > q:
                    missing_vars.append(q2)
                elif q2 == q and q1 > q:
                    missing_vars.append(q1)

    # At high verbosity levels, list all of the known symbols and their value.
    if verbosity >= 2:
        # Map each logical qubit to one or more symbols.
        num2syms = [[] for _ in range(qmasm.sym_map.max_number() + 1)]
        max_sym_name_len = 7
        for q, n in qmasm.sym_map.symbol_number_items():
            num2syms[n].append(q)
            max_sym_name_len = max(max_sym_name_len, len(repr(num2syms[n])) - 1)

        # Output a table of know values
        sys.stderr.write("Elided qubits whose low-energy value can be determined a priori:\n\n")
        if len(fixed_vars) > 0:
            sys.stderr.write("    Logical  %-*s  Value\n" % (max_sym_name_len, "Name(s)"))
            sys.stderr.write("    -------  %s  -----\n" % ("-" * max_sym_name_len))
            truval = {0: "False", +1: "True"}
            for q, b in sorted(fixed_vars.items()):
                try:
                    syms = qmasm.sym_map.to_symbols(q)
                except KeyError:
                    continue
                name_list = " ".join(sorted(syms))
                sys.stderr.write("    %7d  %-*s  %-s\n" % (q, max_sym_name_len, name_list, truval[b]))
            sys.stderr.write("\n")

    # Return the original problem if no qubits could be elided.
    if verbosity >= 2:
        sys.stderr.write("  %6d logical qubits before elision\n" % (qmasm.sym_map.max_number() + 1))
    if len(fixed_vars) == 0:
        if verbosity >= 2:
            sys.stderr.write("  %6d logical qubits after elision\n\n" % (qmasm.sym_map.max_number() + 1))
            if all_gone:
                sys.stderr.write("    Note: A complete solution can be found classically using roof duality and strongly connected components.\n\n")
        return logical

    # Construct a simplified problem, renumbering so as to compact qubit
    # numbers.
    new_obj = copy.deepcopy(logical)
    new_obj.known_values = {s: 2*fixed_vars[n] - 1
                            for s, n in qmasm.sym_map.symbol_number_items()
                            if n in fixed_vars}
    new_obj.simple_offset = simple["offset"]
    hs, Js, ising_offset = qubo_to_ising(new_Q)
    qubits_used = set([i for i in range(len(hs)) if hs[i] != 0.0])
    for q1, q2 in Js.keys():
        qubits_used.add(q1)
        qubits_used.add(q2)
    qmap = dict(zip(sorted(qubits_used), range(len(qubits_used))))
    new_obj.chains = set([(qmap[q1], qmap[q2])
                          for q1, q2 in new_obj.chains
                          if q1 in qmap and q2 in qmap])
    new_obj.antichains = set([(qmap[q1], qmap[q2])
                              for q1, q2 in new_obj.antichains
                              if q1 in qmap and q2 in qmap])
    new_obj.weights = defaultdict(lambda: 0.0,
                                  {qmap[i]: hs[i]
                                   for i in range(len(hs))
                                   if hs[i] != 0.0})
    new_obj.strengths = qmasm.canonicalize_strengths({(qmap[q1], qmap[q2]): wt
                                                      for (q1, q2), wt in Js.items()})
    new_obj.pinned = [(qmap[q], b)
                      for q, b in new_obj.pinned
                      if q in qmap]
    qmasm.sym_map.overwrite_with({s: qmap[q]
                                  for s, q in qmasm.sym_map.symbol_number_items()
                                  if q in qmap})
    if verbosity >= 2:
        # Report the number of logical qubits that remain, but compute the
        # number that could be removed if only a single solution were required.
        sys.stderr.write("  %6d logical qubits after elision\n\n" % (qmasm.sym_map.max_number() + 1))
        if qmasm.sym_map.max_number() > -1 and all_gone:
            sys.stderr.write("    Note: A complete solution can be found classically using roof duality and strongly connected components.\n\n")
    return new_obj