Skip to content

Commit 3607924

Browse files
committed
cleanup and simplifications
1 parent 8e10196 commit 3607924

3 files changed

Lines changed: 262 additions & 63 deletions

File tree

jftools/short_iterative_lanczos.py

Lines changed: 51 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
from scipy import sparse as sp
88

99
try:
10-
from . import short_iterative_lanczos_cython as _sil_cython
10+
from .short_iterative_lanczos_cython import _lanczos_timeprop_cython
1111

1212
have_cython_backend = True
1313
except ImportError:
14-
_sil_cython = None
14+
_lanczos_timeprop_cython = None
1515
have_cython_backend = False
1616

1717
try:
@@ -57,6 +57,17 @@ def _matvec(H, phi):
5757
return H @ phi
5858

5959

60+
def _as_hfun(H):
61+
"""Return an in-place Hfun(t, phi, Hphi) callable for H."""
62+
if callable(H):
63+
return H
64+
65+
H_f = H.dot if hasattr(H, "dot") else H.__matmul__
66+
def Hfun(t, phi, Hphi):
67+
Hphi[:] = H_f(phi)
68+
69+
return Hfun
70+
6071
def _qobj_state_io(phi0):
6172
outdims = phi0.dims
6273
outshape = phi0.full().shape
@@ -73,17 +84,7 @@ class _lanczos_timeprop_reference:
7384
def __init__(self, H, maxsteps, target_convg, debug=0, do_full_order=False):
7485
if have_qutip and isinstance(H, qutip.Qobj):
7586
H = _qobj_to_matrix(H)
76-
77-
if not callable(H):
78-
# time-independent operator
79-
# assume it supports dot or matmul for matrix-vector multiplication
80-
def Hfun(t, phi, Hphi):
81-
Hphi[:] = _matvec(H, phi)
82-
return Hphi
83-
84-
self.Hfun = Hfun
85-
else:
86-
self.Hfun = H
87+
self.Hfun = _as_hfun(H)
8788

8889
self.maxsteps = maxsteps
8990
self.target_convg = target_convg
@@ -95,6 +96,7 @@ def Hfun(t, phi, Hphi):
9596

9697
self.curr_coeff = zeros(maxsteps + 1, dtype=complex)
9798
self.prev_coeff = self.curr_coeff.copy()
99+
self.breakdown_tol = 1e-14
98100

99101
def propagate(self, phi0, ts, maxHT=None):
100102
phi0 = np.asarray(phi0).view(normdotndarray)
@@ -129,6 +131,7 @@ def _step(self, t, HT):
129131
prev_coeff = self.prev_coeff
130132
debug = self.debug
131133
Hfun = self.Hfun
134+
max_lanczos_steps = min(self.maxsteps, phia[0].shape[0])
132135

133136
HT_done = HT
134137

@@ -140,9 +143,12 @@ def _step(self, t, HT):
140143
# doesn't converge at first step
141144
curr_coeff[:] = 0.0
142145

143-
for step in range(1, self.maxsteps + 1):
146+
convg = np.inf
147+
exact_complete = False
148+
149+
for step in range(1, max_lanczos_steps + 1):
144150
# set |phia(step)> to H|phia(step-1)>
145-
phia[step] = Hfun(t, phia[step - 1], phia[step])
151+
Hfun(t, phia[step - 1], phia[step])
146152
prefacs[step] = prefacs[step - 1]
147153
phinorm = prefacs[step] * phia[step].norm()
148154
# phinorm = sqrt(<q(step-1)|H H|q(step-1)>)
@@ -177,18 +183,23 @@ def _step(self, t, HT):
177183
# i.e. to prefac = 1.d0 / sqrt(<phi|phi>)
178184
phinorm = phia[step].norm()
179185
beta[step - 1] = prefacs[step] * phinorm
180-
prefacs[step] = 1.0 / phinorm
181-
if abs(log10(prefacs[step])) > 4.0:
182-
phia[step] *= prefacs[step]
186+
if phinorm <= self.breakdown_tol:
183187
prefacs[step] = 1.0
184-
if abs(beta[step - 1]) < 1e-2 and debug > 2:
185-
print("WARNING! beta[%d]=%g is very small - there seems to be a linearly dependent vector!" % (step, beta[step - 1]))
186-
if debug > 1:
187-
# check if new vector is orthogonal to all others
188-
for ii in range(step):
189-
dotpr = prefacs[ii] * prefacs[step] * phia[ii].dot(phia[step])
190-
if abs(dotpr) > 1e-12:
191-
print("WARNING! vectors not orthogonal. dotpr(%d,%d) = %g" % (ii, step, dotpr))
188+
phia[step][:] = 0.0
189+
exact_complete = True
190+
else:
191+
prefacs[step] = 1.0 / phinorm
192+
if abs(log10(prefacs[step])) > 4.0:
193+
phia[step] *= prefacs[step]
194+
prefacs[step] = 1.0
195+
if abs(beta[step - 1]) < 1e-2 and debug > 2:
196+
print("WARNING! beta[%d]=%g is very small - there seems to be a linearly dependent vector!" % (step, beta[step - 1]))
197+
if debug > 1:
198+
# check if new vector is orthogonal to all others
199+
for ii in range(step):
200+
dotpr = prefacs[ii] * prefacs[step] * phia[ii].dot(phia[step])
201+
if abs(dotpr) > 1e-12:
202+
print("WARNING! vectors not orthogonal. dotpr(%d,%d) = %g" % (ii, step, dotpr))
192203

193204
# check convergence
194205
prev_coeff[:] = curr_coeff[:]
@@ -201,6 +212,9 @@ def _step(self, t, HT):
201212
if debug > 5:
202213
print("convg:", convg)
203214

215+
if exact_complete or step == max_lanczos_steps:
216+
break
217+
204218
if not self.do_full_order and convg < self.target_convg:
205219
break
206220

@@ -209,7 +223,7 @@ def _step(self, t, HT):
209223
print(beta[: step - 1])
210224

211225
# if convergence was reached in lanczos_loop, convg < target_convg, and this loop is never entered
212-
while convg > self.target_convg:
226+
while (not exact_complete) and convg > self.target_convg:
213227
# error (~convg) should be O(HT**maxsteps)
214228
# convg = a * HT**maxsteps
215229
# target_convg = a * HT_new**maxsteps
@@ -258,40 +272,35 @@ def _is_csr_matrix(H):
258272
return csr_array is not None and isinstance(H, csr_array)
259273

260274

261-
def _select_backend(H, backend=None):
262-
if backend is None:
263-
backend = "auto"
264-
else:
265-
backend = backend.strip().lower()
275+
def _select_backend(H, backend):
276+
backend = backend.strip().lower()
266277

267278
if backend == "python":
268279
return "python"
269280

270281
if backend == "cython":
271282
if not have_cython_backend:
272283
raise ValueError("backend='cython' requested but Cython backend extension is not available.")
273-
if _is_dense_matrix(H) or _is_csr_matrix(H):
274-
return "cython"
275-
raise ValueError("backend='cython' requested but Hamiltonian type is unsupported for cython backend.")
284+
return "cython"
276285

277286
if backend != "auto":
278287
raise ValueError("Unknown backend value '%s'. Valid values are 'python', 'cython', 'auto'." % backend)
279288

280-
if callable(H):
281-
return "python"
282-
if have_cython_backend and (_is_dense_matrix(H) or _is_csr_matrix(H)):
289+
if have_cython_backend:
283290
return "cython"
284291
return "python"
285292

286293

287294
class lanczos_timeprop:
288-
def __init__(self, H, maxsteps, target_convg, debug=0, do_full_order=False, backend=None):
295+
def __init__(self, H, maxsteps, target_convg, debug=0, do_full_order=False, backend="auto"):
289296
if have_qutip and isinstance(H, qutip.Qobj):
290297
H = _qobj_to_matrix(H)
291298
self.backend = _select_backend(H, backend)
292299
if self.backend == "cython":
293-
self._impl = _sil_cython.CythonLanczosPropagator(H, maxsteps, target_convg, debug,
294-
do_full_order)
300+
if not (_is_dense_matrix(H) or _is_csr_matrix(H)):
301+
H = _as_hfun(H)
302+
303+
self._impl = _lanczos_timeprop_cython(H, maxsteps, target_convg, debug, do_full_order)
295304
else:
296305
self._impl = _lanczos_timeprop_reference(H, maxsteps, target_convg, debug, do_full_order)
297306

@@ -320,6 +329,6 @@ def __getattr__(self, name):
320329
return getattr(self._impl, name)
321330

322331

323-
def sesolve_lanczos(H, phi0, ts, maxsteps, target_convg, maxHT=None, debug=0, do_full_order=False, backend=None):
332+
def sesolve_lanczos(H, phi0, ts, maxsteps, target_convg, maxHT=None, debug=0, do_full_order=False, backend="auto"):
324333
prop = lanczos_timeprop(H, maxsteps, target_convg, debug, do_full_order, backend)
325334
return prop.propagate(phi0, ts, maxHT)

jftools/short_iterative_lanczos_cython.pyx

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,20 @@ cdef void _csr_matvec(c128[::1] data, i64[::1] indices, i64[::1] indptr,
4949
y[i] = acc
5050

5151

52-
cdef class CythonLanczosPropagator:
52+
cdef class _lanczos_timeprop_cython:
5353
cdef int maxsteps
5454
cdef double target_convg
5555
cdef int debug
5656
cdef bint do_full_order
5757
cdef bint is_csr
5858
cdef int dim
59+
cdef bint is_callable
5960

6061
cdef c128[::1, :] H_dense_f
6162
cdef c128[::1] H_data
6263
cdef i64[::1] H_indices
6364
cdef i64[::1] H_indptr
65+
cdef object H_callable
6466

6567
cdef f64[::1] alpha
6668
cdef f64[::1] beta
@@ -73,32 +75,39 @@ cdef class CythonLanczosPropagator:
7375
cdef f64[:, ::1] coeff_z_buf
7476
cdef f64[::1] coeff_work_buf
7577
cdef int[::1] coeff_iwork_buf
78+
cdef double breakdown_tol
7679

7780
def __cinit__(self, H, int maxsteps, double target_convg, int debug=0, bint do_full_order=False):
7881
self.maxsteps = maxsteps
7982
self.target_convg = target_convg
8083
self.debug = debug
8184
self.do_full_order = do_full_order
85+
self.dim = 0
8286

83-
if sp.isspmatrix_csr(H) or (hasattr(sp, "csr_array") and isinstance(H, sp.csr_array)):
87+
if callable(H):
88+
self.is_callable = True
89+
self.is_csr = False
90+
self.H_callable = H
91+
elif sp.isspmatrix_csr(H) or (hasattr(sp, "csr_array") and isinstance(H, sp.csr_array)):
92+
self.is_callable = False
8493
self.is_csr = True
8594
self.dim = H.shape[0]
8695
H_csr = sp.csr_matrix(H).astype(np.complex128)
8796
self.H_data = np.asarray(H_csr.data, dtype=np.complex128)
8897
self.H_indices = np.asarray(H_csr.indices, dtype=np.int64)
8998
self.H_indptr = np.asarray(H_csr.indptr, dtype=np.int64)
9099
else:
100+
self.is_callable = False
91101
self.is_csr = False
92102
self.H_dense_f = np.asfortranarray(H, dtype=np.complex128)
93103
self.dim = self.H_dense_f.shape[0]
94104

95-
# Allocate working arrays as Cython-owned buffers.
96-
self.phia = view.array(shape=(maxsteps + 1, self.dim), itemsize=sizeof(c128), format="Zd", mode="c")
97105
self.alpha = view.array(shape=(maxsteps + 1,), itemsize=sizeof(f64), format="d")
98106
self.beta = view.array(shape=(maxsteps + 1,), itemsize=sizeof(f64), format="d")
99107
self.prefacs = view.array(shape=(maxsteps + 1,), itemsize=sizeof(f64), format="d")
100108
self.curr_coeff = view.array(shape=(maxsteps + 1,), itemsize=sizeof(c128), format="Zd")
101109
self.prev_coeff = view.array(shape=(maxsteps + 1,), itemsize=sizeof(c128), format="Zd")
110+
self.breakdown_tol = 1e-14
102111

103112
# Allocate LAPACK work buffers once and reuse in _calc_coeff.
104113
self.coeff_d_buf = view.array(shape=(maxsteps,), itemsize=sizeof(f64), format="d")
@@ -115,9 +124,12 @@ cdef class CythonLanczosPropagator:
115124
cdef c128[:, ::1] out_v
116125
cdef c128[::1] phi0_v = phi0
117126

118-
if self.dim != n:
127+
if self.is_callable:
128+
self.dim = n
129+
elif self.dim != n:
119130
raise ValueError("State dimension does not match Hamiltonian dimension")
120131

132+
self.phia = view.array(shape=(self.maxsteps + 1, n), itemsize=sizeof(c128), format="Zd", mode="c")
121133
self.phia[0, :] = phi0_v
122134

123135
tt = ts[0]
@@ -131,7 +143,7 @@ cdef class CythonLanczosPropagator:
131143
HT = tf - tt
132144
if maxHT is not None and HT > maxHT:
133145
HT = maxHT
134-
HT_done = self._step(HT)
146+
HT_done = self._step(tt, HT)
135147
tt += HT_done
136148
out_v[its, :] = self.phia[0, :]
137149
its += 1
@@ -173,10 +185,11 @@ cdef class CythonLanczosPropagator:
173185
for j in range(n):
174186
coeff[j] += self.coeff_z_buf[i, j] * fac
175187

176-
cdef double _step(self, double HT):
177-
cdef int step, ii, n
188+
cdef double _step(self, double t, double HT):
189+
cdef int step, ii, n, max_lanczos_steps
178190
cdef double HT_done = HT
179191
cdef double phinorm, convg, scale
192+
cdef bint exact_complete = False
180193
cdef c128 dotpr
181194
cdef c128 s
182195
cdef c128 alpha_blas, beta_blas
@@ -191,9 +204,12 @@ cdef class CythonLanczosPropagator:
191204
cdef c128[::1] prev_v = self.prev_coeff
192205

193206
n = phia_v.shape[1]
207+
max_lanczos_steps = self.maxsteps
208+
if max_lanczos_steps > n:
209+
max_lanczos_steps = n
194210
vec_n = n
195211
incx = incy = one = 1
196-
if not self.is_csr:
212+
if not self.is_csr and not self.is_callable:
197213
m = ncol = lda = n
198214
trans = 'N'
199215
alpha_blas = 1.0 + 0.0j
@@ -204,8 +220,12 @@ cdef class CythonLanczosPropagator:
204220
for ii in range(self.maxsteps + 1):
205221
curr_v[ii] = 0.0 + 0.0j
206222

207-
for step in range(1, self.maxsteps + 1):
208-
if self.is_csr:
223+
convg = 1e300
224+
225+
for step in range(1, max_lanczos_steps + 1):
226+
if self.is_callable:
227+
self.H_callable(t, np.asarray(phia_v[step - 1]), np.asarray(phia_v[step]))
228+
elif self.is_csr:
209229
_csr_matvec(self.H_data, self.H_indices, self.H_indptr, phia_v[step - 1], phia_v[step])
210230
else:
211231
blas.zgemm(&trans, &trans, &m, &one, &ncol, &alpha_blas, &self.H_dense_f[0, 0], &lda, &phia_v[step - 1, 0], &ncol, &beta_blas, &phia_v[step, 0], &m)
@@ -230,20 +250,28 @@ cdef class CythonLanczosPropagator:
230250

231251
phinorm = _vnorm(phia_v[step])
232252
beta_v[step] = prefacs_v[step] * phinorm
233-
prefacs_v[step] = 1.0 / phinorm
234-
235-
if fabs(log10(prefacs_v[step])) > 4.0:
236-
s = prefacs_v[step]
237-
blas.zscal(&vec_n, &s, &phia_v[step, 0], &incx)
253+
if phinorm <= self.breakdown_tol:
238254
prefacs_v[step] = 1.0
255+
exact_complete = True
256+
for ii in range(n):
257+
phia_v[step, ii] = 0.0 + 0.0j
258+
else:
259+
prefacs_v[step] = 1.0 / phinorm
260+
261+
if fabs(log10(prefacs_v[step])) > 4.0:
262+
s = prefacs_v[step]
263+
blas.zscal(&vec_n, &s, &phia_v[step, 0], &incx)
264+
prefacs_v[step] = 1.0
239265

240266
self.prev_coeff[:] = self.curr_coeff
241267
self._calc_coeff(step, HT_done, curr_v)
242268
convg = _coeff_diff_norm(curr_v, prev_v, step + 1)
269+
if exact_complete or step == max_lanczos_steps:
270+
break
243271
if (not self.do_full_order) and convg < self.target_convg:
244272
break
245273

246-
while convg > self.target_convg:
274+
while (not exact_complete) and convg > self.target_convg:
247275
scale = 0.95 * pow(self.target_convg / convg, 1.0 / step)
248276
if scale < 0.5:
249277
scale = 0.5

0 commit comments

Comments
 (0)