classiq 0.61.0__py3-none-any.whl → 0.63.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/__init__.py +3 -0
- classiq/_internals/api_wrapper.py +6 -26
- classiq/_internals/client.py +1 -9
- classiq/applications/chemistry/chemistry_model_constructor.py +1 -1
- classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +26 -8
- classiq/applications/combinatorial_helpers/optimization_model.py +13 -2
- classiq/applications/combinatorial_helpers/pyomo_utils.py +143 -13
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +58 -23
- classiq/applications/grover/grover_model_constructor.py +1 -1
- classiq/applications/libraries/qmci_library.py +2 -1
- classiq/execution/execution_session.py +66 -96
- classiq/execution/jobs.py +12 -10
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +26 -5
- classiq/interface/backend/pydantic_backend.py +1 -1
- classiq/interface/backend/quantum_backend_providers.py +3 -1
- classiq/interface/chemistry/operator.py +0 -204
- classiq/interface/execution/primitives.py +1 -0
- classiq/interface/generator/compiler_keywords.py +4 -0
- classiq/interface/generator/copy.py +47 -0
- classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
- classiq/interface/generator/functions/type_name.py +6 -0
- classiq/interface/generator/generated_circuit_data.py +22 -7
- classiq/interface/generator/model/model.py +3 -0
- classiq/interface/generator/model/preferences/preferences.py +14 -1
- classiq/interface/generator/quantum_function_call.py +4 -2
- classiq/interface/generator/types/compilation_metadata.py +2 -1
- classiq/interface/model/handle_binding.py +50 -5
- classiq/interface/model/quantum_type.py +16 -0
- classiq/interface/server/routes.py +1 -3
- classiq/model_expansions/capturing/captured_vars.py +114 -28
- classiq/model_expansions/closure.py +25 -65
- classiq/model_expansions/function_builder.py +19 -9
- classiq/model_expansions/generative_functions.py +16 -2
- classiq/model_expansions/interpreter.py +110 -66
- classiq/model_expansions/model_tables.py +4 -0
- classiq/model_expansions/quantum_operations/call_emitter.py +83 -20
- classiq/model_expansions/quantum_operations/classicalif.py +1 -1
- classiq/model_expansions/quantum_operations/control.py +3 -10
- classiq/model_expansions/quantum_operations/emitter.py +3 -4
- classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -2
- classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -1
- classiq/model_expansions/quantum_operations/repeat.py +4 -3
- classiq/model_expansions/quantum_operations/shallow_emitter.py +9 -3
- classiq/model_expansions/scope.py +9 -13
- classiq/model_expansions/scope_initialization.py +34 -25
- classiq/model_expansions/transformers/var_splitter.py +57 -7
- classiq/open_library/__init__.py +4 -0
- classiq/open_library/functions/__init__.py +130 -0
- classiq/{qmod/builtins → open_library}/functions/amplitude_estimation.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/discrete_sine_cosine_transform.py +6 -4
- classiq/{qmod/builtins → open_library}/functions/grover.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/linear_pauli_rotation.py +1 -1
- classiq/{qmod/builtins → open_library}/functions/modular_exponentiation.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/qpe.py +2 -2
- classiq/{qmod/builtins → open_library}/functions/state_preparation.py +6 -149
- classiq/{qmod/builtins → open_library}/functions/swap_test.py +1 -1
- classiq/open_library/functions/utility_functions.py +81 -0
- classiq/{qmod/builtins → open_library}/functions/variational.py +1 -1
- classiq/qmod/builtins/functions/__init__.py +4 -130
- classiq/qmod/builtins/functions/allocation.py +150 -0
- classiq/qmod/builtins/functions/arithmetic.py +0 -34
- classiq/qmod/builtins/functions/operators.py +0 -6
- classiq/qmod/builtins/operations.py +19 -80
- classiq/qmod/create_model_function.py +8 -162
- classiq/qmod/generative.py +0 -16
- classiq/qmod/model_state_container.py +7 -0
- classiq/qmod/native/pretty_printer.py +10 -11
- classiq/qmod/pretty_print/pretty_printer.py +1 -1
- classiq/qmod/python_classical_type.py +1 -5
- classiq/qmod/qfunc.py +11 -12
- classiq/qmod/qmod_variable.py +1 -3
- classiq/qmod/quantum_expandable.py +23 -1
- classiq/qmod/quantum_function.py +69 -7
- {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/METADATA +2 -1
- {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/RECORD +82 -78
- classiq/qmod/builtins/functions/utility_functions.py +0 -43
- /classiq/{qmod/builtins → open_library}/functions/hea.py +0 -0
- /classiq/{qmod/builtins → open_library}/functions/qaoa_penalty.py +0 -0
- /classiq/{qmod/builtins → open_library}/functions/qft_functions.py +0 -0
- /classiq/{qmod/builtins → open_library}/functions/qsvt.py +0 -0
- {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/WHEEL +0 -0
classiq/__init__.py
CHANGED
@@ -46,6 +46,8 @@ from classiq.executor import (
|
|
46
46
|
execute_async,
|
47
47
|
set_quantum_program_execution_preferences,
|
48
48
|
)
|
49
|
+
from classiq.open_library import * # noqa: F403
|
50
|
+
from classiq.open_library import __all__ as _open_library_all
|
49
51
|
from classiq.qmod import * # noqa: F403
|
50
52
|
from classiq.qmod import __all__ as _qmod_all
|
51
53
|
from classiq.synthesis import (
|
@@ -114,6 +116,7 @@ __all__ = (
|
|
114
116
|
+ _sub_modules
|
115
117
|
+ _application_constructors_all
|
116
118
|
+ _qmod_all
|
119
|
+
+ _open_library_all
|
117
120
|
)
|
118
121
|
|
119
122
|
|
@@ -33,10 +33,6 @@ from classiq._internals.client import client
|
|
33
33
|
from classiq._internals.jobs import JobPoller
|
34
34
|
|
35
35
|
ResultType = TypeVar("ResultType", bound=pydantic.BaseModel)
|
36
|
-
CLASSIQ_ACCEPT_HEADER = "X-Classiq-Accept-Version"
|
37
|
-
|
38
|
-
_ACCEPT_HEADER = "X-Classiq-Accept-Version"
|
39
|
-
_CONTENT_TYPE_HEADER = "X-Classiq-Content-Type-Version"
|
40
36
|
|
41
37
|
|
42
38
|
class HTTPMethod(StrEnum):
|
@@ -143,16 +139,10 @@ class ApiWrapper:
|
|
143
139
|
execution_input: dict,
|
144
140
|
http_client: Optional[httpx.AsyncClient] = None,
|
145
141
|
) -> execution_request.ExecutionJobDetails:
|
146
|
-
headers = {
|
147
|
-
_ACCEPT_HEADER: "v1",
|
148
|
-
_CONTENT_TYPE_HEADER: execution_input["version"],
|
149
|
-
}
|
150
142
|
data = await cls._call_task(
|
151
143
|
http_method=HTTPMethod.POST,
|
152
|
-
|
153
|
-
url=routes.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH,
|
144
|
+
url=routes.EXECUTION_JOBS_FULL_PATH,
|
154
145
|
body=execution_input,
|
155
|
-
use_versioned_url=False,
|
156
146
|
http_client=http_client,
|
157
147
|
)
|
158
148
|
return execution_request.ExecutionJobDetails.model_validate(data)
|
@@ -163,12 +153,9 @@ class ApiWrapper:
|
|
163
153
|
job_id: JobID,
|
164
154
|
http_client: Optional[httpx.AsyncClient] = None,
|
165
155
|
) -> execution_request.ExecutionJobDetails:
|
166
|
-
headers = {_ACCEPT_HEADER: "v1"}
|
167
156
|
data = await cls._call_task(
|
168
157
|
http_method=HTTPMethod.GET,
|
169
|
-
|
170
|
-
url=f"{routes.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH}/{job_id.job_id}",
|
171
|
-
use_versioned_url=False,
|
158
|
+
url=f"{routes.EXECUTION_JOBS_FULL_PATH}/{job_id.job_id}",
|
172
159
|
http_client=http_client,
|
173
160
|
)
|
174
161
|
return execution_request.ExecutionJobDetails.model_validate(data)
|
@@ -177,14 +164,11 @@ class ApiWrapper:
|
|
177
164
|
async def call_get_execution_job_result(
|
178
165
|
cls,
|
179
166
|
job_id: JobID,
|
180
|
-
version: str,
|
181
167
|
http_client: Optional[httpx.AsyncClient] = None,
|
182
168
|
) -> classiq.interface.executor.execution_result.ExecuteGeneratedCircuitResults:
|
183
169
|
data = await cls._call_task(
|
184
170
|
http_method=HTTPMethod.GET,
|
185
|
-
url=f"{routes.
|
186
|
-
use_versioned_url=False,
|
187
|
-
headers={CLASSIQ_ACCEPT_HEADER: version},
|
171
|
+
url=f"{routes.EXECUTION_JOBS_FULL_PATH}/{job_id.job_id}/result",
|
188
172
|
http_client=http_client,
|
189
173
|
)
|
190
174
|
return classiq.interface.executor.execution_result.ExecuteGeneratedCircuitResults.model_validate(
|
@@ -200,11 +184,10 @@ class ApiWrapper:
|
|
200
184
|
) -> ExecutionJobDetailsV1:
|
201
185
|
data = await cls._call_task(
|
202
186
|
http_method=HTTPMethod.PATCH,
|
203
|
-
url=f"{routes.
|
187
|
+
url=f"{routes.EXECUTION_JOBS_FULL_PATH}/{job_id.job_id}",
|
204
188
|
params={
|
205
189
|
"name": name,
|
206
190
|
},
|
207
|
-
use_versioned_url=False,
|
208
191
|
http_client=http_client,
|
209
192
|
)
|
210
193
|
return ExecutionJobDetailsV1.model_validate(data)
|
@@ -217,8 +200,7 @@ class ApiWrapper:
|
|
217
200
|
) -> None:
|
218
201
|
await cls._call_task(
|
219
202
|
http_method=HTTPMethod.PUT,
|
220
|
-
url=f"{routes.
|
221
|
-
use_versioned_url=False,
|
203
|
+
url=f"{routes.EXECUTION_JOBS_FULL_PATH}/{job_id.job_id}/cancel",
|
222
204
|
allow_none=True,
|
223
205
|
http_client=http_client,
|
224
206
|
)
|
@@ -232,12 +214,11 @@ class ApiWrapper:
|
|
232
214
|
) -> ExecutionJobsQueryResultsV1:
|
233
215
|
data = await cls._call_task(
|
234
216
|
http_method=HTTPMethod.GET,
|
235
|
-
url=f"{routes.
|
217
|
+
url=f"{routes.EXECUTION_JOBS_FULL_PATH}",
|
236
218
|
params={
|
237
219
|
"offset": offset,
|
238
220
|
"limit": limit,
|
239
221
|
},
|
240
|
-
use_versioned_url=False,
|
241
222
|
http_client=http_client,
|
242
223
|
)
|
243
224
|
return ExecutionJobsQueryResultsV1.model_validate(data)
|
@@ -395,7 +376,6 @@ class ApiWrapper:
|
|
395
376
|
) -> operator.PauliOperator:
|
396
377
|
poller = JobPoller(
|
397
378
|
base_url=routes.GENERATE_HAMILTONIAN_FULL_PATH,
|
398
|
-
use_versioned_url=False,
|
399
379
|
)
|
400
380
|
result = await poller.run_pydantic(
|
401
381
|
problem, timeout_sec=None, http_client=http_client
|
classiq/_internals/client.py
CHANGED
@@ -9,14 +9,7 @@ import sys
|
|
9
9
|
import time
|
10
10
|
from collections.abc import Awaitable
|
11
11
|
from types import TracebackType
|
12
|
-
from typing import
|
13
|
-
Any,
|
14
|
-
Callable,
|
15
|
-
NoReturn,
|
16
|
-
Optional,
|
17
|
-
TypeVar,
|
18
|
-
Union,
|
19
|
-
)
|
12
|
+
from typing import Any, Callable, NoReturn, Optional, TypeVar, Union
|
20
13
|
|
21
14
|
import httpx
|
22
15
|
from typing_extensions import ParamSpec
|
@@ -170,7 +163,6 @@ class Client:
|
|
170
163
|
_UNKNOWN_VERSION = HostChecker._UNKNOWN_VERSION
|
171
164
|
_SESSION_HEADER = "Classiq-Session"
|
172
165
|
_WARNINGS_HEADER = "X-Classiq-Warnings"
|
173
|
-
_LATEST_VERSION_API_PREFIX = "/api/v1"
|
174
166
|
_HTTP_TIMEOUT_SECONDS = 3600 # Needs to be synced with load-balancer timeout
|
175
167
|
|
176
168
|
def __init__(self, conf: config.Configuration) -> None:
|
@@ -53,7 +53,7 @@ from classiq.applications.chemistry.chemistry_execution_parameters import (
|
|
53
53
|
)
|
54
54
|
from classiq.interface.exceptions import ClassiqError
|
55
55
|
from classiq.qmod.utilities import qmod_val_to_expr_str
|
56
|
-
from classiq.
|
56
|
+
from classiq.open_library.functions.hea import full_hea
|
57
57
|
|
58
58
|
_LADDER_OPERATOR_TYPE_INDICATOR_TO_QMOD_MAPPING: dict[str, str] = {
|
59
59
|
"+": "PLUS",
|
@@ -7,6 +7,7 @@ import pyomo.environ as pyo
|
|
7
7
|
|
8
8
|
from classiq.interface.combinatorial_optimization.sense import is_maximization
|
9
9
|
from classiq.interface.combinatorial_optimization.solver_types import QSolver
|
10
|
+
from classiq.interface.exceptions import ClassiqValueError
|
10
11
|
from classiq.interface.executor.vqe_result import VQESolverResult
|
11
12
|
from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
|
12
13
|
|
@@ -20,6 +21,7 @@ from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import
|
|
20
21
|
pauli_operator_to_hamiltonian,
|
21
22
|
)
|
22
23
|
from classiq.applications.combinatorial_helpers.pyomo_utils import (
|
24
|
+
add_var_domain_constraints,
|
23
25
|
convert_pyomo_to_global_presentation,
|
24
26
|
evaluate_objective,
|
25
27
|
pyomo_to_qmod_qstruct,
|
@@ -56,14 +58,30 @@ def pyo_model_to_hamiltonian(
|
|
56
58
|
def pyo_model_to_qmod_problem(
|
57
59
|
pyo_model: pyo.ConcreteModel, penalty_energy: float
|
58
60
|
) -> tuple[type[QStruct], Callable]:
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
61
|
+
with add_var_domain_constraints(pyo_model):
|
62
|
+
optimization_model = OptimizationModel(
|
63
|
+
pyo_model, penalty_energy=penalty_energy, qsolver=QSolver.QAOAPenalty
|
64
|
+
)
|
65
|
+
_validate_var_domains(optimization_model)
|
66
|
+
qmod_struct = pyomo_to_qmod_qstruct(
|
67
|
+
"QAOAVars", optimization_model.vars_not_encoded
|
68
|
+
)
|
69
|
+
cost_func = partial(
|
70
|
+
evaluate_objective, *optimization_model.objective_not_encoded_sympy
|
71
|
+
)
|
72
|
+
return qmod_struct, cost_func
|
73
|
+
|
74
|
+
|
75
|
+
def _validate_var_domains(model: OptimizationModel) -> None:
|
76
|
+
for var in model.vars_not_encoded:
|
77
|
+
if (
|
78
|
+
isinstance(var.bounds, tuple)
|
79
|
+
and len(var.bounds) == 2
|
80
|
+
and var.bounds[0] != 0
|
81
|
+
):
|
82
|
+
raise ClassiqValueError(
|
83
|
+
f"Bounds of variable {var.local_name} must start at 0, got {var.bounds}"
|
84
|
+
)
|
67
85
|
|
68
86
|
|
69
87
|
def _str_to_list_int(str_ints: str) -> list[int]:
|
@@ -23,7 +23,11 @@ from classiq.applications.combinatorial_helpers import (
|
|
23
23
|
)
|
24
24
|
from classiq.applications.combinatorial_helpers.encoding_mapping import EncodingMapping
|
25
25
|
from classiq.applications.combinatorial_helpers.memory import InternalQuantumReg
|
26
|
-
from classiq.applications.combinatorial_helpers.pyomo_utils import
|
26
|
+
from classiq.applications.combinatorial_helpers.pyomo_utils import (
|
27
|
+
get_field_name,
|
28
|
+
index_as_tuple,
|
29
|
+
is_index_var,
|
30
|
+
)
|
27
31
|
from classiq.applications.combinatorial_helpers.transformations import (
|
28
32
|
encoding,
|
29
33
|
ising_converter,
|
@@ -176,7 +180,14 @@ class OptimizationModel:
|
|
176
180
|
for key, value in penalty_map.pyomo2sympy.items():
|
177
181
|
objective_map.pyomo2sympy[key] = value
|
178
182
|
sympy_mapping = {
|
179
|
-
sympy_var:
|
183
|
+
sympy_var: (
|
184
|
+
get_field_name(pyomo_var)
|
185
|
+
if not is_index_var(pyomo_var)
|
186
|
+
else (
|
187
|
+
get_field_name(pyomo_var.parent_component()),
|
188
|
+
index_as_tuple(pyomo_var.index()),
|
189
|
+
)
|
190
|
+
)
|
180
191
|
for pyomo_var, sympy_var in objective_map.pyomo2sympy.items()
|
181
192
|
}
|
182
193
|
self.objective_not_encoded_sympy = sympy_mapping, objective_expr
|
@@ -1,6 +1,11 @@
|
|
1
1
|
import math
|
2
2
|
import re
|
3
|
+
from collections import defaultdict
|
4
|
+
from collections.abc import Iterator
|
5
|
+
from contextlib import contextmanager
|
3
6
|
from enum import Enum
|
7
|
+
from functools import reduce
|
8
|
+
from operator import mul
|
4
9
|
from typing import Any, Optional, TypeVar, Union
|
5
10
|
|
6
11
|
import pydantic
|
@@ -13,6 +18,7 @@ from pyomo.core.base.component import ComponentData
|
|
13
18
|
from pyomo.core.base.constraint import _GeneralConstraintData
|
14
19
|
from pyomo.core.base.indexed_component import IndexedComponent
|
15
20
|
from pyomo.core.base.objective import ScalarObjective
|
21
|
+
from pyomo.core.expr.base import ExpressionBase
|
16
22
|
from pyomo.core.expr.sympy_tools import (
|
17
23
|
Pyomo2SympyVisitor,
|
18
24
|
PyomoSympyBimap,
|
@@ -24,7 +30,7 @@ from classiq.interface.generator.expressions.expression import Expression
|
|
24
30
|
from classiq.interface.generator.functions.classical_type import Integer
|
25
31
|
from classiq.interface.generator.types.struct_declaration import StructDeclaration
|
26
32
|
|
27
|
-
from classiq.qmod.qmod_variable import QBit, QNum, QStruct, QVar
|
33
|
+
from classiq.qmod.qmod_variable import QArray, QBit, QNum, QStruct, QVar
|
28
34
|
from classiq.qmod.symbolic_expr import SymbolicExpr
|
29
35
|
|
30
36
|
ListVars = list[_GeneralVarData]
|
@@ -262,14 +268,74 @@ def pyomo_to_qmod_qstruct(
|
|
262
268
|
struct_name: str, vars: list[_GeneralVarData]
|
263
269
|
) -> type[QStruct]:
|
264
270
|
qmod_struct = type(struct_name, (QStruct,), {})
|
265
|
-
|
266
|
-
qmod_struct.__annotations__ = {
|
267
|
-
var_name: _get_qmod_field_type(var_name, var_data)
|
268
|
-
for var_name, var_data in var_names.items()
|
269
|
-
}
|
271
|
+
qmod_struct.__annotations__ = _get_qstruct_fields(vars)
|
270
272
|
return qmod_struct
|
271
273
|
|
272
274
|
|
275
|
+
def _get_qstruct_fields(vars: list[_GeneralVarData]) -> dict[str, type[QVar]]:
|
276
|
+
array_type_sizes = _get_array_sizes(vars)
|
277
|
+
fields: dict[str, type[QVar]] = {}
|
278
|
+
for var in vars:
|
279
|
+
_add_qmod_field(var, array_type_sizes, fields)
|
280
|
+
return fields
|
281
|
+
|
282
|
+
|
283
|
+
def _get_array_sizes(vars: list[_GeneralVarData]) -> dict[str, tuple[int, ...]]:
|
284
|
+
array_types: dict[str, set[tuple]] = defaultdict(set)
|
285
|
+
for var in vars:
|
286
|
+
if is_index_var(var):
|
287
|
+
array_types[get_field_name(var.parent_component())].add(
|
288
|
+
index_as_tuple(var.index())
|
289
|
+
)
|
290
|
+
return {
|
291
|
+
name: dimensions
|
292
|
+
for name, indices in array_types.items()
|
293
|
+
if (dimensions := _get_indices_dimensions(indices)) is not None
|
294
|
+
}
|
295
|
+
|
296
|
+
|
297
|
+
def _get_indices_dimensions(indices: set[tuple[int, ...]]) -> Optional[tuple[int, ...]]:
|
298
|
+
indices_list = list(indices)
|
299
|
+
if len(indices) == 0:
|
300
|
+
return None
|
301
|
+
first_idx = indices_list[0]
|
302
|
+
if len(first_idx) == 0:
|
303
|
+
return None
|
304
|
+
if any(len(idx) != len(first_idx) for idx in indices_list[1:]):
|
305
|
+
return None
|
306
|
+
dimension_bounds = [(idx, idx) for idx in first_idx]
|
307
|
+
for multi_idx in indices_list[1:]:
|
308
|
+
for dim_idx, idx in enumerate(multi_idx):
|
309
|
+
dimension_bounds[dim_idx] = (
|
310
|
+
min(dimension_bounds[dim_idx][0], idx),
|
311
|
+
max(dimension_bounds[dim_idx][1], idx),
|
312
|
+
)
|
313
|
+
if any(lb != 0 for lb, ub in dimension_bounds):
|
314
|
+
return None
|
315
|
+
dimensions = tuple(ub + 1 for _, ub in dimension_bounds)
|
316
|
+
if reduce(mul, dimensions) != len(indices_list):
|
317
|
+
return None
|
318
|
+
return dimensions
|
319
|
+
|
320
|
+
|
321
|
+
def _add_qmod_field(
|
322
|
+
var: _GeneralVarData,
|
323
|
+
array_type_sizes: dict[str, tuple[int, ...]],
|
324
|
+
fields: dict[str, type[QVar]],
|
325
|
+
) -> None:
|
326
|
+
parent_name = get_field_name(var.parent_component())
|
327
|
+
if parent_name not in array_type_sizes:
|
328
|
+
var_name = get_field_name(var)
|
329
|
+
fields[var_name] = _get_qmod_field_type(var_name, var)
|
330
|
+
return
|
331
|
+
dimensions = array_type_sizes[parent_name]
|
332
|
+
if index_as_tuple(var.index()) == tuple(0 for _ in range(len(dimensions))):
|
333
|
+
qmod_type: type[QVar] = _get_qmod_field_type(parent_name, var)
|
334
|
+
for dim in reversed(dimensions):
|
335
|
+
qmod_type = QArray[qmod_type, dim] # type:ignore[valid-type]
|
336
|
+
fields[parent_name] = qmod_type
|
337
|
+
|
338
|
+
|
273
339
|
def _get_qmod_field_type(var_name: str, var_data: _GeneralVarData) -> type[QVar]:
|
274
340
|
if var_data.domain not in SUPPORTED_TYPES:
|
275
341
|
raise ClassiqValueError(
|
@@ -287,18 +353,22 @@ def _get_qmod_field_type(var_name: str, var_data: _GeneralVarData) -> type[QVar]
|
|
287
353
|
raise ClassiqValueError(
|
288
354
|
f"Non-integer bounds for variable {var_name!r} are not supported"
|
289
355
|
)
|
290
|
-
if lb > 0:
|
291
|
-
ub -= lb
|
292
356
|
qnum: Any = QNum # mypy shenanigans
|
293
|
-
return qnum[math.ceil(math.log2(ub - lb)), False, 0]
|
357
|
+
return qnum[math.ceil(math.log2(ub - lb + 1)), False, 0]
|
294
358
|
|
295
359
|
|
296
360
|
def evaluate_objective(
|
297
|
-
var_mapping: dict,
|
361
|
+
var_mapping: dict[Any, Union[str, tuple[str, tuple[int, ...]]]],
|
362
|
+
sympy_expr: sympy.Expr,
|
363
|
+
struct_obj: Any,
|
298
364
|
) -> Any:
|
299
365
|
sympy_assignment = {
|
300
|
-
sympy_var:
|
301
|
-
|
366
|
+
sympy_var: (
|
367
|
+
getattr(struct_obj, field_accessor)
|
368
|
+
if isinstance(field_accessor, str)
|
369
|
+
else _get_item(getattr(struct_obj, field_accessor[0]), field_accessor[1])
|
370
|
+
)
|
371
|
+
for sympy_var, field_accessor in var_mapping.items()
|
302
372
|
}
|
303
373
|
|
304
374
|
# classical objective evaluation
|
@@ -313,5 +383,65 @@ def evaluate_objective(
|
|
313
383
|
return SymbolicExpr(expr=expr_str, is_quantum=True)
|
314
384
|
|
315
385
|
|
386
|
+
def _get_item(obj: Any, multi_index: tuple[int, ...]) -> Any:
|
387
|
+
for idx in multi_index:
|
388
|
+
obj = obj[idx]
|
389
|
+
return obj
|
390
|
+
|
391
|
+
|
316
392
|
def get_field_name(var: _GeneralVarData) -> str:
|
317
|
-
return var.local_name.replace("[", "_").replace("]", "")
|
393
|
+
return var.local_name.replace("[", "_").replace("]", "").replace(",", "_")
|
394
|
+
|
395
|
+
|
396
|
+
def is_index_var(var: _GeneralVarData) -> bool:
|
397
|
+
index = var.index()
|
398
|
+
return isinstance(index, int) or (
|
399
|
+
isinstance(index, tuple) and all(isinstance(idx, int) for idx in index)
|
400
|
+
)
|
401
|
+
|
402
|
+
|
403
|
+
def index_as_tuple(index: Union[int, tuple[int, ...]]) -> tuple[int, ...]:
|
404
|
+
if isinstance(index, int):
|
405
|
+
return (index,)
|
406
|
+
return index
|
407
|
+
|
408
|
+
|
409
|
+
@contextmanager
|
410
|
+
def add_var_domain_constraints(model: ConcreteModel) -> Iterator[None]:
|
411
|
+
vars = extract(model, _GeneralVarData)
|
412
|
+
constraints = [
|
413
|
+
constraint
|
414
|
+
for var in vars
|
415
|
+
if (constraint := _get_var_domain_constraint(var)) is not None
|
416
|
+
]
|
417
|
+
if len(constraints) == 0:
|
418
|
+
yield
|
419
|
+
return
|
420
|
+
model.var_domain_constraints = pyo.ConstraintList()
|
421
|
+
for constraint in constraints:
|
422
|
+
model.var_domain_constraints.add(constraint)
|
423
|
+
yield
|
424
|
+
model.del_component("var_domain_constraints")
|
425
|
+
|
426
|
+
|
427
|
+
def _get_var_domain_constraint(var: _GeneralVarData) -> Optional[ExpressionBase]:
|
428
|
+
bounds = var.bounds
|
429
|
+
if (
|
430
|
+
type(bounds) is not tuple
|
431
|
+
or len(bounds) != 2
|
432
|
+
or not all(isinstance(bounds[idx], int) for idx in (0, 1))
|
433
|
+
):
|
434
|
+
raise ClassiqValueError(
|
435
|
+
f"Missing bounds for variable {var.local_name}. Expected both lower and "
|
436
|
+
f"upper bounds, got {bounds}"
|
437
|
+
)
|
438
|
+
lb, ub = bounds
|
439
|
+
if ub < lb:
|
440
|
+
raise ClassiqValueError(
|
441
|
+
f"Illegal bounds for variable {var.local_name}. The upper bound ({ub}) is "
|
442
|
+
f"lesser than the lower bound ({lb})"
|
443
|
+
)
|
444
|
+
ub_norm = ub - lb + 1
|
445
|
+
if ub_norm & (ub_norm - 1) == 0:
|
446
|
+
return None
|
447
|
+
return var <= ub
|
classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py
CHANGED
@@ -33,7 +33,7 @@ from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import
|
|
33
33
|
_pauli_terms_to_qmod,
|
34
34
|
)
|
35
35
|
from classiq.applications.combinatorial_optimization import OptimizerConfig, QAOAConfig
|
36
|
-
from classiq.
|
36
|
+
from classiq.open_library.functions.qaoa_penalty import qaoa_penalty
|
37
37
|
|
38
38
|
|
39
39
|
def construct_combi_opt_py_model(
|
@@ -6,20 +6,24 @@ 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
|
9
10
|
|
10
11
|
from classiq.interface.executor.execution_preferences import ExecutionPreferences
|
11
12
|
from classiq.interface.executor.result import ExecutionDetails
|
12
13
|
from classiq.interface.model.model import SerializedModel
|
13
14
|
|
15
|
+
from classiq import Constraints, Preferences
|
14
16
|
from classiq.applications.combinatorial_helpers.combinatorial_problem_utils import (
|
15
17
|
pyo_model_to_qmod_problem,
|
16
18
|
)
|
17
19
|
from classiq.execution import ExecutionSession
|
20
|
+
from classiq.open_library.functions.utility_functions import (
|
21
|
+
apply_to_all,
|
22
|
+
hadamard_transform,
|
23
|
+
)
|
18
24
|
from classiq.qmod.builtins.functions import (
|
19
25
|
RX,
|
20
26
|
allocate,
|
21
|
-
apply_to_all,
|
22
|
-
hadamard_transform,
|
23
27
|
)
|
24
28
|
from classiq.qmod.builtins.operations import phase, repeat
|
25
29
|
from classiq.qmod.cparam import CReal
|
@@ -37,7 +41,7 @@ class CombinatorialProblem:
|
|
37
41
|
num_layers: int,
|
38
42
|
penalty_factor: int = 1,
|
39
43
|
):
|
40
|
-
self.problem_vars_, self.
|
44
|
+
self.problem_vars_, self.cost_func = pyo_model_to_qmod_problem(
|
41
45
|
pyo_model, penalty_factor
|
42
46
|
)
|
43
47
|
self.num_layers_ = num_layers
|
@@ -45,8 +49,26 @@ class CombinatorialProblem:
|
|
45
49
|
self.qprog_ = None
|
46
50
|
self.es_ = None
|
47
51
|
self.optimized_params_ = None
|
52
|
+
self.params_trace_: list[np.ndarray] = []
|
53
|
+
self.cost_trace_: list = []
|
48
54
|
|
49
|
-
|
55
|
+
@property
|
56
|
+
def cost_trace(self) -> list:
|
57
|
+
return self.cost_trace_
|
58
|
+
|
59
|
+
@property
|
60
|
+
def params_trace(self) -> list[np.ndarray]:
|
61
|
+
return self.params_trace_
|
62
|
+
|
63
|
+
@property
|
64
|
+
def optimized_params(self) -> list:
|
65
|
+
return self.optimized_params_ # type:ignore[return-value]
|
66
|
+
|
67
|
+
def get_model(
|
68
|
+
self,
|
69
|
+
constraints: Optional[Constraints] = None,
|
70
|
+
preferences: Optional[Preferences] = None,
|
71
|
+
) -> SerializedModel:
|
50
72
|
@qfunc
|
51
73
|
def main(
|
52
74
|
params: CArray[CReal, self.num_layers_ * 2], # type:ignore[valid-type]
|
@@ -58,13 +80,15 @@ class CombinatorialProblem:
|
|
58
80
|
self.num_layers_,
|
59
81
|
lambda i: [ # type:ignore[arg-type]
|
60
82
|
phase(
|
61
|
-
-self.
|
83
|
+
-self.cost_func(v), params[i]
|
62
84
|
), # type:ignore[func-returns-value]
|
63
85
|
apply_to_all(lambda q: RX(params[self.num_layers_ + i], q), v),
|
64
86
|
],
|
65
87
|
)
|
66
88
|
|
67
|
-
self.model_ = create_model(
|
89
|
+
self.model_ = create_model(
|
90
|
+
main, constraints=constraints, preferences=preferences
|
91
|
+
) # type:ignore[assignment]
|
68
92
|
return self.model_ # type:ignore[return-value]
|
69
93
|
|
70
94
|
def get_qprog(self) -> SerializedQuantumProgram:
|
@@ -77,7 +101,6 @@ class CombinatorialProblem:
|
|
77
101
|
self,
|
78
102
|
execution_preferences: Optional[ExecutionPreferences] = None,
|
79
103
|
maxiter: int = 20,
|
80
|
-
cost_trace: Optional[list[float]] = None,
|
81
104
|
quantile: float = 1.0,
|
82
105
|
) -> list[float]:
|
83
106
|
if self.qprog_ is None:
|
@@ -85,40 +108,52 @@ class CombinatorialProblem:
|
|
85
108
|
self.es_ = ExecutionSession(
|
86
109
|
self.qprog_, execution_preferences # type:ignore[assignment,arg-type]
|
87
110
|
)
|
111
|
+
self.params_trace_ = []
|
112
|
+
self.cost_trace_ = []
|
88
113
|
|
89
114
|
def estimate_cost_wrapper(params: np.ndarray) -> float:
|
90
115
|
cost = self.es_.estimate_cost( # type:ignore[attr-defined]
|
91
|
-
lambda state: self.
|
116
|
+
lambda state: self.cost_func(state["v"]),
|
92
117
|
{"params": params.tolist()},
|
93
118
|
quantile=quantile,
|
94
119
|
)
|
95
|
-
|
96
|
-
|
120
|
+
self.cost_trace_.append(cost)
|
121
|
+
self.params_trace_.append(params)
|
97
122
|
return cost
|
98
123
|
|
99
124
|
initial_params = (
|
100
125
|
np.concatenate(
|
101
126
|
(
|
102
|
-
np.linspace(
|
103
|
-
np.linspace(1,
|
127
|
+
np.linspace(1 / self.num_layers_, 1, self.num_layers_),
|
128
|
+
np.linspace(1, 1 / self.num_layers_, self.num_layers_),
|
104
129
|
)
|
105
130
|
)
|
106
131
|
* math.pi
|
107
132
|
)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
133
|
+
|
134
|
+
with tqdm(total=maxiter, desc="Optimization Progress", leave=True) as pbar:
|
135
|
+
|
136
|
+
def _minimze_callback(xk: np.ndarray) -> None:
|
137
|
+
pbar.update(1) # increment progress bar
|
138
|
+
self.optimized_params_ = xk.tolist() # save recent optimized value
|
139
|
+
|
140
|
+
self.optimized_params_ = scipy.optimize.minimize(
|
141
|
+
estimate_cost_wrapper,
|
142
|
+
callback=_minimze_callback,
|
143
|
+
x0=initial_params,
|
144
|
+
method="COBYLA",
|
145
|
+
options={"maxiter": maxiter},
|
146
|
+
).x.tolist()
|
147
|
+
|
114
148
|
return self.optimized_params_ # type:ignore[return-value]
|
115
149
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
150
|
+
def sample_uniform(self) -> pd.DataFrame:
|
151
|
+
return self.sample([0] * self.num_layers_ * 2)
|
152
|
+
|
153
|
+
def sample(self, params: list) -> pd.DataFrame:
|
119
154
|
assert self.es_ is not None
|
120
155
|
res = self.es_.sample( # type:ignore[unreachable]
|
121
|
-
{"params":
|
156
|
+
{"params": params}
|
122
157
|
)
|
123
158
|
parsed_result = [
|
124
159
|
{
|
@@ -128,7 +163,7 @@ class CombinatorialProblem:
|
|
128
163
|
if not re.match(".*_slack_var_.*", key)
|
129
164
|
},
|
130
165
|
"probability": sampled.shots / res.num_shots,
|
131
|
-
"cost": self.
|
166
|
+
"cost": self.cost_func(sampled.state["v"]),
|
132
167
|
}
|
133
168
|
for sampled in res.parsed_counts
|
134
169
|
]
|
@@ -19,7 +19,7 @@ from classiq.interface.model.variable_declaration_statement import (
|
|
19
19
|
)
|
20
20
|
|
21
21
|
from classiq import RegisterUserInput
|
22
|
-
from classiq.
|
22
|
+
from classiq.open_library.functions.grover import grover_search, phase_oracle
|
23
23
|
|
24
24
|
_OUTPUT_VARIABLE_NAME = "result"
|
25
25
|
|
@@ -1,4 +1,5 @@
|
|
1
|
-
from classiq.
|
1
|
+
from classiq.open_library.functions.amplitude_estimation import amplitude_estimation
|
2
|
+
from classiq.qmod.builtins.functions import Z
|
2
3
|
from classiq.qmod.qfunc import qfunc
|
3
4
|
from classiq.qmod.qmod_variable import QArray, QBit, QNum
|
4
5
|
from classiq.qmod.quantum_callable import QCallable
|