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.
Files changed (49) hide show
  1. qilisdk/analog/__init__.py +1 -2
  2. qilisdk/analog/hamiltonian.py +4 -71
  3. qilisdk/analog/schedule.py +291 -313
  4. qilisdk/backends/backend.py +5 -1
  5. qilisdk/backends/cuda_backend.py +10 -6
  6. qilisdk/backends/qutip_backend.py +24 -32
  7. qilisdk/{common → core}/__init__.py +4 -0
  8. qilisdk/core/interpolator.py +406 -0
  9. qilisdk/{common → core}/model.py +7 -7
  10. qilisdk/core/parameterizable.py +131 -0
  11. qilisdk/{common → core}/qtensor.py +1 -1
  12. qilisdk/{common → core}/variables.py +192 -11
  13. qilisdk/cost_functions/cost_function.py +1 -1
  14. qilisdk/cost_functions/model_cost_function.py +5 -5
  15. qilisdk/cost_functions/observable_cost_function.py +2 -2
  16. qilisdk/digital/ansatz.py +0 -3
  17. qilisdk/digital/circuit.py +3 -2
  18. qilisdk/digital/circuit_transpiler.py +46 -0
  19. qilisdk/digital/circuit_transpiler_passes/__init__.py +18 -0
  20. qilisdk/digital/circuit_transpiler_passes/circuit_transpiler_pass.py +36 -0
  21. qilisdk/digital/circuit_transpiler_passes/decompose_multi_controlled_gates_pass.py +216 -0
  22. qilisdk/digital/circuit_transpiler_passes/numeric_helpers.py +82 -0
  23. qilisdk/digital/gates.py +15 -5
  24. qilisdk/{speqtrum/experiments → experiments}/__init__.py +13 -2
  25. qilisdk/{speqtrum/experiments → experiments}/experiment_functional.py +90 -2
  26. qilisdk/{speqtrum/experiments → experiments}/experiment_result.py +16 -0
  27. qilisdk/functionals/functional.py +2 -2
  28. qilisdk/functionals/functional_result.py +1 -1
  29. qilisdk/functionals/sampling.py +8 -1
  30. qilisdk/functionals/time_evolution.py +8 -4
  31. qilisdk/functionals/time_evolution_result.py +2 -2
  32. qilisdk/functionals/variational_program.py +58 -0
  33. qilisdk/optimizers/optimizer_result.py +1 -1
  34. qilisdk/speqtrum/__init__.py +2 -0
  35. qilisdk/speqtrum/speqtrum.py +537 -152
  36. qilisdk/speqtrum/speqtrum_models.py +258 -2
  37. qilisdk/utils/openfermion/__init__.py +38 -0
  38. qilisdk/{common/algorithm.py → utils/openfermion/__init__.pyi} +2 -3
  39. qilisdk/utils/openfermion/openfermion.py +45 -0
  40. qilisdk/utils/visualization/schedule_renderers.py +22 -9
  41. {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/METADATA +89 -39
  42. qilisdk-0.1.7.dist-info/RECORD +76 -0
  43. {qilisdk-0.1.5.dist-info → qilisdk-0.1.7.dist-info}/WHEEL +1 -1
  44. qilisdk/analog/linear_schedule.py +0 -118
  45. qilisdk/common/parameterizable.py +0 -75
  46. qilisdk-0.1.5.dist-info/RECORD +0 -69
  47. /qilisdk/{common → core}/exceptions.py +0 -0
  48. /qilisdk/{common → core}/result.py +0 -0
  49. {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
@@ -66,7 +66,7 @@ class QTensor:
66
66
  .. code-block:: python
67
67
 
68
68
  import numpy as np
69
- from qilisdk.common import QTensor
69
+ from qilisdk.core import QTensor
70
70
 
71
71
  ket = QTensor(np.array([[1.0], [0.0]]))
72
72
  density = ket * ket.adjoint()
@@ -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.common.exceptions import EvaluationError, InvalidBoundsError, NotSupportedOperation, OutOfBoundsException
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
- return hash((self._label, self._domain.value, self._bounds))
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.common.variables import BinaryVariable
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.common.variables import Domain, Variable
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.common.variables import Parameter
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 set_value(self, value: RealNumber) -> None:
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 = 0) -> 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
- _var_values[var] = var.value
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.common.variables import Number
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.common.model import QUBO, Model
21
- from qilisdk.common.qtensor import QTensor, expect_val, ket
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.common.variables import Number
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.common.model.Model`.
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.common import BinaryVariable, Model, LEQ
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.common.qtensor import QTensor, expect_val, ket, tensor_prod
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.common.variables import Number
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):
@@ -14,8 +14,8 @@
14
14
 
15
15
  import numpy as np
16
16
 
17
- from qilisdk.common.parameterizable import Parameterizable
18
- from qilisdk.common.variables import Parameter, RealNumber
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"]