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.
- classiq/__init__.py +0 -1
- classiq/_internals/__init__.py +20 -0
- classiq/_internals/authentication/authentication.py +11 -0
- classiq/analyzer/analyzer.py +12 -10
- classiq/applications/combinatorial_helpers/combinatorial_problem_utils.py +1 -1
- classiq/applications/combinatorial_helpers/pauli_helpers/pauli_utils.py +1 -1
- classiq/applications/combinatorial_optimization/combinatorial_optimization_model_constructor.py +1 -1
- classiq/applications/libraries/qmci_library.py +4 -9
- classiq/execution/execution_session.py +68 -7
- classiq/executor.py +14 -2
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/backend_preferences.py +189 -0
- classiq/interface/backend/quantum_backend_providers.py +38 -0
- classiq/interface/debug_info/debug_info.py +22 -2
- classiq/interface/exceptions.py +16 -1
- classiq/interface/executor/execution_preferences.py +18 -0
- classiq/interface/generator/application_apis/chemistry_declarations.py +1 -177
- classiq/interface/generator/application_apis/combinatorial_optimization_declarations.py +0 -12
- classiq/interface/generator/application_apis/finance_declarations.py +8 -43
- classiq/interface/generator/application_apis/qsvm_declarations.py +0 -78
- classiq/interface/generator/builtin_api_builder.py +0 -3
- classiq/interface/generator/functions/__init__.py +0 -2
- classiq/interface/generator/functions/builtins/__init__.py +0 -15
- classiq/interface/generator/generated_circuit_data.py +2 -0
- classiq/interface/generator/hardware/hardware_data.py +37 -0
- classiq/interface/generator/model/constraints.py +18 -1
- classiq/interface/generator/model/preferences/preferences.py +53 -1
- classiq/interface/generator/model/quantum_register.py +1 -1
- classiq/interface/generator/quantum_program.py +10 -2
- classiq/interface/generator/transpiler_basis_gates.py +4 -0
- classiq/interface/generator/types/builtin_enum_declarations.py +136 -21
- classiq/interface/generator/types/enum_declaration.py +1 -3
- classiq/interface/generator/types/struct_declaration.py +1 -3
- classiq/interface/hardware.py +5 -0
- classiq/interface/ide/visual_model.py +1 -1
- classiq/interface/model/classical_parameter_declaration.py +6 -0
- classiq/interface/model/inplace_binary_operation.py +0 -14
- classiq/interface/model/model.py +1 -18
- classiq/interface/model/port_declaration.py +4 -2
- classiq/interface/model/quantum_function_declaration.py +19 -6
- classiq/interface/model/quantum_lambda_function.py +11 -1
- classiq/interface/model/quantum_variable_declaration.py +1 -1
- classiq/model_expansions/__init__.py +0 -0
- classiq/model_expansions/atomic_expression_functions_defs.py +250 -0
- classiq/model_expansions/capturing/__init__.py +0 -0
- classiq/model_expansions/capturing/captured_var_manager.py +50 -0
- classiq/model_expansions/capturing/mangling_utils.py +17 -0
- classiq/model_expansions/capturing/propagated_var_stack.py +180 -0
- classiq/model_expansions/closure.py +160 -0
- classiq/model_expansions/debug_flag.py +3 -0
- classiq/model_expansions/evaluators/__init__.py +0 -0
- classiq/model_expansions/evaluators/arg_type_match.py +160 -0
- classiq/model_expansions/evaluators/argument_types.py +42 -0
- classiq/model_expansions/evaluators/classical_expression.py +36 -0
- classiq/model_expansions/evaluators/control.py +144 -0
- classiq/model_expansions/evaluators/parameter_types.py +227 -0
- classiq/model_expansions/evaluators/quantum_type_utils.py +235 -0
- classiq/model_expansions/evaluators/type_type_match.py +90 -0
- classiq/model_expansions/expression_evaluator.py +125 -0
- classiq/model_expansions/expression_renamer.py +76 -0
- classiq/model_expansions/function_builder.py +192 -0
- classiq/model_expansions/generative_functions.py +101 -0
- classiq/model_expansions/interpreter.py +365 -0
- classiq/model_expansions/model_tables.py +105 -0
- classiq/model_expansions/quantum_operations/__init__.py +19 -0
- classiq/model_expansions/quantum_operations/bind.py +64 -0
- classiq/model_expansions/quantum_operations/classicalif.py +39 -0
- classiq/model_expansions/quantum_operations/control.py +235 -0
- classiq/model_expansions/quantum_operations/emitter.py +215 -0
- classiq/model_expansions/quantum_operations/expression_operation.py +218 -0
- classiq/model_expansions/quantum_operations/inplace_binary_operation.py +250 -0
- classiq/model_expansions/quantum_operations/invert.py +38 -0
- classiq/model_expansions/quantum_operations/power.py +74 -0
- classiq/model_expansions/quantum_operations/quantum_assignment_operation.py +174 -0
- classiq/model_expansions/quantum_operations/quantum_function_call.py +15 -0
- classiq/model_expansions/quantum_operations/repeat.py +33 -0
- classiq/model_expansions/quantum_operations/variable_decleration.py +28 -0
- classiq/model_expansions/quantum_operations/within_apply.py +46 -0
- classiq/model_expansions/scope.py +226 -0
- classiq/model_expansions/scope_initialization.py +136 -0
- classiq/model_expansions/sympy_conversion/__init__.py +0 -0
- classiq/model_expansions/sympy_conversion/arithmetics.py +49 -0
- classiq/model_expansions/sympy_conversion/expression_to_sympy.py +150 -0
- classiq/model_expansions/sympy_conversion/sympy_to_python.py +113 -0
- classiq/model_expansions/utils/__init__.py +0 -0
- classiq/model_expansions/utils/counted_name_allocator.py +11 -0
- classiq/model_expansions/visitors/__init__.py +0 -0
- classiq/model_expansions/visitors/boolean_expression_transformers.py +214 -0
- classiq/model_expansions/visitors/variable_references.py +115 -0
- classiq/qmod/__init__.py +1 -3
- classiq/qmod/builtins/enums.py +33 -2
- classiq/qmod/builtins/functions/__init__.py +251 -0
- classiq/qmod/builtins/functions/amplitude_estimation.py +27 -0
- classiq/qmod/builtins/functions/arithmetic.py +68 -0
- classiq/qmod/builtins/functions/benchmarking.py +8 -0
- classiq/qmod/builtins/functions/chemistry.py +91 -0
- classiq/qmod/builtins/functions/discrete_sine_cosine_transform.py +105 -0
- classiq/qmod/builtins/functions/exponentiation.py +111 -0
- classiq/qmod/builtins/functions/finance.py +34 -0
- classiq/qmod/builtins/functions/grover.py +178 -0
- classiq/qmod/builtins/functions/hea.py +59 -0
- classiq/qmod/builtins/functions/linear_pauli_rotation.py +65 -0
- classiq/qmod/builtins/functions/modular_exponentiation.py +137 -0
- classiq/qmod/builtins/functions/operators.py +22 -0
- classiq/qmod/builtins/functions/qaoa_penalty.py +116 -0
- classiq/qmod/builtins/functions/qft.py +23 -0
- classiq/qmod/builtins/functions/qpe.py +39 -0
- classiq/qmod/builtins/functions/qsvm.py +24 -0
- classiq/qmod/builtins/functions/qsvt.py +136 -0
- classiq/qmod/builtins/functions/standard_gates.py +739 -0
- classiq/qmod/builtins/functions/state_preparation.py +357 -0
- classiq/qmod/builtins/functions/swap_test.py +25 -0
- classiq/qmod/builtins/structs.py +50 -28
- classiq/qmod/cparam.py +64 -0
- classiq/qmod/create_model_function.py +190 -0
- classiq/qmod/declaration_inferrer.py +52 -81
- classiq/qmod/expression_query.py +16 -0
- classiq/qmod/generative.py +48 -0
- classiq/qmod/model_state_container.py +1 -2
- classiq/qmod/native/pretty_printer.py +7 -11
- classiq/qmod/pretty_print/pretty_printer.py +7 -11
- classiq/qmod/python_classical_type.py +67 -0
- classiq/qmod/qfunc.py +19 -4
- classiq/qmod/qmod_parameter.py +15 -64
- classiq/qmod/qmod_variable.py +27 -45
- classiq/qmod/quantum_callable.py +1 -1
- classiq/qmod/quantum_expandable.py +10 -4
- classiq/qmod/quantum_function.py +22 -40
- classiq/qmod/semantics/error_manager.py +22 -10
- classiq/qmod/semantics/static_semantics_visitor.py +10 -12
- classiq/qmod/semantics/validation/types_validation.py +6 -7
- classiq/qmod/utilities.py +2 -2
- classiq/qmod/write_qmod.py +14 -0
- classiq/show.py +10 -0
- classiq/synthesis.py +46 -2
- {classiq-0.45.1.dist-info → classiq-0.46.1.dist-info}/METADATA +1 -1
- {classiq-0.45.1.dist-info → classiq-0.46.1.dist-info}/RECORD +138 -74
- classiq/interface/generator/functions/builtins/core_library/__init__.py +0 -16
- classiq/interface/generator/functions/builtins/core_library/atomic_quantum_functions.py +0 -710
- classiq/interface/generator/functions/builtins/core_library/exponentiation_functions.py +0 -105
- classiq/interface/generator/functions/builtins/open_lib_functions.py +0 -2489
- classiq/interface/generator/functions/builtins/quantum_operators.py +0 -24
- classiq/interface/generator/types/builtin_struct_declarations/__init__.py +0 -1
- classiq/interface/generator/types/builtin_struct_declarations/pauli_struct_declarations.py +0 -21
- classiq/qmod/builtins/functions.py +0 -1029
- {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
|