classiq 0.46.1__py3-none-any.whl → 0.47.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- classiq/_internals/api_wrapper.py +45 -8
- classiq/execution/execution_session.py +96 -39
- classiq/execution/jobs.py +108 -1
- classiq/interface/_version.py +1 -1
- classiq/interface/backend/quantum_backend_providers.py +0 -1
- classiq/interface/debug_info/debug_info.py +23 -1
- classiq/interface/execution/primitives.py +9 -0
- classiq/interface/generator/arith/arithmetic_operations.py +5 -2
- classiq/interface/generator/arith/binary_ops.py +21 -14
- classiq/interface/generator/arith/extremum_operations.py +9 -1
- classiq/interface/generator/arith/number_utils.py +6 -0
- classiq/interface/generator/arith/register_user_input.py +30 -21
- classiq/interface/generator/arith/unary_ops.py +13 -1
- classiq/interface/generator/generated_circuit_data.py +47 -2
- classiq/interface/generator/quantum_program.py +10 -2
- classiq/interface/ide/visual_model.py +7 -1
- classiq/interface/interface_version.py +1 -1
- classiq/interface/model/phase_operation.py +11 -0
- classiq/interface/model/statement_block.py +3 -0
- classiq/interface/server/routes.py +0 -3
- classiq/model_expansions/interpreter.py +6 -0
- classiq/model_expansions/quantum_operations/emitter.py +8 -2
- classiq/model_expansions/quantum_operations/phase.py +177 -0
- classiq/qmod/builtins/operations.py +14 -0
- classiq/qmod/native/pretty_printer.py +6 -0
- classiq/qmod/pretty_print/pretty_printer.py +6 -0
- {classiq-0.46.1.dist-info → classiq-0.47.0.dist-info}/METADATA +7 -4
- {classiq-0.46.1.dist-info → classiq-0.47.0.dist-info}/RECORD +29 -26
- {classiq-0.46.1.dist-info → classiq-0.47.0.dist-info}/WHEEL +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Any, Dict, Optional
|
1
|
+
from typing import Any, Dict, Optional, Tuple
|
2
2
|
|
3
3
|
import pydantic
|
4
4
|
|
@@ -14,6 +14,7 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
|
|
14
14
|
size: pydantic.PositiveInt
|
15
15
|
is_signed: bool = pydantic.Field(default=False)
|
16
16
|
fraction_places: pydantic.NonNegativeInt = pydantic.Field(default=0)
|
17
|
+
bypass_bounds_validation: bool = pydantic.Field(default=False)
|
17
18
|
bounds: PydanticFloatTuple = pydantic.Field(default=None)
|
18
19
|
|
19
20
|
@pydantic.root_validator(pre=True)
|
@@ -22,32 +23,39 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
|
|
22
23
|
values.pop("name")
|
23
24
|
return values
|
24
25
|
|
26
|
+
@staticmethod
|
27
|
+
def get_maximal_bounds(
|
28
|
+
*, size: int, is_signed: bool, fraction_places: int
|
29
|
+
) -> Tuple[float, float]:
|
30
|
+
lb = 0 if not is_signed else -(2 ** (size - 1))
|
31
|
+
ub = 2**size - 1 if not is_signed else 2 ** (size - 1) - 1
|
32
|
+
fraction_factor = float(2**-fraction_places)
|
33
|
+
return (lb * fraction_factor, ub * fraction_factor)
|
34
|
+
|
25
35
|
@pydantic.validator("bounds", always=True)
|
26
36
|
def _validate_bounds(
|
27
37
|
cls, bounds: Optional[PydanticFloatTuple], values: Dict[str, Any]
|
28
38
|
) -> PydanticFloatTuple:
|
29
|
-
if bounds is not None:
|
30
|
-
if min(bounds) < 0:
|
31
|
-
assert values.get("is_signed")
|
32
|
-
fraction_places = values.get("fraction_places")
|
33
|
-
if not isinstance(fraction_places, int):
|
34
|
-
raise ClassiqValueError(
|
35
|
-
"RegisterUserInput must have an integer fraction_places"
|
36
|
-
)
|
37
|
-
return number_utils.limit_fraction_places(
|
38
|
-
min(bounds), machine_precision=fraction_places
|
39
|
-
), number_utils.limit_fraction_places(
|
40
|
-
max(bounds), machine_precision=fraction_places
|
41
|
-
)
|
42
|
-
|
43
39
|
size = values.get("size")
|
40
|
+
is_signed = values.get("is_signed", False)
|
41
|
+
fraction_places = values.get("fraction_places", 0)
|
44
42
|
if not isinstance(size, int):
|
45
|
-
raise ClassiqValueError("
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
43
|
+
raise ClassiqValueError("RegisterArithmeticInfo must have an integer size")
|
44
|
+
|
45
|
+
maximal_bounds = cls.get_maximal_bounds(
|
46
|
+
size=size, is_signed=is_signed, fraction_places=fraction_places
|
47
|
+
)
|
48
|
+
if bounds is None:
|
49
|
+
return maximal_bounds
|
50
|
+
|
51
|
+
trimmed_bounds = (
|
52
|
+
number_utils.limit_fraction_places(min(bounds), fraction_places),
|
53
|
+
number_utils.limit_fraction_places(max(bounds), fraction_places),
|
54
|
+
)
|
55
|
+
if not values.get("bypass_bounds_validation", False):
|
56
|
+
assert min(trimmed_bounds) >= min(maximal_bounds), "Illegal bound min"
|
57
|
+
assert max(trimmed_bounds) <= max(maximal_bounds), "Illegal bound max"
|
58
|
+
return trimmed_bounds
|
51
59
|
|
52
60
|
def limit_fraction_places(self, machine_precision: int) -> "RegisterArithmeticInfo":
|
53
61
|
truncated_bits: int = max(self.fraction_places - machine_precision, 0)
|
@@ -56,6 +64,7 @@ class RegisterArithmeticInfo(HashablePydanticBaseModel):
|
|
56
64
|
is_signed=self.is_signed,
|
57
65
|
fraction_places=self.fraction_places - truncated_bits,
|
58
66
|
bounds=self.bounds,
|
67
|
+
bypass_bounds_validation=self.bypass_bounds_validation,
|
59
68
|
)
|
60
69
|
|
61
70
|
@property
|
@@ -5,6 +5,7 @@ import pydantic
|
|
5
5
|
from classiq.interface.exceptions import ClassiqValueError
|
6
6
|
from classiq.interface.generator.arith import argument_utils, number_utils
|
7
7
|
from classiq.interface.generator.arith.arithmetic_operations import (
|
8
|
+
MODULO_WITH_FRACTION_PLACES_ERROR_MSG,
|
8
9
|
ArithmeticOperationParams,
|
9
10
|
)
|
10
11
|
from classiq.interface.generator.arith.register_user_input import RegisterArithmeticInfo
|
@@ -74,6 +75,9 @@ class BitwiseInvert(UnaryOpParams):
|
|
74
75
|
|
75
76
|
|
76
77
|
class Negation(UnaryOpParams):
|
78
|
+
bypass_bounds_validation: bool = pydantic.Field(
|
79
|
+
default=False
|
80
|
+
) # True for efficient subtraction
|
77
81
|
output_name = "negated"
|
78
82
|
|
79
83
|
@staticmethod
|
@@ -88,11 +92,19 @@ class Negation(UnaryOpParams):
|
|
88
92
|
eff_arg = self.arg.limit_fraction_places(self.machine_precision)
|
89
93
|
is_signed = max(eff_arg.bounds) > 0 and self._include_sign
|
90
94
|
bounds = (-max(eff_arg.bounds), -min(eff_arg.bounds))
|
95
|
+
if self.output_size and not self.bypass_bounds_validation:
|
96
|
+
if eff_arg.fraction_places:
|
97
|
+
raise ValueError(MODULO_WITH_FRACTION_PLACES_ERROR_MSG)
|
98
|
+
max_bounds = RegisterArithmeticInfo.get_maximal_bounds(
|
99
|
+
size=self.output_size, is_signed=False, fraction_places=0
|
100
|
+
)
|
101
|
+
bounds = number_utils.bounds_cut(bounds, max_bounds)
|
91
102
|
return RegisterArithmeticInfo(
|
92
103
|
size=self.output_size or self._expected_result_size(eff_arg),
|
93
104
|
fraction_places=eff_arg.fraction_places,
|
94
105
|
is_signed=is_signed,
|
95
|
-
|
106
|
+
bypass_bounds_validation=self.bypass_bounds_validation,
|
107
|
+
bounds=bounds,
|
96
108
|
)
|
97
109
|
|
98
110
|
def zero_input_for_extension(self) -> pydantic.NonNegativeInt:
|
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+
import logging
|
2
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
2
3
|
|
3
4
|
import pydantic
|
4
5
|
from typing_extensions import TypeAlias
|
@@ -10,10 +11,10 @@ from classiq.interface.generator.synthesis_metadata.synthesis_execution_data imp
|
|
10
11
|
)
|
11
12
|
from classiq.interface.ide.visual_model import OperationParameter
|
12
13
|
|
14
|
+
_logger = logging.getLogger(__name__)
|
13
15
|
ParameterName = str
|
14
16
|
IOQubitMapping: TypeAlias = Dict[str, Tuple[int, ...]]
|
15
17
|
|
16
|
-
|
17
18
|
CLASSIQ_HIERARCHY_SEPARATOR: Literal["."] = "."
|
18
19
|
|
19
20
|
VISUALIZATION_HIDE_LIST = [
|
@@ -102,6 +103,7 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
102
103
|
absolute_qubits: Optional[Tuple[int, ...]]
|
103
104
|
is_basis_gate: Optional[bool]
|
104
105
|
parameters: List[OperationParameter] = list()
|
106
|
+
port_to_passed_variable_map: Dict[str, str] = pydantic.Field(default_factory=dict)
|
105
107
|
|
106
108
|
@property
|
107
109
|
def registers(self) -> List[GeneratedRegister]:
|
@@ -120,3 +122,46 @@ class FunctionDebugInfoInterface(pydantic.BaseModel):
|
|
120
122
|
if self.generated_function is None:
|
121
123
|
return list()
|
122
124
|
return self.generated_function.control_states
|
125
|
+
|
126
|
+
@staticmethod
|
127
|
+
def create_parameters_from_dict(
|
128
|
+
parameters: Dict[str, str]
|
129
|
+
) -> List[OperationParameter]:
|
130
|
+
return [
|
131
|
+
OperationParameter(label=key, value=value)
|
132
|
+
for key, value in parameters.items()
|
133
|
+
]
|
134
|
+
|
135
|
+
def update(self, **kwargs: Any) -> None:
|
136
|
+
for key, value in kwargs.items():
|
137
|
+
setattr(self, key, value)
|
138
|
+
|
139
|
+
def propagate_absolute_qubits(self) -> None:
|
140
|
+
if self.absolute_qubits is None:
|
141
|
+
return
|
142
|
+
|
143
|
+
for register in self.registers:
|
144
|
+
register.qubit_indexes_absolute = list(
|
145
|
+
_get_absolute_from_relative(
|
146
|
+
self.absolute_qubits, tuple(register.qubit_indexes_relative)
|
147
|
+
)
|
148
|
+
)
|
149
|
+
|
150
|
+
for child in self.children:
|
151
|
+
child.absolute_qubits = _get_absolute_from_relative(
|
152
|
+
self.absolute_qubits, child.relative_qubits
|
153
|
+
)
|
154
|
+
child.propagate_absolute_qubits()
|
155
|
+
|
156
|
+
|
157
|
+
def _get_absolute_from_relative(
|
158
|
+
absolute_qubits: Tuple[int, ...], relative_qubits: Tuple[int, ...]
|
159
|
+
) -> Tuple[int, ...]:
|
160
|
+
if max(relative_qubits) >= len(absolute_qubits):
|
161
|
+
_logger.warning(
|
162
|
+
"Invalid qubit computation (relative qubits: %s, absolute qubits: %s)",
|
163
|
+
relative_qubits,
|
164
|
+
absolute_qubits,
|
165
|
+
)
|
166
|
+
return tuple()
|
167
|
+
return tuple(absolute_qubits[relative_qubit] for relative_qubit in relative_qubits)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import uuid
|
2
|
-
from datetime import datetime
|
2
|
+
from datetime import datetime, timezone
|
3
3
|
from pathlib import Path
|
4
4
|
from typing import Dict, List, Optional, Tuple, Union
|
5
5
|
|
@@ -10,6 +10,7 @@ from classiq.interface.exceptions import (
|
|
10
10
|
ClassiqMissingOutputFormatError,
|
11
11
|
ClassiqStateInitializationError,
|
12
12
|
)
|
13
|
+
from classiq.interface.execution.primitives import PrimitivesInput
|
13
14
|
from classiq.interface.executor import quantum_code
|
14
15
|
from classiq.interface.executor.quantum_instruction_set import QuantumInstructionSet
|
15
16
|
from classiq.interface.executor.register_initialization import RegisterInitialization
|
@@ -50,16 +51,23 @@ def get_uuid_as_str() -> str:
|
|
50
51
|
return str(uuid.uuid4())
|
51
52
|
|
52
53
|
|
54
|
+
def _get_formatted_utc_current_time() -> str:
|
55
|
+
# The purpose of this method is to replicate the behavior of
|
56
|
+
# datetime.utcnow().isoformat(), since `utcnow` is now deprecated
|
57
|
+
return datetime.now(timezone.utc).isoformat().split("+")[0]
|
58
|
+
|
59
|
+
|
53
60
|
class QuantumProgram(VersionedModel, CircuitCodeInterface):
|
54
61
|
hardware_data: SynthesisHardwareData
|
55
62
|
initial_values: Optional[InitialConditions]
|
56
63
|
data: GeneratedCircuitData
|
57
64
|
model: ExecutionModel
|
58
65
|
transpiled_circuit: Optional[TranspiledCircuitData]
|
59
|
-
creation_time: str = pydantic.Field(default_factory=
|
66
|
+
creation_time: str = pydantic.Field(default_factory=_get_formatted_utc_current_time)
|
60
67
|
synthesis_duration: Optional[SynthesisStepDurations]
|
61
68
|
debug_info: Optional[List[FunctionDebugInfoInterface]]
|
62
69
|
program_id: str = pydantic.Field(default_factory=get_uuid_as_str)
|
70
|
+
execution_primitives_input: Optional[PrimitivesInput] = pydantic.Field(default=None)
|
63
71
|
|
64
72
|
def _hardware_agnostic_program_code(self) -> CodeAndSyntax:
|
65
73
|
circuit_code = self.program_circuit.get_code_by_priority()
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from typing import Dict, List, Optional, Tuple
|
1
|
+
from typing import Any, Dict, List, Optional, Tuple
|
2
2
|
|
3
3
|
import pydantic
|
4
4
|
|
@@ -38,6 +38,7 @@ class OperationParameter(pydantic.BaseModel):
|
|
38
38
|
|
39
39
|
class OperationLink(pydantic.BaseModel):
|
40
40
|
label: str
|
41
|
+
inner_label: Optional[str] = None
|
41
42
|
qubits: Tuple[int, ...]
|
42
43
|
type: str
|
43
44
|
|
@@ -47,6 +48,11 @@ class OperationLink(pydantic.BaseModel):
|
|
47
48
|
def __hash__(self) -> int:
|
48
49
|
return hash((type(self), self.label, self.qubits, self.type))
|
49
50
|
|
51
|
+
def __eq__(self, other: Any) -> bool:
|
52
|
+
if not isinstance(other, OperationLink):
|
53
|
+
return False
|
54
|
+
return hash(self) == hash(other)
|
55
|
+
|
50
56
|
|
51
57
|
class OperationLinks(pydantic.BaseModel):
|
52
58
|
inputs: List[OperationLink]
|
@@ -1 +1 @@
|
|
1
|
-
INTERFACE_VERSION = "
|
1
|
+
INTERFACE_VERSION = "3"
|
@@ -0,0 +1,11 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
3
|
+
from classiq.interface.generator.expressions.expression import Expression
|
4
|
+
from classiq.interface.model.quantum_expressions.quantum_expression import (
|
5
|
+
QuantumExpressionOperation,
|
6
|
+
)
|
7
|
+
|
8
|
+
|
9
|
+
class PhaseOperation(QuantumExpressionOperation):
|
10
|
+
kind: Literal["PhaseOperation"]
|
11
|
+
theta: Expression
|
@@ -9,6 +9,7 @@ from classiq.interface.model.control import Control
|
|
9
9
|
from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperation
|
10
10
|
from classiq.interface.model.invert import Invert
|
11
11
|
from classiq.interface.model.native_function_definition import NativeFunctionDefinition
|
12
|
+
from classiq.interface.model.phase_operation import PhaseOperation
|
12
13
|
from classiq.interface.model.power import Power
|
13
14
|
from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
|
14
15
|
AmplitudeLoadingOperation,
|
@@ -38,6 +39,7 @@ ConcreteQuantumStatement = Annotated[
|
|
38
39
|
ClassicalIf,
|
39
40
|
Control,
|
40
41
|
WithinApply,
|
42
|
+
PhaseOperation,
|
41
43
|
],
|
42
44
|
Field(..., discriminator="kind"),
|
43
45
|
]
|
@@ -52,3 +54,4 @@ Invert.update_forward_refs(StatementBlock=StatementBlock)
|
|
52
54
|
WithinApply.update_forward_refs(StatementBlock=StatementBlock)
|
53
55
|
ClassicalIf.update_forward_refs(StatementBlock=StatementBlock)
|
54
56
|
NativeFunctionDefinition.update_forward_refs(StatementBlock=StatementBlock)
|
57
|
+
PhaseOperation.update_forward_refs(StatementBlock=StatementBlock)
|
@@ -17,9 +17,6 @@ ANALYZER_HC_TABLE_GRAPH_FULL_PATH = ANALYZER_PREFIX + ANALYZER_HC_TABLE_GRAPH
|
|
17
17
|
|
18
18
|
ANALYZER_HC_GRAPH_NEW = "/graphs/hardware_connectivity/new"
|
19
19
|
|
20
|
-
ANALYZER_OPTIONAL_DEVICES = "/graphs/available_devices"
|
21
|
-
ANALYZER_OPTIONAL_DEVICES_FULL_PATH = ANALYZER_PREFIX + ANALYZER_OPTIONAL_DEVICES
|
22
|
-
|
23
20
|
TASKS_SUFFIX = "/tasks"
|
24
21
|
RB = "/rb"
|
25
22
|
ANALYZER_DATA_TASK = f"{TASKS_SUFFIX}/data"
|
@@ -24,6 +24,7 @@ from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperat
|
|
24
24
|
from classiq.interface.model.invert import Invert
|
25
25
|
from classiq.interface.model.model import Model
|
26
26
|
from classiq.interface.model.native_function_definition import NativeFunctionDefinition
|
27
|
+
from classiq.interface.model.phase_operation import PhaseOperation
|
27
28
|
from classiq.interface.model.power import Power
|
28
29
|
from classiq.interface.model.quantum_expressions.quantum_expression import (
|
29
30
|
QuantumAssignmentOperation,
|
@@ -74,6 +75,7 @@ from classiq.model_expansions.quantum_operations import (
|
|
74
75
|
VariableDeclarationStatementEmitter,
|
75
76
|
WithinApplyEmitter,
|
76
77
|
)
|
78
|
+
from classiq.model_expansions.quantum_operations.phase import PhaseEmitter
|
77
79
|
from classiq.model_expansions.scope import Evaluated, QuantumSymbol, Scope
|
78
80
|
from classiq.model_expansions.scope_initialization import (
|
79
81
|
add_entry_point_params_to_scope,
|
@@ -307,6 +309,10 @@ class Interpreter:
|
|
307
309
|
def emit_power(self, power: Power) -> None:
|
308
310
|
PowerEmitter(self).emit(power)
|
309
311
|
|
312
|
+
@emit.register
|
313
|
+
def emit_phase(self, phase: PhaseOperation) -> None:
|
314
|
+
PhaseEmitter(self).emit(phase)
|
315
|
+
|
310
316
|
def _expand_block(self, block: Sequence[QuantumStatement], block_name: str) -> None:
|
311
317
|
with self._builder.block_context(block_name):
|
312
318
|
for statement in block:
|
@@ -131,16 +131,22 @@ class Emitter(Generic[QuantumStatementT]):
|
|
131
131
|
or new_call.func_name == free.func_decl.name
|
132
132
|
)
|
133
133
|
parameters = {
|
134
|
-
arg_decl.name: FunctionDebugInfo.param_controller(value=
|
135
|
-
for arg_decl,
|
134
|
+
arg_decl.name: FunctionDebugInfo.param_controller(value=evaluated_arg.value)
|
135
|
+
for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
|
136
136
|
if isinstance(arg_decl, ClassicalParameterDeclaration)
|
137
137
|
}
|
138
138
|
|
139
|
+
port_to_passed_variable_map = {
|
140
|
+
arg_decl.name: evaluated_arg.value.handle.name
|
141
|
+
for arg_decl, evaluated_arg in zip(new_positional_arg_decls, evaluated_args)
|
142
|
+
if isinstance(arg_decl, PortDeclaration)
|
143
|
+
}
|
139
144
|
self._interpreter._model.debug_info[new_call.uuid] = FunctionDebugInfo(
|
140
145
|
name=new_call.func_name,
|
141
146
|
level=OperationLevel.QMOD_FUNCTION_CALL,
|
142
147
|
parameters=parameters,
|
143
148
|
is_allocate_or_free=is_allocate_or_free,
|
149
|
+
port_to_passed_variable_map=port_to_passed_variable_map,
|
144
150
|
)
|
145
151
|
if is_atomic:
|
146
152
|
new_call.set_func_decl(new_declaration)
|
@@ -0,0 +1,177 @@
|
|
1
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple
|
2
|
+
|
3
|
+
import sympy
|
4
|
+
from sympy import sympify
|
5
|
+
|
6
|
+
from classiq.interface.exceptions import ClassiqExpansionError
|
7
|
+
from classiq.interface.generator.expressions.expression import Expression
|
8
|
+
from classiq.interface.model.bind_operation import BindOperation
|
9
|
+
from classiq.interface.model.handle_binding import HANDLE_ID_SEPARATOR, HandleBinding
|
10
|
+
from classiq.interface.model.phase_operation import PhaseOperation
|
11
|
+
from classiq.interface.model.quantum_function_call import QuantumFunctionCall
|
12
|
+
from classiq.interface.model.quantum_type import (
|
13
|
+
QuantumBitvector,
|
14
|
+
QuantumNumeric,
|
15
|
+
QuantumScalar,
|
16
|
+
)
|
17
|
+
from classiq.interface.model.variable_declaration_statement import (
|
18
|
+
VariableDeclarationStatement,
|
19
|
+
)
|
20
|
+
from classiq.interface.model.within_apply_operation import WithinApply
|
21
|
+
|
22
|
+
from classiq.applications.combinatorial_helpers.transformations.ising_converter import (
|
23
|
+
_find_sub_list_items,
|
24
|
+
_get_coeff_from_expr,
|
25
|
+
_get_vars,
|
26
|
+
_refine_ising_expr,
|
27
|
+
_to_ising_symbolic_objective_function,
|
28
|
+
)
|
29
|
+
from classiq.model_expansions.quantum_operations.expression_operation import (
|
30
|
+
ExpressionOperationEmitter,
|
31
|
+
)
|
32
|
+
from classiq.qmod.builtins.functions.exponentiation import suzuki_trotter
|
33
|
+
from classiq.qmod.semantics.error_manager import ErrorManager
|
34
|
+
|
35
|
+
|
36
|
+
class PhaseEmitter(ExpressionOperationEmitter[PhaseOperation]):
|
37
|
+
def emit(self, phase_op: PhaseOperation, /) -> None:
|
38
|
+
phase_expression = self._evaluate_op_expression(phase_op)
|
39
|
+
phase_op = phase_op.copy(update=dict(expression=phase_expression))
|
40
|
+
arrays_with_subscript = self._get_symbols_to_split(phase_op.expression)
|
41
|
+
if len(arrays_with_subscript) > 0:
|
42
|
+
self._emit_with_split(phase_op, phase_op.expression, arrays_with_subscript)
|
43
|
+
return
|
44
|
+
|
45
|
+
phase_op = self._evaluate_types_in_expression(phase_op, phase_op.expression)
|
46
|
+
if len(phase_op.var_handles) == 0:
|
47
|
+
ErrorManager().add_error(
|
48
|
+
"Cannot perform phase operation on an expression with no quantum variables."
|
49
|
+
)
|
50
|
+
return
|
51
|
+
|
52
|
+
aux_name = self._counted_name_allocator.allocate("phase_aux")
|
53
|
+
if len(phase_op.var_handles) > 1:
|
54
|
+
split_join = True
|
55
|
+
evolution_variable = HandleBinding(name=aux_name)
|
56
|
+
else:
|
57
|
+
split_join = False
|
58
|
+
evolution_variable = phase_op.var_handles[0]
|
59
|
+
expression = self._evaluate_op_expression(phase_op)
|
60
|
+
expression_evolution_function = QuantumFunctionCall(
|
61
|
+
function=suzuki_trotter.func_decl.name,
|
62
|
+
positional_args=[
|
63
|
+
_convert_cost_expression_to_hamiltonian(
|
64
|
+
expression.expr,
|
65
|
+
{
|
66
|
+
var.name: self._current_scope[var.name].value.quantum_type
|
67
|
+
for var in phase_op.var_handles
|
68
|
+
},
|
69
|
+
),
|
70
|
+
phase_op.theta,
|
71
|
+
Expression(expr="1"),
|
72
|
+
Expression(expr="1"),
|
73
|
+
evolution_variable,
|
74
|
+
],
|
75
|
+
source_ref=phase_op.source_ref,
|
76
|
+
)
|
77
|
+
expression_evolution_function.set_func_decl(suzuki_trotter.func_decl)
|
78
|
+
|
79
|
+
if split_join:
|
80
|
+
self._interpreter.emit_statement(
|
81
|
+
VariableDeclarationStatement(
|
82
|
+
name=aux_name, quantum_type=QuantumBitvector()
|
83
|
+
)
|
84
|
+
)
|
85
|
+
self._interpreter.emit_statement(
|
86
|
+
WithinApply(
|
87
|
+
compute=[
|
88
|
+
BindOperation(
|
89
|
+
in_handles=phase_op.var_handles,
|
90
|
+
out_handles=[HandleBinding(name=aux_name)],
|
91
|
+
)
|
92
|
+
],
|
93
|
+
action=[expression_evolution_function],
|
94
|
+
source_ref=phase_op.source_ref,
|
95
|
+
)
|
96
|
+
)
|
97
|
+
else:
|
98
|
+
self._interpreter.emit_statement(
|
99
|
+
expression_evolution_function,
|
100
|
+
)
|
101
|
+
|
102
|
+
|
103
|
+
def _get_single_bit_vars_expression(
|
104
|
+
expr: sympy.Expr, vars_info: Dict[str, QuantumScalar]
|
105
|
+
) -> Tuple[sympy.Expr, List[sympy.Symbol]]:
|
106
|
+
bit_vars = []
|
107
|
+
for var_name, var_info in vars_info.items():
|
108
|
+
size = var_info.size_in_bits
|
109
|
+
var = sympy.Symbol(var_name)
|
110
|
+
if size == 1:
|
111
|
+
bits = [var]
|
112
|
+
is_signed = False
|
113
|
+
fraction_places = 0
|
114
|
+
else:
|
115
|
+
if TYPE_CHECKING:
|
116
|
+
assert isinstance(var_info, QuantumNumeric)
|
117
|
+
bits = [
|
118
|
+
sympy.Symbol(f"{var_name}{HANDLE_ID_SEPARATOR}{i}__split__")
|
119
|
+
for i in range(size)
|
120
|
+
]
|
121
|
+
is_signed = var_info.sign_value
|
122
|
+
fraction_places = var_info.fraction_digits_value
|
123
|
+
bit_vars.extend(bits)
|
124
|
+
split_var = 0
|
125
|
+
for i, bit in enumerate(bits):
|
126
|
+
if is_signed and i == size - 1: # sign bit (MSB)
|
127
|
+
split_var -= bit * 2 ** (size - 1 - fraction_places)
|
128
|
+
else:
|
129
|
+
split_var += bit * 2 ** (i - fraction_places)
|
130
|
+
expr = expr.subs(var, split_var)
|
131
|
+
return expr, bit_vars
|
132
|
+
|
133
|
+
|
134
|
+
def _convert_ising_sympy_to_pauli_terms(
|
135
|
+
ising_expr: sympy.Expr, ordered_sympy_vars: List[sympy.Symbol]
|
136
|
+
) -> str:
|
137
|
+
pauli_terms: List[str] = []
|
138
|
+
for expr_term in ising_expr.args:
|
139
|
+
expr_vars = _get_vars(expr_term)
|
140
|
+
z_vec = _find_sub_list_items(ordered_sympy_vars, expr_vars)
|
141
|
+
pauli_elements = ["I"] * len(z_vec)
|
142
|
+
for index, is_z_op in enumerate(z_vec):
|
143
|
+
if is_z_op:
|
144
|
+
pauli_elements[len(z_vec) - index - 1] = (
|
145
|
+
"Z" # reminder: Pauli reverses the order!
|
146
|
+
)
|
147
|
+
coeff = _get_coeff_from_expr(expr_term)
|
148
|
+
paulis = [f"Pauli.{pauli}" for pauli in pauli_elements]
|
149
|
+
pauli_terms.append(
|
150
|
+
# fmt: off
|
151
|
+
"struct_literal("
|
152
|
+
"PauliTerm,"
|
153
|
+
f"pauli=[{', '.join(paulis)}],"
|
154
|
+
f"coefficient={Expression(expr=str(coeff))},"
|
155
|
+
")"
|
156
|
+
# fmt: on,
|
157
|
+
)
|
158
|
+
return f"[{', '.join(pauli_terms)}]"
|
159
|
+
|
160
|
+
|
161
|
+
def _convert_cost_expression_to_hamiltonian(
|
162
|
+
expr: str,
|
163
|
+
vars: Dict[str, QuantumScalar],
|
164
|
+
) -> Expression:
|
165
|
+
sympy_expr = sympify(expr)
|
166
|
+
single_bit_vars_expression, single_bit_vars = _get_single_bit_vars_expression(
|
167
|
+
sympy_expr, vars
|
168
|
+
)
|
169
|
+
if not single_bit_vars_expression.is_polynomial():
|
170
|
+
raise ClassiqExpansionError(f"phased expression {expr!r} is not polynomial")
|
171
|
+
|
172
|
+
ising_expr = _to_ising_symbolic_objective_function(single_bit_vars_expression)
|
173
|
+
ising_expr = _refine_ising_expr(ising_expr)
|
174
|
+
|
175
|
+
return Expression(
|
176
|
+
expr=_convert_ising_sympy_to_pauli_terms(ising_expr, single_bit_vars)
|
177
|
+
)
|
@@ -31,6 +31,7 @@ from classiq.interface.model.inplace_binary_operation import (
|
|
31
31
|
InplaceBinaryOperation,
|
32
32
|
)
|
33
33
|
from classiq.interface.model.invert import Invert
|
34
|
+
from classiq.interface.model.phase_operation import PhaseOperation
|
34
35
|
from classiq.interface.model.power import Power
|
35
36
|
from classiq.interface.model.quantum_function_call import QuantumFunctionCall
|
36
37
|
from classiq.interface.model.quantum_function_declaration import (
|
@@ -331,6 +332,18 @@ def invert(
|
|
331
332
|
)
|
332
333
|
|
333
334
|
|
335
|
+
def phase(expr: SymbolicExpr, theta: float = 1.0) -> None:
|
336
|
+
assert QCallable.CURRENT_EXPANDABLE is not None
|
337
|
+
source_ref = get_source_ref(sys._getframe(1))
|
338
|
+
QCallable.CURRENT_EXPANDABLE.append_statement_to_body(
|
339
|
+
PhaseOperation(
|
340
|
+
expression=Expression(expr=str(expr)),
|
341
|
+
theta=Expression(expr=str(theta)),
|
342
|
+
source_ref=source_ref,
|
343
|
+
)
|
344
|
+
)
|
345
|
+
|
346
|
+
|
334
347
|
def _validate_operand(stmt_block: Any) -> None:
|
335
348
|
if stmt_block is not None:
|
336
349
|
return
|
@@ -414,6 +427,7 @@ __all__ = [
|
|
414
427
|
"power",
|
415
428
|
"within_apply",
|
416
429
|
"repeat",
|
430
|
+
"phase",
|
417
431
|
]
|
418
432
|
|
419
433
|
|
@@ -37,6 +37,7 @@ from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperat
|
|
37
37
|
from classiq.interface.model.invert import Invert
|
38
38
|
from classiq.interface.model.model import Model
|
39
39
|
from classiq.interface.model.native_function_definition import NativeFunctionDefinition
|
40
|
+
from classiq.interface.model.phase_operation import PhaseOperation
|
40
41
|
from classiq.interface.model.port_declaration import (
|
41
42
|
AnonPortDeclaration,
|
42
43
|
)
|
@@ -255,6 +256,11 @@ class DSLPrettyPrinter(Visitor):
|
|
255
256
|
control += f"{self._indent}}}\n"
|
256
257
|
return control
|
257
258
|
|
259
|
+
def visit_PhaseOperation(self, op: PhaseOperation) -> str:
|
260
|
+
theta = f", {self.visit(op.theta)}" if op.theta else ""
|
261
|
+
phase = f"{self._indent}phase ({self.visit(op.expression)}{theta})\n"
|
262
|
+
return phase
|
263
|
+
|
258
264
|
def visit_ClassicalIf(self, op: ClassicalIf) -> str:
|
259
265
|
classical_if = f"{self._indent}if ({self.visit(op.condition)}) {{\n"
|
260
266
|
if not op.then:
|
@@ -39,6 +39,7 @@ from classiq.interface.model.inplace_binary_operation import InplaceBinaryOperat
|
|
39
39
|
from classiq.interface.model.invert import Invert
|
40
40
|
from classiq.interface.model.model import Model
|
41
41
|
from classiq.interface.model.native_function_definition import NativeFunctionDefinition
|
42
|
+
from classiq.interface.model.phase_operation import PhaseOperation
|
42
43
|
from classiq.interface.model.port_declaration import AnonPortDeclaration
|
43
44
|
from classiq.interface.model.power import Power
|
44
45
|
from classiq.interface.model.quantum_expressions.amplitude_loading_operation import (
|
@@ -392,6 +393,11 @@ class PythonPrettyPrinter(Visitor):
|
|
392
393
|
self._imports["control"] = 1
|
393
394
|
return f"{self._indent}control({self.visit(op.expression)}, {self._visit_body(op.body)})\n"
|
394
395
|
|
396
|
+
def visit_PhaseOperation(self, op: PhaseOperation) -> str:
|
397
|
+
self._imports["phase"] = 1
|
398
|
+
theta = f", {self.visit(op.theta)}" if op.theta is not None else ""
|
399
|
+
return f"{self._indent}phase({self.visit(op.expression)},{theta})\n"
|
400
|
+
|
395
401
|
def visit_ClassicalIf(self, op: ClassicalIf) -> str:
|
396
402
|
self._imports["if_"] = 1
|
397
403
|
return f"{self._indent}if_(condition={self.visit(op.condition)}, then={self._visit_body(op.then)}, else_={self._visit_body(op.else_)})\n"
|
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: classiq
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.47.0
|
4
4
|
Summary: Classiq's Python SDK for quantum computing
|
5
5
|
Home-page: https://classiq.io
|
6
6
|
License: Proprietary
|
7
7
|
Keywords: quantum computing,quantum circuits,quantum algorithms,QAD,QDL
|
8
8
|
Author: Classiq Technologies
|
9
9
|
Author-email: support@classiq.io
|
10
|
-
Requires-Python: >=3.8,<3.
|
10
|
+
Requires-Python: >=3.8,<3.13
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
12
12
|
Classifier: Intended Audience :: Developers
|
13
13
|
Classifier: Intended Audience :: Education
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.9
|
21
21
|
Classifier: Programming Language :: Python :: 3.10
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
23
24
|
Classifier: Programming Language :: Python :: 3 :: Only
|
24
25
|
Classifier: Topic :: Scientific/Engineering
|
25
26
|
Classifier: Topic :: Software Development
|
@@ -38,12 +39,14 @@ Requires-Dist: keyring (>=23.5.0,<24.0.0)
|
|
38
39
|
Requires-Dist: matplotlib (>=3.4.3,<4.0.0)
|
39
40
|
Requires-Dist: networkx (>=2.5.1,<3.0.0)
|
40
41
|
Requires-Dist: numexpr (>=2.7.3,<3.0.0)
|
41
|
-
Requires-Dist: numpy (>=1.20.1,<2.0.0)
|
42
|
+
Requires-Dist: numpy (>=1.20.1,<2.0.0) ; python_version < "3.12"
|
43
|
+
Requires-Dist: numpy (>=1.26.0,<2.0.0) ; python_version >= "3.12"
|
42
44
|
Requires-Dist: packaging (>=23.2,<24.0)
|
43
45
|
Requires-Dist: pandas (>=1.4.0,<3.0.0)
|
44
46
|
Requires-Dist: plotly (>=5.7.0,<6.0.0)
|
45
47
|
Requires-Dist: pydantic (>=1.9.1,<2.0.0)
|
46
|
-
Requires-Dist: scipy (>=1.10.
|
48
|
+
Requires-Dist: scipy (>=1.10.0,<2.0.0) ; python_version < "3.12"
|
49
|
+
Requires-Dist: scipy (>=1.11.0,<2.0.0) ; python_version >= "3.12"
|
47
50
|
Requires-Dist: sympy (>=1.13.0,<2.0.0)
|
48
51
|
Requires-Dist: tabulate (>=0.8.9,<1)
|
49
52
|
Requires-Dist: torch (>=2.0,<3.0) ; extra == "qml"
|