[1]:
import quanguru as qg
import numpy as np

23 - Step 1 : System Description#

This tutorial demonstrates all the functionalities provided by the QuantumSystem and QTerm class.

Name and aliases#

[2]:
qsys =  qg.QuantumSystem(alias='firstSystem')
print(qsys.name, qsys.alias, qsys._firstTerm.name)
QuantumSystem1 ['firstSystem'] QTerm1
[3]:
print(qsys.terms, qsys._firstTerm._paramBoundBase__matrix)
{'QTerm1': <quanguru.classes.QTerms.QTerm object at 0x11a3a9c70>} None

Hamiltonians#

With simple terms#

\(H_{0} = \frac{1}{\sqrt{2}}(a^{\dagger} + a)\)

[4]:
q0 = qg.QuantumSystem(dimension=4, frequency=1/np.sqrt(2), operator=qg.create)

secondTerm = q0.createTerm(operator=qg.destroy, frequency=1/np.sqrt(2))

print(q0.totalHamiltonian.toarray())
[[0.         0.70710678 0.         0.        ]
 [0.70710678 0.         1.         0.        ]
 [0.         1.         0.         1.22474487]
 [0.         0.         1.22474487 0.        ]]
[5]:
thirdTerm = q0.createTerm(operator=qg.sigmam, frequency=1)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/Volumes/T7/Dropbox/Codes/QuanGuru/docs/source/classes/Tutorials/1_Qubit/22_Step1_SystemDescription.ipynb Cell 8 in <cell line: 1>()
----> <a href='vscode-notebook-cell:/Volumes/T7/Dropbox/Codes/QuanGuru/docs/source/classes/Tutorials/1_Qubit/22_Step1_SystemDescription.ipynb#X10sZmlsZQ%3D%3D?line=0'>1</a> thirdTerm = q0.createTerm(operator=qg.sigmam, frequency=1)

File /Volumes/T7/Dropbox/Codes/QuanGuru/src/quanguru/classes/QSystem.py:583, in QuantumSystem.createTerm(self, operator, frequency, qSystem, order, superSys, **kwargs)
    581     qSystem = self.getByNameOrAlias(qSystem)
    582 superSys = self if superSys is None else superSys
--> 583 newTerm = QTerm._createTerm(superSys=superSys, qSystem=qSystem, operator=operator, order=order,#pylint:disable=protected-access
    584                             frequency=frequency, **kwargs)
    585 qObj = self if isinstance(qSystem, (list, tuple)) else qSystem
    586 qObj.addTerms(newTerm)

File /Volumes/T7/Dropbox/Codes/QuanGuru/src/quanguru/classes/QTerms.py:152, in QTerm._createTerm(superSys, qSystem, operator, order, frequency, **kwargs)
    150 newSys = QTerm(superSys=superSys, **kwargs)
    151 newSys.qSystem = qSystem
--> 152 newSys.operator = operator
    153 newSys.order = [1 for _ in qSystem] if (isinstance(qSystem, (list, tuple)) and (order is None)) else order
    154 newSys.frequency = frequency

File /Volumes/T7/Dropbox/Codes/QuanGuru/src/quanguru/classes/QTerms.py:201, in QTerm.operator(self, op)
    199 @operator.setter
    200 def operator(self, op):
--> 201     self._checkAndUpdateParamsWhenMultiple(op, '_QTerm__operator', 'operator')
    202     if ((not isinstance(self.order, (list, tuple))) and isinstance(op, (list, tuple))):
    203         checkVal(self.order, 1, f'order value ({self.order}) for a multi term ({len(op)}) system is ambiguous, '+
    204                                  'set a list/tuple of order values for each term in {op}')

File /Volumes/T7/Dropbox/Codes/QuanGuru/src/quanguru/classes/QTerms.py:187, in QTerm._checkAndUpdateParamsWhenMultiple(self, vals, attrName, attrPrintName)
    185         setAttr(ter, attrName, vals[ind])
    186 if attrName == '_QTerm__operator':
--> 187     self._isCorrectPauliDim(self.qSystem, vals) #pylint:disable=no-member
    188 setAttr(self, attrName, vals)
    189 if self._paramUpdated:

File /Volumes/T7/Dropbox/Codes/QuanGuru/src/quanguru/classes/QTerms.py:284, in QTerm._isCorrectPauliDim(qsys, oper, dim)
    282 else:
    283     dim = qsys.dimension if dim is None else dim
--> 284     checkVal(not (QTerm._isOperPauli(oper) and not dim in (2, 1)), True,
    285              f'dimension ({dim}) of quantum system ({qsys.name}) has to be 2, because it has {oper} as term')

File /Volumes/T7/Dropbox/Codes/QuanGuru/src/quanguru/classes/exceptions.py:52, in checkVal(someObj, val, msg)
     50 def checkVal(someObj, val, msg):
     51     if someObj != val:
---> 52         raise ValueError(msg)
     53     return someObj

ValueError: dimension (4) of quantum system (QuantumSystem2) has to be 2, because it has <function sigmam at 0x1156fe050> as term
[ ]:
q0.simulation.stepSize, q0.stepSize
[9]:
q0.dimension = 2

print(
    q0.frequency is q0._firstTerm.frequency,
    q0.operator is q0._firstTerm.operator,
    q0.order is q0._firstTerm.order,
    q0._freeMatrix is q0._firstTerm._freeMatrix,
    sep='\n'
)

print(
    secondTerm.frequency is secondTerm.frequency,
    secondTerm.operator is secondTerm.operator,
    secondTerm.order is secondTerm.order,
    sep='\n'
)

print(q0.totalHamiltonian.toarray())
True
True
True
True
True
True
True
[[0.         0.70710678]
 [0.70710678 0.        ]]

With non-trivial terms#

\(H_{1} = \omega_{1}a^{\dagger} a + \omega_{2} a^{3} (a^{\dagger})^{2}\)

[10]:
w1 = 1
w2 = 1
dimension = 5

powOfDestroy = 3
powOfCreate = 2

q1 = qg.QuantumSystem(dimension=dimension, operator=qg.number, frequency=w1)

# for the second term, we need to create the term object explicitly then add it to our quantum system
# want this to be intentional
# create term raises an error
# nonLinearTerm = q1.createTerm(qSystem=(q1, q1), operator=(qg.destroy, qg.create), frequency=w2, order=(powOfDestroy, powOfCreate), superSys=q1)
nonLinearTerm = qg.QTerm(qSystem=(q1, q1), operator=(qg.destroy, qg.create), frequency=w2, order=(powOfDestroy, powOfCreate), superSys=q1)
q1.addTerms(nonLinearTerm)

print(q1.totalHamiltonian.toarray())
[[ 0.          6.          0.          0.          0.        ]
 [ 0.          1.         16.97056275  0.          0.        ]
 [ 0.          0.          2.          0.          0.        ]
 [ 0.          0.          0.          3.          0.        ]
 [ 0.          0.          0.          0.          4.        ]]

What if we have only one term ?#

$ H_{2} = \omega_{2} a^{3} (a{:nbsphinx-math:dagger`})`{2}$

[11]:
q2 = qg.QuantumSystem(dimension=dimension, frequency=w2)

# we first modify the qSystem of the _firstTerm
# it is crucial that you do this before setting the operator and/or order
# otherwise it will raise an error
q2._firstTerm.qSystem = (q2, q2)

q2.operator = (qg.destroy, qg.create)
q2.order = (powOfDestroy, powOfCreate)

print(q2.totalHamiltonian.toarray())
[[ 0.          6.          0.          0.          0.        ]
 [ 0.          0.         16.97056275  0.          0.        ]
 [ 0.          0.          0.          0.          0.        ]
 [ 0.          0.          0.          0.          0.        ]
 [ 0.          0.          0.          0.          0.        ]]

Why such a strange syntax ?#

Because it enable us to extend similar syntax to couplings, such as the 3 body coupling in below Hamiltonian (for a composite system with 3 subsystems, a spin-1/2, a spin-2, another system with dimension 6). I wrote \(\otimes \mathbb{1}\) to represent the tensor product with identity operators, which converts bare operators to composite operators.

$ H_{3} = \omega{1}:nbsphinx-math:`sigma`{z}:nbsphinx-math:otimes `:nbsphinx-math:mathbb{1}` \otimes `:nbsphinx-math:mathbb{1}` + \mathbb{1} \otimes `:nbsphinx-math:omega`{2}J{z}^{2}:nbsphinx-math:otimes `:nbsphinx-math:mathbb{1}` + \mathbb{1} \otimes `:nbsphinx-math:mathbb{1}`:nbsphinx-math:otimes `:nbsphinx-math:omega`{3}a^:nbsphinx-math:`dagger `a + :nbsphinx-math:`omega`{4}(\sigma{+}:nbsphinx-math:`otimes `(J{x}J_{y}) \otimes `(a^{3} (a\ :sup:`{:nbsphinx-math:dagger`})`{2}))$

Special classes for common systems#

  • Cavity (will implement a maxPhoton attribute) defaults its first term operator to the number

  • Spin implements a jValue attribute and defaults the first term operator to J_z

  • Qubit is the special case of Spin with jValue=0.5 (ie dimension=2)

[13]:
w1, w2, w3, w4 = 1, 1, 1, 1
spinJ = 2
thirdDim = 6
[15]:
qubit = qg.Qubit(frequency=w1, alias='subSys12')
spin2 = qg.Spin(jValue=spinJ, frequency=w2, order=2, alias='subSys22')
cavity = qg.Cavity(dimension=thirdDim, frequency=w3, alias='subSys32')

qComp = qubit + spin2 + cavity # order of this sum is important. It maps to the order of identity and other operators above

couplingTerm = qComp.createTerm(frequency=w4,
                                operator=(qg.Jx, qg.Jy, qg.destroy, qg.create, qg.sigmap),
                                order=(1, 1, 3, 2, 1),
                                qSystem=(spin2, spin2, cavity, cavity, qubit),
                                alias='couplingTerm')
# alias is kwargs

print(qComp.totalHamiltonian.toarray())
[[4.5+0.j 0. +0.j 0. +0.j ... 0. +0.j 0. +0.j 0. +0.j]
 [0. +0.j 5.5+0.j 0. +0.j ... 0. +0.j 0. +0.j 0. +0.j]
 [0. +0.j 0. +0.j 6.5+0.j ... 0. +0.j 0. +0.j 0. +0.j]
 ...
 [0. +0.j 0. +0.j 0. +0.j ... 6.5+0.j 0. +0.j 0. +0.j]
 [0. +0.j 0. +0.j 0. +0.j ... 0. +0.j 7.5+0.j 0. +0.j]
 [0. +0.j 0. +0.j 0. +0.j ... 0. +0.j 0. +0.j 8.5+0.j]]
[16]:
print(couplingTerm.alias)
print(couplingTerm.qSystem, couplingTerm.superSys, couplingTerm.subSys, sep='\n')
['couplingTerm']
[<quanguru.classes.QSystem.Spin object at 0x12177e240>, <quanguru.classes.QSystem.Spin object at 0x12177e240>, <quanguru.classes.QSystem.Cavity object at 0x1218ddd50>, <quanguru.classes.QSystem.Cavity object at 0x1218ddd50>, <quanguru.classes.QSystem.Qubit object at 0x12177e440>]
QuantumSystem5
{'_QTerm5': <quanguru.classes.QTerms.QTerm object at 0x121904f60>, '_QTerm6': <quanguru.classes.QTerms.QTerm object at 0x121905f30>, '_QTerm7': <quanguru.classes.QTerms.QTerm object at 0x121905fe0>, '_QTerm8': <quanguru.classes.QTerms.QTerm object at 0x121906090>, '_QTerm9': <quanguru.classes.QTerms.QTerm object at 0x121906140>}
[18]:
couplingTerm.subSys['_QTerm5'].operator = qg.Jp
[19]:
couplingTerm.subSys['_QTerm5'].operator
[19]:
<function quanguru.QuantumToolbox.operators.Jp(j: float, sparse: bool = True, isDim: bool = False) -> Union[scipy.sparse._base.spmatrix, numpy.ndarray]>

TimeDependent term#

\(H = f_{1}(t)\sigma_z + f_{2}(t)\sigma_x\)

where

\(f(1) = \sin(t)\)

and

\(f(2) = \cos^2(t)\)

[10]:
qub1 = qg.QuantumSystem(dimension=2, operator=qg.sigmaz)

term2 = qub1.createTerm(operator=qg.sigmax, alias='term2')

def f1(qsys, time):
    qsys.frequency = np.sin(time)
qub1._firstTerm.timeDependency = f1

def f2(qsys, time):
    qsys.frequency = np.cos(time)**2
term2.timeDependency = f2

qub1._timeDependency(0)
print(qub1._firstTerm.frequency, term2.frequency)
0 1.0

Terms and sub-systems#

terms and subSys are aliasDict

[11]:
print(qComp.terms, qComp.subSys, qubit.terms, spin2.terms, cavity.terms, sep='\n\n')
{'QTerm10': <quanguru.classes.QTerms.QTerm object at 0x11e39f3d0>}

{'Qubit1': <quanguru.classes.QSystem.Qubit object at 0x1114fb340>, 'Spin1': <quanguru.classes.QSystem.Spin object at 0x1114fb540>, 'Cavity1': <quanguru.classes.QSystem.Cavity object at 0x11e3546d0>}

{'QTerm7': <quanguru.classes.QTerms.QTerm object at 0x11e39d170>}

{'QTerm8': <quanguru.classes.QTerms.QTerm object at 0x11e39cb40>}

{'QTerm9': <quanguru.classes.QTerms.QTerm object at 0x11e39cd50>}
[12]:
print(qComp._termHamiltonian.toarray(), qComp._subSysHamiltonian.toarray())
[[0.+0.j 0.+0.j 0.+0.j ... 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j ... 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j ... 0.+0.j 0.+0.j 0.+0.j]
 ...
 [0.+0.j 0.+0.j 0.+0.j ... 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j ... 0.+0.j 0.+0.j 0.+0.j]
 [0.+0.j 0.+0.j 0.+0.j ... 0.+0.j 0.+0.j 0.+0.j]] [[4.5 0.  0.  ... 0.  0.  0. ]
 [0.  5.5 0.  ... 0.  0.  0. ]
 [0.  0.  6.5 ... 0.  0.  0. ]
 ...
 [0.  0.  0.  ... 6.5 0.  0. ]
 [0.  0.  0.  ... 0.  7.5 0. ]
 [0.  0.  0.  ... 0.  0.  8.5]]

Dimension information#

[13]:
print(qComp.subSysDimensions, qubit.dimension, spin2.dimension, cavity.dimension)
[2, 5, 6] 2 5 6
[14]:
print(spin2._totalDim, spin2.dimension, spin2._dimsAfter, spin2._dimsBefore)
60 5 6 2

composite operators#

[15]:
spin2._compositeOperator = qg.Jx
print(spin2._compositeOperator[qg.Jx].shape)

spin2.jValue = 1
print(spin2._totalDim, spin2.dimension, spin2._dimsAfter, spin2._dimsBefore)
print(spin2._compositeOperator[qg.Jx].shape)

cavity.dimension = 2
print(spin2._totalDim, spin2.dimension, spin2._dimsAfter, spin2._dimsBefore)
print(spin2._compositeOperator[qg.Jx].shape)
(60, 60)
36 3 6 2
(36, 36)
12 3 2 2
(12, 12)

matrix re-creations#

[16]:
newSys = qg.QuantumSystem(dimension=2, operator=qg.create, frequency=1)
print(newSys._paramBoundBase__matrix)
None
[17]:
print(newSys.totalHamiltonian.toarray(), newSys.totalHamiltonian is newSys._paramBoundBase__matrix)
[[0. 0.]
 [1. 0.]] True
[18]:
newSys.dimension = 4
print(newSys._paramBoundBase__matrix)
print(newSys.totalHamiltonian.toarray(), newSys.totalHamiltonian is newSys._paramBoundBase__matrix)
None
[[0.         0.         0.         0.        ]
 [1.         0.         0.         0.        ]
 [0.         1.41421356 0.         0.        ]
 [0.         0.         1.73205081 0.        ]] True
[19]:
newSys.dimension = 4
print(newSys._paramBoundBase__matrix)
  (1, 0)        1.0
  (2, 1)        1.4142135623730951
  (3, 2)        1.7320508075688772

hc of __matrix#

[20]:
print(newSys._hc.toarray(), newSys.totalHamiltonian.toarray(), sep='\n')
[[0.         1.         0.         0.        ]
 [0.         0.         1.41421356 0.        ]
 [0.         0.         0.         1.73205081]
 [0.         0.         0.         0.        ]]
[[0.         0.         0.         0.        ]
 [1.         0.         0.         0.        ]
 [0.         1.41421356 0.         0.        ]
 [0.         0.         1.73205081 0.        ]]
[21]:
newSys.dimension = 5
# distinction is when newSys is in a composite system and the dimension of the another system is changed
print(newSys._firstTerm._QTerm__HamiltonianTerm, newSys._firstTerm._paramBoundBase__matrix, sep='\n\n')
print(newSys._firstTerm.totalHamiltonian.toarray(), newSys._firstTerm._paramBoundBase__matrix, sep='\n\n')
  (1, 0)        1.0
  (2, 1)        1.4142135623730951
  (3, 2)        1.7320508075688772

None
[[0.         0.         0.         0.         0.        ]
 [1.         0.         0.         0.         0.        ]
 [0.         1.41421356 0.         0.         0.        ]
 [0.         0.         1.73205081 0.         0.        ]
 [0.         0.         0.         2.         0.        ]]

  (1, 0)        1.0
  (2, 1)        1.4142135623730951
  (3, 2)        1.7320508075688772
  (4, 3)        2.0
[56]:
newSys.delMatrices()
print(newSys._paramBoundBase__matrix,
      newSys._firstTerm._QTerm__HamiltonianTerm,
      newSys._firstTerm._paramBoundBase__matrix, sep='\n\n')
None

  (1, 0)        2.0
  (2, 1)        2.8284271247461903
  (3, 2)        3.4641016151377544
  (4, 3)        4.0

None
[22]:
newSys.frequency = 2
print(newSys._paramUpdated, newSys._firstTerm._paramUpdated)
print(newSys._firstTerm._QTerm__HamiltonianTerm, newSys._firstTerm._paramBoundBase__matrix, sep='\n\n')
newSys._firstTerm.totalHamiltonian

newSys.frequency = 2
print(newSys._paramUpdated, newSys._firstTerm._paramUpdated)
newSys.totalHamiltonian
print(newSys._paramUpdated, newSys._firstTerm._paramUpdated)
True True
  (1, 0)        1.0
  (2, 1)        1.4142135623730951
  (3, 2)        1.7320508075688772
  (4, 3)        2.0

  (1, 0)        1.0
  (2, 1)        1.4142135623730951
  (3, 2)        1.7320508075688772
  (4, 3)        2.0
True False
False False
[23]:
print(newSys.name,
      newSys.terms['QTerm13']._paramBound,
      newSys._paramBound,
      newSys.simulation._paramBound,
      newSys._freeEvol.simulation.name, sep='\n')
QuantumSystem7
{'QuantumSystem7': <quanguru.classes.QSystem.QuantumSystem object at 0x11e354b80>}
{'_freeEvolution10': <quanguru.classes.QPro.freeEvolution object at 0x11149f010>}
{'QuantumSystem7': <quanguru.classes.QSystem.QuantumSystem object at 0x11e354b80>, '_Simulation20': <quanguru.classes.QSim.Simulation object at 0x11149f120>}
_Simulation20

Composite system information#

[24]:
print(qComp._isComposite, qubit._isComposite, spin2._isComposite, cavity._isComposite)
True False False False
[25]:
print(qComp.ind, qubit.ind, spin2.ind, cavity.ind)
0 0 1 2
[26]:
qCompCopy = qComp.copy()
print(qCompCopy.terms, qCompCopy.subSys, sep='\n')
print(np.allclose(qCompCopy.totalHamiltonian.toarray(), qComp.totalHamiltonian.toarray()))
{'QTerm17': <quanguru.classes.QTerms.QTerm object at 0x11e3be140>}
{'Qubit2': <quanguru.classes.QSystem.Qubit object at 0x1114fba40>, 'Spin2': <quanguru.classes.QSystem.Spin object at 0x1114fbb40>, 'Cavity2': <quanguru.classes.QSystem.Cavity object at 0x11e354e50>}
True
[27]:
qComp.removeSubSys(spin2)
print(qComp.terms, qComp.subSys, qComp.subSysDimensions, sep='\n')
{}
{'Qubit1': <quanguru.classes.QSystem.Qubit object at 0x1114fb340>, 'Cavity1': <quanguru.classes.QSystem.Cavity object at 0x11e3546d0>}
[2, 2]
[28]:
qComp.resetTerms(), qComp.resetSubSys()
print(qComp.terms, qComp.subSys, qComp.subSysDimensions, sep='\n')
{}
{}
[]
[29]:
print(couplingTerm.subSys.keys())

internalTerms = list(couplingTerm.subSys.values())

couplingTerm.removeSubSys('_QTerm7')
print(couplingTerm.subSys.keys())
dict_keys(['_QTerm5', '_QTerm6', '_QTerm7', '_QTerm8', '_QTerm9'])
dict_keys(['_QTerm5', '_QTerm6', '_QTerm8', '_QTerm9'])
[30]:
couplingTerm.resetSubSys()
print(couplingTerm.subSys.keys())

couplingTerm.subSys = internalTerms
print(couplingTerm.subSys.keys())
dict_keys([])
dict_keys(['_QTerm5', '_QTerm6', '_QTerm7', '_QTerm8', '_QTerm9'])
[31]:
qComp.subSys = [qubit, spin2, cavity]
print(qComp.subSys, qComp.name)
qComp += 4*qg.QuantumSystem(dimension=2)
print(qComp.subSys)
{'Qubit1': <quanguru.classes.QSystem.Qubit object at 0x1114fb340>, 'Spin1': <quanguru.classes.QSystem.Spin object at 0x1114fb540>, 'Cavity1': <quanguru.classes.QSystem.Cavity object at 0x11e3546d0>} QuantumSystem5
{'QuantumSystem5': <quanguru.classes.QSystem.QuantumSystem object at 0x11e3547c0>, 'QuantumSystem10': <quanguru.classes.QSystem.QuantumSystem object at 0x11e354f40>}
[32]:
qComp -= qubit
print(qComp.subSys)
print(qComp.subSys['QuantumSystem5'].subSys)
print(qComp.subSys['QuantumSystem10'].subSys)
{'QuantumSystem5': <quanguru.classes.QSystem.QuantumSystem object at 0x11e3547c0>, 'QuantumSystem10': <quanguru.classes.QSystem.QuantumSystem object at 0x11e354f40>}
{'Spin1': <quanguru.classes.QSystem.Spin object at 0x1114fb540>, 'Cavity1': <quanguru.classes.QSystem.Cavity object at 0x11e3546d0>}
{'QuantumSystem9': <quanguru.classes.QSystem.QuantumSystem object at 0x11e354a90>, 'QuantumSystem11': <quanguru.classes.QSystem.QuantumSystem object at 0x11e355030>, 'QuantumSystem12': <quanguru.classes.QSystem.QuantumSystem object at 0x11e355120>, 'QuantumSystem13': <quanguru.classes.QSystem.QuantumSystem object at 0x11e355210>}

Initial state#

various types of initial state input: single number, list of numbers, dictionary (inpCoef False/True), and matrix (with consistent dimension)#

Before diving into the examples, let’s first define certain conventions and notations representing the eigenvectors of \(\sigma_{z}\) operator.

Matrix form of \(\sigma_{z}\) operator is:

\(\sigma_{z} = \left[\begin{array}{ll}1 & 0 \\ 0 & -1\end{array}\right]\)

and its eigenvectors (with the corresponding eigenvalues) are

\(|0\rangle = \left[\begin{array}{ll} 1 \\ 0 \end{array}\right] \text{ (with eigenvalue 1)}\)

\(|1\rangle = \left[\begin{array}{ll} 0 \\ 1 \end{array}\right] \text{ (with eigenvalue -1)}\)

The labels, 0 and 1, of the ket states here represent the position of the 1 in the column matrices. In other words, the \(|0\rangle\) has 1 at row 0, and the \(|1\rangle\) has 1 at row 1. This convention extends naturally and is used for higher dimensional systems such as larger spins or harmonic oscillators. We will refer these as basis states, and the basis method of QuantumToolbox can be used for the creation of such states, but, here we focus on creating the initial states through the Qubit object.

1. Pure States#

1.1 If Initial state is a basis state#

In order to set \(|0\rangle\) or \(|1\rangle\), we simply give the respective number as input for the initialState

[53]:
qub = qg.Qubit(frequency=1)

qub.initialState = 0
print(qub.initialState.toarray())

qub.initialState = 1
print(qub.initialState.toarray())
[[1.]
 [0.]]
[[0.]
 [1.]]
[54]:
print(qub._initialStateInput, qub._maxInput)
1 1

1.2 If Initial state is a superposition state#

If we want to set some super-position of \(|0\rangle\) and \(|1\rangle\) as the initial state, we can do this in various different ways depending on the superposition state we want to set.

1.2.1 If Initial state is an equal superposition state#

The first method is for the equal superposition state

\(\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle )\)

In this special case, we simply give a list \([1, 0]\) (or \([0, 1]\) the order is not important) of the states, and it is used to create the equal superposition. Note that “equal superposition” here does not mean equal superposition of every basis states, it just means equal superposition of the given input basis states. In other words, for higher dimensional systems, you can create an equal superposition of any number of basis states. For example for a 5 dimensional system, \([1, 0, 3]\) will create equal superposition of these three basis states.

[35]:
qub.initialState = [1, 0]
print(qub.initialState.toarray())
[[0.70710678]
 [0.70710678]]

1.2.2 If Initial state is an arbitrary superposition state#

The second method for creating a superposition state is by using a dictionary, where key:value pairs represent the basis-state:population/coefficient.

population/coefficient here means that there are also two different ways for this approach.

Consider the following superposition state

\(c_{0}|0\rangle + c_{1}|1\rangle\)

where \(c_{i}\) are the complex probability amplitudes, and \(p_{i} = |c_{i}|^{2}\) are the population, satisfying the normalization condition \(\sum_{i}p_{i} = 1\).

Now, we can create our superposition state either by using the population or coefficient.

1.2.2.1 Initial state using the populations#

Let’s cover the population first, which is the default method. Below is an example where we want \(p_{0} = 0.2\) and \(p_{1} = 0.8\). Note that the order of the key:value pairs does not matter, meaning {0:0.2, 1:0.8} is the same as {1:0.8, 0:0.2}.

[36]:
qub.initialState = {0:0.2, 1:0.8}
print(qub.initialState.toarray())

# let's also convert this initial state into density matrix to confirm:
# (i) the populations and (ii) its purity
denMat = qg.densityMatrix(qub.initialState)
print(denMat.toarray(), qg.purity(denMat), sep='\n')
[[0.4472136 ]
 [0.89442719]]
[[0.2 0.4]
 [0.4 0.8]]
1.0000000000000009

Also note that the populations can be relative, meaning the input does not have to sum to 1, and they will be normalized to sum to one. For example, if we input \({0:0.2, 1:0.9}\), the total population is \(1.1\). Therefore, it will be normalized to \({0:(0.2/1.1), 1:(0.9/1.1)}\).

[37]:
qub.initialState = {0:0.2, 1:0.9}
print(qub.initialState.toarray())

denMat = qg.densityMatrix(qub.initialState)
print(denMat.toarray(), qg.purity(denMat), 0.9/1.1, 0.2/1.1, sep='\n')
[[0.42640143]
 [0.90453403]]
[[0.18181818 0.38569461]
 [0.38569461 0.81818182]]
1.0000000000000007
0.8181818181818181
0.18181818181818182
1.2.2.2 If the relative phases are important#

Obviously, the population approach ignores the relative phase between the coefficients, which might be important for us. In such a case, we can give the key:value value pairs as state:coefficient, but we also need to set _inpCoef to True as below, where we compare the population and coefficient approach. Notice that both cases below creates \(p_{0} = 0.66\) and \(p_{1} = 0.33\), but the first one uses the complex probability amplitudes introducing a relative phase, which is observed as the complex parts of the off-diagonal elements of the density matrix.

[38]:
qub._inpCoef = True
qub.initialState = {0:0.2*(1+1j), 1:0.2}
print(qub.initialState.toarray())
denMat = qg.densityMatrix(qub.initialState)
print(denMat.toarray(), qg.purity(denMat), sep='\n')

qub._inpCoef = False
qub.initialState = {0:0.66, 1:0.33}
print(qub.initialState.toarray())
denMat = qg.densityMatrix(qub.initialState)
print(denMat.toarray(), qg.purity(denMat), sep='\n')
[[0.57735027+0.57735027j]
 [0.57735027+0.j        ]]
[[0.66666667+0.j         0.33333333+0.33333333j]
 [0.33333333-0.33333333j 0.33333333+0.j        ]]
(0.9999999999999989+0j)
[[0.81649658]
 [0.57735027]]
[[0.66666667 0.47140452]
 [0.47140452 0.33333333]]
1.0

Mixed states or any other arbitrary state#

initialState also accepts a (ket or density) matrix as input, which makes it possible to set any state we want as the initial state. For example, if we want the initial state to be a mixed state, we can create it using the densityMatrix function of QuantumToolbox then set it through again the initialState as below, where we also calculate the purity of each mixed state.

[39]:
mixedState = qg.densityMatrix([qg.basis(2,1), qg.basis(2,0)], [0.5, 0.5])
qub.initialState = mixedState
print(qub.initialState.toarray(), qg.purity(qub.initialState), sep='\n')

mixedState = qg.densityMatrix([qg.basis(2,1), qg.basis(2,0)], [0.25, 0.75])
qub.initialState = mixedState
print(qub.initialState.toarray(), qg.purity(qub.initialState), sep='\n')
[[0.5 0. ]
 [0.  0.5]]
0.5
[[0.75 0.  ]
 [0.   0.25]]
0.625

_createState function pointer for custom initial state creators#

[40]:
def customStateCreator(qsys, inp=None, _maxInput=1):
    return qg.superPos(qsys.dimension, inp, not qsys._inpCoef) if _maxInput < qsys.dimension else None
qg.QuantumSystem._createState = customStateCreator
qub.initialState = 0, 1
print(qub.initialState.toarray())
[[0.70710678]
 [0.70710678]]

other aux#

  • number of QuantumSystem instances

[41]:
print(qub._instances, qg.Qubit._instances)
print(qg.Qubit._instances, qg.Spin._instances, qg.Cavity._instances, qg.QuantumSystem._instances)
3 3
3 2 2 14

Compositions#

  • setting internal simulation operator parameters directly through self

  • unitary of internal freeEvolution (_freeEvol) and addProtocol

  • results

[69]:
qub.simStepSize = 1
print(qub.stepSize)
qub.stepSize = 2
print(qub.stepSize)
1
2
[70]:
print(qub._freeEvol.unitary() is qub.unitary(), qub._freeEvol.superSys is qub)
True True
[75]:
print(qub.simulation.protocols[0] is qub._freeEvol)
True
[61]:
print(type(qub.qRes), qub.qRes.name, qub.qRes.alias, qub.name)
<class 'quanguru.classes.QRes.qResults'> _qResults98 ['Qubit4Results'] Qubit4

cascaded parameters#

below parameters:

  • initial state

  • step size

  • total time

  • step sample

  • step count

are cascaded from simulation -> system -> protocol

[44]:
spin1 = qg.Spin(jValue=1)
spin2 = qg.Spin(jValue=1)

protocol1 = qg.qProtocol(system=spin2)
protocol2 = qg.qProtocol(system=spin2)

simulation = qg.Simulation(subSys=spin1)
simulation.addSubSys(spin2, protocol1)
simulation.addSubSys(spin2, protocol2)
[44]:
<quanguru.classes.QSystem.Spin at 0x11e408140>
[45]:
simulation.initialStateSystem = spin1
simulation.initialState = 0

print(simulation.initialState is spin1.initialState, simulation.initialState is spin2.initialState)
print(simulation.initialState is protocol1.initialState, simulation.initialState is protocol2.initialState)
True True
True True
[46]:
spin2.initialState = 1

print(simulation.initialState is spin1.initialState, simulation.initialState is spin2.initialState)
print(simulation.initialState is protocol1.initialState, simulation.initialState is protocol2.initialState)
print(spin2.initialState is protocol1.initialState, spin2.initialState is protocol2.initialState)
True False
False False
True True
[47]:
protocol1.initialState = 2

print(simulation.initialState is spin1.initialState, simulation.initialState is spin2.initialState)
print(simulation.initialState is protocol1.initialState, simulation.initialState is protocol2.initialState)
print(spin2.initialState is protocol1.initialState, spin2.initialState is protocol2.initialState)
True False
False False
False True
[48]:
simulation.stepSize = 1

print(simulation.stepSize is spin1.stepSize, simulation.stepSize is spin2.stepSize)
print(simulation.stepSize is protocol1.stepSize, simulation.stepSize is protocol2.stepSize)
True True
True True
[49]:
spin2.stepSize = 2

print(simulation.stepSize is spin1.stepSize, simulation.stepSize is spin2.stepSize)
print(simulation.stepSize is protocol1.stepSize, simulation.stepSize is protocol2.stepSize)
print(spin2.stepSize is protocol1.stepSize, spin2.stepSize is protocol2.stepSize)
True False
False False
True True
[50]:
protocol1.stepSize = 3

print(simulation.stepSize is spin1.stepSize, simulation.stepSize is spin2.stepSize)
print(simulation.stepSize is protocol1.stepSize, simulation.stepSize is protocol2.stepSize)
print(spin2.stepSize is protocol1.stepSize, spin2.stepSize is protocol2.stepSize)
True False
False False
False True

internal vs external names#

[51]:
print(simulation.name,
      spin1.simulation.name,
      protocol2.simulation.name,
      qg.Simulation._instances,
      qg.Simulation._externalInstances,
      qg.Simulation._internalInstances, sep='\n')
Simulation1
_Simulation43
_Simulation48
49
1
48

compute functions#

preCompute, postCompute, compute

[59]:
def compute(sp, st):
    print('computeCall with state: ', st)

spin1.compute = compute

spin1._computeBase__compute(1)
computeCall with state:  1
[ ]: