Skip to content

Conversation

@juanboschero
Copy link
Collaborator

  • Changed circuit class
  • Added PhaseMap class
  • Changed internal decomposer logic
  • Added helper functions
  • Add tests

@juanboschero juanboschero requested a review from elenbaasc July 9, 2025 08:14
Copy link
Collaborator

@elenbaasc elenbaasc left a 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.

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:
Copy link
Collaborator

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.

Copy link
Collaborator

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?

"""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:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def add_qubit_phase(self, qubit: QubitLike, phase: np.complex128) -> None:
def set_qubit_phase(self, qubit: QubitLike, phase: np.complex128) -> None:

Copy link
Collaborator

@elenbaasc elenbaasc left a 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.

def __init__(self, qubit_register_size: int) -> None:
"""Initialize a PhaseMap object."""

self.qubit_phase_map = np.zeros(qubit_register_size, dtype=np.float64)
Copy link
Collaborator

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?

Suggested change
self.qubit_phase_map = np.zeros(qubit_register_size, dtype=np.float64)
self.qubit_phase_map = [0] * qubit_register_size

from opensquirrel.ir import Qubit, QubitLike


class PhaseMap:
Copy link
Collaborator

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.

Comment on lines 126 to 130
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]
Copy link
Collaborator

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.

from opensquirrel.passes.merger import SingleQubitGatesMerger


def test_integration_global_phase() -> None:
Copy link
Collaborator

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))

Copy link
Collaborator Author

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

)


def test_phase_map_circuit_builder() -> None:
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

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:
Copy link
Collaborator

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?

Comment on lines 16 to 20
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])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants