Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolving the issue with pop_frequency calculation for polar materials #931

Merged
merged 4 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 122 additions & 9 deletions amset/tools/phonon_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import click
import numpy as np
import re

__author__ = "Alex Ganose"
__maintainer__ = "Alex Ganose"
Expand All @@ -18,7 +19,6 @@ def phonon_frequency(vasprun, outcar):
from tabulate import tabulate

vasprun = get_file(vasprun, Vasprun)
outcar = get_file(outcar, Outcar)

elements = vasprun.final_structure.composition.elements
if len(set(elements)) == 1:
Expand All @@ -28,9 +28,18 @@ def phonon_frequency(vasprun, outcar):
"pop_frequency."
)

effective_frequency, weights, freqs = effective_phonon_frequency_from_vasp_files(
vasprun, outcar
)
try:
effective_frequency, weights, freqs = effective_phonon_frequency_from_vasp_files(
vasprun, outcar
)
click.echo("Found NAC corrected phonon frequencies\n")
except ValueError:
click.echo("No NAC corrected file provided. Skipping calculation requiring NAC\n")

outcar = get_file(outcar, Outcar)
effective_frequency, weights, freqs = effective_phonon_frequency_from_vasp_files_no_nac(
vasprun, outcar
)

table = tabulate(
list(zip(freqs, weights)),
Expand All @@ -44,11 +53,20 @@ def phonon_frequency(vasprun, outcar):

return effective_frequency


def effective_phonon_frequency_from_vasp_files(vasprun, outcar):
frequencies, eigenvectors, born_effective_charges = extract_gamma_point_data(outcar)

effective_frequency, weights = calculate_effective_phonon_frequency(
frequencies, eigenvectors, born_effective_charges, vasprun.final_structure
)

return effective_frequency, weights, frequencies


def effective_phonon_frequency_from_vasp_files_no_nac(vasprun, outcar):
eigenvalues = -vasprun.normalmode_eigenvals[::-1]
eigenvectors = vasprun.normalmode_eigenvecs[::-1]

# get frequencies from eigenvals (and convert to THz for VASP 5)
major_version = int(vasprun.vasp_version.split(".")[0])
frequencies = np.sqrt(np.abs(eigenvalues)) * np.sign(eigenvalues)
Expand All @@ -62,10 +80,11 @@ def effective_phonon_frequency_from_vasp_files(vasprun, outcar):
effective_frequency, weights = calculate_effective_phonon_frequency(
frequencies, eigenvectors, born_effective_charges, vasprun.final_structure
)

return effective_frequency, weights, frequencies



def calculate_effective_phonon_frequency(
frequencies: np.ndarray,
eigenvectors: np.ndarray,
Expand Down Expand Up @@ -99,23 +118,117 @@ def get_phonon_weight(eigenvector, frequency, born_effective_charges, structure)
born_effective_charges, eigenvector, structure
):
ze = np.dot(atom_born, atom_vec) / np.sqrt(site.specie.atomic_mass * frequency)

zes.append(ze)

all_weights = []
for direction in directions:
# Calculate q.Z*.e(q) / sqrt(M*omega) for each atom
qze = np.dot(zes, direction)

# sum across all atoms
all_weights.append(np.abs(np.sum(qze)))

weight = np.average(all_weights * quad_weights)

if np.isnan(weight):
return 0
else:
return weight

#------block to extract data from new outcar-----#
def parse_frequencies(line):
"""Extract frequencies from a given line."""
return [float(value) for value in re.findall(r"[-+]?\d*\.\d+|\d+", line)]

def extract_real_parts(eigenvector_line):
"""Extract the real parts of eigenvector components from a given line."""
components = re.findall(r"[-+]?\d*\.\d+(?:[eE][-+]?\d+)?", eigenvector_line)
real_parts = [
float(components[i]) for i in range(0, len(components), 2)
] # Take every other value starting from 0 (real part)
return real_parts

def reshape_to_3x3(real_parts):
"""Reshape a flat list of real parts into a 3x3 array."""
return np.array(real_parts).reshape(3, 3)

def extract_gamma_point_data(file_path):
"""Extract frequencies and reshaped eigenvectors for the gamma point."""
with open(file_path, 'r') as file:
content = file.read()

match = re.findall("PHON_BORN_CHARGES = .*", content)

if match:
born = np.array(list(map(float, match[0].split("=")[1].split()))).reshape(-1, 3, 3)
else:
raise ValueError("Could not find PHONON_BORN_CHARGES")

content = content.split("\n")
# Identify the phonon section
phonon_section = []
phonon_started = False
for line in content:
if "Phonons" in line:
phonon_started = True
if phonon_started:
phonon_section.append(line)
if phonon_started and "--------------------------------------------------------------------------------" in line:
break

# Initialize data containers
q_points = []
frequencies = []
eigenvector_3d = []

current_q_point = None
current_frequencies = []
current_real_parts = []

for line in phonon_section:
# Detect q-point line
if re.match(r"\s*\d+\s+[-+]?\d+\.\d+", line):
if current_q_point: # Save previous q-point data
q_points.append(current_q_point)
frequencies.append(np.array(current_frequencies))
eigenvector_3d.append(np.array(current_real_parts))
current_frequencies = []
current_real_parts = []
current_q_point = [float(x) for x in line.split()[1:4]]
# Parse frequencies
elif "[THz]" in line:
current_frequencies = parse_frequencies(line)
# Parse eigenvectors
elif re.match(r"\s+[-+]?\d+\.\d+", line):
real_parts = extract_real_parts(line.strip())
reshaped_vector = reshape_to_3x3(real_parts)
current_real_parts.append(reshaped_vector)

# Save the last q-point's data
if current_q_point:
q_points.append(current_q_point)
frequencies.append(np.array(current_frequencies))
eigenvector_3d.append(np.array(current_real_parts))

# Locate gamma point
gamma_index = None
for i, q_point in enumerate(q_points):
if np.allclose(q_point, [0.0, 0.0, 0.0]):
gamma_index = i
break

if gamma_index is None:
raise ValueError("Gamma point ([0.0, 0.0, 0.0]) not found in the OUTCAR file.")

# Extract gamma point data
gamma_frequencies = frequencies[gamma_index]
gamma_eigenvectors_3d = np.array(eigenvector_3d[gamma_index])

return gamma_frequencies, gamma_eigenvectors_3d, born

#--------block ends here--------#


def get_file(filename, class_type):
if isinstance(filename, str):
Expand Down
22 changes: 20 additions & 2 deletions docs/src/inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,31 @@ Note, DFPT cannot be used with hybrid exchange-correlation functionals. In these
cases the [LCALCEPS](https://www.vasp.at/wiki/index.php/LCALCEPS) flag should be
used in combination with `IBRION = 6`.

**Non-Analytical Term Correction (NAC) for Polar Materials**

In polar materials, LO-TO splitting in the phonon dispersion is significant, and to observe this in phonon dispersion, we have to add a non-analytical term correction (NAC) to the dynamical matrix and re-calculate the phonon frequencies. This treatment is done automatically inside VASP using the [LPHON_POLAR = .TRUE.](https://www.vasp.at/wiki/index.php/LPHON_POLAR) tag and specifying the dielectric tensor using [PHON_DIELECTRIC](https://www.vasp.at/wiki/index.php/PHON_DIELECTRIC) tag along with the Born effective charges using [PHON_BORN_CHARGES](https://www.vasp.at/wiki/index.php/PHON_BORN_CHARGES) tag. This step should be performed after obtaining the Born effective charges and dielectric constants from a previous DFPT calculation.

!!! summary "VASP settings for dielectric constants and phonon frequency "
```python
ADDGRID = True
EDIFF = 1E-8
PREC = Accurate
NSW = 1
IBRION = 6
LPHON_DISPERSION = .TRUE.
PHON_NWRITE = -3
LPHON_POLAR = .TRUE.
PHON_DIELECTRIC = [values]
PHON_BORN_CHARGES = [values]
```

The dielectric constants and polar phonon frequency can be extracted from the
VASP outputs using the command:
```bash
amset phonon-frequency
```
The command should be run in a folder containing the `vasprun.xml` file output
from the DFPT calculation.
The command should be run in a folder containing the `vasprun.xml` and `OUTCAR` file output
from the DFPT calculation. For polar materials, an `OUTCAR` from the NAC calculaiton must be provided.

The effective phonon frequency is determined from the phonon frequencies
$`\omega_{\mathbf{q}\nu}`$ (where $`\nu`$ is a phonon branch and $`\mathbf{q}`$
Expand Down
Loading