|
14 | 14 |
|
15 | 15 | """Utility methods for transforming matrices or vectors."""
|
16 | 16 |
|
17 |
| -from typing import Tuple, Optional, Sequence, List, Union |
| 17 | +import dataclasses |
| 18 | +from typing import Any, List, Optional, Sequence, Tuple, Union |
18 | 19 |
|
19 | 20 | import numpy as np
|
20 | 21 |
|
@@ -168,6 +169,82 @@ def targeted_left_multiply(
|
168 | 169 | ) # type: ignore
|
169 | 170 |
|
170 | 171 |
|
| 172 | +@dataclasses.dataclass |
| 173 | +class _SliceConfig: |
| 174 | + axis: int |
| 175 | + source_index: int |
| 176 | + target_index: int |
| 177 | + |
| 178 | + |
| 179 | +@dataclasses.dataclass |
| 180 | +class _BuildFromSlicesArgs: |
| 181 | + slices: Tuple[_SliceConfig, ...] |
| 182 | + scale: complex |
| 183 | + |
| 184 | + |
| 185 | +def _build_from_slices( |
| 186 | + args: Sequence[_BuildFromSlicesArgs], source: np.ndarray, out: np.ndarray |
| 187 | +) -> np.ndarray: |
| 188 | + """Populates `out` from the desired slices of `source`. |
| 189 | +
|
| 190 | + This function is best described by example. |
| 191 | +
|
| 192 | + For instance in 3*3*3 3D space, one could take a cube array, take all the horizontal slices, |
| 193 | + and add them up into the top slice leaving everything else zero. If the vertical axis was 1, |
| 194 | + and the top was index=2, then this would be written as follows: |
| 195 | +
|
| 196 | + _build_from_slices( |
| 197 | + [ |
| 198 | + _BuildFromSlicesArgs((_SliceConfig(axis=1, source_index=0, target_index=2),), 1), |
| 199 | + _BuildFromSlicesArgs((_SliceConfig(axis=1, source_index=1, target_index=2),), 1), |
| 200 | + _BuildFromSlicesArgs((_SliceConfig(axis=1, source_index=2, target_index=2),), 1), |
| 201 | + ], |
| 202 | + source, |
| 203 | + out, |
| 204 | + ) |
| 205 | +
|
| 206 | + When multiple slices are included in the _BuildFromSlicesArgs, this means to take the |
| 207 | + intersection of the source space and move it to the intersection of the target space. For |
| 208 | + example, the following takes the bottom-left edge and moves it to the top-right, leaving all |
| 209 | + other cells zero. Assume the lateral axis is 2 and right-most index thereof is 2: |
| 210 | +
|
| 211 | + _build_from_slices( |
| 212 | + [ |
| 213 | + _BuildFromSlicesArgs( |
| 214 | + ( |
| 215 | + _SliceConfig(axis=1, source_index=0, target_index=2), # top |
| 216 | + _SliceConfig(axis=2, source_index=0, target_index=2), # right |
| 217 | + ), |
| 218 | + scale=1, |
| 219 | + ), |
| 220 | + ], |
| 221 | + source, |
| 222 | + out, |
| 223 | + ) |
| 224 | +
|
| 225 | + This function is useful for optimizing multiplying a state by one or more one-hot matrices, |
| 226 | + as is common when working with Kraus components. It is more efficient than using an einsum. |
| 227 | +
|
| 228 | + Args: |
| 229 | + args: The list of slice configurations to sum up into the output. |
| 230 | + source: The source tensor for the slice data. |
| 231 | + out: An output tensor that is the same shape as the source. |
| 232 | +
|
| 233 | + Returns: |
| 234 | + The output tensor. |
| 235 | + """ |
| 236 | + d = len(source.shape) |
| 237 | + out[...] = 0 |
| 238 | + for arg in args: |
| 239 | + source_slice: List[Any] = [slice(None)] * d |
| 240 | + target_slice: List[Any] = [slice(None)] * d |
| 241 | + for sleis in arg.slices: |
| 242 | + source_slice[sleis.axis] = sleis.source_index |
| 243 | + target_slice[sleis.axis] = sleis.target_index |
| 244 | + out[tuple(target_slice)] += arg.scale * source[tuple(source_slice)] |
| 245 | + return out |
| 246 | + |
| 247 | + |
171 | 248 | def targeted_conjugate_about(
|
172 | 249 | tensor: np.ndarray,
|
173 | 250 | target: np.ndarray,
|
|
0 commit comments