shotgate 0.2.2__tar.gz → 0.3.0__tar.gz

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.
Files changed (115) hide show
  1. {shotgate-0.2.2 → shotgate-0.3.0}/.github/workflows/ci.yml +73 -10
  2. {shotgate-0.2.2 → shotgate-0.3.0}/CHANGELOG.md +59 -1
  3. {shotgate-0.2.2 → shotgate-0.3.0}/PKG-INFO +39 -19
  4. {shotgate-0.2.2 → shotgate-0.3.0}/README.md +27 -18
  5. {shotgate-0.2.2 → shotgate-0.3.0}/docs/README.md +9 -0
  6. {shotgate-0.2.2 → shotgate-0.3.0}/docs/adr/0006-hardware-oracle-policy.md +5 -3
  7. shotgate-0.3.0/docs/adr/0007-fail-closed-workflow-validation.md +49 -0
  8. shotgate-0.3.0/docs/adr/0008-counts-based-oracle-expansion.md +52 -0
  9. shotgate-0.3.0/docs/adr/0009-noise-model-simulation.md +55 -0
  10. shotgate-0.3.0/docs/adr/0010-noise-aware-expected-distribution.md +56 -0
  11. shotgate-0.3.0/docs/adr/0011-aws-braket-backend.md +48 -0
  12. shotgate-0.3.0/docs/assertions.md +253 -0
  13. {shotgate-0.2.2 → shotgate-0.3.0}/docs/getting-started.md +17 -5
  14. {shotgate-0.2.2 → shotgate-0.3.0}/docs/hardware-validation.md +18 -12
  15. {shotgate-0.2.2 → shotgate-0.3.0}/docs/workflow-spec.md +2 -1
  16. shotgate-0.3.0/examples/bell-state-braket/workflow.yaml +40 -0
  17. shotgate-0.3.0/examples/bell-state-noisy-sim/workflow.yaml +50 -0
  18. shotgate-0.3.0/examples/bell-state-observables/workflow.yaml +43 -0
  19. shotgate-0.3.0/examples/bell-state-qasm3/workflow.yaml +43 -0
  20. {shotgate-0.2.2 → shotgate-0.3.0}/pyproject.toml +13 -10
  21. shotgate-0.3.0/src/shotgate/backends/braket.py +73 -0
  22. shotgate-0.3.0/src/shotgate/backends/local_aer.py +91 -0
  23. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/backends/registry.py +8 -1
  24. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/circuits/loader.py +11 -2
  25. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/cli.py +12 -1
  26. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/config.py +20 -4
  27. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/report.py +25 -2
  28. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/runner.py +13 -1
  29. shotgate-0.3.0/src/shotgate/validation/assertions.py +536 -0
  30. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/validation/metrics.py +102 -0
  31. shotgate-0.3.0/tests/test_assertions.py +231 -0
  32. shotgate-0.3.0/tests/test_braket_backend.py +59 -0
  33. {shotgate-0.2.2 → shotgate-0.3.0}/tests/test_config.py +47 -0
  34. shotgate-0.3.0/tests/test_loader.py +70 -0
  35. {shotgate-0.2.2 → shotgate-0.3.0}/tests/test_metrics.py +50 -0
  36. {shotgate-0.2.2 → shotgate-0.3.0}/tests/test_report.py +4 -0
  37. shotgate-0.3.0/tests/test_runner_integration.py +196 -0
  38. shotgate-0.2.2/docs/assertions.md +0 -140
  39. shotgate-0.2.2/src/shotgate/backends/local_aer.py +0 -46
  40. shotgate-0.2.2/src/shotgate/validation/assertions.py +0 -240
  41. shotgate-0.2.2/tests/test_assertions.py +0 -108
  42. shotgate-0.2.2/tests/test_runner_integration.py +0 -72
  43. {shotgate-0.2.2 → shotgate-0.3.0}/.containerignore +0 -0
  44. {shotgate-0.2.2 → shotgate-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  45. {shotgate-0.2.2 → shotgate-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  46. {shotgate-0.2.2 → shotgate-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  47. {shotgate-0.2.2 → shotgate-0.3.0}/.github/dependabot.yml +0 -0
  48. {shotgate-0.2.2 → shotgate-0.3.0}/.github/scripts/pin-device.py +0 -0
  49. {shotgate-0.2.2 → shotgate-0.3.0}/.github/scripts/pull-base-image.sh +0 -0
  50. {shotgate-0.2.2 → shotgate-0.3.0}/.github/workflows/dockerhub-mirror.yml +0 -0
  51. {shotgate-0.2.2 → shotgate-0.3.0}/.github/workflows/hardware-validation.yml +0 -0
  52. {shotgate-0.2.2 → shotgate-0.3.0}/.github/workflows/pypi-smoke.yml +0 -0
  53. {shotgate-0.2.2 → shotgate-0.3.0}/.github/workflows/release.yml +0 -0
  54. {shotgate-0.2.2 → shotgate-0.3.0}/.gitignore +0 -0
  55. {shotgate-0.2.2 → shotgate-0.3.0}/.gitlab-ci.yml +0 -0
  56. {shotgate-0.2.2 → shotgate-0.3.0}/CODEOWNERS +0 -0
  57. {shotgate-0.2.2 → shotgate-0.3.0}/CODE_OF_CONDUCT.md +0 -0
  58. {shotgate-0.2.2 → shotgate-0.3.0}/CONTRIBUTING.md +0 -0
  59. {shotgate-0.2.2 → shotgate-0.3.0}/Containerfile +0 -0
  60. {shotgate-0.2.2 → shotgate-0.3.0}/GOVERNANCE.md +0 -0
  61. {shotgate-0.2.2 → shotgate-0.3.0}/Jenkinsfile +0 -0
  62. {shotgate-0.2.2 → shotgate-0.3.0}/LICENSE +0 -0
  63. {shotgate-0.2.2 → shotgate-0.3.0}/MAINTAINERS.md +0 -0
  64. {shotgate-0.2.2 → shotgate-0.3.0}/Makefile +0 -0
  65. {shotgate-0.2.2 → shotgate-0.3.0}/NOTICE +0 -0
  66. {shotgate-0.2.2 → shotgate-0.3.0}/SECURITY.md +0 -0
  67. {shotgate-0.2.2 → shotgate-0.3.0}/docs/adr/0001-record-architecture-decisions.md +0 -0
  68. {shotgate-0.2.2 → shotgate-0.3.0}/docs/adr/0002-statistical-validation-core.md +0 -0
  69. {shotgate-0.2.2 → shotgate-0.3.0}/docs/adr/0003-container-and-vm-isolation.md +0 -0
  70. {shotgate-0.2.2 → shotgate-0.3.0}/docs/adr/0004-rename-qforge-to-shotgate.md +0 -0
  71. {shotgate-0.2.2 → shotgate-0.3.0}/docs/adr/0005-license-apache-2-0.md +0 -0
  72. {shotgate-0.2.2 → shotgate-0.3.0}/docs/architecture.md +0 -0
  73. {shotgate-0.2.2 → shotgate-0.3.0}/docs/assets/bell-state-demo.svg +0 -0
  74. {shotgate-0.2.2 → shotgate-0.3.0}/docs/assets/shotgate-logomark.svg +0 -0
  75. {shotgate-0.2.2 → shotgate-0.3.0}/docs/diagrams/architecture.mmd +0 -0
  76. {shotgate-0.2.2 → shotgate-0.3.0}/docs/diagrams/pipeline.mmd +0 -0
  77. {shotgate-0.2.2 → shotgate-0.3.0}/docs/motivation.md +0 -0
  78. {shotgate-0.2.2 → shotgate-0.3.0}/docs/pipeline.md +0 -0
  79. {shotgate-0.2.2 → shotgate-0.3.0}/examples/bell-state/bell.qasm +0 -0
  80. {shotgate-0.2.2 → shotgate-0.3.0}/examples/bell-state/workflow.yaml +0 -0
  81. {shotgate-0.2.2 → shotgate-0.3.0}/examples/bell-state-hardware/bell.qasm +0 -0
  82. {shotgate-0.2.2 → shotgate-0.3.0}/examples/bell-state-hardware/workflow.yaml +0 -0
  83. {shotgate-0.2.2 → shotgate-0.3.0}/examples/bell-state-hardware-oracle-coverage/bell.qasm +0 -0
  84. {shotgate-0.2.2 → shotgate-0.3.0}/examples/bell-state-hardware-oracle-coverage/workflow.yaml +0 -0
  85. {shotgate-0.2.2 → shotgate-0.3.0}/examples/ghz-state/ghz.qasm +0 -0
  86. {shotgate-0.2.2 → shotgate-0.3.0}/examples/ghz-state/workflow.yaml +0 -0
  87. {shotgate-0.2.2 → shotgate-0.3.0}/examples/ghz-state-hardware/ghz.qasm +0 -0
  88. {shotgate-0.2.2 → shotgate-0.3.0}/examples/ghz-state-hardware/workflow.yaml +0 -0
  89. {shotgate-0.2.2 → shotgate-0.3.0}/examples/grover-2q/grover.qasm +0 -0
  90. {shotgate-0.2.2 → shotgate-0.3.0}/examples/grover-2q/workflow.yaml +0 -0
  91. {shotgate-0.2.2 → shotgate-0.3.0}/examples/grover-2q-hardware/grover.qasm +0 -0
  92. {shotgate-0.2.2 → shotgate-0.3.0}/examples/grover-2q-hardware/workflow.yaml +0 -0
  93. {shotgate-0.2.2 → shotgate-0.3.0}/infra/qemu/README.md +0 -0
  94. {shotgate-0.2.2 → shotgate-0.3.0}/infra/qemu/cloud-init/meta-data +0 -0
  95. {shotgate-0.2.2 → shotgate-0.3.0}/infra/qemu/cloud-init/user-data +0 -0
  96. {shotgate-0.2.2 → shotgate-0.3.0}/infra/qemu/create-runner-vm.sh +0 -0
  97. {shotgate-0.2.2 → shotgate-0.3.0}/infra/terraform/README.md +0 -0
  98. {shotgate-0.2.2 → shotgate-0.3.0}/infra/terraform/examples/basic/main.tf +0 -0
  99. {shotgate-0.2.2 → shotgate-0.3.0}/infra/terraform/main.tf +0 -0
  100. {shotgate-0.2.2 → shotgate-0.3.0}/infra/terraform/outputs.tf +0 -0
  101. {shotgate-0.2.2 → shotgate-0.3.0}/infra/terraform/variables.tf +0 -0
  102. {shotgate-0.2.2 → shotgate-0.3.0}/infra/terraform/versions.tf +0 -0
  103. {shotgate-0.2.2 → shotgate-0.3.0}/scripts/demo.sh +0 -0
  104. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/__init__.py +0 -0
  105. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/__main__.py +0 -0
  106. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/backends/__init__.py +0 -0
  107. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/backends/base.py +0 -0
  108. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/backends/ibm_runtime.py +0 -0
  109. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/circuits/__init__.py +0 -0
  110. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/pytest_plugin.py +0 -0
  111. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/telemetry.py +0 -0
  112. {shotgate-0.2.2 → shotgate-0.3.0}/src/shotgate/validation/__init__.py +0 -0
  113. {shotgate-0.2.2 → shotgate-0.3.0}/tests/conftest.py +0 -0
  114. {shotgate-0.2.2 → shotgate-0.3.0}/tests/test_ibm_backend.py +0 -0
  115. {shotgate-0.2.2 → shotgate-0.3.0}/tests/test_pytest_plugin.py +0 -0
@@ -22,8 +22,8 @@ concurrency:
22
22
  cancel-in-progress: true
23
23
 
24
24
  jobs:
25
- lint-and-core-tests:
26
- name: lint + core tests
25
+ lint:
26
+ name: lint + types
27
27
  runs-on: ubuntu-latest
28
28
  steps:
29
29
  - uses: actions/checkout@v6
@@ -34,22 +34,47 @@ jobs:
34
34
  docker.io/library/python:3.12-slim \
35
35
  public.ecr.aws/docker/library/python:3.12-slim
36
36
 
37
- - name: ruff + mypy + core unit tests (containerized)
37
+ - name: ruff + mypy (containerized)
38
38
  run: |
39
39
  podman run --rm -v "$PWD:/work:Z" -w /work \
40
40
  docker.io/library/python:3.12-slim bash -euc "
41
41
  pip install -q -e '.[dev]'
42
42
  ruff check src tests
43
43
  mypy
44
- pytest -q -m 'not integration' --junitxml=core-junit.xml
44
+ "
45
+
46
+ core-tests:
47
+ name: core unit tests (py ${{ matrix.python }})
48
+ runs-on: ubuntu-latest
49
+ # The validation core is SDK-free, so the core suite runs across the declared
50
+ # Python support grid (requires-python >= 3.10) without a quantum SDK.
51
+ strategy:
52
+ fail-fast: false
53
+ matrix:
54
+ python: ["3.10", "3.11", "3.12", "3.13"]
55
+ steps:
56
+ - uses: actions/checkout@v6
57
+
58
+ - name: Pull base image (retry + mirror fallback)
59
+ run: |
60
+ .github/scripts/pull-base-image.sh \
61
+ "docker.io/library/python:${{ matrix.python }}-slim" \
62
+ "public.ecr.aws/docker/library/python:${{ matrix.python }}-slim"
63
+
64
+ - name: core unit tests, SDK-free (containerized)
65
+ run: |
66
+ podman run --rm -v "$PWD:/work:Z" -w /work \
67
+ "docker.io/library/python:${{ matrix.python }}-slim" bash -euc "
68
+ pip install -q -e '.[dev]'
69
+ pytest -q -m 'not integration' --junitxml=core-junit-${{ matrix.python }}.xml
45
70
  "
46
71
 
47
72
  - name: Upload core test report
48
73
  if: always()
49
74
  uses: actions/upload-artifact@v7
50
75
  with:
51
- name: core-junit
52
- path: core-junit.xml
76
+ name: core-junit-${{ matrix.python }}
77
+ path: core-junit-${{ matrix.python }}.xml
53
78
 
54
79
  image-and-integration:
55
80
  name: build image + integration + example gates
@@ -96,8 +121,16 @@ jobs:
96
121
  integration-junit.xml
97
122
 
98
123
  pip-consumer-path:
99
- name: pip path (CLI + pytest plugin)
124
+ name: pip path (qiskit ${{ matrix.label }})
100
125
  runs-on: ubuntu-latest
126
+ # Exercise the consumer path against both ends of the declared qiskit range
127
+ # (qiskit>=1.2,<3): the supported floor and the latest 2.x.
128
+ strategy:
129
+ fail-fast: false
130
+ matrix:
131
+ include:
132
+ - { qiskit: "qiskit==1.2.*", label: "floor-1.2" }
133
+ - { qiskit: "qiskit<3", label: "latest-2.x" }
101
134
  steps:
102
135
  - uses: actions/checkout@v6
103
136
 
@@ -107,11 +140,12 @@ jobs:
107
140
  docker.io/library/python:3.12-slim \
108
141
  public.ecr.aws/docker/library/python:3.12-slim
109
142
 
110
- - name: pip install and exercise the CLI and pytest plugin (containerized)
143
+ - name: pip install (pinned qiskit) and exercise the CLI and pytest plugin
111
144
  run: |
112
145
  podman run --rm -v "$PWD:/work:Z" -w /work \
113
146
  docker.io/library/python:3.12-slim bash -euc "
114
- pip install -q '.[aer,dev]'
147
+ pip install -q '.[aer,dev]' '${{ matrix.qiskit }}'
148
+ python -c 'import qiskit; print(\"using qiskit\", qiskit.__version__)'
115
149
  # The pytest11 entry point must expose --shotgate after a pip install.
116
150
  pytest --help 2>/dev/null | grep -q -- '--shotgate'
117
151
  # CLI consumer path: gate a Bell workflow and emit a JUnit report.
@@ -128,11 +162,40 @@ jobs:
128
162
  if: always()
129
163
  uses: actions/upload-artifact@v7
130
164
  with:
131
- name: pip-path-junit
165
+ name: pip-path-junit-${{ matrix.label }}
132
166
  path: |
133
167
  cli-junit.xml
134
168
  plugin-junit.xml
135
169
 
170
+ braket-local:
171
+ name: braket local simulation
172
+ runs-on: ubuntu-latest
173
+ # The Braket backend's local-simulation path runs with no AWS account; the cloud
174
+ # path (named devices) needs AWS credentials and is not exercised in CI.
175
+ steps:
176
+ - uses: actions/checkout@v6
177
+
178
+ - name: Pull base image (retry + mirror fallback)
179
+ run: |
180
+ .github/scripts/pull-base-image.sh \
181
+ docker.io/library/python:3.12-slim \
182
+ public.ecr.aws/docker/library/python:3.12-slim
183
+
184
+ - name: braket local-simulation backend test (containerized)
185
+ run: |
186
+ podman run --rm -v "$PWD:/work:Z" -w /work \
187
+ docker.io/library/python:3.12-slim bash -euc "
188
+ pip install -q '.[braket,dev]'
189
+ pytest -q tests/test_braket_backend.py --junitxml=/work/braket-junit.xml
190
+ "
191
+
192
+ - name: Upload braket report
193
+ if: always()
194
+ uses: actions/upload-artifact@v7
195
+ with:
196
+ name: braket-junit
197
+ path: braket-junit.xml
198
+
136
199
  terraform:
137
200
  name: terraform fmt + validate
138
201
  runs-on: ubuntu-latest
@@ -6,6 +6,63 @@ All notable changes to this project are documented here. The format is based on
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.3.0] - 2026-06-26
10
+
11
+ > Bundles the work the roadmap sketched across several minor milestones (oracle breadth,
12
+ > noise-aware validation, the AWS Braket backend) into one release after `0.2.2`.
13
+
14
+ ### Added
15
+
16
+ - **AWS Braket backend (ADR-0011).** `provider: braket` runs the same workflows on Braket.
17
+ Local simulation needs **no AWS account** (omit `backend.name`); cloud devices are reached
18
+ by setting `backend.name` (e.g. `SV1`) and need configured AWS credentials. Dependencies
19
+ live in the `braket` extra (`pip install shotgate[braket]`); a dedicated CI job validates
20
+ the local path. Promotes `braket` from a planned to a working backend. The cloud path is
21
+ implemented but not yet validated on real Braket hardware.
22
+ - **Noise-aware expected distributions: `chi_square` and `kl_divergence` can gate on
23
+ hardware (ADR-0010).** An optional `readout_error: { p0, p1 }` block transforms the ideal
24
+ `expected` through a calibration-derived per-qubit readout model, giving the test nonzero
25
+ mass on the device's error states. On the measured `ibm_fez` Bell counts this takes
26
+ `chi_square` from statistic 1.5e17 / p-value 0 to statistic 5.51 / p-value 0.138 (passes
27
+ at alpha 0.01); the readout parameters are declared from calibration, not fit to the
28
+ counts, so the gate stays honest. Partially lifts the ADR-0006 simulator-only policy.
29
+ - **Declarative noise-model simulation on `local-aer` (ADR-0009).** A backend can carry a
30
+ `noise` block (`depolarizing_1q`/`depolarizing_2q`/`readout_p0`/`readout_p1`) so the
31
+ noise-aware `*-hardware` thresholds can be regression-tested in CI without a QPU. A
32
+ simulated-noisy Bell pair lands in the measured device regime (TVD ~0.12 vs the measured
33
+ `ibm_fez` 0.1284); a `bell-state-noisy-sim` example gates it against hardware-profile
34
+ bounds. Non-simulator backends ignore `noise`.
35
+ - **Four new assertion oracles (5 -> 9), see ADR-0008:** `kl_divergence` (bound the
36
+ Kullback-Leibler divergence to an expected distribution, in bits; simulator-oriented
37
+ like `chi_square`), `shannon_entropy` (bound the distribution entropy in a min/max
38
+ window), `expectation_value` (bound the Pauli-Z product `<Z..Z>` in [-1, 1] via a
39
+ window or a target), and `most_frequent_outcome` (assert the modal state, optionally
40
+ above a probability). All are counts-based and keep the validation core SDK-free. A
41
+ `bell-state-observables` example exercises the new observable oracles. The JSON reporter
42
+ now serialises a non-finite metric (a diverged KL) as `null` so output stays valid.
43
+
44
+ ### Changed
45
+
46
+ - **JUnit reports carry `timestamp` and `skipped`** attributes on the root and every
47
+ testsuite, for fuller xUnit-schema compliance in CI dashboards. Declared the supported
48
+ Python grid (3.10-3.13) as trove classifiers.
49
+ - **Fail-closed validation (see ADR-0007).** A job that runs but declares no
50
+ assertions now **fails** by default, instead of silently passing a gate that checked
51
+ nothing; opt out with `--allow-empty` (CLI) or `allow_empty=True` (`Runner`).
52
+ Basis-state keys in `expected`, `state`, and `states` are validated at schema-parse
53
+ time: non-empty, `0`/`1` only, and a single consistent width, so a malformed key
54
+ (e.g. `"0x"`, or mixed `"0"`/`"11"`) raises a clear `ValidationError` instead of being
55
+ silently mis-compared. Both are behaviour changes, intended at `0.x`.
56
+
57
+ ### Fixed
58
+
59
+ - **OpenQASM 3 circuits now load.** The `qasm3` circuit format was advertised in
60
+ the schema but failed at runtime: `qiskit.qasm3.loads` needs the
61
+ `qiskit-qasm3-import` package, which no extra declared. It is now a dependency of
62
+ the `aer` and `ibm` extras (and the published images), the loader raises an
63
+ actionable error if it is somehow absent, and a `bell-state-qasm3` example plus
64
+ loader tests cover the path.
65
+
9
66
  ## [0.2.2] - 2026-06-11
10
67
 
11
68
  ### Added
@@ -193,7 +250,8 @@ All notable changes to this project are documented here. The format is based on
193
250
  (with the math), getting-started guide, and ADRs.
194
251
  - **Examples**: Bell state, 3-qubit GHZ, and 2-qubit Grover workflows.
195
252
 
196
- [Unreleased]: https://github.com/coldqubit/shotgate/compare/v0.2.2...HEAD
253
+ [Unreleased]: https://github.com/coldqubit/shotgate/compare/v0.3.0...HEAD
254
+ [0.3.0]: https://github.com/coldqubit/shotgate/compare/v0.2.2...v0.3.0
197
255
  [0.2.2]: https://github.com/coldqubit/shotgate/compare/v0.2.1...v0.2.2
198
256
  [0.2.1]: https://github.com/coldqubit/shotgate/compare/v0.2.0...v0.2.1
199
257
  [0.2.0]: https://github.com/coldqubit/shotgate/compare/v0.1.1...v0.2.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shotgate
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Container-native CI/CD quality gates for quantum circuits: statistically validate probabilistic output across simulators and QPUs.
5
5
  Project-URL: Homepage, https://github.com/coldqubit/shotgate
6
6
  Project-URL: Documentation, https://github.com/coldqubit/shotgate/tree/main/docs
@@ -15,6 +15,10 @@ Classifier: Development Status :: 3 - Alpha
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: License :: OSI Approved :: Apache Software License
17
17
  Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
18
22
  Classifier: Topic :: Scientific/Engineering :: Physics
19
23
  Classifier: Topic :: Software Development :: Testing
20
24
  Classifier: Topic :: System :: Distributed Computing
@@ -25,13 +29,19 @@ Requires-Dist: pyyaml>=6.0
25
29
  Requires-Dist: rich>=13.0
26
30
  Provides-Extra: aer
27
31
  Requires-Dist: qiskit-aer>=0.15; extra == 'aer'
32
+ Requires-Dist: qiskit-qasm3-import>=0.5; extra == 'aer'
28
33
  Requires-Dist: qiskit<3,>=1.2; extra == 'aer'
29
34
  Provides-Extra: all
35
+ Requires-Dist: amazon-braket-sdk>=1.80; extra == 'all'
30
36
  Requires-Dist: qiskit-aer>=0.15; extra == 'all'
37
+ Requires-Dist: qiskit-braket-provider>=0.4; extra == 'all'
31
38
  Requires-Dist: qiskit-ibm-runtime>=0.30; extra == 'all'
39
+ Requires-Dist: qiskit-qasm3-import>=0.5; extra == 'all'
32
40
  Requires-Dist: qiskit<3,>=1.2; extra == 'all'
33
41
  Provides-Extra: braket
34
42
  Requires-Dist: amazon-braket-sdk>=1.80; extra == 'braket'
43
+ Requires-Dist: qiskit-braket-provider>=0.4; extra == 'braket'
44
+ Requires-Dist: qiskit<3,>=1.2; extra == 'braket'
35
45
  Provides-Extra: dev
36
46
  Requires-Dist: mypy>=1.11; extra == 'dev'
37
47
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
@@ -40,6 +50,7 @@ Requires-Dist: ruff>=0.6; extra == 'dev'
40
50
  Requires-Dist: types-pyyaml; extra == 'dev'
41
51
  Provides-Extra: ibm
42
52
  Requires-Dist: qiskit-ibm-runtime>=0.30; extra == 'ibm'
53
+ Requires-Dist: qiskit-qasm3-import>=0.5; extra == 'ibm'
43
54
  Requires-Dist: qiskit<3,>=1.2; extra == 'ibm'
44
55
  Provides-Extra: mitigation
45
56
  Requires-Dist: mitiq>=0.40; extra == 'mitigation'
@@ -123,15 +134,15 @@ jobs: [
123
134
  $ shotgate run examples/bell-state/workflow.yaml
124
135
  ─────────────────────────── shotgate :: bell-state ───────────────────────────
125
136
  job: bell-pair · aer_simulator · 8192 shots
126
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
127
- ┃ Assertion ┃ Result ┃ Detail
128
- ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
129
- │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
130
- │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
131
- │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
132
- │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
133
- │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
134
- └───────────────────────────┴────────┴───────────────────────────────────────────────┘
137
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
138
+ ┃ Assertion ┃ Result ┃ Detail
139
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
140
+ │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
141
+ │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
142
+ │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
143
+ │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
144
+ │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
145
+ └───────────────────────────┴────────┴───────────────────────────────────────────┘
135
146
  PASSED · 5/5 assertions · 0.214s
136
147
  ```
137
148
 
@@ -226,11 +237,15 @@ For hardware-isolated runs (each pipeline in a throwaway KVM micro-VM), see
226
237
 
227
238
  | Type | Oracle | Use it for |
228
239
  | --- | --- | --- |
229
- | `chi_square` | Pearson χ² GoF test (p-value vs α) | The rigorous statistical "does this match?" test |
230
- | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Robust, shot-count-agnostic distribution check |
240
+ | `chi_square` | Pearson χ² GoF test (p-value vs α) | Formal hypothesis test; **simulator-only** (see catalog) |
241
+ | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Default distribution check; interpretable, shot-count-agnostic |
231
242
  | `hellinger_fidelity` | Classical fidelity ≥ threshold | Fidelity tracking against an ideal distribution |
232
243
  | `state_probability` | Marginal P(state) in a window / ≈ target | Single-outcome amplitude checks (e.g. Grover) |
233
244
  | `allowed_states` | Probability mass outside support ≤ budget | Structural/leakage guarantees (e.g. GHZ corners) |
245
+ | `kl_divergence` | KL D(obs‖exp) ≤ bound (bits) | Information-theoretic distance; **simulator-only** (zero-support) |
246
+ | `shannon_entropy` | Entropy in a min/max window (bits) | Assert the intended randomness/concentration |
247
+ | `expectation_value` | `<Z..Z>` in [-1, 1] via window / target | Correlation/parity observable tracking |
248
+ | `most_frequent_outcome` | Modal state (optional min probability) | Single intended answer (e.g. Grover) |
234
249
 
235
250
  Full reference: [`docs/assertions.md`](docs/assertions.md). The statistical core is pure Python
236
251
  (no SciPy), including a from-scratch χ² survival function via the regularised incomplete gamma
@@ -271,7 +286,7 @@ The same artifact therefore runs in a small CI container and against a real QPU.
271
286
  ```text
272
287
  shotgate/
273
288
  ├── src/shotgate/ # the package (validation core, backends, runner, CLI, pytest plugin)
274
- ├── examples/ # runnable workflows: bell, ghz, grover
289
+ ├── examples/ # runnable workflows: bell/ghz/grover + backend & oracle variants
275
290
  ├── tests/ # unit tests (core) + integration tests (gated on aer)
276
291
  ├── infra/
277
292
  │ ├── terraform/ # IaC module: "quantum workflow as code"
@@ -288,7 +303,7 @@ shotgate/
288
303
  | --- | --- |
289
304
  | `local-aer` (Qiskit Aer simulator) | **Working**, default, baked into the image |
290
305
  | `ibm` (IBM Quantum via Qiskit Runtime) | **Validated on real hardware** (`ibm_fez`, 2026-06-11: Bell/GHZ/Grover gates passed at 4096 shots; [measured baseline](docs/hardware-validation.md)) |
291
- | `braket` (AWS Braket) | **Planned** (not selectable yet) |
306
+ | `braket` (AWS Braket via qiskit-braket-provider) | **Working: local simulation** (no AWS account). Cloud devices need AWS credentials; not yet validated on real Braket hardware. |
292
307
  | Error mitigation ([Mitiq](https://mitiq.readthedocs.io/)) | **Planned** |
293
308
 
294
309
  ## Roadmap
@@ -296,14 +311,19 @@ shotgate/
296
311
  - **v0.1:** YAML workflows, local Aer backend, χ²/TVD/fidelity/structural oracles,
297
312
  JUnit/JSON/MD reporters, Podman and KVM/QEMU isolation, Terraform module, pytest plugin,
298
313
  PyPI package, published GHCR image with an IBM-enabled variant.
299
- - **v0.2 (now):** statistical gates **validated against real quantum hardware** (`ibm_fez`,
314
+ - **v0.2:** statistical gates **validated against real quantum hardware** (`ibm_fez`,
300
315
  2026-06-11, Bell/GHZ/Grover at 4096 shots; measured baseline and runbook in
301
316
  [`docs/hardware-validation.md`](docs/hardware-validation.md)), hardware example gates for
302
317
  GHZ and Grover, and a dispatchable QPU validation workflow.
303
- - **v0.3:** noise-model simulation, a minimal AWS Braket backend, error mitigation via
304
- [Mitiq](https://mitiq.readthedocs.io/), circuit fixtures and property-based generation,
305
- multi-backend differential testing, a Helm chart, and an optional OpenTelemetry exporter
306
- for the telemetry layer (kept out of the core dependencies).
318
+ - **v0.3.0 (latest release):** the OpenQASM 3 fix and fail-closed validation, four more
319
+ oracles (`kl_divergence`, `shannon_entropy`, `expectation_value`, `most_frequent_outcome`),
320
+ declarative noise-model simulation and noise-aware expected distributions (so
321
+ `chi_square`/`kl_divergence` can gate on hardware), and the AWS Braket backend (local
322
+ simulation; the cloud path awaits validation on real hardware).
323
+ - **Planned:** error mitigation via [Mitiq](https://mitiq.readthedocs.io/), multi-backend
324
+ differential testing, circuit fixtures and property-based generation, a Helm chart, an
325
+ optional OpenTelemetry exporter for the telemetry layer (kept out of the core
326
+ dependencies), and validation of the Braket cloud path on real hardware.
307
327
 
308
328
  See [`CHANGELOG.md`](CHANGELOG.md) and the [ADRs](docs/adr/) for decisions and rationale.
309
329
 
@@ -76,15 +76,15 @@ jobs: [
76
76
  $ shotgate run examples/bell-state/workflow.yaml
77
77
  ─────────────────────────── shotgate :: bell-state ───────────────────────────
78
78
  job: bell-pair · aer_simulator · 8192 shots
79
- ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
80
- ┃ Assertion ┃ Result ┃ Detail
81
- ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
82
- │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
83
- │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
84
- │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
85
- │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
86
- │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
87
- └───────────────────────────┴────────┴───────────────────────────────────────────────┘
79
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
80
+ ┃ Assertion ┃ Result ┃ Detail
81
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
82
+ │ chi-square p >= 0.01 │ PASS │ chi-square=0.410 dof=1 p-value=0.5220
83
+ │ TVD <= 0.03 │ PASS │ total variation distance 0.0043 (<= 0.03)
84
+ │ fidelity >= 0.99 │ PASS │ Hellinger fidelity 1.0000 (>= 0.99)
85
+ │ leakage <= 0.0 │ PASS │ support leakage 0.0000 (<= 0.0)
86
+ │ P(00) >= 0.45 and <= 0.55 │ PASS │ P(00) = 0.4957
87
+ └───────────────────────────┴────────┴───────────────────────────────────────────┘
88
88
  PASSED · 5/5 assertions · 0.214s
89
89
  ```
90
90
 
@@ -179,11 +179,15 @@ For hardware-isolated runs (each pipeline in a throwaway KVM micro-VM), see
179
179
 
180
180
  | Type | Oracle | Use it for |
181
181
  | --- | --- | --- |
182
- | `chi_square` | Pearson χ² GoF test (p-value vs α) | The rigorous statistical "does this match?" test |
183
- | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Robust, shot-count-agnostic distribution check |
182
+ | `chi_square` | Pearson χ² GoF test (p-value vs α) | Formal hypothesis test; **simulator-only** (see catalog) |
183
+ | `distribution_tvd` | Total variation distance (TVD) ≤ bound | Default distribution check; interpretable, shot-count-agnostic |
184
184
  | `hellinger_fidelity` | Classical fidelity ≥ threshold | Fidelity tracking against an ideal distribution |
185
185
  | `state_probability` | Marginal P(state) in a window / ≈ target | Single-outcome amplitude checks (e.g. Grover) |
186
186
  | `allowed_states` | Probability mass outside support ≤ budget | Structural/leakage guarantees (e.g. GHZ corners) |
187
+ | `kl_divergence` | KL D(obs‖exp) ≤ bound (bits) | Information-theoretic distance; **simulator-only** (zero-support) |
188
+ | `shannon_entropy` | Entropy in a min/max window (bits) | Assert the intended randomness/concentration |
189
+ | `expectation_value` | `<Z..Z>` in [-1, 1] via window / target | Correlation/parity observable tracking |
190
+ | `most_frequent_outcome` | Modal state (optional min probability) | Single intended answer (e.g. Grover) |
187
191
 
188
192
  Full reference: [`docs/assertions.md`](docs/assertions.md). The statistical core is pure Python
189
193
  (no SciPy), including a from-scratch χ² survival function via the regularised incomplete gamma
@@ -224,7 +228,7 @@ The same artifact therefore runs in a small CI container and against a real QPU.
224
228
  ```text
225
229
  shotgate/
226
230
  ├── src/shotgate/ # the package (validation core, backends, runner, CLI, pytest plugin)
227
- ├── examples/ # runnable workflows: bell, ghz, grover
231
+ ├── examples/ # runnable workflows: bell/ghz/grover + backend & oracle variants
228
232
  ├── tests/ # unit tests (core) + integration tests (gated on aer)
229
233
  ├── infra/
230
234
  │ ├── terraform/ # IaC module: "quantum workflow as code"
@@ -241,7 +245,7 @@ shotgate/
241
245
  | --- | --- |
242
246
  | `local-aer` (Qiskit Aer simulator) | **Working**, default, baked into the image |
243
247
  | `ibm` (IBM Quantum via Qiskit Runtime) | **Validated on real hardware** (`ibm_fez`, 2026-06-11: Bell/GHZ/Grover gates passed at 4096 shots; [measured baseline](docs/hardware-validation.md)) |
244
- | `braket` (AWS Braket) | **Planned** (not selectable yet) |
248
+ | `braket` (AWS Braket via qiskit-braket-provider) | **Working: local simulation** (no AWS account). Cloud devices need AWS credentials; not yet validated on real Braket hardware. |
245
249
  | Error mitigation ([Mitiq](https://mitiq.readthedocs.io/)) | **Planned** |
246
250
 
247
251
  ## Roadmap
@@ -249,14 +253,19 @@ shotgate/
249
253
  - **v0.1:** YAML workflows, local Aer backend, χ²/TVD/fidelity/structural oracles,
250
254
  JUnit/JSON/MD reporters, Podman and KVM/QEMU isolation, Terraform module, pytest plugin,
251
255
  PyPI package, published GHCR image with an IBM-enabled variant.
252
- - **v0.2 (now):** statistical gates **validated against real quantum hardware** (`ibm_fez`,
256
+ - **v0.2:** statistical gates **validated against real quantum hardware** (`ibm_fez`,
253
257
  2026-06-11, Bell/GHZ/Grover at 4096 shots; measured baseline and runbook in
254
258
  [`docs/hardware-validation.md`](docs/hardware-validation.md)), hardware example gates for
255
259
  GHZ and Grover, and a dispatchable QPU validation workflow.
256
- - **v0.3:** noise-model simulation, a minimal AWS Braket backend, error mitigation via
257
- [Mitiq](https://mitiq.readthedocs.io/), circuit fixtures and property-based generation,
258
- multi-backend differential testing, a Helm chart, and an optional OpenTelemetry exporter
259
- for the telemetry layer (kept out of the core dependencies).
260
+ - **v0.3.0 (latest release):** the OpenQASM 3 fix and fail-closed validation, four more
261
+ oracles (`kl_divergence`, `shannon_entropy`, `expectation_value`, `most_frequent_outcome`),
262
+ declarative noise-model simulation and noise-aware expected distributions (so
263
+ `chi_square`/`kl_divergence` can gate on hardware), and the AWS Braket backend (local
264
+ simulation; the cloud path awaits validation on real hardware).
265
+ - **Planned:** error mitigation via [Mitiq](https://mitiq.readthedocs.io/), multi-backend
266
+ differential testing, circuit fixtures and property-based generation, a Helm chart, an
267
+ optional OpenTelemetry exporter for the telemetry layer (kept out of the core
268
+ dependencies), and validation of the Braket cloud path on real hardware.
260
269
 
261
270
  See [`CHANGELOG.md`](CHANGELOG.md) and the [ADRs](docs/adr/) for decisions and rationale.
262
271
 
@@ -1,5 +1,14 @@
1
1
  # shotgate documentation
2
2
 
3
+ shotgate runs container-native CI/CD quality gates for quantum circuits: it executes a
4
+ circuit on a simulator or a real quantum processing unit (QPU) and gates the pipeline on
5
+ the *statistical* properties of the measured shot distribution (chi-square goodness-of-fit,
6
+ total variation distance, Hellinger fidelity, support leakage) rather than on exact
7
+ equality, which probabilistic output makes meaningless. New here? Read
8
+ [Getting Started](getting-started.md) first, then the
9
+ [Workflow Specification](workflow-spec.md) for the YAML schema and the
10
+ [Assertion Catalog](assertions.md) for the oracles and how to set thresholds.
11
+
3
12
  | Document | What it covers |
4
13
  | --- | --- |
5
14
  | [Getting Started](getting-started.md) | Zero to a passing quantum gate with Podman. |
@@ -18,9 +18,11 @@ came out of those runs:
18
18
  on the same counts (TVD 0.1350, fidelity 0.8650, leakage 0.1350, P(`00`) 0.4343).
19
19
  The ideal expected distribution `{00: 0.5, 11: 0.5}` assigns probability 0 to
20
20
  the error states `01`/`10`; the implementation floors expected counts at
21
- `_MIN_EXPECTED = 1e-12` to keep the statistic finite, so each of the 553 leaked
22
- shots contributes on the order of `observed^2 / 1e-12`. Against an ideal support,
23
- any device leakage forces rejection regardless of thresholds.
21
+ `_MIN_EXPECTED = 1e-12` to keep the statistic finite, so each error state
22
+ contributes `observed^2 / 1e-12` (the 304 and 249 counts in `10`/`01` sum to about
23
+ `1.5e17`, a magnitude set by the floor, not the data). Against an ideal support,
24
+ any device leakage forces rejection regardless of thresholds; the rejection is
25
+ floor-independent even though the statistic's magnitude is not.
24
26
 
25
27
  ## Decision
26
28
 
@@ -0,0 +1,49 @@
1
+ # ADR-0007: Fail-closed workflow validation (empty gates and malformed expected distributions)
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-06-26
5
+
6
+ ## Context
7
+
8
+ Two ways a shotgate gate could pass while not actually validating anything were found
9
+ by exercising the runner and the schema:
10
+
11
+ 1. **A job with no assertions reported PASS.** The runner computed
12
+ `all(o.passed for o in outcomes)`, and `all([])` is `True`, so a workflow whose
13
+ jobs declared `assertions: []` passed the pipeline while checking nothing. A quality
14
+ gate that can be green without testing is a correctness hazard, not a convenience.
15
+ 2. **Expected distributions accepted any string key.** The `expected` maps of the
16
+ distribution oracles, and the `state`/`states` fields, were typed `dict[str, float]`
17
+ / `str` / `list[str]` with no constraint, so a 3-bit key against a 2-qubit circuit,
18
+ mixed-width keys, or a typo like `"0x"` parsed cleanly and were then silently
19
+ mis-compared, producing a wrong-but-green result instead of an error.
20
+
21
+ ## Decision
22
+
23
+ shotgate validates fail-closed: a gate that cannot meaningfully check something is an
24
+ error, not a pass.
25
+
26
+ 1. **An empty gate fails by default.** A job that runs but declares no assertions is
27
+ marked failed with an explanatory message. The behaviour is opt-out: `--allow-empty`
28
+ on `shotgate run` (or `allow_empty=True` on `Runner`) restores a passing empty job
29
+ for the rare case where that is intended (a placeholder, a circuit-only smoke run).
30
+ 2. **Basis-state keys are validated at schema-parse time.** Keys in `expected`, and the
31
+ `state`/`states` fields, must be non-empty, contain only `0`/`1` (whitespace from
32
+ multi-register formatting is stripped, matching `metrics.clean_key`), and all
33
+ describe the same number of bits. A malformed key now raises a pydantic
34
+ `ValidationError` with the offending key named.
35
+
36
+ ## Consequences
37
+
38
+ - **+** No silent no-op gates: a green shotgate run means assertions actually ran.
39
+ - **+** Distribution typos fail fast at load time with a clear message, the same way the
40
+ strict (`extra="forbid"`) schema already catches unknown fields.
41
+ - **+** The cross-check against the circuit's qubit width is now well-defined to add at
42
+ evaluate time later, because the keys are guaranteed internally consistent.
43
+ - **-** This is a behaviour change: a previously-passing empty gate now fails, and a
44
+ workflow with a malformed key that previously parsed now raises. Both are pre-1.0
45
+ (`0.x`) and intended; the empty-gate change is documented in `CHANGELOG.md`, and the
46
+ escape hatch (`--allow-empty`) covers the legitimate case.
47
+ - The validation is internal-consistency only (binary, equal width). Matching the key
48
+ width to the executed circuit's classical-bit count is a separate evaluate-time check
49
+ deferred to a later version.
@@ -0,0 +1,52 @@
1
+ # ADR-0008: Counts-based oracle expansion (divergence, entropy, expectation, mode)
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-06-26
5
+
6
+ ## Context
7
+
8
+ shotgate shipped five assertion oracles: a distance test (`distribution_tvd`), a
9
+ goodness-of-fit test (`chi_square`), a closeness score (`hellinger_fidelity`), a marginal
10
+ (`state_probability`), and a structural/leakage bound (`allowed_states`). The quantum
11
+ software-testing field, and adjacent tools such as QUTest, cover a wider assertion set,
12
+ notably observable expectation values, distribution entropy, and information-theoretic
13
+ divergence. These are real gaps for a tool that markets itself as the statistical quality
14
+ gate for quantum circuits.
15
+
16
+ The constraint that has served the project well is that oracles are **counts-based and
17
+ pure-Python**: each assertion's `evaluate(counts, shots)` operates only on a histogram of
18
+ measured bitstrings, so the validation core never imports a quantum SDK (ADR-0002).
19
+
20
+ ## Decision
21
+
22
+ Add four oracles that stay inside that contract, taking the count from five to nine:
23
+
24
+ 1. `kl_divergence`: bound the Kullback-Leibler divergence `D(observed || expected)` in bits.
25
+ 2. `shannon_entropy`: bound the distribution's Shannon entropy (min/max bits).
26
+ 3. `expectation_value`: bound the Pauli-Z product `<Z_{q0} Z_{q1} ...>` in `[-1, 1]`, read
27
+ off the computational-basis counts as a parity expectation.
28
+ 4. `most_frequent_outcome`: assert the modal measured state, optionally above a probability.
29
+
30
+ Two related oracles are **deferred** because they do not fit the `evaluate(counts, shots)`
31
+ contract:
32
+
33
+ - **Arbitrary-Pauli observables** (e.g. `<X>`, `<Y>`, entanglement witnesses) need
34
+ basis-change measurement circuits, not just computational-basis counts. `expectation_value`
35
+ is therefore Z-basis only for now.
36
+ - **Structural assertions on the circuit** (depth, width, gate-set membership) need the
37
+ circuit/telemetry, which `evaluate` does not receive; they require a signature change and
38
+ are left to a later version.
39
+
40
+ ## Consequences
41
+
42
+ - **+** Closes the distribution/observable breadth gap while keeping the core SDK-free and
43
+ unit-tested against analytic values (`<Z0 Z1> = +1` for a Bell pair, `H = 1` bit for a
44
+ balanced two-outcome distribution, `D(p||p) = 0`).
45
+ - **+** `expectation_value` gives a hardware-friendly single-number observable to track,
46
+ complementing the distribution oracles.
47
+ - **-** `kl_divergence` inherits the zero-support pathology of `chi_square`: an ideal
48
+ `expected` that assigns probability 0 to a device's error states diverges to infinity, so
49
+ KL is simulator-oriented unless `expected` is noise-aware (the v0.5 work). The reporter
50
+ serialises a non-finite metric as JSON `null` so output stays valid.
51
+ - **-** `expectation_value` covers only Z-basis products; the broader Pauli and
52
+ entanglement-witness oracles remain future work, as do the circuit-structural oracles.
@@ -0,0 +1,55 @@
1
+ # ADR-0009: Declarative noise-model simulation on the local Aer backend
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-06-26
5
+
6
+ ## Context
7
+
8
+ The `*-hardware` example gates carry noise-aware thresholds (Bell: TVD <= 0.15, fidelity
9
+ >= 0.85, leakage <= 0.15; full table in `docs/hardware-validation.md` section 4). Until now
10
+ those thresholds could only be exercised against a real QPU, which is queued and metered:
11
+ there was no way to regression-test the noise-aware profiles in ordinary CI, and a profile
12
+ could silently rot (become too loose or too tight) between hardware runs.
13
+
14
+ A measurement on this codebase showed that an Aer noise model with a per-gate depolarizing
15
+ error plus a readout error reproduces the measured `ibm_fez` Bell regime closely (simulated
16
+ TVD 0.1223 against a measured 0.1284), so a simulated device is a faithful enough proxy to
17
+ gate the noise-aware thresholds offline.
18
+
19
+ ## Decision
20
+
21
+ Add an optional, declarative `noise` block to a backend spec, consumed by the `local-aer`
22
+ backend:
23
+
24
+ ```yaml
25
+ backend:
26
+ provider: local-aer
27
+ noise:
28
+ depolarizing_1q: 0.004 # per single-qubit gate
29
+ depolarizing_2q: 0.012 # per two-qubit gate
30
+ readout_p0: 0.06 # P(measure 1 | prepared 0)
31
+ readout_p1: 0.07 # P(measure 0 | prepared 1)
32
+ ```
33
+
34
+ - `NoiseSpec` is a strict (`extra="forbid"`) pydantic model with each parameter bounded in
35
+ `[0, 1]`; an all-zero spec is identical to the noiseless simulator.
36
+ - The spec rides through the backend `options` dict (set by the registry) so the `Backend`
37
+ ABC stays minimal. The `local-aer` backend separates it out, builds a Qiskit Aer
38
+ `NoiseModel` (uniform depolarizing on common 1- and 2-qubit gates, plus an assignment
39
+ readout error), and records a `noisy` flag in the result metadata.
40
+ - Real-hardware backends ignore `noise` (a device carries its own noise).
41
+
42
+ ## Consequences
43
+
44
+ - **+** The noise-aware `*-hardware` thresholds become testable in CI with zero QPU time; a
45
+ `bell-state-noisy-sim` example gates a simulated-noisy Bell pair against hardware-profile
46
+ bounds.
47
+ - **+** Contributors can develop and tune noise-aware thresholds offline before spending a
48
+ hardware run.
49
+ - **-** The model is an approximate, uniform device proxy, not a calibration: it applies one
50
+ depolarizing rate per gate class and one readout matrix to every qubit, and only to the
51
+ enumerated gate names (others stay noiseless). It does not capture crosstalk, coherent
52
+ errors, or qubit-specific calibration. It is a threshold-exercising tool, not a device
53
+ digital twin.
54
+ - **-** `noise` is meaningful only for the `local-aer` backend; setting it on `ibm` is a
55
+ silent no-op (documented).