classiq 0.36.0__py3-none-any.whl → 0.37.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 (91) hide show
  1. classiq/__init__.py +1 -0
  2. classiq/_internals/api_wrapper.py +24 -6
  3. classiq/_internals/authentication/device.py +6 -3
  4. classiq/_internals/authentication/token_manager.py +21 -5
  5. classiq/_internals/client.py +7 -2
  6. classiq/_internals/config.py +12 -0
  7. classiq/_internals/host_checker.py +1 -1
  8. classiq/_internals/jobs.py +3 -1
  9. classiq/_internals/type_validation.py +3 -6
  10. classiq/analyzer/analyzer.py +1 -0
  11. classiq/analyzer/rb.py +3 -5
  12. classiq/applications_model_constructors/chemistry_model_constructor.py +42 -67
  13. classiq/applications_model_constructors/grover_model_constructor.py +27 -18
  14. classiq/exceptions.py +5 -0
  15. classiq/execution/jobs.py +13 -4
  16. classiq/executor.py +3 -2
  17. classiq/interface/_version.py +1 -1
  18. classiq/interface/analyzer/analysis_params.py +0 -6
  19. classiq/interface/analyzer/result.py +0 -4
  20. classiq/interface/backend/backend_preferences.py +2 -2
  21. classiq/interface/backend/quantum_backend_providers.py +1 -1
  22. classiq/interface/execution/resource_estimator.py +7 -0
  23. classiq/interface/execution/result.py +5 -0
  24. classiq/interface/executor/register_initialization.py +3 -1
  25. classiq/interface/executor/vqe_result.py +1 -0
  26. classiq/interface/generator/ansatz_library.py +3 -3
  27. classiq/interface/generator/arith/argument_utils.py +4 -4
  28. classiq/interface/generator/arith/arithmetic.py +4 -2
  29. classiq/interface/generator/arith/arithmetic_arg_type_validator.py +11 -5
  30. classiq/interface/generator/arith/arithmetic_expression_parser.py +8 -7
  31. classiq/interface/generator/arith/arithmetic_operations.py +7 -0
  32. classiq/interface/generator/arith/arithmetic_param_getters.py +97 -16
  33. classiq/interface/generator/arith/arithmetic_result_builder.py +13 -3
  34. classiq/interface/generator/arith/binary_ops.py +8 -10
  35. classiq/interface/generator/arith/extremum_operations.py +2 -2
  36. classiq/interface/generator/arith/number_utils.py +20 -23
  37. classiq/interface/generator/arith/register_user_input.py +3 -1
  38. classiq/interface/generator/arith/unary_ops.py +9 -13
  39. classiq/interface/generator/expressions/atomic_expression_functions.py +2 -0
  40. classiq/interface/generator/expressions/expression.py +7 -2
  41. classiq/interface/generator/expressions/qmod_qnum_proxy.py +22 -0
  42. classiq/interface/generator/expressions/qmod_sized_proxy.py +2 -12
  43. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/atomic_quantum_functions.py +63 -3
  44. classiq/interface/generator/functions/core_lib_declarations/quantum_functions/std_lib_functions.py +143 -17
  45. classiq/interface/generator/functions/core_lib_declarations/quantum_operators.py +41 -16
  46. classiq/interface/generator/functions/native_function_definition.py +3 -3
  47. classiq/interface/generator/model/constraints.py +3 -3
  48. classiq/interface/generator/model/preferences/preferences.py +13 -9
  49. classiq/interface/generator/noise_properties.py +5 -5
  50. classiq/interface/generator/qpe.py +5 -5
  51. classiq/interface/generator/quantum_function_call.py +5 -3
  52. classiq/interface/generator/randomized_benchmarking.py +5 -3
  53. classiq/interface/generator/visitor.py +1 -2
  54. classiq/interface/hardware.py +1 -1
  55. classiq/interface/helpers/custom_pydantic_types.py +6 -0
  56. classiq/interface/model/{modular_addition_operation.py → inplace_binary_operation.py} +16 -2
  57. classiq/interface/model/native_function_definition.py +2 -24
  58. classiq/interface/model/operator_synthesis_data.py +6 -0
  59. classiq/interface/model/quantum_expressions/amplitude_loading_operation.py +8 -4
  60. classiq/interface/model/quantum_expressions/arithmetic_operation.py +9 -5
  61. classiq/interface/model/quantum_expressions/control_state.py +38 -0
  62. classiq/interface/model/quantum_expressions/quantum_expression.py +21 -11
  63. classiq/interface/model/quantum_function_call.py +81 -6
  64. classiq/interface/model/quantum_function_declaration.py +3 -3
  65. classiq/interface/model/quantum_if_operation.py +95 -0
  66. classiq/interface/model/resolvers/function_call_resolver.py +1 -1
  67. classiq/interface/model/validations/handles_validator.py +42 -15
  68. classiq/interface/server/routes.py +10 -6
  69. classiq/model/function_handler.pyi +86 -86
  70. classiq/model/model.py +1 -0
  71. classiq/qmod/__init__.py +6 -1
  72. classiq/qmod/builtins/__init__.py +13 -1
  73. classiq/qmod/builtins/classical_execution_primitives.py +109 -0
  74. classiq/qmod/builtins/classical_functions.py +68 -0
  75. classiq/qmod/builtins/functions.py +88 -18
  76. classiq/qmod/builtins/operations.py +60 -35
  77. classiq/qmod/classical_function.py +40 -0
  78. classiq/qmod/declaration_inferrer.py +5 -2
  79. classiq/qmod/qmod_variable.py +17 -10
  80. classiq/qmod/quantum_callable.py +24 -3
  81. classiq/qmod/quantum_expandable.py +131 -21
  82. classiq/qmod/quantum_function.py +12 -2
  83. classiq/qmod/symbolic.py +182 -107
  84. classiq/qmod/symbolic_expr.py +11 -10
  85. classiq/qmod/symbolic_type.py +8 -0
  86. classiq/quantum_functions/decorators.py +2 -4
  87. classiq/quantum_functions/function_library.py +1 -0
  88. {classiq-0.36.0.dist-info → classiq-0.37.0.dist-info}/METADATA +1 -1
  89. {classiq-0.36.0.dist-info → classiq-0.37.0.dist-info}/RECORD +90 -82
  90. classiq/interface/model/local_variable_declaration.py +0 -7
  91. {classiq-0.36.0.dist-info → classiq-0.37.0.dist-info}/WHEEL +0 -0
@@ -1,18 +1,32 @@
1
1
  from typing import Callable, List, Union
2
2
 
3
3
  from classiq.interface.generator.expressions.expression import Expression
4
+ from classiq.interface.generator.functions.core_lib_declarations.quantum_operators import (
5
+ OPERAND_FIELD_NAME,
6
+ )
4
7
  from classiq.interface.model.bind_operation import BindOperation
5
- from classiq.interface.model.modular_addition_operation import ModularAdditionOperation
8
+ from classiq.interface.model.inplace_binary_operation import (
9
+ BinaryOperation,
10
+ InplaceBinaryOperation,
11
+ )
6
12
  from classiq.interface.model.numeric_reinterpretation import (
7
13
  NumericReinterpretationOperation,
8
14
  )
15
+ from classiq.interface.model.quantum_function_declaration import (
16
+ QuantumOperandDeclaration,
17
+ )
18
+ from classiq.interface.model.quantum_if_operation import QuantumIfOperation
9
19
 
10
- from classiq.exceptions import ClassiqValueError
11
- from classiq.qmod.builtins.functions import control_with_value
20
+ from classiq.qmod.builtins.functions import (
21
+ apply,
22
+ compute as compute_operator,
23
+ uncompute,
24
+ )
12
25
  from classiq.qmod.qmod_parameter import QParam
13
26
  from classiq.qmod.qmod_variable import Input, Output, QNum, QVar
14
27
  from classiq.qmod.quantum_callable import QCallable
15
- from classiq.qmod.symbolic_expr import SymbolicEquality, SymbolicExpr
28
+ from classiq.qmod.quantum_expandable import prepare_arg
29
+ from classiq.qmod.symbolic_expr import SymbolicExpr
16
30
 
17
31
 
18
32
  def bind(source: Input[QVar], destination: Output[QVar]) -> None:
@@ -25,42 +39,22 @@ def bind(source: Input[QVar], destination: Output[QVar]) -> None:
25
39
  )
26
40
 
27
41
 
28
- QUANTUM_IF_CONDITION_ARG_ERROR_MESSAGE_PREFIX = (
29
- "quantum_if condition must be of the form '<quantum-variable> == "
30
- "<classical-integer-expression>', but "
31
- )
32
-
33
-
34
42
  def quantum_if(
35
43
  condition: SymbolicExpr, then: Union[QCallable, Callable[[], None]]
36
44
  ) -> None:
37
- if not isinstance(condition, SymbolicEquality):
38
- raise ClassiqValueError(
39
- f"quantum_if condition must be an equality, was " f"{condition!r}"
40
- )
41
- ctrl, ctrl_val = condition.lhs, condition.rhs
42
- if isinstance(ctrl, (int, SymbolicExpr)) and isinstance(ctrl_val, QNum):
43
- ctrl, ctrl_val = ctrl_val, ctrl
44
-
45
- if not isinstance(ctrl, QNum):
46
- raise ClassiqValueError(
47
- QUANTUM_IF_CONDITION_ARG_ERROR_MESSAGE_PREFIX
48
- + f"condition's left-hand side was {ctrl!r} of type "
49
- f"{type(ctrl)}"
50
- )
51
- if not isinstance(ctrl_val, (int, SymbolicExpr)):
52
- raise ClassiqValueError(
53
- QUANTUM_IF_CONDITION_ARG_ERROR_MESSAGE_PREFIX
54
- + f"condition's right-hand side was {ctrl_val!r} of type "
55
- f"{type(ctrl_val)}"
45
+ assert QCallable.CURRENT_EXPANDABLE is not None
46
+ QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
47
+ QuantumIfOperation(
48
+ expression=Expression(expr=str(condition)),
49
+ then=prepare_arg(QuantumOperandDeclaration(name=OPERAND_FIELD_NAME), then),
56
50
  )
57
- control_with_value(ctrl_val, then, ctrl)
51
+ )
58
52
 
59
53
 
60
54
  def reinterpret_num(
55
+ is_signed: Union[QParam[bool], bool],
56
+ fraction_digits: Union[QParam[int], int],
61
57
  target: QNum,
62
- is_signed: Union[QParam[bool], bool] = True,
63
- fraction_digits: Union[QParam[int], int] = 0,
64
58
  ) -> None:
65
59
  assert QCallable.CURRENT_EXPANDABLE is not None
66
60
  QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
@@ -72,20 +66,51 @@ def reinterpret_num(
72
66
  )
73
67
 
74
68
 
75
- def modular_add(
69
+ def inplace_add(
76
70
  value: QNum,
77
71
  target: QNum,
78
72
  ) -> None:
79
73
  assert QCallable.CURRENT_EXPANDABLE is not None
80
74
  QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
81
- ModularAdditionOperation(
75
+ InplaceBinaryOperation(
82
76
  target=target.get_handle_binding(),
83
77
  value=value.get_handle_binding(),
78
+ operation=BinaryOperation.Addition,
84
79
  )
85
80
  )
86
81
 
87
82
 
88
- __all__ = ["bind", "quantum_if", "reinterpret_num", "modular_add"]
83
+ def inplace_xor(
84
+ value: QNum,
85
+ target: QNum,
86
+ ) -> None:
87
+ assert QCallable.CURRENT_EXPANDABLE is not None
88
+ QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
89
+ InplaceBinaryOperation(
90
+ target=target.get_handle_binding(),
91
+ value=value.get_handle_binding(),
92
+ operation=BinaryOperation.Xor,
93
+ )
94
+ )
95
+
96
+
97
+ def within_apply(
98
+ compute: Callable[[], None],
99
+ action: Callable[[], None],
100
+ ) -> None:
101
+ compute_operator(compute)
102
+ apply(action)
103
+ uncompute(compute)
104
+
105
+
106
+ __all__ = [
107
+ "bind",
108
+ "quantum_if",
109
+ "reinterpret_num",
110
+ "inplace_add",
111
+ "inplace_xor",
112
+ "within_apply",
113
+ ]
89
114
 
90
115
 
91
116
  def __dir__() -> List[str]:
@@ -0,0 +1,40 @@
1
+ import ast
2
+ import inspect
3
+ import sys
4
+ from textwrap import dedent
5
+ from typing import Callable
6
+
7
+ from classiq.exceptions import ClassiqValueError
8
+ from classiq.qmod.builtins.classical_execution_primitives import sample, save
9
+
10
+
11
+ def _unparse_function_body(code: str, func: ast.FunctionDef) -> str:
12
+ first_statement = func.body[0]
13
+ body_lines = list(code.split("\n"))[first_statement.lineno - 1 :]
14
+ body_lines[0] = body_lines[0][first_statement.col_offset :]
15
+ if len(body_lines) > 1:
16
+ body_lines = [body_lines[0], dedent("\n".join(body_lines[1:]))]
17
+ return "\n".join(body_lines).strip()
18
+
19
+
20
+ class CFunc:
21
+ @staticmethod
22
+ def default_cmain() -> "CFunc":
23
+ @CFunc
24
+ def cmain() -> None:
25
+ result = sample()
26
+ save({"result": result})
27
+
28
+ return cmain
29
+
30
+ def __init__(self, py_callable: Callable[[], None]):
31
+ code = dedent(inspect.getsource(py_callable))
32
+ func = ast.parse(code).body[0]
33
+ if not isinstance(func, ast.FunctionDef):
34
+ raise ClassiqValueError(f"Use @{CFunc.__name__} to decorate a function")
35
+ if len(func.args.args) > 0:
36
+ raise ClassiqValueError(f"A @{CFunc.__name__} must receive no arguments")
37
+ if sys.version_info >= (3, 9):
38
+ self.code = "\n".join([ast.unparse(statement) for statement in func.body])
39
+ else:
40
+ self.code = _unparse_function_body(code, func)
@@ -29,7 +29,7 @@ from classiq import StructDeclaration
29
29
  from classiq.qmod.model_state_container import ModelStateContainer
30
30
  from classiq.qmod.qmod_parameter import Array, QParam
31
31
  from classiq.qmod.qmod_variable import QVar, get_type_hint_expr
32
- from classiq.qmod.quantum_callable import QCallable
32
+ from classiq.qmod.quantum_callable import QCallable, QCallableList
33
33
  from classiq.qmod.utilities import unmangle_keyword
34
34
 
35
35
  OPERAND_ARG_NAME = "arg{i}"
@@ -125,6 +125,7 @@ def _extract_operand_decl(
125
125
  return QuantumOperandDeclaration(
126
126
  name=name,
127
127
  positional_arg_declarations=_extract_positional_args(arg_dict, qmodule=qmodule),
128
+ is_list=(get_origin(py_type) or py_type) is QCallableList,
128
129
  )
129
130
 
130
131
 
@@ -141,7 +142,9 @@ def _extract_positional_args(
141
142
  elif QVar.from_type_hint(py_type) is not None:
142
143
  result.append(_extract_port_decl(name, py_type))
143
144
  else:
144
- assert get_origin(py_type) or py_type is QCallable
145
+ assert (get_origin(py_type) or py_type) is QCallable or (
146
+ get_origin(py_type) or py_type
147
+ ) is QCallableList
145
148
  result.append(_extract_operand_decl(name, py_type, qmodule=qmodule))
146
149
  return result
147
150
 
@@ -1,5 +1,4 @@
1
1
  import abc
2
- import ast
3
2
  from contextlib import contextmanager
4
3
  from typing import (
5
4
  TYPE_CHECKING,
@@ -41,16 +40,13 @@ from classiq.interface.model.quantum_type import (
41
40
  from classiq.exceptions import ClassiqValueError
42
41
  from classiq.qmod.qmod_parameter import ArrayBase, QParam, QParamScalar
43
42
  from classiq.qmod.quantum_callable import QCallable
44
- from classiq.qmod.symbolic_expr import SymbolicExpr, SymbolicTypes
43
+ from classiq.qmod.symbolic_expr import SymbolicExpr
44
+ from classiq.qmod.symbolic_type import SymbolicTypes
45
45
 
46
46
  ILLEGAL_SLICING_STEP_MSG = "Slicing with a step of a quantum variable is not supported"
47
47
  SLICE_OUT_OF_BOUNDS_MSG = "Slice end index out of bounds"
48
48
 
49
49
 
50
- def _python_expr(expr: SymbolicTypes) -> str:
51
- return ast.unparse(ast.parse(str(expr)))
52
-
53
-
54
50
  def _is_input_output_typehint(type_hint: Any) -> bool:
55
51
  return isinstance(type_hint, _AnnotatedAlias) and isinstance(
56
52
  type_hint.__metadata__[0], PortDeclarationDirection
@@ -131,7 +127,7 @@ class QScalar(QVar, SymbolicExpr):
131
127
  assert QCallable.CURRENT_EXPANDABLE is not None
132
128
  QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
133
129
  ArithmeticOperation(
134
- expression=Expression(expr=_python_expr(expr)),
130
+ expression=Expression(expr=str(expr)),
135
131
  result_var=self.get_handle_binding(),
136
132
  inplace_result=inplace,
137
133
  )
@@ -142,7 +138,7 @@ class QScalar(QVar, SymbolicExpr):
142
138
  assert QCallable.CURRENT_EXPANDABLE is not None
143
139
  QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
144
140
  AmplitudeLoadingOperation(
145
- expression=Expression(expr=_python_expr(expr)),
141
+ expression=Expression(expr=str(expr)),
146
142
  result_var=self.get_handle_binding(),
147
143
  )
148
144
  )
@@ -204,6 +200,18 @@ class QNum(Generic[_T], QScalar):
204
200
  def get_qmod_type(self) -> QuantumType:
205
201
  return self.QMOD_TYPE()
206
202
 
203
+ @property
204
+ def size(self) -> QParamScalar:
205
+ return QParamScalar(f"len({self._name})")
206
+
207
+ @property
208
+ def fraction_digits(self) -> QParamScalar:
209
+ return QParamScalar(f"fraction_digits({self._name})")
210
+
211
+ @property
212
+ def is_signed(self) -> QParamScalar:
213
+ return QParamScalar(f"is_signed({self._name})")
214
+
207
215
 
208
216
  _P = ParamSpec("_P")
209
217
 
@@ -245,8 +253,7 @@ class QArray(ArrayBase[_P], QVar):
245
253
 
246
254
  if TYPE_CHECKING:
247
255
 
248
- def len(self) -> int:
249
- ...
256
+ def len(self) -> int: ...
250
257
 
251
258
  else:
252
259
 
@@ -1,7 +1,14 @@
1
1
  import sys
2
2
  from abc import ABC, abstractmethod
3
- from typing import _GenericAlias # type: ignore[attr-defined]
4
- from typing import Any, ClassVar, Generic, Optional
3
+ from typing import ( # type: ignore[attr-defined]
4
+ TYPE_CHECKING,
5
+ Any,
6
+ ClassVar,
7
+ Generic,
8
+ Optional,
9
+ Union,
10
+ _GenericAlias,
11
+ )
5
12
 
6
13
  from typing_extensions import ParamSpec
7
14
 
@@ -12,6 +19,11 @@ from classiq.interface.model.quantum_function_declaration import (
12
19
  from classiq.interface.model.quantum_statement import QuantumStatement
13
20
  from classiq.interface.model.quantum_type import QuantumType
14
21
 
22
+ from classiq.qmod.qmod_parameter import QParam
23
+
24
+ if TYPE_CHECKING:
25
+ from classiq.qmod.quantum_expandable import QTerminalCallable
26
+
15
27
  P = ParamSpec("P")
16
28
 
17
29
 
@@ -25,7 +37,7 @@ class QExpandableInterface(ABC):
25
37
  raise NotImplementedError()
26
38
 
27
39
 
28
- class QCallable(Generic[P]):
40
+ class QCallable(Generic[P], ABC):
29
41
  CURRENT_EXPANDABLE: ClassVar[Optional[QExpandableInterface]] = None
30
42
 
31
43
  def __call__(self, *args: Any, **kwargs: Any) -> None:
@@ -51,3 +63,12 @@ class QCallable(Generic[P]):
51
63
  self, *args: Any, **kwargs: Any
52
64
  ) -> QuantumFunctionCall:
53
65
  raise NotImplementedError()
66
+
67
+
68
+ class QCallableList(QCallable, Generic[P], ABC):
69
+ if TYPE_CHECKING:
70
+
71
+ def len(self) -> int: ...
72
+
73
+ def __getitem__(self, key: Union[slice, int, QParam]) -> "QTerminalCallable":
74
+ raise NotImplementedError()
@@ -1,6 +1,18 @@
1
1
  from abc import ABC
2
2
  from types import TracebackType
3
- from typing import Any, Callable, ClassVar, Dict, List, Optional, Type, Union
3
+ from typing import (
4
+ TYPE_CHECKING,
5
+ Any,
6
+ Callable,
7
+ ClassVar,
8
+ Dict,
9
+ List,
10
+ Optional,
11
+ Type,
12
+ Union,
13
+ cast,
14
+ overload,
15
+ )
4
16
 
5
17
  from typing_extensions import Self
6
18
 
@@ -11,8 +23,10 @@ from classiq.interface.model.classical_parameter_declaration import (
11
23
  from classiq.interface.model.port_declaration import PortDeclaration
12
24
  from classiq.interface.model.quantum_function_call import (
13
25
  ArgValue,
26
+ OperandIdentifier,
14
27
  QuantumFunctionCall,
15
28
  QuantumLambdaFunction,
29
+ QuantumOperand,
16
30
  )
17
31
  from classiq.interface.model.quantum_function_declaration import (
18
32
  PositionalArg,
@@ -25,8 +39,9 @@ from classiq.interface.model.variable_declaration_statement import (
25
39
  VariableDeclarationStatement,
26
40
  )
27
41
 
42
+ from classiq.exceptions import ClassiqValueError
28
43
  from classiq.qmod.model_state_container import QMODULE, ModelStateContainer
29
- from classiq.qmod.qmod_parameter import QParam, create_param
44
+ from classiq.qmod.qmod_parameter import QParam, QParamScalar, create_param
30
45
  from classiq.qmod.qmod_variable import QVar, create_qvar_for_port_decl
31
46
  from classiq.qmod.quantum_callable import QCallable, QExpandableInterface
32
47
  from classiq.qmod.utilities import mangle_keyword
@@ -97,7 +112,7 @@ class QExpandable(QCallable, QExpandableInterface, ABC):
97
112
  def create_quantum_function_call(
98
113
  self, *args: Any, **kwargs: Any
99
114
  ) -> QuantumFunctionCall:
100
- return _create_quantum_function_call(self.func_decl, *args, **kwargs)
115
+ return _create_quantum_function_call(self.func_decl, None, *args, **kwargs)
101
116
 
102
117
 
103
118
  class QLambdaFunction(QExpandable):
@@ -126,8 +141,42 @@ class QLambdaFunction(QExpandable):
126
141
 
127
142
 
128
143
  class QTerminalCallable(QCallable):
129
- def __init__(self, decl: QuantumFunctionDeclaration) -> None:
144
+ def __init__(
145
+ self,
146
+ decl: QuantumFunctionDeclaration,
147
+ index_: Optional[Union[int, QParamScalar]] = None,
148
+ ) -> None:
130
149
  self._decl = decl
150
+ self._index = index_
151
+
152
+ @property
153
+ def is_list(self) -> bool:
154
+ return isinstance(self._decl, QuantumOperandDeclaration) and self._decl.is_list
155
+
156
+ def __getitem__(self, key: Union[slice, int, QParam]) -> "QTerminalCallable":
157
+ if not self.is_list:
158
+ raise ClassiqValueError("Cannot index a non-list operand")
159
+ if isinstance(key, slice):
160
+ raise NotImplementedError("Operand lists don't support slicing")
161
+ if isinstance(key, QParam) and not isinstance(key, QParamScalar):
162
+ raise ClassiqValueError("Non-classical parameter for slicing")
163
+ return QTerminalCallable(self._decl, key)
164
+
165
+ def __len__(self) -> int:
166
+ raise ValueError(
167
+ "len(<func>) is not supported for quantum callables - use <func>.len() instead (Only if it is an operand list)"
168
+ )
169
+
170
+ if TYPE_CHECKING:
171
+
172
+ def len(self) -> int: ...
173
+
174
+ else:
175
+
176
+ def len(self) -> QParamScalar:
177
+ if not self.is_list:
178
+ raise ClassiqValueError("Cannot get length of a non-list operand")
179
+ return QParamScalar(f"len({self.func_decl.name})")
131
180
 
132
181
  @property
133
182
  def func_decl(self) -> QuantumFunctionDeclaration:
@@ -136,15 +185,39 @@ class QTerminalCallable(QCallable):
136
185
  def create_quantum_function_call(
137
186
  self, *args: Any, **kwargs: Any
138
187
  ) -> QuantumFunctionCall:
139
- return _create_quantum_function_call(self.func_decl, *args, **kwargs)
188
+ if self.is_list and self._index is None:
189
+ raise ClassiqValueError(
190
+ f"Quantum operand {self.func_decl.name!r} is a list and must be indexed"
191
+ )
192
+ return _create_quantum_function_call(
193
+ self.func_decl, self._index, *args, **kwargs
194
+ )
195
+
140
196
 
197
+ @overload
198
+ def prepare_arg(
199
+ arg_decl: PositionalArg, val: Union[QCallable, Callable[[Any], None]]
200
+ ) -> QuantumOperand: ...
141
201
 
142
- def _prepare_arg(arg_decl: PositionalArg, val: Any) -> ArgValue:
202
+
203
+ @overload
204
+ def prepare_arg(arg_decl: PositionalArg, val: Any) -> ArgValue: ...
205
+
206
+
207
+ def prepare_arg(arg_decl: PositionalArg, val: Any) -> ArgValue:
143
208
  if isinstance(arg_decl, ClassicalParameterDeclaration):
144
209
  return Expression(expr=str(val))
145
210
  elif isinstance(arg_decl, PortDeclaration):
146
211
  return val.get_handle_binding()
147
212
  else:
213
+ if isinstance(val, list):
214
+ if not all(isinstance(v, QCallable) or callable(v) for v in val):
215
+ raise ClassiqValueError(
216
+ f"Quantum operand {arg_decl.name!r} cannot be initialized with a list of non-callables"
217
+ )
218
+ val = cast(List[Union[QCallable, Callable[[Any], None]]], val)
219
+ return [prepare_arg(arg_decl, v) for v in val]
220
+
148
221
  if not isinstance(val, QCallable):
149
222
  val = QLambdaFunction(arg_decl, val)
150
223
 
@@ -157,6 +230,32 @@ def _prepare_arg(arg_decl: PositionalArg, val: Any) -> ArgValue:
157
230
  )
158
231
 
159
232
 
233
+ def _get_operand_hint_args(
234
+ func: QuantumFunctionDeclaration, param: PositionalArg, param_value: str
235
+ ) -> str:
236
+ return ", ".join(
237
+ [
238
+ (
239
+ f"{decl.name}={param_value}"
240
+ if decl.name == param.name
241
+ else f"{decl.name}=..."
242
+ )
243
+ for decl in func.get_positional_arg_decls()
244
+ ]
245
+ )
246
+
247
+
248
+ def _get_operand_hint(func: QuantumFunctionDeclaration, param: PositionalArg) -> str:
249
+ return (
250
+ f"\nHint: To create an operand, do not call quantum gates directly "
251
+ f"`{func.name}({_get_operand_hint_args(func, param, 'H(q)')})`. "
252
+ f"Instead, use a lambda function "
253
+ f"`{func.name}({_get_operand_hint_args(func, param, 'lambda: H(q)')})` "
254
+ f"or a quantum function "
255
+ f"`{func.name}({_get_operand_hint_args(func, param, 'my_func')})`"
256
+ )
257
+
258
+
160
259
  def _prepare_args(
161
260
  decl: QuantumFunctionDeclaration, arg_list: List[Any], kwargs: Dict[str, Any]
162
261
  ) -> List[ArgValue]:
@@ -166,35 +265,46 @@ def _prepare_args(
166
265
  arg = arg_list.pop(0)
167
266
  else:
168
267
  arg = kwargs.pop(mangle_keyword(arg_decl.name), None)
169
- if arg is None:
170
- raise ValueError(
171
- f"{decl.name}() missing required argument for {arg_decl.name!r}"
172
- )
173
- result.append(_prepare_arg(arg_decl, arg))
268
+ if arg is None:
269
+ error_message = (
270
+ f"{decl.name!r} is missing required argument for {arg_decl.name!r}"
271
+ )
272
+ if isinstance(arg_decl, QuantumOperandDeclaration):
273
+ error_message += _get_operand_hint(decl, arg_decl)
274
+ raise ClassiqValueError(error_message)
275
+ result.append(prepare_arg(arg_decl, arg))
174
276
 
175
277
  return result
176
278
 
177
279
 
178
280
  def _create_quantum_function_call(
179
- decl: QuantumFunctionDeclaration, *args: Any, **kwargs: Any
281
+ decl_: QuantumFunctionDeclaration,
282
+ index_: Optional[Union[QParamScalar, int]] = None,
283
+ *args: Any,
284
+ **kwargs: Any,
180
285
  ) -> QuantumFunctionCall:
181
- arg_decls = decl.get_positional_arg_decls()
286
+ arg_decls = decl_.get_positional_arg_decls()
182
287
  arg_list = list(args)
183
- prepared_args = _prepare_args(decl, arg_list, kwargs)
288
+ prepared_args = _prepare_args(decl_, arg_list, kwargs)
184
289
 
185
290
  if kwargs:
186
291
  bad_kwarg = next(iter(kwargs))
187
292
  if not all(arg_decl.name == bad_kwarg for arg_decl in arg_decls):
188
- raise ValueError(
189
- f"{decl.name}() got an unexpected keyword argument {bad_kwarg!r}"
293
+ raise ClassiqValueError(
294
+ f"{decl_.name}() got an unexpected keyword argument {bad_kwarg!r}"
190
295
  )
191
296
  else:
192
- raise ValueError(
193
- f"{decl.name}() got multiple values for argument {bad_kwarg!r}"
297
+ raise ClassiqValueError(
298
+ f"{decl_.name}() got multiple values for argument {bad_kwarg!r}"
194
299
  )
195
300
  if arg_list:
196
- raise ValueError(
197
- f"{decl.name}() takes {len(arg_decls)} arguments but {len(args)} were given"
301
+ raise ClassiqValueError(
302
+ f"{decl_.name}() takes {len(arg_decls)} arguments but {len(args)} were given"
303
+ )
304
+ function_ident: Union[str, OperandIdentifier] = decl_.name
305
+ if index_ is not None:
306
+ function_ident = OperandIdentifier(
307
+ index=Expression(expr=str(index_)), name=function_ident
198
308
  )
199
309
 
200
- return QuantumFunctionCall(function=decl.name, positional_args=prepared_args)
310
+ return QuantumFunctionCall(function=function_ident, positional_args=prepared_args)
@@ -11,10 +11,11 @@ from classiq.interface.model.quantum_function_declaration import (
11
11
  )
12
12
 
13
13
  from classiq.exceptions import ClassiqError
14
+ from classiq.qmod.classical_function import CFunc
14
15
  from classiq.qmod.declaration_inferrer import infer_func_decl
15
16
  from classiq.qmod.qmod_parameter import QParam
16
17
  from classiq.qmod.qmod_variable import QVar
17
- from classiq.qmod.quantum_callable import QCallable
18
+ from classiq.qmod.quantum_callable import QCallable, QCallableList
18
19
  from classiq.qmod.quantum_expandable import QExpandable, QTerminalCallable
19
20
  from classiq.qmod.utilities import mangle_keyword, unmangle_keyword
20
21
 
@@ -29,9 +30,10 @@ def create_model(
29
30
  constraints: Optional[Constraints] = None,
30
31
  execution_preferences: Optional[ExecutionPreferences] = None,
31
32
  preferences: Optional[Preferences] = None,
33
+ classical_execution_function: Optional[CFunc] = None,
32
34
  ) -> SerializedModel:
33
35
  return entry_point.create_model(
34
- constraints, execution_preferences, preferences
36
+ constraints, execution_preferences, preferences, classical_execution_function
35
37
  ).get_model()
36
38
 
37
39
 
@@ -57,6 +59,7 @@ class QFunc(QExpandable):
57
59
  constraints: Optional[Constraints] = None,
58
60
  execution_preferences: Optional[ExecutionPreferences] = None,
59
61
  preferences: Optional[Preferences] = None,
62
+ classical_execution_function: Optional[CFunc] = None,
60
63
  ) -> Model:
61
64
  self._qmodule.type_decls = dict()
62
65
  self._qmodule.native_defs = dict()
@@ -66,9 +69,15 @@ class QFunc(QExpandable):
66
69
  ("execution_preferences", execution_preferences),
67
70
  ("preferences", preferences),
68
71
  ]
72
+ classical_execution_function = (
73
+ CFunc.default_cmain()
74
+ if classical_execution_function is None
75
+ else classical_execution_function
76
+ )
69
77
  return Model(
70
78
  functions=list(self._qmodule.native_defs.values()),
71
79
  types=list(self._qmodule.type_decls.values()),
80
+ classical_execution_code=classical_execution_function.code,
72
81
  **{key: value for key, value in model_extra_settings if value},
73
82
  )
74
83
 
@@ -116,6 +125,7 @@ def _validate_no_gen_params(annotations: Dict[str, Any]) -> None:
116
125
  name == "return"
117
126
  or get_origin(annotation) is QParam
118
127
  or (get_origin(annotation) or annotation) is QCallable
128
+ or (get_origin(annotation) or annotation) is QCallableList
119
129
  or QVar.from_type_hint(annotation) is not None
120
130
  )
121
131
  }