# Systems

A System is an object that contains one or more Modes, defines the coupling between them, and generates the drift (time-independent) Hamiltonian in the Hilbert space consisting of all of its Modes, or any subset of them.

The Hilbert space used in generating the drift Hamiltonian is determined by the list system.active_modes, the elements of which must all be in system.modes. Upon initialization, system.active_modes is set to be equal to system.modes.

Setting system.active_modes = list_of_modes automatically updates the space for each Mode in list_of_modes to be equal to list_of_modes, so that each Mode is now aware of the Hilbert space in which it exists.

[1]:

%config InlineBackend.figure_formats = ['svg']
%matplotlib inline
import inspect
import numpy as np
import qutip
from sequencing import Transmon, Cavity, System, CouplingTerm

[2]:

qubit = Transmon('qubit', levels=3, kerr=-200e-3)
cavity = Cavity('cavity', levels=10, kerr=-10e-6)

print('Before system initialization:')
for mode in [qubit, cavity]:
print(f'\t{mode.name}.space is', [m.name for m in mode.space])
print(f'\t{mode.name} Hilbert space dimension is', mode.I.dims)

system = System('system', modes=[qubit, cavity])

print('\nAfter system initialization:')
print(f'\t{system.name}.active_modes is', [m.name for m in system.active_modes])
print(f'\t{system.name} Hilbert space dimension is', system.I().dims)
for mode in [qubit, cavity]:
print(f'\t{mode.name}.space is', [m.name for m in mode.space])
print(f'\t{mode.name} Hilbert space dimension is', mode.I.dims)

Before system initialization:
qubit.space is ['qubit']
qubit Hilbert space dimension is [[3], [3]]
cavity.space is ['cavity']
cavity Hilbert space dimension is [[10], [10]]

After system initialization:
system.active_modes is ['qubit', 'cavity']
system Hilbert space dimension is [[3, 10], [3, 10]]
qubit.space is ['qubit', 'cavity']
qubit Hilbert space dimension is [[3, 10], [3, 10]]
cavity.space is ['qubit', 'cavity']
cavity Hilbert space dimension is [[3, 10], [3, 10]]


## Using a subset of the full system space

Systems have a method called System.use_modes(), which is a contextmanager that allows you to temporarily set system.active_modes to some subset of system.modes and then automatically revert the state of the system when you are done.

[3]:

print('Using full system space:')
print(system.I().dims)

for mode in ['qubit', 'cavity']:
with system.use_modes([mode]):
print(f'\nUsing just the {mode} subspace:')
print(system.I().dims)

print('\nUsing full system space (again):')
print(system.I().dims)

Using full system space:
[[3, 10], [3, 10]]

Using just the qubit subspace:
[[3], [3]]

Using just the cavity subspace:
[[10], [10]]

Using full system space (again):
[[3, 10], [3, 10]]


## Coupling Modes together

Couplings between Modes are defined in the dictionary system.coupling_terms, the keys of which are of the form frozenset({mode0_name, mode1_name}) (the mode order does not matter here), and the values of which are a list of CouplingTerm objects. A list of operators representing all multi-mode couplings in the system is returned by System.couplings().

Cross-Kerrs, which are proportional to mode1.n * mode2.n for two distinct Modes, can be defined using the method System.set_cross_kerr().

[4]:

print('class CouplingTerm:')
print('\n\t'.join([''] + inspect.getdoc(CouplingTerm).split('\n')))

class CouplingTerm:

An object representing a coupling between multiple Modes,
given by a Hamiltonian term of the form strength * product(operators).
If the keyword argument add_hc is provided and is True,
then the Hamiltonian term takes the form
strength * (product(operators) + product(operators).dag()).

Args:
terms (list[tuple[Mode, str]]): List of tuples of (mode, expr),
which defines the coupling. Each expr is a string containing an
algebraic expression involving the Mode's operators.
See :func:Mode.operator_expr for more details.
The resulting operators are given by mode.operator_expr(expr).
strength (optional, float): Coefficient parameterizing the
strength of the coupling. Strength should be given in
units of 2 * pi * GHz. Default: 1.
of the product of the operators. Default: False.

[5]:

system.set_cross_kerr(qubit, cavity, chi=-2e-3)
print('Cross-Kerrs (in GHz):\n\t', system.cross_kerrs)
print('Coupling terms (strength in 2 * pi * GHz):\n\t', dict(system.coupling_terms))
display(system.couplings()[0])

Cross-Kerrs (in GHz):
{frozenset({'qubit', 'cavity'}): -0.002}
Coupling terms (strength in 2 * pi * GHz):
{frozenset({'qubit', 'cavity'}): [CouplingTerm([(qubit, 'n'), (cavity, 'n')], strength=-1.257e-02, add_hc=False)]}

Quantum object: dims = [[3, 10], [3, 10]], shape = (30, 30), type = oper, isherm = True\begin{equation*}\left(\begin{array}{*{11}c}0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\\vdots & \vdots & \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots & \vdots & \vdots\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & -0.126 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & -0.151 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & -0.176 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & -0.201 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & \cdots & 0.0 & 0.0 & 0.0 & 0.0 & -0.226\\\end{array}\right)\end{equation*}

## Drift Hamiltonian

The system time-indenependent (drift) Hamiltonian includes the self-Kerr (anharmonicity) of each mode, the detuning of each mode relative to the frame in which its operators are defined, and all couplings between modes. The method System.H0() returns all of these operators in a list, which can be summed to get the full drift Hamiltonian operator. By default, System.H0() returns a list containing only those operators which have nonzero elements, but the full list of operators can be returned by using the keyword argument clean=False.

Similarly, the method System.c_ops() returns a list of all operators representing loss and dephasing of all modes.

[6]:

len(system.H0()) == 3
# [qubit.self_kerr, cavity.self_kerr, qubit-cavity cross-Kerr]

[6]:

True

[7]:

len(system.H0(clean=False)) == 5
# [qubit.self_kerr, cavity.self_kerr, qubit.detuning, cavity.detuning, qubit-cavity cross-Kerr]

[7]:

True

[8]:

for t1 in ['inf', 100e3]:
qubit.t1 = float(t1)
print(f'With {qubit.name}.t1 = {t1}, there are {len(system.c_ops(clean=True))} nonzero c_ops')
qubit.t2 = 100e3
print(
f'With {qubit.name}.t1 = {t1}, {qubit.name}.t2 = {qubit.t2}, '
f'there are {len(system.c_ops(clean=True))} nonzero c_ops'
)

With qubit.t1 = inf, there are 0 nonzero c_ops
With qubit.t1 = 100000.0, there are 1 nonzero c_ops
With qubit.t1 = 100000.0, qubit.t2 = 100000.0, there are 2 nonzero c_ops

[9]:

from qutip.ipynbtools import version_table
version_table()

[9]:

SoftwareVersion
QuTiP4.7.0
Numpy1.23.2
SciPy1.9.1
matplotlib3.5.3
Cython0.29.32
Number of CPUs2
BLAS InfoOPENBLAS
IPython8.4.0
Python3.8.6 (default, Oct 19 2020, 15:10:29) [GCC 7.5.0]
OSposix [linux]
Tue Aug 30 19:15:33 2022 UTC
[ ]: