classiq 0.32.1__py3-none-any.whl → 0.34.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 (74) hide show
  1. classiq/__init__.py +2 -1
  2. classiq/_analyzer_extras/_ipywidgets_async_extension.py +1 -1
  3. classiq/_internals/api_wrapper.py +34 -23
  4. classiq/_internals/jobs.py +41 -14
  5. classiq/analyzer/__init__.py +3 -1
  6. classiq/applications/__init__.py +3 -1
  7. classiq/applications/benchmarking/__init__.py +3 -1
  8. classiq/applications/chemistry/__init__.py +3 -1
  9. classiq/applications/combinatorial_optimization/__init__.py +3 -1
  10. classiq/applications/combinatorial_optimization/examples/__init__.py +3 -1
  11. classiq/applications/finance/__init__.py +3 -1
  12. classiq/applications/qnn/__init__.py +3 -1
  13. classiq/applications/qnn/datasets/__init__.py +3 -1
  14. classiq/applications/qsvm/qsvm.py +1 -1
  15. classiq/applications_model_constructors/grover_model_constructor.py +25 -8
  16. classiq/builtin_functions/__init__.py +3 -1
  17. classiq/execution/__init__.py +3 -1
  18. classiq/execution/jobs.py +57 -20
  19. classiq/executor.py +4 -11
  20. classiq/interface/_version.py +1 -1
  21. classiq/interface/analyzer/analysis_params.py +1 -1
  22. classiq/interface/backend/backend_preferences.py +17 -0
  23. classiq/interface/backend/pydantic_backend.py +8 -0
  24. classiq/interface/backend/quantum_backend_providers.py +15 -1
  25. classiq/interface/chemistry/ground_state_problem.py +1 -1
  26. classiq/interface/chemistry/operator.py +198 -0
  27. classiq/interface/executor/execution_request.py +5 -12
  28. classiq/interface/generator/circuit_code/types_and_constants.py +1 -1
  29. classiq/interface/generator/complex_type.py +4 -1
  30. classiq/interface/generator/functions/__init__.py +3 -1
  31. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/__init__.py +0 -1
  32. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/atomic_quantum_functions.py +39 -39
  33. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/std_lib_functions.py +251 -87
  34. classiq/interface/generator/functions/core_lib_declarations/quantum_operators.py +5 -54
  35. classiq/interface/generator/generated_circuit.py +14 -43
  36. classiq/interface/generator/generated_circuit_data.py +26 -34
  37. classiq/interface/generator/model/preferences/preferences.py +3 -3
  38. classiq/interface/generator/partitioned_register.py +1 -1
  39. classiq/interface/generator/quantum_function_call.py +1 -1
  40. classiq/interface/generator/validations/validator_functions.py +4 -2
  41. classiq/interface/hardware.py +3 -2
  42. classiq/interface/ide/show.py +1 -14
  43. classiq/interface/model/bind_operation.py +20 -0
  44. classiq/interface/model/handle_binding.py +8 -0
  45. classiq/interface/model/native_function_definition.py +15 -5
  46. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +8 -3
  47. classiq/interface/model/quantum_expressions/arithmetic_operation.py +9 -4
  48. classiq/interface/model/quantum_expressions/quantum_expression.py +10 -5
  49. classiq/interface/model/quantum_function_call.py +24 -347
  50. classiq/interface/model/quantum_function_declaration.py +7 -11
  51. classiq/interface/model/quantum_statement.py +13 -7
  52. classiq/interface/model/validations/handle_validation_base.py +1 -2
  53. classiq/interface/model/validations/handles_validator.py +34 -8
  54. classiq/interface/model/variable_declaration_statement.py +8 -0
  55. classiq/interface/server/routes.py +11 -16
  56. classiq/model/__init__.py +3 -1
  57. classiq/model/function_handler.py +1 -1
  58. classiq/model/function_handler.pyi +88 -88
  59. classiq/qmod/declaration_inferrer.py +37 -18
  60. classiq/qmod/model_state_container.py +6 -3
  61. classiq/qmod/qmod_builtins.py +892 -4
  62. classiq/qmod/qmod_parameter.py +24 -8
  63. classiq/qmod/qmod_variable.py +2 -1
  64. classiq/qmod/quantum_expandable.py +6 -2
  65. classiq/qmod/quantum_function.py +11 -10
  66. classiq/quantum_functions/quantum_function.py +4 -1
  67. {classiq-0.32.1.dist-info → classiq-0.34.0.dist-info}/METADATA +1 -1
  68. {classiq-0.32.1.dist-info → classiq-0.34.0.dist-info}/RECORD +69 -72
  69. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/apps_lib_functions.py +0 -262
  70. classiq/interface/model/clients/__init__.py +0 -0
  71. classiq/interface/model/clients/qmod/__init__.py +0 -0
  72. classiq/interface/model/clients/qmod/qmod_builtins.py +0 -908
  73. classiq/interface/model/semantics.py +0 -15
  74. {classiq-0.32.1.dist-info → classiq-0.34.0.dist-info}/WHEEL +0 -0
classiq/__init__.py CHANGED
@@ -1,5 +1,6 @@
1
1
  """Classiq SDK."""
2
2
  import sys
3
+ from typing import List
3
4
 
4
5
  from classiq.interface._version import VERSION as _VERSION
5
6
  from classiq.interface.generator.application_apis import * # noqa: F403
@@ -103,5 +104,5 @@ __all__ = (
103
104
  )
104
105
 
105
106
 
106
- def __dir__():
107
+ def __dir__() -> List[str]:
107
108
  return __all__
@@ -53,7 +53,7 @@ async def _create_interactive_loop_async(
53
53
  def _wait_for_change(widget: widgets) -> asyncio.Future:
54
54
  future: asyncio.Future = asyncio.Future()
55
55
 
56
- def _get_value(change) -> None:
56
+ def _get_value(change) -> None: # type: ignore[no-untyped-def]
57
57
  future.set_result(change.new)
58
58
  widget.unobserve(_get_value, "value")
59
59
 
@@ -1,8 +1,7 @@
1
1
  import json
2
- from typing import Any, Dict, Optional, Protocol, Type, TypeVar, Union
2
+ from typing import Dict, Optional, Protocol, Type, TypeVar
3
3
 
4
4
  import pydantic
5
- from pydantic import parse_obj_as
6
5
 
7
6
  import classiq.interface.pyomo_extension # noqa: F401 - patches pyomo to add few features
8
7
  from classiq.interface.analyzer import analysis_params, result as analysis_result
@@ -19,10 +18,7 @@ from classiq._internals.enum_utils import StrEnum
19
18
  from classiq._internals.jobs import JobPoller
20
19
  from classiq.exceptions import ClassiqAPIError, ClassiqValueError
21
20
 
22
- _FAIL_FAST_INDICATOR = "{"
23
21
  ResultType = TypeVar("ResultType", bound=pydantic.BaseModel)
24
- OtherResultType = TypeVar("OtherResultType", bound=pydantic.BaseModel)
25
- _Circuit = Union[generator_result.GeneratedCircuit, generator_result.ExecutionCircuit]
26
22
 
27
23
 
28
24
  class HTTPMethod(StrEnum):
@@ -45,16 +41,6 @@ def _parse_job_response(
45
41
  return output_type.parse_obj(description)
46
42
 
47
43
 
48
- def _parse_job_response_multiple_outputs(
49
- job_result: JobDescription[JSONObject],
50
- output_type: Any, # UnionType in Python 3.10,
51
- ) -> Union[ResultType, OtherResultType]:
52
- description = job_result.description
53
- if job_result.status != JobStatus.COMPLETED:
54
- raise ClassiqAPIError(description["details"])
55
- return parse_obj_as(output_type, description)
56
-
57
-
58
44
  class ApiWrapper:
59
45
  @classmethod
60
46
  async def _call_task_pydantic(
@@ -82,22 +68,45 @@ class ApiWrapper:
82
68
  return res
83
69
 
84
70
  @classmethod
85
- async def call_generation_task(cls, model: ModelInput) -> _Circuit:
71
+ async def call_generation_task(
72
+ cls, model: ModelInput
73
+ ) -> generator_result.GeneratedCircuit:
86
74
  poller = JobPoller(base_url=routes.TASKS_GENERATE_FULL_PATH)
87
75
  result = await poller.run_pydantic(model, timeout_sec=None)
88
- return _parse_job_response_multiple_outputs(result, _Circuit)
76
+ return _parse_job_response(result, generator_result.GeneratedCircuit)
89
77
 
90
78
  @classmethod
91
79
  async def call_execute_generated_circuit(
92
- cls,
93
- circuit: _Circuit,
94
- ) -> JobID:
80
+ cls, circuit: generator_result.GeneratedCircuit
81
+ ) -> execution_request.ExecutionJobDetails:
95
82
  data = await cls._call_task_pydantic(
96
83
  http_method=HTTPMethod.POST,
97
- url=routes.EXECUTE_GENERATED_CIRCUIT_FULL_PATH,
84
+ url=routes.EXECUTION_JOBS_FULL_PATH,
98
85
  model=circuit,
99
86
  )
100
- return JobID.parse_obj(data)
87
+ return execution_request.ExecutionJobDetails.parse_obj(data)
88
+
89
+ @classmethod
90
+ async def call_get_execution_job_details(
91
+ cls,
92
+ job_id: JobID,
93
+ ) -> execution_request.ExecutionJobDetails:
94
+ data = await cls._call_task(
95
+ http_method=HTTPMethod.GET,
96
+ url=f"{routes.EXECUTION_JOBS_FULL_PATH }/{job_id.job_id}",
97
+ )
98
+ return execution_request.ExecutionJobDetails.parse_obj(data)
99
+
100
+ @classmethod
101
+ async def call_get_execution_job_result(
102
+ cls,
103
+ job_id: JobID,
104
+ ) -> execution_request.ExecuteGeneratedCircuitResults:
105
+ data = await cls._call_task(
106
+ http_method=HTTPMethod.GET,
107
+ url=f"{routes.EXECUTION_JOBS_FULL_PATH }/{job_id.job_id}/result",
108
+ )
109
+ return execution_request.ExecuteGeneratedCircuitResults.parse_obj(data)
101
110
 
102
111
  @classmethod
103
112
  async def call_execute_estimate(
@@ -209,7 +218,9 @@ class ApiWrapper:
209
218
  async def call_generate_hamiltonian_task(
210
219
  cls, problem: ground_state_problem.CHEMISTRY_PROBLEMS_TYPE
211
220
  ) -> operator.PauliOperator:
212
- poller = JobPoller(base_url=routes.CHEMISTRY_GENERATE_HAMILTONIAN_FULL_PATH)
221
+ poller = JobPoller(
222
+ base_url=routes.GENERATE_HAMILTONIAN_FULL_PATH, use_versioned_url=False
223
+ )
213
224
  result = await poller.run_pydantic(problem, timeout_sec=None)
214
225
  return _parse_job_response(result, operator.PauliOperator)
215
226
 
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  import logging
3
- from typing import Dict, Iterable, Optional, Set
3
+ from typing import Callable, Dict, Iterable, Optional, Set, TypeVar
4
4
 
5
5
  import httpx
6
6
  import pydantic
@@ -15,6 +15,7 @@ from classiq.exceptions import ClassiqAPIError
15
15
  _URL_PATH_SEP = "/"
16
16
  GeneralJobDescription = JobDescription[JSONObject]
17
17
  _logger = logging.getLogger(__name__)
18
+ T = TypeVar("T")
18
19
 
19
20
 
20
21
  def _join_url_path(*parts: str) -> str:
@@ -33,6 +34,17 @@ def _join_url_path(*parts: str) -> str:
33
34
  )
34
35
 
35
36
 
37
+ def _general_job_description_parser(
38
+ json_response: JSONObject,
39
+ ) -> Optional[GeneralJobDescription]:
40
+ job_description: GeneralJobDescription = GeneralJobDescription.parse_obj(
41
+ json_response
42
+ )
43
+ if job_description.status.is_final():
44
+ return job_description
45
+ return None
46
+
47
+
36
48
  class JobPoller:
37
49
  INITIAL_INTERVAL_SEC = 1
38
50
  INTERVAL_FACTOR = 2
@@ -40,11 +52,18 @@ class JobPoller:
40
52
  DEV_INTERVAL = 0.05
41
53
 
42
54
  def __init__(
43
- self, base_url: str, required_headers: Optional[Set[str]] = None
55
+ self,
56
+ base_url: str,
57
+ required_headers: Optional[Set[str]] = None,
58
+ use_versioned_url: bool = True,
44
59
  ) -> None:
45
60
  self._required_headers = required_headers or set()
46
61
  client_instance = client()
47
- self._base_url = client_instance.make_versioned_url(base_url)
62
+ self._base_url = (
63
+ client_instance.make_versioned_url(base_url)
64
+ if use_versioned_url
65
+ else base_url
66
+ )
48
67
  self._async_client = client_instance.async_client()
49
68
  self._mode = client_instance.config.mode
50
69
 
@@ -95,9 +114,12 @@ class JobPoller:
95
114
  interval = min(interval * self.INTERVAL_FACTOR, self.FINAL_INTERVAL_SEC)
96
115
 
97
116
  async def _poll(
98
- self, poll_url: str, timeout_sec: Optional[float]
99
- ) -> GeneralJobDescription:
100
- async def poller():
117
+ self,
118
+ poll_url: str,
119
+ timeout_sec: Optional[float],
120
+ response_parser: Callable[[JSONObject], Optional[T]] = _general_job_description_parser, # type: ignore[assignment]
121
+ ) -> T:
122
+ async def poller() -> JSONObject:
101
123
  nonlocal self, poll_url
102
124
  raw_response = await self._request(http_method="GET", url=poll_url)
103
125
  return raw_response.json()
@@ -105,19 +127,24 @@ class JobPoller:
105
127
  async for json_response in poll_for(
106
128
  poller, timeout_sec=timeout_sec, interval_sec=self._interval_sec()
107
129
  ):
108
- job_description: GeneralJobDescription = GeneralJobDescription.parse_obj(
109
- json_response
110
- )
111
- if job_description.status.is_final():
112
- return job_description
130
+ parsed = response_parser(json_response)
131
+ if parsed is not None:
132
+ return parsed
113
133
  raise ClassiqAPIError("API request timed out")
114
134
 
115
135
  async def poll(
116
- self, job_id: JobID, timeout_sec: Optional[float]
117
- ) -> GeneralJobDescription:
136
+ self,
137
+ job_id: JobID,
138
+ timeout_sec: Optional[float],
139
+ response_parser: Callable[[JSONObject], Optional[T]] = _general_job_description_parser, # type: ignore[assignment]
140
+ ) -> T:
118
141
  poll_url = self._make_poll_url(job_id=job_id)
119
142
  async with self._async_client:
120
- return await self._poll(poll_url=poll_url, timeout_sec=timeout_sec)
143
+ return await self._poll(
144
+ poll_url=poll_url,
145
+ response_parser=response_parser,
146
+ timeout_sec=timeout_sec,
147
+ )
121
148
 
122
149
  async def _cancel(self, poll_url: str) -> None:
123
150
  _logger.info("Cancelling job %s", poll_url, exc_info=True)
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  import classiq.analyzer.show_interactive_hack
2
4
  from classiq.analyzer.analyzer import Analyzer
3
5
 
@@ -6,5 +8,5 @@ from ..analyzer import rb
6
8
  __all__ = ["rb"]
7
9
 
8
10
 
9
- def __dir__():
11
+ def __dir__() -> List[str]:
10
12
  return ["rb"]
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from classiq.applications import (
2
4
  benchmarking,
3
5
  chemistry,
@@ -18,5 +20,5 @@ __all__ = [
18
20
  _NON_IMPORTED_PUBLIC_SUBMODULES = ["qnn"]
19
21
 
20
22
 
21
- def __dir__():
23
+ def __dir__() -> List[str]:
22
24
  return __all__ + _NON_IMPORTED_PUBLIC_SUBMODULES
@@ -1,7 +1,9 @@
1
+ from typing import List
2
+
1
3
  from classiq.applications.benchmarking.mirror_benchmarking import MirrorBenchmarking
2
4
 
3
5
  __all__ = ["MirrorBenchmarking"]
4
6
 
5
7
 
6
- def __dir__():
8
+ def __dir__() -> List[str]:
7
9
  return __all__
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from classiq.interface.chemistry.fermionic_operator import (
2
4
  FermionicOperator,
3
5
  SummedFermionicOperator,
@@ -30,5 +32,5 @@ __all__ = [
30
32
  ]
31
33
 
32
34
 
33
- def __dir__():
35
+ def __dir__() -> List[str]:
34
36
  return __all__
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from classiq.interface.combinatorial_optimization import examples
2
4
  from classiq.interface.combinatorial_optimization.solver_types import QSolver
3
5
 
@@ -11,5 +13,5 @@ __all__ = [
11
13
  ]
12
14
 
13
15
 
14
- def __dir__():
16
+ def __dir__() -> List[str]:
15
17
  return __all__
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from classiq.interface.combinatorial_optimization.examples.ascending_sequence import (
2
4
  ascending_sequence,
3
5
  )
@@ -45,5 +47,5 @@ __all__ = [
45
47
  ]
46
48
 
47
49
 
48
- def __dir__():
50
+ def __dir__() -> List[str]:
49
51
  return __all__
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from classiq.interface.finance import (
2
4
  function_input,
3
5
  gaussian_model_input,
@@ -13,5 +15,5 @@ __all__ = [
13
15
  ]
14
16
 
15
17
 
16
- def __dir__():
18
+ def __dir__() -> List[str]:
17
19
  return __all__
@@ -1,5 +1,7 @@
1
1
  # This file will be called first whenever any file from within this directory is imported.
2
2
  # Thus, we'll test dependencies only here, once.
3
+ from typing import List
4
+
3
5
  try:
4
6
  import torch
5
7
  except ImportError as exc:
@@ -11,5 +13,5 @@ from ..qnn.qlayer import QLayer
11
13
  __all__ = ["datasets", "types", "QLayer"]
12
14
 
13
15
 
14
- def __dir__():
16
+ def __dir__() -> List[str]:
15
17
  return __all__
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from ..datasets import builtin_datasets
2
4
  from ..datasets.builtin_datasets import (
3
5
  DATALOADER_NOT,
@@ -29,5 +31,5 @@ __all__ = [
29
31
  ]
30
32
 
31
33
 
32
- def __dir__():
34
+ def __dir__() -> List[str]:
33
35
  return __all__
@@ -13,5 +13,5 @@ __all__ = [
13
13
  ]
14
14
 
15
15
 
16
- def __dir__():
16
+ def __dir__() -> List[str]:
17
17
  return __all__
@@ -4,6 +4,7 @@ from classiq.interface.generator.expressions.expression import Expression
4
4
  from classiq.interface.generator.functions.port_declaration import (
5
5
  PortDeclarationDirection,
6
6
  )
7
+ from classiq.interface.model.bind_operation import BindOperation
7
8
  from classiq.interface.model.handle_binding import HandleBinding, SlicedHandleBinding
8
9
  from classiq.interface.model.local_variable_declaration import LocalVariableDeclaration
9
10
  from classiq.interface.model.model import Model, SerializedModel
@@ -16,6 +17,7 @@ from classiq.interface.model.quantum_function_call import (
16
17
  QuantumFunctionCall,
17
18
  QuantumLambdaFunction,
18
19
  )
20
+ from classiq.interface.model.quantum_statement import QuantumStatement
19
21
  from classiq.interface.model.quantum_type import (
20
22
  QuantumFixedReal,
21
23
  QuantumInteger,
@@ -33,7 +35,7 @@ def split_registers(
33
35
  register_names: List[str],
34
36
  register_sizes: List[int],
35
37
  input_wire_name: str,
36
- ) -> List[QuantumFunctionCall]:
38
+ ) -> List[QuantumStatement]:
37
39
  if len(register_names) == 0:
38
40
  return []
39
41
  wires = (
@@ -41,6 +43,15 @@ def split_registers(
41
43
  + [f"split{i}" for i in range(len(register_names) - 2)]
42
44
  + [register_names[-1]]
43
45
  )
46
+
47
+ if len(register_names) == 1:
48
+ return [
49
+ BindOperation(
50
+ in_handle=HandleBinding(name=wires[0]),
51
+ out_handle=HandleBinding(name=wires[1]),
52
+ )
53
+ ]
54
+
44
55
  return [
45
56
  QuantumFunctionCall(
46
57
  function="split",
@@ -119,6 +130,18 @@ def grover_main_port_declarations(
119
130
  }
120
131
 
121
132
 
133
+ def _generate_local_variable_declarations(
134
+ definitions: List[Tuple[str, RegisterUserInput]]
135
+ ) -> List[LocalVariableDeclaration]:
136
+ ret = [LocalVariableDeclaration(name="gsq")]
137
+ if len(definitions) >= 2:
138
+ ret += [
139
+ LocalVariableDeclaration(name=f"split{i}")
140
+ for i in range(len(definitions) - 2)
141
+ ]
142
+ return ret
143
+
144
+
122
145
  def construct_grover_model(
123
146
  definitions: List[Tuple[str, RegisterUserInput]],
124
147
  expression: str,
@@ -150,13 +173,7 @@ def construct_grover_model(
150
173
  port_declarations=grover_main_port_declarations(
151
174
  definitions, PortDeclarationDirection.Output
152
175
  ),
153
- local_handles=[
154
- LocalVariableDeclaration(name="gsq"),
155
- *[
156
- LocalVariableDeclaration(name=f"split{i}")
157
- for i in range(len(definitions) - 2)
158
- ],
159
- ],
176
+ local_handles=_generate_local_variable_declarations(definitions),
160
177
  body=[
161
178
  QuantumFunctionCall(
162
179
  function="grover_search",
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from classiq.interface.generator.function_param_list import * # noqa: F403
2
4
  from classiq.interface.generator.function_param_list_without_self_reference import * # noqa: F403
3
5
  from classiq.interface.generator.oracles.oracle_function_param_list import * # noqa: F403
@@ -37,5 +39,5 @@ __all__ = (
37
39
  )
38
40
 
39
41
 
40
- def __dir__():
42
+ def __dir__() -> List[str]:
41
43
  return __all__
@@ -1,3 +1,5 @@
1
+ from typing import List
2
+
1
3
  from ..executor import * # noqa: F403
2
4
  from ..executor import __all__ as _exec_all
3
5
  from ..interface.backend.backend_preferences import * # noqa: F403
@@ -17,5 +19,5 @@ __all__ = (
17
19
  )
18
20
 
19
21
 
20
- def __dir__():
22
+ def __dir__() -> List[str]:
21
23
  return __all__
classiq/execution/jobs.py CHANGED
@@ -1,40 +1,77 @@
1
- from dataclasses import dataclass, field
2
1
  from typing import Optional
3
2
 
3
+ from pydantic import PrivateAttr
4
+
4
5
  from classiq.interface.executor.execution_request import (
5
- ExecuteGeneratedCircuitResults,
6
+ ExecutionJobDetails,
6
7
  ResultsCollection,
7
8
  )
8
- from classiq.interface.server.routes import EXECUTE_GENERATED_CIRCUIT_FULL_PATH
9
+ from classiq.interface.jobs import JobStatus, JSONObject
10
+ from classiq.interface.server.routes import EXECUTION_JOBS_FULL_PATH
9
11
 
10
- from classiq._internals.api_wrapper import _parse_job_response
12
+ from classiq._internals.api_wrapper import ApiWrapper
11
13
  from classiq._internals.async_utils import syncify_function
12
14
  from classiq._internals.jobs import JobID, JobPoller
15
+ from classiq.exceptions import ClassiqAPIError
16
+
17
+
18
+ class ExecutionJob(ExecutionJobDetails):
19
+ _result: Optional[ResultsCollection] = PrivateAttr(default=None)
20
+
21
+ def __init__(self, details: ExecutionJobDetails) -> None:
22
+ super().__init__(**details.dict())
13
23
 
24
+ def _update_details(self, details: ExecutionJobDetails) -> None:
25
+ for k, v in details.dict().items():
26
+ setattr(self, k, v)
14
27
 
15
- @dataclass
16
- class ExecutionJob:
17
- id: str
18
- _result: Optional[ResultsCollection] = field(default=None, repr=False)
28
+ @classmethod
29
+ async def from_id_async(cls, id: str) -> "ExecutionJob":
30
+ details = await ApiWrapper.call_get_execution_job_details(JobID(job_id=id))
31
+ return cls(details)
32
+
33
+ @classmethod
34
+ def from_id(cls, id: str) -> "ExecutionJob":
35
+ return syncify_function(cls.from_id_async)(id)
36
+
37
+ @property
38
+ def _job_id(self) -> JobID:
39
+ return JobID(job_id=self.id)
19
40
 
20
41
  async def result_async(
21
42
  self, timeout_sec: Optional[float] = None
22
43
  ) -> ResultsCollection:
44
+ await self.poll_async(timeout_sec=timeout_sec)
45
+
46
+ if self.status == JobStatus.FAILED:
47
+ raise ClassiqAPIError(self.error or "")
48
+ if self.status == JobStatus.CANCELLED:
49
+ raise ClassiqAPIError("Job has been cancelled.")
50
+
23
51
  if self._result is None:
24
- self._result = await self._poll_result(timeout_sec=timeout_sec)
52
+ self._result = (
53
+ await ApiWrapper.call_get_execution_job_result(self._job_id)
54
+ ).results
25
55
  return self._result
26
56
 
27
57
  result = syncify_function(result_async)
28
58
 
29
- async def _poll_result(
30
- self, timeout_sec: Optional[float] = None
31
- ) -> ResultsCollection:
32
- poller = JobPoller(base_url=EXECUTE_GENERATED_CIRCUIT_FULL_PATH)
33
- response = await poller.poll(
34
- job_id=JobID(job_id=self.id), timeout_sec=timeout_sec
35
- )
36
- raw_result = _parse_job_response(
37
- job_result=response,
38
- output_type=ExecuteGeneratedCircuitResults,
59
+ async def poll_async(self, timeout_sec: Optional[float] = None) -> None:
60
+ if not self.status.is_final():
61
+ await self._poll_job(timeout_sec=timeout_sec)
62
+
63
+ poll = syncify_function(poll_async)
64
+
65
+ async def _poll_job(self, timeout_sec: Optional[float] = None) -> None:
66
+ def response_parser(json_response: JSONObject) -> Optional[bool]:
67
+ self._update_details(ExecutionJobDetails.parse_obj(json_response))
68
+ if self.status.is_final():
69
+ return True
70
+ return None
71
+
72
+ poller = JobPoller(base_url=EXECUTION_JOBS_FULL_PATH)
73
+ await poller.poll(
74
+ job_id=self._job_id,
75
+ response_parser=response_parser,
76
+ timeout_sec=timeout_sec,
39
77
  )
40
- return raw_result.results
classiq/executor.py CHANGED
@@ -3,7 +3,6 @@ import functools
3
3
  from typing import Optional, Tuple, Union
4
4
 
5
5
  import more_itertools
6
- from pydantic import parse_raw_as
7
6
  from typing_extensions import TypeAlias
8
7
 
9
8
  from classiq.interface.backend.backend_preferences import BackendPreferencesTypes
@@ -19,10 +18,7 @@ from classiq.interface.executor.execution_request import (
19
18
  from classiq.interface.executor.quantum_instruction_set import QuantumInstructionSet
20
19
  from classiq.interface.executor.quantum_program import MultipleArguments, QuantumProgram
21
20
  from classiq.interface.executor.result import ExecutionDetails
22
- from classiq.interface.generator.generated_circuit import (
23
- ExecutionCircuit,
24
- GeneratedCircuit,
25
- )
21
+ from classiq.interface.generator.generated_circuit import GeneratedCircuit
26
22
 
27
23
  from classiq._internals.api_wrapper import ApiWrapper
28
24
  from classiq._internals.async_utils import syncify_function
@@ -40,17 +36,14 @@ _MAX_ARGUMENTS_SIZE = 1024
40
36
 
41
37
  def _parse_serialized_qprog(
42
38
  quantum_program: SerializedQuantumProgram,
43
- ) -> Union[GeneratedCircuit, ExecutionCircuit]:
44
- return parse_raw_as(
45
- Union[GeneratedCircuit, ExecutionCircuit], # type:ignore[arg-type]
46
- quantum_program,
47
- )
39
+ ) -> GeneratedCircuit:
40
+ return GeneratedCircuit.parse_raw(quantum_program)
48
41
 
49
42
 
50
43
  async def execute_async(quantum_program: SerializedQuantumProgram) -> ExecutionJob:
51
44
  circuit = _parse_serialized_qprog(quantum_program)
52
45
  result = await ApiWrapper.call_execute_generated_circuit(circuit)
53
- return ExecutionJob(id=result.job_id)
46
+ return ExecutionJob(details=result)
54
47
 
55
48
 
56
49
  execute = syncify_function(execute_async)
@@ -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.32.1'
6
+ SEMVER_VERSION = '0.34.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -82,7 +82,7 @@ class ChemistryGenerationParams(pydantic.BaseModel):
82
82
  title = "Chemistry"
83
83
 
84
84
  molecule: MoleculeProblem = pydantic.Field(
85
- title="Molecule Problem",
85
+ title="Molecule",
86
86
  default=...,
87
87
  description="The molecule to generate the VQE ansatz for",
88
88
  )
@@ -9,6 +9,7 @@ from pydantic import BaseModel, validator
9
9
  from classiq.interface.backend import pydantic_backend
10
10
  from classiq.interface.backend.quantum_backend_providers import (
11
11
  EXACT_SIMULATORS,
12
+ AliceBobBackendNames,
12
13
  AmazonBraketBackendNames,
13
14
  AzureQuantumBackendNames,
14
15
  ClassiqAerBackendNames,
@@ -55,6 +56,19 @@ class BackendPreferences(BaseModel):
55
56
  AWS_DEFAULT_JOB_TIMEOUT_SECONDS = int(timedelta(minutes=5).total_seconds())
56
57
 
57
58
 
59
+ class AliceBobBackendPreferences(BackendPreferences):
60
+ backend_service_provider: ProviderTypeVendor.ALICE_BOB
61
+ api_key: pydantic_backend.PydanticAliceBobApiKeyType = pydantic.Field(
62
+ ..., description="AliceBob API key"
63
+ )
64
+
65
+ @pydantic.root_validator(pre=True)
66
+ def _set_backend_service_provider(cls, values: Dict[str, Any]) -> Dict[str, Any]:
67
+ return values_with_discriminator(
68
+ values, "backend_service_provider", ProviderVendor.ALICE_AND_BOB
69
+ )
70
+
71
+
58
72
  class ClassiqBackendPreferences(BackendPreferences):
59
73
  backend_service_provider: ProviderTypeVendor.CLASSIQ
60
74
 
@@ -214,6 +228,7 @@ BackendPreferencesTypes = Union[
214
228
  AwsBackendPreferences,
215
229
  IonqBackendPreferences,
216
230
  GCPBackendPreferences,
231
+ AliceBobBackendPreferences,
217
232
  ]
218
233
 
219
234
  __all__ = [
@@ -230,6 +245,8 @@ __all__ = [
230
245
  "IonqBackendNames",
231
246
  "ClassiqNvidiaBackendNames",
232
247
  "GCPBackendPreferences",
248
+ "AliceBobBackendPreferences",
249
+ "AliceBobBackendNames",
233
250
  ]
234
251
 
235
252