Skip to content

Commit 7c90c19

Browse files
committed
Merge branch 'release/1.0.4'
Cupy iplementation of KPM
2 parents 16c4cee + 14868e9 commit 7c90c19

23 files changed

+589
-66
lines changed

README.md

+19-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# ![eMaTe](emate.png)
22

3-
eMaTe is a python package implemented in tensorflow which the main goal is provide useful methods capable of estimate spectral densities and trace functions of large sparse matrices.
3+
eMaTe it is a python package which the main goal is to provide methods capable of estimating the spectral densities and trace
4+
functions of large sparse matrices. eMaTe can run in both CPU and GPU and can estimate the spectral density and related trace functions, such as entropy and Estrada index, even in directed or undirected networks with million of nodes.
45

56
## Install
67
```
78
pip install emate
89
```
910

11+
If you a have a GPU you should also install cupy.
1012
## Kernel Polynomial Method (KPM)
1113

1214
The Kernel Polynomial Method can estimate the spectral density of large sparse Hermitan matrices with a computational cost almost linear. This method combines three key ingredients: the Chebyshev expansion + the stochastic trace estimator + kernel smoothing.
@@ -26,15 +28,15 @@ vals = np.linalg.eigvalsh(W).real
2628
```
2729

2830
```python
29-
from emate.hermitian import pykpm
31+
from emate.hermitian import tfkpm
3032
from stdog.utils.misc import ig2sparse
3133

3234
W = ig2sparse(G)
3335

34-
num_moments = 300
35-
num_vecs = 200
36+
num_moments = 40
37+
num_vecs = 40
3638
extra_points = 10
37-
ek, rho = pykpm(W, num_moments, num_vecs, extra_points)
39+
ek, rho = tfkpm(W, num_moments, num_vecs, extra_points)
3840
```
3941

4042
```python
@@ -43,6 +45,18 @@ plt.hist(vals, density=True, bins=100, alpha=.9, color="steelblue")
4345
plt.scatter(ek, rho, c="tomato", zorder=999, alpha=0.9, marker="d")
4446

4547
```
48+
If the CUPY package it is available in your machine, you can also use the cupy implementation. When compared to tf-kpm, the
49+
Cupy-kpm is slower for median matrices (100k) and faster for larger matrices (> 10^6). The main reason it's because the tf-kpm was implemented in order to calc all te moments in a single step.
50+
51+
```python
52+
from emate.hermitian import cupykpm
53+
54+
num_moments = 40
55+
num_vecs = 40
56+
extra_points = 10
57+
ek, rho = cupykpm(W, num_moments, num_vecs, extra_points)
58+
```
59+
4660

4761
![](docs/source/imgs/kpm.png)
4862

@@ -113,8 +127,5 @@ approximated_entropy, exact_entropy
113127
[[2] Ubaru, S., Chen, J., & Saad, Y. (2017). Fast Estimation of tr(f(A)) via Stochastic Lanczos Quadrature. SIAM Journal on Matrix Analysis and Applications, 38(4), 1075-1099.](https://epubs.siam.org/doi/abs/10.1137/16M1104974)
114128

115129

116-
## Acknowledgements
117-
118-
This work has been supported also by FAPESP grants 11/50761-2 and 2015/22308-2. Research carriedout using the computational resources of the Center forMathematical Sciences Applied to Industry (CeMEAI)funded by FAPESP (grant 2013/07375-0).
119130

120131

emate.png

9.84 KB
Loading

emate/__init__.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33

44
from emate import linalg
55
from emate import utils
6-
__version__ = "1.0.3"
6+
7+
try:
8+
import cupy as cp
9+
except:
10+
print("Warning: Cupy package not found")
11+
12+
__version__ = "1.0.4"
713
__license__ = ""
814
__author__ = "Bruno Messias; Thomas K Peron"
915
__author_email__ = "[email protected]"

emate/hermitian/__init__.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from emate.hermitian.kpm import pykpm
2-
from emate.hermitian import tfops
1+
from emate.hermitian.kpm import pykpm, cupykpm, tfkpm
2+
from emate.hermitian import tfops, cupyops
33

4-
__all__ = ["pykpm", "tfops"]
4+
__all__ = ["pykpm", "tfops", "cupykpm", "cupykpm", "tfkpm"]

emate/hermitian/cupyops/__init__.py

Whitespace-only changes.

emate/hermitian/cupyops/kpm.py

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
Kernel Polynomial Method
3+
========================
4+
5+
The kernel polynomial method is an algorithm to obtain an approximation
6+
for the spectral density of a Hermitian matrix. This algorithm combines
7+
expansion in polynomials of Chebyshev with the stochastic trace in order
8+
to obtain such approximation.
9+
10+
Applications
11+
------------
12+
13+
- Hamiltonian matrices associated with quantum mechanics
14+
- Magnetic Laplacian associated with directed graphs
15+
- etc
16+
17+
Available functions
18+
-------------------
19+
20+
21+
"""
22+
import numpy as np
23+
try:
24+
import cupy as cp
25+
except:
26+
cp = None
27+
28+
from emate.utils.cupyops.signal import dctIII
29+
30+
31+
def get_moments(
32+
H_rescaled,
33+
num_moments,
34+
dimension,
35+
precision=32
36+
):
37+
"""
38+
Parameters
39+
----------
40+
H: sparse cupy of rank 2
41+
num_moments: (uint) number of cheby. moments
42+
dimension: (uint) size of the matrix
43+
44+
alpha0: Tensor(shape=(H.shape[0], num_vecs), dtype=tf_complex)
45+
alpha1: Tensor(shape=(H.shape[0], num_vecs), dtype=tf_complex)
46+
47+
48+
Returns
49+
-------
50+
"""
51+
cp_complex = cp.complex64
52+
if precision == 64:
53+
cp_complex = cp.complex128
54+
55+
alpha0 = cp.exp(1j*2*cp.pi*cp.random.rand(dimension))
56+
alpha1 = H_rescaled.dot(alpha0)
57+
mu = cp.zeros(num_moments, dtype=cp_complex)
58+
mu[0] = (alpha0.T.conj()).dot(alpha0)
59+
mu[1] = (alpha0.T.conj()).dot(alpha1)
60+
61+
for i_moment in range(1, num_moments//2):
62+
alpha2 = 2*H_rescaled.dot(alpha1)-alpha0
63+
mu[2*i_moment] = 2*(alpha1.T.conj()).dot(alpha1) - mu[0]
64+
mu[2*i_moment+1] = 2*(alpha2.T.conj()).dot(alpha1) - mu[1]
65+
66+
alpha0 = alpha1
67+
alpha1 = alpha2
68+
69+
return mu
70+
71+
72+
def apply_kernel(
73+
moments,
74+
kernel,
75+
dimension,
76+
num_moments,
77+
num_vecs,
78+
extra_points=1,
79+
):
80+
"""
81+
Parameters
82+
----------
83+
84+
"""
85+
86+
moments = cp.sum(moments.real, axis=0)
87+
moments = moments/num_vecs/dimension
88+
89+
num_points = extra_points+num_moments
90+
91+
if kernel is not None:
92+
moments = moments*kernel
93+
94+
mu_ext = cp.zeros(num_points)
95+
mu_ext[0:num_moments] = moments
96+
97+
smooth_moments = dctIII(mu_ext)
98+
points = cp.arange(0, num_points)
99+
ek = cp.cos(cp.pi*(points+0.5)/num_points)
100+
gk = cp.pi*cp.sqrt(1.-ek**2)
101+
102+
rho = cp.divide(smooth_moments, gk)
103+
104+
return ek, rho
105+
106+
107+
__all__ = ["apply_kernel", "get_moments"]

emate/hermitian/kpm.py

+74-12
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,34 @@
3232
"""
3333
import numpy as np
3434
import tensorflow as tf
35+
try:
36+
import cupy as cp
37+
except:
38+
cp = None
3539

36-
from emate.linalg import rescale_matrix
40+
from emate.linalg import rescale_matrix, get_bounds
41+
from emate.linalg.misc import rescale_cupy
3742

3843
from emate.utils.tfops.vector_factories import normal_complex
3944

40-
from emate.utils.tfops.kernels import jackson as jackson_kernel
41-
from emate.hermitian.tfops.kpm import get_moments, apply_kernel, rescale_kpm
45+
from emate.utils.tfops.kernels import jackson as tf_jackson
46+
from emate.utils.cupyops.kernels import jackson as cupy_jackson
4247

48+
from emate.hermitian.tfops.kpm import get_moments, apply_kernel
49+
from emate.hermitian.cupyops import kpm as cupyops
50+
51+
52+
def rescale_kpm(ek, rho, scale_fact_a, scale_fact_b):
53+
54+
ek = ek*scale_fact_a + scale_fact_b
55+
rho = rho/scale_fact_a
56+
return ek, rho
4357

4458
def pykpm(
4559
H,
46-
num_moments,
47-
num_vecs,
48-
extra_points,
60+
num_moments=10,
61+
num_vecs=10,
62+
extra_points=2,
4963
precision=32,
5064
lmin=None,
5165
lmax=None,
@@ -100,6 +114,9 @@ def pykpm(
100114
101115
"""
102116

117+
if (lmin is None) or (lmax is None):
118+
lmin, lmax = get_bounds(H)
119+
103120
H, scale_fact_a, scale_fact_b = rescale_matrix(H, lmin, lmax,)
104121

105122
coo = H.tocoo()
@@ -128,10 +145,10 @@ def pykpm(
128145

129146
dimension = H.shape[0]
130147

131-
tf.reset_default_graph()
148+
tf.compat.v1.reset_default_graph()
132149
with tf.device(device):
133-
sp_indices = tf.placeholder(dtype=tf.int64, name="sp_indices")
134-
sp_values = tf.placeholder(
150+
sp_indices = tf.compat.v1.placeholder(dtype=tf.int64, name="sp_indices")
151+
sp_values = tf.compat.v1.placeholder(
135152
dtype=tf_type,
136153
name="sp_values"
137154
)
@@ -146,7 +163,7 @@ def pykpm(
146163
precision=precision
147164
)
148165
moments = get_moments(H, num_vecs, num_moments, alpha0)
149-
kernel0 = jackson_kernel(num_moments, precision=32)
166+
kernel0 = tf_jackson(num_moments, precision=32)
150167
if precision == 64:
151168
moments = tf.cast(moments, tf.float32)
152169
ek, rho = apply_kernel(
@@ -159,10 +176,55 @@ def pykpm(
159176
)
160177
ek, rho = rescale_kpm(ek, rho, scale_fact_a, scale_fact_b)
161178

162-
with tf.Session() as sess:
179+
with tf.compat.v1.Session() as sess:
163180
ek, rho = sess.run([ek, rho], feed_dict)
164181

165182
return ek, rho
166183

184+
def cupykpm(
185+
H,
186+
num_moments=10,
187+
num_vecs=10,
188+
extra_points=12,
189+
precision=32,
190+
lmin=None,
191+
lmax=None,
192+
epsilon=0.01
193+
):
194+
195+
dimension = H.shape[0]
196+
197+
if (lmin is None) or (lmax is None):
198+
lmin, lmax = get_bounds(H)
199+
200+
H = cp.sparse.csr_matrix(
201+
(
202+
cp.array(H.data.astype("complex64")),
203+
cp.array(H.indices),
204+
cp.array( H.indptr)
205+
),
206+
shape=H.shape, dtype="complex64"
207+
)
208+
209+
H, scale_fact_a, scale_fact_b = rescale_cupy(H, lmin, lmax, epsilon)
210+
211+
moments = cp.array([
212+
cupyops.get_moments(H, num_moments, dimension, precision=precision)
213+
for i in range(num_vecs)
214+
])
215+
kernel0 = cupy_jackson(num_moments, precision=precision)
216+
217+
ek, rho = cupyops.apply_kernel(
218+
moments,
219+
kernel0,
220+
dimension,
221+
num_moments,
222+
num_vecs,
223+
extra_points
224+
)
225+
ek, rho = rescale_kpm(ek, rho, scale_fact_a, scale_fact_b)
226+
227+
return ek, rho
167228

168-
__all__ = ["pykpm"]
229+
tfkpm = pykpm
230+
__all__ = ["pykpm", "cupykpm", "tfkpm"]

emate/hermitian/tfops/kpm.py

+6-13
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def get_moments(
6969

7070
# first_moment.shape = (num_vecs, )
7171
if complex_eval:
72-
alpha0conj = tf.conj(alpha0)
72+
alpha0conj = tf.math.conj(alpha0)
7373
else:
7474
alpha0conj = alpha0
7575

@@ -128,8 +128,8 @@ def body(
128128
alpha2 = matrix_mul-alpha0
129129

130130
if complex_eval:
131-
alpha1conj = tf.conj(alpha1)
132-
alpha2conj = tf.conj(alpha2)
131+
alpha1conj = tf.math.conj(alpha1)
132+
alpha2conj = tf.math.conj(alpha2)
133133

134134
else:
135135
alpha1conj = alpha1
@@ -256,22 +256,15 @@ def apply_kernel(
256256
num_points = num_moments+extra_points
257257

258258
smooth_moments = tf.reshape(smooth_moments, [num_points])
259-
smooth_moments = tf.spectral.dct(smooth_moments, type=3)
259+
smooth_moments = tf.signal.dct(smooth_moments, type=3)
260260

261261
points = tf.range(num_points, dtype=tf.float32)
262262

263263
ek = tf.cos(np.pi*(points+0.5)/(num_points))
264264
gk = np.pi*tf.sqrt(1.-ek**2)
265-
rho = tf.divide(smooth_moments, gk)
265+
rho = tf.math.divide(smooth_moments, gk)
266266

267267
return ek, rho
268268

269269

270-
def rescale_kpm(ek, rho, scale_fact_a, scale_fact_b):
271-
272-
ek = ek*scale_fact_a + scale_fact_b
273-
rho = rho/scale_fact_a
274-
return ek, rho
275-
276-
277-
__all__ = ["rescale_kpm", "apply_kernel", "get_moments"]
270+
__all__ = [ "apply_kernel", "get_moments"]

emate/linalg/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
"""
1515

1616
from emate.linalg import tfops
17-
from emate.linalg.misc import rescale_matrix, get_bounds
17+
from emate.linalg.misc import rescale_matrix, get_bounds, rescale_cupy
1818

19-
__all__ = ["rescale_matrix", "get_bounds", "tfops"]
19+
__all__ = ["rescale_matrix", "get_bounds", "tfops", "rescale_cupy"]

0 commit comments

Comments
 (0)