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")
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
    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
Ejemplo n.º 10
0
    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