classiq 0.46.1__py3-none-any.whl → 0.48.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 (71) hide show
  1. classiq/_internals/api_wrapper.py +45 -8
  2. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +2 -7
  3. classiq/applications/grover/grover_model_constructor.py +2 -1
  4. classiq/execution/execution_session.py +133 -45
  5. classiq/execution/jobs.py +120 -1
  6. classiq/interface/_version.py +1 -1
  7. classiq/interface/backend/quantum_backend_providers.py +0 -1
  8. classiq/interface/debug_info/debug_info.py +23 -1
  9. classiq/interface/execution/primitives.py +17 -0
  10. classiq/interface/executor/iqae_result.py +3 -3
  11. classiq/interface/executor/result.py +3 -1
  12. classiq/interface/generator/arith/arithmetic_operations.py +5 -2
  13. classiq/interface/generator/arith/binary_ops.py +21 -14
  14. classiq/interface/generator/arith/extremum_operations.py +9 -1
  15. classiq/interface/generator/arith/number_utils.py +6 -0
  16. classiq/interface/generator/arith/register_user_input.py +30 -21
  17. classiq/interface/generator/arith/unary_ops.py +13 -1
  18. classiq/interface/generator/expressions/expression.py +8 -0
  19. classiq/interface/generator/functions/type_name.py +1 -3
  20. classiq/interface/generator/generated_circuit_data.py +47 -2
  21. classiq/interface/generator/quantum_program.py +10 -2
  22. classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +17 -3
  23. classiq/interface/ide/visual_model.py +10 -5
  24. classiq/interface/interface_version.py +1 -1
  25. classiq/interface/model/bind_operation.py +0 -3
  26. classiq/interface/model/phase_operation.py +11 -0
  27. classiq/interface/model/port_declaration.py +1 -12
  28. classiq/interface/model/quantum_expressions/arithmetic_operation.py +34 -6
  29. classiq/interface/model/quantum_lambda_function.py +4 -1
  30. classiq/interface/model/quantum_statement.py +16 -1
  31. classiq/interface/model/quantum_variable_declaration.py +0 -22
  32. classiq/interface/model/statement_block.py +3 -0
  33. classiq/interface/server/global_versions.py +4 -4
  34. classiq/interface/server/routes.py +0 -3
  35. classiq/model_expansions/capturing/propagated_var_stack.py +5 -2
  36. classiq/model_expansions/closure.py +7 -2
  37. classiq/model_expansions/evaluators/quantum_type_utils.py +0 -7
  38. classiq/model_expansions/generative_functions.py +146 -28
  39. classiq/model_expansions/interpreter.py +17 -5
  40. classiq/model_expansions/quantum_operations/classicalif.py +27 -10
  41. classiq/model_expansions/quantum_operations/control.py +22 -15
  42. classiq/model_expansions/quantum_operations/emitter.py +68 -7
  43. classiq/model_expansions/quantum_operations/expression_operation.py +25 -16
  44. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +167 -95
  45. classiq/model_expansions/quantum_operations/invert.py +12 -6
  46. classiq/model_expansions/quantum_operations/phase.py +189 -0
  47. classiq/model_expansions/quantum_operations/power.py +9 -8
  48. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +20 -5
  49. classiq/model_expansions/quantum_operations/quantum_function_call.py +1 -1
  50. classiq/model_expansions/quantum_operations/repeat.py +32 -13
  51. classiq/model_expansions/quantum_operations/within_apply.py +19 -6
  52. classiq/model_expansions/scope.py +16 -5
  53. classiq/model_expansions/scope_initialization.py +11 -1
  54. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +23 -1
  55. classiq/model_expansions/visitors/variable_references.py +11 -7
  56. classiq/qmod/builtins/__init__.py +10 -0
  57. classiq/qmod/builtins/constants.py +10 -0
  58. classiq/qmod/builtins/functions/state_preparation.py +4 -1
  59. classiq/qmod/builtins/operations.py +55 -161
  60. classiq/qmod/create_model_function.py +1 -1
  61. classiq/qmod/generative.py +14 -5
  62. classiq/qmod/native/pretty_printer.py +14 -4
  63. classiq/qmod/pretty_print/pretty_printer.py +14 -4
  64. classiq/qmod/qmod_constant.py +28 -18
  65. classiq/qmod/qmod_variable.py +43 -23
  66. classiq/qmod/quantum_expandable.py +14 -1
  67. classiq/qmod/semantics/static_semantics_visitor.py +10 -0
  68. classiq/qmod/semantics/validation/constants_validation.py +16 -0
  69. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/METADATA +9 -4
  70. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/RECORD +71 -66
  71. {classiq-0.46.1.dist-info → classiq-0.48.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Dict, List, Optional, Protocol, Type, TypeVar
2
+ from typing import Any, Dict, List, Optional, Protocol, Type, TypeVar
3
3
 
4
4
  import pydantic
5
5
 
@@ -7,6 +7,7 @@ import classiq.interface.executor.execution_result
7
7
  import classiq.interface.pyomo_extension
8
8
  from classiq.interface.analyzer import analysis_params, result as analysis_result
9
9
  from classiq.interface.analyzer.analysis_params import AnalysisRBParams
10
+ from classiq.interface.analyzer.result import GraphStatus
10
11
  from classiq.interface.chemistry import ground_state_problem, operator
11
12
  from classiq.interface.enum_utils import StrEnum
12
13
  from classiq.interface.exceptions import ClassiqAPIError, ClassiqValueError
@@ -16,7 +17,7 @@ from classiq.interface.execution.jobs import (
16
17
  )
17
18
  from classiq.interface.executor import execution_request
18
19
  from classiq.interface.generator import quantum_program as generator_result
19
- from classiq.interface.hardware import HardwareInformation
20
+ from classiq.interface.hardware import HardwareInformation, Provider
20
21
  from classiq.interface.jobs import JobDescription, JobID, JSONObject
21
22
  from classiq.interface.model.model import Model
22
23
  from classiq.interface.server import routes
@@ -36,6 +37,7 @@ class HTTPMethod(StrEnum):
36
37
  GET = "GET"
37
38
  POST = "POST"
38
39
  PATCH = "PATCH"
40
+ PUT = "PUT"
39
41
 
40
42
 
41
43
  class StatusType(Protocol):
@@ -80,8 +82,9 @@ class ApiWrapper:
80
82
  params: Optional[Dict] = None,
81
83
  use_versioned_url: bool = True,
82
84
  headers: Optional[Dict[str, str]] = None,
85
+ allow_none: bool = False,
83
86
  ) -> dict:
84
- res = await client().call_api(
87
+ res: Any = await client().call_api(
85
88
  http_method=http_method,
86
89
  url=url,
87
90
  body=body,
@@ -89,6 +92,8 @@ class ApiWrapper:
89
92
  params=params,
90
93
  use_versioned_url=use_versioned_url,
91
94
  )
95
+ if allow_none and res is None:
96
+ return {}
92
97
  if not isinstance(res, dict):
93
98
  raise ClassiqValueError(f"Unexpected returned value: {res}")
94
99
  return res
@@ -169,6 +174,18 @@ class ApiWrapper:
169
174
  )
170
175
  return ExecutionJobDetailsV1.parse_obj(data)
171
176
 
177
+ @classmethod
178
+ async def call_cancel_execution_job(
179
+ cls,
180
+ job_id: JobID,
181
+ ) -> None:
182
+ await cls._call_task(
183
+ http_method=HTTPMethod.PUT,
184
+ url=f"{routes.EXECUTION_JOBS_NON_VERSIONED_FULL_PATH}/{job_id.job_id}/cancel",
185
+ use_versioned_url=False,
186
+ allow_none=True,
187
+ )
188
+
172
189
  @classmethod
173
190
  async def call_query_execution_jobs(
174
191
  cls,
@@ -267,12 +284,32 @@ class ApiWrapper:
267
284
  cls,
268
285
  params: analysis_params.AnalysisOptionalDevicesParams,
269
286
  ) -> analysis_result.DevicesResult:
270
- data = await cls._call_task(
271
- http_method=HTTPMethod.POST,
272
- url=routes.ANALYZER_OPTIONAL_DEVICES_FULL_PATH,
273
- body=params.dict(),
287
+ hardware_info = await cls.call_get_all_hardware_devices()
288
+ return cls._get_devices_from_hardware_info(hardware_info, params)
289
+
290
+ @staticmethod
291
+ def _get_devices_from_hardware_info(
292
+ hardware_info: List[HardwareInformation],
293
+ params: analysis_params.AnalysisOptionalDevicesParams,
294
+ ) -> analysis_result.DevicesResult:
295
+ available_hardware: Dict[Provider, Dict[str, bool]] = {
296
+ Provider.IBM_QUANTUM: {},
297
+ Provider.AMAZON_BRAKET: {},
298
+ Provider.AZURE_QUANTUM: {},
299
+ }
300
+ for info in hardware_info:
301
+ if info.provider not in available_hardware:
302
+ continue
303
+ is_available = info.number_of_qubits >= params.qubit_count
304
+ available_hardware[info.provider][info.display_name] = is_available
305
+ return analysis_result.DevicesResult(
306
+ devices=analysis_result.AvailableHardware(
307
+ ibm_quantum=available_hardware[Provider.IBM_QUANTUM],
308
+ azure_quantum=available_hardware[Provider.AZURE_QUANTUM],
309
+ amazon_braket=available_hardware[Provider.AMAZON_BRAKET],
310
+ ),
311
+ status=GraphStatus.SUCCESS,
274
312
  )
275
- return analysis_result.DevicesResult.parse_obj(data)
276
313
 
277
314
  @classmethod
278
315
  async def call_get_all_hardware_devices(cls) -> List[HardwareInformation]:
@@ -44,10 +44,5 @@ def _pauli_terms_to_qmod(hamiltonian: List[PauliTerm]) -> str:
44
44
  return ", ".join(qmod_strings)
45
45
 
46
46
 
47
- def _pauli_dict_to_str(hamiltonian: List[QmodPyStruct]) -> str:
48
- res = []
49
- for struct in hamiltonian:
50
- pauli_str = ", ".join([pauli_enum_to_str(p) for p in struct["pauli"]])
51
- res.append(f'"pauli": [{pauli_str}], "coefficient": {struct["coefficient"]}')
52
-
53
- return f"{{{', '.join(res)}}}"
47
+ def _pauli_dict_to_pauli_terms(hamiltonian: List[QmodPyStruct]) -> List[PauliTerm]:
48
+ return [PauliTerm(**struct) for struct in hamiltonian]
@@ -11,6 +11,7 @@ from classiq.interface.model.native_function_definition import NativeFunctionDef
11
11
  from classiq.interface.model.port_declaration import PortDeclaration
12
12
  from classiq.interface.model.quantum_expressions.arithmetic_operation import (
13
13
  ArithmeticOperation,
14
+ ArithmeticOperationKind,
14
15
  )
15
16
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
16
17
  from classiq.interface.model.quantum_lambda_function import QuantumLambdaFunction
@@ -110,7 +111,7 @@ def construct_grover_model(
110
111
  ArithmeticOperation(
111
112
  expression=Expression(expr=expression),
112
113
  result_var=HandleBinding(name="res"),
113
- inplace_result=True,
114
+ operation_kind=ArithmeticOperationKind.InplaceXor,
114
115
  ),
115
116
  ],
116
117
  ),
@@ -1,22 +1,22 @@
1
1
  import json
2
2
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union, cast
3
3
 
4
+ from classiq.interface.chemistry.operator import PauliOperator, pauli_integers_to_str
4
5
  from classiq.interface.exceptions import ClassiqValueError
6
+ from classiq.interface.execution.primitives import EstimateInput, PrimitivesInput
5
7
  from classiq.interface.executor.execution_preferences import ExecutionPreferences
6
- from classiq.interface.executor.execution_result import ResultsCollection
7
8
  from classiq.interface.executor.result import (
8
9
  EstimationResult,
9
- EstimationResults,
10
10
  ExecutionDetails,
11
- MultipleExecutionDetails,
12
11
  )
13
12
  from classiq.interface.generator.functions.qmod_python_interface import QmodPyStruct
14
13
  from classiq.interface.generator.quantum_program import QuantumProgram
15
14
 
16
15
  from classiq.applications.combinatorial_helpers.pauli_helpers.pauli_utils import (
17
- _pauli_dict_to_str,
16
+ _pauli_dict_to_pauli_terms,
18
17
  _pauli_terms_to_qmod,
19
18
  )
19
+ from classiq.execution.jobs import ExecutionJob
20
20
  from classiq.executor import execute
21
21
  from classiq.qmod.builtins import PauliTerm
22
22
  from classiq.qmod.builtins.classical_execution_primitives import (
@@ -52,12 +52,26 @@ def _deserialize_program(program: Program) -> QuantumProgram:
52
52
  )
53
53
 
54
54
 
55
+ def hamiltonian_to_pauli_terms(hamiltonian: Hamiltonian) -> List[PauliTerm]:
56
+ if isinstance(hamiltonian[0], PauliTerm):
57
+ return cast(List[PauliTerm], hamiltonian)
58
+ else:
59
+ return _pauli_dict_to_pauli_terms(cast(List[QmodPyStruct], hamiltonian))
60
+
61
+
55
62
  def to_hamiltonian_str(hamiltonian: Hamiltonian) -> str:
56
- return (
57
- _pauli_terms_to_qmod(cast(List[PauliTerm], hamiltonian))
58
- if isinstance(hamiltonian[0], PauliTerm)
59
- else _pauli_dict_to_str(cast(List[QmodPyStruct], hamiltonian))
60
- )
63
+ return _pauli_terms_to_qmod(hamiltonian_to_pauli_terms(hamiltonian))
64
+
65
+
66
+ def _hamiltonian_to_pauli_operator(hamiltonian: Hamiltonian) -> PauliOperator:
67
+ pauli_list = [
68
+ (
69
+ pauli_integers_to_str(elem.pauli), # type: ignore[arg-type]
70
+ elem.coefficient,
71
+ )
72
+ for elem in hamiltonian_to_pauli_terms(hamiltonian)
73
+ ]
74
+ return PauliOperator(pauli_list=pauli_list)
61
75
 
62
76
 
63
77
  def serialize(
@@ -165,14 +179,6 @@ class ExecutionSession:
165
179
  if execution_preferences is not None:
166
180
  self.program.model.execution_preferences = execution_preferences
167
181
 
168
- def _execute_quantum_program(
169
- self, operation: str, **kwargs: Any
170
- ) -> ResultsCollection:
171
- self.program.model.classical_execution_code = generate_code_snippet(
172
- operation, **kwargs
173
- )
174
- return execute(SerializedQuantumProgram(self.qprog)).result()
175
-
176
182
  def sample(self, parameters: Optional[ExecutionParams] = None) -> ExecutionDetails:
177
183
  """
178
184
  Samples the quantum program with the given parameters, if any.
@@ -180,16 +186,34 @@ class ExecutionSession:
180
186
  Args:
181
187
  parameters: The values to set for the parameters of the quantum program when sampling. Each key should be the name of a parameter in the quantum program (parameters of the main function), and the value should be the value to set for that parameter.
182
188
 
189
+ Returns:
190
+ The result of the sampling.
191
+ """
192
+ job = self.submit_sample(parameters=parameters)
193
+ return job.get_sample_result()
194
+
195
+ def submit_sample(
196
+ self, parameters: Optional[ExecutionParams] = None
197
+ ) -> ExecutionJob:
198
+ """
199
+ Initiates an execution job with the `sample` primitive.
200
+
201
+ This is a non-blocking version of `sample`: it gets the same parameters and initiates the same execution job, but instead
202
+ of waiting for the result, it returns the job object immediately.
203
+
204
+ Args:
205
+ parameters: The values to set for the parameters of the quantum program when sampling. Each key should be the name of a parameter in the quantum program (parameters of the main function), and the value should be the value to set for that parameter.
183
206
 
184
207
  Returns:
185
- ExecutionDetails: The result of the sampling.
208
+ The execution job.
186
209
  """
187
- return cast(
188
- ExecutionDetails,
189
- self._execute_quantum_program(
190
- SupportedPrimitives.SAMPLE, parameters=format_parameters(parameters)
191
- )[0].value,
210
+ self.program.model.classical_execution_code = generate_code_snippet(
211
+ SupportedPrimitives.SAMPLE, parameters=format_parameters(parameters)
192
212
  )
213
+ self.program.execution_primitives_input = PrimitivesInput(
214
+ sample=[parse_params(parameters)] if parameters is not None else [{}]
215
+ )
216
+ return execute(SerializedQuantumProgram(self.qprog))
193
217
 
194
218
  def batch_sample(self, parameters: List[ExecutionParams]) -> List[ExecutionDetails]:
195
219
  """
@@ -201,13 +225,29 @@ class ExecutionSession:
201
225
  Returns:
202
226
  List[ExecutionDetails]: The results of all the sampling iterations.
203
227
  """
204
- return cast(
205
- MultipleExecutionDetails,
206
- self._execute_quantum_program(
207
- SupportedPrimitives.BATCH_SAMPLE,
208
- parameters=format_parameters(parameters),
209
- )[0].value,
210
- ).details
228
+ job = self.submit_batch_sample(parameters=parameters)
229
+ return job.get_batch_sample_result()
230
+
231
+ def submit_batch_sample(self, parameters: List[ExecutionParams]) -> ExecutionJob:
232
+ """
233
+ Initiates an execution job with the `batch_sample` primitive.
234
+
235
+ This is a non-blocking version of `batch_sample`: it gets the same parameters and initiates the same execution job, but instead
236
+ of waiting for the result, it returns the job object immediately.
237
+
238
+ Args:
239
+ parameters: A list of the parameters for each iteration. Each item is a dictionary where each key should be the name of a parameter in the quantum program (parameters of the main function), and the value should be the value to set for that parameter.
240
+
241
+ Returns:
242
+ The execution job.
243
+ """
244
+ self.program.model.classical_execution_code = generate_code_snippet(
245
+ SupportedPrimitives.BATCH_SAMPLE, parameters=format_parameters(parameters)
246
+ )
247
+ self.program.execution_primitives_input = PrimitivesInput(
248
+ sample=[parse_params(params) for params in parameters]
249
+ )
250
+ return execute(SerializedQuantumProgram(self.qprog))
211
251
 
212
252
  def estimate(
213
253
  self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
@@ -222,14 +262,39 @@ class ExecutionSession:
222
262
  Returns:
223
263
  EstimationResult: The result of the estimation.
224
264
  """
225
- return cast(
226
- EstimationResult,
227
- self._execute_quantum_program(
228
- SupportedPrimitives.ESTIMATE,
229
- parameters=format_parameters(parameters),
230
- hamiltonian=to_hamiltonian_str(hamiltonian),
231
- )[0].value,
265
+ job = self.submit_estimate(hamiltonian=hamiltonian, parameters=parameters)
266
+ return job.get_estimate_result()
267
+
268
+ def submit_estimate(
269
+ self, hamiltonian: Hamiltonian, parameters: Optional[ExecutionParams] = None
270
+ ) -> ExecutionJob:
271
+ """
272
+ Initiates an execution job with the `estimate` primitive.
273
+
274
+ This is a non-blocking version of `estimate`: it gets the same parameters and initiates the same execution job, but instead
275
+ of waiting for the result, it returns the job object immediately.
276
+
277
+ Args:
278
+ hamiltonian: The Hamiltonian to estimate the expectation value of.
279
+ parameters: The values to set for the parameters of the quantum program when estimating. Each key should be the name of a parameter in the quantum program (parameters of the main function), and the value should be the value to set for that parameter.
280
+
281
+ Returns:
282
+ The execution job.
283
+ """
284
+ self.program.model.classical_execution_code = generate_code_snippet(
285
+ SupportedPrimitives.ESTIMATE,
286
+ parameters=format_parameters(parameters),
287
+ hamiltonian=to_hamiltonian_str(hamiltonian),
288
+ )
289
+ self.program.execution_primitives_input = PrimitivesInput(
290
+ estimate=EstimateInput(
291
+ hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
292
+ parameters=(
293
+ [parse_params(parameters)] if parameters is not None else [{}]
294
+ ),
295
+ )
232
296
  )
297
+ return execute(SerializedQuantumProgram(self.qprog))
233
298
 
234
299
  def batch_estimate(
235
300
  self, hamiltonian: Hamiltonian, parameters: List[ExecutionParams]
@@ -244,11 +309,34 @@ class ExecutionSession:
244
309
  Returns:
245
310
  List[EstimationResult]: The results of all the estimation iterations.
246
311
  """
247
- return cast(
248
- EstimationResults,
249
- self._execute_quantum_program(
250
- SupportedPrimitives.BATCH_ESTIMATE,
251
- parameters=format_parameters(parameters),
252
- hamiltonian=to_hamiltonian_str(hamiltonian),
253
- )[0].value,
254
- ).results
312
+ job = self.submit_batch_estimate(hamiltonian=hamiltonian, parameters=parameters)
313
+ return job.get_batch_estimate_result()
314
+
315
+ def submit_batch_estimate(
316
+ self, hamiltonian: Hamiltonian, parameters: List[ExecutionParams]
317
+ ) -> ExecutionJob:
318
+ """
319
+ Initiates an execution job with the `batch_estimate` primitive.
320
+
321
+ This is a non-blocking version of `batch_estimate`: it gets the same parameters and initiates the same execution job, but instead
322
+ of waiting for the result, it returns the job object immediately.
323
+
324
+ Args:
325
+ hamiltonian: The Hamiltonian to estimate the expectation value of.
326
+ parameters: A list of the parameters for each iteration. Each item is a dictionary where each key should be the name of a parameter in the quantum program (parameters of the main function), and the value should be the value to set for that parameter.
327
+
328
+ Returns:
329
+ The execution job.
330
+ """
331
+ self.program.model.classical_execution_code = generate_code_snippet(
332
+ SupportedPrimitives.BATCH_ESTIMATE,
333
+ parameters=format_parameters(parameters),
334
+ hamiltonian=to_hamiltonian_str(hamiltonian),
335
+ )
336
+ self.program.execution_primitives_input = PrimitivesInput(
337
+ estimate=EstimateInput(
338
+ hamiltonian=_hamiltonian_to_pauli_operator(hamiltonian),
339
+ parameters=[parse_params(params) for params in parameters],
340
+ )
341
+ )
342
+ return execute(SerializedQuantumProgram(self.qprog))
classiq/execution/jobs.py CHANGED
@@ -3,10 +3,19 @@ from datetime import datetime
3
3
  from typing import Any, List, Optional, Union
4
4
  from urllib.parse import urljoin
5
5
 
6
- from classiq.interface.exceptions import ClassiqAPIError
6
+ from classiq.interface.exceptions import (
7
+ ClassiqAPIError,
8
+ ClassiqError,
9
+ )
7
10
  from classiq.interface.execution.jobs import ExecutionJobDetailsV1
8
11
  from classiq.interface.executor.execution_request import ExecutionJobDetails
9
12
  from classiq.interface.executor.execution_result import ResultsCollection
13
+ from classiq.interface.executor.result import (
14
+ EstimationResult,
15
+ EstimationResults,
16
+ ExecutionDetails,
17
+ MultipleExecutionDetails,
18
+ )
10
19
  from classiq.interface.jobs import JobStatus, JSONObject
11
20
  from classiq.interface.server.routes import EXECUTION_JOBS_NON_VERSIONED_FULL_PATH
12
21
 
@@ -21,6 +30,13 @@ _JOB_DETAILS_VERSION = "v1"
21
30
  _JOB_RESULT_VERSION = "v1"
22
31
 
23
32
 
33
+ class ClassiqExecutionResultError(ClassiqError):
34
+ def __init__(self, primitive: str) -> None:
35
+ super().__init__(
36
+ f"Execution job does not contain a single {primitive!r} result, make sure you use the 'get_*_result' method matching the primitive you executed. You can use the 'result' method to see the general result."
37
+ )
38
+
39
+
24
40
  class ExecutionJob:
25
41
  _details: _JobDetails
26
42
  _result: Optional[ResultsCollection]
@@ -112,6 +128,96 @@ class ExecutionJob:
112
128
  def result_value(self, *args: Any, **kwargs: Any) -> Any:
113
129
  return self.result(*args, **kwargs)[0].value
114
130
 
131
+ def get_sample_result(self) -> ExecutionDetails:
132
+ """
133
+ Returns the job's result as a single sample result after validation. If the result is not yet available, waits for it.
134
+
135
+ Returns:
136
+ The sample result of the execution job.
137
+
138
+ Raises:
139
+ ClassiqExecutionResultError: In case the result does not contain a single sample result.
140
+ ClassiqAPIError: In case the job has failed.
141
+ """
142
+ results = self.result()
143
+ if len(results) != 1:
144
+ raise ClassiqExecutionResultError("sample")
145
+
146
+ result = results[0].value
147
+ if isinstance(result, ExecutionDetails):
148
+ return result
149
+ if isinstance(result, MultipleExecutionDetails) and len(result.details) == 1:
150
+ return result.details[0]
151
+ raise ClassiqExecutionResultError("sample")
152
+
153
+ def get_batch_sample_result(self) -> List[ExecutionDetails]:
154
+ """
155
+ Returns the job's result as a single batch_sample result after validation. If the result is not yet available, waits for it.
156
+
157
+ Returns:
158
+ The batch_sample result of the execution job.
159
+
160
+ Raises:
161
+ ClassiqExecutionResultError: In case the result does not contain a single batch_sample result.
162
+ ClassiqAPIError: In case the job has failed.
163
+ """
164
+ results = self.result()
165
+ if len(results) != 1:
166
+ raise ClassiqExecutionResultError("batch_sample")
167
+
168
+ result = results[0].value
169
+ if isinstance(result, ExecutionDetails):
170
+ return [result]
171
+ if isinstance(result, MultipleExecutionDetails):
172
+ return result.details
173
+
174
+ raise ClassiqExecutionResultError("batch_sample")
175
+
176
+ def get_estimate_result(self) -> EstimationResult:
177
+ """
178
+ Returns the job's result as a single estimate result after validation. If the result is not yet available, waits for it.
179
+
180
+ Returns:
181
+ The estimate result of the execution job.
182
+
183
+ Raises:
184
+ ClassiqExecutionResultError: In case the result does not contain a single estimate result.
185
+ ClassiqAPIError: In case the job has failed.
186
+ """
187
+ results = self.result()
188
+ if len(results) != 1:
189
+ raise ClassiqExecutionResultError("estimate")
190
+
191
+ result = results[0].value
192
+ if isinstance(result, EstimationResult):
193
+ return result
194
+ if isinstance(result, EstimationResults) and len(result.results) == 1:
195
+ return result.results[0]
196
+ raise ClassiqExecutionResultError("estimate")
197
+
198
+ def get_batch_estimate_result(self) -> List[EstimationResult]:
199
+ """
200
+ Returns the job's result as a single batch_estimate result after validation. If the result is not yet available, waits for it.
201
+
202
+ Returns:
203
+ The batch_estimate result of the execution job.
204
+
205
+ Raises:
206
+ ClassiqExecutionResultError: In case the result does not contain a single batch_estimate result.
207
+ ClassiqAPIError: In case the job has failed.
208
+ """
209
+ results = self.result()
210
+ if len(results) != 1:
211
+ raise ClassiqExecutionResultError("batch_estimate")
212
+
213
+ result = results[0].value
214
+ if isinstance(result, EstimationResult):
215
+ return [result]
216
+ if isinstance(result, EstimationResults):
217
+ return result.results
218
+
219
+ raise ClassiqExecutionResultError("batch_estimate")
220
+
115
221
  async def poll_async(self, timeout_sec: Optional[float] = None) -> None:
116
222
  if not self.status.is_final():
117
223
  await self._poll_job(timeout_sec=timeout_sec)
@@ -141,6 +247,19 @@ class ExecutionJob:
141
247
 
142
248
  rename = syncify_function(rename_async)
143
249
 
250
+ async def cancel_async(self) -> None:
251
+ """
252
+ Cancels the execution job. This implies the cancellation of any ongoing jobs
253
+ sent to the provider during this execution job.
254
+
255
+ The function returns without waiting to the actual cancellation. It is possible
256
+ to continue polling the job in order to ensure its cancellation, which might
257
+ not be immediate.
258
+ """
259
+ await ApiWrapper.call_cancel_execution_job(self._job_id)
260
+
261
+ cancel = syncify_function(cancel_async)
262
+
144
263
  @property
145
264
  def ide_url(self) -> str:
146
265
  base_url = client().config.ide
@@ -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.46.1'
6
+ SEMVER_VERSION = '0.48.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -68,7 +68,6 @@ class AzureQuantumBackendNames(StrEnum):
68
68
  IONQ_SIMULATOR = "ionq.simulator"
69
69
  MICROSOFT_ESTIMATOR = "microsoft.estimator"
70
70
  MICROSOFT_FULLSTATE_SIMULATOR = "microsoft.simulator.fullstate"
71
- RIGETTI_ASPEN3 = "rigetti.qpu.aspen-m-3"
72
71
  RIGETTI_SIMULATOR = "rigetti.sim.qvm"
73
72
  RIGETTI_ANKAA2 = "rigetti.qpu.ankaa-2"
74
73
  RIGETTI_ANKAA9 = "rigetti.qpu.ankaa-9q-1"
@@ -1,5 +1,5 @@
1
1
  import json
2
- from typing import Any, Dict, Optional, Union
2
+ from typing import Any, Dict, Mapping, Optional, Tuple, Union
3
3
  from uuid import UUID
4
4
 
5
5
  from pydantic import BaseModel, Field
@@ -14,10 +14,12 @@ ParameterValue = Union[float, int, str, None]
14
14
 
15
15
  class FunctionDebugInfo(BaseModel):
16
16
  name: str
17
+ # Parameters describe classical parameters passed to function
17
18
  # ParameterValue appears in type only for backwards compatibility
18
19
  parameters: Dict[str, str] = Field(type=Dict[str, ParameterValue])
19
20
  level: OperationLevel
20
21
  is_allocate_or_free: bool = Field(default=False)
22
+ port_to_passed_variable_map: Dict[str, str] = Field(default_factory=dict)
21
23
 
22
24
  @staticmethod
23
25
  def param_controller(value: Any) -> str:
@@ -26,6 +28,26 @@ class FunctionDebugInfo(BaseModel):
26
28
  except TypeError:
27
29
  return repr(value)
28
30
 
31
+ def update_map_from_port_mapping(self, port_mapping: Mapping[str, str]) -> None:
32
+ new_port_to_passed_variable_map = self.port_to_passed_variable_map.copy()
33
+ for old_key, new_key in port_mapping.items():
34
+ if old_key in new_port_to_passed_variable_map:
35
+ new_port_to_passed_variable_map[new_key] = (
36
+ new_port_to_passed_variable_map.pop(old_key)
37
+ )
38
+ self.port_to_passed_variable_map = new_port_to_passed_variable_map
39
+
40
+ def update_map_from_inout_port_mapping(
41
+ self, port_mapping: Mapping[str, Tuple[str, str]]
42
+ ) -> None:
43
+ new_port_to_passed_variable_map = self.port_to_passed_variable_map.copy()
44
+ for old_key, (new_key1, new_key2) in port_mapping.items():
45
+ if old_key in new_port_to_passed_variable_map:
46
+ value = new_port_to_passed_variable_map.pop(old_key)
47
+ new_port_to_passed_variable_map[new_key1] = value
48
+ new_port_to_passed_variable_map[new_key2] = value
49
+ self.port_to_passed_variable_map = new_port_to_passed_variable_map
50
+
29
51
 
30
52
  class DebugInfoCollection(BaseModel):
31
53
  # Pydantic only started supporting UUID as keys in Pydantic V2
@@ -0,0 +1,17 @@
1
+ from typing import List, Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from classiq.interface.chemistry.operator import PauliOperator
6
+ from classiq.interface.executor.quantum_code import Arguments
7
+ from classiq.interface.helpers.custom_encoders import CUSTOM_ENCODERS
8
+
9
+
10
+ class EstimateInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
11
+ hamiltonian: PauliOperator
12
+ parameters: List[Arguments]
13
+
14
+
15
+ class PrimitivesInput(BaseModel, json_encoders=CUSTOM_ENCODERS):
16
+ sample: Optional[List[Arguments]] = Field(default=None)
17
+ estimate: Optional[EstimateInput] = Field(default=None)
@@ -1,6 +1,6 @@
1
- from typing import List, Tuple
1
+ from typing import List
2
2
 
3
- from pydantic import BaseModel
3
+ from pydantic import BaseModel, Field
4
4
 
5
5
  from classiq.interface.executor.result import ExecutionDetails
6
6
  from classiq.interface.generator.functions.classical_type import QmodPyObject
@@ -14,6 +14,6 @@ class IQAEIterationData(BaseModel):
14
14
 
15
15
  class IQAEResult(VersionedModel, QmodPyObject):
16
16
  estimation: float
17
- confidence_interval: Tuple[float, float]
17
+ confidence_interval: List[float] = Field(min_items=2, max_items=2)
18
18
  iterations_data: List[IQAEIterationData]
19
19
  warnings: List[str]
@@ -305,7 +305,9 @@ class EstimationMetadata(BaseModel, extra=pydantic.Extra.allow):
305
305
 
306
306
  class EstimationResult(BaseModel, QmodPyObject):
307
307
  value: Complex = pydantic.Field(..., description="Estimation for the operator")
308
- variance: Complex = pydantic.Field(..., description="Variance of the estimation")
308
+ variance: Optional[Complex] = pydantic.Field(
309
+ description="Variance of the estimation", default=None
310
+ )
309
311
  metadata: EstimationMetadata = pydantic.Field(
310
312
  ..., description="Metadata for the estimation"
311
313
  )
@@ -1,5 +1,5 @@
1
1
  import abc
2
- from typing import ClassVar, Iterable, Optional, Tuple
2
+ from typing import ClassVar, Final, Iterable, Optional, Tuple
3
3
 
4
4
  import pydantic
5
5
 
@@ -9,7 +9,10 @@ from classiq.interface.generator.arith.machine_precision import (
9
9
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
10
10
  from classiq.interface.generator.function_params import FunctionParams
11
11
 
12
- DEFAULT_GARBAGE_OUT_NAME: str = "extra_qubits"
12
+ DEFAULT_GARBAGE_OUT_NAME: Final[str] = "extra_qubits"
13
+ MODULO_WITH_FRACTION_PLACES_ERROR_MSG: Final[str] = (
14
+ "Modulo with fraction places not supported"
15
+ )
13
16
 
14
17
 
15
18
  class ArithmeticOperationParams(FunctionParams):