qilisdk 0.1.5__py3-none-any.whl → 0.1.7__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.
- qilisdk/analog/__init__.py +1 -2
- qilisdk/analog/hamiltonian.py +4 -71
- qilisdk/analog/schedule.py +291 -313
- qilisdk/backends/backend.py +5 -1
- qilisdk/backends/cuda_backend.py +10 -6
- qilisdk/backends/qutip_backend.py +24 -32
- qilisdk/{common → core}/__init__.py +4 -0
- qilisdk/core/interpolator.py +406 -0
- qilisdk/{common → core}/model.py +7 -7
- qilisdk/core/parameterizable.py +131 -0
- qilisdk/{common → core}/qtensor.py +1 -1
- qilisdk/{common → core}/variables.py +192 -11
- qilisdk/cost_functions/cost_function.py +1 -1
- qilisdk/cost_functions/model_cost_function.py +5 -5
- qilisdk/cost_functions/observable_cost_function.py +2 -2
- qilisdk/digital/ansatz.py +0 -3
- qilisdk/digital/circuit.py +3 -2
- qilisdk/digital/circuit_transpiler.py +46 -0
- qilisdk/digital/circuit_transpiler_passes/__init__.py +18 -0
- qilisdk/digital/circuit_transpiler_passes/circuit_transpiler_pass.py +36 -0
- qilisdk/digital/circuit_transpiler_passes/decompose_multi_controlled_gates_pass.py +216 -0
- qilisdk/digital/circuit_transpiler_passes/numeric_helpers.py +82 -0
- qilisdk/digital/gates.py +15 -5
- qilisdk/{speqtrum/experiments → experiments}/__init__.py +13 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_functional.py +90 -2
- qilisdk/{speqtrum/experiments → experiments}/experiment_result.py +16 -0
- qilisdk/functionals/functional.py +2 -2
- qilisdk/functionals/functional_result.py +1 -1
- qilisdk/functionals/sampling.py +8 -1
- qilisdk/functionals/time_evolution.py +8 -4
- qilisdk/functionals/time_evolution_result.py +2 -2
- qilisdk/functionals/variational_program.py +58 -0
- qilisdk/optimizers/optimizer_result.py +1 -1
- qilisdk/speqtrum/__init__.py +2 -0
- qilisdk/speqtrum/speqtrum.py +537 -152
- qilisdk/speqtrum/speqtrum_models.py +258 -2
- qilisdk/utils/openfermion/__init__.py +38 -0
- qilisdk/{common/algorithm.py → utils/openfermion/__init__.pyi} +2 -3
- qilisdk/utils/openfermion/openfermion.py +45 -0
- qilisdk/utils/visualization/schedule_renderers.py +22 -9
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/METADATA +89 -39
- qilisdk-0.1.7.dist-info/RECORD +76 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/WHEEL +1 -1
- qilisdk/analog/linear_schedule.py +0 -118
- qilisdk/common/parameterizable.py +0 -75
- qilisdk-0.1.5.dist-info/RECORD +0 -69
- /qilisdk/{common → core}/exceptions.py +0 -0
- /qilisdk/{common → core}/result.py +0 -0
- {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/licenses/LICENCE +0 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from abc import ABC
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from qilisdk.core.variables import BaseVariable, ComparisonTerm, Parameter
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Parameterizable(ABC):
|
|
24
|
+
"""Mixin for objects that expose tunable parameters and constraints."""
|
|
25
|
+
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
super(Parameterizable, self).__init__()
|
|
28
|
+
self._parameters: dict[str, Parameter] = {}
|
|
29
|
+
self._parameter_constraints: list[ComparisonTerm] = []
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def nparameters(self) -> int:
|
|
33
|
+
"""Number of tunable parameters defined by the object."""
|
|
34
|
+
return len(self._parameters)
|
|
35
|
+
|
|
36
|
+
def get_parameter_values(self) -> list[float]:
|
|
37
|
+
"""Return the current numerical values of the parameters."""
|
|
38
|
+
return [param.value for param in self._parameters.values()]
|
|
39
|
+
|
|
40
|
+
def get_parameter_names(self) -> list[str]:
|
|
41
|
+
"""Return the ordered list of parameter labels."""
|
|
42
|
+
return list(self._parameters.keys())
|
|
43
|
+
|
|
44
|
+
def get_parameters(self) -> dict[str, float]:
|
|
45
|
+
"""Return a mapping from parameter labels to their current numerical values."""
|
|
46
|
+
return {label: param.value for label, param in self._parameters.items()}
|
|
47
|
+
|
|
48
|
+
def set_parameter_values(self, values: list[float]) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Update all parameter values at once.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
values (list[float]): New parameter values ordered consistently with ``get_parameter_names()``.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If ``values`` does not contain exactly ``nparameters`` entries.
|
|
57
|
+
"""
|
|
58
|
+
if len(values) != self.nparameters:
|
|
59
|
+
raise ValueError(f"Provided {len(values)} but this object has {self.nparameters} parameters.")
|
|
60
|
+
param_names = self.get_parameter_names()
|
|
61
|
+
value_dict = {param_names[i]: values[i] for i in range(len(values))}
|
|
62
|
+
self.set_parameters(value_dict)
|
|
63
|
+
|
|
64
|
+
def set_parameters(self, parameters: dict[str, float]) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Update a subset of parameters by label.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
parameters (dict[str, float]): Mapping from parameter labels to updated numeric values.
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If an unknown parameter label is provided or constraints are violated.
|
|
73
|
+
"""
|
|
74
|
+
if not self.check_constraints(parameters):
|
|
75
|
+
raise ValueError(
|
|
76
|
+
f"New assignation of the parameters breaks the parameter constraints: \n{self.get_constraints()}"
|
|
77
|
+
)
|
|
78
|
+
for label, param in parameters.items():
|
|
79
|
+
if label not in self._parameters:
|
|
80
|
+
raise ValueError(f"Parameter {label} is not defined for this object.")
|
|
81
|
+
self._parameters[label].set_value(param)
|
|
82
|
+
|
|
83
|
+
def get_parameter_bounds(self) -> dict[str, tuple[float, float]]:
|
|
84
|
+
"""Return the ``(lower, upper)`` bounds associated with each parameter."""
|
|
85
|
+
return {label: param.bounds for label, param in self._parameters.items()}
|
|
86
|
+
|
|
87
|
+
def set_parameter_bounds(self, ranges: dict[str, tuple[float, float]]) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Update the allowable ranges for the specified parameters.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
ranges (dict[str, tuple[float, float]]): Mapping from parameter label to ``(lower, upper)`` bounds.
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
ValueError: If an unknown parameter label is provided.
|
|
96
|
+
"""
|
|
97
|
+
for label, bound in ranges.items():
|
|
98
|
+
if label not in self._parameters:
|
|
99
|
+
raise ValueError(
|
|
100
|
+
f"The provided parameter label {label} is not defined in the list of parameters in this object."
|
|
101
|
+
)
|
|
102
|
+
self._parameters[label].set_bounds(bound[0], bound[1])
|
|
103
|
+
|
|
104
|
+
def get_constraints(self) -> list[ComparisonTerm]:
|
|
105
|
+
"""Get all constraints on the parameters.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
list[ComparisonTerm]: A list of comparison terms involving the parameters of the Object.
|
|
109
|
+
"""
|
|
110
|
+
return self._parameter_constraints
|
|
111
|
+
|
|
112
|
+
def check_constraints(self, parameters: dict[str, float]) -> bool:
|
|
113
|
+
"""Validate that proposed parameter updates satisfy all constraints.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
parameters (dict[str, float]): Candidate parameter values keyed by label.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
bool: True if every constraint evaluates to True for the provided values.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ValueError: If an unknown parameter label is provided.
|
|
123
|
+
"""
|
|
124
|
+
evaluate_dict: dict[BaseVariable, float] = {}
|
|
125
|
+
for label, value in parameters.items():
|
|
126
|
+
if label not in self._parameters:
|
|
127
|
+
raise ValueError(f"Parameter {label} is not defined for this object.")
|
|
128
|
+
evaluate_dict[self._parameters[label]] = value
|
|
129
|
+
constraints = self.get_constraints()
|
|
130
|
+
valid = all(con.evaluate(evaluate_dict) for con in constraints)
|
|
131
|
+
return valid
|
|
@@ -18,12 +18,12 @@ import copy
|
|
|
18
18
|
import re
|
|
19
19
|
from abc import ABC, abstractmethod
|
|
20
20
|
from enum import Enum
|
|
21
|
-
from typing import TYPE_CHECKING, Iterator, Mapping, Sequence, TypeVar
|
|
21
|
+
from typing import TYPE_CHECKING, Iterator, Mapping, Sequence, TypeVar, overload
|
|
22
22
|
|
|
23
23
|
import numpy as np
|
|
24
24
|
from loguru import logger
|
|
25
25
|
|
|
26
|
-
from qilisdk.
|
|
26
|
+
from qilisdk.core.exceptions import EvaluationError, InvalidBoundsError, NotSupportedOperation, OutOfBoundsException
|
|
27
27
|
from qilisdk.yaml import yaml
|
|
28
28
|
|
|
29
29
|
if TYPE_CHECKING:
|
|
@@ -246,6 +246,7 @@ class Operation(str, Enum):
|
|
|
246
246
|
ADD = "+"
|
|
247
247
|
DIV = "/"
|
|
248
248
|
SUB = "-"
|
|
249
|
+
MATH_MAP = "mathematical_map"
|
|
249
250
|
|
|
250
251
|
@classmethod
|
|
251
252
|
def to_yaml(cls, representer: RoundTripRepresenter, node: Operation) -> ScalarNode:
|
|
@@ -761,6 +762,7 @@ class BaseVariable(ABC):
|
|
|
761
762
|
if lower_bound > upper_bound:
|
|
762
763
|
raise InvalidBoundsError("lower bound can't be larger than the upper bound.")
|
|
763
764
|
self._bounds = (lower_bound, upper_bound)
|
|
765
|
+
self._hash_cache: int | None = None
|
|
764
766
|
|
|
765
767
|
@property
|
|
766
768
|
def bounds(self) -> tuple[float, float]:
|
|
@@ -819,6 +821,7 @@ class BaseVariable(ABC):
|
|
|
819
821
|
OutOfBoundsException: the lower bound or the upper bound don't correspond to the variable domain.
|
|
820
822
|
InvalidBoundsError: the lower bound is higher than the upper bound.
|
|
821
823
|
"""
|
|
824
|
+
self._hash_cache = None
|
|
822
825
|
if lower_bound is None:
|
|
823
826
|
lower_bound = self._domain.min()
|
|
824
827
|
if upper_bound is None:
|
|
@@ -866,7 +869,7 @@ class BaseVariable(ABC):
|
|
|
866
869
|
domain (Domain): The updated domain of the variable.
|
|
867
870
|
bounds (tuple[float | None, float | None]): The updated bounds of the variable. Defaults to (None, None)
|
|
868
871
|
"""
|
|
869
|
-
|
|
872
|
+
self._hash_cache = None
|
|
870
873
|
self._domain = domain
|
|
871
874
|
self.set_bounds(bounds[0], bounds[1])
|
|
872
875
|
|
|
@@ -890,6 +893,9 @@ class BaseVariable(ABC):
|
|
|
890
893
|
if isinstance(other, Term):
|
|
891
894
|
return other + self
|
|
892
895
|
|
|
896
|
+
if isinstance(other, np.generic):
|
|
897
|
+
other = other.item()
|
|
898
|
+
|
|
893
899
|
return Term(elements=[self, other], operation=Operation.ADD)
|
|
894
900
|
|
|
895
901
|
__radd__ = __add__
|
|
@@ -901,6 +907,9 @@ class BaseVariable(ABC):
|
|
|
901
907
|
if isinstance(other, Term):
|
|
902
908
|
return other * self
|
|
903
909
|
|
|
910
|
+
if isinstance(other, np.generic):
|
|
911
|
+
other = other.item()
|
|
912
|
+
|
|
904
913
|
return Term(elements=[self, other], operation=Operation.MUL)
|
|
905
914
|
|
|
906
915
|
def __rmul__(self, other: Number | BaseVariable | Term) -> Term:
|
|
@@ -909,6 +918,9 @@ class BaseVariable(ABC):
|
|
|
909
918
|
if isinstance(other, Term):
|
|
910
919
|
return other * self
|
|
911
920
|
|
|
921
|
+
if isinstance(other, np.generic):
|
|
922
|
+
other = other.item()
|
|
923
|
+
|
|
912
924
|
return Term(elements=[other, self], operation=Operation.MUL)
|
|
913
925
|
|
|
914
926
|
__imul__ = __mul__
|
|
@@ -916,11 +928,19 @@ class BaseVariable(ABC):
|
|
|
916
928
|
def __sub__(self, other: Number | BaseVariable | Term) -> Term:
|
|
917
929
|
if not isinstance(other, (Number, BaseVariable, Term)):
|
|
918
930
|
return NotImplemented
|
|
931
|
+
|
|
932
|
+
if isinstance(other, np.generic):
|
|
933
|
+
other = other.item()
|
|
934
|
+
|
|
919
935
|
return self + -1 * other
|
|
920
936
|
|
|
921
937
|
def __rsub__(self, other: Number | BaseVariable | Term) -> Term:
|
|
922
938
|
if not isinstance(other, (Number, BaseVariable, Term)):
|
|
923
939
|
return NotImplemented
|
|
940
|
+
|
|
941
|
+
if isinstance(other, np.generic):
|
|
942
|
+
other = other.item()
|
|
943
|
+
|
|
924
944
|
return -1 * self + other
|
|
925
945
|
|
|
926
946
|
__isub__ = __sub__
|
|
@@ -935,6 +955,8 @@ class BaseVariable(ABC):
|
|
|
935
955
|
if other == 0:
|
|
936
956
|
raise ValueError("Division by zero is not allowed")
|
|
937
957
|
|
|
958
|
+
if isinstance(other, np.generic):
|
|
959
|
+
other = other.item()
|
|
938
960
|
other = 1 / other
|
|
939
961
|
return self * other
|
|
940
962
|
|
|
@@ -963,7 +985,9 @@ class BaseVariable(ABC):
|
|
|
963
985
|
return out
|
|
964
986
|
|
|
965
987
|
def __hash__(self) -> int:
|
|
966
|
-
|
|
988
|
+
if self._hash_cache is None:
|
|
989
|
+
self._hash_cache = hash((self._label, self._domain.value, self._bounds))
|
|
990
|
+
return self._hash_cache
|
|
967
991
|
|
|
968
992
|
def __eq__(self, other: object) -> bool:
|
|
969
993
|
if not isinstance(other, BaseVariable):
|
|
@@ -979,7 +1003,7 @@ class BinaryVariable(BaseVariable):
|
|
|
979
1003
|
Example:
|
|
980
1004
|
.. code-block:: python
|
|
981
1005
|
|
|
982
|
-
from qilisdk.
|
|
1006
|
+
from qilisdk.core.variables import BinaryVariable
|
|
983
1007
|
|
|
984
1008
|
x = BinaryVariable("x")
|
|
985
1009
|
"""
|
|
@@ -1048,7 +1072,7 @@ class Variable(BaseVariable):
|
|
|
1048
1072
|
Example:
|
|
1049
1073
|
.. code-block:: python
|
|
1050
1074
|
|
|
1051
|
-
from qilisdk.
|
|
1075
|
+
from qilisdk.core.variables import Domain, Variable
|
|
1052
1076
|
|
|
1053
1077
|
price = Variable("price", domain=Domain.REAL, bounds=(0, 10))
|
|
1054
1078
|
binary_term = price.to_binary()
|
|
@@ -1128,6 +1152,8 @@ class Variable(BaseVariable):
|
|
|
1128
1152
|
return super().update_variable(domain, bounds)
|
|
1129
1153
|
|
|
1130
1154
|
def evaluate(self, value: list[int] | RealNumber) -> RealNumber:
|
|
1155
|
+
if not isinstance(value, (list, RealNumber)):
|
|
1156
|
+
raise ValueError("Invalid Value Provided to evaluate a Variable.")
|
|
1131
1157
|
if isinstance(value, int | float):
|
|
1132
1158
|
if not self.domain.check_value(value):
|
|
1133
1159
|
raise ValueError(f"The value {value} is invalid for the domain {self.domain.value}")
|
|
@@ -1183,7 +1209,7 @@ class Parameter(BaseVariable):
|
|
|
1183
1209
|
Example:
|
|
1184
1210
|
.. code-block:: python
|
|
1185
1211
|
|
|
1186
|
-
from qilisdk.
|
|
1212
|
+
from qilisdk.core.variables import Parameter
|
|
1187
1213
|
|
|
1188
1214
|
theta = Parameter("theta", value=0.5)
|
|
1189
1215
|
theta.set_value(0.75)
|
|
@@ -1209,13 +1235,19 @@ class Parameter(BaseVariable):
|
|
|
1209
1235
|
def value(self) -> RealNumber:
|
|
1210
1236
|
return self._value
|
|
1211
1237
|
|
|
1212
|
-
def
|
|
1238
|
+
def check_value(self, value: RealNumber) -> None:
|
|
1213
1239
|
if not self.domain.check_value(value):
|
|
1214
1240
|
raise ValueError(
|
|
1215
1241
|
f"Parameter value provided ({value}) doesn't correspond to the parameter's domain ({self.domain.name})"
|
|
1216
1242
|
)
|
|
1217
1243
|
if value > self.bounds[1] or value < self.bounds[0]:
|
|
1218
1244
|
raise ValueError(f"The value provided ({value}) is outside the bound of the parameter {self.bounds}")
|
|
1245
|
+
|
|
1246
|
+
def set_value(self, value: RealNumber) -> None:
|
|
1247
|
+
self.check_value(value)
|
|
1248
|
+
|
|
1249
|
+
if isinstance(value, np.generic):
|
|
1250
|
+
value = value.item()
|
|
1219
1251
|
self._value = value
|
|
1220
1252
|
|
|
1221
1253
|
def num_binary_equivalent(self) -> int: # noqa: PLR6301
|
|
@@ -1225,7 +1257,7 @@ class Parameter(BaseVariable):
|
|
|
1225
1257
|
"""
|
|
1226
1258
|
return 0
|
|
1227
1259
|
|
|
1228
|
-
def evaluate(self, value: list[int] | RealNumber =
|
|
1260
|
+
def evaluate(self, value: list[int] | RealNumber | None = None) -> RealNumber:
|
|
1229
1261
|
"""Evaluates the value of the variable given a binary string or a number.
|
|
1230
1262
|
|
|
1231
1263
|
Args:
|
|
@@ -1237,6 +1269,11 @@ class Parameter(BaseVariable):
|
|
|
1237
1269
|
Returns:
|
|
1238
1270
|
float: the evaluated vale of the variable.
|
|
1239
1271
|
"""
|
|
1272
|
+
if value is not None:
|
|
1273
|
+
if isinstance(value, RealNumber):
|
|
1274
|
+
self.check_value(value)
|
|
1275
|
+
return value
|
|
1276
|
+
raise NotImplementedError("Evaluating the value of a parameter with a list is not supported.")
|
|
1240
1277
|
return self.value
|
|
1241
1278
|
|
|
1242
1279
|
def to_binary(self) -> Term:
|
|
@@ -1271,6 +1308,40 @@ class Parameter(BaseVariable):
|
|
|
1271
1308
|
|
|
1272
1309
|
self.set_bounds(lower_bound=bounds[0], upper_bound=bounds[1])
|
|
1273
1310
|
|
|
1311
|
+
__hash__ = BaseVariable.__hash__
|
|
1312
|
+
|
|
1313
|
+
def __eq__(self, other: object) -> bool:
|
|
1314
|
+
if isinstance(other, BaseVariable):
|
|
1315
|
+
return super().__eq__(other)
|
|
1316
|
+
if isinstance(other, (float, int)):
|
|
1317
|
+
return self.value == other
|
|
1318
|
+
return False
|
|
1319
|
+
|
|
1320
|
+
def __ne__(self, other: object) -> bool:
|
|
1321
|
+
if isinstance(other, (float, int)):
|
|
1322
|
+
return self.value != other
|
|
1323
|
+
return NotImplemented
|
|
1324
|
+
|
|
1325
|
+
def __le__(self, other: object) -> bool:
|
|
1326
|
+
if isinstance(other, (float, int)):
|
|
1327
|
+
return self.value <= other
|
|
1328
|
+
return NotImplemented
|
|
1329
|
+
|
|
1330
|
+
def __lt__(self, other: object) -> bool:
|
|
1331
|
+
if isinstance(other, (float, int)):
|
|
1332
|
+
return self.value < other
|
|
1333
|
+
return NotImplemented
|
|
1334
|
+
|
|
1335
|
+
def __ge__(self, other: object) -> bool:
|
|
1336
|
+
if isinstance(other, (float, int)):
|
|
1337
|
+
return self.value >= other
|
|
1338
|
+
return NotImplemented
|
|
1339
|
+
|
|
1340
|
+
def __gt__(self, other: object) -> bool:
|
|
1341
|
+
if isinstance(other, (float, int)):
|
|
1342
|
+
return self.value > other
|
|
1343
|
+
return NotImplemented
|
|
1344
|
+
|
|
1274
1345
|
|
|
1275
1346
|
# Terms ###
|
|
1276
1347
|
|
|
@@ -1449,7 +1520,7 @@ class Term:
|
|
|
1449
1520
|
Returns:
|
|
1450
1521
|
(Term | BaseVariable): the simplified term.
|
|
1451
1522
|
"""
|
|
1452
|
-
if len(self) == 1:
|
|
1523
|
+
if len(self) == 1 and not isinstance(self, MathematicalMap):
|
|
1453
1524
|
item = next(iter(self._elements.keys()))
|
|
1454
1525
|
if self._elements[item] == 1:
|
|
1455
1526
|
return item
|
|
@@ -1567,7 +1638,13 @@ class Term:
|
|
|
1567
1638
|
_var_values = dict(var_values)
|
|
1568
1639
|
for var in self.variables():
|
|
1569
1640
|
if isinstance(var, Parameter):
|
|
1570
|
-
|
|
1641
|
+
if var not in _var_values:
|
|
1642
|
+
_var_values[var] = var.value
|
|
1643
|
+
else:
|
|
1644
|
+
value = _var_values[var]
|
|
1645
|
+
if not isinstance(value, RealNumber):
|
|
1646
|
+
raise ValueError(f"setting a parameter ({var}) value with a list is not supported.")
|
|
1647
|
+
# var.set_value(value)
|
|
1571
1648
|
if var not in _var_values:
|
|
1572
1649
|
raise ValueError(f"Can not evaluate term because the value of the variable {var} is not provided.")
|
|
1573
1650
|
output = complex(0.0) if self.operation in {Operation.ADD, Operation.SUB} else complex(1.0)
|
|
@@ -1665,6 +1742,10 @@ class Term:
|
|
|
1665
1742
|
if not isinstance(other, (Number, BaseVariable, Term)):
|
|
1666
1743
|
return NotImplemented
|
|
1667
1744
|
out = self.to_list() if self.operation == Operation.ADD else [copy.copy(self)]
|
|
1745
|
+
|
|
1746
|
+
if isinstance(other, np.generic):
|
|
1747
|
+
other = other.item()
|
|
1748
|
+
|
|
1668
1749
|
out.append(other)
|
|
1669
1750
|
return Term(out, Operation.ADD)
|
|
1670
1751
|
|
|
@@ -1674,6 +1755,9 @@ class Term:
|
|
|
1674
1755
|
if not isinstance(other, (Number, BaseVariable, Term)):
|
|
1675
1756
|
return NotImplemented
|
|
1676
1757
|
out = self.to_list() if self.operation == Operation.ADD else [copy.copy(self)]
|
|
1758
|
+
|
|
1759
|
+
if isinstance(other, np.generic):
|
|
1760
|
+
other = other.item()
|
|
1677
1761
|
out.insert(0, other)
|
|
1678
1762
|
return Term(out, Operation.ADD)
|
|
1679
1763
|
|
|
@@ -1683,6 +1767,10 @@ class Term:
|
|
|
1683
1767
|
out = self.to_list() if self.operation == Operation.MUL else [copy.copy(self)]
|
|
1684
1768
|
if len(out) == 0:
|
|
1685
1769
|
out = [0]
|
|
1770
|
+
|
|
1771
|
+
if isinstance(other, np.generic):
|
|
1772
|
+
other = other.item()
|
|
1773
|
+
|
|
1686
1774
|
out.append(other)
|
|
1687
1775
|
return Term(out, Operation.MUL)._unfold_parentheses()
|
|
1688
1776
|
|
|
@@ -1694,6 +1782,10 @@ class Term:
|
|
|
1694
1782
|
out = self.to_list() if self.operation == Operation.MUL else [copy.copy(self)]
|
|
1695
1783
|
if len(out) == 0:
|
|
1696
1784
|
out = [0]
|
|
1785
|
+
|
|
1786
|
+
if isinstance(other, np.generic):
|
|
1787
|
+
other = other.item()
|
|
1788
|
+
|
|
1697
1789
|
out.insert(0, other)
|
|
1698
1790
|
return Term(out, Operation.MUL)._unfold_parentheses()
|
|
1699
1791
|
|
|
@@ -1703,6 +1795,10 @@ class Term:
|
|
|
1703
1795
|
def __sub__(self, other: Number | BaseVariable | Term) -> Term:
|
|
1704
1796
|
if not isinstance(other, (Number, BaseVariable, Term)):
|
|
1705
1797
|
return NotImplemented
|
|
1798
|
+
|
|
1799
|
+
if isinstance(other, np.generic):
|
|
1800
|
+
other = other.item()
|
|
1801
|
+
|
|
1706
1802
|
return self + -1 * other
|
|
1707
1803
|
|
|
1708
1804
|
def __rsub__(self, other: Number | BaseVariable | Term) -> Term:
|
|
@@ -1929,3 +2025,88 @@ class ComparisonTerm:
|
|
|
1929
2025
|
"Symbolic Constraint Term objects do not have an inherent truth value. "
|
|
1930
2026
|
"Use a method like .evaluate() to obtain a Boolean value."
|
|
1931
2027
|
)
|
|
2028
|
+
|
|
2029
|
+
def __hash__(self) -> int:
|
|
2030
|
+
return hash((hash(self._lhs), self.operation, hash(self._rhs)))
|
|
2031
|
+
|
|
2032
|
+
def __eq__(self, other: object) -> bool:
|
|
2033
|
+
if not isinstance(other, ComparisonTerm):
|
|
2034
|
+
return False
|
|
2035
|
+
return hash(self) == hash(other)
|
|
2036
|
+
|
|
2037
|
+
|
|
2038
|
+
class MathematicalMap(Term, ABC):
|
|
2039
|
+
"""Base class for applying a mathematical map (e.g., sin, cos) to a single term or parameter."""
|
|
2040
|
+
|
|
2041
|
+
MATH_SYMBOL = ""
|
|
2042
|
+
|
|
2043
|
+
@overload
|
|
2044
|
+
def __init__(self, arg: Term, /) -> None: ...
|
|
2045
|
+
@overload
|
|
2046
|
+
def __init__(self, arg: Parameter, /) -> None: ...
|
|
2047
|
+
@overload
|
|
2048
|
+
def __init__(self, arg: BaseVariable, /) -> None: ...
|
|
2049
|
+
|
|
2050
|
+
def __init__(self, arg: Term | Parameter | BaseVariable) -> None:
|
|
2051
|
+
if isinstance(arg, Term):
|
|
2052
|
+
self._initialize_with_term(arg)
|
|
2053
|
+
elif isinstance(arg, Parameter):
|
|
2054
|
+
self._initialize_with_parameter(arg)
|
|
2055
|
+
elif isinstance(arg, BaseVariable):
|
|
2056
|
+
self._initialize_with_variable(arg)
|
|
2057
|
+
else:
|
|
2058
|
+
raise TypeError("Sin expects Term | Parameter | BaseVariable")
|
|
2059
|
+
|
|
2060
|
+
def _initialize_with_term(self, term: Term) -> None:
|
|
2061
|
+
super().__init__(elements=[term], operation=Operation.MATH_MAP)
|
|
2062
|
+
|
|
2063
|
+
def _initialize_with_parameter(self, parameter: Parameter) -> None:
|
|
2064
|
+
super().__init__(elements=[parameter], operation=Operation.MATH_MAP)
|
|
2065
|
+
|
|
2066
|
+
def _initialize_with_variable(self, variable: BaseVariable) -> None:
|
|
2067
|
+
super().__init__(elements=[variable], operation=Operation.MATH_MAP)
|
|
2068
|
+
|
|
2069
|
+
@abstractmethod
|
|
2070
|
+
def _apply_mathematical_map(self, value: Number) -> Number: ...
|
|
2071
|
+
|
|
2072
|
+
def evaluate(self, var_values: Mapping[BaseVariable, list[int] | RealNumber]) -> Number:
|
|
2073
|
+
value: Number = 0
|
|
2074
|
+
|
|
2075
|
+
for e in self:
|
|
2076
|
+
if e not in var_values and isinstance(e, Parameter):
|
|
2077
|
+
aux: Number = e.evaluate()
|
|
2078
|
+
else:
|
|
2079
|
+
aux = e.evaluate(var_values) if isinstance(e, Term) else e.evaluate(var_values[e])
|
|
2080
|
+
|
|
2081
|
+
value += aux * self[e]
|
|
2082
|
+
|
|
2083
|
+
return self._apply_mathematical_map(value)
|
|
2084
|
+
|
|
2085
|
+
def __repr__(self) -> str:
|
|
2086
|
+
return f"{self.MATH_SYMBOL}[{super().__repr__()}]"
|
|
2087
|
+
|
|
2088
|
+
__str__ = __repr__
|
|
2089
|
+
|
|
2090
|
+
|
|
2091
|
+
class Sin(MathematicalMap):
|
|
2092
|
+
"""Apply a sine map to a parameter or term."""
|
|
2093
|
+
|
|
2094
|
+
MATH_SYMBOL = "sin"
|
|
2095
|
+
|
|
2096
|
+
def _apply_mathematical_map(self, value: Number) -> Number: # noqa: PLR6301
|
|
2097
|
+
return float(np.sin(_assert_real(value)))
|
|
2098
|
+
|
|
2099
|
+
def __copy__(self) -> Sin:
|
|
2100
|
+
return Sin(super().__copy__())
|
|
2101
|
+
|
|
2102
|
+
|
|
2103
|
+
class Cos(MathematicalMap):
|
|
2104
|
+
"""Apply a cosine map to a parameter or term."""
|
|
2105
|
+
|
|
2106
|
+
MATH_SYMBOL = "cos"
|
|
2107
|
+
|
|
2108
|
+
def _apply_mathematical_map(self, value: Number) -> Number: # noqa: PLR6301
|
|
2109
|
+
return float(np.cos(_assert_real(value)))
|
|
2110
|
+
|
|
2111
|
+
def __copy__(self) -> Cos:
|
|
2112
|
+
return Cos(super().__copy__())
|
|
@@ -21,7 +21,7 @@ from qilisdk.functionals.sampling_result import SamplingResult
|
|
|
21
21
|
from qilisdk.functionals.time_evolution_result import TimeEvolutionResult
|
|
22
22
|
|
|
23
23
|
if TYPE_CHECKING:
|
|
24
|
-
from qilisdk.
|
|
24
|
+
from qilisdk.core.variables import Number
|
|
25
25
|
|
|
26
26
|
TResult = TypeVar("TResult", bound=FunctionalResult)
|
|
27
27
|
|
|
@@ -17,24 +17,24 @@ from typing import TYPE_CHECKING
|
|
|
17
17
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
|
|
20
|
-
from qilisdk.
|
|
21
|
-
from qilisdk.
|
|
20
|
+
from qilisdk.core.model import QUBO, Model
|
|
21
|
+
from qilisdk.core.qtensor import QTensor, expect_val, ket
|
|
22
22
|
from qilisdk.cost_functions.cost_function import CostFunction
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from qilisdk.
|
|
25
|
+
from qilisdk.core.variables import Number
|
|
26
26
|
from qilisdk.functionals.sampling_result import SamplingResult
|
|
27
27
|
from qilisdk.functionals.time_evolution_result import TimeEvolutionResult
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class ModelCostFunction(CostFunction):
|
|
31
31
|
"""
|
|
32
|
-
Evaluate the cost of functional results with respect to a :class:`~qilisdk.
|
|
32
|
+
Evaluate the cost of functional results with respect to a :class:`~qilisdk.core.model.Model`.
|
|
33
33
|
|
|
34
34
|
Example:
|
|
35
35
|
.. code-block:: python
|
|
36
36
|
|
|
37
|
-
from qilisdk.
|
|
37
|
+
from qilisdk.core import BinaryVariable, Model, LEQ
|
|
38
38
|
from qilisdk.cost_functions import ModelCostFunction
|
|
39
39
|
|
|
40
40
|
model = Model("demo")
|
|
@@ -18,11 +18,11 @@ from typing import TYPE_CHECKING
|
|
|
18
18
|
import numpy as np
|
|
19
19
|
|
|
20
20
|
from qilisdk.analog.hamiltonian import Hamiltonian, PauliOperator
|
|
21
|
-
from qilisdk.
|
|
21
|
+
from qilisdk.core.qtensor import QTensor, expect_val, ket, tensor_prod
|
|
22
22
|
from qilisdk.cost_functions.cost_function import CostFunction
|
|
23
23
|
|
|
24
24
|
if TYPE_CHECKING:
|
|
25
|
-
from qilisdk.
|
|
25
|
+
from qilisdk.core.variables import Number
|
|
26
26
|
from qilisdk.functionals.sampling_result import SamplingResult
|
|
27
27
|
from qilisdk.functionals.time_evolution_result import TimeEvolutionResult
|
|
28
28
|
|
qilisdk/digital/ansatz.py
CHANGED
|
@@ -188,9 +188,6 @@ class HardwareEfficientAnsatz(Ansatz):
|
|
|
188
188
|
# Parameter iterator covering all single-qubit blocks, in order
|
|
189
189
|
parameter_iterator = iter(self._parameter_blocks())
|
|
190
190
|
|
|
191
|
-
# U(0)
|
|
192
|
-
self._apply_single_qubit_block(parameter_iterator)
|
|
193
|
-
|
|
194
191
|
# For each remaining layer: U -> E
|
|
195
192
|
if self.structure == "grouped":
|
|
196
193
|
for _ in range(self.layers):
|
qilisdk/digital/circuit.py
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import numpy as np
|
|
16
16
|
|
|
17
|
-
from qilisdk.
|
|
18
|
-
from qilisdk.
|
|
17
|
+
from qilisdk.core.parameterizable import Parameterizable
|
|
18
|
+
from qilisdk.core.variables import Parameter, RealNumber
|
|
19
19
|
from qilisdk.utils.visualization import CircuitStyle
|
|
20
20
|
from qilisdk.yaml import yaml
|
|
21
21
|
|
|
@@ -32,6 +32,7 @@ class Circuit(Parameterizable):
|
|
|
32
32
|
Args:
|
|
33
33
|
nqubits (int): The number of qubits in the circuit.
|
|
34
34
|
"""
|
|
35
|
+
super(Circuit, self).__init__()
|
|
35
36
|
self._nqubits: int = nqubits
|
|
36
37
|
self._gates: list[Gate] = []
|
|
37
38
|
self._init_state: np.ndarray = np.zeros(nqubits)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from qilisdk.digital import Circuit
|
|
15
|
+
|
|
16
|
+
from .circuit_transpiler_passes import CircuitTranspilerPass, DecomposeMultiControlledGatesPass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CircuitTranspiler:
|
|
20
|
+
"""Apply an ordered pipeline of circuit transpilation passes.
|
|
21
|
+
|
|
22
|
+
The transpiler acts as a thin orchestrator: each pass receives the circuit from the previous
|
|
23
|
+
pass and must return a brand-new circuit, allowing both structural rewrites and device-specific
|
|
24
|
+
lowering steps to be chained deterministically. Today the pipeline defaults to a single
|
|
25
|
+
`DecomposeMultiControlledGatesPass`, but the API is designed so additional passes—e.g. layout,
|
|
26
|
+
routing, or hardware-aware optimizers—can be composed in future iterations without changing
|
|
27
|
+
backend code.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
pipeline (list[CircuitTranspilerPass] | None): Sequential list of passes to execute while transpiling.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, pipeline: list[CircuitTranspilerPass] | None = None) -> None:
|
|
34
|
+
self._pipeline = pipeline or [DecomposeMultiControlledGatesPass()]
|
|
35
|
+
|
|
36
|
+
def transpile(self, circuit: Circuit) -> Circuit:
|
|
37
|
+
"""Run the configured pass pipeline over the provided circuit.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
circuit (Circuit): Circuit to be rewritten by the transpiler passes.
|
|
41
|
+
Returns:
|
|
42
|
+
Circuit: The circuit returned by the last pass in the pipeline.
|
|
43
|
+
"""
|
|
44
|
+
for transpiler_pass in self._pipeline:
|
|
45
|
+
circuit = transpiler_pass.run(circuit)
|
|
46
|
+
return circuit
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Copyright 2025 Qilimanjaro Quantum Tech
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from .circuit_transpiler_pass import CircuitTranspilerPass
|
|
16
|
+
from .decompose_multi_controlled_gates_pass import DecomposeMultiControlledGatesPass
|
|
17
|
+
|
|
18
|
+
__all__ = ["CircuitTranspilerPass", "DecomposeMultiControlledGatesPass"]
|