classiq 0.34.0__py3-none-any.whl → 0.35.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 (38) hide show
  1. classiq/_internals/api_wrapper.py +52 -3
  2. classiq/_internals/client.py +4 -1
  3. classiq/applications_model_constructors/grover_model_constructor.py +1 -1
  4. classiq/execution/__init__.py +9 -2
  5. classiq/execution/jobs.py +84 -11
  6. classiq/interface/_version.py +1 -1
  7. classiq/interface/backend/quantum_backend_providers.py +2 -0
  8. classiq/interface/execution/__init__.py +0 -0
  9. classiq/interface/execution/jobs.py +28 -0
  10. classiq/interface/generator/arith/arithmetic_expression_validator.py +1 -0
  11. classiq/interface/generator/arith/arithmetic_param_getters.py +12 -0
  12. classiq/interface/generator/arith/binary_ops.py +34 -0
  13. classiq/interface/generator/expressions/expression.py +3 -0
  14. classiq/interface/generator/expressions/qmod_sized_proxy.py +12 -2
  15. classiq/interface/generator/function_param_list_without_self_reference.py +2 -0
  16. classiq/interface/generator/function_params.py +4 -0
  17. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/std_lib_functions.py +27 -0
  18. classiq/interface/generator/model/preferences/preferences.py +2 -0
  19. classiq/interface/hardware.py +1 -0
  20. classiq/interface/model/bind_operation.py +10 -0
  21. classiq/interface/model/handle_binding.py +18 -0
  22. classiq/interface/model/model.py +12 -3
  23. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +2 -1
  24. classiq/interface/model/quantum_expressions/arithmetic_operation.py +3 -1
  25. classiq/interface/model/quantum_expressions/quantum_expression.py +9 -6
  26. classiq/interface/server/routes.py +3 -0
  27. classiq/model/function_handler.pyi +85 -84
  28. classiq/qmod/__init__.py +2 -2
  29. classiq/qmod/builtins/__init__.py +8 -0
  30. classiq/qmod/{qmod_builtins.py → builtins/functions.py} +30 -136
  31. classiq/qmod/builtins/operations.py +19 -0
  32. classiq/qmod/builtins/structs.py +128 -0
  33. classiq/qmod/qmod_variable.py +4 -4
  34. classiq/qmod/quantum_callable.py +2 -2
  35. classiq/qmod/quantum_expandable.py +1 -1
  36. {classiq-0.34.0.dist-info → classiq-0.35.0.dist-info}/METADATA +1 -1
  37. {classiq-0.34.0.dist-info → classiq-0.35.0.dist-info}/RECORD +38 -33
  38. {classiq-0.34.0.dist-info → classiq-0.35.0.dist-info}/WHEEL +0 -0
@@ -7,6 +7,10 @@ import classiq.interface.pyomo_extension # noqa: F401 - patches pyomo to add fe
7
7
  from classiq.interface.analyzer import analysis_params, result as analysis_result
8
8
  from classiq.interface.analyzer.analysis_params import AnalysisRBParams
9
9
  from classiq.interface.chemistry import ground_state_problem, operator
10
+ from classiq.interface.execution.jobs import (
11
+ ExecutionJobDetailsV1,
12
+ ExecutionJobsQueryResultsV1,
13
+ )
10
14
  from classiq.interface.executor import execution_request, result as execute_result
11
15
  from classiq.interface.generator import generated_circuit as generator_result
12
16
  from classiq.interface.jobs import JobDescription, JobID, JobStatus, JSONObject
@@ -25,6 +29,7 @@ class HTTPMethod(StrEnum):
25
29
  # Partial backport from Python 3.11
26
30
  GET = "GET"
27
31
  POST = "POST"
32
+ PATCH = "PATCH"
28
33
 
29
34
 
30
35
  class StatusType(Protocol):
@@ -44,13 +49,19 @@ def _parse_job_response(
44
49
  class ApiWrapper:
45
50
  @classmethod
46
51
  async def _call_task_pydantic(
47
- cls, http_method: str, url: str, model: pydantic.BaseModel
52
+ cls,
53
+ http_method: str,
54
+ url: str,
55
+ model: pydantic.BaseModel,
56
+ use_versioned_url: bool = True,
48
57
  ) -> dict:
49
58
  # TODO: we can't use model.dict() - it doesn't serialize complex class.
50
59
  # This was added because JSON serializer doesn't serialize complex type, and pydantic does.
51
60
  # We should add support for smarter json serialization.
52
61
  body = json.loads(model.json())
53
- return await cls._call_task(http_method, url, body)
62
+ return await cls._call_task(
63
+ http_method, url, body, use_versioned_url=use_versioned_url
64
+ )
54
65
 
55
66
  @classmethod
56
67
  async def _call_task(
@@ -59,9 +70,14 @@ class ApiWrapper:
59
70
  url: str,
60
71
  body: Optional[Dict] = None,
61
72
  params: Optional[Dict] = None,
73
+ use_versioned_url: bool = True,
62
74
  ) -> dict:
63
75
  res = await client().call_api(
64
- http_method=http_method, url=url, body=body, params=params
76
+ http_method=http_method,
77
+ url=url,
78
+ body=body,
79
+ params=params,
80
+ use_versioned_url=use_versioned_url,
65
81
  )
66
82
  if not isinstance(res, dict):
67
83
  raise ClassiqValueError(f"Unexpected returned value: {res}")
@@ -108,6 +124,39 @@ class ApiWrapper:
108
124
  )
109
125
  return execution_request.ExecuteGeneratedCircuitResults.parse_obj(data)
110
126
 
127
+ @classmethod
128
+ async def call_patch_execution_job(
129
+ cls,
130
+ job_id: JobID,
131
+ name: str,
132
+ ) -> ExecutionJobDetailsV1:
133
+ data = await cls._call_task(
134
+ http_method=HTTPMethod.PATCH,
135
+ url=f"{routes.EXECUTION_SERVICE_JOBS_FULL_PATH}/{job_id.job_id}",
136
+ params={
137
+ "name": name,
138
+ },
139
+ use_versioned_url=False,
140
+ )
141
+ return ExecutionJobDetailsV1.parse_obj(data)
142
+
143
+ @classmethod
144
+ async def call_query_execution_jobs(
145
+ cls,
146
+ offset: int,
147
+ limit: int,
148
+ ) -> ExecutionJobsQueryResultsV1:
149
+ data = await cls._call_task(
150
+ http_method=HTTPMethod.GET,
151
+ url=f"{routes.EXECUTION_SERVICE_JOBS_FULL_PATH}",
152
+ params={
153
+ "offset": offset,
154
+ "limit": limit,
155
+ },
156
+ use_versioned_url=False,
157
+ )
158
+ return ExecutionJobsQueryResultsV1.parse_obj(data)
159
+
111
160
  @classmethod
112
161
  async def call_execute_estimate(
113
162
  cls, request: execution_request.ExecutionRequest
@@ -195,11 +195,14 @@ class Client:
195
195
  url: str,
196
196
  body: Optional[Dict] = None,
197
197
  params: Optional[Dict] = None,
198
+ use_versioned_url: bool = True,
198
199
  ) -> Union[Dict, str]:
200
+ if use_versioned_url:
201
+ url = self.make_versioned_url(url)
199
202
  async with self.async_client() as async_client:
200
203
  response = await async_client.request(
201
204
  method=http_method,
202
- url=self.make_versioned_url(url),
205
+ url=url,
203
206
  json=body,
204
207
  params=params,
205
208
  )
@@ -162,7 +162,7 @@ def construct_grover_model(
162
162
  port_declarations=predicate_port_decls,
163
163
  body=[
164
164
  ArithmeticOperation(
165
- expr_str=expression,
165
+ expression=Expression(expr=expression),
166
166
  result_var=HandleBinding(name="res"),
167
167
  inplace_result=True,
168
168
  ),
@@ -9,13 +9,20 @@ from ..interface.executor.execution_preferences import __all__ as _ep_all
9
9
  from ..interface.executor.iqae_result import IQAEResult
10
10
  from ..interface.executor.result import ExecutionDetails
11
11
  from ..interface.executor.vqe_result import VQESolverResult
12
- from .jobs import ExecutionJob
12
+ from .jobs import ExecutionJob, get_execution_jobs, get_execution_jobs_async
13
13
 
14
14
  __all__ = (
15
15
  _be_all
16
16
  + _ep_all
17
17
  + _exec_all
18
- + ["ExecutionDetails", "VQESolverResult", "IQAEResult", "ExecutionJob"]
18
+ + [
19
+ "ExecutionDetails",
20
+ "VQESolverResult",
21
+ "IQAEResult",
22
+ "ExecutionJob",
23
+ "get_execution_jobs",
24
+ "get_execution_jobs_async",
25
+ ]
19
26
  )
20
27
 
21
28
 
classiq/execution/jobs.py CHANGED
@@ -1,7 +1,9 @@
1
- from typing import Optional
2
-
3
- from pydantic import PrivateAttr
1
+ import webbrowser
2
+ from datetime import datetime
3
+ from typing import List, Optional, Union
4
+ from urllib.parse import urljoin
4
5
 
6
+ from classiq.interface.execution.jobs import ExecutionJobDetailsV1
5
7
  from classiq.interface.executor.execution_request import (
6
8
  ExecutionJobDetails,
7
9
  ResultsCollection,
@@ -11,19 +13,67 @@ from classiq.interface.server.routes import EXECUTION_JOBS_FULL_PATH
11
13
 
12
14
  from classiq._internals.api_wrapper import ApiWrapper
13
15
  from classiq._internals.async_utils import syncify_function
16
+ from classiq._internals.client import client
14
17
  from classiq._internals.jobs import JobID, JobPoller
15
18
  from classiq.exceptions import ClassiqAPIError
16
19
 
20
+ _JobDetails = Union[ExecutionJobDetails, ExecutionJobDetailsV1]
21
+
22
+
23
+ class ExecutionJob:
24
+ _details: _JobDetails
25
+ _result: Optional[ResultsCollection]
26
+
27
+ def __init__(self, details: _JobDetails) -> None:
28
+ self._details = details
29
+ self._result = None
30
+
31
+ @property
32
+ def id(self) -> str:
33
+ return self._details.id
34
+
35
+ @property
36
+ def name(self) -> Optional[str]:
37
+ return self._details.name
38
+
39
+ @property
40
+ def start_time(self) -> datetime:
41
+ return self._details.start_time
42
+
43
+ @property
44
+ def end_time(self) -> Optional[datetime]:
45
+ return self._details.end_time
17
46
 
18
- class ExecutionJob(ExecutionJobDetails):
19
- _result: Optional[ResultsCollection] = PrivateAttr(default=None)
47
+ @property
48
+ def provider(self) -> Optional[str]:
49
+ return self._details.provider
20
50
 
21
- def __init__(self, details: ExecutionJobDetails) -> None:
22
- super().__init__(**details.dict())
51
+ @property
52
+ def backend_name(self) -> Optional[str]:
53
+ return self._details.backend_name
23
54
 
24
- def _update_details(self, details: ExecutionJobDetails) -> None:
25
- for k, v in details.dict().items():
26
- setattr(self, k, v)
55
+ @property
56
+ def status(self) -> JobStatus:
57
+ return self._details.status
58
+
59
+ @property
60
+ def num_shots(self) -> Optional[int]:
61
+ return self._details.num_shots
62
+
63
+ @property
64
+ def program_id(self) -> Optional[str]:
65
+ return self._details.program_id
66
+
67
+ @property
68
+ def error(self) -> Optional[str]:
69
+ return self._details.error
70
+
71
+ def __repr__(self) -> str:
72
+ class_name = self.__class__.__name__
73
+ if self.name is None:
74
+ return f"{class_name}(id={self.id!r})"
75
+ else:
76
+ return f"{class_name}(name={self.name!r}, id={self.id!r})"
27
77
 
28
78
  @classmethod
29
79
  async def from_id_async(cls, id: str) -> "ExecutionJob":
@@ -64,7 +114,7 @@ class ExecutionJob(ExecutionJobDetails):
64
114
 
65
115
  async def _poll_job(self, timeout_sec: Optional[float] = None) -> None:
66
116
  def response_parser(json_response: JSONObject) -> Optional[bool]:
67
- self._update_details(ExecutionJobDetails.parse_obj(json_response))
117
+ self._details = ExecutionJobDetails.parse_obj(json_response)
68
118
  if self.status.is_final():
69
119
  return True
70
120
  return None
@@ -75,3 +125,26 @@ class ExecutionJob(ExecutionJobDetails):
75
125
  response_parser=response_parser,
76
126
  timeout_sec=timeout_sec,
77
127
  )
128
+
129
+ async def rename_async(self, name: str) -> None:
130
+ self._details = await ApiWrapper.call_patch_execution_job(self._job_id, name)
131
+
132
+ rename = syncify_function(rename_async)
133
+
134
+ @property
135
+ def ide_url(self) -> str:
136
+ base_url = client().config.ide
137
+ return urljoin(base_url, f"jobs/{self.id}")
138
+
139
+ def open_in_ide(self) -> None:
140
+ webbrowser.open_new_tab(self.ide_url)
141
+
142
+
143
+ async def get_execution_jobs_async(
144
+ offset: int = 0, limit: int = 50
145
+ ) -> List[ExecutionJob]:
146
+ result = await ApiWrapper().call_query_execution_jobs(offset=offset, limit=limit)
147
+ return [ExecutionJob(details) for details in result.results]
148
+
149
+
150
+ get_execution_jobs = syncify_function(get_execution_jobs_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.34.0'
6
+ SEMVER_VERSION = '0.35.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -17,6 +17,7 @@ class ProviderVendor(StrEnum):
17
17
  IONQ = "IonQ"
18
18
  GOOGLE = "Google"
19
19
  ALICE_AND_BOB = "Alice and Bob"
20
+ OQC = "OQC"
20
21
 
21
22
 
22
23
  class ProviderTypeVendor:
@@ -27,6 +28,7 @@ class ProviderTypeVendor:
27
28
  IONQ = Literal[ProviderVendor.IONQ]
28
29
  GOOGLE = Literal[ProviderVendor.GOOGLE]
29
30
  ALICE_BOB = Literal[ProviderVendor.ALICE_AND_BOB]
31
+ OQC = Literal[ProviderVendor.OQC]
30
32
 
31
33
 
32
34
  class ClassiqAerBackendNames(StrEnum):
File without changes
@@ -0,0 +1,28 @@
1
+ from datetime import datetime
2
+ from typing import List, Optional
3
+
4
+ from pydantic import BaseModel, Extra
5
+
6
+ from classiq.interface.jobs import JobStatus
7
+
8
+
9
+ class ExecutionJobDetailsV1(BaseModel, extra=Extra.ignore):
10
+ id: str
11
+
12
+ name: Optional[str]
13
+ start_time: datetime
14
+ end_time: Optional[datetime]
15
+
16
+ provider: Optional[str]
17
+ backend_name: Optional[str]
18
+
19
+ status: JobStatus
20
+
21
+ num_shots: Optional[int]
22
+ program_id: Optional[str]
23
+
24
+ error: Optional[str]
25
+
26
+
27
+ class ExecutionJobsQueryResultsV1(BaseModel, extra=Extra.ignore):
28
+ results: List[ExecutionJobDetailsV1]
@@ -50,6 +50,7 @@ SupportedNodesTypes = Union[
50
50
  ast.RShift,
51
51
  ast.Call,
52
52
  ast.Mult,
53
+ ast.Pow,
53
54
  ]
54
55
 
55
56
  DEFAULT_SUPPORTED_NODE_TYPES = get_args(SupportedNodesTypes)
@@ -25,6 +25,7 @@ from classiq.interface.generator.arith.binary_ops import (
25
25
  Modulo,
26
26
  Multiplier,
27
27
  NotEqual,
28
+ Power,
28
29
  RegisterOrInt,
29
30
  RShift,
30
31
  Subtractor,
@@ -155,6 +156,16 @@ def multiplier_params_getter(
155
156
  return Multiplier(left_arg=left_arg, right_arg=right_arg, output_size=output_size)
156
157
 
157
158
 
159
+ def power_params_getter(
160
+ left_arg: RegisterArithmeticInfo,
161
+ right_arg: int,
162
+ output_size: Optional[int] = None,
163
+ inplace_arg: Optional[str] = None,
164
+ target: Optional[RegisterArithmeticInfo] = None,
165
+ ) -> ArithmeticOperationParams:
166
+ return Power(left_arg=left_arg, right_arg=right_arg, output_size=output_size)
167
+
168
+
158
169
  def min_params_getter(
159
170
  left_arg: RegisterOrFloat,
160
171
  right_arg: RegisterOrFloat,
@@ -367,4 +378,5 @@ params_getter_map: Dict[str, ParamsGetter] = dict(
367
378
  Mod=modulo_params_getter,
368
379
  min=min_params_getter,
369
380
  max=max_params_getter,
381
+ Pow=power_params_getter,
370
382
  )
@@ -413,6 +413,40 @@ class LessEqual(Comparator):
413
413
  output_name = "is_less_equal"
414
414
 
415
415
 
416
+ class Power(BinaryOpParams[RegisterArithmeticInfo, pydantic.PositiveInt]):
417
+ output_name = "powered"
418
+
419
+ @pydantic.validator("right_arg", pre=True)
420
+ def _validate_legal_power(cls, right_arg: Any) -> pydantic.PositiveInt:
421
+ if not float(right_arg).is_integer():
422
+ raise ValueError("Power must be an integer")
423
+ if right_arg <= 0:
424
+ raise ValueError("Power must be greater than one")
425
+ return int(right_arg)
426
+
427
+ def _get_result_bounds(self) -> Tuple[float, float]:
428
+ if (self.right_arg % 2) or min(self.left_arg.bounds) >= 0:
429
+ return (
430
+ self.left_arg.bounds[0] ** self.right_arg,
431
+ self.left_arg.bounds[1] ** self.right_arg,
432
+ )
433
+ return 0.0, max(abs(bound) for bound in self.left_arg.bounds) ** self.right_arg
434
+
435
+ def _get_result_register(self) -> RegisterArithmeticInfo:
436
+ if self.output_size:
437
+ return RegisterArithmeticInfo(size=self.output_size)
438
+
439
+ fraction_places: int = self.left_arg.fraction_places * self.right_arg
440
+ bounds = self._get_result_bounds()
441
+ size = number_utils.bounds_to_integer_part_size(*bounds) + fraction_places
442
+ return RegisterArithmeticInfo(
443
+ size=size,
444
+ is_signed=self.left_arg.is_signed and (self.right_arg % 2 == 1),
445
+ fraction_places=fraction_places,
446
+ bounds=bounds,
447
+ )
448
+
449
+
416
450
  class EffectiveUnaryOpParams(
417
451
  InplacableBinaryOpParams[RegisterArithmeticInfo, RightDataT], Generic[RightDataT]
418
452
  ):
@@ -82,3 +82,6 @@ class Expression(HashablePydanticBaseModel):
82
82
 
83
83
  class Config:
84
84
  frozen = True
85
+
86
+ def __str__(self) -> str:
87
+ return self.expr
@@ -1,6 +1,16 @@
1
- class QmodSizedProxy:
2
- def __init__(self, size: int) -> None:
1
+ from sympy import Symbol
2
+
3
+
4
+ class QmodSizedProxy(Symbol):
5
+ def __new__(cls, name, **assumptions):
6
+ return super().__new__(cls, name, **assumptions)
7
+
8
+ def __init__(self, name: str, size: int) -> None:
3
9
  self._size = size
4
10
 
11
+ @property
12
+ def size(self) -> int:
13
+ return self.args[0]
14
+
5
15
  def __len__(self) -> int:
6
16
  return self._size
@@ -18,6 +18,7 @@ from classiq.interface.generator.arith.binary_ops import (
18
18
  Modulo,
19
19
  Multiplier,
20
20
  NotEqual,
21
+ Power,
21
22
  RShift,
22
23
  Subtractor,
23
24
  )
@@ -136,6 +137,7 @@ function_param_library_without_self_reference: FunctionParamLibrary = (
136
137
  WeightedAdder,
137
138
  LinearPauliRotations,
138
139
  Multiplier,
140
+ Power,
139
141
  LinearGCI,
140
142
  HartreeFock,
141
143
  UCC,
@@ -102,6 +102,10 @@ GenerationOnlyExpressionSupportedNodeTypes = Union[
102
102
  ast.Slice,
103
103
  ast.keyword,
104
104
  ast.Attribute,
105
+ ast.BoolOp,
106
+ ast.Or,
107
+ ast.And,
108
+ ast.Not,
105
109
  ]
106
110
 
107
111
  GenerationExpressionSupportedNodeTypes = Union[
@@ -34,6 +34,32 @@ QFT = QuantumFunctionDeclaration.parse_raw(
34
34
  }"""
35
35
  )
36
36
 
37
+ STANDARD_QPE = QuantumFunctionDeclaration.parse_raw(
38
+ """{
39
+ "name": "standard_qpe",
40
+ "param_decls": {
41
+ "precision": {
42
+ "kind": "int"
43
+ }
44
+ },
45
+ "port_declarations": {
46
+ "phase": {
47
+ "name": "phase",
48
+ "size": {
49
+ "expr": "precision"
50
+ },
51
+ "direction": "inout"
52
+ }
53
+ },
54
+ "operand_declarations": {
55
+ "unitary": {
56
+ "name": "unitary"
57
+ }
58
+ },
59
+ "positional_arg_declarations": []
60
+ }"""
61
+ )
62
+
37
63
  QPE = QuantumFunctionDeclaration.parse_raw(
38
64
  """{
39
65
  "name": "qpe",
@@ -660,6 +686,7 @@ FULL_HEA = QuantumFunctionDeclaration.parse_raw(
660
686
  __all__ = [
661
687
  "QFT_STEP",
662
688
  "QFT",
689
+ "STANDARD_QPE",
663
690
  "QPE",
664
691
  "SINGLE_PAULI",
665
692
  "LINEAR_PAULI_ROTATIONS",
@@ -65,6 +65,8 @@ class TranspilationOption(StrEnum):
65
65
  AUTO_OPTIMIZE = "auto optimize"
66
66
  LIGHT = "light"
67
67
  MEDIUM = "medium"
68
+ INTENSIVE = "intensive"
69
+ CUSTOM = "custom"
68
70
 
69
71
  def __bool__(self) -> bool:
70
72
  return self != TranspilationOption.NONE
@@ -14,6 +14,7 @@ class Provider(StrEnum):
14
14
  CLASSIQ = "Classiq"
15
15
  GOOGLE = "Google"
16
16
  ALICE_AND_BOB = "Alice and Bob"
17
+ OQC = "OQC"
17
18
 
18
19
  @property
19
20
  def id(self):
@@ -1,8 +1,12 @@
1
1
  from typing import Mapping
2
2
 
3
+ import pydantic
4
+
3
5
  from classiq.interface.model.handle_binding import HandleBinding
4
6
  from classiq.interface.model.quantum_statement import QuantumOperation
5
7
 
8
+ from classiq.exceptions import ClassiqValueError
9
+
6
10
  BIND_INPUT_NAME = "bind_input"
7
11
  BIND_OUTPUT_NAME = "bind_output"
8
12
 
@@ -18,3 +22,9 @@ class BindOperation(QuantumOperation):
18
22
  @property
19
23
  def wiring_outputs(self) -> Mapping[str, HandleBinding]:
20
24
  return {BIND_OUTPUT_NAME: self.out_handle}
25
+
26
+ @pydantic.validator("in_handle", "out_handle")
27
+ def validate_handle(cls, handle: HandleBinding) -> HandleBinding:
28
+ if not handle.is_bindable():
29
+ raise ClassiqValueError(f"Cannot bind '{handle}'") # noqa: B907
30
+ return handle
@@ -10,6 +10,12 @@ class HandleBinding(BaseModel):
10
10
  frozen = True
11
11
  extra = Extra.forbid
12
12
 
13
+ def __str__(self) -> str:
14
+ return self.name
15
+
16
+ def is_bindable(self) -> bool:
17
+ return True
18
+
13
19
 
14
20
  class SubscriptHandleBinding(HandleBinding):
15
21
  index: Expression
@@ -18,6 +24,12 @@ class SubscriptHandleBinding(HandleBinding):
18
24
  frozen = True
19
25
  extra = Extra.forbid
20
26
 
27
+ def __str__(self) -> str:
28
+ return f"{self.name}[{self.index}]"
29
+
30
+ def is_bindable(self) -> bool:
31
+ return False
32
+
21
33
 
22
34
  class SlicedHandleBinding(HandleBinding):
23
35
  start: Expression
@@ -26,3 +38,9 @@ class SlicedHandleBinding(HandleBinding):
26
38
  class Config:
27
39
  frozen = True
28
40
  extra = Extra.forbid
41
+
42
+ def __str__(self) -> str:
43
+ return f"{self.name}[{self.start}:{self.end}]"
44
+
45
+ def is_bindable(self) -> bool:
46
+ return False
@@ -54,8 +54,8 @@ TYPE_NAME_CONFLICT_USER = (
54
54
  )
55
55
 
56
56
 
57
- def _create_default_functions() -> List[NativeFunctionDefinition]:
58
- return [NativeFunctionDefinition(name=MAIN_FUNCTION_NAME)]
57
+ def _create_empty_main_function() -> NativeFunctionDefinition:
58
+ return NativeFunctionDefinition(name=MAIN_FUNCTION_NAME)
59
59
 
60
60
 
61
61
  class VersionedSerializedModel(VersionedModel):
@@ -71,7 +71,7 @@ class Model(VersionedModel):
71
71
 
72
72
  # Must be validated before logic_flow
73
73
  functions: List[NativeFunctionDefinition] = pydantic.Field(
74
- default_factory=_create_default_functions,
74
+ default_factory=list,
75
75
  description="The user-defined custom type library.",
76
76
  )
77
77
 
@@ -119,6 +119,15 @@ class Model(VersionedModel):
119
119
  def function_dict(self) -> Dict[str, QuantumFunctionDeclaration]:
120
120
  return nameables_to_dict(self.functions)
121
121
 
122
+ @pydantic.validator("functions", always=True)
123
+ def _add_empty_main(
124
+ cls, functions: List[NativeFunctionDefinition]
125
+ ) -> List[NativeFunctionDefinition]:
126
+ function_dict = nameables_to_dict(functions)
127
+ if MAIN_FUNCTION_NAME not in function_dict:
128
+ functions.append(_create_empty_main_function())
129
+ return functions
130
+
122
131
  @pydantic.root_validator()
123
132
  def validate_static_correctness(cls, values: Dict[str, Any]) -> Dict[str, Any]:
124
133
  functions: Optional[List[QuantumFunctionDeclaration]] = values.get("functions")
@@ -40,7 +40,8 @@ class AmplitudeLoadingOperation(QuantumExpressionOperation):
40
40
  ) -> Mapping[
41
41
  str, Union[SlicedHandleBinding, SubscriptHandleBinding, HandleBinding]
42
42
  ]:
43
- assert len(self.var_handles) > 0
43
+ if len(self.var_handles) == 0:
44
+ return dict()
44
45
  return {AMPLITUDE_IO_NAME: self.var_handles[0]}
45
46
 
46
47
  def initialize_var_types(self, var_types: Dict[str, QuantumType]) -> None:
@@ -24,7 +24,9 @@ class ArithmeticOperation(QuantumExpressionOperation):
24
24
 
25
25
  def initialize_var_types(self, var_types: Dict[str, QuantumType]) -> None:
26
26
  super().initialize_var_types(var_types)
27
- self._result_type = compute_arithmetic_result_type(self.expr_str, var_types)
27
+ self._result_type = compute_arithmetic_result_type(
28
+ self.expression.expr, var_types
29
+ )
28
30
 
29
31
  @property
30
32
  def wiring_inouts(