classiq 0.76.0__py3-none-any.whl → 0.78.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.
- classiq/applications/chemistry/chemistry_model_constructor.py +7 -6
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +9 -1
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -3
- classiq/applications/iqae/__init__.py +0 -0
- classiq/applications/iqae/iqae.py +207 -0
- classiq/execution/__init__.py +1 -1
- classiq/interface/_version.py +1 -1
- classiq/interface/applications/iqae/__init__.py +0 -0
- classiq/interface/applications/iqae/generic_iqae.py +222 -0
- classiq/interface/applications/iqae/iqae_result.py +45 -0
- classiq/interface/debug_info/debug_info.py +3 -0
- classiq/interface/executor/execution_result.py +1 -1
- classiq/interface/executor/user_budget.py +1 -1
- classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +9 -3
- classiq/interface/generator/expressions/proxies/quantum/qmod_qarray_proxy.py +6 -15
- classiq/interface/generator/expressions/proxies/quantum/qmod_qscalar_proxy.py +14 -5
- classiq/interface/generator/expressions/proxies/quantum/qmod_sized_proxy.py +5 -3
- classiq/interface/generator/generated_circuit_data.py +18 -7
- classiq/interface/ide/visual_model.py +2 -0
- classiq/interface/model/handle_binding.py +8 -0
- classiq/interface/model/model.py +3 -6
- classiq/interface/model/quantum_function_call.py +31 -1
- classiq/interface/model/quantum_statement.py +14 -1
- classiq/interface/source_reference.py +7 -2
- classiq/model_expansions/capturing/captured_vars.py +16 -6
- classiq/model_expansions/closure.py +1 -58
- classiq/model_expansions/evaluators/arg_type_match.py +2 -2
- classiq/model_expansions/evaluators/argument_types.py +4 -5
- classiq/model_expansions/evaluators/classical_expression.py +9 -9
- classiq/model_expansions/evaluators/parameter_types.py +19 -11
- classiq/model_expansions/expression_evaluator.py +20 -11
- classiq/model_expansions/generative_functions.py +1 -1
- classiq/model_expansions/interpreters/base_interpreter.py +27 -15
- classiq/model_expansions/interpreters/frontend_generative_interpreter.py +0 -16
- classiq/model_expansions/interpreters/generative_interpreter.py +4 -4
- classiq/model_expansions/quantum_operations/allocate.py +2 -2
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +3 -1
- classiq/model_expansions/quantum_operations/call_emitter.py +91 -42
- classiq/model_expansions/quantum_operations/emitter.py +7 -7
- classiq/model_expansions/quantum_operations/function_calls_cache.py +84 -0
- classiq/model_expansions/scope.py +73 -13
- classiq/model_expansions/transformers/model_renamer.py +2 -2
- classiq/model_expansions/transformers/type_qualifier_inference.py +183 -0
- classiq/model_expansions/utils/text_utils.py +4 -2
- classiq/model_expansions/visitors/symbolic_param_inference.py +4 -15
- classiq/open_library/functions/lookup_table.py +1 -1
- classiq/open_library/functions/state_preparation.py +1 -1
- classiq/qmod/builtins/classical_execution_primitives.py +1 -1
- classiq/qmod/create_model_function.py +21 -3
- classiq/qmod/global_declarative_switch.py +19 -0
- classiq/qmod/native/pretty_printer.py +4 -0
- classiq/qmod/pretty_print/pretty_printer.py +4 -0
- classiq/qmod/qfunc.py +31 -23
- classiq/qmod/qmod_variable.py +7 -4
- classiq/qmod/quantum_expandable.py +29 -1
- classiq/qmod/quantum_function.py +26 -19
- classiq/qmod/utilities.py +4 -0
- classiq/qmod/write_qmod.py +36 -10
- classiq/synthesis.py +7 -6
- {classiq-0.76.0.dist-info → classiq-0.78.0.dist-info}/METADATA +1 -1
- {classiq-0.76.0.dist-info → classiq-0.78.0.dist-info}/RECORD +62 -55
- classiq/interface/executor/iqae_result.py +0 -17
- {classiq-0.76.0.dist-info → classiq-0.78.0.dist-info}/WHEEL +0 -0
@@ -53,6 +53,7 @@ from classiq.qmod.builtins.structs import (
|
|
53
53
|
MoleculeProblem as QmodMoleculeProblem,
|
54
54
|
Position as QmodPosition,
|
55
55
|
)
|
56
|
+
from classiq.qmod.global_declarative_switch import set_global_declarative_switch
|
56
57
|
from classiq.qmod.utilities import qmod_val_to_expr_str
|
57
58
|
|
58
59
|
# isort: split
|
@@ -60,6 +61,11 @@ from classiq.qmod.utilities import qmod_val_to_expr_str
|
|
60
61
|
# This import causes a circular import if done earlier. We use isort: split to avoid it
|
61
62
|
from classiq.open_library.functions.hea import full_hea
|
62
63
|
|
64
|
+
with set_global_declarative_switch():
|
65
|
+
_FULL_HEA = cast(
|
66
|
+
NativeFunctionDefinition, full_hea.create_model().function_dict["full_hea"]
|
67
|
+
)
|
68
|
+
|
63
69
|
_LADDER_OPERATOR_TYPE_INDICATOR_TO_QMOD_MAPPING: dict[str, str] = {
|
64
70
|
"+": "PLUS",
|
65
71
|
"-": "MINUS",
|
@@ -506,12 +512,7 @@ def construct_chemistry_model(
|
|
506
512
|
)
|
507
513
|
]
|
508
514
|
if isinstance(ansatz_parameters, HEAParameters):
|
509
|
-
chemistry_functions.append(
|
510
|
-
cast(
|
511
|
-
NativeFunctionDefinition,
|
512
|
-
full_hea.create_model().function_dict["full_hea"],
|
513
|
-
)
|
514
|
-
)
|
515
|
+
chemistry_functions.append(_FULL_HEA)
|
515
516
|
model = Model(
|
516
517
|
functions=chemistry_functions,
|
517
518
|
classical_execution_code=_get_chemistry_classical_code(
|
classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py
CHANGED
@@ -36,6 +36,14 @@ from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import
|
|
36
36
|
)
|
37
37
|
from classiq.applications.combinatorial_optimization import OptimizerConfig, QAOAConfig
|
38
38
|
from classiq.open_library.functions.qaoa_penalty import qaoa_penalty
|
39
|
+
from classiq.qmod.global_declarative_switch import set_global_declarative_switch
|
40
|
+
|
41
|
+
with set_global_declarative_switch():
|
42
|
+
_LIBRARY_FUNCTIONS = [
|
43
|
+
f.model_dump()
|
44
|
+
for f in qaoa_penalty.create_model().functions
|
45
|
+
if f.name != "main"
|
46
|
+
]
|
39
47
|
|
40
48
|
|
41
49
|
def construct_combi_opt_py_model(
|
@@ -107,7 +115,7 @@ def construct_combi_opt_py_model(
|
|
107
115
|
),
|
108
116
|
],
|
109
117
|
),
|
110
|
-
*
|
118
|
+
*_LIBRARY_FUNCTIONS,
|
111
119
|
],
|
112
120
|
classical_execution_code=f"""
|
113
121
|
vqe_result = vqe(
|
@@ -24,6 +24,7 @@ from classiq.open_library.functions.utility_functions import (
|
|
24
24
|
from classiq.qmod.builtins.functions import RX
|
25
25
|
from classiq.qmod.builtins.operations import allocate, phase, repeat
|
26
26
|
from classiq.qmod.cparam import CReal
|
27
|
+
from classiq.qmod.create_model_function import create_model
|
27
28
|
from classiq.qmod.qfunc import qfunc
|
28
29
|
from classiq.qmod.qmod_parameter import CArray
|
29
30
|
from classiq.qmod.qmod_variable import Output, QVar
|
@@ -80,9 +81,9 @@ class CombinatorialProblem:
|
|
80
81
|
],
|
81
82
|
)
|
82
83
|
|
83
|
-
self.model_ =
|
84
|
-
constraints=constraints, preferences=preferences
|
85
|
-
)
|
84
|
+
self.model_ = create_model(
|
85
|
+
main, constraints=constraints, preferences=preferences
|
86
|
+
) # type:ignore[assignment]
|
86
87
|
return self.model_ # type:ignore[return-value]
|
87
88
|
|
88
89
|
def get_qprog(self) -> QuantumProgram:
|
File without changes
|
@@ -0,0 +1,207 @@
|
|
1
|
+
from typing import Literal, Optional, cast
|
2
|
+
|
3
|
+
from classiq.interface.applications.iqae.generic_iqae import GenericIQAE
|
4
|
+
from classiq.interface.applications.iqae.iqae_result import (
|
5
|
+
IQAEIterationData,
|
6
|
+
IQAEResult,
|
7
|
+
)
|
8
|
+
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
9
|
+
from classiq.interface.generator.model import Constraints, Preferences
|
10
|
+
from classiq.interface.generator.quantum_program import QuantumProgram
|
11
|
+
from classiq.interface.model.model import SerializedModel
|
12
|
+
|
13
|
+
from classiq.execution import ExecutionSession
|
14
|
+
from classiq.open_library import amplitude_amplification
|
15
|
+
from classiq.qmod import (
|
16
|
+
CInt,
|
17
|
+
Output,
|
18
|
+
QArray,
|
19
|
+
QBit,
|
20
|
+
QCallable,
|
21
|
+
)
|
22
|
+
from classiq.qmod.builtins import Z, allocate, bind, within_apply
|
23
|
+
from classiq.qmod.create_model_function import create_model
|
24
|
+
from classiq.qmod.qfunc import qfunc
|
25
|
+
from classiq.synthesis import synthesize
|
26
|
+
|
27
|
+
|
28
|
+
class IQAE:
|
29
|
+
"""
|
30
|
+
Implementation of Iterative Quantum Amplitude Estimation [1].
|
31
|
+
Given $A$ s.t. $A|0>_n|0> = \\sqrt{1-a}|\\psi_0>_n|0> + \\sqrt{a}|\\psi_1>_n|1>$, the algorithm estimates
|
32
|
+
$a$ by iteratively sampling $Q^kA$, where $Q=AS_0A^{\\dagger}S_{\\psi_0}$, and $k$ is an integer variable.
|
33
|
+
|
34
|
+
For estimating $a$, The algorithm estimates $\\theta_a$ which is defined by $a = sin^2(\\theta_a)$, so it starts with a
|
35
|
+
confidence interval $(0, \\pi/2)$ and narrows down this interval on each iteration according to the sample results.
|
36
|
+
|
37
|
+
References:
|
38
|
+
[1]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019).
|
39
|
+
Iterative Quantum Amplitude Estimation.
|
40
|
+
`arXiv:1912.05559 <https://arxiv.org/abs/1912.05559>`.
|
41
|
+
"""
|
42
|
+
|
43
|
+
_NUM_SHOUTS = 2048
|
44
|
+
|
45
|
+
def __init__(
|
46
|
+
self,
|
47
|
+
state_prep_op: QCallable[QArray[QBit, Literal["problem_vars_size"]], QBit],
|
48
|
+
problem_vars_size: int,
|
49
|
+
constraints: Optional[Constraints] = None,
|
50
|
+
preferences: Optional[Preferences] = None,
|
51
|
+
) -> None:
|
52
|
+
self._state_prep_op = state_prep_op
|
53
|
+
self._problem_vars_size: int = problem_vars_size
|
54
|
+
self._constraints: Optional[Constraints] = constraints
|
55
|
+
self._preferences: Optional[Preferences] = preferences
|
56
|
+
self._model: Optional[SerializedModel] = None
|
57
|
+
self._qprog: Optional[QuantumProgram] = None
|
58
|
+
|
59
|
+
"""
|
60
|
+
Args:
|
61
|
+
state_prep_op (Qfunc): implementation of the operator $A$ in Qmod.
|
62
|
+
problem_vars_size (int): The size of the problem in terms of the number of qubits, e.g., $n$ of the first register A works on.
|
63
|
+
constraints (Constraints): Constraints for the synthesis of the model. See Constraints (Optional).
|
64
|
+
preferences (Preferences): Preferences for the synthesis of the model. See Preferences (Optional).
|
65
|
+
"""
|
66
|
+
|
67
|
+
def get_model(self) -> SerializedModel:
|
68
|
+
"""
|
69
|
+
Implement the quantum part of IQAE in terms of the Qmod Model
|
70
|
+
|
71
|
+
Args:
|
72
|
+
|
73
|
+
|
74
|
+
Returns:
|
75
|
+
SerializedModel (str): A serialized model.
|
76
|
+
|
77
|
+
"""
|
78
|
+
|
79
|
+
state_prep_op = self._state_prep_op
|
80
|
+
problem_vars_size = self._problem_vars_size
|
81
|
+
|
82
|
+
@qfunc
|
83
|
+
def space_transform(est_reg: QArray) -> None:
|
84
|
+
state_prep_op(est_reg[0 : est_reg.len - 1], est_reg[est_reg.len - 1])
|
85
|
+
|
86
|
+
@qfunc
|
87
|
+
def oracle(est_reg: QArray) -> None:
|
88
|
+
Z(est_reg[est_reg.len - 1])
|
89
|
+
|
90
|
+
@qfunc
|
91
|
+
def main(
|
92
|
+
k: CInt,
|
93
|
+
indicator: Output[QBit],
|
94
|
+
) -> None:
|
95
|
+
est_reg: QArray = QArray("est_reg")
|
96
|
+
problem_vars: QArray = QArray("problem_vars", length=problem_vars_size)
|
97
|
+
allocate(problem_vars)
|
98
|
+
allocate(indicator)
|
99
|
+
within_apply(
|
100
|
+
lambda: bind([problem_vars, indicator], est_reg),
|
101
|
+
lambda: amplitude_amplification(
|
102
|
+
k,
|
103
|
+
oracle,
|
104
|
+
space_transform,
|
105
|
+
est_reg,
|
106
|
+
),
|
107
|
+
)
|
108
|
+
|
109
|
+
if self._model is None:
|
110
|
+
self._model = create_model(
|
111
|
+
main,
|
112
|
+
constraints=self._constraints,
|
113
|
+
preferences=self._preferences,
|
114
|
+
)
|
115
|
+
return self._model
|
116
|
+
|
117
|
+
def get_qprog(self) -> QuantumProgram:
|
118
|
+
"""
|
119
|
+
Create an executable quantum Program for IQAE.
|
120
|
+
|
121
|
+
Args:
|
122
|
+
|
123
|
+
Returns:
|
124
|
+
QuantumProgram (QuantumProgram): Quantum program. See QuantumProgram.
|
125
|
+
"""
|
126
|
+
|
127
|
+
if self._qprog is None:
|
128
|
+
model = self.get_model()
|
129
|
+
self._qprog = synthesize(model)
|
130
|
+
return self._qprog
|
131
|
+
|
132
|
+
def run(
|
133
|
+
self,
|
134
|
+
epsilon: float,
|
135
|
+
alpha: float,
|
136
|
+
execution_preferences: Optional[ExecutionPreferences] = None,
|
137
|
+
) -> IQAEResult:
|
138
|
+
"""
|
139
|
+
Executes IQAE's quantum program with the provided epsilon, alpha, and execution
|
140
|
+
preferences.
|
141
|
+
If execution_preferences has been proved, or if it does not contain num_shot, then num_shot is set to 2048.
|
142
|
+
|
143
|
+
Args:
|
144
|
+
epsilon (float): Target accuracy in therm of $\\theta_a$ e.g $a = sin^2(\\theta_a \\pm \\epsilon)$ .
|
145
|
+
alpha (float): Specifies the confidence level (1 - alpha)
|
146
|
+
execution_preferences (Preferences): Preferences for the execution of the model. See ExecutionPreferences (Optional).
|
147
|
+
Returns:
|
148
|
+
IQAEResult (IQAEResult): A result of the IQAE algorithm. See IQAEResult.
|
149
|
+
"""
|
150
|
+
|
151
|
+
if self._qprog is None:
|
152
|
+
self._qprog = self.get_qprog()
|
153
|
+
|
154
|
+
if execution_preferences is None:
|
155
|
+
execution_preferences = ExecutionPreferences(
|
156
|
+
num_shots=self._NUM_SHOUTS,
|
157
|
+
)
|
158
|
+
elif execution_preferences.num_shots is None:
|
159
|
+
execution_preferences.num_shots = self._NUM_SHOUTS
|
160
|
+
|
161
|
+
return self._run(
|
162
|
+
epsilon=epsilon,
|
163
|
+
alpha=alpha,
|
164
|
+
execution_preferences=execution_preferences,
|
165
|
+
quantum_program=self._qprog,
|
166
|
+
)
|
167
|
+
|
168
|
+
def _run(
|
169
|
+
self,
|
170
|
+
epsilon: float,
|
171
|
+
alpha: float,
|
172
|
+
execution_preferences: ExecutionPreferences,
|
173
|
+
quantum_program: QuantumProgram,
|
174
|
+
) -> IQAEResult:
|
175
|
+
|
176
|
+
iterations_data: list[IQAEIterationData] = []
|
177
|
+
warnings: list[str] = []
|
178
|
+
with ExecutionSession(quantum_program, execution_preferences) as executor:
|
179
|
+
|
180
|
+
def _iqae_sample(k: int, _: int) -> int:
|
181
|
+
sample_results = executor.sample({"k": k})
|
182
|
+
iterations_data.append(
|
183
|
+
IQAEIterationData(
|
184
|
+
grover_iterations=k,
|
185
|
+
sample_results=sample_results,
|
186
|
+
)
|
187
|
+
)
|
188
|
+
return sample_results.counts_of_output("indicator").get("1", 0)
|
189
|
+
|
190
|
+
iqae = GenericIQAE(
|
191
|
+
epsilon=epsilon,
|
192
|
+
alpha=alpha,
|
193
|
+
num_shots=cast(int, execution_preferences.num_shots),
|
194
|
+
sample_callable=_iqae_sample,
|
195
|
+
)
|
196
|
+
|
197
|
+
try:
|
198
|
+
iqae.run()
|
199
|
+
except RuntimeError as ex:
|
200
|
+
warnings.append(f"Algorithm error: {ex.args[0]}")
|
201
|
+
|
202
|
+
return IQAEResult(
|
203
|
+
estimation=iqae.current_estimation(),
|
204
|
+
confidence_interval=iqae.current_estimation_confidence_interval().tolist(),
|
205
|
+
iterations_data=iterations_data,
|
206
|
+
warnings=warnings,
|
207
|
+
)
|
classiq/execution/__init__.py
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
from ..executor import * # noqa: F403
|
2
2
|
from ..executor import __all__ as _exec_all
|
3
|
+
from ..interface.applications.iqae.iqae_result import IQAEResult
|
3
4
|
from ..interface.backend.backend_preferences import * # noqa: F403
|
4
5
|
from ..interface.backend.backend_preferences import __all__ as _be_all
|
5
6
|
from ..interface.executor.execution_preferences import * # noqa: F403
|
6
7
|
from ..interface.executor.execution_preferences import __all__ as _ep_all
|
7
|
-
from ..interface.executor.iqae_result import IQAEResult
|
8
8
|
from ..interface.executor.result import ExecutionDetails
|
9
9
|
from ..interface.executor.vqe_result import VQESolverResult
|
10
10
|
from .execution_session import ExecutionSession
|
classiq/interface/_version.py
CHANGED
File without changes
|
@@ -0,0 +1,222 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from dataclasses import dataclass
|
5
|
+
|
6
|
+
import numpy as np
|
7
|
+
|
8
|
+
MAX_ITERATIONS_NUMBER = 1000
|
9
|
+
|
10
|
+
|
11
|
+
class GenericIQAE:
|
12
|
+
"""
|
13
|
+
The implementation is based on Algorithm 1 & Algorithm 2 in [1], with the intent of demistifying variables names
|
14
|
+
and simplifying the code flow.
|
15
|
+
Moreover, we separated the algorithm flow from quantum execution to allow migrating this code to any execution
|
16
|
+
interface and to improve its testability.
|
17
|
+
|
18
|
+
References:
|
19
|
+
[1]: Grinko, D., Gacon, J., Zoufal, C., & Woerner, S. (2019).
|
20
|
+
Iterative Quantum Amplitude Estimation.
|
21
|
+
`arXiv:1912.05559 <https://arxiv.org/abs/1912.05559>`.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(
|
25
|
+
self,
|
26
|
+
epsilon: float,
|
27
|
+
alpha: float,
|
28
|
+
num_shots: int,
|
29
|
+
sample_callable: Callable[[int, int], int],
|
30
|
+
) -> None:
|
31
|
+
"""
|
32
|
+
Parameters:
|
33
|
+
epsilon: Target accuracy.
|
34
|
+
alpha: Specifies the confidence level (1 - alpha).
|
35
|
+
num_shots: The maximum number of shots in each iteration.
|
36
|
+
sample_callable: A callable which gets k and num_shots, and returns the count of good states for $Q^kA$
|
37
|
+
(states with 1 in the last qubit).
|
38
|
+
This callable is responsible for the quantum execution and the results parsing.
|
39
|
+
"""
|
40
|
+
self._epsilon = epsilon
|
41
|
+
self._alpha = alpha
|
42
|
+
self._num_shots = num_shots
|
43
|
+
self._sample_callable = sample_callable
|
44
|
+
|
45
|
+
self.iterations: list[IterationInfo] = []
|
46
|
+
|
47
|
+
# Prepare initial values (lines 2-6, Algorithm 1, [1])
|
48
|
+
self._new_iteration()
|
49
|
+
self._current().k = 0
|
50
|
+
self._current().is_upper_plane = True
|
51
|
+
self._current().confidence_interval = np.array([0, np.pi / 2])
|
52
|
+
|
53
|
+
self._max_rounds = np.ceil(np.log2(np.pi / (8 * self._epsilon)))
|
54
|
+
|
55
|
+
def _new_iteration(self) -> None:
|
56
|
+
if len(self.iterations) > MAX_ITERATIONS_NUMBER:
|
57
|
+
raise RuntimeError(
|
58
|
+
f"Maximum number of iterations ({MAX_ITERATIONS_NUMBER}) achieved."
|
59
|
+
)
|
60
|
+
self.iterations.append(IterationInfo())
|
61
|
+
|
62
|
+
def _current(self) -> IterationInfo:
|
63
|
+
return self.iterations[-1]
|
64
|
+
|
65
|
+
def _prev(self) -> IterationInfo:
|
66
|
+
return self.iterations[-2]
|
67
|
+
|
68
|
+
def run(self) -> float:
|
69
|
+
"""
|
70
|
+
Execute the estimation algorithm.
|
71
|
+
See Algorithm 1, [1].
|
72
|
+
"""
|
73
|
+
while interval_len(self._current().confidence_interval) > 2 * self._epsilon:
|
74
|
+
self._new_iteration()
|
75
|
+
self._find_next_K()
|
76
|
+
self._sample()
|
77
|
+
self._calculate_confidence_interval()
|
78
|
+
|
79
|
+
return self.current_estimation()
|
80
|
+
|
81
|
+
def current_estimation_confidence_interval(self) -> np.ndarray:
|
82
|
+
return np.sin(self._current().confidence_interval) ** 2
|
83
|
+
|
84
|
+
def current_estimation(self) -> float:
|
85
|
+
return self.current_estimation_confidence_interval().mean()
|
86
|
+
|
87
|
+
def _find_next_K(self, r: int = 2) -> None: # noqa: N802
|
88
|
+
self._current().K, self._current().is_upper_plane = self.find_next_K(
|
89
|
+
K=self._prev().K,
|
90
|
+
is_upper_plane=self._prev().is_upper_plane,
|
91
|
+
confidence_interval=self._prev().confidence_interval,
|
92
|
+
r=r,
|
93
|
+
)
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def find_next_K( # noqa: N802
|
97
|
+
K: int, # noqa: N803
|
98
|
+
is_upper_plane: bool,
|
99
|
+
confidence_interval: np.ndarray,
|
100
|
+
r: int = 2,
|
101
|
+
) -> tuple[int, bool]:
|
102
|
+
"""
|
103
|
+
We want to find the largest K (with some lower and upper bounds) such that the K-scaled confidence interval
|
104
|
+
lies completely in the upper or lower half planes.
|
105
|
+
See Algorithm 2, [1].
|
106
|
+
"""
|
107
|
+
K_max = int(np.pi // interval_len(confidence_interval)) # noqa: N806
|
108
|
+
K_max = K_max - (K_max - 2) % 4 # noqa: N806
|
109
|
+
K_min = r * K # noqa: N806
|
110
|
+
|
111
|
+
for K_cand in range(K_max, K_min - 1, -4): # noqa: N806
|
112
|
+
scaled_confidence_interval = (K_cand * confidence_interval) % (2 * np.pi)
|
113
|
+
|
114
|
+
if all(scaled_confidence_interval <= np.pi):
|
115
|
+
return K_cand, True
|
116
|
+
if all(scaled_confidence_interval >= np.pi):
|
117
|
+
return K_cand, False
|
118
|
+
|
119
|
+
return K, is_upper_plane
|
120
|
+
|
121
|
+
def _sample(self) -> None:
|
122
|
+
"""
|
123
|
+
Use the external sample callable to get the count of good states for $Q^kA$ (states with 1 in the last qubit).
|
124
|
+
Effectively implements line 16, Algorithm 1, [1].
|
125
|
+
"""
|
126
|
+
# To optimize results, the paper's algorithm applies the "no-overshooting condition" which limits
|
127
|
+
# the number of shots in each iteration (lines 12-15, Algorithm 1, [1]). As the calculation is not very
|
128
|
+
# simple, we currently don't support this and use constant number of shots.
|
129
|
+
self._current().num_shots = self._num_shots
|
130
|
+
|
131
|
+
self._current().good_counts = self._sample_callable(
|
132
|
+
self._current().k, self._current().num_shots
|
133
|
+
)
|
134
|
+
|
135
|
+
def _calculate_confidence_interval(self) -> None:
|
136
|
+
"""
|
137
|
+
Calculate the next confidence interval based on the last sample's results.
|
138
|
+
Effectively implements lines 17-28, Algorithm 1, [1].
|
139
|
+
"""
|
140
|
+
prob = self._current().good_counts / self._current().num_shots
|
141
|
+
|
142
|
+
# The paper specifies two possibles confidence interval methods: Clopper-Perason or Chernoff-Hoeffding.
|
143
|
+
# We currently support only the latter.
|
144
|
+
prob_min, prob_max = self._chernoff_hoeffding(prob)
|
145
|
+
|
146
|
+
if self._current().is_upper_plane:
|
147
|
+
theta = np.arccos(1 - 2 * np.array([prob_min, prob_max]))
|
148
|
+
else:
|
149
|
+
theta = 2 * np.pi - np.arccos(1 - 2 * np.array([prob_max, prob_min]))
|
150
|
+
|
151
|
+
scaled_confidence_interval = (
|
152
|
+
self._current().K * self._prev().confidence_interval
|
153
|
+
)
|
154
|
+
number_of_wraps = scaled_confidence_interval // (2 * np.pi)
|
155
|
+
|
156
|
+
# Sometimes we have edge cases where the lower or upper bound of the scaled interval fall exactly
|
157
|
+
# on 2pi*T for some integer T, and the number of wraps might be rounded up or down wrongfuly.
|
158
|
+
# To fix it, use the number of wraps of the middle point in the scaled interval.
|
159
|
+
if number_of_wraps[0] + 1 == number_of_wraps[1]:
|
160
|
+
number_of_wraps_of_middle = np.mean(scaled_confidence_interval) // (
|
161
|
+
2 * np.pi
|
162
|
+
)
|
163
|
+
number_of_wraps = np.array(
|
164
|
+
[number_of_wraps_of_middle, number_of_wraps_of_middle]
|
165
|
+
)
|
166
|
+
|
167
|
+
if number_of_wraps[0] != number_of_wraps[1]:
|
168
|
+
raise RuntimeError(
|
169
|
+
f"Number of wraps of the lower and upper bounds should be equal, got {number_of_wraps}"
|
170
|
+
)
|
171
|
+
|
172
|
+
self._current().confidence_interval = (
|
173
|
+
2 * np.pi * number_of_wraps + theta
|
174
|
+
) / self._current().K
|
175
|
+
|
176
|
+
def _chernoff_hoeffding(self, prob: float) -> tuple[float, float]:
|
177
|
+
"""
|
178
|
+
The Chernoff-Hoeffding confidence interval method.
|
179
|
+
Effectively implements lines 20-22, Algorithm 1, [1].
|
180
|
+
"""
|
181
|
+
epsilon = np.sqrt(
|
182
|
+
np.log(2 * self._max_rounds / self._alpha)
|
183
|
+
/ (2 * self._accumulated_num_shots())
|
184
|
+
)
|
185
|
+
return max(0, prob - epsilon), min(1, prob + epsilon)
|
186
|
+
|
187
|
+
def _accumulated_num_shots(self) -> int:
|
188
|
+
num_shots = 0
|
189
|
+
for iteration in reversed(self.iterations):
|
190
|
+
if iteration.K == self._current().K:
|
191
|
+
num_shots += iteration.num_shots
|
192
|
+
else:
|
193
|
+
break
|
194
|
+
return num_shots
|
195
|
+
|
196
|
+
|
197
|
+
@dataclass(init=False, repr=False, eq=False)
|
198
|
+
class IterationInfo:
|
199
|
+
"""
|
200
|
+
The information stored on each iteration of IQAE.
|
201
|
+
"""
|
202
|
+
|
203
|
+
K: int # K = 4k + 2 where k is the power of Q on each iteration
|
204
|
+
is_upper_plane: bool # Wheter the scaled confidence interval is in the upper or lower half plane (in the paper: "up")
|
205
|
+
confidence_interval: (
|
206
|
+
np.ndarray
|
207
|
+
) # The current confidence interval (in the paper: "(theta_l, theta_u)")
|
208
|
+
|
209
|
+
good_counts: int
|
210
|
+
num_shots: int = 0
|
211
|
+
|
212
|
+
@property
|
213
|
+
def k(self) -> int:
|
214
|
+
return (self.K - 2) // 4
|
215
|
+
|
216
|
+
@k.setter
|
217
|
+
def k(self, k: int) -> None:
|
218
|
+
self.K = 4 * k + 2
|
219
|
+
|
220
|
+
|
221
|
+
def interval_len(interval: np.ndarray) -> float:
|
222
|
+
return interval[1] - interval[0]
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from pydantic import BaseModel, Field
|
2
|
+
|
3
|
+
from classiq.interface.executor.result import ExecutionDetails
|
4
|
+
from classiq.interface.generator.functions.classical_type import QmodPyObject
|
5
|
+
from classiq.interface.helpers.versioned_model import VersionedModel
|
6
|
+
|
7
|
+
|
8
|
+
class IQAEIterationData(BaseModel):
|
9
|
+
"""
|
10
|
+
Handles the data storage for a single iteration of the Iterative Quantum Amplitude
|
11
|
+
Estimation algorithm.
|
12
|
+
|
13
|
+
This class is intended to represent the results and state of a single Grover iteration
|
14
|
+
of the IQAE process.
|
15
|
+
|
16
|
+
Attributes:
|
17
|
+
grover_iterations (int): The iteration number of Grover's algorithm.
|
18
|
+
sample_results (ExecutionDetails): The `ExecutionDetails` of Grover iteration. See ExecutionDetails.
|
19
|
+
"""
|
20
|
+
|
21
|
+
grover_iterations: int
|
22
|
+
sample_results: ExecutionDetails
|
23
|
+
|
24
|
+
|
25
|
+
class IQAEResult(VersionedModel, QmodPyObject):
|
26
|
+
"""
|
27
|
+
Represents the result of an Iterative Quantum Amplitude Estimation (IQAE)
|
28
|
+
process.
|
29
|
+
|
30
|
+
This class encapsulates the output of the IQAE algorithm, including the
|
31
|
+
estimated value, confidence interval, intermediate iteration data, and
|
32
|
+
any warnings generated during the computation.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
estimation (float): Estimation of the amplitude.
|
36
|
+
confidence_interval (list[float]): The interval in which the amplitude is within, with a probability equal to epsilon.
|
37
|
+
iterations_data (list[IQAEIterationData]): List of `IQAEIterationData` of each Grover iteration.
|
38
|
+
See IQAEIterationData.
|
39
|
+
warnings (list[str]): List of warnings generated during the IQAE process of each Grover iteration.
|
40
|
+
"""
|
41
|
+
|
42
|
+
estimation: float
|
43
|
+
confidence_interval: list[float] = Field(min_length=2, max_length=2)
|
44
|
+
iterations_data: list[IQAEIterationData]
|
45
|
+
warnings: list[str]
|
@@ -24,6 +24,7 @@ class FunctionDebugInfo(BaseModel):
|
|
24
24
|
statement_type: Union[StatementType, None] = None
|
25
25
|
is_inverse: bool = Field(default=False)
|
26
26
|
release_by_inverse: bool = Field(default=False)
|
27
|
+
control_variable: Optional[str] = Field(default=None)
|
27
28
|
port_to_passed_variable_map: dict[str, str] = Field(default_factory=dict)
|
28
29
|
node: Optional[ConcreteQuantumStatement] = None
|
29
30
|
|
@@ -85,6 +86,8 @@ def get_back_refs(
|
|
85
86
|
# For backwards compatibility, we make sure that the back_ref is not a block
|
86
87
|
# Remove this check when we start saving blocks in the debug info collection.
|
87
88
|
assert not isinstance(node, Block)
|
89
|
+
if len(back_refs) > 0 and node.back_ref == back_refs[0].back_ref:
|
90
|
+
break
|
88
91
|
back_refs.insert(0, node)
|
89
92
|
if node.back_ref is None:
|
90
93
|
break
|
@@ -3,8 +3,8 @@ from typing import Annotated, Any, Literal, Union
|
|
3
3
|
from pydantic import BaseModel, ConfigDict, Field
|
4
4
|
from typing_extensions import TypeAlias
|
5
5
|
|
6
|
+
from classiq.interface.applications.iqae.iqae_result import IQAEResult
|
6
7
|
from classiq.interface.enum_utils import StrEnum
|
7
|
-
from classiq.interface.executor.iqae_result import IQAEResult
|
8
8
|
from classiq.interface.executor.result import (
|
9
9
|
EstimationResult,
|
10
10
|
EstimationResults,
|
@@ -29,7 +29,7 @@ class UserBudgets(VersionedModel):
|
|
29
29
|
def format_row(
|
30
30
|
provider: str, available: float, used: float, currency: str
|
31
31
|
) -> str:
|
32
|
-
return f"| {provider:<20} | {available:<18.
|
32
|
+
return f"| {provider:<20} | {available:<18.3f} | {used:<18.3f} | {currency:<8} |"
|
33
33
|
|
34
34
|
table_data: dict = defaultdict(
|
35
35
|
lambda: {"used": 0.0, "available": 0.0, "currency": "USD"}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import inspect
|
2
|
-
from typing import Any
|
2
|
+
from typing import Any, NoReturn
|
3
3
|
|
4
4
|
import sympy
|
5
5
|
|
@@ -37,5 +37,11 @@ class AnyClassicalValue(sympy.Symbol):
|
|
37
37
|
return super().__getattribute__(attr)
|
38
38
|
return AnyClassicalValue(f"get_field({self}, '{attr}')")
|
39
39
|
|
40
|
-
def __len__(self) ->
|
41
|
-
|
40
|
+
def __len__(self) -> NoReturn:
|
41
|
+
raise TypeError("object of type 'AnyClassicalValue' has no len()")
|
42
|
+
|
43
|
+
def __iter__(self) -> NoReturn:
|
44
|
+
raise TypeError("'AnyClassicalValue' object is not iterable")
|
45
|
+
|
46
|
+
def __bool__(self) -> bool:
|
47
|
+
return True
|