Skip to content

Commit b29a0a6

Browse files
authored
Add PhononDos.get_last_peak() (#3525)
* add PhononDos.get_last_peak() * add TestPhononDos.test_get_last_peak
1 parent 4e40e57 commit b29a0a6

File tree

2 files changed

+50
-0
lines changed

2 files changed

+50
-0
lines changed

pymatgen/phonon/dos.py

+41
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,47 @@ def mae(self, other: PhononDos, two_sided: bool = True) -> float:
354354

355355
return self_mae
356356

357+
def get_last_peak(self, threshold: float = 0.1) -> float:
358+
"""Find the last peak in the phonon DOS defined as the highest frequency with a DOS
359+
value at least threshold * height of the overall highest DOS peak.
360+
A peak is any local maximum of the DOS as a function of frequency.
361+
Use dos.get_interpolated_value(peak_freq) to get density at peak_freq.
362+
363+
TODO method added by @janosh on 2023-12-18. seems to work well in most cases but
364+
was not extensively tested. PRs with improvements welcome!
365+
366+
Args:
367+
threshold (float, optional): Minimum ratio of the height of the last peak
368+
to the height of the highest peak. Defaults to 0.1 = 10%. In case no peaks
369+
are high enough to match, the threshold is reset to half the height of the
370+
second-highest peak.
371+
372+
Returns:
373+
float: last DOS peak frequency (in THz)
374+
"""
375+
first_deriv = np.gradient(self.densities, self.frequencies)
376+
second_deriv = np.gradient(first_deriv, self.frequencies)
377+
378+
maxima = ( # maxima indices of the first DOS derivative w.r.t. frequency
379+
(first_deriv[:-1] > 0) & (first_deriv[1:] < 0) & (second_deriv[:-1] < 0)
380+
)
381+
# get mean of the two nearest frequencies around the maximum as better estimate
382+
maxima_freqs = (self.frequencies[:-1][maxima] + self.frequencies[1:][maxima]) / 2
383+
384+
# filter maxima based on the threshold
385+
max_dos = max(self.densities)
386+
threshold = threshold * max_dos
387+
filtered_maxima_freqs = maxima_freqs[self.densities[:-1][maxima] >= threshold]
388+
389+
if len(filtered_maxima_freqs) == 0:
390+
# if no maxima reach the threshold (i.e. 1 super high peak and all other peaks
391+
# tiny), use half the height of second highest peak as threshold
392+
second_highest_peak = sorted(self.densities)[-2]
393+
threshold = second_highest_peak / 2
394+
filtered_maxima_freqs = maxima_freqs[self.densities[:-1][maxima] >= threshold]
395+
396+
return max(filtered_maxima_freqs)
397+
357398

358399
class CompletePhononDos(PhononDos):
359400
"""This wrapper class defines a total dos, and also provides a list of PDos.

tests/phonon/test_dos.py

+9
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ def test_mae(self):
102102
assert self.dos.mae(dos2 - 1, two_sided=False) == pytest.approx(1.00000000000031)
103103
assert self.dos.mae(2 * dos2, two_sided=False) == pytest.approx(0.786546967)
104104

105+
def test_get_last_peak(self):
106+
peak_freq = self.dos.get_last_peak()
107+
assert peak_freq == approx(5.9909763191)
108+
assert self.dos.get_interpolated_value(peak_freq) == approx(1.1700016497)
109+
110+
# try different threshold
111+
peak_freq = self.dos.get_last_peak(threshold=0.5)
112+
assert peak_freq == approx(4.9662820761)
113+
105114

106115
class TestCompletePhononDos(PymatgenTest):
107116
def setUp(self):

0 commit comments

Comments
 (0)