classiq 0.84.0__py3-none-any.whl → 0.86.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.
- classiq/applications/combinatorial_optimization/combinatorial_problem.py +24 -45
- classiq/evaluators/classical_expression.py +32 -15
- classiq/evaluators/qmod_annotated_expression.py +207 -0
- classiq/evaluators/qmod_expression_visitors/__init__.py +0 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_bwc.py +134 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_evaluator.py +232 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_renamer.py +44 -0
- classiq/evaluators/qmod_expression_visitors/qmod_expression_simplifier.py +308 -0
- classiq/evaluators/qmod_node_evaluators/__init__.py +0 -0
- classiq/evaluators/qmod_node_evaluators/attribute_evaluation.py +112 -0
- classiq/evaluators/qmod_node_evaluators/binary_op_evaluation.py +132 -0
- classiq/evaluators/qmod_node_evaluators/bool_op_evaluation.py +70 -0
- classiq/evaluators/qmod_node_evaluators/classical_function_evaluation.py +311 -0
- classiq/evaluators/qmod_node_evaluators/compare_evaluation.py +107 -0
- classiq/evaluators/qmod_node_evaluators/constant_evaluation.py +67 -0
- classiq/evaluators/qmod_node_evaluators/list_evaluation.py +107 -0
- classiq/evaluators/qmod_node_evaluators/measurement_evaluation.py +25 -0
- classiq/evaluators/qmod_node_evaluators/name_evaluation.py +50 -0
- classiq/evaluators/qmod_node_evaluators/struct_instantiation_evaluation.py +66 -0
- classiq/evaluators/qmod_node_evaluators/subscript_evaluation.py +225 -0
- classiq/evaluators/qmod_node_evaluators/unary_op_evaluation.py +58 -0
- classiq/evaluators/qmod_node_evaluators/utils.py +80 -0
- classiq/execution/execution_session.py +53 -6
- classiq/interface/_version.py +1 -1
- classiq/interface/analyzer/analysis_params.py +1 -1
- classiq/interface/analyzer/result.py +1 -1
- classiq/interface/debug_info/debug_info.py +0 -4
- classiq/interface/executor/quantum_code.py +2 -2
- classiq/interface/generator/arith/arithmetic_expression_validator.py +5 -1
- classiq/interface/generator/arith/binary_ops.py +43 -51
- classiq/interface/generator/arith/number_utils.py +3 -2
- classiq/interface/generator/arith/register_user_input.py +15 -0
- classiq/interface/generator/arith/unary_ops.py +32 -28
- classiq/interface/generator/expressions/atomic_expression_functions.py +5 -0
- classiq/interface/generator/expressions/expression_types.py +2 -2
- classiq/interface/generator/expressions/proxies/classical/qmod_struct_instance.py +7 -0
- classiq/interface/generator/functions/builtins/internal_operators.py +2 -0
- classiq/interface/generator/functions/classical_function_declaration.py +0 -4
- classiq/interface/generator/functions/classical_type.py +0 -32
- classiq/interface/generator/functions/concrete_types.py +20 -0
- classiq/interface/generator/generated_circuit_data.py +7 -10
- classiq/interface/generator/quantum_program.py +6 -1
- classiq/interface/generator/synthesis_metadata/synthesis_execution_data.py +29 -0
- classiq/interface/ide/operation_registry.py +45 -0
- classiq/interface/ide/visual_model.py +84 -2
- classiq/interface/model/bounds.py +12 -2
- classiq/interface/model/quantum_expressions/arithmetic_operation.py +7 -4
- classiq/interface/model/quantum_type.py +67 -33
- classiq/interface/model/variable_declaration_statement.py +33 -6
- classiq/model_expansions/arithmetic.py +115 -0
- classiq/model_expansions/arithmetic_compute_result_attrs.py +71 -0
- classiq/model_expansions/atomic_expression_functions_defs.py +10 -6
- classiq/model_expansions/function_builder.py +4 -1
- classiq/model_expansions/generative_functions.py +15 -2
- classiq/model_expansions/interpreters/base_interpreter.py +7 -0
- classiq/model_expansions/interpreters/generative_interpreter.py +18 -1
- classiq/model_expansions/quantum_operations/assignment_result_processor.py +63 -21
- classiq/model_expansions/quantum_operations/bounds.py +7 -1
- classiq/model_expansions/quantum_operations/call_emitter.py +5 -2
- classiq/model_expansions/quantum_operations/classical_var_emitter.py +16 -0
- classiq/model_expansions/quantum_operations/variable_decleration.py +30 -10
- classiq/model_expansions/scope.py +7 -0
- classiq/model_expansions/scope_initialization.py +2 -0
- classiq/model_expansions/sympy_conversion/sympy_to_python.py +1 -1
- classiq/model_expansions/transformers/type_modifier_inference.py +5 -0
- classiq/model_expansions/transformers/var_splitter.py +1 -1
- classiq/model_expansions/visitors/boolean_expression_transformers.py +1 -1
- classiq/open_library/functions/__init__.py +0 -2
- classiq/open_library/functions/qaoa_penalty.py +8 -1
- classiq/open_library/functions/state_preparation.py +1 -32
- classiq/qmod/__init__.py +2 -0
- classiq/qmod/builtins/operations.py +66 -2
- classiq/qmod/classical_variable.py +74 -0
- classiq/qmod/declaration_inferrer.py +5 -3
- classiq/qmod/native/pretty_printer.py +18 -14
- classiq/qmod/pretty_print/pretty_printer.py +34 -15
- classiq/qmod/qfunc.py +2 -19
- classiq/qmod/qmod_variable.py +5 -8
- classiq/qmod/quantum_expandable.py +1 -1
- classiq/qmod/quantum_function.py +42 -2
- classiq/qmod/symbolic_type.py +2 -1
- classiq/qmod/write_qmod.py +3 -1
- {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/METADATA +1 -1
- {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/RECORD +86 -62
- classiq/interface/model/quantum_variable_declaration.py +0 -7
- /classiq/{model_expansions/sympy_conversion/arithmetics.py → evaluators/qmod_expression_visitors/sympy_wrappers.py} +0 -0
- {classiq-0.84.0.dist-info → classiq-0.86.0.dist-info}/WHEEL +0 -0
@@ -52,8 +52,6 @@ VISUALIZATION_HIDE_LIST = [
|
|
52
52
|
"stmt_block",
|
53
53
|
]
|
54
54
|
|
55
|
-
CONTROLLED_PREFIX = "c_"
|
56
|
-
|
57
55
|
|
58
56
|
def last_name_in_call_hierarchy(name: str) -> str:
|
59
57
|
return name.split(CLASSIQ_HIERARCHY_SEPARATOR)[-1]
|
@@ -147,6 +145,8 @@ class StatementType(StrEnum):
|
|
147
145
|
INPLACE_XOR = "inplace xor"
|
148
146
|
INPLACE_ADD = "inplace add"
|
149
147
|
REPEAT = "repeat"
|
148
|
+
BLOCK = "block"
|
149
|
+
IF = "if"
|
150
150
|
|
151
151
|
|
152
152
|
# Mapping between statement kind (or sub-kind) and statement type (visualization name)
|
@@ -167,6 +167,8 @@ STATEMENTS_NAME: dict[str, StatementType] = {
|
|
167
167
|
ArithmeticOperationKind.InplaceXor.value: StatementType.INPLACE_XOR,
|
168
168
|
ArithmeticOperationKind.InplaceAdd.value: StatementType.INPLACE_ADD,
|
169
169
|
"Repeat": StatementType.REPEAT,
|
170
|
+
"Block": StatementType.BLOCK,
|
171
|
+
"ClassicalIf": StatementType.IF,
|
170
172
|
}
|
171
173
|
|
172
174
|
|
@@ -207,8 +209,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
207
209
|
ARITH_ENGINE_PREFIX
|
208
210
|
)
|
209
211
|
name_with_suffix = self.add_suffix_from_generated_name(generated_name, name)
|
210
|
-
|
211
|
-
return modified_name
|
212
|
+
return name_with_suffix
|
212
213
|
|
213
214
|
statement_kind: str = back_ref.kind
|
214
215
|
if isinstance(back_ref, ArithmeticOperation):
|
@@ -217,11 +218,6 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
217
218
|
generated_name, STATEMENTS_NAME[statement_kind]
|
218
219
|
)
|
219
220
|
|
220
|
-
def modify_name_for_controlled_qfunc(self, generated_name: str) -> str:
|
221
|
-
if self.control_variable is None:
|
222
|
-
return generated_name
|
223
|
-
return f"{CONTROLLED_PREFIX}{generated_name}"
|
224
|
-
|
225
221
|
def add_suffix_from_generated_name(self, generated_name: str, name: str) -> str:
|
226
222
|
if part_match := PART_SUFFIX_REGEX.match(generated_name):
|
227
223
|
suffix = f" [{part_match.group(1)}]"
|
@@ -272,7 +268,8 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
272
268
|
for register in self.registers
|
273
269
|
for qubit in register.qubit_indexes_absolute
|
274
270
|
if register.role is RegisterRole.INPUT
|
275
|
-
and register.name
|
271
|
+
and self.port_to_passed_variable_map.get(register.name, register.name)
|
272
|
+
== self.control_variable
|
276
273
|
)
|
277
274
|
|
278
275
|
def propagate_absolute_qubits(self) -> "FunctionDebugInfoInterface":
|
@@ -76,6 +76,8 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
|
|
76
76
|
execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
|
77
77
|
synthesis_warnings: Optional[list[str]] = pydantic.Field(default=None)
|
78
78
|
should_warn: bool = pydantic.Field(default=False)
|
79
|
+
# Unique identifier for the circuit (since the program_id might change when running show). Used for the circuit store.
|
80
|
+
circuit_id: str = pydantic.Field(default_factory=get_uuid_as_str)
|
79
81
|
|
80
82
|
def __str__(self) -> str:
|
81
83
|
return self.model_dump_json(indent=2)
|
@@ -168,7 +170,10 @@ class QuantumProgram(VersionedModel, CircuitCodeInterface):
|
|
168
170
|
|
169
171
|
@property
|
170
172
|
def _can_use_transpiled_code(self) -> bool:
|
171
|
-
return
|
173
|
+
return (
|
174
|
+
self.data.execution_data is None
|
175
|
+
or not self.data.execution_data.function_execution
|
176
|
+
)
|
172
177
|
|
173
178
|
@property
|
174
179
|
def program_circuit(self) -> CircuitCodeInterface:
|
@@ -3,6 +3,7 @@ from typing import Optional
|
|
3
3
|
|
4
4
|
import pydantic
|
5
5
|
import sympy
|
6
|
+
from typing_extensions import Self
|
6
7
|
|
7
8
|
from classiq.interface.backend.pydantic_backend import PydanticExecutionParameter
|
8
9
|
from classiq.interface.generator.parameters import ParameterType
|
@@ -34,3 +35,31 @@ class ExecutionData(pydantic.BaseModel):
|
|
34
35
|
if function_execution_data.power_vars is not None
|
35
36
|
)
|
36
37
|
)
|
38
|
+
|
39
|
+
def to_inverse(self) -> Self:
|
40
|
+
return type(self)(
|
41
|
+
function_execution={
|
42
|
+
self._inverse_name(name): value
|
43
|
+
for name, value in self.function_execution.items()
|
44
|
+
}
|
45
|
+
)
|
46
|
+
|
47
|
+
def to_control(self) -> Self:
|
48
|
+
return type(self)(
|
49
|
+
function_execution={
|
50
|
+
self._control_name(name): value
|
51
|
+
for name, value in self.function_execution.items()
|
52
|
+
}
|
53
|
+
)
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
def _inverse_name(name: str) -> str:
|
57
|
+
# see inverse of qiskit.circuit.Instruction
|
58
|
+
if name.endswith("_dg"):
|
59
|
+
return name[:-3]
|
60
|
+
return f"{name}_dg"
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def _control_name(name: str) -> str:
|
64
|
+
# see inverse of qiskit.circuit.QuantumCircuit
|
65
|
+
return f"c_{name}"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from classiq.interface.ide.visual_model import Operation
|
4
|
+
|
5
|
+
|
6
|
+
class OperationRegistry:
|
7
|
+
def __init__(self) -> None:
|
8
|
+
self._operation_hash_to_op_id: dict[int, int] = {}
|
9
|
+
self._id_to_operations: dict[int, Operation] = {}
|
10
|
+
self._unique_op_counter = 0
|
11
|
+
self._deduped_op_counter = 0
|
12
|
+
|
13
|
+
def build_operation(self, **kwargs: Any) -> Operation:
|
14
|
+
operation = Operation(**kwargs)
|
15
|
+
return self._add_operation(operation)
|
16
|
+
|
17
|
+
def _add_operation(self, op: Operation) -> Operation:
|
18
|
+
"""
|
19
|
+
Adds an operation to the global dictionaries for operations.
|
20
|
+
if operation already exist in the registry, it returns the existing operation.
|
21
|
+
"""
|
22
|
+
op_hash = hash(op)
|
23
|
+
if op_hash not in self._operation_hash_to_op_id:
|
24
|
+
self._operation_hash_to_op_id[op_hash] = op.id
|
25
|
+
self._id_to_operations[op.id] = op
|
26
|
+
self._unique_op_counter += 1
|
27
|
+
else:
|
28
|
+
self._deduped_op_counter += 1
|
29
|
+
op = self._id_to_operations[self._operation_hash_to_op_id[op_hash]]
|
30
|
+
return op
|
31
|
+
|
32
|
+
def get_operation_mapping(self) -> dict[int, Operation]:
|
33
|
+
return self._id_to_operations
|
34
|
+
|
35
|
+
def get_operations(self, op_ids: list[int]) -> list[Operation]:
|
36
|
+
"""
|
37
|
+
Returns a list of operations based on their IDs.
|
38
|
+
"""
|
39
|
+
return [self._id_to_operations[op_id] for op_id in op_ids]
|
40
|
+
|
41
|
+
def get_unique_op_number(self) -> int:
|
42
|
+
return self._unique_op_counter
|
43
|
+
|
44
|
+
def get_deduped_op_number(self) -> int:
|
45
|
+
return self._deduped_op_counter
|
@@ -1,9 +1,12 @@
|
|
1
|
+
import json
|
1
2
|
from collections import Counter
|
3
|
+
from collections.abc import Iterator
|
2
4
|
from functools import cached_property
|
5
|
+
from itertools import count
|
3
6
|
from typing import Any, Optional
|
4
7
|
|
5
8
|
import pydantic
|
6
|
-
from pydantic import ConfigDict
|
9
|
+
from pydantic import ConfigDict, field_validator
|
7
10
|
|
8
11
|
from classiq.interface.enum_utils import StrEnum
|
9
12
|
from classiq.interface.generator.generated_circuit_data import (
|
@@ -13,6 +16,26 @@ from classiq.interface.generator.hardware.hardware_data import SynthesisHardware
|
|
13
16
|
from classiq.interface.helpers.versioned_model import VersionedModel
|
14
17
|
|
15
18
|
|
19
|
+
class OperationIdCounter:
|
20
|
+
_op_id_counter: Iterator[int] = count()
|
21
|
+
|
22
|
+
def next_id(self) -> int:
|
23
|
+
return next(self._op_id_counter)
|
24
|
+
|
25
|
+
def reset_operation_counter(self) -> None:
|
26
|
+
self._op_id_counter = count()
|
27
|
+
|
28
|
+
|
29
|
+
_operation_id_counter = OperationIdCounter()
|
30
|
+
|
31
|
+
|
32
|
+
def reset_operation_counter() -> None:
|
33
|
+
"""
|
34
|
+
Call this at the start of every new task to restart ids at 0.
|
35
|
+
"""
|
36
|
+
_operation_id_counter.reset_operation_counter()
|
37
|
+
|
38
|
+
|
16
39
|
class OperationType(StrEnum):
|
17
40
|
REGULAR = "REGULAR"
|
18
41
|
INVISIBLE = "INVISIBLE"
|
@@ -27,6 +50,8 @@ class OperationData(pydantic.BaseModel):
|
|
27
50
|
width: int
|
28
51
|
gate_count: Counter[str] = pydantic.Field(default_factory=dict)
|
29
52
|
|
53
|
+
model_config = ConfigDict(frozen=True)
|
54
|
+
|
30
55
|
|
31
56
|
class CircuitMetrics(pydantic.BaseModel):
|
32
57
|
depth: int
|
@@ -60,6 +85,21 @@ class OperationLinks(pydantic.BaseModel):
|
|
60
85
|
inputs: list[OperationLink]
|
61
86
|
outputs: list[OperationLink]
|
62
87
|
|
88
|
+
model_config = ConfigDict(frozen=True)
|
89
|
+
|
90
|
+
@field_validator("inputs", "outputs", mode="after")
|
91
|
+
@classmethod
|
92
|
+
def sort_links(cls, v: list[OperationLink]) -> Any:
|
93
|
+
"""
|
94
|
+
sorting the input/output links on creation
|
95
|
+
the sort is done by 'label-qubits-type'
|
96
|
+
since hash is non-deterministic between runs
|
97
|
+
"""
|
98
|
+
return sorted(v, key=hash)
|
99
|
+
|
100
|
+
def __hash__(self) -> int:
|
101
|
+
return hash(json.dumps(self.model_dump(exclude_none=True), sort_keys=True))
|
102
|
+
|
63
103
|
@cached_property
|
64
104
|
def input_width(self) -> int:
|
65
105
|
return sum(len(link.qubits) for link in self.inputs)
|
@@ -108,6 +148,8 @@ class AtomicGate(StrEnum):
|
|
108
148
|
|
109
149
|
class Operation(pydantic.BaseModel):
|
110
150
|
name: str
|
151
|
+
inner_label: Optional[str] = None
|
152
|
+
_id: int = pydantic.PrivateAttr(default_factory=_operation_id_counter.next_id)
|
111
153
|
qasm_name: str = pydantic.Field(default="")
|
112
154
|
details: str = pydantic.Field(default="")
|
113
155
|
children: list["Operation"] = pydantic.Field(default_factory=list)
|
@@ -120,7 +162,7 @@ class Operation(pydantic.BaseModel):
|
|
120
162
|
target_qubits: tuple[int, ...]
|
121
163
|
operation_level: OperationLevel
|
122
164
|
operation_type: OperationType = pydantic.Field(
|
123
|
-
description="Identifies unique operations that are visualized differently"
|
165
|
+
description="Identifies unique operations that are visualized differently",
|
124
166
|
)
|
125
167
|
gate: AtomicGate = pydantic.Field(
|
126
168
|
default=AtomicGate.UNKNOWN, description="Gate type"
|
@@ -128,6 +170,40 @@ class Operation(pydantic.BaseModel):
|
|
128
170
|
is_daggered: bool = pydantic.Field(default=False)
|
129
171
|
expanded: bool = pydantic.Field(default=False)
|
130
172
|
show_expanded_label: bool = pydantic.Field(default=False)
|
173
|
+
is_low_level_fallback: bool = pydantic.Field(default=False)
|
174
|
+
|
175
|
+
model_config = ConfigDict(frozen=True)
|
176
|
+
|
177
|
+
@property
|
178
|
+
def id(self) -> int:
|
179
|
+
return self._id
|
180
|
+
|
181
|
+
def __hash__(self) -> int:
|
182
|
+
"""
|
183
|
+
using a custom hashable_dict in order to compare the operation
|
184
|
+
with the qubits in order
|
185
|
+
"""
|
186
|
+
js = json.dumps(
|
187
|
+
self._hashable_dict(),
|
188
|
+
sort_keys=True,
|
189
|
+
default=lambda o: o.value if hasattr(o, "value") else str(o),
|
190
|
+
)
|
191
|
+
return hash(js)
|
192
|
+
|
193
|
+
def __eq__(self, other: object) -> bool:
|
194
|
+
return (
|
195
|
+
isinstance(other, Operation)
|
196
|
+
and self._hashable_dict() == other._hashable_dict()
|
197
|
+
)
|
198
|
+
|
199
|
+
def _hashable_dict(self) -> dict:
|
200
|
+
data = self.model_dump(
|
201
|
+
exclude_none=True,
|
202
|
+
)
|
203
|
+
# force qubit order for equality
|
204
|
+
for key in ("target_qubits", "auxiliary_qubits", "control_qubits"):
|
205
|
+
data[key] = sorted(data[key])
|
206
|
+
return data
|
131
207
|
|
132
208
|
|
133
209
|
class ProgramVisualModel(VersionedModel):
|
@@ -135,3 +211,9 @@ class ProgramVisualModel(VersionedModel):
|
|
135
211
|
id_to_operations: dict[int, Operation] = pydantic.Field(default_factory=dict)
|
136
212
|
main_operation_id: int = pydantic.Field(default=None)
|
137
213
|
program_data: ProgramData
|
214
|
+
|
215
|
+
@property
|
216
|
+
def main_op_from_mapping(self) -> Operation:
|
217
|
+
if self.main_operation_id is None:
|
218
|
+
raise ValueError("Main operation ID is not set.")
|
219
|
+
return self.id_to_operations[self.main_operation_id]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing import Literal, Optional
|
2
2
|
|
3
|
-
from classiq.interface.
|
3
|
+
from classiq.interface.generator.expressions.expression import Expression
|
4
4
|
from classiq.interface.model.handle_binding import ConcreteHandleBinding
|
5
5
|
from classiq.interface.model.quantum_statement import QuantumOperation
|
6
6
|
|
@@ -9,4 +9,14 @@ class SetBoundsStatement(QuantumOperation):
|
|
9
9
|
kind: Literal["SetBoundsStatement"]
|
10
10
|
|
11
11
|
target: ConcreteHandleBinding
|
12
|
-
|
12
|
+
lower_bound: Optional[Expression]
|
13
|
+
upper_bound: Optional[Expression]
|
14
|
+
|
15
|
+
@property
|
16
|
+
def expressions(self) -> list[Expression]:
|
17
|
+
exprs = []
|
18
|
+
if self.lower_bound is not None:
|
19
|
+
exprs.append(self.lower_bound)
|
20
|
+
if self.upper_bound is not None:
|
21
|
+
exprs.append(self.upper_bound)
|
22
|
+
return exprs
|
@@ -1,6 +1,8 @@
|
|
1
1
|
from collections.abc import Mapping, Sequence
|
2
2
|
from typing import Literal
|
3
3
|
|
4
|
+
from pydantic import PrivateAttr
|
5
|
+
|
4
6
|
from classiq.interface.enum_utils import StrEnum
|
5
7
|
from classiq.interface.generator.arith.arithmetic import (
|
6
8
|
ARITHMETIC_EXPRESSION_RESULT_NAME,
|
@@ -27,6 +29,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
27
29
|
kind: Literal["ArithmeticOperation"]
|
28
30
|
|
29
31
|
operation_kind: ArithmeticOperationKind
|
32
|
+
_classical_assignment: bool = PrivateAttr(default=False)
|
30
33
|
|
31
34
|
@property
|
32
35
|
def is_inplace(self) -> bool:
|
@@ -50,7 +53,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
50
53
|
self,
|
51
54
|
) -> Mapping[str, ConcreteHandleBinding]:
|
52
55
|
inouts = dict(super().wiring_inouts)
|
53
|
-
if self.is_inplace:
|
56
|
+
if self.is_inplace and not self._classical_assignment:
|
54
57
|
inouts[self.result_name()] = self.result_var
|
55
58
|
return inouts
|
56
59
|
|
@@ -60,7 +63,7 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
60
63
|
HandleMetadata(handle=handle, readable_location="in an expression")
|
61
64
|
for handle in self.var_handles
|
62
65
|
]
|
63
|
-
if self.is_inplace:
|
66
|
+
if self.is_inplace and not self._classical_assignment:
|
64
67
|
inouts.append(
|
65
68
|
HandleMetadata(
|
66
69
|
handle=self.result_var,
|
@@ -71,13 +74,13 @@ class ArithmeticOperation(QuantumAssignmentOperation):
|
|
71
74
|
|
72
75
|
@property
|
73
76
|
def wiring_outputs(self) -> Mapping[str, HandleBinding]:
|
74
|
-
if self.is_inplace:
|
77
|
+
if self.is_inplace or self._classical_assignment:
|
75
78
|
return {}
|
76
79
|
return super().wiring_outputs
|
77
80
|
|
78
81
|
@property
|
79
82
|
def readable_outputs(self) -> Sequence[HandleMetadata]:
|
80
|
-
if self.is_inplace:
|
83
|
+
if self.is_inplace or self._classical_assignment:
|
81
84
|
return []
|
82
85
|
return [
|
83
86
|
HandleMetadata(
|
@@ -85,6 +85,35 @@ class QuantumScalar(QuantumType):
|
|
85
85
|
def get_proxy(self, handle: "HandleBinding") -> QmodQScalarProxy:
|
86
86
|
return QmodQScalarProxy(handle, size=self.size_in_bits)
|
87
87
|
|
88
|
+
@property
|
89
|
+
def has_sign(self) -> bool:
|
90
|
+
raise NotImplementedError
|
91
|
+
|
92
|
+
@property
|
93
|
+
def sign_value(self) -> bool:
|
94
|
+
raise NotImplementedError
|
95
|
+
|
96
|
+
@property
|
97
|
+
def has_fraction_digits(self) -> bool:
|
98
|
+
raise NotImplementedError
|
99
|
+
|
100
|
+
@property
|
101
|
+
def fraction_digits_value(self) -> int:
|
102
|
+
raise NotImplementedError
|
103
|
+
|
104
|
+
def get_effective_bounds(
|
105
|
+
self, machine_precision: Optional[int] = None
|
106
|
+
) -> tuple[float, float]:
|
107
|
+
raise NotImplementedError
|
108
|
+
|
109
|
+
@property
|
110
|
+
def is_qbit(self) -> bool:
|
111
|
+
return (
|
112
|
+
self.size_in_bits == 1
|
113
|
+
and self.fraction_digits_value == 0
|
114
|
+
and not self.sign_value
|
115
|
+
)
|
116
|
+
|
88
117
|
|
89
118
|
class QuantumBit(QuantumScalar):
|
90
119
|
kind: Literal["qbit"]
|
@@ -117,6 +146,27 @@ class QuantumBit(QuantumScalar):
|
|
117
146
|
def is_evaluated(self) -> bool:
|
118
147
|
return True
|
119
148
|
|
149
|
+
@property
|
150
|
+
def has_sign(self) -> bool:
|
151
|
+
return True
|
152
|
+
|
153
|
+
@property
|
154
|
+
def sign_value(self) -> bool:
|
155
|
+
return False
|
156
|
+
|
157
|
+
@property
|
158
|
+
def has_fraction_digits(self) -> bool:
|
159
|
+
return True
|
160
|
+
|
161
|
+
@property
|
162
|
+
def fraction_digits_value(self) -> int:
|
163
|
+
return 0
|
164
|
+
|
165
|
+
def get_effective_bounds(
|
166
|
+
self, machine_precision: Optional[int] = None
|
167
|
+
) -> tuple[float, float]:
|
168
|
+
return (0, 1)
|
169
|
+
|
120
170
|
|
121
171
|
class QuantumBitvector(QuantumType):
|
122
172
|
element_type: "ConcreteQuantumType" = Field(
|
@@ -243,14 +293,6 @@ class QuantumNumeric(QuantumScalar):
|
|
243
293
|
0 if self.fraction_digits is None else self.fraction_digits.to_int_value()
|
244
294
|
)
|
245
295
|
|
246
|
-
@property
|
247
|
-
def is_qbit(self) -> bool:
|
248
|
-
return (
|
249
|
-
self.size_in_bits == 1
|
250
|
-
and self.fraction_digits_value == 0
|
251
|
-
and not self.sign_value
|
252
|
-
)
|
253
|
-
|
254
296
|
def _update_size_in_bits_from_declaration(self) -> None:
|
255
297
|
if self.size is not None and self.size.is_evaluated():
|
256
298
|
self._size_in_bits = self.size.to_int_value()
|
@@ -302,20 +344,8 @@ class QuantumNumeric(QuantumScalar):
|
|
302
344
|
exprs.append(self.fraction_digits)
|
303
345
|
return exprs
|
304
346
|
|
305
|
-
def get_bounds(
|
306
|
-
self
|
307
|
-
) -> Optional[tuple[float, float]]:
|
308
|
-
if (
|
309
|
-
self._bounds is None
|
310
|
-
or machine_precision is None
|
311
|
-
or self.fraction_digits_value <= machine_precision
|
312
|
-
):
|
313
|
-
return self._bounds
|
314
|
-
|
315
|
-
return (
|
316
|
-
number_utils.limit_fraction_places(self._bounds[0], machine_precision),
|
317
|
-
number_utils.limit_fraction_places(self._bounds[1], machine_precision),
|
318
|
-
)
|
347
|
+
def get_bounds(self) -> Optional[tuple[float, float]]:
|
348
|
+
return self._bounds
|
319
349
|
|
320
350
|
def set_bounds(self, bounds: Optional[tuple[float, float]]) -> None:
|
321
351
|
self._bounds = bounds
|
@@ -323,19 +353,23 @@ class QuantumNumeric(QuantumScalar):
|
|
323
353
|
def reset_bounds(self) -> None:
|
324
354
|
self.set_bounds(None)
|
325
355
|
|
326
|
-
def get_maximal_bounds(
|
356
|
+
def get_maximal_bounds(self) -> tuple[float, float]:
|
357
|
+
return RegisterArithmeticInfo.get_maximal_bounds(
|
358
|
+
size=self.size_in_bits,
|
359
|
+
is_signed=self.sign_value,
|
360
|
+
fraction_places=self.fraction_digits_value,
|
361
|
+
)
|
362
|
+
|
363
|
+
def get_effective_bounds(
|
327
364
|
self, machine_precision: Optional[int] = None
|
328
365
|
) -> tuple[float, float]:
|
329
|
-
|
330
|
-
fraction_digits = self.fraction_digits_value
|
331
|
-
if machine_precision is not None and fraction_digits > machine_precision:
|
332
|
-
size -= fraction_digits - machine_precision
|
333
|
-
fraction_digits = machine_precision
|
366
|
+
bounds = self.get_bounds() or self.get_maximal_bounds()
|
334
367
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
368
|
+
if machine_precision is None or machine_precision >= self.fraction_digits_value:
|
369
|
+
return bounds
|
370
|
+
return (
|
371
|
+
number_utils.limit_fraction_places(bounds[0], machine_precision),
|
372
|
+
number_utils.limit_fraction_places(bounds[1], machine_precision),
|
339
373
|
)
|
340
374
|
|
341
375
|
|
@@ -359,7 +393,7 @@ def register_info_to_quantum_type(reg_info: RegisterArithmeticInfo) -> QuantumNu
|
|
359
393
|
result.set_size_in_bits(reg_info.size)
|
360
394
|
result.is_signed = Expression(expr=str(reg_info.is_signed))
|
361
395
|
result.fraction_digits = Expression(expr=str(reg_info.fraction_places))
|
362
|
-
result.set_bounds(reg_info.bounds)
|
396
|
+
result.set_bounds(tuple(reg_info.bounds)) # type: ignore[arg-type]
|
363
397
|
return result
|
364
398
|
|
365
399
|
|
@@ -1,15 +1,42 @@
|
|
1
|
-
from typing import Literal
|
1
|
+
from typing import Any, Literal, Optional
|
2
|
+
|
3
|
+
import pydantic
|
2
4
|
|
3
5
|
from classiq.interface.generator.expressions.expression import Expression
|
4
|
-
from classiq.interface.
|
5
|
-
|
6
|
-
|
6
|
+
from classiq.interface.generator.functions.concrete_types import (
|
7
|
+
ConcreteQuantumType,
|
8
|
+
ConcreteType,
|
7
9
|
)
|
10
|
+
from classiq.interface.model.quantum_statement import QuantumStatement
|
11
|
+
from classiq.interface.model.quantum_type import QuantumType
|
8
12
|
|
9
13
|
|
10
|
-
class VariableDeclarationStatement(QuantumStatement
|
14
|
+
class VariableDeclarationStatement(QuantumStatement):
|
11
15
|
kind: Literal["VariableDeclarationStatement"]
|
12
16
|
|
17
|
+
name: str
|
18
|
+
quantum_type: Optional[ConcreteQuantumType] = None
|
19
|
+
qmod_type: ConcreteType
|
20
|
+
|
21
|
+
@pydantic.model_validator(mode="before")
|
22
|
+
@classmethod
|
23
|
+
def _set_qmod_type(cls, values: Any) -> dict[str, Any]:
|
24
|
+
if isinstance(values, dict):
|
25
|
+
if "quantum_type" in values and (
|
26
|
+
"qmod_type" not in values or values["qmod_type"] is None
|
27
|
+
):
|
28
|
+
values["qmod_type"] = values["quantum_type"]
|
29
|
+
values["quantum_type"] = None
|
30
|
+
return values
|
31
|
+
if values.quantum_type is not None and values.qmod_type is None:
|
32
|
+
values.qmod_type = values.quantum_type
|
33
|
+
values.quantum_type = None
|
34
|
+
return values
|
35
|
+
|
13
36
|
@property
|
14
37
|
def expressions(self) -> list[Expression]:
|
15
|
-
return self.
|
38
|
+
return self.qmod_type.expressions
|
39
|
+
|
40
|
+
@property
|
41
|
+
def is_quantum(self) -> bool:
|
42
|
+
return isinstance(self.qmod_type, QuantumType)
|
@@ -0,0 +1,115 @@
|
|
1
|
+
from dataclasses import dataclass
|
2
|
+
from typing import Optional, Union
|
3
|
+
|
4
|
+
from classiq.interface.generator.arith import number_utils
|
5
|
+
from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
|
6
|
+
from classiq.interface.model.quantum_type import (
|
7
|
+
QuantumScalar,
|
8
|
+
register_info_to_quantum_type,
|
9
|
+
)
|
10
|
+
|
11
|
+
|
12
|
+
@dataclass
|
13
|
+
class NumericAttributes:
|
14
|
+
size: int
|
15
|
+
is_signed: bool
|
16
|
+
fraction_digits: int
|
17
|
+
bounds: tuple[float, float]
|
18
|
+
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
size: int,
|
22
|
+
is_signed: bool,
|
23
|
+
fraction_digits: int,
|
24
|
+
bounds: Optional[tuple[float, float]] = None,
|
25
|
+
trim_bounds: bool = False,
|
26
|
+
) -> None:
|
27
|
+
self.size = size
|
28
|
+
self.is_signed = is_signed
|
29
|
+
self.fraction_digits = fraction_digits
|
30
|
+
if bounds is None:
|
31
|
+
bounds = RegisterArithmeticInfo.get_maximal_bounds(
|
32
|
+
size=size,
|
33
|
+
is_signed=is_signed,
|
34
|
+
fraction_places=fraction_digits,
|
35
|
+
)
|
36
|
+
if trim_bounds:
|
37
|
+
bounds = (
|
38
|
+
number_utils.limit_fraction_places(bounds[0], fraction_digits),
|
39
|
+
number_utils.limit_fraction_places(bounds[1], fraction_digits),
|
40
|
+
)
|
41
|
+
self.bounds = bounds
|
42
|
+
|
43
|
+
@property
|
44
|
+
def lb(self) -> float:
|
45
|
+
return self.bounds[0]
|
46
|
+
|
47
|
+
@property
|
48
|
+
def ub(self) -> float:
|
49
|
+
return self.bounds[1]
|
50
|
+
|
51
|
+
@classmethod
|
52
|
+
def from_bounds(
|
53
|
+
cls, lb: float, ub: float, fraction_places: int, machine_precision: int
|
54
|
+
) -> "NumericAttributes":
|
55
|
+
size, is_signed, fraction_digits = number_utils.bounds_to_attributes(
|
56
|
+
lb, ub, fraction_places, machine_precision
|
57
|
+
)
|
58
|
+
return cls(
|
59
|
+
size=size,
|
60
|
+
is_signed=is_signed,
|
61
|
+
fraction_digits=fraction_digits,
|
62
|
+
bounds=(lb, ub),
|
63
|
+
)
|
64
|
+
|
65
|
+
@classmethod
|
66
|
+
def from_constant(
|
67
|
+
cls,
|
68
|
+
value: float,
|
69
|
+
machine_precision: Optional[int] = None,
|
70
|
+
) -> "NumericAttributes":
|
71
|
+
if machine_precision is not None:
|
72
|
+
value = number_utils.limit_fraction_places(value, machine_precision)
|
73
|
+
|
74
|
+
return cls(
|
75
|
+
size=number_utils.size(value),
|
76
|
+
is_signed=value < 0,
|
77
|
+
fraction_digits=number_utils.fraction_places(value),
|
78
|
+
bounds=(value, value),
|
79
|
+
)
|
80
|
+
|
81
|
+
@classmethod
|
82
|
+
def from_quantum_scalar(
|
83
|
+
cls,
|
84
|
+
quantum_type: QuantumScalar,
|
85
|
+
machine_precision: Optional[int] = None,
|
86
|
+
) -> "NumericAttributes":
|
87
|
+
return cls(
|
88
|
+
size=quantum_type.size_in_bits,
|
89
|
+
is_signed=quantum_type.sign_value,
|
90
|
+
fraction_digits=quantum_type.fraction_digits_value,
|
91
|
+
bounds=quantum_type.get_effective_bounds(machine_precision),
|
92
|
+
)
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def from_register_arithmetic_info(
|
96
|
+
cls,
|
97
|
+
register: RegisterArithmeticInfo,
|
98
|
+
machine_precision: Optional[int] = None,
|
99
|
+
) -> "NumericAttributes":
|
100
|
+
return cls.from_quantum_scalar(
|
101
|
+
quantum_type=register_info_to_quantum_type(register),
|
102
|
+
machine_precision=machine_precision,
|
103
|
+
)
|
104
|
+
|
105
|
+
@classmethod
|
106
|
+
def from_type_or_constant(
|
107
|
+
cls,
|
108
|
+
from_: Union[float, QuantumScalar, RegisterArithmeticInfo],
|
109
|
+
machine_precision: Optional[int] = None,
|
110
|
+
) -> "NumericAttributes":
|
111
|
+
if isinstance(from_, QuantumScalar):
|
112
|
+
return cls.from_quantum_scalar(from_, machine_precision)
|
113
|
+
if isinstance(from_, RegisterArithmeticInfo):
|
114
|
+
return cls.from_register_arithmetic_info(from_, machine_precision)
|
115
|
+
return cls.from_constant(from_, machine_precision)
|