quantum-exercises 0.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantum_exercises/__init__.py +44 -0
- quantum_exercises/_course/exercises/01_environment/README.md +34 -0
- quantum_exercises/_course/exercises/01_environment/check.py +63 -0
- quantum_exercises/_course/exercises/01_environment/exercise.py +11 -0
- quantum_exercises/_course/exercises/01_environment/hints.md +30 -0
- quantum_exercises/_course/exercises/01_environment/meta.toml +4 -0
- quantum_exercises/_course/exercises/01_environment/solution.py +5 -0
- quantum_exercises/_course/exercises/01_environment/template.py +11 -0
- quantum_exercises/_course/exercises/02_dictionaries/README.md +41 -0
- quantum_exercises/_course/exercises/02_dictionaries/check.py +91 -0
- quantum_exercises/_course/exercises/02_dictionaries/exercise.py +40 -0
- quantum_exercises/_course/exercises/02_dictionaries/hints.md +40 -0
- quantum_exercises/_course/exercises/02_dictionaries/meta.toml +4 -0
- quantum_exercises/_course/exercises/02_dictionaries/solution.py +15 -0
- quantum_exercises/_course/exercises/02_dictionaries/template.py +40 -0
- quantum_exercises/_course/exercises/03_first_circuit/README.md +39 -0
- quantum_exercises/_course/exercises/03_first_circuit/check.py +49 -0
- quantum_exercises/_course/exercises/03_first_circuit/exercise.py +13 -0
- quantum_exercises/_course/exercises/03_first_circuit/hints.md +28 -0
- quantum_exercises/_course/exercises/03_first_circuit/meta.toml +4 -0
- quantum_exercises/_course/exercises/03_first_circuit/solution.py +6 -0
- quantum_exercises/_course/exercises/03_first_circuit/template.py +13 -0
- quantum_exercises/_course/exercises/04_measurement/README.md +48 -0
- quantum_exercises/_course/exercises/04_measurement/check.py +88 -0
- quantum_exercises/_course/exercises/04_measurement/exercise.py +25 -0
- quantum_exercises/_course/exercises/04_measurement/hints.md +31 -0
- quantum_exercises/_course/exercises/04_measurement/meta.toml +4 -0
- quantum_exercises/_course/exercises/04_measurement/solution.py +13 -0
- quantum_exercises/_course/exercises/04_measurement/template.py +25 -0
- quantum_exercises/_course/exercises/05_reading_counts/README.md +64 -0
- quantum_exercises/_course/exercises/05_reading_counts/check.py +74 -0
- quantum_exercises/_course/exercises/05_reading_counts/exercise.py +28 -0
- quantum_exercises/_course/exercises/05_reading_counts/hints.md +38 -0
- quantum_exercises/_course/exercises/05_reading_counts/meta.toml +4 -0
- quantum_exercises/_course/exercises/05_reading_counts/solution.py +9 -0
- quantum_exercises/_course/exercises/05_reading_counts/template.py +28 -0
- quantum_exercises/_course/exercises/06_born_rule/README.md +68 -0
- quantum_exercises/_course/exercises/06_born_rule/check.py +93 -0
- quantum_exercises/_course/exercises/06_born_rule/exercise.py +29 -0
- quantum_exercises/_course/exercises/06_born_rule/hints.md +33 -0
- quantum_exercises/_course/exercises/06_born_rule/meta.toml +4 -0
- quantum_exercises/_course/exercises/06_born_rule/solution.py +17 -0
- quantum_exercises/_course/exercises/06_born_rule/template.py +29 -0
- quantum_exercises/_course/exercises/07_statevector/README.md +51 -0
- quantum_exercises/_course/exercises/07_statevector/check.py +73 -0
- quantum_exercises/_course/exercises/07_statevector/exercise.py +15 -0
- quantum_exercises/_course/exercises/07_statevector/hints.md +45 -0
- quantum_exercises/_course/exercises/07_statevector/meta.toml +4 -0
- quantum_exercises/_course/exercises/07_statevector/solution.py +13 -0
- quantum_exercises/_course/exercises/07_statevector/template.py +15 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/README.md +51 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/check.py +58 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/exercise.py +15 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/hints.md +43 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/meta.toml +4 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/solution.py +9 -0
- quantum_exercises/_course/exercises/08_gates_as_matrices/template.py +15 -0
- quantum_exercises/_course/exercises/09_interference/README.md +69 -0
- quantum_exercises/_course/exercises/09_interference/check.py +157 -0
- quantum_exercises/_course/exercises/09_interference/exercise.py +24 -0
- quantum_exercises/_course/exercises/09_interference/hints.md +43 -0
- quantum_exercises/_course/exercises/09_interference/meta.toml +4 -0
- quantum_exercises/_course/exercises/09_interference/solution.py +17 -0
- quantum_exercises/_course/exercises/09_interference/template.py +24 -0
- quantum_exercises/_course/exercises/10_deutsch/README.md +91 -0
- quantum_exercises/_course/exercises/10_deutsch/check.py +156 -0
- quantum_exercises/_course/exercises/10_deutsch/exercise.py +62 -0
- quantum_exercises/_course/exercises/10_deutsch/hints.md +55 -0
- quantum_exercises/_course/exercises/10_deutsch/meta.toml +4 -0
- quantum_exercises/_course/exercises/10_deutsch/solution.py +43 -0
- quantum_exercises/_course/exercises/10_deutsch/template.py +62 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/README.md +62 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/check.py +121 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/exercise.py +23 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/hints.md +45 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/meta.toml +4 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/solution.py +13 -0
- quantum_exercises/_course/exercises/11_bell_entanglement/template.py +23 -0
- quantum_exercises/_course/exercises/12_exponential_wall/README.md +114 -0
- quantum_exercises/_course/exercises/12_exponential_wall/check.py +276 -0
- quantum_exercises/_course/exercises/12_exponential_wall/exercise.py +47 -0
- quantum_exercises/_course/exercises/12_exponential_wall/hints.md +52 -0
- quantum_exercises/_course/exercises/12_exponential_wall/meta.toml +4 -0
- quantum_exercises/_course/exercises/12_exponential_wall/solution.py +22 -0
- quantum_exercises/_course/exercises/12_exponential_wall/template.py +47 -0
- quantum_exercises/_course/exercises/13_migration/README.md +62 -0
- quantum_exercises/_course/exercises/13_migration/check.py +102 -0
- quantum_exercises/_course/exercises/13_migration/exercise.py +30 -0
- quantum_exercises/_course/exercises/13_migration/hints.md +54 -0
- quantum_exercises/_course/exercises/13_migration/meta.toml +4 -0
- quantum_exercises/_course/exercises/13_migration/solution.py +18 -0
- quantum_exercises/_course/exercises/13_migration/template.py +30 -0
- quantum_exercises/_course/exercises/14_real_hardware/README.md +77 -0
- quantum_exercises/_course/exercises/14_real_hardware/check.py +195 -0
- quantum_exercises/_course/exercises/14_real_hardware/exercise.py +31 -0
- quantum_exercises/_course/exercises/14_real_hardware/hints.md +43 -0
- quantum_exercises/_course/exercises/14_real_hardware/meta.toml +5 -0
- quantum_exercises/_course/exercises/14_real_hardware/solution.py +17 -0
- quantum_exercises/_course/exercises/14_real_hardware/template.py +31 -0
- quantum_exercises/_course/exercises/15_honest_reading/README.md +71 -0
- quantum_exercises/_course/exercises/15_honest_reading/check.py +134 -0
- quantum_exercises/_course/exercises/15_honest_reading/exercise.py +43 -0
- quantum_exercises/_course/exercises/15_honest_reading/hints.md +45 -0
- quantum_exercises/_course/exercises/15_honest_reading/meta.toml +4 -0
- quantum_exercises/_course/exercises/15_honest_reading/solution.py +20 -0
- quantum_exercises/_course/exercises/15_honest_reading/template.py +43 -0
- quantum_exercises/_course/exercises/16_readout_repair/README.md +113 -0
- quantum_exercises/_course/exercises/16_readout_repair/check.py +304 -0
- quantum_exercises/_course/exercises/16_readout_repair/exercise.py +55 -0
- quantum_exercises/_course/exercises/16_readout_repair/hints.md +54 -0
- quantum_exercises/_course/exercises/16_readout_repair/meta.toml +4 -0
- quantum_exercises/_course/exercises/16_readout_repair/solution.py +39 -0
- quantum_exercises/_course/exercises/16_readout_repair/template.py +55 -0
- quantum_exercises/_course/exercises/17_estimator/README.md +79 -0
- quantum_exercises/_course/exercises/17_estimator/check.py +162 -0
- quantum_exercises/_course/exercises/17_estimator/exercise.py +31 -0
- quantum_exercises/_course/exercises/17_estimator/hints.md +47 -0
- quantum_exercises/_course/exercises/17_estimator/meta.toml +4 -0
- quantum_exercises/_course/exercises/17_estimator/solution.py +17 -0
- quantum_exercises/_course/exercises/17_estimator/template.py +31 -0
- quantum_exercises/_course/exercises/18_measurement_bases/README.md +74 -0
- quantum_exercises/_course/exercises/18_measurement_bases/check.py +180 -0
- quantum_exercises/_course/exercises/18_measurement_bases/exercise.py +33 -0
- quantum_exercises/_course/exercises/18_measurement_bases/hints.md +55 -0
- quantum_exercises/_course/exercises/18_measurement_bases/meta.toml +4 -0
- quantum_exercises/_course/exercises/18_measurement_bases/solution.py +19 -0
- quantum_exercises/_course/exercises/18_measurement_bases/template.py +33 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/README.md +106 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/check.py +446 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/exercise.py +53 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/hints.md +63 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/meta.toml +4 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/solution.py +36 -0
- quantum_exercises/_course/exercises/19_bloch_sphere/template.py +53 -0
- quantum_exercises/_course/exercises/20_chsh/README.md +124 -0
- quantum_exercises/_course/exercises/20_chsh/check.py +216 -0
- quantum_exercises/_course/exercises/20_chsh/exercise.py +48 -0
- quantum_exercises/_course/exercises/20_chsh/hints.md +50 -0
- quantum_exercises/_course/exercises/20_chsh/meta.toml +4 -0
- quantum_exercises/_course/exercises/20_chsh/solution.py +33 -0
- quantum_exercises/_course/exercises/20_chsh/template.py +48 -0
- quantum_exercises/_course/notebooks/lab-1-qiskit-patterns.ipynb +344 -0
- quantum_exercises/_course/notebooks/lab-2-noise.ipynb +327 -0
- quantum_exercises/_course/notebooks/lab-3-dynamic-circuits.ipynb +350 -0
- quantum_exercises/_course/notebooks/playground.ipynb +235 -0
- quantum_exercises/backends.py +270 -0
- quantum_exercises/checks.py +451 -0
- quantum_exercises/cli.py +831 -0
- quantum_exercises/doctor.py +342 -0
- quantum_exercises/errors.py +395 -0
- quantum_exercises/registry.py +303 -0
- quantum_exercises/runner.py +341 -0
- quantum_exercises/state.py +310 -0
- quantum_exercises/theme.py +189 -0
- quantum_exercises/ui.py +497 -0
- quantum_exercises/watch.py +115 -0
- quantum_exercises/worker.py +365 -0
- quantum_exercises-0.8.0.dist-info/METADATA +542 -0
- quantum_exercises-0.8.0.dist-info/RECORD +162 -0
- quantum_exercises-0.8.0.dist-info/WHEEL +4 -0
- quantum_exercises-0.8.0.dist-info/entry_points.txt +2 -0
- quantum_exercises-0.8.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""qx: a command line course in quantum computing with Qiskit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import sys
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
|
|
10
|
+
__version__ = "0.8.0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@lru_cache(maxsize=1)
|
|
14
|
+
def invocation() -> str:
|
|
15
|
+
"""How to spell the command in guidance the tool prints.
|
|
16
|
+
|
|
17
|
+
`qx` once it is installed globally, `uv run qx` otherwise. The quickstart
|
|
18
|
+
installs it, so every written file says plainly `qx`; this exists for the
|
|
19
|
+
reader who skipped that step, so the guidance still matches what they can
|
|
20
|
+
type. Printing one form unconditionally would contradict one of them.
|
|
21
|
+
|
|
22
|
+
Lives here rather than in ui so that registry and runner can use it too,
|
|
23
|
+
since ui imports both of them.
|
|
24
|
+
"""
|
|
25
|
+
# A qx inside this interpreter's own environment is the one `uv run` or an
|
|
26
|
+
# activated venv puts on PATH for the moment. It is not there in the reader's
|
|
27
|
+
# next shell, so answering "qx" on the strength of it would be exactly the
|
|
28
|
+
# mistake this helper exists to prevent.
|
|
29
|
+
#
|
|
30
|
+
# So look past it: search every PATH entry and keep the first qx that is not
|
|
31
|
+
# inside this environment. Finding one means a global install really is there
|
|
32
|
+
# and `qx` is what the reader should type, whether or not a venv happens to be
|
|
33
|
+
# active right now. Compared without resolving symlinks on purpose, because
|
|
34
|
+
# `uv tool install` leaves a link in ~/.local/bin pointing back into the
|
|
35
|
+
# tool's own environment, and that link is a real, permanent entry on PATH.
|
|
36
|
+
prefix = os.path.normcase(os.path.abspath(sys.prefix)) + os.sep
|
|
37
|
+
for directory in os.get_exec_path():
|
|
38
|
+
candidate = shutil.which("qx", path=directory)
|
|
39
|
+
if candidate and not os.path.normcase(os.path.abspath(candidate)).startswith(prefix):
|
|
40
|
+
return "qx"
|
|
41
|
+
return "uv run qx"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
__all__ = ["__version__", "invocation"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# 01 - Your environment works
|
|
2
|
+
|
|
3
|
+
Before any quantum mechanics, one boring question has to be settled: does code you
|
|
4
|
+
write in this repository actually run, against the Qiskit that is actually installed?
|
|
5
|
+
|
|
6
|
+
Plenty of quantum tutorials fail at this step and never say so. You get an error
|
|
7
|
+
about `execute` or `Aer`, you assume you misunderstood the physics, and you stop.
|
|
8
|
+
You did not misunderstand anything. You were reading instructions for a version of
|
|
9
|
+
the library that no longer exists.
|
|
10
|
+
|
|
11
|
+
So the first exercise checks the ground you are standing on.
|
|
12
|
+
|
|
13
|
+
## Your task
|
|
14
|
+
|
|
15
|
+
Open `exercise.py` and do two things:
|
|
16
|
+
|
|
17
|
+
1. Import the `qiskit` package.
|
|
18
|
+
2. Set `qiskit_version` to the version string Qiskit reports about itself.
|
|
19
|
+
|
|
20
|
+
The runner compares your answer against the version genuinely installed in this
|
|
21
|
+
project's virtual environment. Copying a number from a blog post will not pass.
|
|
22
|
+
|
|
23
|
+
## Run it
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
qx run 1
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## What you should take away
|
|
30
|
+
|
|
31
|
+
A Python package usually exposes its own version as `package.__version__`. That
|
|
32
|
+
attribute is the single most useful thing to print when something behaves
|
|
33
|
+
differently from a tutorial, because almost every "this doesn't work" in the
|
|
34
|
+
Qiskit world is a version mismatch.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Verification for exercise 01."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import qiskit
|
|
7
|
+
|
|
8
|
+
from quantum_exercises.checks import CheckFailed, require, text_artifact
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def check(mod):
|
|
12
|
+
reported = require(mod, "qiskit_version", str)
|
|
13
|
+
actual = qiskit.__version__
|
|
14
|
+
|
|
15
|
+
_require_asking_the_package(mod)
|
|
16
|
+
|
|
17
|
+
if reported != actual:
|
|
18
|
+
raise CheckFailed(
|
|
19
|
+
f"You reported Qiskit {reported!r}, but this environment has {actual!r}.",
|
|
20
|
+
detail=(
|
|
21
|
+
"Read the value from the package rather than typing it in, so the answer "
|
|
22
|
+
"stays true after the next `uv sync`."
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return text_artifact(
|
|
27
|
+
f"Qiskit SDK {actual}\n"
|
|
28
|
+
f"qiskit-aer {_safe_version('qiskit-aer')}\n"
|
|
29
|
+
f"IBM Runtime {_safe_version('qiskit-ibm-runtime')}",
|
|
30
|
+
caption="Your quantum stack",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _require_asking_the_package(mod) -> None:
|
|
35
|
+
"""Typing the right number in also matches, and stops being right next sync.
|
|
36
|
+
|
|
37
|
+
The exercise is about asking the package, so read the file and insist on it.
|
|
38
|
+
"""
|
|
39
|
+
source_file = getattr(mod, "__file__", None)
|
|
40
|
+
if not source_file:
|
|
41
|
+
return
|
|
42
|
+
try:
|
|
43
|
+
source = Path(source_file).read_text(encoding="utf-8")
|
|
44
|
+
except OSError:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
if "__version__" in source or "importlib" in source:
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
raise CheckFailed(
|
|
51
|
+
"The version is written in by hand rather than read from the package.",
|
|
52
|
+
detail=(
|
|
53
|
+
"A literal is right until the next `uv sync` and then quietly wrong, which is the "
|
|
54
|
+
"exact failure this exercise is about. Ask qiskit what version it is."
|
|
55
|
+
),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _safe_version(package: str) -> str:
|
|
60
|
+
try:
|
|
61
|
+
return version(package)
|
|
62
|
+
except Exception: # noqa: BLE001 - a missing optional package is not a failure here
|
|
63
|
+
return "not installed"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Exercise 01 - your environment works.
|
|
2
|
+
|
|
3
|
+
Fill in the TODOs, then run: qx run 1
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# TODO: import the qiskit package.
|
|
7
|
+
|
|
8
|
+
# TODO: replace None with the version string that qiskit reports about itself.
|
|
9
|
+
# A package normally exposes this as a module-level attribute whose name
|
|
10
|
+
# starts and ends with two underscores.
|
|
11
|
+
qiskit_version = None
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Hint 1
|
|
2
|
+
|
|
3
|
+
Two lines are enough. The first is an `import`. The second assigns to
|
|
4
|
+
`qiskit_version`.
|
|
5
|
+
|
|
6
|
+
Do not type the version number by hand, even if you know it. Ask the package.
|
|
7
|
+
|
|
8
|
+
## Hint 2
|
|
9
|
+
|
|
10
|
+
Almost every Python package stores its own version in an attribute named with two
|
|
11
|
+
leading and two trailing underscores. You have seen the pattern before in
|
|
12
|
+
`__init__` and `__main__`.
|
|
13
|
+
|
|
14
|
+
Try this in a terminal to see it:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv run python -c "import qiskit; print(dir(qiskit))"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Look through the names that start with `__`.
|
|
21
|
+
|
|
22
|
+
## Hint 3
|
|
23
|
+
|
|
24
|
+
The attribute is `__version__`:
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import qiskit
|
|
28
|
+
|
|
29
|
+
qiskit_version = qiskit.__version__
|
|
30
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Exercise 01 - your environment works.
|
|
2
|
+
|
|
3
|
+
Fill in the TODOs, then run: qx run 1
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# TODO: import the qiskit package.
|
|
7
|
+
|
|
8
|
+
# TODO: replace None with the version string that qiskit reports about itself.
|
|
9
|
+
# A package normally exposes this as a module-level attribute whose name
|
|
10
|
+
# starts and ends with two underscores.
|
|
11
|
+
qiskit_version = None
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# 02 - Counts is just a dictionary
|
|
2
|
+
|
|
3
|
+
Every measurement result you will see in this course arrives in the same shape:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
{"00": 508, "11": 516}
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Keys are the outcomes, as strings of bits. Values are how many of your shots
|
|
10
|
+
produced that outcome. That is the whole data structure. No quantum magic.
|
|
11
|
+
|
|
12
|
+
This exercise exists because the single most common stumbling block in the first
|
|
13
|
+
week is not superposition, it is reaching for `counts[0]` and getting a
|
|
14
|
+
`KeyError`. `"00"` is a string key, not a position. A dict is not a list.
|
|
15
|
+
|
|
16
|
+
## Your task
|
|
17
|
+
|
|
18
|
+
Implement three functions in `exercise.py`. You will use all three repeatedly for
|
|
19
|
+
the rest of the course.
|
|
20
|
+
|
|
21
|
+
| Function | Returns |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `total_shots(counts)` | how many shots produced this result |
|
|
24
|
+
| `most_common(counts)` | the outcome that occurred most often |
|
|
25
|
+
| `outcome_probabilities(counts)` | `{outcome: estimated probability}` |
|
|
26
|
+
|
|
27
|
+
The runner calls your functions with several different dictionaries, so hardcoding
|
|
28
|
+
one answer will not pass.
|
|
29
|
+
|
|
30
|
+
## Run it
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
qx run 2
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What you should take away
|
|
37
|
+
|
|
38
|
+
`outcome_probabilities` is doing real physics, even though it is three lines of
|
|
39
|
+
Python. Dividing a count by the number of shots is how you estimate a probability
|
|
40
|
+
from a finite sample, and it is exactly why your numbers never come out at a clean
|
|
41
|
+
0.5. You are sampling, not reading a value off the state.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Verification for exercise 02."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from quantum_exercises.checks import CheckFailed, require, text_artifact
|
|
6
|
+
|
|
7
|
+
# Several inputs, so a hardcoded answer to one of them fails on the others.
|
|
8
|
+
# No ties in any case, so most_common has a single correct answer.
|
|
9
|
+
CASES = [
|
|
10
|
+
{"00": 500, "11": 524},
|
|
11
|
+
{"0": 750, "1": 250},
|
|
12
|
+
{"000": 10, "001": 30, "111": 60},
|
|
13
|
+
{"1010": 4096},
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def check(mod):
|
|
18
|
+
total_shots = _callable(mod, "total_shots")
|
|
19
|
+
most_common = _callable(mod, "most_common")
|
|
20
|
+
outcome_probabilities = _callable(mod, "outcome_probabilities")
|
|
21
|
+
|
|
22
|
+
if total_shots({}) != 0:
|
|
23
|
+
raise CheckFailed(
|
|
24
|
+
"total_shots({}) should be 0.",
|
|
25
|
+
detail="Summing an empty collection gives 0. Python's sum() already does this.",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
for counts in CASES:
|
|
29
|
+
expected_total = sum(counts.values())
|
|
30
|
+
actual_total = total_shots(counts)
|
|
31
|
+
if actual_total != expected_total:
|
|
32
|
+
raise CheckFailed(
|
|
33
|
+
f"total_shots({counts}) returned {actual_total!r}, expected {expected_total}.",
|
|
34
|
+
detail="Add up the values, not the keys. counts.values() gives you the numbers.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
expected_common = max(counts, key=lambda outcome: counts[outcome])
|
|
38
|
+
actual_common = most_common(counts)
|
|
39
|
+
if actual_common != expected_common:
|
|
40
|
+
raise CheckFailed(
|
|
41
|
+
f"most_common({counts}) returned {actual_common!r}, expected {expected_common!r}.",
|
|
42
|
+
detail=(
|
|
43
|
+
"Return the outcome itself, the key, not how many times it happened. "
|
|
44
|
+
f"Here {expected_common!r} occurred {counts[expected_common]} times."
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
expected_probs = {k: v / expected_total for k, v in counts.items()}
|
|
49
|
+
actual_probs = outcome_probabilities(counts)
|
|
50
|
+
_compare_probs(counts, actual_probs, expected_probs)
|
|
51
|
+
|
|
52
|
+
lines = []
|
|
53
|
+
for counts in CASES[:2]:
|
|
54
|
+
probs = outcome_probabilities(counts)
|
|
55
|
+
rendered = ", ".join(f"{k}: {v:.3f}" for k, v in sorted(probs.items()))
|
|
56
|
+
lines.append(
|
|
57
|
+
f"{counts}\n -> {total_shots(counts)} shots, most common {most_common(counts)!r}"
|
|
58
|
+
)
|
|
59
|
+
lines.append(f" -> probabilities {{{rendered}}}")
|
|
60
|
+
|
|
61
|
+
return text_artifact("\n".join(lines), caption="Your functions on real data")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _callable(mod, name):
|
|
65
|
+
value = require(mod, name)
|
|
66
|
+
if not callable(value):
|
|
67
|
+
raise CheckFailed(f"`{name}` should be a function, but it is a {type(value).__name__}.")
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _compare_probs(counts, actual, expected):
|
|
72
|
+
if not isinstance(actual, dict):
|
|
73
|
+
raise CheckFailed(
|
|
74
|
+
f"outcome_probabilities({counts}) returned a {type(actual).__name__}, expected a dict.",
|
|
75
|
+
)
|
|
76
|
+
if set(actual) != set(expected):
|
|
77
|
+
raise CheckFailed(
|
|
78
|
+
f"outcome_probabilities({counts}) returned keys {sorted(actual)}, "
|
|
79
|
+
f"expected {sorted(expected)}.",
|
|
80
|
+
detail="Keep the same outcomes as the input; only the values change.",
|
|
81
|
+
)
|
|
82
|
+
for outcome, want in expected.items():
|
|
83
|
+
got = actual[outcome]
|
|
84
|
+
if not isinstance(got, (int, float)) or not math.isclose(float(got), want, abs_tol=1e-9):
|
|
85
|
+
raise CheckFailed(
|
|
86
|
+
f"outcome_probabilities({counts})[{outcome!r}] is {got!r}, expected {want}.",
|
|
87
|
+
detail=(
|
|
88
|
+
"Divide each count by the total number of shots. If you got a whole number, "
|
|
89
|
+
"check that you are dividing with / and not //."
|
|
90
|
+
),
|
|
91
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Exercise 02 - counts is just a dictionary.
|
|
2
|
+
|
|
3
|
+
Fill in the TODOs, then run: qx run 2
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def total_shots(counts: dict[str, int]) -> int:
|
|
8
|
+
"""Return how many shots produced this result.
|
|
9
|
+
|
|
10
|
+
That is the sum of all the values. An empty dict must return 0.
|
|
11
|
+
|
|
12
|
+
>>> total_shots({"00": 500, "11": 524})
|
|
13
|
+
1024
|
|
14
|
+
"""
|
|
15
|
+
# TODO: replace this with the sum of the dictionary's values.
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def most_common(counts: dict[str, int]) -> str:
|
|
20
|
+
"""Return the outcome that occurred most often.
|
|
21
|
+
|
|
22
|
+
`counts` always has at least one entry, and there is never a tie.
|
|
23
|
+
|
|
24
|
+
>>> most_common({"00": 500, "11": 524})
|
|
25
|
+
'11'
|
|
26
|
+
"""
|
|
27
|
+
# TODO: return the key whose value is the largest.
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def outcome_probabilities(counts: dict[str, int]) -> dict[str, float]:
|
|
32
|
+
"""Return each outcome's share of the shots.
|
|
33
|
+
|
|
34
|
+
`counts` always has at least one entry and at least one shot.
|
|
35
|
+
|
|
36
|
+
>>> outcome_probabilities({"0": 750, "1": 250})
|
|
37
|
+
{'0': 0.75, '1': 0.25}
|
|
38
|
+
"""
|
|
39
|
+
# TODO: build a new dict mapping each outcome to count / total.
|
|
40
|
+
return {}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
## Hint 1
|
|
2
|
+
|
|
3
|
+
Three built-ins do almost all the work here: `sum`, `max`, and a dict
|
|
4
|
+
comprehension.
|
|
5
|
+
|
|
6
|
+
For `total_shots`, remember that a dict has three views: `.keys()`, `.values()`
|
|
7
|
+
and `.items()`. You want the one holding the numbers.
|
|
8
|
+
|
|
9
|
+
## Hint 2
|
|
10
|
+
|
|
11
|
+
`max(counts)` alone gives the largest **key** in alphabetical order, which is not
|
|
12
|
+
what you want. `max` takes a `key=` argument telling it what to rank by:
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
max(counts, key=lambda outcome: counts[outcome])
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Read that as: go through the outcomes, and rank each one by the number it maps to.
|
|
19
|
+
|
|
20
|
+
For the probabilities, a dict comprehension has the shape
|
|
21
|
+
`{k: f(v) for k, v in counts.items()}`.
|
|
22
|
+
|
|
23
|
+
## Hint 3
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
def total_shots(counts):
|
|
27
|
+
return sum(counts.values())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def most_common(counts):
|
|
31
|
+
return max(counts, key=lambda outcome: counts[outcome])
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def outcome_probabilities(counts):
|
|
35
|
+
shots = total_shots(counts)
|
|
36
|
+
return {outcome: count / shots for outcome, count in counts.items()}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Note `/` and not `//`. Integer division floors, so every probability short of a
|
|
40
|
+
whole 1 comes back as 0.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Exercise 02 - reference solution."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def total_shots(counts: dict[str, int]) -> int:
|
|
5
|
+
return sum(counts.values())
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def most_common(counts: dict[str, int]) -> str:
|
|
9
|
+
# max() over the keys, ranked by the value each key maps to.
|
|
10
|
+
return max(counts, key=lambda outcome: counts[outcome])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def outcome_probabilities(counts: dict[str, int]) -> dict[str, float]:
|
|
14
|
+
shots = total_shots(counts)
|
|
15
|
+
return {outcome: count / shots for outcome, count in counts.items()}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Exercise 02 - counts is just a dictionary.
|
|
2
|
+
|
|
3
|
+
Fill in the TODOs, then run: qx run 2
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def total_shots(counts: dict[str, int]) -> int:
|
|
8
|
+
"""Return how many shots produced this result.
|
|
9
|
+
|
|
10
|
+
That is the sum of all the values. An empty dict must return 0.
|
|
11
|
+
|
|
12
|
+
>>> total_shots({"00": 500, "11": 524})
|
|
13
|
+
1024
|
|
14
|
+
"""
|
|
15
|
+
# TODO: replace this with the sum of the dictionary's values.
|
|
16
|
+
return 0
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def most_common(counts: dict[str, int]) -> str:
|
|
20
|
+
"""Return the outcome that occurred most often.
|
|
21
|
+
|
|
22
|
+
`counts` always has at least one entry, and there is never a tie.
|
|
23
|
+
|
|
24
|
+
>>> most_common({"00": 500, "11": 524})
|
|
25
|
+
'11'
|
|
26
|
+
"""
|
|
27
|
+
# TODO: return the key whose value is the largest.
|
|
28
|
+
return ""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def outcome_probabilities(counts: dict[str, int]) -> dict[str, float]:
|
|
32
|
+
"""Return each outcome's share of the shots.
|
|
33
|
+
|
|
34
|
+
`counts` always has at least one entry and at least one shot.
|
|
35
|
+
|
|
36
|
+
>>> outcome_probabilities({"0": 750, "1": 250})
|
|
37
|
+
{'0': 0.75, '1': 0.25}
|
|
38
|
+
"""
|
|
39
|
+
# TODO: build a new dict mapping each outcome to count / total.
|
|
40
|
+
return {}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 03 - Your first circuit
|
|
2
|
+
|
|
3
|
+
A quantum circuit in Qiskit is an object you build up line by line. You create it
|
|
4
|
+
with a number of wires, then you call methods on it to add gates.
|
|
5
|
+
|
|
6
|
+
```python
|
|
7
|
+
qc = QuantumCircuit(2) # two qubits, no classical bits
|
|
8
|
+
qc.h(0) # Hadamard on qubit 0
|
|
9
|
+
qc.cx(0, 1) # controlled-NOT, control 0, target 1
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The Hadamard gate is where every course starts, because it is the cheapest way to
|
|
13
|
+
create superposition. Applied to a qubit sitting in state 0, it produces a state
|
|
14
|
+
that is equally likely to be measured as 0 or as 1.
|
|
15
|
+
|
|
16
|
+
## Your task
|
|
17
|
+
|
|
18
|
+
In `exercise.py`, build a circuit with exactly **one qubit and no classical bits**,
|
|
19
|
+
and apply a Hadamard gate to qubit 0. Leave the result in `qc`.
|
|
20
|
+
|
|
21
|
+
Do not add a measurement. This exercise looks at the circuit as a matrix, and a
|
|
22
|
+
measurement is not a matrix. Exercise 04 adds the measurement.
|
|
23
|
+
|
|
24
|
+
## Run it
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
qx run 3
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## What you should take away
|
|
31
|
+
|
|
32
|
+
The runner does not compare your code to the solution as text. It builds the
|
|
33
|
+
matrix your circuit represents and compares it to the matrix a Hadamard gate
|
|
34
|
+
represents, using `Operator.equiv`.
|
|
35
|
+
|
|
36
|
+
`equiv` deliberately ignores global phase. If your circuit produces a matrix that
|
|
37
|
+
differs from Hadamard's by an overall factor like -1 or `i`, it still passes,
|
|
38
|
+
because no measurement can ever distinguish those two circuits. That is physics,
|
|
39
|
+
not leniency. You will meet this again in exercise 07.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Verification for exercise 03."""
|
|
2
|
+
|
|
3
|
+
from qiskit.circuit.library import HGate
|
|
4
|
+
from qiskit.quantum_info import Operator
|
|
5
|
+
|
|
6
|
+
from quantum_exercises.checks import (
|
|
7
|
+
CheckFailed,
|
|
8
|
+
assert_operator_equiv,
|
|
9
|
+
matrix_artifact,
|
|
10
|
+
require_circuit,
|
|
11
|
+
text_artifact,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def check(mod):
|
|
16
|
+
qc = require_circuit(mod, "qc")
|
|
17
|
+
|
|
18
|
+
if qc.num_qubits != 1:
|
|
19
|
+
raise CheckFailed(
|
|
20
|
+
f"Your circuit has {qc.num_qubits} qubits, but this exercise asks for exactly 1.",
|
|
21
|
+
detail="QuantumCircuit(1) creates a single-qubit circuit.",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if qc.num_clbits != 0:
|
|
25
|
+
raise CheckFailed(
|
|
26
|
+
f"Your circuit has {qc.num_clbits} classical bits, but this exercise asks for none.",
|
|
27
|
+
detail=(
|
|
28
|
+
"A classical register is only needed to store measurement results, and this "
|
|
29
|
+
"exercise has no measurement. QuantumCircuit(1) with a single argument gives "
|
|
30
|
+
"you zero classical bits."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if len(qc.data) == 0:
|
|
35
|
+
raise CheckFailed(
|
|
36
|
+
"Your circuit is empty: no gate was added.",
|
|
37
|
+
detail="Creating the circuit is only half the task. Now apply the Hadamard gate.",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
operator = assert_operator_equiv(
|
|
41
|
+
qc,
|
|
42
|
+
Operator(HGate()),
|
|
43
|
+
message="Your circuit does not implement a Hadamard gate.",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return [
|
|
47
|
+
text_artifact(str(qc.draw(output="text")), caption="Your circuit"),
|
|
48
|
+
matrix_artifact(operator, caption="The matrix it represents"),
|
|
49
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Exercise 03 - your first circuit.
|
|
2
|
+
|
|
3
|
+
Fill in the TODOs, then run: qx run 3
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from qiskit import QuantumCircuit
|
|
7
|
+
|
|
8
|
+
# TODO: create a circuit with exactly one qubit and no classical bits.
|
|
9
|
+
# QuantumCircuit takes the number of qubits as its first argument.
|
|
10
|
+
qc = None
|
|
11
|
+
|
|
12
|
+
# TODO: apply a Hadamard gate to qubit 0.
|
|
13
|
+
# Gate methods are named after the gate and take the qubit index.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
## Hint 1
|
|
2
|
+
|
|
3
|
+
Two lines, replacing the two TODOs.
|
|
4
|
+
|
|
5
|
+
`QuantumCircuit(1)` and `QuantumCircuit(1, 1)` are both valid Python, but they
|
|
6
|
+
build different things. The second argument is the number of classical bits, and
|
|
7
|
+
this exercise wants none of those.
|
|
8
|
+
|
|
9
|
+
## Hint 2
|
|
10
|
+
|
|
11
|
+
Gate methods on a circuit are short and lowercase, named after the gate itself:
|
|
12
|
+
|
|
13
|
+
- `qc.h(0)` - Hadamard
|
|
14
|
+
- `qc.x(0)` - NOT
|
|
15
|
+
- `qc.z(0)` - phase flip
|
|
16
|
+
- `qc.cx(0, 1)` - controlled-NOT
|
|
17
|
+
|
|
18
|
+
They mutate the circuit in place, so you call `qc.h(0)` on its own line. Writing
|
|
19
|
+
`qc = qc.h(0)` would throw away your circuit.
|
|
20
|
+
|
|
21
|
+
## Hint 3
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from qiskit import QuantumCircuit
|
|
25
|
+
|
|
26
|
+
qc = QuantumCircuit(1)
|
|
27
|
+
qc.h(0)
|
|
28
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Exercise 03 - your first circuit.
|
|
2
|
+
|
|
3
|
+
Fill in the TODOs, then run: qx run 3
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from qiskit import QuantumCircuit
|
|
7
|
+
|
|
8
|
+
# TODO: create a circuit with exactly one qubit and no classical bits.
|
|
9
|
+
# QuantumCircuit takes the number of qubits as its first argument.
|
|
10
|
+
qc = None
|
|
11
|
+
|
|
12
|
+
# TODO: apply a Hadamard gate to qubit 0.
|
|
13
|
+
# Gate methods are named after the gate and take the qubit index.
|