Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

scqubits

Package Overview
Dependencies
Maintainers
2
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

scqubits - npm Package Compare versions

Comparing version
4.2.0
to
4.3
+32
.github/ISSUE_TEMPLATE/bug_report.md
---
name: Bug report
about: Create a bug report to help us improve scqubits
title: "[Bug report]"
labels: ''
assignees: jkochNU
---
### Documentation check
If the bug manifests in unexpected behavior (as opposed to a crash), confirm that you have consulted the
[API documentation](https://scqubits.readthedocs.io/en/latest/api-doc/apidoc.html)
- [ ] I have checked the API documentation.
- [ ] I could not locate relevant information in the documentation or information is missing.
### Describe the bug
Please provide a concise description of what the bug is.
### Expected behavior
To minimize misunderstandings, please state briefly what you expected to happen.
### To Reproduce
If not clear from your description above, please provide the steps to reproduce the behavior:
### OS and version used (please complete the following information):
- OS: [e.g. Linux]
- scqubits version [e.g., 1.1]
- Python version [e.g. 3.7]
### Additional context
Any additional information you would like to provide to aid us.
---
name: Ideas, Suggestions, Feature Requests
about: Suggest an idea for enhancing scqubits
title: "[Suggestion/request]"
labels: enhancement
assignees: jkochNU
---
**Idea for future enhancement of scqubits**
We welcome new ideas and suggestions for improving scqubits and enhancing its functionality in the future! Please describe what you have in mind. If your suggestion is related to a problem you have encountered, please describe the underlying problem.
---
name: Other issues
about: All other issues go here.
title: "[Other issue]"
labels: ''
assignees: jkochNU
---
name: black-check-latest
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install latest Black version
run: pip install black
- name: Run black --check --diff .
run: black --check --diff .
name: Locally install scqubits with mambabuild and run pytests
on:
push:
branches:
- main
- devel_peterg
- spc-main-devel
workflow_dispatch:
jobs:
build-all:
name: scqubits testing (${{ matrix.python-version }}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash -el {0}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
python-version: ['3.9', '3.10', '3.11', '3.12']
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Python setup for the rest of the jobs
uses: conda-incubator/setup-miniconda@v3
with:
miniforge-variant: Miniforge3
mamba-version: "*"
use-mamba: true
auto-update-conda: true
python-version: ${{ matrix.python-version }}
channels: conda-forge, defaults
channel-priority: true
auto-activate-base: true
- name: Add bin and Scripts to system path
run: |
echo $CONDA/bin >> $GITHUB_PATH
echo $CONDA/Scripts >> $GITHUB_PATH
- name: conda info
run: conda info
- name: Lint with flake8
run: |
conda install flake8
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Build with mambabuild
run: |
mamba install boa
conda mambabuild . --no-test
- name: Install scqubits locally and run tests
run: |
mamba install --use-local scqubits
mamba install pytest
mamba install pytest-cov
mamba install pathos
pytest -v --pyargs --cov=scqubits --cov-report=xml
pytest -v --pyargs scqubits --num_cpus=4
- name: Upload results to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true
name: Upload Python Package to PyPi
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: |
python setup.py sdist bdist_wheel
twine upload dist/*
.DS_Store
.idea/
.asv/
# Byte compiled
./scqubits/io_utils/__pycache__
./scqubits/utils/__pycache__
./scqubits/core/__pycache__
./scqubits/ui/__pycache__
./scqubits/__pycache__
*.py[cod]
# Packages
*.egg
*.egg-info
*.pdf
/scqubits/.ipynb_checkpoints/
/scqubits/version.py
/scqubits.egg-info/
/build/
/dist/
/sc_qubits.egg-info/
/scqubits/tests/_tempdata/
/scqubits/core/hspace.py
/scratch/
/.vscode/
/.pytest_cache/
/.coverage
/.ipynb_checkpoints/
/example/
scqubits/ui/test.ipynb
scqubits/ui/.ipynb_checkpoints/test-checkpoint.ipynb
scqubits/.vscode/settings.json
*.ipynb
/.asv/
# Python package
# Create and test a Python package on multiple Python versions.
# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
trigger:
branches:
include:
- main
- spc-main-devel
- jk-devel
- Danny_vchos
- devel_peterg
- peterg_qutip5
jobs:
- job: Linux
pool:
vmImage: 'ubuntu-latest'
strategy:
matrix:
Python39:
python.version: '3.9'
Python310:
python.version: '3.10'
Python311:
python.version: '3.11'
Python312:
python.version: '3.12'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'
- script: |
pip install pytest pytest-azurepipelines
pip install ".[gui, develop]"
displayName: 'Install scqubits'
- script: |
pip install pipdeptree
pipdeptree
displayName: 'pipdeptree'
- script: |
pytest -v --pyargs scqubits
displayName: 'Pytest: single-core'
- script: |
export OMP_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export MKL_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
export NUMEXPR_NUM_THREADS=1
pytest -v --pyargs scqubits --num_cpus=4
displayName: 'Pytest: multiprocessing'
#
#
- job: Windows
pool:
vmImage: 'windows-latest'
strategy:
matrix:
Python39:
python.version: '3.9'
Python310:
python.version: '3.10'
Python311:
python.version: '3.11'
Python312:
python.version: '3.12'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'
- script: |
pip install pytest-azurepipelines
pip install ".[gui, develop]"
displayName: 'Install scqubits'
- script: |
pip install pipdeptree
pipdeptree
displayName: 'pipdeptree'
- script: |
pytest -v --pyargs scqubits
displayName: 'Pytest: single-core'
- script: |
set OMP_NUM_THREADS=1
set OPENBLAS_NUM_THREADS=1
set MKL_NUM_THREADS=1
set VECLIB_MAXIMUM_THREADS=1
set NUMEXPR_NUM_THREADS=1
pytest -v --pyargs scqubits --num_cpus=4
displayName: 'Pytest: multiprocessing'
#
#
- job: macOS
pool:
vmImage: "macos-latest"
strategy:
matrix:
Python39:
python.version: '3.9'
Python310:
python.version: '3.10'
Python311:
python.version: '3.11'
Python312:
python.version: '3.12'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '$(python.version)'
displayName: 'Use Python $(python.version)'
- script: |
pip install pytest-azurepipelines
pip install pytest-xdist
pip install ".[gui, develop]"
displayName: 'Install scqubits'
- script: |
pip install pipdeptree
pipdeptree
displayName: 'pipdeptree'
- script: |
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
pytest -v --pyargs scqubits
displayName: 'Pytest: single-core'
- script: |
export OMP_NUM_THREADS=1
export OPENBLAS_NUM_THREADS=1
export MKL_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1
export NUMEXPR_NUM_THREADS=1
export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES
pytest -v --pyargs scqubits --num_cpus=4
displayName: 'Pytest: multiprocessing'
@article{scqubits2,
title = {Computer-aided quantization and numerical analysis of superconducting circuits},
author = {S. P. Chitta et al.},
year = 2022,
journal = {New J.\ Phys.},
volume = 24,
pages = 103020,
doi = {10.1088/1367-2630/ac94f2},
url = {https://doi.org/10.1088/1367-2630/ac94f2}
}
@article{scqubits1,
title = {scqubits: a Python package for superconducting circuits},
author = {P. Groszkowski and Jens Koch},
year = 2021,
journal = {Quantum},
volume = 5,
pages = 583,
doi = {10.22331/q-2021-11-17-583},
url = {https://doi.org/10.22331/q-2021-11-17-583}
}
{% set name = "scqubits" %}
{% set version = "4.1.0" %}
package:
name: {{ name|lower }}
version: {{ version }}
source:
# url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz
# sha256: b721600b0d782c9ffea03ff7cd69f7b01ed7f72f3022ff37e943881d7b17af41
path: .
build:
number: 0
skip: true # [py<37]
script: "{{ PYTHON }} -m pip install . -vv"
channels:
- conda-forge
requirements:
host:
- python
- pip
- cython >=0.29.20
- numpy >=1.14.2
build:
- python
- cython >=0.29.20
- numpy >=1.14.2
- scipy
- {{ compiler('cxx') }}
run:
- python
- cycler
- matplotlib >=3.5.1
- numpy >=1.14.2
- qutip >=4.3.1
- scipy >=1.5
- dill
- sympy
- tqdm
- typing_extensions
- h5py >=2.10
- pathos >=0.3.0
- traitlets
# Optional dependencies
- ipywidgets
- ipyvuetify
test:
requires:
- pytest
- pytest-cov
- pathos
source_files:
- scqubits/
imports:
- scqubits
commands:
- pytest -v --pyargs scqubits
- pytest -v --pyargs scqubits --num_cpus=4
about:
home: https://github.com/scqubits/scqubits
license: BSD-3-Clause
license_family: BSD
license_file: LICENSE
summary: Superconducting qubits in Python
description: |
scqubits is an open-source Python library for simulating superconducting qubits. It is meant to give the user a
convenient way to obtain energy spectra of common superconducting qubits, plot energy levels as a function of
external parameters, calculate matrix elements etc. The library further provides an interface to QuTiP, making it
easy to work with composite Hilbert spaces consisting of coupled superconducting qubits and harmonic modes.
Internally, numerics within scqubits is carried out with the help of Numpy and Scipy; plotting capabilities rely
on Matplotlib.
doc_url: https://scqubits.readthedocs.io/
dev_url: https://github.com/scqubits/scqubits
extra:
recipe-maintainers:
- jkochNU
- petergthatsme
[build-system]
requires = ["setuptools", "wheel", "setuptools_scm"]
build-backend = "setuptools.build_meta"
# Use setuptools_scm to generate version from Git tags
[tool.setuptools_scm]
write_to = "scqubits/version.py"
version_scheme = "only-version"
[project]
name = "scqubits"
description = "scqubits: superconducting qubits in Python"
dynamic = ["version"]
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
{ name = "Jens Koch", email = "jens-koch@northwestern.edu" },
{ name = "Peter Groszkowski", email = "piotrekg@gmail.com" },
]
keywords = [
"qubits",
"superconducting",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: BSD License",
"Operating System :: MacOS",
"Operating System :: Microsoft :: Windows",
"Operating System :: POSIX",
"Operating System :: Unix",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Topic :: Scientific/Engineering",
]
dependencies = [
"cycler",
"dill",
"pathos>=0.3.0",
"matplotlib>=3.5.1",
"numpy>=1.14.2",
"pathos>=0.3.0",
"qutip>=4.3.1",
"scipy>=1.5 ; sys_platform != 'darwin'",
"scipy>=1.5 ; sys_platform == 'darwin' and python_version < '3.10'",
"scipy>=1.5,<=1.13.1 ; sys_platform == 'darwin' and python_version >= '3.10'",
"sympy",
"tqdm",
"typing_extensions",
]
[project.optional-dependencies]
gui = [
"ipywidgets (>=7.5)", "ipyvuetify", "matplotlib-label-lines (>=0.3.6)", "h5py (>=2.10)",
]
develop = [
"pytest", "traitlets", "h5py (>=2.10)",
]
[project.urls]
Homepage = "https://scqubits.readthedocs.io"
Repository = "https://github.com/scqubits/scqubits"
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
if TYPE_CHECKING:
from scqubits.core.circuit import Subsystem
import numpy as np
import sympy as sm
from matplotlib import pyplot as plt
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from numpy import ndarray
import scqubits.core.discretization as discretization
import scqubits.core.oscillator as osc
import scqubits.core.storage as storage
import scqubits.utils.plot_defaults as defaults
import scqubits.utils.plotting as plot
from scqubits import get_units
from scqubits.io_utils.fileio_serializers import dict_serialize
from scqubits.core.circuit_utils import (
sawtooth_potential,
get_trailing_number,
)
from scqubits.utils.misc import (
flatten_list_recursive,
list_intersection,
unique_elements_in_list,
)
from scqubits.utils.plot_utils import _process_options
from abc import ABC
class CircuitPlot(ABC):
# ****************************************************************
# ************* Functions for plotting wave function *************
# ****************************************************************
def _recursive_basis_change(
self, wf_reshaped, wf_dim, subsystem, relevant_indices=None
):
"""Method to change the basis recursively, to reverse hierarchical
diagonalization and get to the basis in which the variables were initially
defined.
Parameters
----------
wf_dim:
The dimension of the wave function which needs to be rewritten in terms of
the initial basis
"""
U_subsys = subsystem.eigensys(evals_count=subsystem.truncated_dim)[
1
] # eigensys(evals_count=subsystem.truncated_dim)
wf_sublist = list(range(len(wf_reshaped.shape)))
U_sublist = [wf_dim, len(wf_sublist)]
target_sublist = wf_sublist.copy()
target_sublist[wf_dim] = len(wf_sublist)
wf_new_basis = np.einsum(
wf_reshaped, wf_sublist, U_subsys.T, U_sublist, target_sublist
)
if subsystem.hierarchical_diagonalization:
wf_shape = list(wf_new_basis.shape)
wf_shape[wf_dim] = [
sub_subsys.truncated_dim for sub_subsys in subsystem.subsystems
]
wf_new_basis = wf_new_basis.reshape(flatten_list_recursive(wf_shape))
for sub_subsys_index, sub_subsys in enumerate(subsystem.subsystems):
if len(set(relevant_indices) & set(sub_subsys.dynamic_var_indices)) > 0:
wf_new_basis = self._recursive_basis_change(
wf_new_basis,
wf_dim + sub_subsys_index,
sub_subsys,
relevant_indices=relevant_indices,
)
else:
if len(set(relevant_indices) & set(subsystem.dynamic_var_indices)) > 0:
wf_shape = list(wf_new_basis.shape)
wf_shape[wf_dim] = [
(
getattr(subsystem, cutoff_attrib)
if "ext" in cutoff_attrib
else (2 * getattr(subsystem, cutoff_attrib) + 1)
)
for cutoff_attrib in subsystem.cutoff_names
]
wf_new_basis = wf_new_basis.reshape(flatten_list_recursive(wf_shape))
return wf_new_basis
def _basis_change_harm_osc_to_n(
self, wf_original_basis, wf_dim, var_index, grid_n: discretization.Grid1d
):
"""Method to change the basis from harmonic oscillator to n basis."""
U_ho_n = np.array(
[
osc.harm_osc_wavefunction(
n,
grid_n.make_linspace(),
abs(self.get_osc_param(var_index, which_param="length")),
)
for n in range(getattr(self, "cutoff_ext_" + str(var_index)))
]
)
wf_sublist = [idx for idx, _ in enumerate(wf_original_basis.shape)]
U_sublist = [wf_dim, len(wf_sublist)]
target_sublist = wf_sublist.copy()
target_sublist[wf_dim] = len(wf_sublist)
wf_new_basis = np.einsum(
wf_original_basis, wf_sublist, U_ho_n.T, U_sublist, target_sublist
)
return wf_new_basis
def _basis_change_harm_osc_to_phi(
self, wf_original_basis, wf_dim, var_index, grid_phi: discretization.Grid1d
):
"""Method to change the basis from harmonic oscillator to phi basis."""
U_ho_phi = np.array(
[
osc.harm_osc_wavefunction(
n,
grid_phi.make_linspace(),
abs(self.get_osc_param(var_index, which_param="length")),
)
for n in range(getattr(self, "cutoff_ext_" + str(var_index)))
]
)
wf_sublist = [idx for idx, _ in enumerate(wf_original_basis.shape)]
U_sublist = [wf_dim, len(wf_sublist)]
target_sublist = wf_sublist.copy()
target_sublist[wf_dim] = len(wf_sublist)
wf_ext_basis = np.einsum(
wf_original_basis, wf_sublist, U_ho_phi, U_sublist, target_sublist
)
return wf_ext_basis
def _basis_change_n_to_phi(
self, wf_original_basis, wf_dim, var_index, grid_phi: discretization.Grid1d
):
"""Method to change the basis from harmonic oscillator to phi basis."""
U_n_phi = np.array(
[
np.exp(n * grid_phi.make_linspace() * 1j)
for n in range(
-getattr(self, "cutoff_n_" + str(var_index)),
getattr(self, "cutoff_n_" + str(var_index)) + 1,
)
]
)
wf_sublist = list(range(len(wf_original_basis.shape)))
U_sublist = [wf_dim, len(wf_sublist)]
target_sublist = wf_sublist.copy()
target_sublist[wf_dim] = len(wf_sublist)
wf_ext_basis = np.einsum(
wf_original_basis, wf_sublist, U_n_phi, U_sublist, target_sublist
)
return wf_ext_basis
def _get_var_dim_for_reshaped_wf(self, wf_var_indices, var_index):
wf_dim = 0
if not self.hierarchical_diagonalization:
return self.dynamic_var_indices.index(var_index)
for subsys in self.subsystems:
intersection = list_intersection(subsys.dynamic_var_indices, wf_var_indices)
if len(intersection) > 0 and var_index not in intersection:
if subsys.hierarchical_diagonalization:
wf_dim += subsys._get_var_dim_for_reshaped_wf(
wf_var_indices, var_index
)
else:
wf_dim += len(subsys.dynamic_var_indices)
elif len(intersection) > 0 and var_index in intersection:
if subsys.hierarchical_diagonalization:
wf_dim += subsys._get_var_dim_for_reshaped_wf(
wf_var_indices, var_index
)
else:
wf_dim += subsys.dynamic_var_indices.index(var_index)
break
else:
wf_dim += 1
return wf_dim
def _dims_to_be_summed(self, var_indices: Tuple[int], num_wf_dims) -> List[int]:
all_var_indices = self.dynamic_var_indices
non_summed_dims = []
for var_index in all_var_indices:
if var_index in var_indices:
non_summed_dims.append(
self._get_var_dim_for_reshaped_wf(var_indices, var_index)
)
return [dim for dim in range(num_wf_dims) if dim not in non_summed_dims]
def _reshape_and_change_to_variable_basis(
self, wf: ndarray, var_indices: Tuple[int]
) -> ndarray:
"""This method changes the basis of the wavefunction when hierarchical
diagonalization is used.
Then reshapes the wavefunction to represent each of the variable indices as a
separate dimension.
"""
if self.hierarchical_diagonalization:
subsys_index_for_var_index = unique_elements_in_list(
[self.get_subsystem_index(index) for index in var_indices]
) # getting the subsystem index for each of the variable indices
subsys_index_for_var_index.sort()
subsys_trunc_dims = [sys.truncated_dim for sys in self.subsystems]
# reshaping the wave functions to truncated dims of subsystems
wf_hd_reshaped = wf.reshape(*subsys_trunc_dims)
# **** Converting to the basis in which the variables are defined *****
wf_original_basis = wf_hd_reshaped
for subsys_index in subsys_index_for_var_index:
wf_dim = 0
for sys_index in range(subsys_index):
if sys_index in subsys_index_for_var_index:
wf_dim += len(self.subsystems[sys_index].dynamic_var_indices)
else:
wf_dim += 1
wf_original_basis = self._recursive_basis_change(
wf_original_basis,
wf_dim,
self.subsystems[subsys_index],
relevant_indices=var_indices,
)
else:
wf_original_basis = wf.reshape(
*[
(
getattr(self, cutoff_attrib)
if "ext" in cutoff_attrib
else (2 * getattr(self, cutoff_attrib) + 1)
)
for cutoff_attrib in self.cutoff_names
]
)
return wf_original_basis
def _basis_for_var_index(self, var_index: int) -> str:
"""Returns the ext_basis of the subsystem with no further subsystems to which
the var_index belongs."""
if self.hierarchical_diagonalization:
subsys = self.subsystems[self.get_subsystem_index(var_index)]
return subsys._basis_for_var_index(var_index)
else:
if var_index in self.var_categories["extended"]:
return self.ext_basis
else:
return "periodic"
def _change_to_phi_basis(
self,
wf_original_basis: ndarray,
var_indices: Tuple[int],
grids_dict: Dict[int, Union[discretization.Grid1d, ndarray]],
change_discrete_charge_to_phi: bool,
):
"""Changes the basis of the varaible indices to discretized phi basis which is
amenable to plotting."""
wf_ext_basis = wf_original_basis
for var_index in var_indices:
# finding the dimension corresponding to the var_index
if not self.hierarchical_diagonalization:
wf_dim = self.dynamic_var_indices.index(var_index)
else:
wf_dim = self._get_var_dim_for_reshaped_wf(var_indices, var_index)
var_basis = self._basis_for_var_index(var_index)
if var_basis == "harmonic":
wf_ext_basis = self._basis_change_harm_osc_to_phi(
wf_ext_basis, wf_dim, var_index, grids_dict[var_index]
)
elif var_basis == "periodic" and change_discrete_charge_to_phi:
wf_ext_basis = self._basis_change_n_to_phi(
wf_ext_basis, wf_dim, var_index, grids_dict[var_index]
)
return wf_ext_basis
def generate_wf_plot_data(
self,
which: int = 0,
mode: str = "abs-sqr",
var_indices: Tuple[int] = (1,),
eigensys: ndarray = None,
change_discrete_charge_to_phi: bool = True,
grids_dict: Dict[int, discretization.Grid1d] = None,
):
"""Returns treated wave function of the current Circuit instance for the
specified variables.
Parameters
----------
which:
integer to choose which wave function to plot
mode:
"abs", "real", "imag", "abs-sqr" - decides which part of the wave
function is plotted.
var_indices:
A tuple containing the indices of the variables chosen to plot the
wave function in. Should not have more than 2 entries.
eigensys:
eigenvalues and eigenstates of the Circuit instance; if not provided,
calling this method will perform a diagonalization to obtain these.
extended_variable_basis: str
The basis in which the extended variables are plotted. Can be either
"phi" or "charge".
periodic_variable_basis: str
The basis in which the periodic variables are plotted. Can be either
"phi" or "charge".
grids_dict:
A dictionary which pairs var indices with the requested grids used to create
the plot.
"""
# checking to see if eigensys needs to be generated
if eigensys is None:
_, wfs = self.eigensys(evals_count=which + 1)
else:
_, wfs = eigensys
wf = wfs[:, which]
# change the wf to the basis in which the variables were initially defined
wf_original_basis = self._reshape_and_change_to_variable_basis(
wf=wf, var_indices=var_indices
)
# making a basis change to the desired basis for every var_index
wf_ext_basis = self._change_to_phi_basis(
wf_original_basis,
var_indices=var_indices,
grids_dict=grids_dict,
change_discrete_charge_to_phi=change_discrete_charge_to_phi,
)
# sum over the dimensions not relevant to the ones in var_indices
# finding the dimensions which needs to be summed over
dims_to_be_summed = self._dims_to_be_summed(
var_indices, len(wf_ext_basis.shape)
)
# summing over the dimensions
# summing over the dimensions
if mode == "abs-sqr":
wf_plot = np.sum(
np.abs(wf_ext_basis) ** 2,
axis=tuple(dims_to_be_summed),
)
return wf_plot
if mode == "abs":
if len(dims_to_be_summed) == 0:
return np.abs(wf_ext_basis)
else:
raise AttributeError(
"Cannot plot the absolute value of the wave function in more than 2 dimensions."
)
elif mode == "real":
if len(dims_to_be_summed) == 0:
return np.real(wf_ext_basis)
else:
raise AttributeError(
"Cannot plot the real part of the wave function in more than 2 dimensions."
)
elif mode == "imag":
if len(dims_to_be_summed) == 0:
return np.imag(wf_ext_basis)
else:
raise AttributeError(
"Cannot plot the imaginary part of the wave function in more than 2 dimensions."
)
def plot_wavefunction(
self,
which=0,
mode: str = "abs-sqr",
var_indices: Tuple[int] = (1,),
esys: Tuple[ndarray, ndarray] = None,
change_discrete_charge_to_phi: bool = True,
zero_calibrate: bool = True,
grids_dict: Dict[int, discretization.Grid1d] = {},
**kwargs,
) -> Tuple[Figure, Axes]:
"""Returns the plot of the wavefunction in the requested variables. At most 2
numbers of variables for wavefunction can be specified as plotting axis. If the
number of plotting variables for wave function is smaller than the number of
variables in the circuit, the marginal probability distribution of the state
with respect to the specified variables is plotted. This means the norm square
of the wave function is integrated over the rest of the variables and is then
plotted.
Parameters
----------
which:
integer to choose which wave function to plot
mode:
"abs", "real", "imag", "abs-sqr" - decides which part of the wave function is plotted,
by default "abs-sqr"
var_indices:
A tuple containing the indices of the variables chosen to plot the
wave function in. It should not have more than 2 entries.
esys:
The object returned by the method `.eigensys`, is used to avoid the
re-evaluation of the eigen systems if already evaluated.
change_discrete_charge_to_phi:
If True, the wave function is plotted in the phi basis for the periodic
variables. If False, the wave function is plotted in the charge basis
for the periodic variables.
zero_calibrate: bool, optional
if True, colors are adjusted to use zero wavefunction amplitude as the
neutral color in the palette
grids_dict:
A dictionary which pairs var indices with the grids used to create
the plot. The way to specify the grids is as follows:
1. For extended variables, the grids should be of type `discretization.Grid1d`.
2. When the discretized phi basis is used for the extended variable, the grids
used in the diagonalization is used to plot the wave function instead of
the grids specified here.
3. For periodic variables, only if `change_discrete_charge_to_phi` is True,
the grid specified here will used for plotting. The grid is specified as an integer
which is the number of points in the grid. The grid has a minimum and maximum value
of -pi and pi respectively.
4. If the grid is not specified for a variable that requires a grid for plotting (i.e.
extended variable with harmonic oscillator basis, or periodic variable with
`change_discrete_charge_to_phi` set to True), the default grid is used.
**kwargs:
plotting parameters
Returns
-------
Returns a axes and figure for further editing.
"""
if len(var_indices) > 2:
raise AttributeError(
"Cannot plot wave function in more than 2 dimensions. The number of "
"dimensions should be less than 2."
)
var_indices = np.sort(var_indices)
grids_per_varindex_dict = grids_dict or self.discretized_grids_dict_for_vars()
plot_data = self.generate_wf_plot_data(
which=which,
mode=mode,
var_indices=var_indices,
eigensys=esys,
change_discrete_charge_to_phi=change_discrete_charge_to_phi,
grids_dict=grids_per_varindex_dict,
)
var_types = []
for var_index in var_indices:
if var_index in self.var_categories["periodic"]:
if not change_discrete_charge_to_phi:
var_types.append("Charge in units of 2e, periodic variable:")
else:
var_types.append("Dimensionless flux, periodic variable:")
if var_index in self.var_categories["extended"]:
var_types.append("Dimensionless flux, extended variable:")
if len(var_indices) == 1:
return self._plot_wf_pdf_1D(
plot_data,
mode,
var_indices,
grids_per_varindex_dict,
change_discrete_charge_to_phi,
kwargs,
)
elif len(var_indices) == 2:
return self._plot_wf_pdf_2D(
plot_data,
var_indices,
grids_per_varindex_dict,
change_discrete_charge_to_phi,
zero_calibrate=zero_calibrate,
kwargs=kwargs,
)
def _plot_wf_pdf_2D(
self,
wf_plot: ndarray,
var_indices,
grids_per_varindex_dict,
change_discrete_charge_to_phi: bool,
zero_calibrate: bool,
kwargs,
) -> Tuple[Figure, Axes]:
# check if each variable is periodic
grids = []
labels = []
for index_order in [1, 0]:
if not change_discrete_charge_to_phi and (
var_indices[index_order] in self.var_categories["periodic"]
):
grids.append(
[
-getattr(self, "cutoff_n_" + str(var_indices[index_order])),
getattr(self, "cutoff_n_" + str(var_indices[index_order])),
2 * getattr(self, "cutoff_n_" + str(var_indices[index_order]))
+ 1,
]
)
labels.append(r"$n_{{{}}}$".format(str(var_indices[index_order])))
else:
grids.append(
list(
grids_per_varindex_dict[var_indices[index_order]]
.get_initdata()
.values()
),
)
labels.append(r"$\theta_{{{}}}$".format(str(var_indices[index_order])))
wavefunc_grid = discretization.GridSpec(np.asarray(grids))
wavefunc = storage.WaveFunctionOnGrid(wavefunc_grid, wf_plot)
# obtain fig and axes from
fig, axes = plot.wavefunction2d(
wavefunc,
zero_calibrate=zero_calibrate,
ylabel=labels[1],
xlabel=labels[0],
**kwargs,
)
# change frequency of tick mark for variables in charge basis
# also force the tick marks to be integers
if not change_discrete_charge_to_phi:
if var_indices[0] in self.var_categories["periodic"]:
if getattr(self, "cutoff_n_" + str(var_indices[0])) >= 6:
axes.yaxis.set_major_locator(plt.MaxNLocator(13, integer=True))
else:
axes.yaxis.set_major_locator(
plt.MaxNLocator(
1 + 2 * getattr(self, "cutoff_n_" + str(var_indices[0])),
integer=True,
)
)
if var_indices[1] in self.var_categories["periodic"]:
if getattr(self, "cutoff_n_" + str(var_indices[1])) >= 15:
axes.xaxis.set_major_locator(plt.MaxNLocator(31, integer=True))
else:
axes.xaxis.set_major_locator(
plt.MaxNLocator(
1 + 2 * getattr(self, "cutoff_n_" + str(var_indices[1])),
integer=True,
)
)
return fig, axes
def _plot_wf_pdf_1D(
self,
wf_plot: ndarray,
mode: str,
var_indices,
grids_per_varindex_dict,
change_discrete_charge_to_phi: bool,
kwargs,
) -> Tuple[Figure, Axes]:
var_index = var_indices[0]
if not change_discrete_charge_to_phi and (
var_indices[0] in self.var_categories["periodic"]
):
ncut = self.cutoffs_dict()[var_indices[0]]
wavefunc = storage.WaveFunction(
basis_labels=np.linspace(-ncut, ncut, 2 * ncut + 1),
amplitudes=wf_plot,
)
kwargs = {
**defaults.wavefunction1d_discrete("abs_sqr"),
**kwargs,
}
wavefunc.basis_labels = np.arange(
-getattr(self, "cutoff_n_" + str(var_index)),
getattr(self, "cutoff_n_" + str(var_index)) + 1,
)
fig, axes = plot.wavefunction1d_discrete(wavefunc, **kwargs)
# changing the tick frequency for axes
if getattr(self, "cutoff_n_" + str(var_index)) >= 7:
axes.xaxis.set_major_locator(plt.MaxNLocator(15, integer=True))
else:
axes.xaxis.set_major_locator(
plt.MaxNLocator(1 + 2 * getattr(self, "cutoff_n_" + str(var_index)))
)
else:
wavefunc = storage.WaveFunction(
basis_labels=grids_per_varindex_dict[var_indices[0]].make_linspace(),
amplitudes=wf_plot,
)
if mode == "abs":
ylabel = r"$|\psi(\theta_{{{}}})|$".format(str(var_indices[0]))
elif mode == "abs-sqr":
ylabel = r"$|\psi(\theta_{{{}}})|^2$".format(str(var_indices[0]))
elif mode == "real":
ylabel = r"$\mathrm{{Re}}(\psi(\theta_{{{}}}))$".format(
str(var_indices[0])
)
elif mode == "imag":
ylabel = r"$\mathrm{{Im}}(\psi(\theta_{{{}}}))$".format(
str(var_indices[0])
)
fig, axes = plot.wavefunction1d_nopotential(
wavefunc,
0,
xlabel=r"$\theta_{{{}}}$".format(str(var_indices[0])),
ylabel=ylabel,
**kwargs,
)
return fig, axes
# ****************************************************************
# ************* Functions for plotting potential *****************
# ****************************************************************
def potential_energy(self, **kwargs) -> ndarray:
"""Returns the full potential of the circuit evaluated in a grid of points as
chosen by the user or using default variable ranges.
Parameters
----------
θ<index>:
value(s) for variable :math:`\theta_i` in the potential.
"""
periodic_indices = self.var_categories["periodic"]
discretized_ext_indices = self.var_categories["extended"]
var_categories = discretized_ext_indices + periodic_indices
# substituting the parameters
potential_sym = self.potential_symbolic.subs("I", 1)
for ext_flux in self.external_fluxes:
potential_sym = potential_sym.subs(ext_flux, ext_flux * 2 * np.pi)
# constructing the grids
parameters = dict.fromkeys(
[f"θ{index}" for index in var_categories]
+ [var.name for var in self.external_fluxes]
+ [var.name for var in self.symbolic_params]
)
for var_name in kwargs:
if isinstance(kwargs[var_name], np.ndarray):
parameters[var_name] = kwargs[var_name]
elif isinstance(kwargs[var_name], (int, float)):
parameters[var_name] = kwargs[var_name]
else:
raise AttributeError(
"Only float, int or Numpy ndarray assignments are allowed."
)
for var_name in parameters.keys():
if parameters[var_name] is None:
if var_name in [
var.name
for var in list(self.symbolic_params.keys()) + self.external_fluxes
]:
parameters[var_name] = getattr(self, var_name)
elif var_name in [f"θ{index}" for index in var_categories]:
raise AttributeError(var_name + " is not set.")
# creating a meshgrid for multiple dimensions
sweep_vars = {}
for var_name in kwargs:
if isinstance(kwargs[var_name], np.ndarray):
sweep_vars[var_name] = kwargs[var_name]
if len(sweep_vars) > 1:
sweep_vars.update(
zip(
sweep_vars,
np.meshgrid(*[grid for grid in sweep_vars.values()]),
)
)
for var_name in sweep_vars:
parameters[var_name] = sweep_vars[var_name]
potential_func = sm.lambdify(
parameters.keys(), potential_sym, [{"saw": sawtooth_potential}, "numpy"]
)
return potential_func(*parameters.values())
def plot_potential(self, **kwargs) -> Tuple[Figure, Axes]:
r"""Returns the plot of the potential for the circuit instance. Make sure to not
set more than two variables in the instance.potential to a Numpy array, as the
the code cannot plot with more than 3 dimensions.
Parameters
----------
θ<index>:
value(s) for the variable :math:`\theta_i` occurring in the potential.
Returns
-------
Returns a axes and figure for further editing.
"""
periodic_indices = self.var_categories["periodic"]
discretized_ext_indices = self.var_categories["extended"]
var_categories = discretized_ext_indices + periodic_indices
# constructing the grids
parameters = dict.fromkeys(
[f"θ{index}" for index in var_categories]
+ [var.name for var in self.external_fluxes]
+ [var.name for var in self.symbolic_params]
)
# filtering the plotting options
plot_kwargs = {}
list_of_keys = list(kwargs.keys())
for key in list_of_keys:
if key not in parameters:
plot_kwargs[key] = kwargs[key]
del kwargs[key]
sweep_vars = {}
for var_name in kwargs:
if isinstance(kwargs[var_name], np.ndarray):
sweep_vars[var_name] = kwargs[var_name]
if len(sweep_vars) > 1:
sweep_vars.update(zip(sweep_vars, np.meshgrid(*list(sweep_vars.values()))))
for var_name in sweep_vars:
parameters[var_name] = sweep_vars[var_name]
if len(sweep_vars) > 2:
raise AttributeError(
"Cannot plot with a dimension greater than 3; Only give a maximum of "
"two grid inputs"
)
potential_energies = self.potential_energy(**kwargs)
fig, axes = kwargs.get("fig_ax") or plt.subplots()
if len(sweep_vars) == 1:
axes.plot(*(list(sweep_vars.values()) + [potential_energies]))
axes.set_xlabel(
r"$\theta_{{{}}}$".format(
get_trailing_number(list(sweep_vars.keys())[0])
)
)
axes.set_ylabel("Potential energy in " + get_units())
if len(sweep_vars) == 2:
contourset = axes.contourf(
*(list(sweep_vars.values()) + [potential_energies])
)
var_indices = [
get_trailing_number(var_name) for var_name in list(sweep_vars.keys())
]
axes.set_xlabel(r"$\theta_{{{}}}$".format(var_indices[0]))
axes.set_ylabel(r"$\theta_{{{}}}$".format(var_indices[1]))
cbar = plt.colorbar(contourset, ax=axes)
cbar.set_label("Potential energy in " + get_units())
_process_options(fig, axes, **plot_kwargs)
return fig, axes
import functools
import itertools
import operator as builtin_op
import re
from typing import Any, Callable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING
if TYPE_CHECKING:
from scqubits.core.circuit import Subsystem
import numpy as np
import qutip as qt
import sympy as sm
from sympy import latex
try:
from IPython.display import display, Latex
except ImportError:
_HAS_IPYTHON = False
else:
_HAS_IPYTHON = True
from scqubits.core.circuit_utils import (
is_potential_term,
get_trailing_number,
round_symbolic_expr,
)
from scqubits.utils.misc import (
flatten_list_recursive,
list_intersection,
check_sync_status_circuit,
unique_elements_in_list,
)
from abc import ABC
class CircuitSymMethods(ABC):
@staticmethod
def _contains_trigonometric_terms(hamiltonian):
"""Check if the hamiltonian contains any trigonometric terms."""
trigonometric_operators = [sm.cos, sm.sin, sm.Function("saw", real=True)]
return any(hamiltonian.atoms(operator) for operator in trigonometric_operators)
@staticmethod
def _is_symbol_periodic_charge(sym):
return sym.name[0] == "n" and sym.name[1:].isnumeric()
@staticmethod
def _is_symbol_continuous_charge(sym):
return sym.name[0] == "Q" and sym.name[1:].isnumeric()
@staticmethod
def _is_symbol_phase(sym):
return sym.name[0] == "θ" and sym.name[1:].isnumeric()
@staticmethod
def _find_and_categorize_variable_indices(hamiltonian):
periodic_var_indices = set(
get_trailing_number(symbol.name)
for symbol in hamiltonian.free_symbols
if CircuitSymMethods._is_symbol_periodic_charge(symbol)
)
extended_var_indices = set(
get_trailing_number(symbol.name)
for symbol in hamiltonian.free_symbols
if CircuitSymMethods._is_symbol_continuous_charge(symbol)
)
phase_var_indices = set(
get_trailing_number(symbol.name)
for symbol in hamiltonian.free_symbols
if CircuitSymMethods._is_symbol_phase(symbol)
)
return periodic_var_indices, extended_var_indices, phase_var_indices
# @staticmethod
def _is_expression_purely_harmonic(self, hamiltonian):
"""Method used to check if the hamiltonian is purely harmonic."""
# if the hamiltonian contains any cos or sin term, return False
if self._contains_trigonometric_terms(hamiltonian):
return False
# if the hamiltonian contains any charge operator of periodic variables, return false
(
periodic_charge_variable_index,
extended_charge_variable_index,
phase_variable_index,
) = self._find_and_categorize_variable_indices(hamiltonian)
if len(periodic_charge_variable_index) > 0:
return False
# if the hamiltonian has any DoF where only its charge or flux operator is present, return False
if extended_charge_variable_index != phase_variable_index:
return False
return True
def _constants_in_subsys(
self, H_sys: sm.Expr, constants_list: List[sm.Expr]
) -> List[sm.Expr]:
"""Returns an expression of constants that belong to the subsystem with the
Hamiltonian H_sys.
Parameters
----------
H_sys:
subsystem hamiltonian
Returns
-------
expression of constants belonging to the subsystem
"""
constants_subsys_list = []
subsys_free_symbols = set(H_sys.free_symbols)
for term in constants_list:
if set(term.free_symbols) & subsys_free_symbols == set(term.free_symbols):
constants_subsys_list.append(term)
return constants_subsys_list
def _list_of_constants_from_expr(self, expr: sm.Expr) -> List[sm.Expr]:
ordered_terms = expr.as_ordered_terms()
constants = [
term
for term in ordered_terms
if (
set(
self.external_fluxes
+ self.offset_charges
+ self.free_charges
+ list(self.symbolic_params.keys())
+ [sm.symbols("I")]
)
& set(term.free_symbols)
)
== set(term.free_symbols)
]
return constants
def _sym_subsystem_hamiltonian_and_interactions(
self,
hamiltonian: sm.Expr,
subsys_indices: list,
non_operator_symbols: List[sm.Symbol],
):
systems_sym = []
interaction_sym = []
constants = self._list_of_constants_from_expr(hamiltonian)
hamiltonian = self._remove_constants_from_hamiltonian(hamiltonian, constants)
for subsys_index_list in subsys_indices:
subsys_index_list = flatten_list_recursive(subsys_index_list)
H_sys, H_int = self._find_subsys_hamiltonian(
hamiltonian, subsys_index_list, non_operator_symbols
)
# add the constants that belong to the subsystem
subsys_const_list = self._constants_in_subsys(H_sys, constants)
systems_sym.append(H_sys + sum(subsys_const_list))
# remove the constants that are already added
constants = [const for const in constants if const not in subsys_const_list]
interaction_sym.append(H_int)
hamiltonian -= H_sys + H_int
if len(constants) > 0:
systems_sym[0] += sum(constants)
return systems_sym, interaction_sym
def _remove_constants_from_hamiltonian(self, hamiltonian, constants):
for const in constants:
hamiltonian -= const
return hamiltonian
def _find_subsys_hamiltonian(
self, hamiltonian, subsys_index_list, non_operator_symbols
):
hamiltonian_terms = hamiltonian.as_ordered_terms()
H_sys = 0 * sm.symbols("x")
H_int = 0 * sm.symbols("x")
for term in hamiltonian_terms:
term_operator_indices = [
get_trailing_number(var_sym.name)
for var_sym in term.free_symbols
if var_sym not in non_operator_symbols
]
term_operator_indices_unique = unique_elements_in_list(
term_operator_indices
)
if len(set(term_operator_indices_unique) - set(subsys_index_list)) == 0:
H_sys += term
if (
len(set(term_operator_indices_unique) - set(subsys_index_list)) > 0
and len(set(term_operator_indices_unique) & set(subsys_index_list)) > 0
):
H_int += term
return H_sys, H_int
@check_sync_status_circuit
def _evaluate_symbolic_expr(self, sym_expr, bare_esys=None) -> qt.Qobj:
sym_expr = self._substitute_parameters(sym_expr)
if sym_expr == 0:
return 0
expr_dict = sym_expr.as_coefficients_dict()
terms = list(expr_dict.keys())
eval_matrix_list = [
self._evaluate_term(term, expr_dict[term], bare_esys) for term in terms
]
return sum(eval_matrix_list)
def _substitute_parameters(self, sym_expr):
param_symbols = (
self.external_fluxes
+ self.offset_charges
+ self.free_charges
+ list(self.symbolic_params.keys())
)
for param in param_symbols:
sym_expr = sym_expr.subs(param, getattr(self, param.name))
return sym_expr
def _evaluate_term(self, term, coefficient_sympy, bare_esys):
if term == 1:
return self._identity_qobj() * float(coefficient_sympy)
factors = term.as_ordered_factors()
factor_op_list = [
self._evaluate_factor(factor, bare_esys) for factor in factors
]
operator_list = self._combine_factors(factor_op_list, bare_esys)
return functools.reduce(builtin_op.mul, operator_list) * float(
coefficient_sympy
)
def _evaluate_factor(self, factor, bare_esys):
if any([arg.has(sm.cos) or arg.has(sm.sin) for arg in (1.0 * factor).args]):
return self._evaluate_matrix_cosine_terms(factor, bare_esys=bare_esys)
elif any(
[arg.has(sm.Function("saw", real=True)) for arg in (1.0 * factor).args]
):
return self._evaluate_sawtooth_factor(factor, bare_esys)
else:
return self._evaluate_operator_factor(factor)
def _evaluate_sawtooth_factor(self, factor, bare_esys):
if not self.hierarchical_diagonalization:
return self._evaluate_matrix_sawtooth_terms(factor, bare_esys=bare_esys)
index_subsystem = [
self.return_root_child(get_trailing_number(sym.name))
for sym in factor.free_symbols
]
if len(np.unique(index_subsystem)) > 1:
raise Exception(
"Sawtooth function terms must belong to the same subsystem."
)
operator = index_subsystem[0]._evaluate_matrix_sawtooth_terms(factor)
return self.identity_wrap_for_hd(
operator, index_subsystem[0], bare_esys=bare_esys
)
def _evaluate_operator_factor(self, factor):
power_dict = dict(factor.as_powers_dict())
free_sym = list(factor.free_symbols)[0]
if not self.hierarchical_diagonalization:
return self.get_operator_by_name(free_sym.name, power=power_dict[free_sym])
subsys = self.return_root_child(get_trailing_number(free_sym.name))
operator = subsys.get_operator_by_name(
free_sym.name, power=power_dict[free_sym]
)
return (subsys, operator)
def _combine_factors(self, factor_op_list, bare_esys):
operators_per_subsys = {}
operator_list = []
for factor_op in factor_op_list:
if not isinstance(factor_op, tuple):
operator_list.append(factor_op)
continue
subsys, operator = factor_op
if subsys not in operators_per_subsys:
operators_per_subsys[subsys] = [operator]
else:
operators_per_subsys[subsys].append(operator)
operator_list += [
self.identity_wrap_for_hd(
functools.reduce(builtin_op.mul, operators_per_subsys[subsys]),
subsys,
bare_esys=bare_esys,
)
for subsys in operators_per_subsys
]
return operator_list
def _shift_harmonic_oscillator_potential(self, hamiltonian: sm.Expr) -> sm.Expr:
# shifting the harmonic oscillator potential to the point of external fluxes
flux_shift_vars = {}
for var_index in self.var_categories["extended"]:
flux_shift_vars[var_index] = sm.symbols("Δθ" + str(var_index))
hamiltonian = hamiltonian.replace(
sm.symbols(f"θ{var_index}"),
sm.symbols(f"θ{var_index}") + flux_shift_vars[var_index],
) # substituting the flux offset variable offsets to collect the
# coefficients later
hamiltonian = hamiltonian.expand()
flux_shift_equations = [
hamiltonian.coeff(f"θ{var_index}").subs(
[(f"θ{i}", 0) for i in self.var_categories["extended"]]
)
for var_index in flux_shift_vars.keys()
] # finding the coefficients of the linear terms
A, b = sm.linear_eq_to_matrix(
flux_shift_equations, tuple(flux_shift_vars.values())
)
flux_shifts = sm.linsolve(
(A, b), tuple(flux_shift_vars.values())
) # solving for the flux offsets
if len(flux_shifts) != 0:
flux_shifts = list(list(flux_shifts)[0])
else:
flux_shifts = []
flux_shifts_dict = dict(zip(list(flux_shift_vars.values()), list(flux_shifts)))
hamiltonian = hamiltonian.subs(
list(flux_shifts_dict.items())
) # substituting the flux offsets to remove the linear terms
hamiltonian = hamiltonian.subs(
[(var, 0) for var in flux_shift_vars.values()]
) # removing the shift vars from the Hamiltonian
# remove constants from Hamiltonian
hamiltonian -= hamiltonian.as_coefficients_dict()[1]
return round_symbolic_expr(hamiltonian.expand(), 16)
# * ##########################################################################
def _generate_sym_potential(self):
# and bringing the potential into the same form as for the class Circuit
potential_symbolic = 0 * sm.symbols("x")
for term in self.hamiltonian_symbolic.as_ordered_terms():
if is_potential_term(term):
potential_symbolic += term
for i in self.dynamic_var_indices:
potential_symbolic = (
potential_symbolic.replace(
sm.symbols(f"cosθ{i}"), sm.cos(1.0 * sm.symbols(f"θ{i}"))
)
.replace(sm.symbols(f"sinθ{i}"), sm.sin(1.0 * sm.symbols(f"θ{i}")))
.subs(sm.symbols("I"), 1 / (2 * np.pi))
)
return potential_symbolic
def _is_mat_mul_replacement_necessary(self, term):
return (
set(self.var_categories["extended"])
& set([get_trailing_number(str(i)) for i in term.free_symbols])
) and "*" in str(term)
def _replace_mat_mul_operator(self, term: sm.Expr):
if not self._is_mat_mul_replacement_necessary(term):
return str(term)
if self.ext_basis == "discretized":
term_string = str(term)
term_var_categories = [
get_trailing_number(str(i)) for i in term.free_symbols
]
if len(set(term_var_categories) & set(self.var_categories["extended"])) > 1:
if all(["Q" in var.name for var in term.free_symbols]):
term_string = str(term).replace(
"*", "@"
) # replacing all the * with @
elif self.ext_basis == "harmonic":
# replace ** with np.matrix_power
if "**" in str(term):
operators = [
match.replace("**", "")
for match in re.findall(r"[^*]+\*{2}", str(term), re.MULTILINE)
]
exponents = re.findall(r"(?<=\*{2})\d", str(term), re.MULTILINE)
new_string_list = []
for idx, operator in enumerate(operators):
if get_trailing_number(operator) in self.var_categories["extended"]:
new_string_list.append(
f"matrix_power({operator},{exponents[idx]})"
)
else:
new_string_list.append(operator + "**" + exponents[idx])
term_string = "*".join(new_string_list)
else:
term_string = str(term)
# replace * with @ in the entire term
if len(term.free_symbols) > 1:
term_string = re.sub(
r"(?<=[^*])\*(?!\*)", "@", term_string, re.MULTILINE
)
return term_string
def _generate_hamiltonian_sym_for_numerics(
self,
hamiltonian: Optional[sm.Expr] = None,
return_exprs=False,
):
"""Generates a symbolic expression which is ready for numerical evaluation
starting from the expression stored in the attribute :attr:`Circuit.hamiltonian_symbolic`.
Stores the result in the attribute :attr:`Circuit._hamiltonian_sym_for_numerics`.
"""
hamiltonian = hamiltonian or (
self.hamiltonian_symbolic.expand()
) # applying expand is critical; otherwise the replacement of p^2 with ps2
# would not succeed
if self.ext_basis == "discretized":
# marking the squared momentum operators with a separate symbol
for i in self.var_categories["extended"]:
hamiltonian = hamiltonian.replace(
sm.symbols(f"Q{i}") ** 2, sm.symbols("Qs" + str(i))
)
# associate an identity matrix with the external flux vars
for ext_flux in self.external_fluxes:
hamiltonian = hamiltonian.subs(
ext_flux, ext_flux * sm.symbols("I") * 2 * np.pi
)
# associate an identity matrix with offset and free charge vars
for charge_var in self.offset_charges + self.free_charges:
hamiltonian = hamiltonian.subs(charge_var, charge_var * sm.symbols("I"))
# finding the cosine terms
cos_terms = sum(
[term for term in hamiltonian.as_ordered_terms() if "cos" in str(term)]
)
if return_exprs:
return hamiltonian, cos_terms
setattr(self, "_hamiltonian_sym_for_numerics", hamiltonian)
setattr(self, "junction_potential", cos_terms)
def _get_eval_hamiltonian_string(self, H: sm.Expr) -> str:
"""Returns the string which defines the expression for Hamiltonian in harmonic
oscillator basis."""
expr_dict = H.as_coefficients_dict()
# removing zero terms
expr_dict = {key: expr_dict[key] for key in expr_dict if expr_dict[key] != 0}
terms_list = list(expr_dict.keys())
coeff_list = list(expr_dict.values())
H_string = ""
for idx, term in enumerate(terms_list):
term_string = f"{coeff_list[idx]}*{self._replace_mat_mul_operator(term)}"
if float(coeff_list[idx]) > 0:
term_string = "+" + term_string
H_string += term_string
# replace all position, sin and cos operators with methods
H_string = re.sub(r"(?P<x>(θ\d)|(cosθ\d))", r"\g<x>_operator()", H_string)
# replace all other operators with methods
operator_symbols_list = flatten_list_recursive(
[
(
list(short_op_dict.values())
if isinstance(short_op_dict, dict)
else short_op_dict
)
for short_op_dict in list(self.vars.values())
]
)
operator_name_list = [symbol.name for symbol in operator_symbols_list]
for operator_name in operator_name_list:
if "θ" not in operator_name:
H_string = H_string.replace(
operator_name, operator_name + "_operator()"
)
return H_string
def _qutip_parameter_function_factory(
self,
parameter_expr: sm.Expr,
free_var_func_dict: Dict[str, Callable],
lambdify_func: Callable,
) -> Callable:
def parameter_func(t, args):
return lambdify_func(
*[
free_var_func_dict[sym.name](t, args)
for sym in parameter_expr.free_symbols
]
)
return parameter_func
@check_sync_status_circuit
def hamiltonian_for_qutip_dynamics(
self,
free_var_func_dict: Dict[str, Callable],
prefactor: float = 1.0,
extra_terms: Optional[str] = None,
) -> Tuple[
List[Union[qt.Qobj, Tuple[qt.Qobj, Callable]]], sm.Expr, Dict[qt.Qobj, sm.Expr]
]:
"""
Returns the Hamiltonian in a format amenable to be forwarded to mesolve in Qutip. Also returns the symbolic expressions of time independent and time dependent terms of the Hamiltonian, which can be used for reference. `free_var_func_dict` is a dictionary with key-value pair `{"var": f}`, where `f` is a function returning the value of the variable `var` at time `t`. If one has extra terms to be added to the Hamiltonian (for instance, charge driving a fluxonium where there is no offset charge) they can be passed as a string in `extra_terms`.
For example, to get the Hamiltonian for a circuit where Φ1 is the time varying parameter, this method can be called in the following way::
def flux_t(t, args):
return 0.5 + 0.02*np.sin(t*2)
def ng_t(t, args):
return 0.5 + 0.02*np.cos(t*2)
def EJ_t(t, args):
return (1-np.exp(-t/1))*0.2
free_var_func_dict = {"Φ1": flux_t, "EJ": EJ_t, "ng": ng_t}
mesolve_input_H = self.hamiltonian_for_qutip_dynamics(free_var_func_dict, extra_terms="0.1*ng*Q1")
Parameters
----------
free_var_func_dict:
Dict, as defined in the description above
prefactor:
prefactor with which the Hamiltonian and corresponding operators are multiplied, useful to set it to `2*np.pi` for some qutip simulations
extra_terms:
a string which will be converted into sympy expression, containing terms which are not present in the Circuit Hamiltonian. It is useful to define custom drive operators.
"""
free_var_names = list(free_var_func_dict.keys())
free_var_symbols = [sm.symbols(sym_name) for sym_name in free_var_names]
fixed_hamiltonian = 0 * sm.symbols("x")
time_varying_hamiltonian = []
# adding extra terms to the Hamiltonian
if extra_terms:
extra_terms_sym = sm.parse_expr(extra_terms)
for extra_sym in extra_terms_sym.free_symbols:
if (
extra_sym not in self.hamiltonian_symbolic.free_symbols
and extra_sym not in free_var_symbols
):
raise Exception(f"{extra_sym.name} is unknown.")
else:
extra_terms_sym = 0
sym_hamiltonian = self._hamiltonian_sym_for_numerics + extra_terms_sym
sym_hamiltonian = sym_hamiltonian.subs("I", 1).expand()
expr_dict = sym_hamiltonian.expand().as_coefficients_dict()
terms = list(expr_dict.keys())
time_dep_terms = {}
for term in terms:
if len(list_intersection(list(term.free_symbols), free_var_symbols)) == 0:
fixed_hamiltonian = fixed_hamiltonian + term * expr_dict[term]
continue
# if the term does have a free variable
# expand trigonometrically
should_trig_expand = any(
[
(free_sym in term.free_symbols and term.coeff(free_sym) == 0)
for free_sym in free_var_symbols
]
)
term_expanded = term.expand(trig=should_trig_expand)
term_expr_dict = term_expanded.as_coefficients_dict()
terms_in_term = list(term_expr_dict.keys())
for inner_term in terms_in_term:
operator_expr, parameter_expr = inner_term.as_independent(
*free_var_symbols, as_Mul=True
)
if parameter_expr in time_dep_terms:
time_dep_terms[parameter_expr] = (
operator_expr * expr_dict[term] * term_expr_dict[inner_term]
+ time_dep_terms[parameter_expr]
)
else:
time_dep_terms[parameter_expr] = round_symbolic_expr(
operator_expr * expr_dict[term] * term_expr_dict[inner_term], 13
)
for parameter_expr in time_dep_terms:
# separating the time independent constants
for sym in parameter_expr.free_symbols:
if sym not in free_var_symbols:
parameter_expr = parameter_expr.subs(sym, getattr(self, sym.name))
lambdify_func = sm.lambdify(
list(parameter_expr.free_symbols), parameter_expr, "numpy"
)
parameter_func = self._qutip_parameter_function_factory(
parameter_expr, free_var_func_dict, lambdify_func
)
operator_matrix = (
self._evaluate_symbolic_expr(time_dep_terms[parameter_expr]) * prefactor
) # also multiplying the constant to the operator
if operator_matrix == 0:
continue
time_varying_hamiltonian.append([operator_matrix, parameter_func])
fixed_hamiltonian = fixed_hamiltonian.subs("I", 1)
return (
[self._evaluate_symbolic_expr(fixed_hamiltonian) * prefactor]
+ time_varying_hamiltonian,
fixed_hamiltonian,
dict((val, key) for key, val in time_dep_terms.items()),
)
# ****************************************************************
# ***** Functions for pretty display of symbolic expressions *****
# ****************************************************************
@staticmethod
def print_expr_in_latex(expr: Union[sm.Expr, List["sm.Equality"]]) -> None:
"""Print a sympy expression or a list of equalities in LaTeX.
Parameters
----------
expr:
a sympy expressions or a list of equalities
"""
if isinstance(expr, sm.Expr):
display(Latex("$ " + sm.printing.latex(expr) + " $"))
elif isinstance(expr, list):
equalities_in_latex = "$ "
for eqn in expr:
equalities_in_latex += sm.printing.latex(eqn) + r" \\\ "
equalities_in_latex = equalities_in_latex[:-4] + " $"
display(Latex(equalities_in_latex))
def __repr__(self) -> str:
# string to describe the Circuit
return self._id_str
def _repr_latex_(self):
"""Describes the Circuit instance, its parameters, and the symbolic
Hamiltonian."""
# string to describe the Circuit
if not _HAS_IPYTHON:
return self._id_str
# Hamiltonian string
H_latex_str = (
"$H=" + sm.printing.latex(self.sym_hamiltonian(return_expr=True)) + "$"
)
# describe the variables
cutoffs_dict = self.cutoffs_dict()
var_str = "Operators (flux, charge) - cutoff: "
if len(self.var_categories["periodic"]) > 0:
var_str += " \n Discrete Charge Basis: "
for var_index in self.var_categories["periodic"]:
var_str += (
f"$(θ{var_index}, n{var_index}) - {cutoffs_dict[var_index]}$, "
)
var_str_discretized = " \nDiscretized Phi basis: "
var_str_harmonic = " \nHarmonic oscillator basis: "
for var_index in self.var_categories["extended"]:
var_index_basis = self._basis_for_var_index(var_index)
if var_index_basis == "discretized":
var_str_discretized += (
f"$(θ{var_index}, Q{var_index}) - {cutoffs_dict[var_index]}$, "
)
if var_index_basis == "harmonic":
var_str_harmonic += (
f"$(θ{var_index}, Q{var_index}) - {cutoffs_dict[var_index]}$, "
)
if var_str_discretized == " \nDiscretized Phi basis: ":
var_str_discretized = ""
if var_str_harmonic == " \nHarmonic oscillator basis: ":
var_str_harmonic = ""
display(Latex(H_latex_str))
display(
Latex(
var_str
+ var_str_discretized
+ (" \n" if var_str_discretized else "")
+ var_str_harmonic
)
)
# symbolic parameters
if len(self.symbolic_params) > 0:
sym_params_str = "Symbolic parameters (symbol, default value): "
for sym, val in self.symbolic_params.items():
sym_params_str += f"$({sym.name}, {val})$, "
display(Latex(sym_params_str))
if len(self.external_fluxes) > 0:
sym_params_str = "External fluxes (symbol, default value): "
for sym in self.external_fluxes:
sym_params_str += f"$({sym.name}, {getattr(self, sym.name)})$, "
display(Latex(sym_params_str))
if len(self.offset_charges) > 0:
sym_params_str = "Offset charges (symbol, default value): "
for sym in self.offset_charges:
sym_params_str += f"$({sym.name}, {getattr(self, sym.name)})$, "
display(Latex(sym_params_str))
if len(self.free_charges) > 0:
sym_params_str = "Free charges (symbol, default value): "
for sym in self.free_charges:
sym_params_str += f"$({sym.name}, {getattr(self, sym.name)})$, "
display(Latex(sym_params_str))
if self.hierarchical_diagonalization:
display(Latex(f"System hierarchy: {self.system_hierarchy}"))
display(Latex(f"Truncated Dimensions: {self.subsystem_trunc_dims}"))
def _make_expr_human_readable(self, expr: sm.Expr, float_round: int = 6) -> sm.Expr:
"""Method returns a user readable symbolic expression for the current instance.
Parameters
----------
expr:
A symbolic sympy expression
float_round:
Number of digits after the decimal to which floats are rounded
Returns
-------
Sympy expression which is simplified to make it human readable.
"""
expr_modified = expr
# rounding the decimals in the coefficients
# citation:
# https://stackoverflow.com/questions/43804701/round-floats-within-an-expression
# accepted answer
for term in sm.preorder_traversal(expr):
if isinstance(term, sm.Float):
expr_modified = expr_modified.subs(term, round(term, float_round))
for var_index in self.dynamic_var_indices:
# replace sinθ with sin(..) and similarly with cos
expr_modified = (
expr_modified.replace(
sm.symbols(f"cosθ{var_index}"),
sm.cos(1.0 * sm.symbols(f"θ{var_index}")),
)
.replace(
sm.symbols(f"sinθ{var_index}"),
sm.sin(1.0 * sm.symbols(f"θ{var_index}")),
)
.replace(
(1.0 * sm.symbols(f"θ{var_index}")),
(sm.symbols(f"θ{var_index}")),
)
)
# replace Qs with Q^2 etc
expr_modified = expr_modified.replace(
sm.symbols("Qs" + str(var_index)), sm.symbols(f"Q{var_index}") ** 2
)
expr_modified = expr_modified.replace(
sm.symbols("ng" + str(var_index)), sm.symbols("n_g" + str(var_index))
)
# replace I by 1
expr_modified = expr_modified.replace(sm.symbols("I"), 1)
for ext_flux_var in self.external_fluxes:
# removing 1.0 decimals from flux vars
expr_modified = expr_modified.replace(1.0 * ext_flux_var, ext_flux_var)
return expr_modified
def sym_potential(
self, float_round: int = 6, print_latex: bool = False, return_expr: bool = False
) -> Union[sm.Expr, None]:
"""Method prints a user readable symbolic potential for the current instance.
Parameters
----------
float_round:
Number of digits after the decimal to which floats are rounded
print_latex:
if set to True, the expression is additionally printed as LaTeX code
return_expr:
if set to True, all printing is suppressed and the function will silently
return the sympy expression
"""
potential = self._make_expr_human_readable(
self.potential_symbolic, float_round=float_round
)
for external_flux in self.external_fluxes:
potential = potential.replace(
external_flux,
sm.symbols(
"(2π" + "Φ_{" + str(get_trailing_number(str(external_flux))) + "})"
),
)
if print_latex:
print(latex(potential))
if _HAS_IPYTHON:
self.print_expr_in_latex(potential)
else:
print(potential)
def sym_hamiltonian(
self,
subsystem_index: Optional[int] = None,
float_round: int = 6,
print_latex: bool = False,
return_expr: bool = False,
) -> Union[sm.Expr, None]:
"""Prints a user readable symbolic Hamiltonian for the current instance.
Parameters
----------
subsystem_index:
when set to an index, the Hamiltonian for the corresponding subsystem is
returned.
float_round:
Number of digits after the decimal to which floats are rounded
print_latex:
if set to True, the expression is additionally printed as LaTeX code
return_expr:
if set to True, all printing is suppressed and the function will silently
return the sympy expression
"""
if subsystem_index is not None:
if not self.hierarchical_diagonalization:
raise Exception(
"Hierarchical diagonalization was not enabled. Hence there "
"are no identified subsystems addressable by "
"subsystem_index."
)
# start with the raw system hamiltonian
sym_hamiltonian = self._make_expr_human_readable(
self.subsystems[subsystem_index].hamiltonian_symbolic.expand(),
float_round=float_round,
)
# create PE symbolic expressions
sym_hamiltonian_PE = self._make_expr_human_readable(
self.subsystems[subsystem_index].potential_symbolic.expand(),
float_round=float_round,
)
# obtain the KE of hamiltonian
pot_symbols = (
self.external_fluxes
+ [
sm.symbols("θ" + str(idx))
for idx in self.var_categories["extended"]
]
+ [
sm.symbols("θ" + str(idx))
for idx in self.var_categories["periodic"]
]
)
sym_hamiltonian_KE = 0 * sm.Symbol("x")
for term in sym_hamiltonian.args:
if term.free_symbols.isdisjoint(pot_symbols):
sym_hamiltonian_KE = sm.Add(sym_hamiltonian_KE, term)
# add a symbolic 2pi
for external_flux in self.external_fluxes:
sym_hamiltonian_PE = self._make_expr_human_readable(
sym_hamiltonian_PE.replace(
external_flux,
sm.symbols(
"(2π"
+ "Φ_{"
+ str(get_trailing_number(str(external_flux)))
+ "})"
),
),
float_round=float_round,
)
# substitute free charges
for free_charge in self.free_charges:
sym_hamiltonian_PE = sym_hamiltonian_PE.subs(
free_charge, getattr(self, free_charge.name)
)
# obtain system symbolic hamiltonian by glueing KE and PE
sym_hamiltonian = sm.Add(
sym_hamiltonian_KE, sym_hamiltonian_PE, evaluate=False
)
else:
# create KE and PE symbolic expressions
sym_hamiltonian = self._make_expr_human_readable(
self.hamiltonian_symbolic.expand(),
float_round=float_round,
)
# substitute free charges
for free_charge in self.free_charges:
sym_hamiltonian = sym_hamiltonian.subs(
free_charge, getattr(self, free_charge.name)
)
pot_symbols = (
self.external_fluxes
+ [
sm.symbols("θ" + str(idx))
for idx in self.var_categories["extended"]
]
+ [
sm.symbols("θ" + str(idx))
for idx in self.var_categories["periodic"]
]
)
sym_hamiltonian_KE = 0 * sm.Symbol("x")
for term in sym_hamiltonian.args:
if term.free_symbols.isdisjoint(pot_symbols):
sym_hamiltonian_KE = sm.Add(sym_hamiltonian_KE, term)
sym_hamiltonian_PE = self._make_expr_human_readable(
self.potential_symbolic.expand(), float_round=float_round
)
# add a 2pi coefficient in front of external fluxes, since the the external
# fluxes are measured in 2pi numerically
for external_flux in self.external_fluxes:
sym_hamiltonian_PE = sym_hamiltonian_PE.replace(
external_flux,
sm.symbols(
"(2π"
+ "Φ_{"
+ str(get_trailing_number(str(external_flux)))
+ "})"
),
)
# add the KE and PE and suppress the evaluation
sym_hamiltonian = sm.Add(
sym_hamiltonian_KE, sym_hamiltonian_PE, evaluate=False
)
if return_expr:
return sym_hamiltonian
if print_latex:
print(latex(sym_hamiltonian))
if _HAS_IPYTHON:
self.print_expr_in_latex(sym_hamiltonian)
else:
print(sym_hamiltonian)
def sym_interaction(
self,
subsystem_indices: Tuple[int],
float_round: int = 6,
print_latex: bool = False,
return_expr: bool = False,
) -> Union[sm.Expr, None]:
"""Print the interaction between any set of subsystems for the current instance.
It would print the interaction terms having operators from all the subsystems
mentioned in the tuple.
Parameters
----------
subsystem_indices:
Tuple of subsystem indices
float_round:
Number of digits after the decimal to which floats are rounded
print_latex:
if set to True, the expression is additionally printed as LaTeX code
return_expr:
if set to True, all printing is suppressed and the function will silently
return the sympy expression
"""
interaction = sm.symbols("x") * 0
for subsys_index_pair in itertools.combinations(subsystem_indices, 2):
for term in self.subsystem_interactions[
min(subsys_index_pair)
].as_ordered_terms():
term_mod = term.subs(
[
(symbol, 1)
for symbol in self.external_fluxes
+ self.offset_charges
+ self.free_charges
+ list(self.symbolic_params.keys())
+ [sm.symbols("I")]
]
)
interaction_var_indices = [
self.get_subsystem_index(get_trailing_number(symbol.name))
for symbol in term_mod.free_symbols
]
if np.array_equal(
np.sort(interaction_var_indices), np.sort(subsystem_indices)
):
interaction += term
for external_flux in self.external_fluxes:
interaction = self._make_expr_human_readable(
interaction.replace(external_flux, external_flux / (2 * np.pi)),
float_round=float_round,
)
interaction = interaction.replace(
external_flux,
sm.symbols(
"(2π" + "Φ_{" + str(get_trailing_number(str(external_flux))) + "})"
),
)
if return_expr:
return interaction
if print_latex:
print(latex(interaction))
if _HAS_IPYTHON:
self.print_expr_in_latex(interaction)
else:
print(interaction)
def operator_names_in_hamiltonian_symbolic(self) -> List[str]:
"""Returns a list of the names (strings) of all operators occurring in the
symbolic Hamiltonian."""
return [
symbol.name
for symbol in self.hamiltonian_symbolic.free_symbols
if ("ng" not in symbol.name and "Φ" not in symbol.name)
and symbol not in self.symbolic_params
]
def offset_charge_transformation(self) -> None:
"""Prints the variable transformation between offset charges of transformed
variables and the node charges."""
if not hasattr(self, "symbolic_circuit"):
raise Exception(
f"{self._id_str} instance is not generated from a SymbolicCircuit instance, and hence does not have any associated branches."
)
trans_mat = np.linalg.inv(self.transformation_matrix.T)
node_offset_charge_vars = [
sm.symbols(f"q_n{index}")
for index in range(
1, len(self.symbolic_circuit.nodes) - self.is_grounded + 1
)
]
periodic_offset_charge_vars = [
sm.symbols(f"ng{index}")
for index in self.symbolic_circuit.var_categories["periodic"]
]
periodic_offset_charge_eqns = []
for idx, node_var in enumerate(periodic_offset_charge_vars):
periodic_offset_charge_eqns.append(
self._make_expr_human_readable(
sm.Eq(
periodic_offset_charge_vars[idx],
np.sum(trans_mat[idx, :] * node_offset_charge_vars),
)
)
)
if _HAS_IPYTHON:
self.print_expr_in_latex(periodic_offset_charge_eqns)
else:
print(periodic_offset_charge_eqns)
import copy
import itertools
import warnings
from typing import Any, Dict, List, Optional, Set, Tuple, Union
import numpy as np
from numpy import ndarray
import scipy as sp
import sympy
import sympy as sm
from sympy import symbols, Symbol
from scqubits.core.circuit_utils import (
round_symbolic_expr,
_capacitance_variable_for_branch,
_junction_order,
get_trailing_number,
)
import scqubits.io_utils.fileio_serializers as serializers
import scqubits.settings as settings
from itertools import chain
from scqubits.utils.misc import (
flatten_list_recursive,
unique_elements_in_list,
)
from scqubits.core.circuit_input import (
remove_comments,
remove_branchline,
strip_empty_lines,
parse_code_line,
process_param,
)
from abc import ABC
class Node:
"""Class representing a circuit node, and handled by `Circuit`. The attribute
`branches` is a list of `Branch` objects containing all branches connected to the
node.
Parameters
----------
id: int
integer identifier of the node
marker: int
An internal attribute used to group nodes and identify sub-circuits in the
method independent_modes.
"""
def __init__(self, index: int, marker: int = 0):
self.index: int = index
self.marker: int = marker
self._init_params: Dict[str, int] = {"id": self.index, "marker": self.marker}
self.branches: List[Branch] = []
def __str__(self) -> str:
return "Node {}".format(self.index)
def __repr__(self) -> str:
return "Node({})".format(self.index)
def connected_nodes(self, branch_type: str) -> List["Node"]:
"""Returns a list of all nodes directly connected by branches to the current
node, either considering all branches or a specified `branch_type` - ("C", "L", "JJ", "all") for capacitive, inductive, Josephson junction, or all types of branches.
"""
result = []
if branch_type == "all":
branch_list = self.branches
else:
branch_list = [
branch for branch in self.branches if branch.type == branch_type
]
for branch in branch_list:
if branch.nodes[0].index == self.index:
result.append(branch.nodes[1])
else:
result.append(branch.nodes[0])
return result
def is_ground(self) -> bool:
"""Returns a bool if the node is a ground node (Node with index set to 0)."""
return True if self.index == 0 else False
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, copy.deepcopy(v, memo))
return result
class Branch:
"""Class describing a circuit branch, used in the Circuit class.
Parameters
----------
n_i:
initial `Node` of the branch
n_f:
final `Node` of the branch
branch_type:
is the type of this Branch, example `"C"`, `"JJ"` or `"L"`
parameters:
list of parameters for the branch, namely for
capacitance: `[EC]`;
for inductance: `[10]`;
for Josephson Junction: `[EJ, 1]`
aux_params:
Dictionary of auxiliary parameters which map a symbol from the input file a numeric parameter.
Examples
--------
`Branch("C", Node(1, 0), Node(2, 0))` is a capacitive branch connecting the nodes with indices 0 and 1.
"""
def __init__(
self,
n_i: Node,
n_f: Node,
branch_type: str,
parameters: List[Union[float, Symbol, int]],
index: Optional[int] = None,
aux_params: Dict[Symbol, float] = {},
):
self.nodes = (n_i, n_f)
self.type = branch_type
self.index = index
# store info of current branch inside the provided nodes
# setting the parameters if it is provided
self._set_parameters(parameters)
self.aux_params = aux_params
self.nodes[0].branches.append(self)
self.nodes[1].branches.append(self)
def __str__(self) -> str:
return (
"Branch "
+ self.type
+ " connecting nodes: ("
+ str(self.nodes[0].index)
+ ","
+ str(self.nodes[1].index)
+ "); "
+ str(self.parameters)
)
def __repr__(self) -> str:
return f"Branch({self.type}, {self.nodes[0].index}, {self.nodes[1].index}, index: {self.index})"
def _set_parameters(self, parameters) -> None:
if self.type in ["C", "L"]:
self.parameters = {f"E{self.type}": parameters[0]}
elif "JJ" in self.type:
number_of_junc_params = _junction_order(self.type)
self.parameters = {}
for junc_order in range(1, number_of_junc_params + 1):
if junc_order == 1:
self.parameters["EJ"] = parameters[0]
else:
self.parameters[f"EJ{junc_order}"] = parameters[junc_order - 1]
self.parameters["ECJ"] = parameters[number_of_junc_params]
def node_ids(self) -> Tuple[int, int]:
"""Returns the indices of the nodes connected by the branch."""
return self.nodes[0].index, self.nodes[1].index
def is_connected(self, branch) -> bool:
"""Returns a boolean indicating whether the current branch is connected to the
given `branch`"""
distinct_node_count = len(set(self.nodes + branch.nodes))
if distinct_node_count < 4:
return True
return False
def common_node(self, branch) -> Set[Node]:
"""Returns the common nodes between self and the `branch` given as input."""
return set(self.nodes) & set(branch.nodes)
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, copy.deepcopy(v, memo))
return result
class Coupler:
"""Coupler class is used to define elements which couple two existing branches in
the Circuit class.
Parameters
----------
branch1, branch2:
Branch objects which are being coupled.
coupling_type:
The type of coupling between the branches - allowed is mutual inductance - "ML"
parameters:
List of parameters for the coupling, namely for mutual inductance: {"EM": <value>}
aux_params:
Dictionary of auxiliary parameters which map a symbol from the input file a numeric parameter.
Examples
--------
`Coupler("ML", branch1, branch2, "ML", 1e2)`
"""
def __init__(
self,
branch1: Branch,
branch2: Branch,
coupling_type: str,
parameters: List[Union[float, Symbol, int]],
index: Optional[int] = None,
aux_params: Dict[Symbol, float] = {},
):
self.branches = (branch1, branch2)
self.type = coupling_type
self._set_parameters(parameters)
self.aux_params = aux_params
self.index = index
def _set_parameters(self, parameters) -> None:
if self.type in ["ML"]:
self.parameters = {f"E{self.type}": parameters[0]}
def __repr__(self) -> str:
return f"Coupler({self.type}, ({self.branches[0].type}, {self.branches[0].node_ids()}), ({self.branches[1].type}, {self.branches[1].node_ids()}), index: {self.index})"
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, copy.deepcopy(v, memo))
return result
def make_coupler(
branches_list: List[Branch],
coupler_type: str,
idx1: int,
idx2: int,
params,
aux_params,
_branch_count: int,
):
params_dict = {}
params = [process_param(param) for param in params]
if coupler_type == "ML":
params_dict[sm.symbols("EML")] = params[0][0] or params[0][1]
for idx in [idx1, idx2]:
if branches_list[idx].type != "L":
raise ValueError(
"Mutual inductance coupling is only allowed between inductive branches."
)
branch1 = branches_list[idx1]
branch2 = branches_list[idx2]
sym_params_dict = {
param[0]: param[1] for param in params if param[0]
} # dictionary of symbolic params and the default values
return (
Coupler(
branch1,
branch2,
coupler_type,
list(params_dict.values()),
_branch_count,
process_param(aux_params),
),
sym_params_dict,
)
def make_branch(
nodes_list: List[Node],
branch_type: str,
idx1: int,
idx2: int,
params,
aux_params,
_branch_count: int,
):
params_dict = {}
params = [process_param(param) for param in params]
if "JJ" in branch_type:
for idx, param in enumerate(
params[:-1]
): # getting EJi for all orders i specified
params_dict[sm.symbols(f"EJ{idx + 1}" if idx > 0 else "EJ")] = (
param[0] or param[1]
)
params_dict[sm.symbols("EC")] = params[-1][0] or params[-1][1]
if branch_type == "C":
params_dict[sm.symbols("EC")] = params[-1][0] or params[-1][1]
elif branch_type == "L":
params_dict[sm.symbols("EL")] = params[-1][0] or params[-1][1]
# return idx1, idx2, branch_type, list(params_dict.keys()), str(_branch_count), process_param(aux_params)
is_grounded = True if any([node.is_ground() for node in nodes_list]) else False
node_1 = nodes_list[idx1 if is_grounded else idx1 - 1]
node_2 = nodes_list[idx2 if is_grounded else idx2 - 1]
sym_params_dict = {
param[0]: param[1] for param in params if param[0]
} # dictionary of symbolic params and the default values
return (
Branch(
node_1,
node_2,
branch_type,
list(params_dict.values()),
_branch_count,
process_param(aux_params),
),
sym_params_dict,
)
class SymbolicCircuitGraph(ABC):
def _spanning_tree(
self, consider_capacitive_loops: bool = False, use_closure_branches: bool = True
):
r"""Returns a spanning tree (as a list of branches) for the given instance.
Notice that if the circuit contains multiple capacitive islands, the returned
spanning tree will not include the capacitive twig between two capacitive
islands. Option `use_closure_branches` can be set to `False` if one does not
want to use the internally set closure_branches.
This function also returns all the branches that form superconducting loops, and a
list of lists of nodes (node_sets), which keeps the generation info for nodes, e.g.,
for the following spanning tree:
/---Node(2)
Node(1)---'
'---Node(3)---Node(4)
has the node_sets returned as [[Node(1)], [Node(2),Node(3)], [Node(4)]]
Returns
-------
A list of spanning trees in the circuit, which does not include capacitor branches,
a list of branches that forms superconducting loops for each tree, and a list of lists of nodes
(node_sets) for each tree (which keeps the generation info for nodes of branches on the path)
and list of closure branches for each tree.
"""
# Make a copy of self; do not need symbolic expressions etc., so do a minimal
# initialization only
circ_copy = copy.deepcopy(self)
# adding an attribute for node list without ground
# circ_copy.nodes = circ_copy.nodes
if circ_copy.ground_node:
circ_copy.nodes.remove(circ_copy.ground_node)
# **************** removing all the capacitive branches and updating the nodes *
# identifying capacitive branches
branches_to_be_removed = []
if not consider_capacitive_loops:
branches_to_be_removed = [
branch for branch in list(circ_copy.branches) if branch.type == "C"
]
for c_branch in branches_to_be_removed:
for (
node
) in (
c_branch.nodes
): # updating the branches attribute for each node that this branch
# connects
node.branches = [b for b in node.branches if b is not c_branch]
circ_copy.branches.remove(c_branch) # removing the branch
num_float_nodes = 1
while num_float_nodes > 0: # breaks when no floating nodes are detected
num_float_nodes = 0 # setting
for node in circ_copy.nodes:
if len(node.branches) == 0:
circ_copy.nodes.remove(node)
num_float_nodes += 1
continue
if len(node.branches) == 1:
branches_connected_to_node = node.branches[0]
circ_copy.branches.remove(branches_connected_to_node)
for new_node in branches_connected_to_node.nodes:
if new_node != node:
new_node.branches = [
i
for i in new_node.branches
if i is not branches_connected_to_node
]
num_float_nodes += 1
continue
else:
circ_copy.nodes.remove(node)
if circ_copy.nodes == []:
return {
"list_of_trees": [],
"loop_branches_for_trees": [],
"node_sets_for_trees": [],
"closure_branches_for_trees": [],
}
# *****************************************************************************
# **************** Constructing the node_sets ***************
node_sets_for_trees = [] # seperate node sets for separate trees
if circ_copy.is_grounded:
node_sets = [[circ_copy.ground_node]]
else:
node_sets = [
[circ_copy.nodes[0]]
] # starting with the first set that has the first node as the only element
node_sets_for_trees.append(node_sets)
num_nodes = len(circ_copy.nodes)
# this needs to be done as the ground node is not included in self.nodes
if circ_copy.is_grounded:
num_nodes += 1
# finding all the sets of nodes and filling node_sets
node_set_index = 0
tree_index = 0
while (
len(flatten_list_recursive(node_sets_for_trees))
< num_nodes # checking to see if all the nodes are present in node_sets
):
node_set = []
for node in node_sets_for_trees[tree_index][node_set_index]:
node_set += node.connected_nodes("all")
node_set = [
x
for x in unique_elements_in_list(node_set)
if x
not in flatten_list_recursive(
node_sets_for_trees[tree_index][: node_set_index + 1]
)
]
if node_set:
node_set.sort(key=lambda node: node.index)
# code to handle two different capacitive islands in the circuit.
if node_set == []:
node_sets_for_trees.append([])
for node in circ_copy.nodes:
if node not in flatten_list_recursive(
node_sets_for_trees[tree_index]
):
tree_index += 1
node_sets_for_trees[tree_index].append([node])
node_set_index = 0
break
continue
node_sets_for_trees[tree_index].append(node_set)
node_set_index += 1
# ***************************
# **************** constructing the spanning tree ##########
def connecting_branches(n1: Node, n2: Node):
return [branch for branch in n1.branches if branch in n2.branches]
def is_same_branch(branch_1: Branch, branch_2: Branch):
return branch_1.index == branch_2.index
def fetch_same_branch_from_circ(branch: Branch, circ):
for b in circ.branches:
if is_same_branch(b, branch):
return b
def fetch_same_node_from_circ(node: Node, circ):
for n in circ.nodes:
if n.index == node.index:
return n
list_of_trees = []
for node_sets in node_sets_for_trees:
tree = [] # tree having branches of the instance that is copied
# find the branch connecting this node to another node in a previous node set.
for index, node_set in enumerate(node_sets):
if index == 0:
continue
for node in node_set:
for prev_node in node_sets[index - 1]:
if len(connecting_branches(node, prev_node)) != 0:
tree.append(connecting_branches(node, prev_node)[0])
break
list_of_trees.append(tree)
# as the capacitors are removed to form the spanning tree, and as a result
# floating branches as well, the set of all branches which form the
# superconducting loops would be in circ_copy.
closure_branches_for_trees = [[] for tree in list_of_trees]
loop_branches_for_trees = []
for tree_idx, tree in enumerate(list_of_trees):
loop_branches = tree.copy()
nodes_in_tree = flatten_list_recursive(node_sets_for_trees[tree_idx])
for branch in [
branch for branch in circ_copy.branches if branch not in tree
]:
if len([node for node in branch.nodes if node in nodes_in_tree]) == 2:
loop_branches.append(branch)
closure_branches_for_trees[tree_idx].append(branch)
loop_branches_for_trees.append(loop_branches)
# get branches from the original circuit
for tree_idx, tree in enumerate(list_of_trees):
list_of_trees[tree_idx] = [
fetch_same_branch_from_circ(branch, self) for branch in tree
]
loop_branches_for_trees[tree_idx] = [
fetch_same_branch_from_circ(branch, self)
for branch in loop_branches_for_trees[tree_idx]
]
closure_branches_for_trees[tree_idx] = [
fetch_same_branch_from_circ(branch, self)
for branch in closure_branches_for_trees[tree_idx]
]
node_sets_for_trees[tree_idx] = [
[fetch_same_node_from_circ(node, self) for node in node_set]
for node_set in node_sets_for_trees[tree_idx]
]
# if the closure branches are manually set, then the spanning tree would be all
# the superconducting loop branches except the closure branches
if (self.closure_branches != [] and use_closure_branches) and np.all(
[isinstance(elem, Branch) for elem in self.closure_branches]
):
closure_branches_for_trees = [
[] for loop_branches in loop_branches_for_trees
]
list_of_trees = []
for tree_idx, loop_branches in enumerate(loop_branches_for_trees):
list_of_trees.append(
[
branch
for branch in loop_branches
if branch not in self.closure_branches
]
)
closure_branches_for_trees[tree_idx] = [
branch
for branch in loop_branches
if branch in self.closure_branches
]
return {
"list_of_trees": list_of_trees,
"loop_branches_for_trees": loop_branches_for_trees,
"node_sets_for_trees": node_sets_for_trees,
"closure_branches_for_trees": closure_branches_for_trees,
}
def _closure_branches(self, spanning_tree_dict=None):
r"""Returns and stores the closure branches in the circuit."""
return flatten_list_recursive(
(spanning_tree_dict or self.spanning_tree_dict)[
"closure_branches_for_trees"
]
)
def _time_dependent_flux_distribution(self):
closure_branches = self._closure_branches()
# constructing the constraint matrix
R = np.zeros([len(self.branches), len(closure_branches)])
# constructing branch capacitance matrix
C_diag = np.identity(len(self.branches)) * 0
# constructing the matrix which transforms node to branch variables
W = np.zeros([len(self.branches), len(self.nodes) - self.is_grounded])
for closure_brnch_idx, closure_branch in enumerate(closure_branches):
loop_branches = self._find_loop(closure_branch)
# setting the loop direction from the direction of the closure branch
R_prev_brnch = 1
for b_idx, branch in enumerate(loop_branches):
R_elem = 1
if b_idx == 0:
start_node = list(branch.common_node(loop_branches[1]))[0]
start_node_idx = branch.nodes.index(start_node)
if start_node_idx == 0:
R_elem *= -1
if b_idx > 0:
start_node_idx = 1 if R_prev_brnch > 0 else 0
start_node = loop_branches[b_idx - 1].nodes[start_node_idx]
R_elem = R_prev_brnch
if branch.node_ids()[start_node_idx] == start_node.index:
R_elem *= -1
R_prev_brnch = R_elem
R[self.branches.index(branch), closure_brnch_idx] = R_elem
if R[self.branches.index(closure_branch), closure_brnch_idx] < 0:
R[:, closure_brnch_idx] = R[:, closure_brnch_idx] * -1
for idx, branch in enumerate(self.branches):
if branch.type == "C" or "JJ" in branch.type:
EC = (
branch.parameters["EC"]
if branch.type == "C"
else branch.parameters["ECJ"]
)
if isinstance(EC, sympy.Symbol):
EC = self.symbolic_params[EC]
C_diag[idx, idx] = 1 / (EC * 8)
for node_idx, node in enumerate(branch.nodes):
if node.is_ground():
continue
n_id = self.nodes.index(node) - self.is_grounded
W[idx, n_id] = (-1) ** node_idx
M = np.vstack([(W.T @ C_diag), R.T])
I = np.vstack(
[
np.zeros([len(self.nodes) - self.is_grounded, len(closure_branches)]),
np.identity(len(closure_branches)),
]
)
B = (np.linalg.pinv(M)) @ I
return B.round(10) @ self.external_fluxes
def _find_path_to_root(
self, node: Node, spanning_tree_dict=None
) -> Tuple[int, List[Node], List[Branch], int]:
r"""Returns all the nodes and branches in the spanning tree path between the
input node and the root of the spanning tree. Also returns the distance
(generation) between the input node and the root node. The root of the spanning
tree is node 0 if there is a physical ground node, otherwise it is node 1.
Notice that the branches that sit on the boundaries of capacitive islands are
not included in the branch list.
Parameters
----------
node: Node
Node variable which is the input
Returns
-------
An integer for the generation number, a list of ancestor nodes, and a list
of branches on the path
"""
# extract spanning trees node_sets (to determine the generation of the node)
tree_info_dict = spanning_tree_dict or self.spanning_tree_dict
# find out the generation number of the node in the spanning tree
for tree_idx, tree in enumerate(tree_info_dict["list_of_trees"]):
node_sets = tree_info_dict["node_sets_for_trees"][tree_idx]
tree = tree_info_dict["list_of_trees"][tree_idx]
# generation number begins from 0
for igen, nodes in enumerate(node_sets):
nodes_id = [node.index for node in nodes]
if node.index in nodes_id:
generation = igen
break
# find out the path from the node to the root
current_node = node
ancestor_nodes_list = []
branch_path_to_root = []
root_node = node_sets[0][0]
if root_node == node:
return (0, [], [], tree_idx)
tree_perm_gen = (perm for perm in itertools.permutations(tree))
while root_node not in ancestor_nodes_list:
ancestor_nodes_list = []
branch_path_to_root = []
current_node = node
try:
tree_perm = next(tree_perm_gen)
except StopIteration:
break
# finding the parent of the current_node, and the branch that links the
# parent and current_node
for branch in tree_perm:
common_node_list = [
n for n in branch.nodes if n not in [current_node]
]
if (
len(common_node_list) == 1
and common_node_list[0] not in ancestor_nodes_list
):
second_node = common_node_list[0]
ancestor_nodes_list.append(second_node)
branch_path_to_root.append(branch)
current_node = second_node
if current_node.index == root_node.index:
break
if root_node in ancestor_nodes_list:
break
ancestor_nodes_list.reverse()
branch_path_to_root.reverse()
return generation, ancestor_nodes_list, branch_path_to_root, tree_idx
def _find_loop(
self, closure_branch: Branch, spanning_tree_dict=None
) -> List["Branch"]:
r"""Find out the loop that is closed by the closure branch.
Parameters
----------
closure_branch: Branch
The input closure branch
Returns
-------
A list of branches that corresponds to the loop closed by the closure branch
"""
# find out ancestor nodes, path to root and generation number for each node in the
# closure branch
tree_info_dict = spanning_tree_dict or self.spanning_tree_dict
_, _, path_1, tree_idx_0 = self._find_path_to_root(
closure_branch.nodes[0], tree_info_dict
)
_, _, path_2, tree_idx_1 = self._find_path_to_root(
closure_branch.nodes[1], tree_info_dict
)
# find branches that are not common in the paths, and then add the closure
# branch to form the loop
path_1 = unique_elements_in_list(path_1)
path_2 = unique_elements_in_list(path_2)
loop = (
[branch for branch in path_1 if branch not in path_2]
+ [branch for branch in path_2 if branch not in path_1]
+ [closure_branch]
)
return self._order_branches_in_loop(loop)
def _order_branches_in_loop(self, loop_branches):
branches_in_order = [loop_branches[0]]
branch_node_ids = [branch.node_ids() for branch in loop_branches]
prev_node_id = branch_node_ids[0][0]
while len(branches_in_order) < len(loop_branches):
for branch in [
brnch for brnch in loop_branches if brnch not in branches_in_order
]:
if prev_node_id in branch.node_ids():
branches_in_order.append(branch)
break
prev_node_id = [idx for idx in branch.node_ids() if idx != prev_node_id][0]
return branches_in_order
def _set_external_fluxes(
self,
closure_branches: Optional[List[Union[Branch, Dict[Branch, float]]]] = None,
):
# setting the class properties
if self.is_purely_harmonic and not self.use_dynamic_flux_grouping:
self.external_fluxes = []
self.closure_branches = []
return 0
if closure_branches:
closure_branch_list = [
branch if isinstance(branch, Branch) else list(branch.keys())
for branch in closure_branches
]
closure_branch_list = flatten_list_recursive(closure_branch_list)
for branch in closure_branch_list:
if branch.type == "C" and not self.use_dynamic_flux_grouping:
raise ValueError(
"The closure branch cannot be a capacitive branch, when dynamic flux grouping is not used."
)
closure_branches = closure_branches or self._closure_branches()
if len(closure_branches) > 0:
self.closure_branches = closure_branches
self.external_fluxes = [
symbols("Φ" + str(i + 1)) for i in range(len(closure_branches))
]
@staticmethod
def are_branchsets_disconnected(
branch_list1: List[Branch], branch_list2: List[Branch]
) -> bool:
"""Determines whether two sets of branches are disconnected.
Parameters
----------
branch_list1:
first list of branches
branch_list2:
second list of branches
Returns
-------
bool
Returns True if the branches have a connection, else False
"""
node_array1 = np.array([branch.node_ids() for branch in branch_list1]).flatten()
node_array2 = np.array([branch.node_ids() for branch in branch_list2]).flatten()
return np.intersect1d(node_array1, node_array2).size == 0
@staticmethod
def _parse_nodes(branches_list) -> List[Node]:
node_index_list = []
for branch_list_input in [
branch for branch in branches_list if branch[0] != "ML"
]:
for idx in [1, 2]:
node_idx = branch_list_input[idx]
if node_idx not in node_index_list:
node_index_list.append(node_idx)
node_index_list.sort()
return [Node(idx) for idx in node_index_list]
@classmethod
def from_yaml(
cls,
input_string: str,
from_file: bool = True,
basis_completion: str = "heuristic",
use_dynamic_flux_grouping: bool = False,
initiate_sym_calc: bool = True,
):
"""
Constructs the instance of Circuit from an input string. Here is an example of
an input string that is used to initiate an object of the
class `SymbolicCircuit`::
#zero-pi.yaml
nodes : 4
# zero-pi
branches:
- [JJ, 1,2, EJ = 10, 20]
- [JJ, 3,4, 10, 20]
- [L, 2,3, 0.008]
- [L, 4,1, 0.008]
- [C, 1,3, 0.02]
- [C, 2,4, 0.02]
Parameters
----------
input_string:
String describing the number of nodes and branches connecting then along
with their parameters
from_file:
Set to True by default, when a file name should be provided to
`input_string`, else the circuit graph description in YAML should be
provided as a string.
basis_completion:
choices: "heuristic" or "canonical"; used to choose a type of basis
for completing the transformation matrix. Set to "heuristic" by default.
use_dynamic_flux_grouping: bool
set to False by default. Indicates if the flux allocation is done by
assuming that flux is time dependent. When set to True, it disables the
option to change the closure branches.
initiate_sym_calc:
set to True by default. Initiates the object attributes by calling
the function `initiate_symboliccircuit` method when set to True.
Set to False for debugging.
Returns
-------
Instance of the class `SymbolicCircuit`
"""
if from_file:
file = open(input_string, "r")
circuit_desc = file.read()
file.close()
else:
circuit_desc = input_string
input_str = remove_comments(circuit_desc)
input_str = remove_branchline(input_str)
input_str = strip_empty_lines(input_str)
parsed_branches = [
parse_code_line(code_line, branch_count)
for branch_count, code_line in enumerate(input_str.split("\n"))
]
# find and create the nodes
nodes_list = cls._parse_nodes(parsed_branches)
# if the node indices do not start from 0, raise an error
node_ids = [node.index for node in nodes_list]
if min(node_ids) not in [0, 1]:
raise ValueError("The node indices should start from 0 or 1.")
# parse branches and couplers
branches_list = []
couplers_list = []
branch_var_dict = {}
# make individual branches
individual_branches = [
branch for branch in parsed_branches if branch[0] != "ML"
]
for parsed_branch in individual_branches:
branch, sym_params = make_branch(nodes_list, *parsed_branch)
for sym_param in sym_params:
if sym_param in branch_var_dict and sym_params[sym_param]:
raise Exception(
f"Symbol {sym_param} has already been assigned a value."
)
if sym_params[sym_param]:
branch_var_dict[sym_param] = sym_params[sym_param]
branches_list.append(branch)
# make couplers
coupler_branches = [
branch for branch in parsed_branches if branch not in individual_branches
]
for parsed_branch in coupler_branches:
coupler, sym_params = make_coupler(branches_list, *parsed_branch)
for sym_param in sym_params:
if sym_param in branch_var_dict and sym_params[sym_param]:
raise Exception(
f"Symbol {sym_param} has already been assigned a value."
)
if sym_params[sym_param]:
branch_var_dict[sym_param] = sym_params[sym_param]
couplers_list.append(coupler)
circuit = cls(
nodes_list,
branches_list,
couplers_list,
use_dynamic_flux_grouping=use_dynamic_flux_grouping,
branch_var_dict=branch_var_dict,
basis_completion=basis_completion,
initiate_sym_calc=initiate_sym_calc,
input_string=circuit_desc,
)
return circuit
def _independent_modes(
self,
branch_subset: List[Branch],
single_nodes: bool = True,
basisvec_entries: Optional[List[int]] = None,
):
"""Returns the vectors which span a subspace where there is no generalized flux
difference across the branches present in the branch_subset.
Parameters
----------
single_nodes:
if the single nodes are taken into consideration for basis vectors.
"""
if basisvec_entries is None:
basisvec_entries = [1, 0]
nodes_copy = copy.copy(self.nodes) # copying self.nodes as it is being modified
# making sure that the ground node is placed at the end of the list
if self.ground_node:
nodes_copy.pop(0) # removing the ground node
nodes_copy = nodes_copy + [
copy.copy(self.ground_node)
] # reversing the order of the nodes
for node in nodes_copy: # reset the node markers
node.marker = 0
# step 2: finding the maximum connected set of independent branches in
# branch_subset, then identifying the sets of nodes in each of those sets
branch_subset_copy = branch_subset.copy()
max_connected_subgraphs: List[List[Branch]] = (
[]
) # list containing the maximum connected subgraphs
while (
len(branch_subset_copy) > 0
): # while loop ends when all the branches are sorted
b_0 = branch_subset_copy.pop(0)
max_connected_subgraph = [b_0]
while not self.are_branchsets_disconnected(
max_connected_subgraph, branch_subset_copy
):
for b1 in branch_subset_copy:
for b2 in max_connected_subgraph:
if b1.is_connected(b2):
max_connected_subgraph.append(b1)
branch_subset_copy.remove(b1)
break
max_connected_subgraphs.append(max_connected_subgraph)
# finding the nodes in each of the maximum connected subgraph
nodes_in_max_connected_branchsets = [
unique_elements_in_list(
list(chain(*[branch.nodes for branch in branch_set]))
)
for branch_set in max_connected_subgraphs
]
# using node.marker to mark the maximum connected subgraph to which a node
# belongs
for node_set_index, node_set in enumerate(nodes_in_max_connected_branchsets):
for node in node_set:
if any([n.is_ground() for n in node_set]):
node.marker = -1
else:
node.marker = node_set_index + 1
# marking ground nodes separately
for node in nodes_copy:
if node.is_ground():
node.marker = -1
node_branch_set_indices = [
node.marker for node in nodes_copy
] # identifies which node belongs to which maximum connected subgraphs;
# different numbers on two nodes indicates that they are not connected through
# any of the branches in branch_subset. 0 implies the node does not belong to
# any of the branches in max connected branch subsets and -1 implies the max
# connected branch set is connected to ground.
# step 3: Finding the linearly independent vectors spanning the vector space
# represented by branch_set_index
basis = []
unique_branch_set_markers = unique_elements_in_list(node_branch_set_indices)
# removing the marker -1 as it is grounded.
branch_set_markers_ungrounded = [
marker for marker in unique_branch_set_markers if marker != -1
]
for index in branch_set_markers_ungrounded:
basis.append(
[
basisvec_entries[0] if i == index else basisvec_entries[1]
for i in node_branch_set_indices
]
)
if single_nodes: # taking the case where the node_branch_set_index is 0
single_node_modes = []
if node_branch_set_indices.count(0) > 0:
ref_vector = [
basisvec_entries[0] if i == 0 else basisvec_entries[1]
for i in node_branch_set_indices
]
positions = [
index
for index, num in enumerate(ref_vector)
if num == basisvec_entries[0]
]
for pos in positions:
single_node_modes.append(
[
basisvec_entries[0] if x == pos else basisvec_entries[1]
for x, num in enumerate(node_branch_set_indices)
]
)
for mode in single_node_modes:
mat = np.array(basis + [mode])
if np.linalg.matrix_rank(mat) == len(mat):
basis.append(mode)
if (
self.is_grounded
): # if grounded remove the last column and first row corresponding to the
basis = [i[:-1] for i in basis]
return basis
@staticmethod
def _mode_in_subspace(mode, subspace) -> bool:
"""Method to check if the vector mode is a part of the subspace provided as a
set of vectors.
Parameters
----------
mode:
numpy ndarray of one dimension.
subspace:
numpy ndarray which represents a collection of basis vectors for a vector
subspace
"""
if len(subspace) == 0:
return False
matrix = np.vstack([subspace, np.array(mode)])
return np.linalg.matrix_rank(matrix) == len(subspace)
def check_transformation_matrix(
self, transformation_matrix: ndarray, enable_warnings: bool = True
) -> Dict[str, List[int]]:
"""Method to identify the different modes in the transformation matrix provided
by the user.
Parameters
----------
transformation_matrix:
numpy ndarray which is a square matrix having the dimensions of the number
of nodes present in the circuit.
warnings:
If False, will not raise the warnings regarding any unidentified modes. It
is set to True by default.
Returns
-------
A dictionary of lists which has the variable indices classified with
var indices corresponding to the rows of the transformation matrix
"""
# basic check to see if the matrix is invertible
if np.linalg.det(transformation_matrix) == 0:
raise Exception("The transformation matrix provided is not invertible.")
# find all the different types of modes present in the circuit.
# *************************** Finding the Periodic Modes **********************
selected_branches = [branch for branch in self.branches if branch.type == "L"]
periodic_modes = self._independent_modes(selected_branches)
# *************************** Finding the frozen modes **********************
selected_branches = [branch for branch in self.branches if branch.type != "L"]
frozen_modes = self._independent_modes(selected_branches, single_nodes=True)
# *************************** Finding the Cyclic Modes ****************
selected_branches = [branch for branch in self.branches if branch.type != "C"]
free_modes = self._independent_modes(selected_branches)
# ***************************# Finding the LC Modes ****************
selected_branches = [branch for branch in self.branches if "JJ" in branch.type]
LC_modes = self._independent_modes(selected_branches, single_nodes=False)
# ******************* including the Σ mode ****************
Σ = [1] * (len(self.nodes) - self.is_grounded)
if not self.is_grounded: # only append if the circuit is not grounded
mat = np.array(frozen_modes + [Σ])
# check to see if the vectors are still independent
if np.linalg.matrix_rank(mat) < len(frozen_modes) + 1:
frozen_modes = frozen_modes[1:] + [Σ]
else:
frozen_modes.append(Σ)
# *********** Adding periodic, free and extended modes to frozen ************
modes = [] # starting with the frozen modes
for m in (
frozen_modes + free_modes + periodic_modes + LC_modes # + extended_modes
): # This order is important
if not self._mode_in_subspace(m, modes):
modes.append(m)
for m in LC_modes: # adding the LC modes to the basis
if not self._mode_in_subspace(m, modes):
modes.append(m)
var_categories_circuit: Dict[str, list] = {
"periodic": [],
"extended": [],
"free": [],
"frozen": [],
}
for x, mode in enumerate(modes):
# calculate the number of periodic modes
if self._mode_in_subspace(Σ, [mode]) and not self.is_grounded:
continue
if self._mode_in_subspace(mode, frozen_modes):
var_categories_circuit["frozen"].append(x + 1)
continue
if self._mode_in_subspace(mode, free_modes):
var_categories_circuit["free"].append(x + 1)
continue
if self._mode_in_subspace(mode, periodic_modes):
var_categories_circuit["periodic"].append(x + 1)
continue
# Any mode which survived the above conditionals is an extended mode
var_categories_circuit["extended"].append(x + 1)
# Classifying the modes given in the transformation by the user
user_given_modes = transformation_matrix.transpose()
var_categories_user: Dict[str, list] = {
"periodic": [],
"extended": [],
"free": [],
"frozen": [],
"sigma": [],
}
sigma_mode_found = False
for x, mode in enumerate(user_given_modes):
# calculate the number of periodic modes
if self._mode_in_subspace(Σ, [mode]) and not self.is_grounded:
sigma_mode_found = True
var_categories_user["sigma"].append(x + 1)
continue
if self._mode_in_subspace(mode, frozen_modes):
var_categories_user["frozen"].append(x + 1)
continue
if self._mode_in_subspace(mode, free_modes):
var_categories_user["free"].append(x + 1)
continue
if self._mode_in_subspace(mode, periodic_modes):
var_categories_user["periodic"].append(x + 1)
continue
# Any mode which survived the above conditionals is an extended mode
var_categories_user["extended"].append(x + 1)
# comparing the modes in the user defined and the code generated transformation
mode_types = ["periodic", "extended", "free", "frozen"]
for mode_type in mode_types:
num_extra_modes = len(var_categories_circuit[mode_type]) - len(
var_categories_user[mode_type]
)
if num_extra_modes > 0 and enable_warnings:
warnings.warn(
"Number of extra "
+ mode_type
+ " modes found: "
+ str(num_extra_modes)
+ "\n"
)
if not self.is_grounded and not sigma_mode_found:
raise Exception(
"This circuit is not grounded, and so has a sigma mode. This transformation does not have a sigma mode."
)
return var_categories_user
def variable_transformation_matrix(self) -> Tuple[ndarray, Dict[str, List[int]]]:
"""Evaluates the boundary conditions and constructs the variable transformation
matrix, which is returned along with the dictionary `var_categories` which
classifies the types of variables present in the circuit.
Returns
-------
tuple of transformation matrix for the node variables and `var_categories`
dict which classifies the variable types for each variable index
"""
# **************** Finding the Periodic Modes ****************
selected_branches = [branch for branch in self.branches if branch.type == "L"]
periodic_modes = self._independent_modes(selected_branches)
# **************** Finding the frozen modes ****************
selected_branches = [branch for branch in self.branches if branch.type != "L"]
frozen_modes = self._independent_modes(selected_branches, single_nodes=True)
# **************** Finding the Cyclic Modes ****************
selected_branches = [branch for branch in self.branches if branch.type != "C"]
free_modes = self._independent_modes(selected_branches)
# **************** including the Σ mode ****************
Σ = [1] * (len(self.nodes) - self.is_grounded)
if not self.is_grounded: # only append if the circuit is not grounded
mat = np.array(frozen_modes + [Σ])
# check to see if the vectors are still independent
if np.linalg.matrix_rank(mat) < len(frozen_modes) + 1:
frozen_modes = frozen_modes[1:] + [Σ]
else:
frozen_modes.append(Σ)
# **************** Finding the LC Modes ****************
selected_branches = [branch for branch in self.branches if "JJ" in branch.type]
LC_modes = self._independent_modes(
selected_branches, single_nodes=False, basisvec_entries=[-1, 1]
)
# **************** Adding frozen, free, periodic , LC and extended modes ****
modes = [] # starting with an empty list
for m in (
frozen_modes + free_modes + periodic_modes + LC_modes # + extended_modes
): # This order is important
mat = np.array(modes + [m])
if np.linalg.matrix_rank(mat) == len(mat):
modes.append(m)
# ********** Completing the Basis ****************
# step 4: construct the new set of basis vectors
# constructing a standard basis
if self.basis_completion == "heuristic":
node_count = len(self.nodes) - self.is_grounded
standard_basis = [np.ones(node_count)]
vector_ref = np.zeros(node_count)
if node_count > 2:
vector_ref[: node_count - 2] = 1
else:
vector_ref[: node_count - 1] = 1
vector_set = (
permutation
for permutation in itertools.permutations(vector_ref, node_count)
) # making a generator
while np.linalg.matrix_rank(np.array(standard_basis)) < node_count:
a = next(vector_set)
mat = np.array(standard_basis + [a])
if np.linalg.matrix_rank(mat) == len(mat):
standard_basis = standard_basis + [list(a)]
standard_basis = np.array(standard_basis)
elif self.basis_completion == "canonical":
standard_basis = np.identity(len(self.nodes) - self.is_grounded)
new_basis = modes.copy()
for m in standard_basis: # completing the basis
mat = np.array([i for i in new_basis] + [m])
if np.linalg.matrix_rank(mat) == len(mat):
new_basis.append(m)
new_basis = np.array(new_basis)
# sorting the basis so that the free, periodic and frozen variables occur at
# the beginning.
if not self.is_grounded:
pos_Σ = [i for i in range(len(new_basis)) if new_basis[i].tolist() == Σ]
else:
pos_Σ = []
pos_free = [
i
for i in range(len(new_basis))
if i not in pos_Σ
if new_basis[i].tolist() in free_modes
]
pos_periodic = [
i
for i in range(len(new_basis))
if i not in pos_Σ
if i not in pos_free
if new_basis[i].tolist() in periodic_modes
]
pos_frozen = [
i
for i in range(len(new_basis))
if i not in pos_Σ
if i not in pos_free
if i not in pos_periodic
if new_basis[i].tolist() in frozen_modes
]
pos_rest = [
i
for i in range(len(new_basis))
if i not in pos_Σ
if i not in pos_free
if i not in pos_periodic
if i not in pos_frozen
]
pos_list = pos_periodic + pos_rest + pos_free + pos_frozen + pos_Σ
# transforming the new_basis matrix
new_basis = new_basis[pos_list].T
# saving the variable identification to a dict
var_categories = {
"periodic": [
i + 1 for i in range(len(pos_list)) if pos_list[i] in pos_periodic
],
"extended": [
i + 1 for i in range(len(pos_list)) if pos_list[i] in pos_rest
],
"free": [i + 1 for i in range(len(pos_list)) if pos_list[i] in pos_free],
"frozen": [
i + 1 for i in range(len(pos_list)) if pos_list[i] in pos_frozen
],
"sigma": (
[i + 1 for i in range(len(pos_list)) if pos_list[i] == pos_Σ[0]]
if not self.is_grounded
else []
),
}
return np.array(new_basis), var_categories

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

+0
-0

@@ -0,0 +0,0 @@ BSD 3-Clause License

@@ -0,0 +0,0 @@ include README.md

+145
-42

@@ -1,64 +0,167 @@

Metadata-Version: 2.1
Metadata-Version: 2.2
Name: scqubits
Version: 4.2.0
Version: 4.3
Summary: scqubits: superconducting qubits in Python
Home-page: https://scqubits.readthedocs.io
Author: Jens Koch, Peter Groszkowski
Author-email: jens-koch@northwestern.edu, piotrekg@gmail.com
License: BSD
Keywords: superconducting qubits
Platform: Linux
Platform: Mac OSX
Platform: Unix
Platform: Windows
Author-email: Jens Koch <jens-koch@northwestern.edu>, Peter Groszkowski <piotrekg@gmail.com>
License: BSD 3-Clause License
Copyright (c) 2019 and later, Jens Koch and Peter Groszkowski
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Project-URL: Homepage, https://scqubits.readthedocs.io
Project-URL: Repository, https://github.com/scqubits/scqubits
Keywords: qubits,superconducting
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Operating System :: Microsoft :: Windows
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.14.2
Requires-Dist: scipy<=1.13.1,>=1.5; sys_platform == "darwin" and python_version >= "3.10"
Requires-Dist: scipy>=1.5; sys_platform == "darwin" and python_version < "3.10"
Requires-Dist: scipy>=1.5; sys_platform != "darwin"
Requires-Dist: cycler
Requires-Dist: cython>=0.29.20
Requires-Dist: dill
Requires-Dist: pathos>=0.3.0
Requires-Dist: matplotlib>=3.5.1
Requires-Dist: numpy>=1.14.2
Requires-Dist: pathos>=0.3.0
Requires-Dist: qutip>=4.3.1
Requires-Dist: scipy>=1.5; sys_platform != "darwin"
Requires-Dist: scipy>=1.5; sys_platform == "darwin" and python_version < "3.10"
Requires-Dist: scipy<=1.13.1,>=1.5; sys_platform == "darwin" and python_version >= "3.10"
Requires-Dist: sympy
Requires-Dist: tqdm
Requires-Dist: typing_extensions
Requires-Dist: pathos
Requires-Dist: dill
Provides-Extra: graphics
Requires-Dist: matplotlib-label-lines>=0.3.6; extra == "graphics"
Provides-Extra: explorer
Requires-Dist: ipywidgets>=7.5; extra == "explorer"
Provides-Extra: h5-support
Requires-Dist: h5py>=2.10; extra == "h5-support"
Provides-Extra: pathos
Requires-Dist: pathos; extra == "pathos"
Requires-Dist: dill; extra == "pathos"
Provides-Extra: gui
Requires-Dist: ipywidgets>=7.5; extra == "gui"
Requires-Dist: ipyvuetify; extra == "gui"
Requires-Dist: matplotlib-label-lines>=0.3.6; extra == "gui"
Requires-Dist: h5py>=2.10; extra == "gui"
Provides-Extra: develop
Requires-Dist: pytest; extra == "develop"
Requires-Dist: traitlets; extra == "develop"
Requires-Dist: h5py>=2.10; extra == "develop"
scqubits: superconducting qubits in Python
===========================================
scqubits is an open-source Python library for simulating superconducting qubits. It is
meant to give the user a convenient way to obtain energy spectra of common
superconducting qubits, plot energy levels as a function of external parameters,
calculate matrix elements etc. The library further provides an interface to QuTiP,
making it easy to work with composite Hilbert spaces consisting of coupled
superconducting qubits and harmonic modes. Internally, numerics within scqubits is
carried out with the help of Numpy and Scipy; plotting capabilities rely on
[![Anaconda-Server Badge](https://anaconda.org/conda-forge/scqubits/badges/downloads.svg)](https://anaconda.org/conda-forge/scqubits)
[![CodeFactor](https://www.codefactor.io/repository/github/scqubits/scqubits/badge)](https://www.codefactor.io/repository/github/scqubits/scqubits)
[![codecov](https://codecov.io/gh/scqubits/scqubits/branch/main/graph/badge.svg?token=PUBXSHF6HU)](https://codecov.io/gh/scqubits/scqubits)
[J. Koch](https://github.com/jkochNU), [P. Groszkowski](https://github.com/petergthatsme)
<br>
> **Join the scqubits mailing list!** Receive information about new releases
> and opportunities to contribute
> to new developments.
> |[SIGN UP](https://sites.northwestern.edu/koch/scqubits-news-sign-up/)|
> |---------------------------------------------------------------------|
<br>
scqubits is an open-source Python library for simulating superconducting qubits. It is meant to give the user
a convenient way to obtain energy spectra of common superconducting qubits, plot energy levels as a function of
external parameters, calculate matrix elements etc. The library further provides an interface to QuTiP, making it
easy to work with composite Hilbert spaces consisting of coupled superconducting qubits and harmonic modes.
Internally, numerics within scqubits is carried out with the help of Numpy and Scipy; plotting capabilities rely on
Matplotlib.
If scqubits is helpful to you in your research, please support its continued
development and maintenance. Use of scqubits in research publications is
If scqubits is helpful to you in your research, please support its continued
development and maintenance. Use of scqubits in research publications is
appropriately acknowledged by citing:
Peter Groszkowski and Jens Koch, 'scqubits: a Python package for superconducting qubits',
Quantum 5, 583 (2021). https://quantum-journal.org/papers/q-2021-11-17-583/
&nbsp; &nbsp; Peter Groszkowski and Jens Koch,<br>
&nbsp; &nbsp; *scqubits: a Python package for superconducting qubits*,<br>
&nbsp; &nbsp; Quantum 5, 583 (2021).<br>
&nbsp; &nbsp; https://quantum-journal.org/papers/q-2021-11-17-583/
&nbsp; &nbsp; Sai Pavan Chitta, Tianpu Zhao, Ziwen Huang, Ian Mondragon-Shem, and Jens Koch,<br>
&nbsp; &nbsp; *Computer-aided quantization and numerical analysis of superconducting circuits*,<br>
&nbsp; &nbsp; New J. Phys. 24 103020 (2022).<br>
&nbsp; &nbsp; https://iopscience.iop.org/article/10.1088/1367-2630/ac94f2
Download and Installation
-------------------------
For Python 3.9 - 3.12: installation via conda is supported.
```
conda install -c conda-forge scqubits
```
Alternatively, scqubits can be installed via pip (although it should be noted that installing via pip under a conda environment is strongly discouraged, and is not guaranteed to work - see conda documentation).
```
pip install scqubits
```
Documentation
-------------
The documentation for scqubits is available at: https://scqubits.readthedocs.io
Related Packages
----------------
There are two related packages on github:
documentation source code: https://github.com/scqubits/scqubits-doc
example notebooks: https://github.com/scqubits/scqubits-examples
Contribute
----------
You are welcome to contribute to scqubits development by forking this repository and sending pull requests,
or filing bug reports at the
[issues page](https://github.com/scqubits/scqubits/issues).
All contributions are acknowledged in the
[contributors](https://scqubits.readthedocs.io/en/latest/contributors.html)
section in the documentation.
All contributions are expected to be consistent with [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/).
License
-------
[![license](https://img.shields.io/badge/license-New%20BSD-blue.svg)](http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22Revised_BSD_License.22.2C_.22New_BSD_License.22.2C_or_.22Modified_BSD_License.22.29)
You are free to use this software, with or without modification, provided that the conditions listed in the LICENSE file are satisfied.

@@ -6,3 +6,3 @@ scqubits: superconducting qubits in Python

[![CodeFactor](https://www.codefactor.io/repository/github/scqubits/scqubits/badge)](https://www.codefactor.io/repository/github/scqubits/scqubits)
[![codecov](https://codecov.io/gh/scqubits/scqubits/branch/master/graph/badge.svg?token=PUBXSHF6HU)](https://codecov.io/gh/scqubits/scqubits)
[![codecov](https://codecov.io/gh/scqubits/scqubits/branch/main/graph/badge.svg?token=PUBXSHF6HU)](https://codecov.io/gh/scqubits/scqubits)

@@ -9,0 +9,0 @@

@@ -1,64 +0,167 @@

Metadata-Version: 2.1
Metadata-Version: 2.2
Name: scqubits
Version: 4.2.0
Version: 4.3
Summary: scqubits: superconducting qubits in Python
Home-page: https://scqubits.readthedocs.io
Author: Jens Koch, Peter Groszkowski
Author-email: jens-koch@northwestern.edu, piotrekg@gmail.com
License: BSD
Keywords: superconducting qubits
Platform: Linux
Platform: Mac OSX
Platform: Unix
Platform: Windows
Author-email: Jens Koch <jens-koch@northwestern.edu>, Peter Groszkowski <piotrekg@gmail.com>
License: BSD 3-Clause License
Copyright (c) 2019 and later, Jens Koch and Peter Groszkowski
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Project-URL: Homepage, https://scqubits.readthedocs.io
Project-URL: Repository, https://github.com/scqubits/scqubits
Keywords: qubits,superconducting
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: BSD License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Operating System :: Unix
Classifier: Operating System :: Microsoft :: Windows
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.14.2
Requires-Dist: scipy<=1.13.1,>=1.5; sys_platform == "darwin" and python_version >= "3.10"
Requires-Dist: scipy>=1.5; sys_platform == "darwin" and python_version < "3.10"
Requires-Dist: scipy>=1.5; sys_platform != "darwin"
Requires-Dist: cycler
Requires-Dist: cython>=0.29.20
Requires-Dist: dill
Requires-Dist: pathos>=0.3.0
Requires-Dist: matplotlib>=3.5.1
Requires-Dist: numpy>=1.14.2
Requires-Dist: pathos>=0.3.0
Requires-Dist: qutip>=4.3.1
Requires-Dist: scipy>=1.5; sys_platform != "darwin"
Requires-Dist: scipy>=1.5; sys_platform == "darwin" and python_version < "3.10"
Requires-Dist: scipy<=1.13.1,>=1.5; sys_platform == "darwin" and python_version >= "3.10"
Requires-Dist: sympy
Requires-Dist: tqdm
Requires-Dist: typing_extensions
Requires-Dist: pathos
Requires-Dist: dill
Provides-Extra: graphics
Requires-Dist: matplotlib-label-lines>=0.3.6; extra == "graphics"
Provides-Extra: explorer
Requires-Dist: ipywidgets>=7.5; extra == "explorer"
Provides-Extra: h5-support
Requires-Dist: h5py>=2.10; extra == "h5-support"
Provides-Extra: pathos
Requires-Dist: pathos; extra == "pathos"
Requires-Dist: dill; extra == "pathos"
Provides-Extra: gui
Requires-Dist: ipywidgets>=7.5; extra == "gui"
Requires-Dist: ipyvuetify; extra == "gui"
Requires-Dist: matplotlib-label-lines>=0.3.6; extra == "gui"
Requires-Dist: h5py>=2.10; extra == "gui"
Provides-Extra: develop
Requires-Dist: pytest; extra == "develop"
Requires-Dist: traitlets; extra == "develop"
Requires-Dist: h5py>=2.10; extra == "develop"
scqubits: superconducting qubits in Python
===========================================
scqubits is an open-source Python library for simulating superconducting qubits. It is
meant to give the user a convenient way to obtain energy spectra of common
superconducting qubits, plot energy levels as a function of external parameters,
calculate matrix elements etc. The library further provides an interface to QuTiP,
making it easy to work with composite Hilbert spaces consisting of coupled
superconducting qubits and harmonic modes. Internally, numerics within scqubits is
carried out with the help of Numpy and Scipy; plotting capabilities rely on
[![Anaconda-Server Badge](https://anaconda.org/conda-forge/scqubits/badges/downloads.svg)](https://anaconda.org/conda-forge/scqubits)
[![CodeFactor](https://www.codefactor.io/repository/github/scqubits/scqubits/badge)](https://www.codefactor.io/repository/github/scqubits/scqubits)
[![codecov](https://codecov.io/gh/scqubits/scqubits/branch/main/graph/badge.svg?token=PUBXSHF6HU)](https://codecov.io/gh/scqubits/scqubits)
[J. Koch](https://github.com/jkochNU), [P. Groszkowski](https://github.com/petergthatsme)
<br>
> **Join the scqubits mailing list!** Receive information about new releases
> and opportunities to contribute
> to new developments.
> |[SIGN UP](https://sites.northwestern.edu/koch/scqubits-news-sign-up/)|
> |---------------------------------------------------------------------|
<br>
scqubits is an open-source Python library for simulating superconducting qubits. It is meant to give the user
a convenient way to obtain energy spectra of common superconducting qubits, plot energy levels as a function of
external parameters, calculate matrix elements etc. The library further provides an interface to QuTiP, making it
easy to work with composite Hilbert spaces consisting of coupled superconducting qubits and harmonic modes.
Internally, numerics within scqubits is carried out with the help of Numpy and Scipy; plotting capabilities rely on
Matplotlib.
If scqubits is helpful to you in your research, please support its continued
development and maintenance. Use of scqubits in research publications is
If scqubits is helpful to you in your research, please support its continued
development and maintenance. Use of scqubits in research publications is
appropriately acknowledged by citing:
Peter Groszkowski and Jens Koch, 'scqubits: a Python package for superconducting qubits',
Quantum 5, 583 (2021). https://quantum-journal.org/papers/q-2021-11-17-583/
&nbsp; &nbsp; Peter Groszkowski and Jens Koch,<br>
&nbsp; &nbsp; *scqubits: a Python package for superconducting qubits*,<br>
&nbsp; &nbsp; Quantum 5, 583 (2021).<br>
&nbsp; &nbsp; https://quantum-journal.org/papers/q-2021-11-17-583/
&nbsp; &nbsp; Sai Pavan Chitta, Tianpu Zhao, Ziwen Huang, Ian Mondragon-Shem, and Jens Koch,<br>
&nbsp; &nbsp; *Computer-aided quantization and numerical analysis of superconducting circuits*,<br>
&nbsp; &nbsp; New J. Phys. 24 103020 (2022).<br>
&nbsp; &nbsp; https://iopscience.iop.org/article/10.1088/1367-2630/ac94f2
Download and Installation
-------------------------
For Python 3.9 - 3.12: installation via conda is supported.
```
conda install -c conda-forge scqubits
```
Alternatively, scqubits can be installed via pip (although it should be noted that installing via pip under a conda environment is strongly discouraged, and is not guaranteed to work - see conda documentation).
```
pip install scqubits
```
Documentation
-------------
The documentation for scqubits is available at: https://scqubits.readthedocs.io
Related Packages
----------------
There are two related packages on github:
documentation source code: https://github.com/scqubits/scqubits-doc
example notebooks: https://github.com/scqubits/scqubits-examples
Contribute
----------
You are welcome to contribute to scqubits development by forking this repository and sending pull requests,
or filing bug reports at the
[issues page](https://github.com/scqubits/scqubits/issues).
All contributions are acknowledged in the
[contributors](https://scqubits.readthedocs.io/en/latest/contributors.html)
section in the documentation.
All contributions are expected to be consistent with [PEP 8 -- Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/).
License
-------
[![license](https://img.shields.io/badge/license-New%20BSD-blue.svg)](http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22Revised_BSD_License.22.2C_.22New_BSD_License.22.2C_or_.22Modified_BSD_License.22.29)
You are free to use this software, with or without modification, provided that the conditions listed in the LICENSE file are satisfied.

@@ -1,5 +0,7 @@

numpy>=1.14.2
cycler
cython>=0.29.20
dill
pathos>=0.3.0
matplotlib>=3.5.1
numpy>=1.14.2
pathos>=0.3.0
qutip>=4.3.1

@@ -9,4 +11,2 @@ sympy

typing_extensions
pathos
dill

@@ -22,13 +22,11 @@ [:sys_platform != "darwin"]

[explorer]
[develop]
pytest
traitlets
h5py>=2.10
[gui]
ipywidgets>=7.5
[graphics]
ipyvuetify
matplotlib-label-lines>=0.3.6
[h5-support]
h5py>=2.10
[pathos]
pathos
dill

@@ -0,7 +1,15 @@

.gitignore
CITATION.bib
LICENSE
MANIFEST.in
README.md
optional-requirements.txt
requirements.txt
setup.py
azure-pipelines.yml
meta.yaml
pyproject.toml
.github/ISSUE_TEMPLATE/bug_report.md
.github/ISSUE_TEMPLATE/ideas--suggestions--feature-requests.md
.github/ISSUE_TEMPLATE/other-issues.md
.github/workflows/black-check-latest.yml
.github/workflows/mamba-install-and-run-pytests.yml
.github/workflows/pythonpublish.yml
scqubits/__init__.py

@@ -15,3 +23,2 @@ scqubits/py.typed

scqubits.egg-info/dependency_links.txt
scqubits.egg-info/not-zip-safe
scqubits.egg-info/requires.txt

@@ -24,3 +31,5 @@ scqubits.egg-info/top_level.txt

scqubits/core/circuit_noise.py
scqubits/core/circuit_plotting.py
scqubits/core/circuit_routines.py
scqubits/core/circuit_sym_methods.py
scqubits/core/circuit_utils.py

@@ -46,2 +55,3 @@ scqubits/core/constants.py

scqubits/core/symbolic_circuit.py
scqubits/core/symbolic_circuit_graph.py
scqubits/core/transmon.py

@@ -71,2 +81,3 @@ scqubits/core/units.py

scqubits/io_utils/fileio_serializers.py
scqubits/tests/.coverage
scqubits/tests/__init__.py

@@ -89,2 +100,3 @@ scqubits/tests/conftest.py

scqubits/tests/test_parametersweep.py
scqubits/tests/test_snailmon.future
scqubits/tests/test_spectrumlookup.py

@@ -96,2 +108,3 @@ scqubits/tests/test_transmon.py

scqubits/tests/data/circuit_fluxonium.yaml
scqubits/tests/data/circuit_qutip_evolution_data.hdf5
scqubits/tests/data/circuit_zeropi.yaml

@@ -98,0 +111,0 @@ scqubits/tests/data/cos2phiqubit_1.hdf5

scqubits
scqubits/core
scqubits/explorer
scqubits/io_utils
scqubits/tests
scqubits/ui
scqubits/utils

@@ -11,3 +11,4 @@ # scqubits: superconducting qubits in Python

# LICENSE file in the root directory of this source tree.
"""scqubits is an open-source Python library for simulating superconducting qubits.
"""Scqubits is an open-source Python library for simulating superconducting qubits.
It is meant to give the user a convenient way to obtain energy spectra of common

@@ -18,4 +19,4 @@ superconducting qubits, plot energy levels as a function of external parameters,

superconducting qubits and harmonic modes. Internally, numerics within scqubits is
carried out with the help of Numpy and Scipy; plotting capabilities rely on
Matplotlib."""
carried out with the help of Numpy and Scipy; plotting capabilities rely on Matplotlib.
"""
#######################################################################################

@@ -22,0 +23,0 @@

@@ -0,0 +0,0 @@ # This file is part of scqubits: a Python package for superconducting qubits,

@@ -43,5 +43,3 @@ # central_dispatch.py

class CentralDispatch:
"""
Primary class managing the central dispatch system.
"""
"""Primary class managing the central dispatch system."""

@@ -60,3 +58,3 @@ def __init__(self):

"""For given `event`, return the dict mapping each registered client to their
callback routine
callback routine.

@@ -77,4 +75,3 @@ Parameters

) -> None:
"""
Register object `who` for event `event`. (This modifies `clients_dict`.)
"""Register object `who` for event `event`. (This modifies `clients_dict`.)

@@ -81,0 +78,0 @@ Parameters

@@ -0,5 +1,5 @@

from typing import Tuple, Optional, Union, Dict
import pyparsing as pp
import os
from typing import List, Tuple
from scqubits.utils.misc import is_string_float

@@ -209,7 +209,9 @@ from pyparsing import Group, Opt, Or, Literal, Suppress

Args:
Parameters
----------
code_line (str): string describing the branch from the input file
_branch_count (_type_): the count of the branch in a given circuit
Returns:
Returns
-------
branch_type: str

@@ -231,14 +233,17 @@ node_idx1: int

def convert_value_to_GHz(val, units):
"""
Converts a given value and units to energy in GHz. The units are given in a string in the format "pU"
where p is an optional multiplier prefix and U is units. For example: "pH", "nA", "fF", "eV"
"""Converts a given value and units to energy in GHz. The units are given in a
string in the format "pU" where p is an optional multiplier prefix and U is units.
For example: "pH", "nA", "fF", "eV".
Args:
Parameters
----------
val (float): value in given units
units (str): units described in a string with an optional multiplier prefix
Raises:
Raises
------
ValueError: If the unit is unknown.
Returns:
Returns
-------
float: Energy in GHz

@@ -275,7 +280,11 @@ """

def process_param(pattern):
def process_param(
pattern,
) -> Union[Dict[sm.Symbol, float], Tuple[Optional[sm.Symbol], Optional[float]]]:
"""Returns a tuple containing (symbol, value) given a pattern as detected by
pyparsing.
Either the symbol or the value can be returned to be none, when the symbol is
already assigned or no symbol is assigned to the given branch parameter.
"""
Returns a tuple containing (symbol, value) given a pattern as detected by pyparsing.
Either the symbol or the value can be returned to be none, when the symbol is already assigned or no symbol is assigned to the given branch parameter.
"""
name = pattern.getName()

@@ -285,11 +294,11 @@ if name == "ASSIGN":

val = pattern[1]
units = None if pattern[-1] == None else pattern[-2:]
units = None if pattern[-1] is None else pattern[-2:]
val_converted = convert_value_to_GHz(val, units)
return sym, val_converted
return (sym, val_converted)
if name == "SYMBOL":
return sm.symbols(pattern[0]), None
return (sm.symbols(pattern[0]), None)
if name == "VALUE":
units = None if pattern[-1] == None else pattern[-2:]
units = None if pattern[-1] is None else pattern[-2:]
converted_val = convert_value_to_GHz(pattern[0], units)
return None, converted_val
return (None, converted_val)
if name == "AUX_PARAM":

@@ -296,0 +305,0 @@ num_of_aux_params = int(len(pattern) / 2)

@@ -14,3 +14,3 @@ # circuit-utils.py

import re
from typing import TYPE_CHECKING, Any, Callable, List, Union, Optional, Tuple, Dict
from typing import TYPE_CHECKING, Callable, List, Union, Optional, Tuple, Dict

@@ -20,3 +20,2 @@ import numpy as np

import sympy as sm
import qutip as qt
from numpy import ndarray

@@ -40,5 +39,4 @@ from scipy import sparse

def _junction_order(branch_type: str) -> int:
"""
Returns the order of the branch if it is a JJ branch,
if the order is n its energy is given by cos(phi) + cos(2*phi) + ... + cos(n*phi)
"""Returns the order of the branch if it is a JJ branch, if the order is n its
energy is given by cos(phi) + cos(2*phi) + ... + cos(n*phi)

@@ -51,3 +49,3 @@ Args:

Returns:
Returns
_type_: int, order of the josephson junction

@@ -68,8 +66,9 @@ """

def sawtooth_operator(x: Union[ndarray, csc_matrix]):
"""
Returns the operator evaluated using applying the sawtooth_potential function on the
diagonal elements of the operator x
"""Returns the operator evaluated using applying the sawtooth_potential function on
the diagonal elements of the operator x.
Args:
x (Union[ndarray, csc_matrix]): argument of the sawtooth operator in the Hamiltonian
Parameters
----------
x:
argument of the sawtooth operator in the Hamiltonian
"""

@@ -103,6 +102,4 @@ diagonal_elements = sawtooth_potential(x.diagonal())

def _capactiance_variable_for_branch(branch_type: str):
"""
Returns the parameter name that stores the capacitance of the branch
"""
def _capacitance_variable_for_branch(branch_type: str):
"""Returns the parameter name that stores the capacitance of the branch."""
if "C" in branch_type:

@@ -119,5 +116,4 @@ return "EC"

) -> list:
"""
Function to generate a template for defining the truncated dimensions for subsystems
when hierarchical diagonalization is used.
"""Function to generate a template for defining the truncated dimensions for
subsystems when hierarchical diagonalization is used.

@@ -152,6 +148,4 @@ Parameters

def get_trailing_number(input_str: str) -> Union[int, None]:
"""
Returns the number trailing a string given as input. Example:
$ get_trailing_number("a23")
$ 23
"""Returns the number trailing a string given as input. Example: $
get_trailing_number("a23") $ 23.

@@ -171,27 +165,4 @@ Parameters

def get_operator_number(input_str: str) -> int:
"""
Returns the number inside an operator name. Example:
$ get_operator_number("annihilation9_operator")
$ 9
Parameters
----------
input_str:
operator name (one of the methods ending with `_operator`)
Returns
-------
returns the integer as int, else returns None
"""
match = re.search(r"(\d+)", input_str)
number = int(match.group())
if not number:
raise Exception(f"{input_str} is not a valid operator name.")
return number
def _identity_phi(grid: discretization.Grid1d) -> csc_matrix:
"""
Returns identity operator in the discretized_phi basis.
"""Returns identity operator in the discretized_phi basis.

@@ -212,4 +183,3 @@ Parameters

def _phi_operator(grid: discretization.Grid1d) -> csc_matrix:
"""
Returns phi operator in the discretized_phi basis.
"""Returns phi operator in the discretized_phi basis.

@@ -234,4 +204,3 @@ Parameters

def _i_d_dphi_operator(grid: discretization.Grid1d) -> csc_matrix:
"""
Returns i*d/dphi operator in the discretized_phi basis.
"""Returns i*d/dphi operator in the discretized_phi basis.

@@ -251,4 +220,3 @@ Parameters

def _i_d2_dphi2_operator(grid: discretization.Grid1d) -> csc_matrix:
"""
Returns i*d2/dphi2 operator in the discretized_phi basis.
"""Returns i*d2/dphi2 operator in the discretized_phi basis.

@@ -268,4 +236,3 @@ Parameters

def _cos_phi(grid: discretization.Grid1d) -> csc_matrix:
"""
Returns cos operator in the discretized_phi basis.
"""Returns cos operator in the discretized_phi basis.

@@ -290,4 +257,3 @@ Parameters

def _sin_phi(grid: discretization.Grid1d) -> csc_matrix:
"""
Returns sin operator in the discretized_phi basis.
"""Returns sin operator in the discretized_phi basis.

@@ -312,5 +278,3 @@ Parameters

def _identity_theta(ncut: int) -> csc_matrix:
"""
Returns Operator identity in the charge basis.
"""
"""Returns Operator identity in the charge basis."""
dim_theta = 2 * ncut + 1

@@ -321,5 +285,3 @@ return sparse.identity(dim_theta, format="csc")

def _n_theta_operator(ncut: int) -> csc_matrix:
"""
Returns charge operator `n` in the charge basis.
"""
"""Returns charge operator `n` in the charge basis."""
dim_theta = 2 * ncut + 1

@@ -334,5 +296,3 @@ diag_elements = np.arange(-ncut, ncut + 1)

def _exp_i_theta_operator(ncut, prefactor=1) -> csc_matrix:
r"""
Operator :math:`\cos(\theta)`, acting only on the `\theta` Hilbert subspace.
"""
r"""Operator :math:`\cos(\theta)`, acting only on the `\theta` Hilbert subspace."""
# if type(prefactor) != int:

@@ -349,5 +309,3 @@ # raise ValueError("Prefactor must be an integer")

def _exp_i_theta_operator_conjugate(ncut) -> csc_matrix:
r"""
Operator :math:`\cos(\theta)`, acting only on the `\theta` Hilbert subspace.
"""
r"""Operator :math:`\cos(\theta)`, acting only on the `\theta` Hilbert subspace."""
dim_theta = 2 * ncut + 1

@@ -362,3 +320,3 @@ matrix = sparse.dia_matrix(

def _cos_theta(ncut: int) -> csc_matrix:
"""Returns operator :math:`\\cos \\varphi` in the charge basis"""
"""Returns operator :math:`\\cos \\varphi` in the charge basis."""
cos_op = 0.5 * (_exp_i_theta_operator(ncut) + _exp_i_theta_operator_conjugate(ncut))

@@ -369,3 +327,3 @@ return cos_op

def _sin_theta(ncut: int) -> csc_matrix:
"""Returns operator :math:`\\sin \\varphi` in the charge basis"""
"""Returns operator :math:`\\sin \\varphi` in the charge basis."""
sin_op = (

@@ -382,5 +340,4 @@ -1j

) -> List[sm.Symbol]:
"""
Returns the list of symbols generated using the var_str + iterable as the name
of the symbol.
"""Returns the list of symbols generated using the var_str + iterable as the name of
the symbol.

@@ -398,4 +355,3 @@ Parameters

def is_potential_term(term: sm.Expr) -> bool:
"""
Determines if a given sympy expression term is part of the potential
"""Determines if a given sympy expression term is part of the potential.

@@ -419,5 +375,4 @@ Parameters

def example_circuit(qubit: str) -> str:
"""
Returns example input strings for AnalyzeQCircuit and CustomQCircuit for some of the
popular qubits.
"""Returns example input strings for AnalyzeQCircuit and CustomQCircuit for some of
the popular qubits.

@@ -448,6 +403,9 @@ Parameters

def grid_operator_func_factory(inner_op: Callable, index: int) -> Callable:
def operator_func(self: "Subsystem"):
return self._kron_operator(
def operator_func(
self: "Subsystem", energy_esys: Union[bool, Tuple[ndarray, ndarray]] = False
):
native = self._kron_operator(
inner_op(self.discretized_grids_dict_for_vars()[index]), index
)
return self.process_op(native_op=native, energy_esys=energy_esys)

@@ -458,5 +416,27 @@ return operator_func

def hierarchical_diagonalization_func_factory(symbol_name: str) -> Callable:
def operator_func(self: "Subsystem"):
return Qobj_to_scipy_csc_matrix(self.get_operator_by_name(symbol_name))
def operator_func(
self: "Subsystem", energy_esys: Union[bool, Tuple[ndarray, ndarray]] = False
):
"""Returns the operator <op_name> (corresponds to the name of the method
"<op_name>_operator") for the Circuit/Subsystem instance.
Parameters
----------
energy_esys:
If `False` (default), returns charge operator n in the charge basis.
If `True`, energy eigenspectrum is computed, returns charge operator n in the energy eigenbasis.
If `energy_esys = esys`, where `esys` is a tuple containing two ndarrays (eigenvalues and energy
eigenvectors), returns charge operator n in the energy eigenbasis, and does not have to recalculate the
eigenspectrum.
Returns
-------
Returns the operator <op_name>(corresponds to the name of the method "<op_name>_operator").
For `energy_esys=True`, n has dimensions of :attr:`truncated_dim` x :attr:`truncated_dim`.
If an actual eigensystem is handed to `energy_sys`, then `n` has dimensions of m x m,
where m is the number of given eigenvectors.
"""
native = Qobj_to_scipy_csc_matrix(self.get_operator_by_name(symbol_name))
return self.process_op(native_op=native, energy_esys=energy_esys)
return operator_func

@@ -483,3 +463,24 @@

) -> Callable:
def operator_func(self, op_type=op_type):
def operator_func(
self: "Subsystem", energy_esys: Union[bool, Tuple[ndarray, ndarray]] = False
):
"""Returns the operator <op_name> (corresponds to the name of the method
"<op_name>_operator") for the Circuit/Subsystem instance.
Parameters
----------
energy_esys:
If `False` (default), returns charge operator n in the charge basis.
If `True`, energy eigenspectrum is computed, returns charge operator n in the energy eigenbasis.
If `energy_esys = esys`, where `esys` is a tuple containing two ndarrays (eigenvalues and energy
eigenvectors), returns charge operator n in the energy eigenbasis, and does not have to recalculate the
eigenspectrum.
Returns
-------
Returns the operator <op_name>(corresponds to the name of the method "<op_name>_operator").
For `energy_esys=True`, n has dimensions of :attr:`truncated_dim` x :attr:`truncated_dim`.
If an actual eigensystem is handed to `energy_sys`, then `n` has dimensions of m x m,
where m is the number of given eigenvectors.
"""
prefactor = None

@@ -492,7 +493,8 @@ if self.ext_basis == "harmonic":

if prefactor:
return self._kron_operator(
native = self._kron_operator(
inner_op(self.cutoffs_dict()[index], prefactor=prefactor), index
)
else:
return self._kron_operator(inner_op(self.cutoffs_dict()[index]), index)
native = self._kron_operator(inner_op(self.cutoffs_dict()[index]), index)
return self.process_op(native_op=native, energy_esys=energy_esys)

@@ -502,16 +504,5 @@ return operator_func

def compose(f: Callable, g: Callable) -> Callable:
"""Returns the function f o g: x |-> f(g(x))"""
def g_after_f(x: Any) -> Any:
return f(g(x))
return g_after_f
def _cos_dia(x: csc_matrix) -> csc_matrix:
"""
Take the diagonal of the array x, compute its cosine, and fill the result into
the diagonal of a sparse matrix
"""
"""Take the diagonal of the array x, compute its cosine, and fill the result into
the diagonal of a sparse matrix."""
return sparse.diags(np.cos(x.diagonal())).tocsc()

@@ -521,6 +512,4 @@

def _sin_dia(x: csc_matrix) -> csc_matrix:
"""
Take the diagonal of the array x, compute its sine, and fill the result into
the diagonal of a sparse matrix.
"""
"""Take the diagonal of the array x, compute its sine, and fill the result into the
diagonal of a sparse matrix."""
return sparse.diags(np.sin(x.diagonal())).tocsc()

@@ -530,5 +519,3 @@

def _sin_dia_dense(x: ndarray) -> ndarray:
"""
This is a special function to calculate the sin of dense diagonal matrices
"""
"""This is a special function to calculate the sin of dense diagonal matrices."""
return np.diag(np.sin(x.diagonal()))

@@ -538,5 +525,3 @@

def _cos_dia_dense(x: ndarray) -> ndarray:
"""
This is a special function to calculate the cos of dense diagonal matrices
"""
"""This is a special function to calculate the cos of dense diagonal matrices."""
return np.diag(np.cos(x.diagonal()))

@@ -858,6 +843,5 @@

) -> ndarray:
"""
Assemble a transformation matrix for a large circuit that are made of smaller sub-circuits
and coupling elements. This method takes a list of sub-circuit transformation matrices as
the argument.
"""Assemble a transformation matrix for a large circuit that are made of smaller
sub-circuits and coupling elements. This method takes a list of sub-circuit
transformation matrices as the argument.

@@ -864,0 +848,0 @@ Parameters

@@ -15,9 +15,6 @@ # circuit.py

import warnings
from typing import Any, Dict, List, Optional, Tuple, Union, Callable
from typing import Any, Dict, List, Optional, Tuple, Union, Callable, get_type_hints
import numpy as np
import sympy as sm
from matplotlib import pyplot as plt
from matplotlib.axes import Axes
from matplotlib.figure import Figure
from numpy import ndarray

@@ -27,3 +24,3 @@ from sympy import latex

try:
from IPython.display import Latex, display
import IPython
except ImportError:

@@ -39,6 +36,4 @@ _HAS_IPYTHON = False

from scqubits import settings
from scqubits.core.circuit_utils import (
get_trailing_number,
is_potential_term,
)

@@ -54,7 +49,13 @@ from scqubits.core.symbolic_circuit import Branch, SymbolicCircuit

from scqubits.core.circuit_routines import CircuitRoutines
from scqubits.core.circuit_plotting import CircuitPlot
from scqubits.core.circuit_sym_methods import CircuitSymMethods
from scqubits.core.circuit_noise import NoisyCircuit
class CircuitABC(CircuitRoutines, CircuitSymMethods, CircuitPlot):
pass
class Subsystem(
CircuitRoutines,
CircuitABC,
base.QubitBaseClass,

@@ -65,5 +66,4 @@ serializers.Serializable,

):
"""
Defines a subsystem for a circuit, which can further be used recursively to define
subsystems within subsystem.
"""Defines a subsystem for a circuit, which can further be used recursively to
define subsystems within subsystem.

@@ -87,2 +87,35 @@ Parameters

sets the truncated dimension for the current subsystem, set to 10 by default.
Attributes
----------
hierarchical_diagonalization: bool
set to True when the circuit is defined hierarchically, by default `False`
hamiltonian_symbolic: Sympy.Expr
the symbolic Hamiltonian for the circuit
external_fluxes: List[Sympy.Symbol]
list of external flux variables
offset_charges: List[Sympy.Symbol]
list of offset charge variables
free_charges: List[Sympy.Symbol]
list of free charge variables
var_categories: Dict[str, List[int]]
dictionary with keys "periodic", "extended", "free", "frozen" and values as
the indices of the respective variable types
cutoff_names: List[str]
list of cutoff names for the variables
discretized_phi_range: Dict[int, Tuple[float, float]]
dictionary with keys as the indices of the extended variables and values as
the range of discretized phi variables
type_of_matrices: str
type of matrices used to construct the operators "dense" or "sparse", by default "sparse"
system_hierarchy: list
list of lists containing variable indices which is provided by the user to define subsystems
subsystem_trunc_dims: list
list of truncated dimensions for the subsystems inside the current subsystem
hilbert_space: HilbertSpace
HilbertSpace instance for the circuit, when hierarchical diagonalization is used
truncated_dim: int
truncated dimension for the current instance
is_purely_harmonic: bool
internally set to True when the instance is purely harmonic
"""

@@ -95,5 +128,5 @@

ext_basis: Union[str, List],
system_hierarchy: Optional[List] = None,
subsystem_trunc_dims: Optional[List] = None,
truncated_dim: Optional[int] = 10,
system_hierarchy: list = [],
subsystem_trunc_dims: list = [],
truncated_dim: int = 10,
evals_method: Union[Callable, str, None] = None,

@@ -117,6 +150,6 @@ evals_method_options: Union[dict, None] = None,

self.system_hierarchy = system_hierarchy
self.truncated_dim = truncated_dim
self.truncated_dim: int = truncated_dim
self.subsystem_trunc_dims = subsystem_trunc_dims
self.is_child = True
self.is_child: bool = True
self.parent = parent

@@ -128,12 +161,3 @@ self.hamiltonian_symbolic = hamiltonian_symbolic

self._H_LC_str_harmonic = None
# attribute to keep track if the symbolic Hamiltonian needs to be updated
self._make_property(
"_user_changed_parameter",
False,
"update_user_changed_parameter",
use_central_dispatch=False,
)
self.ext_basis = ext_basis
self._find_and_set_sym_attrs()

@@ -143,6 +167,2 @@ self.dynamic_var_indices: List[int] = flatten_list_recursive(

)
parent_cutoffs_dict = self.parent.cutoffs_dict()
cutoffs: List[int] = [
parent_cutoffs_dict[var_index] for var_index in self.dynamic_var_indices
]

@@ -174,3 +194,3 @@ self.var_categories: Dict[str, List[int]] = {}

self.potential_symbolic = self.generate_sym_potential()
self.potential_symbolic = self._generate_sym_potential()

@@ -196,7 +216,7 @@ self.hierarchical_diagonalization: bool = (

def _find_and_set_sym_attrs(self):
"""Finds the symbolic and other circuit params from the symbolic Hamiltonian,
and sets the attribs external_fluxes, offset_charges and symbolic_params.
Only works when _frozen is set to False, or the above attribs are already set.
"""
Finds the symbolic and other circuit params from the symbolic Hamiltonian, and sets the attribs
external_fluxes, offset_charges and symbolic_params. Only works when _frozen is set to False, or
the above attribs are already set.
"""
self.external_fluxes = [

@@ -222,8 +242,2 @@ var

}
def _configure(self) -> None:
"""
Function which is used to initiate the subsystem instance.
"""
self._frozen = False
for idx, param in enumerate(self.symbolic_params):

@@ -247,3 +261,2 @@ self._make_property(

)
for cutoff_str in self.cutoff_names:

@@ -253,2 +266,9 @@ self._make_property(

)
def _configure(self) -> None:
"""Function which is used to initiate the subsystem instance."""
self._frozen = False
self._find_and_set_sym_attrs()
# if subsystem hamiltonian is purely harmonic

@@ -264,23 +284,12 @@ if (

# Creating the attributes for purely harmonic circuits
if (
isinstance(self, Circuit) and self.parent.is_purely_harmonic
): # assuming that the parent has only extended variables and are ordered
# starting from 1, 2, 3, ...
self.is_purely_harmonic = self.parent.is_purely_harmonic
self.normal_mode_freqs = self.parent.normal_mode_freqs[
[var_idx - 1 for var_idx in self.var_categories["extended"]]
]
if self.hierarchical_diagonalization:
# attribute to note updated subsystem indices
self.affected_subsystem_indices = []
self._hamiltonian_sym_for_numerics = self.hamiltonian_symbolic.copy()
self.generate_subsystems()
self._generate_subsystems()
self.ext_basis = self.get_ext_basis()
self.update_interactions()
self._update_interactions()
self._check_truncation_indices()
self.affected_subsystem_indices = list(range(len(self.subsystems)))
else:
self.generate_hamiltonian_sym_for_numerics()
self._generate_hamiltonian_sym_for_numerics()
if self.is_purely_harmonic and self.ext_basis == "harmonic":

@@ -290,4 +299,5 @@ self._diagonalize_purely_harmonic_hamiltonian()

self._set_vars()
self.operators_by_name = self.set_operators()
self.operators_by_name = self._set_operators()
self._out_of_sync_with_parent = False
if self.hierarchical_diagonalization:

@@ -298,5 +308,12 @@ self._out_of_sync = False # for use with CentralDispatch

def _is_diagonalization_necessary(self) -> bool:
"""Checks if the subsystem needs to be diagonalized."""
parent_subsys_idx = self.parent.subsystems.index(self)
if parent_subsys_idx in self.parent.affected_subsystem_indices:
return True
return False
class Circuit(
CircuitRoutines,
CircuitABC,
base.QubitBaseClass,

@@ -307,31 +324,65 @@ serializers.Serializable,

):
"""
Class for analysis of custom superconducting circuits.
"""Class for analysis of custom superconducting circuits.
Parameters
----------
input_string: str
input_string:
String describing the number of nodes and branches connecting then along
with their parameters
from_file: bool
from_file:
Set to True by default, when a file name should be provided to
`input_string`, else the circuit graph description in YAML should be
provided as a string.
basis_completion: str
basis_completion:
either "heuristic" or "canonical", defines the matrix used for completing the
transformation matrix. Sometimes used to change the variable transformation
to result in a simpler symbolic Hamiltonian, by default "heuristic"
ext_basis: str
ext_basis:
can be "discretized" or "harmonic" which chooses whether to use discretized
phi or harmonic oscillator basis for extended variables,
by default "discretized"
use_dynamic_flux_grouping: bool
use_dynamic_flux_grouping:
set to False by default. Indicates if the flux allocation is done by assuming
that flux is time dependent. When set to True, it disables the option to change
the closure branches.
initiate_sym_calc: bool
attribute to initiate Circuit instance, by default `True`
truncated_dim: Optional[int]
initiate_sym_calc:
parameter to initiate Circuit instance, by default `True`
truncated_dim:
truncated dimension if the user wants to use this circuit instance in
HilbertSpace, by default `None`
Attributes
----------
hierarchical_diagonalization: bool
set to True when the circuit is defined hierarchically, by default `False`
hamiltonian_symbolic: sm.Expr
the symbolic Hamiltonian for the circuit
external_fluxes: List[sm.Symbol]
list of external flux variables
offset_charges: List[sm.Symbol]
list of offset charge variables
free_charges: List[sm.Symbol]
list of free charge variables
var_categories: Dict[str, List[int]]
dictionary with keys "periodic", "extended", "free", "frozen" and values as
the indices of the respective variable types
cutoff_names: List[str]
list of cutoff names for the variables
discretized_phi_range: Dict[int, Tuple[float, float]]
dictionary with keys as the indices of the extended variables and values as
the range of discretized phi variables
type_of_matrices: str
type of matrices used to construct the operators "dense" or "sparse", by default "sparse"
truncated_dim: int
truncated dimension for the current instance
is_purely_harmonic: bool
internally set to True when the instance is purely harmonic
dynamic_var_indices: List[int]
list of dynamic variable indices, showing the degrees of freedom of the circuit.
hilbert_space: HilbertSpace
HilbertSpace instance for the instance, when hierarchical diagonalization is used
system_hierarchy: list
list of lists containing variable indices which is provided by the user to define subsystems
subsystem_trunc_dims: list
list of truncated dimensions for the subsystems inside the current subsystem
"""

@@ -343,3 +394,3 @@

from_file: bool = True,
basis_completion="heuristic",
basis_completion: str = "heuristic",
ext_basis: str = "discretized",

@@ -350,4 +401,4 @@ use_dynamic_flux_grouping: bool = False,

truncated_dim: int = 10,
symbolic_param_dict: Dict[str, float] = None,
symbolic_hamiltonian: sm.Expr = None,
symbolic_param_dict: Dict[str, float] = {},
symbolic_hamiltonian: Optional[sm.Expr] = None,
evals_method: Union[Callable, str, None] = None,

@@ -370,4 +421,8 @@ evals_method_options: Union[dict, None] = None,

raise Exception(
"Circuit instance cannot be initialized with both input_string and symbolic_hamiltonian."
"Circuit instance cannot be initialized with both input_string and a symbolic hamiltonian."
)
if not symbolic_hamiltonian and not input_string:
raise Exception(
"Circuit instance must be initialized with either input_string or a symbolic hamiltonian."
)
if input_string:

@@ -385,3 +440,3 @@ self.from_yaml(

else:
elif symbolic_hamiltonian:
if use_dynamic_flux_grouping or generate_noise_methods:

@@ -391,3 +446,3 @@ raise Exception(

)
self.from_symbolic_hamiltonian(
self._from_symbolic_hamiltonian(
symbolic_hamiltonian=symbolic_hamiltonian,

@@ -400,3 +455,3 @@ symbolic_param_dict=symbolic_param_dict,

def from_symbolic_hamiltonian(
def _from_symbolic_hamiltonian(
self,

@@ -422,4 +477,4 @@ symbolic_hamiltonian: sm.Expr,

self.truncated_dim: int = truncated_dim
self.system_hierarchy: list = None
self.subsystem_trunc_dims: list = None
self.system_hierarchy: list = []
self.subsystem_trunc_dims: list = []
self.operators_by_name = None

@@ -442,9 +497,2 @@

self._out_of_sync = False # for use with CentralDispatch
self._make_property(
"_user_changed_parameter",
False,
"update_user_changed_parameter",
use_central_dispatch=False,
)
if initiate_sym_calc:

@@ -464,7 +512,6 @@ self.configure()

initiate_sym_calc: bool = True,
truncated_dim: int = None,
truncated_dim: int = 10,
):
"""
Wrapper to Circuit __init__ to create a class instance. This is deprecated and
will not be supported in future releases.
"""Wrapper to Circuit __init__ to create a class instance. This is deprecated
and will not be supported in future releases.

@@ -518,4 +565,4 @@ Parameters

self.truncated_dim: int = truncated_dim
self.system_hierarchy: list = None
self.subsystem_trunc_dims: list = None
self.system_hierarchy: list = []
self.subsystem_trunc_dims: list = []
self.operators_by_name = None

@@ -560,8 +607,2 @@

self._out_of_sync = False # for use with CentralDispatch
self._make_property(
"_user_changed_parameter",
False,
"update_user_changed_parameter",
use_central_dispatch=False,
)

@@ -598,5 +639,3 @@ if initiate_sym_calc:

def _clear_unnecessary_attribs(self):
"""
Clear all the attributes which are not part of the circuit description
"""
"""Clear all the attributes which are not part of the circuit description."""
necessary_attrib_names = (

@@ -628,3 +667,3 @@ self.cutoff_names

closure_branches: Optional[List[Union[Branch, Dict[Branch, float]]]] = None,
ext_basis: Optional[str] = None,
ext_basis: Optional[Union[str, List[str]]] = None,
use_dynamic_flux_grouping: Optional[bool] = None,

@@ -634,4 +673,3 @@ generate_noise_methods: bool = False,

):
"""
Method which re-initializes a circuit instance to update, hierarchical
"""Method which re-initializes a circuit instance to update, hierarchical
diagonalization parameters or closure branches or the variable transformation

@@ -773,6 +811,5 @@ used to describe the circuit.

subsys_dict: Optional[Dict[str, Any]] = None,
ext_basis: Optional[str] = None,
ext_basis: Optional[Union[str, List[str]]] = None,
):
"""
Method which re-initializes a circuit instance to update, hierarchical
"""Method which re-initializes a circuit instance to update, hierarchical
diagonalization parameters or closure branches or the variable transformation

@@ -866,3 +903,3 @@ used to describe the circuit.

self.potential_symbolic = self.generate_sym_potential()
self.potential_symbolic = self._generate_sym_potential()

@@ -886,3 +923,3 @@ # changing the matrix type if necessary

self.ext_basis = ext_basis or self.ext_basis
self.generate_hamiltonian_sym_for_numerics()
self._generate_hamiltonian_sym_for_numerics()
if self.is_purely_harmonic and self.ext_basis == "harmonic":

@@ -895,6 +932,5 @@ # using the default methods

self._set_vars() # setting the attribute vars to store operator symbols
self.operators_by_name = self.set_operators()
self.operators_by_name = self._set_operators()
else:
# list for updating necessary subsystems when calling build hilbertspace
self.affected_subsystem_indices = []
self.operators_by_name = None

@@ -911,4 +947,4 @@ self.system_hierarchy = system_hierarchy

self.ext_basis = ext_basis
self.generate_hamiltonian_sym_for_numerics()
self.generate_subsystems(subsys_dict=subsys_dict)
self._generate_hamiltonian_sym_for_numerics()
self._generate_subsystems(subsys_dict=subsys_dict)
self.ext_basis = (

@@ -919,5 +955,5 @@ self.get_ext_basis()

self._check_truncation_indices()
self.operators_by_name = self.set_operators()
self.operators_by_name = self._set_operators()
self.affected_subsystem_indices = list(range(len(self.subsystems)))
self.update_interactions()
self._update_interactions()

@@ -935,3 +971,3 @@ # clear unnecessary attribs

closure_branches: Optional[List[Union[Branch, Dict[Branch, float]]]] = None,
ext_basis: Optional[str] = None,
ext_basis: Optional[Union[str, List[str]]] = None,
use_dynamic_flux_grouping: Optional[bool] = None,

@@ -941,4 +977,3 @@ subsys_dict: Optional[Dict[str, Any]] = None,

):
"""
Method which re-initializes a circuit instance to update, hierarchical
"""Method which re-initializes a circuit instance to update, hierarchical
diagonalization parameters or closure branches or the variable transformation

@@ -967,3 +1002,3 @@ used to describe the circuit.

subsys_dict:
User provided dictionary with two keys "systems_sym" and "interaction_sym" defining the symbolic Hamiltonians and interactions for the subsystems. By default set to None, and is internally generated.
User provided dictionary with two keys `"systems_sym"` and `"interaction_sym"` defining the symbolic Hamiltonians and interactions for the subsystems. By default set to None, and is internally generated.
generate_noise_methods:

@@ -1104,3 +1139,3 @@ set to False by default. Indicates if the noise methods should be generated for the circuit instance.

self.ext_basis = "harmonic"
self.generate_hamiltonian_sym_for_numerics()
self._generate_hamiltonian_sym_for_numerics()
self.ext_basis = ext_basis or self.ext_basis

@@ -1115,3 +1150,2 @@ if self.is_purely_harmonic and self.ext_basis == "harmonic":

# list for updating necessary subsystems when calling build hilbertspace
self.affected_subsystem_indices = []
self.operators_by_name = None

@@ -1126,7 +1160,7 @@ self.system_hierarchy = system_hierarchy

self.subsystem_trunc_dims = subsystem_trunc_dims
self.generate_hamiltonian_sym_for_numerics()
self._generate_hamiltonian_sym_for_numerics()
self.ext_basis = ext_basis or self.ext_basis
self.generate_subsystems(subsys_dict=subsys_dict)
self._generate_subsystems(subsys_dict=subsys_dict)
self.ext_basis = self.get_ext_basis()
self.update_interactions()
self._update_interactions()
self._check_truncation_indices()

@@ -1136,3 +1170,3 @@ self.affected_subsystem_indices = list(range(len(self.subsystems)))

self._set_vars() # setting the attribute vars to store operator symbols
self.operators_by_name = self.set_operators()
self.operators_by_name = self._set_operators()
# clear unnecessary attribs

@@ -1146,3 +1180,3 @@ self._clear_unnecessary_attribs()

def supported_noise_channels(self) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
if not hasattr(self, "_noise_methods_generated"):

@@ -1170,5 +1204,3 @@ raise Exception(

def variable_transformation(self, new_vars_to_node_vars=True) -> None:
"""
Prints the variable transformation used in this circuit
"""
"""Prints the variable transformation used in this circuit."""
trans_mat = self.transformation_matrix

@@ -1210,4 +1242,4 @@ if new_vars_to_node_vars:

) -> Union[sm.Expr, None]:
"""
Method that gives a user readable symbolic Lagrangian for the current instance
"""Method that gives a user readable symbolic Lagrangian for the current
instance.

@@ -1293,5 +1325,4 @@ Parameters

def sym_external_fluxes(self) -> Dict[sm.Expr, Tuple["Branch", List["Branch"]]]:
"""
Method returns a dictionary of Human readable external fluxes with associated
branches and loops (represented as lists of branches) for the current instance
"""Method returns a dictionary of Human readable external fluxes with associated
branches and loops (represented as lists of branches) for the current instance.

@@ -1321,8 +1352,7 @@ Returns

def oscillator_list(self, osc_index_list: List[int]):
"""
If hierarchical diagonalization is used, specify subsystems that corresponds to
single-mode oscillators, if there is any. The attributes `_osc_subsys_list` and
`osc_subsys_list` of the `hilbert_space` attribute of the Circuit instance will
be assigned accordingly, enabling the correct identification of harmonic modes
for the dispersive regime analysis in ParameterSweep.
"""If hierarchical diagonalization is used, specify subsystems that corresponds
to single-mode oscillators, if there is any. The attributes `_osc_subsys_list`
and `osc_subsys_list` of the :attr:`hilbert_space` attribute of the Circuit instance
will be assigned accordingly, enabling the correct identification of harmonic
modes for the dispersive regime analysis in ParameterSweep.

@@ -1344,3 +1374,3 @@ Parameters

raise Exception(
f"the subsystem has more than one harmonic oscillator mode"
"the subsystem has more than one harmonic oscillator mode"
)

@@ -1352,8 +1382,7 @@ else:

def qubit_list(self, qbt_index_list: List[int]):
"""
If hierarchical diagonalization is used, specify subsystems that corresponds to
single-mode oscillators, if there is any. The attributes `_osc_subsys_list` and
`osc_subsys_list` of the `hilbert_space` attribute of the Circuit instance will
be assigned accordingly, enabling the correct identification of harmonic modes
for the dispersive regime analysis in ParameterSweep.
"""If hierarchical diagonalization is used, specify subsystems that corresponds
to single-mode oscillators, if there is any. The attributes `_osc_subsys_list`
and `osc_subsys_list` of the `hilbert_space` attribute of the Circuit instance
will be assigned accordingly, enabling the correct identification of harmonic
modes for the dispersive regime analysis in ParameterSweep.

@@ -1360,0 +1389,0 @@ Parameters

@@ -0,0 +0,0 @@ # constants.py

@@ -23,5 +23,3 @@ # descriptors.py

class ReadOnlyProperty(Generic[TargetType]):
"""
Descriptor for read-only properties (stored in xxx._name)
"""
"""Descriptor for read-only properties (stored in xxx._name)"""

@@ -44,6 +42,5 @@ def __init__(self, target_type: Type[TargetType]):

class WatchedProperty(Generic[TargetType]):
"""
Descriptor class for properties that are to be monitored for changes. Upon change
"""Descriptor class for properties that are to be monitored for changes. Upon change
of the value, the instance class invokes its `broadcast()` method to send the
appropriate event notification to CentralDispatch
appropriate event notification to CentralDispatch.

@@ -50,0 +47,0 @@ Parameters

@@ -34,8 +34,6 @@ # diag.py

) -> Dict[str, Any]:
"""
Selective dictionary merge. This function makes a copy of the given
dictionary `d` and selectively updates/adds entries from `d_other`,
as long as the keys are not given in `exclude`.
Whether entries in `d` are overwritten by entries in `d_other` is
determined by the value of the `overwrite` parameter
"""Selective dictionary merge. This function makes a copy of the given dictionary
`d` and selectively updates/adds entries from `d_other`, as long as the keys are not
given in `exclude`. Whether entries in `d` are overwritten by entries in `d_other`
is determined by the value of the `overwrite` parameter.

@@ -56,3 +54,2 @@ Parameters

merged dictionary
"""

@@ -72,6 +69,5 @@ exclude = [] if exclude is None else exclude

) -> Union[ndarray, csc_matrix, Qobj]:
"""
Casts a given operator (possibly given as a `Qobj`) into a
required form ('sparse' or 'dense' numpy array or scipy martrix) as
defined by `cast_to` parameter.
"""Casts a given operator (possibly given as a `Qobj`) into a required form
('sparse' or 'dense' numpy array or scipy martrix) as defined by `cast_to`
parameter.

@@ -103,3 +99,2 @@ Operators of the type `Qobj` are first converted to a `ndarray` or a

matrix in the sparse or dense form
"""

@@ -138,7 +133,6 @@ if cast_to not in ["sparse", "dense"]:

def _convert_evecs_to_qobjs(evecs: ndarray, matrix_qobj, wrap: bool = False) -> ndarray:
"""
Converts an `ndarray` containing eigenvectors (that would be typically
returned from a diagonalization routine, such as `eighs` or `eigh`),
to a numpy array of qutip's Qobjs.
Potentially also wraps those into `scqubits.io_utils.fileio_qutip.QutipEigenstates`.
"""Converts an `ndarray` containing eigenvectors (that would be typically returned
from a diagonalization routine, such as `eighs` or `eigh`), to a numpy array of
qutip's Qobjs. Potentially also wraps those into
`scqubits.io_utils.fileio_qutip.QutipEigenstates`.

@@ -157,3 +151,2 @@ Parameters

eigenvectors represented in terms of Qobjs
"""

@@ -181,5 +174,4 @@ evecs_count = evecs.shape[1]

) -> ndarray:
"""
Diagonalization based on scipy's (dense) `eigh` function.
Only evals are returned.
"""Diagonalization based on scipy's (dense) `eigh` function. Only evals are
returned.

@@ -198,3 +190,2 @@ Parameters

eigenvalues of matrix
"""

@@ -212,5 +203,4 @@ m = _cast_matrix(matrix, "dense")

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on scipy's (dense) eigh function.
Both evals and evecs are returned.
"""Diagonalization based on scipy's (dense) eigh function. Both evals and evecs are
returned.

@@ -229,3 +219,2 @@ Parameters

a tuple of eigenvalues and eigenvectors. Eigenvectors are Qobjs if matrix is a Qobj instance
"""

@@ -246,5 +235,4 @@ m = _cast_matrix(matrix, "dense")

) -> ndarray:
"""
Diagonalization based on scipy's (sparse) `eigsh` function.
Only evals are returned.
"""Diagonalization based on scipy's (sparse) `eigsh` function. Only evals are
returned.

@@ -288,5 +276,4 @@ Note the convoluted convention when it comes to ordering and how it is related

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on scipy's (sparse) `eigsh` function.
Both evals and evecs are returned.
"""Diagonalization based on scipy's (sparse) `eigsh` function. Both evals and evecs
are returned.

@@ -319,3 +306,2 @@ Note the convoluted convention when it comes to ordering and how it is related

a tuple of eigenvalues and eigenvectors. Eigenvectors are Qobjs if matrix is a Qobj instance
"""

@@ -351,5 +337,4 @@ m = _cast_matrix(matrix, "sparse")

) -> ndarray:
"""
Diagonalization based on primme's (sparse) `eigsh` function.
Only evals are returned.
"""Diagonalization based on primme's (sparse) `eigsh` function. Only evals are
returned.

@@ -370,3 +355,2 @@ Requires that the primme library is installed.

eigenvalues of matrix
"""

@@ -397,5 +381,4 @@ try:

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on primme's (sparse) `eigsh` function.
Both evals and evecs are returned.
"""Diagonalization based on primme's (sparse) `eigsh` function. Both evals and evecs
are returned.

@@ -448,5 +431,4 @@ Requires that the primme library is installed.

) -> ndarray:
"""
Diagonalization based on cupy's (dense) `eighvalsh` function
Only evals are returned.
"""Diagonalization based on cupy's (dense) `eighvalsh` function Only evals are
returned.

@@ -467,3 +449,2 @@ Requires that the cupy library is installed.

eigenvalues of matrix
"""

@@ -486,5 +467,4 @@ try:

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on cupy's (dense) `eigh` function.
Both evals and evecs are returned.
"""Diagonalization based on cupy's (dense) `eigh` function. Both evals and evecs are
returned.

@@ -505,3 +485,2 @@ Requires that the cupy library is installed.

a tuple of eigenvalues and eigenvectors. Eigenvectors are Qobjs if matrix is a Qobj instance
"""

@@ -530,5 +509,4 @@ try:

) -> ndarray:
"""
Diagonalization based on cupy's (sparse) `eigsh` function.
Only evals are returned.
"""Diagonalization based on cupy's (sparse) `eigsh` function. Only evals are
returned.

@@ -576,5 +554,4 @@ Requires that the cupy (and cupyx) library is installed.

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on cupy's (sparse) eigsh function.
Both evals and evecs are returned.
"""Diagonalization based on cupy's (sparse) eigsh function. Both evals and evecs are
returned.

@@ -630,5 +607,4 @@ Requires that the cupy library is installed.

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on jax's (dense) jax.scipy.linalg.eigh function.
Only eigenvalues are returned.
"""Diagonalization based on jax's (dense) jax.scipy.linalg.eigh function. Only
eigenvalues are returned.

@@ -654,3 +630,2 @@ If available, different backends/devics (e.g., particular GPUs) can be set

eigenvalues of matrix
"""

@@ -679,5 +654,4 @@ try:

) -> Union[Tuple[ndarray, ndarray], Tuple[ndarray, QutipEigenstates]]:
"""
Diagonalization based on jax's (dense) jax.scipy.linalg.eigh function.
Both evals and evecs are returned.
"""Diagonalization based on jax's (dense) jax.scipy.linalg.eigh function. Both evals
and evecs are returned.

@@ -703,3 +677,2 @@ If available, different backends/devics (e.g., particular GPUs) can be set

a tuple of eigenvalues and eigenvectors. Eigenvectors are Qobjs if matrix is a Qobj instance
"""

@@ -746,3 +719,3 @@ try:

evals_count,
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True),
),

@@ -752,3 +725,3 @@ "esys_scipy_sparse_LA_shift-inverse": lambda matrix, evals_count, **kwargs: esys_scipy_sparse(

evals_count,
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True),
),

@@ -758,3 +731,3 @@ "evals_scipy_sparse_LM_shift-inverse": lambda matrix, evals_count, **kwargs: evals_scipy_sparse(

evals_count,
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True),
),

@@ -764,3 +737,3 @@ "esys_scipy_sparse_LM_shift-inverse": lambda matrix, evals_count, **kwargs: esys_scipy_sparse(

evals_count,
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True),
),

@@ -773,3 +746,3 @@ # primme sparse

evals_count=evals_count,
**_dict_merge(dict(which="SM"), kwargs, overwrite=True)
**_dict_merge(dict(which="SM"), kwargs, overwrite=True),
),

@@ -782,3 +755,3 @@ "esys_primme_sparse_SM": lambda matrix, evals_count, **kwargs: esys_primme_sparse(

evals_count=evals_count,
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True),
),

@@ -788,3 +761,3 @@ "esys_primme_sparse_LA_shift-inverse": lambda matrix, evals_count, **kwargs: esys_primme_sparse(

evals_count=evals_count,
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LA", sigma=0), kwargs, overwrite=True),
),

@@ -794,3 +767,3 @@ "evals_primme_sparse_LM_shift-inverse": lambda matrix, evals_count, **kwargs: evals_primme_sparse(

evals_count=evals_count,
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True),
),

@@ -800,3 +773,3 @@ "esys_primme_sparse_LM_shift-inverse": lambda matrix, evals_count, **kwargs: esys_primme_sparse(

evals_count=evals_count,
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True)
**_dict_merge(dict(which="LM", sigma=0), kwargs, overwrite=True),
),

@@ -803,0 +776,0 @@ # cupy dense

@@ -50,9 +50,7 @@ # discretization.py

) -> csc_matrix:
"""
Returns a dim x dim sparse matrix with constant diagonals of values `band_coeffs[
"""Returns a dim x dim sparse matrix with constant diagonals of values `band_coeffs[
0]`, `band_coeffs[1]`, ... along the (off-)diagonals specified by the offsets
`band_offsets[0]`, `band_offsets[1]`, ... The `has_corners` option allows
generation of band matrices with corner elements, in which lower off-diagonals
wrap into the top right corner and upper off-diagonals wrap into the bottom left
corner.
`band_offsets[0]`, `band_offsets[1]`, ... The `has_corners` option allows generation
of band matrices with corner elements, in which lower off-diagonals wrap into the
top right corner and upper off-diagonals wrap into the bottom left corner.

@@ -141,2 +139,3 @@ Parameters

"""Returns dict appropriate for creating/initializing a new Grid1d object.
Returns

@@ -157,3 +156,4 @@ -------

def make_linspace(self) -> ndarray:
"""Returns a numpy array of the grid points
"""Returns a numpy array of the grid points.
Returns

@@ -168,4 +168,4 @@ -------

) -> csc_matrix:
"""Generate sparse matrix for first derivative of the form
:math:`\\partial_{x_i}`. Uses STENCIL setting to construct the matrix with a
r"""Generate sparse matrix for first derivative of the form
:math:`\partial_{x_i}`. Uses STENCIL setting to construct the matrix with a
multi-point stencil.

@@ -203,4 +203,4 @@

) -> csc_matrix:
"""Generate sparse matrix for second derivative of the form
:math:`\\partial^2_{x_i}`. Uses STENCIL setting to construct the matrix with
r"""Generate sparse matrix for second derivative of the form
:math:`\partial^2_{x_i}`. Uses STENCIL setting to construct the matrix with
a multi-point stencil.

@@ -237,4 +237,4 @@

class GridSpec(dispatch.DispatchClient, serializers.Serializable):
"""Class for specifying a general discretized coordinate grid
(arbitrary dimensions).
"""Class for specifying a general discretized coordinate grid (arbitrary
dimensions).

@@ -241,0 +241,0 @@ Parameters

@@ -67,6 +67,5 @@ # flux_qubit.py

get_rate: bool = False,
**kwargs
**kwargs,
) -> float:
r"""
Calculate the 1/f dephasing time (or rate) due to critical current noise of
r"""Calculate the 1/f dephasing time (or rate) due to critical current noise of
junction associated with Josephson energy :math:`EJ1`.

@@ -89,5 +88,4 @@

-------
decoherence time in units of :math:`2\pi ({\rm system\,\,units})`,
decoherence time in units of :math:`2\pi` (system units),
or rate in inverse units.
"""

@@ -107,3 +105,3 @@ if "tphi_1_over_f_cc1" not in self.supported_noise_channels():

get_rate=get_rate,
**kwargs
**kwargs,
)

@@ -118,6 +116,5 @@

get_rate: bool = False,
**kwargs
**kwargs,
) -> float:
r"""
Calculate the 1/f dephasing time (or rate) due to critical current noise of
r"""Calculate the 1/f dephasing time (or rate) due to critical current noise of
junction associated with Josephson energy :math:`EJ2`.

@@ -141,3 +138,3 @@

:math:`T_{\phi}` time or rate:
decoherence time in units of :math:`2\pi ({\rm system\,\,units})`, or rate in inverse units.
decoherence time in units of :math:`2\pi` (system units), or rate in inverse units.
"""

@@ -157,3 +154,3 @@ if "tphi_1_over_f_cc2" not in self.supported_noise_channels():

get_rate=get_rate,
**kwargs
**kwargs,
)

@@ -168,7 +165,6 @@

get_rate: bool = False,
**kwargs
**kwargs,
) -> float:
r"""
Calculate the 1/f dephasing time (or rate) due to critical current noise of junction associated with
Josephson energy :math:`EJ3`.
r"""Calculate the 1/f dephasing time (or rate) due to critical current noise of
junction associated with Josephson energy :math:`EJ3`.

@@ -190,3 +186,3 @@ Parameters

-------
decoherence time in units of :math:`2\pi ({\rm system\,\,units})`, or rate in inverse units.
decoherence time in units of :math:`2\pi` (system units), or rate in inverse units.
"""

@@ -206,3 +202,3 @@ if "tphi_1_over_f_cc3" not in self.supported_noise_channels():

get_rate=get_rate,
**kwargs
**kwargs,
)

@@ -217,8 +213,7 @@

get_rate: bool = False,
**kwargs
**kwargs,
) -> float:
r"""
Calculate the 1/f dephasing time (or rate) due to critical-current noise
from all three Josephson junctions :math:`EJ1`, :math:`EJ2` and :math:`EJ3`.
The combined noise is calculated by summing the rates from the individual
r"""Calculate the 1/f dephasing time (or rate) due to critical-current noise from
all three Josephson junctions :math:`EJ1`, :math:`EJ2` and :math:`EJ3`. The
combined noise is calculated by summing the rates from the individual
contributions.

@@ -269,3 +264,3 @@

class FluxQubit(base.QubitBaseClass, serializers.Serializable, NoisyFluxQubit):
r"""Flux Qubit
r"""Flux Qubit.

@@ -316,12 +311,12 @@ | [1] Orlando et al., Physical Review B, 60, 15398 (1999).

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.
esys_method:
method for esys diagonalization, callable or string representation
esys_method_options:
dictionary with esys diagonalization options
evals_method:
method for evals diagonalization, callable or string representation
evals_method_options:
dictionary with evals diagonalization options
esys_method:
method for esys diagonalization, callable or string representation
esys_method_options:
dictionary with esys diagonalization options
evals_method:
method for evals diagonalization, callable or string representation
evals_method_options:
dictionary with evals diagonalization options
"""

@@ -408,3 +403,3 @@

def supported_noise_channels(cls) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
return [

@@ -422,3 +417,3 @@ "tphi_1_over_f_cc1",

def EC_matrix(self) -> ndarray:
"""Return the charging energy matrix"""
"""Return the charging energy matrix."""
Cmat = np.zeros((2, 2))

@@ -547,4 +542,3 @@ CJ1 = 1.0 / (2 * self.ECJ1) # capacitances in units where e is set to 1

) -> ndarray:
"""
Return Hamiltonian in the basis obtained by employing charge basis for both
"""Return Hamiltonian in the basis obtained by employing charge basis for both
degrees of freedom or in the eigenenergy basis.

@@ -563,4 +557,4 @@

Hamiltonian in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, the Hamiltonian has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
unless `energy_esys` is specified, the Hamiltonian has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
for m given eigenvectors.

@@ -576,4 +570,3 @@ """

) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
"""Returns operator representing a derivative of the Hamiltonian with respect to
EJ1 in the native Hamiltonian basis or eigenenergy basis.

@@ -592,4 +585,4 @@

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -605,4 +598,3 @@ """

) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
"""Returns operator representing a derivative of the Hamiltonian with respect to
EJ2 in the native Hamiltonian basis or eigenenergy basis.

@@ -621,4 +613,4 @@

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -634,4 +626,3 @@ """

) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
"""Returns operator representing a derivative of the Hamiltonian with respect to
EJ3 in the native Hamiltonian basis or eigenenergy basis.

@@ -650,4 +641,4 @@

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -673,5 +664,4 @@ """

) -> ndarray:
"""
Returns the operator representing a derivative of the Hamiltonian with respect to flux
in the native Hamiltonian basis or eigenenergy basis.
"""Returns the operator representing a derivative of the Hamiltonian with
respect to flux in the native Hamiltonian basis or eigenenergy basis.

@@ -689,4 +679,4 @@ Parameters

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -731,4 +721,4 @@ """

) -> ndarray:
r"""
Returns the charge number operator conjugate to :math:`\phi_1` in the charge? or eigenenergy basis.
r"""Returns the charge number operator conjugate to :math:`\phi_1` in the charge?
or eigenenergy basis.

@@ -746,4 +736,4 @@ Parameters

Charge number operator conjugate to :math:`\phi_1` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m, for m given eigenvectors.
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m, for m given eigenvectors.
"""

@@ -756,4 +746,4 @@ native = np.kron(self._n_operator(), self._identity())

) -> ndarray:
r"""
Returns the charge number operator conjugate to :math:`\phi_2` in the charge? or eigenenergy basis.
r"""Returns the charge number operator conjugate to :math:`\phi_2` in the charge?
or eigenenergy basis.

@@ -771,4 +761,4 @@ Parameters

Charge number operator conjugate to :math:`\phi_2` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m, for m given eigenvectors.
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m, for m given eigenvectors.
"""

@@ -781,4 +771,3 @@ native = np.kron(self._identity(), self._n_operator())

) -> ndarray:
r"""
Returns operator :math:`e^{i\phi_1}` in the charge or eigenenergy basis.
r"""Returns operator :math:`e^{i\phi_1}` in the charge or eigenenergy basis.

@@ -797,3 +786,3 @@ Parameters

unless energy_esys is specified, :math:`e^{i\phi_1}` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`e^{i\phi_1}` has dimensions of m x m,
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`e^{i\phi_1}` has dimensions of m x m,
for m given eigenvectors.

@@ -807,4 +796,3 @@ """

) -> ndarray:
r"""
Returns operator :math:`e^{i\phi_2}` in the charge or eigenenergy basis.
r"""Returns operator :math:`e^{i\phi_2}` in the charge or eigenenergy basis.

@@ -823,3 +811,3 @@ Parameters

unless energy_esys is specified, :math:`e^{i\phi_2}` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`e^{i\phi_2}` has dimensions of m x m,
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`e^{i\phi_2}` has dimensions of m x m,
for m given eigenvectors.

@@ -833,4 +821,4 @@ """

) -> ndarray:
"""
Returns operator :math:`\\cos \\phi_1` in the charge or eigenenergy basis.
r"""
Returns operator :math:`\cos \phi_1` in the charge or eigenenergy basis.

@@ -840,12 +828,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`\\cos \\phi_1` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\\cos \\phi_1` in the energy eigenbasis.
If `False` (default), returns operator :math:`\cos \phi_1` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\cos \phi_1` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`\\cos \\phi_1` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`\cos \phi_1` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\cos \\phi_1` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\cos \\phi_1` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\cos \\phi_1` has dimensions of m x m,
Operator :math:`\cos \phi_1` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\cos \phi_1` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\cos \phi_1` has dimensions of m x m,
for m given eigenvectors.

@@ -861,4 +849,4 @@ """

) -> ndarray:
"""
Returns operator :math:`\\cos \\phi_2` in the charge or eigenenergy basis.
r"""
Returns operator :math:`\cos \phi_2` in the charge or eigenenergy basis.

@@ -868,12 +856,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`\\cos \\phi_2` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\\cos \\phi_2` in the energy eigenbasis.
If `False` (default), returns operator :math:`\cos \phi_2` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\cos \phi_2` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`\\cos \\phi_2` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`\cos \phi_2` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\cos \\phi_2` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\cos \\phi_2` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\cos \\phi_2` has dimensions of m x m,
Operator :math:`\cos \phi_2` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\cos \phi_2` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\cos \phi_2` has dimensions of m x m,
for m given eigenvectors.

@@ -889,4 +877,4 @@ """

) -> ndarray:
"""
Returns operator :math:`\\sin \\phi_1` in the charge or eigenenergy basis.
r"""
Returns operator :math:`\sin \phi_1` in the charge or eigenenergy basis.

@@ -896,12 +884,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`\\sin \\phi_1` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\\sin \\phi_1` in the energy eigenbasis.
If `False` (default), returns operator :math:`\sin \phi_1` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\sin \phi_1` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`\\sin \\phi_1` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`\sin \phi_1` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\sin \\phi_1` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\sin \\phi_1` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\sin \\phi_1` has dimensions of m x m,
Operator :math:`\sin \phi_1` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\sin \phi_1` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\sin \phi_1` has dimensions of m x m,
for m given eigenvectors.

@@ -917,4 +905,4 @@ """

) -> ndarray:
"""
Returns operator :math:`\\sin \\phi_2` in the charge or eigenenergy basis.
r"""
Returns operator :math:`\sin \phi_2` in the charge or eigenenergy basis.

@@ -924,12 +912,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`\\sin \\phi_2` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\\sin \\phi_1` in the energy eigenbasis.
If `False` (default), returns operator :math:`\sin \phi_2` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\sin \phi_1` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`\\sin \\phi_1` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`\sin \phi_1` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\sin \\phi_2` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\sin \\phi_2` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\sin \\phi_2` has dimensions of m x m,
Operator :math:`\sin \phi_2` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\sin \phi_2` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\sin \phi_2` has dimensions of m x m,
for m given eigenvectors.

@@ -946,5 +934,5 @@ """

contour_vals: ndarray = None,
**kwargs
**kwargs,
) -> Tuple[Figure, Axes]:
"""
r"""
Draw contour plot of the potential energy.

@@ -955,3 +943,3 @@

phi_grid:
used for setting a custom grid for phi; if None use self._default_grid
used for setting a custom grid for :math:`\phi`; if None use self._default_grid
contour_vals:

@@ -976,4 +964,4 @@ specific contours to draw

) -> storage.WaveFunctionOnGrid:
"""
Return a flux qubit wave function in phi1, phi2 basis
r"""
Return a flux qubit wave function in :math:`\phi_1`, :math:`\phi2` basis

@@ -987,3 +975,3 @@ Parameters

phi_grid:
used for setting a custom grid for phi; if None use self._default_grid
used for setting a custom grid for :math:`\phi`; if None use self._default_grid
"""

@@ -1025,5 +1013,5 @@ evals_count = max(which + 1, 3)

zero_calibrate: bool = True,
**kwargs
**kwargs,
) -> Tuple[Figure, Axes]:
"""Plots 2d phase-basis wave function.
r"""Plots 2d phase-basis wave function.

@@ -1035,8 +1023,8 @@ Parameters

which:
index of wave function to be plotted (default value = (0)
index of wave function to be plotted (default value = 0)
phi_grid:
used for setting a custom grid for phi; if None use self._default_grid
used for setting a custom grid for :math:`\phi`; if None use self._default_grid
mode:
choices as specified in `constants.MODE_FUNC_DICT`
(default value = 'abs_sqr')
(default value = 'abs')
zero_calibrate:

@@ -1043,0 +1031,0 @@ if True, colors are adjusted to use zero wavefunction amplitude as the

@@ -61,3 +61,3 @@ # fluxonium.py

id_str: str
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.

@@ -123,3 +123,3 @@ esys_method:

def supported_noise_channels(cls) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
return [

@@ -163,4 +163,3 @@ "tphi_1_over_f_cc",

) -> ndarray:
"""
Returns the phi operator in the LC harmonic oscillator or eigenenergy basis.
"""Returns the phi operator in the LC harmonic oscillator or eigenenergy basis.

@@ -179,3 +178,3 @@ Parameters

unless energy_esys is specified, phi operator has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, phi operator has dimensions of m x m,
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, phi operator has dimensions of m x m,
for m given eigenvectors.

@@ -195,4 +194,4 @@ """

) -> ndarray:
"""
Returns the :math:`n = - i d/d\\phi` operator in the LC harmonic oscillator or eigenenergy basis.
r"""
Returns the :math:`n = - i d/d\phi` operator in the LC harmonic oscillator or eigenenergy basis.

@@ -202,12 +201,12 @@ Parameters

energy_esys:
If `False` (default), returns the :math:`n = - i d/d\\phi` operator in the LC harmonic oscillator basis.
If `True`, the energy eigenspectrum is computed, returns the :math:`n = - i d/d\\phi` operator in the energy eigenbasis.
If `False` (default), returns the :math:`n = - i d/d\phi` operator in the LC harmonic oscillator basis.
If `True`, the energy eigenspectrum is computed, returns the :math:`n = - i d/d\phi` operator in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns the :math:`n = - i d/d\\phi` operator in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns the :math:`n = - i d/d\phi` operator in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`n = - i d/d\\phi` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`n = - i d/d\\phi` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`n = - i d/d\\phi` has dimensions of
Operator :math:`n = - i d/d\phi` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`n = - i d/d\phi` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`n = - i d/d\phi` has dimensions of
m x m, for m given eigenvectors.

@@ -229,4 +228,4 @@ """

) -> ndarray:
"""
Returns the :math:`e^{i (\\alpha \\phi + \\beta) }` operator, with :math:`\\alpha` and :math:`\\beta` being
r"""
Returns the :math:`e^{i (\alpha \phi + \beta) }` operator, with :math:`\alpha` and :math:`\beta` being
numbers, in the LC harmonic oscillator or eigenenergy basis.

@@ -237,7 +236,7 @@

energy_esys:
If `False` (default), returns the :math:`e^{i (\\alpha \\phi + \\beta) }` operator in the LC harmonic
If `False` (default), returns the :math:`e^{i (\alpha \phi + \beta) }` operator in the LC harmonic
oscillator basis. If `True`, the energy eigenspectrum is computed, returns the
:math:`e^{i (\\alpha \\phi + \\beta) }` operator in the energy eigenbasis.
:math:`e^{i (\alpha \phi + \beta) }` operator in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns the :math:`e^{i (\\alpha \\phi + \\beta) }` operator in the energy eigenbasis, and does not have to
returns the :math:`e^{i (\alpha \phi + \beta) }` operator in the energy eigenbasis, and does not have to
recalculate eigenspectrum.

@@ -247,6 +246,6 @@

-------
Operator :math:`e^{i (\\alpha \\phi + \\beta) }` in chosen basis as ndarray. If the eigenenergy basis is
chosen, unless energy_esys is specified, :math:`e^{i (\\alpha \\phi + \\beta) }` has dimensions of
Operator :math:`e^{i (\alpha \phi + \beta) }` in chosen basis as ndarray. If the eigenenergy basis is
chosen, unless energy_esys is specified, :math:`e^{i (\alpha \phi + \beta) }` has dimensions of
`truncated_dim`x `truncated_dim`. Otherwise, if eigenenergy basis is chosen,
:math:`e^{i (\\alpha \\phi + \\beta) }` has dimensions of m x m, for m given eigenvectors.
:math:`e^{i (\alpha \phi + \beta) }` has dimensions of m x m, for m given eigenvectors.
"""

@@ -263,4 +262,4 @@ exponent = 1j * (alpha * self.phi_operator())

) -> ndarray:
"""
Returns the :math:`\\cos (\\alpha \\phi + \\beta)` operator with :math:`\\alpha` and :math:`\\beta` being
r"""
Returns the :math:`\cos (\alpha \phi + \beta)` operator with :math:`\alpha` and :math:`\beta` being
numbers, in the LC harmonic oscillator or eigenenergy basis.

@@ -271,12 +270,12 @@

energy_esys:
If `False` (default), returns the :math:`\\cos (\\alpha \\phi + \\beta)` operator in the LC harmonic oscillator basis.
If `True`, the energy eigenspectrum is computed, returns the :math:`\\cos (\\alpha \\phi + \\beta)` operator in the energy eigenbasis.
If `False` (default), returns the :math:`\cos (\alpha \phi + \beta)` operator in the LC harmonic oscillator basis.
If `True`, the energy eigenspectrum is computed, returns the :math:`\cos (\alpha \phi + \beta)` operator in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns the :math:`\\cos (\\alpha \\phi + \\beta)` operator in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns the :math:`\cos (\alpha \phi + \beta)` operator in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\cos (\\alpha \\phi + \\beta)` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\cos (\\alpha \\phi + \\beta)` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\cos (\\alpha \\phi + \\beta)` has dimensions of m x m, for m given eigenvectors.
Operator :math:`\cos (\alpha \phi + \beta)` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\cos (\alpha \phi + \beta)` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\cos (\alpha \phi + \beta)` has dimensions of m x m, for m given eigenvectors.
"""

@@ -293,4 +292,4 @@ argument = alpha * self.phi_operator() + beta * np.eye(self.hilbertdim())

) -> ndarray:
"""
Returns the :math:`\\sin (\\alpha \\phi + \\beta)` operator with :math:`\\alpha` and :math:`\\beta` being
r"""
Returns the :math:`\sin (\alpha \phi + \beta)` operator with :math:`\alpha` and :math:`\beta` being
numbers, in the LC harmonic oscillator or eigenenergy basis.

@@ -301,12 +300,12 @@

energy_esys:
If `False` (default), returns the :math:`\\sin (\\alpha \\phi + \\beta)` operator in the LC harmonic oscillator basis.
If `True`, the energy eigenspectrum is computed, returns the :math:`\\sin (\\alpha \\phi + \\beta)` operator in the energy eigenbasis.
If `False` (default), returns the :math:`\sin (\alpha \phi + \beta)` operator in the LC harmonic oscillator basis.
If `True`, the energy eigenspectrum is computed, returns the :math:`\sin (\alpha \phi + \beta)` operator in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns the :math:`\\sin (\\alpha \\phi + \\beta)` operator in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns the :math:`\sin (\alpha \phi + \beta)` operator in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\sin (\\alpha \\phi + \\beta)` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\sin (\\alpha \\phi + \\beta)` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\sin (\\alpha \\phi + \\beta)` has dimensions of m x m, for m given eigenvectors.
Operator :math:`\sin (\alpha \phi + \beta)` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\sin (\alpha \phi + \beta)` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\sin (\alpha \phi + \beta)` has dimensions of m x m, for m given eigenvectors.
"""

@@ -320,5 +319,4 @@ argument = alpha * self.phi_operator() + beta * np.eye(self.hilbertdim())

) -> ndarray: # follow Zhu et al., PRB 87, 024510 (2013)
"""
Constructs Hamiltonian matrix in harmonic-oscillator, following Zhu
et al., PRB 87, 024510 (2013), or eigenenergy basis.
"""Constructs Hamiltonian matrix in harmonic-oscillator, following Zhu et al.,
PRB 87, 024510 (2013), or eigenenergy basis.

@@ -336,4 +334,4 @@ Parameters

Hamiltonian in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, the Hamiltonian has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
unless `energy_esys` is specified, the Hamiltonian has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
for m given eigenvectors.

@@ -355,5 +353,5 @@ """

) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
EJ in the harmonic-oscillator or eigenenergy basis. The flux is grouped as in the Hamiltonian.
"""Returns operator representing a derivative of the Hamiltonian with respect to
EJ in the harmonic-oscillator or eigenenergy basis. The flux is grouped as in
the Hamiltonian.

@@ -371,4 +369,4 @@ Parameters

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -380,15 +378,8 @@ """

# def d_hamiltonian_d_flux(self, energy_esys: Union[bool, Tuple[ndarray, ndarray]] = False) -> ndarray:
# """Returns operator representing a derivative of the Hamiltonian with respect
# to flux.
#
# The flux is grouped as in the Hamiltonian."""
# return -2 * np.pi * self.EJ * self.sin_phi_operator(1, 2 * np.pi * self.flux, energy_esys=energy_esys)
def d_hamiltonian_d_flux(
self, energy_esys: Union[bool, Tuple[ndarray, ndarray]] = False
) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
flux in the harmonic-oscillator or eigenenergy basis. The flux is grouped as in the Hamiltonian.
"""Returns operator representing a derivative of the Hamiltonian with respect to
flux in the harmonic-oscillator or eigenenergy basis. The flux is grouped as in
the Hamiltonian.

@@ -406,4 +397,4 @@ Parameters

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -422,7 +413,7 @@ """

def potential(self, phi: Union[float, ndarray]) -> ndarray:
"""Fluxonium potential evaluated at `phi`.
r"""Fluxonium potential evaluated at :math:`\phi`.
Parameters
----------
float value of the phase variable `phi`
float value of the phase variable :math:`\phi`

@@ -443,3 +434,3 @@ Returns

) -> storage.WaveFunction:
"""Returns a fluxonium wave function in `phi` basis
r"""Returns a fluxonium wave function in :math:`\phi` basis

@@ -453,3 +444,3 @@ Parameters

phi_grid:
used for setting a custom grid for phi; if None use self._default_grid
used for setting a custom grid for :math:`\phi`; if None use self._default_grid
"""

@@ -456,0 +447,0 @@ if esys is None:

@@ -41,3 +41,3 @@ # generic_qubit.py

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.

@@ -64,3 +64,3 @@ """

def hilbertdim(self) -> int:
"""Returns Hilbert space dimension"""
"""Returns Hilbert space dimension."""
return 2

@@ -67,0 +67,0 @@

@@ -29,2 +29,3 @@ # hilbert_space.py

Union,
Literal,
cast,

@@ -139,5 +140,4 @@ overload,

) -> qt.Qobj:
"""
Returns the full Hamiltonian of the interacting quantum system described by the
HilbertSpace object
"""Returns the full Hamiltonian of the interacting quantum system described by
the HilbertSpace object.

@@ -173,4 +173,3 @@ Parameters

) -> List[qt.Qobj]:
"""
Returns a list of identity-wrapped operators, one for each operator in
"""Returns a list of identity-wrapped operators, one for each operator in
operator_list. Note: at this point, any callable operator is actually evaluated.

@@ -192,3 +191,2 @@

list of identity-wrapped operators
"""

@@ -330,3 +328,7 @@ id_wrapped_operators = []

idwrapped_ops_by_name[name] = spec_utils.identity_wrap(
op, subsys_list[subsys_index], subsys_list, evecs=evecs
op,
subsys_list[subsys_index],
subsys_list,
evecs=evecs,
op_in_eigenbasis=False,
)

@@ -371,6 +373,6 @@ return idwrapped_ops_by_name

multiple subsystems. The class provides methods to turn subsystem operators into
operators acting on the full Hilbert space, and establishes the interface to
qutip. Returned operators are of the `qutip.Qobj` type. The class also provides
methods for obtaining eigenvalues, absorption and emission spectra as a function
of an external parameter.
operators acting on the full Hilbert space, and establishes the interface to qutip.
Returned operators are of the :py:obj:`~qutip.Qobj` type. The class also provides methods for
obtaining eigenvalues, absorption and emission spectra as a function of an external
parameter.

@@ -384,3 +386,3 @@ Parameters

`add_interaction` method. Alternatively, a list of interaction term objects
can be supplied here upon initialization of a `HilbertSpace` instance.
can be supplied here upon initialization of a :class:`HilbertSpace` instance.
esys_method:

@@ -518,4 +520,3 @@ method for esys diagonalization, callable or string representation

"""[Legacy] Auxiliary reference to self for compatibility with
SpectrumLookupMixin
class."""
SpectrumLookupMixin class."""
return self

@@ -538,6 +539,4 @@

def deserialize(cls, io_data: "IOData") -> HilbertSpace:
"""
Take the given IOData and return an instance of the described class,
initialized with the data stored in io_data.
"""
"""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()

@@ -551,5 +550,3 @@ alldata_dict["ignore_low_overlap"] = alldata_dict.pop("_ignore_low_overlap")

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

@@ -603,5 +600,3 @@ init_parameters.remove("ignore_low_overlap")

def get_subsys_index(self, subsys: QuantumSys) -> int:
"""
Return the index of the given subsystem in the HilbertSpace.
"""
"""Return the index of the given subsystem in the HilbertSpace."""
return self._subsystems.index(subsys)

@@ -615,3 +610,3 @@

def subsystem_dims(self) -> List[int]:
"""Returns list of the Hilbert space dimensions of each subsystem"""
"""Returns list of the Hilbert space dimensions of each subsystem."""
return [subsystem.truncated_dim for subsystem in self]

@@ -621,3 +616,3 @@

def dimension(self) -> int:
"""Returns total dimension of joint Hilbert space"""
"""Returns total dimension of joint Hilbert space."""
return np.prod(np.asarray(self.subsystem_dims)).item()

@@ -627,3 +622,3 @@

def subsystem_count(self) -> int:
"""Returns number of subsystems composing the joint Hilbert space"""
"""Returns number of subsystems composing the joint Hilbert space."""
return len(self._subsystems)

@@ -634,3 +629,51 @@

###################################################################################
def generate_lookup(self, update_subsystem_indices: List[int] = None) -> None:
def generate_lookup(
self,
ordering: Literal["DE", "LX", "BE"] = "DE",
subsys_priority: Union[List[int], None] = None,
BEs_count: Union[int, None] = None,
update_subsystem_indices: Union[List[int], None] = None,
) -> None:
"""
Label the dressed states by bare labels and generate the lookup table
with one of the following methods:
- Dressed Energy (ordering="DE"): traverse the eigenstates
in the order of their dressed energy, and find the corresponding bare
state label by overlaps (default)
- Lexical (ordering="LX"): traverse the bare states in `lexical order`_,
and perform the branch analysis generalized from Dumas et al. (2024).
- Bare Energy (ordering="BE"): traverse the bare states in the order of
their energy before coupling and perform label assignment. This is particularly
useful when the Hilbert space is too large and not all the eigenstates need
to be labeled.
Parameters
----------
ordering:
the ordering method for the labeling
- "DE": Dressed Energy (default)
- "LX": Lexical ordering
- "BE": Bare Energy
subsys_priority:
a permutation of the subsystem indices and bare labels. If it is provided,
lexical ordering is performed on the permuted labels. A "branch" is defined
as a series of eigenstates formed by putting excitations into the last
subsystem in the list.
BEs_count:
the number of eigenstates to be assigned, for "BE" scheme only. If None,
all eigenstates will be generated and labeled.
Returns
-------
a NamedSlotsNdarray object containing the branch analysis results
organized by the parameter indices.
For each parameter point, a flattened multi-dimensional array
is stored, representing the dressed indices organized by the
bare indices. E.g. if the dimensions of the subsystems are D0, D1 and D2,
the returned array will be ravelled from the shape (D0, D1, D2).
.. _lexical order: https://en.wikipedia.org/wiki/Lexicographic_order#Cartesian_products/
"""
self._lookup_exists = True

@@ -642,5 +685,8 @@ bare_esys_dict = self.generate_bare_esys(

evals, evecs = self.eigensys(
evals_count=self.dimension, bare_esys=bare_esys_dict
)
if ordering == "DE" or ordering == "LX" or BEs_count is None:
num_evals = self.dimension
else:
num_evals = BEs_count
evals, evecs = self.eigensys(evals_count=num_evals, bare_esys=bare_esys_dict)
# The following workaround ensures that eigenvectors maintain QutipEigenstates

@@ -654,3 +700,6 @@ # view when getting placed inside an outer array

self._data["dressed_indices"] = spec_lookup.SpectrumLookupMixin.generate_lookup(
self
self,
ordering=ordering,
subsys_priority=subsys_priority,
BEs_count=BEs_count,
)

@@ -679,2 +728,3 @@

)
subsys.affected_subsystem_indices = []
# diagonalizing only those subsystems present in update_subsystem_indices

@@ -713,4 +763,5 @@ if subsys_index in update_subsystem_indices:

) -> ndarray:
"""Calculates eigenvalues of the full Hamiltonian. Qutip's `qutip.Qobj.eigenenergies()` is
used by default, unless `self.evals_method` has been set to something other than `None`.
"""Calculates eigenvalues of the full Hamiltonian. Qutip's
:py:obj:`~qutip.Qobj.eigenenergies` is used by default, unless `self.evals_method` has
been set to something other than `None`.

@@ -755,4 +806,4 @@ Parameters

"""Calculates eigenvalues and eigenvectors of the full Hamiltonian. Qutip's
`qutip.Qobj.eigenenergies()` is used by default, unless `self.evals_method`
has been set to something other than `None`.
`qutip.Qobj.eigenenergies()` is used by default, unless `self.evals_method` has
been set to something other than `None`.

@@ -869,5 +920,4 @@ Parameters

) -> qt.Qobj:
"""
Returns the interaction Hamiltonian, based on the interaction terms specified
for the current HilbertSpace object
"""Returns the interaction Hamiltonian, based on the interaction terms specified
for the current HilbertSpace object.

@@ -922,3 +972,5 @@ Parameters

diag_qt_op = qt.Qobj(np.diagflat(evals[0:evals_count])) # type:ignore
return spec_utils.identity_wrap(diag_qt_op, subsystem, self.subsystem_list)
return spec_utils.identity_wrap(
diag_qt_op, subsystem, self.subsystem_list, op_in_eigenbasis=True
)

@@ -930,5 +982,5 @@ ###################################################################################

def diag_operator(self, diag_elements: ndarray, subsystem: QuantumSys) -> qt.Qobj:
"""For given diagonal elements of a diagonal operator in `subsystem`, return
the `Qobj` operator for the full Hilbert space (perform wrapping in
identities for other subsystems).
"""For given diagonal elements of a diagonal operator in `subsystem`, return the
`Qobj` operator for the full Hilbert space (perform wrapping in identities for
other subsystems).

@@ -946,6 +998,8 @@ Parameters

diag_matrix[index, index] = diag_elements
return spec_utils.identity_wrap(diag_matrix, subsystem, self.subsystem_list)
return spec_utils.identity_wrap(
diag_matrix, subsystem, self.subsystem_list, op_in_eigenbasis=True
)
def hubbard_operator(self, j: int, k: int, subsystem: QuantumSys) -> qt.Qobj:
"""Hubbard operator :math:`|j\\rangle\\langle k|` for system `subsystem`
r"""Hubbard operator :math:`|j\rangle\langle k|` for system `subsystem`

@@ -961,3 +1015,5 @@ Parameters

operator = qt.states.basis(dim, j) * qt.states.basis(dim, k).dag()
return spec_utils.identity_wrap(operator, subsystem, self.subsystem_list)
return spec_utils.identity_wrap(
operator, subsystem, self.subsystem_list, op_in_eigenbasis=True
)

@@ -974,3 +1030,5 @@ def annihilate(self, subsystem: QuantumSys) -> qt.Qobj:

operator = qt.destroy(dim)
return spec_utils.identity_wrap(operator, subsystem, self.subsystem_list)
return spec_utils.identity_wrap(
operator, subsystem, self.subsystem_list, op_in_eigenbasis=True
)

@@ -989,8 +1047,7 @@ ###################################################################################

) -> SpectrumData:
"""Return eigenvalues (and optionally eigenstates) of the full Hamiltonian as
a function of a parameter. Parameter values are specified as a list or array
in `param_vals`. The Hamiltonian `hamiltonian_func` must be a function of
that particular parameter, and is expected to internally set subsystem
parameters. If a `filename` string is provided, then eigenvalue data is
written to that file.
"""Return eigenvalues (and optionally eigenstates) of the full Hamiltonian as a
function of a parameter. Parameter values are specified as a list or array in
`param_vals`. The Hamiltonian `hamiltonian_func` must be a function of that
particular parameter, and is expected to internally set subsystem parameters. If
a `filename` string is provided, then eigenvalue data is written to that file.

@@ -1076,5 +1133,3 @@ Parameters

def standardize_eigenvector_phases(self) -> None:
"""
Standardize the phases of the (dressed) eigenvectors.
"""
"""Standardize the phases of the (dressed) eigenvectors."""
for idx, evec in enumerate(self._data["evecs"][0]):

@@ -1085,2 +1140,3 @@ array = utils.Qobj_to_scipy_csc_matrix(evec)

@utils.check_lookup_exists
def op_in_dressed_eigenbasis(

@@ -1109,3 +1165,3 @@ self,

.op_in_dressed_eigenbasis(op=<Callable>, truncated_dim=<int>)
.op_in_dressed_eigenbasis(op_callable_or_tuple=<Callable>, truncated_dim=<int>)

@@ -1117,3 +1173,3 @@ 2. subsystem operators may be passed as arrays, along with the

.op_in_dressed_eigenbasis(op=(<ndarray>, <subsys>),
.op_in_dressed_eigenbasis(op_callable_or_tuple=(<ndarray>, <subsys>),
truncated_dim=<int>,

@@ -1157,4 +1213,3 @@ op_in_bare_eigenbasis=<Bool>)

) -> None:
"""
Specify the interaction between subsystems making up the `HilbertSpace`
"""Specify the interaction between subsystems making up the :py:class:`HilbertSpace`
instance. `add_interaction(...)` offers three different interfaces:

@@ -1167,3 +1222,3 @@

1. Simple interface for operator products
Specify `ndarray`, `csc_matrix`, or `dia_matrix` (subsystem operator in
Specify :py:class:`ndarray`, :py:class:`csc_matrix`, or :py:class:`dia_matrix` (subsystem operator in
subsystem-internal basis) along with the corresponding subsystem

@@ -1207,4 +1262,4 @@

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.
optional string by which this instance can be referred to in :py:class:`HilbertSpace`
and :py:class:`scqubits.ParameterSweep`. If not provided, an id is auto-generated.
"""

@@ -1211,0 +1266,0 @@ if "expr" in kwargs:

@@ -18,3 +18,3 @@ # namedslots_array.py

from collections import OrderedDict
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union, Literal

@@ -95,4 +95,3 @@ import numpy as np

) -> NpIndexTuple:
"""
converts name-based and value-based indexing to standard numpy indexing
"""Converts name-based and value-based indexing to standard numpy indexing.

@@ -121,4 +120,3 @@ Parameters

) -> NpIndexTupleNoEllipsis:
"""
Removes `...` from the multi-index by explicit slicing.
"""Removes `...` from the multi-index by explicit slicing.

@@ -155,5 +153,7 @@ Parameters

class ExtIndexObject:
"""Object used for enabling enhanced indexing in NamedSlotsNdarray. Handles a
single idx_entry in multi-index"""
"""Object used for enabling enhanced indexing in NamedSlotsNdarray.
Handles a single idx_entry in multi-index
"""
def __init__(

@@ -169,4 +169,4 @@ self, idx_entry: ExtIndex, parameters: "Parameters", slot: Optional[int] = None

def convert_to_np_slice_entry(self, slice_entry: ExtSliceEntry) -> NpSliceEntry:
"""Handles value-based slices, converting a float or complex value based
entry into the corresponding position-based entry"""
"""Handles value-based slices, converting a float or complex value based entry
into the corresponding position-based entry."""
if isinstance(slice_entry, (int, np.integer)):

@@ -186,3 +186,3 @@ return slice_entry

"""Convert a generalized multi-index entry into a valid numpy multi-index entry,
and returns that along with a str recording the idx_entry type"""
and returns that along with a str recording the idx_entry type."""
if isinstance(idx_entry, (int, np.integer)):

@@ -342,3 +342,3 @@ return "int", idx_entry

"""Returns a dictionary specifying for each parameter name the number of
parameter values"""
parameter values."""
return {

@@ -352,3 +352,3 @@ name: len(self.paramvals_by_name[name])

"""Return a list of range objects suitable for looping over each parameter
set"""
set."""
return [range(count) for count in self.counts]

@@ -358,3 +358,3 @@

def paramvals_list(self) -> List[ndarray]:
"""Return list of all parameter values sets"""
"""Return list of all parameter values sets."""
return [self.paramvals_by_name[name] for name in self.paramnames_list]

@@ -364,3 +364,3 @@

def counts(self) -> Tuple[int, ...]:
"""Returns list of the number of parameter values for each parameter set"""
"""Returns list of the number of parameter values for each parameter set."""
return tuple(len(paramvals) for paramvals in self)

@@ -373,5 +373,4 @@

) -> "Parameters":
"""
Creates and returns a reduced Parameters object reflecting the fixing of a
subset of parameters
"""Creates and returns a reduced Parameters object reflecting the fixing of a
subset of parameters.

@@ -407,4 +406,3 @@ Parameters

) -> "Parameters":
"""
Create and return a sliced Parameters object according to numpy slicing
"""Create and return a sliced Parameters object according to numpy slicing
information.

@@ -457,3 +455,30 @@

def meshgrids_by_paramname(
self,
indexing: Literal["ij", "xy"] = "ij",
) -> OrderedDict[str, "NamedSlotsNdarray"]:
"""
Creates and returns returns a dictionary containing the meshgrids of the
parameter lists. All meshgrids are instances of the NamedSlotNdarray
Parameters
----------
indexing: {'ij', 'xy'}
Matrix ('ij', default) or cartesian ('xy') or indexing of output. This
argument will be passed to the np.meshgrid() directly
Returns
-------
An ordered dictionary or a list containing the meshgrids
"""
param_mesh = np.meshgrid(*self.paramvals_list, indexing=indexing)
param_mesh_nsarray = [
NamedSlotsNdarray(mesh, self.paramvals_by_name) for mesh in param_mesh
]
return OrderedDict(zip(self.paramnames_list, param_mesh_nsarray))
class NamedSlotsNdarray(np.ndarray, Serializable):

@@ -584,6 +609,4 @@ """

def deserialize(cls, io_data: IOData) -> "NamedSlotsNdarray":
"""
Take the given IOData and return an instance of the described class, initialized
with the data stored in io_data.
"""
"""Take the given IOData and return an instance of the described class,
initialized with the data stored in io_data."""
if "input_array" in io_data.ndarrays:

@@ -600,5 +623,3 @@ input_array = io_data.ndarrays["input_array"]

def serialize(self) -> IOData:
"""
Convert the content of the current class instance into IOData format.
"""
"""Convert the content of the current class instance into IOData format."""
import scqubits.io_utils.fileio as io

@@ -636,3 +657,3 @@

xlabel=self._parameters.names[0],
**kwargs
**kwargs,
)

@@ -639,0 +660,0 @@

@@ -23,6 +23,4 @@ # operators.py

def annihilation(dimension: int) -> ndarray:
"""
Returns a dense matrix of size dimension x dimension representing the annihilation
operator in number basis.
"""
"""Returns a dense matrix of size dimension x dimension representing the
annihilation operator in number basis."""
offdiag_elements = np.sqrt(range(1, dimension))

@@ -34,4 +32,3 @@ return np.diagflat(offdiag_elements, 1)

"""Returns a matrix of size dimension x dimension representing the annihilation
operator in the format of a scipy sparse.csc_matrix.
"""
operator in the format of a scipy sparse.csc_matrix."""
offdiag_elements = np.sqrt(range(dimension))

@@ -44,6 +41,4 @@ return sp.sparse.dia_matrix(

def creation(dimension: int) -> ndarray:
"""
Returns a dense matrix of size dimension x dimension representing the creation
operator in number basis.
"""
"""Returns a dense matrix of size dimension x dimension representing the creation
operator in number basis."""
return annihilation(dimension).T

@@ -54,4 +49,3 @@

"""Returns a matrix of size dimension x dimension representing the creation operator
in the format of a scipy sparse.csc_matrix
"""
in the format of a scipy sparse.csc_matrix."""
return annihilation_sparse(dimension).transpose().tocsc()

@@ -61,3 +55,3 @@

def hubbard_sparse(j1: int, j2: int, dimension: int) -> csc_matrix:
"""The Hubbard operator :math:`|j1\\rangle>\\langle j2|` is returned as a matrix of
r"""The Hubbard operator :math:`|j_1\rangle>\langle j_2|` is returned as a matrix of
linear size dimension.

@@ -84,4 +78,4 @@

"""Number operator matrix of size dimension x dimension in sparse matrix
representation. An additional prefactor can be directly included in the
generation of the matrix by supplying 'prefactor'.
representation. An additional prefactor can be directly included in the generation
of the matrix by supplying 'prefactor'.

@@ -110,4 +104,4 @@ Parameters

"""Number operator matrix of size dimension x dimension in sparse matrix
representation. An additional prefactor can be directly included in the
generation of the matrix by supplying 'prefactor'.
representation. An additional prefactor can be directly included in the generation
of the matrix by supplying 'prefactor'.

@@ -136,3 +130,3 @@ Parameters

) -> csc_matrix:
"""Operator matrix for prefactor(a+a^dag) of size dimension x dimension in
r"""Operator matrix for prefactor(:math:`a+a^\dagger`) of size dimension x dimension in
sparse matrix representation.

@@ -150,3 +144,3 @@

-------
prefactor * (a + a^dag) as sparse operator matrix, size dimension x dimension
prefactor * (:math:`a+a^\dagger`) as sparse operator matrix, size dimension x dimension
"""

@@ -160,3 +154,3 @@ prefactor = prefactor if prefactor is not None else 1.0

) -> ndarray:
"""Operator matrix for prefactor(a+a^dag) of size dimension x dimension in
r"""Operator matrix for prefactor(:math:`a+a^\dagger`) of size dimension x dimension in
sparse matrix representation.

@@ -174,3 +168,3 @@

-------
prefactor (a + a^dag) as ndarray, size dimension x dimension
prefactor * (:math:`a+a^\dagger`) as ndarray, size dimension x dimension
"""

@@ -183,3 +177,3 @@ return a_plus_adag_sparse(dimension, prefactor=prefactor).toarray()

) -> ndarray:
"""Operator matrix for cos(prefactor(a+a^dag)) of size dimension x dimension in
r"""Operator matrix for cos(prefactor(:math:`a+a^\dagger`)) of size dimension x dimension in
sparse matrix representation.

@@ -197,3 +191,3 @@

-------
prefactor (a + a^dag) as ndarray, size dimension x dimension
prefactor * (:math:`a+a^\dagger`) as ndarray, size dimension x dimension
"""

@@ -206,3 +200,3 @@ return sp.linalg.cosm(a_plus_adag_sparse(dimension, prefactor=prefactor).toarray())

) -> ndarray:
"""Operator matrix for sin(prefactor(a+a^dag)) of size dimension x dimension in
r"""Operator matrix for sin(prefactor(:math:`a+a^\dagger`)) of size dimension x dimension in
sparse matrix representation.

@@ -220,3 +214,3 @@

-------
prefactor (a + a^dag) as ndarray, size dimension x dimension
prefactor * (:math:`a+a^\dagger`) as ndarray, size dimension x dimension
"""

@@ -229,3 +223,3 @@ return sp.linalg.sinm(a_plus_adag_sparse(dimension, prefactor=prefactor).toarray())

) -> csc_matrix:
"""Operator matrix for prefactor(ia-ia^dag) of size dimension x dimension as
r"""Operator matrix for prefactor(:math:`ia-ia^\dagger`) of size dimension x dimension as
ndarray

@@ -243,3 +237,3 @@

-------
prefactor (ia - ia^dag) as sparse operator matrix, size dimension x dimension
prefactor * (:math:`ia-ia^\dagger`) as sparse operator matrix, size dimension x dimension
"""

@@ -255,3 +249,3 @@ prefactor = prefactor if prefactor is not None else 1.0

) -> ndarray:
"""Operator matrix for prefactor(ia-ia^dag) of size dimension x dimension as
r"""Operator matrix for prefactor(:math:`ia-ia^\dagger`) of size dimension x dimension as
ndarray

@@ -269,3 +263,3 @@

-------
prefactor (ia - ia^dag) as ndarray, size dimension x dimension
prefactor * (:math:`ia-ia^\dagger`) as ndarray, size dimension x dimension
"""

@@ -272,0 +266,0 @@ return iadag_minus_ia_sparse(dimension, prefactor=prefactor).toarray()

@@ -33,5 +33,5 @@ # oscillator.py

) -> Union[float, ndarray]:
r"""For given quantum number n=0,1,2,... return the value of the harmonic
oscillator wave function :math:`\psi_n(x) = N H_n(x/l_{osc}) \exp(-x^2/2l_\text{
osc})`, N being the proper normalization factor.
r"""For given quantum number :math:`n=0,1,2,\ldots` return the value of the harmonic
oscillator wave function :math:`\psi_n(x) = N H_n(x/l_{\rm osc}) \exp(-x^2/2
l_{\rm osc})`, N being the proper normalization factor.

@@ -63,3 +63,3 @@ Directly uses `scipy.special.pbdv` (implementation of the parabolic cylinder

r"""Returns the oscillator energy given a harmonic Hamiltonian of the form
:math:`H=\frac{1}{2}E_{\text{kin}}p^2 + \frac{1}{2}E_{\text{pot}}x^2`"""
:math:`H=\frac{1}{2}E_{\rm kin}p^2 + \frac{1}{2}E_{\rm pot}x^2`"""
return np.sqrt(E_kin * E_pot)

@@ -70,3 +70,3 @@

r"""Returns the oscillator length given a harmonic Hamiltonian of the form
:math:`H=\frac{1}{2}E_{\text{kin}}p^2 + \frac{1}{2}E_{\text{pot}}x^2`"""
:math:`H=\frac{1}{2}E_{\rm kin}p^2 + \frac{1}{2}E_{\rm pot}x^2`"""
return (E_kin / E_pot) ** (1 / 4)

@@ -80,3 +80,3 @@

r"""Class representing a harmonic oscillator/resonator governed by a Hamiltonian
:math:`H=E_\text{osc} a^{\dagger} a`, with :math:`a` being the annihilation
:math:`H=E_{\rm osc} a^\dagger a`, with :math:`a` being the annihilation
operator.

@@ -93,3 +93,3 @@

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.

@@ -134,3 +134,3 @@ """

) -> Tuple[ndarray, ndarray]:
"""Returns array of eigenvalues and eigenvectors
"""Returns array of eigenvalues and eigenvectors.

@@ -149,11 +149,11 @@ Parameters

def hilbertdim(self) -> int:
"""Returns Hilbert space dimension"""
"""Returns Hilbert space dimension."""
return self.truncated_dim
def creation_operator(self) -> ndarray:
"""Returns the creation operator"""
"""Returns the creation operator."""
return op.creation(self.truncated_dim)
def annihilation_operator(self) -> ndarray:
"""Returns the annihilation operator"""
"""Returns the annihilation operator."""
return op.annihilation(self.truncated_dim)

@@ -168,4 +168,4 @@

r"""Returns the phase operator defined as
:math:`l_\text{osc} (a + a^{\dagger})/\sqrt{2}`, with :math:`a` representing
an annihilation operator, and :math:`l_\text{osc}` the oscillator length.
:math:`l_{\rm osc} (a + a^\dagger)/\sqrt{2}`, with :math:`a` representing
an annihilation operator, and :math:`l_{\rm osc}` the oscillator length.
"""

@@ -183,4 +183,4 @@ if self.l_osc is None:

r"""Returns the charge-number n operator defined as
:math:`i (a^{\dagger} - a)/ ( \sqrt{2} l_\text{osc})`, with :math:`a` representing
an annihilation operator, and :math:`l_\text{osc}` the oscillator length.
:math:`i (a^\dagger - a)/ ( \sqrt{2} l_{\rm osc})`, with :math:`a` representing
an annihilation operator, and :math:`l_{\rm osc}` the oscillator length.
"""

@@ -203,3 +203,3 @@

r"""Class representing a nonlinear Kerr oscillator/resonator governed by a Hamiltonian
:math:`H_\text{Kerr}=E_\text{osc} a^{\dagger} a - K a^{\dagger} a^{\dagger} a a`, with :math:`a`
:math:`H_{\rm Kerr}=E_{\rm osc} a^\dagger a - K a^\dagger a^\dagger a a`, with :math:`a`
being the annihilation operator.

@@ -218,3 +218,3 @@

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.

@@ -221,0 +221,0 @@ """

@@ -12,5 +12,3 @@ # qubit_base.py

############################################################################
"""
Provides the base classes for qubits
"""
"""Provides the base classes for qubits."""

@@ -34,2 +32,3 @@ import functools

Type,
Callable,
)

@@ -91,4 +90,11 @@

class QuantumSystem(DispatchClient, ABC):
"""Generic quantum system class"""
"""Generic quantum system class.
Attributes
----------
truncated_dim: int
Hilbert space dimension
"""
truncated_dim = descriptors.WatchedProperty(int, "QUANTUMSYSTEM_UPDATE")

@@ -202,8 +208,8 @@ _init_params: List[str]

def hilbertdim(self) -> int:
"""Returns dimension of Hilbert space"""
"""Returns dimension of Hilbert space."""
@classmethod
def get_operator_names(cls) -> List[str]:
"""Returns a list of all operator names for the quantum system.
Note that this list omits any operators that start with "_".
"""Returns a list of all operator names for the quantum system. Note that this
list omits any operators that start with "_".

@@ -227,3 +233,3 @@ Parameters

def create(cls) -> "QuantumSystem":
"""Use ipywidgets to create a new class instance"""
"""Use ipywidgets to create a new class instance."""
init_params = cls.default_params()

@@ -235,3 +241,3 @@ instance = cls(**init_params)

def widget(self, params: Optional[Dict[str, Any]] = None):
"""Use ipywidgets to modify parameters of class instance"""
"""Use ipywidgets to modify parameters of class instance."""
init_params = params or self.get_initdata()

@@ -246,9 +252,7 @@ init_params.pop("id_str", None)

def default_params() -> Dict[str, Any]:
"""Return dictionary with default parameter values for initialization of
class instance"""
"""Return dictionary with default parameter values for initialization of class
instance."""
def set_params_from_gui(self, change):
"""
Set new parameters through the provided dictionary.
"""
"""Set new parameters through the provided dictionary."""
param_name = change["owner"].name

@@ -259,5 +263,3 @@ param_val = change["owner"].num_value

def set_params(self, **kwargs):
"""
Set new parameters through the provided dictionary.
"""
"""Set new parameters through the provided dictionary."""
for param_name, param_val in kwargs.items():

@@ -267,6 +269,6 @@ setattr(self, param_name, param_val)

def supported_noise_channels(self) -> List:
"""Returns a list of noise channels this QuantumSystem supports.
If none, return an empty list.
"""
Returns a list of noise channels this QuantumSystem supports. If none,
return an empty list.
"""
return []

@@ -279,4 +281,25 @@

class QubitBaseClass(QuantumSystem, ABC):
"""Base class for superconducting qubit objects. Provide general mechanisms and
routines for plotting spectra, matrix elements, and writing data to files
"""Base class for superconducting qubit objects.
Provide general mechanisms and routines for plotting spectra, matrix elements, and
writing data to files
Attributes
----------
truncated_dim: int
Hilbert space dimension
_default_grid: Grid1d
Discretization grid
_sys_type: str
Type of quantum system
_init_params: list
List of parameters used for initialization
evals_method: Union[Callable, str, None]
Method for calculating eigenvalues
evals_method_options: Union[Dict, None]
Options for eigenvalue calculation
esys_method: Union[Callable, str, None]
Method for calculating eigenvalues and eigenvectors
esys_method_options: Union[Dict, None]
Options for eigenvalue and eigenvector calculation
"""

@@ -293,5 +316,5 @@

id_str: Union[str, None],
evals_method: Union[str, None] = None,
evals_method: Union[Callable, str, None] = None,
evals_method_options: Union[Dict, None] = None,
esys_method: Union[str, None] = None,
esys_method: Union[Callable, str, None] = None,
esys_method_options: Union[Dict, None] = None,

@@ -317,3 +340,3 @@ ):

def hamiltonian(self):
"""Returns the Hamiltonian"""
"""Returns the Hamiltonian."""

@@ -411,3 +434,3 @@ def _evals_calc(self, evals_count: int) -> ndarray:

filename: Optional[str] = None,
return_spectrumdata: "Literal[False]" = False,
return_spectrumdata: bool = False,
) -> Tuple[ndarray, ndarray]: ...

@@ -420,3 +443,3 @@

filename: Optional[str],
return_spectrumdata: "Literal[True]",
return_spectrumdata: bool,
) -> SpectrumData: ...

@@ -478,5 +501,5 @@

) -> Union[ndarray, csc_matrix]:
"""Processes the operator `native_op`: either hand back `native_op` unchanged, or transform it into the
energy eigenbasis. (Native basis refers to the basis used internally by each qubit, e.g., charge basis in the
case of `Transmon`.
"""Processes the operator `native_op`: either hand back `native_op` unchanged,
or transform it into the energy eigenbasis. (Native basis refers to the basis
used internally by each qubit, e.g., charge basis in the case of :class:`Transmon`.

@@ -574,5 +597,5 @@ Parameters

operator: Union[str, ndarray, qt.Qobj, spmatrix],
evecs: ndarray = None,
evecs: Optional[ndarray] = None,
evals_count: int = 6,
filename: str = None,
filename: Optional[str] = None,
return_datastore: bool = False,

@@ -584,4 +607,4 @@ ) -> Union[DataStore, ndarray]:

the matrix element table for the charge operator is given by
`trm.op_matrixelement_table('n_operator')`. When `esys` is set to `None`,
the eigensystem is calculated on-the-fly.
`trm.op_matrixelement_table('n_operator')`. When `esys` is set to `None`, the
eigensystem is calculated on-the-fly.

@@ -623,4 +646,2 @@ Parameters

setattr(self, param_name, paramval)
if hasattr(self, "hierarchical_diagonalization"):
self.update()
return self.eigensys(evals_count=evals_count)

@@ -632,4 +653,2 @@

setattr(self, param_name, paramval)
if hasattr(self, "hierarchical_diagonalization"):
self.update()
return self.eigenvals(evals_count)

@@ -647,5 +666,5 @@

) -> SpectrumData:
"""Calculates eigenvalues/eigenstates for a varying system parameter,
given an array of parameter values. Returns a `SpectrumData` object with
`energy_data[n]` containing eigenvalues calculated for parameter value
"""Calculates eigenvalues/eigenstates for a varying system parameter, given an
array of parameter values. Returns a :class:`SpectrumData` object with
`energy_table[n]` containing eigenvalues calculated for parameter value
`param_vals[n]`.

@@ -808,5 +827,5 @@

) -> SpectrumData:
"""Calculates eigenvalues/eigenstates for a varying system parameter,
given an array of parameter values. Returns a `SpectrumData` object with
`energy_data[n]` containing eigenvalues calculated for parameter value
"""Calculates eigenvalues/eigenstates for a varying system parameter, given an
array of parameter values. Returns a :class:`SpectrumData` object with
`energy_table[n]` containing eigenvalues calculated for parameter value
`param_vals[n]`.

@@ -903,5 +922,5 @@

) -> SpectrumData:
"""Calculates matrix elements for a varying system parameter, given an array
of parameter values. Returns a `SpectrumData` object containing matrix
element data, eigenvalue data, and eigenstate data..
"""Calculates matrix elements for a varying system parameter, given an array of
parameter values. Returns a :class:`SpectrumData` object containing matrix element
data, eigenvalue data, and eigenstate data..

@@ -960,4 +979,4 @@ Parameters

"""Generates a simple plot of a set of eigenvalues as a function of one
parameter. The individual points correspond to the a provided array of
parameter values.
parameter. The individual points correspond to the a provided array of parameter
values.

@@ -1079,7 +1098,7 @@ Parameters

) -> Union[Tuple[Figure, Tuple[Axes, Axes]], Tuple[Figure, Axes]]:
"""Plots matrix elements for `operator`, given as a string referring to a
class method that returns an operator matrix. E.g., for instance `trm` of
Transmon, the matrix element plot for the charge operator `n` is obtained by
`trm.plot_matrixelements('n')`. When `esys` is set to None, the eigensystem
with `which` eigenvectors is calculated.
"""Plots matrix elements for `operator`, given as a string referring to a class
method that returns an operator matrix. E.g., for instance `trm` of Transmon,
the matrix element plot for the charge operator `n` is obtained by
`trm.plot_matrixelements('n')`. When `esys` is set to None, the eigensystem with
`which` eigenvectors is calculated.

@@ -1135,4 +1154,4 @@ Parameters

"""Generates a simple plot of a set of eigenvalues as a function of one
parameter. The individual points correspond to the a provided array of
parameter values.
parameter. The individual points correspond to the a provided array of parameter
values.

@@ -1206,4 +1225,5 @@ Parameters

"""Base class for superconducting qubit objects with one degree of freedom.
Provide general mechanisms and routines for plotting spectra, matrix elements,
and writing data to files.
Provide general mechanisms and routines for plotting spectra, matrix elements, and
writing data to files.
"""

@@ -1257,4 +1277,4 @@

) -> Tuple[Figure, Axes]:
"""Plot 1d phase-basis wave function(s). Must be overwritten by
higher-dimensional qubits like FluxQubits and ZeroPi.
"""Plot 1d phase-basis wave function(s). Must be overwritten by higher-
dimensional qubits like FluxQubits and ZeroPi.

@@ -1269,3 +1289,3 @@ Parameters

choices as specified in `constants.MODE_FUNC_DICT`
(default value = 'abs_sqr')
(default value = 'real')
esys:

@@ -1272,0 +1292,0 @@ eigenvalues, eigenvectors

@@ -15,4 +15,6 @@ # spec_lookup.py

import numbers
from copy import copy
from warnings import warn
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union, Literal

@@ -30,4 +32,5 @@ import numpy as np

from scqubits.core.namedslots_array import NamedSlotsNdarray
from scqubits.core.namedslots_array import NamedSlotsNdarray, convert_to_std_npindex
from scqubits.utils.typedefs import NpIndexTuple, NpIndices
from scqubits.utils.spectrum_utils import identity_wrap

@@ -59,4 +62,5 @@ if TYPE_CHECKING:

class SpectrumLookupMixin(MixinCompatible):
"""
SpectrumLookupMixin is used as a mix-in class by `ParameterSweep`. It makes various
"""SpectrumLookupMixin is used as a mix-in class by `ParameterSweep`.
It makes various
spectrum and spectrum lookup related methods directly available at the

@@ -79,23 +83,94 @@ `ParameterSweep` level.

else:
self._current_param_indices = slice(None, None, None)
self._current_param_indices = (
slice(None, None, None),
) * self._parameters.ndim()
@property
def _bare_product_states_labels(self) -> List[Tuple[int, ...]]:
"""Generates the list of bare-state labels in canonical order.
For example,
for a Hilbert space composed of two subsystems 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 then 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)]
"""
Generates the list of bare-state labels in canonical order. For example,
for a Hilbert space composed of two subsystems 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 then 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)]
"""
return list(np.ndindex(*self.hilbertspace.subsystem_dims))
def generate_lookup(self) -> NamedSlotsNdarray:
def generate_lookup(
self,
ordering: Literal["DE", "LX", "BE"] = "DE",
subsys_priority: Union[List[int], None] = None,
BEs_count: Union[int, None] = None,
) -> NamedSlotsNdarray:
"""
Label the dressed states by bare labels and generate the lookup table
with one of the following methods:
- Dressed Energy (ordering="DE"): traverse the eigenstates
in the order of their dressed energy, and find the corresponding bare
state label by overlaps (default)
- Lexical (ordering="LX"): traverse the bare states in `lexical order`_,
and perform the branch analysis generalized from Dumas et al. (2024).
- Bare Energy (ordering="BE"): traverse the bare states in the order of
their energy before coupling and perform label assignment. This is particularly
useful when the Hilbert space is too large and not all the eigenstates need
to be labeled.
Parameters
----------
ordering:
the ordering method for the dressed state labeling
- "DE": Dressed Energy (default)
- "LX": Lexical ordering
- "BE": Bare Energy
subsys_priority:
a permutation of the subsystem indices and bare labels. If it is provided,
lexical ordering is performed on the permuted labels. A "branch" is defined
as a series of eigenstates formed by putting excitations into the last
subsystem in the list.
BEs_count:
the number of eigenstates to be assigned, for "BE" scheme only. If None,
all available eigenstates will be labeled.
Returns
-------
a NamedSlotsNdarray object containing the branch analysis results
organized by the parameter indices.
For each parameter point, a flattened multi-dimensional array
is stored, representing the dressed indices organized by the
bare indices. E.g. if the dimensions of the subsystems are D0, D1 and D2,
the returned array will be ravelled from the shape (D0, D1, D2).
.. _lexical order: https://en.wikipedia.org/wiki/Lexicographic_order#Cartesian_products/
"""
if ordering == "LX" or ordering == "BE":
return self._branch_analysis(
ordering=ordering,
subsys_priority=subsys_priority,
transpose=False,
BEs_count=BEs_count,
)
elif ordering == "DE":
if BEs_count is not None:
warn(
"BEs_count is not supported for DE ordering, " "it will be ignored."
)
if subsys_priority is not None:
warn(
"subsys_priority is not supported for DE ordering, "
"it will be ignored."
)
return self._generate_lookup_by_overlap()
else:
raise ValueError(f"Invalid ordering method: {ordering}")
def _generate_lookup_by_overlap(self) -> NamedSlotsNdarray:
"""
For each parameter value of the parameter sweep, generate the map between
bare states and
dressed states.
bare states and dressed states based on the overlap criterion.

@@ -112,3 +187,3 @@ Returns

for index in param_indices:
dressed_indices[index] = self._generate_single_mapping(index)
dressed_indices[index] = self._generate_single_mapping_by_overlap(index)
dressed_indices = np.asarray(dressed_indices[:].tolist())

@@ -119,11 +194,10 @@

def _generate_single_mapping(
def _generate_single_mapping_by_overlap(
self,
param_indices: Tuple[int, ...],
) -> ndarray:
"""
For a single set of parameter values, specified by a tuple of indices
``param_indices``, create an array of the dressed-state indices in an order
that corresponds one-to-one to the bare product states with largest overlap
(whenever possible).
"""For a single set of parameter values, specified by a tuple of indices
``param_indices``, create an array of the dressed-state indices in an order that
corresponds one-to-one to the bare product states with largest overlap (whenever
possible).

@@ -173,5 +247,3 @@ Parameters

) -> NpIndexTuple:
"""
Convert the NpIndices parameter indices to a tuple of NpIndices.
"""
"""Convert the NpIndices parameter indices to a tuple of NpIndices."""
param_indices = param_indices or self._current_param_indices

@@ -189,4 +261,3 @@ if not isinstance(param_indices, tuple):

) -> Union[ndarray, int, None]:
"""
For given bare product state return the corresponding dressed-state index.
"""For given bare product state return the corresponding dressed-state index.

@@ -223,4 +294,3 @@ Parameters

) -> Union[Tuple[int, ...], None]:
"""
For given dressed index, look up the corresponding bare index.
"""For given dressed index, look up the corresponding bare index.

@@ -256,4 +326,3 @@ Returns

) -> ndarray:
"""
Return the list of dressed eigenvectors
"""Return the list of dressed eigenvectors.

@@ -301,4 +370,4 @@ Parameters

) -> Union[float, NamedSlotsNdarray]: # the return value may also be np.nan
"""
Look up dressed energy most closely corresponding to the given bare-state labels
"""Look up dressed energy most closely corresponding to the given bare-state
labels.

@@ -354,5 +423,4 @@ Parameters

) -> Union[float, NamedSlotsNdarray]:
"""
Look up the dressed eigenenergy belonging to the given dressed index,
usually to be used with pre-slicing
"""Look up the dressed eigenenergy belonging to the given dressed index, usually
to be used with pre-slicing.

@@ -385,4 +453,4 @@ Parameters

) -> NamedSlotsNdarray:
"""
Return ndarray of bare eigenstates for given subsystems and parameter index.
"""Return ndarray of bare eigenstates for given subsystems and parameter index.
Eigenstates are expressed in the basis internal to the subsystems. Usually to be

@@ -403,4 +471,3 @@ used with pre-slicing when part of `ParameterSweep`.

) -> NamedSlotsNdarray:
"""
Return `NamedSlotsNdarray` of bare eigenenergies for given subsystem, usually
"""Return :obj:`.NamedSlotsNdarray` of bare eigenenergies for given subsystem, usually
to be used with preslicing.

@@ -429,6 +496,5 @@

) -> Qobj:
"""
Return the bare product state specified by `bare_index`. Note: no parameter
dependence here, since the Hamiltonian is always represented in the bare
product eigenbasis.
"""Return the bare product state specified by `bare_index`. Note: no parameter
dependence here, since the Hamiltonian is always represented in the bare product
eigenbasis.

@@ -451,4 +517,3 @@ Parameters

def all_params_fixed(self, param_indices: Union[slice, tuple]) -> bool:
"""
Checks whether the indices provided fix all the parameters.
"""Checks whether the indices provided fix all the parameters.

@@ -463,6 +528,561 @@ Parameters

True if all parameters are being fixed by `param_indices`.
"""
param_indices_std = convert_to_std_npindex(
np.index_exp[param_indices], self._parameters
)
# Check if each dimension is being fixed to a single value or a length-1 array
fixed = []
for params, idx in zip(
self._parameters.paramvals_by_name.values(), param_indices_std
):
fixed.append(np.size(params[idx]) == 1)
return all(fixed)
@utils.check_lookup_exists
@utils.check_sync_status
def dressed_state_components(
self,
state_label: Union[Tuple[int, ...], List[int], int],
components_count: Union[int, None] = None,
return_probability: bool = True,
param_npindices: Optional[NpIndices] = None,
) -> Dict[Tuple[int, ...], float]:
"""
if isinstance(param_indices, slice):
param_indices = (param_indices,)
return len(self._parameters) == len(param_indices)
A dressed state is a superposition of bare states. This function returns
a dressed state's bare conponents and the associated occupation
probabilities. They are sorted by probability in descending order.
Parameters
----------
state_label:
The bare label of the dressed state of interest. Could be
- a tuple/list of bare labels (int)
- a single dressed label (int)
components_count:
The number of components to be returned. If None, all components
will be returned.
return_probability:
Whether to return the occupation probabilities. If not, return
the probability amplitudes.
param_npindices:
This method only allows for a HilbertSpace object or a single
parameter ParameterSweep. If it's a multi-dimensional sweep,
param_npindices should be provided to specify a point in the
parameter space. If None, the current parameter preslicing will
be used.
Returns
-------
A dictionary of the bare labels and their associated probability
(or probability amplitude if specified).
"""
param_npindices = self.set_npindextuple(param_npindices)
if not self.all_params_fixed(param_npindices):
raise ValueError(
"All parameters must be fixed to concrete values for "
"the use of `.dressed_state_component`."
)
evecs = self["evecs"][param_npindices]
# find the desired state vector
if isinstance(state_label, tuple | list):
raveled_label = np.ravel_multi_index(
state_label, self.hilbertspace.subsystem_dims
)
drs_idx = self["dressed_indices"][param_npindices][raveled_label]
if drs_idx is None:
raise IndexError(f"no dressed state found for bare label {state_label}")
elif isinstance(state_label, int | np.int_):
drs_idx = state_label
evec_1 = evecs[drs_idx]
ordered_label = np.argsort(np.abs(evec_1.full()[:, 0]))[::-1]
bare_label_list = []
prob_list = []
for idx in range(evec_1.shape[0]):
raveled_label = int(ordered_label[idx])
bare_label = np.unravel_index(
raveled_label, self.hilbertspace.subsystem_dims
)
prob_amp = evec_1.full()[raveled_label, 0]
bare_label_list.append(bare_label)
if return_probability:
prob = np.abs(prob_amp) ** 2
prob_list.append(prob)
else:
prob_list.append(prob_amp)
if components_count is not None:
bare_label_list = bare_label_list[:components_count]
prob_list = prob_list[:components_count]
return dict(zip(bare_label_list, prob_list))
def _branch_analysis_excite_op(
self,
mode: "Union[int, QuantumSys]",
) -> Qobj:
"""
Branch analysis requires a step by step excitation of a chosen state,
which help to cover the entire Hilbert space and complete the
assignment of dressed indices.
This function returns the excitation operator for a given mode.
For the moment, it returns the creation operator for linear modes,
and Sum_i |i+1><i| operator for other modes.
Parameters
----------
mode:
The mode to be excited.
Returns
-------
The excitation operator for the given mode, tensor producted with
the identity operators of the other subsystems.
"""
hilbertspace = self.hilbertspace
if isinstance(mode, int):
mode_idx = mode
mode = hilbertspace.subsystem_list[mode]
else:
mode_idx = hilbertspace.subsystem_list.index(mode)
if mode in hilbertspace.osc_subsys_list:
# annhilation operator
return hilbertspace.annihilate(mode).dag()
else:
# sum_j |j+1><j|
dims = hilbertspace.subsystem_dims
op = qt.qdiags(
np.ones(dims[mode_idx] - 1),
-1,
)
return identity_wrap(
op, mode, hilbertspace.subsystem_list, op_in_eigenbasis=True
)
def _branch_analysis_LX_step(
self,
subsys_priority: List[int],
recusion_depth: int,
init_drs_idx: int,
init_state: qt.Qobj,
remaining_drs_indices: List[int],
remaining_evecs: List[qt.Qobj],
) -> Tuple[List, List]:
"""
Perform a single branch analysis according to Dumas et al. (2024). This
is a core function to be run recursively, which realized a depth-first
search in the tree - its leaves can be labeled by bare labels.
In a nutshell, the function will:
1. Start from the "ground" state / starting point the branch, find
all of the branch states
2. Remove the found states from the remaining candidates
3. [If at the end of the depth-first search] Return the branch states
4. [If not at the end] For each branch state, use it as an init state to
start such search again, which will return a (nested) list of branch
states. Combine the list of branch states and return a nested list of
those states
In such way, the function will recursively go through this multi-dimensional
Hilbert space and assign the eigenstates to their labels.
Parameters
----------
subsys_priority:
a permutation of the subsystem indices and bare labels. If it is
provided, lexical ordering is performed on the permuted labels.
It also represents the depth of the subsystem labels to be traversed. The later
the subsystem appears in the list, the deeper it is in the recursion.
A "branch" is defined as a series of eigenstates formed by
putting excitations into the last subsystem in the list.
recusion_depth:
the current depth of the recursion. It should be 0 at the beginning.
init_drs_idx:
the dressed index of the initial state of this branch.
init_state:
the initial state of this branch.
remaining_drs_indices:
the list of the remaining dressed indices to be assigned.
remaining_evecs:
The list of the remaining eigenstates to be assigned.
Returns
-------
branch_drs_indices, branch_states
The (nested) list of the branch states and their dressed indices.
"""
hspace = self.hilbertspace
mode_index = subsys_priority[recusion_depth]
mode = hspace.subsystem_list[mode_index]
terminate_branch_length = hspace.subsystem_dims[mode_index]
# photon addition operator
excite_op = self._branch_analysis_excite_op(mode)
# loop over and find all states that matches the excited initial state
current_state = init_state
current_drs_idx = init_drs_idx
branch_drs_indices = []
branch_states = []
while True:
if recusion_depth == len(subsys_priority) - 1:
# we are at the end of the depth-first search:
# just add the state to the branch
branch_drs_indices.append(current_drs_idx)
branch_states.append(current_state)
else:
# continue the depth-first search:
# recursively call the function and append all the branch states
(_branch_drs_indices, _branch_states) = self._branch_analysis_LX_step(
subsys_priority,
recusion_depth + 1,
current_drs_idx,
current_state,
remaining_drs_indices,
remaining_evecs,
)
branch_drs_indices.append(_branch_drs_indices)
branch_states.append(_branch_states)
# if the branch is long enough, terminate the loop
if len(branch_states) == terminate_branch_length:
break
# find the closest state to the excited current state
if len(remaining_evecs) == 0:
raise ValueError(
"No enough eigenstates to be assigned with a label. "
"It's likely that the eignestates are not complete. "
"Please try to obtain a complete set of eigenstates by "
"increasing `evals_count` before running the branch analysis."
)
excited_state = (excite_op * current_state).unit()
overlaps = [np.abs(excited_state.overlap(evec)) for evec in remaining_evecs]
max_overlap_index = np.argmax(overlaps)
current_state = remaining_evecs[max_overlap_index]
current_drs_idx = remaining_drs_indices[max_overlap_index]
# remove the state from the remaining states
remaining_evecs.pop(max_overlap_index)
remaining_drs_indices.pop(max_overlap_index)
return branch_drs_indices, branch_states
def _branch_analysis_LX(
self,
param_indices: Tuple[int, ...],
subsys_priority: Optional[List[int]] = None,
transpose: bool = False,
) -> np.ndarray:
"""
Perform a full branch analysis according to Dumas et al. (2024) for
a single parameter point using lexical ordering. Running through all
bare labels in the lexical order is equivalent to a depth-first traversal
in a tree structure. The method will start a recursive labeling using
method `_branch_analysis_LX_step`.
The eigenstates-bare-state-paring is based on the
"first-come-first-served" principle, the ordering of such traversal will
permute the bare labels and change the traversal order based on the
lexical order. For the last mode in the list, its states will be labelled
sequentially and organized in a single branch.
At the end, this function will organize the eigenstates into a
multi-dimensional array according to the mode_priority.
Parameters
----------
param_indices:
the indices of the parameter sweep to be analyzed.
subsys_priority:
a permutation of the subsystem indices and bare labels. If
it is provided, lexical ordering is performed on the permuted labels.
A "branch" is defined as a series of eigenstates formed by putting
excitations into the last subsystem in the list.
transpose:
if True, the returned array will be transposed, according to the
mode_priority. Otherwise, the array will be in the
shape of the subsystem dimensions in the original order. Now
it is a purely internal knob for testing.
Returns
-------
branch_drs_indices
the multi-dimensional array of the dressed indices organized by
the mode_priority. If the dimensions of the subsystems are
D0, D1 and D2, the returned array will have the shape (D0, D1, D2).
If transposed is True, the array will be transposed according to
the mode_priority.
"""
if subsys_priority is None:
subsys_priority = list(range(self.hilbertspace.subsystem_count))
else:
# check if the subsys_priority is a valid permutation of
# the subsystem indices: length and unique
if len(subsys_priority) != self.hilbertspace.subsystem_count:
raise ValueError(
"The length of subsys_priority does not match "
"the number of subsystems."
)
if len(subsys_priority) != len(set(subsys_priority)):
raise ValueError(
"subsys_priority contains duplicate values, "
"which is supposed to be a permutation."
)
# we assume that the ground state always has bare label (0, 0, ...)
evecs = self._data["evecs"][param_indices]
init_state = evecs[0]
remaining_evecs = list(evecs[1:])
remaining_drs_indices = list(range(1, self.hilbertspace.dimension))
branch_drs_indices, _ = self._branch_analysis_LX_step(
subsys_priority, 0, 0, init_state, remaining_drs_indices, remaining_evecs
)
branch_drs_indices = np.array(branch_drs_indices)
if not transpose:
reversed_permutation = np.argsort(subsys_priority)
return np.transpose(branch_drs_indices, reversed_permutation)
return branch_drs_indices
def _branch_analysis_BE(
self,
param_indices: Tuple[int, ...],
subsys_priority: Optional[List[int]] = None,
BEs_count: Union[int, None] = None,
source_maj_vote: bool = False,
) -> np.ndarray:
"""
Perform a full branch analysis according to Dumas et al. (2024) for
a single parameter point for a few eigenstates with the lowest bare
energies. It is particularly useful when the Hilbert space is too large
and not all the eigenstates need to be labeled.
In the bare energy ordering for branch analysis, the way to obtain the
excited dressed states
is ambiguous, e.g. |21> can be excited from |11> or |20>. So we need the
user to input `subsys_priority` to specify the path / branch to be taken.
It specifies the order of the subsystems to be excited, the last subsystem
in the list will be excited if possible.
Parameters
----------
param_indices:
the indices of the parameter sweep to be analyzed.
subsys_priority:
a permutation of the subsystem indices and bare labels. If
it is provided, lexical ordering is performed on the permuted labels.
A "branch" is defined as a series of eigenstates formed by putting
excitations into the last subsystem in the list.
BEs_count:
the number of states to be assigned. If None, all available eigenstates
will be assigned.
source_maj_vote:
if True, the branch will be determined by majority vote of the
potential candidates. It is purely an internal knob to test the
behavior of the branch analysis. It overrides mode_priority.
Returns
-------
the multi-dimensional array of the dressed indices
"""
hspace = self.hilbertspace
dims = hspace.subsystem_dims
if subsys_priority is None:
subsys_priority = list(range(hspace.subsystem_count))
if BEs_count is None:
BEs_count = len(self._data["evecs"][param_indices])
elif len(self._data["evecs"][param_indices]) < BEs_count:
BEs_count = len(self._data["evecs"][param_indices])
warn(
"evals_count is less than BEs_count, BEs_count is set to "
f"{len(self._data['evecs'][param_indices])}."
)
# get the associated excitation operators
excite_op_list = [
self._branch_analysis_excite_op(mode) for mode in hspace.subsystem_list
]
# generate a list of their bare energies
bare_evals_by_sys = self._data["bare_evals"]
bare_evals = np.zeros(dims)
for idx in np.ndindex(tuple(dims)):
subsys_eval = [
bare_evals_by_sys[subsys_idx][param_indices][level_idx]
for subsys_idx, level_idx in enumerate(idx)
]
bare_evals[idx] = np.sum(subsys_eval)
bare_evals = bare_evals.ravel()
# sort the bare energies
# which will be the order of state assignment
sorted_indices = np.argsort(bare_evals)[:BEs_count]
# mode assignment
branch_drs_indices = np.ndarray(dims, dtype=object)
branch_drs_indices.fill(None)
evecs = self._data["evecs"][param_indices]
remaining_evecs = list(evecs)
remaining_drs_indices = list(range(0, self.hilbertspace.dimension))
for raveled_bare_idx in sorted_indices:
# assign the dressed index for bare_idx
bare_idx = list(np.unravel_index(raveled_bare_idx, dims))
if raveled_bare_idx == 0:
# the (0, 0, ...) is always assigned the dressed index 0
branch_drs_indices[tuple(bare_idx)] = 0
remaining_drs_indices.pop(0)
remaining_evecs.pop(0)
continue
# get previously assigned states (one less excitation)
# By comparing the excited states with the dressed states,
# we can find the dressed index of the current state
prev_bare_indices = []
potential_drs_indices = []
for subsys_idx in subsys_priority[::-1]:
# obtain the a bare index with one less excitation
prev_idx = copy(bare_idx)
if prev_idx[subsys_idx] == 0:
continue
prev_idx[subsys_idx] -= 1
prev_drs_idx = branch_drs_indices[tuple(prev_idx)]
prev_bare_indices.append(prev_idx)
# state vector
prev_state = evecs[prev_drs_idx]
excited_state = excite_op_list[subsys_idx] * prev_state
excited_state = excited_state.unit()
# find the dressed index
overlaps = [
np.abs(excited_state.overlap(evec)) for evec in remaining_evecs
]
max_overlap_index = np.argmax(overlaps)
potential_drs_indices.append(remaining_drs_indices[max_overlap_index])
if not source_maj_vote:
# we only need one path, which is the last one in the mode_priority
break
else:
# we need to check all the paths
continue
# do a majority vote, if equal, chose the first one
# this also works for source_maj_vote = False, when all lists are length 1
unique_votes, counts = np.unique(potential_drs_indices, return_counts=True)
vote_result = np.argmax(counts)
drs_idx = unique_votes[vote_result]
idx_in_remaining_list = remaining_drs_indices.index(drs_idx)
# remove the state from the remaining states
remaining_evecs.pop(idx_in_remaining_list)
remaining_drs_indices.pop(idx_in_remaining_list)
branch_drs_indices[tuple(bare_idx)] = drs_idx
return branch_drs_indices
def _branch_analysis(
self,
ordering: Literal["LX", "BE"] = "BE",
subsys_priority: Optional[List[int]] = None,
transpose: bool = False,
BEs_count: Union[int, None] = None,
) -> NamedSlotsNdarray:
"""
Perform a full branch analysis for all parameter points, according to
Dumas et al. (2024). We provide two orderings methods for the labeling:
- Lexical (ordering="LX"): traverse the bare states in `lexical order`_,
and perform the branch analysis generalized from Dumas et al. (2024).
- Bare Energy (ordering="BE"): traverse the bare states in the order of
their energy before coupling and perform label assignment. This is particularly
useful when the Hilbert space is too large and not all the eigenstates need
to be labeled.
Parameters
----------
ordering:
the ordering method for the labeling
- "LX": Lexical ordering
- "BE": Bare Energy
mode_priority:
a permutation of the subsystem indices and bare labels. If it
is provided, lexical ordering is performed on the permuted labels.
A "branch" is defined as a series of eigenstates formed by putting
excitations into the last subsystem in the list.
BEs_count:
the number of eigenstates to be labeled, for "BE" scheme only. If
None, all available eigenstates will be labeled.
Returns
-------
a NamedSlotsNdarray object containing the branch analysis results
organized by the parameter indices.
For each parameter point, a flattened multi-dimensional array
is stored, representing the dressed indices organized by the
bare indices. E.g. if the dimensions of the subsystems are D0, D1 and D2,
the returned array will be ravelled from the shape (D0, D1, D2).
.. _lexical order: https://en.wikipedia.org/wiki/Lexicographic_order#Cartesian_products/
"""
dressed_indices = np.empty(shape=self._parameters.counts, dtype=object)
param_indices = itertools.product(*map(range, self._parameters.counts))
for index in param_indices:
if ordering == "LX":
if BEs_count is not None:
warn(
"BEs_count is not supported for lexical ordering, "
"it will be ignored."
)
dressed_indices[index] = self._branch_analysis_LX(
index,
subsys_priority,
transpose,
)
elif ordering == "BE":
dressed_indices[index] = self._branch_analysis_BE(
index,
subsys_priority,
BEs_count,
)
else:
raise ValueError(f"Ordering {ordering} is not supported.")
dressed_indices = np.asarray(dressed_indices[:].tolist())
parameter_dict = self._parameters.ordered_dict.copy()
shape = self._parameters.counts
return NamedSlotsNdarray(
dressed_indices.reshape(shape + (-1,)),
parameter_dict,
)

@@ -33,4 +33,4 @@ # storage.py

class WaveFunction:
"""Container for wave function amplitudes defined for a specific basis.
Optionally, a corresponding energy is saved as well.
"""Container for wave function amplitudes defined for a specific basis. Optionally,
a corresponding energy is saved as well.

@@ -56,9 +56,8 @@ Parameters

def rescale(self, scale_factor: float) -> None:
"""Rescale the wavefunction amplitudes by a given factor"""
"""Rescale the wavefunction amplitudes by a given factor."""
self.amplitudes *= scale_factor
def rescale_to_potential(self, potential_vals: np.ndarray):
"""
Rescale the dimensionless amplitude to a (pseudo-)energy that allows us to plot
wavefunctions and potential energies in the same plot.
"""Rescale the dimensionless amplitude to a (pseudo-)energy that allows us to
plot wavefunctions and potential energies in the same plot.

@@ -69,3 +68,2 @@ Parameters

array of potential energy values (that determine the energy range on the y axis
"""

@@ -75,5 +73,5 @@ self.amplitudes *= self.amplitude_scale_factor(potential_vals)

def amplitude_scale_factor(self, potential_vals: np.ndarray) -> float:
"""
Returnn scale factor that converts the dimensionless amplitude to a (pseudo-)energy that allows us to plot
wavefunctions and potential energies in the same plot.
"""Returnn scale factor that converts the dimensionless amplitude to a
(pseudo-)energy that allows us to plot wavefunctions and potential energies in
the same plot.

@@ -88,3 +86,2 @@ Parameters

scale factor
"""

@@ -149,3 +146,3 @@ FILL_FACTOR = 0.1

param_vals: np.ndarray = None,
**kwargs
**kwargs,
) -> None:

@@ -169,4 +166,3 @@ self.system_params = system_params

def add_data(self, **kwargs) -> None:
"""
Adds one or several data sets to the DataStorage object.
"""Adds one or several data sets to the DataStorage object.

@@ -192,4 +188,4 @@ Parameters

"""Container holding energy and state data as a function of a particular parameter
that is varied. Also stores all other system parameters used for generating the
set, and provides method for writing data to file.
that is varied. Also stores all other system parameters used for generating the set,
and provides method for writing data to file.

@@ -223,3 +219,3 @@ Parameters

matrixelem_table: np.ndarray = None,
**kwargs
**kwargs,
) -> None:

@@ -239,7 +235,7 @@ self.system_params = system_params

matrixelem_table=matrixelem_table,
**kwargs
**kwargs,
)
def subtract_ground(self) -> None:
"""Subtract ground state energies from spectrum"""
"""Subtract ground state energies from spectrum."""
self.energy_table -= self.energy_table[:, 0] # type:ignore

@@ -252,6 +248,6 @@

label_list: List[str] = None,
**kwargs
**kwargs,
) -> "Tuple[Figure, Axes]":
"""Plots eigenvalues of as a function of one parameter, as stored in
`SpectrumData` object.
:class:`SpectrumData` object.

@@ -281,3 +277,3 @@ Parameters

label_list=label_list,
**kwargs
**kwargs,
)

@@ -32,5 +32,4 @@ # sweeps.py

) -> np.ndarray:
"""
Given parameter sweep data, compute and return a matrix element table using the bare
states of the specified subsystem.
"""Given parameter sweep data, compute and return a matrix element table using the
bare states of the specified subsystem.

@@ -72,4 +71,3 @@ Parameters

) -> np.ndarray:
"""
Given parameter sweep data, compute and return a matrix element table using the
"""Given parameter sweep data, compute and return a matrix element table using the
dressed states of the composite Hilbert space.

@@ -76,0 +74,0 @@

@@ -66,3 +66,3 @@ # transmon.py

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.

@@ -119,3 +119,3 @@ esys_method:

def supported_noise_channels(cls) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
return [

@@ -130,4 +130,4 @@ "tphi_1_over_f_cc",

def effective_noise_channels(cls) -> List[str]:
"""Return a default list of channels used when calculating effective t1 and
t2 noise."""
"""Return a default list of channels used when calculating effective t1 and t2
noise."""
noise_channels = cls.supported_noise_channels()

@@ -175,4 +175,3 @@ noise_channels.remove("t1_charge_impedance")

) -> Tuple[float, float]:
"""
Finds the EJ and EC values given a qubit splitting `E01` and `anharmonicity`.
"""Finds the EJ and EC values given a qubit splitting `E01` and `anharmonicity`.

@@ -213,4 +212,3 @@ Parameters

) -> ndarray:
"""
Returns charge operator n in the charge or eigenenergy basis.
"""Returns charge operator n in the charge or eigenenergy basis.

@@ -229,3 +227,3 @@ Parameters

Charge operator n in chosen basis as ndarray.
For `energy_esys=True`, n has dimensions of `truncated_dim` x `truncated_dim`.
For `energy_esys=True`, n has dimensions of :attr:`truncated_dim` x :attr:`truncated_dim`.
If an actual eigensystem is handed to `energy_sys`, then `n` has dimensions of m x m,

@@ -241,4 +239,4 @@ where m is the number of given eigenvectors.

) -> ndarray:
"""
Returns operator :math:`e^{i\\varphi}` in the charge or eigenenergy basis.
r"""
Returns operator :math:`e^{i\varphi}` in the charge or eigenenergy basis.

@@ -248,12 +246,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`e^{i\\varphi}` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`e^{i\\varphi}` in the energy eigenbasis.
If `False` (default), returns operator :math:`e^{i\varphi}` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`e^{i\varphi}` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`e^{i\\varphi}` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`e^{i\varphi}` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`e^{i\\varphi}` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`e^{i\\varphi}` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`e^{i\\varphi}` has dimensions of m x m,
Operator :math:`e^{i\varphi}` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`e^{i\varphi}` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`e^{i\varphi}` has dimensions of m x m,
for m given eigenvectors.

@@ -269,4 +267,4 @@ """

) -> ndarray:
"""
Returns operator :math:`\\cos \\varphi` in the charge or eigenenergy basis.
r"""
Returns operator :math:`\cos \varphi` in the charge or eigenenergy basis.

@@ -276,12 +274,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`\\cos \\varphi` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\\cos \\varphi` in the energy eigenbasis.
If `False` (default), returns operator :math:`\cos \varphi` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\cos \varphi` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`\\cos \\varphi` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`\cos \varphi` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\cos \\varphi` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\cos \\varphi` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\cos \\varphi` has dimensions of m x m,
Operator :math:`\cos \varphi` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\cos \varphi` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\cos \varphi` has dimensions of m x m,
for m given eigenvectors.

@@ -296,4 +294,4 @@ """

) -> ndarray:
"""
Returns operator :math:`\\sin \\varphi` in the charge or eigenenergy basis.
r"""
Returns operator :math:`\sin \varphi` in the charge or eigenenergy basis.

@@ -303,12 +301,12 @@ Parameters

energy_esys:
If `False` (default), returns operator :math:`\\sin \\varphi` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\\sin \\varphi` in the energy eigenbasis.
If `False` (default), returns operator :math:`\sin \varphi` in the charge basis.
If `True`, the energy eigenspectrum is computed, returns operator :math:`\sin \varphi` in the energy eigenbasis.
If `energy_esys = esys`, where esys is a tuple containing two ndarrays (eigenvalues and energy eigenvectors),
returns operator :math:`\\sin \\varphi` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
returns operator :math:`\sin \varphi` in the energy eigenbasis, and does not have to recalculate eigenspectrum.
Returns
-------
Operator :math:`\\sin \\varphi` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\\sin \\varphi` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\\sin \\varphi` has dimensions of m x m,
Operator :math:`\sin \varphi` in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless energy_esys is specified, :math:`\sin \varphi` has dimensions of truncated_dim
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, :math:`\sin \varphi` has dimensions of m x m,
for m given eigenvectors.

@@ -323,4 +321,3 @@ """

) -> ndarray:
"""
Returns Hamiltonian in the charge or eigenenergy basis.
"""Returns Hamiltonian in the charge or eigenenergy basis.

@@ -338,3 +335,3 @@ Parameters

Hamiltonian in chosen basis as ndarray. For `energy_esys=False`, the Hamiltonian has dimensions of
`truncated_dim` x `truncated_dim`. For `energy_sys=esys`, the Hamiltonian has dimensions of m x m,
:attr:`truncated_dim` x :attr:`truncated_dim`. For `energy_sys=esys`, the Hamiltonian has dimensions of m x m,
for m given eigenvectors.

@@ -359,5 +356,4 @@ """

) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
charge offset `ng` in the charge or eigenenergy basis.
"""Returns operator representing a derivative of the Hamiltonian with respect to
charge offset :attr:`ng` in the charge or eigenenergy basis.

@@ -375,4 +371,4 @@ Parameters

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -386,4 +382,3 @@ """

) -> ndarray:
"""
Returns operator representing a derivative of the Hamiltonian with respect to
"""Returns operator representing a derivative of the Hamiltonian with respect to
EJ in the charge or eigenenergy basis.

@@ -402,4 +397,4 @@

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -411,7 +406,7 @@ """

def hilbertdim(self) -> int:
"""Returns Hilbert space dimension"""
"""Returns Hilbert space dimension."""
return 2 * self.ncut + 1
def potential(self, phi: Union[float, ndarray]) -> ndarray:
"""Transmon phase-basis potential evaluated at `phi`.
r"""Transmon phase-basis potential evaluated at :math:`\phi`.

@@ -431,5 +426,5 @@ Parameters

nrange: Tuple[int, int] = None,
**kwargs
**kwargs,
) -> Tuple[Figure, Axes]:
"""Plots transmon wave function in charge basis
"""Plots transmon wave function in charge basis.

@@ -441,7 +436,7 @@ Parameters

mode:
`'abs_sqr', 'abs', 'real', 'imag'`
choices as specified in `constants.MODE_FUNC_DICT` (default value = 'real')
which:
index or indices of wave functions to plot (default value = 0)
index or indices of wave functions to plot (default value = 0)
nrange:
range of `n` to be included on the x-axis (default value = (-5,6))
range of `n` to be included on the x-axis (default value = (-5,6))
**kwargs:

@@ -468,5 +463,5 @@ plotting parameters

scaling: float = None,
**kwargs
**kwargs,
) -> Tuple[Figure, Axes]:
"""Alias for plot_wavefunction"""
"""Alias for plot_wavefunction."""
return self.plot_wavefunction(

@@ -478,3 +473,3 @@ esys=esys,

scaling=scaling,
**kwargs
**kwargs,
)

@@ -627,7 +622,6 @@

dense form in the number basis, :math:`H_\text{CPB}=4E_\text{C}(\hat{
n}-n_g)^2-\frac{\mathcal{E}_\text{J}(\Phi)}{2}(|n\rangle\langle n+1|+\text{
h.c.})`, Here, the effective Josephson energy is flux-tunable: :math:`\mathcal{
E}_J(\Phi) = E_{J,\text{max}} \sqrt{\cos^2(\pi\Phi/\Phi_0) + d^2 \sin^2(
\pi\Phi/\Phi_0)}` and :math:`d=(E_{J2}-E_{J1})(E_{J1}+E_{J2})` parametrizes the
junction asymmetry.
n}-n_g)^2-\frac{\mathcal{E}_\text{J}(\Phi)}{2}(|n\rangle\langle n+1|+\text{ h.c.})`,
Here, the effective Josephson energy is flux-tunable: :math:`\mathcal{ E}_J(\Phi) =
E_{J,\text{max}} \sqrt{\cos^2(\pi\Phi/\Phi_0) + d^2 \sin^2( \pi\Phi/\Phi_0)}` and
:math:`d=(E_{J2}-E_{J1})(E_{J1}+E_{J2})` parametrizes the junction asymmetry.

@@ -656,3 +650,3 @@ Initialize with, for example::

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.

@@ -708,4 +702,4 @@ esys_method:

def EJ(self) -> float: # type: ignore
"""This is the effective, flux dependent Josephson energy, playing the role
of EJ in the parent class `Transmon`"""
"""This is the effective, flux dependent Josephson energy, playing the role of
EJ in the parent class :class:`Transmon`"""
return self.EJmax * np.sqrt(

@@ -729,3 +723,3 @@ np.cos(np.pi * self.flux) ** 2 + self.d**2 * np.sin(np.pi * self.flux) ** 2

def supported_noise_channels(cls) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
return [

@@ -744,3 +738,3 @@ "tphi_1_over_f_flux",

r"""Returns operator representing a derivative of the Hamiltonian with respect to
`flux` in the charge or eigenenergy basis.
:attr:`flux` in the charge or eigenenergy basis.

@@ -764,4 +758,4 @@ Here, the derivative is taken with respect to flux before the qubit's :math:`\phi` degree of

Operator in chosen basis as ndarray. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, operator has dimensions of m x m,
for m given eigenvectors.

@@ -768,0 +762,0 @@ """

@@ -45,3 +45,4 @@ # units.py

"""The set_units function is used to set the system units for all qubit instances.
The default unit system is GHz, but this can be changed by calling `set_units()` with one of the `_supported_units`
The default unit system is GHz, but this can be changed by calling `set_units()`
with one of the `_supported_units`

@@ -84,3 +85,3 @@ Parameters

def get_units_time_label(units: Optional[str] = None) -> str:
"""Get a latex representation of 1/units"""
"""Get a LaTeX representation of 1/units"""
units = units or _current_units

@@ -104,4 +105,4 @@ if units not in _supported_units:

r"""
Converts `value` (a frequency or angular frequency) from currently set system units,
to standard units (Hz or 2pi/s).
Converts `value` (a frequency or angular frequency) from currently set system units
to standard units (Hz or 2pi/s).

@@ -121,5 +122,4 @@ Parameters

def from_standard_units(value: float) -> float:
r"""
Converts `value` (a frequency or angular frequency) from standard units
(`[Hz]` or `2\pi / [s]`) to currently set system units.
r"""Converts `value` (a frequency or angular frequency) from standard units (`[Hz]`
or `2\pi / [s]`) to currently set system units.

@@ -130,3 +130,3 @@ Parameters

a frequency or angular frequency assumed to be in standard units
(`[Hz]` or `2\pi / [s]`)
(`[Hz]` or `2\pi / [s]`)

@@ -136,3 +136,2 @@ Returns

frequency or angular frequency converted to system units
"""

@@ -143,5 +142,6 @@ return value / _units_factor[_current_units]

def units_scale_factor(units: Optional[str] = None) -> float:
"""The units_scale_factor function returns a numerical scaling factor that converts from Hz to the `units` given as
a string argument. If no argument is given, the current units stored in `_current_units` are used. If the units are
not supported, a `ValueError` is raised.
"""The units_scale_factor function returns a numerical scaling factor that converts
from Hz to the `units` given as a string argument. If no argument is given, the
current units stored in `_current_units` are used. If the units are not supported, a
`ValueError` is raised.

@@ -148,0 +148,0 @@ Parameters

@@ -43,3 +43,3 @@ # zeropi_full.py

class FullZeroPi(base.QubitBaseClass, serializers.Serializable, NoisyFullZeroPi):
r"""Zero-Pi qubit [Brooks2013]_ [Dempster2014]_ including coupling to the zeta mode.
r"""Zero-Pi qubit [Brooks2013]_ [Dempster2014]_ including coupling to the :math:`\zeta` mode.
The circuit is described by the Hamiltonian

@@ -62,4 +62,4 @@ :math:`H = H_{0-\pi} + H_\text{int} + H_\zeta`, where

:math:`dC`, :math:`dE_\text{L}` follows [Groszkowski2018]_. Internally,
the ``FullZeroPi`` class formulates the Hamiltonian matrix via the
product basis of the decoupled Zero-Pi qubit (see ``ZeroPi``) on one hand, and the
the `FullZeroPi` class formulates the Hamiltonian matrix via the
product basis of the decoupled Zero-Pi qubit (see :class:`ZeroPi`) on one hand, and the
zeta LC oscillator on the other hand.

@@ -104,12 +104,12 @@

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.
esys_method:
method for esys diagonalization, callable or string representation
esys_method_options:
dictionary with esys diagonalization options
evals_method:
method for evals diagonalization, callable or string representation
evals_method_options:
dictionary with evals diagonalization options
esys_method:
method for esys diagonalization, callable or string representation
esys_method_options:
dictionary with esys diagonalization options
evals_method:
method for evals diagonalization, callable or string representation
evals_method_options:
dictionary with evals diagonalization options
"""

@@ -244,3 +244,3 @@

def supported_noise_channels(cls) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
return [

@@ -295,3 +295,3 @@ "tphi_1_over_f_cc",

def set_EC_via_ECS(self, ECS: float) -> None:
"""Helper function to set `EC` by providing `ECS`, keeping `ECJ` constant."""
"""Helper function to set :attr:`EC` by providing `ECS`, keeping `ECJ` constant."""
self._zeropi.set_EC_via_ECS(ECS)

@@ -301,3 +301,3 @@

def E_zeta(self) -> float:
"""Returns energy quantum of the zeta mode"""
"""Returns energy quantum of the zeta mode."""
return (8.0 * self.EL * self.EC) ** 0.5

@@ -307,4 +307,4 @@

raise ValueError(
"Cannot directly set `E_zeta`. Instead one can set its value through `EL`"
" or `EC`."
"Cannot directly set `E_zeta`. Instead one can set its value through :attr:`EL`"
" or :attr:`EC`."
)

@@ -350,3 +350,4 @@

r"""Returns Hamiltonian in basis obtained by discretizing :math:`\phi`, employing
charge basis for :math:`\theta`, and Fock basis for :math:`\zeta`, or in the eigenenergy basis.
charge basis for :math:`\theta`, and Fock basis for :math:`\zeta`, or in the
eigenenergy basis.

@@ -367,6 +368,5 @@ Parameters

Hamiltonian in chosen basis as csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, the Hamiltonian has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
unless `energy_esys` is specified, the Hamiltonian has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
for m given eigenvectors.
"""

@@ -423,5 +423,5 @@ zeropi_dim = self.zeropi_cutoff

) -> Union[ndarray, csc_matrix]:
r"""
Calculates a derivative of the Hamiltonian w.r.t flux, at the current value of flux,
as stored in the object. The returned operator is in the product basis or eigenenergy basis.
r"""Calculates a derivative of the Hamiltonian w.r.t flux, at the current value
of flux, as stored in the object. The returned operator is in the product basis
or eigenenergy basis.

@@ -450,3 +450,3 @@ Helper method _zeropi_operator_in_product_basis is employed which converts

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -465,5 +465,4 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Calculates a derivative of the Hamiltonian w.r.t EJ. The returned operator is in the
product basis or eigenenergy basis.
r"""Calculates a derivative of the Hamiltonian w.r.t EJ. The returned operator is
in the product basis or eigenenergy basis.

@@ -492,3 +491,3 @@ Helper method _zeropi_operator_in_product_basis is employed which converts

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -505,6 +504,5 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Calculates a derivative of the Hamiltonian w.r.t ng.
Returns matrix representing a derivative of the Hamiltonian in the native Hamiltonian basis
or eigenenergy basis.
r"""Calculates a derivative of the Hamiltonian w.r.t ng. Returns matrix
representing a derivative of the Hamiltonian in the native Hamiltonian basis or
eigenenergy basis.

@@ -523,3 +521,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -566,4 +564,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`i d/d\phi` operator in the product or eigenenergy basis.
r"""Returns :math:`i d/d\phi` operator in the product or eigenenergy basis.

@@ -592,3 +589,3 @@ Helper method _zeropi_operator_in_product_basis is employed which converts

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -607,4 +604,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`n_\theta` operator in the product or eigenenergy basis.
r"""Returns :math:`n_\theta` operator in the product or eigenenergy basis.

@@ -633,3 +629,3 @@ Helper method _zeropi_operator_in_product_basis is employed which converts

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -648,4 +644,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`\phi` operator in the product or eigenenergy basis.
r"""Returns :math:`\phi` operator in the product or eigenenergy basis.

@@ -674,3 +669,3 @@ Helper method _zeropi_operator_in_product_basis is employed which converts

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -685,3 +680,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

def hilbertdim(self) -> int:
"""Returns Hilbert space dimension
"""Returns Hilbert space dimension.

@@ -724,3 +719,3 @@ Returns

def g_phi_coupling_matrix(self, zeropi_states: ndarray) -> ndarray:
"""Returns a matrix of coupling strengths g^\\phi_{ll'}
r"""Returns a matrix of coupling strengths :math:`g^\phi_{ll'}`
[cmp. Dempster et al., Eq. (18)], using the states from the list

@@ -736,3 +731,3 @@ `zeropi_states`. Most commonly, `zeropi_states` will contain eigenvectors of the

def g_theta_coupling_matrix(self, zeropi_states: ndarray) -> ndarray:
"""Returns a matrix of coupling strengths i*g^\\theta_{ll'}
r"""Returns a matrix of coupling strengths :math:`ig^\theta_{ll'}`
[cmp. Dempster et al., Eq. (17)], using the states from the list

@@ -749,3 +744,3 @@ 'zeropi_states'.

) -> ndarray:
"""Returns a matrix of coupling strengths g_{ll'} [cmp. Dempster et al., text
r"""Returns a matrix of coupling strengths :math:`g_{ll'}` [cmp. Dempster et al., text
above Eq. (17)], using the states from 'zeropi_states'. If

@@ -752,0 +747,0 @@ `zeropi_states==None`, then a set of `self.zeropi` eigenstates is calculated.

@@ -51,3 +51,3 @@ # zeropi.py

class ZeroPi(base.QubitBaseClass, serializers.Serializable, NoisyZeroPi):
r"""Zero-Pi Qubit
r"""Zero-Pi Qubit.

@@ -58,3 +58,3 @@ | [1] Brooks et al., Physical Review A, 87(5), 052306 (2013). http://doi.org/10.1103/PhysRevA.87.052306

Zero-Pi qubit without coupling to the `zeta` mode, i.e., no disorder in `EC` and
Zero-Pi qubit without coupling to the :math:`\zeta` mode, i.e., no disorder in `EC` and
`EL`, see Eq. (4) in Groszkowski et al., New J. Phys. 20, 043053 (2018),

@@ -69,4 +69,4 @@

Formulation of the Hamiltonian matrix proceeds by discretization of the `phi`
variable, and using charge basis for the `theta` variable.
Formulation of the Hamiltonian matrix proceeds by discretization of the :math:`\phi`
variable, and using charge basis for the :math:`\theta` variable.

@@ -95,3 +95,3 @@ Parameters

ncut:
charge number cutoff for `n_theta`, `n_theta = -ncut, ..., ncut`
charge number cutoff for :math:`n_\theta`, :math:`n_\theta = -{\rm ncut}, \dots, {\rm ncut}`
ECS:

@@ -103,13 +103,13 @@ total charging energy including large shunting capacitances and junction

id_str:
optional string by which this instance can be referred to in `HilbertSpace`
optional string by which this instance can be referred to in :class:`HilbertSpace`
and `ParameterSweep`. If not provided, an id is auto-generated.
esys_method:
method for esys diagonalization, callable or string representation
esys_method_options:
dictionary with esys diagonalization options
evals_method:
method for evals diagonalization, callable or string representation
evals_method_options:
dictionary with evals diagonalization options
"""
esys_method:
method for esys diagonalization, callable or string representation
esys_method_options:
dictionary with esys diagonalization options
evals_method:
method for evals diagonalization, callable or string representation
evals_method_options:
dictionary with evals diagonalization options
"""

@@ -208,3 +208,3 @@ EJ = descriptors.WatchedProperty(float, "QUANTUMSYSTEM_UPDATE")

def supported_noise_channels(cls) -> List[str]:
"""Return a list of supported noise channels"""
"""Return a list of supported noise channels."""
return [

@@ -279,7 +279,7 @@ "tphi_1_over_f_cc",

def set_EC_via_ECS(self, ECS: float) -> None:
"""Helper function to set `EC` by providing `ECS`, keeping `ECJ` constant."""
"""Helper function to set :attr:`EC` by providing `ECS`, keeping `ECJ` constant."""
self.EC = 1 / (1 / ECS - 1 / self.ECJ)
def hilbertdim(self) -> int:
"""Returns Hilbert space dimension"""
"""Returns Hilbert space dimension."""
return self.grid.pt_count * (2 * self.ncut + 1)

@@ -291,3 +291,3 @@

-------
value of the potential energy evaluated at phi, theta
value of the potential energy evaluated at :math:`\phi`, :math:`\theta`
"""

@@ -305,4 +305,3 @@ return (

def sparse_kinetic_mat(self) -> csc_matrix:
"""
Kinetic energy portion of the Hamiltonian.
"""Kinetic energy portion of the Hamiltonian.

@@ -343,4 +342,3 @@ Returns

def sparse_potential_mat(self) -> csc_matrix:
"""
Potential energy portion of the Hamiltonian.
"""Potential energy portion of the Hamiltonian.

@@ -398,6 +396,5 @@ Returns

) -> csc_matrix:
r"""
Calculates Hamiltonian in basis obtained by discretizing :math:`\phi` and employing
charge basis for :math:`\theta` or in the eigenenergy basis. Returns matrix representing
the potential energy operator.
r"""Calculates Hamiltonian in basis obtained by discretizing :math:`\phi` and
employing charge basis for :math:`\theta` or in the eigenenergy basis. Returns
matrix representing the potential energy operator.

@@ -416,4 +413,4 @@ Parameters

Hamiltonian in chosen basis as csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, the Hamiltonian has dimensions of `truncated_dim`
x `truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
unless `energy_esys` is specified, the Hamiltonian has dimensions of :attr:`truncated_dim`
x :attr:`truncated_dim`. Otherwise, if eigenenergy basis is chosen, Hamiltonian has dimensions of m x m,
for m given eigenvectors.

@@ -427,9 +424,8 @@ """

def sparse_d_potential_d_flux_mat(self) -> csc_matrix:
r"""
Calculates a derivative of the potential energy w.r.t flux, at the current value of
flux, as stored in the object.
r"""Calculates a derivative of the potential energy w.r.t flux, at the current
value of flux, as stored in the object.
The flux is assumed to be given in the units of the ratio \Phi_{ext}/\Phi_0.
So if \frac{\partial U}{ \partial \Phi_{\rm ext}}, is needed, the expr returned
by this function, needs to be multiplied by 1/\Phi_0.
The flux is assumed to be given in the units of the ratio :math:`\Phi_{\rm ext}/\Phi_0`.
So if :math:`\frac{\partial U}{\partial \Phi_{\rm ext}}`, is needed, the expr returned
by this function, needs to be multiplied by :math:`1/\Phi_0`.

@@ -455,8 +451,6 @@ Returns

) -> Union[ndarray, csc_matrix]:
r"""
Calculates a derivative of the Hamiltonian w.r.t flux, at the current value
r"""Calculates a derivative of the Hamiltonian w.r.t flux, at the current value
of flux, as stored in the object. The flux is assumed to be given in the units
of the ratio :math:`\Phi_{ext}/\Phi_0`.
Returns matrix representing a derivative of the Hamiltonian in the native Hamiltonian basis
or eigenenergy basis.
of the ratio :math:`\Phi_{ext}/\Phi_0`. Returns matrix representing a derivative
of the Hamiltonian in the native Hamiltonian basis or eigenenergy basis.

@@ -475,3 +469,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -499,6 +493,5 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Calculates a derivative of the Hamiltonian w.r.t EJ.
Returns matrix representing a derivative of the Hamiltonian in the native Hamiltonian basis
or eigenenergy basis.
r"""Calculates a derivative of the Hamiltonian w.r.t EJ. Returns matrix
representing a derivative of the Hamiltonian in the native Hamiltonian basis or
eigenenergy basis.

@@ -517,3 +510,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -528,6 +521,5 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Calculates a derivative of the Hamiltonian w.r.t ng as stored in the object.
Returns matrix representing a derivative of the Hamiltonian in the native Hamiltonian basis
or eigenenergy basis.
r"""Calculates a derivative of the Hamiltonian w.r.t ng as stored in the object.
Returns matrix representing a derivative of the Hamiltonian in the native
Hamiltonian basis or eigenenergy basis.

@@ -546,3 +538,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -556,3 +548,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

r"""
Identity operator acting only on the `\phi` Hilbert subspace.
Identity operator acting only on the :math:`\phi` Hilbert subspace.
"""

@@ -564,3 +556,3 @@ pt_count = self.grid.pt_count

r"""
Identity operator acting only on the `\theta` Hilbert subspace.
Identity operator acting only on the :math:`\theta` Hilbert subspace.
"""

@@ -571,5 +563,3 @@ dim_theta = 2 * self.ncut + 1

def i_d_dphi_operator(self) -> csc_matrix:
r"""
Operator :math:`i d/d\phi`.
"""
r"""Operator :math:`i d/d\phi`."""
return sparse.kron(

@@ -583,3 +573,3 @@ self.grid.first_derivative_matrix(prefactor=1j),

r"""
Operator :math:`\phi`, acting only on the `\phi` Hilbert subspace.
Operator :math:`\phi`, acting only on the :math:`\phi` Hilbert subspace.
"""

@@ -596,4 +586,3 @@ pt_count = self.grid.pt_count

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`\phi` operator in the native or eigenenergy basis.
r"""Returns :math:`\phi` operator in the native or eigenenergy basis.

@@ -612,3 +601,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -623,4 +612,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`n_\theta` operator in the native or eigenenergy basis.
r"""Returns :math:`n_\theta` operator in the native or eigenenergy basis.

@@ -639,3 +627,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -654,3 +642,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

r"""
Operator :math:`\sin(\phi + x)`, acting only on the `\phi` Hilbert subspace.x
Operator :math:`\sin(\phi + x)`, acting only on the :math:`\phi` Hilbert subspace.
"""

@@ -667,3 +655,3 @@ pt_count = self.grid.pt_count

r"""
Operator :math:`\cos(\phi + x)`, acting only on the `\phi` Hilbert subspace.
Operator :math:`\cos(\phi + x)`, acting only on the :math:`\phi` Hilbert subspace.
"""

@@ -680,3 +668,3 @@ pt_count = self.grid.pt_count

r"""
Operator :math:`\cos(\theta)`, acting only on the `\theta` Hilbert subspace.
Operator :math:`\cos(\theta)`, acting only on the :math:`\theta` Hilbert subspace.
"""

@@ -700,4 +688,3 @@ dim_theta = 2 * self.ncut + 1

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`\cos(\theta)` operator in the native or eigenenergy basis.
r"""Returns :math:`\cos(\theta)` operator in the native or eigenenergy basis.

@@ -716,3 +703,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -728,3 +715,3 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

r"""
Operator :math:`\sin(\theta)`, acting only on the `\theta` Hilbert space.
Operator :math:`\sin(\theta)`, acting only on the :math:`\theta` Hilbert space.
"""

@@ -749,4 +736,3 @@ dim_theta = 2 * self.ncut + 1

) -> Union[ndarray, csc_matrix]:
r"""
Returns :math:`\sin(\theta)` operator in the native or eigenenergy basis.
r"""Returns :math:`\sin(\theta)` operator in the native or eigenenergy basis.

@@ -765,3 +751,3 @@ Parameters

returned as a csc_matrix. If the eigenenergy basis is chosen,
unless `energy_esys` is specified, operator has dimensions of `truncated_dim`
unless `energy_esys` is specified, operator has dimensions of :attr:`truncated_dim`
x truncated_dim, and is returned as an ndarray. Otherwise, if eigenenergy basis is chosen,

@@ -779,5 +765,5 @@ operator has dimensions of m x m, for m given eigenvectors, and is returned as an ndarray.

contour_vals: Union[List[float], ndarray] = None,
**kwargs
**kwargs,
) -> Tuple[Figure, Axes]:
"""Draw contour plot of the potential energy.
r"""Draw contour plot of the potential energy.

@@ -787,3 +773,3 @@ Parameters

theta_grid:
used for setting a custom grid for theta; if None use self._default_grid
used for setting a custom grid for :math:`\theta`; if None use self._default_grid
contour_vals:

@@ -804,3 +790,3 @@ **kwargs:

ylabel=r"$\theta$",
**kwargs
**kwargs,
)

@@ -814,3 +800,3 @@

) -> WaveFunctionOnGrid:
"""Returns a zero-pi wave function in `phi`, `theta` basis
r"""Returns a zero-pi wave function in :math:`\phi,\theta` basis

@@ -824,3 +810,3 @@ Parameters

theta_grid:
used for setting a custom grid for theta; if None use self._default_grid
used for setting a custom grid for :math:`\theta`; if None use self._default_grid
"""

@@ -862,3 +848,3 @@ evals_count = max(which + 1, 3)

zero_calibrate: bool = True,
**kwargs
**kwargs,
) -> Tuple[Figure, Axes]:

@@ -872,3 +858,3 @@ """Plots 2d phase-basis wave function.

which:
index of wave function to be plotted (default value = (0)
index of wave function to be plotted (default value = 0)
theta_grid:

@@ -878,3 +864,3 @@ used for setting a custom grid for theta; if None use self._default_grid

choices as specified in `constants.MODE_FUNC_DICT`
(default value = 'abs_sqr')
(default value = 'abs')
zero_calibrate:

@@ -896,3 +882,3 @@ if True, colors are adjusted to use zero wavefunction amplitude as the

ylabel=r"$\theta$",
**kwargs
**kwargs,
)

@@ -158,3 +158,3 @@ # explorer_panels.py

shape=(paramvals_count, subsys.truncated_dim, subsys.truncated_dim),
dtype=np.complex_,
dtype=np.complex128,
)

@@ -161,0 +161,0 @@ for index, paramval in enumerate(param_vals):

@@ -46,4 +46,3 @@ # explorer_settings.py

class ExplorerSettings:
"""
Generates the UI for Explorer settings.
"""Generates the UI for Explorer settings.

@@ -50,0 +49,0 @@ Parameters

@@ -64,4 +64,7 @@ # explorer_widget.py

class PlotID:
"""Class for storing plot identifiers. Used for plot panel selection."""
"""Class for storing plot identifiers.
Used for plot panel selection.
"""
SEP = " | "

@@ -96,4 +99,3 @@

class Explorer:
"""
Generates the UI for exploring `ParameterSweep` objects.
"""Generates the UI for exploring `ParameterSweep` objects.

@@ -503,3 +505,4 @@ Parameters

def active_switches_by_plot_id(self) -> Dict[PlotID, "ui.LinkedSwitch"]:
"""Returns a dictionary labeling all selected switches by their plot_id names."""
"""Returns a dictionary labeling all selected switches by their plot_id
names."""
return {

@@ -513,9 +516,9 @@ plot_id: switch

def selected_plot_id_list(self) -> List[PlotID]:
"""Returns a list of strings capturing the names of all panels selected via
the switches."""
"""Returns a list of strings capturing the names of all panels selected via the
switches."""
return list(self.active_switches_by_plot_id.keys())
def create_sliders(self) -> Dict[str, "v.VuetifyWidget"]:
"""Returns a list of selection sliders, one for each parameter that is part
of the underlying ParameterSweep object."""
"""Returns a list of selection sliders, one for each parameter that is part of
the underlying ParameterSweep object."""
slider_by_name = {

@@ -522,0 +525,0 @@ param_name: ui.DiscreteSetSlider(

@@ -12,5 +12,3 @@ # fileio_backends.py

############################################################################
"""
Helper routines for writing data to h5 files.
"""
"""Helper routines for writing data to h5 files."""

@@ -44,4 +42,3 @@ import ast

class IOWriter(ABC):
"""
ABC for writing class instance data to file.
"""ABC for writing class instance data to file.

@@ -78,11 +75,10 @@ Parameters

class H5Writer(IOWriter):
"""Writes IOData to a custom-format h5 file"""
"""Writes IOData to a custom-format h5 file."""
def write_attributes(self, h5file_group: Union["Group", "File"]) -> None: # type: ignore
"""
Attribute data consists of
"""Attribute data consists of.
1. `__init__` parameters that are of type str or numerical. These are
directly written into `h5py.Group.attrs` 2. lists are stored under
`<h5py.Group>/__lists` 3. dicts are stored under `<h5py.Group>/__dicts`
1. `__init__` parameters that are of type str or numerical. These are
directly written into `h5py.Group.attrs` 2. lists are stored under
`<h5py.Group>/__lists` 3. dicts are stored under `<h5py.Group>/__dicts`
"""

@@ -112,6 +108,4 @@ h5file_group.attrs.create(

def write_ndarrays(self, h5file_group: Union["Group", "File"]) -> None: # type: ignore
"""
Writes ndarray (float or complex) data contained in `self.iodata` to the
provided `h5py.Group` as a `h5py.Dataset`, using gzip compression.
"""
"""Writes ndarray (float or complex) data contained in `self.iodata` to the
provided `h5py.Group` as a `h5py.Dataset`, using gzip compression."""
data_group = h5file_group.file.require_group("__data")

@@ -127,7 +121,5 @@ for name, array in self.io_data.ndarrays.items():

def write_objects(self, h5file_group: Union["Group", "File"]) -> None: # type: ignore
"""
Writes data representing a Python object other than ndarray, list and dict,
"""Writes data representing a Python object other than ndarray, list and dict,
contained in `self.iodata` to the provided `h5py.Group` und
`<h5py.Group>/__objects`.
"""
`<h5py.Group>/__objects`."""
h5file_group = h5file_group.create_group("__objects")

@@ -141,6 +133,4 @@ for obj_name in self.io_data.objects.keys():

def to_file(self, io_data: io.IOData, file_handle: "Group" = None) -> None:
"""
Takes the serialized IOData and writes it either to a new h5 file with file
name given by `self.filename` to to the given h5py.Group of an open h5 file.
"""
"""Takes the serialized IOData and writes it either to a new h5 file with file
name given by `self.filename` to to the given h5py.Group of an open h5 file."""
self.io_data = io_data

@@ -164,5 +154,3 @@ if file_handle is None:

class H5Reader:
"""
Enables reading h5 files generated with scqubits.
"""
"""Enables reading h5 files generated with scqubits."""

@@ -178,4 +166,3 @@ def __init__(self, filename: str, file_handle: "Group" = None) -> None:

) -> Dict[str, Union[float, str, int]]:
"""
Converts h5 attribute data to a Python dictionary.
"""Converts h5 attribute data to a Python dictionary.

@@ -190,6 +177,5 @@ Parameters

def read_attributes(self, h5file_group: Union["Group", "File"]) -> Dict[str, Any]:
"""
Read data from h5 file group that is stored directly as `<h5py.Group>.attrs`,
or saved in subgroups titled `<h5py.Group>/__lists` and `<h5py.Group>/__dicts`.
"""
"""Read data from h5 file group that is stored directly as `<h5py.Group>.attrs`,
or saved in subgroups titled `<h5py.Group>/__lists` and
`<h5py.Group>/__dicts`."""
attributes = self.h5_attrs_to_dict(h5file_group.attrs)

@@ -209,5 +195,3 @@ if "__dicts" in h5file_group:

def read_ndarrays(self, h5file_group: Union["Group", "File"]) -> Dict[str, ndarray]:
"""
Read numpy array data from h5 file group.
"""
"""Read numpy array data from h5 file group."""
ndarrays = {}

@@ -235,6 +219,4 @@

) -> Dict[str, io.IOData]:
"""
Read data from the given h5 file group that represents a Python object other
than an ndarray, list, or dict.
"""
"""Read data from the given h5 file group that represents a Python object other
than an ndarray, list, or dict."""
inner_objects = {}

@@ -247,7 +229,8 @@ h5file_group = h5file_group["__objects"]

def from_file(self, filename: str, file_handle: "Group" = None) -> io.IOData:
"""Either opens a new h5 file for reading or accesses an already opened file via
the given h5.Group handle.
Reads all data from the three categories of attributes (incl. lists and dicts),
ndarrays, and objects.
"""
Either opens a new h5 file for reading or accesses an already opened file via
the given h5.Group handle. Reads all data from the three categories of
attributes (incl. lists and dicts), ndarrays, and objects.
"""
if file_handle is None:

@@ -268,11 +251,9 @@ h5file_group = h5py.File(filename, "r", rdcc_nbytes=1024**2 * 200)

class CSVWriter(IOWriter):
"""
Given `filename="somename.csv"`, write initdata into somename.csv Then, additional
csv files are written for each dataset, with filenames: `"somename_" + dataname0 +
".csv"` etc.
"""
"""Given `filename="somename.csv"`, write initdata into somename.csv Then,
additional csv files are written for each dataset, with filenames: `"somename_" +
dataname0 + ".csv"` etc."""
def append_ndarray_info(self, attributes):
"""Add data set information to attributes, so that dataset names and
dimensions are available in attributes CSV file."""
"""Add data set information to attributes, so that dataset names and dimensions
are available in attributes CSV file."""
for index, dataname in enumerate(self.io_data.ndarrays.keys()):

@@ -356,3 +337,3 @@ data = self.io_data.ndarrays[dataname]

except ValueError:
data_array = np.loadtxt(filename, dtype=np.complex_)
data_array = np.loadtxt(filename, dtype=np.complex128)
if slices > 1:

@@ -386,4 +367,3 @@ nrows, ncols = data_array.shape

def np_savetxt_3d(array3d: ndarray, filename: str):
"""
Helper function that splits a 3d numpy array into 2d slices for writing as csv
"""Helper function that splits a 3d numpy array into 2d slices for writing as csv
data to a new file. Slices are separated by a comment row `# New slice`.

@@ -390,0 +370,0 @@

@@ -22,11 +22,9 @@ # fileio_qutip.py

class QutipEigenstates(np.ndarray, Serializable):
"""Wrapper class that adds serialization functionality to the numpy
ndarray class."""
"""Wrapper class that adds serialization functionality to the numpy ndarray
class."""
@classmethod
def deserialize(cls, io_data: IOData) -> np.ndarray: # type:ignore
"""
Take the given IOData and return an instance of the described class, initialized
with the data stored in io_data.
"""
"""Take the given IOData and return an instance of the described class,
initialized with the data stored in io_data."""
# Qobj in Qutip>=5 wants this to be a nested list

@@ -44,5 +42,3 @@ qobj_dims = io_data.ndarrays["qobj_dims"].tolist()

def serialize(self) -> IOData:
"""
Convert the content of the current class instance into IOData format.
"""
"""Convert the content of the current class instance into IOData format."""
import scqubits.io_utils.fileio as io

@@ -65,6 +61,9 @@

def filewrite(self, filename: str):
"""Convenience method bound to the class. Simply accesses the
`write` function."""
"""Convenience method bound to the class.
Simply accesses the
`write` function.
"""
import scqubits.io_utils.fileio as io
io.write(self, filename)

@@ -12,5 +12,3 @@ # fileio_serializers.py

############################################################################
"""
Helper classes for writing data to files.
"""
"""Helper classes for writing data to files."""

@@ -50,4 +48,7 @@ import inspect

def __new__(cls: Type[SerializableType], *args, **kwargs) -> SerializableType:
"""Modified `__new__` to set up `cls._init_params`. The latter is used to
record which of the `__init__` parameters are to be stored/read in file IO."""
"""Modified `__new__` to set up `cls._init_params`.
The latter is used to
record which of the `__init__` parameters are to be stored/read in file IO.
"""
cls._init_params = get_init_params(cls)

@@ -66,12 +67,8 @@ return super().__new__(cls)

def deserialize(cls: Type[SerializableType], io_data: "IOData") -> SerializableType:
"""
Take the given IOData and return an instance of the described class,
initialized with the data stored in io_data.
"""
"""Take the given IOData and return an instance of the described class,
initialized with the data stored in io_data."""
return cls(**io_data.as_kwargs())
def serialize(self) -> "IOData":
"""
Convert the content of the current class instance into IOData format.
"""
"""Convert the content of the current class instance into IOData format."""
initdata = {name: getattr(self, name) for name in self._init_params}

@@ -85,4 +82,7 @@ if hasattr(self, "_id_str"):

def filewrite(self, filename: str) -> None:
"""Convenience method bound to the class. Simply accesses the `write`
function."""
"""Convenience method bound to the class.
Simply accesses the `write`
function.
"""
import scqubits.io_utils.fileio as io

@@ -157,6 +157,4 @@

def type_dispatch(entity: Serializable) -> Callable:
"""
Based on the type of the object ``entity``, return the appropriate function that
converts the entity into the appropriate category of IOData
"""
"""Based on the type of the object ``entity``, return the appropriate function that
converts the entity into the appropriate category of IOData."""
if isinstance(entity, TO_ATTRIBUTE):

@@ -177,5 +175,3 @@ return _add_attribute

def Expr_serialize(expr_instance: Expr) -> "IOData":
"""
Create an IODate instance for a sympy expression via string conversion
"""
"""Create an IODate instance for a sympy expression via string conversion."""
import scqubits.io_utils.fileio as io

@@ -196,5 +192,3 @@

def dict_serialize(dict_instance: Dict[str, Any]) -> "IOData":
"""
Create an IOData instance from dictionary data.
"""
"""Create an IOData instance from dictionary data."""
import scqubits.io_utils.fileio as io

@@ -217,5 +211,3 @@

def OrderedDict_serialize(dict_instance: Dict[str, Any]) -> "IOData":
"""
Create an IOData instance from dictionary data.
"""
"""Create an IOData instance from dictionary data."""
import scqubits.io_utils.fileio as io

@@ -240,5 +232,3 @@

def csc_matrix_serialize(csc_matrix_instance: csc_matrix) -> "IOData":
"""
Create an IOData instance from dictionary data.
"""
"""Create an IOData instance from dictionary data."""
import scqubits.io_utils.fileio as io

@@ -267,5 +257,3 @@

def NoneType_serialize(none_instance: None) -> "IOData":
"""
Create an IOData instance to write `None` to file.
"""
"""Create an IOData instance to write `None` to file."""
import scqubits.io_utils.fileio as io

@@ -282,5 +270,3 @@

def listlike_serialize(listlike_instance: Union[List, Tuple]) -> "IOData":
"""
Create an IOData instance from list data.
"""
"""Create an IOData instance from list data."""
import scqubits.io_utils.fileio as io

@@ -310,5 +296,3 @@

def range_serialize(range_instance: range) -> "IOData":
"""
Create an IOData instance from range data.
"""
"""Create an IOData instance from range data."""
import scqubits.io_utils.fileio as io

@@ -328,3 +312,3 @@

def Expr_deserialize(iodata: "IOData") -> Expr:
"""Turn IOData instance back into a dict"""
"""Turn IOData instance back into a dict."""
from sympy import sympify

@@ -336,3 +320,3 @@

def dict_deserialize(iodata: "IOData") -> Dict[str, Any]:
"""Turn IOData instance back into a dict"""
"""Turn IOData instance back into a dict."""
return dict(**iodata.as_kwargs())

@@ -342,3 +326,3 @@

def OrderedDict_deserialize(iodata: "IOData") -> Dict[str, Any]:
"""Turn IOData instance back into a dict"""
"""Turn IOData instance back into a dict."""
dict_data = iodata.as_kwargs()

@@ -349,3 +333,3 @@ return OrderedDict([dict_data[key] for key in sorted(dict_data, key=int)])

def csc_matrix_deserialize(iodata: "IOData") -> csc_matrix:
"""Turn IOData instance back into a csc_matrix"""
"""Turn IOData instance back into a csc_matrix."""
csc_dict = dict(**iodata.as_kwargs())

@@ -359,3 +343,3 @@ return csc_matrix(

def NoneType_deserialize(iodata: "IOData") -> None:
"""Turn IOData instance back into a csc_matrix"""
"""Turn IOData instance back into a csc_matrix."""
return None

@@ -365,3 +349,3 @@

def list_deserialize(iodata: "IOData") -> List[Any]:
"""Turn IOData instance back into a list"""
"""Turn IOData instance back into a list."""
dict_data = iodata.as_kwargs()

@@ -372,3 +356,3 @@ return [dict_data[key] for key in sorted(dict_data, key=int)]

def tuple_deserialize(iodata: "IOData") -> Tuple:
"""Turn IOData instance back into a tuple"""
"""Turn IOData instance back into a tuple."""
return tuple(list_deserialize(iodata))

@@ -393,6 +377,4 @@

def get_init_params(obj: Serializable) -> List[str]:
"""
Returns a list of the parameters entering the `__init__` method of the given
object `obj`.
"""
"""Returns a list of the parameters entering the `__init__` method of the given
object `obj`."""
init_params = list(inspect.signature(obj.__init__).parameters.keys()) # type: ignore

@@ -399,0 +381,0 @@ if "self" in init_params:

@@ -12,5 +12,3 @@ # fileio.py

############################################################################
"""
Helper routines for writing data to files.
"""
"""Helper routines for writing data to files."""

@@ -34,5 +32,3 @@ import os

class IOData:
"""
Class for processing input/output data
"""
"""Class for processing input/output data."""

@@ -53,3 +49,3 @@ def __init__(

"""Return a joint dictionary of attributes, ndarrays, and objects, as used in
__init__ calls"""
__init__ calls."""
return {**self.attributes, **self.ndarrays, **self.objects}

@@ -59,5 +55,4 @@

def serialize(the_object: "Serializable") -> IOData:
"""
Turn the given Python object into an IOData object, needed for writing data to file.
"""
"""Turn the given Python object into an IOData object, needed for writing data to
file."""
if hasattr(the_object, "serialize"):

@@ -77,4 +72,4 @@ return the_object.serialize()

def deserialize(iodata: IOData) -> Any:
"""
Turn IOData back into a Python object of the appropriate kind.
"""Turn IOData back into a Python object of the appropriate kind.
An object is deemed deserializable if

@@ -99,4 +94,3 @@ 1) it is recorded in SERIALIZABLE_REGISTRY and has a `.deserialize` method

def write(the_object: Any, filename: str, file_handle: "h5py.Group" = None) -> None:
"""
Write `the_object` to a file with name `filename`. The optional `file_handle`
"""Write `the_object` to a file with name `filename`. The optional `file_handle`
parameter is used as a group name in case of h5 files.

@@ -119,4 +113,3 @@

def read(filename: str, file_handle: "h5py.Group" = None) -> Any:
"""
Read a Serializable object from file.
"""Read a Serializable object from file.

@@ -140,3 +133,3 @@ Parameters

class FileIOFactory:
"""Factory method for choosing reader/writer according to given format"""
"""Factory method for choosing reader/writer according to given format."""

@@ -146,6 +139,4 @@ def get_writer(

) -> "IOWriter":
"""
Based on the extension of the provided file name, return the appropriate
writer engine.
"""
"""Based on the extension of the provided file name, return the appropriate
writer engine."""
import scqubits.io_utils.fileio_backends as io_backends

@@ -169,6 +160,4 @@

) -> Union["CSVReader", "H5Reader"]:
"""
Based on the extension of the provided file name, return the appropriate
reader engine.
"""
"""Based on the extension of the provided file name, return the appropriate
reader engine."""
if get_external_reader:

@@ -175,0 +164,0 @@ return get_external_reader(file_name, file_handle=file_handle)

# Marker file for PEP 561.

@@ -0,0 +0,0 @@ """

@@ -19,6 +19,4 @@ # testing.py

def run():
"""
Run the pytest scripts for scqubits.
"""
"""Run the pytest scripts for scqubits."""
# runs tests in scqubits.tests directory
pytest.main(["-v", TESTDIR])

@@ -72,3 +72,3 @@ # conftest.py --- for use with pytest

class BaseTest:
"""Used as base class for the pytests of qubit classes"""
"""Used as base class for the pytests of qubit classes."""

@@ -79,3 +79,3 @@ qbt = None # class instance of qubit to be tested

def set_tmpdir(self, request):
"""Pytest fixture that provides a temporary directory for writing test files"""
"""Pytest fixture that provides a temporary directory for writing test files."""
setattr(self, "tmpdir", request.getfixturevalue("tmpdir"))

@@ -82,0 +82,0 @@

@@ -0,0 +0,0 @@ branches:

@@ -0,0 +0,0 @@ branches:

@@ -0,0 +0,0 @@ # zero-pi

@@ -0,0 +0,0 @@ # test_centraldispatch.py

@@ -24,5 +24,5 @@ import os

system_hierarchy=system_hierarchy,
subsystem_trunc_dims=[100, 30],
subsystem_trunc_dims=[10, 10],
)
circ.update()
esys = circ.eigensys()

@@ -33,3 +33,6 @@

def test_plot_wf():
circ.plot_wavefunction(which=0, var_indices=(2, 3))
circ.plot_wavefunction(which=0, var_indices=(2, 3), esys=esys)
circ.subsystems[0].plot_wavefunction(which=0, var_indices=(1, 3), mode="abs")
circ.subsystems[0].plot_wavefunction(which=0, var_indices=(1, 3), mode="real")
circ.subsystems[0].plot_wavefunction(which=0, var_indices=(1, 3), mode="imag")

@@ -36,0 +39,0 @@ @staticmethod

@@ -16,3 +16,5 @@ # test_circuit.py

import numpy as np
import qutip as qt
import pytest
from scqubits.io_utils.fileio import read

@@ -50,5 +52,3 @@ import scqubits as scq

def test_zero_pi_discretized():
"""
Test for symmetric zero-pi in discretized phi basis.
"""
"""Test for symmetric zero-pi in discretized phi basis."""
zp_yaml = """# zero-pi circuit

@@ -67,6 +67,5 @@ branches:

circ_d.cutoff_ext_2 = 30
circ_d.cutoff_ext_3 = 80
circ_d.cutoff_ext_3 = 200
circ_d.configure(system_hierarchy=[[1, 3], [2]], subsystem_trunc_dims=[30, 20])
circ_d.cutoff_ext_3 = 200
sym_zp = circ_d.subsystems[0]

@@ -90,5 +89,3 @@ eigensys = sym_zp.eigensys()

def test_circuit_with_symbolic_hamiltonian():
"""
Test for initiating Circuit module with symbolic Hamiltonian.
"""
"""Test for initiating Circuit module with symbolic Hamiltonian."""
import sympy as sm

@@ -184,3 +181,2 @@

DFC.cutoff_ext_4 = 110
DFC.update()

@@ -207,3 +203,2 @@ eigs = DFC.eigenvals()

circ.EJ = 0.01
circ.update()
eigs_ref = np.array(

@@ -252,3 +247,3 @@ [

system_hierarchy = [[[1], [3]], [2], [4]]
subsystem_trunc_dims = [[34, [6, 6]], 6, 6]
subsystem_trunc_dims = [[6, [6, 6]], 6, 6]

@@ -265,8 +260,7 @@ DFC.configure(

DFC.cutoff_ext_1 = 110
DFC.cutoff_ext_2 = 110
DFC.cutoff_ext_3 = 110
DFC.cutoff_ext_4 = 110
DFC.update()
DFC.get_spectrum_vs_paramvals("Φ1", np.linspace(0, 1, 11), num_cpus=num_cpus)
DFC.cutoff_ext_1 = 40
DFC.cutoff_ext_2 = 40
DFC.cutoff_ext_3 = 40
DFC.cutoff_ext_4 = 40
DFC.get_spectrum_vs_paramvals("Φ1", np.linspace(0, 1, 3), num_cpus=num_cpus)

@@ -282,3 +276,2 @@ paramvals_by_name = {

DFC.Φ2 = Φ2
DFC.update()

@@ -292,1 +285,67 @@ ps = scq.ParameterSweep(

)
@staticmethod
def test_qutip_dynamics(num_cpus):
# Let's start by defining a fluxonium
inp_yaml = """
branches:
- [JJ, 1, 2, 4, 0.5]
- [L, 1, 2, 1.3]
- [C, 1, 2, 2]
"""
circ = scq.Circuit(
inp_yaml,
from_file=False,
use_dynamic_flux_grouping=True,
ext_basis="discretized",
)
circ.cutoff_ext_1 = 100
circ.Φ1 = 0.5
# defining Hierarchical diagonalization to limit to the lowest two states
circ.configure(system_hierarchy=[[1]], subsystem_trunc_dims=[10])
# Define time dependent functions for the parameters
def flux(t, args):
freq = args["freq"]
return 0.001 * np.sin(2 * np.pi * freq * t) + 0.5
# to charge drive the fluxonium, we need an extra parameter ng1. This can be added using extra_terms
def charge(t, args):
freq = args["freq"]
return 0.02 * np.sin(2 * np.pi * freq * t + np.pi / 2)
# Generating necessary operators and time dependent coefficients
H_mesolve, *H_sym_ref = circ.hamiltonian_for_qutip_dynamics(
free_var_func_dict={"Φ1": flux, "ng1": charge},
extra_terms="Q1*ng1",
prefactor=np.pi * 2,
)
# H_mesolve can be used to evolve the system using qutip functions like mesolve
# ground state as initial state
eigs, evecs = circ.eigensys(evals_count=5)
wf0 = qt.Qobj(evecs[:, 0])
initial_state_proj = wf0 * wf0.dag() # to see the overlap
tf = 100 # final time in nanoseconds
freq = eigs[1] - eigs[0] # transition frequency between the first two states
# time evolve the system
result = qt.mesolve(
H_mesolve,
wf0,
np.linspace(0, tf, 500),
args={"freq": freq},
e_ops=[initial_state_proj],
options=dict(atol=1e-12),
)
expectation_vals = result.expect[0]
ref_expectation_vals = np.empty_like(expectation_vals)
ref_expectation_vals[:] = read(DATADIR + "/circuit_qutip_evolution_data.hdf5")[
:
]
assert np.allclose(
expectation_vals,
ref_expectation_vals,
)

@@ -0,0 +0,0 @@ # test_cos2phi_qubit.py

@@ -137,3 +137,4 @@ # test_diag.py

def test_custom_diagonalization_evals_method_matches_default(library):
"""Test custom diagonalization gives the same eigenvalues as using the default method."""
"""Test custom diagonalization gives the same eigenvalues as using the default
method."""

@@ -180,3 +181,4 @@ if library == "jax":

def test_custom_diagonalization_matches_default_with_composite_systems(library):
"""Test custom diagonalization gives same eigenvalues as using the default method for composite systems."""
"""Test custom diagonalization gives same eigenvalues as using the default method
for composite systems."""

@@ -223,3 +225,4 @@ if library == "jax":

def test_custom_diagonalization_matches_default_using_custom_procedure():
"""Test custom diagonalization gives same result as using the default method when using a custom procedure."""
"""Test custom diagonalization gives same result as using the default method when
using a custom procedure."""

@@ -226,0 +229,0 @@ def custom_esys(matrix, evals_count, **kwargs):

@@ -0,0 +0,0 @@ # test_explorer.py

@@ -0,0 +0,0 @@ # test_fluxonium.py

@@ -0,0 +0,0 @@ # test_fluxqubit.py

@@ -0,0 +0,0 @@ # test_fullzeropi.py

@@ -0,0 +0,0 @@ # test_gui.py

@@ -479,1 +479,119 @@ # test_hilbertspace.py

assert op1 == op2
def test_HilbertSpace_labeling(self):
hspace = self.hilbertspace_initialize2()
reference_indices = np.array(
[
0,
3,
7,
15,
1,
5,
12,
22,
2,
6,
14,
24,
13,
23,
32,
40,
4,
10,
18,
28,
8,
16,
25,
33,
9,
17,
27,
35,
26,
34,
41,
45,
11,
21,
31,
39,
19,
29,
36,
42,
20,
30,
38,
44,
37,
43,
46,
47,
],
dtype=int,
)
reference_indices_BE10 = np.array(
[
0,
3,
7,
None,
1,
5,
None,
None,
2,
6,
None,
None,
None,
None,
None,
None,
4,
None,
None,
None,
8,
None,
None,
None,
9,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
None,
],
dtype=object,
)
hspace.generate_lookup(ordering="LX", subsys_priority=[2, 1, 0])
assert np.all(hspace["dressed_indices"][0].astype(int) == reference_indices)
hspace.generate_lookup(ordering="BE", subsys_priority=[2, 1, 0], BEs_count=10)
assert np.all(hspace["dressed_indices"][0] == reference_indices_BE10)
hspace.generate_lookup(ordering="DE")
assert np.all(hspace["dressed_indices"][0].astype(int) == reference_indices)

@@ -0,0 +0,0 @@ # test_namedslotsndarray.py

@@ -0,0 +0,0 @@ # test_noise.py

@@ -54,4 +54,4 @@ # test_parameters.py

def test_paravals_list():
def test_paramvals_list():
tst = Parameters(paramvals_by_name)
assert tst.paramvals_list == [paramvals1, paramvals2]

@@ -29,3 +29,3 @@ # test_parametersweep.py

def initialize(self, num_cpus):
def initialize(self, num_cpus, evals_count=20):
# Set up the components / subspaces of our Hilbert space

@@ -97,3 +97,3 @@ scq.settings.MULTIPROC = "pathos"

update_hilbertspace=update_hilbertspace,
evals_count=20,
evals_count=evals_count,
subsys_update_info=subsys_update_info,

@@ -139,1 +139,66 @@ num_cpus=num_cpus,

sweep_copy = scq.read(self.tmpdir + "test.h5")
def test_ParameterSweep_labeling(self, num_cpus):
sweep = self.initialize(num_cpus, evals_count=36)
# DE ordering is already generated by default
# we compare 9th state only
ref_indices_DE = np.array(
[
[8, 8, 8],
[8, 8, 8],
[8, 8, 8],
[11, 11, 11],
[9, 9, 9],
[8, 8, 8],
[6, 6, 6],
[11, 11, 11],
[None, None, None],
[9, 9, 9],
[7, 7, 7],
],
dtype=object,
)
assert np.all(sweep["dressed_indices"][..., 9] == ref_indices_DE)
# BE ordering
indices_BE10 = sweep.generate_lookup(
ordering="BE", subsys_priority=[2, 1, 0], BEs_count=10
)
ref_indices_BE10 = np.array(
[
[8, 8, 8],
[8, 8, 8],
[8, 8, 8],
[None, None, None],
[9, 9, 9],
[8, 8, 8],
[6, 6, 6],
[None, None, None],
[None, None, None],
[9, 9, 9],
[7, 7, 7],
],
dtype=object,
)
assert np.all(indices_BE10[..., 9] == ref_indices_BE10)
# LX ordering
indices_LX = sweep.generate_lookup(ordering="LX", subsys_priority=[2, 1, 0])
ref_indices_LX = np.array(
[
[8, 8, 8],
[8, 8, 8],
[8, 8, 8],
[11, 11, 11],
[9, 9, 9],
[8, 8, 8],
[6, 6, 6],
[11, 11, 11],
[11, 11, 11],
[9, 9, 9],
[7, 7, 7],
],
dtype=object,
)
assert np.all(indices_LX[..., 9] == ref_indices_LX)

@@ -0,0 +0,0 @@ # test_spectrumlookup.py

@@ -0,0 +0,0 @@ # test_transmon.py

@@ -0,0 +0,0 @@ # test_units.py

@@ -0,0 +0,0 @@ # test_zeropi.py

@@ -0,0 +0,0 @@ # This file is part of scqubits: a Python package for superconducting qubits,

@@ -120,4 +120,6 @@ # gui_custom_widgets.py

class NumberEntryWidget(ValidatedNumberField):
"""A widget consisting of a text field and a slider, linked to each other. The text field acts as the main
class, while the slider is stored as a class attribute and displayed alongside.
"""A widget consisting of a text field and a slider, linked to each other.
The text field acts as the main class, while the slider is stored as a class
attribute and displayed alongside.
"""

@@ -414,3 +416,4 @@

def setup_card(self, new_panel: ClosablePanel):
"""Sets up a new card, connecting its close button to the card collection's `close_card` method."""
"""Sets up a new card, connecting its close button to the card collection's
`close_card` method."""
card = new_panel.card

@@ -469,4 +472,4 @@ card.id = new_panel.panel_id

def change_cols(self, ncols: int):
"""
Changes the number of columns in the grid by setting `explorer.cols` and resizing all widgets in the grid.
"""Changes the number of columns in the grid by setting `explorer.cols` and
resizing all widgets in the grid.

@@ -477,3 +480,2 @@ Parameters

number of columns in the grid
"""

@@ -484,7 +486,5 @@ self.ncols = ncols

def show(self):
"""
The show function is the main function of a component. It returns
a VDOM object that will be rendered in the browser. The show function
is called every time an event occurs, and it's return value is used to
update the DOM.
"""The show function is the main function of a component. It returns a VDOM
object that will be rendered in the browser. The show function is called
every time an event occurs, and it's return value is used to update the DOM.

@@ -491,0 +491,0 @@ Returns

@@ -0,0 +0,0 @@ # gui_defaults.py

@@ -0,0 +0,0 @@ # gui_navbar.py

@@ -65,6 +65,6 @@ # gui_setup.py

def init_noise_param_floattextfield(noise_param: str) -> ui.ValidatedNumberField:
"""
Creates a `ValidatedNumberField` widget for each noise parameter.
The function takes one argument, the name of the noise parameter, and returns a `ValidatedNumberField` widget set to
the current value of that noise parameter in the `NOISE_PARAMS` dictionary.
"""Creates a `ValidatedNumberField` widget for each noise parameter. The function
takes one argument, the name of the noise parameter, and returns a
`ValidatedNumberField` widget set to the current value of that noise parameter in
the `NOISE_PARAMS` dictionary.

@@ -207,3 +207,3 @@ Parameters

def init_dict_v_noise_params(active_qubit) -> Dict[str, v.VuetifyWidget]:
"""Creates all the widgets associated with coherence times plots"""
"""Creates all the widgets associated with coherence times plots."""
dict_v_noise_params = {}

@@ -239,5 +239,3 @@ noise_params = ["T", "omega_low", "omega_high", "t_exp"]

) -> Dict[str, v.VuetifyWidget]:
"""Creates all the widgets associated with the parameters of the
chosen qubit.
"""
"""Creates all the widgets associated with the parameters of the chosen qubit."""
dict_v_qubit_params = {}

@@ -301,5 +299,4 @@

) -> Dict[str, Any]:
"""Creates all the widgets associated with changing the ranges of
certain qubit plot options as well as all of the qubit's parameters.
"""
"""Creates all the widgets associated with changing the ranges of certain qubit plot
options as well as all of the qubit's parameters."""
dict_v_ranges = {}

@@ -306,0 +303,0 @@ total_dict = {

@@ -53,4 +53,4 @@ # hspace_widget.py

class HilbertSpaceUi:
"""Class for setup and display of the widget used for creation of a
HilbertSpace object."""
"""Class for setup and display of the widget used for creation of a HilbertSpace
object."""

@@ -585,5 +585,4 @@ @utils.Required(ipyvuetify=_HAS_IPYVUETIFY)

def create_hilbertspace_widget(callback_func):
"""
Display ipywidgets interface for creating a HilbertSpace object. Typically,
this function will be called by `HilbertSpace.create()``.
"""Display ipywidgets interface for creating a HilbertSpace object. Typically, this
function will be called by `HilbertSpace.create()``.

@@ -590,0 +589,0 @@ Parameters

@@ -42,4 +42,3 @@ # qubit_widget.py

) -> None:
"""
Displays ipyvuetify for initialization of a QuantumSystem object.
"""Displays ipyvuetify for initialization of a QuantumSystem object.

@@ -46,0 +45,0 @@ Parameters

@@ -0,0 +0,0 @@ # This file is part of scqubits: a Python package for superconducting qubits,

@@ -19,4 +19,3 @@ # cpu_switch.py

def get_map_method(num_cpus: int) -> Callable:
"""
Selects the correct `.map` method depending on the specified number of desired
"""Selects the correct `.map` method depending on the specified number of desired
cores. If num_cpus>1, the multiprocessing/pathos pool is started here.

@@ -23,0 +22,0 @@

@@ -62,4 +62,3 @@ # misc.py

def make_bare_labels(subsystem_count: int, *args) -> Tuple[int, ...]:
"""
For two given subsystem states, return the full-system bare state label obtained
"""For two given subsystem states, return the full-system bare state label obtained
by placing all remaining subsys_list in their ground states.

@@ -87,3 +86,3 @@

def drop_private_keys(full_dict: Dict[str, Any]) -> Dict[str, Any]:
"""Filter for entries in the full dictionary that have numerical values"""
"""Filter for entries in the full dictionary that have numerical values."""
return {key: value for key, value in full_dict.items() if key[0] != "_"}

@@ -191,7 +190,14 @@

def wrapper(self, *args, **kwargs):
if self.is_child and self._out_of_sync_with_parent:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
warnings.warn(
"[scqubits] Some quantum system parameters have been changed and"
" generated circuit data could be outdated, potentially leading to"
" incorrect results. Circuit data can be refreshed via"
" <Circuit>.generate_circuit()",
Warning,
)
# update the circuit if necessary
if (self._user_changed_parameter) or (
self.hierarchical_diagonalization
and (self._out_of_sync or len(self.affected_subsystem_indices) > 0)
):
if self.hierarchical_diagonalization:
self.update()

@@ -269,9 +275,7 @@ return func(self, *args, **kwargs)

def get_shape(lst, shape=()):
"""
returns the shape of nested lists similarly to numpy's shape.
"""Returns the shape of nested lists similarly to numpy's shape.
:param lst: the nested list
:param shape: the shape up to the current recursion depth
:return: the shape including the current depth
(finally this will be the full depth)
:return: the shape including the current depth (finally this will be the full depth)
"""

@@ -328,5 +332,4 @@ if not isinstance(lst, Sequence):

def about(print_info=True) -> Optional[str]:
"""Prints or returns a string with basic information about
scqubits as well as installed version of various packages
that scqubits depends on.
"""Prints or returns a string with basic information about scqubits as well as
installed version of various packages that scqubits depends on.

@@ -370,4 +373,3 @@ Parameters

def cite(print_info=True):
"""Prints or returns a string with scqubits citation
information.
"""Prints or returns a string with scqubits citation information.

@@ -383,3 +385,2 @@ Parameters

None or str
"""

@@ -420,4 +421,3 @@ fs = StringIO()

def flatten_list(nested_list):
"""
Flattens a list of lists once, not recursive.
"""Flattens a list of lists once, not recursive.

@@ -438,4 +438,3 @@ Parameters

def flatten_list_recursive(some_list: list) -> list:
"""
Flattens a list of lists recursively.
"""Flattens a list of lists recursively.

@@ -461,4 +460,3 @@ Parameters

def unique_elements_in_list(list_object: list) -> list:
"""
Returns a list of all the unique elements in the list
"""Returns a list of all the unique elements in the list.

@@ -480,5 +478,4 @@ Parameters

def number_of_lists_in_list(list_object: list) -> int:
"""
Takes a list as an argument and returns the number of lists in that list. (Counts lists at root level only, no
recursion.)
"""Takes a list as an argument and returns the number of lists in that list. (Counts
lists at root level only, no recursion.)

@@ -502,4 +499,3 @@ Parameters

) -> List[str]:
"""
Find all public names in a module.
"""Find all public names in a module.

@@ -506,0 +502,0 @@ Parameters

@@ -49,4 +49,3 @@ # plot_defaults.py

) -> float:
"""
Sets the scaling parameter for 1d wavefunctions
"""Sets the scaling parameter for 1d wavefunctions.

@@ -132,3 +131,4 @@ Returns

mode:
amplitude modifier, needed to give the correct default y label"""
amplitude modifier, needed to give the correct default y label
"""
ylabel = r"$\psi_j(n)$"

@@ -141,3 +141,3 @@ if mode:

def wavefunction2d() -> Dict[str, Any]:
"""Plot defaults for plotting.wavefunction2d"""
"""Plot defaults for plotting.wavefunction2d."""
return {"figsize": (8, 3)}

@@ -149,3 +149,3 @@

) -> Dict[str, Any]:
"""Plot defaults for plotting.contours"""
"""Plot defaults for plotting.contours."""
aspect_ratio = (y_vals[-1] - y_vals[0]) / (x_vals[-1] - x_vals[0])

@@ -157,3 +157,3 @@ figsize = (8, 8 * aspect_ratio)

def matrix() -> Dict[str, Any]:
"""Plot defaults for plotting.matrix"""
"""Plot defaults for plotting.matrix."""
return {"figsize": (10, 5)}

@@ -163,3 +163,3 @@

def evals_vs_paramvals(specdata: "SpectrumData", **kwargs) -> Dict[str, Any]:
"""Plot defaults for plotting.evals_vs_paramvals"""
"""Plot defaults for plotting.evals_vs_paramvals."""
kwargs["xlabel"] = kwargs.get("xlabel") or recast_name(specdata.param_name)

@@ -173,3 +173,3 @@ kwargs["ylabel"] = kwargs.get("ylabel") or "energy [{}]".format(units.get_units())

) -> Dict[str, Any]:
"""Plot defaults for plotting.matelem_vs_paramvals"""
"""Plot defaults for plotting.matelem_vs_paramvals."""
return {"xlabel": recast_name(specdata.param_name), "ylabel": "matrix element"}

@@ -179,3 +179,3 @@

def chi(param_name: Union[str, None], **kwargs) -> Dict[str, Any]:
"""Plot defaults for sweep_plotting.chi"""
"""Plot defaults for sweep_plotting.chi."""
kwargs["xlabel"] = kwargs.get("xlabel") or recast_name(param_name)

@@ -189,3 +189,3 @@ kwargs["ylabel"] = kwargs.get("ylabel") or r"$\chi_j$ [{}]".format(

def chi01(param_name: Union[str, None], yval: float, **kwargs) -> Dict[str, Any]:
"""Plot defaults for sweep_plotting.chi01"""
"""Plot defaults for sweep_plotting.chi01."""
kwargs["xlabel"] = kwargs.get("xlabel") or recast_name(param_name)

@@ -202,3 +202,3 @@ kwargs["ylabel"] = kwargs.get("ylabel") or r"$\chi_{{01}}$ [{}]".format(

def charge_matrixelem(param_name: str, **kwargs) -> Dict[str, Any]:
"""Plot defaults for sweep_plotting.charge_matrixelem"""
"""Plot defaults for sweep_plotting.charge_matrixelem."""
kwargs["xlabel"] = kwargs.get("xlabel") or recast_name(param_name)

@@ -205,0 +205,0 @@ kwargs["ylabel"] = kwargs.get("ylabel") or r"$|\langle i |n| j \rangle|$"

@@ -56,4 +56,3 @@ # plot_utils.py

) -> Dict[str, Any]:
"""
Select options from kwargs for a given plot_type and return them in a dictionary.
"""Select options from kwargs for a given plot_type and return them in a dictionary.

@@ -72,3 +71,2 @@ Parameters

dictionary with key/value pairs corresponding to selected options from kwargs
"""

@@ -91,4 +89,3 @@ direct_plot_options = direct_plot_options or _direct_plot_options

) -> None:
"""
Processes plotting options.
"""Processes plotting options.

@@ -137,4 +134,3 @@ Parameters

"""Processes a single 'special' option, i.e., one internal to scqubits and not to be
handed further down to matplotlib.
"""
handed further down to matplotlib."""
if key == "ymax":

@@ -141,0 +137,0 @@ ymax = value

@@ -57,4 +57,3 @@ # plotting.py

) -> Tuple[Figure, Axes]:
"""
Plots the amplitude of a single real-valued 1d wave function, along with the
"""Plots the amplitude of a single real-valued 1d wave function, along with the
potential energy if provided.

@@ -101,4 +100,3 @@

) -> Tuple[Figure, Axes]:
"""
Plots the amplitude of a single real-valued 1d wave function, along with the
"""Plots the amplitude of a single real-valued 1d wave function, along with the
potential energy if provided.

@@ -137,4 +135,3 @@

def wavefunction1d_discrete(wavefunc: "WaveFunction", **kwargs) -> Tuple[Figure, Axes]:
"""
Plots the amplitude of a real-valued 1d wave function in a discrete basis.
"""Plots the amplitude of a real-valued 1d wave function in a discrete basis.
(Example: transmon in the charge basis.)

@@ -170,4 +167,3 @@

) -> Tuple[Figure, Axes]:
"""
Creates a density plot of the amplitude of a real-valued wave function in 2
"""Creates a density plot of the amplitude of a real-valued wave function in 2
"spatial" dimensions.

@@ -278,4 +274,3 @@

) -> Tuple[Figure, Tuple[Axes, Axes]]:
"""
Create a "skyscraper" plot and a 2d color-coded plot of a matrix.
"""Create a "skyscraper" plot and a 2d color-coded plot of a matrix.

@@ -318,3 +313,3 @@ Parameters

) -> Tuple[Figure, Axes]:
"""Display a 3d skyscraper plot of the matrix
"""Display a 3d skyscraper plot of the matrix.

@@ -465,4 +460,4 @@ Parameters

) -> Tuple[Figure, Axes]:
"""Plot of a set of ydata vs xdata.
The individual points correspond to the a provided array of parameter values.
"""Plot of a set of ydata vs xdata. The individual points correspond to the a
provided array of parameter values.

@@ -572,4 +567,4 @@ Parameters

) -> Tuple[Figure, Axes]:
"""Generates a simple plot of matrix elements as a function of one parameter.
The individual points correspond to the a provided array of parameter values.
"""Generates a simple plot of matrix elements as a function of one parameter. The
individual points correspond to the a provided array of parameter values.

@@ -576,0 +571,0 @@ Parameters

@@ -42,3 +42,4 @@ # spectrum_utils.py

2. Test for degenerate eigenvalues. If there are any, need to orthogonalize the
eigenvectors properly."""
eigenvectors properly.
"""
mat_size = args[0].shape[0]

@@ -92,4 +93,4 @@ kwargs["v0"] = settings.RANDOM_ARRAY[:mat_size]

not specified, the `position` is set as follows. Find the maximum between the
leftmost point and the halfway point of the wavefunction. The position of that
point is used to determine the phase factor to be eliminated.
leftmost point and the halfway point of the wavefunction. The position of that point
is used to determine the phase factor to be eliminated.

@@ -113,4 +114,4 @@ Parameters

def standardize_phases(complex_array: np.ndarray) -> np.ndarray:
"""Uses `extract_phase` to obtain global phase from `array` and returns
standardized array with global phase factor standardized.
"""Uses `extract_phase` to obtain global phase from `array` and returns standardized
array with global phase factor standardized.

@@ -132,5 +133,5 @@ Parameters

Summing up to the midpoint only is to address the danger that the sum is
actually zero, which may be the case for odd wavefunctions taken over an interval
centered at zero.
Summing up to the midpoint only is to address the danger that the sum is actually
zero, which may be the case for odd wavefunctions taken over an interval centered at
zero.
"""

@@ -268,5 +269,7 @@ halfway_position = len(real_array) // 2

"""Takes spectral data of energy eigenvalues and returns the absorption spectrum
relative to a state of given index. Calculated by subtracting from eigenenergies
the energy of the select state. Resulting negative frequencies, if the reference
state is not the ground state, are omitted.
relative to a state of given index.
Calculated by subtracting from eigenenergies the energy of the select state.
Resulting negative frequencies, if the reference state is not the ground state, are
omitted.
"""

@@ -280,6 +283,7 @@ assert isinstance(spectrum_data.energy_table, ndarray)

"""Takes spectral data of energy eigenvalues and returns the emission spectrum
relative to a state of given index. The resulting "upwards" transition
frequencies are calculated by subtracting from eigenenergies the energy of the
select state, and multiplying the result by -1. Resulting negative frequencies,
corresponding to absorption instead, are omitted.
relative to a state of given index.
The resulting "upwards" transition frequencies are calculated by subtracting from
eigenenergies the energy of the select state, and multiplying the result by -1.
Resulting negative frequencies, corresponding to absorption instead, are omitted.
"""

@@ -293,4 +297,4 @@ assert isinstance(spectrum_data.energy_table, ndarray)

def convert_evecs_to_ndarray(evecs_qutip: ndarray) -> np.ndarray:
"""Takes a qutip eigenstates array, as obtained with .eigenstates(), and converts
it into a pure numpy array.
"""Takes a qutip eigenstates array, as obtained with .eigenstates(), and converts it
into a pure numpy array.

@@ -350,8 +354,9 @@ Parameters

if isinstance(operator, qt.Qobj):
return operator
if isinstance(operator, (np.ndarray, csc_matrix, csr_matrix, dia_matrix)):
return convert_matrix_to_qobj(operator, subsystem, op_in_eigenbasis, evecs)
operator = Qobj_to_scipy_csc_matrix(operator)
if isinstance(operator, str):
return convert_opstring_to_qobj(operator, subsystem, evecs)
raise TypeError("Unsupported operator type: ", type(operator))
elif isinstance(operator, (np.ndarray, csc_matrix, csr_matrix, dia_matrix)):
return convert_matrix_to_qobj(operator, subsystem, op_in_eigenbasis, evecs)
else:
raise TypeError("Unsupported operator type: ", type(operator))

@@ -362,8 +367,8 @@

) -> List[Tuple[int, ...]]:
"""Based on a bare state label (i1, i2, ...) with i1 being the excitation level
of subsystem 1, i2 the excitation level of subsystem 2 etc., generate a list of
new bare state labels. These bare state labels correspond to target states
reached from the given initial one by single-photon qubit transitions. These are
transitions where one of the qubit excitation levels increases at a time. There
are no changes in oscillator photon numbers.
"""Based on a bare state label (i1, i2, ...) with i1 being the excitation level of
subsystem 1, i2 the excitation level of subsystem 2 etc., generate a list of new
bare state labels. These bare state labels correspond to target states reached from
the given initial one by single-photon qubit transitions. These are transitions
where one of the qubit excitation levels increases at a time. There are no changes
in oscillator photon numbers.

@@ -394,5 +399,4 @@ Parameters

) -> Tuple[np.ndarray, List[np.ndarray]]:
"""
Takes data generated by a map of eigensystem calls and returns the eigenvalue and
eigenstate tables
"""Takes data generated by a map of eigensystem calls and returns the eigenvalue and
eigenstate tables.

@@ -418,8 +422,7 @@ Returns

) -> Qobj:
"""Takes the `operator` belonging to `subsystem` and "wraps" it in identities.
The full Hilbert space is taken to consist of all subsystems given as
`subsys_list`. `subsystem` must be one element in that list. For each of the
other subsystems in the list, an identity operator of the correct dimension is
generated and inserted into the appropriate Kronecker product "sandwiching" the
operator.
"""Takes the `operator` belonging to `subsystem` and "wraps" it in identities. The
full Hilbert space is taken to consist of all subsystems given as `subsys_list`.
`subsystem` must be one element in that list. For each of the other subsystems in
the list, an identity operator of the correct dimension is generated and inserted
into the appropriate Kronecker product "sandwiching" the operator.

@@ -437,4 +440,3 @@ Parameters

whether `operator` is given in the `subsystem` eigenbasis; otherwise,
`operator` is assumed to be in the internal QuantumSystem basis. This
argument is ignored if `operator` is given as a Qobj.
`operator` is assumed to be in the internal QuantumSystem basis.
evecs:

@@ -447,11 +449,7 @@ internal `QuantumSystem` eigenstates, used to convert `operator` into eigenbasis

operator is expressed in the bare product basis consisting of the energy
eigenstates of each subsystem (unless `operator` is provided as a `Qobj`,
in which case no conversion takes place).
eigenstates of each subsystem.
"""
# checking if operator is not qt.Qobj, as callable(qt.Qobj) returns True.
if not isinstance(operator, qt.Qobj) and callable(operator):
try:
operator = operator(energy_esys=(None, evecs))
except TypeError:
operator = operator()
op_in_eigenbasis = True
operator = operator()

@@ -458,0 +456,0 @@ subsys_operator = convert_operator_to_qobj(

@@ -0,0 +0,0 @@ # typedefs.py

@@ -1,4 +0,16 @@

# THIS FILE IS GENERATED FROM scqubits SETUP.PY
short_version = '4.2.0'
version = '4.2.0'
release = True
# file generated by setuptools_scm
# don't change, don't track in version control
TYPE_CHECKING = False
if TYPE_CHECKING:
from typing import Tuple, Union
VERSION_TUPLE = Tuple[Union[int, str], ...]
else:
VERSION_TUPLE = object
version: str
__version__: str
__version_tuple__: VERSION_TUPLE
version_tuple: VERSION_TUPLE
__version__ = version = '4.3'
__version_tuple__ = version_tuple = (4, 3)

@@ -0,0 +0,0 @@ [egg_info]

h5py>=2.10
pytest
ipywidgets
ipyvuetify
traitlets
typing_extensions
numpy>=1.14.2
scipy>=1.5,<=1.13.1 ; sys_platform == 'darwin' and python_version >= '3.10'
scipy>=1.5 ; sys_platform == 'darwin' and python_version < '3.10'
scipy>=1.5 ; sys_platform != 'darwin'
cycler
cython>=0.29.20
matplotlib>=3.5.1
qutip>=4.3.1
sympy
tqdm
typing_extensions
pathos
dill

Sorry, the diff of this file is not supported yet

"""scqubits: superconducting qubits in Python
===========================================
scqubits is an open-source Python library for simulating superconducting qubits. It is
meant to give the user a convenient way to obtain energy spectra of common
superconducting qubits, plot energy levels as a function of external parameters,
calculate matrix elements etc. The library further provides an interface to QuTiP,
making it easy to work with composite Hilbert spaces consisting of coupled
superconducting qubits and harmonic modes. Internally, numerics within scqubits is
carried out with the help of Numpy and Scipy; plotting capabilities rely on
Matplotlib.
If scqubits is helpful to you in your research, please support its continued
development and maintenance. Use of scqubits in research publications is
appropriately acknowledged by citing:
Peter Groszkowski and Jens Koch, 'scqubits: a Python package for superconducting qubits',
Quantum 5, 583 (2021). https://quantum-journal.org/papers/q-2021-11-17-583/
"""
#
# 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 os
import sys
import setuptools
DOCLINES = __doc__.split("\n")
CLASSIFIERS = """\
Development Status :: 5 - Production/Stable
Intended Audience :: Science/Research
License :: OSI Approved :: BSD License
Programming Language :: Python
Programming Language :: Python :: 3
Topic :: Scientific/Engineering
Operating System :: MacOS
Operating System :: POSIX
Operating System :: Unix
Operating System :: Microsoft :: Windows
"""
EXTRA_KWARGS = {}
# version information about scqubits goes here
MAJOR = 4
MINOR = 2
MICRO = 0
ISRELEASED = True
VERSION = "%d.%d.%d" % (MAJOR, MINOR, MICRO)
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
with open(os.path.join(CURRENT_DIR, "requirements.txt")) as requirements:
INSTALL_REQUIRES = requirements.read().splitlines()
EXTRAS_REQUIRE = {
"graphics": ["matplotlib-label-lines (>=0.3.6)"],
"explorer": ["ipywidgets (>=7.5)"],
"h5-support": ["h5py (>=2.10)"],
"pathos": ["pathos", "dill"],
}
TESTS_REQUIRE = ["h5py (>=2.7.1)", "pathos", "dill", "ipywidgets", "pytest"]
PACKAGES = [
"scqubits",
"scqubits/core",
"scqubits/tests",
"scqubits/utils",
"scqubits/ui",
"scqubits/io_utils",
"scqubits/explorer",
]
PYTHON_VERSION = ">=3.9"
NAME = "scqubits"
AUTHOR = "Jens Koch, Peter Groszkowski"
AUTHOR_EMAIL = "jens-koch@northwestern.edu, piotrekg@gmail.com"
LICENSE = "BSD"
DESCRIPTION = DOCLINES[0]
LONG_DESCRIPTION = "\n".join(DOCLINES[2:])
KEYWORDS = "superconducting qubits"
URL = "https://scqubits.readthedocs.io"
CLASSIFIERS = [_f for _f in CLASSIFIERS.split("\n") if _f]
PLATFORMS = ["Linux", "Mac OSX", "Unix", "Windows"]
def git_short_hash():
try:
git_str = "+" + os.popen('git log -1 --format="%h"').read().strip()
except OSError:
git_str = ""
else:
if git_str == "+": # fixes setuptools PEP issues with versioning
git_str = ""
return git_str
FULLVERSION = VERSION
if not ISRELEASED:
FULLVERSION += ".dev" + str(MICRO) + git_short_hash()
def write_version_py(filename="scqubits/version.py"):
cnt = """\
# THIS FILE IS GENERATED FROM scqubits SETUP.PY
short_version = '%(version)s'
version = '%(fullversion)s'
release = %(isrelease)s
"""
versionfile = open(filename, "w")
try:
versionfile.write(
cnt
% {
"version": VERSION,
"fullversion": FULLVERSION,
"isrelease": str(ISRELEASED),
}
)
finally:
versionfile.close()
local_path = os.path.dirname(os.path.abspath(sys.argv[0]))
os.chdir(local_path)
sys.path.insert(0, local_path)
sys.path.insert(0, os.path.join(local_path, "scqubits")) # to retrieve _version
# always rewrite _version
if os.path.exists("scqubits/version.py"):
os.remove("scqubits/version.py")
write_version_py()
setuptools.setup(
name=NAME,
version=FULLVERSION,
packages=PACKAGES,
author=AUTHOR,
author_email=AUTHOR_EMAIL,
license=LICENSE,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
keywords=KEYWORDS,
url=URL,
classifiers=CLASSIFIERS,
platforms=PLATFORMS,
install_requires=INSTALL_REQUIRES,
extras_require=EXTRAS_REQUIRE,
tests_require=TESTS_REQUIRE,
zip_safe=False,
include_package_data=True,
python_requires=PYTHON_VERSION,
**EXTRA_KWARGS
)

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display