def npp_unmapping(sampleset: dimod.SampleSet, elements: List, incoming: List, outgoing: List) -> str: """ Unmapping -- Decode spins to a problem solution """ outputs = [] outputs.append("") outputs.append("INPUT -->") outputs.append(f" {[e[1][1] for e in elements]}") outputs.append(f" (length: {len(elements)})") outputs.append("SOLUTION ==>") # Decode spins to solution spins = sampleset.samples()[0] set_p, set_n = [], [] n_set_p = n_set_n = 0 for e in elements: if spins[f"x[{e[1][0]}]"] == 1: set_p.append(e[1][1]) n_set_p += e[1][1] elif spins[f"x[{e[1][0]}]"] == -1: set_n.append(e[1][1]) n_set_n += e[1][1] outputs.append(f" Set(+) : sum={n_set_p}, elements={set_p}") outputs.append(f" Set(-) : sum={n_set_n}, elements={set_n}") outputs.append(f" diff : {abs(n_set_p - n_set_n)}") return "\n".join(outputs)
def convert_sampleset_to_measurements( sampleset: SampleSet, change_bitstring_convention: bool = False, ) -> Measurements: """ Converts dimod SampleSet to zquantum.core Measurements. Works only for the sampleset with "BINARY" vartype and variables being range of integers starting from 0. Note: Since Measurements doesn't hold information about the energy of the samples, this conversion is lossy. For more explanation regarding change_bitstring_convention please read docs of `convert_measurements_to_sampleset`. Args: sampleset: SampleSet we want to convert change_bitstring_convention: whether to flip the bits in bitstrings to, depends on the convention one is using (see note). Returns: Measurements object """ if sampleset.vartype != dimod.BINARY: raise TypeError("Sampleset needs to have vartype BINARY") for i in range(max(sampleset.variables)): if sampleset.variables[i] != i: raise ValueError( "Variables of sampleset need to be ordered list of integers") bitstrings = [ tuple( int(change_bitstring_convention != sample[i]) for i in range(len(sample))) for sample in sampleset.samples() ] return Measurements(bitstrings)
def convert_measurements_to_sampleset( measurements: Measurements, bqm: Optional[BinaryQuadraticModel] = None, change_bitstring_convention: bool = False, ) -> SampleSet: """ Converts dimod SampleSet to zquantum.core Measurements. If no bqm is specified, the vartype of the SampleSet will be "BINARY" and the energies will be NaN. If bqm is specified, its vartype will be preserved and the energy values will be calculated. Note: The convention commonly used in quantum computing is that 0 in a bitstring represents eigenvalue 1 of an Ising Hamiltonian, and 1 represents eigenvalue -1. However, there is another convention, used in dimod, where the mapping is 0 -> -1 and 1 -> 1 instead. Therefore if we try to use the bitstrings coming from solving the problem framed in one convention to evaluate energy for problem state in the second one, the results will be incorrect. This can be fixed with changing the value of `change_bitstring_convention` flag. Currently we changed the conventions appropriately in `convert_qubo_to_openfermion_ising` and `convert_openfermion_ising_to_qubo`, however, this still might be an issue in cases where qubo/Ising representation is created using different tools. It's hard to envision a specific example at the time of writing, but experience shows that it needs to be handled with caution. Args: measurements: Measurements object to be converted bqm: if provided, SampleSet will include energy values for each sample. change_bitstring_convention: whether to flip the bits in bitstrings to, depends on the convention one is using (see note). Returns: SampleSet object """ bitstrings = [ tuple(int(change_bitstring_convention != bit) for bit in bitstring) for bitstring in measurements.bitstrings ] if not bqm: return SampleSet.from_samples(bitstrings, "BINARY", [np.nan for _ in bitstrings]) if bqm.vartype != dimod.BINARY: raise TypeError("BQM needs to have vartype BINARY") return SampleSet.from_samples_bqm(bitstrings, bqm)
def tsp_unmapping(sampleset: dimod.SampleSet, elements: List[Tuple[float, Tuple[int, Dict[str, List[float]]]]], incoming: List, outgoing: List) -> str: """ Unmapping -- Decode spins to a problem solution Parameters ---------- sampleset : dimod.SampleSet Sampling result of the QUBO. elements : List Elements in the current window. incoming : List Elements just coming into the current window. outgoing : List Elements just going from the current window. Returns ------- order_to_visit : str Solution (order of the cities) as a string. """ outputs = [ "", "INPUT -->", " " + str([e[1][1] for e in elements]), "SOLUTION ==>" ] solution = sampleset.samples()[0] # Get order from the solution order_to_visit = [] # store order of the city for i, _ in enumerate(elements): for j, e in enumerate(elements): if solution[f"city[{i}][{j}]"] == 1: order_to_visit.append(list(e[1][1].keys())[0]) break return "\n".join(outputs) + "\n " + " -> ".join(order_to_visit)
def test_parse_ouput1(self): file1 = os.path.join(root_dir, "data", "small.csv") costs, weights, capacity = parse_inputs(file1, 10) samples = np.asarray([[1., 0., 1., 0., 0., 1., 0.], [0., 0., 0., 0., 0., 1., 0.], [0., 1., 0., 0., 0., 1., 0.], [0., 0., 1., 0., 0., 1., 0.]]) sampleset = SampleSet.from_samples(samples, 'BINARY', [-10, -11, -8, -9]) sampleset_infeasible = append_data_vectors(sampleset, is_feasible=[False] * 4) # Verify error for infeasible constraint input with self.assertRaises(ValueError): s = redirect_output(sampleset_infeasible, costs, weights) sampleset_feasible = append_data_vectors( sampleset, is_feasible=[False, True, False, True]) s = redirect_output(sampleset_feasible, costs, weights) self.assertIn('Found best solution at energy -11', s) self.assertIn('Selected item numbers (0-indexed): [5]', s) self.assertIn('Selected item weights: [10]', s) self.assertIn('Selected item costs: [80]', s)
def acquire_samples(self, verbosity, composites, physical, samples, anneal_time, spin_revs, postproc): "Acquire a number of samples from either a hardware or software sampler." # Wrap composites around our sampler if requested. sampler, bqm = self.sampler, physical.bqm for c in composites: try: if c == "VirtualGraph": sampler, bqm = self._wrap_virtual_graph(sampler, bqm) else: self.qmasm.abend( 'Internal error: unrecognized composite "%s"' % c) except SystemExit as err: raise err except: self.qmasm.abend( "Failed to wrap a %s composite around the underlying sampler" % c) # 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 = self._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 = self._compute_sample_counts(samples, anneal_time) spin_rev_list = self._compute_spin_rev_counts(spin_revs, samples_list) nqmis = len(samples_list) # Number of (non-unique) QMIs to submit # QBSolv writes to standard output, but we want it to write to standard # error instead. Note that the following is imperfect because of C # buffering. Setting PYTHONUNBUFFERED=1 in the environment seems to # help, though. if self.qbsolv_sampler != None: stdout_fileno = os.dup(sys.stdout.fileno()) os.dup2(sys.stderr.fileno(), sys.stdout.fileno()) # Submit all of our QMIs asynchronously. results = [None for i in range(nqmis)] executor = ThreadPoolExecutor() overall_start_time = time.time_ns() for i in range(nqmis): solver_params = dict(chains=list(physical.embedding.values()), num_reads=samples_list[i], annealing_time=anneal_time, num_spin_reversal_transforms=spin_rev_list[i], postprocess=postproc) for p in self.rejected_params: del solver_params[p] future = executor.submit(self._submit_and_block, sampler, bqm, verbosity, **solver_params) results[i] = SampleSet.from_future(future) # Wait for the QMIs to finish. executor.shutdown(wait=False) if verbosity == 1: if nqmis == 1: sys.stderr.write("Waiting for the problem to complete.\n\n") else: sys.stderr.write( "Waiting for all %d subproblems to complete.\n\n" % nqmis) elif 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_ns() ncomplete = 0 prev_ncomplete = 0 while ncomplete < nqmis: ncomplete = sum([int(r.done()) for r in results]) if verbosity >= 2 and ncomplete > prev_ncomplete: end_time = time.time_ns() sys.stderr.write( " %*d of %d (%3.0f%%) after %*.0f second(s)\n" % (cdigits, ncomplete, nqmis, 100.0 * float(ncomplete) / float(nqmis), tdigits, (end_time - start_time) / 1e9)) prev_ncomplete = ncomplete if ncomplete < nqmis: time.sleep(1) overall_end_time = time.time_ns() if verbosity >= 2: sys.stderr.write("\n") sys.stderr.write( " Average time per subproblem: %.2g second(s)\n\n" % ((overall_end_time - overall_start_time) / (nqmis * 1e9))) if "problem_id" in results[0].info: sys.stderr.write("IDs of completed subproblems:\n\n") for i in range(nqmis): sys.stderr.write(" %s\n" % results[i].info["problem_id"]) sys.stderr.write("\n") # Merge the result of seperate runs into a composite answer. answer = self._merge_results(results) answer.info["timing"]["round_trip_time"] = (overall_end_time - overall_start_time) // 1000 # Reset standard output. if self.qbsolv_sampler != None: os.dup2(stdout_fileno, sys.stdout.fileno()) # Return a Solutions object for further processing. return Solutions(answer, physical, verbosity >= 2)
def acquire_samples(self, verbosity, composites, physical, anneal_sched, samples, anneal_time, spin_revs, postproc): "Acquire a number of samples from either a hardware or software sampler." # Wrap composites around our sampler if requested. sampler, bqm = self.sampler, physical.bqm for c in composites: try: if c == "VirtualGraph": sampler, bqm = self._wrap_virtual_graph(sampler, bqm) else: self.qmasm.abend('Internal error: unrecognized composite "%s"' % c) except SystemExit as err: raise err except: self.qmasm.abend("Failed to wrap a %s composite around the underlying sampler" % c) # 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 = self._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 = self._compute_sample_counts(samples, anneal_time) spin_rev_list = self._compute_spin_rev_counts(spin_revs, samples_list) nqmis = len(samples_list) # Number of (non-unique) QMIs to submit # QBSolv writes to standard output, but we want it to write to standard # error instead. Note that the following is imperfect because of C # buffering. Setting PYTHONUNBUFFERED=1 in the environment seems to # help, though. if getattr(self, "qbsolv_sampler", None) != None: stdout_fileno = os.dup(sys.stdout.fileno()) os.dup2(sys.stderr.fileno(), sys.stdout.fileno()) # Submit all of our QMIs asynchronously. results = [None for i in range(nqmis)] futures = [None for i in range(nqmis)] executor = ThreadPoolExecutor() overall_start_time = time.time_ns() for i in range(nqmis): # Construct a set of solver parameters by combining typical # parameters (e.g., num_reads) with solver-specific parameters # (e.g., qpu_sampler), handling mutually exclusive parameters # (e.g., annealing_time and anneal_schedule), and subtracting off # any parameters previously rejected by the solver. solver_params = dict(chains=list(physical.embedding.values()), num_reads=samples_list[i], num_spin_reversal_transforms=spin_rev_list[i], postprocess=postproc) solver_params.update(self.extra_solver_params) if anneal_sched == None: solver_params["annealing_time"] = anneal_time else: solver_params["anneal_schedule"] = anneal_sched try: # Some, but not all, solvers report the parameters they accept. accepted_params = set(sampler.solver.properties["parameters"]) solver_params = {k: v for k, v in solver_params.items() if k in accepted_params} except AttributeError: pass with self.rejected_params_lock: for p in self.rejected_params: del solver_params[p] # Submit the QMI to the solver in a background thread. futures[i] = executor.submit(self._submit_and_block, sampler, bqm, i == 0, **solver_params) results[i] = SampleSet.from_future(futures[i]) # Wait for the QMIs to finish then return the results. executor.shutdown(wait=False) return self.complete_sample_acquisition(verbosity, futures, results, overall_start_time, physical)