-
Notifications
You must be signed in to change notification settings - Fork 4
[CQT-243] Implementation of global phase #563
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
juanboschero
commented
Jul 9, 2025
- Changed circuit class
- Added PhaseMap class
- Changed internal decomposer logic
- Added helper functions
- Add tests
elenbaasc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the effort!
I think that the PhaseMap could simply track the phase angle (so no need for an array of complex values).
Also, the phase map still needs a separate test module.
And don't forget to add an entry to the Changelog.
opensquirrel/common.py
Outdated
| return are_matrices_equivalent_up_to_global_phase(matrix, np.eye(matrix.shape[0], dtype=np.complex128)) | ||
|
|
||
|
|
||
| def get_phase_angle(scalar: np.complex128) -> np.complex128: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The phase angle will never be complex, right? It's just a float value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it could also be a simply built-in float, instead of a numpy.float ... right?
opensquirrel/phasemap.py
Outdated
| """Checks if qubit is in the phase map.""" | ||
| return qubit in self.qubit_phase_map | ||
|
|
||
| def add_qubit_phase(self, qubit: QubitLike, phase: np.complex128) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def add_qubit_phase(self, qubit: QubitLike, phase: np.complex128) -> None: | |
| def set_qubit_phase(self, qubit: QubitLike, phase: np.complex128) -> None: |
Co-authored-by: Chris Elenbaas <[email protected]>
elenbaasc
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the adjustments!
Mainly the tests need to be updated. It is not clear what the function is of the phase map (at least not from the tests). There needs to be a test that explicitly shows that the compiled circuit has been adjusted/corrected for phase differences between interacting qubits. Current tests don't show/test this.
Other than that, there are still some minor changes and some comments still left from the previous review round.
opensquirrel/phase_map.py
Outdated
| def __init__(self, qubit_register_size: int) -> None: | ||
| """Initialize a PhaseMap object.""" | ||
|
|
||
| self.qubit_phase_map = np.zeros(qubit_register_size, dtype=np.float64) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason why this needs to be a numpy array?
| self.qubit_phase_map = np.zeros(qubit_register_size, dtype=np.float64) | |
| self.qubit_phase_map = [0] * qubit_register_size |
opensquirrel/phase_map.py
Outdated
| from opensquirrel.ir import Qubit, QubitLike | ||
|
|
||
|
|
||
| class PhaseMap: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is more a PhaseRecord instead. It does not 'map' from a domain to a range, but it records the value of the phase as you pass through the circuit. Replace 'map' -> 'record' in the rest of the code.
| Rz(0.78539824) q[1] | ||
| X90 q[1] | ||
| Rz(-3.1415927) q[1] | ||
| Rz(1.5707963) q[1] | ||
| X90 q[1] | ||
| Rz(1.5707963) q[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes are due to the change made in the in check_gate_replacement where in the conditional statement if is_identity_matrix_up_to_a_global_phase(replaced_matrix): now an empty list is returned, whereas before nothing was returned. So, after the first decomposition pass (SWAP2CNOTDecomposer) the Rz(tau) q[1] is removed from the circuit (because it is identity). This is unwanted behaviour, we should not want a 2-qubit decomposition pass to process single-qubit gates, even if they are identity.
Moreover, these differences are not due to the correction of a phase difference. If you remove the Rz(tau) q[1], the compiled circuit you end up with is the same with the 'phase map' functionality, as it is without.
tests/test_phase_map.py
Outdated
| from opensquirrel.passes.merger import SingleQubitGatesMerger | ||
|
|
||
|
|
||
| def test_integration_global_phase() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is too high level to test the integration of the global phase and also the resulting (compiled) circuit is the same without the phase map functionality (same output, if this test is run on develop).
A test that specifically tests the functionality of the phase map is needed, whereby the resulting circuit has been corrected for the global phase difference (i.e., the compiled circuit should look different if run on current develop branch and be incorrect (semantically))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it a bit simpler and explicitly left it as a high level test
tests/test_phase_map.py
Outdated
| ) | ||
|
|
||
|
|
||
| def test_phase_map_circuit_builder() -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The creation and evolution of the phase map does not depend on whether the parser or circuit builder is used, so it is no need to test it explicitly.
This test does show that the phase map is updated (although not in a very predictable way). Is they expected result obvious, given the circuit and compile passes used? Would be better to make this more simply and have a couple of different circuits, resulting in different values for the recorded phases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made it smaller, I realized now that there was a blatant error in the decomposer. I was modifying the list without storing it (I forgot that Python does not implicitly do deep copies). You will now see how this impacts the rest of our code.
opensquirrel/common.py
Outdated
| return are_matrices_equivalent_up_to_global_phase(matrix, np.eye(matrix.shape[0], dtype=np.complex128)) | ||
|
|
||
|
|
||
| def get_phase_angle(scalar: np.complex128) -> np.complex128: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it could also be a simply built-in float, instead of a numpy.float ... right?
opensquirrel/phase_map.py
Outdated
| def add_qubit_phase(self, qubit: QubitLike, phase: np.float64) -> None: | ||
| self.qubit_phase_map[Qubit(qubit).index] += phase | ||
|
|
||
| def get_qubit_phase(self, qubit: QubitLike) -> np.float64: | ||
| return np.float64(self.qubit_phase_map[Qubit(qubit).index]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| def add_qubit_phase(self, qubit: QubitLike, phase: np.float64) -> None: | |
| self.qubit_phase_map[Qubit(qubit).index] += phase | |
| def get_qubit_phase(self, qubit: QubitLike) -> np.float64: | |
| return np.float64(self.qubit_phase_map[Qubit(qubit).index]) | |
| def add_qubit_phase(self, qubit: QubitLike, phase: float) -> None: | |
| self.qubit_phase_map[Qubit(qubit).index] += phase | |
| def get_qubit_phase(self, qubit: QubitLike) -> float: | |
| return self.qubit_phase_map[Qubit(qubit).index] |