Esempio n. 1
0
    def __init__(
        self,
        hilbertspace: HilbertSpace,
        paramvals_by_name: Dict[str, ndarray],
        update_hilbertspace: Callable,
        evals_count: int = 20,
        subsys_update_info: Optional[Dict[str, List[QuantumSys]]] = None,
        bare_only: bool = False,
        autorun: bool = settings.AUTORUN_SWEEP,
        num_cpus: Optional[int] = None,
    ) -> None:
        num_cpus = num_cpus or settings.NUM_CPUS
        self._parameters = Parameters(paramvals_by_name)
        self._hilbertspace = hilbertspace
        self._evals_count = evals_count
        self._update_hilbertspace = update_hilbertspace
        self._subsys_update_info = subsys_update_info
        self._data: Dict[str, Optional[NamedSlotsNdarray]] = {}
        self._bare_only = bare_only
        self._num_cpus = num_cpus
        self.tqdm_disabled = settings.PROGRESSBAR_DISABLED or (num_cpus > 1)

        self._out_of_sync = False
        self._current_param_indices = None

        dispatch.CENTRAL_DISPATCH.register("PARAMETERSWEEP_UPDATE", self)
        dispatch.CENTRAL_DISPATCH.register("HILBERTSPACE_UPDATE", self)

        if autorun:
            self.run()
Esempio n. 2
0
    def __init__(self, paramvals_by_name, hilbertspace, evals_count,
                 _data) -> None:
        self._parameters = Parameters(paramvals_by_name)
        self._hilbertspace = hilbertspace
        self._evals_count = evals_count
        self._data = _data

        self._out_of_sync = False
        self._current_param_indices = None
Esempio n. 3
0
class ParameterSweep(
        ParameterSweepBase,
        SpectrumLookupMixin,
        dispatch.DispatchClient,
        serializers.Serializable,
):
    """
    `ParameterSweep` supports array-like access ("pre-slicing") and dict-like access.
    With dict-like access via string-keywords `<ParameterSweep>[<str>]`,
    the following data is returned:

    `"evals"` and `"evecs"`
        dressed eigenenergies and eigenstates as
        `NamedSlotsNdarray`; eigenstates are decomposed in the bare product-state basis
        of the non-interacting subsystems' eigenbases
    `"bare_evals"` and `"bare_evecs"`
        bare eigenenergies and eigenstates as `NamedSlotsNdarray`
    `"lamb"`, `"chi"`, and `"kerr"`
        dispersive energy coefficients
    `"<custom sweep>"`
        NamedSlotsNdarray for custom data generated with `add_sweep`.

    Array-like access is responsible for "pre-slicing",
    enable lookup functionality such as
    `<Sweep>[p1, p2, ...].eigensys()`

    Parameters
    ----------
    hilbertspace:
        HilbertSpace object describing the quantum system of interest
    paramvals_by_name:
        Dictionary that, for each set of parameter values, specifies a parameter name
        and the set of values to be used in the sweep.
    update_hilbertspace:
        function that updates the associated `hilbertspace` object with a given
        set of parameters
    evals_count:
        number of dressed eigenvalues/eigenstates to keep. (The number of bare
        eigenvalues/eigenstates is determined for each subsystems by `truncated_dim`.)
        [default: 20]
    subsys_update_info:
        To speed up calculations, the user may provide information that specifies which
        subsystems are being updated for each of the given parameter sweeps. This
        information is specified by a dictionary of the following form::

            {
                "<parameter name 1>": [<subsystem a>],
                "<parameter name 2>": [<subsystem b>, <subsystem c>, ...],
                ...
            }

        This indicates that changes in `<parameter name 1>` only require updates of
        `<subsystem a>` while leaving other subsystems unchanged. Similarly, sweeping
        `<parameter name 2>` affects `<subsystem b>`, `<subsystem c>` etc.
    bare_only:
        if set to True, only bare eigendata is calculated; useful when performing a
        sweep for a single quantum system, no interaction (default: False)
    autorun:
        Determines whether to directly run the sweep or delay it until `.run()` is
        called manually. (Default: `settings.AUTORUN_SWEEP=True`)
    num_cpus:
        number of CPU cores requested for computing the sweep
        (default value `settings.NUM_CPUS`)

    """
    def __new__(cls, *args,
                **kwargs) -> "Union[ParameterSweep, _ParameterSweep]":
        if args and isinstance(args[0], str) or "param_name" in kwargs:
            # old-style ParameterSweep interface is being used
            warnings.warn(
                "The implementation of the `ParameterSweep` class has changed and this "
                "old-style interface will cease to be supported in the future.",
                FutureWarning,
            )
            return _ParameterSweep(*args, **kwargs)
        else:
            return super().__new__(cls, *args, **kwargs)

    def __init__(
        self,
        hilbertspace: HilbertSpace,
        paramvals_by_name: Dict[str, ndarray],
        update_hilbertspace: Callable,
        evals_count: int = 20,
        subsys_update_info: Optional[Dict[str, List[QuantumSys]]] = None,
        bare_only: bool = False,
        autorun: bool = settings.AUTORUN_SWEEP,
        num_cpus: Optional[int] = None,
    ) -> None:
        num_cpus = num_cpus or settings.NUM_CPUS
        self._parameters = Parameters(paramvals_by_name)
        self._hilbertspace = hilbertspace
        self._evals_count = evals_count
        self._update_hilbertspace = update_hilbertspace
        self._subsys_update_info = subsys_update_info
        self._data: Dict[str, Optional[NamedSlotsNdarray]] = {}
        self._bare_only = bare_only
        self._num_cpus = num_cpus
        self.tqdm_disabled = settings.PROGRESSBAR_DISABLED or (num_cpus > 1)

        self._out_of_sync = False
        self._current_param_indices = None

        dispatch.CENTRAL_DISPATCH.register("PARAMETERSWEEP_UPDATE", self)
        dispatch.CENTRAL_DISPATCH.register("HILBERTSPACE_UPDATE", self)

        if autorun:
            self.run()

    def cause_dispatch(self) -> None:
        initial_parameters = tuple(paramvals[0]
                                   for paramvals in self._parameters)
        self._update_hilbertspace(*initial_parameters)

    @classmethod
    def deserialize(cls, iodata: "IOData") -> "StoredSweep":
        pass

    def serialize(self) -> "IOData":
        """
        Convert the content of the current class instance into IOData format.

        Returns
        -------
        IOData
        """
        initdata = {
            "paramvals_by_name": self._parameters.ordered_dict,
            "hilbertspace": self._hilbertspace,
            "evals_count": self._evals_count,
            "_data": self._data,
        }
        iodata = serializers.dict_serialize(initdata)
        iodata.typename = "StoredSweep"
        return iodata

    @property
    def param_info(self) -> Dict[str, ndarray]:
        """Return a dictionary of the parameter names and values used in this sweep."""
        return self._parameters.paramvals_by_name

    def run(self) -> None:
        """Create all sweep data: bare spectral data, dressed spectral data, lookup
        data and custom sweep data."""
        # generate one dispatch before temporarily disabling CENTRAL_DISPATCH
        self.cause_dispatch()
        settings.DISPATCH_ENABLED = False
        self._data["bare_evals"], self._data[
            "bare_evecs"] = self._bare_spectrum_sweep()
        if not self._bare_only:
            self._data["evals"], self._data[
                "evecs"] = self._dressed_spectrum_sweep()
            self._data["dressed_indices"] = self.generate_lookup()
            (
                self._data["lamb"],
                self._data["chi"],
                self._data["kerr"],
            ) = self._dispersive_coefficients()
        settings.DISPATCH_ENABLED = True

    def _bare_spectrum_sweep(
            self) -> Tuple[NamedSlotsNdarray, NamedSlotsNdarray]:
        """
        The bare energy spectra are computed according to the following scheme.
        1. Perform a loop over all subsystems to separately obtain the bare energy
            eigenvalues and eigenstates for each subsystems.
        2. If `update_subsystem_info` is given, remove those sweeps that leave the
            subsystems fixed.
        3. If self._num_cpus > 1, parallelize.

        Returns
        -------
            NamedSlotsNdarray[<paramname1>, <paramname2>, ..., "subsys"] for evals,
            likewise for evecs;
            here, "subsys": 0, 1, ... enumerates subsystems and
        """
        bare_evals = np.empty((self.subsystem_count, ), dtype=object)
        bare_evecs = np.empty((self.subsystem_count, ), dtype=object)

        for subsys_index, subsystem in enumerate(self._hilbertspace):
            bare_esys = self._subsys_bare_spectrum_sweep(subsystem)
            bare_evals[subsys_index] = NamedSlotsNdarray(
                np.asarray(bare_esys[..., 0].tolist()),
                self._parameters.paramvals_by_name,
            )
            bare_evecs[subsys_index] = NamedSlotsNdarray(
                np.asarray(bare_esys[..., 1].tolist()),
                self._parameters.paramvals_by_name,
            )

        return (
            NamedSlotsNdarray(bare_evals,
                              {"subsys": np.arange(self.subsystem_count)}),
            NamedSlotsNdarray(bare_evecs,
                              {"subsys": np.arange(self.subsystem_count)}),
        )

    def _update_subsys_compute_esys(self, update_func: Callable,
                                    subsystem: QuantumSys,
                                    paramval_tuple: Tuple[float]) -> ndarray:
        update_func(*paramval_tuple)
        evals, evecs = subsystem.eigensys(evals_count=subsystem.truncated_dim)
        esys_array = np.empty(shape=(2, ), dtype=object)
        esys_array[0] = evals
        esys_array[1] = evecs
        return esys_array

    def _paramnames_no_subsys_update(self, subsystem) -> List[str]:
        if self._subsys_update_info is None:
            return []
        updating_parameters = [
            name for name in self._subsys_update_info.keys()
            if subsystem in self._subsys_update_info[name]
        ]
        return list(set(self._parameters.names) - set(updating_parameters))

    def _subsys_bare_spectrum_sweep(self, subsystem) -> ndarray:
        """

        Parameters
        ----------
        subsystem:
            subsystem for which the bare spectrum sweep is to be computed

        Returns
        -------
            multidimensional array of the format
            array[p1, p2, p3, ..., pN] = np.asarray[[evals, evecs]]
        """
        fixed_paramnames = self._paramnames_no_subsys_update(subsystem)
        reduced_parameters = self._parameters.create_reduced(fixed_paramnames)
        total_count = np.prod(
            [len(param_vals) for param_vals in reduced_parameters])

        multi_cpu = self._num_cpus > 1
        target_map = cpu_switch.get_map_method(self._num_cpus)

        with utils.InfoBar(
                "Parallel compute bare eigensys [num_cpus={}]".format(
                    self._num_cpus),
                self._num_cpus,
        ) as p:
            bare_eigendata = tqdm(
                target_map(
                    functools.partial(
                        self._update_subsys_compute_esys,
                        self._update_hilbertspace,
                        subsystem,
                    ),
                    itertools.product(*reduced_parameters.paramvals_list),
                ),
                total=total_count,
                desc="Bare spectra",
                leave=False,
                disable=multi_cpu,
            )

        bare_eigendata = np.asarray(list(bare_eigendata), dtype=object)
        bare_eigendata = bare_eigendata.reshape(
            (*reduced_parameters.counts, 2))

        # Bare spectral data was only computed once for each parameter that has no
        # update effect on the subsystems. Now extend the array to reflect this
        # for the full parameter array by repeating
        for name in fixed_paramnames:
            index = self._parameters.index_by_name[name]
            param_count = self._parameters.counts[index]
            bare_eigendata = np.repeat(bare_eigendata, param_count, axis=index)

        return bare_eigendata

    def _update_and_compute_dressed_esys(
        self,
        hilbertspace: HilbertSpace,
        evals_count: int,
        update_func: Callable,
        paramindex_tuple: Tuple[int],
    ) -> ndarray:
        paramval_tuple = self._parameters[paramindex_tuple]
        update_func(*paramval_tuple)

        assert self._data is not None
        bare_esys = {
            subsys_index: [
                self._data["bare_evals"][subsys_index][paramindex_tuple],
                self._data["bare_evecs"][subsys_index][paramindex_tuple],
            ]
            for subsys_index, _ in enumerate(self._hilbertspace)
        }

        evals, evecs = hilbertspace.eigensys(evals_count=evals_count,
                                             bare_esys=bare_esys)
        esys_array = np.empty(shape=(2, ), dtype=object)
        esys_array[0] = evals
        esys_array[1] = evecs
        return esys_array

    def _dressed_spectrum_sweep(
        self, ) -> Tuple[NamedSlotsNdarray, NamedSlotsNdarray]:
        """

        Returns
        -------
            NamedSlotsNdarray[<paramname1>, <paramname2>, ...] of eigenvalues,
            likewise for eigenvectors
        """
        multi_cpu = self._num_cpus > 1
        target_map = cpu_switch.get_map_method(self._num_cpus)
        total_count = np.prod(self._parameters.counts)

        with utils.InfoBar(
                "Parallel compute dressed eigensys [num_cpus={}]".format(
                    self._num_cpus),
                self._num_cpus,
        ) as p:
            spectrum_data = list(
                tqdm(
                    target_map(
                        functools.partial(
                            self._update_and_compute_dressed_esys,
                            self._hilbertspace,
                            self._evals_count,
                            self._update_hilbertspace,
                        ),
                        itertools.product(*self._parameters.ranges),
                    ),
                    total=total_count,
                    desc="Dressed spectrum",
                    leave=False,
                    disable=multi_cpu,
                ))

        spectrum_data = np.asarray(spectrum_data, dtype=object)
        spectrum_data = spectrum_data.reshape((*self._parameters.counts, 2))
        slotparamvals_by_name = OrderedDict(
            self._parameters.ordered_dict.copy())

        evals = np.asarray(spectrum_data[..., 0].tolist())
        evecs = spectrum_data[..., 1]

        return NamedSlotsNdarray(evals,
                                 slotparamvals_by_name), NamedSlotsNdarray(
                                     evecs, slotparamvals_by_name)

    def _energies_1(self, subsys):
        bare_label = np.zeros(len(self._hilbertspace))
        bare_label[self.get_subsys_index(subsys)] = 1

        energies_all_l = np.empty(self._parameters.counts +
                                  (subsys.truncated_dim, ))
        for l in range(subsys.truncated_dim):
            energies_all_l[..., l] = self[:].energy_by_bare_index(
                tuple(l * bare_label))
        return energies_all_l

    def _energies_2(self, subsys1, subsys2):
        bare_label1 = np.zeros(len(self._hilbertspace))
        bare_label1[self.get_subsys_index(subsys1)] = 1
        bare_label2 = np.zeros(len(self._hilbertspace))
        bare_label2[self.get_subsys_index(subsys2)] = 1

        energies_all_l1_l2 = np.empty(self._parameters.counts +
                                      (subsys1.truncated_dim, ) +
                                      (subsys2.truncated_dim, ))
        for l1 in range(subsys1.truncated_dim):
            for l2 in range(subsys2.truncated_dim):
                energies_all_l1_l2[..., l1, l2] = self[:].energy_by_bare_index(
                    tuple(l1 * bare_label1 + l2 * bare_label2))
        return energies_all_l1_l2

    def _dispersive_coefficients(
        self,
    ) -> Tuple[NamedSlotsNdarray, NamedSlotsNdarray, NamedSlotsNdarray]:
        energy_0 = self[:].energy_by_dressed_index(0).toarray()

        lamb_data = np.empty(self.subsystem_count, dtype=object)
        kerr_data = np.empty((self.subsystem_count, self.subsystem_count),
                             dtype=object)

        for subsys_index1, subsys1 in enumerate(self._hilbertspace):
            energy_subsys1_all_l1 = self._energies_1(subsys1)
            bare_energy_subsys1_all_l1 = self["bare_evals"][
                subsys_index1].toarray()
            lamb_subsys1_all_l1 = (
                energy_subsys1_all_l1 - energy_0[..., None] -
                bare_energy_subsys1_all_l1 +
                bare_energy_subsys1_all_l1[..., 0][..., None])
            lamb_data[subsys_index1] = NamedSlotsNdarray(
                lamb_subsys1_all_l1, self._parameters.paramvals_by_name)

            for subsys_index2, subsys2 in enumerate(self._hilbertspace):
                energy_subsys2_all_l2 = self._energies_1(subsys2)
                energy_subsys1_subsys2_all_l1_l2 = self._energies_2(
                    subsys1, subsys2)
                kerr_subsys1_subsys2_all_l1_l2 = (
                    energy_subsys1_subsys2_all_l1_l2 +
                    energy_0[..., None, None] -
                    energy_subsys1_all_l1[..., :, None] -
                    energy_subsys2_all_l2[..., None, :])
                if subsys1 is subsys2:
                    kerr_subsys1_subsys2_all_l1_l2 /= 2.0  # self-Kerr needs factor 1/2

                kerr_data[subsys_index1, subsys_index2] = NamedSlotsNdarray(
                    kerr_subsys1_subsys2_all_l1_l2,
                    self._parameters.paramvals_by_name)

        sys_indices = np.arange(self.subsystem_count)
        lamb_data = NamedSlotsNdarray(lamb_data, {"subsys": sys_indices})
        kerr_data = NamedSlotsNdarray(kerr_data, {
            "subsys1": sys_indices,
            "subsys2": sys_indices
        })
        chi_data = kerr_data.copy()

        for subsys_index1, subsys1 in enumerate(self._hilbertspace):
            for subsys_index2, subsys2 in enumerate(self._hilbertspace):
                if subsys1 in self.osc_subsys_list:
                    if subsys2 in self.qbt_subsys_list:
                        chi_data[subsys_index1,
                                 subsys_index2] = chi_data[subsys_index1,
                                                           subsys_index2][...,
                                                                          1, :]
                        kerr_data[subsys_index1,
                                  subsys_index2] = np.asarray([])
                    else:
                        chi_data[subsys_index1, subsys_index2] = np.asarray([])
                elif subsys1 in self.qbt_subsys_list:
                    if subsys2 in self.osc_subsys_list:
                        chi_data[subsys_index1, subsys_index2] = chi_data[
                            subsys_index1, subsys_index2][..., :, 1]
                        kerr_data[subsys_index1,
                                  subsys_index2] = np.asarray([])
                    else:
                        chi_data[subsys_index1, subsys_index2] = np.asarray([])

        return lamb_data, chi_data, kerr_data
Esempio n. 4
0
def tst_name():
    tst = Parameters(paramvals_by_name)
    assert tst.names[0] == "p1"
Esempio n. 5
0
def test_paravals_list():
    tst = Parameters(paramvals_by_name)
    assert tst.paramvals_list == [paramvals1, paramvals2]
Esempio n. 6
0
def test_iterate():
    tst = Parameters(paramvals_by_name)
    lst = [paramvals1, paramvals2]
    for index, paramvals in enumerate(tst):
        assert np.allclose(paramvals, lst[index])
Esempio n. 7
0
def test_params_count():
    tst = Parameters(paramvals_by_name)
    assert tst.counts[1] == len(paramvals2)
Esempio n. 8
0
def test_get_by_index():
    tst = Parameters(paramvals_by_name)
    assert np.allclose(tst[1], paramvals2)
Esempio n. 9
0
def test_get_by_name():
    tst = Parameters(paramvals_by_name)
    assert np.allclose(tst["p1"], paramvals1)
Esempio n. 10
0
def test_initialize_parameters():
    tst = Parameters(paramvals_by_name)