amplify-quantum 1.0.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.
- amplify_quantum/__init__.py +92 -0
- amplify_quantum/__version__.py +24 -0
- amplify_quantum/algo/__init__.py +4 -0
- amplify_quantum/algo/base.py +76 -0
- amplify_quantum/algo/qaoa/__init__.py +42 -0
- amplify_quantum/algo/qaoa/components.py +397 -0
- amplify_quantum/algo/qaoa/impls/__init__.py +10 -0
- amplify_quantum/algo/qaoa/impls/qaoa_auto.py +40 -0
- amplify_quantum/algo/qaoa/impls/qaoa_n_hot.py +45 -0
- amplify_quantum/algo/qaoa/impls/qaoa_original.py +45 -0
- amplify_quantum/algo/qaoa/interface.py +213 -0
- amplify_quantum/algo/qaoa/run.py +216 -0
- amplify_quantum/algo/qaoa/type.py +282 -0
- amplify_quantum/algo/rqaoa/__init__.py +23 -0
- amplify_quantum/algo/rqaoa/interface.py +224 -0
- amplify_quantum/algo/rqaoa/run.py +314 -0
- amplify_quantum/algo/rqaoa/type.py +116 -0
- amplify_quantum/algo/utility.py +159 -0
- amplify_quantum/circuit/__init__.py +4 -0
- amplify_quantum/circuit/base.py +127 -0
- amplify_quantum/circuit/qiskit.py +235 -0
- amplify_quantum/circuit/qulacs.py +227 -0
- amplify_quantum/client/__init__.py +4 -0
- amplify_quantum/client/aer.py +213 -0
- amplify_quantum/client/aqt.py +132 -0
- amplify_quantum/client/base.py +160 -0
- amplify_quantum/client/braketsimulator.py +144 -0
- amplify_quantum/client/ibm.py +188 -0
- amplify_quantum/client/ionq.py +133 -0
- amplify_quantum/client/iqm.py +133 -0
- amplify_quantum/client/qulacs.py +45 -0
- amplify_quantum/client/rigetti.py +133 -0
- amplify_quantum/minimize/__init__.py +4 -0
- amplify_quantum/minimize/base.py +57 -0
- amplify_quantum/minimize/no_op.py +80 -0
- amplify_quantum/minimize/scipy.py +168 -0
- amplify_quantum/sampler/__init__.py +4 -0
- amplify_quantum/sampler/base.py +92 -0
- amplify_quantum/sampler/braket.py +560 -0
- amplify_quantum/sampler/qiskit.py +652 -0
- amplify_quantum/sampler/qulacs.py +119 -0
- amplify_quantum/utils/__init__.py +4 -0
- amplify_quantum/utils/braket.py +110 -0
- amplify_quantum/utils/qiskit.py +48 -0
- amplify_quantum-1.0.0.dist-info/METADATA +45 -0
- amplify_quantum-1.0.0.dist-info/RECORD +48 -0
- amplify_quantum-1.0.0.dist-info/WHEEL +4 -0
- amplify_quantum-1.0.0.dist-info/licenses/LICENSE.txt +203 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright (c) Fixstars Corporation and Fixstars Amplify Corporation.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the Apache2.0 license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
try:
|
|
6
|
+
# ruff: disable[F401]
|
|
7
|
+
import qiskit
|
|
8
|
+
import qiskit_aer
|
|
9
|
+
import qiskit_ibm_runtime
|
|
10
|
+
import qulacs
|
|
11
|
+
import scipy
|
|
12
|
+
import typing_extensions
|
|
13
|
+
# ruff: enable[F401]
|
|
14
|
+
except ImportError:
|
|
15
|
+
pass
|
|
16
|
+
else:
|
|
17
|
+
from .algo.base import QuantumAlgoProtocol
|
|
18
|
+
from .algo.qaoa import (
|
|
19
|
+
QAOA,
|
|
20
|
+
QAOADurations,
|
|
21
|
+
QAOAHistoryItem,
|
|
22
|
+
QAOAResult,
|
|
23
|
+
QAOAType,
|
|
24
|
+
)
|
|
25
|
+
from .algo.rqaoa import (
|
|
26
|
+
RQAOA,
|
|
27
|
+
NormalElimination,
|
|
28
|
+
RQAOADurations,
|
|
29
|
+
RQAOAHistoryItem,
|
|
30
|
+
RQAOAResult,
|
|
31
|
+
RQAOAType,
|
|
32
|
+
UnintentionalElimination,
|
|
33
|
+
)
|
|
34
|
+
from .circuit.qiskit import QiskitCircuit
|
|
35
|
+
from .circuit.qulacs import QulacsCircuit
|
|
36
|
+
from .client.aer import AerClient
|
|
37
|
+
from .client.aqt import AQTClient
|
|
38
|
+
from .client.base import QuantumBaseClient
|
|
39
|
+
from .client.braketsimulator import BraketSimulatorClient
|
|
40
|
+
from .client.ibm import IBMClient
|
|
41
|
+
from .client.ionq import IonQClient
|
|
42
|
+
from .client.iqm import IQMClient
|
|
43
|
+
from .client.qulacs import QulacsClient
|
|
44
|
+
from .client.rigetti import RigettiClient
|
|
45
|
+
from .minimize.base import MinimizeProtocol, MinimizeResult
|
|
46
|
+
from .minimize.no_op import NoOpMinimize, NoOpMinimizeResult
|
|
47
|
+
from .minimize.scipy import ScipyMinimize, ScipyMinimizeOptions, ScipyMinimizeResult
|
|
48
|
+
from .sampler.base import IsingSeqFreqList, SamplerProtocol, SamplingDurations
|
|
49
|
+
from .sampler.braket import BraketJobMeta
|
|
50
|
+
from .sampler.qiskit import AerDeviceType, QiskitJobMeta
|
|
51
|
+
from .sampler.qulacs import QulacsJobMeta
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"QAOA",
|
|
55
|
+
"RQAOA",
|
|
56
|
+
"AQTClient",
|
|
57
|
+
"AerClient",
|
|
58
|
+
"AerDeviceType",
|
|
59
|
+
"BraketJobMeta",
|
|
60
|
+
"BraketSimulatorClient",
|
|
61
|
+
"IBMClient",
|
|
62
|
+
"IQMClient",
|
|
63
|
+
"IonQClient",
|
|
64
|
+
"IsingSeqFreqList",
|
|
65
|
+
"MinimizeProtocol",
|
|
66
|
+
"MinimizeResult",
|
|
67
|
+
"NoOpMinimize",
|
|
68
|
+
"NoOpMinimizeResult",
|
|
69
|
+
"NormalElimination",
|
|
70
|
+
"QAOADurations",
|
|
71
|
+
"QAOAHistoryItem",
|
|
72
|
+
"QAOAResult",
|
|
73
|
+
"QAOAType",
|
|
74
|
+
"QiskitCircuit",
|
|
75
|
+
"QiskitJobMeta",
|
|
76
|
+
"QuantumAlgoProtocol",
|
|
77
|
+
"QuantumBaseClient",
|
|
78
|
+
"QulacsCircuit",
|
|
79
|
+
"QulacsClient",
|
|
80
|
+
"QulacsJobMeta",
|
|
81
|
+
"RQAOADurations",
|
|
82
|
+
"RQAOAHistoryItem",
|
|
83
|
+
"RQAOAResult",
|
|
84
|
+
"RQAOAType",
|
|
85
|
+
"RigettiClient",
|
|
86
|
+
"SamplerProtocol",
|
|
87
|
+
"SamplingDurations",
|
|
88
|
+
"ScipyMinimize",
|
|
89
|
+
"ScipyMinimizeOptions",
|
|
90
|
+
"ScipyMinimizeResult",
|
|
91
|
+
"UnintentionalElimination",
|
|
92
|
+
]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# file generated by vcs-versioning
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
"__version__",
|
|
7
|
+
"__version_tuple__",
|
|
8
|
+
"version",
|
|
9
|
+
"version_tuple",
|
|
10
|
+
"__commit_id__",
|
|
11
|
+
"commit_id",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
version: str
|
|
15
|
+
__version__: str
|
|
16
|
+
__version_tuple__: tuple[int | str, ...]
|
|
17
|
+
version_tuple: tuple[int | str, ...]
|
|
18
|
+
commit_id: str | None
|
|
19
|
+
__commit_id__: str | None
|
|
20
|
+
|
|
21
|
+
__version__ = version = '1.0.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
|
23
|
+
|
|
24
|
+
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Copyright (c) Fixstars Corporation and Fixstars Amplify Corporation.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the Apache2.0 license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Final, Literal, Protocol, overload
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
|
|
13
|
+
from amplify import AcceptableDegrees, CustomClientResultProtocol, Model
|
|
14
|
+
|
|
15
|
+
from amplify_quantum.sampler.base import SamplerProtocol, SamplingMeta_co
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class QuantumAlgoProtocol(Protocol):
|
|
19
|
+
"""Protocol for quantum optimization algorithms.
|
|
20
|
+
|
|
21
|
+
Implement this protocol to define a custom algorithm compatible with any
|
|
22
|
+
:class:`~amplify.QuantumBaseClient` subclass. A conforming class
|
|
23
|
+
must declare :attr:`acceptable_degrees`, a :attr:`Parameters` class, and a
|
|
24
|
+
static :meth:`run` method.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
Parameters: Final[type]
|
|
28
|
+
"""Configuration class for the algorithm. Instances are passed to :meth:`run`
|
|
29
|
+
and to :attr:`acceptable_degrees` when it is a callable."""
|
|
30
|
+
|
|
31
|
+
acceptable_degrees: Final[
|
|
32
|
+
AcceptableDegrees
|
|
33
|
+
| Callable[
|
|
34
|
+
[
|
|
35
|
+
Any # QuantumAlgoProtocol.Parameters
|
|
36
|
+
],
|
|
37
|
+
AcceptableDegrees,
|
|
38
|
+
]
|
|
39
|
+
]
|
|
40
|
+
"""Polynomial degrees accepted by the algorithm's objective function.
|
|
41
|
+
Either a static :class:`~amplify.AcceptableDegrees` value, or a callable
|
|
42
|
+
that takes a :attr:`Parameters` instance and returns one."""
|
|
43
|
+
|
|
44
|
+
@overload
|
|
45
|
+
@staticmethod
|
|
46
|
+
def run(
|
|
47
|
+
sampler: SamplerProtocol[SamplingMeta_co, Any],
|
|
48
|
+
model: Model,
|
|
49
|
+
parameters: Any, # noqa: ANN401
|
|
50
|
+
dry_run: Literal[False],
|
|
51
|
+
) -> CustomClientResultProtocol: ...
|
|
52
|
+
@overload
|
|
53
|
+
@staticmethod
|
|
54
|
+
def run(
|
|
55
|
+
sampler: SamplerProtocol[SamplingMeta_co, Any],
|
|
56
|
+
model: Model,
|
|
57
|
+
parameters: Any, # noqa: ANN401
|
|
58
|
+
dry_run: Literal[True],
|
|
59
|
+
) -> None: ...
|
|
60
|
+
|
|
61
|
+
@staticmethod
|
|
62
|
+
def run(
|
|
63
|
+
sampler: SamplerProtocol[SamplingMeta_co, Any], model: Model, parameters: Any, dry_run: bool = False
|
|
64
|
+
) -> CustomClientResultProtocol | None:
|
|
65
|
+
"""Run the algorithm on the given model.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
sampler (SamplerProtocol[SamplingMeta_co, Any]): Backend sampler that executes quantum circuits.
|
|
69
|
+
model (Model): The optimization model to solve.
|
|
70
|
+
parameters (Any): Algorithm-specific configuration (e.g., :class:`QAOA.Parameters`).
|
|
71
|
+
dry_run (bool): If ``True``, validate inputs without executing circuits and return ``None``.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
CustomClientResultProtocol | None: The algorithm result, or ``None`` if *dry_run* is ``True``.
|
|
75
|
+
"""
|
|
76
|
+
...
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright (c) Fixstars Corporation and Fixstars Amplify Corporation.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the Apache2.0 license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from .components import (
|
|
8
|
+
CostPhaseSeparator,
|
|
9
|
+
IdentityPhaseSeparator,
|
|
10
|
+
UnconstrainedQAOAProblem,
|
|
11
|
+
UniformSuperpositionInitialState,
|
|
12
|
+
XMixer,
|
|
13
|
+
)
|
|
14
|
+
from .interface import QAOA, QAOAType
|
|
15
|
+
from .type import (
|
|
16
|
+
InitialStateProtocol,
|
|
17
|
+
MixerProtocol,
|
|
18
|
+
PhaseSeparatorProtocol,
|
|
19
|
+
QAOADurations,
|
|
20
|
+
QAOAHistoryItem,
|
|
21
|
+
QAOAImplProtocol,
|
|
22
|
+
QAOAProblemProtocol,
|
|
23
|
+
QAOAResult,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"QAOA",
|
|
28
|
+
"CostPhaseSeparator",
|
|
29
|
+
"IdentityPhaseSeparator",
|
|
30
|
+
"InitialStateProtocol",
|
|
31
|
+
"MixerProtocol",
|
|
32
|
+
"PhaseSeparatorProtocol",
|
|
33
|
+
"QAOADurations",
|
|
34
|
+
"QAOAHistoryItem",
|
|
35
|
+
"QAOAImplProtocol",
|
|
36
|
+
"QAOAProblemProtocol",
|
|
37
|
+
"QAOAResult",
|
|
38
|
+
"QAOAType",
|
|
39
|
+
"UnconstrainedQAOAProblem",
|
|
40
|
+
"UniformSuperpositionInitialState",
|
|
41
|
+
"XMixer",
|
|
42
|
+
]
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
# Copyright (c) Fixstars Corporation and Fixstars Amplify Corporation.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the Apache2.0 license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from itertools import islice
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from amplify import Degree, Matrix, Model, VariableType
|
|
12
|
+
|
|
13
|
+
from amplify_quantum.algo.utility import (
|
|
14
|
+
gather_disjoint_n_hot_greedy,
|
|
15
|
+
is_satisfied_group_constraints,
|
|
16
|
+
raise_subclass_error,
|
|
17
|
+
raise_type_error,
|
|
18
|
+
validate_poly,
|
|
19
|
+
)
|
|
20
|
+
from amplify_quantum.circuit.base import SupportsAnsatz, SupportsAnsatzObservable, SupportsCAnsatz, SupportsHam
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from collections.abc import Iterable, Iterator, Sequence
|
|
24
|
+
|
|
25
|
+
from amplify import Poly
|
|
26
|
+
|
|
27
|
+
from amplify_quantum.algo.qaoa.type import QAOAProblemProtocol
|
|
28
|
+
from amplify_quantum.circuit.base import CircuitProtocol
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _generate_x_observable(
|
|
32
|
+
obs_class: type[SupportsAnsatzObservable], num_qubits: int, active_qubits: Iterable[int]
|
|
33
|
+
) -> SupportsAnsatzObservable:
|
|
34
|
+
observable = obs_class(num_qubits)
|
|
35
|
+
for qubit in active_qubits:
|
|
36
|
+
observable.add_pauli_x(num_qubits, qubit, 1.0)
|
|
37
|
+
return observable
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _apply_x_rotation_to_qubits(circuit: SupportsAnsatz, num_qubits: int, qubits: Iterable[int], angle: float) -> None:
|
|
41
|
+
qubit_list = list(qubits)
|
|
42
|
+
if not qubit_list:
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
observable_class = type(circuit).get_observable_class()
|
|
46
|
+
observable = _generate_x_observable(observable_class, num_qubits, qubit_list)
|
|
47
|
+
circuit.add_observable_rotation_gate(observable, angle, num_qubits)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _generate_hamiltonian(obs_class: type[SupportsHam], ising_poly: Poly, num_qubits: int) -> SupportsHam:
|
|
51
|
+
hamiltonian = obs_class(num_qubits)
|
|
52
|
+
for key, value in ising_poly:
|
|
53
|
+
if key:
|
|
54
|
+
hamiltonian.add_pauli_z(num_qubits, (i.id for i in key), value)
|
|
55
|
+
return hamiltonian
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class UniformSuperpositionInitialState:
|
|
59
|
+
"""Initial state that places all active qubits in an equal superposition.
|
|
60
|
+
|
|
61
|
+
Applies a Hadamard gate to each qubit listed in ``active_qubits``.
|
|
62
|
+
Requires no variational parameters.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def num_parameters(self, problem: QAOAProblemProtocol) -> int:
|
|
66
|
+
"""Return 0 — this component takes no variational parameters."""
|
|
67
|
+
_ = problem
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
def apply(self, circuit: CircuitProtocol, problem: QAOAProblemProtocol, parameters: Iterator[float]) -> None:
|
|
71
|
+
"""Apply a Hadamard gate to each active qubit.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
circuit (CircuitProtocol): The circuit to modify in place.
|
|
75
|
+
problem (QAOAProblemProtocol): Provides ``active_qubits``.
|
|
76
|
+
parameters (Iterator[float]): Unused; no parameters are consumed.
|
|
77
|
+
"""
|
|
78
|
+
_ = parameters
|
|
79
|
+
if not isinstance(circuit, SupportsAnsatz):
|
|
80
|
+
raise_type_error("'circuit'", "SupportsAnsatz", circuit)
|
|
81
|
+
|
|
82
|
+
for qubit in problem.active_qubits:
|
|
83
|
+
circuit.add_h_gate(qubit)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CostPhaseSeparator:
|
|
87
|
+
"""Phase separator that applies a rotation generated by the cost Hamiltonian.
|
|
88
|
+
|
|
89
|
+
Consumes one variational parameter per layer (the phase angle *gamma*).
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def num_parameters_per_layer(self, problem: QAOAProblemProtocol, layer_index: int) -> int:
|
|
93
|
+
"""Return 1 — one phase angle per layer."""
|
|
94
|
+
_ = problem
|
|
95
|
+
_ = layer_index
|
|
96
|
+
return 1
|
|
97
|
+
|
|
98
|
+
def apply_layer(
|
|
99
|
+
self, circuit: CircuitProtocol, problem: QAOAProblemProtocol, parameters: Iterator[float], layer_index: int
|
|
100
|
+
) -> None:
|
|
101
|
+
"""Apply a cost Hamiltonian rotation gate to *circuit*.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
circuit (CircuitProtocol): The circuit to modify in place.
|
|
105
|
+
problem (QAOAProblemProtocol): Provides the Ising polynomial for the cost Hamiltonian.
|
|
106
|
+
parameters (Iterator[float]): Supplies the phase angle *gamma*.
|
|
107
|
+
layer_index (int): Zero-based index of the current QAOA layer (unused).
|
|
108
|
+
"""
|
|
109
|
+
_ = layer_index
|
|
110
|
+
p = next(parameters, None)
|
|
111
|
+
if p is None:
|
|
112
|
+
raise ValueError("CostPhaseSeparator expects exactly one parameter per layer.")
|
|
113
|
+
|
|
114
|
+
if not isinstance(circuit, SupportsAnsatz):
|
|
115
|
+
raise_type_error("'circuit'", "SupportsAnsatz", circuit)
|
|
116
|
+
|
|
117
|
+
observable_class = type(circuit).get_observable_class()
|
|
118
|
+
|
|
119
|
+
if not issubclass(observable_class, SupportsHam): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
120
|
+
raise_subclass_error("observable class of 'circuit'", "SupportsHam", observable_class)
|
|
121
|
+
if not issubclass(observable_class, SupportsAnsatzObservable): # pyright: ignore[reportUnnecessaryIsInstance]
|
|
122
|
+
raise_subclass_error("observable class of 'circuit'", "SupportsAnsatzObservable", observable_class)
|
|
123
|
+
|
|
124
|
+
hamiltonian = _generate_hamiltonian(observable_class, problem.ising_poly, problem.num_qubits)
|
|
125
|
+
|
|
126
|
+
if not isinstance(hamiltonian, SupportsAnsatzObservable):
|
|
127
|
+
raise_type_error("observable class of 'circuit'", "SupportsAnsatzObservable", circuit)
|
|
128
|
+
|
|
129
|
+
circuit.add_observable_rotation_gate(hamiltonian, p, problem.num_qubits)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class IdentityPhaseSeparator:
|
|
133
|
+
"""Phase separator that applies no gates (identity operation).
|
|
134
|
+
|
|
135
|
+
Used when the phase-separation step is handled externally or should be skipped.
|
|
136
|
+
Requires no variational parameters.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def num_parameters_per_layer(self, problem: QAOAProblemProtocol, layer_index: int) -> int:
|
|
140
|
+
"""Return 0 — this component takes no variational parameters."""
|
|
141
|
+
_ = problem
|
|
142
|
+
_ = layer_index
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
def apply_layer(
|
|
146
|
+
self, circuit: CircuitProtocol, problem: QAOAProblemProtocol, parameters: Iterator[float], layer_index: int
|
|
147
|
+
) -> None:
|
|
148
|
+
"""Do nothing — identity operation.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
circuit (CircuitProtocol): Unused.
|
|
152
|
+
problem (QAOAProblemProtocol): Unused.
|
|
153
|
+
parameters (Iterator[float]): Unused; no parameters are consumed.
|
|
154
|
+
layer_index (int): Unused.
|
|
155
|
+
"""
|
|
156
|
+
_ = circuit
|
|
157
|
+
_ = problem
|
|
158
|
+
_ = layer_index
|
|
159
|
+
_ = parameters
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class XMixer:
|
|
163
|
+
"""Mixer that applies a transverse-field (X) rotation to all active qubits.
|
|
164
|
+
|
|
165
|
+
Consumes one variational parameter per layer (the mixing angle *beta*).
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
def num_parameters_per_layer(self, problem: QAOAProblemProtocol, layer_index: int) -> int:
|
|
169
|
+
"""Return 1 — one mixing angle per layer."""
|
|
170
|
+
_ = problem
|
|
171
|
+
_ = layer_index
|
|
172
|
+
return 1
|
|
173
|
+
|
|
174
|
+
def apply_layer(
|
|
175
|
+
self, circuit: CircuitProtocol, problem: QAOAProblemProtocol, parameters: Iterator[float], layer_index: int
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Apply an X-rotation to each active qubit.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
circuit (CircuitProtocol): The circuit to modify in place.
|
|
181
|
+
problem (QAOAProblemProtocol): Provides ``active_qubits``.
|
|
182
|
+
parameters (Iterator[float]): Supplies the mixing angle *beta*.
|
|
183
|
+
layer_index (int): Zero-based index of the current QAOA layer (unused).
|
|
184
|
+
"""
|
|
185
|
+
_ = layer_index
|
|
186
|
+
|
|
187
|
+
p = next(parameters, None)
|
|
188
|
+
if p is None:
|
|
189
|
+
raise ValueError("XMixer expects exactly one parameter per layer.")
|
|
190
|
+
|
|
191
|
+
if not isinstance(circuit, SupportsAnsatz):
|
|
192
|
+
raise_type_error("'circuit'", "SupportsAnsatz", circuit)
|
|
193
|
+
|
|
194
|
+
_apply_x_rotation_to_qubits(circuit, problem.num_qubits, problem.active_qubits, p)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class UnconstrainedQAOAProblem:
|
|
198
|
+
"""Problem representation for standard (unconstrained) QAOA.
|
|
199
|
+
|
|
200
|
+
Converts an :class:`~amplify.Model` into an Ising polynomial and exposes
|
|
201
|
+
the qubit indices and objective evaluation needed by the QAOA circuit
|
|
202
|
+
construction. Used internally by the standard QAOA implementation.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def __init__(self, model: Model) -> None:
|
|
206
|
+
"""Initialize from an optimization model.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
model (Model): The optimization model to solve. Constraints are
|
|
210
|
+
dropped; only the objective is converted to an Ising polynomial.
|
|
211
|
+
"""
|
|
212
|
+
self.ising_poly = model.to_unconstrained_poly()
|
|
213
|
+
self.active_qubits = frozenset(i.id for i in self.ising_poly.variables)
|
|
214
|
+
self.num_qubits = max(self.active_qubits) + 1 if len(self.active_qubits) > 0 else 0
|
|
215
|
+
|
|
216
|
+
validate_poly(self.ising_poly, {VariableType.Ising: Degree.HighOrder})
|
|
217
|
+
|
|
218
|
+
def compute_objective_value(self, sol: Sequence[int]) -> float | None:
|
|
219
|
+
"""Evaluate the Ising cost function at the given spin assignment.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
sol (Sequence[int]): Spin values (``+1`` or ``-1``) indexed by qubit id.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
float | None: The objective value.
|
|
226
|
+
"""
|
|
227
|
+
val = self.ising_poly.substitute({i: sol[i.id] for i in self.ising_poly.variables})
|
|
228
|
+
if not val.is_number():
|
|
229
|
+
raise RuntimeError(f"Expected the substituted value to be a number, but got {val}.")
|
|
230
|
+
return float(val)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class NHOTQAOAProblem:
|
|
234
|
+
"""Problem representation for equality-constrained QAOA.
|
|
235
|
+
|
|
236
|
+
Converts an :class:`~amplify.Model` into an Ising polynomial while
|
|
237
|
+
separating disjoint n-hot (equality) constraints from the objective.
|
|
238
|
+
Constraints not recognized as n-hot are added as penalty terms.
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(self, model: Model) -> None:
|
|
242
|
+
"""Initialize from an optimization model.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
model (Model): The optimization model to solve. Disjoint n-hot
|
|
246
|
+
constraints are separated; unrecognized constraints are added
|
|
247
|
+
as penalty terms.
|
|
248
|
+
"""
|
|
249
|
+
group_list, unused_constraint = gather_disjoint_n_hot_greedy(model.constraints)
|
|
250
|
+
# objective function
|
|
251
|
+
poly = model.objective
|
|
252
|
+
if isinstance(poly, Matrix):
|
|
253
|
+
poly = poly.to_poly()
|
|
254
|
+
# penalty terms for constraints that are not in the shape of n-hot
|
|
255
|
+
for c in unused_constraint:
|
|
256
|
+
poly += c.penalty
|
|
257
|
+
|
|
258
|
+
self.ising_poly = poly
|
|
259
|
+
self.n_hot_constraints = group_list
|
|
260
|
+
|
|
261
|
+
self.constrained_indices = frozenset(i.id for group, _ in group_list for i in group)
|
|
262
|
+
self.unconstrained_indices = frozenset(
|
|
263
|
+
i.id for i in self.ising_poly.variables if i.id not in self.constrained_indices
|
|
264
|
+
)
|
|
265
|
+
self.active_qubits = self.unconstrained_indices | self.constrained_indices
|
|
266
|
+
self.num_qubits = max(
|
|
267
|
+
max(self.unconstrained_indices) + 1 if len(self.unconstrained_indices) > 0 else 0,
|
|
268
|
+
max(self.constrained_indices) + 1 if len(self.constrained_indices) > 0 else 0,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
# TODO: Poly から変数の種類ごと (Ising, Binary など) の degree を取れるようになったら # noqa: FIX002
|
|
272
|
+
# ここではなく algo.qaoa._details._generate_hamiltonian で validate するようにする
|
|
273
|
+
validate_poly(self.ising_poly, {VariableType.Ising: Degree.HighOrder})
|
|
274
|
+
|
|
275
|
+
def compute_objective_value(self, sol: Sequence[int]) -> float | None:
|
|
276
|
+
"""Evaluate the Ising cost function at the given spin assignment.
|
|
277
|
+
|
|
278
|
+
Returns ``None`` if the assignment violates any n-hot constraint.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
sol (Sequence[int]): Spin values (``+1`` or ``-1``) indexed by qubit id.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
float | None: The objective value, or ``None`` if the solution is infeasible.
|
|
285
|
+
"""
|
|
286
|
+
if not is_satisfied_group_constraints(sol, self.n_hot_constraints):
|
|
287
|
+
return None
|
|
288
|
+
val = self.ising_poly.substitute({i: sol[i.id] for i in self.ising_poly.variables})
|
|
289
|
+
if not val.is_number():
|
|
290
|
+
raise RuntimeError(f"Expected the substituted value to be a number, but got {val}.")
|
|
291
|
+
return float(val)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class FeasibleInitialState:
|
|
295
|
+
"""Initial state that prepares a feasible state satisfying n-hot constraints.
|
|
296
|
+
|
|
297
|
+
Applies X gates to place constrained qubits into a feasible configuration
|
|
298
|
+
and Rx rotations to unconstrained qubits.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
def num_parameters(self, problem: NHOTQAOAProblem) -> int:
|
|
302
|
+
"""Return the number of variational parameters (one per unconstrained qubit)."""
|
|
303
|
+
return len(problem.unconstrained_indices)
|
|
304
|
+
|
|
305
|
+
def apply(self, circuit: CircuitProtocol, problem: NHOTQAOAProblem, parameters: Iterator[float]) -> None:
|
|
306
|
+
"""Prepare a feasible initial state.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
circuit (CircuitProtocol): The circuit to modify in place.
|
|
310
|
+
problem (NHOTQAOAProblem): Provides n-hot constraints and unconstrained qubit indices.
|
|
311
|
+
parameters (Iterator[float]): Supplies Rx rotation angles for unconstrained qubits.
|
|
312
|
+
"""
|
|
313
|
+
num_parameters = self.num_parameters(problem)
|
|
314
|
+
ps = list(islice(parameters, num_parameters))
|
|
315
|
+
if len(ps) != num_parameters:
|
|
316
|
+
raise ValueError(f"FeasibleInitialState expects exactly {num_parameters} parameters per layer.")
|
|
317
|
+
|
|
318
|
+
if not isinstance(circuit, SupportsCAnsatz):
|
|
319
|
+
raise_type_error("'circuit'", "SupportsCAnsatz", circuit)
|
|
320
|
+
|
|
321
|
+
for group, num_flips in problem.n_hot_constraints:
|
|
322
|
+
for i in range(num_flips):
|
|
323
|
+
circuit.add_x_gate(group[i].id)
|
|
324
|
+
|
|
325
|
+
for param_index, i in enumerate(problem.unconstrained_indices):
|
|
326
|
+
circuit.add_rx_gate(i, ps[param_index])
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def _apply_xy_rotation(circuit: SupportsCAnsatz, first: int, second: int, angle: float) -> None:
|
|
330
|
+
circuit.add_cnot_gate(first, second)
|
|
331
|
+
circuit.add_ry_gate(first, angle)
|
|
332
|
+
circuit.add_cnot_gate(second, first)
|
|
333
|
+
circuit.add_ry_gate(first, -angle)
|
|
334
|
+
circuit.add_cnot_gate(first, second)
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
class ConstraintPreservingMixer:
|
|
338
|
+
"""Mixer that preserves n-hot constraints using XY-rotation gates.
|
|
339
|
+
|
|
340
|
+
Applies pairwise XY rotations within each constraint group so that
|
|
341
|
+
the quantum state remains in the feasible subspace throughout the circuit.
|
|
342
|
+
"""
|
|
343
|
+
|
|
344
|
+
def num_parameters_per_layer(self, problem: NHOTQAOAProblem, layer_index: int) -> int:
|
|
345
|
+
"""Return the number of variational parameters per layer."""
|
|
346
|
+
_ = layer_index
|
|
347
|
+
num_parameters = 0
|
|
348
|
+
for group, _ in problem.n_hot_constraints:
|
|
349
|
+
m = len(group)
|
|
350
|
+
num_parameters += m // 2 + (m - 1) // 2
|
|
351
|
+
return num_parameters
|
|
352
|
+
|
|
353
|
+
def apply_layer(
|
|
354
|
+
self, circuit: CircuitProtocol, problem: NHOTQAOAProblem, parameters: Iterator[float], layer_index: int
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Apply pairwise XY rotations within each constraint group.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
circuit (CircuitProtocol): The circuit to modify in place.
|
|
360
|
+
problem (NHOTQAOAProblem): Provides n-hot constraint groups.
|
|
361
|
+
parameters (Iterator[float]): Supplies rotation angles for XY gates.
|
|
362
|
+
layer_index (int): Zero-based index of the current QAOA layer (unused).
|
|
363
|
+
"""
|
|
364
|
+
_ = layer_index
|
|
365
|
+
num_parameters = self.num_parameters_per_layer(problem, layer_index)
|
|
366
|
+
ps = list(islice(parameters, num_parameters))
|
|
367
|
+
if len(ps) != num_parameters:
|
|
368
|
+
raise ValueError(f"ConstraintPreservingMixer expects exactly {num_parameters} parameters per layer.")
|
|
369
|
+
|
|
370
|
+
if not isinstance(circuit, SupportsCAnsatz):
|
|
371
|
+
raise_type_error("'circuit'", "SupportsCAnsatz", circuit)
|
|
372
|
+
|
|
373
|
+
param_iter = iter(ps)
|
|
374
|
+
|
|
375
|
+
for group, _ in problem.n_hot_constraints:
|
|
376
|
+
m = len(group)
|
|
377
|
+
for index in range(0, m - 1, 2):
|
|
378
|
+
_apply_xy_rotation(circuit, group[index].id, group[index + 1].id, next(param_iter))
|
|
379
|
+
for index in range(1, m - 1, 2):
|
|
380
|
+
_apply_xy_rotation(circuit, group[index].id, group[index + 1].id, next(param_iter))
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if TYPE_CHECKING:
|
|
384
|
+
from amplify_quantum.algo.qaoa.type import (
|
|
385
|
+
InitialStateProtocol,
|
|
386
|
+
MixerProtocol,
|
|
387
|
+
PhaseSeparatorProtocol,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
_0: type[InitialStateProtocol] = UniformSuperpositionInitialState
|
|
391
|
+
_0 = FeasibleInitialState # pyright: ignore[reportConstantRedefinition]
|
|
392
|
+
_1: type[PhaseSeparatorProtocol] = CostPhaseSeparator
|
|
393
|
+
_1 = IdentityPhaseSeparator # pyright: ignore[reportConstantRedefinition]
|
|
394
|
+
_2: type[MixerProtocol] = XMixer
|
|
395
|
+
_2 = ConstraintPreservingMixer # pyright: ignore[reportConstantRedefinition]
|
|
396
|
+
_3: type[QAOAProblemProtocol] = UnconstrainedQAOAProblem
|
|
397
|
+
_3 = NHOTQAOAProblem # pyright: ignore[reportConstantRedefinition]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Copyright (c) Fixstars Corporation and Fixstars Amplify Corporation.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the Apache2.0 license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
from .qaoa_auto import AutoQAOAImpl
|
|
7
|
+
from .qaoa_n_hot import NHOTQAOAImpl
|
|
8
|
+
from .qaoa_original import OriginalQAOAImpl
|
|
9
|
+
|
|
10
|
+
__all__ = ["AutoQAOAImpl", "NHOTQAOAImpl", "OriginalQAOAImpl"]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Copyright (c) Fixstars Corporation and Fixstars Amplify Corporation.
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the Apache2.0 license found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from amplify_quantum.algo.utility import gather_disjoint_n_hot_greedy
|
|
11
|
+
|
|
12
|
+
from .qaoa_n_hot import NHOTQAOAImpl
|
|
13
|
+
from .qaoa_original import OriginalQAOAImpl
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from amplify import Model
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AutoQAOAImpl:
|
|
20
|
+
"""QAOA implementation that automatically selects the best strategy.
|
|
21
|
+
|
|
22
|
+
Returns :class:`NHOTQAOAImpl` when the model contains
|
|
23
|
+
disjoint n-hot constraints, otherwise :class:`OriginalQAOAImpl`.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __new__(cls, model: Model, reps: int) -> NHOTQAOAImpl | OriginalQAOAImpl:
|
|
27
|
+
"""Select and instantiate the best QAOA implementation for the given model.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
model (Model): The optimization model to solve.
|
|
31
|
+
reps (int): Number of QAOA layers (circuit depth *p*).
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
NHOTQAOAImpl | OriginalQAOAImpl: The selected implementation instance.
|
|
35
|
+
"""
|
|
36
|
+
group_list, _ = gather_disjoint_n_hot_greedy(model.constraints)
|
|
37
|
+
|
|
38
|
+
if len(group_list) > 0:
|
|
39
|
+
return NHOTQAOAImpl(model, reps)
|
|
40
|
+
return OriginalQAOAImpl(model, reps)
|