Skip to content
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

Support contiguous qubits for OpenQASM IR #237

Closed
wants to merge 3 commits into from
Closed

Conversation

yitchen-tim
Copy link

@yitchen-tim yitchen-tim commented Apr 5, 2024

Description of changes:

  • Qubits are mapped to contiguous qubits before simulation
  • Adapt the indices of measuredQubit in the returned simulation result

Testing done:
tox

Merge Checklist

Put an x in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your pull request.

General

Tests

  • I have added tests that prove my fix is effective or that my feature works (if appropriate)
  • I have checked that my tests are not configured for a specific region or account (if appropriate)

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link

codecov bot commented Apr 5, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (420aeaf) to head (1b9fdcf).
Report is 28 commits behind head on main.

Current head 1b9fdcf differs from pull request most recent head ae762a6

Please upload reports for the commit ae762a6 to get more accurate results.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #237   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           48        48           
  Lines         3770      3798   +28     
  Branches       915       925   +10     
=========================================
+ Hits          3770      3798   +28     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@yitchen-tim yitchen-tim marked this pull request as ready for review April 5, 2024 15:49
@yitchen-tim yitchen-tim requested a review from a team as a code owner April 5, 2024 15:49
@yitchen-tim yitchen-tim marked this pull request as draft April 5, 2024 16:58
measuredQubits=(
measured_qubits if measured_qubits else self._get_all_qubits(simulation.qubit_count)
),
measuredQubits=(measured_qubits if measured_qubits else used_qubits),
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the order have an effect? Does it align with classical bit indices?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is used only in the case that there's no requested measurement, and changes the default behavior to measure all used qubits rather than all declared qubits?

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the order have an effect? Does it align with classical bit indices?

This must be the sorted order of the used qubits, right?

@@ -301,7 +301,7 @@ def test_properties():
],
"supportPhysicalQubits": False,
"supportsPartialVerbatimBox": False,
"requiresContiguousQubitIndices": True,
"requiresContiguousQubitIndices": False,
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder why the test properties don't use the actual properties?

@@ -795,3 +795,21 @@ def test_measure_targets():
assert 400 < np.sum(measurements, axis=0)[0] < 600
assert len(measurements[0]) == 1
assert result.measuredQubits == [0]


def test_non_contiguous_qubits_with_shots0():
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please add tests showing returning measurements for multiple qubits? Does the ordering follow the user's order, or is it sorted by the qubit index?

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also add tests with sampled results? I think that's the kind of result that _formatted_measurements acts on, is that right?

@@ -438,8 +475,12 @@ def run_openqasm(
"""
circuit = self.parse_program(openqasm_ir).circuit
qubit_count = circuit.num_qubits
used_qubits = sorted(list(circuit.qubit_set))
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer it when variables are defined closer to where they are used. That requires less jumping around the code to figure out what a variable means, and less "mental mapping" where I have to store a lot of info in my head while reading a function. Since this is only used once, you don't even have to define it at all, and can just pass it to the function that needs it.

If you do define it here, why not use it for line 481? used_qubits[-1] would be faster than max(set).

If you keep the variable, could we call it sorted_used_qubits? It's a bit verbose, but it was my first question when I saw this variable name elsewhere.

@@ -479,7 +520,9 @@ def run_openqasm(
else:
simulation.evolve(circuit.basis_rotation_instructions)

return self._create_results_obj(results, openqasm_ir, simulation, measured_qubits)
return self._create_results_obj(
results, openqasm_ir, simulation, used_qubits, measured_qubits
Copy link
Contributor

Choose a reason for hiding this comment

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

What if you moved the measured_qubits if measured_qubits else used_qubits logic here?

It looks to me at the moment that _create_results_obj won't ever need both of them -- and the used_qubits simply are the measured qubits when measured qubits is None. This has the extra benefit of reducing the number of variables passed to that function, making it a little easier to understand what the function does.

measured_qubits = circuit.measured_qubits

if max(circuit.qubit_set) != len(circuit.qubit_set) - 1:
circuit = self._map_to_contiguous_qubits(circuit)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe _map_circuit_to_contiguous_qubits? The name as is makes me expect indices as inputs. Note how similar, semantically, this name is to _contiguous_qubit_mapping. They're synonyms.

Another nit: Can we move line 481 into this function? _formatted_measurements also containerizes this check.

I prefer it because it makes it clear that every circuit will be mapped to operate on contiguous qubits. It just so happens that some circuits already trivially do this, but that fact might be distracting here -- a cursory look might make someone think we're only conditionally getting contiguous qubits

"""Map the qubits in operations and result types to contiguous qubits.

Args:
circuit (Circuit): Circuit which qubits are not contiguous.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
circuit (Circuit): Circuit which qubits are not contiguous.
circuit (Circuit): Circuit in which qubits may not be contiguous.

Or simply, Circuit to map.

circuit (Circuit): Circuit which qubits are not contiguous.

Returns:
Circuit: Circuit qubits mapped to with contiguous qubits.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Circuit: Circuit qubits mapped to with contiguous qubits.
Circuit: Circuit with qubits mapped to contiguous qubits.

return measurements

def _map_to_contiguous_qubits(self, circuit: Circuit) -> Circuit:
"""Map the qubits in operations and result types to contiguous qubits.
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to include here a Note: describing what contiguous qubits are, explaining that simulators require them for computation, and we handle the user's requested mapping here (which is useful for running on QPUs, that might be TMI though, idk)

measurements = np.array(measurements)[:, measured_qubits].tolist()
if max(measured_qubits) != len(measured_qubits) - 1:
qubit_map = BaseLocalSimulator._contiguous_qubit_mapping(measured_qubits)
mapped_measured_qubits = [qubit_map[q] for q in measured_qubits]
Copy link
Contributor

Choose a reason for hiding this comment

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

is mapped_measured_qubits ever different from the inverse of qubit_map?

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.

None yet

2 participants