classiq 0.67.0__py3-none-any.whl → 0.69.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 (87) hide show
  1. classiq/_internals/api_wrapper.py +9 -9
  2. classiq/_internals/async_utils.py +1 -1
  3. classiq/_internals/authentication/password_manager.py +1 -1
  4. classiq/_internals/client.py +1 -1
  5. classiq/applications/combinatorial_optimization/combinatorial_problem.py +8 -11
  6. classiq/applications/qnn/gradients/quantum_gradient.py +1 -1
  7. classiq/applications/qnn/gradients/simple_quantum_gradient.py +1 -1
  8. classiq/applications/qnn/torch_utils.py +1 -1
  9. classiq/execution/execution_session.py +7 -3
  10. classiq/execution/jobs.py +2 -5
  11. classiq/executor.py +7 -2
  12. classiq/interface/_version.py +1 -1
  13. classiq/interface/ast_node.py +1 -1
  14. classiq/interface/backend/quantum_backend_providers.py +2 -3
  15. classiq/interface/chemistry/operator.py +12 -8
  16. classiq/interface/debug_info/back_ref_util.py +22 -0
  17. classiq/interface/debug_info/debug_info.py +26 -21
  18. classiq/interface/executor/optimizer_preferences.py +1 -0
  19. classiq/interface/generator/arith/arithmetic.py +96 -1
  20. classiq/interface/generator/arith/arithmetic_expression_parser.py +1 -1
  21. classiq/interface/generator/arith/arithmetic_param_getters.py +3 -3
  22. classiq/interface/generator/functions/classical_type.py +12 -1
  23. classiq/interface/generator/generated_circuit_data.py +64 -23
  24. classiq/interface/generator/quantum_program.py +18 -1
  25. classiq/interface/generator/types/builtin_enum_declarations.py +1 -0
  26. classiq/interface/generator/types/enum_declaration.py +45 -3
  27. classiq/interface/ide/visual_model.py +0 -2
  28. classiq/interface/model/classical_if.py +2 -2
  29. classiq/interface/model/control.py +2 -2
  30. classiq/interface/model/invert.py +2 -2
  31. classiq/interface/model/power.py +2 -2
  32. classiq/interface/model/quantum_function_call.py +4 -0
  33. classiq/interface/model/quantum_statement.py +1 -1
  34. classiq/interface/model/repeat.py +2 -2
  35. classiq/interface/model/statement_block.py +1 -1
  36. classiq/interface/model/within_apply_operation.py +2 -2
  37. classiq/interface/server/routes.py +0 -6
  38. classiq/model_expansions/generative_functions.py +4 -3
  39. classiq/model_expansions/interpreters/generative_interpreter.py +78 -18
  40. classiq/model_expansions/quantum_operations/allocate.py +3 -1
  41. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -0
  42. classiq/model_expansions/quantum_operations/bind.py +2 -1
  43. classiq/model_expansions/quantum_operations/block_evaluator.py +76 -0
  44. classiq/model_expansions/quantum_operations/call_emitter.py +0 -13
  45. classiq/model_expansions/quantum_operations/classicalif.py +5 -4
  46. classiq/model_expansions/quantum_operations/composite_emitter.py +27 -0
  47. classiq/model_expansions/quantum_operations/emitter.py +16 -2
  48. classiq/model_expansions/quantum_operations/expression_evaluator.py +33 -0
  49. classiq/model_expansions/quantum_operations/handle_evaluator.py +28 -0
  50. classiq/model_expansions/quantum_operations/quantum_function_call.py +3 -2
  51. classiq/model_expansions/quantum_operations/repeat.py +2 -1
  52. classiq/model_expansions/quantum_operations/variable_decleration.py +2 -1
  53. classiq/model_expansions/scope_initialization.py +5 -19
  54. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +1 -1
  55. classiq/open_library/functions/__init__.py +1 -2
  56. classiq/open_library/functions/amplitude_amplification.py +11 -12
  57. classiq/open_library/functions/discrete_sine_cosine_transform.py +17 -14
  58. classiq/open_library/functions/grover.py +7 -11
  59. classiq/open_library/functions/hea.py +3 -3
  60. classiq/open_library/functions/modular_exponentiation.py +17 -33
  61. classiq/open_library/functions/qft_functions.py +2 -2
  62. classiq/open_library/functions/qsvt.py +8 -8
  63. classiq/open_library/functions/state_preparation.py +16 -17
  64. classiq/open_library/functions/swap_test.py +1 -1
  65. classiq/open_library/functions/utility_functions.py +12 -4
  66. classiq/qmod/builtins/classical_functions.py +24 -7
  67. classiq/qmod/builtins/enums.py +1 -0
  68. classiq/qmod/builtins/functions/__init__.py +2 -0
  69. classiq/qmod/builtins/functions/chemistry.py +6 -38
  70. classiq/qmod/builtins/functions/exponentiation.py +24 -0
  71. classiq/qmod/builtins/operations.py +26 -11
  72. classiq/qmod/cparam.py +32 -5
  73. classiq/qmod/python_classical_type.py +10 -4
  74. classiq/qmod/quantum_callable.py +2 -1
  75. classiq/qmod/quantum_expandable.py +30 -6
  76. classiq/qmod/quantum_function.py +3 -2
  77. classiq/qmod/semantics/error_manager.py +1 -1
  78. classiq/qmod/semantics/validation/types_validation.py +1 -1
  79. classiq/qmod/symbolic.py +2 -1
  80. classiq/qmod/utilities.py +31 -2
  81. classiq/qmod/write_qmod.py +10 -7
  82. classiq/synthesis.py +25 -9
  83. {classiq-0.67.0.dist-info → classiq-0.69.0.dist-info}/METADATA +1 -1
  84. {classiq-0.67.0.dist-info → classiq-0.69.0.dist-info}/RECORD +85 -81
  85. classiq/interface/execution/jobs.py +0 -31
  86. classiq/model_expansions/quantum_operations/shallow_emitter.py +0 -166
  87. {classiq-0.67.0.dist-info → classiq-0.69.0.dist-info}/WHEEL +0 -0
@@ -3,6 +3,7 @@ from typing import Any, Optional, Protocol, TypeVar
3
3
 
4
4
  import httpx
5
5
  import pydantic
6
+ from pydantic.main import IncEx
6
7
 
7
8
  import classiq.interface.executor.execution_result
8
9
  import classiq.interface.pyomo_extension
@@ -21,10 +22,6 @@ from classiq.interface.execution.iqcc import (
21
22
  IQCCProbeAuthData,
22
23
  IQCCProbeAuthResponse,
23
24
  )
24
- from classiq.interface.execution.jobs import (
25
- ExecutionJobDetailsV1,
26
- ExecutionJobsQueryResultsV1,
27
- )
28
25
  from classiq.interface.execution.primitives import PrimitivesInput
29
26
  from classiq.interface.executor import execution_request
30
27
  from classiq.interface.generator import quantum_program as generator_result
@@ -72,11 +69,12 @@ class ApiWrapper:
72
69
  model: pydantic.BaseModel,
73
70
  use_versioned_url: bool = True,
74
71
  http_client: Optional[httpx.AsyncClient] = None,
72
+ exclude: Optional[IncEx] = None,
75
73
  ) -> dict:
76
74
  # TODO: we can't use model.dict() - it doesn't serialize complex class.
77
75
  # This was added because JSON serializer doesn't serialize complex type, and pydantic does.
78
76
  # We should add support for smarter json serialization.
79
- body = json.loads(model.model_dump_json())
77
+ body = json.loads(model.model_dump_json(exclude=exclude))
80
78
  return await cls._call_task(
81
79
  http_method,
82
80
  url,
@@ -135,6 +133,7 @@ class ApiWrapper:
135
133
  url=routes.EXECUTION_SESSIONS_PREFIX,
136
134
  model=circuit,
137
135
  http_client=http_client,
136
+ exclude={"debug_info"},
138
137
  )
139
138
  return raw_result["id"]
140
139
 
@@ -164,6 +163,7 @@ class ApiWrapper:
164
163
  url=routes.CONVERSION_GENERATED_CIRCUIT_TO_EXECUTION_INPUT_FULL,
165
164
  model=circuit,
166
165
  http_client=http_client,
166
+ exclude={"debug_info"},
167
167
  )
168
168
 
169
169
  @classmethod
@@ -214,7 +214,7 @@ class ApiWrapper:
214
214
  job_id: JobID,
215
215
  name: str,
216
216
  http_client: Optional[httpx.AsyncClient] = None,
217
- ) -> ExecutionJobDetailsV1:
217
+ ) -> execution_request.ExecutionJobDetails:
218
218
  data = await cls._call_task(
219
219
  http_method=HTTPMethod.PATCH,
220
220
  url=f"{routes.EXECUTION_JOBS_FULL_PATH}/{job_id.job_id}",
@@ -223,7 +223,7 @@ class ApiWrapper:
223
223
  },
224
224
  http_client=http_client,
225
225
  )
226
- return ExecutionJobDetailsV1.model_validate(data)
226
+ return execution_request.ExecutionJobDetails.model_validate(data)
227
227
 
228
228
  @classmethod
229
229
  async def call_cancel_execution_job(
@@ -244,7 +244,7 @@ class ApiWrapper:
244
244
  offset: int,
245
245
  limit: int,
246
246
  http_client: Optional[httpx.AsyncClient] = None,
247
- ) -> ExecutionJobsQueryResultsV1:
247
+ ) -> execution_request.ExecutionJobsQueryResults:
248
248
  data = await cls._call_task(
249
249
  http_method=HTTPMethod.GET,
250
250
  url=f"{routes.EXECUTION_JOBS_FULL_PATH}",
@@ -254,7 +254,7 @@ class ApiWrapper:
254
254
  },
255
255
  http_client=http_client,
256
256
  )
257
- return ExecutionJobsQueryResultsV1.model_validate(data)
257
+ return execution_request.ExecutionJobsQueryResults.model_validate(data)
258
258
 
259
259
  @classmethod
260
260
  async def call_analysis_task(
@@ -58,7 +58,7 @@ def enable_jupyter_notebook() -> None:
58
58
 
59
59
 
60
60
  def _make_iterable_interval(
61
- interval_sec: Union[SupportsFloat, Iterable[SupportsFloat]]
61
+ interval_sec: Union[SupportsFloat, Iterable[SupportsFloat]],
62
62
  ) -> Iterable[float]:
63
63
  if isinstance(interval_sec, Iterable):
64
64
  return map(float, interval_sec)
@@ -117,7 +117,7 @@ class DummyPasswordManager(PasswordManager):
117
117
 
118
118
  class FilePasswordManager(PasswordManager):
119
119
  _CLASSIQ_CREDENTIALS_FILE_PATH: str = "{}/.classiq-credentials".format(
120
- os.getenv("HOME")
120
+ os.getenv("CLASSIQ_DIR", os.getenv("HOME"))
121
121
  )
122
122
 
123
123
  def __init__(self) -> None:
@@ -89,7 +89,7 @@ P = ParamSpec("P")
89
89
 
90
90
 
91
91
  def try_again_on_failure(
92
- func: Callable[P, Awaitable[Ret]]
92
+ func: Callable[P, Awaitable[Ret]],
93
93
  ) -> Callable[P, Awaitable[Ret]]:
94
94
  def check_approved_api_error(error_message: str) -> bool:
95
95
  for approved_api_error in APPROVED_API_ERROR_MESSAGES_FOR_RESTART:
@@ -24,7 +24,6 @@ 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
28
27
  from classiq.qmod.qfunc import qfunc
29
28
  from classiq.qmod.qmod_parameter import CArray
30
29
  from classiq.qmod.qmod_variable import Output, QVar
@@ -75,17 +74,15 @@ class CombinatorialProblem:
75
74
  hadamard_transform(v)
76
75
  repeat(
77
76
  self.num_layers_,
78
- lambda i: [ # type:ignore[arg-type]
79
- phase(
80
- -self.cost_func(v), params[i]
81
- ), # type:ignore[func-returns-value]
77
+ lambda i: [
78
+ phase(-self.cost_func(v), params[i]),
82
79
  apply_to_all(lambda q: RX(params[self.num_layers_ + i], q), v),
83
80
  ],
84
81
  )
85
82
 
86
- self.model_ = create_model(
87
- main, constraints=constraints, preferences=preferences
88
- ) # type:ignore[assignment]
83
+ self.model_ = main.create_model(
84
+ constraints=constraints, preferences=preferences
85
+ ).get_model() # type:ignore[assignment]
89
86
  return self.model_ # type:ignore[return-value]
90
87
 
91
88
  def get_qprog(self) -> SerializedQuantumProgram:
@@ -199,13 +196,13 @@ def execute_qaoa(
199
196
  hadamard_transform(v)
200
197
  repeat(
201
198
  num_layers,
202
- lambda i: [ # type:ignore[arg-type]
203
- phase(-cost_func(v), params[i]), # type:ignore[func-returns-value]
199
+ lambda i: [
200
+ phase(-cost_func(v), params[i]),
204
201
  apply_to_all(lambda q: RX(params[num_layers + i], q), v),
205
202
  ],
206
203
  )
207
204
 
208
- model = create_model(main)
205
+ model = main.create_model().get_model()
209
206
  qprog = synthesize(model)
210
207
 
211
208
  with ExecutionSession(qprog, execution_preferences) as es:
@@ -18,7 +18,7 @@ class QuantumGradient(abc.ABC):
18
18
  execute: ExecuteFunction,
19
19
  post_process: PostProcessFunction,
20
20
  *args: Any,
21
- **kwargs: Any
21
+ **kwargs: Any,
22
22
  ) -> None:
23
23
  self._execute = execute
24
24
  self._post_process = post_process
@@ -71,7 +71,7 @@ class SimpleQuantumGradient(QuantumGradient):
71
71
  post_process: PostProcessFunction,
72
72
  epsilon: float = EPSILON,
73
73
  *args: Any,
74
- **kwargs: Any
74
+ **kwargs: Any,
75
75
  ) -> None:
76
76
  super().__init__(quantum_program, execute, post_process)
77
77
  self._epsilon = epsilon
@@ -94,7 +94,7 @@ def iter_inputs_weights(
94
94
  post_process: PostProcessFunction,
95
95
  *,
96
96
  expected_shape: Shape = (),
97
- requires_grad: Optional[bool] = None
97
+ requires_grad: Optional[bool] = None,
98
98
  ) -> Tensor:
99
99
  if is_single_layer_circuit(weights):
100
100
  iter_weights = torch.reshape(weights, (1, weights.shape[0]))
@@ -1,4 +1,3 @@
1
- import json
2
1
  import random
3
2
  from types import TracebackType
4
3
  from typing import Callable, Optional, Union, cast
@@ -16,7 +15,10 @@ from classiq.interface.executor.result import (
16
15
  )
17
16
  from classiq.interface.generator.arith import number_utils
18
17
  from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
19
- from classiq.interface.generator.quantum_program import QuantumProgram
18
+ from classiq.interface.generator.quantum_program import (
19
+ OMIT_DEBUG_INFO_FLAG,
20
+ QuantumProgram,
21
+ )
20
22
  from classiq.interface.model.quantum_type import QuantumBit, QuantumNumeric
21
23
 
22
24
  from classiq._internals import async_utils
@@ -46,7 +48,9 @@ def _deserialize_program(program: Program) -> QuantumProgram:
46
48
  return (
47
49
  program
48
50
  if isinstance(program, QuantumProgram)
49
- else QuantumProgram.model_validate(json.loads(program))
51
+ else QuantumProgram.model_validate_json(
52
+ program, context={OMIT_DEBUG_INFO_FLAG: True}
53
+ )
50
54
  )
51
55
 
52
56
 
classiq/execution/jobs.py CHANGED
@@ -9,7 +9,6 @@ from classiq.interface.exceptions import (
9
9
  ClassiqAPIError,
10
10
  ClassiqError,
11
11
  )
12
- from classiq.interface.execution.jobs import ExecutionJobDetailsV1
13
12
  from classiq.interface.executor.execution_request import ExecutionJobDetails, JobCost
14
13
  from classiq.interface.executor.execution_result import ResultsCollection
15
14
  from classiq.interface.executor.result import (
@@ -26,8 +25,6 @@ from classiq._internals.async_utils import syncify_function
26
25
  from classiq._internals.client import client
27
26
  from classiq._internals.jobs import JobID, JobPoller
28
27
 
29
- _JobDetails = Union[ExecutionJobDetails, ExecutionJobDetailsV1]
30
-
31
28
 
32
29
  class ClassiqExecutionResultError(ClassiqError):
33
30
  def __init__(self, primitive: str) -> None:
@@ -37,10 +34,10 @@ class ClassiqExecutionResultError(ClassiqError):
37
34
 
38
35
 
39
36
  class ExecutionJob:
40
- _details: _JobDetails
37
+ _details: ExecutionJobDetails
41
38
  _result: Optional[ResultsCollection]
42
39
 
43
- def __init__(self, details: _JobDetails) -> None:
40
+ def __init__(self, details: ExecutionJobDetails) -> None:
44
41
  self._details = details
45
42
  self._result = None
46
43
 
classiq/executor.py CHANGED
@@ -10,7 +10,10 @@ from classiq.interface.executor.execution_preferences import ExecutionPreference
10
10
  from classiq.interface.executor.quantum_code import QuantumCode
11
11
  from classiq.interface.executor.quantum_instruction_set import QuantumInstructionSet
12
12
  from classiq.interface.executor.result import ExecutionDetails
13
- from classiq.interface.generator.quantum_program import QuantumProgram
13
+ from classiq.interface.generator.quantum_program import (
14
+ OMIT_DEBUG_INFO_FLAG,
15
+ QuantumProgram,
16
+ )
14
17
 
15
18
  from classiq._internals import async_utils
16
19
  from classiq._internals.api_wrapper import ApiWrapper
@@ -27,7 +30,9 @@ BackendPreferencesAndResult: TypeAlias = tuple[
27
30
  def _parse_serialized_qprog(
28
31
  quantum_program: SerializedQuantumProgram,
29
32
  ) -> QuantumProgram:
30
- return QuantumProgram.model_validate_json(quantum_program)
33
+ return QuantumProgram.model_validate_json(
34
+ quantum_program, context={OMIT_DEBUG_INFO_FLAG: True}
35
+ )
31
36
 
32
37
 
33
38
  async def execute_async(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
@@ -3,5 +3,5 @@ from packaging.version import Version
3
3
  # This file was generated automatically
4
4
  # Please don't track in version control (DONTTRACK)
5
5
 
6
- SEMVER_VERSION = '0.67.0'
6
+ SEMVER_VERSION = '0.69.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -20,7 +20,7 @@ class ASTNode(HashablePydanticBaseModel):
20
20
  return self
21
21
 
22
22
 
23
- def without_statement_blocks(
23
+ def reset_lists(
24
24
  ast_node: ASTNodeType, statement_block_fields: list[str]
25
25
  ) -> ASTNodeType:
26
26
  return ast_node.model_copy(update={field: [] for field in statement_block_fields})
@@ -172,15 +172,14 @@ class ClassiqNvidiaBackendNames(StrEnum):
172
172
 
173
173
  SIMULATOR = "nvidia_simulator"
174
174
  SIMULATOR_STATEVECTOR = "nvidia_simulator_statevector"
175
+ BRAKET_NVIDIA_SIMULATOR = "braket_nvidia_simulator"
176
+ BRAKET_NVIDIA_SIMULATOR_STATEVECTOR = "braket_nvidia_simulator_statevector"
175
177
 
176
178
 
177
179
  class IntelBackendNames(StrEnum):
178
180
  SIMULATOR = "intel_qsdk_simulator"
179
181
 
180
182
 
181
- AllClassiqBackendNames = Union[ClassiqSimulatorBackendNames, ClassiqNvidiaBackendNames]
182
-
183
-
184
183
  class GoogleNvidiaBackendNames(StrEnum):
185
184
  """
186
185
  Google backend names which Classiq Supports running on.
@@ -1,5 +1,6 @@
1
1
  from collections.abc import Collection
2
2
  from functools import reduce
3
+ from itertools import combinations
3
4
  from typing import (
4
5
  Any,
5
6
  Optional,
@@ -69,7 +70,7 @@ class PauliOperator(HashablePydanticBaseModel, VersionedModel):
69
70
 
70
71
  @staticmethod
71
72
  def _validate_monomial_coefficient(
72
- coeff: Union[sympy.Expr, ParameterComplexType]
73
+ coeff: Union[sympy.Expr, ParameterComplexType],
73
74
  ) -> ParameterComplexType:
74
75
  if isinstance(coeff, str):
75
76
  validate_expression_str(coeff)
@@ -144,16 +145,19 @@ class PauliOperator(HashablePydanticBaseModel, VersionedModel):
144
145
  @property
145
146
  def is_commutative(self) -> bool:
146
147
  return all(
147
- self._is_sub_pauli_commutative(
148
- [summand[0][qubit_num] for summand in self.pauli_list]
149
- )
150
- for qubit_num in range(self.num_qubits)
148
+ self._do_paulis_commute(first[0], second[0])
149
+ for first, second in combinations(self.pauli_list, 2)
151
150
  )
152
151
 
153
152
  @staticmethod
154
- def _is_sub_pauli_commutative(qubit_pauli_string: Union[list[str], str]) -> bool:
155
- unique_paulis = set(qubit_pauli_string) - {"I"}
156
- return len(unique_paulis) <= 1
153
+ def _do_paulis_commute(
154
+ first: PydanticPauliMonomialStr, second: PydanticPauliMonomialStr
155
+ ) -> bool:
156
+ commute = True
157
+ for c1, c2 in zip(first, second):
158
+ if (c1 != "I") and (c2 != "I") and (c1 != c2):
159
+ commute = not commute
160
+ return commute
157
161
 
158
162
  @property
159
163
  def num_qubits(self) -> int:
@@ -0,0 +1,22 @@
1
+ from classiq.interface.model.allocate import Allocate
2
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
3
+ from classiq.interface.model.statement_block import (
4
+ ConcreteQuantumStatement,
5
+ StatementBlock,
6
+ )
7
+
8
+ """
9
+ This module contains helper functions to determine if a given quantum statement
10
+ is an allocation or free statement.
11
+ """
12
+
13
+
14
+ def is_allocate_or_free(concrete_quantum_statement: ConcreteQuantumStatement) -> bool:
15
+ return isinstance(concrete_quantum_statement, Allocate) or (
16
+ isinstance(concrete_quantum_statement, QuantumFunctionCall)
17
+ and concrete_quantum_statement.function == "free"
18
+ )
19
+
20
+
21
+ def is_allocate_or_free_by_backref(back_refs: StatementBlock) -> bool:
22
+ return len(back_refs) > 0 and is_allocate_or_free(back_refs[0])
@@ -1,34 +1,23 @@
1
- import json
2
1
  from collections.abc import Mapping
3
- from typing import Any, Optional, Union
2
+ from typing import Optional, Union
4
3
  from uuid import UUID
5
4
 
6
5
  from pydantic import BaseModel, Field
7
6
 
8
- from classiq.interface.enum_utils import StrEnum
7
+ from classiq.interface.debug_info import back_ref_util
9
8
  from classiq.interface.generator.generated_circuit_data import (
10
9
  FunctionDebugInfoInterface,
11
10
  OperationLevel,
11
+ StatementType,
12
12
  )
13
13
  from classiq.interface.model.statement_block import ConcreteQuantumStatement
14
14
 
15
15
  ParameterValue = Union[float, int, str, None]
16
16
 
17
17
 
18
- class StatementType(StrEnum):
19
- CONTROL = "control"
20
- POWER = "power"
21
- INVERT = "invert"
22
- WITHIN_APPLY = "within_apply"
23
- ASSIGNMENT = "assignment"
24
- REPEAT = "repeat"
25
-
26
-
27
18
  class FunctionDebugInfo(BaseModel):
28
19
  name: str
29
- # Parameters describe classical parameters passed to function
30
- parameters: dict[str, str]
31
- level: OperationLevel
20
+ level: OperationLevel = Field(default=OperationLevel.UNKNOWN)
32
21
  statement_type: Union[StatementType, None] = None
33
22
  is_allocate_or_free: bool = Field(default=False)
34
23
  is_inverse: bool = Field(default=False)
@@ -36,12 +25,13 @@ class FunctionDebugInfo(BaseModel):
36
25
  port_to_passed_variable_map: dict[str, str] = Field(default_factory=dict)
37
26
  node: Optional[ConcreteQuantumStatement] = None
38
27
 
39
- @staticmethod
40
- def param_controller(value: Any) -> str:
41
- try:
42
- return json.dumps(value)
43
- except TypeError:
44
- return repr(value)
28
+ @property
29
+ def is_allocate_or_free_(self) -> bool:
30
+ return (
31
+ back_ref_util.is_allocate_or_free(self.node)
32
+ if self.node is not None
33
+ else self.is_allocate_or_free
34
+ )
45
35
 
46
36
  def update_map_from_port_mapping(self, port_mapping: Mapping[str, str]) -> None:
47
37
  new_port_to_passed_variable_map = self.port_to_passed_variable_map.copy()
@@ -89,6 +79,21 @@ class DebugInfoCollection(BaseModel):
89
79
  return self.blackbox_data.get(debug_info.name)
90
80
 
91
81
 
82
+ def get_back_refs(
83
+ debug_info: FunctionDebugInfo, collected_debug_info: DebugInfoCollection
84
+ ) -> list[ConcreteQuantumStatement]:
85
+ back_refs: list[ConcreteQuantumStatement] = []
86
+ while (node := debug_info.node) is not None:
87
+ back_refs.insert(0, node)
88
+ if node.back_ref is None:
89
+ break
90
+ next_debug_info = collected_debug_info.get(node.back_ref)
91
+ if next_debug_info is None:
92
+ break
93
+ debug_info = next_debug_info
94
+ return back_refs
95
+
96
+
92
97
  def new_function_debug_info_by_node(
93
98
  node: ConcreteQuantumStatement,
94
99
  ) -> FunctionDebugInfo:
@@ -21,6 +21,7 @@ class OptimizerType(StrEnum):
21
21
  L_BFGS_B = "L_BFGS_B"
22
22
  NELDER_MEAD = "NELDER_MEAD"
23
23
  ADAM = "ADAM"
24
+ SLSQP = "SLSQP"
24
25
 
25
26
 
26
27
  class OptimizerPreferences(BaseModel):
@@ -1,4 +1,5 @@
1
- from typing import Any, Final, Optional
1
+ import ast
2
+ from typing import Any, Final, Optional, cast
2
3
 
3
4
  import networkx as nx
4
5
  import pydantic
@@ -95,6 +96,9 @@ def get_arithmetic_params(
95
96
  machine_precision: int,
96
97
  enable_target: bool = False,
97
98
  ) -> Arithmetic:
99
+ expr_str, var_types = _substitute_quantum_subscripts(
100
+ expr_str, var_types, machine_precision
101
+ )
98
102
  return Arithmetic(
99
103
  expression=expr_str,
100
104
  definitions={
@@ -121,3 +125,94 @@ def compute_arithmetic_result_type(
121
125
  return register_info_to_quantum_type(
122
126
  arith_param.outputs[ARITHMETIC_EXPRESSION_RESULT_NAME]
123
127
  )
128
+
129
+
130
+ def aggregate_numeric_types(
131
+ numeric_types: list[QuantumNumeric],
132
+ ) -> RegisterArithmeticInfo:
133
+ if all(
134
+ numeric_type.size_in_bits == 1
135
+ and numeric_type.sign_value
136
+ and numeric_type.fraction_digits_value == 1
137
+ for numeric_type in numeric_types
138
+ ):
139
+ return RegisterArithmeticInfo(size=1, is_signed=True, fraction_places=1)
140
+ int_size = max(
141
+ numeric_type.size_in_bits
142
+ - int(numeric_type.sign_value)
143
+ - numeric_type.fraction_digits_value
144
+ for numeric_type in numeric_types
145
+ )
146
+ is_signed = any(numeric_type.sign_value for numeric_type in numeric_types)
147
+ frac_size = max(
148
+ numeric_type.fraction_digits_value for numeric_type in numeric_types
149
+ )
150
+ total_size = int_size + int(is_signed) + frac_size
151
+ return RegisterArithmeticInfo(
152
+ size=total_size, is_signed=is_signed, fraction_places=frac_size
153
+ )
154
+
155
+
156
+ class _QuantumSubscriptRemover(ast.NodeTransformer):
157
+ def __init__(self, machine_precision: int) -> None:
158
+ self._machine_precision = machine_precision
159
+ self.substitutions_types: dict[str, QuantumNumeric] = {}
160
+
161
+ def visit_Call(self, node: ast.Call) -> ast.expr:
162
+ if not isinstance(node.func, ast.Name) or node.func.id != "Piecewise":
163
+ return node
164
+ items = [
165
+ cast(float, cast(ast.Num, cast(ast.Tuple, arg).elts[0]).value)
166
+ for arg in node.args
167
+ ]
168
+ numeric_types = [
169
+ compute_arithmetic_result_type(str(num), {}, self._machine_precision)
170
+ for num in items
171
+ ]
172
+ unified_numeric_type = register_info_to_quantum_type(
173
+ aggregate_numeric_types(numeric_types)
174
+ )
175
+ substitution_var_name = f"__lut__{len(self.substitutions_types)}__"
176
+ self.substitutions_types[substitution_var_name] = unified_numeric_type
177
+ return ast.Name(id=substitution_var_name)
178
+
179
+
180
+ class _NameCollector(ast.NodeVisitor):
181
+ def __init__(self) -> None:
182
+ self.names: set[str] = set()
183
+
184
+ def visit_Name(self, node: ast.Name) -> None:
185
+ self.names.add(node.id)
186
+
187
+
188
+ def _substitute_quantum_subscripts(
189
+ expr_str: str, var_types: dict[str, QuantumType], machine_precision: int
190
+ ) -> tuple[str, dict[str, QuantumType]]:
191
+ """
192
+ Remove quantum lookup expressions ([1, 2, 3, 4][n]) from an arithmetic expression
193
+ for the purpose of calculating its numeric attributes.
194
+ Each quantum lookup expression is replaced by a numeric value with equivalent
195
+ numeric properties.
196
+
197
+ Args:
198
+ expr_str: arithmetic expression
199
+ var_types: quantum variable type mapping
200
+ machine_precision: global machine precision
201
+
202
+ Returns:
203
+ 1. the reduced expression
204
+ 2. updated type mapping
205
+ """
206
+ expr_ast = ast.parse(expr_str)
207
+ subscript_remover = _QuantumSubscriptRemover(machine_precision)
208
+ expr_ast = subscript_remover.visit(expr_ast)
209
+ var_types_substituted = var_types | subscript_remover.substitutions_types
210
+ expr_str_substituted = ast.unparse(expr_ast)
211
+ names_collector = _NameCollector()
212
+ names_collector.visit(ast.parse(expr_str_substituted))
213
+ var_types_substituted = {
214
+ var_name: var_type
215
+ for var_name, var_type in var_types_substituted.items()
216
+ if var_name in names_collector.names
217
+ }
218
+ return expr_str_substituted, var_types_substituted
@@ -91,7 +91,7 @@ class ExpressionVisitor(ExpressionValidator):
91
91
  class InDegreeLimiter:
92
92
  @staticmethod
93
93
  def _sort_in_edges(
94
- in_edges: Collection[tuple[Node, str]]
94
+ in_edges: Collection[tuple[Node, str]],
95
95
  ) -> list[tuple[Node, str]]:
96
96
  return sorted(
97
97
  in_edges,
@@ -59,7 +59,7 @@ def get_params(
59
59
  machine_precision: int,
60
60
  output_size: Optional[int] = None,
61
61
  inplace_arg: Optional[str] = None,
62
- target: Optional[RegisterArithmeticInfo] = None
62
+ target: Optional[RegisterArithmeticInfo] = None,
63
63
  ) -> ArithmeticOperationParams:
64
64
  operation = id2op(node_id)
65
65
  if target and not operation_allows_target(operation):
@@ -334,7 +334,7 @@ def logical_and_params_getter(
334
334
  machine_precision: int,
335
335
  output_size: Optional[int] = None,
336
336
  inplace_arg: Optional[str] = None,
337
- target: Optional[RegisterArithmeticInfo] = None
337
+ target: Optional[RegisterArithmeticInfo] = None,
338
338
  ) -> ArithmeticOperationParams:
339
339
  return LogicalAnd(args=arg, target=target, machine_precision=machine_precision)
340
340
 
@@ -344,7 +344,7 @@ def logical_or_params_getter(
344
344
  machine_precision: int,
345
345
  output_size: Optional[int] = None,
346
346
  inplace_arg: Optional[str] = None,
347
- target: Optional[RegisterArithmeticInfo] = None
347
+ target: Optional[RegisterArithmeticInfo] = None,
348
348
  ) -> ArithmeticOperationParams:
349
349
  return LogicalOr(args=arg, target=target, machine_precision=machine_precision)
350
350
 
@@ -1,8 +1,9 @@
1
1
  from typing import TYPE_CHECKING, Any, Literal, Union
2
2
 
3
3
  import pydantic
4
- from pydantic import ConfigDict
4
+ from pydantic import ConfigDict, PrivateAttr
5
5
  from sympy import IndexedBase, Symbol
6
+ from typing_extensions import Self
6
7
 
7
8
  from classiq.interface.ast_node import HashableASTNode
8
9
  from classiq.interface.generator.expressions.expression_types import RuntimeExpression
@@ -19,6 +20,8 @@ NamedSymbol = Union[IndexedBase, Symbol]
19
20
 
20
21
 
21
22
  class ClassicalType(HashableASTNode):
23
+ _is_generative: bool = PrivateAttr(default=False)
24
+
22
25
  def as_symbolic(self, name: str) -> Union[NamedSymbol, list[NamedSymbol]]:
23
26
  return Symbol(name)
24
27
 
@@ -27,6 +30,14 @@ class ClassicalType(HashableASTNode):
27
30
  def __str__(self) -> str:
28
31
  return str(type(self).__name__)
29
32
 
33
+ def set_generative(self) -> Self:
34
+ self._is_generative = True
35
+ return self
36
+
37
+ @property
38
+ def is_generative(self) -> bool:
39
+ return self._is_generative
40
+
30
41
 
31
42
  class Integer(ClassicalType):
32
43
  kind: Literal["int"]