Skip to content
Snippets Groups Projects
Commit d65a2b4a authored by Pablo Piskunow's avatar Pablo Piskunow Committed by Joseph Weston
Browse files

fix bug when an odd number of KPM moments is used

Also refactor '_update_moments_list' and add some tests
to check for when an odd number of KPM moments is used.
parent 886d083c
No related branches found
No related tags found
No related merge requests found
Pipeline #
......@@ -179,7 +179,6 @@ class SpectralDensity:
else:
raise ValueError('Parameter `operator` has no `.dot` '
'attribute and is not callable.')
self.num_rand_vecs = num_rand_vecs
self.num_moments = num_moments
# Default number of sampling points
if num_sampling_points is None:
......@@ -190,21 +189,28 @@ class SpectralDensity:
else:
self.num_sampling_points = num_sampling_points
# calculate raw moments
self._vector_factory = vector_factory or \
(lambda n: np.exp(2j * np.pi * rng.random_sample(n)))
# store this vector for reproducibility
self._v0 = self._vector_factory(ham.shape[0])
self._rand_vect_list = []
self._last_two_alphas = []
self._moments_list = [0.] * self.num_rand_vecs
# Hamiltonian rescaled as in Eq. (24)
self.ham, (self._a, self._b) = _rescale(ham, epsilon=self.epsilon,
v0=self._v0, bounds=bounds)
self.bounds = (self._b - self._a, self._b + self._a)
self._update_moments_list(self.num_moments, self.num_rand_vecs)
for r in range(num_rand_vecs):
self._rand_vect_list.append(self._vector_factory(self.ham.shape[0]))
self._last_two_alphas = [0.] * num_rand_vecs
self._moments_list = [0.] * num_rand_vecs
self.num_rand_vecs = 0 # new random vectors will be used
self._update_moments_list(self.num_moments, num_rand_vecs)
#update the number of random vectors
self.num_rand_vecs = num_rand_vecs
# sum moments of all random vectors
moments = np.sum(np.asarray(self._moments_list), axis=0)
moments = np.sum(np.asarray(self._moments_list).real, axis=0)
# divide by the number of random vectors
moments = moments / self.num_rand_vecs
# divide by the length of the vectors to normalize
......@@ -242,12 +248,12 @@ class SpectralDensity:
else:
energy = np.asarray(energy, dtype=complex)
rescaled_energy = (energy - self._b) / self._a
g_e = np.pi * np.sqrt(1 - rescaled_energy) *\
np.sqrt(1 + rescaled_energy)
g_e = (np.pi * np.sqrt(1 - rescaled_energy)
* np.sqrt(1 + rescaled_energy))
# calculate the coefficients for Chebyshev expansion
# sum moments of all random vectors
moments = np.sum(np.asarray(self._moments_list), axis=0)
moments = np.sum(np.asarray(self._moments_list).real, axis=0)
# divide by the number of random vectors
moments = moments / self.num_rand_vecs
# divide by the length of the vectors to normalize
......@@ -328,6 +334,11 @@ class SpectralDensity:
# 2nd. update moments with the updated random vectors,
# 3rd. update sampling points and FFT densities.
num_rand_vecs = num_rand_vecs or self.num_rand_vecs
new_rand_vect = num_rand_vecs - self.num_rand_vecs
for r in range(new_rand_vect):
self._rand_vect_list.append(self._vector_factory(self.ham.shape[0]))
self._moments_list.extend([0.] * new_rand_vect)
self._last_two_alphas.extend([0.] * new_rand_vect)
self._update_moments_list(self.num_moments, num_rand_vecs)
self.num_rand_vecs = num_rand_vecs
......@@ -335,6 +346,10 @@ class SpectralDensity:
self._update_moments_list(num_moments, self.num_rand_vecs)
self.num_moments = num_moments
## cut random vectors and moments when necessary
self._moments_list[:] = [m[:num_moments]
for m in self._moments_list[:num_rand_vecs]]
num_sampling_points = num_sampling_points or self.num_sampling_points
if num_sampling_points < self.num_moments:
raise ValueError(
......@@ -343,7 +358,7 @@ class SpectralDensity:
self.num_sampling_points = num_sampling_points
# sum moments of all random vectors
moments = np.sum(np.asarray(self._moments_list), axis=0)
moments = np.sum(np.asarray(self._moments_list).real, axis=0)
# divide by the number of random vectors
moments = moments / self.num_rand_vecs
# divide by the length of the vectors to normalize
......@@ -373,42 +388,39 @@ class SpectralDensity:
n_rand : integer
Number of random vectors used for sampling.
"""
dim = self.ham.shape[0]
if len(self._rand_vect_list) == 0:
for r in range(n_rand):
self._rand_vect_list.append(self._vector_factory(dim))
if self.num_rand_vecs == n_rand:
r_start = 0
r_stop = self.num_rand_vecs
new_rand_vect = 0
elif self.num_rand_vecs < n_rand:
r_start = self.num_rand_vecs
new_rand_vect = n_rand - self.num_rand_vecs
else:
if self.num_rand_vecs == n_rand:
r_start = 0
r_stop = self.num_rand_vecs
elif self.num_rand_vecs < n_rand:
new_rand_vect = n_rand - self.num_rand_vecs
for r in range(new_rand_vect):
self._rand_vect_list.append(self._vector_factory(dim))
self._moments_list.extend([0.] * new_rand_vect)
r_start = self.num_rand_vecs
r_stop = n_rand
else:
warnings.warn('Decreasing number of random vectors')
r_start = 0
r_stop = n_rand
warnings.warn('Decreasing number of random vectors')
return
if n_moments == self.num_moments:
start = 2
m_start = 2
new_moments = 0
if new_rand_vect == 0:
# nothing new to calculate
return
else:
start = self.num_moments
new_moments = n_moments - self.num_moments
stop = n_moments
m_start = self.num_moments
if new_moments < 0:
warnings.warn('Decreasing the number of moments.')
return
for r in range(r_start, r_stop):
assert new_rand_vect == 0,\
("Only 'num_moments' *or* 'num_rand_vecs' may be updated "
"at a time.")
for r in range(r_start, n_rand):
alpha_zero = self._rand_vect_list[r]
one_moment = [0.] * n_moments
if new_moments == 0:
if new_rand_vect > 0:
alpha = alpha_zero
alpha_next = self.ham.matvec(alpha)
if self.operator is None:
......@@ -418,11 +430,9 @@ class SpectralDensity:
one_moment[0] = self.operator(alpha_zero, alpha_zero)
one_moment[1] = self.operator(alpha_zero, alpha_next)
elif new_moments > 0:
if new_moments > 0:
(alpha, alpha_next) = self._last_two_alphas[r]
one_moment[0:self.num_moments] = self._moments_list[r]
else:
raise ValueError('Attempt to decrease the number of moments.')
# Iteration over the moments
# Two cases can occur, depicted in Eq. (28) and in Eq. (29),
# respectively.
......@@ -434,24 +444,28 @@ class SpectralDensity:
# In the second case, the operator is not None and a matrix
# multiplication should be used.
if self.operator is None:
for n in range(start//2, stop//2):
for n in range(m_start//2, n_moments//2):
alpha_save = alpha_next
alpha_next = 2 * self.ham.matvec(alpha_next) - alpha
alpha = alpha_save
# Following Eqs. (34) and (35)
one_moment[2*n] = 2 * np.vdot(alpha, alpha) -\
one_moment[0]
one_moment[2*n+1] = 2 * np.vdot(alpha_next, alpha) -\
one_moment[1]
one_moment[2*n] = (2 * np.vdot(alpha, alpha)
- one_moment[0])
one_moment[2*n+1] = (2 * np.vdot(alpha_next, alpha)
- one_moment[1])
if n_moments % 2:
# odd moment
one_moment[n_moments - 1] = (2 * np.vdot(alpha_next, alpha_next)
- one_moment[0])
else:
for n in range(start, stop):
for n in range(m_start, n_moments):
alpha_save = alpha_next
alpha_next = 2 * self.ham.matvec(alpha_next) - alpha
alpha = alpha_save
one_moment[n] = self.operator(alpha_zero, alpha_next)
self._last_two_alphas.append((alpha, alpha_next))
self._moments_list[r] = np.asarray(one_moment).real
self._last_two_alphas[r] = (alpha, alpha_next)
self._moments_list[r] = one_moment[:]
# ### Auxiliary functions
......
......@@ -163,7 +163,7 @@ def test_api_operator():
def test_api_lower_num_moments():
spectrum = SpectralDensity(kwant.rmt.gaussian(dim))
num_moments = spectrum.num_moments - 1
with pytest.raises(ValueError):
with pytest.warns(UserWarning):
spectrum.increase_accuracy(num_moments=num_moments)
......@@ -319,10 +319,27 @@ def test_increase_num_moments():
spectrum.increase_accuracy(num_moments=precise.num_moments)
# Check bit for bit equality
assert np.all(spectrum_raise.densities == spectrum.densities)
assert np.all(np.array(spectrum_raise._moments_list) ==
np.array(spectrum._moments_list))
# ### increase num_moments with an operator (different algorithm)
# test case when there are an odd number of moments
# (this code path is treated differently internally)
odd = copy(p)
odd.num_moments = p.num_moments + 1
spectrum_odd = make_spectrum(ham, odd, rng=1)
assert not np.all(np.asarray(spectrum_odd._moments_list)[:, -1] == 0)
# test when increasing num_moments by 1 from an even to an odd number
spectrum_even = make_spectrum(ham, p, rng=1)
spectrum_even.increase_accuracy(num_moments=p.num_moments + 1)
assert np.all(np.array(spectrum_even._moments_list) ==
np.array(spectrum_odd._moments_list))
# ### increase num_moments with an operator (different algorithm)
def test_increase_num_moments_op():
ham = kwant.rmt.gaussian(dim)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment