qadence 1.1.0__tar.gz → 1.2.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.
- {qadence-1.1.0 → qadence-1.2.0}/.github/workflows/test_all.yml +25 -0
- {qadence-1.1.0 → qadence-1.2.0}/PKG-INFO +2 -2
- {qadence-1.1.0 → qadence-1.2.0}/mkdocs.yml +1 -2
- {qadence-1.1.0 → qadence-1.2.0}/pyproject.toml +4 -4
- qadence-1.2.0/qadence/analog/__init__.py +7 -0
- qadence-1.2.0/qadence/analog/addressing.py +167 -0
- qadence-1.2.0/qadence/analog/constants.py +59 -0
- qadence-1.2.0/qadence/analog/device.py +82 -0
- qadence-1.2.0/qadence/analog/hamiltonian_terms.py +101 -0
- qadence-1.2.0/qadence/analog/parse_analog.py +120 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backend.py +27 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/braket/backend.py +1 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/__init__.py +0 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/backend.py +30 -15
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/config.py +19 -10
- qadence-1.2.0/qadence/backends/pulser/devices.py +71 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/pulses.py +70 -12
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pyqtorch/backend.py +2 -3
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pyqtorch/config.py +18 -12
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pyqtorch/convert_ops.py +12 -4
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pytorch_wrapper.py +2 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/utils.py +1 -10
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/abstract.py +5 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/analog.py +18 -9
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/block_to_tensor.py +11 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/primitive.py +81 -9
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/__init__.py +4 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/feature_maps.py +84 -60
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/hamiltonians.py +27 -98
- qadence-1.2.0/qadence/constructors/rydberg_feature_maps.py +113 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/divergences.py +12 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/utils.py +1 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/extensions.py +1 -6
- qadence-1.2.0/qadence/finitediff.py +47 -0
- qadence-1.2.0/qadence/mitigations/readout.py +160 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/models/qnn.py +88 -23
- {qadence-1.1.0 → qadence-1.2.0}/qadence/operations.py +55 -70
- {qadence-1.1.0 → qadence-1.2.0}/qadence/parameters.py +10 -2
- {qadence-1.1.0 → qadence-1.2.0}/qadence/register.py +91 -43
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/__init__.py +1 -0
- qadence-1.2.0/qadence/transpile/apply_fn.py +40 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/block.py +15 -7
- {qadence-1.1.0 → qadence-1.2.0}/qadence/types.py +19 -1
- {qadence-1.1.0 → qadence-1.2.0}/qadence/utils.py +35 -0
- qadence-1.1.0/qadence/analog/__init__.py +0 -5
- qadence-1.1.0/qadence/analog/interaction.py +0 -198
- qadence-1.1.0/qadence/analog/utils.py +0 -132
- qadence-1.1.0/qadence/backends/pulser/devices.py +0 -77
- qadence-1.1.0/qadence/mitigations/readout.py +0 -93
- {qadence-1.1.0 → qadence-1.2.0}/.coveragerc +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.github/dependabot.yml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.github/workflows/build_docs.yml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.github/workflows/dependabot.yml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.github/workflows/lint.yml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.github/workflows/test_examples.yml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.github/workflows/test_fast.yml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.gitignore +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/.pre-commit-config.yaml +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/CODE_OF_CONDUCT.md +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/CONTRIBUTING.md +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/LICENSE +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/MANIFEST.in +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/README.md +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/adjoint.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/api.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/braket/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/braket/config.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/braket/convert_ops.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/gpsr.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/channels.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/cloud.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/convert_ops.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pulser/waveforms.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/backends/pyqtorch/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/composite.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/embedding.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/manipulate.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/matrix.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/blocks/utils.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/circuit.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/ansatze.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/daqc/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/daqc/daqc.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/daqc/gen_parser.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/daqc/utils.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/iia.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/qft.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/rydberg_hea.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/constructors/utils.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/decompose.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/assets/dark/measurement.png +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/assets/dark/measurement.svg +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/assets/light/measurement.png +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/assets/light/measurement.svg +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/themes.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/draw/vizbackend.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/exceptions/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/exceptions/exceptions.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/execution.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/logger.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/measurements/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/measurements/protocols.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/measurements/shadow.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/measurements/tomography.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/mitigations/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/mitigations/analog_zne.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/mitigations/protocols.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/config.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/data.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/models.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/optimize_step.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/parameters.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/printing.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/saveload.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/tensors.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/train_grad.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/train_no_grad.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/ml_tools/utils.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/models/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/models/quantum_model.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/noise/__init__.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/noise/protocols.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/noise/readout.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/overlap.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/py.typed +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/qubit_support.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/serialization.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/states.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/circuit.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/digitalize.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/flatten.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/invert.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/qadence/transpile/transpile.py +0 -0
- {qadence-1.1.0 → qadence-1.2.0}/setup.py +0 -0
@@ -10,6 +10,7 @@ concurrency:
|
|
10
10
|
group: all-${{ github.head_ref || github.run_id }}
|
11
11
|
cancel-in-progress: true
|
12
12
|
|
13
|
+
|
13
14
|
jobs:
|
14
15
|
test_qadence_ubuntu:
|
15
16
|
name: Test Qadence (ubuntu)
|
@@ -33,3 +34,27 @@ jobs:
|
|
33
34
|
- name: Run tests
|
34
35
|
run: |
|
35
36
|
hatch -v run test
|
37
|
+
|
38
|
+
test_fast_qadence_windows_mac:
|
39
|
+
name: Test Qadence (Windows, MacOS)
|
40
|
+
runs-on: ${{ matrix.os }}
|
41
|
+
strategy:
|
42
|
+
matrix:
|
43
|
+
os: [macos-latest, windows-latest]
|
44
|
+
python-version: ["3.10"]
|
45
|
+
steps:
|
46
|
+
- name: Checkout Qadence
|
47
|
+
uses: actions/checkout@v4
|
48
|
+
|
49
|
+
- name: Set up Python ${{ matrix.python-version }}
|
50
|
+
uses: actions/setup-python@v4
|
51
|
+
with:
|
52
|
+
python-version: ${{ matrix.python-version }}
|
53
|
+
|
54
|
+
- name: Install Hatch
|
55
|
+
run: |
|
56
|
+
pip install hatch
|
57
|
+
|
58
|
+
- name: Run tests
|
59
|
+
run: |
|
60
|
+
hatch -v run test -m "not slow"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qadence
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.0
|
4
4
|
Summary: Pasqal interface for circuit-based quantum computing SDKs
|
5
5
|
Author-email: Aleksander Wennersteen <aleksander.wennersteen@pasqal.com>, Gert-Jan Both <gert-jan.both@pasqal.com>, Niklas Heim <niklas.heim@pasqal.com>, Mario Dagrada <mario.dagrada@pasqal.com>, Vincent Elfving <vincent.elfving@pasqal.com>, Dominik Seitz <dominik.seitz@pasqal.com>, Roland Guichard <roland.guichard@pasqal.com>, "Joao P. Moutinho" <joao.moutinho@pasqal.com>, Vytautas Abramavicius <vytautas.abramavicius@pasqal.com>
|
6
6
|
License: Apache 2.0
|
@@ -19,7 +19,7 @@ Requires-Dist: jsonschema
|
|
19
19
|
Requires-Dist: matplotlib
|
20
20
|
Requires-Dist: nevergrad
|
21
21
|
Requires-Dist: openfermion
|
22
|
-
Requires-Dist: pyqtorch==1.0.
|
22
|
+
Requires-Dist: pyqtorch==1.0.3
|
23
23
|
Requires-Dist: rich
|
24
24
|
Requires-Dist: scipy
|
25
25
|
Requires-Dist: sympytorch>=0.1.2
|
@@ -23,10 +23,9 @@ nav:
|
|
23
23
|
- digital_analog_qc/index.md
|
24
24
|
- Basic operations on neutral-atoms: digital_analog_qc/analog-basics.md
|
25
25
|
- Fitting a simple function: digital_analog_qc/analog-qcl.md
|
26
|
-
-
|
26
|
+
- Restricted local addressability: digital_analog_qc/semi-local-addressing.md
|
27
27
|
- Pulse-level programming with Pulser: digital_analog_qc/pulser-basic.md
|
28
28
|
- Solve a QUBO problem: digital_analog_qc/analog-qubo.md
|
29
|
-
- Pulse-level programming with Pulser: digital_analog_qc/pulser-basic.md
|
30
29
|
- CNOT with interacting qubits: digital_analog_qc/daqc-cnot.md
|
31
30
|
|
32
31
|
- Variational quantum algorithms:
|
@@ -19,7 +19,7 @@ authors = [
|
|
19
19
|
]
|
20
20
|
requires-python = ">=3.9,<3.12"
|
21
21
|
license = {text = "Apache 2.0"}
|
22
|
-
version = "1.
|
22
|
+
version = "1.2.0"
|
23
23
|
classifiers=[
|
24
24
|
"License :: OSI Approved :: Apache Software License",
|
25
25
|
"Programming Language :: Python",
|
@@ -40,7 +40,7 @@ dependencies = [
|
|
40
40
|
"jsonschema",
|
41
41
|
"nevergrad",
|
42
42
|
"scipy",
|
43
|
-
"pyqtorch==1.0.
|
43
|
+
"pyqtorch==1.0.3",
|
44
44
|
"matplotlib"
|
45
45
|
]
|
46
46
|
|
@@ -111,11 +111,11 @@ filterwarnings = [
|
|
111
111
|
|
112
112
|
[tool.hatch.envs.docs]
|
113
113
|
dependencies = [
|
114
|
-
"mkdocs
|
114
|
+
"mkdocs",
|
115
115
|
"mkdocs-material",
|
116
116
|
"mkdocstrings",
|
117
117
|
"mkdocstrings-python",
|
118
|
-
"mkdocs-section-index
|
118
|
+
"mkdocs-section-index",
|
119
119
|
"mkdocs-exclude",
|
120
120
|
"markdown-exec",
|
121
121
|
"mike",
|
@@ -0,0 +1,7 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from .addressing import AddressingPattern
|
4
|
+
from .device import IdealDevice, RealisticDevice, RydbergDevice
|
5
|
+
from .parse_analog import add_background_hamiltonian
|
6
|
+
|
7
|
+
__all__ = ["RydbergDevice", "IdealDevice", "RealisticDevice", "AddressingPattern"]
|
@@ -0,0 +1,167 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, fields
|
4
|
+
from typing import Union
|
5
|
+
from warnings import warn
|
6
|
+
|
7
|
+
from sympy import Expr, Heaviside, exp
|
8
|
+
from torch import Tensor, pi
|
9
|
+
|
10
|
+
from qadence.parameters import Parameter, evaluate
|
11
|
+
|
12
|
+
# FIXME: Clarify the roles of these values in the context
|
13
|
+
# device specification and how they relate with the
|
14
|
+
# maximum values for delta and omega.
|
15
|
+
GLOBAL_MAX_AMPLITUDE = 300
|
16
|
+
GLOBAL_MAX_DETUNING = 2 * pi * 2000
|
17
|
+
LOCAL_MAX_AMPLITUDE = 3
|
18
|
+
LOCAL_MAX_DETUNING = 2 * pi * 20
|
19
|
+
|
20
|
+
TWeight = Union[str, float, Tensor, Parameter]
|
21
|
+
|
22
|
+
|
23
|
+
def sigmoid(x: Tensor, a: float, b: float) -> Expr:
|
24
|
+
return 1.0 / (1.0 + exp(-a * (x + b)))
|
25
|
+
|
26
|
+
|
27
|
+
@dataclass
|
28
|
+
class AddressingPattern:
|
29
|
+
"""Semi-local addressing pattern."""
|
30
|
+
|
31
|
+
n_qubits: int
|
32
|
+
"""Number of qubits in register."""
|
33
|
+
|
34
|
+
weights_amp: dict[int, TWeight]
|
35
|
+
"""List of weights for fixed amplitude pattern that cannot be changed during the execution."""
|
36
|
+
|
37
|
+
weights_det: dict[int, TWeight]
|
38
|
+
"""List of weights for fixed detuning pattern that cannot be changed during the execution."""
|
39
|
+
|
40
|
+
amp: TWeight = LOCAL_MAX_AMPLITUDE
|
41
|
+
"""Maximum amplitude of the amplitude pattern felt by a single qubit."""
|
42
|
+
|
43
|
+
det: TWeight = LOCAL_MAX_DETUNING
|
44
|
+
"""Maximum detuning of the detuning pattern felt by a single qubit."""
|
45
|
+
|
46
|
+
def _validate_weights(
|
47
|
+
self,
|
48
|
+
weights: dict[int, TWeight],
|
49
|
+
) -> None:
|
50
|
+
for v in weights.values():
|
51
|
+
if not isinstance(v, (str, Parameter)):
|
52
|
+
if not (v >= 0.0 and v <= 1.0):
|
53
|
+
raise ValueError("Addressing pattern weights must sum fall in range [0.0, 1.0]")
|
54
|
+
|
55
|
+
def _constrain_weights(
|
56
|
+
self,
|
57
|
+
weights: dict[int, TWeight],
|
58
|
+
) -> dict:
|
59
|
+
# augment weight dict if needed
|
60
|
+
weights = {
|
61
|
+
i: Parameter(0.0)
|
62
|
+
if i not in weights
|
63
|
+
else (Parameter(weights[i]) if not isinstance(weights[i], Parameter) else weights[i])
|
64
|
+
for i in range(self.n_qubits)
|
65
|
+
}
|
66
|
+
|
67
|
+
# restrict weights to [0, 1] range - equal to 0 everywhere else
|
68
|
+
weights = {
|
69
|
+
k: v if v.is_number else abs(v * (sigmoid(v, 20, 1) - sigmoid(v, 20.0, -1))) # type: ignore [union-attr]
|
70
|
+
for k, v in weights.items()
|
71
|
+
}
|
72
|
+
|
73
|
+
return weights
|
74
|
+
|
75
|
+
def _constrain_max_vals(self) -> None:
|
76
|
+
# enforce constraints:
|
77
|
+
# 0 <= amp <= GLOBAL_MAX_AMPLITUDE
|
78
|
+
# 0 <= abs(det) <= GLOBAL_MAX_DETUNING
|
79
|
+
self.amp = abs(
|
80
|
+
self.amp
|
81
|
+
* (
|
82
|
+
Heaviside(self.amp + GLOBAL_MAX_AMPLITUDE) # type: ignore [operator]
|
83
|
+
- Heaviside(self.amp - GLOBAL_MAX_AMPLITUDE) # type: ignore [operator]
|
84
|
+
)
|
85
|
+
)
|
86
|
+
self.det = -abs(
|
87
|
+
self.det
|
88
|
+
* (
|
89
|
+
Heaviside(self.det + GLOBAL_MAX_DETUNING)
|
90
|
+
- Heaviside(self.det - GLOBAL_MAX_DETUNING)
|
91
|
+
)
|
92
|
+
)
|
93
|
+
|
94
|
+
def _create_local_constraint(self, val: Expr, weights: dict, max_val: float) -> dict:
|
95
|
+
# enforce local constraints:
|
96
|
+
# amp * w_amp_i < LOCAL_MAX_AMPLITUDE or
|
97
|
+
# abs(det) * w_det_i < LOCAL_MAX_DETUNING
|
98
|
+
local_constr = {k: val * v for k, v in weights.items()}
|
99
|
+
local_constr = {k: Heaviside(v) - Heaviside(v - max_val) for k, v in local_constr.items()}
|
100
|
+
|
101
|
+
return local_constr
|
102
|
+
|
103
|
+
def _create_global_constraint(self, val: Expr, weights: dict, max_val: float) -> Expr:
|
104
|
+
# enforce global constraints:
|
105
|
+
# amp * sum(w_amp_0, w_amp_1, ...) < GLOBAL_MAX_AMPLITUDE or
|
106
|
+
# abs(det) * sum(w_det_0, w_det_1, ...) < GLOBAL_MAX_DETUNING
|
107
|
+
weighted_vals_global = val * sum([v for v in weights.values()])
|
108
|
+
weighted_vals_global = Heaviside(weighted_vals_global) - Heaviside(
|
109
|
+
weighted_vals_global - max_val
|
110
|
+
)
|
111
|
+
|
112
|
+
return weighted_vals_global
|
113
|
+
|
114
|
+
def __post_init__(self) -> None:
|
115
|
+
# validate amplitude/detuning weights
|
116
|
+
self._validate_weights(self.weights_amp)
|
117
|
+
self._validate_weights(self.weights_det)
|
118
|
+
|
119
|
+
# validate maximum global amplitude/detuning values
|
120
|
+
if not isinstance(self.amp, (str, Parameter)):
|
121
|
+
if self.amp > GLOBAL_MAX_AMPLITUDE:
|
122
|
+
warn("Maximum absolute value of amplitude is exceeded")
|
123
|
+
elif isinstance(self.amp, str):
|
124
|
+
self.amp = Parameter(self.amp, trainable=True)
|
125
|
+
if not isinstance(self.det, (str, Parameter)):
|
126
|
+
if abs(self.det) > GLOBAL_MAX_DETUNING:
|
127
|
+
warn("Maximum absolute value of detuning is exceeded")
|
128
|
+
elif isinstance(self.det, str):
|
129
|
+
self.det = Parameter(self.det, trainable=True)
|
130
|
+
|
131
|
+
# constrain amplitude/detuning parameterized weights to [0.0, 1.0] interval
|
132
|
+
self.weights_amp = self._constrain_weights(self.weights_amp)
|
133
|
+
self.weights_det = self._constrain_weights(self.weights_det)
|
134
|
+
|
135
|
+
# constrain max global amplitude and detuning to strict interval
|
136
|
+
self._constrain_max_vals()
|
137
|
+
|
138
|
+
# create additional local and global constraints for amplitude/detuning masks
|
139
|
+
self.local_constr_amp = self._create_local_constraint(
|
140
|
+
self.amp, self.weights_amp, LOCAL_MAX_AMPLITUDE
|
141
|
+
)
|
142
|
+
self.local_constr_det = self._create_local_constraint(
|
143
|
+
-self.det, self.weights_det, LOCAL_MAX_DETUNING
|
144
|
+
)
|
145
|
+
self.global_constr_amp = self._create_global_constraint(
|
146
|
+
self.amp, self.weights_amp, GLOBAL_MAX_AMPLITUDE
|
147
|
+
)
|
148
|
+
self.global_constr_det = self._create_global_constraint(
|
149
|
+
-self.det, self.weights_det, GLOBAL_MAX_DETUNING
|
150
|
+
)
|
151
|
+
|
152
|
+
# validate number of qubits in mask
|
153
|
+
if max(list(self.weights_amp.keys())) >= self.n_qubits:
|
154
|
+
raise ValueError("Amplitude weight specified for non-existing qubit")
|
155
|
+
if max(list(self.weights_det.keys())) >= self.n_qubits:
|
156
|
+
raise ValueError("Detuning weight specified for non-existing qubit")
|
157
|
+
|
158
|
+
def evaluate(self, weights: dict, values: dict) -> dict:
|
159
|
+
# evaluate weight expressions with actual values
|
160
|
+
return {k: evaluate(v, values, as_torch=True).flatten() for k, v in weights.items()} # type: ignore [union-attr]
|
161
|
+
|
162
|
+
def _to_dict(self) -> dict:
|
163
|
+
return {field.name: getattr(self, field.name) for field in fields(self)}
|
164
|
+
|
165
|
+
@classmethod
|
166
|
+
def _from_dict(cls, d: dict) -> AddressingPattern | None:
|
167
|
+
return cls(**d) if len(d) > 0 else None
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
# Ising coupling coefficient depending on the Rydberg level
|
4
|
+
# Include a normalization to the Planck constant hbar
|
5
|
+
# In units of [rad . µm^6 / µs]
|
6
|
+
|
7
|
+
C6_DICT = {
|
8
|
+
50: 96120.72,
|
9
|
+
51: 122241.6,
|
10
|
+
52: 154693.02,
|
11
|
+
53: 194740.36,
|
12
|
+
54: 243973.91,
|
13
|
+
55: 304495.01,
|
14
|
+
56: 378305.98,
|
15
|
+
57: 468027.05,
|
16
|
+
58: 576714.85,
|
17
|
+
59: 707911.38,
|
18
|
+
60: 865723.02,
|
19
|
+
61: 1054903.11,
|
20
|
+
62: 1281042.11,
|
21
|
+
63: 1550531.15,
|
22
|
+
64: 1870621.31,
|
23
|
+
65: 2249728.57,
|
24
|
+
66: 2697498.69,
|
25
|
+
67: 3224987.51,
|
26
|
+
68: 3844734.37,
|
27
|
+
69: 4571053.32,
|
28
|
+
70: 5420158.53,
|
29
|
+
71: 6410399.4,
|
30
|
+
72: 7562637.31,
|
31
|
+
73: 8900342.14,
|
32
|
+
74: 10449989.62,
|
33
|
+
75: 12241414.53,
|
34
|
+
76: 14308028.03,
|
35
|
+
77: 16687329.94,
|
36
|
+
78: 19421333.62,
|
37
|
+
79: 22557029.94,
|
38
|
+
80: 26146720.74,
|
39
|
+
81: 30248886.65,
|
40
|
+
82: 34928448.69,
|
41
|
+
83: 40257623.67,
|
42
|
+
84: 46316557.88,
|
43
|
+
85: 53194043.52,
|
44
|
+
86: 60988354.64,
|
45
|
+
87: 69808179.15,
|
46
|
+
88: 79773468.88,
|
47
|
+
89: 91016513.07,
|
48
|
+
90: 103677784.57,
|
49
|
+
91: 117933293.96,
|
50
|
+
92: 133943541.9,
|
51
|
+
93: 151907135.94,
|
52
|
+
94: 172036137.34,
|
53
|
+
95: 194562889.89,
|
54
|
+
96: 219741590.56,
|
55
|
+
97: 247850178.91,
|
56
|
+
98: 279192193.77,
|
57
|
+
99: 314098829.39,
|
58
|
+
100: 352931119.11,
|
59
|
+
}
|
@@ -0,0 +1,82 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, fields
|
4
|
+
|
5
|
+
from torch import pi
|
6
|
+
|
7
|
+
from qadence.analog import AddressingPattern
|
8
|
+
from qadence.types import DeviceType, Interaction
|
9
|
+
|
10
|
+
|
11
|
+
@dataclass(frozen=True, eq=True)
|
12
|
+
class RydbergDevice:
|
13
|
+
"""
|
14
|
+
Dataclass for interacting Rydberg atoms under an Hamiltonian:
|
15
|
+
|
16
|
+
H = ∑_i [Ω/2 * (cos(φ) * Xᵢ - sin(φ) * Yᵢ) - δ * N_i] + H_int,
|
17
|
+
|
18
|
+
where:
|
19
|
+
|
20
|
+
H_int = ∑_(j<i) (C_6 / R**6) * (N_i @ N_j) for the NN interaction;
|
21
|
+
|
22
|
+
H_int = ∑_(j<i) (C_3 / R**3) * ((X_i @ X_j) + (Y_i @ Y_j)) for the XY interaction;
|
23
|
+
"""
|
24
|
+
|
25
|
+
interaction: Interaction = Interaction.NN
|
26
|
+
"""Defines the interaction Hamiltonian."""
|
27
|
+
|
28
|
+
rydberg_level: int = 60
|
29
|
+
"""Rydberg level affecting the value of C_6."""
|
30
|
+
|
31
|
+
coeff_xy: float = 3700.00
|
32
|
+
"""Value of C_3."""
|
33
|
+
|
34
|
+
max_detuning: float = 2 * pi * 4
|
35
|
+
"""Maximum value of the detuning δ."""
|
36
|
+
|
37
|
+
max_amp: float = 2 * pi * 3
|
38
|
+
"""Maximum value of the amplitude Ω."""
|
39
|
+
|
40
|
+
pattern: AddressingPattern | None = None
|
41
|
+
"""Semi-local addressing pattern configuration."""
|
42
|
+
|
43
|
+
type: DeviceType = DeviceType.IDEALIZED
|
44
|
+
"""DeviceType.IDEALIZED or REALISTIC to convert to the Pulser backend."""
|
45
|
+
|
46
|
+
def __post_init__(self) -> None:
|
47
|
+
# FIXME: Currently not supporting custom interaction functions.
|
48
|
+
if self.interaction not in [Interaction.NN, Interaction.XY]:
|
49
|
+
raise KeyError(
|
50
|
+
"RydbergDevice currently only supports Interaction.NN or Interaction.XY."
|
51
|
+
)
|
52
|
+
|
53
|
+
def _to_dict(self) -> dict:
|
54
|
+
device_dict = {}
|
55
|
+
for field in fields(self):
|
56
|
+
if field.name != "pattern":
|
57
|
+
device_dict[field.name] = getattr(self, field.name)
|
58
|
+
else:
|
59
|
+
device_dict[field.name] = (
|
60
|
+
self.pattern._to_dict() if self.pattern is not None else {}
|
61
|
+
)
|
62
|
+
return device_dict
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def _from_dict(cls, d: dict) -> RydbergDevice:
|
66
|
+
pattern = AddressingPattern._from_dict(d["pattern"])
|
67
|
+
d["pattern"] = pattern
|
68
|
+
return cls(**d)
|
69
|
+
|
70
|
+
|
71
|
+
def IdealDevice(pattern: AddressingPattern | None = None) -> RydbergDevice:
|
72
|
+
return RydbergDevice(
|
73
|
+
pattern=pattern,
|
74
|
+
type=DeviceType.IDEALIZED,
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
def RealisticDevice(pattern: AddressingPattern | None = None) -> RydbergDevice:
|
79
|
+
return RydbergDevice(
|
80
|
+
pattern=pattern,
|
81
|
+
type=DeviceType.REALISTIC,
|
82
|
+
)
|
@@ -0,0 +1,101 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from sympy import cos, sin
|
4
|
+
from torch import float64, tensor
|
5
|
+
|
6
|
+
from qadence.analog.constants import C6_DICT
|
7
|
+
from qadence.blocks import add
|
8
|
+
from qadence.blocks.abstract import AbstractBlock
|
9
|
+
from qadence.blocks.analog import ConstantAnalogRotation
|
10
|
+
from qadence.constructors import hamiltonian_factory
|
11
|
+
from qadence.operations import I, N, X, Y, Z
|
12
|
+
from qadence.register import Register
|
13
|
+
from qadence.types import Interaction
|
14
|
+
|
15
|
+
|
16
|
+
def rydberg_interaction_hamiltonian(
|
17
|
+
register: Register,
|
18
|
+
) -> AbstractBlock:
|
19
|
+
"""
|
20
|
+
Computes the Rydberg Ising or XY interaction Hamiltonian for a register of qubits.
|
21
|
+
|
22
|
+
H_int = ∑_(j<i) (C_6 / R**6) * kron(N_i, N_j)
|
23
|
+
|
24
|
+
H_int = ∑_(j<i) (C_3 / R**3) * (kron(X_i, X_j) + kron(Y_i, Y_j))
|
25
|
+
|
26
|
+
Args:
|
27
|
+
register: the register of qubits.
|
28
|
+
"""
|
29
|
+
|
30
|
+
distances = tensor(list(register.distances.values()), dtype=float64)
|
31
|
+
device_specs = register.device_specs
|
32
|
+
|
33
|
+
if device_specs.interaction == Interaction.NN:
|
34
|
+
c6 = C6_DICT[device_specs.rydberg_level]
|
35
|
+
strength_list = c6 / (distances**6)
|
36
|
+
elif device_specs.interaction == Interaction.XY:
|
37
|
+
c3 = device_specs.coeff_xy
|
38
|
+
strength_list = c3 / (distances**3)
|
39
|
+
|
40
|
+
return hamiltonian_factory(
|
41
|
+
register,
|
42
|
+
interaction=device_specs.interaction,
|
43
|
+
interaction_strength=strength_list,
|
44
|
+
use_all_node_pairs=True,
|
45
|
+
)
|
46
|
+
|
47
|
+
|
48
|
+
def rydberg_drive_hamiltonian(block: ConstantAnalogRotation, register: Register) -> AbstractBlock:
|
49
|
+
"""
|
50
|
+
Computes the Rydberg drive Hamiltonian for some local or global rotation.
|
51
|
+
|
52
|
+
H_d = ∑_i (Ω/2 cos(φ) * X_i - sin(φ) * Y_i) - δ * N_i
|
53
|
+
|
54
|
+
Args:
|
55
|
+
block: the ConstantAnalogRotation block containing the parameters.
|
56
|
+
register: the register of qubits.
|
57
|
+
"""
|
58
|
+
|
59
|
+
if block.qubit_support.is_global:
|
60
|
+
qubit_support = tuple(register.nodes)
|
61
|
+
else:
|
62
|
+
qubit_support = block.qubit_support
|
63
|
+
|
64
|
+
omega = block.parameters.omega
|
65
|
+
delta = block.parameters.delta
|
66
|
+
phase = block.parameters.phase
|
67
|
+
|
68
|
+
x_terms = (omega / 2) * add(cos(phase) * X(i) for i in qubit_support)
|
69
|
+
y_terms = (omega / 2) * add(sin(phase) * Y(i) for i in qubit_support)
|
70
|
+
n_terms = delta * add(N(i) for i in qubit_support)
|
71
|
+
h_drive: AbstractBlock = x_terms - y_terms - n_terms
|
72
|
+
return h_drive
|
73
|
+
|
74
|
+
|
75
|
+
def rydberg_pattern_hamiltonian(register: Register) -> AbstractBlock | None:
|
76
|
+
support = tuple(range(register.n_qubits))
|
77
|
+
pattern = register.device_specs.pattern
|
78
|
+
if pattern is not None:
|
79
|
+
amp = pattern.amp
|
80
|
+
det = pattern.det
|
81
|
+
weights_amp = pattern.weights_amp
|
82
|
+
weights_det = pattern.weights_det
|
83
|
+
local_constr_amp = pattern.local_constr_amp
|
84
|
+
local_constr_det = pattern.local_constr_det
|
85
|
+
global_constr_amp = pattern.global_constr_amp
|
86
|
+
global_constr_det = pattern.global_constr_det
|
87
|
+
|
88
|
+
p_amp_terms: AbstractBlock = (
|
89
|
+
(1 / 2) # type: ignore [operator]
|
90
|
+
* amp
|
91
|
+
* global_constr_amp
|
92
|
+
* add(X(i) * weights_amp[i] * local_constr_amp[i] for i in support) # type: ignore [operator]
|
93
|
+
)
|
94
|
+
p_det_terms: AbstractBlock = (
|
95
|
+
-det # type: ignore [operator]
|
96
|
+
* global_constr_det
|
97
|
+
* add(0.5 * (I(i) - Z(i)) * weights_det[i] * local_constr_det[i] for i in support) # type: ignore [operator]
|
98
|
+
)
|
99
|
+
return p_amp_terms + p_det_terms
|
100
|
+
else:
|
101
|
+
return None
|
@@ -0,0 +1,120 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from qadence.analog.hamiltonian_terms import (
|
4
|
+
rydberg_drive_hamiltonian,
|
5
|
+
rydberg_interaction_hamiltonian,
|
6
|
+
rydberg_pattern_hamiltonian,
|
7
|
+
)
|
8
|
+
from qadence.blocks import chain
|
9
|
+
from qadence.blocks.abstract import AbstractBlock
|
10
|
+
from qadence.blocks.analog import (
|
11
|
+
AnalogKron,
|
12
|
+
ConstantAnalogRotation,
|
13
|
+
WaitBlock,
|
14
|
+
)
|
15
|
+
from qadence.circuit import QuantumCircuit
|
16
|
+
from qadence.operations import HamEvo
|
17
|
+
from qadence.register import Register
|
18
|
+
from qadence.transpile import apply_fn_to_blocks
|
19
|
+
|
20
|
+
|
21
|
+
def add_background_hamiltonian(
|
22
|
+
circuit: QuantumCircuit | AbstractBlock,
|
23
|
+
register: Register | None = None,
|
24
|
+
) -> QuantumCircuit | AbstractBlock:
|
25
|
+
"""
|
26
|
+
Parses a `QuantumCircuit` to transform `AnalogBlocks` to `HamEvo`.
|
27
|
+
|
28
|
+
Depends on the circuit `Register` and included `RydbergDevice` specifications.
|
29
|
+
|
30
|
+
Currently checks if input is either circuit or block and adjusts
|
31
|
+
the ouput accordingly. Running this function on single blocks is
|
32
|
+
currently used for eigenvalue computation for GPSR.
|
33
|
+
|
34
|
+
Arguments:
|
35
|
+
circuit: the circuit to parse, or single block to transform.
|
36
|
+
register: needed for calling the function on a single block.
|
37
|
+
"""
|
38
|
+
# FIXME: revisit eigenvalues of analog blocks and clean this code.
|
39
|
+
|
40
|
+
is_circuit_input = isinstance(circuit, QuantumCircuit)
|
41
|
+
|
42
|
+
if not is_circuit_input and register is None:
|
43
|
+
raise ValueError("Block input requires an input to the `register` argument.")
|
44
|
+
|
45
|
+
input_block: AbstractBlock = circuit.block if is_circuit_input else circuit # type: ignore
|
46
|
+
input_register: Register = circuit.register if is_circuit_input else register # type: ignore
|
47
|
+
|
48
|
+
if input_register.device_specs is not None:
|
49
|
+
# Create interaction hamiltonian:
|
50
|
+
h_int = rydberg_interaction_hamiltonian(input_register)
|
51
|
+
|
52
|
+
# Create addressing pattern:
|
53
|
+
h_addr = rydberg_pattern_hamiltonian(input_register)
|
54
|
+
|
55
|
+
output_block = apply_fn_to_blocks(
|
56
|
+
input_block,
|
57
|
+
_analog_to_hevo,
|
58
|
+
input_register,
|
59
|
+
(h_int, h_addr),
|
60
|
+
)
|
61
|
+
else:
|
62
|
+
output_block = input_block
|
63
|
+
|
64
|
+
if is_circuit_input:
|
65
|
+
return QuantumCircuit(input_register, output_block)
|
66
|
+
else:
|
67
|
+
return output_block
|
68
|
+
|
69
|
+
|
70
|
+
def _build_ham_evo(
|
71
|
+
block: WaitBlock | ConstantAnalogRotation,
|
72
|
+
h_int: AbstractBlock,
|
73
|
+
h_drive: AbstractBlock | None,
|
74
|
+
h_addr: AbstractBlock | None,
|
75
|
+
) -> HamEvo:
|
76
|
+
duration = block.parameters.duration
|
77
|
+
h_block = h_int
|
78
|
+
if h_drive is not None:
|
79
|
+
h_block += h_drive
|
80
|
+
if block.add_pattern and h_addr is not None:
|
81
|
+
h_block += h_addr
|
82
|
+
return HamEvo(h_block, duration / 1000)
|
83
|
+
|
84
|
+
|
85
|
+
def _analog_to_hevo(
|
86
|
+
block: AbstractBlock,
|
87
|
+
register: Register,
|
88
|
+
h_terms: tuple[AbstractBlock, AbstractBlock | None],
|
89
|
+
) -> AbstractBlock:
|
90
|
+
"""
|
91
|
+
Converter from AnalogBlock to the respective HamEvo.
|
92
|
+
|
93
|
+
Any other block not covered by the specific conditions below is left unchanged.
|
94
|
+
"""
|
95
|
+
|
96
|
+
h_int, h_addr = h_terms
|
97
|
+
|
98
|
+
if isinstance(block, WaitBlock):
|
99
|
+
return _build_ham_evo(block, h_int, None, h_addr)
|
100
|
+
|
101
|
+
if isinstance(block, ConstantAnalogRotation):
|
102
|
+
h_drive = rydberg_drive_hamiltonian(block, register)
|
103
|
+
return _build_ham_evo(block, h_int, h_drive, h_addr)
|
104
|
+
|
105
|
+
if isinstance(block, AnalogKron):
|
106
|
+
# Needed to ensure kronned Analog blocks are implemented
|
107
|
+
# in sequence, consistent with the current Pulser implementation.
|
108
|
+
# FIXME: Revisit this assumption and the need for AnalogKron to have
|
109
|
+
# the same duration, and clean this code accordingly.
|
110
|
+
# https://github.com/pasqal-io/qadence/issues/226
|
111
|
+
ops = []
|
112
|
+
for block in block.blocks:
|
113
|
+
if isinstance(block, ConstantAnalogRotation):
|
114
|
+
h_drive = rydberg_drive_hamiltonian(block, register)
|
115
|
+
ops.append(_build_ham_evo(block, h_int, h_drive, h_addr))
|
116
|
+
if len(ops) == 0:
|
117
|
+
ops.append(_build_ham_evo(block, h_int, None, h_addr)) # type: ignore [arg-type]
|
118
|
+
return chain(*ops)
|
119
|
+
|
120
|
+
return block
|
@@ -26,6 +26,7 @@ from qadence.mitigations import Mitigations
|
|
26
26
|
from qadence.noise import Noise
|
27
27
|
from qadence.parameters import stringify
|
28
28
|
from qadence.types import BackendName, DiffMode, Endianness
|
29
|
+
from qadence.utils import validate_values_and_state
|
29
30
|
|
30
31
|
logger = get_logger(__file__)
|
31
32
|
|
@@ -255,7 +256,7 @@ class Backend(ABC):
|
|
255
256
|
raise NotImplementedError
|
256
257
|
|
257
258
|
@abstractmethod
|
258
|
-
def
|
259
|
+
def _run(
|
259
260
|
self,
|
260
261
|
circuit: ConvertedCircuit,
|
261
262
|
param_values: dict[str, Tensor] = {},
|
@@ -277,6 +278,31 @@ class Backend(ABC):
|
|
277
278
|
"""
|
278
279
|
raise NotImplementedError
|
279
280
|
|
281
|
+
def run(
|
282
|
+
self,
|
283
|
+
circuit: ConvertedCircuit,
|
284
|
+
param_values: dict[str, Tensor] = {},
|
285
|
+
state: Tensor | None = None,
|
286
|
+
endianness: Endianness = Endianness.BIG,
|
287
|
+
*args: Any,
|
288
|
+
**kwargs: Any,
|
289
|
+
) -> Tensor:
|
290
|
+
"""Run a circuit and return the resulting wave function.
|
291
|
+
|
292
|
+
Arguments:
|
293
|
+
circuit: A converted circuit as returned by `backend.circuit`.
|
294
|
+
param_values: _**Already embedded**_ parameters of the circuit. See
|
295
|
+
[`embedding`][qadence.blocks.embedding.embedding] for more info.
|
296
|
+
state: Initial state.
|
297
|
+
endianness: Endianness of the resulting wavefunction.
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
A list of Counter objects where each key represents a bitstring
|
301
|
+
and its value the number of times it has been sampled from the given wave function.
|
302
|
+
"""
|
303
|
+
validate_values_and_state(state, circuit.abstract.n_qubits, param_values)
|
304
|
+
return self._run(circuit, param_values, state, endianness, *args, **kwargs)
|
305
|
+
|
280
306
|
@abstractmethod
|
281
307
|
def run_dm(
|
282
308
|
self,
|