classiq 0.54.0__py3-none-any.whl → 0.55.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 (33) hide show
  1. classiq/interface/_version.py +1 -1
  2. classiq/interface/generator/functions/builtins/internal_operators.py +9 -1
  3. classiq/interface/generator/generated_circuit_data.py +0 -1
  4. classiq/interface/generator/model/preferences/preferences.py +4 -0
  5. classiq/interface/generator/types/compilation_metadata.py +5 -0
  6. classiq/interface/ide/visual_model.py +3 -1
  7. classiq/interface/model/control.py +22 -1
  8. classiq/interface/model/model.py +4 -0
  9. classiq/interface/model/native_function_definition.py +1 -1
  10. classiq/interface/model/quantum_expressions/arithmetic_operation.py +2 -26
  11. classiq/interface/model/quantum_statement.py +3 -0
  12. classiq/model_expansions/closure.py +74 -11
  13. classiq/model_expansions/function_builder.py +0 -6
  14. classiq/model_expansions/generative_functions.py +2 -2
  15. classiq/model_expansions/interpreter.py +22 -25
  16. classiq/model_expansions/quantum_operations/control.py +79 -20
  17. classiq/model_expansions/quantum_operations/emitter.py +24 -8
  18. classiq/model_expansions/quantum_operations/expression_operation.py +25 -1
  19. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +3 -26
  20. classiq/model_expansions/quantum_operations/quantum_function_call.py +2 -32
  21. classiq/model_expansions/quantum_operations/within_apply.py +0 -16
  22. classiq/model_expansions/scope_initialization.py +0 -1
  23. classiq/qmod/builtins/operations.py +113 -17
  24. classiq/qmod/create_model_function.py +10 -12
  25. classiq/qmod/model_state_container.py +5 -1
  26. classiq/qmod/native/pretty_printer.py +6 -1
  27. classiq/qmod/pretty_print/pretty_printer.py +25 -11
  28. classiq/qmod/quantum_function.py +25 -19
  29. classiq/qmod/synthesize_separately.py +1 -2
  30. {classiq-0.54.0.dist-info → classiq-0.55.0.dist-info}/METADATA +1 -1
  31. {classiq-0.54.0.dist-info → classiq-0.55.0.dist-info}/RECORD +32 -32
  32. classiq/model_expansions/call_to_model_converter.py +0 -190
  33. {classiq-0.54.0.dist-info → classiq-0.55.0.dist-info}/WHEEL +0 -0
@@ -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.54.0'
6
+ SEMVER_VERSION = '0.55.0'
7
7
  VERSION = str(Version(SEMVER_VERSION))
@@ -3,6 +3,14 @@ CONTROL_OPERATOR_NAME = "control"
3
3
  INVERT_OPERATOR_NAME = "invert"
4
4
  REPEAT_OPERATOR_NAME = "iteration"
5
5
  POWER_OPERATOR_NAME = "power"
6
- COMPUTE_OPERATOR_NAME = "compute"
7
6
  UNCOMPUTE_OPERATOR_NAME = "uncompute"
8
7
  WITHIN_APPLY_NAME = "within_apply"
8
+
9
+ All_BUILTINS_OPERATORS = {
10
+ CONTROL_OPERATOR_NAME,
11
+ INVERT_OPERATOR_NAME,
12
+ REPEAT_OPERATOR_NAME,
13
+ POWER_OPERATOR_NAME,
14
+ UNCOMPUTE_OPERATOR_NAME,
15
+ WITHIN_APPLY_NAME,
16
+ }
@@ -59,7 +59,6 @@ class GeneratedFunction(pydantic.BaseModel):
59
59
  registers: list[GeneratedRegister] = list()
60
60
  depth: Optional[int] = pydantic.Field(default=None)
61
61
  width: Optional[int] = pydantic.Field(default=None)
62
- released_auxiliary_qubits: list[int] = list()
63
62
  dangling_inputs: dict[str, GeneratedRegister] = dict()
64
63
  dangling_outputs: dict[str, GeneratedRegister] = dict()
65
64
 
@@ -154,6 +154,10 @@ class Preferences(pydantic.BaseModel, extra="forbid"):
154
154
  "Setting this option to False can potentially speed up the synthesis, and is "
155
155
  "recommended for executing iterative algorithms.",
156
156
  )
157
+ synthesize_all_separately: bool = pydantic.Field(
158
+ default=False,
159
+ description="If true, all functions will be synthesized separately",
160
+ )
157
161
  output_format: PydanticConstrainedQuantumFormatList = pydantic.Field(
158
162
  default=[QuantumFormat.QASM],
159
163
  description="The quantum circuit output format(s). ",
@@ -0,0 +1,5 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class CompilationMetadata(BaseModel):
5
+ should_synthesize_separately: bool = Field(default=False)
@@ -1,3 +1,4 @@
1
+ from collections import Counter
1
2
  from typing import Any, Optional
2
3
 
3
4
  import pydantic
@@ -23,7 +24,7 @@ class OperationType(StrEnum):
23
24
  class OperationData(pydantic.BaseModel):
24
25
  approximated_depth: Optional[int] = None
25
26
  width: int
26
- gate_count: dict[str, int] = pydantic.Field(default_factory=dict)
27
+ gate_count: Counter[str] = pydantic.Field(default_factory=dict)
27
28
 
28
29
 
29
30
  class CircuitMetrics(pydantic.BaseModel):
@@ -104,6 +105,7 @@ class AtomicGate(StrEnum):
104
105
 
105
106
  class Operation(pydantic.BaseModel):
106
107
  name: str
108
+ details: str = pydantic.Field(default="")
107
109
  children: list["Operation"]
108
110
  operation_data: Optional[OperationData] = None
109
111
  operation_links: OperationLinks
@@ -1,10 +1,12 @@
1
- from typing import TYPE_CHECKING, Literal
1
+ from typing import TYPE_CHECKING, Literal, Optional
2
2
 
3
3
  import pydantic
4
4
 
5
+ from classiq.interface.generator.arith.arithmetic import compute_arithmetic_result_type
5
6
  from classiq.interface.model.quantum_expressions.quantum_expression import (
6
7
  QuantumExpressionOperation,
7
8
  )
9
+ from classiq.interface.model.quantum_type import QuantumType
8
10
 
9
11
  if TYPE_CHECKING:
10
12
  from classiq.interface.model.statement_block import StatementBlock
@@ -13,8 +15,12 @@ if TYPE_CHECKING:
13
15
  class Control(QuantumExpressionOperation):
14
16
  kind: Literal["Control"]
15
17
  body: "StatementBlock"
18
+ else_block: Optional["StatementBlock"] = None
16
19
 
17
20
  _ctrl_size: int = pydantic.PrivateAttr(default=0)
21
+ _result_type: Optional[QuantumType] = pydantic.PrivateAttr(
22
+ default=None,
23
+ )
18
24
 
19
25
  @property
20
26
  def ctrl_size(self) -> int:
@@ -22,3 +28,18 @@ class Control(QuantumExpressionOperation):
22
28
 
23
29
  def set_ctrl_size(self, ctrl_size: int) -> None:
24
30
  self._ctrl_size = ctrl_size
31
+
32
+ @property
33
+ def result_type(self) -> QuantumType:
34
+ assert self._result_type is not None
35
+ return self._result_type
36
+
37
+ def initialize_var_types(
38
+ self,
39
+ var_types: dict[str, QuantumType],
40
+ machine_precision: int,
41
+ ) -> None:
42
+ super().initialize_var_types(var_types, machine_precision)
43
+ self._result_type = compute_arithmetic_result_type(
44
+ self.expression.expr, var_types, machine_precision
45
+ )
@@ -15,6 +15,7 @@ from classiq.interface.generator.functions.port_declaration import (
15
15
  from classiq.interface.generator.model.constraints import Constraints
16
16
  from classiq.interface.generator.model.preferences.preferences import Preferences
17
17
  from classiq.interface.generator.quantum_function_call import SUFFIX_RANDOMIZER
18
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
18
19
  from classiq.interface.generator.types.enum_declaration import EnumDeclaration
19
20
  from classiq.interface.generator.types.qstruct_declaration import QStructDeclaration
20
21
  from classiq.interface.generator.types.struct_declaration import StructDeclaration
@@ -98,6 +99,9 @@ class Model(VersionedModel, ASTNode):
98
99
  debug_info: DebugInfoCollection = pydantic.Field(
99
100
  default_factory=DebugInfoCollection
100
101
  )
102
+ functions_compilation_metadata: dict[str, CompilationMetadata] = pydantic.Field(
103
+ default_factory=dict
104
+ )
101
105
 
102
106
  @property
103
107
  def main_func(self) -> NativeFunctionDefinition:
@@ -29,5 +29,5 @@ class NativeFunctionDefinition(NamedParamsQuantumFunctionDeclaration):
29
29
  default_factory=list, description="List of function calls to perform."
30
30
  )
31
31
  synthesis_data: FunctionSynthesisData = pydantic.Field(
32
- default_factory=FunctionSynthesisData,
32
+ default_factory=FunctionSynthesisData, deprecated=True, exclude=True
33
33
  )
@@ -1,8 +1,5 @@
1
1
  from collections.abc import Mapping, Sequence
2
- from typing import Literal, Optional
3
-
4
- import pydantic
5
- from pydantic_core.core_schema import ValidationInfo
2
+ from typing import Literal
6
3
 
7
4
  from classiq.interface.enum_utils import StrEnum
8
5
  from classiq.interface.generator.arith.arithmetic import (
@@ -29,28 +26,7 @@ class ArithmeticOperationKind(StrEnum):
29
26
  class ArithmeticOperation(QuantumAssignmentOperation):
30
27
  kind: Literal["ArithmeticOperation"]
31
28
 
32
- inplace_result: Optional[bool] = pydantic.Field(
33
- description="Determines whether the result variable is initialized",
34
- default=None,
35
- exclude=True,
36
- )
37
-
38
- operation_kind: ArithmeticOperationKind = pydantic.Field(
39
- default=None, validate_default=True
40
- )
41
-
42
- @pydantic.field_validator("operation_kind", mode="before")
43
- @classmethod
44
- def _propagate_inplace_result(
45
- cls, operation_kind: Optional[ArithmeticOperationKind], info: ValidationInfo
46
- ) -> ArithmeticOperationKind:
47
- if operation_kind is None:
48
- operation_kind = (
49
- ArithmeticOperationKind.InplaceXor
50
- if info.data["inplace_result"]
51
- else ArithmeticOperationKind.Assignment
52
- )
53
- return operation_kind
29
+ operation_kind: ArithmeticOperationKind
54
30
 
55
31
  @property
56
32
  def is_inplace(self) -> bool:
@@ -75,6 +75,9 @@ class QuantumOperation(QuantumStatement):
75
75
  def set_generative_block(self, block_name: str, py_callable: Callable) -> None:
76
76
  self._generative_blocks[block_name] = py_callable
77
77
 
78
+ def remove_generative_block(self, block_name: str) -> None:
79
+ self._generative_blocks.pop(block_name)
80
+
78
81
  def get_generative_block(self, block_name: str) -> Callable:
79
82
  return self._generative_blocks[block_name]
80
83
 
@@ -1,14 +1,21 @@
1
+ import json
2
+ import uuid
1
3
  from collections import defaultdict
2
- from collections.abc import Sequence
4
+ from collections.abc import Collection, Sequence
3
5
  from dataclasses import dataclass, field
4
- from functools import cached_property
6
+ from functools import cached_property, singledispatch
7
+ from symtable import Symbol
5
8
  from typing import Any, Optional, Union
6
9
 
7
10
  from typing_extensions import Self
8
11
 
9
- from classiq.interface.exceptions import ClassiqInternalExpansionError
12
+ from classiq.interface.exceptions import (
13
+ ClassiqInternalExpansionError,
14
+ )
15
+ from classiq.interface.generator.functions.builtins.internal_operators import (
16
+ All_BUILTINS_OPERATORS,
17
+ )
10
18
  from classiq.interface.generator.visitor import Visitor
11
- from classiq.interface.model.native_function_definition import FunctionSynthesisData
12
19
  from classiq.interface.model.port_declaration import PortDeclaration
13
20
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
14
21
  from classiq.interface.model.quantum_function_declaration import (
@@ -20,8 +27,14 @@ from classiq.interface.model.variable_declaration_statement import (
20
27
  VariableDeclarationStatement,
21
28
  )
22
29
 
30
+ from classiq import ClassicalParameterDeclaration
23
31
  from classiq.model_expansions.expression_renamer import ExpressionRenamer
24
- from classiq.model_expansions.scope import Scope
32
+ from classiq.model_expansions.scope import (
33
+ Evaluated,
34
+ QuantumSymbol,
35
+ Scope,
36
+ evaluated_to_str as evaluated_classical_param_to_str,
37
+ )
25
38
  from classiq.qmod.builtins.functions import permute
26
39
  from classiq.qmod.quantum_function import GenerativeQFunc
27
40
 
@@ -52,7 +65,21 @@ class FunctionClosure(Closure):
52
65
  is_lambda: bool = False
53
66
  is_atomic: bool = False
54
67
  signature_scope: Scope = field(default_factory=Scope)
55
- synthesis_data: FunctionSynthesisData = field(default_factory=FunctionSynthesisData)
68
+
69
+ # creates a unique id for the function closure based on the arguments values.
70
+ # The closure is changing across the interpreter flow so it's closure_id may change
71
+ @property
72
+ def closure_id(self) -> str:
73
+ # builtins operators have side effects, so generate a unique id for than
74
+ # may create a bugs. Therefore, with each call to closure_id a new id is
75
+ # created
76
+ if self.is_lambda or self.name in All_BUILTINS_OPERATORS:
77
+ signature = str(uuid.uuid4())
78
+ else:
79
+ signature = _generate_closure_id(
80
+ self.positional_arg_declarations, self.scope.data.values()
81
+ )
82
+ return f"{self.name}__{signature}"
56
83
 
57
84
  @property
58
85
  def body(self) -> Sequence[QuantumStatement]:
@@ -77,7 +104,6 @@ class FunctionClosure(Closure):
77
104
  expr_renamer: Optional[ExpressionRenamer] = None,
78
105
  is_lambda: bool = False,
79
106
  is_atomic: bool = False,
80
- synthesis_data: Optional[FunctionSynthesisData] = None,
81
107
  **kwargs: Any,
82
108
  ) -> Self:
83
109
  if expr_renamer:
@@ -90,9 +116,6 @@ class FunctionClosure(Closure):
90
116
  body = expr_renamer.visit(body)
91
117
 
92
118
  blocks = {"body": body} if body is not None else {}
93
- synthesis_data = (
94
- synthesis_data if synthesis_data is not None else FunctionSynthesisData()
95
- )
96
119
  return cls(
97
120
  name,
98
121
  blocks,
@@ -100,7 +123,6 @@ class FunctionClosure(Closure):
100
123
  positional_arg_declarations,
101
124
  is_lambda,
102
125
  is_atomic,
103
- synthesis_data=synthesis_data,
104
126
  **kwargs,
105
127
  )
106
128
 
@@ -171,3 +193,44 @@ class VariableCollector(Visitor):
171
193
  defining_function = lambda_environment[var].defining_function
172
194
  if defining_function is not None:
173
195
  self._variables[var].add(defining_function.name)
196
+
197
+
198
+ def _generate_closure_id(
199
+ args_declaration: Sequence[PositionalArg], evaluated_args: Collection[Evaluated]
200
+ ) -> str:
201
+ args_signature: dict = {}
202
+ for arg_declara, eval_arg in zip(args_declaration, evaluated_args):
203
+ args_signature |= _generate_arg_id(arg_declara, eval_arg)
204
+ return json.dumps(args_signature)
205
+
206
+
207
+ def _generate_arg_id(
208
+ arg_declaration: PositionalArg, evaluated_arg: Evaluated
209
+ ) -> dict[str, str]:
210
+ arg_value = evaluated_arg.value
211
+ arg_name = arg_declaration.name
212
+ if isinstance(arg_declaration, ClassicalParameterDeclaration):
213
+ return {arg_name: evaluated_classical_param_to_str(arg_value)}
214
+ return {arg_name: _evaluated_arg_to_str(arg_value)}
215
+
216
+
217
+ @singledispatch
218
+ def _evaluated_arg_to_str(arg: Any) -> str:
219
+ if isinstance(arg, str):
220
+ return arg
221
+ return str(uuid.uuid4())
222
+
223
+
224
+ @_evaluated_arg_to_str.register
225
+ def _evaluated_quantum_symbol_to_str(port: QuantumSymbol) -> str:
226
+ return port.quantum_type.model_dump_json(exclude_none=True, exclude={"name"})
227
+
228
+
229
+ @_evaluated_arg_to_str.register
230
+ def _evaluated_symbol_to_str(port: Symbol) -> str:
231
+ return repr(port)
232
+
233
+
234
+ @_evaluated_arg_to_str.register
235
+ def _evaluated_operand_to_str(operand: FunctionClosure) -> str:
236
+ return operand.closure_id
@@ -19,7 +19,6 @@ from classiq.interface.generator.functions.port_declaration import (
19
19
  )
20
20
  from classiq.interface.model.model import MAIN_FUNCTION_NAME
21
21
  from classiq.interface.model.native_function_definition import (
22
- FunctionSynthesisData,
23
22
  NativeFunctionDefinition,
24
23
  )
25
24
  from classiq.interface.model.port_declaration import PortDeclaration
@@ -76,10 +75,6 @@ class FunctionContext(OperationContext[FunctionClosure]):
76
75
  def is_lambda(self) -> bool:
77
76
  return self.closure.is_lambda
78
77
 
79
- @property
80
- def synthesis_data(self) -> FunctionSynthesisData:
81
- return self.closure.synthesis_data
82
-
83
78
 
84
79
  class OperationBuilder:
85
80
  def __init__(self) -> None:
@@ -176,7 +171,6 @@ class OperationBuilder:
176
171
  name=name,
177
172
  body=function_context.body,
178
173
  positional_arg_declarations=new_parameters,
179
- synthesis_data=function_context.synthesis_data,
180
174
  )
181
175
 
182
176
 
@@ -186,7 +186,7 @@ def _expand_operand_as_declarative(
186
186
 
187
187
 
188
188
  def _register_declarative_function(interpreter: "Interpreter", func_name: str) -> None:
189
- if func_name in nameables_to_dict(interpreter._expanded_functions):
189
+ if func_name in nameables_to_dict(list(interpreter._expanded_functions.values())):
190
190
  return
191
191
 
192
192
  for user_gen_func in interpreter._generative_functions:
@@ -199,7 +199,7 @@ def _register_declarative_function(interpreter: "Interpreter", func_name: str) -
199
199
  dec_func = QFunc(user_gen_func._py_callable)
200
200
  dec_func.expand()
201
201
  dec_func_def = QMODULE.native_defs[func_name]
202
- interpreter._expanded_functions.append(dec_func_def)
202
+ interpreter._expanded_functions[func_name] = dec_func_def
203
203
  _DecFuncVisitor(interpreter).visit(dec_func_def)
204
204
 
205
205
 
@@ -12,6 +12,7 @@ from classiq.interface.exceptions import (
12
12
  ClassiqInternalExpansionError,
13
13
  )
14
14
  from classiq.interface.generator.expressions.expression import Expression
15
+ from classiq.interface.generator.types.compilation_metadata import CompilationMetadata
15
16
  from classiq.interface.model.bind_operation import BindOperation
16
17
  from classiq.interface.model.classical_if import ClassicalIf
17
18
  from classiq.interface.model.control import Control
@@ -46,7 +47,6 @@ from classiq.interface.model.variable_declaration_statement import (
46
47
  )
47
48
  from classiq.interface.model.within_apply_operation import WithinApply
48
49
 
49
- from classiq.model_expansions.call_to_model_converter import BlockFunctionInfo
50
50
  from classiq.model_expansions.capturing.propagated_var_stack import PropagatedVarStack
51
51
  from classiq.model_expansions.closure import (
52
52
  Closure,
@@ -108,7 +108,7 @@ class Interpreter:
108
108
  self._model = model
109
109
  self._current_scope = Scope()
110
110
  self._builder = OperationBuilder()
111
- self._expanded_functions: list[NativeFunctionDefinition] = []
111
+ self._expanded_functions: dict[str, NativeFunctionDefinition] = {}
112
112
  self._propagated_var_stack = PropagatedVarStack(
113
113
  self._current_scope, self._builder
114
114
  )
@@ -119,10 +119,11 @@ class Interpreter:
119
119
  generative_functions = []
120
120
  self._generative_functions = generative_functions
121
121
  init_top_level_scope(model, generative_functions, self._current_scope)
122
-
122
+ self._expanded_functions_compilation_metadata: dict[
123
+ str, CompilationMetadata
124
+ ] = dict()
123
125
  self._counted_name_allocator = CountedNameAllocator()
124
126
  self._error_manager: ErrorManager = ErrorManager()
125
- self._synthesized_separately_blocks: dict[str, BlockFunctionInfo] = {}
126
127
 
127
128
  @contextmanager
128
129
  def _scope_guard(self, scope: Scope) -> Iterator[None]:
@@ -153,14 +154,13 @@ class Interpreter:
153
154
  main_closure.positional_arg_declarations, main_closure
154
155
  )
155
156
  context = self._expand_operation(main_closure)
156
- self._expanded_functions.append(
157
+ self._expanded_functions[main_closure.closure_id] = (
157
158
  self._builder.create_definition(cast(FunctionContext, context))
158
159
  )
159
160
 
160
- def expand(self) -> tuple[Model, dict[str, BlockFunctionInfo]]:
161
+ def expand(self) -> Model:
161
162
  try:
162
163
  with self._error_manager.call("main"):
163
- self._synthesized_separately_blocks = {}
164
164
  self._expand_main_func()
165
165
  except Exception as e:
166
166
  if isinstance(e, ClassiqInternalExpansionError) or debug_mode.get():
@@ -172,20 +172,18 @@ class Interpreter:
172
172
  finally:
173
173
  self._error_manager.report_errors(ClassiqExpansionError)
174
174
 
175
- return (
176
- Model(
177
- constraints=self._model.constraints,
178
- preferences=self._model.preferences,
179
- classical_execution_code=self._model.classical_execution_code,
180
- execution_preferences=self._model.execution_preferences,
181
- functions=self._expanded_functions,
182
- constants=self._model.constants,
183
- enums=self._model.enums,
184
- types=self._model.types,
185
- qstructs=self._model.qstructs,
186
- debug_info=self._model.debug_info,
187
- ),
188
- self._synthesized_separately_blocks,
175
+ return Model(
176
+ constraints=self._model.constraints,
177
+ preferences=self._model.preferences,
178
+ classical_execution_code=self._model.classical_execution_code,
179
+ execution_preferences=self._model.execution_preferences,
180
+ functions=list(self._expanded_functions.values()),
181
+ constants=self._model.constants,
182
+ enums=self._model.enums,
183
+ types=self._model.types,
184
+ qstructs=self._model.qstructs,
185
+ debug_info=self._model.debug_info,
186
+ functions_compilation_metadata=self._expanded_functions_compilation_metadata,
189
187
  )
190
188
 
191
189
  @singledispatchmethod
@@ -346,9 +344,8 @@ class Interpreter:
346
344
 
347
345
  def _expand_operation(self, operation: Closure) -> OperationContext:
348
346
  with self._builder.operation_context(operation) as context:
349
- if (
350
- isinstance(operation, FunctionClosure)
351
- and operation.synthesis_data.should_synthesize_separately
347
+ if isinstance(operation, FunctionClosure) and (
348
+ self._expanded_functions.get(operation.closure_id) is not None
352
349
  ):
353
350
  pass
354
351
  elif isinstance(operation, FunctionClosure) and operation.name == "permute":
@@ -385,5 +382,5 @@ class Interpreter:
385
382
  return (
386
383
  self._model.functions
387
384
  + [gen_func.func_decl for gen_func in self._generative_functions]
388
- + self._expanded_functions # type:ignore[operator]
385
+ + list(self._expanded_functions.values())
389
386
  )
@@ -9,6 +9,7 @@ from classiq.interface.exceptions import (
9
9
  from classiq.interface.generator.compiler_keywords import INPLACE_ARITH_AUX_VAR_PREFIX
10
10
  from classiq.interface.generator.expressions.expression import Expression
11
11
  from classiq.interface.generator.expressions.expression_types import ExpressionValue
12
+ from classiq.interface.generator.expressions.qmod_qarray_proxy import QmodQArrayProxy
12
13
  from classiq.interface.generator.expressions.qmod_qscalar_proxy import QmodQNumProxy
13
14
  from classiq.interface.generator.expressions.qmod_sized_proxy import QmodSizedProxy
14
15
  from classiq.interface.generator.functions.builtins.internal_operators import (
@@ -21,6 +22,7 @@ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
21
22
  ArithmeticOperation,
22
23
  ArithmeticOperationKind,
23
24
  )
25
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
24
26
  from classiq.interface.model.quantum_type import QuantumBit, QuantumBitvector
25
27
  from classiq.interface.model.statement_block import ConcreteQuantumStatement
26
28
  from classiq.interface.model.variable_declaration_statement import (
@@ -40,6 +42,7 @@ from classiq.model_expansions.quantum_operations.expression_operation import (
40
42
  ExpressionOperationEmitter,
41
43
  )
42
44
  from classiq.model_expansions.scope import Scope
45
+ from classiq.qmod.builtins.functions.standard_gates import X
43
46
 
44
47
  ARRAY_CAST_SUFFIX = HANDLE_ID_SEPARATOR + "array_cast"
45
48
 
@@ -47,22 +50,42 @@ ARRAY_CAST_SUFFIX = HANDLE_ID_SEPARATOR + "array_cast"
47
50
  class ControlEmitter(ExpressionOperationEmitter[Control]):
48
51
  def emit(self, control: Control, /) -> None:
49
52
  condition = self._evaluate_op_expression(control)
50
- control = control.model_copy(update=dict(expression=condition))
51
53
 
52
54
  arrays_with_subscript = self._get_symbols_to_split(condition)
53
55
  if len(arrays_with_subscript) > 0:
54
56
  self._emit_with_split(control, condition, arrays_with_subscript)
55
57
  return
56
58
 
59
+ control = self._evaluate_types_in_expression(control, condition)
57
60
  condition_val = condition.value.value
58
61
  if isinstance(condition_val, QmodSizedProxy):
59
- self._validate_canonical_condition(condition_val)
60
- self._emit_canonical_control(control)
61
- return
62
+ if control.else_block is None:
63
+ self._validate_canonical_condition(condition_val)
64
+ self._emit_canonical_control(control)
65
+ return
66
+ else:
67
+ self._interpreter.emit_statement(
68
+ control.model_copy(
69
+ update=dict(
70
+ expression=self._uncanonize_condition(condition_val)
71
+ )
72
+ )
73
+ )
74
+ return
62
75
 
63
- self._validate_condition(condition_val)
76
+ self._validate_condition(control)
64
77
  self._emit_with_boolean(control)
65
78
 
79
+ def _uncanonize_condition(self, condition_val: QmodSizedProxy) -> Expression:
80
+ lhs = (
81
+ " & ".join(
82
+ f"{condition_val.handle}[{idx}]" for idx in range(condition_val.size)
83
+ )
84
+ if isinstance(condition_val, QmodQArrayProxy)
85
+ else condition_val.handle
86
+ )
87
+ return Expression(expr=f"{lhs} == 1")
88
+
66
89
  def _emit_canonical_control(self, control: Control) -> None:
67
90
  # canonical means control(q, body) where q is a single quantum variable
68
91
  control = self._evaluate_types_in_expression(control, control.expression)
@@ -129,9 +152,10 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
129
152
  condition_val = control.expression.value.value
130
153
  if self._is_simple_equality(condition_val):
131
154
  ctrl, ctrl_state = resolve_num_condition(condition_val)
132
- self._emit_with_x_gates(control, ctrl, ctrl_state)
133
- else:
134
- self._emit_with_arithmetic(control)
155
+ if control.else_block is None or ctrl.size == 1:
156
+ self._emit_with_x_gates(control, ctrl, ctrl_state)
157
+ return
158
+ self._emit_with_arithmetic(control)
135
159
 
136
160
  @staticmethod
137
161
  def _is_simple_equality(condition_val: ExpressionValue) -> TypeGuard[Equality]:
@@ -151,13 +175,37 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
151
175
 
152
176
  def _create_canonical_control_op(
153
177
  self, control: Control, handle_name: str
154
- ) -> Control:
178
+ ) -> list[ConcreteQuantumStatement]:
155
179
  handle_expr = self._interpreter.evaluate(Expression(expr=handle_name)).emit()
156
- return control.model_copy(update=dict(expression=handle_expr))
180
+ control_then = control.model_copy(
181
+ update=dict(expression=handle_expr, else_block=None)
182
+ )
183
+ if control.else_block is None:
184
+ return [control_then]
185
+ else_compute_call = QuantumFunctionCall(
186
+ function="X", positional_args=[HandleBinding(name=handle_name)]
187
+ )
188
+ else_compute_call.set_func_decl(X.func_decl)
189
+ control_else_inner = control.model_copy(
190
+ update=dict(
191
+ expression=handle_expr, body=control.else_block, else_block=None
192
+ ),
193
+ deep=True,
194
+ )
195
+ control_else = WithinApply(
196
+ compute=[else_compute_call],
197
+ action=[control_else_inner],
198
+ )
199
+ if control_else_inner.is_generative():
200
+ control_else_inner.set_generative_block(
201
+ "body", control_else_inner.get_generative_block("else_block")
202
+ )
203
+ control_else_inner.remove_generative_block("else_block")
204
+ return [control_then, control_else]
157
205
 
158
- def _emit_with_x_gates(
206
+ def _control_with_x_gates(
159
207
  self, control: Control, ctrl: QmodSizedProxy, ctrl_state: str
160
- ) -> None:
208
+ ) -> ConcreteQuantumStatement:
161
209
  compute_op: list[ConcreteQuantumStatement] = []
162
210
 
163
211
  x_gate_value = self._get_x_gate_value(ctrl_state)
@@ -175,12 +223,19 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
175
223
  cast_decl, bind_op = self._get_array_cast_ops(ctrl)
176
224
  self._interpreter.emit_statement(cast_decl)
177
225
  compute_op.append(bind_op)
178
- control_op = self._create_canonical_control_op(control, str(cast_decl.name))
226
+ control_ops = self._create_canonical_control_op(
227
+ control, str(cast_decl.name)
228
+ )
179
229
  else:
180
- control_op = self._create_canonical_control_op(control, str(ctrl.handle))
230
+ control_ops = self._create_canonical_control_op(control, str(ctrl.handle))
231
+
232
+ return WithinApply(compute=compute_op, action=control_ops)
181
233
 
234
+ def _emit_with_x_gates(
235
+ self, control: Control, ctrl: QmodSizedProxy, ctrl_state: str
236
+ ) -> None:
182
237
  self._interpreter.emit_statement(
183
- WithinApply(compute=compute_op, action=[control_op])
238
+ self._control_with_x_gates(control, ctrl, ctrl_state)
184
239
  )
185
240
 
186
241
  @staticmethod
@@ -217,14 +272,18 @@ class ControlEmitter(ExpressionOperationEmitter[Control]):
217
272
  self._interpreter.emit_statement(
218
273
  WithinApply(
219
274
  compute=[arith_expression],
220
- action=[self._create_canonical_control_op(control, aux_var)],
275
+ action=self._create_canonical_control_op(control, aux_var),
221
276
  )
222
277
  )
223
278
 
224
- @staticmethod
225
- def _validate_condition(condition_val: ExpressionValue) -> None:
226
- if not isinstance(condition_val, Boolean):
227
- raise ClassiqExpansionError(_condition_err_msg(condition_val))
279
+ def _validate_condition(self, control: Control) -> None:
280
+ condition_value = control.expression.value.value
281
+ if not (
282
+ isinstance(condition_value, Boolean)
283
+ or self._all_vars_boolean(control)
284
+ and self._is_res_boolean(control)
285
+ ):
286
+ raise ClassiqExpansionError(_condition_err_msg(condition_value))
228
287
 
229
288
  @staticmethod
230
289
  def _validate_canonical_condition(condition_val: ExpressionValue) -> None: