classiq 0.91.1__py3-none-any.whl → 0.93.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 (43) hide show
  1. classiq/__init__.py +13 -0
  2. classiq/_internals/config.py +1 -1
  3. classiq/analyzer/show_interactive_hack.py +1 -1
  4. classiq/applications/__init__.py +2 -6
  5. classiq/applications/qsp/__init__.py +7 -0
  6. classiq/applications/qsp/qsp.py +366 -0
  7. classiq/evaluators/parameter_types.py +13 -7
  8. classiq/execution/jobs.py +18 -8
  9. classiq/interface/_version.py +1 -1
  10. classiq/interface/backend/backend_preferences.py +8 -8
  11. classiq/interface/exceptions.py +4 -0
  12. classiq/interface/executor/result.py +4 -0
  13. classiq/interface/generator/functions/builtins/internal_operators.py +1 -0
  14. classiq/interface/generator/generated_circuit_data.py +5 -17
  15. classiq/interface/generator/transpiler_basis_gates.py +1 -1
  16. classiq/interface/helpers/versioned_model.py +0 -2
  17. classiq/interface/interface_version.py +1 -1
  18. classiq/interface/model/bind_operation.py +0 -12
  19. classiq/interface/model/handle_binding.py +3 -0
  20. classiq/interface/model/skip_control.py +11 -0
  21. classiq/interface/model/statement_block.py +3 -0
  22. classiq/interface/server/routes.py +0 -3
  23. classiq/model_expansions/interpreters/generative_interpreter.py +18 -1
  24. classiq/model_expansions/quantum_operations/assignment_result_processor.py +6 -0
  25. classiq/model_expansions/quantum_operations/bind.py +14 -0
  26. classiq/model_expansions/quantum_operations/skip_control_verifier.py +20 -0
  27. classiq/model_expansions/quantum_operations/variable_decleration.py +59 -27
  28. classiq/open_library/functions/__init__.py +2 -0
  29. classiq/open_library/functions/discrete_sine_cosine_transform.py +15 -10
  30. classiq/open_library/functions/qsvt.py +60 -6
  31. classiq/qmod/builtins/operations.py +24 -0
  32. classiq/qmod/classical_variable.py +4 -2
  33. classiq/qmod/native/pretty_printer.py +8 -0
  34. classiq/qmod/pretty_print/pretty_printer.py +5 -0
  35. classiq/qmod/quantum_expandable.py +31 -15
  36. classiq/qmod/symbolic_expr.py +12 -4
  37. classiq/synthesis.py +1 -1
  38. {classiq-0.91.1.dist-info → classiq-0.93.0.dist-info}/METADATA +39 -34
  39. {classiq-0.91.1.dist-info → classiq-0.93.0.dist-info}/RECORD +41 -37
  40. classiq-0.93.0.dist-info/WHEEL +4 -0
  41. classiq-0.93.0.dist-info/licenses/LICENSE.txt +27 -0
  42. classiq/interface/ide/ide_data.py +0 -102
  43. classiq-0.91.1.dist-info/WHEEL +0 -4
@@ -148,6 +148,7 @@ class StatementType(StrEnum):
148
148
  REPEAT = "repeat"
149
149
  BLOCK = "block"
150
150
  IF = "if"
151
+ SKIP_CONTROL = "skip control"
151
152
 
152
153
 
153
154
  # Mapping between statement kind (or sub-kind) and statement type (visualization name)
@@ -170,6 +171,7 @@ STATEMENTS_NAME: dict[str, StatementType] = {
170
171
  "Repeat": StatementType.REPEAT,
171
172
  "Block": StatementType.BLOCK,
172
173
  "ClassicalIf": StatementType.IF,
174
+ "SkipControl": StatementType.SKIP_CONTROL,
173
175
  }
174
176
 
175
177
 
@@ -197,9 +199,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
197
199
  @property
198
200
  def name(self) -> str:
199
201
  generated_name = self.generated_function.name if self.generated_function else ""
200
- # Temp fix for remaining old "supported" statements - power
201
- if generated_name == StatementType.POWER:
202
- return generated_name
203
202
 
204
203
  back_ref = self.first_back_ref
205
204
  if back_ref is None:
@@ -235,10 +234,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
235
234
 
236
235
  @property
237
236
  def level(self) -> OperationLevel:
238
- # Temp fix for remaining old "supported" statements - power
239
- if self.name == StatementType.POWER:
240
- return OperationLevel.QMOD_STATEMENT
241
-
242
237
  if self.first_back_ref is None:
243
238
  # we use ENGINE_FUNCTION_CALL in case where there's not back ref
244
239
  return OperationLevel.ENGINE_FUNCTION_CALL
@@ -300,15 +295,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
300
295
 
301
296
  updated_children: list[FunctionDebugInfoInterface] = []
302
297
  for child in self.children:
303
- updated_child = child.white_new_absolute_qubits(self.absolute_qubits)
304
- if updated_child.override_debug_info is None:
305
- updated_child = updated_child.propagate_absolute_qubits()
306
- else:
307
- updated_child.override_debug_info = (
308
- updated_child.override_debug_info.white_new_absolute_qubits(
309
- absolute_qubits=self.absolute_qubits
310
- ).propagate_absolute_qubits()
311
- )
298
+ updated_child = child._write_new_absolute_qubits(self.absolute_qubits)
299
+ updated_child = updated_child.propagate_absolute_qubits()
312
300
  updated_children.append(updated_child)
313
301
 
314
302
  return self.model_copy(
@@ -318,7 +306,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
318
306
  )
319
307
  )
320
308
 
321
- def white_new_absolute_qubits(
309
+ def _write_new_absolute_qubits(
322
310
  self, absolute_qubits: tuple[int, ...]
323
311
  ) -> "FunctionDebugInfoInterface":
324
312
  return self.model_copy(
@@ -61,7 +61,7 @@ NON_UNITARY_GATES: BasisGates = frozenset(("if_else",))
61
61
 
62
62
  TWO_QUBIT_GATES = BASIC_TWO_QUBIT_GATES | EXTRA_TWO_QUBIT_GATES
63
63
 
64
- THREE_QUBIT_GATES: BasisGates = frozenset(("ccx", "cswap"))
64
+ THREE_QUBIT_GATES: BasisGates = frozenset(("ccx", "cswap", "ccz"))
65
65
  DEFAULT_BASIS_GATES: BasisGates = SINGLE_QUBIT_GATES | BASIC_TWO_QUBIT_GATES
66
66
  ALL_GATES: BasisGates = (
67
67
  SINGLE_QUBIT_GATES | TWO_QUBIT_GATES | THREE_QUBIT_GATES | NON_UNITARY_GATES
@@ -2,12 +2,10 @@ from typing import Any
2
2
 
3
3
  import pydantic
4
4
 
5
- from classiq.interface._version import VERSION
6
5
  from classiq.interface.interface_version import INTERFACE_VERSION
7
6
 
8
7
 
9
8
  class VersionedModel(pydantic.BaseModel):
10
- version: str = pydantic.Field(default=VERSION)
11
9
  interface_version: str = pydantic.Field(default="0")
12
10
 
13
11
  @pydantic.model_validator(mode="before")
@@ -1 +1 @@
1
- INTERFACE_VERSION = "12"
1
+ INTERFACE_VERSION = "13"
@@ -1,9 +1,6 @@
1
1
  from collections.abc import Mapping, Sequence
2
2
  from typing import Literal
3
3
 
4
- import pydantic
5
-
6
- from classiq.interface.exceptions import ClassiqValueError
7
4
  from classiq.interface.model.handle_binding import ConcreteHandleBinding, HandleBinding
8
5
  from classiq.interface.model.quantum_statement import HandleMetadata, QuantumOperation
9
6
 
@@ -49,12 +46,3 @@ class BindOperation(QuantumOperation):
49
46
  )
50
47
  for handle in self.out_handles
51
48
  ]
52
-
53
- @pydantic.field_validator("in_handles", "out_handles")
54
- @classmethod
55
- def validate_handle(cls, handles: list[HandleBinding]) -> list[HandleBinding]:
56
- for handle in handles:
57
- if not handle.is_bindable():
58
- raise ClassiqValueError(f"Cannot bind '{handle}'")
59
-
60
- return handles
@@ -382,6 +382,9 @@ FieldHandleBinding.model_rebuild()
382
382
  class HandlesList(ASTNode):
383
383
  handles: list["GeneralHandle"]
384
384
 
385
+ def __str__(self) -> str:
386
+ return f"[{', '.join(map(str, self.handles))}]"
387
+
385
388
 
386
389
  GeneralHandle = Union[ConcreteHandleBinding, HandlesList]
387
390
  HandlesList.model_rebuild()
@@ -0,0 +1,11 @@
1
+ from typing import TYPE_CHECKING, Literal
2
+
3
+ from classiq.interface.model.quantum_statement import QuantumOperation
4
+
5
+ if TYPE_CHECKING:
6
+ from classiq.interface.model.statement_block import StatementBlock
7
+
8
+
9
+ class SkipControl(QuantumOperation):
10
+ kind: Literal["SkipControl"]
11
+ body: "StatementBlock"
@@ -22,6 +22,7 @@ from classiq.interface.model.quantum_expressions.arithmetic_operation import (
22
22
  from classiq.interface.model.quantum_function_call import QuantumFunctionCall
23
23
  from classiq.interface.model.quantum_lambda_function import QuantumLambdaFunction
24
24
  from classiq.interface.model.repeat import Repeat
25
+ from classiq.interface.model.skip_control import SkipControl
25
26
  from classiq.interface.model.variable_declaration_statement import (
26
27
  VariableDeclarationStatement,
27
28
  )
@@ -53,6 +54,7 @@ ConcreteQuantumStatement = Annotated[
53
54
  Action,
54
55
  Uncompute,
55
56
  SetBoundsStatement,
57
+ SkipControl,
56
58
  ],
57
59
  Field(..., discriminator="kind"),
58
60
  ]
@@ -69,3 +71,4 @@ WithinApply.model_rebuild()
69
71
  ClassicalIf.model_rebuild()
70
72
  NativeFunctionDefinition.model_rebuild()
71
73
  PhaseOperation.model_rebuild()
74
+ SkipControl.model_rebuild()
@@ -25,7 +25,6 @@ RB = "/rb"
25
25
  ANALYZER_DATA_TASK = f"{TASKS_SUFFIX}/data"
26
26
  ANALYZER_QASM_TASK = f"{TASKS_SUFFIX}/qasm"
27
27
  ANALYZER_GET_VISUAL_MODEL = "/get_visual_model"
28
- ANALYZER_GET_IDE_DATA = "/get_ide_data"
29
28
  IDE_EVENT_TASK = f"{TASKS_SUFFIX}/event"
30
29
  DATA_DOG_EVENT_TASK = f"{TASKS_SUFFIX}/data_dog_event"
31
30
 
@@ -33,14 +32,12 @@ ANALYZER_DATA_TASK_UPLOAD_FILE = f"{TASKS_SUFFIX}/data/file_upload"
33
32
  ANALYZER_DATA_FULL_PATH = f"{ANALYZER_PREFIX}{ANALYZER_DATA_TASK}"
34
33
  ANALYZER_QASM_FULL_PATH = f"{ANALYZER_PREFIX}{ANALYZER_QASM_TASK}"
35
34
  ANALYZER_GET_VISUAL_MODEL_FULL_PATH = f"{ANALYZER_PREFIX}{ANALYZER_GET_VISUAL_MODEL}"
36
- ANALYZER_GET_IDE_DATA_FULL_PATH = f"{ANALYZER_PREFIX}{ANALYZER_GET_IDE_DATA}"
37
35
  IDE_EVENT_TASK_FULL_PATH = f"{ANALYZER_PREFIX}{IDE_EVENT_TASK}"
38
36
  DATA_DOG_EVENT_TASK_FULL_PATH = f"{ANALYZER_PREFIX}{DATA_DOG_EVENT_TASK}"
39
37
 
40
38
  IDE_QASM_TASK = f"{TASKS_SUFFIX}/generated_circuit_from_qasm"
41
39
  IDE_QASM_FULL_PATH = f"{ANALYZER_PREFIX}{IDE_QASM_TASK}"
42
40
  TASKS_GENERATE_SUFFIX = TASKS_SUFFIX + "/generate"
43
- TASKS_VISUALIZE_SUFFIX = TASKS_SUFFIX + "/visualize"
44
41
  TASKS_VISUAL_MODEL_SUFFIX = TASKS_SUFFIX + "/visual_model"
45
42
  TASKS_SOLVE_SUFFIX = "/tasks/solve"
46
43
 
@@ -13,6 +13,7 @@ from classiq.interface.generator.functions.builtins.internal_operators import (
13
13
  INVERT_OPERATOR_NAME,
14
14
  POWER_OPERATOR_NAME,
15
15
  REPEAT_OPERATOR_NAME,
16
+ SKIP_CONTROL_OPERATOR_NAME,
16
17
  WITHIN_APPLY_NAME,
17
18
  )
18
19
  from classiq.interface.model.allocate import Allocate
@@ -43,6 +44,7 @@ from classiq.interface.model.quantum_lambda_function import (
43
44
  )
44
45
  from classiq.interface.model.quantum_statement import QuantumStatement
45
46
  from classiq.interface.model.repeat import Repeat
47
+ from classiq.interface.model.skip_control import SkipControl
46
48
  from classiq.interface.model.variable_declaration_statement import (
47
49
  VariableDeclarationStatement,
48
50
  )
@@ -84,6 +86,9 @@ from classiq.model_expansions.quantum_operations.handle_evaluator import HandleE
84
86
  from classiq.model_expansions.quantum_operations.repeat_block_evaluator import (
85
87
  RepeatBlockEvaluator,
86
88
  )
89
+ from classiq.model_expansions.quantum_operations.skip_control_verifier import (
90
+ SkipControlVerifier,
91
+ )
87
92
  from classiq.model_expansions.scope import Evaluated, Scope
88
93
  from classiq.model_expansions.scope_initialization import (
89
94
  add_constants_to_scope,
@@ -240,7 +245,9 @@ class GenerativeInterpreter(BaseInterpreter):
240
245
  def emit_variable_declaration(
241
246
  self, variable_declaration: VariableDeclarationStatement
242
247
  ) -> None:
243
- VariableDeclarationStatementEmitter(self).emit(variable_declaration)
248
+ VariableDeclarationStatementEmitter(
249
+ self, allow_symbolic_vars=self._symbolic_parameters_switch
250
+ ).emit(variable_declaration)
244
251
 
245
252
  @emit.register
246
253
  def emit_classical_if(self, classical_if: ClassicalIf) -> None:
@@ -278,6 +285,16 @@ class GenerativeInterpreter(BaseInterpreter):
278
285
  def emit_invert(self, invert: Invert) -> None:
279
286
  BlockEvaluator(self, INVERT_OPERATOR_NAME, "body").emit(invert)
280
287
 
288
+ @emit.register
289
+ def emit_skip_control(self, skip_control: SkipControl) -> None:
290
+ CompositeEmitter[SkipControl](
291
+ self,
292
+ [
293
+ SkipControlVerifier(self),
294
+ BlockEvaluator(self, SKIP_CONTROL_OPERATOR_NAME, "body"),
295
+ ],
296
+ ).emit(skip_control)
297
+
281
298
  @emit.register
282
299
  def _emit_repeat(self, repeat: Repeat) -> None:
283
300
  self.emit_repeat(repeat)
@@ -62,6 +62,12 @@ class AssignmentResultProcessor(Emitter[QuantumAssignmentOperation]):
62
62
  if isinstance(result_symbol, ClassicalSymbol):
63
63
  return False
64
64
  result_type = result_symbol.quantum_type
65
+ if not isinstance(result_type, QuantumScalar):
66
+ raise ClassiqExpansionError(
67
+ f"Cannot assign into a non-scalar quantum variable "
68
+ f"{str(result_symbol.handle)!r} of type "
69
+ f"{result_type.raw_qmod_type_name}"
70
+ )
65
71
 
66
72
  if not (
67
73
  isinstance(op, ArithmeticOperation)
@@ -25,6 +25,12 @@ if TYPE_CHECKING:
25
25
  from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
26
26
 
27
27
 
28
+ BIND_PATH_EXPR_MESSAGE = (
29
+ "Cannot use variable part (subscript, slice, or field access) in the source or "
30
+ "destination of a 'bind' statement"
31
+ )
32
+
33
+
28
34
  class BindEmitter(Emitter[BindOperation]):
29
35
  def __init__(
30
36
  self, interpreter: "BaseInterpreter", allow_symbolic_size: bool = False
@@ -60,6 +66,7 @@ class BindEmitter(Emitter[BindOperation]):
60
66
  evaluated_outputs: list[Evaluated] = [
61
67
  self._interpreter.evaluate(arg) for arg in bind.out_handles
62
68
  ]
69
+ self._validate_var_types(evaluated_inputs + evaluated_outputs)
63
70
  self._validate_handle_states(evaluated_inputs, evaluated_outputs)
64
71
  inputs: list[QuantumSymbol] = [
65
72
  input.as_type(QuantumSymbol) for input in evaluated_inputs
@@ -71,6 +78,13 @@ class BindEmitter(Emitter[BindOperation]):
71
78
  outputs = evaluate_types_in_quantum_symbols(outputs, self._current_scope)
72
79
  return inputs, outputs
73
80
 
81
+ def _validate_var_types(self, vars: list[Evaluated]) -> None:
82
+ path_expr_vars = [
83
+ var.value for var in vars if not var.value.handle.is_bindable()
84
+ ]
85
+ if len(path_expr_vars) > 0:
86
+ raise ClassiqExpansionError(BIND_PATH_EXPR_MESSAGE)
87
+
74
88
  def _validate_handle_states(
75
89
  self, inputs: list[Evaluated], outputs: list[Evaluated]
76
90
  ) -> None:
@@ -0,0 +1,20 @@
1
+ from classiq.interface.exceptions import ClassiqExpansionError
2
+ from classiq.interface.helpers.backward_compatibility import zip_strict
3
+ from classiq.interface.model.skip_control import SkipControl
4
+
5
+ from classiq.model_expansions.function_builder import FunctionContext
6
+ from classiq.model_expansions.quantum_operations.emitter import Emitter
7
+
8
+
9
+ class SkipControlVerifier(Emitter[SkipControl]):
10
+ def emit(self, skip_control: SkipControl, /) -> bool:
11
+ for op, block in list(
12
+ zip_strict(self._builder._operations, self._builder._blocks, strict=True)
13
+ )[::-1]:
14
+ if isinstance(op, FunctionContext):
15
+ break
16
+ if block in ("action", "apply"):
17
+ raise ClassiqExpansionError(
18
+ "skip_control cannot be used under within-apply's 'apply' block"
19
+ )
20
+ return False
@@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Union, cast
3
3
  from classiq.interface.exceptions import ClassiqExpansionError
4
4
  from classiq.interface.generator.functions.classical_type import ClassicalType
5
5
  from classiq.interface.generator.functions.concrete_types import ConcreteType
6
+ from classiq.interface.helpers.text_utils import readable_list, s
6
7
  from classiq.interface.model.handle_binding import HandleBinding
7
8
  from classiq.interface.model.quantum_type import QuantumType
8
9
  from classiq.interface.model.variable_declaration_statement import (
@@ -13,11 +14,21 @@ from classiq.evaluators.parameter_types import (
13
14
  evaluate_type_in_classical_symbol,
14
15
  evaluate_type_in_quantum_symbol,
15
16
  )
17
+ from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
16
18
  from classiq.model_expansions.quantum_operations.emitter import Emitter
17
19
  from classiq.model_expansions.scope import ClassicalSymbol, Evaluated, QuantumSymbol
18
20
 
21
+ if TYPE_CHECKING:
22
+ from classiq.model_expansions.interpreters.base_interpreter import BaseInterpreter
23
+
19
24
 
20
25
  class VariableDeclarationStatementEmitter(Emitter[VariableDeclarationStatement]):
26
+ def __init__(
27
+ self, interpreter: "BaseInterpreter", allow_symbolic_vars: bool = False
28
+ ) -> None:
29
+ super().__init__(interpreter)
30
+ self._allow_symbolic_vars = allow_symbolic_vars
31
+
21
32
  def emit(self, variable_declaration: VariableDeclarationStatement, /) -> bool:
22
33
  var_decl = variable_declaration.model_copy(
23
34
  update=dict(back_ref=variable_declaration.uuid)
@@ -29,36 +40,57 @@ class VariableDeclarationStatementEmitter(Emitter[VariableDeclarationStatement])
29
40
  )
30
41
  var_value: Union[QuantumSymbol, ClassicalSymbol]
31
42
  if variable_declaration.is_quantum:
32
- if TYPE_CHECKING:
33
- assert isinstance(var_decl.qmod_type, QuantumType)
34
- updated_quantum_type = evaluate_type_in_quantum_symbol(
35
- var_decl.qmod_type,
36
- self._current_scope,
37
- var_decl.name,
38
- )
39
- var_decl.qmod_type = updated_quantum_type
40
- var_value = QuantumSymbol(
41
- handle=HandleBinding(name=var_decl.name),
42
- quantum_type=updated_quantum_type,
43
- )
44
- self._builder.current_block.captured_vars.init_var(
45
- var_decl.name, self._builder.current_function
46
- )
43
+ var_value = self._get_quantum_var(var_decl)
47
44
  else:
48
- if TYPE_CHECKING:
49
- assert isinstance(var_decl.qmod_type, ClassicalType)
50
- updated_classical_type = evaluate_type_in_classical_symbol(
51
- var_decl.qmod_type,
52
- self._current_scope,
53
- var_decl.name,
54
- )
55
- var_decl.qmod_type = cast(ConcreteType, updated_classical_type)
56
- var_value = ClassicalSymbol(
57
- handle=HandleBinding(name=var_decl.name),
58
- classical_type=updated_classical_type,
59
- )
45
+ var_value = self._get_classical_var(var_decl)
60
46
  self._current_scope[variable_declaration.name] = Evaluated(
61
47
  value=var_value, defining_function=self._builder.current_function
62
48
  )
63
49
  self.emit_statement(var_decl)
64
50
  return True
51
+
52
+ def _get_quantum_var(self, var_decl: VariableDeclarationStatement) -> QuantumSymbol:
53
+ updated_quantum_type = evaluate_type_in_quantum_symbol(
54
+ cast(QuantumType, var_decl.qmod_type),
55
+ self._current_scope,
56
+ var_decl.name,
57
+ )
58
+ if not self._allow_symbolic_vars:
59
+ symbolic_variables = list(
60
+ dict.fromkeys(
61
+ classical_var.name
62
+ for expr in updated_quantum_type.expressions
63
+ if isinstance(expr_val := expr.value.value, QmodAnnotatedExpression)
64
+ for classical_var in expr_val.get_classical_vars().values()
65
+ )
66
+ )
67
+ if len(symbolic_variables) > 0:
68
+ raise ClassiqExpansionError(
69
+ f"Variable type is instantiated with non-compile-time "
70
+ f"variable{s(symbolic_variables)} "
71
+ f"{readable_list(symbolic_variables, quote=True)}"
72
+ )
73
+ var_decl.qmod_type = updated_quantum_type
74
+ var_value = QuantumSymbol(
75
+ handle=HandleBinding(name=var_decl.name),
76
+ quantum_type=updated_quantum_type,
77
+ )
78
+ self._builder.current_block.captured_vars.init_var(
79
+ var_decl.name, self._builder.current_function
80
+ )
81
+ return var_value
82
+
83
+ def _get_classical_var(
84
+ self, var_decl: VariableDeclarationStatement
85
+ ) -> ClassicalSymbol:
86
+ updated_classical_type = evaluate_type_in_classical_symbol(
87
+ cast(ClassicalType, var_decl.qmod_type),
88
+ self._current_scope,
89
+ var_decl.name,
90
+ )
91
+ var_decl.qmod_type = cast(ConcreteType, updated_classical_type)
92
+ var_value = ClassicalSymbol(
93
+ handle=HandleBinding(name=var_decl.name),
94
+ classical_type=updated_classical_type,
95
+ )
96
+ return var_value
@@ -53,6 +53,7 @@ OPEN_LIBRARY_FUNCTIONS = [
53
53
  qsvt_inversion,
54
54
  qsvt_lcu,
55
55
  qsvt_lcu_step,
56
+ gqsp,
56
57
  qaoa_mixer_layer,
57
58
  qaoa_cost_layer,
58
59
  qaoa_layer,
@@ -94,6 +95,7 @@ __all__ = [
94
95
  "encode_on_bloch",
95
96
  "exact_amplitude_amplification",
96
97
  "full_hea",
98
+ "gqsp",
97
99
  "grover_diffuser",
98
100
  "grover_operator",
99
101
  "grover_search",
@@ -13,6 +13,7 @@ from classiq.qmod.builtins.operations import (
13
13
  from classiq.qmod.qfunc import qfunc
14
14
  from classiq.qmod.qmod_variable import Const, Permutable, QArray, QBit, QNum
15
15
  from classiq.qmod.symbolic import pi
16
+ from classiq.qmod.utilities import suppress_return_value
16
17
 
17
18
 
18
19
  def _b_operator(q: QBit) -> None:
@@ -27,7 +28,7 @@ def _qct_d_operator(x: Const[QNum], q: QBit) -> None:
27
28
 
28
29
 
29
30
  @qfunc
30
- def _qct_pi_operator(x: Permutable[QArray[QBit]], q: Const[QBit]) -> None:
31
+ def _qct_pi_operator(x: Permutable[QNum], q: Const[QBit]) -> None:
31
32
  control(
32
33
  q == 1,
33
34
  lambda: [
@@ -65,8 +66,8 @@ def _d1_operator(x: QArray[QBit], q: QBit) -> None:
65
66
  PHASE(-omega_exp, q)
66
67
 
67
68
 
68
- def _pi2_operator(x: QArray[QBit], q: QBit) -> None:
69
- control(q == 1, lambda: inplace_add(1, x)) # type:ignore[arg-type]
69
+ def _pi2_operator(x: QNum, q: QBit) -> None:
70
+ control(q == 1, lambda: inplace_add(1, x)), # type:ignore[arg-type]
70
71
 
71
72
 
72
73
  def _j_operator(q: QBit) -> None:
@@ -78,19 +79,23 @@ def _b_t_operator(q: QBit) -> None:
78
79
  S(q)
79
80
 
80
81
 
81
- def _d0dt_operator(x: QArray, q: QBit) -> None:
82
- x_num: QNum = QNum(size=x.len)
83
- bind(x, x_num)
82
+ @suppress_return_value
83
+ def _d0dt_operator(x: QNum, q: QBit) -> None:
84
84
  _b_t_operator(q)
85
- control(x_num == 0, lambda: _j_operator(q))
86
- bind(x_num, x)
85
+ control(x == 0, lambda: _j_operator(q))
87
86
 
88
87
 
89
88
  def _un_dag_operator(x: QArray[QBit], q: QBit) -> None:
90
89
  _d1_operator(x, q)
91
90
  invert(lambda: _qct_pi_operator(x, q))
92
- _d0dt_operator(x, q)
93
- invert(lambda: _pi2_operator(x, q))
91
+ x_num: QNum = QNum(size=x.len)
92
+ within_apply(
93
+ lambda: bind(x, x_num),
94
+ lambda: [
95
+ _d0dt_operator(x_num, q),
96
+ invert(lambda: _pi2_operator(x_num, q)),
97
+ ],
98
+ )
94
99
 
95
100
 
96
101
  @qfunc
@@ -1,7 +1,7 @@
1
- from classiq.qmod.builtins.functions.standard_gates import IDENTITY, RZ, H
1
+ from classiq.qmod.builtins.functions.standard_gates import IDENTITY, RZ, H, U, X, Z
2
2
  from classiq.qmod.builtins.operations import control, if_, invert, repeat, within_apply
3
3
  from classiq.qmod.qfunc import qfunc
4
- from classiq.qmod.qmod_parameter import CArray, CReal
4
+ from classiq.qmod.qmod_parameter import CArray, CInt, CReal
5
5
  from classiq.qmod.qmod_variable import QArray, QBit
6
6
  from classiq.qmod.quantum_callable import QCallable
7
7
  from classiq.qmod.symbolic import floor, min as qmin
@@ -267,8 +267,8 @@ def qsvt_lcu(
267
267
  Note: the two polynomials should have the same degree up to a difference of 1.
268
268
 
269
269
  Args:
270
- phase_seq_odd: A sequence of phase angles of length d+1 for the odd polynomial.
271
- phase_seq_even: A sequence of phase angles of length d+1 for the even polynomial.
270
+ phase_seq_odd: A sequence of phase angles of length d+(d%2) for the odd polynomial.
271
+ phase_seq_even: A sequence of phase angles of length d+(d+1)%2 for the even polynomial.
272
272
  proj_cnot_1: Projector-controlled-not unitary that locates the encoded matrix columns within U. Accepts a quantum variable of the same size as qvar, and a qubit that is set to |1> when the state is in the block.
273
273
  proj_cnot_2: Projector-controlled-not unitary that locates the encoded matrix rows within U. Accepts a quantum variable of the same size as qvar, and a qubit that is set to |1> when the state is in the block.
274
274
  u: A block encoded unitary matrix.
@@ -281,7 +281,7 @@ def qsvt_lcu(
281
281
  phase_seq_even[0], phase_seq_odd[0], proj_cnot_1, qvar, aux, lcu
282
282
  )
283
283
  repeat(
284
- count=floor((qmin(phase_seq_odd.len, phase_seq_even.len)) / 2),
284
+ count=floor((qmin(phase_seq_odd.len - 1, phase_seq_even.len - 1)) / 2),
285
285
  iteration=lambda index: qsvt_lcu_step(
286
286
  phase_seq_even[2 * index + 1 : 2 * index + 3],
287
287
  phase_seq_odd[2 * index + 1 : 2 * index + 3],
@@ -310,7 +310,7 @@ def qsvt_lcu(
310
310
  then=lambda: (
311
311
  u(qvar),
312
312
  projector_controlled_double_phase(
313
- phase_seq_even[phase_seq_even.len - 1],
313
+ phase_seq_even[phase_seq_even.len - 2],
314
314
  phase_seq_odd[phase_seq_odd.len - 1],
315
315
  proj_cnot_2,
316
316
  qvar,
@@ -329,3 +329,57 @@ def qsvt_lcu(
329
329
  ),
330
330
  )
331
331
  H(aux)
332
+
333
+
334
+ @qfunc
335
+ def _gqsp_r_gate(theta: CReal, phi: CReal, _lambda: CReal, q: QBit) -> None:
336
+ """
337
+ Implements the R gate from the paper https://arxiv.org/abs/2308.01501 using the U gate.
338
+ """
339
+ within_apply(lambda: X(q), lambda: U(2 * theta, phi, _lambda, 0, q))
340
+ Z(q)
341
+
342
+
343
+ @qfunc
344
+ def gqsp(
345
+ u: QCallable, aux: QBit, phases: CArray[CArray[CReal], 3], negative_power: CInt # type: ignore[valid-type]
346
+ ) -> None:
347
+ """
348
+ Implements Generalized Quantum Signal Processing (GQSP), which realizes a
349
+ (Laurent) polynomial transformation of degree d on the eigenvalues of the given
350
+ signal unitary `u`. The protocol is according to https://arxiv.org/abs/2308.01501
351
+ Fig.2.
352
+
353
+ Notes:
354
+ - The user is encouraged to use the function `gqsp_phases` to find `phases` that
355
+ correspond to the wanted polynomial transformation.
356
+ - Feasibility: the target polynomial must satisfy |P(e^{i*theta})| <= 1 for all
357
+ theta in [0, 2*pi). This ensures a unitary completion exists.
358
+ - Using `negative_power = m` (m >= 0) you can realize Laurent polynomials with
359
+ negative exponents: the implemented transform is equivalent to applying
360
+ z^{-m} * P(z) (i.e., shift the minimal degree to -m).
361
+ For ordinary (non-Laurent) polynomials, set `negative_power = 0`.
362
+
363
+ Args:
364
+ u: The signal unitary.
365
+ aux: Auxiliary qubit used for the phase rotations. Should start in |0>.
366
+ phases: 3 x (d+1) real array of angles: phases[0] = thetas, phases[1] = phis,
367
+ phases[2] = lambdas.
368
+ negative_power: Integer m in [0, d]. Encodes the minimal Laurent power -m of
369
+ the realized transformation.
370
+ """
371
+ degree = phases[0].len - 1
372
+ _gqsp_r_gate(phases[0][0], phases[1][0], phases[2][0], aux)
373
+ repeat(
374
+ degree,
375
+ lambda index: [
376
+ if_(
377
+ index + 1 <= degree - negative_power,
378
+ lambda: control(aux == 0, lambda: u()),
379
+ lambda: control(aux == 1, lambda: invert(u)),
380
+ ),
381
+ _gqsp_r_gate(
382
+ phases[0][index + 1], phases[1][index + 1], phases[2][index + 1], aux
383
+ ),
384
+ ],
385
+ )
@@ -45,6 +45,7 @@ from classiq.interface.model.quantum_function_declaration import (
45
45
  )
46
46
  from classiq.interface.model.quantum_lambda_function import QuantumLambdaFunction
47
47
  from classiq.interface.model.repeat import Repeat
48
+ from classiq.interface.model.skip_control import SkipControl
48
49
  from classiq.interface.model.statement_block import StatementBlock
49
50
  from classiq.interface.model.within_apply_operation import WithinApply
50
51
 
@@ -254,6 +255,20 @@ def control(
254
255
  QCallable.CURRENT_EXPANDABLE.append_statement_to_body(control_stmt)
255
256
 
256
257
 
258
+ @suppress_return_value
259
+ def skip_control(stmt_block: Union[QCallable, Callable[[], Statements]]) -> None:
260
+ _validate_operand(stmt_block)
261
+ assert QCallable.CURRENT_EXPANDABLE is not None
262
+ source_ref = get_source_ref(sys._getframe(1))
263
+ sc_stmt = SkipControl(
264
+ body=_operand_to_body(stmt_block, "stmt_block"),
265
+ source_ref=source_ref,
266
+ )
267
+ if is_generative_mode():
268
+ sc_stmt.set_generative_block("body", stmt_block)
269
+ QCallable.CURRENT_EXPANDABLE.append_statement_to_body(sc_stmt)
270
+
271
+
257
272
  @suppress_return_value
258
273
  def assign(expression: SymbolicExpr, target_var: QScalar) -> None:
259
274
  """
@@ -645,6 +660,14 @@ def _validate_operand(stmt_block: Any, num_params: int = 0) -> None:
645
660
  )
646
661
  if isinstance(stmt_block, QCallable):
647
662
  return
663
+ if not callable(stmt_block):
664
+ _raise_operand_error(
665
+ lambda operation_name, operand_arg_name: (
666
+ f"Argument {operand_arg_name!r} to {operation_name!r} must be a "
667
+ f"callable object"
668
+ ),
669
+ num_params,
670
+ )
648
671
  op_spec = inspect.getfullargspec(stmt_block)
649
672
  params = op_spec.args[: len(op_spec.args) - len(op_spec.defaults or ())]
650
673
  if len(params) > num_params or (
@@ -748,6 +771,7 @@ __all__ = [
748
771
  "power",
749
772
  "repeat",
750
773
  "reset_bounds",
774
+ "skip_control",
751
775
  "within_apply",
752
776
  ]
753
777
 
@@ -1,7 +1,7 @@
1
1
  import sys
2
2
  from typing import TYPE_CHECKING, Any
3
3
 
4
- from classiq.interface.exceptions import ClassiqInternalError
4
+ from classiq.interface.exceptions import ClassiqInternalError, ClassiqTypeError
5
5
  from classiq.interface.generator.expressions.expression import Expression
6
6
  from classiq.interface.generator.functions.classical_type import Bool, ClassicalType
7
7
  from classiq.interface.model.handle_binding import HandleBinding
@@ -37,7 +37,9 @@ def declare_classical_variable(
37
37
 
38
38
  def assign_classical_variable(target: CParam, value: Any, frame_depth: int) -> None:
39
39
  if not isinstance(value, SYMBOLIC_TYPES):
40
- raise TypeError(f"Invalid argument {value!r} for classical variable assignment")
40
+ raise ClassiqTypeError(
41
+ f"Invalid argument {value!r} for classical variable assignment"
42
+ )
41
43
 
42
44
  if TYPE_CHECKING:
43
45
  assert QCallable.CURRENT_EXPANDABLE is not None