classiq 0.86.1__py3-none-any.whl → 0.88.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.

Potentially problematic release.


This version of classiq might be problematic. Click here for more details.

Files changed (131) hide show
  1. classiq/__init__.py +2 -0
  2. classiq/applications/__init__.py +1 -2
  3. classiq/applications/chemistry/hartree_fock.py +5 -1
  4. classiq/applications/chemistry/op_utils.py +2 -2
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  6. classiq/applications/combinatorial_helpers/encoding_mapping.py +11 -15
  7. classiq/applications/combinatorial_helpers/encoding_utils.py +6 -6
  8. classiq/applications/combinatorial_helpers/memory.py +4 -4
  9. classiq/applications/combinatorial_helpers/optimization_model.py +5 -5
  10. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +6 -10
  11. classiq/applications/combinatorial_helpers/pyomo_utils.py +27 -26
  12. classiq/applications/combinatorial_helpers/sympy_utils.py +2 -2
  13. classiq/applications/combinatorial_helpers/transformations/encoding.py +4 -6
  14. classiq/applications/combinatorial_helpers/transformations/fixed_variables.py +4 -4
  15. classiq/applications/combinatorial_helpers/transformations/ising_converter.py +2 -2
  16. classiq/applications/combinatorial_helpers/transformations/penalty_support.py +3 -3
  17. classiq/applications/combinatorial_optimization/combinatorial_problem.py +4 -0
  18. classiq/applications/hamiltonian/pauli_decomposition.py +34 -2
  19. classiq/evaluators/argument_types.py +15 -6
  20. classiq/evaluators/parameter_types.py +43 -39
  21. classiq/evaluators/qmod_annotated_expression.py +117 -17
  22. classiq/evaluators/qmod_expression_visitors/out_of_place_node_transformer.py +19 -0
  23. classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +0 -5
  24. classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +66 -16
  25. classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +48 -26
  26. classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +65 -72
  27. classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +13 -6
  28. classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +175 -28
  29. classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +36 -19
  30. classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +17 -5
  31. classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +24 -2
  32. classiq/evaluators/qmod_node_evaluators/min_max_evaluation.py +97 -0
  33. classiq/evaluators/qmod_node_evaluators/name_evaluation.py +11 -26
  34. classiq/evaluators/qmod_node_evaluators/numeric_attrs_utils.py +56 -0
  35. classiq/evaluators/qmod_node_evaluators/piecewise_evaluation.py +40 -0
  36. classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +3 -4
  37. classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +51 -24
  38. classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +53 -9
  39. classiq/evaluators/qmod_node_evaluators/utils.py +28 -6
  40. classiq/evaluators/qmod_type_inference/classical_type_inference.py +188 -0
  41. classiq/evaluators/qmod_type_inference/quantum_type_inference.py +330 -0
  42. classiq/evaluators/quantum_type_utils.py +0 -131
  43. classiq/evaluators/type_type_match.py +1 -1
  44. classiq/execution/execution_session.py +18 -3
  45. classiq/execution/qnn.py +4 -1
  46. classiq/execution/user_budgets.py +1 -1
  47. classiq/interface/_version.py +1 -1
  48. classiq/interface/backend/backend_preferences.py +10 -30
  49. classiq/interface/backend/quantum_backend_providers.py +63 -52
  50. classiq/interface/execution/primitives.py +1 -0
  51. classiq/interface/generator/application_apis/__init__.py +0 -1
  52. classiq/interface/generator/arith/binary_ops.py +107 -115
  53. classiq/interface/generator/arith/extremum_operations.py +33 -45
  54. classiq/interface/generator/arith/number_utils.py +4 -1
  55. classiq/interface/generator/circuit_code/types_and_constants.py +0 -9
  56. classiq/interface/generator/compiler_keywords.py +2 -0
  57. classiq/interface/generator/expressions/atomic_expression_functions.py +0 -2
  58. classiq/interface/generator/function_param_list.py +129 -5
  59. classiq/interface/generator/functions/classical_type.py +67 -2
  60. classiq/interface/generator/functions/qmod_python_interface.py +15 -0
  61. classiq/interface/generator/functions/type_name.py +12 -0
  62. classiq/interface/generator/model/preferences/preferences.py +1 -17
  63. classiq/interface/generator/quantum_program.py +1 -13
  64. classiq/interface/generator/transpiler_basis_gates.py +5 -1
  65. classiq/interface/generator/types/builtin_enum_declarations.py +0 -8
  66. classiq/interface/helpers/model_normalizer.py +2 -2
  67. classiq/interface/helpers/text_utils.py +7 -2
  68. classiq/interface/interface_version.py +1 -1
  69. classiq/interface/model/classical_if.py +48 -0
  70. classiq/interface/model/classical_parameter_declaration.py +4 -0
  71. classiq/interface/model/handle_binding.py +28 -16
  72. classiq/interface/model/port_declaration.py +12 -0
  73. classiq/interface/model/quantum_function_declaration.py +12 -0
  74. classiq/interface/model/quantum_type.py +117 -2
  75. classiq/interface/pretty_print/expression_to_qmod.py +7 -8
  76. classiq/interface/pyomo_extension/__init__.py +0 -4
  77. classiq/interface/pyomo_extension/pyomo_sympy_bimap.py +2 -2
  78. classiq/model_expansions/arithmetic.py +43 -1
  79. classiq/model_expansions/arithmetic_compute_result_attrs.py +255 -0
  80. classiq/model_expansions/capturing/captured_vars.py +2 -5
  81. classiq/model_expansions/quantum_operations/allocate.py +23 -16
  82. classiq/model_expansions/quantum_operations/arithmetic/explicit_boolean_expressions.py +1 -3
  83. classiq/model_expansions/quantum_operations/assignment_result_processor.py +52 -71
  84. classiq/model_expansions/quantum_operations/bind.py +15 -7
  85. classiq/model_expansions/quantum_operations/call_emitter.py +2 -10
  86. classiq/model_expansions/quantum_operations/classical_var_emitter.py +6 -0
  87. classiq/model_expansions/quantum_operations/handle_evaluator.py +2 -8
  88. classiq/open_library/functions/__init__.py +4 -0
  89. classiq/open_library/functions/lcu.py +117 -0
  90. classiq/open_library/functions/state_preparation.py +47 -5
  91. classiq/qmod/builtins/__init__.py +0 -3
  92. classiq/qmod/builtins/classical_functions.py +0 -28
  93. classiq/qmod/builtins/enums.py +26 -20
  94. classiq/qmod/builtins/functions/__init__.py +0 -5
  95. classiq/qmod/builtins/operations.py +142 -0
  96. classiq/qmod/builtins/structs.py +33 -29
  97. classiq/qmod/native/pretty_printer.py +1 -1
  98. classiq/qmod/pretty_print/expression_to_python.py +1 -6
  99. classiq/qmod/pretty_print/pretty_printer.py +4 -1
  100. classiq/qmod/qmod_variable.py +94 -2
  101. classiq/qmod/semantics/annotation/call_annotation.py +4 -2
  102. classiq/qmod/semantics/annotation/qstruct_annotator.py +20 -5
  103. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/METADATA +5 -5
  104. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/RECORD +106 -124
  105. classiq/applications/finance/__init__.py +0 -15
  106. classiq/interface/finance/finance_modelling_params.py +0 -11
  107. classiq/interface/finance/function_input.py +0 -102
  108. classiq/interface/finance/gaussian_model_input.py +0 -50
  109. classiq/interface/finance/log_normal_model_input.py +0 -40
  110. classiq/interface/finance/model_input.py +0 -22
  111. classiq/interface/generator/amplitude_estimation.py +0 -34
  112. classiq/interface/generator/application_apis/finance_declarations.py +0 -108
  113. classiq/interface/generator/expressions/enums/__init__.py +0 -0
  114. classiq/interface/generator/expressions/enums/finance_functions.py +0 -12
  115. classiq/interface/generator/finance.py +0 -107
  116. classiq/interface/generator/function_param_list_without_self_reference.py +0 -160
  117. classiq/interface/generator/grover_diffuser.py +0 -93
  118. classiq/interface/generator/grover_operator.py +0 -106
  119. classiq/interface/generator/oracles/__init__.py +0 -3
  120. classiq/interface/generator/oracles/arithmetic_oracle.py +0 -82
  121. classiq/interface/generator/oracles/custom_oracle.py +0 -65
  122. classiq/interface/generator/oracles/oracle_abc.py +0 -76
  123. classiq/interface/generator/oracles/oracle_function_param_list.py +0 -6
  124. classiq/interface/generator/piecewise_linear_amplitude_loading.py +0 -165
  125. classiq/interface/generator/qpe.py +0 -169
  126. classiq/interface/grover/__init__.py +0 -0
  127. classiq/interface/grover/grover_modelling_params.py +0 -13
  128. classiq/model_expansions/transformers/var_splitter.py +0 -224
  129. classiq/qmod/builtins/functions/finance.py +0 -34
  130. /classiq/{interface/finance → evaluators/qmod_type_inference}/__init__.py +0 -0
  131. {classiq-0.86.1.dist-info → classiq-0.88.0.dist-info}/WHEEL +0 -0
@@ -1,5 +1,5 @@
1
1
  import ast
2
- from typing import Any
2
+ from typing import TYPE_CHECKING, Any
3
3
 
4
4
  from classiq.interface.exceptions import (
5
5
  ClassiqExpansionError,
@@ -7,10 +7,23 @@ from classiq.interface.exceptions import (
7
7
  )
8
8
  from classiq.interface.generator.functions.classical_type import Integer, Real
9
9
  from classiq.interface.generator.functions.type_name import TypeName
10
+ from classiq.interface.model.quantum_type import (
11
+ QuantumNumeric,
12
+ QuantumScalar,
13
+ )
10
14
 
11
15
  from classiq.evaluators.qmod_annotated_expression import QmodAnnotatedExpression
12
16
  from classiq.evaluators.qmod_node_evaluators.bool_op_evaluation import is_bool_type
13
- from classiq.evaluators.qmod_node_evaluators.utils import QmodType, is_numeric_type
17
+ from classiq.evaluators.qmod_node_evaluators.utils import (
18
+ QmodType,
19
+ is_classical_type,
20
+ is_numeric_type,
21
+ )
22
+ from classiq.model_expansions.arithmetic import NumericAttributes
23
+ from classiq.model_expansions.arithmetic_compute_result_attrs import (
24
+ compute_result_attrs_bitwise_invert,
25
+ compute_result_attrs_negate,
26
+ )
14
27
 
15
28
 
16
29
  def unary_op_allowed(op: ast.AST, operand_type: QmodType) -> bool:
@@ -19,7 +32,42 @@ def unary_op_allowed(op: ast.AST, operand_type: QmodType) -> bool:
19
32
  return is_numeric_type(operand_type)
20
33
 
21
34
 
22
- def eval_unary_op(expr_val: QmodAnnotatedExpression, node: ast.UnaryOp) -> None:
35
+ def _infer_unary_op_type(
36
+ op: ast.AST, operand_type: QmodType, machine_precision: int
37
+ ) -> QmodType:
38
+ if is_classical_type(operand_type):
39
+ if isinstance(operand_type, TypeName):
40
+ # The operand is enum, return as integer
41
+ return Integer()
42
+ return operand_type
43
+
44
+ if TYPE_CHECKING:
45
+ assert isinstance(operand_type, QuantumScalar)
46
+
47
+ if isinstance(op, (ast.Not, ast.UAdd)):
48
+ return operand_type
49
+
50
+ if not operand_type.is_evaluated:
51
+ return QuantumNumeric()
52
+
53
+ operand_attrs = NumericAttributes.from_quantum_scalar(
54
+ operand_type, machine_precision
55
+ )
56
+ if isinstance(op, ast.Invert):
57
+ result_attrs = compute_result_attrs_bitwise_invert(
58
+ operand_attrs, machine_precision
59
+ )
60
+ elif isinstance(op, ast.USub):
61
+ result_attrs = compute_result_attrs_negate(operand_attrs, machine_precision)
62
+ else:
63
+ raise ClassiqInternalExpansionError
64
+
65
+ return result_attrs.to_quantum_numeric()
66
+
67
+
68
+ def eval_unary_op(
69
+ expr_val: QmodAnnotatedExpression, node: ast.UnaryOp, machine_precision: int
70
+ ) -> None:
23
71
  operand = node.operand
24
72
  op = node.op
25
73
 
@@ -34,12 +82,8 @@ def eval_unary_op(expr_val: QmodAnnotatedExpression, node: ast.UnaryOp) -> None:
34
82
  raise ClassiqExpansionError(
35
83
  f"Operation {type(op).__name__!r} on a real value is not supported"
36
84
  )
37
- op_type: QmodType
38
- if isinstance(operand_type, TypeName):
39
- op_type = Integer()
40
- else:
41
- op_type = operand_type
42
- expr_val.set_type(node, op_type)
85
+
86
+ expr_val.set_type(node, _infer_unary_op_type(op, operand_type, machine_precision))
43
87
 
44
88
  if not expr_val.has_value(operand):
45
89
  return
@@ -1,7 +1,8 @@
1
- from typing import Any, Optional, Union, cast
1
+ from typing import Optional, Union, cast
2
2
 
3
3
  import sympy
4
4
 
5
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
5
6
  from classiq.interface.generator.functions.classical_type import (
6
7
  ClassicalArray,
7
8
  ClassicalTuple,
@@ -24,10 +25,6 @@ NumberValueType = Union[IntegerValueType, RealValueType]
24
25
  SYMPY_SYMBOLS = {sym: getattr(sympy, sym) for sym in sympy.__all__}
25
26
 
26
27
 
27
- def get_qmod_type_name(type_: Any) -> str:
28
- return getattr(type_, "type_name", type(type_).__name__)
29
-
30
-
31
28
  def is_classical_type(qmod_type: QmodType) -> bool:
32
29
  if isinstance(qmod_type, TypeName):
33
30
  return qmod_type.has_classical_struct_decl or qmod_type.is_enum
@@ -42,6 +39,31 @@ def qnum_is_qbit(qmod_type: QuantumNumeric) -> bool:
42
39
  )
43
40
 
44
41
 
42
+ def get_numeric_properties(
43
+ qmod_type: QuantumScalar,
44
+ ) -> tuple[Optional[int], Optional[bool], Optional[int]]:
45
+ if isinstance(qmod_type, QuantumBit):
46
+ return 1, False, 0
47
+ if not isinstance(qmod_type, QuantumNumeric):
48
+ raise ClassiqInternalExpansionError
49
+ size = qmod_type.size_in_bits if qmod_type.has_size_in_bits else None
50
+ is_signed_expr = qmod_type.is_signed
51
+ if is_signed_expr is None:
52
+ is_signed = False
53
+ elif is_signed_expr.is_evaluated() and is_signed_expr.is_constant():
54
+ is_signed = is_signed_expr.to_bool_value()
55
+ else:
56
+ is_signed = None
57
+ fraction_digits_expr = qmod_type.fraction_digits
58
+ if fraction_digits_expr is None:
59
+ fraction_digits = 0
60
+ elif fraction_digits_expr.is_evaluated() and fraction_digits_expr.is_constant():
61
+ fraction_digits = fraction_digits_expr.to_int_value()
62
+ else:
63
+ fraction_digits = None
64
+ return size, is_signed, fraction_digits
65
+
66
+
45
67
  def qnum_is_qint(qmod_type: QuantumType) -> bool:
46
68
  return isinstance(qmod_type, QuantumBit) or (
47
69
  isinstance(qmod_type, QuantumNumeric)
@@ -63,7 +85,7 @@ def array_len(
63
85
  ) -> Optional[int]:
64
86
  if isinstance(classical_type, ClassicalTuple):
65
87
  return len(classical_type.element_types)
66
- if classical_type.has_length:
88
+ if classical_type.has_constant_length:
67
89
  return classical_type.length_value
68
90
  return None
69
91
 
@@ -0,0 +1,188 @@
1
+ from enum import IntEnum
2
+ from typing import Any, Optional, Union
3
+
4
+ import sympy
5
+
6
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
7
+ from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.generator.expressions.proxies.classical.qmod_struct_instance import (
9
+ QmodStructInstance,
10
+ )
11
+ from classiq.interface.generator.functions.classical_type import (
12
+ Bool,
13
+ ClassicalArray,
14
+ ClassicalTuple,
15
+ ClassicalType,
16
+ Integer,
17
+ Real,
18
+ )
19
+ from classiq.interface.generator.functions.type_name import Enum, Struct, TypeName
20
+ from classiq.interface.generator.types.struct_declaration import StructDeclaration
21
+ from classiq.interface.helpers.backward_compatibility import zip_strict
22
+
23
+
24
+ def _copy_generative_flag(
25
+ from_type: ClassicalType, to_type: Optional[ClassicalType]
26
+ ) -> Optional[ClassicalType]:
27
+ if to_type is None:
28
+ return None
29
+ if from_type.is_generative:
30
+ return to_type.set_generative()
31
+ return to_type
32
+
33
+
34
+ def infer_classical_type(value: Any) -> ClassicalType:
35
+ if isinstance(value, IntEnum):
36
+ return Enum(name=type(value).__name__)
37
+ if isinstance(value, bool):
38
+ return Bool()
39
+ if isinstance(value, int):
40
+ return Integer()
41
+ if isinstance(value, (float, complex)):
42
+ return Real()
43
+ if isinstance(value, list):
44
+ return ClassicalTuple(
45
+ element_types=[infer_classical_type(item) for item in value]
46
+ )
47
+ if isinstance(value, QmodStructInstance):
48
+ struct_name = value.struct_declaration.name
49
+ classical_type = Struct(name=struct_name)
50
+ classical_type.set_classical_struct_decl(
51
+ StructDeclaration(
52
+ name=struct_name,
53
+ variables={
54
+ field_name: infer_classical_type(field_value)
55
+ for field_name, field_value in value.fields.items()
56
+ },
57
+ )
58
+ )
59
+ return classical_type
60
+ # FIXME: Remove sympy compatibility (CLS-3214)
61
+ if (
62
+ isinstance(value, sympy.Basic)
63
+ and len(value.free_symbols) == 0
64
+ and value.is_complex
65
+ ):
66
+ return Real()
67
+ raise ClassiqInternalExpansionError
68
+
69
+
70
+ def _inject_classical_array_attributes(
71
+ from_type: ClassicalType, to_type: Union[ClassicalArray, ClassicalTuple]
72
+ ) -> Optional[ClassicalType]:
73
+ if isinstance(to_type, ClassicalArray):
74
+ if isinstance(from_type, ClassicalArray):
75
+ length: Optional[Expression]
76
+ if from_type.has_constant_length:
77
+ if (
78
+ to_type.has_constant_length
79
+ and from_type.length_value != to_type.length_value
80
+ ):
81
+ return None
82
+ length = from_type.length
83
+ else:
84
+ length = to_type.length
85
+ element_type = inject_classical_type_attributes(
86
+ from_type.element_type, to_type.element_type
87
+ )
88
+ if element_type is None:
89
+ return None
90
+ return ClassicalArray(element_type=element_type, length=length)
91
+ if isinstance(from_type, ClassicalTuple):
92
+ if (
93
+ to_type.has_constant_length
94
+ and len(from_type.element_types) != to_type.length_value
95
+ ):
96
+ return None
97
+ element_types = [
98
+ inject_classical_type_attributes(element_type, to_type.element_type)
99
+ for element_type in from_type.element_types
100
+ ]
101
+ if None in element_types:
102
+ return None
103
+ return ClassicalTuple(element_types=element_types)
104
+ return None
105
+ if isinstance(from_type, ClassicalArray):
106
+ if from_type.has_constant_length and from_type.length_value != len(
107
+ to_type.element_types
108
+ ):
109
+ return None
110
+ element_types = [
111
+ inject_classical_type_attributes(from_type.element_type, element_type)
112
+ for element_type in to_type.element_types
113
+ ]
114
+ if None in element_types:
115
+ return None
116
+ return ClassicalTuple(element_types=element_types)
117
+ if isinstance(from_type, ClassicalTuple):
118
+ if len(from_type.element_types) != len(to_type.element_types):
119
+ return None
120
+ element_types = [
121
+ inject_classical_type_attributes(from_element_type, to_element_type)
122
+ for from_element_type, to_element_type in zip_strict(
123
+ from_type.element_types, to_type.element_types, strict=True
124
+ )
125
+ ]
126
+ if None in element_types:
127
+ return None
128
+ return ClassicalTuple(element_types=element_types)
129
+ return None
130
+
131
+
132
+ def _inject_classical_type_name_attributes(
133
+ from_type: ClassicalType, to_type: TypeName
134
+ ) -> Optional[ClassicalType]:
135
+ if to_type.is_enum:
136
+ if isinstance(from_type, Integer) or (
137
+ isinstance(from_type, TypeName) and from_type.name == to_type.name
138
+ ):
139
+ return Enum(name=to_type.name)
140
+ return None
141
+ if to_type.has_classical_struct_decl:
142
+ if not isinstance(from_type, TypeName) or from_type.name != to_type.name:
143
+ return None
144
+ classical_type = Struct(name=to_type.name)
145
+ field_types = {
146
+ field_name: inject_classical_type_attributes(
147
+ from_type.classical_struct_decl.variables[field_name],
148
+ to_type.classical_struct_decl.variables[field_name],
149
+ )
150
+ for field_name in to_type.classical_struct_decl.variables
151
+ }
152
+ if None in field_types.values():
153
+ return None
154
+ classical_type.set_classical_struct_decl(
155
+ StructDeclaration(name=to_type.name, variables=field_types)
156
+ )
157
+ return classical_type
158
+ return None
159
+
160
+
161
+ def inject_classical_type_attributes(
162
+ from_type: ClassicalType, to_type: ClassicalType
163
+ ) -> Optional[ClassicalType]:
164
+ if isinstance(to_type, Bool):
165
+ if isinstance(from_type, Bool):
166
+ return _copy_generative_flag(to_type, Bool())
167
+ return None
168
+ if isinstance(to_type, Integer):
169
+ if isinstance(from_type, (Integer, Real)) or (
170
+ isinstance(from_type, TypeName) and from_type.is_enum
171
+ ):
172
+ return _copy_generative_flag(to_type, Integer())
173
+ return None
174
+ if isinstance(to_type, Real):
175
+ if isinstance(from_type, (Integer, Real)) or (
176
+ isinstance(from_type, TypeName) and from_type.is_enum
177
+ ):
178
+ return _copy_generative_flag(to_type, Real())
179
+ return None
180
+ if isinstance(to_type, (ClassicalArray, ClassicalTuple)):
181
+ return _copy_generative_flag(
182
+ to_type, _inject_classical_array_attributes(from_type, to_type)
183
+ )
184
+ if isinstance(to_type, TypeName):
185
+ return _copy_generative_flag(
186
+ to_type, _inject_classical_type_name_attributes(from_type, to_type)
187
+ )
188
+ return None
@@ -0,0 +1,330 @@
1
+ from typing import TYPE_CHECKING, Optional
2
+
3
+ from classiq.interface.exceptions import (
4
+ ClassiqExpansionError,
5
+ ClassiqInternalExpansionError,
6
+ )
7
+ from classiq.interface.generator.expressions.expression import Expression
8
+ from classiq.interface.generator.functions.type_name import TypeName
9
+ from classiq.interface.model.quantum_type import (
10
+ QuantumBit,
11
+ QuantumBitvector,
12
+ QuantumNumeric,
13
+ QuantumType,
14
+ )
15
+
16
+ from classiq.evaluators.quantum_type_utils import set_bounds
17
+
18
+
19
+ def _normalized_qnum(quantum_type: QuantumType) -> QuantumType:
20
+ if not isinstance(quantum_type, QuantumNumeric):
21
+ return quantum_type
22
+ normalized_qnum = QuantumNumeric(
23
+ size=quantum_type.size,
24
+ is_signed=(
25
+ quantum_type.is_signed
26
+ if quantum_type.is_signed is not None
27
+ else Expression(expr="False")
28
+ ),
29
+ fraction_digits=(
30
+ quantum_type.fraction_digits
31
+ if quantum_type.fraction_digits is not None
32
+ else Expression(expr="0")
33
+ ),
34
+ )
35
+ normalized_qnum.set_bounds(quantum_type.get_bounds())
36
+ return normalized_qnum
37
+
38
+
39
+ def _same_shape(quantum_type_1: QuantumType, quantum_type_2: QuantumType) -> bool:
40
+ if isinstance(quantum_type_1, QuantumBit) and isinstance(
41
+ quantum_type_2, QuantumBit
42
+ ):
43
+ return True
44
+ if (
45
+ isinstance(quantum_type_1, QuantumNumeric)
46
+ and isinstance(quantum_type_2, QuantumNumeric)
47
+ and (
48
+ not quantum_type_1.has_size_in_bits
49
+ or not quantum_type_2.has_size_in_bits
50
+ or quantum_type_1.size_in_bits == quantum_type_2.size_in_bits
51
+ )
52
+ ):
53
+ return True
54
+ if (
55
+ isinstance(quantum_type_1, QuantumBitvector)
56
+ and isinstance(quantum_type_2, QuantumBitvector)
57
+ and (
58
+ not quantum_type_1.has_constant_length
59
+ or not quantum_type_2.has_constant_length
60
+ or quantum_type_1.length_value == quantum_type_2.length_value
61
+ )
62
+ and _same_shape(quantum_type_1.element_type, quantum_type_2.element_type)
63
+ ):
64
+ return True
65
+ return (
66
+ isinstance(quantum_type_1, TypeName)
67
+ and quantum_type_1.has_fields
68
+ and isinstance(quantum_type_2, TypeName)
69
+ and quantum_type_2.has_fields
70
+ and quantum_type_1.name == quantum_type_2.name
71
+ and all(
72
+ _same_shape(
73
+ quantum_type_1.fields[field_name], quantum_type_2.fields[field_name]
74
+ )
75
+ for field_name in quantum_type_1.fields
76
+ )
77
+ )
78
+
79
+
80
+ def _inject_qnum_type_attributes(
81
+ from_type: QuantumType, to_type: QuantumNumeric
82
+ ) -> QuantumNumeric:
83
+ size: Optional[Expression]
84
+ if from_type.has_size_in_bits:
85
+ size = Expression(expr=str(from_type.size_in_bits))
86
+ else:
87
+ size = to_type.size
88
+ if to_type.is_signed is not None:
89
+ if to_type.is_signed.is_evaluated():
90
+ is_signed = Expression(expr=str(to_type.sign_value))
91
+ else:
92
+ is_signed = Expression(expr="False")
93
+ else:
94
+ is_signed = None
95
+ if to_type.fraction_digits is not None:
96
+ if to_type.fraction_digits.is_evaluated():
97
+ fraction_digits = Expression(expr=str(to_type.fraction_digits_value))
98
+ else:
99
+ fraction_digits = Expression(expr="0")
100
+ else:
101
+ fraction_digits = None
102
+ if isinstance(from_type, QuantumNumeric) and not to_type.has_size_in_bits:
103
+ if (
104
+ is_signed is None
105
+ and from_type.is_signed is not None
106
+ and from_type.is_signed.is_evaluated()
107
+ ):
108
+ is_signed = from_type.is_signed
109
+ if (
110
+ fraction_digits is None
111
+ and from_type.fraction_digits is not None
112
+ and from_type.fraction_digits.is_evaluated()
113
+ ):
114
+ fraction_digits = from_type.fraction_digits
115
+ if size is not None and is_signed is None:
116
+ is_signed = Expression(expr="False")
117
+ if size is not None and fraction_digits is None:
118
+ fraction_digits = Expression(expr="0")
119
+ updated_type = QuantumNumeric(
120
+ size=size, is_signed=is_signed, fraction_digits=fraction_digits
121
+ )
122
+ updated_type.set_bounds(to_type.get_bounds())
123
+ set_bounds(_normalized_qnum(from_type), updated_type)
124
+ return updated_type
125
+
126
+
127
+ def _inject_qarray_type_attributes(
128
+ from_type: QuantumType, to_type: QuantumBitvector
129
+ ) -> Optional[QuantumBitvector]:
130
+ if _same_shape(from_type, to_type):
131
+ if TYPE_CHECKING:
132
+ assert isinstance(from_type, QuantumBitvector)
133
+ if to_type.has_length:
134
+ length = to_type.length
135
+ elif from_type.has_length:
136
+ length = from_type.length
137
+ elif from_type.has_size_in_bits:
138
+ raise ClassiqExpansionError(
139
+ f"Could not infer the length attribute of type {to_type.qmod_type_name}"
140
+ )
141
+ else:
142
+ length = None
143
+ element_type = inject_quantum_type_attributes(
144
+ from_type.element_type, to_type.element_type
145
+ )
146
+ else:
147
+ if to_type.has_constant_length:
148
+ if (
149
+ from_type.has_size_in_bits
150
+ and from_type.size_in_bits % to_type.length_value != 0
151
+ ):
152
+ return None
153
+ length = to_type.length
154
+ elif from_type.has_size_in_bits:
155
+ if not to_type.element_type.has_size_in_bits:
156
+ raise ClassiqExpansionError(
157
+ f"Could not infer the length attribute of type "
158
+ f"{to_type.qmod_type_name}"
159
+ )
160
+ if from_type.size_in_bits % to_type.element_type.size_in_bits != 0:
161
+ return None
162
+ length = Expression(
163
+ expr=str(from_type.size_in_bits // to_type.element_type.size_in_bits)
164
+ )
165
+ else:
166
+ length = None
167
+ if length is not None and from_type.has_size_in_bits:
168
+ element_type = inject_quantum_type_attributes(
169
+ QuantumBitvector(
170
+ length=Expression(
171
+ expr=str(from_type.size_in_bits // length.to_int_value())
172
+ )
173
+ ),
174
+ to_type.element_type,
175
+ )
176
+ else:
177
+ element_type = inject_quantum_type_attributes(
178
+ QuantumBitvector(), to_type.element_type
179
+ )
180
+ return QuantumBitvector(element_type=element_type, length=length)
181
+
182
+
183
+ def _inject_qstruct_type_attributes(
184
+ from_type: QuantumType, to_type: TypeName
185
+ ) -> Optional[TypeName]:
186
+ if isinstance(from_type, TypeName) and from_type.name == to_type.name:
187
+ fields = {
188
+ field_name: inject_quantum_type_attributes(
189
+ from_type.fields[field_name], to_type.fields[field_name]
190
+ )
191
+ for field_name in to_type.fields
192
+ }
193
+ if None in fields.values():
194
+ return None
195
+ elif not from_type.has_size_in_bits:
196
+ fields = {
197
+ field_name: inject_quantum_type_attributes(QuantumBitvector(), field_type)
198
+ for field_name, field_type in to_type.fields.items()
199
+ }
200
+ else:
201
+ initialized_fields = {
202
+ field_name: field_type.has_size_in_bits
203
+ for field_name, field_type in to_type.fields.items()
204
+ }
205
+ if sum(initialized_fields.values()) == len(initialized_fields) - 1:
206
+ flexible_field_name = [
207
+ field_name
208
+ for field_name, is_initialized in initialized_fields.items()
209
+ if not is_initialized
210
+ ][0]
211
+ flexible_field_size = from_type.size_in_bits - sum(
212
+ field_type.size_in_bits
213
+ for field_name, field_type in to_type.fields.items()
214
+ if initialized_fields[field_name]
215
+ )
216
+ flexible_field_type = inject_quantum_type_attributes(
217
+ QuantumBitvector(length=Expression(expr=str(flexible_field_size))),
218
+ to_type.fields[flexible_field_name],
219
+ )
220
+ if flexible_field_type is None:
221
+ return None
222
+ fields = {
223
+ field_name: (
224
+ flexible_field_type
225
+ if field_name == flexible_field_name
226
+ else inject_quantum_type_attributes(QuantumBitvector(), field_type)
227
+ )
228
+ for field_name, field_type in to_type.fields.items()
229
+ }
230
+ else:
231
+ fields = {
232
+ field_name: inject_quantum_type_attributes(
233
+ QuantumBitvector(), field_type
234
+ )
235
+ for field_name, field_type in to_type.fields.items()
236
+ }
237
+ classical_type = TypeName(name=to_type.name)
238
+ classical_type.set_fields(fields) # type:ignore[arg-type]
239
+ return classical_type
240
+
241
+
242
+ def inject_quantum_type_attributes(
243
+ from_type: QuantumType, to_type: QuantumType
244
+ ) -> Optional[QuantumType]:
245
+ for qmod_type in (from_type, to_type):
246
+ if isinstance(qmod_type, TypeName) and not qmod_type.has_fields:
247
+ raise ClassiqInternalExpansionError
248
+ if to_type.has_size_in_bits and (
249
+ (from_type.has_size_in_bits and to_type.size_in_bits != from_type.size_in_bits)
250
+ or (from_type.minimal_size_in_bits > to_type.size_in_bits)
251
+ ):
252
+ return None
253
+ if isinstance(to_type, QuantumBit):
254
+ return QuantumBit()
255
+ if isinstance(to_type, QuantumNumeric):
256
+ return _inject_qnum_type_attributes(from_type, to_type)
257
+ if isinstance(to_type, QuantumBitvector):
258
+ return _inject_qarray_type_attributes(from_type, to_type)
259
+ if isinstance(to_type, TypeName):
260
+ return _inject_qstruct_type_attributes(from_type, to_type)
261
+ raise ClassiqInternalExpansionError
262
+
263
+
264
+ def inject_quantum_type_attributes_inplace(
265
+ from_type: QuantumType, to_type: QuantumType
266
+ ) -> bool:
267
+ updated_type = inject_quantum_type_attributes(from_type, to_type)
268
+ if updated_type is None:
269
+ return False
270
+ if isinstance(to_type, QuantumBit) and isinstance(updated_type, QuantumBit):
271
+ return True
272
+ if isinstance(to_type, QuantumNumeric) and isinstance(updated_type, QuantumNumeric):
273
+ to_type.size = updated_type.size
274
+ to_type.is_signed = updated_type.is_signed
275
+ to_type.fraction_digits = updated_type.fraction_digits
276
+ to_type.set_bounds(updated_type.get_bounds())
277
+ return True
278
+ if isinstance(to_type, QuantumBitvector) and isinstance(
279
+ updated_type, QuantumBitvector
280
+ ):
281
+ to_type.length = updated_type.length
282
+ to_type.element_type = updated_type.element_type
283
+ return True
284
+ if (
285
+ isinstance(to_type, TypeName)
286
+ and to_type.has_fields
287
+ and isinstance(updated_type, TypeName)
288
+ and updated_type.has_fields
289
+ ):
290
+ to_type.set_fields(updated_type.fields)
291
+ return True
292
+ raise ClassiqInternalExpansionError
293
+
294
+
295
+ def validate_quantum_type_attributes(quantum_type: QuantumType) -> None:
296
+ if isinstance(quantum_type, TypeName):
297
+ if len(quantum_type.fields) == 0:
298
+ raise ClassiqExpansionError(
299
+ f"QStruct {quantum_type.name} must have at least one field"
300
+ )
301
+ for field_type in quantum_type.fields.values():
302
+ validate_quantum_type_attributes(field_type)
303
+ return
304
+ if isinstance(quantum_type, QuantumBitvector):
305
+ validate_quantum_type_attributes(quantum_type.element_type)
306
+ if quantum_type.has_constant_length and quantum_type.length_value < 1:
307
+ raise ClassiqExpansionError(
308
+ f"QArray length must be positive, got {quantum_type.length_value}"
309
+ )
310
+ return
311
+ if isinstance(quantum_type, QuantumNumeric):
312
+ if quantum_type.has_size_in_bits and quantum_type.size_in_bits < 1:
313
+ raise ClassiqExpansionError(
314
+ f"QNum size must be positive, got {quantum_type.size_in_bits}"
315
+ )
316
+ if quantum_type.has_fraction_digits:
317
+ if quantum_type.fraction_digits_value < 0:
318
+ raise ClassiqExpansionError(
319
+ f"QNum fraction digits must be positive, got "
320
+ f"{quantum_type.fraction_digits_value}"
321
+ )
322
+ if (
323
+ quantum_type.has_size_in_bits
324
+ and quantum_type.fraction_digits_value > quantum_type.size_in_bits
325
+ ):
326
+ raise ClassiqExpansionError(
327
+ f"QNum size ({quantum_type.size_in_bits}) must be greater or "
328
+ f"equals than the fraction digits "
329
+ f"({quantum_type.fraction_digits_value})"
330
+ )