feat: qudit noise model (2/5) (#1854) - #1859
Conversation
|
| Project | pyquil |
| Branch | 1854-qudit-noise-model |
| Testbed | ci-runner-linux |
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the--ci-only-thresholdsflag.
Click to view all benchmark results
| Benchmark | Latency | seconds (s) |
|---|---|---|
| test/benchmarks/test_program.py::test_copy_everything_except_instructions | 📈 view plot | 8.21 s |
| test/benchmarks/test_program.py::test_instructions | 📈 view plot | 2.59 s |
| test/benchmarks/test_program.py::test_iteration | 📈 view plot | 2.60 s |
f1884bc to
414f175
Compare
asaites
left a comment
There was a problem hiding this comment.
I've made several comments, which I hope will be useful. I might spend a little more time looking through _noise_model.py, but for the most part, I think I've gone through this pretty thoroughly (and am doing my best to keep up with the physics).
Most of my comments are more style than anything, but I have a few concerns. Overall, though, this looks pretty solid!
| if rows < 2: | ||
| return 0 | ||
| for base in range(2, rows + 1): | ||
| k = int(round(math.log(rows, base))) | ||
| if base**k == rows: | ||
| return k | ||
| return 1 |
There was a problem hiding this comment.
This might not matter much for the expected values of rows/reasonable values of len(self.matrix), but it is very slow for large numbers. As a much faster alternative, you can calculate it by iteratively checking if k, and you can reduce the problem to finding the solution for that new base, then multiply its factors by the one you've found.
Concretely:
def _int_n_root(x: int, n: int) -> int | None:
"""Return the largest integer less than or equal to n-th root of x."""
# This helper quickly checks if `x` is a perfect `n-th` root,
# and will do so without the numerical instability
# of the floating point check.
# There are faster algorithms for very large numbers,
# but this is already O(log(x)), and simple to implement.
low, high = 1, x
while low <= high:
mid = (low + high) // 2
if (mid_pow_n := mid ** n) == x:
return mid
elif mid_pow_n < x:
low = mid + 1
else:
high = mid - 1
return low - 1
SMALL_PRIMES = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61]
def find_k(r: int) -> tuple[int, int]:
"""For positive integer `r`, return integers `(b, k)` such that `r = b**k`
and `k` is the largest integer for any integer `b >= 1` that satisfies this relation.
If `r` is not a perfect power, this function returns `(r, 1)`.
"""
if r < 2:
return r, 1
# Check if `r` is a perfect p-th power of a small prime `p`.
# When it is, reduce `r` to that p-th root and build up the exponent `k`.
base, k = r, 1
primes = iter(SMALL_PRIMES)
p = next(primes, None)
while p:
if (b := _int_n_root(base, p)) < 2:
return base, k
elif b ** p != base:
p = next(primes, None)
else:
k *= p
base = b
raise ValueError("r is too large to efficiently find k")This approach is
There was a problem hiding this comment.
On further thought, this entire concept has a huge problem - it doesn't support quarts. Besides qubits and qutrits this the most likely qudit size. I think I'll need to add a option to specify the qudit dims
There was a problem hiding this comment.
No good solution to this right now. I think we will just have to accept only prime qudit dims
Introduce the new noise model under pyquil/noise/ (Channel, MeasurementChannel, ResetChannel, CycleChannel and the quax-based NoiseModel), preserving the legacy pyquil.noise API via _legacy_noise.py. Add qudit DefGate support in quilbase and enable float64 for tests. Part of splitting PR #1848 into a reviewable stack. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a2e40e8 to
65aa919
Compare
☂️ Python Coverage
Overall Coverage
New Files
Modified Files
|
| For a matrix of dimension d^k, returns k where d is the smallest | ||
| integer base >= 2 such that rows = d^k. | ||
| """ | ||
| decomposition = _integer_base_and_exponent(len(self.matrix)) |
There was a problem hiding this comment.
This doesn't work if the exponent is composite.
|
I've done a substantial refactor to add support for Lindbladian channels. The way this works is as follows:
|
|
I think there's a problem with the |
- Remove the awkward `SupportsReal` protocol; type `_resolve_params` against the canonical `Sequence[ParameterDesignator]` (drops the `ResolvableParam` alias and the unused `Protocol`/`SupportsFloat`/ `runtime_checkable` imports). - Inline the one-line noise combination into `Channel.__add__` and have `__matmul__` delegate to it (drop `_combine_noise_keeping_gate`). - Fix D401 (imperative mood) on the two `as_post_gate_noise` docstrings. - Drop `# type: ignore[attr-defined]` in the channel `__eq__` methods: mypy narrows `other` after the `type(self) is not type(other)` guard, so the comments were flagged unused. - Sort the `quilbase` import block (ruff I001). Makes `ruff check`, `ruff format --check`, and `mypy pyquil` pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ViuQ59tEfPw6z36i1fkFhg
The locked jax 0.10.0 predates the `enable_eigvec_derivs` kwarg that rigetti-quax 0.7.0 passes to `jax.lax.linalg.eig`, so every quax-backed Channel construction raised `TypeError` in CI. Update the lock to the newest jax each supported interpreter allows — jax 0.10.2 on Python 3.11 and jax 0.11.0 on Python 3.12 (0.11.0 dropped 3.11) — both of which include the kwarg. Proper version pinning to follow upstream in quax. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ViuQ59tEfPw6z36i1fkFhg
The pinned `pandoc` 2.4b0 (a beta) imports the removed `pkg_resources` module in `pandoc/utils.py`, which fails under the project's setuptools >=83 (setuptools dropped `pkg_resources` in 81). The docs `conf.py` imports `pandoc` in its `builder-inited` handler to render the changelog, so `make doctest` aborted with `No module named 'pkg_resources'`. The stable 2.4 release switched to `importlib.resources` and imports cleanly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ViuQ59tEfPw6z36i1fkFhg
Confirmed, we need to bump the minimum version of quax |
Part of the effort to land the experimental noise model and simulation module (originally PR #1848, branch
nonbreaking-noise-model) as a reviewable stack. This is PR 2 of 5, stacked on #1858 — review/merge in order. Closes #1854.This PR introduces the new quax-backed qudit noise model under
pyquil/noise/:Channel,MeasurementChannel,ResetChannel, andCycleChannel(_channels.py), and the newNoiseModelcontainer plusDepolarizingNoiseModel/CompositeNoiseModeland fidelity estimators (_noise_model.py).pyquil.noisepublic API is preserved:pyquil/noise.pyis moved topyquil/noise/_legacy_noise.pyand re-exported frompyquil/noise/__init__.py, with the legacyNoiseModel/KrausModelnow marked deprecated in favor of the quax-based model.It also adds qudit
DefGatesupport inquilbase.py(matrix dimensions may now be any perfect power, e.g. 3, 9, …, not just powers of two), which the qutrit noise channels and later simulators rely on, and enables float64 (jax_enable_x64) for the test suite.A handful of
ResetChanneltests verify behavior end-to-end through the density-matrix simulator; since that simulator lands in the next PR, those specific assertions are deferred to #1855 (they are restored there). Everything else in the noise module is fully tested here (test_noise_model.py).Stack
Tracked in #1863.