classiq 0.45.1__py3-none-any.whl → 0.46.1__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 (146) hide show
  1. classiq/__init__.py +0 -1
  2. classiq/_internals/__init__.py +20 -0
  3. classiq/_internals/authentication/authentication.py +11 -0
  4. classiq/analyzer/analyzer.py +12 -10
  5. classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
  6. classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +1 -1
  7. classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
  8. classiq/applications/libraries/qmci_library.py +4 -9
  9. classiq/execution/execution_session.py +68 -7
  10. classiq/executor.py +14 -2
  11. classiq/interface/_version.py +1 -1
  12. classiq/interface/backend/backend_preferences.py +189 -0
  13. classiq/interface/backend/quantum_backend_providers.py +38 -0
  14. classiq/interface/debug_info/debug_info.py +22 -2
  15. classiq/interface/exceptions.py +16 -1
  16. classiq/interface/executor/execution_preferences.py +18 -0
  17. classiq/interface/generator/application_apis/chemistry_declarations.py +1 -177
  18. classiq/interface/generator/application_apis/combinatorial_optimization_declarations.py +0 -12
  19. classiq/interface/generator/application_apis/finance_declarations.py +8 -43
  20. classiq/interface/generator/application_apis/qsvm_declarations.py +0 -78
  21. classiq/interface/generator/builtin_api_builder.py +0 -3
  22. classiq/interface/generator/functions/__init__.py +0 -2
  23. classiq/interface/generator/functions/builtins/__init__.py +0 -15
  24. classiq/interface/generator/generated_circuit_data.py +2 -0
  25. classiq/interface/generator/hardware/hardware_data.py +37 -0
  26. classiq/interface/generator/model/constraints.py +18 -1
  27. classiq/interface/generator/model/preferences/preferences.py +53 -1
  28. classiq/interface/generator/model/quantum_register.py +1 -1
  29. classiq/interface/generator/quantum_program.py +10 -2
  30. classiq/interface/generator/transpiler_basis_gates.py +4 -0
  31. classiq/interface/generator/types/builtin_enum_declarations.py +136 -21
  32. classiq/interface/generator/types/enum_declaration.py +1 -3
  33. classiq/interface/generator/types/struct_declaration.py +1 -3
  34. classiq/interface/hardware.py +5 -0
  35. classiq/interface/ide/visual_model.py +1 -1
  36. classiq/interface/model/classical_parameter_declaration.py +6 -0
  37. classiq/interface/model/inplace_binary_operation.py +0 -14
  38. classiq/interface/model/model.py +1 -18
  39. classiq/interface/model/port_declaration.py +4 -2
  40. classiq/interface/model/quantum_function_declaration.py +19 -6
  41. classiq/interface/model/quantum_lambda_function.py +11 -1
  42. classiq/interface/model/quantum_variable_declaration.py +1 -1
  43. classiq/model_expansions/__init__.py +0 -0
  44. classiq/model_expansions/atomic_expression_functions_defs.py +250 -0
  45. classiq/model_expansions/capturing/__init__.py +0 -0
  46. classiq/model_expansions/capturing/captured_var_manager.py +50 -0
  47. classiq/model_expansions/capturing/mangling_utils.py +17 -0
  48. classiq/model_expansions/capturing/propagated_var_stack.py +180 -0
  49. classiq/model_expansions/closure.py +160 -0
  50. classiq/model_expansions/debug_flag.py +3 -0
  51. classiq/model_expansions/evaluators/__init__.py +0 -0
  52. classiq/model_expansions/evaluators/arg_type_match.py +160 -0
  53. classiq/model_expansions/evaluators/argument_types.py +42 -0
  54. classiq/model_expansions/evaluators/classical_expression.py +36 -0
  55. classiq/model_expansions/evaluators/control.py +144 -0
  56. classiq/model_expansions/evaluators/parameter_types.py +227 -0
  57. classiq/model_expansions/evaluators/quantum_type_utils.py +235 -0
  58. classiq/model_expansions/evaluators/type_type_match.py +90 -0
  59. classiq/model_expansions/expression_evaluator.py +125 -0
  60. classiq/model_expansions/expression_renamer.py +76 -0
  61. classiq/model_expansions/function_builder.py +192 -0
  62. classiq/model_expansions/generative_functions.py +101 -0
  63. classiq/model_expansions/interpreter.py +365 -0
  64. classiq/model_expansions/model_tables.py +105 -0
  65. classiq/model_expansions/quantum_operations/__init__.py +19 -0
  66. classiq/model_expansions/quantum_operations/bind.py +64 -0
  67. classiq/model_expansions/quantum_operations/classicalif.py +39 -0
  68. classiq/model_expansions/quantum_operations/control.py +235 -0
  69. classiq/model_expansions/quantum_operations/emitter.py +215 -0
  70. classiq/model_expansions/quantum_operations/expression_operation.py +218 -0
  71. classiq/model_expansions/quantum_operations/inplace_binary_operation.py +250 -0
  72. classiq/model_expansions/quantum_operations/invert.py +38 -0
  73. classiq/model_expansions/quantum_operations/power.py +74 -0
  74. classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +174 -0
  75. classiq/model_expansions/quantum_operations/quantum_function_call.py +15 -0
  76. classiq/model_expansions/quantum_operations/repeat.py +33 -0
  77. classiq/model_expansions/quantum_operations/variable_decleration.py +28 -0
  78. classiq/model_expansions/quantum_operations/within_apply.py +46 -0
  79. classiq/model_expansions/scope.py +226 -0
  80. classiq/model_expansions/scope_initialization.py +136 -0
  81. classiq/model_expansions/sympy_conversion/__init__.py +0 -0
  82. classiq/model_expansions/sympy_conversion/arithmetics.py +49 -0
  83. classiq/model_expansions/sympy_conversion/expression_to_sympy.py +150 -0
  84. classiq/model_expansions/sympy_conversion/sympy_to_python.py +113 -0
  85. classiq/model_expansions/utils/__init__.py +0 -0
  86. classiq/model_expansions/utils/counted_name_allocator.py +11 -0
  87. classiq/model_expansions/visitors/__init__.py +0 -0
  88. classiq/model_expansions/visitors/boolean_expression_transformers.py +214 -0
  89. classiq/model_expansions/visitors/variable_references.py +115 -0
  90. classiq/qmod/__init__.py +1 -3
  91. classiq/qmod/builtins/enums.py +33 -2
  92. classiq/qmod/builtins/functions/__init__.py +251 -0
  93. classiq/qmod/builtins/functions/amplitude_estimation.py +27 -0
  94. classiq/qmod/builtins/functions/arithmetic.py +68 -0
  95. classiq/qmod/builtins/functions/benchmarking.py +8 -0
  96. classiq/qmod/builtins/functions/chemistry.py +91 -0
  97. classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +105 -0
  98. classiq/qmod/builtins/functions/exponentiation.py +111 -0
  99. classiq/qmod/builtins/functions/finance.py +34 -0
  100. classiq/qmod/builtins/functions/grover.py +178 -0
  101. classiq/qmod/builtins/functions/hea.py +59 -0
  102. classiq/qmod/builtins/functions/linear_pauli_rotation.py +65 -0
  103. classiq/qmod/builtins/functions/modular_exponentiation.py +137 -0
  104. classiq/qmod/builtins/functions/operators.py +22 -0
  105. classiq/qmod/builtins/functions/qaoa_penalty.py +116 -0
  106. classiq/qmod/builtins/functions/qft.py +23 -0
  107. classiq/qmod/builtins/functions/qpe.py +39 -0
  108. classiq/qmod/builtins/functions/qsvm.py +24 -0
  109. classiq/qmod/builtins/functions/qsvt.py +136 -0
  110. classiq/qmod/builtins/functions/standard_gates.py +739 -0
  111. classiq/qmod/builtins/functions/state_preparation.py +357 -0
  112. classiq/qmod/builtins/functions/swap_test.py +25 -0
  113. classiq/qmod/builtins/structs.py +50 -28
  114. classiq/qmod/cparam.py +64 -0
  115. classiq/qmod/create_model_function.py +190 -0
  116. classiq/qmod/declaration_inferrer.py +52 -81
  117. classiq/qmod/expression_query.py +16 -0
  118. classiq/qmod/generative.py +48 -0
  119. classiq/qmod/model_state_container.py +1 -2
  120. classiq/qmod/native/pretty_printer.py +7 -11
  121. classiq/qmod/pretty_print/pretty_printer.py +7 -11
  122. classiq/qmod/python_classical_type.py +67 -0
  123. classiq/qmod/qfunc.py +19 -4
  124. classiq/qmod/qmod_parameter.py +15 -64
  125. classiq/qmod/qmod_variable.py +27 -45
  126. classiq/qmod/quantum_callable.py +1 -1
  127. classiq/qmod/quantum_expandable.py +10 -4
  128. classiq/qmod/quantum_function.py +22 -40
  129. classiq/qmod/semantics/error_manager.py +22 -10
  130. classiq/qmod/semantics/static_semantics_visitor.py +10 -12
  131. classiq/qmod/semantics/validation/types_validation.py +6 -7
  132. classiq/qmod/utilities.py +2 -2
  133. classiq/qmod/write_qmod.py +14 -0
  134. classiq/show.py +10 -0
  135. classiq/synthesis.py +46 -2
  136. {classiq-0.45.1.dist-info → classiq-0.46.1.dist-info}/METADATA +1 -1
  137. {classiq-0.45.1.dist-info → classiq-0.46.1.dist-info}/RECORD +138 -74
  138. classiq/interface/generator/functions/builtins/core_library/__init__.py +0 -16
  139. classiq/interface/generator/functions/builtins/core_library/atomic_quantum_functions.py +0 -710
  140. classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +0 -105
  141. classiq/interface/generator/functions/builtins/open_lib_functions.py +0 -2489
  142. classiq/interface/generator/functions/builtins/quantum_operators.py +0 -24
  143. classiq/interface/generator/types/builtin_struct_declarations/__init__.py +0 -1
  144. classiq/interface/generator/types/builtin_struct_declarations/pauli_struct_declarations.py +0 -21
  145. classiq/qmod/builtins/functions.py +0 -1029
  146. {classiq-0.45.1.dist-info → classiq-0.46.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,49 @@
1
+ from typing import Any, Optional
2
+
3
+ from sympy import Function, Integer
4
+ from sympy.logic.boolalg import BooleanFunction
5
+
6
+
7
+ class BitwiseAnd(Function):
8
+ @classmethod
9
+ def eval(cls, a: Any, b: Any) -> Optional[int]:
10
+ if isinstance(a, Integer) and isinstance(b, Integer):
11
+ return a & b
12
+
13
+ return None
14
+
15
+
16
+ class BitwiseXor(Function):
17
+ @classmethod
18
+ def eval(cls, a: Any, b: Any) -> Optional[int]:
19
+ if isinstance(a, Integer) and isinstance(b, Integer):
20
+ return a ^ b
21
+
22
+ return None
23
+
24
+
25
+ class LogicalXor(BooleanFunction):
26
+ @classmethod
27
+ def eval(cls, a: Any, b: Any) -> Optional[bool]:
28
+ if isinstance(a, bool) and isinstance(b, bool):
29
+ return a ^ b
30
+
31
+ return None
32
+
33
+
34
+ class BitwiseOr(Function):
35
+ @classmethod
36
+ def eval(cls, a: Any, b: Any) -> Optional[int]:
37
+ if isinstance(a, Integer) and isinstance(b, Integer):
38
+ return a | b
39
+
40
+ return None
41
+
42
+
43
+ class BitwiseNot(Function):
44
+ @classmethod
45
+ def eval(cls, a: Any) -> Optional[int]:
46
+ if isinstance(a, Integer):
47
+ return ~a
48
+
49
+ return None
@@ -0,0 +1,150 @@
1
+ import ast
2
+ from typing import TYPE_CHECKING, Dict, Type
3
+
4
+ from classiq.interface.exceptions import ClassiqExpansionError
5
+
6
+ MISSING_SLICE_VALUE_PLACEHOLDER = "MISSING_SLICE_VALUE"
7
+
8
+
9
+ def translate_to_sympy(expr: str) -> str:
10
+ node = ast.parse(expr)
11
+ node = ExpressionSympyTranslator().visit(node)
12
+ # node is a Module, we want an Expression
13
+ if TYPE_CHECKING:
14
+ assert isinstance(node.body[0], ast.Expr)
15
+ expression = ast.Expression(node.body[0].value)
16
+
17
+ return ast.unparse(ast.fix_missing_locations(expression))
18
+
19
+
20
+ class ExpressionSympyTranslator(ast.NodeTransformer):
21
+ BINARY_OPERATORS: Dict[Type[ast.AST], str] = {
22
+ ast.BitOr: "BitwiseOr",
23
+ ast.BitAnd: "BitwiseAnd",
24
+ ast.BitXor: "BitwiseXor",
25
+ ast.Div: "do_div",
26
+ }
27
+
28
+ UNARY_OPERATORS: Dict[Type[ast.AST], str] = {
29
+ ast.Invert: "BitwiseNot",
30
+ ast.Not: "Not",
31
+ }
32
+
33
+ BOOLEAN_OPERATORS: Dict[Type[ast.AST], str] = {
34
+ ast.Or: "Or",
35
+ ast.And: "And",
36
+ }
37
+
38
+ COMPARE_OPERATORS: Dict[Type[ast.AST], str] = {
39
+ ast.Eq: "Eq",
40
+ ast.NotEq: "Ne",
41
+ }
42
+
43
+ SPECIAL_FUNCTIONS: Dict[str, str] = {
44
+ "max": "Max",
45
+ "min": "Min",
46
+ }
47
+
48
+ def visit_BinOp(self, node: ast.BinOp) -> ast.AST:
49
+ sympy_class = self.BINARY_OPERATORS.get(node.op.__class__)
50
+ if sympy_class is not None:
51
+ left = self.visit(node.left)
52
+ right = self.visit(node.right)
53
+
54
+ new_node = ast.Call(
55
+ func=ast.Name(id=sympy_class, ctx=ast.Load()),
56
+ args=[left, right],
57
+ starargs=None,
58
+ keywords=[],
59
+ kwargs=None,
60
+ )
61
+
62
+ return new_node
63
+ return self.generic_visit(node)
64
+
65
+ def visit_Compare(self, node: ast.Compare) -> ast.AST:
66
+ if len(node.ops) > 1:
67
+ raise ClassiqExpansionError(
68
+ f"Qmod expressions do not support chained comparison, as done in {ast.unparse(node)}"
69
+ )
70
+ sympy_class = self.COMPARE_OPERATORS.get(node.ops[0].__class__)
71
+ if sympy_class is not None:
72
+ left = self.visit(node.left)
73
+ right = self.visit(node.comparators[0])
74
+
75
+ new_node = ast.Call(
76
+ func=ast.Name(id=sympy_class, ctx=ast.Load()),
77
+ args=[left, right],
78
+ starargs=None,
79
+ keywords=[],
80
+ kwargs=None,
81
+ )
82
+
83
+ return new_node
84
+ return self.generic_visit(node)
85
+
86
+ def visit_BoolOp(self, node: ast.BoolOp) -> ast.AST:
87
+ sympy_class = self.BOOLEAN_OPERATORS.get(node.op.__class__)
88
+ if sympy_class is not None:
89
+ values = [self.visit(value) for value in node.values]
90
+
91
+ new_node = ast.Call(
92
+ func=ast.Name(id=sympy_class, ctx=ast.Load()),
93
+ args=values,
94
+ starargs=None,
95
+ keywords=[],
96
+ kwargs=None,
97
+ )
98
+
99
+ return new_node
100
+ return self.generic_visit(node)
101
+
102
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> ast.AST:
103
+ sympy_class = self.UNARY_OPERATORS.get(node.op.__class__)
104
+ if sympy_class is not None:
105
+ operand = self.visit(node.operand)
106
+
107
+ new_node = ast.Call(
108
+ func=ast.Name(id=sympy_class, ctx=ast.Load()),
109
+ args=[operand],
110
+ starargs=None,
111
+ keywords=[],
112
+ kwargs=None,
113
+ )
114
+
115
+ return new_node
116
+ return self.generic_visit(node)
117
+
118
+ def visit_Call(self, node: ast.Call) -> ast.AST:
119
+ if (
120
+ not isinstance(node.func, ast.Name)
121
+ or node.func.id not in self.SPECIAL_FUNCTIONS
122
+ ):
123
+ return self.generic_visit(node)
124
+
125
+ return ast.Call(
126
+ func=ast.Name(self.SPECIAL_FUNCTIONS[node.func.id]),
127
+ args=[self.visit(arg) for arg in (node.args)],
128
+ keywords=[self.visit(arg) for arg in node.keywords],
129
+ )
130
+
131
+ def visit_Subscript(self, node: ast.Subscript) -> ast.AST:
132
+ if isinstance(node.slice, ast.Slice):
133
+ if node.slice.lower is not None:
134
+ lower = self.visit(node.slice.lower)
135
+ else:
136
+ lower = ast.Name(MISSING_SLICE_VALUE_PLACEHOLDER)
137
+ if node.slice.upper is not None:
138
+ upper = self.visit(node.slice.upper)
139
+ else:
140
+ upper = ast.Name(MISSING_SLICE_VALUE_PLACEHOLDER)
141
+ return ast.Call(
142
+ func=ast.Name("do_slice"),
143
+ args=[self.visit(node.value), lower, upper],
144
+ keywords=[],
145
+ )
146
+ return ast.Call(
147
+ func=ast.Name("do_subscript"),
148
+ args=[self.visit(node.value), self.visit(node.slice)],
149
+ keywords=[],
150
+ )
@@ -0,0 +1,113 @@
1
+ import functools
2
+ from typing import Any, Dict, Optional, get_args
3
+
4
+ from sympy import (
5
+ Array,
6
+ Basic,
7
+ Expr,
8
+ Float,
9
+ Integer,
10
+ Matrix,
11
+ Piecewise,
12
+ Rational,
13
+ Symbol,
14
+ )
15
+ from sympy.logic.boolalg import BooleanAtom
16
+ from sympy.printing.pycode import PythonCodePrinter
17
+
18
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
19
+ from classiq.interface.generator.expressions.expression_types import ExpressionValue
20
+
21
+ from classiq.model_expansions.sympy_conversion.arithmetics import LogicalXor
22
+
23
+
24
+ def sympy_to_python(
25
+ value: Any, locals: Optional[Dict[str, ExpressionValue]] = None
26
+ ) -> ExpressionValue:
27
+ if isinstance(value, Integer):
28
+ value = int(value)
29
+ elif isinstance(value, Float):
30
+ value = float(value)
31
+ elif isinstance(value, BooleanAtom):
32
+ value = bool(value)
33
+ elif isinstance(value, Array):
34
+ value = sympy_to_python(value.tolist(), locals)
35
+ elif isinstance(value, Rational):
36
+ value = float(value.evalf())
37
+ elif isinstance(value, list):
38
+ value = [sympy_to_python(element, locals) for element in value]
39
+ elif isinstance(value, Matrix):
40
+ value = [sympy_to_python(element, locals) for element in value.tolist()]
41
+ elif isinstance(value, Symbol) and locals is not None and value.name in locals:
42
+ return locals[value.name]
43
+ if value is None:
44
+ value = False
45
+
46
+ if not isinstance(value, get_args(ExpressionValue)):
47
+ raise ClassiqInternalExpansionError(
48
+ f"Invalid evaluated expression {value} of type {type(value)}"
49
+ )
50
+
51
+ return value
52
+
53
+
54
+ def _conditional_true(*args: Any, **kwargs: Any) -> bool:
55
+ return True
56
+
57
+
58
+ class SympyToQuantumExpressionTranslator(PythonCodePrinter):
59
+ _operators = {**PythonCodePrinter._operators, **{"not": "~", "xor": "^"}}
60
+ _kf = {
61
+ **PythonCodePrinter._kf,
62
+ **{"max": "max", "min": "min", "Max": "max", "Min": "min"},
63
+ }
64
+ BINARY_BITWISE_OPERATORS_MAPPING = {
65
+ "BitwiseAnd": "&",
66
+ "BitwiseOr": "|",
67
+ "BitwiseXor": "^",
68
+ "LogicalXor": "^",
69
+ }
70
+ UNARY_BITWISE_OPERATORS_MAPPING = {"BitwiseNot": "~"}
71
+
72
+ @staticmethod
73
+ def _print_bitwise_binary_operator(
74
+ left_arg: Expr, right_arg: Expr, operator: str
75
+ ) -> str:
76
+ return f"(({left_arg}) {operator} ({right_arg}))"
77
+
78
+ @staticmethod
79
+ def _print_bitwise_unary_operator(arg: Expr, operator: str) -> str:
80
+ return f"({operator} ({arg}))"
81
+
82
+ def __init__(self) -> None:
83
+ super().__init__(settings={"fully_qualified_modules": False})
84
+ for binary_operator in self.BINARY_BITWISE_OPERATORS_MAPPING:
85
+ self.known_functions[binary_operator] = [
86
+ (
87
+ _conditional_true,
88
+ functools.partial(
89
+ self._print_bitwise_binary_operator,
90
+ operator=self.BINARY_BITWISE_OPERATORS_MAPPING[binary_operator],
91
+ ),
92
+ )
93
+ ]
94
+ for unary_operator in self.UNARY_BITWISE_OPERATORS_MAPPING:
95
+ self.known_functions[unary_operator] = [
96
+ (
97
+ _conditional_true,
98
+ functools.partial(
99
+ self._print_bitwise_unary_operator,
100
+ operator=self.UNARY_BITWISE_OPERATORS_MAPPING[unary_operator],
101
+ ),
102
+ )
103
+ ]
104
+
105
+ def _print_Piecewise(self, expr: Piecewise) -> str: # noqa: N802
106
+ return str(expr)
107
+
108
+ def _print_LogicalXor(self, expr: LogicalXor) -> str: # noqa: N802
109
+ return f"(({self._print(expr.args[0])}) ^ ({self._print(expr.args[1])}))"
110
+
111
+
112
+ def translate_sympy_quantum_expression(expr: Basic) -> str:
113
+ return SympyToQuantumExpressionTranslator().doprint(expr)
File without changes
@@ -0,0 +1,11 @@
1
+ from collections import Counter
2
+
3
+
4
+ class CountedNameAllocator:
5
+ def __init__(self) -> None:
6
+ self._count: Counter[str] = Counter()
7
+
8
+ def allocate(self, prefix: str) -> str:
9
+ allocated = f"{prefix}_{self._count[prefix]}"
10
+ self._count[prefix] += 1
11
+ return allocated
File without changes
@@ -0,0 +1,214 @@
1
+ import ast
2
+ import copy
3
+ from typing import TYPE_CHECKING, Union
4
+
5
+ from classiq.interface.exceptions import ClassiqInternalExpansionError
6
+
7
+ from classiq.model_expansions.sympy_conversion.arithmetics import LogicalXor
8
+
9
+
10
+ class BooleanExpressionOptimizer(ast.NodeTransformer):
11
+ """
12
+ This class assumes that all variables in the expression are single qubit.
13
+ It does the following:
14
+ It checks whether the expression can be transformed into a boolean expression with boolean
15
+ variables (i.e, single-qubit variables), and if so, does the transformation by converting bitwise
16
+ ops to their boolean analogs.
17
+ The condition is that the expression consists of bitwise operations and relational operations only,
18
+ and not operations like addition.
19
+ The transformation results in better circuits.
20
+ """
21
+
22
+ def __init__(self) -> None:
23
+ self._is_convertible: bool = False
24
+
25
+ @property
26
+ def is_convertible(self) -> bool:
27
+ return self._is_convertible
28
+
29
+ def visit_Expr(self, node: ast.Expr) -> ast.Expr:
30
+ self._is_convertible = False
31
+ original_expr = copy.deepcopy(node)
32
+ self.generic_visit(node)
33
+ return node if self._is_convertible else original_expr
34
+
35
+ def visit_operator(self, node: ast.operator) -> ast.operator:
36
+ self._is_convertible = False
37
+ return node
38
+
39
+ def visit_BinOp(self, node: ast.BinOp) -> Union[ast.BinOp, ast.BoolOp]:
40
+ self.generic_visit(node)
41
+ self._is_convertible = self._is_bool(node)
42
+ if not self._is_convertible:
43
+ return node
44
+
45
+ return self.visit(self._convert_bin_op_to_bool_op(node))
46
+
47
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> ast.AST:
48
+ self.generic_visit(node)
49
+ self._is_convertible = self._is_bool(node)
50
+ if not self._is_convertible:
51
+ return node
52
+
53
+ return ast.UnaryOp(op=ast.Not(), operand=node.operand)
54
+
55
+ def visit_Compare(self, node: ast.Compare) -> ast.AST:
56
+ if len(node.ops) != 1 or len(node.comparators) != 1:
57
+ raise ClassiqInternalExpansionError
58
+
59
+ self.generic_visit(node)
60
+ self._is_convertible = self._is_bool(node)
61
+ if not self._is_convertible:
62
+ return node
63
+
64
+ if not (
65
+ isinstance(node.left, ast.Constant)
66
+ or isinstance(node.comparators[0], ast.Constant)
67
+ ):
68
+ return node
69
+
70
+ return self._simplify_trivial_equality(node)
71
+
72
+ def visit_Call(self, node: ast.Call) -> ast.Call:
73
+ self._is_convertible = (
74
+ isinstance(node.func, ast.Name) and node.func.id == LogicalXor.__name__
75
+ )
76
+ return node
77
+
78
+ def _is_bool(self, node: ast.AST) -> bool:
79
+ if isinstance(node, (ast.BoolOp, ast.Name)): # Name due to boolean vars
80
+ return True
81
+ if isinstance(node, ast.BinOp):
82
+ return (
83
+ self._is_bool(node.left)
84
+ and self._is_bool(node.right)
85
+ and isinstance(node.op, (ast.BitOr, ast.BitAnd, ast.BitXor))
86
+ )
87
+ if isinstance(node, ast.Constant):
88
+ return isinstance(node.value, int) and node.value in (0, 1)
89
+ if isinstance(node, ast.UnaryOp):
90
+ return isinstance(node.op, (ast.Invert, ast.Not)) and self._is_bool(
91
+ node.operand
92
+ )
93
+ if isinstance(node, ast.Compare):
94
+ return (
95
+ self._is_bool(node.left)
96
+ and self._is_bool(node.comparators[0])
97
+ and isinstance(node.ops[0], (ast.Eq, ast.NotEq))
98
+ )
99
+ if isinstance(node, ast.Call):
100
+ return (
101
+ isinstance(node.func, ast.Name) and node.func.id == LogicalXor.__name__
102
+ )
103
+ return False
104
+
105
+ def _convert_bin_op_to_bool_op(self, node: ast.BinOp) -> ast.AST:
106
+ if isinstance(node.op, ast.BitOr):
107
+ return ast.BoolOp(op=ast.Or(), values=[node.left, node.right])
108
+ if isinstance(node.op, ast.BitAnd):
109
+ return ast.BoolOp(op=ast.And(), values=[node.left, node.right])
110
+ if isinstance(node.op, ast.BitXor):
111
+ return self._simplify_xor(node)
112
+ raise ClassiqInternalExpansionError
113
+
114
+ @staticmethod
115
+ def _simplify_xor(node: ast.BinOp) -> ast.AST:
116
+ if not (
117
+ isinstance(node.left, ast.Constant) or isinstance(node.right, ast.Constant)
118
+ ):
119
+ return ast.Call(
120
+ func=ast.Name(LogicalXor.__name__),
121
+ args=[node.left, node.right],
122
+ keywords=[],
123
+ )
124
+
125
+ if isinstance(node.left, ast.Constant):
126
+ constant = node.left.value
127
+ other = node.right
128
+ else:
129
+ if TYPE_CHECKING:
130
+ assert isinstance(node.right, ast.Constant)
131
+ try:
132
+ constant = node.right.value
133
+ except AttributeError as e:
134
+ raise e
135
+ other = node.left
136
+
137
+ return other if constant == 0 else ast.UnaryOp(op=ast.Not(), operand=other)
138
+
139
+ @staticmethod
140
+ def _simplify_trivial_equality(node: ast.Compare) -> ast.AST:
141
+ if isinstance(node.left, ast.Constant):
142
+ val = node.left.value
143
+ other = node.comparators[0]
144
+ else:
145
+ if TYPE_CHECKING:
146
+ assert isinstance(node.comparators[0], ast.Constant)
147
+ val = node.comparators[0].value
148
+ other = node.left
149
+
150
+ to_invert = (val == 0 and isinstance(node.ops[0], ast.Eq)) or (
151
+ val == 1 and isinstance(node.ops[0], ast.NotEq)
152
+ )
153
+
154
+ return ast.UnaryOp(op=ast.Not(), operand=other) if to_invert else other
155
+
156
+
157
+ class BooleanExpressionFuncLibAdapter(ast.NodeTransformer):
158
+ """
159
+ The class assumes that the expression result is single-qubit.
160
+
161
+ Due to limitations on the inplace arithmetic in our function library, this visitor checks whether
162
+ the expression has one of the following forms:
163
+ a. A single, simple boolean assignment (res ^= x).
164
+ b. A bitwise-not operation on a boolean expression (res ^= ~(x > 5)).
165
+ The former will be transformed into res ^= (x == 1). The latter to (res ^= x > 5) and then the Interpreter
166
+ will append an X gate to the result variable.
167
+ To understand the necessity of this transformation, see the _OPERATIONS_ALLOWING_TARGET variable
168
+ which limits the possible inplace operations. Not is forbidden, and a simple assignment res ^= x
169
+ is converted into an addition res ^= x + 0, which is also forbidden.
170
+
171
+ """
172
+
173
+ def __init__(self, is_boolean_optimized: bool) -> None:
174
+ self._to_invert: bool = False
175
+ self._is_boolean_optimized = is_boolean_optimized
176
+
177
+ @property
178
+ def to_invert(self) -> bool:
179
+ return self._to_invert
180
+
181
+ def visit_Expr(self, node: ast.Expr) -> ast.Expr:
182
+ self.generic_visit(node)
183
+ if isinstance(node.value, ast.Name):
184
+ node.value = ast.Compare(
185
+ left=node.value,
186
+ ops=[ast.Eq()],
187
+ comparators=[ast.Constant(value=1)],
188
+ )
189
+ if isinstance(node.value, ast.UnaryOp) and isinstance(
190
+ node.value.op, (ast.Not, ast.Invert)
191
+ ):
192
+ self._to_invert = not self._to_invert
193
+ node.value = node.value.operand
194
+ return self.visit(node)
195
+
196
+ return node
197
+
198
+ def visit_UnaryOp(self, node: ast.UnaryOp) -> ast.UnaryOp:
199
+ """Due to Sympy crap, we need to translate the Invert nodes to Not"""
200
+ if not (self._is_boolean_optimized and isinstance(node.op, ast.Invert)):
201
+ return node
202
+ self.generic_visit(node)
203
+ return ast.UnaryOp(op=ast.Not(), operand=node.operand)
204
+
205
+ def visit_BinOp(self, node: ast.BinOp) -> Union[ast.BinOp, ast.Call]:
206
+ """Due to Sympy crap, we need to translate the Xor nodes to our LogicalXor"""
207
+ if not (self._is_boolean_optimized and isinstance(node.op, ast.BitXor)):
208
+ return node
209
+ self.generic_visit(node)
210
+ return ast.Call(
211
+ func=ast.Name(LogicalXor.__name__),
212
+ args=[node.left, node.right],
213
+ keywords=[],
214
+ )
@@ -0,0 +1,115 @@
1
+ import ast
2
+ from contextlib import contextmanager
3
+ from typing import Dict, Iterator, Optional, Set, Union
4
+
5
+ from classiq.interface.exceptions import (
6
+ ClassiqExpansionError,
7
+ ClassiqInternalExpansionError,
8
+ )
9
+ from classiq.interface.generator.arith.arithmetic_expression_validator import (
10
+ DEFAULT_SUPPORTED_FUNC_NAMES,
11
+ )
12
+ from classiq.interface.generator.expressions.expression import Expression
13
+ from classiq.interface.generator.expressions.sympy_supported_expressions import (
14
+ SYMPY_SUPPORTED_EXPRESSIONS,
15
+ )
16
+ from classiq.interface.model.handle_binding import (
17
+ FieldHandleBinding,
18
+ HandleBinding,
19
+ SlicedHandleBinding,
20
+ SubscriptHandleBinding,
21
+ )
22
+
23
+
24
+ class VarRefCollector(ast.NodeVisitor):
25
+ def __init__(self, ignore_duplicated_handles: bool = False) -> None:
26
+ self.var_handles: Set[HandleBinding] = set()
27
+ self._ignore_duplicated_handles = ignore_duplicated_handles
28
+ self._is_nested = False
29
+
30
+ def visit(self, node: ast.AST) -> Union[
31
+ SubscriptHandleBinding,
32
+ SlicedHandleBinding,
33
+ FieldHandleBinding,
34
+ HandleBinding,
35
+ None,
36
+ ]:
37
+ res = super().visit(node)
38
+ if not self._ignore_duplicated_handles and len(self.var_handles) != len(
39
+ {handle.name for handle in self.var_handles}
40
+ ):
41
+ raise ClassiqExpansionError(
42
+ "Multiple non-identical variable references in an expression are not supported."
43
+ )
44
+ return res
45
+
46
+ def visit_Subscript(
47
+ self, node: ast.Subscript
48
+ ) -> Union[SubscriptHandleBinding, SlicedHandleBinding, None]:
49
+ with self.set_nested():
50
+ base_handle = self.visit(node.value)
51
+ if base_handle is None:
52
+ return None
53
+
54
+ handle: Union[SubscriptHandleBinding, SlicedHandleBinding]
55
+ if isinstance(node.slice, ast.Num):
56
+ handle = SubscriptHandleBinding(
57
+ base_handle=base_handle,
58
+ index=Expression(expr=str(node.slice.value)),
59
+ )
60
+ elif isinstance(node.slice, ast.Slice):
61
+ if not isinstance(node.slice.lower, ast.Num) or not isinstance(
62
+ node.slice.upper, ast.Num
63
+ ):
64
+ raise ClassiqInternalExpansionError("Unevaluated slice bounds.")
65
+ handle = SlicedHandleBinding(
66
+ base_handle=base_handle,
67
+ start=Expression(expr=str(node.slice.lower.value)),
68
+ end=Expression(expr=str(node.slice.upper.value)),
69
+ )
70
+ else:
71
+ raise ClassiqInternalExpansionError("Unevaluated slice.")
72
+
73
+ if not self._is_nested:
74
+ self.var_handles.add(handle)
75
+ return handle
76
+
77
+ def visit_Attribute(self, node: ast.Attribute) -> Optional[FieldHandleBinding]:
78
+ with self.set_nested():
79
+ base_handle = self.visit(node.value)
80
+ if base_handle is None:
81
+ return None
82
+ handle = FieldHandleBinding(
83
+ base_handle=base_handle,
84
+ field=node.attr,
85
+ )
86
+ if not self._is_nested:
87
+ self.var_handles.add(handle)
88
+ return handle
89
+
90
+ def visit_Name(self, node: ast.Name) -> Optional[HandleBinding]:
91
+ if node.id in set(SYMPY_SUPPORTED_EXPRESSIONS) | set(
92
+ DEFAULT_SUPPORTED_FUNC_NAMES
93
+ ):
94
+ return None
95
+ handle = HandleBinding(name=node.id)
96
+ if not self._is_nested:
97
+ self.var_handles.add(handle)
98
+ return handle
99
+
100
+ @contextmanager
101
+ def set_nested(self) -> Iterator[None]:
102
+ previous_is_nested = self._is_nested
103
+ self._is_nested = True
104
+ yield
105
+ self._is_nested = previous_is_nested
106
+
107
+
108
+ class VarRefTransformer(ast.NodeTransformer):
109
+ def __init__(self, var_mapping: Dict[str, str]) -> None:
110
+ self.var_mapping = var_mapping
111
+
112
+ def visit_Name(self, node: ast.Name) -> ast.Name:
113
+ if node.id in self.var_mapping:
114
+ node.id = self.var_mapping[node.id]
115
+ return node
classiq/qmod/__init__.py CHANGED
@@ -1,14 +1,13 @@
1
- from . import symbolic
2
1
  from .builtins import * # noqa: F403
3
2
  from .builtins import __all__ as _builtins_all
4
3
  from .cfunc import cfunc
4
+ from .create_model_function import create_model
5
5
  from .expression_query import get_expression_numeric_attributes
6
6
  from .qfunc import qfunc
7
7
  from .qmod_constant import QConstant
8
8
  from .qmod_parameter import Array, CArray, CBool, CInt, CReal
9
9
  from .qmod_variable import Input, Output, QArray, QBit, QNum, QStruct
10
10
  from .quantum_callable import QCallable, QCallableList
11
- from .quantum_function import create_model
12
11
  from .write_qmod import write_qmod
13
12
 
14
13
  __all__ = [
@@ -30,6 +29,5 @@ __all__ = [
30
29
  "create_model",
31
30
  "get_expression_numeric_attributes",
32
31
  "qfunc",
33
- "symbolic",
34
32
  "write_qmod",
35
33
  ] + _builtins_all