Source code for scqubits.core.spec_lookup

# spec_lookup.py
#
# This file is part of scqubits.
#
#    Copyright (c) 2019, 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

import numpy as np
import qutip as qt

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


def check_sync_status(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        if self._out_of_sync:
            warnings.warn("SCQUBITS\nSpectrum lookup data is out of sync with systems originally involved in generating it. This "
                          "will generally lead to incorrect results. Consider regenerating the lookup data using "
                          "<HilbertSpace>.generate_lookup() or <ParameterSweep>.run()", Warning)
        return func(self, *args, **kwargs)
    return wrapper


[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: HilbertSpace or ParameterSweep dressed_specdata: SpectrumData dressed spectral data needed for generating the lookup mapping bare_specdata_list: SpectrumData bare spectral data needed for generating the lookup mapping """ def __init__(self, framework, dressed_specdata, bare_specdata_list): self._dressed_specdata = dressed_specdata self._bare_specdata_list = bare_specdata_list # 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.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: raise TypeError 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. self._out_of_sync = False # Setup for Serializable operations self._init_params = ['_dressed_specdata', '_bare_specdata_list'] def _generate_bare_labels(self): """ 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)] Returns ------- list of tuples of ints """ 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): """ 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 ------- list 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): """ 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: int Returns ------- list of int dressed-state indices """ overlap_matrix = spec_utils.convert_esys_to_ndarray(self._dressed_specdata.state_table[param_index]) dressed_indices = [] 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] @check_sync_status def dressed_index(self, bare_labels, param_index=0): """ For given bare product state return the corresponding dressed-state index. Parameters ---------- bare_labels: tuple(int) bare_labels = (index, index2, ...) param_index: int, optional index of parameter value of interest Returns ------- int 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] @check_sync_status def bare_index(self, dressed_index, param_index=0): """ For given dressed index, look up the corresponding bare index. Parameters ---------- dressed_index: int param_index: int Returns ------- tuple of int 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] @check_sync_status def dressed_eigenstates(self, param_index=0): """ Return the list of dressed eigenvectors Parameters ---------- param_index: int, optional position index of parameter value in question, if called from within ParameterSweep Returns ------- list of qutip.qobj eigenvectors dressed eigenvectors for the external parameter fixed to the value indicated by the provided index """ return self._dressed_specdata.state_table[param_index]
[docs] @check_sync_status def dressed_eigenenergies(self, param_index=0): """ Return the array of dressed eigenenergies Parameters ---------- param_index: int, optional position index of parameter value in question Returns ------- ndarray dressed eigenenergies for the external parameter fixed to the value indicated by the provided index """ return self._dressed_specdata.energy_table[param_index]
[docs] @check_sync_status def energy_bare_index(self, bare_tuple, param_index=0): """ Look up dressed energy most closely corresponding to the given bare-state labels Parameters ---------- bare_tuple: tuple(int) bare state indices param_index: int index specifying the position in the self.param_vals array Returns ------- float or None 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] @check_sync_status def energy_dressed_index(self, dressed_index, param_index=0): """ Look up the dressed eigenenergy belonging to the given dressed index. Parameters ---------- dressed_index: int param_index: int relevant if used in the context of a ParameterSweep Returns ------- dressed energy: float """ return self._dressed_specdata.energy_table[param_index][dressed_index]
[docs] @check_sync_status def bare_eigenstates(self, subsys, param_index=0): """ Return ndarray of bare eigenstates for given subsystem and parameter index. Eigenstates are expressed in the basis internal to the subsystem. Parameters ---------- subsys: QuantumSystem param_index: int, optional Returns ------- ndarray """ 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] @check_sync_status def bare_eigenenergies(self, subsys, param_index=0): """ Return list of bare eigenenergies for given subsystem. Parameters ---------- subsys: QuantumSystem Hilbert space subsystem for which bare eigendata is to be looked up param_index: int, optional position index of parameter value in question Returns ------- ndarray bare eigenenergies for the specified subsystem and the external parameter fixed to the value indicated by its index """ subsys_index = self._hilbertspace.index(subsys) return self._bare_specdata_list[subsys_index].energy_table[param_index]
[docs] def bare_productstate(self, bare_index): """ Return the bare product state specified by `bare_index`. Parameters ---------- bare_index: tuple of int Returns ------- qutip.Qobj 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)