classiq 0.46.0__py3-none-any.whl → 0.47.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 (44) hide show
  1. classiq/_internals/api_wrapper.py +45 -8
  2. classiq/execution/execution_session.py +96 -39
  3. classiq/execution/jobs.py +108 -1
  4. classiq/interface/_version.py +1 -1
  5. classiq/interface/backend/quantum_backend_providers.py +0 -1
  6. classiq/interface/debug_info/debug_info.py +23 -1
  7. classiq/interface/execution/primitives.py +9 -0
  8. classiq/interface/generator/arith/arithmetic_operations.py +5 -2
  9. classiq/interface/generator/arith/binary_ops.py +21 -14
  10. classiq/interface/generator/arith/extremum_operations.py +9 -1
  11. classiq/interface/generator/arith/number_utils.py +6 -0
  12. classiq/interface/generator/arith/register_user_input.py +30 -21
  13. classiq/interface/generator/arith/unary_ops.py +13 -1
  14. classiq/interface/generator/generated_circuit_data.py +47 -2
  15. classiq/interface/generator/quantum_program.py +10 -2
  16. classiq/interface/ide/visual_model.py +7 -1
  17. classiq/interface/interface_version.py +1 -1
  18. classiq/interface/model/phase_operation.py +11 -0
  19. classiq/interface/model/statement_block.py +3 -0
  20. classiq/interface/server/routes.py +0 -3
  21. classiq/model_expansions/interpreter.py +6 -0
  22. classiq/model_expansions/quantum_operations/emitter.py +8 -2
  23. classiq/model_expansions/quantum_operations/phase.py +177 -0
  24. classiq/qmod/builtins/functions/amplitude_estimation.py +2 -1
  25. classiq/qmod/builtins/functions/arithmetic.py +2 -2
  26. classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +16 -16
  27. classiq/qmod/builtins/functions/exponentiation.py +12 -11
  28. classiq/qmod/builtins/functions/grover.py +28 -29
  29. classiq/qmod/builtins/functions/hea.py +1 -1
  30. classiq/qmod/builtins/functions/linear_pauli_rotation.py +8 -8
  31. classiq/qmod/builtins/functions/modular_exponentiation.py +9 -9
  32. classiq/qmod/builtins/functions/qaoa_penalty.py +10 -10
  33. classiq/qmod/builtins/functions/qft.py +1 -1
  34. classiq/qmod/builtins/functions/qpe.py +2 -2
  35. classiq/qmod/builtins/functions/qsvt.py +15 -15
  36. classiq/qmod/builtins/functions/standard_gates.py +13 -13
  37. classiq/qmod/builtins/functions/state_preparation.py +18 -17
  38. classiq/qmod/builtins/functions/swap_test.py +3 -3
  39. classiq/qmod/builtins/operations.py +14 -0
  40. classiq/qmod/native/pretty_printer.py +6 -0
  41. classiq/qmod/pretty_print/pretty_printer.py +6 -0
  42. {classiq-0.46.0.dist-info → classiq-0.47.0.dist-info}/METADATA +7 -4
  43. {classiq-0.46.0.dist-info → classiq-0.47.0.dist-info}/RECORD +44 -41
  44. {classiq-0.46.0.dist-info → classiq-0.47.0.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, Optional
1
+ from typing import Any, Dict, Optional, Tuple
2
2
 
3
3
  import pydantic
4
4
 
@@ -14,6 +14,7 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
14
14
  size: pydantic.PositiveInt
15
15
  is_signed: bool = pydantic.Field(default=False)
16
16
  fraction_places: pydantic.NonNegativeInt = pydantic.Field(default=0)
17
+ bypass_bounds_validation: bool = pydantic.Field(default=False)
17
18
  bounds: PydanticFloatTuple = pydantic.Field(default=None)
18
19
 
19
20
  @pydantic.root_validator(pre=True)
@@ -22,32 +23,39 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
22
23
  values.pop("name")
23
24
  return values
24
25
 
26
+ @staticmethod
27
+ def get_maximal_bounds(
28
+ *, size: int, is_signed: bool, fraction_places: int
29
+ ) -> Tuple[float, float]:
30
+ lb = 0 if not is_signed else -(2 ** (size - 1))
31
+ ub = 2**size - 1 if not is_signed else 2 ** (size - 1) - 1
32
+ fraction_factor = float(2**-fraction_places)
33
+ return (lb * fraction_factor, ub * fraction_factor)
34
+
25
35
  @pydantic.validator("bounds", always=True)
26
36
  def _validate_bounds(
27
37
  cls, bounds: Optional[PydanticFloatTuple], values: Dict[str, Any]
28
38
  ) -> PydanticFloatTuple:
29
- if bounds is not None:
30
- if min(bounds) < 0:
31
- assert values.get("is_signed")
32
- fraction_places = values.get("fraction_places")
33
- if not isinstance(fraction_places, int):
34
- raise ClassiqValueError(
35
- "RegisterUserInput must have an integer fraction_places"
36
- )
37
- return number_utils.limit_fraction_places(
38
- min(bounds), machine_precision=fraction_places
39
- ), number_utils.limit_fraction_places(
40
- max(bounds), machine_precision=fraction_places
41
- )
42
-
43
39
  size = values.get("size")
40
+ is_signed = values.get("is_signed", False)
41
+ fraction_places = values.get("fraction_places", 0)
44
42
  if not isinstance(size, int):
45
- raise ClassiqValueError("RegisterUserInput must have an integer size")
46
- is_signed: bool = values.get("is_signed", False)
47
- lb = 0 if not is_signed else -(2 ** (size - 1))
48
- ub = 2**size - 1 if not is_signed else 2 ** (size - 1) - 1
49
- fraction_factor = float(2 ** -values.get("fraction_places", 0))
50
- return (lb * fraction_factor, ub * fraction_factor)
43
+ raise ClassiqValueError("RegisterArithmeticInfo must have an integer size")
44
+
45
+ maximal_bounds = cls.get_maximal_bounds(
46
+ size=size, is_signed=is_signed, fraction_places=fraction_places
47
+ )
48
+ if bounds is None:
49
+ return maximal_bounds
50
+
51
+ trimmed_bounds = (
52
+ number_utils.limit_fraction_places(min(bounds), fraction_places),
53
+ number_utils.limit_fraction_places(max(bounds), fraction_places),
54
+ )
55
+ if not values.get("bypass_bounds_validation", False):
56
+ assert min(trimmed_bounds) >= min(maximal_bounds), "Illegal bound min"
57
+ assert max(trimmed_bounds) <= max(maximal_bounds), "Illegal bound max"
58
+ return trimmed_bounds
51
59
 
52
60
  def limit_fraction_places(self, machine_precision: int) -> "RegisterArithmeticInfo":
53
61
  truncated_bits: int = max(self.fraction_places - machine_precision, 0)
@@ -56,6 +64,7 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
56
64
  is_signed=self.is_signed,
57
65
  fraction_places=self.fraction_places - truncated_bits,
58
66
  bounds=self.bounds,
67
+ bypass_bounds_validation=self.bypass_bounds_validation,
59
68
  )
60
69
 
61
70
  @property
@@ -5,6 +5,7 @@ import pydantic
5
5
  from classiq.interface.exceptions import ClassiqValueError
6
6
  from classiq.interface.generator.arith import argument_utils, number_utils
7
7
  from classiq.interface.generator.arith.arithmetic_operations import (
8
+ MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
8
9
  ArithmeticOperationParams,
9
10
  )
10
11
  from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
@@ -74,6 +75,9 @@ class BitwiseInvert(UnaryOpParams):
74
75
 
75
76
 
76
77
  class Negation(UnaryOpParams):
78
+ bypass_bounds_validation: bool = pydantic.Field(
79
+ default=False
80
+ ) # True for efficient subtraction
77
81
  output_name = "negated"
78
82
 
79
83
  @staticmethod
@@ -88,11 +92,19 @@ class Negation(UnaryOpParams):
88
92
  eff_arg = self.arg.limit_fraction_places(self.machine_precision)
89
93
  is_signed = max(eff_arg.bounds) > 0 and self._include_sign
90
94
  bounds = (-max(eff_arg.bounds), -min(eff_arg.bounds))
95
+ if self.output_size and not self.bypass_bounds_validation:
96
+ if eff_arg.fraction_places:
97
+ raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
98
+ max_bounds = RegisterArithmeticInfo.get_maximal_bounds(
99
+ size=self.output_size, is_signed=False, fraction_places=0
100
+ )
101
+ bounds = number_utils.bounds_cut(bounds, max_bounds)
91
102
  return RegisterArithmeticInfo(
92
103
  size=self.output_size or self._expected_result_size(eff_arg),
93
104
  fraction_places=eff_arg.fraction_places,
94
105
  is_signed=is_signed,
95
- bounds=bounds if (is_signed or min(bounds) >= 0) else None,
106
+ bypass_bounds_validation=self.bypass_bounds_validation,
107
+ bounds=bounds,
96
108
  )
97
109
 
98
110
  def zero_input_for_extension(self) -> pydantic.NonNegativeInt:
@@ -1,4 +1,5 @@
1
- from typing import Dict, List, Literal, Optional, Tuple, Union
1
+ import logging
2
+ from typing import Any, Dict, List, Literal, Optional, Tuple, Union
2
3
 
3
4
  import pydantic
4
5
  from typing_extensions import TypeAlias
@@ -10,10 +11,10 @@ from classiq.interface.generator.synthesis_metadata.synthesis_execution_data imp
10
11
  )
11
12
  from classiq.interface.ide.visual_model import OperationParameter
12
13
 
14
+ _logger = logging.getLogger(__name__)
13
15
  ParameterName = str
14
16
  IOQubitMapping: TypeAlias = Dict[str, Tuple[int, ...]]
15
17
 
16
-
17
18
  CLASSIQ_HIERARCHY_SEPARATOR: Literal["."] = "."
18
19
 
19
20
  VISUALIZATION_HIDE_LIST = [
@@ -102,6 +103,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
102
103
  absolute_qubits: Optional[Tuple[int, ...]]
103
104
  is_basis_gate: Optional[bool]
104
105
  parameters: List[OperationParameter] = list()
106
+ port_to_passed_variable_map: Dict[str, str] = pydantic.Field(default_factory=dict)
105
107
 
106
108
  @property
107
109
  def registers(self) -> List[GeneratedRegister]:
@@ -120,3 +122,46 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
120
122
  if self.generated_function is None:
121
123
  return list()
122
124
  return self.generated_function.control_states
125
+
126
+ @staticmethod
127
+ def create_parameters_from_dict(
128
+ parameters: Dict[str, str]
129
+ ) -> List[OperationParameter]:
130
+ return [
131
+ OperationParameter(label=key, value=value)
132
+ for key, value in parameters.items()
133
+ ]
134
+
135
+ def update(self, **kwargs: Any) -> None:
136
+ for key, value in kwargs.items():
137
+ setattr(self, key, value)
138
+
139
+ def propagate_absolute_qubits(self) -> None:
140
+ if self.absolute_qubits is None:
141
+ return
142
+
143
+ for register in self.registers:
144
+ register.qubit_indexes_absolute = list(
145
+ _get_absolute_from_relative(
146
+ self.absolute_qubits, tuple(register.qubit_indexes_relative)
147
+ )
148
+ )
149
+
150
+ for child in self.children:
151
+ child.absolute_qubits = _get_absolute_from_relative(
152
+ self.absolute_qubits, child.relative_qubits
153
+ )
154
+ child.propagate_absolute_qubits()
155
+
156
+
157
+ def _get_absolute_from_relative(
158
+ absolute_qubits: Tuple[int, ...], relative_qubits: Tuple[int, ...]
159
+ ) -> Tuple[int, ...]:
160
+ if max(relative_qubits) >= len(absolute_qubits):
161
+ _logger.warning(
162
+ "Invalid qubit computation (relative qubits: %s, absolute qubits: %s)",
163
+ relative_qubits,
164
+ absolute_qubits,
165
+ )
166
+ return tuple()
167
+ return tuple(absolute_qubits[relative_qubit] for relative_qubit in relative_qubits)
@@ -1,5 +1,5 @@
1
1
  import uuid
2
- from datetime import datetime
2
+ from datetime import datetime, timezone
3
3
  from pathlib import Path
4
4
  from typing import Dict, List, Optional, Tuple, Union
5
5
 
@@ -10,6 +10,7 @@ from classiq.interface.exceptions import (
10
10
  ClassiqMissingOutputFormatError,
11
11
  ClassiqStateInitializationError,
12
12
  )
13
+ from classiq.interface.execution.primitives import PrimitivesInput
13
14
  from classiq.interface.executor import quantum_code
14
15
  from classiq.interface.executor.quantum_instruction_set import QuantumInstructionSet
15
16
  from classiq.interface.executor.register_initialization import RegisterInitialization
@@ -50,16 +51,23 @@ def get_uuid_as_str() -> str:
50
51
  return str(uuid.uuid4())
51
52
 
52
53
 
54
+ def _get_formatted_utc_current_time() -> str:
55
+ # The purpose of this method is to replicate the behavior of
56
+ # datetime.utcnow().isoformat(), since `utcnow` is now deprecated
57
+ return datetime.now(timezone.utc).isoformat().split("+")[0]
58
+
59
+
53
60
  class QuantumProgram(VersionedModel, CircuitCodeInterface):
54
61
  hardware_data: SynthesisHardwareData
55
62
  initial_values: Optional[InitialConditions]
56
63
  data: GeneratedCircuitData
57
64
  model: ExecutionModel
58
65
  transpiled_circuit: Optional[TranspiledCircuitData]
59
- creation_time: str = pydantic.Field(default_factory=datetime.utcnow().isoformat)
66
+ creation_time: str = pydantic.Field(default_factory=_get_formatted_utc_current_time)
60
67
  synthesis_duration: Optional[SynthesisStepDurations]
61
68
  debug_info: Optional[List[FunctionDebugInfoInterface]]
62
69
  program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
70
+ execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
63
71
 
64
72
  def _hardware_agnostic_program_code(self) -> CodeAndSyntax:
65
73
  circuit_code = self.program_circuit.get_code_by_priority()
@@ -1,4 +1,4 @@
1
- from typing import Dict, List, Optional, Tuple
1
+ from typing import Any, Dict, List, Optional, Tuple
2
2
 
3
3
  import pydantic
4
4
 
@@ -38,6 +38,7 @@ class OperationParameter(pydantic.BaseModel):
38
38
 
39
39
  class OperationLink(pydantic.BaseModel):
40
40
  label: str
41
+ inner_label: Optional[str] = None
41
42
  qubits: Tuple[int, ...]
42
43
  type: str
43
44
 
@@ -47,6 +48,11 @@ class OperationLink(pydantic.BaseModel):
47
48
  def __hash__(self) -> int:
48
49
  return hash((type(self), self.label, self.qubits, self.type))
49
50
 
51
+ def __eq__(self, other: Any) -> bool:
52
+ if not isinstance(other, OperationLink):
53
+ return False
54
+ return hash(self) == hash(other)
55
+
50
56
 
51
57
  class OperationLinks(pydantic.BaseModel):
52
58
  inputs: List[OperationLink]
@@ -1 +1 @@
1
- INTERFACE_VERSION = "2"
1
+ INTERFACE_VERSION = "3"
@@ -0,0 +1,11 @@
1
+ from typing import Literal
2
+
3
+ from classiq.interface.generator.expressions.expression import Expression
4
+ from classiq.interface.model.quantum_expressions.quantum_expression import (
5
+ QuantumExpressionOperation,
6
+ )
7
+
8
+
9
+ class PhaseOperation(QuantumExpressionOperation):
10
+ kind: Literal["PhaseOperation"]
11
+ theta: Expression
@@ -9,6 +9,7 @@ from classiq.interface.model.control import Control
9
9
  from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperation
10
10
  from classiq.interface.model.invert import Invert
11
11
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
12
+ from classiq.interface.model.phase_operation import PhaseOperation
12
13
  from classiq.interface.model.power import Power
13
14
  from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
14
15
  AmplitudeLoadingOperation,
@@ -38,6 +39,7 @@ ConcreteQuantumStatement = Annotated[
38
39
  ClassicalIf,
39
40
  Control,
40
41
  WithinApply,
42
+ PhaseOperation,
41
43
  ],
42
44
  Field(..., discriminator="kind"),
43
45
  ]
@@ -52,3 +54,4 @@ Invert.update_forward_refs(StatementBlock=StatementBlock)
52
54
  WithinApply.update_forward_refs(StatementBlock=StatementBlock)
53
55
  ClassicalIf.update_forward_refs(StatementBlock=StatementBlock)
54
56
  NativeFunctionDefinition.update_forward_refs(StatementBlock=StatementBlock)
57
+ PhaseOperation.update_forward_refs(StatementBlock=StatementBlock)
@@ -17,9 +17,6 @@ ANALYZER_HC_TABLE_GRAPH_FULL_PATH = ANALYZER_PREFIX + ANALYZER_HC_TABLE_GRAPH
17
17
 
18
18
  ANALYZER_HC_GRAPH_NEW = "/graphs/hardware_connectivity/new"
19
19
 
20
- ANALYZER_OPTIONAL_DEVICES = "/graphs/available_devices"
21
- ANALYZER_OPTIONAL_DEVICES_FULL_PATH = ANALYZER_PREFIX + ANALYZER_OPTIONAL_DEVICES
22
-
23
20
  TASKS_SUFFIX = "/tasks"
24
21
  RB = "/rb"
25
22
  ANALYZER_DATA_TASK = f"{TASKS_SUFFIX}/data"
@@ -24,6 +24,7 @@ from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperat
24
24
  from classiq.interface.model.invert import Invert
25
25
  from classiq.interface.model.model import Model
26
26
  from classiq.interface.model.native_function_definition import NativeFunctionDefinition
27
+ from classiq.interface.model.phase_operation import PhaseOperation
27
28
  from classiq.interface.model.power import Power
28
29
  from classiq.interface.model.quantum_expressions.quantum_expression import (
29
30
  QuantumAssignmentOperation,
@@ -74,6 +75,7 @@ from classiq.model_expansions.quantum_operations import (
74
75
  VariableDeclarationStatementEmitter,
75
76
  WithinApplyEmitter,
76
77
  )
78
+ from classiq.model_expansions.quantum_operations.phase import PhaseEmitter
77
79
  from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
78
80
  from classiq.model_expansions.scope_initialization import (
79
81
  add_entry_point_params_to_scope,
@@ -307,6 +309,10 @@ class Interpreter:
307
309
  def emit_power(self, power: Power) -> None:
308
310
  PowerEmitter(self).emit(power)
309
311
 
312
+ @emit.register
313
+ def emit_phase(self, phase: PhaseOperation) -> None:
314
+ PhaseEmitter(self).emit(phase)
315
+
310
316
  def _expand_block(self, block: Sequence[QuantumStatement], block_name: str) -> None:
311
317
  with self._builder.block_context(block_name):
312
318
  for statement in block:
@@ -131,16 +131,22 @@ class Emitter(Generic[QuantumStatementT]):
131
131
  or new_call.func_name == free.func_decl.name
132
132
  )
133
133
  parameters = {
134
- arg_decl.name: FunctionDebugInfo.param_controller(value=valuated_arg.value)
135
- for arg_decl, valuated_arg in zip(new_positional_arg_decls, evaluated_args)
134
+ arg_decl.name: FunctionDebugInfo.param_controller(value=evaluated_arg.value)
135
+ for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
136
136
  if isinstance(arg_decl, ClassicalParameterDeclaration)
137
137
  }
138
138
 
139
+ port_to_passed_variable_map = {
140
+ arg_decl.name: evaluated_arg.value.handle.name
141
+ for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
142
+ if isinstance(arg_decl, PortDeclaration)
143
+ }
139
144
  self._interpreter._model.debug_info[new_call.uuid] = FunctionDebugInfo(
140
145
  name=new_call.func_name,
141
146
  level=OperationLevel.QMOD_FUNCTION_CALL,
142
147
  parameters=parameters,
143
148
  is_allocate_or_free=is_allocate_or_free,
149
+ port_to_passed_variable_map=port_to_passed_variable_map,
144
150
  )
145
151
  if is_atomic:
146
152
  new_call.set_func_decl(new_declaration)
@@ -0,0 +1,177 @@
1
+ from typing import TYPE_CHECKING, Dict, List, Tuple
2
+
3
+ import sympy
4
+ from sympy import sympify
5
+
6
+ from classiq.interface.exceptions import ClassiqExpansionError
7
+ from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.model.bind_operation import BindOperation
9
+ from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
10
+ from classiq.interface.model.phase_operation import PhaseOperation
11
+ from classiq.interface.model.quantum_function_call import QuantumFunctionCall
12
+ from classiq.interface.model.quantum_type import (
13
+ QuantumBitvector,
14
+ QuantumNumeric,
15
+ QuantumScalar,
16
+ )
17
+ from classiq.interface.model.variable_declaration_statement import (
18
+ VariableDeclarationStatement,
19
+ )
20
+ from classiq.interface.model.within_apply_operation import WithinApply
21
+
22
+ from classiq.applications.combinatorial_helpers.transformations.ising_converter import (
23
+ _find_sub_list_items,
24
+ _get_coeff_from_expr,
25
+ _get_vars,
26
+ _refine_ising_expr,
27
+ _to_ising_symbolic_objective_function,
28
+ )
29
+ from classiq.model_expansions.quantum_operations.expression_operation import (
30
+ ExpressionOperationEmitter,
31
+ )
32
+ from classiq.qmod.builtins.functions.exponentiation import suzuki_trotter
33
+ from classiq.qmod.semantics.error_manager import ErrorManager
34
+
35
+
36
+ class PhaseEmitter(ExpressionOperationEmitter[PhaseOperation]):
37
+ def emit(self, phase_op: PhaseOperation, /) -> None:
38
+ phase_expression = self._evaluate_op_expression(phase_op)
39
+ phase_op = phase_op.copy(update=dict(expression=phase_expression))
40
+ arrays_with_subscript = self._get_symbols_to_split(phase_op.expression)
41
+ if len(arrays_with_subscript) > 0:
42
+ self._emit_with_split(phase_op, phase_op.expression, arrays_with_subscript)
43
+ return
44
+
45
+ phase_op = self._evaluate_types_in_expression(phase_op, phase_op.expression)
46
+ if len(phase_op.var_handles) == 0:
47
+ ErrorManager().add_error(
48
+ "Cannot perform phase operation on an expression with no quantum variables."
49
+ )
50
+ return
51
+
52
+ aux_name = self._counted_name_allocator.allocate("phase_aux")
53
+ if len(phase_op.var_handles) > 1:
54
+ split_join = True
55
+ evolution_variable = HandleBinding(name=aux_name)
56
+ else:
57
+ split_join = False
58
+ evolution_variable = phase_op.var_handles[0]
59
+ expression = self._evaluate_op_expression(phase_op)
60
+ expression_evolution_function = QuantumFunctionCall(
61
+ function=suzuki_trotter.func_decl.name,
62
+ positional_args=[
63
+ _convert_cost_expression_to_hamiltonian(
64
+ expression.expr,
65
+ {
66
+ var.name: self._current_scope[var.name].value.quantum_type
67
+ for var in phase_op.var_handles
68
+ },
69
+ ),
70
+ phase_op.theta,
71
+ Expression(expr="1"),
72
+ Expression(expr="1"),
73
+ evolution_variable,
74
+ ],
75
+ source_ref=phase_op.source_ref,
76
+ )
77
+ expression_evolution_function.set_func_decl(suzuki_trotter.func_decl)
78
+
79
+ if split_join:
80
+ self._interpreter.emit_statement(
81
+ VariableDeclarationStatement(
82
+ name=aux_name, quantum_type=QuantumBitvector()
83
+ )
84
+ )
85
+ self._interpreter.emit_statement(
86
+ WithinApply(
87
+ compute=[
88
+ BindOperation(
89
+ in_handles=phase_op.var_handles,
90
+ out_handles=[HandleBinding(name=aux_name)],
91
+ )
92
+ ],
93
+ action=[expression_evolution_function],
94
+ source_ref=phase_op.source_ref,
95
+ )
96
+ )
97
+ else:
98
+ self._interpreter.emit_statement(
99
+ expression_evolution_function,
100
+ )
101
+
102
+
103
+ def _get_single_bit_vars_expression(
104
+ expr: sympy.Expr, vars_info: Dict[str, QuantumScalar]
105
+ ) -> Tuple[sympy.Expr, List[sympy.Symbol]]:
106
+ bit_vars = []
107
+ for var_name, var_info in vars_info.items():
108
+ size = var_info.size_in_bits
109
+ var = sympy.Symbol(var_name)
110
+ if size == 1:
111
+ bits = [var]
112
+ is_signed = False
113
+ fraction_places = 0
114
+ else:
115
+ if TYPE_CHECKING:
116
+ assert isinstance(var_info, QuantumNumeric)
117
+ bits = [
118
+ sympy.Symbol(f"{var_name}{HANDLE_ID_SEPARATOR}{i}__split__")
119
+ for i in range(size)
120
+ ]
121
+ is_signed = var_info.sign_value
122
+ fraction_places = var_info.fraction_digits_value
123
+ bit_vars.extend(bits)
124
+ split_var = 0
125
+ for i, bit in enumerate(bits):
126
+ if is_signed and i == size - 1: # sign bit (MSB)
127
+ split_var -= bit * 2 ** (size - 1 - fraction_places)
128
+ else:
129
+ split_var += bit * 2 ** (i - fraction_places)
130
+ expr = expr.subs(var, split_var)
131
+ return expr, bit_vars
132
+
133
+
134
+ def _convert_ising_sympy_to_pauli_terms(
135
+ ising_expr: sympy.Expr, ordered_sympy_vars: List[sympy.Symbol]
136
+ ) -> str:
137
+ pauli_terms: List[str] = []
138
+ for expr_term in ising_expr.args:
139
+ expr_vars = _get_vars(expr_term)
140
+ z_vec = _find_sub_list_items(ordered_sympy_vars, expr_vars)
141
+ pauli_elements = ["I"] * len(z_vec)
142
+ for index, is_z_op in enumerate(z_vec):
143
+ if is_z_op:
144
+ pauli_elements[len(z_vec) - index - 1] = (
145
+ "Z" # reminder: Pauli reverses the order!
146
+ )
147
+ coeff = _get_coeff_from_expr(expr_term)
148
+ paulis = [f"Pauli.{pauli}" for pauli in pauli_elements]
149
+ pauli_terms.append(
150
+ # fmt: off
151
+ "struct_literal("
152
+ "PauliTerm,"
153
+ f"pauli=[{', '.join(paulis)}],"
154
+ f"coefficient={Expression(expr=str(coeff))},"
155
+ ")"
156
+ # fmt: on,
157
+ )
158
+ return f"[{', '.join(pauli_terms)}]"
159
+
160
+
161
+ def _convert_cost_expression_to_hamiltonian(
162
+ expr: str,
163
+ vars: Dict[str, QuantumScalar],
164
+ ) -> Expression:
165
+ sympy_expr = sympify(expr)
166
+ single_bit_vars_expression, single_bit_vars = _get_single_bit_vars_expression(
167
+ sympy_expr, vars
168
+ )
169
+ if not single_bit_vars_expression.is_polynomial():
170
+ raise ClassiqExpansionError(f"phased expression {expr!r} is not polynomial")
171
+
172
+ ising_expr = _to_ising_symbolic_objective_function(single_bit_vars_expression)
173
+ ising_expr = _refine_ising_expr(ising_expr)
174
+
175
+ return Expression(
176
+ expr=_convert_ising_sympy_to_pauli_terms(ising_expr, single_bit_vars)
177
+ )
@@ -12,7 +12,8 @@ def amplitude_estimation(
12
12
  ) -> None:
13
13
  """
14
14
  [Qmod Classiq-library function]
15
- Amplitude estimation function is used to estimate the probability of a state being marked by the operand `oracle` as a "good state."
15
+
16
+ Estimate the probability of a state being marked by the operand `oracle` as a "good state."
16
17
 
17
18
  The algorithm prepares the state in the `packed_vars` register and estimates the probability of this state being marked by the oracle as a "good state."
18
19
  This is done using the Quantum Phase Estimation (QPE) algorithm, where the unitary for QPE is the Grover operator, which is composed of the `oracle` and `space_transform` operators.
@@ -14,7 +14,7 @@ def unitary(
14
14
  """
15
15
  [Qmod core-library function]
16
16
 
17
- Apply a unitary matrix on a quantum state.
17
+ Applies a unitary matrix on a quantum state.
18
18
 
19
19
  Args:
20
20
  elements: A 2d array of complex numbers representing the unitary matrix. This matrix must be unitary.
@@ -51,7 +51,7 @@ def integer_xor(left: QArray[QBit], right: QArray[QBit]) -> None:
51
51
  @qfunc(external=True)
52
52
  def modular_increment(a: CInt, x: QNum) -> None:
53
53
  """
54
- Qmod Classiq-library function.
54
+ [Qmod Classiq-library function]
55
55
 
56
56
  Adds $a$ to $x$ modulo the range of $x$, assumed that $x$ is a non-negative integer and $a$ is an integer.
57
57
  Mathematically it is described as:
@@ -14,22 +14,22 @@ def _qct_pi_operator(x: QArray[QBit], q: QBit) -> None:
14
14
 
15
15
  @qfunc(external=True)
16
16
  def qct_qst_type1(x: QArray[QBit]) -> None:
17
- r"""
18
- Qmod Classiq-library function.
17
+ """
18
+ [Qmod Classiq-library function]
19
19
 
20
20
  Applies the quantum discrete cosine (DCT) and sine (DST)
21
21
  transform of type 1 to the qubit array `x`.
22
- Corresponds to the matrix (with $n\equiv$`x.len`):
22
+ Corresponds to the matrix (with $n\\equiv$`x.len`):
23
23
 
24
24
  $$
25
- \left(
25
+ \\left(
26
26
  \begin{array}{ccc|c}
27
27
  {} &{} &{} \\
28
28
  {}&{\rm DCT}^{(1)}(2^{n-1}+1) & {}& 0\\
29
29
  {} &{} &{} \\
30
- \hline
30
+ \\hline
31
31
  {} & 0 & {} & i{\rm DST}^{(1)}(2^{n-1}-1)
32
- \end{array}
32
+ \\end{array}
33
33
  \right)
34
34
  $$
35
35
 
@@ -44,20 +44,20 @@ def qct_qst_type1(x: QArray[QBit]) -> None:
44
44
 
45
45
  @qfunc(external=True)
46
46
  def qct_qst_type2(x: QArray[QBit], q: QBit) -> None:
47
- r"""
48
- Qmod Classiq-library function.
47
+ """
48
+ [Qmod Classiq-library function]
49
49
 
50
50
  Applies the quantum discrete cosine (DCT) and sine (DST)
51
51
  transform of type 2 to the qubit array `x` concatenated with `q`, with `q` being the MSB.
52
- Corresponds to the matrix (with $n\equiv$`x.len`+1):
52
+ Corresponds to the matrix (with $n\\equiv$`x.len`+1):
53
53
 
54
54
  $$
55
- \left(
55
+ \\left(
56
56
  \begin{array}{c|c}
57
57
  {\rm DCT}^{(2)}(2^{n-1}) & 0\\
58
- \hline
58
+ \\hline
59
59
  0 & -{\rm DST}^{(2)}(2^{n-1})
60
- \end{array}
60
+ \\end{array}
61
61
  \right)
62
62
  $$
63
63
 
@@ -73,8 +73,8 @@ def qct_qst_type2(x: QArray[QBit], q: QBit) -> None:
73
73
 
74
74
  @qfunc(external=True)
75
75
  def qct_type2(x: QArray[QBit]) -> None:
76
- r"""
77
- Qmod Classiq-library function.
76
+ """
77
+ [Qmod Classiq-library function]
78
78
 
79
79
  Applies the quantum discrete cosine (DCT)
80
80
  transform of type 2, ${\rm DCT}^{(2)}$, to the qubit array `x`.
@@ -90,8 +90,8 @@ def qct_type2(x: QArray[QBit]) -> None:
90
90
 
91
91
  @qfunc(external=True)
92
92
  def qst_type2(x: QArray[QBit]) -> None:
93
- r"""
94
- Qmod Classiq-library function.
93
+ """
94
+ [Qmod Classiq-library function]
95
95
 
96
96
  Applies the quantum discrete sine (DST)
97
97
  transform of type 2, ${\rm DST}^{(2)}$, to the qubit array `x`.