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

Commit c2399c8

Browse files
authored
Block sparse (#417)
* started implementing block-sparse tensors * removed files * working on AbelianIndex * working in block sparisty * added reshape and lots of other stuff * added Index, an index type for symmetric tensors * added small tutorial * added docstring * fixed bug in retrieve_diagonal_blocks * TODO added * improved initialization a bit * more efficient initialization * just formatting * added random * added fuse_degeneracies * fixed bug in reshape * dosctring, typing * removed TODO * removed confusing code line * bug removed * comment * added __mul__ to Index * added sparse_shape and updated reshape to accept both int and Index lists * more in tutorial * comment * added new test function * testing function hacking * docstring * small speed up * Remove gui directory (migrated to another repo) (#399) * a slightly more elegant code * use one more np function * removed some crazy slow code * faster code * Update README.md (#404) * add return_data * doc * bug fix * a little faster * substantial speedup * renaming * removed todo * some comments * comments * fixed some bug in reshape * comments * default value changed * fixed bug, old version is now faster again * cleaned up reshape * started adding tests * replace kron with broadcasting * column-major -> row-major * documentation * added function to compute unique charges and charge degeneracies Function avoids explicit full fusion of all legs, and instead only keeps track of the unique charges and their degeneracies upon fusion * improved block finding, fixed bug in reshape re-intorduced BlockSparseTensor.dense_shape new method for fusing charges and degeneracies (faster for very rectangular matrices) * fuse_charge_pair added fuse_charges added * use is_leave * new tests * removed TODO, BlockSparseTensor.shape returns ref instead of copy * added tests * added tests * column-major -> row-major forgot to fix fusing order of charges and degeneracies * fix broken tests * test added * mostly docstring * docstring
1 parent 587f4a3 commit c2399c8

File tree

4 files changed

+201
-80
lines changed

4 files changed

+201
-80
lines changed

tensornetwork/block_tensor/block_tensor.py

Lines changed: 60 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -160,24 +160,35 @@ def compute_nonzero_block_shapes(charges: List[np.ndarray],
160160
return charge_shape_dict
161161

162162

163-
def retrieve_non_zero_diagonal_blocks_old_version(
163+
def retrieve_non_zero_diagonal_blocks(
164164
data: np.ndarray,
165-
charges: List[np.ndarray],
166-
flows: List[Union[bool, int]],
165+
row_charges: List[Union[List, np.ndarray]],
166+
column_charges: List[Union[List, np.ndarray]],
167+
row_flows: List[Union[bool, int]],
168+
column_flows: List[Union[bool, int]],
167169
return_data: Optional[bool] = True) -> Dict:
168170
"""
169-
Deprecated: this version is about 2 times slower (worst case) than the current used
170-
implementation
171171
Given the meta data and underlying data of a symmetric matrix, compute
172172
all diagonal blocks and return them in a dict.
173+
`row_charges` and `column_charges` are lists of np.ndarray. The tensor
174+
is viewed as a matrix with rows given by fusing `row_charges` and
175+
columns given by fusing `column_charges`. Note that `column_charges`
176+
are never explicitly fused (`row_charges` are).
173177
Args:
174178
data: An np.ndarray of the data. The number of elements in `data`
175179
has to match the number of non-zero elements defined by `charges`
176180
and `flows`
177-
charges: List of np.ndarray, one for each leg.
178-
Each np.ndarray `charges[leg]` is of shape `(D[leg],)`.
181+
row_charges: List of np.ndarray, one for each leg of the row-indices.
182+
Each np.ndarray `row_charges[leg]` is of shape `(D[leg],)`.
179183
The bond dimension `D[leg]` can vary on each leg.
180-
flows: A list of integers, one for each leg,
184+
column_charges: List of np.ndarray, one for each leg of the column-indices.
185+
Each np.ndarray `row_charges[leg]` is of shape `(D[leg],)`.
186+
The bond dimension `D[leg]` can vary on each leg.
187+
row_flows: A list of integers, one for each entry in `row_charges`.
188+
with values `1` or `-1`, denoting the flow direction
189+
of the charges on each leg. `1` is inflowing, `-1` is outflowing
190+
charge.
191+
column_flows: A list of integers, one for each entry in `column_charges`.
181192
with values `1` or `-1`, denoting the flow direction
182193
of the charges on each leg. `1` is inflowing, `-1` is outflowing
183194
charge.
@@ -193,20 +204,25 @@ def retrieve_non_zero_diagonal_blocks_old_version(
193204
dict: Dictionary mapping quantum numbers (integers) to either an np.ndarray
194205
or a python list of locations and shapes, depending on the value of `return_data`.
195206
"""
196-
if len(charges) != 2:
197-
raise ValueError("input has to be a two-dimensional symmetric matrix")
207+
flows = row_flows.copy()
208+
flows.extend(column_flows)
198209
check_flows(flows)
199-
if len(flows) != len(charges):
200-
raise ValueError("`len(flows)` is different from `len(charges)`")
210+
if len(flows) != (len(row_charges) + len(column_charges)):
211+
raise ValueError(
212+
"`len(flows)` is different from `len(row_charges) + len(column_charges)`"
213+
)
201214

202-
#we multiply the flows into the charges
203-
row_charges = flows[0] * charges[0] # a list of charges on each row
204-
column_charges = flows[1] * charges[1] # a list of charges on each column
215+
#since we are using row-major we have to fuse the row charges anyway.
216+
fused_row_charges = fuse_charges(row_charges, row_flows)
217+
#get the unique row-charges
218+
unique_row_charges, row_dims = np.unique(
219+
fused_row_charges, return_counts=True)
205220

206-
#get the unique charges
207-
unique_row_charges, row_dims = np.unique(row_charges, return_counts=True)
208-
unique_column_charges, column_dims = np.unique(
209-
column_charges, return_counts=True)
221+
#get the unique column-charges
222+
#we only care about their degeneracies, not their order; that's much faster
223+
#to compute since we don't have to fuse all charges explicitly
224+
unique_column_charges, column_dims = compute_fused_charge_degeneracies(
225+
column_charges, column_flows)
210226
#get the charges common to rows and columns (only those matter)
211227
common_charges = np.intersect1d(
212228
unique_row_charges, -unique_column_charges, assume_unique=True)
@@ -217,8 +233,8 @@ def retrieve_non_zero_diagonal_blocks_old_version(
217233
column_degeneracies = dict(zip(unique_column_charges, column_dims))
218234

219235
# we only care about charges common to row and columns
220-
mask = np.isin(row_charges, common_charges)
221-
relevant_row_charges = row_charges[mask]
236+
mask = np.isin(fused_row_charges, common_charges)
237+
relevant_row_charges = fused_row_charges[mask]
222238

223239
#some numpy magic to get the index locations of the blocks
224240
#we generate a vector of `len(relevant_row_charges) which,
@@ -261,35 +277,24 @@ def retrieve_non_zero_diagonal_blocks_old_version(
261277
return blocks
262278

263279

264-
def retrieve_non_zero_diagonal_blocks(
280+
def retrieve_non_zero_diagonal_blocks_old_version(
265281
data: np.ndarray,
266-
row_charges: List[Union[List, np.ndarray]],
267-
column_charges: List[Union[List, np.ndarray]],
268-
row_flows: List[Union[bool, int]],
269-
column_flows: List[Union[bool, int]],
282+
charges: List[np.ndarray],
283+
flows: List[Union[bool, int]],
270284
return_data: Optional[bool] = True) -> Dict:
271285
"""
286+
Deprecated: this version is about 2 times slower (worst case) than the current used
287+
implementation
272288
Given the meta data and underlying data of a symmetric matrix, compute
273289
all diagonal blocks and return them in a dict.
274-
`row_charges` and `column_charges` are lists of np.ndarray. The tensor
275-
is viewed as a matrix with rows given by fusing `row_charges` and
276-
columns given by fusing `column_charges`. Note that `column_charges`
277-
are never explicitly fused (`row_charges` are).
278290
Args:
279291
data: An np.ndarray of the data. The number of elements in `data`
280292
has to match the number of non-zero elements defined by `charges`
281293
and `flows`
282-
row_charges: List of np.ndarray, one for each leg of the row-indices.
283-
Each np.ndarray `row_charges[leg]` is of shape `(D[leg],)`.
284-
The bond dimension `D[leg]` can vary on each leg.
285-
column_charges: List of np.ndarray, one for each leg of the column-indices.
286-
Each np.ndarray `row_charges[leg]` is of shape `(D[leg],)`.
294+
charges: List of np.ndarray, one for each leg.
295+
Each np.ndarray `charges[leg]` is of shape `(D[leg],)`.
287296
The bond dimension `D[leg]` can vary on each leg.
288-
row_flows: A list of integers, one for each entry in `row_charges`.
289-
with values `1` or `-1`, denoting the flow direction
290-
of the charges on each leg. `1` is inflowing, `-1` is outflowing
291-
charge.
292-
column_flows: A list of integers, one for each entry in `column_charges`.
297+
flows: A list of integers, one for each leg,
293298
with values `1` or `-1`, denoting the flow direction
294299
of the charges on each leg. `1` is inflowing, `-1` is outflowing
295300
charge.
@@ -305,25 +310,20 @@ def retrieve_non_zero_diagonal_blocks(
305310
dict: Dictionary mapping quantum numbers (integers) to either an np.ndarray
306311
or a python list of locations and shapes, depending on the value of `return_data`.
307312
"""
308-
flows = row_flows.copy()
309-
flows.extend(column_flows)
313+
if len(charges) != 2:
314+
raise ValueError("input has to be a two-dimensional symmetric matrix")
310315
check_flows(flows)
311-
if len(flows) != (len(row_charges) + len(column_charges)):
312-
raise ValueError(
313-
"`len(flows)` is different from `len(row_charges) + len(column_charges)`"
314-
)
316+
if len(flows) != len(charges):
317+
raise ValueError("`len(flows)` is different from `len(charges)`")
315318

316-
#since we are using row-major we have to fuse the row charges anyway.
317-
fused_row_charges = fuse_charges(row_charges, row_flows)
318-
#get the unique row-charges
319-
unique_row_charges, row_dims = np.unique(
320-
fused_row_charges, return_counts=True)
319+
#we multiply the flows into the charges
320+
row_charges = flows[0] * charges[0] # a list of charges on each row
321+
column_charges = flows[1] * charges[1] # a list of charges on each column
321322

322-
#get the unique column-charges
323-
#we only care about their degeneracies, not their order; that's much faster
324-
#to compute since we don't have to fuse all charges explicitly
325-
unique_column_charges, column_dims = compute_fused_charge_degeneracies(
326-
column_charges, column_flows)
323+
#get the unique charges
324+
unique_row_charges, row_dims = np.unique(row_charges, return_counts=True)
325+
unique_column_charges, column_dims = np.unique(
326+
column_charges, return_counts=True)
327327
#get the charges common to rows and columns (only those matter)
328328
common_charges = np.intersect1d(
329329
unique_row_charges, -unique_column_charges, assume_unique=True)
@@ -334,8 +334,8 @@ def retrieve_non_zero_diagonal_blocks(
334334
column_degeneracies = dict(zip(unique_column_charges, column_dims))
335335

336336
# we only care about charges common to row and columns
337-
mask = np.isin(fused_row_charges, common_charges)
338-
relevant_row_charges = fused_row_charges[mask]
337+
mask = np.isin(row_charges, common_charges)
338+
relevant_row_charges = row_charges[mask]
339339

340340
#some numpy magic to get the index locations of the blocks
341341
#we generate a vector of `len(relevant_row_charges) which,
@@ -585,8 +585,8 @@ def compute_mapping_table(charges: List[np.ndarray],
585585
# is moving quickest when iterating through the linear data
586586
# transposing is done taking, for each value of the indices i_0 to i_N-2
587587
# the junk i_N-1 that gives non-zero
588-
tables = np.meshgrid([np.arange(c.shape[0]) for c in charges], indexing='ij')
589-
tables = tables[::-1] #reverse the order
588+
589+
#for example
590590
raise NotImplementedError()
591591

592592

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import numpy as np
2+
import pytest
3+
# pylint: disable=line-too-long
4+
from tensornetwork.block_tensor.block_tensor import BlockSparseTensor, compute_num_nonzero
5+
from index import Index
6+
7+
np_dtypes = [np.float32, np.float16, np.float64, np.complex64, np.complex128]
8+
9+
10+
@pytest.mark.parametrize("dtype", np_dtypes)
11+
def test_block_sparse_init(dtype):
12+
D = 10 #bond dimension
13+
B = 10 #number of blocks
14+
rank = 4
15+
flows = np.asarray([1 for _ in range(rank)])
16+
flows[-2::] = -1
17+
charges = [
18+
np.random.randint(-B // 2, B // 2 + 1, D).astype(np.int16)
19+
for _ in range(rank)
20+
]
21+
indices = [
22+
Index(charges=charges[n], flow=flows[n], name='index{}'.format(n))
23+
for n in range(rank)
24+
]
25+
num_elements = compute_num_nonzero([i.charges for i in indices],
26+
[i.flow for i in indices])
27+
A = BlockSparseTensor.random(indices=indices, dtype=dtype)
28+
assert A.dtype == dtype
29+
for r in range(rank):
30+
assert A.indices[r].name == 'index{}'.format(r)
31+
assert A.dense_shape == tuple([D] * rank)
32+
assert len(A.data) == num_elements

tensornetwork/block_tensor/index.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,10 @@ def fuse_charge_pair(q1: Union[List, np.ndarray], flow1: int,
130130
for U(1) charges). `q1` and `q2` typically belong to two consecutive
131131
legs of `BlockSparseTensor`.
132132
Given `q1 = [0,1,2]` and `q2 = [10,100]`, this returns
133-
`[10, 11, 12, 100, 101, 102]`.
133+
`[10, 100, 11, 101, 12, 102]`.
134134
When using row-major ordering of indices in `BlockSparseTensor`,
135135
the position of q1 should be "to the left" of the position of q2.
136+
136137
Args:
137138
q1: Iterable of integers
138139
flow1: Flow direction of charge `q1`.
@@ -142,15 +143,17 @@ def fuse_charge_pair(q1: Union[List, np.ndarray], flow1: int,
142143
np.ndarray: The result of fusing `q1` with `q2`.
143144
"""
144145
return np.reshape(
145-
flow2 * np.asarray(q2)[:, None] + flow1 * np.asarray(q1)[None, :],
146+
flow1 * np.asarray(q1)[:, None] + flow2 * np.asarray(q2)[None, :],
146147
len(q1) * len(q2))
147148

148149

149150
def fuse_charges(charges: List[Union[List, np.ndarray]],
150151
flows: List[int]) -> np.ndarray:
151152
"""
152153
Fuse all `charges` by simple addition (valid
153-
for U(1) charges).
154+
for U(1) charges). Charges are fused from "right to left",
155+
in accordance with row-major order (see `fuse_charges_pair`).
156+
154157
Args:
155158
chargs: A list of charges to be fused.
156159
flows: A list of flows, one for each element in `charges`.
@@ -173,19 +176,17 @@ def fuse_degeneracies(degen1: Union[List, np.ndarray],
173176
Fuse degeneracies `degen1` and `degen2` of two leg-charges
174177
by simple kronecker product. `degen1` and `degen2` typically belong to two
175178
consecutive legs of `BlockSparseTensor`.
176-
Given `q1 = [0,1,2]` and `q2 = [10,100]`, this returns
177-
`[10, 11, 12, 100, 101, 102]`.
179+
Given `degen1 = [1, 2, 3]` and `degen2 = [10, 100]`, this returns
180+
`[10, 100, 20, 200, 30, 300]`.
178181
When using row-major ordering of indices in `BlockSparseTensor`,
179-
the position of q1 should be "to the left" of the position of q2.
182+
the position of `degen1` should be "to the left" of the position of `degen2`.
180183
Args:
181-
q1: Iterable of integers
182-
flow1: Flow direction of charge `q1`.
183-
q2: Iterable of integers
184-
flow2: Flow direction of charge `q2`.
184+
degen1: Iterable of integers
185+
degen2: Iterable of integers
185186
Returns:
186-
np.ndarray: The result of fusing `q1` with `q2`.
187+
np.ndarray: The result of fusing `dege1` with `degen2`.
187188
"""
188-
return np.reshape(degen2[:, None] * degen1[None, :],
189+
return np.reshape(degen1[:, None] * degen2[None, :],
189190
len(degen1) * len(degen2))
190191

191192

0 commit comments

Comments
 (0)