classiq 0.83.0__py3-none-any.whl → 0.85.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 (103) 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/applications/combinatorial_optimization/combinatorial_problem.py +20 -42
  11. classiq/{model_expansions/evaluators → evaluators}/arg_type_match.py +12 -4
  12. classiq/{model_expansions/evaluators → evaluators}/argument_types.py +1 -1
  13. classiq/evaluators/classical_expression.py +53 -0
  14. classiq/{model_expansions/evaluators → evaluators}/classical_type_inference.py +3 -4
  15. classiq/{model_expansions/evaluators → evaluators}/parameter_types.py +17 -15
  16. classiq/execution/__init__.py +12 -1
  17. classiq/execution/execution_session.py +238 -49
  18. classiq/execution/jobs.py +26 -1
  19. classiq/execution/qnn.py +2 -2
  20. classiq/execution/user_budgets.py +39 -0
  21. classiq/interface/_version.py +1 -1
  22. classiq/interface/constants.py +1 -0
  23. classiq/interface/debug_info/debug_info.py +0 -4
  24. classiq/interface/execution/primitives.py +29 -1
  25. classiq/interface/executor/estimate_cost.py +35 -0
  26. classiq/interface/executor/execution_result.py +13 -0
  27. classiq/interface/executor/result.py +116 -1
  28. classiq/interface/executor/user_budget.py +26 -33
  29. classiq/interface/generator/expressions/atomic_expression_functions.py +10 -1
  30. classiq/interface/generator/expressions/proxies/classical/any_classical_value.py +0 -6
  31. classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
  32. classiq/interface/generator/functions/classical_type.py +2 -35
  33. classiq/interface/generator/functions/concrete_types.py +20 -3
  34. classiq/interface/generator/functions/type_modifier.py +0 -19
  35. classiq/interface/generator/generated_circuit_data.py +5 -18
  36. classiq/interface/generator/types/compilation_metadata.py +0 -3
  37. classiq/interface/ide/operation_registry.py +45 -0
  38. classiq/interface/ide/visual_model.py +68 -3
  39. classiq/interface/model/bounds.py +12 -2
  40. classiq/interface/model/model.py +12 -7
  41. classiq/interface/model/port_declaration.py +2 -24
  42. classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
  43. classiq/interface/model/variable_declaration_statement.py +33 -6
  44. classiq/interface/pretty_print/__init__.py +0 -0
  45. classiq/{qmod/native → interface/pretty_print}/expression_to_qmod.py +18 -11
  46. classiq/interface/server/routes.py +4 -0
  47. classiq/model_expansions/atomic_expression_functions_defs.py +47 -6
  48. classiq/model_expansions/function_builder.py +4 -1
  49. classiq/model_expansions/interpreters/base_interpreter.py +3 -3
  50. classiq/model_expansions/interpreters/generative_interpreter.py +16 -1
  51. classiq/model_expansions/quantum_operations/allocate.py +1 -1
  52. classiq/model_expansions/quantum_operations/assignment_result_processor.py +64 -22
  53. classiq/model_expansions/quantum_operations/bind.py +2 -2
  54. classiq/model_expansions/quantum_operations/bounds.py +7 -1
  55. classiq/model_expansions/quantum_operations/call_emitter.py +26 -20
  56. classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
  57. classiq/model_expansions/quantum_operations/variable_decleration.py +31 -11
  58. classiq/model_expansions/scope.py +7 -0
  59. classiq/model_expansions/scope_initialization.py +3 -3
  60. classiq/model_expansions/transformers/model_renamer.py +6 -4
  61. classiq/model_expansions/transformers/type_modifier_inference.py +81 -43
  62. classiq/model_expansions/transformers/var_splitter.py +1 -1
  63. classiq/model_expansions/visitors/symbolic_param_inference.py +2 -3
  64. classiq/open_library/functions/__init__.py +3 -2
  65. classiq/open_library/functions/amplitude_amplification.py +10 -18
  66. classiq/open_library/functions/discrete_sine_cosine_transform.py +5 -5
  67. classiq/open_library/functions/grover.py +14 -6
  68. classiq/open_library/functions/modular_exponentiation.py +22 -20
  69. classiq/open_library/functions/qaoa_penalty.py +8 -1
  70. classiq/open_library/functions/state_preparation.py +18 -32
  71. classiq/qmod/__init__.py +2 -0
  72. classiq/qmod/builtins/enums.py +23 -0
  73. classiq/qmod/builtins/functions/__init__.py +2 -0
  74. classiq/qmod/builtins/functions/exponentiation.py +32 -4
  75. classiq/qmod/builtins/operations.py +65 -1
  76. classiq/qmod/builtins/structs.py +55 -3
  77. classiq/qmod/classical_variable.py +74 -0
  78. classiq/qmod/declaration_inferrer.py +3 -2
  79. classiq/qmod/native/pretty_printer.py +20 -20
  80. classiq/qmod/pretty_print/expression_to_python.py +2 -1
  81. classiq/qmod/pretty_print/pretty_printer.py +35 -21
  82. classiq/qmod/python_classical_type.py +12 -5
  83. classiq/qmod/qfunc.py +2 -19
  84. classiq/qmod/qmod_constant.py +2 -5
  85. classiq/qmod/qmod_parameter.py +2 -5
  86. classiq/qmod/qmod_variable.py +61 -23
  87. classiq/qmod/quantum_expandable.py +5 -3
  88. classiq/qmod/quantum_function.py +49 -4
  89. classiq/qmod/semantics/annotation/qstruct_annotator.py +1 -1
  90. classiq/qmod/semantics/validation/main_validation.py +1 -9
  91. classiq/qmod/symbolic_type.py +2 -1
  92. classiq/qmod/utilities.py +0 -2
  93. classiq/qmod/write_qmod.py +1 -1
  94. {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/METADATA +4 -1
  95. {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/RECORD +101 -90
  96. classiq/interface/model/quantum_variable_declaration.py +0 -7
  97. classiq/model_expansions/evaluators/classical_expression.py +0 -36
  98. /classiq/{model_expansions/evaluators → evaluators}/__init__.py +0 -0
  99. /classiq/{model_expansions/evaluators → evaluators}/control.py +0 -0
  100. /classiq/{model_expansions → evaluators}/expression_evaluator.py +0 -0
  101. /classiq/{model_expansions/evaluators → evaluators}/quantum_type_utils.py +0 -0
  102. /classiq/{model_expansions/evaluators → evaluators}/type_type_match.py +0 -0
  103. {classiq-0.83.0.dist-info → classiq-0.85.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,8 @@
1
+ import json
1
2
  from collections import Counter
3
+ from collections.abc import Iterator
2
4
  from functools import cached_property
5
+ from itertools import count
3
6
  from typing import Any, Optional
4
7
 
5
8
  import pydantic
@@ -13,6 +16,26 @@ from classiq.interface.generator.hardware.hardware_data import SynthesisHardware
13
16
  from classiq.interface.helpers.versioned_model import VersionedModel
14
17
 
15
18
 
19
+ class OperationIdCounter:
20
+ _op_id_counter: Iterator[int] = count()
21
+
22
+ def next_id(self) -> int:
23
+ return next(self._op_id_counter)
24
+
25
+ def reset_operation_counter(self) -> None:
26
+ self._op_id_counter = count()
27
+
28
+
29
+ _operation_id_counter = OperationIdCounter()
30
+
31
+
32
+ def reset_operation_counter() -> None:
33
+ """
34
+ Call this at the start of every new task to restart ids at 0.
35
+ """
36
+ _operation_id_counter.reset_operation_counter()
37
+
38
+
16
39
  class OperationType(StrEnum):
17
40
  REGULAR = "REGULAR"
18
41
  INVISIBLE = "INVISIBLE"
@@ -108,9 +131,12 @@ class AtomicGate(StrEnum):
108
131
 
109
132
  class Operation(pydantic.BaseModel):
110
133
  name: str
134
+ _id: int = pydantic.PrivateAttr(default_factory=_operation_id_counter.next_id)
111
135
  qasm_name: str = pydantic.Field(default="")
112
136
  details: str = pydantic.Field(default="")
113
- children: list["Operation"]
137
+ children: list["Operation"] = pydantic.Field(default_factory=list)
138
+ # children_ids is optional in order to support backwards compatibility.
139
+ children_ids: list[int] = pydantic.Field(default_factory=list)
114
140
  operation_data: Optional[OperationData] = None
115
141
  operation_links: OperationLinks
116
142
  control_qubits: tuple[int, ...] = pydantic.Field(default_factory=tuple)
@@ -118,7 +144,7 @@ class Operation(pydantic.BaseModel):
118
144
  target_qubits: tuple[int, ...]
119
145
  operation_level: OperationLevel
120
146
  operation_type: OperationType = pydantic.Field(
121
- description="Identifies unique operations that are visualized differently"
147
+ description="Identifies unique operations that are visualized differently",
122
148
  )
123
149
  gate: AtomicGate = pydantic.Field(
124
150
  default=AtomicGate.UNKNOWN, description="Gate type"
@@ -127,7 +153,46 @@ class Operation(pydantic.BaseModel):
127
153
  expanded: bool = pydantic.Field(default=False)
128
154
  show_expanded_label: bool = pydantic.Field(default=False)
129
155
 
156
+ @property
157
+ def id(self) -> int:
158
+ return self._id
159
+
160
+ def __hash__(self) -> int:
161
+ """
162
+ using a custom hashable_dict in order to compare the operation
163
+ with the qubits in order
164
+ """
165
+ js = json.dumps(
166
+ self._hashable_dict(),
167
+ sort_keys=True,
168
+ default=lambda o: o.value if hasattr(o, "value") else str(o),
169
+ )
170
+ return hash(js)
171
+
172
+ def __eq__(self, other: object) -> bool:
173
+ return (
174
+ isinstance(other, Operation)
175
+ and self._hashable_dict() == other._hashable_dict()
176
+ )
177
+
178
+ def _hashable_dict(self) -> dict:
179
+ data = self.model_dump(
180
+ exclude_none=True,
181
+ )
182
+ # force qubit order for equality
183
+ for key in ("target_qubits", "auxiliary_qubits", "control_qubits"):
184
+ data[key] = sorted(data[key])
185
+ return data
186
+
130
187
 
131
188
  class ProgramVisualModel(VersionedModel):
132
- main_operation: Operation
189
+ main_operation: Operation = pydantic.Field(default=None)
190
+ id_to_operations: dict[int, Operation] = pydantic.Field(default_factory=dict)
191
+ main_operation_id: int = pydantic.Field(default=None)
133
192
  program_data: ProgramData
193
+
194
+ @property
195
+ def main_op_from_mapping(self) -> Operation:
196
+ if self.main_operation_id is None:
197
+ raise ValueError("Main operation ID is not set.")
198
+ return self.id_to_operations[self.main_operation_id]
@@ -1,6 +1,6 @@
1
1
  from typing import Literal, Optional
2
2
 
3
- from classiq.interface.helpers.custom_pydantic_types import PydanticFloatTuple
3
+ from classiq.interface.generator.expressions.expression import Expression
4
4
  from classiq.interface.model.handle_binding import ConcreteHandleBinding
5
5
  from classiq.interface.model.quantum_statement import QuantumOperation
6
6
 
@@ -9,4 +9,14 @@ class SetBoundsStatement(QuantumOperation):
9
9
  kind: Literal["SetBoundsStatement"]
10
10
 
11
11
  target: ConcreteHandleBinding
12
- bounds: Optional[PydanticFloatTuple]
12
+ lower_bound: Optional[Expression]
13
+ upper_bound: Optional[Expression]
14
+
15
+ @property
16
+ def expressions(self) -> list[Expression]:
17
+ exprs = []
18
+ if self.lower_bound is not None:
19
+ exprs.append(self.lower_bound)
20
+ if self.upper_bound is not None:
21
+ exprs.append(self.upper_bound)
22
+ return exprs
@@ -185,11 +185,16 @@ class Model(VersionedModel, ASTNode):
185
185
  return constants
186
186
 
187
187
  def dump_no_metadata(self) -> dict[str, Any]:
188
- return self.model_dump(
189
- exclude={
190
- "constraints",
191
- "execution_preferences",
192
- "preferences",
193
- "functions_compilation_metadata",
194
- },
188
+ compilation_metadata_with_unchecked = {
189
+ name: CompilationMetadata(unchecked=comp_metadata.unchecked)
190
+ for name, comp_metadata in self.functions_compilation_metadata.items()
191
+ if len(comp_metadata.unchecked) > 0
192
+ }
193
+ model = self.model_copy(
194
+ update={
195
+ "functions_compilation_metadata": compilation_metadata_with_unchecked,
196
+ }
197
+ )
198
+ return model.model_dump(
199
+ exclude={"constraints", "execution_preferences", "preferences"},
195
200
  )
@@ -1,4 +1,4 @@
1
- from typing import Any, Literal, Optional
1
+ from typing import Any, Literal
2
2
 
3
3
  import pydantic
4
4
  from pydantic_core.core_schema import ValidationInfo
@@ -10,7 +10,6 @@ from classiq.interface.generator.functions.port_declaration import (
10
10
  )
11
11
  from classiq.interface.generator.functions.type_modifier import (
12
12
  TypeModifier,
13
- TypeQualifier,
14
13
  )
15
14
  from classiq.interface.helpers.pydantic_model_helpers import values_with_discriminator
16
15
  from classiq.interface.model.parameter import Parameter
@@ -19,35 +18,14 @@ from classiq.interface.model.parameter import Parameter
19
18
  class AnonPortDeclaration(Parameter):
20
19
  quantum_type: ConcreteQuantumType
21
20
  direction: PortDeclarationDirection
22
- type_qualifier: Optional[TypeQualifier] = pydantic.Field(
23
- default=None, exclude=True
24
- ) # TODO remove after BWC breaking release https://classiq.atlassian.net/browse/CLS-2777
25
21
  kind: Literal["PortDeclaration"]
26
- type_modifier: TypeModifier = pydantic.Field(default=None)
22
+ type_modifier: TypeModifier
27
23
 
28
24
  @pydantic.model_validator(mode="before")
29
25
  @classmethod
30
26
  def _set_kind(cls, values: Any) -> dict[str, Any]:
31
27
  return values_with_discriminator(values, "kind", "PortDeclaration")
32
28
 
33
- @pydantic.model_validator(mode="before")
34
- @classmethod
35
- def _set_type_modifier(cls, values: Any) -> dict[str, Any]:
36
- if values.get("type_modifier") is None:
37
- type_qualifier = values.get("type_qualifier")
38
- if type_qualifier is not None:
39
- if isinstance(type_qualifier, TypeQualifier):
40
- values["type_modifier"] = type_qualifier.to_modifier()
41
- elif isinstance(type_qualifier, str):
42
- values["type_modifier"] = TypeQualifier(
43
- type_qualifier
44
- ).to_modifier()
45
- else:
46
- raise pydantic.ValidationError("Missing a type modifier")
47
- else:
48
- raise pydantic.ValidationError("Missing a type modifier")
49
- return values
50
-
51
29
  @pydantic.field_validator("direction", mode="before")
52
30
  @classmethod
53
31
  def _direction_validator(
@@ -1,6 +1,8 @@
1
1
  from collections.abc import Mapping, Sequence
2
2
  from typing import Literal
3
3
 
4
+ from pydantic import PrivateAttr
5
+
4
6
  from classiq.interface.enum_utils import StrEnum
5
7
  from classiq.interface.generator.arith.arithmetic import (
6
8
  ARITHMETIC_EXPRESSION_RESULT_NAME,
@@ -27,6 +29,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
27
29
  kind: Literal["ArithmeticOperation"]
28
30
 
29
31
  operation_kind: ArithmeticOperationKind
32
+ _classical_assignment: bool = PrivateAttr(default=False)
30
33
 
31
34
  @property
32
35
  def is_inplace(self) -> bool:
@@ -50,7 +53,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
50
53
  self,
51
54
  ) -> Mapping[str, ConcreteHandleBinding]:
52
55
  inouts = dict(super().wiring_inouts)
53
- if self.is_inplace:
56
+ if self.is_inplace and not self._classical_assignment:
54
57
  inouts[self.result_name()] = self.result_var
55
58
  return inouts
56
59
 
@@ -60,7 +63,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
60
63
  HandleMetadata(handle=handle, readable_location="in an expression")
61
64
  for handle in self.var_handles
62
65
  ]
63
- if self.is_inplace:
66
+ if self.is_inplace and not self._classical_assignment:
64
67
  inouts.append(
65
68
  HandleMetadata(
66
69
  handle=self.result_var,
@@ -71,13 +74,13 @@ class ArithmeticOperation(QuantumAssignmentOperation):
71
74
 
72
75
  @property
73
76
  def wiring_outputs(self) -> Mapping[str, HandleBinding]:
74
- if self.is_inplace:
77
+ if self.is_inplace or self._classical_assignment:
75
78
  return {}
76
79
  return super().wiring_outputs
77
80
 
78
81
  @property
79
82
  def readable_outputs(self) -> Sequence[HandleMetadata]:
80
- if self.is_inplace:
83
+ if self.is_inplace or self._classical_assignment:
81
84
  return []
82
85
  return [
83
86
  HandleMetadata(
@@ -1,15 +1,42 @@
1
- from typing import Literal
1
+ from typing import Any, Literal, Optional
2
+
3
+ import pydantic
2
4
 
3
5
  from classiq.interface.generator.expressions.expression import Expression
4
- from classiq.interface.model.quantum_statement import QuantumStatement
5
- from classiq.interface.model.quantum_variable_declaration import (
6
- QuantumVariableDeclaration,
6
+ from classiq.interface.generator.functions.concrete_types import (
7
+ ConcreteQuantumType,
8
+ ConcreteType,
7
9
  )
10
+ from classiq.interface.model.quantum_statement import QuantumStatement
11
+ from classiq.interface.model.quantum_type import QuantumType
8
12
 
9
13
 
10
- class VariableDeclarationStatement(QuantumStatement, QuantumVariableDeclaration):
14
+ class VariableDeclarationStatement(QuantumStatement):
11
15
  kind: Literal["VariableDeclarationStatement"]
12
16
 
17
+ name: str
18
+ quantum_type: Optional[ConcreteQuantumType] = None
19
+ qmod_type: ConcreteType
20
+
21
+ @pydantic.model_validator(mode="before")
22
+ @classmethod
23
+ def _set_qmod_type(cls, values: Any) -> dict[str, Any]:
24
+ if isinstance(values, dict):
25
+ if "quantum_type" in values and (
26
+ "qmod_type" not in values or values["qmod_type"] is None
27
+ ):
28
+ values["qmod_type"] = values["quantum_type"]
29
+ values["quantum_type"] = None
30
+ return values
31
+ if values.quantum_type is not None and values.qmod_type is None:
32
+ values.qmod_type = values.quantum_type
33
+ values.quantum_type = None
34
+ return values
35
+
13
36
  @property
14
37
  def expressions(self) -> list[Expression]:
15
- return self.quantum_type.expressions
38
+ return self.qmod_type.expressions
39
+
40
+ @property
41
+ def is_quantum(self) -> bool:
42
+ return isinstance(self.qmod_type, QuantumType)
File without changes
@@ -6,7 +6,7 @@ from typing import Callable, Optional
6
6
 
7
7
  import numpy as np
8
8
 
9
- from classiq.qmod.utilities import DEFAULT_DECIMAL_PRECISION
9
+ from classiq.interface.constants import DEFAULT_DECIMAL_PRECISION
10
10
 
11
11
  IDENTIFIER = re.compile(r"[a-zA-Z_]\w*")
12
12
  BINARY_OPS: Mapping[type[ast.operator], str] = {
@@ -40,6 +40,10 @@ COMPARE_OPS: Mapping[type[ast.cmpop], str] = {
40
40
  LIST_FORMAT_CHAR_LIMIT = 20
41
41
 
42
42
 
43
+ class PrettyPrinterError(AssertionError):
44
+ pass
45
+
46
+
43
47
  @dataclass
44
48
  class ASTToQMODCode:
45
49
  level: int
@@ -59,10 +63,13 @@ class ASTToQMODCode:
59
63
  elif isinstance(node, ast.Attribute):
60
64
  # Enum attribute access
61
65
  if not isinstance(node.value, ast.Name) or not isinstance(node.attr, str):
62
- raise AssertionError("Error parsing enum attribute access")
66
+ raise PrettyPrinterError("Error parsing enum attribute access")
63
67
  if not (IDENTIFIER.match(node.value.id) and IDENTIFIER.match(node.attr)):
64
- raise AssertionError("Error parsing enum attribute access")
65
- return f"{node.value.id!s}::{node.attr!s}"
68
+ raise PrettyPrinterError("Error parsing enum attribute access")
69
+ # FIXME: identify enum member accesses by type name (CLS-2858)
70
+ if len(node.value.id) > 0 and node.value.id[0].isupper():
71
+ return f"{node.value.id!s}::{node.attr!s}"
72
+ return f"{node.value.id!s}.{node.attr!s}"
66
73
  elif isinstance(node, ast.Name):
67
74
  return node.id
68
75
  elif isinstance(node, ast.Num):
@@ -91,7 +98,7 @@ class ASTToQMODCode:
91
98
  )
92
99
  elif isinstance(node, ast.Compare):
93
100
  if len(node.ops) != 1 or len(node.comparators) != 1:
94
- raise AssertionError("Error parsing comparison expression.")
101
+ raise PrettyPrinterError("Error parsing comparison expression.")
95
102
  return "({} {} {})".format(
96
103
  self.ast_to_code(node.left),
97
104
  COMPARE_OPS[type(node.ops[0])],
@@ -108,20 +115,20 @@ class ASTToQMODCode:
108
115
  elif isinstance(node, ast.Slice):
109
116
  # A QMOD expression does not support slice step
110
117
  if node.lower is None or node.upper is None or node.step is not None:
111
- raise AssertionError("Error parsing slice expression.")
118
+ raise PrettyPrinterError("Error parsing slice expression.")
112
119
  return f"{self.ast_to_code(node.lower)}:{self.ast_to_code(node.upper)}"
113
120
  elif isinstance(node, ast.Call):
114
121
  func = self.ast_to_code(node.func)
115
122
  if func == "get_field":
116
123
  if len(node.args) != 2:
117
- raise AssertionError("Error parsing struct field access.")
124
+ raise PrettyPrinterError("Error parsing struct field access.")
118
125
  field = str(self.ast_to_code(node.args[1])).replace("'", "")
119
126
  if not IDENTIFIER.match(field):
120
- raise AssertionError("Error parsing struct field access.")
127
+ raise PrettyPrinterError("Error parsing struct field access.")
121
128
  return f"{self.ast_to_code(node.args[0])}.{field}"
122
129
  elif func == "struct_literal":
123
130
  if len(node.args) != 1 or not isinstance(node.args[0], ast.Name):
124
- raise AssertionError("Error parsing struct literal.")
131
+ raise PrettyPrinterError("Error parsing struct literal.")
125
132
  keywords = node.keywords
126
133
  initializer_list = self.indent_items(
127
134
  lambda: [
@@ -133,7 +140,7 @@ class ASTToQMODCode:
133
140
  return f"{self.ast_to_code(node.args[0])} {{{initializer_list}}}"
134
141
  elif func == "do_subscript":
135
142
  if len(node.args) != 2:
136
- raise AssertionError("Error parsing array access.")
143
+ raise PrettyPrinterError("Error parsing array access.")
137
144
  return f"{self.ast_to_code(node.args[0])}[{self.ast_to_code(node.args[1])}]"
138
145
  else:
139
146
  return "{}({})".format(
@@ -142,7 +149,7 @@ class ASTToQMODCode:
142
149
  elif isinstance(node, ast.Expr):
143
150
  return self._cleaned_ast_to_code(node.value)
144
151
  else:
145
- raise AssertionError("Error parsing expression: unsupported AST node.")
152
+ raise PrettyPrinterError("Error parsing expression: unsupported AST node.")
146
153
 
147
154
  def indent_items(self, items: Callable[[], list[str]]) -> str:
148
155
  should_indent = (
@@ -91,3 +91,7 @@ IQCC_LIST_AUTH_TARGETS_FULL_PATH = IQCC_PREFIX + IQCC_LIST_AUTH_TARGETS_SUFFIX
91
91
 
92
92
  USER_BUDGETS_SUFFIX = "/all"
93
93
  USER_BUDGETS_FULL_PATH = USER_BUDGETS_PREFIX + USER_BUDGETS_SUFFIX
94
+ USER_BUDGET_SET_LIMIT_SUFFIX = "/set_limit"
95
+ USER_BUDGET_SET_LIMIT_FULL_PATH = USER_BUDGETS_PREFIX + USER_BUDGET_SET_LIMIT_SUFFIX
96
+ USER_BUDGET_CLEAR_LIMIT_SUFFIX = "/clear_limit"
97
+ USER_BUDGET_CLEAR_LIMIT_FULL_PATH = USER_BUDGETS_PREFIX + USER_BUDGET_CLEAR_LIMIT_SUFFIX
@@ -9,6 +9,9 @@ from classiq.interface.exceptions import (
9
9
  ClassiqExpansionError,
10
10
  ClassiqInternalExpansionError,
11
11
  )
12
+ from classiq.interface.generator.expressions.atomic_expression_functions import (
13
+ MEASUREMENT_FUNCTIONS,
14
+ )
12
15
  from classiq.interface.generator.expressions.expression_types import (
13
16
  ExpressionValue,
14
17
  QmodStructInstance,
@@ -39,7 +42,6 @@ from classiq.interface.generator.functions.classical_function_declaration import
39
42
  from classiq.interface.generator.functions.classical_type import (
40
43
  Bool,
41
44
  ClassicalArray,
42
- ClassicalList,
43
45
  ClassicalTuple,
44
46
  ClassicalType,
45
47
  OpaqueHandle,
@@ -91,7 +93,7 @@ def qmod_val_to_python(val: ExpressionValue, qmod_type: ClassicalType) -> Any:
91
93
  if isinstance(val, (Enum, int)):
92
94
  return val
93
95
 
94
- elif isinstance(qmod_type, (ClassicalArray, ClassicalList)):
96
+ elif isinstance(qmod_type, ClassicalArray):
95
97
  if isinstance(val, list):
96
98
  return [qmod_val_to_python(elem, qmod_type.element_type) for elem in val]
97
99
 
@@ -145,9 +147,9 @@ def python_val_to_qmod(val: Any, qmod_type: ClassicalType) -> ExpressionValue:
145
147
  field_name: python_val_to_qmod(val[field_name], field_type)
146
148
  for field_name, field_type in struct_decl.variables.items()
147
149
  }
148
- return QmodStructInstance(struct_decl.model_copy(), qmod_dict)
150
+ return QmodStructInstance(struct_decl.model_copy(deep=True), qmod_dict)
149
151
 
150
- if isinstance(qmod_type, (ClassicalList, ClassicalArray)):
152
+ if isinstance(qmod_type, ClassicalArray):
151
153
  if not isinstance(val, list):
152
154
  raise ClassiqInternalExpansionError("Bad value for list")
153
155
  return [python_val_to_qmod(elem, qmod_type.element_type) for elem in val]
@@ -182,7 +184,7 @@ def python_call_wrapper(func: Callable, *args: ExpressionValue) -> Any:
182
184
 
183
185
  def struct_literal(struct_type_symbol: Symbol, **kwargs: Any) -> QmodStructInstance:
184
186
  return QmodStructInstance(
185
- QMODULE.type_decls[struct_type_symbol.name].model_copy(),
187
+ QMODULE.type_decls[struct_type_symbol.name].model_copy(deep=True),
186
188
  {field: sympy_to_python(field_value) for field, field_value in kwargs.items()},
187
189
  )
188
190
 
@@ -330,6 +332,40 @@ def mod_inverse(a: Any, b: Any) -> Any:
330
332
  return sympy.mod_inverse(a, b)
331
333
 
332
334
 
335
+ def min_wrapper(*vals: Any) -> Any:
336
+ try:
337
+ return sympy.Min(*vals)
338
+ except ValueError:
339
+ return AnyClassicalValue(f"Min({', '.join(map(str, vals))})")
340
+
341
+
342
+ min_wrapper.__name__ = "min"
343
+
344
+
345
+ def Min_wrapper(*vals: Any) -> Any: # noqa: N802
346
+ return min_wrapper(*vals)
347
+
348
+
349
+ Min_wrapper.__name__ = "Min"
350
+
351
+
352
+ def max_wrapper(*vals: Any) -> Any:
353
+ try:
354
+ return sympy.Max(*vals)
355
+ except ValueError:
356
+ return AnyClassicalValue(f"Max({', '.join(map(str, vals))})")
357
+
358
+
359
+ max_wrapper.__name__ = "max"
360
+
361
+
362
+ def Max_wrapper(*vals: Any) -> Any: # noqa: N802
363
+ return max_wrapper(*vals)
364
+
365
+
366
+ Max_wrapper.__name__ = "Max"
367
+
368
+
333
369
  CORE_LIB_FUNCTIONS_LIST: list[Callable] = [
334
370
  print,
335
371
  do_sum,
@@ -347,6 +383,10 @@ CORE_LIB_FUNCTIONS_LIST: list[Callable] = [
347
383
  RShift,
348
384
  LShift,
349
385
  mod_inverse,
386
+ min_wrapper,
387
+ Min_wrapper,
388
+ max_wrapper,
389
+ Max_wrapper,
350
390
  ]
351
391
 
352
392
 
@@ -361,7 +401,8 @@ def _symbolic_function(func: str) -> Callable:
361
401
 
362
402
 
363
403
  QMOD_CLASSICAL_FUNCTIONS = [
364
- _symbolic_function(func) for func in qmod_classical_functions
404
+ _symbolic_function(func)
405
+ for func in qmod_classical_functions + list(MEASUREMENT_FUNCTIONS)
365
406
  ]
366
407
 
367
408
  ATOMIC_EXPRESSION_FUNCTIONS = {
@@ -9,6 +9,7 @@ from classiq.interface.generator.compiler_keywords import (
9
9
  LAMBDA_KEYWORD,
10
10
  )
11
11
  from classiq.interface.generator.functions.builtins.internal_operators import (
12
+ BLOCK_OPERATOR_NAME,
12
13
  WITHIN_APPLY_NAME,
13
14
  )
14
15
  from classiq.interface.model.model import MAIN_FUNCTION_NAME
@@ -35,6 +36,8 @@ from classiq.model_expansions.utils.counted_name_allocator import CountedNameAll
35
36
 
36
37
  ClosureType = TypeVar("ClosureType", bound=Closure)
37
38
 
39
+ BLOCKS_ALLOWED_CAPTURING = (WITHIN_APPLY_NAME, BLOCK_OPERATOR_NAME)
40
+
38
41
 
39
42
  @dataclass
40
43
  class Block:
@@ -142,7 +145,7 @@ class OperationBuilder:
142
145
  captured_vars = self.current_block.captured_vars
143
146
  if (
144
147
  not isinstance(self.current_operation, FunctionClosure)
145
- and self.current_operation.name != WITHIN_APPLY_NAME
148
+ and self.current_operation.name not in BLOCKS_ALLOWED_CAPTURING
146
149
  ):
147
150
  validate_captured_directions(
148
151
  captured_vars.filter_var_decls(
@@ -39,14 +39,14 @@ from classiq.interface.model.quantum_lambda_function import (
39
39
  )
40
40
  from classiq.interface.model.quantum_statement import QuantumStatement
41
41
 
42
+ from classiq.evaluators.classical_expression import (
43
+ evaluate_classical_expression,
44
+ )
42
45
  from classiq.model_expansions.closure import (
43
46
  Closure,
44
47
  FunctionClosure,
45
48
  )
46
49
  from classiq.model_expansions.debug_flag import debug_mode
47
- from classiq.model_expansions.evaluators.classical_expression import (
48
- evaluate_classical_expression,
49
- )
50
50
  from classiq.model_expansions.function_builder import (
51
51
  FunctionContext,
52
52
  OperationBuilder,
@@ -7,6 +7,7 @@ from numpy.random import permutation
7
7
  from classiq.interface.generator.constant import Constant
8
8
  from classiq.interface.generator.expressions.expression import Expression
9
9
  from classiq.interface.generator.functions.builtins.internal_operators import (
10
+ BLOCK_OPERATOR_NAME,
10
11
  CLASSICAL_IF_OPERATOR_NAME,
11
12
  CONTROL_OPERATOR_NAME,
12
13
  INVERT_OPERATOR_NAME,
@@ -15,6 +16,7 @@ from classiq.interface.generator.functions.builtins.internal_operators import (
15
16
  )
16
17
  from classiq.interface.model.allocate import Allocate
17
18
  from classiq.interface.model.bind_operation import BindOperation
19
+ from classiq.interface.model.block import Block
18
20
  from classiq.interface.model.bounds import SetBoundsStatement
19
21
  from classiq.interface.model.classical_if import ClassicalIf
20
22
  from classiq.interface.model.control import Control
@@ -68,6 +70,9 @@ from classiq.model_expansions.quantum_operations.block_evaluator import (
68
70
  RepeatElimination,
69
71
  )
70
72
  from classiq.model_expansions.quantum_operations.bounds import SetBoundsEmitter
73
+ from classiq.model_expansions.quantum_operations.classical_var_emitter import (
74
+ ClassicalVarEmitter,
75
+ )
71
76
  from classiq.model_expansions.quantum_operations.composite_emitter import (
72
77
  CompositeEmitter,
73
78
  )
@@ -192,6 +197,7 @@ class GenerativeInterpreter(BaseInterpreter):
192
197
  [
193
198
  HandleEvaluator(self, "result_var"),
194
199
  ExpressionEvaluator(self, "expression"),
200
+ ClassicalVarEmitter(self),
195
201
  AssignmentResultProcessor(self),
196
202
  ],
197
203
  ).emit(op)
@@ -303,9 +309,18 @@ class GenerativeInterpreter(BaseInterpreter):
303
309
  def emit_set_bounds(self, op: SetBoundsStatement) -> None:
304
310
  CompositeEmitter[SetBoundsStatement](
305
311
  self,
306
- [HandleEvaluator(self, "target"), SetBoundsEmitter(self)],
312
+ [
313
+ ExpressionEvaluator(self, "lower_bound"),
314
+ ExpressionEvaluator(self, "upper_bound"),
315
+ HandleEvaluator(self, "target"),
316
+ SetBoundsEmitter(self),
317
+ ],
307
318
  ).emit(op)
308
319
 
320
+ @emit.register
321
+ def emit_block(self, block: Block) -> None:
322
+ BlockEvaluator(self, BLOCK_OPERATOR_NAME, "statements").emit(block)
323
+
309
324
  def _expand_body(self, operation: Closure) -> None:
310
325
  if isinstance(operation, FunctionClosure) and operation.name == "permute":
311
326
  # special expansion since permute is generative
@@ -12,7 +12,7 @@ from classiq.interface.model.quantum_type import (
12
12
  QuantumNumeric,
13
13
  )
14
14
 
15
- from classiq.model_expansions.evaluators.quantum_type_utils import copy_type_information
15
+ from classiq.evaluators.quantum_type_utils import copy_type_information
16
16
  from classiq.model_expansions.quantum_operations.emitter import Emitter
17
17
  from classiq.model_expansions.scope import QuantumSymbol
18
18