classiq 0.83.0__py3-none-any.whl → 0.84.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 (82) hide show
  1. classiq/_internals/api_wrapper.py +27 -0
  2. classiq/applications/chemistry/chemistry_model_constructor.py +0 -2
  3. classiq/applications/chemistry/hartree_fock.py +68 -0
  4. classiq/applications/chemistry/mapping.py +85 -0
  5. classiq/applications/chemistry/op_utils.py +79 -0
  6. classiq/applications/chemistry/problems.py +195 -0
  7. classiq/applications/chemistry/ucc.py +109 -0
  8. classiq/applications/chemistry/z2_symmetries.py +368 -0
  9. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +30 -1
  10. classiq/{model_expansions/evaluators → evaluators}/arg_type_match.py +12 -4
  11. classiq/{model_expansions/evaluators → evaluators}/argument_types.py +1 -1
  12. classiq/{model_expansions/evaluators → evaluators}/classical_expression.py +1 -1
  13. classiq/{model_expansions/evaluators → evaluators}/classical_type_inference.py +3 -4
  14. classiq/{model_expansions/evaluators → evaluators}/parameter_types.py +17 -15
  15. classiq/execution/__init__.py +12 -1
  16. classiq/execution/execution_session.py +189 -43
  17. classiq/execution/jobs.py +26 -1
  18. classiq/execution/qnn.py +2 -2
  19. classiq/execution/user_budgets.py +39 -0
  20. classiq/interface/_version.py +1 -1
  21. classiq/interface/constants.py +1 -0
  22. classiq/interface/execution/primitives.py +29 -1
  23. classiq/interface/executor/estimate_cost.py +35 -0
  24. classiq/interface/executor/execution_result.py +13 -0
  25. classiq/interface/executor/result.py +116 -1
  26. classiq/interface/executor/user_budget.py +26 -33
  27. classiq/interface/generator/expressions/atomic_expression_functions.py +5 -1
  28. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +0 -6
  29. classiq/interface/generator/functions/classical_type.py +2 -35
  30. classiq/interface/generator/functions/concrete_types.py +0 -3
  31. classiq/interface/generator/functions/type_modifier.py +0 -19
  32. classiq/interface/generator/generated_circuit_data.py +0 -8
  33. classiq/interface/generator/types/compilation_metadata.py +0 -3
  34. classiq/interface/ide/visual_model.py +6 -2
  35. classiq/interface/model/model.py +12 -7
  36. classiq/interface/model/port_declaration.py +2 -24
  37. classiq/interface/pretty_print/__init__.py +0 -0
  38. classiq/{qmod/native → interface/pretty_print}/expression_to_qmod.py +18 -11
  39. classiq/interface/server/routes.py +4 -0
  40. classiq/model_expansions/atomic_expression_functions_defs.py +42 -5
  41. classiq/model_expansions/interpreters/base_interpreter.py +3 -3
  42. classiq/model_expansions/quantum_operations/allocate.py +1 -1
  43. classiq/model_expansions/quantum_operations/assignment_result_processor.py +1 -1
  44. classiq/model_expansions/quantum_operations/bind.py +2 -2
  45. classiq/model_expansions/quantum_operations/call_emitter.py +26 -20
  46. classiq/model_expansions/quantum_operations/variable_decleration.py +1 -1
  47. classiq/model_expansions/scope_initialization.py +3 -3
  48. classiq/model_expansions/transformers/model_renamer.py +6 -4
  49. classiq/model_expansions/transformers/type_modifier_inference.py +81 -43
  50. classiq/model_expansions/visitors/symbolic_param_inference.py +2 -3
  51. classiq/open_library/functions/__init__.py +3 -0
  52. classiq/open_library/functions/amplitude_amplification.py +10 -18
  53. classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
  54. classiq/open_library/functions/grover.py +14 -6
  55. classiq/open_library/functions/modular_exponentiation.py +22 -20
  56. classiq/open_library/functions/state_preparation.py +17 -0
  57. classiq/qmod/builtins/enums.py +23 -0
  58. classiq/qmod/builtins/functions/__init__.py +2 -0
  59. classiq/qmod/builtins/functions/exponentiation.py +32 -4
  60. classiq/qmod/builtins/structs.py +55 -3
  61. classiq/qmod/declaration_inferrer.py +3 -2
  62. classiq/qmod/native/pretty_printer.py +2 -6
  63. classiq/qmod/pretty_print/expression_to_python.py +2 -1
  64. classiq/qmod/pretty_print/pretty_printer.py +1 -6
  65. classiq/qmod/python_classical_type.py +12 -5
  66. classiq/qmod/qmod_constant.py +2 -5
  67. classiq/qmod/qmod_parameter.py +2 -5
  68. classiq/qmod/qmod_variable.py +56 -15
  69. classiq/qmod/quantum_expandable.py +4 -2
  70. classiq/qmod/quantum_function.py +7 -2
  71. classiq/qmod/semantics/annotation/qstruct_annotator.py +1 -1
  72. classiq/qmod/semantics/validation/main_validation.py +1 -9
  73. classiq/qmod/utilities.py +0 -2
  74. classiq/qmod/write_qmod.py +1 -1
  75. {classiq-0.83.0.dist-info → classiq-0.84.0.dist-info}/METADATA +4 -1
  76. {classiq-0.83.0.dist-info → classiq-0.84.0.dist-info}/RECORD +82 -73
  77. /classiq/{model_expansions/evaluators → evaluators}/__init__.py +0 -0
  78. /classiq/{model_expansions/evaluators → evaluators}/control.py +0 -0
  79. /classiq/{model_expansions → evaluators}/expression_evaluator.py +0 -0
  80. /classiq/{model_expansions/evaluators → evaluators}/quantum_type_utils.py +0 -0
  81. /classiq/{model_expansions/evaluators → evaluators}/type_type_match.py +0 -0
  82. {classiq-0.83.0.dist-info → classiq-0.84.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,6 @@
1
1
  from classiq.open_library.functions.qft_functions import qft, qft_no_swap
2
2
  from classiq.qmod.builtins.classical_functions import qft_const_adder_phase
3
+ from classiq.qmod.builtins.functions.allocation import free
3
4
  from classiq.qmod.builtins.functions.standard_gates import PHASE, SWAP, X
4
5
  from classiq.qmod.builtins.operations import (
5
6
  allocate,
@@ -11,7 +12,7 @@ from classiq.qmod.builtins.operations import (
11
12
  )
12
13
  from classiq.qmod.cparam import CInt
13
14
  from classiq.qmod.qfunc import qfunc
14
- from classiq.qmod.qmod_variable import QArray, QBit
15
+ from classiq.qmod.qmod_variable import Const, Permutable, QArray, QBit
15
16
  from classiq.qmod.symbolic import min, mod_inverse
16
17
 
17
18
 
@@ -50,8 +51,10 @@ def qft_space_add_const(value: CInt, phi_b: QArray[QBit]) -> None:
50
51
  )
51
52
 
52
53
 
53
- @qfunc
54
- def cc_modular_add(n: CInt, a: CInt, phi_b: QArray[QBit], c1: QBit, c2: QBit) -> None:
54
+ @qfunc(unchecked=["phi_b"])
55
+ def cc_modular_add(
56
+ n: CInt, a: CInt, phi_b: Permutable[QArray[QBit]], c1: Const[QBit], c2: Const[QBit]
57
+ ) -> None:
55
58
  """
56
59
  [Qmod Classiq-library function]
57
60
 
@@ -69,11 +72,9 @@ def cc_modular_add(n: CInt, a: CInt, phi_b: QArray[QBit], c1: QBit, c2: QBit) ->
69
72
  ctrl: QArray[QBit] = QArray()
70
73
  aux = QBit()
71
74
 
75
+ allocate(aux)
72
76
  within_apply(
73
- lambda: (
74
- allocate(aux),
75
- bind([c1, c2], ctrl),
76
- ),
77
+ lambda: bind([c1, c2], ctrl),
77
78
  lambda: (
78
79
  control(ctrl, lambda: qft_space_add_const(a, phi_b)),
79
80
  invert(lambda: qft_space_add_const(n, phi_b)),
@@ -87,11 +88,16 @@ def cc_modular_add(n: CInt, a: CInt, phi_b: QArray[QBit], c1: QBit, c2: QBit) ->
87
88
  ),
88
89
  ),
89
90
  )
91
+ free(aux)
90
92
 
91
93
 
92
- @qfunc
94
+ @qfunc(unchecked=["b"])
93
95
  def c_modular_multiply(
94
- n: CInt, a: CInt, b: QArray[QBit], x: QArray[QBit], ctrl: QBit
96
+ n: CInt,
97
+ a: CInt,
98
+ b: Permutable[QArray[QBit]],
99
+ x: Const[QArray[QBit]],
100
+ ctrl: Const[QBit],
95
101
  ) -> None:
96
102
  """
97
103
  [Qmod Classiq-library function]
@@ -118,7 +124,7 @@ def c_modular_multiply(
118
124
 
119
125
 
120
126
  @qfunc
121
- def multiswap(x: QArray[QBit], y: QArray[QBit]) -> None:
127
+ def multiswap(x: Permutable[QArray[QBit]], y: Permutable[QArray[QBit]]) -> None:
122
128
  """
123
129
  [Qmod Classiq-library function]
124
130
 
@@ -150,16 +156,12 @@ def inplace_c_modular_multiply(n: CInt, a: CInt, x: QArray[QBit], ctrl: QBit) ->
150
156
  x: The quantum factor.
151
157
  ctrl: The control bit.
152
158
  """
153
- b: QArray[QBit] = QArray()
154
-
155
- within_apply(
156
- lambda: allocate(x.len + 1, b),
157
- lambda: (
158
- c_modular_multiply(n, a, b, x, ctrl),
159
- control(ctrl, lambda: multiswap(x, b)),
160
- invert(lambda: c_modular_multiply(n, mod_inverse(a, n), b, x, ctrl)),
161
- ),
162
- )
159
+ b: QArray[QBit] = QArray(length=x.len + 1)
160
+ allocate(b)
161
+ c_modular_multiply(n, a, b, x, ctrl)
162
+ control(ctrl, lambda: multiswap(x, b))
163
+ invert(lambda: c_modular_multiply(n, mod_inverse(a, n), b, x, ctrl))
164
+ free(b)
163
165
 
164
166
 
165
167
  @qfunc
@@ -483,3 +483,20 @@ def prepare_dicke_state(k: int, qvar: QArray[QBit]) -> None:
483
483
  if k > 0:
484
484
  apply_to_all(X, qvar[0:k])
485
485
  prepare_dicke_state_unary_input(k, qvar)
486
+
487
+
488
+ @qfunc
489
+ def prepare_basis_state(state: list[bool], arr: Output[QArray]) -> None:
490
+ """
491
+ [Qmod Classiq-library function]
492
+
493
+ Initializes a quantum array in the specified basis state.
494
+
495
+ Args:
496
+ values: The desired basis state, given as a list of boolean values for each qubit.
497
+ arr: The quantum array to prepare.
498
+ """
499
+ allocate(len(state), arr)
500
+ for idx, value in enumerate(state):
501
+ if value:
502
+ X(arr[idx])
@@ -1,7 +1,11 @@
1
1
  from enum import IntEnum
2
+ from typing import TYPE_CHECKING
2
3
 
3
4
  from classiq.interface.generator.types.enum_declaration import EnumDeclaration
4
5
 
6
+ if TYPE_CHECKING:
7
+ from classiq.qmod.builtins.structs import SparsePauliOp
8
+
5
9
 
6
10
  class Element(IntEnum):
7
11
  H = 0
@@ -176,6 +180,25 @@ class Pauli(IntEnum):
176
180
  """Z (int): Pauli-Z matrix, represented by the integer 3. \n
177
181
  $Z = \\begin{bmatrix} 1 & 0 \\\\ 0 & -1 \\end{bmatrix}$"""
178
182
 
183
+ def __call__(self, index: int) -> "SparsePauliOp":
184
+ from classiq.qmod.builtins.structs import (
185
+ IndexedPauli,
186
+ SparsePauliOp,
187
+ SparsePauliTerm,
188
+ )
189
+
190
+ return SparsePauliOp(
191
+ terms=[ # type:ignore[arg-type]
192
+ SparsePauliTerm(
193
+ paulis=[ # type:ignore[arg-type]
194
+ IndexedPauli(pauli=self, index=index) # type:ignore[arg-type]
195
+ ],
196
+ coefficient=1.0, # type:ignore[arg-type]
197
+ )
198
+ ],
199
+ num_qubits=index + 1, # type:ignore[arg-type]
200
+ )
201
+
179
202
 
180
203
  class QSVMFeatureMapEntanglement(IntEnum):
181
204
  FULL = 0
@@ -71,6 +71,7 @@ CORE_LIB_DECLS = [
71
71
  single_pauli_exponent,
72
72
  commuting_paulis_exponent,
73
73
  suzuki_trotter,
74
+ multi_suzuki_trotter,
74
75
  parametric_suzuki_trotter,
75
76
  sparse_suzuki_trotter,
76
77
  qdrift,
@@ -132,6 +133,7 @@ __all__ = [ # noqa: RUF022
132
133
  "molecule_hartree_fock",
133
134
  "molecule_hva",
134
135
  "molecule_ucc",
136
+ "multi_suzuki_trotter",
135
137
  "parametric_suzuki_trotter",
136
138
  "pauli_feature_map",
137
139
  "permute",
@@ -55,13 +55,11 @@ def commuting_paulis_exponent(
55
55
 
56
56
  @qfunc(external=True)
57
57
  def suzuki_trotter(
58
- pauli_operator: CArray[PauliTerm],
58
+ pauli_operator: SparsePauliOp, # FIXME: Rename to hamiltonian (CLS-2912)
59
59
  evolution_coefficient: CReal,
60
60
  order: CInt,
61
61
  repetitions: CInt,
62
- qbv: QArray[
63
- QBit, Literal["get_field(get_field(pauli_operator[0], 'pauli'), 'len')"]
64
- ],
62
+ qbv: QArray[QBit], # FIXME: Add length expr (CLS-2912)
65
63
  ) -> None:
66
64
  """
67
65
  [Qmod core-library function]
@@ -82,6 +80,36 @@ def suzuki_trotter(
82
80
  pass
83
81
 
84
82
 
83
+ @qfunc(external=True)
84
+ def multi_suzuki_trotter(
85
+ hamiltonians: CArray[SparsePauliOp],
86
+ evolution_coefficients: CArray[CReal],
87
+ order: CInt,
88
+ repetitions: CInt,
89
+ qbv: QArray,
90
+ ) -> None:
91
+ """
92
+ [Qmod core-library function]
93
+
94
+ Applies the Suzuki-Trotter decomposition jointly to a sum of Hamiltonians
95
+ (represented as Pauli operators), each with its separate evolution coefficient,
96
+ approximating $\\exp{-iH_1t_1+H_2t_2+\\dots}$ with a specified order and number of
97
+ repetitions.
98
+
99
+ The Suzuki-Trotter decomposition is a method for approximating the exponential of a sum of operators by a product of exponentials of each operator.
100
+ The Suzuki-Trotter decomposition of a given order nullifies the error of the Taylor series expansion of the product of exponentials up to that order.
101
+ The error of a Suzuki-Trotter decomposition decreases as the order and number of repetitions increase.
102
+
103
+ Args:
104
+ hamiltonians: The hamitonians to be exponentiated, in sparse representation.
105
+ evolution_coefficients: The hamiltonian coefficients (can be link-time).
106
+ order: The order of the Suzuki-Trotter decomposition.
107
+ repetitions: The number of repetitions of the Suzuki-Trotter decomposition.
108
+ qbv: The target quantum variable of the exponentiation.
109
+ """
110
+ pass
111
+
112
+
85
113
  @qfunc(external=True)
86
114
  def parametric_suzuki_trotter(
87
115
  paulis: CArray[CArray[Pauli]],
@@ -1,6 +1,9 @@
1
1
  from dataclasses import dataclass, fields, is_dataclass
2
+ from typing import Union
2
3
 
4
+ from classiq.interface.exceptions import ClassiqValueError
3
5
  from classiq.interface.generator.types.struct_declaration import StructDeclaration
6
+ from classiq.interface.helpers.text_utils import are, readable_list, s
4
7
 
5
8
  from classiq.qmod.builtins.enums import LadderOperator, Pauli
6
9
  from classiq.qmod.cparam import CArray, CBool, CInt, CReal
@@ -56,13 +59,62 @@ class SparsePauliOp:
56
59
  Represents a collection of sparse Pauli operators.
57
60
 
58
61
  Attributes:
59
- paulis (CArray[SparsePauliTerm]): The list of chosen sparse Pauli operators in the term, corresponds to a product of them. (See: SparsePauliTerm)
62
+ terms (CArray[SparsePauliTerm]): The list of chosen sparse Pauli terms, corresponds to a product of them. (See: SparsePauliTerm)
60
63
  num_qubits (CInt): The number of qubits in the Hamiltonian.
61
64
  """
62
65
 
63
- paulis: CArray[SparsePauliTerm]
66
+ terms: CArray[SparsePauliTerm]
64
67
  num_qubits: CInt
65
68
 
69
+ def __mul__(self, obj: Union[float, "SparsePauliOp"]) -> "SparsePauliOp":
70
+ if isinstance(obj, (int, float, complex)):
71
+ return SparsePauliOp(
72
+ terms=[ # type:ignore[arg-type]
73
+ SparsePauliTerm(
74
+ paulis=term.paulis,
75
+ coefficient=obj * term.coefficient,
76
+ )
77
+ for term in self.terms # type:ignore[attr-defined]
78
+ ],
79
+ num_qubits=self.num_qubits,
80
+ )
81
+ if len(self.terms) != 1 or len(obj.terms) != 1: # type:ignore[arg-type]
82
+ raise ClassiqValueError("Cannot attach a pauli to multiple pauli terms")
83
+ existing_indices = {
84
+ indexed_pauli.index for indexed_pauli in self.terms[0].paulis
85
+ }
86
+ added_indices = {indexed_pauli.index for indexed_pauli in obj.terms[0].paulis}
87
+ overlapping_indices = sorted(existing_indices.intersection(added_indices))
88
+ if len(overlapping_indices):
89
+ raise ClassiqValueError(
90
+ f"Pauli{s(overlapping_indices)} at "
91
+ f"{'indices' if len(overlapping_indices) > 1 else 'index'} "
92
+ f"{readable_list(overlapping_indices)} {are(overlapping_indices)} "
93
+ f"already assigned"
94
+ )
95
+ return SparsePauliOp(
96
+ terms=[ # type:ignore[arg-type]
97
+ SparsePauliTerm(
98
+ paulis=self.terms[0].paulis + obj.terms[0].paulis,
99
+ coefficient=self.terms[0].coefficient * obj.terms[0].coefficient,
100
+ )
101
+ ],
102
+ num_qubits=max(
103
+ self.num_qubits, obj.num_qubits # type:ignore[call-overload]
104
+ ),
105
+ )
106
+
107
+ def __rmul__(self, obj: Union[float, "SparsePauliOp"]) -> "SparsePauliOp":
108
+ return self.__mul__(obj)
109
+
110
+ def __add__(self, other: "SparsePauliOp") -> "SparsePauliOp":
111
+ return SparsePauliOp(
112
+ terms=self.terms + other.terms, # type:ignore[arg-type]
113
+ num_qubits=max(
114
+ self.num_qubits, other.num_qubits # type:ignore[call-overload]
115
+ ),
116
+ )
117
+
66
118
 
67
119
  @dataclass
68
120
  class Position:
@@ -167,7 +219,7 @@ BUILTIN_STRUCT_DECLARATIONS = {
167
219
  struct_decl.__name__: StructDeclaration(
168
220
  name=struct_decl.__name__,
169
221
  variables={
170
- field.name: PythonClassicalType().convert(field.type)
222
+ field.name: PythonClassicalType().convert(field.type, nested=True)
171
223
  for field in fields(struct_decl)
172
224
  },
173
225
  )
@@ -75,14 +75,15 @@ class _PythonClassicalType(PythonClassicalType):
75
75
  all_decls = BUILTIN_STRUCT_DECLARATIONS | self.qmodule.type_decls
76
76
  if py_type.__name__ in all_decls:
77
77
  classical_type.set_classical_struct_decl(
78
- all_decls[py_type.__name__].model_copy()
78
+ all_decls[py_type.__name__].model_copy(deep=True)
79
79
  )
80
80
  return classical_type
81
81
 
82
82
  struct_decl = StructDeclaration(
83
83
  name=py_type.__name__,
84
84
  variables={
85
- f.name: self.convert(f.type) for f in dataclasses.fields(py_type)
85
+ f.name: self.convert(f.type, nested=True)
86
+ for f in dataclasses.fields(py_type)
86
87
  },
87
88
  )
88
89
  check_duplicate_types([struct_decl, *self.qmodule.user_types()])
@@ -1,13 +1,13 @@
1
1
  from collections.abc import Mapping
2
2
  from typing import Optional, Union
3
3
 
4
+ from classiq.interface.constants import DEFAULT_DECIMAL_PRECISION
4
5
  from classiq.interface.exceptions import ClassiqInternalError
5
6
  from classiq.interface.generator.constant import Constant
6
7
  from classiq.interface.generator.expressions.expression import Expression
7
8
  from classiq.interface.generator.functions.classical_type import (
8
9
  Bool,
9
10
  ClassicalArray,
10
- ClassicalList,
11
11
  ClassicalTuple,
12
12
  Integer,
13
13
  Real,
@@ -85,11 +85,10 @@ from classiq.interface.model.variable_declaration_statement import (
85
85
  VariableDeclarationStatement,
86
86
  )
87
87
  from classiq.interface.model.within_apply_operation import WithinApply
88
+ from classiq.interface.pretty_print.expression_to_qmod import transform_expression
88
89
 
89
90
  from classiq.open_library.functions import OPEN_LIBRARY_FUNCTIONS
90
- from classiq.qmod.native.expression_to_qmod import transform_expression
91
91
  from classiq.qmod.semantics.annotation.call_annotation import resolve_function_calls
92
- from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
93
92
 
94
93
 
95
94
  class DSLPrettyPrinter(ModelVisitor):
@@ -248,9 +247,6 @@ class DSLPrettyPrinter(ModelVisitor):
248
247
  def visit_Bool(self, ctbool: Bool) -> str:
249
248
  return "bool"
250
249
 
251
- def visit_ClassicalList(self, ctlist: ClassicalList) -> str:
252
- return f"{self.visit(ctlist.element_type)}[]"
253
-
254
250
  def visit_ClassicalArray(self, ctarray: ClassicalArray) -> str:
255
251
  element_type = self.visit(ctarray.element_type)
256
252
  if ctarray.length is not None:
@@ -6,8 +6,9 @@ from typing import Callable
6
6
 
7
7
  import numpy as np
8
8
 
9
+ from classiq.interface.constants import DEFAULT_DECIMAL_PRECISION
10
+
9
11
  import classiq
10
- from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
11
12
 
12
13
  IDENTIFIER = re.compile(r"[a-zA-Z_]\w*")
13
14
  BINARY_OPS: Mapping[type[ast.operator], str] = {
@@ -3,13 +3,13 @@ from typing import Optional, Union, cast
3
3
 
4
4
  import black
5
5
 
6
+ from classiq.interface.constants import DEFAULT_DECIMAL_PRECISION
6
7
  from classiq.interface.exceptions import ClassiqInternalError
7
8
  from classiq.interface.generator.constant import Constant
8
9
  from classiq.interface.generator.expressions.expression import Expression
9
10
  from classiq.interface.generator.functions.classical_type import (
10
11
  Bool,
11
12
  ClassicalArray,
12
- ClassicalList,
13
13
  ClassicalTuple,
14
14
  Integer,
15
15
  Real,
@@ -88,7 +88,6 @@ from classiq.interface.model.within_apply_operation import WithinApply
88
88
  import classiq
89
89
  from classiq.qmod.builtins.functions import BUILTIN_FUNCTION_DECLARATIONS
90
90
  from classiq.qmod.pretty_print.expression_to_python import transform_expression
91
- from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
92
91
 
93
92
 
94
93
  class VariableDeclarationAssignment(Visitor):
@@ -338,10 +337,6 @@ class PythonPrettyPrinter(ModelVisitor):
338
337
  self._imports["CBool"] = 1
339
338
  return "CBool"
340
339
 
341
- def visit_ClassicalList(self, ctlist: ClassicalList) -> str:
342
- self._imports["CArray"] = 1
343
- return f"CArray[{self.visit(ctlist.element_type)}]"
344
-
345
340
  def visit_ClassicalArray(self, ctarray: ClassicalArray) -> str:
346
341
  self._imports["CArray"] = 1
347
342
  element_type = self.visit(ctarray.element_type)
@@ -31,7 +31,9 @@ CARRAY_ERROR_MESSAGE = (
31
31
 
32
32
 
33
33
  class PythonClassicalType:
34
- def convert(self, py_type: type) -> Optional[ConcreteClassicalType]:
34
+ def convert(
35
+ self, py_type: type, nested: bool = False
36
+ ) -> Optional[ConcreteClassicalType]:
35
37
  if py_type is int:
36
38
  return Integer().set_generative()
37
39
  elif py_type is CInt:
@@ -45,16 +47,18 @@ class PythonClassicalType:
45
47
  elif py_type is CBool:
46
48
  return Bool()
47
49
  elif get_origin(py_type) is list:
48
- element_type = self.convert(get_args(py_type)[0])
50
+ element_type = self.convert(get_args(py_type)[0], nested=True)
49
51
  if element_type is not None:
50
52
  return ClassicalArray(element_type=element_type).set_generative()
51
53
  elif get_origin(py_type) is CArray:
52
54
  array_args = version_portable_get_args(py_type)
53
55
  if len(array_args) == 1:
54
- return ClassicalArray(element_type=self.convert(array_args[0]))
56
+ return ClassicalArray(
57
+ element_type=self.convert(array_args[0], nested=True)
58
+ )
55
59
  elif len(array_args) == 2:
56
60
  return ClassicalArray(
57
- element_type=self.convert(array_args[0]),
61
+ element_type=self.convert(array_args[0], nested=True),
58
62
  length=Expression(expr=get_type_hint_expr(array_args[1])),
59
63
  )
60
64
  raise ClassiqValueError(CARRAY_ERROR_MESSAGE)
@@ -62,7 +66,10 @@ class PythonClassicalType:
62
66
  return self.register_struct(py_type)
63
67
  elif inspect.isclass(py_type) and isinstance(py_type, EnumMeta):
64
68
  self.register_enum(py_type)
65
- return Enum(name=py_type.__name__).set_generative()
69
+ enum_type = Enum(name=py_type.__name__)
70
+ if not nested:
71
+ enum_type.set_generative()
72
+ return enum_type
66
73
  elif py_type in (CArray, list):
67
74
  raise ClassiqValueError(CARRAY_ERROR_MESSAGE)
68
75
  return None
@@ -5,10 +5,7 @@ from typing import TYPE_CHECKING, Any, Optional, cast, get_origin
5
5
  from classiq.interface.exceptions import ClassiqError, ClassiqValueError
6
6
  from classiq.interface.generator.constant import Constant
7
7
  from classiq.interface.generator.expressions.expression import Expression
8
- from classiq.interface.generator.functions.classical_type import (
9
- ClassicalArray,
10
- ClassicalList,
11
- )
8
+ from classiq.interface.generator.functions.classical_type import ClassicalArray
12
9
 
13
10
  from classiq.qmod.cparam import CArray, CParamScalar
14
11
  from classiq.qmod.declaration_inferrer import python_type_to_qmod
@@ -118,7 +115,7 @@ class QConstant(SymbolicExpr):
118
115
  if qmod_type is None:
119
116
  raise ClassiqError("Invalid QMOD type")
120
117
 
121
- if not isinstance(qmod_type, (ClassicalList, ClassicalArray)):
118
+ if not isinstance(qmod_type, ClassicalArray):
122
119
  raise ClassiqError("Invalid subscript to non-list constant")
123
120
 
124
121
  return CParamList(
@@ -4,7 +4,6 @@ from classiq.interface.exceptions import ClassiqInternalError, ClassiqValueError
4
4
  from classiq.interface.generator.functions.classical_type import (
5
5
  Bool,
6
6
  ClassicalArray,
7
- ClassicalList,
8
7
  ClassicalTuple,
9
8
  ClassicalType,
10
9
  Integer,
@@ -42,7 +41,7 @@ class CParamList(CParam):
42
41
  def __init__(
43
42
  self,
44
43
  expr: str,
45
- list_type: Union[ClassicalList, ClassicalArray, ClassicalTuple],
44
+ list_type: Union[ClassicalArray, ClassicalTuple],
46
45
  qmodule: ModelStateContainer,
47
46
  ) -> None:
48
47
  super().__init__(expr)
@@ -149,7 +148,7 @@ def create_param(
149
148
  decl = ctype.classical_struct_decl
150
149
  ctype = Struct(name=ctype.name)
151
150
  ctype.set_classical_struct_decl(decl)
152
- if isinstance(ctype, (ClassicalList, ClassicalArray, ClassicalTuple)):
151
+ if isinstance(ctype, (ClassicalArray, ClassicalTuple)):
153
152
  return CParamList(expr_str, ctype, qmodule=qmodule)
154
153
  elif isinstance(ctype, Struct):
155
154
  return CParamStruct(expr_str, ctype, qmodule=qmodule)
@@ -164,8 +163,6 @@ def get_qmod_type(ctype: ClassicalType) -> type:
164
163
  return CReal
165
164
  elif isinstance(ctype, Bool):
166
165
  return CBool
167
- elif isinstance(ctype, ClassicalList):
168
- return CArray[get_qmod_type(ctype.element_type)] # type: ignore[misc]
169
166
  elif isinstance(ctype, ClassicalArray):
170
167
  if ctype.length is None:
171
168
  return CArray[get_qmod_type(ctype.element_type)] # type: ignore[misc]
@@ -34,19 +34,20 @@ from classiq.interface.generator.expressions.proxies.quantum.qmod_qarray_proxy i
34
34
  ILLEGAL_SLICE_MSG,
35
35
  ILLEGAL_SLICING_STEP_MSG,
36
36
  )
37
+ from classiq.interface.generator.functions.concrete_types import ConcreteQuantumType
37
38
  from classiq.interface.generator.functions.port_declaration import (
38
39
  PortDeclarationDirection,
39
40
  )
40
41
  from classiq.interface.generator.functions.type_modifier import TypeModifier
41
- from classiq.interface.generator.functions.type_name import TypeName
42
+ from classiq.interface.generator.functions.type_name import Struct, TypeName
42
43
  from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
44
+ from classiq.interface.helpers.classproperty import classproperty
43
45
  from classiq.interface.model.handle_binding import (
44
46
  FieldHandleBinding,
45
47
  HandleBinding,
46
48
  SlicedHandleBinding,
47
49
  SubscriptHandleBinding,
48
50
  )
49
- from classiq.interface.model.port_declaration import AnonPortDeclaration
50
51
  from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
51
52
  AmplitudeLoadingOperation,
52
53
  )
@@ -136,7 +137,7 @@ class QVar(Symbolic):
136
137
  return self._base_handle
137
138
 
138
139
  @abc.abstractmethod
139
- def get_qmod_type(self) -> QuantumType:
140
+ def get_qmod_type(self) -> ConcreteQuantumType:
140
141
  raise NotImplementedError()
141
142
 
142
143
  @classmethod
@@ -164,6 +165,14 @@ class QVar(Symbolic):
164
165
  return self.get_qmod_type().type_name
165
166
 
166
167
 
168
+ class QmodExpressionCreator(Protocol):
169
+ """
170
+ A callable that creates a Qmod expression from the provided QVars.
171
+ """
172
+
173
+ def __call__(self, **kwargs: QVar) -> SymbolicExpr: ...
174
+
175
+
167
176
  _Q = TypeVar("_Q", bound=QVar)
168
177
  Output = Annotated[_Q, PortDeclarationDirection.Output]
169
178
  Input = Annotated[_Q, PortDeclarationDirection.Input]
@@ -273,7 +282,7 @@ class QBit(QScalar):
273
282
  ) -> "QBit":
274
283
  return QBit(origin, _expr_str=expr_str)
275
284
 
276
- def get_qmod_type(self) -> QuantumType:
285
+ def get_qmod_type(self) -> ConcreteQuantumType:
277
286
  return QuantumBit()
278
287
 
279
288
 
@@ -329,7 +338,7 @@ class QNum(Generic[_P], QScalar):
329
338
  ) -> "QNum":
330
339
  return QNum(origin, *_get_qnum_attributes(type_hint), _expr_str=expr_str)
331
340
 
332
- def get_qmod_type(self) -> QuantumType:
341
+ def get_qmod_type(self) -> ConcreteQuantumType:
333
342
  return QuantumNumeric(
334
343
  size=self._size,
335
344
  is_signed=self._is_signed,
@@ -498,8 +507,15 @@ class QStruct(QVar):
498
507
  setattr(self, field_name, var)
499
508
  super().__init__(name, expr_str=_expr_str)
500
509
 
501
- def get_qmod_type(self) -> QuantumType:
502
- return TypeName(name=self._struct_name)
510
+ def get_qmod_type(self) -> ConcreteQuantumType:
511
+ classical_type = Struct(name=self._struct_name)
512
+ classical_type.set_fields(
513
+ {
514
+ field_name: field_var.get_qmod_type()
515
+ for field_name, field_var in self._fields.items()
516
+ }
517
+ )
518
+ return classical_type
503
519
 
504
520
  @classmethod
505
521
  def to_qvar(
@@ -529,9 +545,23 @@ class QStruct(QVar):
529
545
  _expr_str=expr_str,
530
546
  )
531
547
 
548
+ @classproperty
549
+ def num_qubits(cls) -> int: # noqa: N805
550
+ """
551
+ The total number of qubits in this quantum struct.
552
+ Raises an error if the struct doesn't have a fixed size.
553
+ """
554
+ qvar = cls.to_qvar(HandleBinding(name="dummy"), type_hint=cls, expr_str=None)
555
+ quantum_type = qvar.get_qmod_type()
556
+ if not quantum_type.has_size_in_bits:
557
+ raise ClassiqValueError(
558
+ f"Could not infer the size of struct {qvar._struct_name!r}"
559
+ )
560
+ return quantum_type.size_in_bits
561
+
532
562
 
533
- def create_qvar_for_port_decl(port: AnonPortDeclaration, name: str) -> QVar:
534
- return _create_qvar_for_qtype(port.quantum_type, HandleBinding(name=name))
563
+ def create_qvar_from_quantum_type(quantum_type: ConcreteQuantumType, name: str) -> QVar:
564
+ return _create_qvar_for_qtype(quantum_type, HandleBinding(name=name))
535
565
 
536
566
 
537
567
  def _create_qvar_for_qtype(
@@ -689,17 +719,27 @@ def _get_quantum_bit_vector(type_hint: type[QArray]) -> QuantumBitvector:
689
719
  return QuantumBitvector(element_type=element_type, length=length_expr)
690
720
 
691
721
 
692
- def _get_quantum_struct(type_hint: type[QStruct]) -> TypeName:
693
- _register_qstruct(type_hint, qmodule=QMODULE)
694
- return TypeName(name=type_hint.__name__)
722
+ def _get_quantum_struct(type_hint: type[QStruct]) -> Struct:
723
+ decl = _register_qstruct(type_hint, qmodule=QMODULE)
724
+ classical_type = Struct(name=type_hint.__name__)
725
+ if decl is not None:
726
+ classical_type.set_fields(
727
+ {
728
+ field_name: field_type.model_copy(deep=True)
729
+ for field_name, field_type in decl.fields.items()
730
+ }
731
+ )
732
+ return classical_type
695
733
 
696
734
 
697
735
  def _register_qstruct(
698
736
  type_hint: type[QStruct], *, qmodule: ModelStateContainer
699
- ) -> None:
737
+ ) -> Optional[QStructDeclaration]:
700
738
  struct_name = type_hint.__name__
701
- if type_hint is QStruct or struct_name in qmodule.qstruct_decls:
702
- return
739
+ if type_hint is QStruct:
740
+ return None
741
+ if struct_name in qmodule.qstruct_decls:
742
+ return qmodule.qstruct_decls[struct_name]
703
743
 
704
744
  # temp assignment for recursive qstruct definitions
705
745
  qmodule.qstruct_decls[struct_name] = QStructDeclaration(name=struct_name)
@@ -714,6 +754,7 @@ def _register_qstruct(
714
754
  qmodule.qstruct_decls[struct_name] = struct_decl
715
755
  QStructAnnotator().visit(struct_decl)
716
756
  validate_qstruct(struct_decl)
757
+ return struct_decl
717
758
 
718
759
 
719
760
  def _validate_fields(type_hint: type[QStruct]) -> None:
@@ -68,7 +68,7 @@ from classiq.qmod.qmod_parameter import (
68
68
  )
69
69
  from classiq.qmod.qmod_variable import (
70
70
  QVar,
71
- create_qvar_for_port_decl,
71
+ create_qvar_from_quantum_type,
72
72
  )
73
73
  from classiq.qmod.quantum_callable import QCallable, QExpandableInterface
74
74
  from classiq.qmod.symbolic_expr import SymbolicExpr
@@ -163,7 +163,9 @@ class QExpandable(QCallable, QExpandableInterface, ABC):
163
163
  create_param(actual_name, arg.classical_type, self._qmodule)
164
164
  )
165
165
  elif isinstance(arg, AnonPortDeclaration):
166
- result.append(create_qvar_for_port_decl(arg, actual_name))
166
+ result.append(
167
+ create_qvar_from_quantum_type(arg.quantum_type, actual_name)
168
+ )
167
169
  else:
168
170
  assert isinstance(arg, AnonQuantumOperandDeclaration)
169
171
  result.append(QTerminalCallable(arg, idx))