classiq 0.84.0__py3-none-any.whl → 0.85.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/combinatorial_optimization/combinatorial_problem.py +20 -42
- classiq/evaluators/classical_expression.py +32 -15
- classiq/execution/execution_session.py +49 -6
- classiq/interface/_version.py +1 -1
- classiq/interface/debug_info/debug_info.py +0 -4
- classiq/interface/generator/expressions/atomic_expression_functions.py +5 -0
- classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
- classiq/interface/generator/functions/concrete_types.py +20 -0
- classiq/interface/generator/generated_circuit_data.py +5 -10
- classiq/interface/ide/operation_registry.py +45 -0
- classiq/interface/ide/visual_model.py +62 -1
- classiq/interface/model/bounds.py +12 -2
- classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
- classiq/interface/model/variable_declaration_statement.py +33 -6
- classiq/model_expansions/atomic_expression_functions_defs.py +5 -1
- classiq/model_expansions/function_builder.py +4 -1
- classiq/model_expansions/interpreters/generative_interpreter.py +16 -1
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +63 -21
- classiq/model_expansions/quantum_operations/bounds.py +7 -1
- classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
- classiq/model_expansions/quantum_operations/variable_decleration.py +30 -10
- classiq/model_expansions/scope.py +7 -0
- classiq/model_expansions/transformers/var_splitter.py +1 -1
- classiq/open_library/functions/__init__.py +0 -2
- classiq/open_library/functions/qaoa_penalty.py +8 -1
- classiq/open_library/functions/state_preparation.py +1 -32
- classiq/qmod/__init__.py +2 -0
- classiq/qmod/builtins/operations.py +65 -1
- classiq/qmod/classical_variable.py +74 -0
- classiq/qmod/native/pretty_printer.py +18 -14
- classiq/qmod/pretty_print/pretty_printer.py +34 -15
- classiq/qmod/qfunc.py +2 -19
- classiq/qmod/qmod_variable.py +5 -8
- classiq/qmod/quantum_expandable.py +1 -1
- classiq/qmod/quantum_function.py +42 -2
- classiq/qmod/symbolic_type.py +2 -1
- {classiq-0.84.0.dist-info → classiq-0.85.0.dist-info}/METADATA +1 -1
- {classiq-0.84.0.dist-info → classiq-0.85.0.dist-info}/RECORD +39 -37
- classiq/interface/model/quantum_variable_declaration.py +0 -7
- {classiq-0.84.0.dist-info → classiq-0.85.0.dist-info}/WHEEL +0 -0
@@ -1,12 +1,11 @@
|
|
1
1
|
import math
|
2
2
|
import re
|
3
|
-
from typing import Callable, Optional
|
3
|
+
from typing import Callable, Optional, cast
|
4
4
|
|
5
5
|
import numpy as np
|
6
6
|
import pandas as pd
|
7
7
|
import pyomo.core as pyo
|
8
8
|
import scipy
|
9
|
-
from tqdm import tqdm
|
10
9
|
|
11
10
|
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
12
11
|
from classiq.interface.executor.result import ExecutionDetails
|
@@ -44,8 +43,8 @@ class CombinatorialProblem:
|
|
44
43
|
self.num_layers_ = num_layers
|
45
44
|
self.model_ = None
|
46
45
|
self.qprog_ = None
|
47
|
-
self.
|
48
|
-
self.
|
46
|
+
self._es: ExecutionSession | None = None
|
47
|
+
self._optimized_params: list[float] | None = None
|
49
48
|
self.params_trace_: list[np.ndarray] = []
|
50
49
|
self.cost_trace_: list = []
|
51
50
|
|
@@ -58,8 +57,8 @@ class CombinatorialProblem:
|
|
58
57
|
return self.params_trace_
|
59
58
|
|
60
59
|
@property
|
61
|
-
def optimized_params(self) -> list:
|
62
|
-
return self.
|
60
|
+
def optimized_params(self) -> list[float]:
|
61
|
+
return self._optimized_params # type:ignore[return-value]
|
63
62
|
|
64
63
|
def get_model(
|
65
64
|
self,
|
@@ -100,22 +99,9 @@ class CombinatorialProblem:
|
|
100
99
|
) -> list[float]:
|
101
100
|
if self.qprog_ is None:
|
102
101
|
self.get_qprog()
|
103
|
-
|
104
|
-
self.qprog_, execution_preferences # type:ignore[
|
102
|
+
_es = ExecutionSession(
|
103
|
+
self.qprog_, execution_preferences # type:ignore[arg-type]
|
105
104
|
)
|
106
|
-
self.params_trace_ = []
|
107
|
-
self.cost_trace_ = []
|
108
|
-
|
109
|
-
def estimate_cost_wrapper(params: np.ndarray) -> float:
|
110
|
-
cost = self.es_.estimate_cost( # type:ignore[attr-defined]
|
111
|
-
lambda state: self.cost_func(state["v"]),
|
112
|
-
{"params": params.tolist()},
|
113
|
-
quantile=quantile,
|
114
|
-
)
|
115
|
-
self.cost_trace_.append(cost)
|
116
|
-
self.params_trace_.append(params)
|
117
|
-
return cost
|
118
|
-
|
119
105
|
initial_params = (
|
120
106
|
np.concatenate(
|
121
107
|
(
|
@@ -125,31 +111,23 @@ class CombinatorialProblem:
|
|
125
111
|
)
|
126
112
|
* math.pi
|
127
113
|
)
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
x0=initial_params,
|
139
|
-
method="COBYLA",
|
140
|
-
options={"maxiter": maxiter},
|
141
|
-
).x.tolist()
|
142
|
-
|
143
|
-
return self.optimized_params_ # type:ignore[return-value]
|
114
|
+
result = _es.minimize(
|
115
|
+
lambda v: self.cost_func(v), # type:ignore[arg-type]
|
116
|
+
{"params": initial_params.tolist()},
|
117
|
+
maxiter,
|
118
|
+
quantile,
|
119
|
+
)
|
120
|
+
_optimized_params = cast(list[float], result[-1][1]["params"])
|
121
|
+
self._optimized_params = _optimized_params
|
122
|
+
self._es = _es
|
123
|
+
return _optimized_params
|
144
124
|
|
145
125
|
def sample_uniform(self) -> pd.DataFrame:
|
146
126
|
return self.sample([0] * self.num_layers_ * 2)
|
147
127
|
|
148
128
|
def sample(self, params: list) -> pd.DataFrame:
|
149
|
-
assert self.
|
150
|
-
res = self.
|
151
|
-
{"params": params}
|
152
|
-
)
|
129
|
+
assert self._es is not None
|
130
|
+
res = self._es.sample({"params": params})
|
153
131
|
parsed_result = [
|
154
132
|
{
|
155
133
|
"solution": {
|
@@ -157,7 +135,7 @@ class CombinatorialProblem:
|
|
157
135
|
for key, value in sampled.state["v"].items()
|
158
136
|
if not re.match(".*_slack_var_.*", key)
|
159
137
|
},
|
160
|
-
"probability": sampled.shots / res.num_shots,
|
138
|
+
"probability": sampled.shots / res.num_shots, # type:ignore[operator]
|
161
139
|
"cost": self.cost_func(sampled.state["v"]),
|
162
140
|
}
|
163
141
|
for sampled in res.parsed_counts
|
@@ -11,26 +11,43 @@ from classiq.interface.generator.expressions.proxies.classical.any_classical_val
|
|
11
11
|
from classiq.interface.model.handle_binding import HandleBinding
|
12
12
|
|
13
13
|
from classiq.evaluators.expression_evaluator import evaluate
|
14
|
-
from classiq.model_expansions.scope import
|
14
|
+
from classiq.model_expansions.scope import (
|
15
|
+
ClassicalSymbol,
|
16
|
+
Evaluated,
|
17
|
+
QuantumSymbol,
|
18
|
+
Scope,
|
19
|
+
)
|
15
20
|
|
16
21
|
|
17
22
|
def evaluate_classical_expression(expr: Expression, scope: Scope) -> Evaluated:
|
18
23
|
all_symbols = scope.items()
|
19
|
-
locals_dict =
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
locals_dict = (
|
25
|
+
{
|
26
|
+
name: EvaluatedExpression(value=evaluated.value)
|
27
|
+
for name, evaluated in all_symbols
|
28
|
+
if isinstance(evaluated.value, get_args(ExpressionValue))
|
29
|
+
}
|
30
|
+
| {
|
31
|
+
name: EvaluatedExpression(
|
32
|
+
value=(
|
33
|
+
evaluated.value.quantum_type.get_proxy(HandleBinding(name=name))
|
34
|
+
if evaluated.value.quantum_type.is_evaluated
|
35
|
+
else AnyClassicalValue(name)
|
36
|
+
)
|
37
|
+
)
|
38
|
+
for name, evaluated in all_symbols
|
39
|
+
if isinstance(evaluated.value, QuantumSymbol)
|
40
|
+
}
|
41
|
+
| {
|
42
|
+
name: EvaluatedExpression(
|
43
|
+
value=evaluated.value.classical_type.get_classical_proxy(
|
44
|
+
HandleBinding(name=name)
|
45
|
+
)
|
29
46
|
)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
for name, evaluated in all_symbols
|
48
|
+
if isinstance(evaluated.value, ClassicalSymbol)
|
49
|
+
}
|
50
|
+
)
|
34
51
|
|
35
52
|
ret = evaluate(expr, locals_dict)
|
36
53
|
return Evaluated(value=ret.value)
|
@@ -1,10 +1,15 @@
|
|
1
1
|
import inspect
|
2
2
|
import random
|
3
|
+
import warnings
|
3
4
|
from types import TracebackType
|
4
|
-
from typing import Callable, Optional, Union, cast
|
5
|
+
from typing import Any, Callable, Optional, Union, cast
|
5
6
|
|
6
7
|
from classiq.interface.chemistry.operator import PauliOperator, pauli_integers_to_str
|
7
|
-
from classiq.interface.exceptions import
|
8
|
+
from classiq.interface.exceptions import (
|
9
|
+
ClassiqDeprecationWarning,
|
10
|
+
ClassiqError,
|
11
|
+
ClassiqValueError,
|
12
|
+
)
|
8
13
|
from classiq.interface.execution.primitives import (
|
9
14
|
EstimateInput,
|
10
15
|
MinimizeClassicalCostInput,
|
@@ -71,6 +76,20 @@ def parse_params(params: ExecutionParams) -> ParsedExecutionParams:
|
|
71
76
|
return result
|
72
77
|
|
73
78
|
|
79
|
+
def _hamiltonian_deprecation_warning(hamiltonian: Any) -> None:
|
80
|
+
if isinstance(hamiltonian, list):
|
81
|
+
warnings.warn(
|
82
|
+
(
|
83
|
+
"Parameter type list[PauliTerm] to 'ExecutionSession' methods is "
|
84
|
+
"deprecated and will no longer be supported starting on 21/7/2025 "
|
85
|
+
"at the earliest. Instead, send a 'SparsePauliOp' (see "
|
86
|
+
"https://docs.classiq.io/latest/qmod-reference/language-reference/classical-types/#hamiltonians)."
|
87
|
+
),
|
88
|
+
ClassiqDeprecationWarning,
|
89
|
+
stacklevel=3,
|
90
|
+
)
|
91
|
+
|
92
|
+
|
74
93
|
class ExecutionSession:
|
75
94
|
"""
|
76
95
|
A session for executing a quantum program.
|
@@ -228,11 +247,18 @@ class ExecutionSession:
|
|
228
247
|
Returns:
|
229
248
|
EstimationResult: The result of the estimation.
|
230
249
|
"""
|
231
|
-
|
250
|
+
_hamiltonian_deprecation_warning(hamiltonian)
|
251
|
+
job = self.submit_estimate(
|
252
|
+
hamiltonian=hamiltonian, parameters=parameters, _check_deprecation=False
|
253
|
+
)
|
232
254
|
return job.get_estimate_result(_http_client=self._async_client)
|
233
255
|
|
234
256
|
def submit_estimate(
|
235
|
-
self,
|
257
|
+
self,
|
258
|
+
hamiltonian: Hamiltonian,
|
259
|
+
parameters: Optional[ExecutionParams] = None,
|
260
|
+
*,
|
261
|
+
_check_deprecation: bool = True,
|
236
262
|
) -> ExecutionJob:
|
237
263
|
"""
|
238
264
|
Initiates an execution job with the `estimate` primitive.
|
@@ -247,6 +273,8 @@ class ExecutionSession:
|
|
247
273
|
Returns:
|
248
274
|
The execution job.
|
249
275
|
"""
|
276
|
+
if _check_deprecation:
|
277
|
+
_hamiltonian_deprecation_warning(hamiltonian)
|
250
278
|
execution_primitives_input = PrimitivesInput(
|
251
279
|
estimate=EstimateInput(
|
252
280
|
hamiltonian=self._hamiltonian_to_pauli_operator(hamiltonian),
|
@@ -270,11 +298,18 @@ class ExecutionSession:
|
|
270
298
|
Returns:
|
271
299
|
List[EstimationResult]: The results of all the estimation iterations.
|
272
300
|
"""
|
273
|
-
|
301
|
+
_hamiltonian_deprecation_warning(hamiltonian)
|
302
|
+
job = self.submit_batch_estimate(
|
303
|
+
hamiltonian=hamiltonian, parameters=parameters, _check_deprecation=False
|
304
|
+
)
|
274
305
|
return job.get_batch_estimate_result(_http_client=self._async_client)
|
275
306
|
|
276
307
|
def submit_batch_estimate(
|
277
|
-
self,
|
308
|
+
self,
|
309
|
+
hamiltonian: Hamiltonian,
|
310
|
+
parameters: list[ExecutionParams],
|
311
|
+
*,
|
312
|
+
_check_deprecation: bool = True,
|
278
313
|
) -> ExecutionJob:
|
279
314
|
"""
|
280
315
|
Initiates an execution job with the `batch_estimate` primitive.
|
@@ -289,6 +324,8 @@ class ExecutionSession:
|
|
289
324
|
Returns:
|
290
325
|
The execution job.
|
291
326
|
"""
|
327
|
+
if _check_deprecation:
|
328
|
+
_hamiltonian_deprecation_warning(hamiltonian)
|
292
329
|
execution_primitives_input = PrimitivesInput(
|
293
330
|
estimate=EstimateInput(
|
294
331
|
hamiltonian=self._hamiltonian_to_pauli_operator(hamiltonian),
|
@@ -322,11 +359,13 @@ class ExecutionSession:
|
|
322
359
|
A list of tuples, each containing the estimated cost and the corresponding parameters for that iteration.
|
323
360
|
`cost` is a float, and `parameters` is a dictionary matching the execution parameter format.
|
324
361
|
"""
|
362
|
+
_hamiltonian_deprecation_warning(cost_function)
|
325
363
|
job = self.submit_minimize(
|
326
364
|
cost_function=cost_function,
|
327
365
|
initial_params=initial_params,
|
328
366
|
max_iteration=max_iteration,
|
329
367
|
quantile=quantile,
|
368
|
+
_check_deprecation=False,
|
330
369
|
)
|
331
370
|
result = job.get_minimization_result(_http_client=self._async_client)
|
332
371
|
|
@@ -340,6 +379,8 @@ class ExecutionSession:
|
|
340
379
|
initial_params: ExecutionParams,
|
341
380
|
max_iteration: int,
|
342
381
|
quantile: float = 1.0,
|
382
|
+
*,
|
383
|
+
_check_deprecation: bool = True,
|
343
384
|
) -> ExecutionJob:
|
344
385
|
"""
|
345
386
|
Initiates an execution job with the `minimize` primitive.
|
@@ -363,6 +404,8 @@ class ExecutionSession:
|
|
363
404
|
Returns:
|
364
405
|
The execution job.
|
365
406
|
"""
|
407
|
+
if _check_deprecation:
|
408
|
+
_hamiltonian_deprecation_warning(cost_function)
|
366
409
|
if len(initial_params) != 1:
|
367
410
|
raise ClassiqValueError(
|
368
411
|
"The initial parameters must be a dictionary with a single key-value pair."
|
classiq/interface/_version.py
CHANGED
@@ -9,7 +9,6 @@ from classiq.interface.generator.generated_circuit_data import (
|
|
9
9
|
FunctionDebugInfoInterface,
|
10
10
|
StatementType,
|
11
11
|
)
|
12
|
-
from classiq.interface.model.block import Block
|
13
12
|
from classiq.interface.model.handle_binding import ConcreteHandleBinding
|
14
13
|
from classiq.interface.model.port_declaration import PortDeclaration
|
15
14
|
from classiq.interface.model.quantum_function_call import ArgValue
|
@@ -82,9 +81,6 @@ def get_back_refs(
|
|
82
81
|
) -> list[ConcreteQuantumStatement]:
|
83
82
|
back_refs: list[ConcreteQuantumStatement] = []
|
84
83
|
while (node := debug_info.node) is not None:
|
85
|
-
# For backwards compatibility, we make sure that the back_ref is not a block
|
86
|
-
# Remove this check when we start saving blocks in the debug info collection.
|
87
|
-
assert not isinstance(node, Block)
|
88
84
|
if len(back_refs) > 0 and node.back_ref == back_refs[0].back_ref:
|
89
85
|
break
|
90
86
|
back_refs.insert(0, node)
|
@@ -20,9 +20,14 @@ CLASSIQ_EXPR_FUNCTIONS = {
|
|
20
20
|
"get_field",
|
21
21
|
}
|
22
22
|
|
23
|
+
MEASUREMENT_FUNCTIONS = {
|
24
|
+
"measure",
|
25
|
+
}
|
26
|
+
|
23
27
|
SUPPORTED_CLASSIQ_BUILTIN_FUNCTIONS = {
|
24
28
|
*CLASSIQ_BUILTIN_CLASSICAL_FUNCTIONS,
|
25
29
|
*CLASSIQ_EXPR_FUNCTIONS,
|
30
|
+
*MEASUREMENT_FUNCTIONS,
|
26
31
|
}
|
27
32
|
|
28
33
|
SUPPORTED_CLASSIQ_SYMPY_WRAPPERS = {
|
@@ -6,6 +6,7 @@ CLASSICAL_IF_OPERATOR_NAME = "classical_if"
|
|
6
6
|
POWER_OPERATOR_NAME = "power"
|
7
7
|
UNCOMPUTE_OPERATOR_NAME = "uncompute"
|
8
8
|
WITHIN_APPLY_NAME = "within_apply"
|
9
|
+
BLOCK_OPERATOR_NAME = "block"
|
9
10
|
|
10
11
|
All_BUILTINS_OPERATORS = {
|
11
12
|
CONTROL_OPERATOR_NAME,
|
@@ -14,4 +15,5 @@ All_BUILTINS_OPERATORS = {
|
|
14
15
|
POWER_OPERATOR_NAME,
|
15
16
|
UNCOMPUTE_OPERATOR_NAME,
|
16
17
|
WITHIN_APPLY_NAME,
|
18
|
+
BLOCK_OPERATOR_NAME,
|
17
19
|
}
|
@@ -53,3 +53,23 @@ QuantumBitvector.model_rebuild()
|
|
53
53
|
TypeName.model_rebuild()
|
54
54
|
QStructDeclaration.model_rebuild()
|
55
55
|
RegisterQuantumType.model_rebuild()
|
56
|
+
|
57
|
+
ConcreteType = Annotated[
|
58
|
+
Union[
|
59
|
+
Integer,
|
60
|
+
Real,
|
61
|
+
Bool,
|
62
|
+
StructMetaType,
|
63
|
+
TypeName,
|
64
|
+
ClassicalArray,
|
65
|
+
ClassicalTuple,
|
66
|
+
VQEResult,
|
67
|
+
Histogram,
|
68
|
+
Estimation,
|
69
|
+
IQAERes,
|
70
|
+
QuantumBit,
|
71
|
+
QuantumBitvector,
|
72
|
+
QuantumNumeric,
|
73
|
+
],
|
74
|
+
Field(discriminator="kind"),
|
75
|
+
]
|
@@ -52,8 +52,6 @@ VISUALIZATION_HIDE_LIST = [
|
|
52
52
|
"stmt_block",
|
53
53
|
]
|
54
54
|
|
55
|
-
CONTROLLED_PREFIX = "c_"
|
56
|
-
|
57
55
|
|
58
56
|
def last_name_in_call_hierarchy(name: str) -> str:
|
59
57
|
return name.split(CLASSIQ_HIERARCHY_SEPARATOR)[-1]
|
@@ -147,6 +145,7 @@ class StatementType(StrEnum):
|
|
147
145
|
INPLACE_XOR = "inplace xor"
|
148
146
|
INPLACE_ADD = "inplace add"
|
149
147
|
REPEAT = "repeat"
|
148
|
+
BLOCK = "block"
|
150
149
|
|
151
150
|
|
152
151
|
# Mapping between statement kind (or sub-kind) and statement type (visualization name)
|
@@ -167,6 +166,7 @@ STATEMENTS_NAME: dict[str, StatementType] = {
|
|
167
166
|
ArithmeticOperationKind.InplaceXor.value: StatementType.INPLACE_XOR,
|
168
167
|
ArithmeticOperationKind.InplaceAdd.value: StatementType.INPLACE_ADD,
|
169
168
|
"Repeat": StatementType.REPEAT,
|
169
|
+
"Block": StatementType.BLOCK,
|
170
170
|
}
|
171
171
|
|
172
172
|
|
@@ -207,8 +207,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
207
207
|
ARITH_ENGINE_PREFIX
|
208
208
|
)
|
209
209
|
name_with_suffix = self.add_suffix_from_generated_name(generated_name, name)
|
210
|
-
|
211
|
-
return modified_name
|
210
|
+
return name_with_suffix
|
212
211
|
|
213
212
|
statement_kind: str = back_ref.kind
|
214
213
|
if isinstance(back_ref, ArithmeticOperation):
|
@@ -217,11 +216,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
217
216
|
generated_name, STATEMENTS_NAME[statement_kind]
|
218
217
|
)
|
219
218
|
|
220
|
-
def modify_name_for_controlled_qfunc(self, generated_name: str) -> str:
|
221
|
-
if self.control_variable is None:
|
222
|
-
return generated_name
|
223
|
-
return f"{CONTROLLED_PREFIX}{generated_name}"
|
224
|
-
|
225
219
|
def add_suffix_from_generated_name(self, generated_name: str, name: str) -> str:
|
226
220
|
if part_match := PART_SUFFIX_REGEX.match(generated_name):
|
227
221
|
suffix = f" [{part_match.group(1)}]"
|
@@ -272,7 +266,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
272
266
|
for register in self.registers
|
273
267
|
for qubit in register.qubit_indexes_absolute
|
274
268
|
if register.role is RegisterRole.INPUT
|
275
|
-
and register.name
|
269
|
+
and self.port_to_passed_variable_map.get(register.name, register.name)
|
270
|
+
== self.control_variable
|
276
271
|
)
|
277
272
|
|
278
273
|
def propagate_absolute_qubits(self) -> "FunctionDebugInfoInterface":
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from classiq.interface.ide.visual_model import Operation
|
4
|
+
|
5
|
+
|
6
|
+
class OperationRegistry:
|
7
|
+
def __init__(self) -> None:
|
8
|
+
self._operation_hash_to_op_id: dict[int, int] = {}
|
9
|
+
self._id_to_operations: dict[int, Operation] = {}
|
10
|
+
self._unique_op_counter = 0
|
11
|
+
self._deduped_op_counter = 0
|
12
|
+
|
13
|
+
def build_operation(self, **kwargs: Any) -> Operation:
|
14
|
+
operation = Operation(**kwargs)
|
15
|
+
return self.add_operation(operation)
|
16
|
+
|
17
|
+
def add_operation(self, op: Operation) -> Operation:
|
18
|
+
"""
|
19
|
+
Adds an operation to the global dictionaries for operations.
|
20
|
+
if operation already exist in the registry, it returns the existing operation.
|
21
|
+
"""
|
22
|
+
op_hash = hash(op)
|
23
|
+
if op_hash not in self._operation_hash_to_op_id:
|
24
|
+
self._operation_hash_to_op_id[op_hash] = op.id
|
25
|
+
self._id_to_operations[op.id] = op
|
26
|
+
self._unique_op_counter += 1
|
27
|
+
else:
|
28
|
+
self._deduped_op_counter += 1
|
29
|
+
op = self._id_to_operations[self._operation_hash_to_op_id[op_hash]]
|
30
|
+
return op
|
31
|
+
|
32
|
+
def get_operation_mapping(self) -> dict[int, Operation]:
|
33
|
+
return self._id_to_operations
|
34
|
+
|
35
|
+
def get_operations(self, op_ids: list[int]) -> list[Operation]:
|
36
|
+
"""
|
37
|
+
Returns a list of operations based on their IDs.
|
38
|
+
"""
|
39
|
+
return [self._id_to_operations[op_id] for op_id in op_ids]
|
40
|
+
|
41
|
+
def get_unique_op_number(self) -> int:
|
42
|
+
return self._unique_op_counter
|
43
|
+
|
44
|
+
def get_deduped_op_number(self) -> int:
|
45
|
+
return self._deduped_op_counter
|
@@ -1,5 +1,8 @@
|
|
1
|
+
import json
|
1
2
|
from collections import Counter
|
3
|
+
from collections.abc import Iterator
|
2
4
|
from functools import cached_property
|
5
|
+
from itertools import count
|
3
6
|
from typing import Any, Optional
|
4
7
|
|
5
8
|
import pydantic
|
@@ -13,6 +16,26 @@ from classiq.interface.generator.hardware.hardware_data import SynthesisHardware
|
|
13
16
|
from classiq.interface.helpers.versioned_model import VersionedModel
|
14
17
|
|
15
18
|
|
19
|
+
class OperationIdCounter:
|
20
|
+
_op_id_counter: Iterator[int] = count()
|
21
|
+
|
22
|
+
def next_id(self) -> int:
|
23
|
+
return next(self._op_id_counter)
|
24
|
+
|
25
|
+
def reset_operation_counter(self) -> None:
|
26
|
+
self._op_id_counter = count()
|
27
|
+
|
28
|
+
|
29
|
+
_operation_id_counter = OperationIdCounter()
|
30
|
+
|
31
|
+
|
32
|
+
def reset_operation_counter() -> None:
|
33
|
+
"""
|
34
|
+
Call this at the start of every new task to restart ids at 0.
|
35
|
+
"""
|
36
|
+
_operation_id_counter.reset_operation_counter()
|
37
|
+
|
38
|
+
|
16
39
|
class OperationType(StrEnum):
|
17
40
|
REGULAR = "REGULAR"
|
18
41
|
INVISIBLE = "INVISIBLE"
|
@@ -108,6 +131,7 @@ class AtomicGate(StrEnum):
|
|
108
131
|
|
109
132
|
class Operation(pydantic.BaseModel):
|
110
133
|
name: str
|
134
|
+
_id: int = pydantic.PrivateAttr(default_factory=_operation_id_counter.next_id)
|
111
135
|
qasm_name: str = pydantic.Field(default="")
|
112
136
|
details: str = pydantic.Field(default="")
|
113
137
|
children: list["Operation"] = pydantic.Field(default_factory=list)
|
@@ -120,7 +144,7 @@ class Operation(pydantic.BaseModel):
|
|
120
144
|
target_qubits: tuple[int, ...]
|
121
145
|
operation_level: OperationLevel
|
122
146
|
operation_type: OperationType = pydantic.Field(
|
123
|
-
description="Identifies unique operations that are visualized differently"
|
147
|
+
description="Identifies unique operations that are visualized differently",
|
124
148
|
)
|
125
149
|
gate: AtomicGate = pydantic.Field(
|
126
150
|
default=AtomicGate.UNKNOWN, description="Gate type"
|
@@ -129,9 +153,46 @@ class Operation(pydantic.BaseModel):
|
|
129
153
|
expanded: bool = pydantic.Field(default=False)
|
130
154
|
show_expanded_label: bool = pydantic.Field(default=False)
|
131
155
|
|
156
|
+
@property
|
157
|
+
def id(self) -> int:
|
158
|
+
return self._id
|
159
|
+
|
160
|
+
def __hash__(self) -> int:
|
161
|
+
"""
|
162
|
+
using a custom hashable_dict in order to compare the operation
|
163
|
+
with the qubits in order
|
164
|
+
"""
|
165
|
+
js = json.dumps(
|
166
|
+
self._hashable_dict(),
|
167
|
+
sort_keys=True,
|
168
|
+
default=lambda o: o.value if hasattr(o, "value") else str(o),
|
169
|
+
)
|
170
|
+
return hash(js)
|
171
|
+
|
172
|
+
def __eq__(self, other: object) -> bool:
|
173
|
+
return (
|
174
|
+
isinstance(other, Operation)
|
175
|
+
and self._hashable_dict() == other._hashable_dict()
|
176
|
+
)
|
177
|
+
|
178
|
+
def _hashable_dict(self) -> dict:
|
179
|
+
data = self.model_dump(
|
180
|
+
exclude_none=True,
|
181
|
+
)
|
182
|
+
# force qubit order for equality
|
183
|
+
for key in ("target_qubits", "auxiliary_qubits", "control_qubits"):
|
184
|
+
data[key] = sorted(data[key])
|
185
|
+
return data
|
186
|
+
|
132
187
|
|
133
188
|
class ProgramVisualModel(VersionedModel):
|
134
189
|
main_operation: Operation = pydantic.Field(default=None)
|
135
190
|
id_to_operations: dict[int, Operation] = pydantic.Field(default_factory=dict)
|
136
191
|
main_operation_id: int = pydantic.Field(default=None)
|
137
192
|
program_data: ProgramData
|
193
|
+
|
194
|
+
@property
|
195
|
+
def main_op_from_mapping(self) -> Operation:
|
196
|
+
if self.main_operation_id is None:
|
197
|
+
raise ValueError("Main operation ID is not set.")
|
198
|
+
return self.id_to_operations[self.main_operation_id]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import Literal, Optional
|
2
2
|
|
3
|
-
from classiq.interface.
|
3
|
+
from classiq.interface.generator.expressions.expression import Expression
|
4
4
|
from classiq.interface.model.handle_binding import ConcreteHandleBinding
|
5
5
|
from classiq.interface.model.quantum_statement import QuantumOperation
|
6
6
|
|
@@ -9,4 +9,14 @@ class SetBoundsStatement(QuantumOperation):
|
|
9
9
|
kind: Literal["SetBoundsStatement"]
|
10
10
|
|
11
11
|
target: ConcreteHandleBinding
|
12
|
-
|
12
|
+
lower_bound: Optional[Expression]
|
13
|
+
upper_bound: Optional[Expression]
|
14
|
+
|
15
|
+
@property
|
16
|
+
def expressions(self) -> list[Expression]:
|
17
|
+
exprs = []
|
18
|
+
if self.lower_bound is not None:
|
19
|
+
exprs.append(self.lower_bound)
|
20
|
+
if self.upper_bound is not None:
|
21
|
+
exprs.append(self.upper_bound)
|
22
|
+
return exprs
|
@@ -1,6 +1,8 @@
|
|
1
1
|
from collections.abc import Mapping, Sequence
|
2
2
|
from typing import Literal
|
3
3
|
|
4
|
+
from pydantic import PrivateAttr
|
5
|
+
|
4
6
|
from classiq.interface.enum_utils import StrEnum
|
5
7
|
from classiq.interface.generator.arith.arithmetic import (
|
6
8
|
ARITHMETIC_EXPRESSION_RESULT_NAME,
|
@@ -27,6 +29,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
27
29
|
kind: Literal["ArithmeticOperation"]
|
28
30
|
|
29
31
|
operation_kind: ArithmeticOperationKind
|
32
|
+
_classical_assignment: bool = PrivateAttr(default=False)
|
30
33
|
|
31
34
|
@property
|
32
35
|
def is_inplace(self) -> bool:
|
@@ -50,7 +53,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
50
53
|
self,
|
51
54
|
) -> Mapping[str, ConcreteHandleBinding]:
|
52
55
|
inouts = dict(super().wiring_inouts)
|
53
|
-
if self.is_inplace:
|
56
|
+
if self.is_inplace and not self._classical_assignment:
|
54
57
|
inouts[self.result_name()] = self.result_var
|
55
58
|
return inouts
|
56
59
|
|
@@ -60,7 +63,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
60
63
|
HandleMetadata(handle=handle, readable_location="in an expression")
|
61
64
|
for handle in self.var_handles
|
62
65
|
]
|
63
|
-
if self.is_inplace:
|
66
|
+
if self.is_inplace and not self._classical_assignment:
|
64
67
|
inouts.append(
|
65
68
|
HandleMetadata(
|
66
69
|
handle=self.result_var,
|
@@ -71,13 +74,13 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
71
74
|
|
72
75
|
@property
|
73
76
|
def wiring_outputs(self) -> Mapping[str, HandleBinding]:
|
74
|
-
if self.is_inplace:
|
77
|
+
if self.is_inplace or self._classical_assignment:
|
75
78
|
return {}
|
76
79
|
return super().wiring_outputs
|
77
80
|
|
78
81
|
@property
|
79
82
|
def readable_outputs(self) -> Sequence[HandleMetadata]:
|
80
|
-
if self.is_inplace:
|
83
|
+
if self.is_inplace or self._classical_assignment:
|
81
84
|
return []
|
82
85
|
return [
|
83
86
|
HandleMetadata(
|