Skip to content
This repository was archived by the owner on Nov 7, 2024. It is now read-only.

Commit 35d1247

Browse files
mganahlChase Roberts
andauthored
Final PR for block sparsity (#506)
* remove line * add docstring * add svd * fix bug in svd and diag bug appeared when tensors where empty * fixed empyt-array bug in qrr * added tests * fix bug for empty arrays * sone typing stuff * tests * uncomment tests * added eig, sqrt, trace * add routines, fix some typing issues * formatting of exception * linting * added pinv, ones, rannd, randn, zeros * fix typing issues * add pinv remove conj and T from subclassed BlockSparseTensor again * added conj and transpose back typing with __new__ is a pain ... * some more tests * fix transpose arguments add docstring to conj * more complete docstring for diag * typo * space * docstring * fix return type in docstring * improve docstring of outerproduct * set numpy seed * disable annoying linting error * dito * ditto * add dtpye fixture to test_tensordot_reshape * fake line * remove fake line * numpy>=1.17 * added decompositions * add files * rename rand to random * rand -> random, fix bug * add `relative` argument to svd_decomposition * add `relative` argument to svd_decomposition * add `relative` * remove , * fix svd_decomposition * added 'symmetric' backend * adding symmetric backend * remove comment * added init * fix __init__ * test tensordot raises * more tests * added test files * added symmetric backend support * added sparse_shape * fixing the last pieces * added sparse_shape to TestNode definition * final touches * change size dtypes to np.unit64 * changed size dtypes to np.int64 * add size_t to change index-array size types * capitalize size_t * newline * typo-bug * add unit tests * added tests * fix dtype issue (again) * fix edge case of empty arrays * add tests * more comprehensive testing * fix tests * fix bug in _find_diagonal_sparse_blocks for empty tensors * remove print statements * fix edge case of empty charges * fix tests * reduce_charges: edge case of empty charge * linting * remove comment * linting * fix edge case of empty input in BaseCharge.__init__ * raise if input to __eq__ is empty * fix edge case of empty charges in __add__ * fix edge case of empty charges in `isin` * fix bug in cahrge_test.py * fix pytest warning * fix warning * more comprehensive testing * test svd for empty tensors * more comprehensive testing * more comprehensive testing * more comprehensive testing of edge cases * edge case tests for eig * shorten code * edge case fixed * fix bug _find_transposed_diagonal_sparse_blocks * fix edge case in diag * testing edge cases of `diag` * raise error when reshaping empty tensors * fix tests * fix reshape * linting * better testing * linting * more comprehensive testing * remove redundant test * more comprehensive testing * linting * remove lines * shorten import code * change import * fix bug in tensordot for inner product mode add outerproduct mode to tensordot * fix tests * more comprehensive testing * return np.ndarray for tensordot in inner mode * fix import * fix import * fix import * fix bug * fix warning * more checks for input to tensordot * conform to numpy convention when using tensordot for inner product on empty tensors * fix tests * remove print * fix outerproduct in tensordot * raise ValueError in pinv * add raise-tests * fix typo * comment * added two more checks to tensordot * more tests * fix return type of tensordot * restructuring files * added files * remove unused type * remove unused types * remove unused types * remove unused types * import outerproduct * remove unused imports * fix imports * fix `relative` case * formatting * import norm * fix imports * remove unneeded import * add TODO * fix import * tensordot returns BlockSparseTensor for inner product. * fix test * fix imports in tests * fix issue with divison by 0 for empty tensors * remove comment * fix typo * add LABEL_SIZE_T variable to set the size type of charge_labels * remove hard coded dtypes for charges * fix overflow bug when using > 3 symmetries * remove comment * remove comment * rename variable * remove sparse_shape * remove sparse_shape * more comprehensive testing * fix tests * remove unused functions * extend tests to include more than 1 symmetry * linting * more tests * typo in docstring * adding broadcast_right_multiplication to backends * fix test * bradcasting support added * remove backend specific code Co-authored-by: Chase Roberts <chaseriley@google.com>
1 parent 7404ed8 commit 35d1247

11 files changed

Lines changed: 922 additions & 125 deletions

tensornetwork/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from tensornetwork.matrixproductstates.finite_mps import FiniteMPS
1515
from tensornetwork.matrixproductstates.infinite_mps import InfiniteMPS
1616
from tensornetwork.backend_contextmanager import DefaultBackend, set_default_backend
17+
from tensornetwork import block_sparse
1718
from tensornetwork.block_sparse.blocksparsetensor import BlockSparseTensor, ChargeArray
1819
from tensornetwork.block_sparse.index import Index
1920
from tensornetwork.block_sparse.charge import U1Charge, BaseCharge

tensornetwork/block_sparse/blocksparsetensor.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ def outerproduct(tensor1: BlockSparseTensor,
766766
def tensordot(tensor1: BlockSparseTensor,
767767
tensor2: BlockSparseTensor,
768768
axes: Optional[Union[Sequence[Sequence[int]], int]] = 2
769-
) -> Union[BlockSparseTensor, np.number]:
769+
) -> BlockSparseTensor:
770770
"""
771771
Contract two `BlockSparseTensor`s along `axes`.
772772
Args:
@@ -856,9 +856,20 @@ def tensordot(tensor1: BlockSparseTensor,
856856
if (len(axes1) == tensor1.ndim) and (len(axes2) == tensor2.ndim):
857857
t1 = tensor1.transpose(axes1).transpose_data()
858858
t2 = tensor2.transpose(axes2).transpose_data()
859-
#NOTE (mganahl): for t1.data=[] and t2.data=[] this returns 0.0,
860-
#is consistent with numpy behaviour.
861-
return np.dot(t1.data, t2.data)
859+
data = np.dot(t1.data, t2.data)
860+
charge = tensor1._charges[0]
861+
final_charge = charge.__new__(type(charge))
862+
863+
final_charge.__init__(
864+
np.empty((charge.num_symmetries, 0), dtype=np.int16),
865+
charge_labels=np.empty(0, dtype=np.int16),
866+
charge_types=charge.charge_types)
867+
return BlockSparseTensor(
868+
data=data,
869+
charges=[final_charge],
870+
flows=[False],
871+
order=[[0]],
872+
check_consistency=False)
862873

863874
#in all other cases we perform a regular tensordot
864875
free_axes1 = sorted(set(np.arange(tensor1.ndim)) - set(axes1))

tensornetwork/block_sparse/charge.py

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def __next__(self):
5959
def __init__(self,
6060
charges: np.ndarray,
6161
charge_labels: Optional[np.ndarray] = None,
62-
charge_types: Optional[List[Type["BaseCharge"]]] = None) -> None:
62+
charge_types: Optional[List[Type["BaseCharge"]]] = None,
63+
charge_dtype: Optional[Type[np.number]] = np.int16) -> None:
6364
if charges.ndim == 1:
6465
charges = charges[None, :]
6566

@@ -68,20 +69,25 @@ def __init__(self,
6869
"`len(charge_types) = {}` does not match `charges.shape[0]={}`"
6970
.format(len(charge_types), charges.shape[0]))
7071

72+
if charges.shape[0] <= 3:
73+
label_dtype = np.int16
74+
else:
75+
label_dtype = np.int32
7176
self.charge_types = charge_types
7277
if charge_labels is None:
7378
if charges.shape[1] > 0:
7479
self.unique_charges, self.charge_labels = np.unique(
75-
charges.astype(np.int16), return_inverse=True, axis=1)
76-
self.charge_labels = self.charge_labels.astype(np.int16)
80+
charges.astype(charge_dtype), return_inverse=True, axis=1)
81+
self.charge_labels = self.charge_labels.astype(label_dtype)
7782
else:
78-
self.unique_charges = np.empty((charges.shape[0], 0), dtype=np.int16)
79-
self.charge_labels = np.empty(0, dtype=np.int16)
83+
self.unique_charges = np.empty((charges.shape[0], 0),
84+
dtype=charge_dtype)
85+
self.charge_labels = np.empty(0, dtype=label_dtype)
8086
else:
81-
self.charge_labels = np.asarray(charge_labels, dtype=np.int16)
87+
self.charge_labels = np.asarray(charge_labels, dtype=label_dtype)
8288

83-
self.unique_charges = charges.astype(np.int16)
84-
self.charge_labels = charge_labels.astype(np.int16)
89+
self.unique_charges = charges.astype(charge_dtype)
90+
self.charge_labels = charge_labels.astype(label_dtype)
8591

8692
@staticmethod
8793
def fuse(charge1, charge2):
@@ -142,10 +148,15 @@ def charges(self):
142148
def dtype(self):
143149
return self.unique_charges.dtype
144150

151+
@property
152+
def label_dtype(self):
153+
return self.charge_labels.dtype
154+
145155
@property
146156
def degeneracies(self):
147157
exp1 = self.charge_labels[:, None]
148-
exp2 = np.arange(self.unique_charges.shape[1], dtype=np.int16)[None, :]
158+
exp2 = np.arange(
159+
self.unique_charges.shape[1], dtype=self.label_dtype)[None, :]
149160
return np.sum(exp1 == exp2, axis=0)
150161

151162
def __repr__(self):
@@ -194,8 +205,8 @@ def identity_charges(self) -> "BaseCharge":
194205
"""
195206
unique_charges = np.asarray(
196207
[ct.identity_charge() for ct in self.charge_types],
197-
dtype=np.int16)[:, None]
198-
charge_labels = np.zeros(1, dtype=np.int16)
208+
dtype=self.dtype)[:, None]
209+
charge_labels = np.zeros(1, dtype=self.label_dtype)
199210
obj = self.__new__(type(self))
200211
obj.__init__(unique_charges, charge_labels, self.charge_types)
201212
return obj
@@ -217,19 +228,19 @@ def __add__(self, other: "BaseCharge") -> "BaseCharge":
217228
other.charge_labels) == 0):
218229
obj = self.__new__(type(self))
219230
obj.__init__(
220-
np.empty((self.num_symmetries, 0), dtype=np.int16),
221-
np.empty(0, dtype=np.int16), self.charge_types)
231+
np.empty((self.num_symmetries, 0), dtype=self.dtype),
232+
np.empty(0, dtype=self.label_dtype), self.charge_types)
222233
return obj
223234
unique_charges, charge_labels = np.unique(
224235
comb_charges, return_inverse=True, axis=1)
225236
charge_labels = charge_labels.reshape(self.unique_charges.shape[1],
226237
other.unique_charges.shape[1]).astype(
227-
np.int16)
238+
self.label_dtype)
228239
# find new labels using broadcasting
229240
left_labels = self.charge_labels[:, None] + np.zeros([1, len(other)],
230-
dtype=np.int16)
231-
right_labels = other.charge_labels[None, :] + np.zeros([len(self), 1],
232-
dtype=np.int16)
241+
dtype=self.label_dtype)
242+
right_labels = other.charge_labels[None, :] + np.zeros(
243+
[len(self), 1], dtype=self.label_dtype)
233244
charge_labels = charge_labels[np.ravel(left_labels), np.ravel(right_labels)]
234245

235246
obj = self.__new__(type(self))
@@ -392,8 +403,9 @@ def unique(self,
392403
unique_charges = tmp_charge.unique_charges[:, tmp]
393404
obj.__init__(
394405
charges=unique_charges,
395-
charge_labels=np.arange(unique_charges.shape[1], dtype=np.int16),
396-
charge_types=tmp_charge.charge_types)
406+
charge_labels=np.arange(
407+
unique_charges.shape[1], dtype=self.label_dtype),
408+
charge_types=self.charge_types)
397409
out = [obj]
398410
if return_index or return_inverse or return_counts:
399411
for n in range(1, len(tmp)):
@@ -421,23 +433,24 @@ def reduce(self,
421433
of target values.
422434
"""
423435
if isinstance(target_charges, (np.integer, int)):
424-
target_charges = np.asarray([target_charges], dtype=np.int16)
436+
target_charges = np.asarray([target_charges], dtype=self.dtype)
425437
if target_charges.ndim == 1:
426438
target_charges = target_charges[None, :]
427-
target_charges = np.asarray(target_charges, dtype=np.int16)
439+
target_charges = np.asarray(target_charges, dtype=self.dtype)
428440
# find intersection of index charges and target charges
429441
reduced_charges, label_to_unique, _ = intersect(
430442
self.unique_charges, target_charges, axis=1, return_indices=True)
431443
num_unique = len(label_to_unique)
432444

433445
# construct the map to the reduced charges
434-
map_to_reduced = np.full(self.dim, fill_value=-1, dtype=np.int16)
435-
map_to_reduced[label_to_unique] = np.arange(num_unique, dtype=np.int16)
446+
map_to_reduced = np.full(self.dim, fill_value=-1, dtype=self.label_dtype)
447+
map_to_reduced[label_to_unique] = np.arange(
448+
num_unique, dtype=self.label_dtype)
436449

437450
# construct the map to the reduced charges
438451
reduced_ind_labels = map_to_reduced[self.charge_labels]
439452
reduced_locs = reduced_ind_labels >= 0
440-
new_ind_labels = reduced_ind_labels[reduced_locs].astype(np.int16)
453+
new_ind_labels = reduced_ind_labels[reduced_locs].astype(self.label_dtype)
441454
obj = self.__new__(type(self))
442455
obj.__init__(reduced_charges, new_ind_labels, self.charge_types)
443456

@@ -517,8 +530,13 @@ class U1Charge(BaseCharge):
517530
def __init__(self,
518531
charges: np.ndarray,
519532
charge_labels: Optional[np.ndarray] = None,
520-
charge_types: Optional[List[Type["BaseCharge"]]] = None) -> None:
521-
super().__init__(charges, charge_labels, charge_types=[type(self)])
533+
charge_types: Optional[List[Type["BaseCharge"]]] = None,
534+
charge_dtype: Optional[Type[np.number]] = np.int16) -> None:
535+
super().__init__(
536+
charges,
537+
charge_labels,
538+
charge_types=[type(self)],
539+
charge_dtype=charge_dtype)
522540

523541
@staticmethod
524542
def fuse(charge1, charge2) -> np.ndarray:

tensornetwork/block_sparse/linalg_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,7 +472,7 @@ def test_trace_matrix(dtype, num_charges, D):
472472
dtype=dtype)
473473
res = trace(matrix)
474474
res_dense = np.trace(matrix.todense())
475-
np.testing.assert_allclose(res, res_dense)
475+
np.testing.assert_allclose(res.data, res_dense)
476476

477477

478478
@pytest.mark.parametrize("dtype", [np.float64, np.complex128])

tensornetwork/block_sparse/utils.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ def compute_sparse_lookup(
107107
return_inverse=True, sort=False)
108108
_, label_to_unique, _ = unique_charges.intersect(
109109
target_charges, return_indices=True)
110-
tmp = np.full(len(unique_charges), fill_value=-1, dtype=np.int16)
110+
tmp = np.full(
111+
len(unique_charges), fill_value=-1, dtype=charges[0].label_dtype)
112+
111113
tmp[label_to_unique] = label_to_unique
112114
lookup = tmp[inverse]
113115
lookup = lookup[lookup >= 0]
@@ -297,8 +299,8 @@ def reduce_charges(charges: List[BaseCharge],
297299
right_ind.charge_labels) == 0):
298300
obj = charges[0].__new__(type(charges[0]))
299301
obj.__init__(
300-
np.empty((charges[0].num_symmetries, 0), dtype=np.int16),
301-
np.empty(0, dtype=np.int16), charges[0].charge_types)
302+
np.empty((charges[0].num_symmetries, 0), dtype=charges[0].dtype),
303+
np.empty(0, dtype=charges[0].label_dtype), charges[0].charge_types)
302304
if return_locations:
303305
return obj, np.empty(0, dtype=SIZE_T)
304306
return obj
@@ -310,7 +312,7 @@ def reduce_charges(charges: List[BaseCharge],
310312
# intersect combined qnums and target_charges
311313
reduced_qnums, label_to_unique, _ = intersect(
312314
unique_comb_qnums, target_charges, axis=1, return_indices=True)
313-
map_to_kept = -np.ones(num_unique, dtype=np.int16)
315+
map_to_kept = -np.ones(num_unique, dtype=charges[0].label_dtype)
314316
map_to_kept[label_to_unique] = np.arange(len(label_to_unique))
315317
#new_comb_labels is a matrix of shape (left_ind.num_unique, right_ind.num_unique)
316318
#each row new_comb_labels[n,:] contains integers values. Positions where values > 0
@@ -386,14 +388,14 @@ def _find_diagonal_sparse_blocks(
386388
# special cases (matrix of trivial height or width)
387389
num_nonzero = compute_num_nonzero(charges, flows)
388390
block_maps = [np.arange(0, num_nonzero, dtype=SIZE_T).ravel()]
389-
block_qnums = np.zeros([num_syms, 1], dtype=np.int16)
391+
block_qnums = np.zeros([num_syms, 1], dtype=charges[0].dtype)
390392
block_dims = np.array([[1], [num_nonzero]])
391393

392394
if partition == len(flows):
393395
block_dims = np.flipud(block_dims)
394396

395397
obj = charges[0].__new__(type(charges[0]))
396-
obj.__init__(block_qnums, np.arange(0, dtype=np.int16),
398+
obj.__init__(block_qnums, np.arange(0, dtype=charges[0].label_dtype),
397399
charges[0].charge_types)
398400

399401
return block_maps, obj, block_dims
@@ -412,8 +414,8 @@ def _find_diagonal_sparse_blocks(
412414
if num_blocks == 0:
413415
obj = charges[0].__new__(type(charges[0]))
414416
obj.__init__(
415-
np.zeros((charges[0].num_symmetries, 0), dtype=np.int16),
416-
np.arange(0, dtype=np.int16), charges[0].charge_types)
417+
np.zeros((charges[0].num_symmetries, 0), dtype=charges[0].dtype),
418+
np.arange(0, dtype=charges[0].label_dtype), charges[0].charge_types)
417419

418420
return [], obj, np.empty((2, 0), dtype=SIZE_T)
419421

@@ -445,7 +447,8 @@ def _find_diagonal_sparse_blocks(
445447
np.arange(block_dims[1, n])[None, :]) for n in range(num_blocks)
446448
]
447449
obj = charges[0].__new__(type(charges[0]))
448-
obj.__init__(block_qnums, np.arange(block_qnums.shape[1], dtype=np.int16),
450+
obj.__init__(block_qnums,
451+
np.arange(block_qnums.shape[1], dtype=charges[0].label_dtype),
449452
charges[0].charge_types)
450453

451454
return block_maps, obj, block_dims
@@ -508,8 +511,8 @@ def _find_transposed_diagonal_sparse_blocks(
508511
# special case: trivial number of non-zero elements
509512
obj = charges[0].__new__(type(charges[0]))
510513
obj.__init__(
511-
np.empty((charges[0].num_symmetries, 0), dtype=np.int16),
512-
np.arange(0, dtype=np.int16), charges[0].charge_types)
514+
np.empty((charges[0].num_symmetries, 0), dtype=charges[0].dtype),
515+
np.arange(0, dtype=charges[0].label_dtype), charges[0].charge_types)
513516

514517
return [], obj, np.empty((2, 0), dtype=SIZE_T)
515518

@@ -518,8 +521,9 @@ def _find_transposed_diagonal_sparse_blocks(
518521
np.logical_not(flows[orig_partition:]))
519522

520523
inv_row_map = -np.ones(
521-
orig_unique_row_qnums.unique_charges.shape[1], dtype=np.int16)
522-
inv_row_map[row_map] = np.arange(len(row_map), dtype=np.int16)
524+
orig_unique_row_qnums.unique_charges.shape[1],
525+
dtype=charges[0].label_dtype)
526+
inv_row_map[row_map] = np.arange(len(row_map), dtype=charges[0].label_dtype)
523527

524528
all_degens = np.append(orig_col_degen[col_map],
525529
0)[inv_row_map[orig_row_ind.charge_labels]]
@@ -564,7 +568,8 @@ def _find_transposed_diagonal_sparse_blocks(
564568
block_maps = [(all_cumul_degens[orig_row_posR] +
565569
dense_to_sparse[orig_col_posR]).ravel()]
566570
obj = charges[0].__new__(type(charges[0]))
567-
obj.__init__(block_qnums, np.arange(block_qnums.shape[1], dtype=np.int16),
571+
obj.__init__(block_qnums,
572+
np.arange(block_qnums.shape[1], dtype=charges[0].label_dtype),
568573
charges[0].charge_types)
569574

570575
elif tr_partition == len(charges):
@@ -595,7 +600,8 @@ def _find_transposed_diagonal_sparse_blocks(
595600
block_maps = [(all_cumul_degens[orig_row_posL] +
596601
dense_to_sparse[orig_col_posL]).ravel()]
597602
obj = charges[0].__new__(type(charges[0]))
598-
obj.__init__(block_qnums, np.arange(block_qnums.shape[1], dtype=np.int16),
603+
obj.__init__(block_qnums,
604+
np.arange(block_qnums.shape[1], dtype=charges[0].label_dtype),
599605
charges[0].charge_types)
600606
else:
601607

@@ -639,7 +645,8 @@ def _find_transposed_diagonal_sparse_blocks(
639645
all_cumul_degens[np.add.outer(orig_row_posL, orig_row_posR)] +
640646
dense_to_sparse[np.add.outer(orig_col_posL, orig_col_posR)]).ravel()
641647
obj = charges[0].__new__(type(charges[0]))
642-
obj.__init__(block_qnums, np.arange(block_qnums.shape[1], dtype=np.int16),
648+
obj.__init__(block_qnums,
649+
np.arange(block_qnums.shape[1], dtype=charges[0].label_dtype),
643650
charges[0].charge_types)
644651

645652
return block_maps, obj, block_dims

0 commit comments

Comments
 (0)