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.
Files changed (83) hide show
  1. classiq/__init__.py +3 -0
  2. classiq/_internals/api_wrapper.py +6 -26
  3. classiq/_internals/client.py +1 -9
  4. classiq/applications/chemistry/chemistry_model_constructor.py +1 -1
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +26 -8
  6. classiq/applications/combinatorial_helpers/optimization_model.py +13 -2
  7. classiq/applications/combinatorial_helpers/pyomo_utils.py +143 -13
  8. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
  9. classiq/applications/combinatorial_optimization/combinatorial_problem.py +58 -23
  10. classiq/applications/grover/grover_model_constructor.py +1 -1
  11. classiq/applications/libraries/qmci_library.py +2 -1
  12. classiq/execution/execution_session.py +66 -96
  13. classiq/execution/jobs.py +12 -10
  14. classiq/interface/_version.py +1 -1
  15. classiq/interface/backend/backend_preferences.py +26 -5
  16. classiq/interface/backend/pydantic_backend.py +1 -1
  17. classiq/interface/backend/quantum_backend_providers.py +3 -1
  18. classiq/interface/chemistry/operator.py +0 -204
  19. classiq/interface/execution/primitives.py +1 -0
  20. classiq/interface/generator/compiler_keywords.py +4 -0
  21. classiq/interface/generator/copy.py +47 -0
  22. classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
  23. classiq/interface/generator/functions/type_name.py +6 -0
  24. classiq/interface/generator/generated_circuit_data.py +22 -7
  25. classiq/interface/generator/model/model.py +3 -0
  26. classiq/interface/generator/model/preferences/preferences.py +14 -1
  27. classiq/interface/generator/quantum_function_call.py +4 -2
  28. classiq/interface/generator/types/compilation_metadata.py +2 -1
  29. classiq/interface/model/handle_binding.py +50 -5
  30. classiq/interface/model/quantum_type.py +16 -0
  31. classiq/interface/server/routes.py +1 -3
  32. classiq/model_expansions/capturing/captured_vars.py +114 -28
  33. classiq/model_expansions/closure.py +25 -65
  34. classiq/model_expansions/function_builder.py +19 -9
  35. classiq/model_expansions/generative_functions.py +16 -2
  36. classiq/model_expansions/interpreter.py +110 -66
  37. classiq/model_expansions/model_tables.py +4 -0
  38. classiq/model_expansions/quantum_operations/call_emitter.py +83 -20
  39. classiq/model_expansions/quantum_operations/classicalif.py +1 -1
  40. classiq/model_expansions/quantum_operations/control.py +3 -10
  41. classiq/model_expansions/quantum_operations/emitter.py +3 -4
  42. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -2
  43. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -1
  44. classiq/model_expansions/quantum_operations/repeat.py +4 -3
  45. classiq/model_expansions/quantum_operations/shallow_emitter.py +9 -3
  46. classiq/model_expansions/scope.py +9 -13
  47. classiq/model_expansions/scope_initialization.py +34 -25
  48. classiq/model_expansions/transformers/var_splitter.py +57 -7
  49. classiq/open_library/__init__.py +4 -0
  50. classiq/open_library/functions/__init__.py +130 -0
  51. classiq/{qmod/builtins → open_library}/functions/amplitude_estimation.py +2 -2
  52. classiq/{qmod/builtins → open_library}/functions/discrete_sine_cosine_transform.py +6 -4
  53. classiq/{qmod/builtins → open_library}/functions/grover.py +2 -2
  54. classiq/{qmod/builtins → open_library}/functions/linear_pauli_rotation.py +1 -1
  55. classiq/{qmod/builtins → open_library}/functions/modular_exponentiation.py +2 -2
  56. classiq/{qmod/builtins → open_library}/functions/qpe.py +2 -2
  57. classiq/{qmod/builtins → open_library}/functions/state_preparation.py +6 -149
  58. classiq/{qmod/builtins → open_library}/functions/swap_test.py +1 -1
  59. classiq/open_library/functions/utility_functions.py +81 -0
  60. classiq/{qmod/builtins → open_library}/functions/variational.py +1 -1
  61. classiq/qmod/builtins/functions/__init__.py +4 -130
  62. classiq/qmod/builtins/functions/allocation.py +150 -0
  63. classiq/qmod/builtins/functions/arithmetic.py +0 -34
  64. classiq/qmod/builtins/functions/operators.py +0 -6
  65. classiq/qmod/builtins/operations.py +19 -80
  66. classiq/qmod/create_model_function.py +8 -162
  67. classiq/qmod/generative.py +0 -16
  68. classiq/qmod/model_state_container.py +7 -0
  69. classiq/qmod/native/pretty_printer.py +10 -11
  70. classiq/qmod/pretty_print/pretty_printer.py +1 -1
  71. classiq/qmod/python_classical_type.py +1 -5
  72. classiq/qmod/qfunc.py +11 -12
  73. classiq/qmod/qmod_variable.py +1 -3
  74. classiq/qmod/quantum_expandable.py +23 -1
  75. classiq/qmod/quantum_function.py +69 -7
  76. {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/METADATA +2 -1
  77. {classiq-0.61.0.dist-info → classiq-0.63.0.dist-info}/RECORD +82 -78
  78. classiq/qmod/builtins/functions/utility_functions.py +0 -43
  79. /classiq/{qmod/builtins → open_library}/functions/hea.py +0 -0
  80. /classiq/{qmod/builtins → open_library}/functions/qaoa_penalty.py +0 -0
  81. /classiq/{qmod/builtins → open_library}/functions/qft_functions.py +0 -0
  82. /classiq/{qmod/builtins → open_library}/functions/qsvt.py +0 -0
  83. {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
- headers=headers,
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
- headers=headers,
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.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH}/{job_id.job_id}/result",
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.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH}/{job_id.job_id}",
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.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH}/{job_id.job_id}/cancel",
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.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH}",
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
@@ -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.qmod.builtins.functions.hea import full_hea
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
- optimization_model = OptimizationModel(
60
- pyo_model, penalty_energy=penalty_energy, qsolver=QSolver.QAOAPenalty
61
- )
62
- qmod_struct = pyomo_to_qmod_qstruct("QAOAVars", optimization_model.vars_not_encoded)
63
- cost_func = partial(
64
- evaluate_objective, *optimization_model.objective_not_encoded_sympy
65
- )
66
- return qmod_struct, cost_func
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 get_field_name
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: get_field_name(pyomo_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
- var_names = {get_field_name(var): var for var in vars}
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, sympy_expr: sympy.Expr, struct_obj: Any
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: getattr(struct_obj, field_name)
301
- for sympy_var, field_name in var_mapping.items()
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
@@ -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.qmod.builtins.functions import qaoa_penalty
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.cost_func_ = pyo_model_to_qmod_problem(
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
- def get_model(self) -> SerializedModel:
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.cost_func_(v), params[i]
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(main) # type:ignore[assignment]
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.cost_func_(state["v"]),
116
+ lambda state: self.cost_func(state["v"]),
92
117
  {"params": params.tolist()},
93
118
  quantile=quantile,
94
119
  )
95
- if cost_trace is not None:
96
- cost_trace.append(cost)
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(0, 1, self.num_layers_),
103
- np.linspace(1, 0, self.num_layers_),
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
- self.optimized_params_ = scipy.optimize.minimize(
109
- estimate_cost_wrapper,
110
- x0=initial_params,
111
- method="COBYLA",
112
- options={"maxiter": maxiter},
113
- ).x.tolist()
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 get_results(self) -> pd.DataFrame:
117
- if self.optimized_params_ is None:
118
- self.optimize()
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": self.optimized_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.cost_func_(sampled.state["v"]),
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.qmod.builtins.functions.grover import grover_search, phase_oracle
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.qmod.builtins.functions import Z, amplitude_estimation
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