QuanGuru (pronounced Kangaroo) is a Python library for numerical modelling of quantum systems. It is still under-development, and it consists of tools for numerical simulations of Quantum systems.
Another integral step in achieving issue #213 is to be able to have protocols objects whose unitary isn't generated by a function such as matrix exponentiation or via a time-dependent hamiltonian, but rather by transforming the unitary generated by another protocol object.
In the following example code, the fallingEdge object is a transformed protocol. Internally, it's unitary will be generated by taking the transpose of the unitary cached in risingEdge (which is likely stored in the risingEdge._paramBoundBase__matrix attribute after it is generated in the risingEdge object).
The function with which the unitary will be transformed can be any arbitrary user defined function
This user defined function should take a protocol object and a unitary matrix as its arguments (the protocol object actually passed to it will be the transformed protocol object, i.e. fallingEdge here)
There should be minimal changes to the risingEdge object upon instantiation of the fallingEdge object
Transformation function
For users to be able to define this transformation function in their own code, the required arguments, output, and how it is properly used to generate a transformed protocol object should be clear. The arguments should be consistent with the types of arguments expected in other user defined functions which are passed to other parts of the library.
Arguments
qPro
the protocol from which the transformed protocol is being copied from
this argument is important because the transformation function may need to access parameters/variables stored in the protocol or other systems (simulations, qSystems, etc.)
UIn
the input unitary (effectively the output of qPro.unitary())
Returns
UOut
The transformed unitary
These arguments reflect similar arguments used by the compute functions (superSys, state) compared to (qPro, unitary) here.
Proposed instantiation method:
It is unclear whether the transformed protocol object can
where transform is a built in method of all protocols and it's first argument is the unitary transformation object
Other notes
The unitaries of transformed protocol objects should only be generated upon calling their .unitary method (this is not a strict condition, however)
For this feature, we can assume that the unitaries of original protocol objects will always be generated before the unitaries of the transformed protocol objects
Parameter Updating
For the sake of computational efficiency, we should minimise the number of times that the transformation function (which may be computationally expensive) needs to be computed. E.g. if we call for the unitary matrix of the transformed protocol object and neither it nor the object it has been copied from have been updated since the previous unitary call, then we should not have to apply the transformation function again but rather fetch the unitary from some previous cache.
Here we only consider what happens when the original protocol and its associated simulation/protocols are modified
conditions for updating (what happens when either of the two objects are updated)
suggested implementation (what can be done about updating)
Backwards Compatibility
Should copyStep be changed to suit our needs, or should we create a new class in its image?
Questions
What should the transformed protocol actually be?
What attributes should it have?
Should it be a protocol object in it's own right, i.e. should it function like a normal protocol except its unitary generation method points at a function which transforms another unitary?
It is essentially an object which provides a unitary based on the unitary of another protocol object. Does it need to have all the inner workings of a protocol object?
Can it be possible for a transformed protocol to have different parameters to its original? Doesn't that mean it is no longer a transformed copy of the original? What unique parameters would it even need to hold?
In a nutshell, we can form a picture of what attributes and structure it requires more simply by considering what functionalities we want it to have.
How should the transformed protocol object handle a unitary call if its "original" protocol has not yet generated its unitary
I think the simple solution to this (as well as many other issues with regards to parameter updating) can be solved simply by having the transformed protocol object .createUnitary() method point to a wrapped function (or otherwise involving the user defined transformation function) of the .unitary() method of the original protocol
Benefits of this:
Parameter updating is already handled by the chain of method calls under .unitary() (see above)
If the unitary of the original protocol has been generated, then this call will just result in the matrix being retrieved from the paramBoundBase_matrix cache
If the unitary of the original protocol hasn't been generated, then calling the transformed protocol .unitary() will generated the original protocol unitary
Please describe the expected enhancement
Another integral step in achieving issue #213 is to be able to have protocols objects whose unitary isn't generated by a function such as matrix exponentiation or via a time-dependent hamiltonian, but rather by transforming the unitary generated by another protocol object.
In the following example code, the
fallingEdge
object is a transformed protocol. Internally, it's unitary will be generated by taking the transpose of the unitary cached inrisingEdge
(which is likely stored in therisingEdge._paramBoundBase__matrix
attribute after it is generated in therisingEdge
object).Some notes on this:
fallingEdge
here)risingEdge
object upon instantiation of thefallingEdge
objectTransformation function For users to be able to define this transformation function in their own code, the required arguments, output, and how it is properly used to generate a transformed protocol object should be clear. The arguments should be consistent with the types of arguments expected in other user defined functions which are passed to other parts of the library.
Arguments
qPro
UIn
qPro.unitary()
)Returns
UOut
These arguments reflect similar arguments used by the compute functions (superSys, state) compared to (qPro, unitary) here.
Proposed instantiation method: It is unclear whether the transformed protocol object can
where transform is a built in method of all protocols and it's first argument is the unitary transformation object
Other notes
Parameter Updating For the sake of computational efficiency, we should minimise the number of times that the transformation function (which may be computationally expensive) needs to be computed. E.g. if we call for the unitary matrix of the transformed protocol object and neither it nor the object it has been copied from have been updated since the previous unitary call, then we should not have to apply the transformation function again but rather fetch the unitary from some previous cache.
Here we only consider what happens when the original protocol and its associated simulation/protocols are modified
Backwards Compatibility Should copyStep be changed to suit our needs, or should we create a new class in its image?
Questions