emu-sv 1.0.1__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
emu_sv/__init__.py CHANGED
@@ -1,42 +1,38 @@
1
- from emu_sv.state_vector import StateVector, inner
2
- from emu_sv.dense_operator import DenseOperator
3
- from emu_sv.sv_backend import SVBackend, SVConfig
4
- from emu_base.base_classes import Results
5
- from emu_base.base_classes.callback import AggregationType
1
+ from pulser.backend.results import Results
6
2
 
7
- from emu_base.base_classes import (
8
- Callback,
3
+ from pulser.backend import (
9
4
  BitStrings,
10
5
  CorrelationMatrix,
11
6
  Energy,
12
7
  EnergyVariance,
8
+ EnergySecondMoment,
13
9
  Expectation,
14
- QubitDensity,
15
- StateResult,
16
- SecondMomentOfEnergy,
17
10
  Fidelity,
11
+ Occupation,
12
+ StateResult,
18
13
  )
19
14
 
15
+ from .dense_operator import DenseOperator
16
+ from .sv_backend import SVBackend, SVConfig
17
+ from .state_vector import StateVector, inner
18
+
20
19
  __all__ = [
21
20
  "__version__",
22
- "StateVector",
23
- "DenseOperator",
24
- "inner",
25
- "SVBackend",
26
- "SVConfig",
27
- "Callback",
28
21
  "BitStrings",
29
22
  "CorrelationMatrix",
23
+ "DenseOperator",
30
24
  "Energy",
25
+ "EnergySecondMoment",
31
26
  "EnergyVariance",
32
27
  "Expectation",
33
28
  "Fidelity",
34
- "QubitDensity",
35
- "StateResult",
36
- "SecondMomentOfEnergy",
37
- "AggregationType",
29
+ "Occupation",
38
30
  "Results",
31
+ "SVBackend",
32
+ "SVConfig",
33
+ "StateResult",
34
+ "StateVector",
35
+ "inner",
39
36
  ]
40
37
 
41
-
42
- __version__ = "1.0.1"
38
+ __version__ = "2.0.1"
@@ -1,93 +1,115 @@
1
- import math
2
1
  import torch
3
2
 
4
- from emu_base.base_classes.config import BackendConfig
5
- from emu_base.base_classes.default_callbacks import (
6
- QubitDensity,
7
- EnergyVariance,
8
- SecondMomentOfEnergy,
3
+ from pulser.backend import (
9
4
  CorrelationMatrix,
5
+ EmulationConfig,
6
+ EnergySecondMoment,
7
+ EnergyVariance,
8
+ Occupation,
9
+ Energy,
10
10
  )
11
- from emu_base.base_classes.operator import Operator
12
11
 
13
- from emu_sv import StateVector
12
+ from emu_sv.state_vector import StateVector
13
+ from emu_sv.dense_operator import DenseOperator
14
14
  from emu_sv.hamiltonian import RydbergHamiltonian
15
15
 
16
16
 
17
- def qubit_density_sv_impl(
18
- self: QubitDensity, config: BackendConfig, t: int, state: StateVector, H: Operator
17
+ def qubit_occupation_sv_impl(
18
+ self: Occupation,
19
+ *,
20
+ config: EmulationConfig,
21
+ state: StateVector,
22
+ hamiltonian: DenseOperator,
19
23
  ) -> torch.Tensor:
20
24
  """
21
- Custom implementation of the qubit density ❬ψ|nᵢ|ψ❭ for the state vector solver.
25
+ Custom implementation of the occupation ❬ψ|nᵢ|ψ❭ for the state vector solver.
22
26
  """
23
- nqubits = int(math.log2(len(state.vector)))
24
- state_tensor = state.vector.reshape((2,) * nqubits)
25
-
26
- qubit_density = torch.zeros(nqubits, dtype=torch.float64, device=state_tensor.device)
27
+ nqubits = state.n_qudits
28
+ occupation = torch.zeros(nqubits, dtype=torch.float64, device=state.vector.device)
27
29
  for i in range(nqubits):
28
- qubit_density[i] = state_tensor.select(i, 1).norm() ** 2
29
- return qubit_density
30
+ state_tensor = state.vector.view(2**i, 2, -1)
31
+ # nᵢ is a projector and therefore nᵢ == nᵢnᵢ
32
+ # ❬ψ|nᵢ|ψ❭ == ❬ψ|nᵢnᵢ|ψ❭ == ❬ψ|nᵢ * nᵢ|ψ❭ == ❬ϕ|ϕ❭ == |ϕ|**2
33
+ occupation[i] = torch.linalg.vector_norm(state_tensor[:, 1]) ** 2
34
+ return occupation.cpu()
30
35
 
31
36
 
32
37
  def correlation_matrix_sv_impl(
33
38
  self: CorrelationMatrix,
34
- config: BackendConfig,
35
- t: int,
39
+ *,
40
+ config: EmulationConfig,
36
41
  state: StateVector,
37
- H: Operator,
42
+ hamiltonian: DenseOperator,
38
43
  ) -> torch.Tensor:
39
44
  """
40
- Custom implementation of the density-density correlation ❬ψ|nᵢnⱼ|ψ❭ for the state vector solver.
41
-
45
+ Custom implementation of the density-density correlation ❬ψ|nᵢnⱼ|ψ❭
46
+ for the state vector solver.
42
47
  TODO: extend to arbitrary two-point correlation ❬ψ|AᵢBⱼ|ψ❭
43
48
  """
44
- nqubits = int(math.log2(len(state.vector)))
45
- state_tensor = state.vector.reshape((2,) * nqubits)
46
-
49
+ nqubits = state.n_qudits
47
50
  correlation = torch.zeros(
48
- nqubits, nqubits, dtype=torch.float64, device=state_tensor.device
51
+ nqubits, nqubits, dtype=torch.float64, device=state.vector.device
49
52
  )
50
53
 
51
54
  for i in range(nqubits):
52
- select_i = state_tensor.select(i, 1)
55
+ select_i = state.vector.view(2**i, 2, -1)
56
+ select_i = select_i[:, 1]
53
57
  for j in range(i, nqubits): # select the upper triangle
54
58
  if i == j:
55
- value = select_i.norm() ** 2
59
+ value = torch.linalg.vector_norm(select_i) ** 2
60
+ correlation[j, j] = value
56
61
  else:
57
- value = select_i.select(j - 1, 1).norm() ** 2
62
+ select_i = select_i.view(2**i, 2 ** (j - i - 1), 2, -1)
63
+ select_ij = select_i[:, :, 1, :]
64
+ value = torch.linalg.vector_norm(select_ij) ** 2
65
+ correlation[i, j] = value
66
+ correlation[j, i] = value
58
67
 
59
- correlation[i, j] = value
60
- correlation[j, i] = value
61
- return correlation
68
+ return correlation.cpu()
62
69
 
63
70
 
64
71
  def energy_variance_sv_impl(
65
72
  self: EnergyVariance,
66
- config: BackendConfig,
67
- t: int,
73
+ *,
74
+ config: EmulationConfig,
68
75
  state: StateVector,
69
- H: RydbergHamiltonian,
76
+ hamiltonian: RydbergHamiltonian,
70
77
  ) -> torch.Tensor:
71
78
  """
72
79
  Custom implementation of the energy variance ❬ψ|H²|ψ❭-❬ψ|H|ψ❭² for the state vector solver.
73
80
  """
74
- hstate = H * state.vector
81
+ hstate = hamiltonian * state.vector
75
82
  h_squared = torch.vdot(hstate, hstate).real
76
83
  energy = torch.vdot(state.vector, hstate).real
77
- energy_variance: torch.Tensor = h_squared - energy**2
78
- return energy_variance
84
+ en_var: torch.Tensor = h_squared - energy**2
85
+ return en_var.cpu()
79
86
 
80
87
 
81
- def second_moment_sv_impl(
82
- self: SecondMomentOfEnergy,
83
- config: BackendConfig,
84
- t: int,
88
+ def energy_second_moment_sv_impl(
89
+ self: EnergySecondMoment,
90
+ *,
91
+ config: EmulationConfig,
85
92
  state: StateVector,
86
- H: RydbergHamiltonian,
93
+ hamiltonian: RydbergHamiltonian,
87
94
  ) -> torch.Tensor:
88
95
  """
89
96
  Custom implementation of the second moment of energy ❬ψ|H²|ψ❭
90
97
  for the state vector solver.
91
98
  """
92
- hstate = H * state.vector
93
- return torch.vdot(hstate, hstate).real
99
+ hstate = hamiltonian * state.vector
100
+ en_2_mom: torch.Tensor = torch.vdot(hstate, hstate).real
101
+ return en_2_mom.cpu()
102
+
103
+
104
+ def energy_sv_impl(
105
+ self: Energy,
106
+ *,
107
+ config: EmulationConfig,
108
+ state: StateVector,
109
+ hamiltonian: RydbergHamiltonian,
110
+ ) -> torch.Tensor:
111
+ """
112
+ Custom implementation of the energy ❬ψ|H|ψ❭ for the state vector solver.
113
+ """
114
+ en: torch.Tensor = hamiltonian.expect(state)
115
+ return en
emu_sv/dense_operator.py CHANGED
@@ -1,12 +1,22 @@
1
1
  from __future__ import annotations
2
+
2
3
  import itertools
3
- from typing import Any, Iterable
4
+ from functools import reduce
4
5
 
5
6
  import torch
6
- from emu_base.base_classes.operator import FullOp, QuditOp
7
- from emu_base import Operator, State, DEVICE_COUNT
7
+
8
+ from typing import Sequence, Type
9
+
10
+ from emu_base import DEVICE_COUNT
8
11
  from emu_sv.state_vector import StateVector
9
12
 
13
+ from pulser.backend import (
14
+ Operator,
15
+ State,
16
+ )
17
+ from pulser.backend.operator import FullOp, QuditOp
18
+ from pulser.backend.state import Eigenstate
19
+
10
20
  dtype = torch.complex128
11
21
 
12
22
 
@@ -29,8 +39,8 @@ def _validate_operator_targets(operations: FullOp, nqubits: int) -> None:
29
39
  )
30
40
 
31
41
 
32
- class DenseOperator(Operator):
33
- """Operators in EMU-SV are dense matrices"""
42
+ class DenseOperator(Operator[complex, torch.Tensor, StateVector]):
43
+ """DenseOperator in EMU-SV use dense matrices"""
34
44
 
35
45
  def __init__(
36
46
  self,
@@ -46,154 +56,145 @@ class DenseOperator(Operator):
46
56
 
47
57
  def __matmul__(self, other: Operator) -> DenseOperator:
48
58
  """
49
- Apply this operator to a other. The ordering is that
50
- self is applied after other.
59
+ Compose two DenseOperators via matrix multiplication.
51
60
 
52
61
  Args:
53
- other: the operator to compose with self
62
+ other: a DenseOperator instance.
54
63
 
55
64
  Returns:
56
- the composed operator
65
+ A new DenseOperator representing the product `self @ other`.
57
66
  """
58
67
  assert isinstance(
59
68
  other, DenseOperator
60
- ), "DenseOperator can only be multiplied with Operator"
61
-
69
+ ), "DenseOperator can only be multiplied with a DenseOperator."
62
70
  return DenseOperator(self.matrix @ other.matrix)
63
71
 
64
72
  def __add__(self, other: Operator) -> DenseOperator:
65
73
  """
66
- Returns the sum of two matrices
74
+ Element-wise addition of two DenseOperators.
67
75
 
68
76
  Args:
69
- other: the other operator
77
+ other: a DenseOperator instance.
70
78
 
71
79
  Returns:
72
- the summed operator
80
+ A new DenseOperator representing the sum.
73
81
  """
74
- assert isinstance(other, DenseOperator), "MPO can only be added to another MPO"
75
-
82
+ assert isinstance(
83
+ other, DenseOperator
84
+ ), "DenseOperator can only be added to another DenseOperator."
76
85
  return DenseOperator(self.matrix + other.matrix)
77
86
 
78
87
  def __rmul__(self, scalar: complex) -> DenseOperator:
79
88
  """
80
- Multiply a DenseOperator by scalar.
89
+ Scalar multiplication of the DenseOperator.
81
90
 
82
91
  Args:
83
- scalar: the scale factor to multiply with
92
+ scalar: a number to scale the operator.
84
93
 
85
94
  Returns:
86
- the scaled MPO
95
+ A new DenseOperator scaled by the given scalar.
87
96
  """
88
97
 
89
- return DenseOperator(self.matrix * scalar)
98
+ return DenseOperator(scalar * self.matrix)
90
99
 
91
- def __mul__(self, other: State) -> StateVector:
100
+ def apply_to(self, other: State) -> StateVector:
92
101
  """
93
- Applies this DenseOperator to the given StateVector.
102
+ Apply the DenseOperator to a given StateVector.
94
103
 
95
104
  Args:
96
- other: the state to apply this operator to
105
+ other: a StateVector instance.
97
106
 
98
107
  Returns:
99
- the resulting state
108
+ A new StateVector after applying the operator.
100
109
  """
101
110
  assert isinstance(
102
111
  other, StateVector
103
- ), "DenseOperator can only be applied to another DenseOperator"
112
+ ), "DenseOperator can only be applied to a StateVector."
104
113
 
105
114
  return StateVector(self.matrix @ other.vector)
106
115
 
107
- def expect(self, state: State) -> float | complex:
116
+ def expect(self, state: State) -> torch.Tensor:
108
117
  """
109
- Compute the expectation value of self on the given state.
118
+ Compute the expectation value of the operator with respect to a state.
110
119
 
111
120
  Args:
112
- state: the state with which to compute
121
+ state: a StateVector instance.
113
122
 
114
123
  Returns:
115
- the expectation
124
+ The expectation value as a float or complex number.
116
125
  """
117
126
  assert isinstance(
118
127
  state, StateVector
119
- ), "currently, only expectation values of StateVectors are \
120
- supported"
121
-
122
- return torch.vdot(state.vector, self.matrix @ state.vector).item()
123
-
124
- @staticmethod
125
- def from_operator_string(
126
- basis: Iterable[str],
127
- nqubits: int,
128
- operations: FullOp,
129
- operators: dict[str, QuditOp] = {},
130
- /,
131
- **kwargs: Any,
132
- ) -> DenseOperator:
128
+ ), "Only expectation values of StateVectors are supported."
129
+
130
+ return torch.vdot(state.vector, self.apply_to(state).vector).cpu()
131
+
132
+ @classmethod
133
+ def _from_operator_repr(
134
+ cls: Type[DenseOperator],
135
+ *,
136
+ eigenstates: Sequence[Eigenstate],
137
+ n_qudits: int,
138
+ operations: FullOp[complex],
139
+ ) -> tuple[DenseOperator, FullOp[complex]]:
133
140
  """
134
- See the base class
141
+ Construct a DenseOperator from an operator representation.
135
142
 
136
143
  Args:
137
- basis: the eigenstates in the basis to use e.g. ('r', 'g')
138
- nqubits: how many qubits there are in the state
139
- operations: which bitstrings make up the state with what weight
140
- operators: additional symbols to be used in operations
144
+ eigenstates: the eigenstates of the basis to use, e.g. ("r", "g") or ("0", "1").
145
+ n_qudits: number of qudits in the system.
146
+ operations: which bitstrings make up the state with what weight.
141
147
 
142
148
  Returns:
143
- the operator in MPO form.
149
+ A DenseOperator instance corresponding to the given representation.
144
150
  """
145
151
 
146
- _validate_operator_targets(operations, nqubits)
152
+ _validate_operator_targets(operations, n_qudits)
153
+ assert len(set(eigenstates)) == 2, "Only qubits are supported in EMU-SV."
147
154
 
148
- operators_with_tensors: dict[str, torch.Tensor | QuditOp] = dict(operators)
155
+ operators_with_tensors: dict[str, torch.Tensor | QuditOp] = dict()
149
156
 
150
- basis = set(basis)
151
- if basis == {"r", "g"}:
157
+ if set(eigenstates) == {"r", "g"}:
152
158
  # operators_with_tensors will now contain the basis for single qubit ops,
153
- # and potentially user defined strings in terms of these
159
+ # and potentially user defined strings in terms of {r, g} or {0, 1}
154
160
  operators_with_tensors |= {
155
- "gg": torch.tensor([[1.0, 0.0], [0.0, 0.0]], dtype=torch.complex128),
156
- "gr": torch.tensor([[0.0, 0.0], [1.0, 0.0]], dtype=torch.complex128),
157
- "rg": torch.tensor([[0.0, 1.0], [0.0, 0.0]], dtype=torch.complex128),
158
- "rr": torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=torch.complex128),
159
- }
160
- elif basis == {"0", "1"}:
161
- # operators_with_tensors will now contain the basis for single qubit ops,
162
- # and potentially user defined strings in terms of these
163
- operators_with_tensors |= {
164
- "00": torch.tensor([[1.0, 0.0], [0.0, 0.0]], dtype=torch.complex128),
165
- "01": torch.tensor([[0.0, 0.0], [1.0, 0.0]], dtype=torch.complex128),
166
- "10": torch.tensor([[0.0, 1.0], [0.0, 0.0]], dtype=torch.complex128),
167
- "11": torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=torch.complex128),
161
+ "gg": torch.tensor([[1.0, 0.0], [0.0, 0.0]], dtype=dtype),
162
+ "gr": torch.tensor([[0.0, 0.0], [1.0, 0.0]], dtype=dtype),
163
+ "rg": torch.tensor([[0.0, 1.0], [0.0, 0.0]], dtype=dtype),
164
+ "rr": torch.tensor([[0.0, 0.0], [0.0, 1.0]], dtype=dtype),
168
165
  }
166
+ elif set(eigenstates) == {"0", "1"}:
167
+ raise NotImplementedError(
168
+ "{'0','1'} basis is related to XY Hamiltonian, which is not implemented"
169
+ )
169
170
  else:
170
- raise ValueError("Unsupported basis provided")
171
-
172
- accum_res = torch.zeros(2**nqubits, 2**nqubits, dtype=torch.complex128)
173
- for coeff, tensorop in operations:
174
- # this function will recurse through the operators_with_tensors,
175
- # and replace any definitions in terms of strings by the computed matrix
176
- def replace_operator_string(op: QuditOp | torch.Tensor) -> torch.Tensor:
177
- if isinstance(op, torch.Tensor):
178
- return op
179
-
180
- result = torch.zeros(2, 2, dtype=torch.complex128)
181
- for opstr, coeff in op.items():
182
- tensor = replace_operator_string(operators_with_tensors[opstr])
171
+ raise ValueError("An unsupported basis of eigenstates has been provided.")
172
+
173
+ accum_res = torch.zeros(2**n_qudits, 2**n_qudits, dtype=dtype)
174
+ for coeff, oper_torch_with_target_qubits in operations:
175
+
176
+ def build_torch_operator_from_string(
177
+ oper: QuditOp | torch.Tensor,
178
+ ) -> torch.Tensor:
179
+ if isinstance(oper, torch.Tensor):
180
+ return oper
181
+
182
+ result = torch.zeros((2, 2), dtype=dtype)
183
+ for opstr, coeff in oper.items():
184
+ tensor = build_torch_operator_from_string(
185
+ operators_with_tensors[opstr]
186
+ )
183
187
  operators_with_tensors[opstr] = tensor
184
188
  result += tensor * coeff
185
189
  return result
186
190
 
187
- total_op_per_qubit = [torch.eye(2, 2, dtype=torch.complex128)] * nqubits
191
+ single_qubit_gates = [torch.eye(2, dtype=dtype) for _ in range(n_qudits)]
188
192
 
189
- for op in tensorop:
190
- factor = replace_operator_string(op[0])
191
- for target_qubit in op[1]:
192
- total_op_per_qubit[target_qubit] = factor
193
+ for operator_torch, target_qubits in oper_torch_with_target_qubits:
194
+ factor = build_torch_operator_from_string(operator_torch)
195
+ for target_qubit in target_qubits:
196
+ single_qubit_gates[target_qubit] = factor
193
197
 
194
- dense_op = total_op_per_qubit[0]
195
- for single_qubit_operator in total_op_per_qubit[1:]:
196
- dense_op = torch.kron(dense_op, single_qubit_operator)
198
+ accum_res += coeff * reduce(torch.kron, single_qubit_gates)
197
199
 
198
- accum_res += coeff * dense_op
199
- return DenseOperator(accum_res)
200
+ return DenseOperator(accum_res), operations