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
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
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
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")
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
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()})
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())
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()})
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
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