[1]:
import quanguru as qg

updateBase#

This the base class for the _sweep and Update classes.

_sweep objects are used/created internally with/by the Sweep class, and, as their names suggest, these are used to sweep parameters.

Update is created by the user when a certain parameter need to be updated in a certain step of a protocol.

In both of these cases, the parameter that needs to be changed is just the value of an attribute in some other object/s. This task requires 3 information: which object/s?, which attribute ? (its name), and what is the value of the attribute? updateBase implements the first two and expects the value to be provided. So, first let’s see how updateBase is used, and explain its details later.

In below examples, I will use Qubit objects, but updateBase works with any named and their attributes. Actually, NOTE THAT updateBase works only with named objects or the _auxiliaryClass. This is a deliberate design choice, and it specifically looks for the type of the given object/s and rejects other types of object. Without this type restriction, the functionality of updateBase would work for any object and its attribute, but it could create undefined behaviors for the sweeps. Still, there are ways to go around of this restriction, in case it might be needed for advanced users. I start by showing this hack but don’t really recommend this hack unless you are absolutely sure what you are doing.

[2]:
# so, first let's create a dummy class
class testClass:#pylint:disable=too-few-public-methods
    def __init__(self) -> None:
        super().__init__()

ob1 = testClass()
# create and set an attribute to it
setattr(ob1, "newAttribute", 5)

# now, we can change the value of this newAttribute with an updateBase as follows
s0 = qg.baseClasses.updateBase(key='newAttribute')
# explicitly add this object in the name mangled `_qBase__subSys` dictionary with some arbitrary key
s0._qBase__subSys["testClass"] = ob1

# print the current newAttribute
print(ob1.newAttribute)

# run the update as follows with the new value for the newAttribute
s0._runUpdate(1)
# print the updated/current newAttribute
print(ob1.newAttribute)

5
1

Having covered some arbitrary object, I will use named objects from this point on, and, when I simply say object, I will mean a named object in all my explanations.

So, first let’s cover the easiest case: we have a single object, and we have an explicit reference to it.

[3]:
# say we have the below Qubit objects with the references qub1, qub2
qub1 = qg.Qubit(frequency=1)
qub2 = qg.Qubit(frequency=2)
# and, we want to change their frequencies through a sweep or update

# we can create an updateBase object as follows
# by providing the reference qub1 and the name of the attribute 'frequency'
s1 = qg.baseClasses.updateBase(system=qub1, key='frequency')

# you can also create an update first, and set the system and key after that
s2 = qg.baseClasses.updateBase()
s2.system = qub2
s2.key = 'frequency'

# print the current qubit frequency
print(qub1.frequency, qub2.frequency)

# run the update as follows with the new value for the frequency
s1._runUpdate(3)
print(qub1.frequency, qub2.frequency)
s2._runUpdate(5)
# print the updated/current frequency
print(qub1.frequency, qub2.frequency)
1 2
3 2
3 5

Now, let’s say we want to update the same attribute for more than one objects to the same value.

Even further, let’s say we created these objects in a way that we don’t have an explicit reference to them.

In this case, we first of all don’t want to create several updateBases for each object to update same attribute to the same value. What we want is that, instead of a single object reference, we just give a list of system objects to the updateBase.

Even further, we could just use their names or aliases, if we don’t have an explicit reference to them. After all the names and aliases of named object are unique.

[4]:
# above situation might happen when we create composite system with + operator as follows
threeQubits = qg.Qubit(frequency=1) + qg.Qubit(frequency=1, alias='qub3') +  qg.Qubit(frequency=1, alias=['qub4', "4"])

# now we don't have explicit references to the individual qubits
# still, we could get references to them using getByNameOrAlias
# but updateBase do that for us
# so let's pass a list containing the name and aliases of our qubits
# we know that the left most one is Qubit3 (because we created only 2 other qubits before this cell),
# and for the other two, we gave them aliases, so we can use any alias we want
# this shows the importance of an alias and why it needs to be unique,
# with aliases, we don't have to track the number of qubits created above,
# also, it might not be clear to everyone if the left-most or right-most is Qubit3
s3 = qg.baseClasses.updateBase(system=['Qubit3', 'qub3', "4"], key='frequency')

# also you can add more systems into this update as
s3.system = 'Qubit2' # this does not remove/replace the existing list, but adds the Qubit2 into it

# let's print the current frequencies
print( [q.frequency for q in threeQubits.subSys.values()] , qub2.frequency)
# run the update as follows with the new value for the frequency
s3._runUpdate(3)
# print the updated/current frequency
print( [q.frequency for q in threeQubits.subSys.values()] , qub2.frequency)
[1, 1, 1] 5
[3, 3, 3] 3

As noted above, the updateBase can also work with the _auxiliaryClass so that we may sweep/update our auxiliary information stored in the auxObj class attribute of qBase, which is an _auxiliaryClass instance.

[5]:
# let's create a Qubit (which is a qBase instance), and store something in auxObj
qa = qg.Qubit()
qa.auxObj.someAuxInfo = 3

s4 = qg.baseClasses.updateBase(system=qa.auxObj, key='someAuxInfo')

# let's print the current someAuxInfo
print( qa.auxObj.someAuxInfo )
# run the update as follows with the new value for the someAuxInfo
s4._runUpdate(2)
# print the updated/current someAuxInfo
print( qa.auxObj.someAuxInfo )
3
2

Further, it also can change the value inside the aux dictionary of qBase as follows

[6]:
# let's use the above qubit to store some value in aux dictionary
qa.auxDict['some key'] = 10

# let's create a updateBase, but this time we won't give system, but set _aux to True
s5 = qg.baseClasses.updateBase(_aux=True, key='some key')
# let's print the current aux['some key']
print( qa.auxDict['some key'] )
# run the update as follows with the new value for the aux['some key']
s5._runUpdate(1)
# print the updated/current aux['some key']
print( qa.auxDict['some key'] )
10
1

Now, let’s discuss some of the implementation details.

  • system is just another name for subSys. This is implemented to create terminology. Otherwise, above examples would work exactly the same if we had used subSys. So, we can achieve the same things, like using object name/alias etc., that can achieve with subSys using system.

  • the only difference is that the system getter returns a list (values in the subSys dictionary), subSys getter returns a dictionary

  • name of the attribute that we want to update is stored in key, as it is obvious from above

  • _runUpdate is the default update behavior, and if we want to have some other custom function, we can store it in the __function attribute, which is used inside the child classes.

  • _aux boolean is to indicate that we are updating the aux dictionary.

[ ]: