classiq 0.58.1__py3-none-any.whl → 0.60.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 (44) hide show
  1. classiq/_internals/api_wrapper.py +91 -20
  2. classiq/_internals/client.py +48 -11
  3. classiq/_internals/jobs.py +47 -40
  4. classiq/execution/execution_session.py +62 -22
  5. classiq/execution/jobs.py +64 -23
  6. classiq/execution/qaoa.py +17 -15
  7. classiq/execution/qnn.py +17 -18
  8. classiq/executor.py +2 -1
  9. classiq/interface/_version.py +1 -1
  10. classiq/interface/generator/arith/arithmetic_operations.py +1 -0
  11. classiq/interface/generator/register_role.py +8 -0
  12. classiq/interface/model/handle_binding.py +22 -3
  13. classiq/model_expansions/capturing/captured_vars.py +316 -0
  14. classiq/model_expansions/capturing/mangling_utils.py +18 -9
  15. classiq/model_expansions/closure.py +29 -74
  16. classiq/model_expansions/function_builder.py +51 -66
  17. classiq/model_expansions/interpreter.py +4 -7
  18. classiq/model_expansions/quantum_operations/bind.py +1 -3
  19. classiq/model_expansions/quantum_operations/call_emitter.py +46 -11
  20. classiq/model_expansions/quantum_operations/classicalif.py +2 -5
  21. classiq/model_expansions/quantum_operations/control.py +13 -16
  22. classiq/model_expansions/quantum_operations/emitter.py +36 -8
  23. classiq/model_expansions/quantum_operations/expression_operation.py +9 -19
  24. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +4 -6
  25. classiq/model_expansions/quantum_operations/invert.py +5 -8
  26. classiq/model_expansions/quantum_operations/power.py +5 -10
  27. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +1 -3
  28. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -3
  29. classiq/model_expansions/quantum_operations/repeat.py +3 -3
  30. classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
  31. classiq/model_expansions/quantum_operations/within_apply.py +1 -5
  32. classiq/model_expansions/scope.py +2 -2
  33. classiq/model_expansions/transformers/var_splitter.py +32 -19
  34. classiq/model_expansions/utils/handles_collector.py +33 -0
  35. classiq/model_expansions/visitors/variable_references.py +18 -2
  36. classiq/qmod/qfunc.py +9 -13
  37. classiq/qmod/quantum_expandable.py +1 -21
  38. classiq/qmod/quantum_function.py +16 -0
  39. {classiq-0.58.1.dist-info → classiq-0.60.0.dist-info}/METADATA +1 -1
  40. {classiq-0.58.1.dist-info → classiq-0.60.0.dist-info}/RECORD +41 -42
  41. classiq/interface/executor/aws_execution_cost.py +0 -90
  42. classiq/model_expansions/capturing/captured_var_manager.py +0 -48
  43. classiq/model_expansions/capturing/propagated_var_stack.py +0 -194
  44. {classiq-0.58.1.dist-info → classiq-0.60.0.dist-info}/WHEEL +0 -0
@@ -1,4 +1,6 @@
1
1
  import json
2
+ from functools import cached_property
3
+ from types import TracebackType
2
4
  from typing import Any, Callable, Optional, Union, cast
3
5
 
4
6
  import numpy as np
@@ -15,12 +17,14 @@ from classiq.interface.executor.result import (
15
17
  from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
16
18
  from classiq.interface.generator.quantum_program import QuantumProgram
17
19
 
20
+ from classiq._internals import async_utils
21
+ from classiq._internals.api_wrapper import ApiWrapper
22
+ from classiq._internals.client import client
18
23
  from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import (
19
24
  _pauli_dict_to_pauli_terms,
20
25
  _pauli_terms_to_qmod,
21
26
  )
22
27
  from classiq.execution.jobs import ExecutionJob
23
- from classiq.executor import execute
24
28
  from classiq.qmod.builtins import PauliTerm
25
29
  from classiq.qmod.builtins.classical_execution_primitives import (
26
30
  CARRAY_SEPARATOR,
@@ -145,6 +149,7 @@ class ExecutionSession:
145
149
  """
146
150
  A session for executing a quantum program.
147
151
  `ExecutionSession` allows to execute the quantum program with different parameters and operations without the need to re-synthesize the model.
152
+ The session must be closed in order to ensure resources are properly cleaned up. It's recommended to use `ExecutionSession` as a context manager for this purpose. Alternatively, you can directly use the `close` method.
148
153
 
149
154
  Attributes:
150
155
  quantum_program (Union[SerializedQuantumProgram, QuantumProgram]): The quantum program to execute.
@@ -158,14 +163,49 @@ class ExecutionSession:
158
163
  ):
159
164
  self.program: QuantumProgram = _deserialize_program(quantum_program)
160
165
  self.update_execution_preferences(execution_preferences)
166
+ # When the primitives are called, we always override the
167
+ # classical_execution_code, and we don't want the conversion route to fail
168
+ # because cmain is expected in some cases
169
+ self.program.model.classical_execution_code = "dummy"
161
170
 
162
- @property
163
- def qprog(self) -> SerializedQuantumProgram:
171
+ self._async_client = client().async_client()
172
+
173
+ def __enter__(self) -> "ExecutionSession":
174
+ return self
175
+
176
+ def __exit__(
177
+ self,
178
+ exc_type: Optional[type[BaseException]],
179
+ exc_val: Optional[BaseException],
180
+ exc_tb: Optional[TracebackType],
181
+ ) -> None:
182
+ self.close()
183
+
184
+ def close(self) -> None:
164
185
  """
165
- Returns:
166
- SerializedQuantumProgram: The serialized quantum program (str). See `QuantumProgram`.
186
+ Close the session and clean up its resources.
167
187
  """
168
- return SerializedQuantumProgram(self.program.model_dump_json(indent=2))
188
+ async_utils.run(self._async_client.aclose())
189
+
190
+ @cached_property
191
+ def _execution_input(self) -> dict:
192
+ return async_utils.run(
193
+ ApiWrapper.call_convert_quantum_program(self.program, self._async_client)
194
+ )
195
+
196
+ def _execute(
197
+ self, classical_execution_code: str, primitives_input: PrimitivesInput
198
+ ) -> ExecutionJob:
199
+ execution_input = self._execution_input.copy()
200
+ execution_input["classical_execution_code"] = classical_execution_code
201
+ # The use of `model_dump_json` is necessary for complex numbers serialization
202
+ execution_input["primitives_input"] = json.loads(
203
+ primitives_input.model_dump_json()
204
+ )
205
+ result = async_utils.run(
206
+ ApiWrapper.call_execute_execution_input(execution_input, self._async_client)
207
+ )
208
+ return ExecutionJob(details=result)
169
209
 
170
210
  def update_execution_preferences(
171
211
  self, execution_preferences: Optional[ExecutionPreferences]
@@ -193,7 +233,7 @@ class ExecutionSession:
193
233
  The result of the sampling.
194
234
  """
195
235
  job = self.submit_sample(parameters=parameters)
196
- return job.get_sample_result()
236
+ return job.get_sample_result(_http_client=self._async_client)
197
237
 
198
238
  def submit_sample(
199
239
  self, parameters: Optional[ExecutionParams] = None
@@ -210,13 +250,13 @@ class ExecutionSession:
210
250
  Returns:
211
251
  The execution job.
212
252
  """
213
- self.program.model.classical_execution_code = generate_code_snippet(
253
+ classical_execution_code = generate_code_snippet(
214
254
  SupportedPrimitives.SAMPLE, parameters=format_parameters(parameters)
215
255
  )
216
- self.program.execution_primitives_input = PrimitivesInput(
256
+ execution_primitives_input = PrimitivesInput(
217
257
  sample=[parse_params(parameters)] if parameters is not None else [{}]
218
258
  )
219
- return execute(SerializedQuantumProgram(self.qprog))
259
+ return self._execute(classical_execution_code, execution_primitives_input)
220
260
 
221
261
  def batch_sample(self, parameters: list[ExecutionParams]) -> list[ExecutionDetails]:
222
262
  """
@@ -229,7 +269,7 @@ class ExecutionSession:
229
269
  List[ExecutionDetails]: The results of all the sampling iterations.
230
270
  """
231
271
  job = self.submit_batch_sample(parameters=parameters)
232
- return job.get_batch_sample_result()
272
+ return job.get_batch_sample_result(_http_client=self._async_client)
233
273
 
234
274
  def submit_batch_sample(self, parameters: list[ExecutionParams]) -> ExecutionJob:
235
275
  """
@@ -244,13 +284,13 @@ class ExecutionSession:
244
284
  Returns:
245
285
  The execution job.
246
286
  """
247
- self.program.model.classical_execution_code = generate_code_snippet(
287
+ classical_execution_code = generate_code_snippet(
248
288
  SupportedPrimitives.BATCH_SAMPLE, parameters=format_parameters(parameters)
249
289
  )
250
- self.program.execution_primitives_input = PrimitivesInput(
290
+ execution_primitives_input = PrimitivesInput(
251
291
  sample=[parse_params(params) for params in parameters]
252
292
  )
253
- return execute(SerializedQuantumProgram(self.qprog))
293
+ return self._execute(classical_execution_code, execution_primitives_input)
254
294
 
255
295
  def estimate(
256
296
  self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
@@ -266,7 +306,7 @@ class ExecutionSession:
266
306
  EstimationResult: The result of the estimation.
267
307
  """
268
308
  job = self.submit_estimate(hamiltonian=hamiltonian, parameters=parameters)
269
- return job.get_estimate_result()
309
+ return job.get_estimate_result(_http_client=self._async_client)
270
310
 
271
311
  def submit_estimate(
272
312
  self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
@@ -284,12 +324,12 @@ class ExecutionSession:
284
324
  Returns:
285
325
  The execution job.
286
326
  """
287
- self.program.model.classical_execution_code = generate_code_snippet(
327
+ classical_execution_code = generate_code_snippet(
288
328
  SupportedPrimitives.ESTIMATE,
289
329
  parameters=format_parameters(parameters),
290
330
  hamiltonian=to_hamiltonian_str(hamiltonian),
291
331
  )
292
- self.program.execution_primitives_input = PrimitivesInput(
332
+ execution_primitives_input = PrimitivesInput(
293
333
  estimate=EstimateInput(
294
334
  hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
295
335
  parameters=(
@@ -297,7 +337,7 @@ class ExecutionSession:
297
337
  ),
298
338
  )
299
339
  )
300
- return execute(SerializedQuantumProgram(self.qprog))
340
+ return self._execute(classical_execution_code, execution_primitives_input)
301
341
 
302
342
  def batch_estimate(
303
343
  self, hamiltonian: Hamiltonian, parameters: list[ExecutionParams]
@@ -313,7 +353,7 @@ class ExecutionSession:
313
353
  List[EstimationResult]: The results of all the estimation iterations.
314
354
  """
315
355
  job = self.submit_batch_estimate(hamiltonian=hamiltonian, parameters=parameters)
316
- return job.get_batch_estimate_result()
356
+ return job.get_batch_estimate_result(_http_client=self._async_client)
317
357
 
318
358
  def submit_batch_estimate(
319
359
  self, hamiltonian: Hamiltonian, parameters: list[ExecutionParams]
@@ -331,18 +371,18 @@ class ExecutionSession:
331
371
  Returns:
332
372
  The execution job.
333
373
  """
334
- self.program.model.classical_execution_code = generate_code_snippet(
374
+ classical_execution_code = generate_code_snippet(
335
375
  SupportedPrimitives.BATCH_ESTIMATE,
336
376
  parameters=format_parameters(parameters),
337
377
  hamiltonian=to_hamiltonian_str(hamiltonian),
338
378
  )
339
- self.program.execution_primitives_input = PrimitivesInput(
379
+ execution_primitives_input = PrimitivesInput(
340
380
  estimate=EstimateInput(
341
381
  hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
342
382
  parameters=[parse_params(params) for params in parameters],
343
383
  )
344
384
  )
345
- return execute(SerializedQuantumProgram(self.qprog))
385
+ return self._execute(classical_execution_code, execution_primitives_input)
346
386
 
347
387
  def estimate_cost(
348
388
  self,
classiq/execution/jobs.py CHANGED
@@ -3,6 +3,8 @@ from datetime import datetime
3
3
  from typing import Any, Optional, Union
4
4
  from urllib.parse import urljoin
5
5
 
6
+ import httpx
7
+
6
8
  from classiq.interface.exceptions import (
7
9
  ClassiqAPIError,
8
10
  ClassiqError,
@@ -100,22 +102,28 @@ class ExecutionJob:
100
102
  return f"{self._details.cost.total_cost} {self._details.cost.currency_code}"
101
103
 
102
104
  @classmethod
103
- async def from_id_async(cls, id: str) -> "ExecutionJob":
104
- details = await ApiWrapper.call_get_execution_job_details(JobID(job_id=id))
105
+ async def from_id_async(
106
+ cls,
107
+ id: str,
108
+ _http_client: Optional[httpx.AsyncClient] = None,
109
+ ) -> "ExecutionJob":
110
+ details = await ApiWrapper.call_get_execution_job_details(
111
+ JobID(job_id=id), http_client=_http_client
112
+ )
105
113
  return cls(details)
106
114
 
107
- @classmethod
108
- def from_id(cls, id: str) -> "ExecutionJob":
109
- return syncify_function(cls.from_id_async)(id)
115
+ from_id = syncify_function(from_id_async)
110
116
 
111
117
  @property
112
118
  def _job_id(self) -> JobID:
113
119
  return JobID(job_id=self.id)
114
120
 
115
121
  async def result_async(
116
- self, timeout_sec: Optional[float] = None
122
+ self,
123
+ timeout_sec: Optional[float] = None,
124
+ _http_client: Optional[httpx.AsyncClient] = None,
117
125
  ) -> ResultsCollection:
118
- await self.poll_async(timeout_sec=timeout_sec)
126
+ await self.poll_async(timeout_sec=timeout_sec, _http_client=_http_client)
119
127
 
120
128
  if self.status == JobStatus.FAILED:
121
129
  raise ClassiqAPIError(self.error or "")
@@ -125,7 +133,9 @@ class ExecutionJob:
125
133
  if self._result is None:
126
134
  self._result = (
127
135
  await ApiWrapper.call_get_execution_job_result(
128
- job_id=self._job_id, version=_JOB_RESULT_VERSION
136
+ job_id=self._job_id,
137
+ version=_JOB_RESULT_VERSION,
138
+ http_client=_http_client,
129
139
  )
130
140
  ).results
131
141
  return self._result
@@ -135,7 +145,9 @@ class ExecutionJob:
135
145
  def result_value(self, *args: Any, **kwargs: Any) -> Any:
136
146
  return self.result(*args, **kwargs)[0].value
137
147
 
138
- def get_sample_result(self) -> ExecutionDetails:
148
+ def get_sample_result(
149
+ self, _http_client: Optional[httpx.AsyncClient] = None
150
+ ) -> ExecutionDetails:
139
151
  """
140
152
  Returns the job's result as a single sample result after validation. If the result is not yet available, waits for it.
141
153
 
@@ -146,7 +158,7 @@ class ExecutionJob:
146
158
  ClassiqExecutionResultError: In case the result does not contain a single sample result.
147
159
  ClassiqAPIError: In case the job has failed.
148
160
  """
149
- results = self.result()
161
+ results = self.result(_http_client=_http_client)
150
162
  if len(results) != 1:
151
163
  raise ClassiqExecutionResultError("sample")
152
164
 
@@ -157,7 +169,9 @@ class ExecutionJob:
157
169
  return result.details[0]
158
170
  raise ClassiqExecutionResultError("sample")
159
171
 
160
- def get_batch_sample_result(self) -> list[ExecutionDetails]:
172
+ def get_batch_sample_result(
173
+ self, _http_client: Optional[httpx.AsyncClient] = None
174
+ ) -> list[ExecutionDetails]:
161
175
  """
162
176
  Returns the job's result as a single batch_sample result after validation. If the result is not yet available, waits for it.
163
177
 
@@ -168,7 +182,7 @@ class ExecutionJob:
168
182
  ClassiqExecutionResultError: In case the result does not contain a single batch_sample result.
169
183
  ClassiqAPIError: In case the job has failed.
170
184
  """
171
- results = self.result()
185
+ results = self.result(_http_client=_http_client)
172
186
  if len(results) != 1:
173
187
  raise ClassiqExecutionResultError("batch_sample")
174
188
 
@@ -180,7 +194,9 @@ class ExecutionJob:
180
194
 
181
195
  raise ClassiqExecutionResultError("batch_sample")
182
196
 
183
- def get_estimate_result(self) -> EstimationResult:
197
+ def get_estimate_result(
198
+ self, _http_client: Optional[httpx.AsyncClient] = None
199
+ ) -> EstimationResult:
184
200
  """
185
201
  Returns the job's result as a single estimate result after validation. If the result is not yet available, waits for it.
186
202
 
@@ -191,7 +207,7 @@ class ExecutionJob:
191
207
  ClassiqExecutionResultError: In case the result does not contain a single estimate result.
192
208
  ClassiqAPIError: In case the job has failed.
193
209
  """
194
- results = self.result()
210
+ results = self.result(_http_client=_http_client)
195
211
  if len(results) != 1:
196
212
  raise ClassiqExecutionResultError("estimate")
197
213
 
@@ -202,7 +218,9 @@ class ExecutionJob:
202
218
  return result.results[0]
203
219
  raise ClassiqExecutionResultError("estimate")
204
220
 
205
- def get_batch_estimate_result(self) -> list[EstimationResult]:
221
+ def get_batch_estimate_result(
222
+ self, _http_client: Optional[httpx.AsyncClient] = None
223
+ ) -> list[EstimationResult]:
206
224
  """
207
225
  Returns the job's result as a single batch_estimate result after validation. If the result is not yet available, waits for it.
208
226
 
@@ -213,7 +231,7 @@ class ExecutionJob:
213
231
  ClassiqExecutionResultError: In case the result does not contain a single batch_estimate result.
214
232
  ClassiqAPIError: In case the job has failed.
215
233
  """
216
- results = self.result()
234
+ results = self.result(_http_client=_http_client)
217
235
  if len(results) != 1:
218
236
  raise ClassiqExecutionResultError("batch_estimate")
219
237
 
@@ -225,13 +243,21 @@ class ExecutionJob:
225
243
 
226
244
  raise ClassiqExecutionResultError("batch_estimate")
227
245
 
228
- async def poll_async(self, timeout_sec: Optional[float] = None) -> None:
246
+ async def poll_async(
247
+ self,
248
+ timeout_sec: Optional[float] = None,
249
+ _http_client: Optional[httpx.AsyncClient] = None,
250
+ ) -> None:
229
251
  if not self.status.is_final():
230
- await self._poll_job(timeout_sec=timeout_sec)
252
+ await self._poll_job(timeout_sec=timeout_sec, _http_client=_http_client)
231
253
 
232
254
  poll = syncify_function(poll_async)
233
255
 
234
- async def _poll_job(self, timeout_sec: Optional[float] = None) -> None:
256
+ async def _poll_job(
257
+ self,
258
+ timeout_sec: Optional[float] = None,
259
+ _http_client: Optional[httpx.AsyncClient] = None,
260
+ ) -> None:
235
261
  def response_parser(json_response: JSONObject) -> Optional[bool]:
236
262
  self._details = ExecutionJobDetails.model_validate(json_response)
237
263
  if self.status.is_final():
@@ -247,14 +273,26 @@ class ExecutionJob:
247
273
  job_id=self._job_id,
248
274
  response_parser=response_parser,
249
275
  timeout_sec=timeout_sec,
276
+ http_client=_http_client,
250
277
  )
251
278
 
252
- async def rename_async(self, name: str) -> None:
253
- self._details = await ApiWrapper.call_patch_execution_job(self._job_id, name)
279
+ async def rename_async(
280
+ self,
281
+ name: str,
282
+ _http_client: Optional[httpx.AsyncClient] = None,
283
+ ) -> None:
284
+ self._details = await ApiWrapper.call_patch_execution_job(
285
+ self._job_id,
286
+ name,
287
+ http_client=_http_client,
288
+ )
254
289
 
255
290
  rename = syncify_function(rename_async)
256
291
 
257
- async def cancel_async(self) -> None:
292
+ async def cancel_async(
293
+ self,
294
+ _http_client: Optional[httpx.AsyncClient] = None,
295
+ ) -> None:
258
296
  """
259
297
  Cancels the execution job. This implies the cancellation of any ongoing jobs
260
298
  sent to the provider during this execution job.
@@ -263,7 +301,10 @@ class ExecutionJob:
263
301
  to continue polling the job in order to ensure its cancellation, which might
264
302
  not be immediate.
265
303
  """
266
- await ApiWrapper.call_cancel_execution_job(self._job_id)
304
+ await ApiWrapper.call_cancel_execution_job(
305
+ self._job_id,
306
+ http_client=_http_client,
307
+ )
267
308
 
268
309
  cancel = syncify_function(cancel_async)
269
310
 
classiq/execution/qaoa.py CHANGED
@@ -65,20 +65,22 @@ def execute_qaoa(
65
65
  model = create_model(main)
66
66
  qprog = synthesize(model)
67
67
 
68
- es = ExecutionSession(qprog, execution_preferences)
69
- initial_params = (
70
- np.concatenate((np.linspace(0, 1, num_layers), np.linspace(1, 0, num_layers)))
71
- * math.pi
72
- )
73
- final_params = scipy.optimize.minimize(
74
- lambda params: es.estimate_cost(
75
- lambda state: cost_func(state["v"]),
76
- {"params": params.tolist()},
77
- ),
78
- x0=initial_params,
79
- method="COBYLA",
80
- options={"maxiter": maxiter},
81
- ).x.tolist()
82
- result = es.sample({"params": final_params})
68
+ with ExecutionSession(qprog, execution_preferences) as es:
69
+ initial_params = (
70
+ np.concatenate(
71
+ (np.linspace(0, 1, num_layers), np.linspace(1, 0, num_layers))
72
+ )
73
+ * math.pi
74
+ )
75
+ final_params = scipy.optimize.minimize(
76
+ lambda params: es.estimate_cost(
77
+ lambda state: cost_func(state["v"]),
78
+ {"params": params.tolist()},
79
+ ),
80
+ x0=initial_params,
81
+ method="COBYLA",
82
+ options={"maxiter": maxiter},
83
+ ).x.tolist()
84
+ result = es.sample({"params": final_params})
83
85
 
84
86
  return model, qprog, result
classiq/execution/qnn.py CHANGED
@@ -59,22 +59,21 @@ def execute_qnn(
59
59
  arguments: MultipleArguments,
60
60
  observable: Optional[PauliOperator] = None,
61
61
  ) -> ResultsCollection:
62
- session = ExecutionSession(quantum_program)
62
+ with ExecutionSession(quantum_program) as session:
63
+ if observable:
64
+ execute_function = functools.partial(
65
+ _execute_qnn_estimate,
66
+ session=session,
67
+ observable=observable,
68
+ )
69
+ else:
70
+ execute_function = functools.partial(
71
+ _execute_qnn_sample,
72
+ session=session,
73
+ )
63
74
 
64
- if observable:
65
- execute_function = functools.partial(
66
- _execute_qnn_estimate,
67
- session=session,
68
- observable=observable,
69
- )
70
- else:
71
- execute_function = functools.partial(
72
- _execute_qnn_sample,
73
- session=session,
74
- )
75
-
76
- result: ResultsCollection = []
77
- for chunk in more_itertools.chunked(arguments, _MAX_ARGUMENTS_SIZE):
78
- chunk_result = execute_function(arguments=chunk)
79
- result.extend(chunk_result)
80
- return result
75
+ result: ResultsCollection = []
76
+ for chunk in more_itertools.chunked(arguments, _MAX_ARGUMENTS_SIZE):
77
+ chunk_result = execute_function(arguments=chunk)
78
+ result.extend(chunk_result)
79
+ return result
classiq/executor.py CHANGED
@@ -32,7 +32,8 @@ def _parse_serialized_qprog(
32
32
 
33
33
  async def execute_async(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
34
34
  circuit = _parse_serialized_qprog(quantum_program)
35
- result = await ApiWrapper.call_execute_generated_circuit(circuit)
35
+ execution_input = await ApiWrapper.call_convert_quantum_program(circuit)
36
+ result = await ApiWrapper.call_execute_execution_input(execution_input)
36
37
  return ExecutionJob(details=result)
37
38
 
38
39
 
@@ -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.58.1'
6
+ SEMVER_VERSION = '0.60.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -10,6 +10,7 @@ from classiq.interface.generator.arith.machine_precision import (
10
10
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
11
11
  from classiq.interface.generator.function_params import FunctionParams
12
12
 
13
+ IMPLICIT_OUTPUTS: Final[str] = "implicit_outputs"
13
14
  DEFAULT_GARBAGE_OUT_NAME: Final[str] = "extra_qubits"
14
15
  MODULO_WITH_FRACTION_PLACES_ERROR_MSG: Final[str] = (
15
16
  "Modulo with fraction places not supported"
@@ -29,3 +29,11 @@ class RegisterRole(StrEnum):
29
29
  RegisterRole.AUXILIARY,
30
30
  RegisterRole.EXPLICIT_ZERO_INPUT,
31
31
  }
32
+
33
+ @staticmethod
34
+ def clean_output_roles() -> set["RegisterRole"]:
35
+ return {RegisterRole.ZERO_OUTPUT, RegisterRole.AUXILIARY}
36
+
37
+ @staticmethod
38
+ def dirty_output_roles() -> set["RegisterRole"]:
39
+ return {RegisterRole.OUTPUT, RegisterRole.GARBAGE_OUTPUT}
@@ -19,6 +19,10 @@ class HandleBinding(ASTNode):
19
19
  def __str__(self) -> str:
20
20
  return self.name
21
21
 
22
+ @property
23
+ def qmod_expr(self) -> str:
24
+ return self.name
25
+
22
26
  def is_bindable(self) -> bool:
23
27
  return True
24
28
 
@@ -57,6 +61,9 @@ class HandleBinding(ASTNode):
57
61
  return replacement
58
62
  return self
59
63
 
64
+ def __contains__(self, other_handle: "HandleBinding") -> bool:
65
+ return self.collapse() in other_handle.collapse().prefixes()
66
+
60
67
 
61
68
  class NestedHandleBinding(HandleBinding):
62
69
  base_handle: "ConcreteHandleBinding"
@@ -66,8 +73,8 @@ class NestedHandleBinding(HandleBinding):
66
73
  def _set_name(cls, values: Any) -> dict[str, Any]:
67
74
  if isinstance(values, dict):
68
75
  orig = values
69
- while "base_handle" in values:
70
- values = values["base_handle"]
76
+ while "base_handle" in dict(values):
77
+ values = dict(values)["base_handle"]
71
78
  orig["name"] = dict(values).get("name")
72
79
  return orig
73
80
  if isinstance(values, NestedHandleBinding):
@@ -106,6 +113,10 @@ class SubscriptHandleBinding(NestedHandleBinding):
106
113
  def __str__(self) -> str:
107
114
  return f"{self.base_handle}[{self.index}]"
108
115
 
116
+ @property
117
+ def qmod_expr(self) -> str:
118
+ return f"{self.base_handle.qmod_expr}[{self.index}]"
119
+
109
120
  @property
110
121
  def identifier(self) -> str:
111
122
  return f"{self.base_handle.identifier}{HANDLE_ID_SEPARATOR}{self.index}"
@@ -157,6 +168,10 @@ class SlicedHandleBinding(NestedHandleBinding):
157
168
  def __str__(self) -> str:
158
169
  return f"{self.base_handle}[{self.start}:{self.end}]"
159
170
 
171
+ @property
172
+ def qmod_expr(self) -> str:
173
+ return f"{self.base_handle.qmod_expr}[{self.start}:{self.end}]"
174
+
160
175
  @property
161
176
  def identifier(self) -> str:
162
177
  return (
@@ -165,7 +180,7 @@ class SlicedHandleBinding(NestedHandleBinding):
165
180
 
166
181
  def collapse(self) -> HandleBinding:
167
182
  if isinstance(self.base_handle, SlicedHandleBinding):
168
- return SubscriptHandleBinding(
183
+ return SlicedHandleBinding(
169
184
  base_handle=self.base_handle.base_handle,
170
185
  start=self._get_collapsed_start(),
171
186
  end=self._get_collapsed_stop(),
@@ -224,6 +239,10 @@ class FieldHandleBinding(NestedHandleBinding):
224
239
  def __str__(self) -> str:
225
240
  return f"{self.base_handle}.{self.field}"
226
241
 
242
+ @property
243
+ def qmod_expr(self) -> str:
244
+ return f"get_field({self.base_handle.qmod_expr}, '{self.field}')"
245
+
227
246
  @property
228
247
  def identifier(self) -> str:
229
248
  return f"{self.base_handle.identifier}{HANDLE_ID_SEPARATOR}{self.field}"