Skip to content

docs: Add example to postselection #161

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"sphinx.ext.viewcode",
"sphinx_autodoc_typehints",
"myst_nb",
"jupyter_sphinx",
]

intersphinx_mapping = {
Expand Down
4 changes: 1 addition & 3 deletions docs/postselection.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
qermit.postselection
====================

.. autofunction:: qermit.postselection.postselect_mitres.gen_postselect_mitres

.. autoclass:: qermit.postselection.postselect_manager.PostselectMgr
:members:
:special-members:

.. autofunction:: qermit.postselection.postselect_mitres.gen_postselect_mitres
724 changes: 629 additions & 95 deletions poetry.lock

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ authors = [
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.10,<4"
python = ">=3.10,<3.14"
pytket-qiskit = "^0.60.0"
matplotlib = "^3.8.3"

Expand All @@ -23,11 +23,12 @@ mypy = { version = "^1.9.0", optional = true }
sphinx = { version = "^8.1.3", optional = true }
pytest-cov = { version = "^6.0.0", optional = true }
qiskit-ibm-provider = { version = "^0.11.0", optional = true }
pytket-quantinuum = { version = "^0.41.0", optional = true }
ruff = { version = "^0.8.1", optional = true }
pytket-quantinuum = {extras = ["pecos"], version = "^0.41.0", optional = true}
furo = { version = "^2024.8.6", optional = true }
myst-nb = { version = "^1.1.2", optional = true}
sphinx-autodoc-typehints = {version = "^2.5.0", optional = true}
sphinx-autodoc-typehints = { version = "^2.5.0", optional = true }
jupyter-sphinx = { version = "^0.5.3", optional = true}

[tool.poetry.extras]
tests = [
Expand All @@ -44,8 +45,12 @@ docs = [
"pytket-quantinuum",
"myst-nb",
"sphinx-autodoc-typehints",
"jupyter-sphinx"
]

[tool.ruff]
exclude = ["docs/jupyter_execute/"]

[tool.ruff.lint]
select = ["I001"]

Expand Down
94 changes: 83 additions & 11 deletions qermit/postselection/postselect_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,76 @@

class PostselectMgr:
"""Class for tracking and applying post selection to results.
Includes other methods to analyse the results after post selection.

An example use case might be the following. Here a Bell state is
prepared. We would like to keep one bit of the results, conditioned
on the other being 0. That's to say that the postselected
bits should all be 0.

.. jupyter-execute::

from pytket import Circuit, Bit, Qubit
from pytket.circuit.display import render_circuit_jupyter

# Two qubits. The result of measuring the first will
# be used to postselect the result of measuring the second.
post_q = Qubit(0)
comp_q = Qubit(1)

# Construct Bell state preparation circuit.
circuit = Circuit()
circuit.add_qubit(post_q)
circuit.add_qubit(comp_q)

circuit.H(post_q)
circuit.CX(post_q, comp_q)

post_b = Bit(0)
comp_b = Bit(1)

circuit.add_bit(post_b)
circuit.add_bit(comp_b)

circuit.Measure(comp_q, comp_b)
circuit.Measure(post_q, post_b)

render_circuit_jupyter(circuit)

Running this circuit gives a roughly equal mix of 00 and 11
computation basis states.

.. jupyter-execute::

from qermit.postselection import PostselectMgr
from pytket.extensions.quantinuum import QuantinuumBackend, QuantinuumAPIOffline

backend = QuantinuumBackend(
device_name="H1-1LE",
api_handler = QuantinuumAPIOffline(),
)

compiled_circuit = backend.get_compiled_circuit(circuit)
result = backend.run_circuit(compiled_circuit, 100)
result.get_counts()

"""

def __init__(
self,
compute_cbits: List[Bit],
postselect_cbits: List[Bit],
):
"""Initialisation method.
"""
This class is straightforwardly initialised with the computation
and post selection bits. The computation bits are post selected based
on the results of the post selection bits.

.. jupyter-execute::

postselect_mgr = PostselectMgr(
compute_cbits=[comp_b],
postselect_cbits=[post_b]
)

:param compute_cbits: Bits in the circuit which are not affected
by post selection.
Expand All @@ -37,7 +98,7 @@ def __init__(

self.cbits: List[Bit] = compute_cbits + postselect_cbits

def get_postselected_shot(self, shot: Tuple[int, ...]) -> Tuple[int, ...]:
def _get_postselected_shot(self, shot: Tuple[int, ...]) -> Tuple[int, ...]:
"Removes postselection bits from shot."
return tuple(
[
Expand All @@ -47,7 +108,7 @@ def get_postselected_shot(self, shot: Tuple[int, ...]) -> Tuple[int, ...]:
]
)

def is_postselect_shot(self, shot: Tuple[int, ...]) -> bool:
def _is_postselect_shot(self, shot: Tuple[int, ...]) -> bool:
"Determines if shot survives postselection"

# TODO: It may be nice to generalise this so that other functions
Expand All @@ -58,10 +119,11 @@ def is_postselect_shot(self, shot: Tuple[int, ...]) -> bool:
if reg in self.postselect_cbits
)

def dict_to_result(self, result_dict: Dict[Tuple[int, ...], int]) -> BackendResult:
def _dict_to_result(self, result_dict: Dict[Tuple[int, ...], int]) -> BackendResult:
"""Convert dictionary to BackendResult.

:param result_dict: Dictionary to convert.
:param result_dict: Dictionary to convert. Should be in the form of
map from shot to count.
:return: Corresponding BackendResult.
"""

Expand All @@ -84,29 +146,39 @@ def postselect_result(self, result: BackendResult) -> BackendResult:
"""Transforms BackendResult to keep only shots which should be
post selected.

.. jupyter-execute::

post_result = postselect_mgr.postselect_result(result=result)
post_result.get_counts()

:param result: Result to be modified.
:return: Postselected shots.
"""

return self.dict_to_result(
return self._dict_to_result(
{
self.get_postselected_shot(shot): count
self._get_postselected_shot(shot): count
for shot, count in result.get_counts(cbits=self.cbits).items()
if self.is_postselect_shot(shot)
if self._is_postselect_shot(shot)
}
)

def merge_result(self, result: BackendResult) -> BackendResult:
"""Transforms BackendResult so that postselection bits are
removed, but no shots are removed by postselection.

.. jupyter-execute::

merge_result = postselect_mgr.merge_result(result=result)
merge_result.get_counts()

:param result: Result to be transformed.
:return: Result with postselection bits removed.
"""

merge_dict: Dict[Tuple[int, ...], int] = {}
for shot, count in result.get_counts(cbits=self.cbits).items():
postselected_shot = self.get_postselected_shot(shot)
postselected_shot = self._get_postselected_shot(shot)
merge_dict[postselected_shot] = merge_dict.get(postselected_shot, 0) + count

return self.dict_to_result(merge_dict)
return self._dict_to_result(merge_dict)
51 changes: 50 additions & 1 deletion qermit/postselection/postselect_mitres.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def task(
return (circ_shots_list, [postselect_mgr for _ in circ_shots_list])

return MitTask(
_label="ConstantNode",
_label="UniformPostselection",
_n_in_wires=1,
_n_out_wires=2,
_method=task,
Expand All @@ -77,6 +77,55 @@ def gen_postselect_mitres(
) -> MitRes:
"""Generates MitRes running given circuit and applying postselection.

In the following example we prepare and measure a Bell state.

.. jupyter-execute::

from pytket import Circuit
from pytket.circuit.display import render_circuit_jupyter

circuit = Circuit(2,2).H(0).CX(0,1).measure_all()
render_circuit_jupyter(circuit)

We would like to postselect one measurement outcome based on the
other being 0. We prepare a postselect mitres accordingly.

.. jupyter-execute::

from qermit.postselection import gen_postselect_mitres
from pytket.extensions.quantinuum import QuantinuumBackend, QuantinuumAPIOffline
from qermit.postselection import PostselectMgr

backend = QuantinuumBackend(
device_name="H1-1LE",
api_handler = QuantinuumAPIOffline()
)

postselect_mgr = PostselectMgr(
compute_cbits=[circuit.bits[0]],
postselect_cbits=[circuit.bits[1]]
)

mitres = gen_postselect_mitres(
backend=backend,
postselect_mgr=postselect_mgr,
)
mitres.get_task_graph()

We can then construct the experiment we wish to run, and pass it through
the postselect mitres.

.. jupyter-execute::

from qermit import CircuitShots

circ_shots = CircuitShots(
Circuit=backend.get_compiled_circuit(circuit),
Shots=100
)
result_list = mitres.run([circ_shots])
result_list[0].get_counts()

:param backend: Backend on this circuits are run.
:param postselect_mgr: Postselection manager.
:return: MitRes running given circuit and applying postselection.
Expand Down