def test_submit_problem(self): num_variables, num_samples = 10, 100 problem = self._sample_fm_problem(num_variables=num_variables, num_samples=num_samples) result = simulated_annealing(*problem) self.assertTrue(len(result) == 2, "Sampler should return two values") samples, energies = result # ensure samples are all valid samples self.assertTrue(type(samples) is np.ndarray) # ensure correct number of samples and samples are have the correct # length self.assertTrue(samples.shape == (num_samples, num_variables), "Sampler returned wrong shape for samples") # make sure samples contain only +-1 self.assertTrue( set(np.unique(samples)) == {-1, 1}, "Sampler returned spins with values not equal to +-1") # ensure energies is valid self.assertTrue(type(energies) is np.ndarray) # one energy per sample self.assertTrue(energies.shape == (num_samples, ), "Sampler returned wrong number of energies")
def test_immediate_interrupt(self): num_variables = 5 problem = self._sample_fm_problem(num_variables=num_variables) # should only get one sample back samples, energies = simulated_annealing( *problem, interrupt_function=lambda: True) self.assertEqual(samples.shape, (1, 5)) self.assertEqual(energies.shape, (1, ))
def test_seed(self): # no need to do a bunch of sweeps, in fact the less we do the more # sure we can be that the same seed is returning the same result # problem = self._sample_fm_problem(num_variables=40, num_samples=1000, num_sweeps=10) num_variables, num_sweeps, num_samples = 100, 10, 1000 h = [0] * num_variables (coupler_starts, coupler_ends, coupler_weights) = zip(*((u, v, 1) for u in range(num_variables) for v in range(u, num_variables))) beta_schedule = np.linspace(0.3, 0.4, num=num_sweeps) sweeps_at_beta = 1 np_rand = np.random.RandomState(1234) initial_states = np_rand.randint(1, size=(num_samples, num_variables)) initial_states = 2 * initial_states.astype(np.int8) - 1 previous_samples = [] for seed in (1, 40, 235, 152436, 3462354, 92352355): samples0, _ = simulated_annealing(num_samples, h, coupler_starts, coupler_ends, coupler_weights, sweeps_at_beta, beta_schedule, seed, np.copy(initial_states)) samples1, _ = simulated_annealing(num_samples, h, coupler_starts, coupler_ends, coupler_weights, sweeps_at_beta, beta_schedule, seed, np.copy(initial_states)) self.assertTrue(np.array_equal(samples0, samples1), "Same seed returned different results") for previous_sample in previous_samples: self.assertFalse(np.array_equal(samples0, previous_sample), "Different seed returned same results") previous_samples.append(samples0)
def test_5_interrupt(self): num_variables = 5 problem = self._sample_fm_problem(num_variables=num_variables) count = [1] def stop(): if count[0] >= 5: return True count[0] += 1 return False # should only get one sample back samples, energies = simulated_annealing(*problem, interrupt_function=stop) self.assertEqual(samples.shape, (5, 5)) self.assertEqual(energies.shape, (5, ))
def test_good_results(self): num_variables = 5 problem = self._sample_fm_problem(num_variables=num_variables) samples, energies = simulated_annealing(*problem) ground_state = [1] * num_variables ground_energy = -(num_variables + 3) * num_variables / 2 # we should definitely have gotten to the ground state self.assertTrue(ground_state in samples, "Ground state not found in samples from easy problem") mean_energy = np.mean(energies) self.assertAlmostEqual( ground_energy, mean_energy, delta=2, msg="Sampler returned poor mean energy for easy problem")
def test_initial_states(self): num_variables, num_sweeps, num_samples = 100, 0, 1000 h = [0] * num_variables (coupler_starts, coupler_ends, coupler_weights) = zip(*((u, v, 1) for u in range(num_variables) for v in range(u, num_variables))) beta_schedule = np.linspace(0.3, 0.4, num=num_sweeps) sweeps_at_beta = 1 seed = 1234567890 np_rand = np.random.RandomState(1234) initial_states = np_rand.randint(1, size=(num_samples, num_variables)) initial_states = 2 * initial_states.astype(np.int8) - 1 samples, _ = simulated_annealing(num_samples, h, coupler_starts, coupler_ends, coupler_weights, sweeps_at_beta, beta_schedule, seed, np.copy(initial_states)) self.assertTrue(np.array_equal(initial_states, samples), "Initial states do not match samples with 0 sweeps")
def sample(self, bqm, beta_range=None, num_reads=None, num_sweeps=1000, beta_schedule_type="geometric", seed=None, interrupt_function=None, initial_states=None, initial_states_generator="random", **kwargs): """Sample from a binary quadratic model using an implemented sample method. Args: bqm (:class:`dimod.BinaryQuadraticModel`): The binary quadratic model to be sampled. beta_range (tuple, optional): A 2-tuple defining the beginning and end of the beta schedule, where beta is the inverse temperature. The schedule is applied linearly in beta. Default range is set based on the total bias associated with each node. num_reads (int, optional, default=len(initial_states) or 1): Number of reads. Each read is generated by one run of the simulated annealing algorithm. If `num_reads` is not explicitly given, it is selected to match the number of initial states given. If initial states are not provided, only one read is performed. num_sweeps (int, optional, default=1000): Number of sweeps or steps. beta_schedule_type (string, optional, default='geometric'): Beta schedule type, or how the beta values are interpolated between the given 'beta_range'. Supported values are: * linear * geometric seed (int, optional): Seed to use for the PRNG. Specifying a particular seed with a constant set of parameters produces identical results. If not provided, a random seed is chosen. initial_states (:class:`dimod.SampleSet` or tuple(numpy.ndarray, dict), optional): One or more samples, each defining an initial state for all the problem variables. Initial states are given one per read, but if fewer than `num_reads` initial states are defined, additional values are generated as specified by `initial_states_generator`. Initial states are provided either as: * :class:`dimod.SampleSet`, or * [deprecated] tuple, where the first value is a numpy array of initial states to seed the simulated annealing runs, and the second is a dict defining a linear variable labelling. In tuple format, initial states provided are assumed to use the same vartype the BQM is using. initial_states_generator (str, 'none'/'tile'/'random', optional, default='random'): Defines the expansion of `initial_states` if fewer than `num_reads` are specified: * "none": If the number of initial states specified is smaller than `num_reads`, raises ValueError. * "tile": Reuses the specified initial states if fewer than `num_reads` or truncates if greater. * "random": Expands the specified initial states with randomly generated states if fewer than `num_reads` or truncates if greater. interrupt_function (function, optional): If provided, interrupt_function is called with no parameters between each sample of simulated annealing. If the function returns True, then simulated annealing will terminate and return with all of the samples and energies found so far. Returns: :obj:`dimod.Response`: A `dimod` :obj:`~dimod.Response` object. Examples: This example runs simulated annealing on a binary quadratic model with some different input parameters. >>> import dimod >>> import neal ... >>> sampler = neal.SimulatedAnnealingSampler() >>> bqm = dimod.BinaryQuadraticModel({'a': .5, 'b': -.5}, {('a', 'b'): -1}, 0.0, dimod.SPIN) >>> # Run with default parameters >>> response = sampler.sample(bqm) >>> # Run with specified parameters >>> response = sampler.sample(bqm, seed=1234, beta_range=[0.1, 4.2], ... num_reads=1, num_sweeps=20, ... beta_schedule_type='geometric') >>> # Reuse a seed >>> a1 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a2 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a1 == a2 True """ if 'sweeps' in kwargs: warnings.warn("The 'sweeps' parameter is deprecated in " "favor of 'num_sweeps'.", DeprecationWarning) num_sweeps = kwargs.get('sweeps', num_sweeps) # if already index-labelled, just continue if all(v in bqm.linear for v in range(len(bqm))): _bqm = bqm use_label_map = False else: try: inverse_mapping = dict(enumerate(sorted(bqm.linear))) except TypeError: # in python3 unlike types cannot be sorted inverse_mapping = dict(enumerate(bqm.linear)) mapping = {v: i for i, v in iteritems(inverse_mapping)} _bqm = bqm.relabel_variables(mapping, inplace=False) use_label_map = True # beta_range, num_sweeps are handled by simulated_annealing if not (seed is None or isinstance(seed, Integral)): raise TypeError("'seed' should be None or a positive integer") if isinstance(seed, Integral) and not (0 < seed < (2**64 - 1)): error_msg = "'seed' should be an integer between 0 and 2^64 - 1" raise ValueError(error_msg) if seed is None: # pick a random seed seed = randint(0, (1 << 64 - 1)) if interrupt_function and not callable(interrupt_function): raise TypeError("'interrupt_function' should be a callable") num_variables = len(_bqm) # get the Ising linear biases linear = _bqm.spin.linear h = [linear[v] for v in range(num_variables)] quadratic = _bqm.spin.quadratic if len(quadratic) > 0: couplers, coupler_weights = zip(*iteritems(quadratic)) couplers = map(lambda c: (c[0], c[1]), couplers) coupler_starts, coupler_ends = zip(*couplers) else: coupler_starts, coupler_ends, coupler_weights = [], [], [] if beta_range is None: beta_range = _default_ising_beta_range(linear, quadratic) num_sweeps_per_beta = max(1, num_sweeps // 1000.0) num_betas = int(math.ceil(num_sweeps / num_sweeps_per_beta)) beta_ranges = [] num_beta_sum = [num_betas//(len(beta_range)-1) for _ in range(1, len(beta_range))] num_beta_sum[-1] += num_betas - sum(num_beta_sum) if beta_schedule_type == "linear": # interpolate a linear beta schedule for i in range(1, len(beta_range)): beta_ranges.append(np.linspace(beta_range[i - 1], beta_range[i], num_beta_sum[i - 1])) elif beta_schedule_type == "geometric": # interpolate a geometric beta schedule for i in range(1, len(beta_range)): beta_ranges.append(np.geomspace(beta_range[i - 1], beta_range[i], num_beta_sum[i - 1])) else: raise ValueError("Beta schedule type {} not implemented".format(beta_schedule_type)) beta_schedule = np.concatenate(beta_ranges) _generators = { 'none': self._none_generator, 'tile': self._tile_generator, 'random': self._random_generator } # unpack initial_states from sampleset/samples_like to numpy array, label map and vartype if isinstance(initial_states, dimod.SampleSet): initial_states_array = initial_states.record.sample init_label_map = dict(map(reversed, enumerate(initial_states.variables))) init_vartype = initial_states.vartype else: if initial_states is None: initial_states = (np.empty((0, num_variables)), None) else: warnings.warn("tuple format for 'initial_states' is deprecated " "in favor of 'dimod.SampleSet'.", DeprecationWarning) initial_states_array, init_label_map = initial_states # assume initial states samples have bqm's vartype init_vartype = bqm.vartype if initial_states_array.size: if init_label_map and set(init_label_map) ^ bqm.variables: raise ValueError("mismatch between variables in 'initial_states' and 'bqm'") elif initial_states_array.shape[1] != num_variables: raise ValueError("mismatch in number of variables in 'initial_states' and 'bqm'") if initial_states_generator not in _generators: raise ValueError("unknown value for 'initial_states_generator'") # reorder initial states array according to label map if init_label_map is not None: identity = lambda i: i get_label = inverse_mapping.get if use_label_map else identity ordered_labels = [init_label_map[get_label(i)] for i in range(num_variables)] initial_states_array = initial_states_array[:, ordered_labels] numpy_initial_states = np.ascontiguousarray(initial_states_array, dtype=np.int8) # convert to ising, if provided in binary if init_vartype == dimod.BINARY: numpy_initial_states = 2 * numpy_initial_states - 1 elif init_vartype != dimod.SPIN: raise TypeError("unsupported vartype") # validate num_reads and/or infer them from initial_states if num_reads is None: num_reads = len(numpy_initial_states) or 1 if not isinstance(num_reads, Integral): raise TypeError("'samples' should be a positive integer") if num_reads < 1: raise ValueError("'samples' should be a positive integer") # extrapolate and/or truncate initial states, if necessary extrapolate = _generators[initial_states_generator] numpy_initial_states = extrapolate(numpy_initial_states, num_reads, num_variables, seed) numpy_initial_states = self._truncate_filter(numpy_initial_states, num_reads) # run the simulated annealing algorithm samples, energies = sa.simulated_annealing( num_reads, h, coupler_starts, coupler_ends, coupler_weights, num_sweeps_per_beta, beta_schedule, seed, numpy_initial_states, interrupt_function) off = _bqm.spin.offset info = { "beta_range": beta_range, "beta_schedule_type": beta_schedule_type } response = dimod.SampleSet.from_samples( samples, energy=energies+off, info=info, vartype=dimod.SPIN ) response.change_vartype(_bqm.vartype, inplace=True) if use_label_map: response.relabel_variables(inverse_mapping, inplace=True) return response
def sample(self, bqm, beta_range=None, num_reads=None, num_sweeps=1000, beta_schedule_type="geometric", seed=None, interrupt_function=None, initial_states=None, initial_states_generator="random", **kwargs): """Sample from a binary quadratic model using an implemented sample method. Args: bqm (:class:`dimod.BinaryQuadraticModel`): The binary quadratic model to be sampled. beta_range (tuple, optional): A 2-tuple defining the beginning and end of the beta schedule, where beta is the inverse temperature. The schedule is applied linearly in beta. Default range is set based on the total bias associated with each node. num_reads (int, optional, default=len(initial_states) or 1): Number of reads. Each read is generated by one run of the simulated annealing algorithm. If `num_reads` is not explicitly given, it is selected to match the number of initial states given. If initial states are not provided, only one read is performed. num_sweeps (int, optional, default=1000): Number of sweeps or steps. beta_schedule_type (string, optional, default='geometric'): Beta schedule type, or how the beta values are interpolated between the given 'beta_range'. Supported values are: * linear * geometric seed (int, optional): Seed to use for the PRNG. Specifying a particular seed with a constant set of parameters produces identical results. If not provided, a random seed is chosen. initial_states (samples-like, optional, default=None): One or more samples, each defining an initial state for all the problem variables. Initial states are given one per read, but if fewer than `num_reads` initial states are defined, additional values are generated as specified by `initial_states_generator`. See func:`.as_samples` for a description of "samples-like". initial_states_generator (str, 'none'/'tile'/'random', optional, default='random'): Defines the expansion of `initial_states` if fewer than `num_reads` are specified: * "none": If the number of initial states specified is smaller than `num_reads`, raises ValueError. * "tile": Reuses the specified initial states if fewer than `num_reads` or truncates if greater. * "random": Expands the specified initial states with randomly generated states if fewer than `num_reads` or truncates if greater. interrupt_function (function, optional): If provided, interrupt_function is called with no parameters between each sample of simulated annealing. If the function returns True, then simulated annealing will terminate and return with all of the samples and energies found so far. Returns: :obj:`dimod.Response`: A `dimod` :obj:`~dimod.Response` object. Examples: This example runs simulated annealing on a binary quadratic model with some different input parameters. >>> import dimod >>> import neal ... >>> sampler = neal.SimulatedAnnealingSampler() >>> bqm = dimod.BinaryQuadraticModel({'a': .5, 'b': -.5}, {('a', 'b'): -1}, 0.0, dimod.SPIN) >>> # Run with default parameters >>> response = sampler.sample(bqm) >>> # Run with specified parameters >>> response = sampler.sample(bqm, seed=1234, beta_range=[0.1, 4.2], ... num_reads=1, num_sweeps=20, ... beta_schedule_type='geometric') >>> # Reuse a seed >>> a1 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a2 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a1 == a2 True """ # get the original vartype so we can return consistently original_vartype = bqm.vartype # convert to spin (if needed) if bqm.vartype is not dimod.SPIN: bqm = bqm.change_vartype(dimod.SPIN, inplace=False) # parse_initial_states could handle seed generation, but because we're # sharing it with the SA algo, we handle it out here if seed is None: seed = randint(0, (1 << 32 - 1)) elif not isinstance(seed, Integral): raise TypeError("'seed' should be None or a positive integer") if not (0 < seed < (2**32 - 1)): error_msg = "'seed' should be an integer between 0 and 2^64 - 1" raise ValueError(error_msg) # parse the inputs parsed = self.parse_initial_states( bqm, num_reads=num_reads, initial_states=initial_states, initial_states_generator=initial_states_generator, seed=seed) num_reads = parsed.num_reads # read out the initial states and the variable order initial_states_array = np.ascontiguousarray( parsed.initial_states.record.sample) variable_order = parsed.initial_states.variables # read out the BQM ldata, (irow, icol, qdata), off = bqm.to_numpy_vectors( variable_order=variable_order, dtype=np.double, index_dtype=np.intc) if interrupt_function and not callable(interrupt_function): raise TypeError("'interrupt_function' should be a callable") # handle beta_schedule et al if beta_range is None: beta_range = _default_ising_beta_range(bqm.linear, bqm.quadratic) num_sweeps_per_beta = max(1, num_sweeps // 1000.0) num_betas = int(math.ceil(num_sweeps / num_sweeps_per_beta)) if beta_schedule_type == "linear": # interpolate a linear beta schedule beta_schedule = np.linspace(*beta_range, num=num_betas) elif beta_schedule_type == "geometric": # interpolate a geometric beta schedule beta_schedule = np.geomspace(*beta_range, num=num_betas) else: raise ValueError("Beta schedule type {} not implemented".format( beta_schedule_type)) # run the simulated annealing algorithm samples, energies = sa.simulated_annealing( num_reads, ldata, irow, icol, qdata, num_sweeps_per_beta, beta_schedule, seed, initial_states_array, interrupt_function) info = { "beta_range": beta_range, "beta_schedule_type": beta_schedule_type } response = dimod.SampleSet.from_samples( (samples, variable_order), energy=energies + bqm.offset, # add back in the offset info=info, vartype=dimod.SPIN) response.change_vartype(original_vartype, inplace=True) return response
def sample(self, _bqm, beta_range=None, num_reads=10, sweeps=1000, beta_schedule_type="geometric", seed=None, interrupt_function=None, initial_states=None): """Sample from a binary quadratic model using an implemented sample method. Args: bqm (:obj:`dimod.BinaryQuadraticModel`): The binary quadratic model to be sampled. beta_range (tuple, optional): A 2-tuple defining the beginning and end of the beta schedule, where beta is the inverse temperature. The schedule is applied linearly in beta. Default range is set based on the total bias associated with each node. num_reads (int, optional, default=10): Each read is the result of a single run of the simulated annealing algorithm. sweeps (int, optional, default=1000): Number of sweeps or steps. beta_schedule_type (string, optional, default='geometric'): Beta schedule type, or how the beta values are interpolated between the given 'beta_range'. Supported values are: * linear * geometric seed (int, optional): Seed to use for the PRNG. Specifying a particular seed with a constant set of parameters produces identical results. If not provided, a random seed is chosen. initial_states (tuple(numpy.ndarray, dict), optional): A tuple where the first value is a numpy array of initial states to seed the simulated annealing runs, and the second is a dict defining a linear variable labelling. interrupt_function (function, optional): If provided, interrupt_function is called with no parameters between each sample of simulated annealing. If the function returns True, then simulated annealing will terminate and return with all of the samples and energies found so far. Returns: :obj:`dimod.Response`: A `dimod` :obj:`~dimod.Response` object. Examples: This example runs simulated annealing on a binary quadratic model with some different input paramters. >>> import dimod >>> import neal ... >>> sampler = neal.SimulatedAnnealingSampler() >>> bqm = dimod.BinaryQuadraticModel({'a': .5, 'b': -.5}, {('a', 'b'): -1}, 0.0, dimod.SPIN) >>> # Run with default parameters >>> response = sampler.sample(bqm) >>> # Run with specified parameters >>> response = sampler.sample(bqm, seed=1234, beta_range=[0.1, 4.2], ... num_reads=1, sweeps=20, ... beta_schedule_type='geometric') >>> # Reuse a seed >>> a1 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a2 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a1 == a2 True """ # if already index-labelled, just continue if all(v in _bqm.linear for v in range(len(_bqm))): bqm = _bqm use_label_map = False else: try: inverse_mapping = dict(enumerate(sorted(_bqm.linear))) except TypeError: # in python3 unlike types cannot be sorted inverse_mapping = dict(enumerate(_bqm.linear)) mapping = {v: i for i, v in iteritems(inverse_mapping)} bqm = _bqm.relabel_variables(mapping, inplace=False) use_label_map = True # beta_range, sweeps are handled by simulated_annealing if not isinstance(num_reads, Integral): raise TypeError("'samples' should be a positive integer") if num_reads < 1: raise ValueError("'samples' should be a positive integer") if not (seed is None or isinstance(seed, Integral)): raise TypeError("'seed' should be None or a positive integer") if isinstance(seed, Integral) and not (0 < seed < (2**64 - 1)): error_msg = "'seed' should be an integer between 0 and 2^64 - 1" raise ValueError(error_msg) if interrupt_function is None: def interrupt_function(): return False num_variables = len(bqm) # get the Ising linear biases linear = bqm.spin.linear h = [linear[v] for v in range(num_variables)] quadratic = bqm.spin.quadratic if len(quadratic) > 0: couplers, coupler_weights = zip(*iteritems(quadratic)) couplers = map(lambda c: (c[0], c[1]), couplers) coupler_starts, coupler_ends = zip(*couplers) else: coupler_starts, coupler_ends, coupler_weights = [], [], [] if beta_range is None: beta_range = _default_ising_beta_range(linear, quadratic) sweeps_per_beta = max(1, sweeps // 1000.0) num_betas = int(math.ceil(sweeps / sweeps_per_beta)) if beta_schedule_type == "linear": # interpolate a linear beta schedule beta_schedule = np.linspace(*beta_range, num=num_betas) elif beta_schedule_type == "geometric": # interpolate a geometric beta schedule beta_schedule = np.geomspace(*beta_range, num=num_betas) else: raise ValueError("Beta schedule type {} not implemented".format( beta_schedule_type)) if seed is None: # pick a random seed seed = randint(0, (1 << 64 - 1)) np_rand = np.random.RandomState(seed % 2**32) states_shape = (num_reads, num_variables) if initial_states is not None: initial_states_array, init_label_map = initial_states if not initial_states_array.shape == states_shape: raise ValueError("`initial_states` must have shape " "{}".format(states_shape)) if init_label_map is not None: get_label = inverse_mapping.get if use_label_map else lambda i: i initial_states_array = initial_states_array[:, [ init_label_map[get_label(i)] for i in range(num_variables) ]] numpy_initial_states = np.ascontiguousarray(initial_states_array, dtype=np.int8) else: numpy_initial_states = 2 * np_rand.randint( 2, size=(num_reads, num_variables)).astype(np.int8) - 1 # run the simulated annealing algorithm samples, energies = sa.simulated_annealing( num_reads, h, coupler_starts, coupler_ends, coupler_weights, sweeps_per_beta, beta_schedule, seed, numpy_initial_states, interrupt_function) off = bqm.spin.offset info = { "beta_range": beta_range, "beta_schedule_type": beta_schedule_type } response = dimod.Response.from_samples(samples, {'energy': energies + off}, info=info, vartype=dimod.SPIN) response.change_vartype(bqm.vartype, inplace=True) if use_label_map: response.relabel_variables(inverse_mapping, inplace=True) return response
def sample(self, bqm, beta_range=None, num_reads=None, num_sweeps=None, num_sweeps_per_beta=1, beta_schedule_type="geometric", seed=None, interrupt_function=None, beta_schedule=None, initial_states=None, initial_states_generator="random", **kwargs): """Sample from a binary quadratic model using an implemented sample method. Args: bqm (:class:`dimod.BinaryQuadraticModel`): The binary quadratic model to be sampled. beta_range (tuple or list, optional, default = None): A 2-tuple or list defining the beginning and end of the beta schedule, where beta is the inverse temperature. The schedule is interpolated within this range according to the value specified by ``beta_schedule_type``. Default range is set based on the total bias associated with each node. num_reads (int, optional, default=len(initial_states) or 1): Number of reads. Each read is generated by one run of the simulated annealing algorithm. If `num_reads` is not explicitly given, it is selected to match the number of initial states given. If initial states are not provided, only one read is performed. num_sweeps (int, optional, default=``len(beta_schedule)*num_sweeps_per_beta`` or 1000): Number of sweeps used in annealing. If no value is provided and ``beta_schedule`` is None the value is defaulted to 1000. num_sweeps_per_beta (int, optional, default=1) Number of sweeps to perform at each beta. One sweep consists of a sequential Metropolis update of all spins. beta_schedule_type (string, optional, default="geometric"): Beta schedule type, or how the beta values are interpolated between the given `beta_range`. Supported values are: * "linear" * "geometric" * "custom" "custom" is recommended for high-performance applications, which typically require optimizing beta schedules beyond those of the "linear" and "geometric" options, with bounds beyond those provided by default. ``num_sweeps_per_beta`` and ``beta_schedule`` fully specify a custom schedule. beta_schedule (array-like, optional, default = None) Sequence of beta values swept. Format compatible with numpy.array(beta_schedule,dtype=float) required. Values should be non-negative. seed (int, optional, default = None): Seed to use for the PRNG. Specifying a particular seed with a constant set of parameters produces identical results. If not provided, a random seed is chosen. initial_states (samples-like, optional, default=None): One or more samples, each defining an initial state for all the problem variables. Initial states are given one per read, but if fewer than ``num_reads`` initial states are defined, additional values are generated as specified by ``initial_states_generator``. See func:``.as_samples`` for a description of "samples-like". initial_states_generator (str, "none"/"tile"/"random", optional, default="random"): Defines the expansion of ``initial_states`` if fewer than ``num_reads`` are specified: * "none": If the number of initial states specified is smaller than ``num_reads``, raises ValueError. * "tile": Reuses the specified initial states if fewer than ``num_reads`` or truncates if greater. * "random": Expands the specified initial states with randomly generated states if fewer than ``num_reads`` or truncates if greater. interrupt_function (function, optional): If provided, interrupt_function is called with no parameters between each sample of simulated annealing. If the function returns True, then simulated annealing will terminate and return with all of the samples and energies found so far. Returns: :obj:``dimod.Response``: A ``dimod`` :obj:``~dimod.Response`` object. Examples: This example runs simulated annealing on a binary quadratic model with some different input parameters. >>> import dimod >>> import neal ... >>> sampler = neal.SimulatedAnnealingSampler() >>> bqm = dimod.BinaryQuadraticModel({'a': .5, 'b': -.5}, ... {('a', 'b'): -1}, 0.0, ... dimod.SPIN) >>> # Run with default parameters >>> sampleset = sampler.sample(bqm) >>> # Run with specified parameters >>> sampleset = sampler.sample(bqm, seed=1234, ... beta_range=[0.1, 4.2], ... num_sweeps=20, ... beta_schedule_type='geometric') >>> # Reuse a seed >>> a1 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a2 = next((sampler.sample(bqm, seed=88)).samples())['a'] >>> a1 == a2 True """ # get the original vartype so we can return consistently original_vartype = bqm.vartype # convert to spin (if needed) if bqm.vartype is not dimod.SPIN: bqm = bqm.change_vartype(dimod.SPIN, inplace=False) # parse_initial_states could handle seed generation, but because we're # sharing it with the SA algo, we handle it out here if seed is None: seed = randint(2**32) elif not isinstance(seed, Integral): error_msg = "'seed' should be None or an integer between 0 and 2^32 - 1: value =" + str( seed) raise TypeError(error_msg) elif not (0 <= seed < 2**32): error_msg = "'seed' should be an integer between 0 and 2^32 - 1: value =" + str( seed) raise ValueError(error_msg) # parse the inputs parsed = self.parse_initial_states( bqm, num_reads=num_reads, initial_states=initial_states, initial_states_generator=initial_states_generator, seed=seed) num_reads = parsed.num_reads # read out the initial states and the variable order initial_states_array = np.ascontiguousarray( parsed.initial_states.record.sample) variable_order = parsed.initial_states.variables # read out the BQM ldata, (irow, icol, qdata), off = bqm.to_numpy_vectors( variable_order=variable_order, dtype=np.double, index_dtype=np.intc) if interrupt_function and not callable(interrupt_function): raise TypeError("'interrupt_function' should be a callable") if not isinstance(num_sweeps_per_beta, Integral): error_msg = "'num_sweeps_per_beta' should be a positive integer: value =" + str( num_sweeps_per_beta) raise TypeError(error_msg) elif num_sweeps_per_beta < 1: error_msg = "'num_sweeps_per_beta' should be a positive integer: value = " + str( num_sweeps_per_beta) raise ValueError(error_msg) # handle beta_schedule et al if beta_schedule_type == "custom": if beta_schedule is None: error_msg = "'beta_schedule' must be provided for beta_schedule_type = 'custom': value is None" raise ValueError(error_msg) elif num_sweeps is not None and num_sweeps != len( beta_schedule) * num_sweeps_per_beta: error_msg = "'num_sweeps' should be set to None, or a value consistent with 'beta_schedule' and 'num_sweeps_per_beta' for 'beta_schedule_type' = 'custom': value = " + str( num_sweeps) raise ValueError(error_msg) elif beta_range is not None and ( beta_range[0] != beta_schedule[0] or beta_range[-1] != beta_schedule[-1]): error_msg = "'beta_range' should be set to None, or a value consistent with 'beta_schedule', for 'beta_schedule_type'='custom'." raise ValueError(error_msg) elif min(beta_schedule) < 0: error_msg = "'beta_schedule' cannot include negative values." raise ValueError(error_msg) else: if beta_schedule is not None: error_msg = "'beta_schedule' must be set to None for 'beta_schedule_type' not equal to 'custom'" raise ValueError(error_msg) elif num_sweeps is None: num_sweeps = 1000 num_betas, rem = divmod(num_sweeps, num_sweeps_per_beta) if rem > 0 or num_betas < 1: error_msg = "'num_sweeps' must be a positive value divisible by 'num_sweeps_per_beta'." raise ValueError(error_msg) if beta_range is None: beta_range = _default_ising_beta_range(bqm.linear, bqm.quadratic) elif len(beta_range) != 2 or min(beta_range) < 0: error_msg = "'beta_range' should be a 2-tuple, or 2 element list of positive numbers. The latter value is the target value." raise ValueError(error_msg) if num_betas == 1: #One sweep in the target model beta_schedule = np.array([beta_range[-1]], dtype=float) else: if beta_schedule_type == "linear": # interpolate a linear beta schedule beta_schedule = np.linspace(*beta_range, num=num_betas) elif beta_schedule_type == "geometric": if min(beta_range) <= 0: error_msg = "'beta_range' must contain non-zero values for 'beta_schedule_type' = 'geometric'" raise ValueError(error_msg) # interpolate a geometric beta schedule beta_schedule = np.geomspace(*beta_range, num=num_betas) else: raise ValueError( "Beta schedule type {} not implemented".format( beta_schedule_type)) # run the simulated annealing algorithm samples, energies = sa.simulated_annealing( num_reads, ldata, irow, icol, qdata, num_sweeps_per_beta, beta_schedule, seed, initial_states_array, interrupt_function) info = { "beta_range": beta_range, "beta_schedule_type": beta_schedule_type } response = dimod.SampleSet.from_samples( (samples, variable_order), energy=energies + bqm.offset, # add back in the offset info=info, vartype=dimod.SPIN) response.change_vartype(original_vartype, inplace=True) return response