Skip to content

Commit 677df8b

Browse files
committedJun 2, 2022
refactor pss2coo and add from numpy method in backend
1 parent 25e81e3 commit 677df8b

File tree

7 files changed

+90
-33
lines changed

7 files changed

+90
-33
lines changed
 

‎CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66

77
- Add `quoperator` method to get `QuOperator` representation of the circuit unitary
88

9+
- Add `coo_sparse_matrix_from_numpy` method on backend, where the scipy coo matrix is converted to sparse tensor in corresponding backend
10+
11+
- Add sparse tensor to scipy coo matrix implementation in `numpy` method
12+
13+
### Changed
14+
15+
- `tc.quantum.PauliStringSum2COO`, `tc.quantum.PauliStringSum2Dense`, and `tc.quantum.heisenberg_hamiltonian` now return the tensor in current backend format if `numpy` option sets to False. (Breaking change: previously, the return are fixed in TensorFlow format)
16+
917
## 0.1.0
1018

1119
### Added

‎tensorcircuit/backends/abstract_backend.py

+15
Original file line numberDiff line numberDiff line change
@@ -957,6 +957,21 @@ def coo_sparse_matrix(
957957
"Backend '{}' has not implemented `coo`.".format(self.name)
958958
)
959959

960+
def coo_sparse_matrix_from_numpy(self: Any, a: Tensor) -> Tensor:
961+
"""
962+
Generate the coo format sparse matrix from scipy coo sparse matrix.
963+
964+
:param a: Scipy coo format sparse matrix
965+
:type a: Tensor
966+
:return: SparseTensor in backend format
967+
:rtype: Tensor
968+
"""
969+
return self.coo_sparse_matrix(
970+
indices=np.array([a.row, a.col]).T,
971+
values=a.data,
972+
shape=a.shape,
973+
)
974+
960975
def sparse_dense_matmul(
961976
self: Any,
962977
sp_a: Tensor,

‎tensorcircuit/backends/jax_backend.py

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Callable, Optional, Sequence, Tuple, Union
99

1010
import numpy as np
11+
from scipy.sparse import coo_matrix
1112
import tensornetwork
1213
from tensornetwork.backends.jax import jax_backend
1314

@@ -290,6 +291,10 @@ def kron(self, a: Tensor, b: Tensor) -> Tensor:
290291
return jnp.kron(a, b)
291292

292293
def numpy(self, a: Tensor) -> Tensor:
294+
if self.is_sparse(a):
295+
return coo_matrix(
296+
(a.data, (a.indices[:, 0], a.indices[:, 1])), shape=a.shape
297+
)
293298
return np.array(a)
294299

295300
def i(self, dtype: Any = None) -> Tensor:

‎tensorcircuit/backends/tensorflow_backend.py

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from operator import mul
88
from typing import Any, Callable, Optional, Sequence, Tuple, Union
99

10+
from scipy.sparse import coo_matrix
1011
import tensornetwork
1112
from tensornetwork.backends.tensorflow import tensorflow_backend
1213

@@ -326,6 +327,11 @@ def kron(self, a: Tensor, b: Tensor) -> Tensor:
326327
)
327328

328329
def numpy(self, a: Tensor) -> Tensor:
330+
if self.is_sparse(a):
331+
return coo_matrix(
332+
(a.values, (a.indices[:, 0], a.indices[:, 1])), shape=a.get_shape()
333+
)
334+
329335
return a.numpy() # only valid in eager mode
330336

331337
def i(self, dtype: Any = None) -> Tensor:

‎tensorcircuit/quantum.py

+39-24
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ def quimb2qop(qb_mpo: Any) -> QuOperator:
11041104

11051105
try:
11061106
compiled_jit = partial(get_backend("tensorflow").jit, jit_compile=True)
1107-
# TODO(@refraction-ray): at least make the final returned sparse tensor backend agnostic?
1107+
11081108
def heisenberg_hamiltonian(
11091109
g: Graph,
11101110
hzz: float = 1.0,
@@ -1196,7 +1196,7 @@ def heisenberg_hamiltonian(
11961196
r = PauliStringSum2COO_numpy(ls, weight)
11971197
if numpy:
11981198
return r
1199-
return _numpy2tf_sparse(r)
1199+
return backend.coo_sparse_matrix_from_numpy(r)
12001200
return PauliStringSum2Dense(ls, weight, numpy=numpy)
12011201

12021202
def PauliStringSum2Dense(
@@ -1205,50 +1205,60 @@ def PauliStringSum2Dense(
12051205
numpy: bool = False,
12061206
) -> Tensor:
12071207
"""
1208-
Generate tensorflow dense matrix from Pauli string sum
1208+
Generate dense matrix from Pauli string sum
12091209
12101210
:param ls: 2D Tensor, each row is for a Pauli string,
12111211
e.g. [1, 0, 0, 3, 2] is for :math:`X_0Z_3Y_4`
12121212
:type ls: Sequence[Sequence[int]]
12131213
:param weight: 1D Tensor, each element corresponds the weight for each Pauli string
12141214
defaults to None (all Pauli strings weight 1.0)
12151215
:type weight: Optional[Sequence[float]], optional
1216+
:param numpy: default False. If True, return numpy coo
1217+
else return backend compatible sparse tensor
1218+
:type numpy: bool
12161219
:return: the tensorflow dense matrix
12171220
:rtype: Tensor
12181221
"""
12191222
sparsem = PauliStringSum2COO_numpy(ls, weight)
12201223
if numpy:
12211224
return sparsem.todense()
1222-
sparsem = _numpy2tf_sparse(sparsem)
1223-
densem = get_backend("tensorflow").to_dense(sparsem)
1225+
sparsem = backend.coo_sparse_matrix_from_numpy(sparsem)
1226+
densem = backend.to_dense(sparsem)
12241227
return densem
12251228

1226-
def _tf2numpy_sparse(a: Tensor) -> Tensor:
1227-
return get_backend("numpy").coo_sparse_matrix(
1228-
indices=a.indices,
1229-
values=a.values,
1230-
shape=a.get_shape(),
1231-
)
1232-
1233-
def _numpy2tf_sparse(a: Tensor) -> Tensor:
1234-
return get_backend("tensorflow").coo_sparse_matrix(
1235-
indices=np.array([a.row, a.col]).T,
1236-
values=a.data,
1237-
shape=a.shape,
1238-
)
1229+
# already implemented as backend method
1230+
#
1231+
# def _tf2numpy_sparse(a: Tensor) -> Tensor:
1232+
# return get_backend("numpy").coo_sparse_matrix(
1233+
# indices=a.indices,
1234+
# values=a.values,
1235+
# shape=a.get_shape(),
1236+
# )
1237+
1238+
# def _numpy2tf_sparse(a: Tensor) -> Tensor:
1239+
# return get_backend("tensorflow").coo_sparse_matrix(
1240+
# indices=np.array([a.row, a.col]).T,
1241+
# values=a.data,
1242+
# shape=a.shape,
1243+
# )
12391244

1240-
def PauliStringSum2COO_numpy(
1241-
ls: Sequence[Sequence[int]], weight: Optional[Sequence[float]] = None
1245+
def PauliStringSum2COO(
1246+
ls: Sequence[Sequence[int]],
1247+
weight: Optional[Sequence[float]] = None,
1248+
numpy: bool = False,
12421249
) -> Tensor:
12431250
"""
1244-
Generate scipy sparse matrix from Pauli string sum
1251+
Generate sparse tensor from Pauli string sum
12451252
12461253
:param ls: 2D Tensor, each row is for a Pauli string,
12471254
e.g. [1, 0, 0, 3, 2] is for :math:`X_0Z_3Y_4`
12481255
:type ls: Sequence[Sequence[int]]
12491256
:param weight: 1D Tensor, each element corresponds the weight for each Pauli string
12501257
defaults to None (all Pauli strings weight 1.0)
12511258
:type weight: Optional[Sequence[float]], optional
1259+
:param numpy: default False. If True, return numpy coo
1260+
else return backend compatible sparse tensor
1261+
:type numpy: bool
12521262
:return: the scipy coo sparse matrix
12531263
:rtype: Tensor
12541264
"""
@@ -1267,11 +1277,16 @@ def PauliStringSum2COO_numpy(
12671277
shape=(s, s),
12681278
)
12691279
for i in range(nterms):
1270-
rsparse += _tf2numpy_sparse(PauliString2COO(ls[i], weight[i])) # type: ignore
1280+
rsparse += get_backend("tensorflow").numpy(PauliString2COO(ls[i], weight[i])) # type: ignore
12711281
# auto transformed into csr format!!
1272-
return rsparse.tocoo()
1282+
rsparse = rsparse.tocoo()
1283+
if numpy:
1284+
return rsparse
1285+
return backend.coo_sparse_matrix_from_numpy(rsparse)
12731286

1274-
def PauliStringSum2COO(
1287+
PauliStringSum2COO_numpy = partial(PauliStringSum2COO, numpy=True)
1288+
1289+
def PauliStringSum2COO_tf(
12751290
ls: Sequence[Sequence[int]], weight: Optional[Sequence[float]] = None
12761291
) -> Tensor:
12771292
"""

‎tests/test_backends.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -639,20 +639,28 @@ def test_sparse_methods(backend):
639639
indices = tc.backend.cast(indices, "int64")
640640
spa = tc.backend.coo_sparse_matrix(indices, values, shape=[4, 4])
641641
vec = tc.backend.ones([4, 1])
642+
da = np.array(
643+
[[1, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], dtype=np.complex64
644+
)
642645
assert tc.backend.is_sparse(spa) is True
643646
assert tc.backend.is_sparse(vec) is False
644647
np.testing.assert_allclose(
645648
tc.backend.to_dense(spa),
646-
np.array(
647-
[[1, 0, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], dtype=np.complex64
648-
),
649+
da,
649650
atol=1e-5,
650651
)
651652
np.testing.assert_allclose(
652653
tc.backend.sparse_dense_matmul(spa, vec),
653654
np.array([[1], [2], [0], [0]], dtype=np.complex64),
654655
atol=1e-5,
655656
)
657+
spa_np = tc.backend.numpy(spa)
658+
np.testing.assert_allclose(spa_np.todense(), da, atol=1e-6)
659+
np.testing.assert_allclose(
660+
tc.backend.to_dense(tc.backend.coo_sparse_matrix_from_numpy(spa_np)),
661+
da,
662+
atol=1e-5,
663+
)
656664

657665

658666
@pytest.mark.parametrize("backend", [lf("npb"), lf("tfb"), lf("jaxb")])

‎tests/test_quantum.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,8 @@ def test_tn2qop(backend):
373373
h1 = qu_mpo.eval_matrix()
374374
g = tc.templates.graphs.Line1D(nwires, pbc=False)
375375
h2 = tc.quantum.heisenberg_hamiltonian(
376-
g, hzz=0, hxx=1, hyy=0, hz=1, hx=0, hy=0, sparse=False
377-
).numpy()
376+
g, hzz=0, hxx=1, hyy=0, hz=1, hx=0, hy=0, sparse=False, numpy=True
377+
)
378378
np.testing.assert_allclose(h1, h2, atol=1e-5)
379379

380380

@@ -390,8 +390,8 @@ def test_qb2qop(backend):
390390
h1 = qu_mpo.eval_matrix()
391391
g = tc.templates.graphs.Line1D(nwires, pbc=True)
392392
h2 = tc.quantum.heisenberg_hamiltonian(
393-
g, hzz=1, hxx=0, hyy=0, hz=0, hx=-1, hy=0, sparse=False
394-
).numpy()
393+
g, hzz=1, hxx=0, hyy=0, hz=0, hx=-1, hy=0, sparse=False, numpy=True
394+
)
395395
np.testing.assert_allclose(h1, h2, atol=1e-5)
396396

397397
# in out edge order test
@@ -404,6 +404,6 @@ def test_qb2qop(backend):
404404
m1 = h.eval_matrix()
405405
g = tc.templates.graphs.Line1D(3, pbc=False)
406406
m2 = tc.quantum.heisenberg_hamiltonian(
407-
g, hzz=0, hxx=0, hyy=0, hz=0, hy=0.5, hx=0.5, sparse=False
408-
).numpy()
407+
g, hzz=0, hxx=0, hyy=0, hz=0, hy=0.5, hx=0.5, sparse=False, numpy=True
408+
)
409409
np.testing.assert_allclose(m1, m2, atol=1e-5)

0 commit comments

Comments
 (0)