Source code for scqubits.core.spec_lookup

# spec_lookup.py
#
# This file is part of scqubits.
#
#    Copyright (c) 2019 and later, Jens Koch and Peter Groszkowski
#    All rights reserved.
#
#    This source code is licensed under the BSD-style license found in the
#    LICENSE file in the root directory of this source tree.
############################################################################

import itertools
import warnings
import weakref

from functools import wraps
from typing import TYPE_CHECKING, Callable, List, Tuple, Union

import numpy as np
import qutip as qt

from numpy import ndarray
from qutip import Qobj

import scqubits
import scqubits.io_utils.fileio_serializers as serializers
import scqubits.utils.misc as utils
import scqubits.utils.spectrum_utils as spec_utils

if TYPE_CHECKING:
    from scqubits import HilbertSpace, ParameterSweep, SpectrumData
    from scqubits.core.qubit_base import QuantumSystem
    from scqubits.io_utils.fileio import IOData
    from scqubits.io_utils.fileio_qutip import QutipEigenstates


[docs]class SpectrumLookup(serializers.Serializable): """ The `SpectrumLookup` is an integral building block of the `HilbertSpace` and `ParameterSweep` classes. In both cases it provides a convenient way to translate back and forth between labelling of eigenstates and eigenenergies via the indices of the dressed spectrum j = 0, 1, 2, ... on one hand, and the bare product-state labels of the form (0,0,0), (0,0,1), (0,1,0),... (here for the example of three subsys_list). The lookup table stored in a `SpectrumLookup` instance should be generated by calling `<HilbertSpace>.generate_lookup()` in the case of a `HilbertSpace` object. For `ParameterSweep` objects, the lookup table is generated automatically upon init, or manually via `<ParameterSweep>.run()`. Parameters ---------- framework: dressed_specdata: dressed spectral data needed for generating the lookup mapping bare_specdata_list: bare spectral data needed for generating the lookup mapping auto_run: boolean variable that determines whether the lookup data is immediately generated upon initialization """ def __init__( self, framework: "Union[ParameterSweep, HilbertSpace, None]", dressed_specdata: "SpectrumData", bare_specdata_list: List["SpectrumData"], auto_run: bool = True, ) -> None: self._dressed_specdata = dressed_specdata self._bare_specdata_list = bare_specdata_list self._canonical_bare_labels: List[Tuple[int, ...]] self._dressed_indices: List[List[Union[int, None]]] self._out_of_sync = False self._init_params = [ "_dressed_specdata", "_bare_specdata_list", "_canonical_bare_labels", "_dressed_indices", ] # Store ParameterSweep and/or HilbertSpace objects only as weakref.proxy # objects to avoid circular references that would prevent objects from # expiring appropriately and being garbage collected if isinstance(framework, scqubits.core._param_sweep._ParameterSweep): self._sweep = weakref.proxy(framework) self._hilbertspace = weakref.proxy(self._sweep._hilbertspace) elif isinstance(framework, scqubits.HilbertSpace): self._sweep = None self._hilbertspace = weakref.proxy(framework) else: self._sweep = None self._hilbertspace = None if auto_run: self.run() def run(self): self._canonical_bare_labels = self._generate_bare_labels() self._dressed_indices = ( self._generate_mappings() ) # lists of as many elements as there are parameter values. # For HilbertSpace objects the above is a single-element list.
[docs] @classmethod def deserialize(cls, io_data: "IOData") -> "SpectrumLookup": """ Take the given IOData and return an instance of the described class, initialized with the data stored in io_data. """ alldata_dict = io_data.as_kwargs() new_spectrum_lookup = cls( framework=None, dressed_specdata=alldata_dict["_dressed_specdata"], bare_specdata_list=alldata_dict["_bare_specdata_list"], auto_run=False, ) new_spectrum_lookup._canonical_bare_labels = alldata_dict[ "_canonical_bare_labels" ] new_spectrum_lookup._dressed_indices = alldata_dict["_dressed_indices"] return new_spectrum_lookup
def _generate_bare_labels(self) -> List[Tuple[int, ...]]: """ Generates the list of bare-state labels in canonical order. For example, for a Hilbert space composed of two subsys_list sys1 and sys2, each label is of the type (3,0) meaning sys1 is in bare eigenstate 3, sys2 in bare eigenstate 0. The full list the reads [(0,0), (0,1), (0,2), ..., (0,max_2), (1,0), (1,1), (1,2), ..., (1,max_2), ... (max_1,0), (max_1,1), (max_1,2), ..., (max_1,max_2)] """ dim_list = self._hilbertspace.subsystem_dims subsys_count = self._hilbertspace.subsystem_count basis_label_ranges = [] for subsys_index in range(subsys_count): basis_label_ranges.append(range(dim_list[subsys_index])) basis_labels_list = list( itertools.product(*basis_label_ranges) ) # generate list of bare basis states (tuples) return basis_labels_list def _generate_mappings(self) -> List[List[Union[int, None]]]: """ For each parameter value of the parameter sweep (may only be one if called from HilbertSpace, so no sweep), generate the map between bare states and dressed states. Returns ------- each list item is a list of dressed indices whose order corresponds to the ordering of bare indices (as stored in .canonical_bare_labels, thus establishing the mapping """ param_indices = range(self._dressed_specdata.param_count) dressed_indices_list = [] for index in param_indices: dressed_indices = self._generate_single_mapping(index) dressed_indices_list.append(dressed_indices) return dressed_indices_list def _generate_single_mapping(self, param_index: int) -> List[Union[int, None]]: """ For a single parameter value with index `param_index`, create a list of the dressed-state indices in an order that corresponds one to one to the canonical bare-state product states with largest overlap (whenever possible). Parameters ---------- param_index: index of the parameter value Returns ------- dressed-state indices """ overlap_matrix = spec_utils.convert_evecs_to_ndarray( self._dressed_specdata.state_table[param_index] ) dressed_indices: List[Union[int, None]] = [] for bare_basis_index in range( self._hilbertspace.dimension ): # for given bare basis index, find dressed index max_position = (np.abs(overlap_matrix[:, bare_basis_index])).argmax() max_overlap = np.abs(overlap_matrix[max_position, bare_basis_index]) if max_overlap < 0.5: # overlap too low, make no assignment dressed_indices.append(None) else: dressed_indices.append(max_position) return dressed_indices
[docs] @utils.check_sync_status def dressed_index( self, bare_labels: Tuple[int, ...], param_index: int = 0 ) -> Union[int, None]: """ For given bare product state return the corresponding dressed-state index. Parameters ---------- bare_labels: bare_labels = (index, index2, ...) param_index: index of parameter value of interest Returns ------- dressed state index closest to the specified bare state """ try: lookup_position = self._canonical_bare_labels.index(bare_labels) except ValueError: return None return self._dressed_indices[param_index][lookup_position]
[docs] @utils.check_sync_status def bare_index( self, dressed_index: int, param_index: int = 0 ) -> Union[Tuple[int, ...], None]: """ For given dressed index, look up the corresponding bare index. Returns ------- Bare state specification in tuple form. Example: (1,0,3) means subsystem 1 is in bare state 1, subsystem 2 in bare state 0, and subsystem 3 in bare state 3. """ try: lookup_position = self._dressed_indices[param_index].index(dressed_index) except ValueError: return None basis_labels = self._canonical_bare_labels[lookup_position] return basis_labels
[docs] @utils.check_sync_status def dressed_eigenstates(self, param_index: int = 0) -> List["QutipEigenstates"]: """ Return the list of dressed eigenvectors Parameters ---------- param_index: position index of parameter value in question, if called from within `ParameterSweep` Returns ------- dressed eigenvectors for the external parameter fixed to the value indicated by the provided index """ return self._dressed_specdata.state_table[param_index]
[docs] @utils.check_sync_status def dressed_eigenenergies(self, param_index: int = 0) -> ndarray: """ Return the array of dressed eigenenergies Parameters ---------- position index of parameter value in question Returns ------- dressed eigenenergies for the external parameter fixed to the value indicated by the provided index """ return self._dressed_specdata.energy_table[param_index]
[docs] @utils.check_sync_status def energy_bare_index( self, bare_tuple: Tuple[int, ...], param_index: int = 0 ) -> Union[float, None]: """ Look up dressed energy most closely corresponding to the given bare-state labels Parameters ---------- bare_tuple: bare state indices param_index: index specifying the position in the self.param_vals array Returns ------- dressed energy, if lookup successful """ dressed_index = self.dressed_index(bare_tuple, param_index) if dressed_index is None: return None return self._dressed_specdata.energy_table[param_index][dressed_index]
[docs] @utils.check_sync_status def energy_dressed_index(self, dressed_index: int, param_index: int = 0) -> float: """ Look up the dressed eigenenergy belonging to the given dressed index. Parameters ---------- dressed_index: index of dressed state of interest param_index: relevant if used in the context of a ParameterSweep Returns ------- dressed energy """ return self._dressed_specdata.energy_table[param_index][dressed_index]
[docs] @utils.check_sync_status def bare_eigenstates( self, subsys: "QuantumSystem", param_index: int = 0 ) -> ndarray: """ Return ndarray of bare eigenstates for given subsystem and parameter index. Eigenstates are expressed in the basis internal to the subsystem. """ framework = self._sweep or self._hilbertspace subsys_index = framework.get_subsys_index(subsys) return self._bare_specdata_list[subsys_index].state_table[param_index]
[docs] @utils.check_sync_status def bare_eigenenergies( self, subsys: "QuantumSystem", param_index: int = 0 ) -> ndarray: """ Return list of bare eigenenergies for given subsystem. Parameters ---------- subsys: Hilbert space subsystem for which bare eigendata is to be looked up param_index: position index of parameter value in question Returns ------- bare eigenenergies for the specified subsystem and the external parameter fixed to the value indicated by its index """ subsys_index = self._hilbertspace.get_subsys_index(subsys) return self._bare_specdata_list[subsys_index].energy_table[param_index]
[docs] def bare_productstate(self, bare_index: Tuple[int, ...]) -> Qobj: """ Return the bare product state specified by `bare_index`. Parameters ---------- bare_index: Returns ------- ket in full Hilbert space """ subsys_dims = self._hilbertspace.subsystem_dims product_state_list = [] for subsys_index, state_index in enumerate(bare_index): dim = subsys_dims[subsys_index] product_state_list.append(qt.basis(dim, state_index)) return qt.tensor(*product_state_list)