def scale_weights_strengths(physical, verbosity): "Manually scale the weights and strengths so Qubist doesn't complain." h_range = physical.h_range j_range = physical.j_range weight_list = qmasm.dict_to_list(physical.weights) old_cap = max( [abs(w) for w in weight_list + list(physical.strengths.values())]) new_cap = min(-h_range[0], h_range[1], -j_range[0], j_range[1]) if old_cap == 0.0: # Handle the obscure case of a zero old_cap. old_cap = new_cap new_weights = qmasm.list_to_dict( [w * new_cap / old_cap for w in weight_list]) new_strengths = { js: w * new_cap / old_cap for js, w in physical.strengths.items() } if verbosity >= 1 and old_cap != new_cap: sys.stderr.write( "Scaling weights and strengths from [%.10g, %.10g] to [%.10g, %.10g].\n\n" % (-old_cap, old_cap, -new_cap, new_cap)) new_physical = copy.deepcopy(physical) new_physical.weights = new_weights new_physical.strengths = new_strengths return new_physical
def submit_dwave_problem(verbosity, physical, samples, anneal_time, spin_revs, postproc): "Submit a QMI to the D-Wave." # Map abbreviated to full names for postprocessing types. postproc = {"": "", "opt": "optimization", "sample": "sampling"}[postproc] # Submit a QMI to the D-Wave and get back a list of solution vectors. solver_params = dict(chains=physical.embedding, num_reads=samples, annealing_time=anneal_time, num_spin_reversal_transforms=spin_revs, postprocess=postproc) unused_params = dict() while True: # Repeatedly remove parameters the particular solver doesn't like until # it actually works -- or fails for a different reason. try: weight_list = qmasm.dict_to_list(physical.weights) answer = solve_ising(qmasm.solver, weight_list, physical.strengths, **solver_params) break except ValueError as e: # Is there a better way to extract the failing symbol than a regular # expression match? bad_name_match = re.match(r'"(.*?)"', str(e)) if bad_name_match == None: raise e bad_name = bad_name_match.group(1) unused_params[bad_name] = solver_params[bad_name] del solver_params[bad_name] except RuntimeError as e: qmasm.abend(e) if verbosity >= 2: # Output parameters we kept and those we discarded sys.stderr.write("Parameters accepted by the %s solver:\n" % qmasm.solver_name) if len(solver_params) > 0: for k, v in solver_params.items(): sys.stderr.write(" %s = %s\n" % (k, v)) else: sys.stderr.write(" [none]\n") sys.stderr.write("\n") sys.stderr.write("Parameters rejected by the %s solver:\n" % qmasm.solver_name) if len(unused_params) > 0: for k, v in unused_params.items(): sys.stderr.write(" %s = %s\n" % (k, v)) else: sys.stderr.write(" [none]\n") sys.stderr.write("\n") # Tally the occurrences of each solution solutions = answer["solutions"] semifinal_answer = unembed_answer(solutions, physical.embedding, broken_chains="vote") try: num_occurrences = {tuple(k): v for k, v in zip(semifinal_answer, answer["num_occurrences"])} except KeyError: num_occurrences = {tuple(a): 1 for a in semifinal_answer} # Discard solutions with broken pins or broken chains. valid_solns = [s for s in solutions if solution_is_intact(physical, s)] final_answer = unembed_answer(valid_solns, physical.embedding, broken_chains="discard") return answer, final_answer, num_occurrences
def embed_problem_on_dwave(logical, optimize, verbosity): """Embed a logical problem in the D-Wave's physical topology. Return a physical Problem object.""" # Embed the problem. Abort on failure. find_dwave_embedding(logical, optimize, verbosity) try: h_range = qmasm.solver.properties["h_range"] j_range = qmasm.solver.properties["j_range"] except KeyError: h_range = [-1.0, 1.0] j_range = [-1.0, 1.0] weight_list = qmasm.dict_to_list(logical.weights) smearable = any([s != 0.0 for s in list(logical.strengths.values())]) try: [new_weights, new_strengths, new_chains, new_embedding] = embed_problem( weight_list, logical.strengths, logical.embedding, logical.hw_adj, True, smearable, h_range, j_range) except ValueError as e: qmasm.abend("Failed to embed the problem in the solver (%s)" % e) # Construct a physical Problem object. physical = copy.deepcopy(logical) physical.chains = new_chains physical.embedding = new_embedding physical.h_range = h_range physical.j_range = j_range physical.strengths = new_strengths physical.weights = defaultdict(lambda: 0.0, {q: new_weights[q] for q in range(len(new_weights)) if new_weights[q] != 0.0}) physical.pinned = [] for l, v in logical.pinned: physical.pinned.extend([(p, v) for p in physical.embedding[l]]) return physical
def embed_problem_on_dwave(logical, optimization, verbosity, hw_adj_file): """Embed a logical problem in the D-Wave's physical topology. Return a physical Problem object.""" # Embed the problem. Abort on failure. find_dwave_embedding(logical, optimization, verbosity, hw_adj_file) try: h_range = qmasm.solver.properties["h_range"] j_range = qmasm.solver.properties["j_range"] except KeyError: h_range = [-1.0, 1.0] j_range = [-1.0, 1.0] weight_list = qmasm.dict_to_list(logical.weights) smearable = any([s != 0.0 for s in logical.strengths.values()]) try: [new_weights, new_strengths, new_chains, new_embedding] = embed_problem( weight_list, logical.strengths, logical.embedding, logical.hw_adj, True, smearable, h_range, j_range) except ValueError as e: qmasm.abend("Failed to embed the problem in the solver (%s)" % e) # Construct a physical Problem object. physical = copy.deepcopy(logical) physical.embedding = new_embedding physical.embedder_chains = set(new_chains) physical.h_range = h_range physical.j_range = j_range physical.strengths = new_strengths physical.weights = defaultdict(lambda: 0.0, {q: new_weights[q] for q in range(len(new_weights)) if new_weights[q] != 0.0}) physical.pinned = [] for l, v in logical.pinned: physical.pinned.extend([(p, v) for p in physical.embedding[l]]) return physical
def scale_weights_strengths(self, verbosity): "Manually scale the weights and strengths so Qubist doesn't complain." h_range = self.h_range j_range = self.j_range weight_list = qmasm.dict_to_list(self.weights) old_cap = max([abs(w) for w in weight_list + list(self.strengths.values())]) new_cap = min(-h_range[0], h_range[1], -j_range[0], j_range[1]) if old_cap == 0.0: # Handle the obscure case of a zero old_cap. old_cap = new_cap self.range_scale = new_cap/old_cap self.weights = qmasm.list_to_dict([w*self.range_scale for w in weight_list]) self.strengths = {js: w*self.range_scale for js, w in self.strengths.items()} self.offset *= self.range_scale if verbosity >= 1 and old_cap != new_cap: sys.stderr.write("Scaling weights and strengths from [%.10g, %.10g] to [%.10g, %.10g].\n\n" % (-old_cap, old_cap, -new_cap, new_cap))
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, _ = ising_to_qubo(qmasm.dict_to_list(self.weights), self.strengths) new_obj.weights = defaultdict(lambda: 0.0, {q1: wt for (q1, q2), wt in list(qmatrix.items()) if q1 == q2}) new_obj.strengths = defaultdict(lambda: 0.0, {(q1, q2): wt for (q1, q2), wt in list(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 submit_dwave_problem(physical, samples, anneal_time): "Submit a QMI to the D-Wave." # Submit a QMI to the D-Wave and get back a list of solution vectors. solver_params = dict(chains=physical.embedding, num_reads=samples, annealing_time=anneal_time) while True: # Repeatedly remove parameters the particular solver doesn't like until # it actually works -- or fails for a different reason. try: weight_list = qmasm.dict_to_list(physical.weights) answer = solve_ising(qmasm.solver, weight_list, physical.strengths, **solver_params) break except ValueError as e: # Is there a better way to extract the failing symbol than a regular # expression match? bad_name = re.match(r'"(.*?)"', str(e)) if bad_name == None: raise e del solver_params[bad_name.group(1)] except RuntimeError as e: qmasm.abend(e) # Tally the occurrences of each solution solutions = answer["solutions"] semifinal_answer = unembed_answer(solutions, physical.embedding, broken_chains="vote") try: num_occurrences = { tuple(k): v for k, v in zip(semifinal_answer, answer["num_occurrences"]) } except KeyError: num_occurrences = {tuple(a): 1 for a in semifinal_answer} # Discard solutions with broken pins or broken chains. valid_solns = [s for s in solutions if solution_is_intact(physical, s)] final_answer = unembed_answer(valid_solns, physical.embedding, broken_chains="discard") return answer, final_answer, num_occurrences
def scale_weights_strengths(self, verbosity): "Manually scale the weights and strengths so Qubist doesn't complain." h_range = self.h_range j_range = self.j_range weight_list = qmasm.dict_to_list(self.weights) old_cap = max( [abs(w) for w in weight_list + list(self.strengths.values())]) new_cap = min(-h_range[0], h_range[1], -j_range[0], j_range[1]) if old_cap == 0.0: # Handle the obscure case of a zero old_cap. old_cap = new_cap self.range_scale = new_cap / old_cap self.weights = qmasm.list_to_dict( [w * self.range_scale for w in weight_list]) self.strengths = { js: w * self.range_scale for js, w in self.strengths.items() } if verbosity >= 1 and old_cap != new_cap: sys.stderr.write( "Scaling weights and strengths from [%.10g, %.10g] to [%.10g, %.10g].\n\n" % (-old_cap, old_cap, -new_cap, new_cap))
def submit_dwave_problem(verbosity, physical, samples, anneal_time, spin_revs, postproc, discard): "Submit a QMI to the D-Wave." # Map abbreviated to full names for postprocessing types. postproc = { "none": "", "opt": "optimization", "sample": "sampling" }[postproc] # Determine the annealing time to use. if anneal_time == None: anneal_time = get_default_annealing_time() # Compute a list of the number of samples to take each iteration # and the number of spin reversals to perform. samples_list = compute_sample_counts(samples, anneal_time) spin_rev_list = compute_spin_rev_counts(spin_revs, samples_list) nqmis = len(samples_list) # Number of (non-unique) QMIs to submit # Submit one or more QMIs to the D-Wave. problems = [] for i in range(nqmis): solver_params = dict(chains=physical.embedding, num_reads=samples_list[i], annealing_time=anneal_time, num_spin_reversal_transforms=spin_rev_list[i], postprocess=postproc) unused_params = dict() while True: # Repeatedly remove parameters the particular solver doesn't like # until it actually works -- or fails for a different reason. try: weight_list = qmasm.dict_to_list(physical.weights) p = async_solve_ising(qmasm.solver, weight_list, physical.strengths, **solver_params) problems.append(p) break except ValueError as e: # Is there a better way to extract the failing symbol than a # regular expression match? bad_name_match = re.match(r'"(.*?)"', str(e)) if bad_name_match == None: raise e bad_name = bad_name_match.group(1) unused_params[bad_name] = solver_params[bad_name] del solver_params[bad_name] except RuntimeError as e: qmasm.abend(e) if verbosity >= 2: report_parameters_used(solver_params, unused_params) # Output problem IDs as soon as they become available. if verbosity >= 1: try: while any([ problems[i].status()["problem_id"] == "" for i in range(nqmis) ]): await_completion(problems, nqmis, 1) report_subproblems_submitted(nqmis, problems, samples_list, spin_rev_list) except KeyError: pass # Not all solvers support "problem_id". # Wait for the solver to complete. if verbosity >= 2: sys.stderr.write("Number of subproblems completed:\n\n") cdigits = len(str(nqmis)) # Digits in the number of completed QMIs tdigits = len(str(nqmis * 5)) # Estimate 5 seconds per QMI submission start_time = time.time() done = False while not done: done = await_completion(problems, nqmis, 10) if verbosity >= 2: ncomplete = sum([ problems[i].status()["state"] == "DONE" for i in range(nqmis) ]) sys.stderr.write( " %*d of %d (%3.0f%%) after %*.0f seconds\n" % (cdigits, ncomplete, nqmis, 100.0 * float(ncomplete) / float(nqmis), tdigits, time.time() - start_time)) if verbosity >= 2: sys.stderr.write("\n") answers = [p.result() for p in problems] # Tally the occurrences of each solution answer = merge_answers(answers) solutions = answer["solutions"] semifinal_answer = unembed_answer(solutions, physical.embedding, broken_chains="minimize_energy", h=physical.weights, j=physical.strengths) try: num_occurrences = { tuple(k): v for k, v in zip(semifinal_answer, answer["num_occurrences"]) } except KeyError: num_occurrences = {tuple(a): 1 for a in semifinal_answer} # Discard solutions with broken pins or broken chains unless instructed # not to. valid_solns = [s for s in solutions if solution_is_intact(physical, s)] num_not_broken = len(valid_solns) if discard in ["yes", "maybe"]: final_answer = unembed_answer(valid_solns, physical.embedding, broken_chains="discard", h=physical.weights, j=physical.strengths) if discard == "no" or (discard == "maybe" and len(final_answer) == 0): final_answer = semifinal_answer return answer, final_answer, num_occurrences, num_not_broken
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 submit_dwave_problem(verbosity, physical, samples, anneal_time, spin_revs, postproc, discard): "Submit a QMI to the D-Wave." # Map abbreviated to full names for postprocessing types. postproc = {"": "", "opt": "optimization", "sample": "sampling"}[postproc] # Determine the annealing time to use. if anneal_time == None: try: # Use the default value. anneal_time = qmasm.solver.properties["default_annealing_time"] except KeyError: try: # If the default value is undefined, use the minimum allowed # value. anneal_time = qmasm.solver.properties["annealing_time_range"][0] except KeyError: # If all else fails, use 20 as a reasonable default. annealing_time = 20 # Submit a QMI to the D-Wave and get back a list of solution vectors. solver_params = dict(chains=physical.embedding, num_reads=samples, annealing_time=anneal_time, num_spin_reversal_transforms=spin_revs, postprocess=postproc) unused_params = dict() while True: # Repeatedly remove parameters the particular solver doesn't like until # it actually works -- or fails for a different reason. try: weight_list = qmasm.dict_to_list(physical.weights) answer = solve_ising(qmasm.solver, weight_list, physical.strengths, **solver_params) break except ValueError as e: # Is there a better way to extract the failing symbol than a regular # expression match? bad_name_match = re.match(r'"(.*?)"', str(e)) if bad_name_match == None: raise e bad_name = bad_name_match.group(1) unused_params[bad_name] = solver_params[bad_name] del solver_params[bad_name] except RuntimeError as e: qmasm.abend(e) if verbosity >= 2: # Output parameters we kept and those we discarded sys.stderr.write("Parameters accepted by the %s solver:\n" % qmasm.solver_name) if len(solver_params) > 0: for k, v in list(solver_params.items()): sys.stderr.write(" %s = %s\n" % (k, v)) else: sys.stderr.write(" [none]\n") sys.stderr.write("\n") sys.stderr.write("Parameters rejected by the %s solver:\n" % qmasm.solver_name) if len(unused_params) > 0: for k, v in list(unused_params.items()): sys.stderr.write(" %s = %s\n" % (k, v)) else: sys.stderr.write(" [none]\n") sys.stderr.write("\n") # Tally the occurrences of each solution solutions = answer["solutions"] semifinal_answer = unembed_answer(solutions, physical.embedding, broken_chains="minimize_energy", h=physical.weights, j=physical.strengths) try: num_occurrences = {tuple(k): v for k, v in zip(semifinal_answer, answer["num_occurrences"])} except KeyError: num_occurrences = {tuple(a): 1 for a in semifinal_answer} # Discard solutions with broken pins or broken chains unless instructed not to. valid_solns = [s for s in solutions if solution_is_intact(physical, s)] num_not_broken = len(valid_solns) if discard in ["yes", "maybe"]: final_answer = unembed_answer(valid_solns, physical.embedding, broken_chains="discard", h=physical.weights, j=physical.strengths) if discard == "no" or (discard == "maybe" and len(final_answer) == 0): final_answer = semifinal_answer return answer, final_answer, num_occurrences, num_not_broken
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 submit_dwave_problem(verbosity, physical, samples, anneal_time, spin_revs, postproc): "Submit a QMI to the D-Wave." # Map abbreviated to full names for postprocessing types. postproc = {"none": "", "opt": "optimization", "sample": "sampling"}[postproc] # Determine the annealing time to use. if anneal_time == None: anneal_time = get_default_annealing_time() # Compute a list of the number of samples to take each iteration # and the number of spin reversals to perform. samples_list = compute_sample_counts(samples, anneal_time) spin_rev_list = compute_spin_rev_counts(spin_revs, samples_list) nqmis = len(samples_list) # Number of (non-unique) QMIs to submit # Submit one or more QMIs to the D-Wave. problems = [] for i in range(nqmis): solver_params = dict(chains=physical.embedding, num_reads=samples_list[i], annealing_time=anneal_time, num_spin_reversal_transforms=spin_rev_list[i], postprocess=postproc) unused_params = dict() while True: # Repeatedly remove parameters the particular solver doesn't like # until it actually works -- or fails for a different reason. try: weight_list = qmasm.dict_to_list(physical.weights) p = async_solve_ising(qmasm.solver, weight_list, physical.strengths, **solver_params) problems.append(p) break except ValueError as e: # Is there a better way to extract the failing symbol than a # regular expression match? bad_name_match = re.match(r'"(.*?)"', str(e)) if bad_name_match == None: raise e bad_name = bad_name_match.group(1) unused_params[bad_name] = solver_params[bad_name] del solver_params[bad_name] except RuntimeError as e: qmasm.abend(e) if verbosity >= 2: report_parameters_used(solver_params, unused_params) # Output problem IDs as soon as they become available. if verbosity >= 1: try: while any([problems[i].status()["problem_id"] == "" for i in range(nqmis)]): await_completion(problems, nqmis, 1) report_subproblems_submitted(nqmis, problems, samples_list, spin_rev_list) except KeyError: pass # Not all solvers support "problem_id". # Wait for the solver to complete. if verbosity >= 2: sys.stderr.write("Number of subproblems completed:\n\n") cdigits = len(str(nqmis)) # Digits in the number of completed QMIs tdigits = len(str(nqmis*5)) # Estimate 5 seconds per QMI submission start_time = time.time() done = False while not done: done = await_completion(problems, nqmis, 10) if verbosity >= 2: ncomplete = sum([problems[i].status()["state"] == "DONE" for i in range(nqmis)]) sys.stderr.write(" %*d of %d (%3.0f%%) after %*.0f seconds\n" % (cdigits, ncomplete, nqmis, 100.0*float(ncomplete)/float(nqmis), tdigits, time.time() - start_time)) if verbosity >= 2: sys.stderr.write("\n") answers = [p.result() for p in problems] # Merge the result of seperate runs into a composite answer. answer = merge_answers(answers) # Return a Solutions object for further processing. return qmasm.Solutions(answer, physical, verbosity >= 2)
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