Skip to content

Commit 5c58e9f

Browse files
committed
Add qpoints phonons mode as available in phonopy
1 parent b3c7bc4 commit 5c58e9f

File tree

5 files changed

+159
-3
lines changed

5 files changed

+159
-3
lines changed

docs/source/user_guide/command_line.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,13 @@ with 101 sampling points for each path segment.
623623
:height: 700px
624624
:align: center
625625

626+
List of q-points
627+
++++++++++++++++
628+
629+
Band mode outputs results in q lines or segments. Phonons data on a list of arbitrary points can be obtained with the ``--qpoints`` option. This corresponds to the ``QPOINTS`` tag in phonopy.
630+
631+
Input q-points have to be supplied in file ``QPOINTS``, formatted as prescribed by `phonopy <https://phonopy.github.io/phonopy/input-files.html#qpoints-file>`_.
632+
626633

627634
Nudged Elastic Band
628635
-------------------

janus_core/calculations/phonons.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ase import Atoms
99
from numpy import ndarray
1010
import phonopy
11-
from phonopy.file_IO import write_force_constants_to_hdf5
11+
from phonopy.file_IO import parse_QPOINTS, write_force_constants_to_hdf5
1212
from phonopy.phonon.band_structure import (
1313
get_band_qpoints_and_path_connections,
1414
get_band_qpoints_by_seekpath,
@@ -342,11 +342,13 @@ def __init__(
342342
self.phonopy_file = self._build_filename("phonopy.yml")
343343
self.force_consts_file = self._build_filename("force_constants.hdf5")
344344

345-
filename = "bands" + (".hdf5" if hdf5 else ".yml")
345+
suffix = ".hdf5" if hdf5 else ".yml"
346+
filename = "bands" + suffix
346347
if not self.qpoint_file:
347348
filename = f"auto_{filename}"
348349
self.bands_file = self._build_filename(filename)
349350
self.bands_plot_file = self._build_filename("bands.svg")
351+
self.qpoints_file = self._build_filename("qpoints" + suffix)
350352
self.dos_file = self._build_filename("dos.dat")
351353
self.dos_plot_file = self._build_filename("dos.svg")
352354
self.bands_dos_plot_file = self._build_filename("bs-dos.svg")
@@ -438,6 +440,11 @@ def output_files(self) -> None:
438440
if self.write_results and "bands" in self.calcs
439441
else None
440442
),
443+
"qpoints": (
444+
self.qpoints_file
445+
if self.write_results and "qpoints" in self.calcs
446+
else None
447+
),
441448
"bands_plot": self.bands_plot_file if self.plot_to_file else None,
442449
"dos": (
443450
self.dos_file if self.write_results and "dos" in self.calcs else None
@@ -712,6 +719,86 @@ def write_bands(
712719
build_file_dir(self.bands_plot_file)
713720
bplt.savefig(self.bands_plot_file)
714721

722+
def calc_qpoints(self, write_qpoints: bool | None = None, **kwargs) -> None:
723+
"""
724+
Calculate phonons at qpoints supplied by file QPOINTS, analoguous to phonopy.
725+
726+
Parameters
727+
----------
728+
write_qpoints
729+
Whether to write out results to file. Default is self.write_results.
730+
**kwargs
731+
Additional keyword arguments to pass to `write_bands`.
732+
"""
733+
if write_qpoints is None:
734+
write_qpoints = self.write_results
735+
736+
# Calculate phonons if not already in results
737+
if "phonon" not in self.results:
738+
# Use general (self.write_results) setting for writing force constants
739+
self.calc_force_constants(write_force_consts=self.write_results)
740+
741+
if write_qpoints:
742+
self.write_qpoints(**kwargs)
743+
744+
def write_qpoints(
745+
self,
746+
*,
747+
hdf5: bool | None = None,
748+
qpoints_file: PathLike | None = None,
749+
) -> None:
750+
"""
751+
Write results of qpoints mode calculations.
752+
753+
Parameters
754+
----------
755+
hdf5
756+
Whether to save the bands in an hdf5 file. Default is
757+
self.hdf5.
758+
qpoints_file
759+
Name of yaml file to save band structure. Default is inferred from
760+
`file_prefix`.
761+
"""
762+
if "phonon" not in self.results:
763+
raise ValueError(
764+
"Force constants have not been calculated yet. "
765+
"Please run `calc_force_constants` first"
766+
)
767+
768+
if hdf5 is None:
769+
hdf5 = self.hdf5
770+
771+
if qpoints_file:
772+
self.qpoints_file = qpoints_file
773+
774+
# maybe use self.qpoint_file or allow custom input filename
775+
# also allow passing a list of points programmatically
776+
q_points = parse_QPOINTS()
777+
778+
fonons = self.results["phonon"]
779+
780+
fonons.run_qpoints(
781+
q_points,
782+
with_eigenvectors=self.write_full,
783+
with_group_velocities=self.write_full,
784+
with_dynamical_matrices=self.write_full,
785+
# nac_q_direction = self.nac_q_direction,
786+
)
787+
788+
build_file_dir(self.qpoints_file)
789+
if hdf5:
790+
# not in phonopy yet
791+
# fonons.write_hdf5_qpoints_phonon(filename=self.qpoints_file)
792+
793+
# until the above is implemented in phonopy
794+
fonons._qpoints.write_hdf5(filename=self.qpoints_file)
795+
else:
796+
# not in phonopy yet
797+
# fonons.write_yaml_qpoints_phonon(filename=self.qpoints_file)
798+
799+
# until the above is implemented in phonopy
800+
fonons._qpoints.write_yaml(filename=self.qpoints_file)
801+
715802
def calc_thermal_props(
716803
self,
717804
mesh: tuple[int, int, int] | None = None,
@@ -1014,12 +1101,16 @@ def run(self) -> None:
10141101
if "bands" in self.calcs:
10151102
self.calc_bands()
10161103

1104+
if "qpoints" in self.calcs:
1105+
self.calc_qpoints()
1106+
10171107
# Calculate thermal properties if specified
10181108
if "thermal" in self.calcs:
10191109
self.calc_thermal_props()
10201110

10211111
# Calculate DOS and PDOS if specified
10221112
if "dos" in self.calcs:
10231113
self.calc_dos(plot_bands="bands" in self.calcs)
1114+
10241115
if "pdos" in self.calcs:
10251116
self.calc_pdos()

janus_core/cli/phonons.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ def phonons(
6868
help="Whether to compute band structure.", rich_help_panel="Calculation"
6969
),
7070
] = False,
71+
qpoints: Annotated[
72+
bool,
73+
Option(
74+
help="Whether to compute for qpoints supplied in file QPOINTS.",
75+
rich_help_panel="Calculation",
76+
),
77+
] = False,
7178
n_qpoints: Annotated[
7279
int,
7380
Option(
@@ -218,6 +225,8 @@ def phonons(
218225
Mesh for sampling. Default is (10, 10, 10).
219226
bands
220227
Whether to calculate and save the band structure. Default is False.
228+
qpoints
229+
Whether to compute for qpoints supplied in file QPOINTS. Default is False.
221230
n_qpoints
222231
Number of q-points to sample along generated path, including end points.
223232
Unused if `qpoint_file` is specified. Default is 51.
@@ -360,6 +369,8 @@ def phonons(
360369
calcs = []
361370
if bands:
362371
calcs.append("bands")
372+
if qpoints:
373+
calcs.append("qpoints")
363374
if thermal:
364375
calcs.append("thermal")
365376
if dos:

janus_core/helpers/janus_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ class Correlation(TypedDict, total=True):
138138
Devices = Literal["cpu", "cuda", "mps", "xpu"]
139139
Ensembles = Literal["nph", "npt", "nve", "nvt", "nvt-nh", "nvt-csvr", "npt-mtk"]
140140
Properties = Literal["energy", "stress", "forces", "hessian"]
141-
PhononCalcs = Literal["bands", "dos", "pdos", "thermal"]
141+
PhononCalcs = Literal["bands", "dos", "pdos", "qpoints", "thermal"]
142142
Interpolators = Literal["ase", "pymatgen"]
143143

144144

tests/test_phonons_cli.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,53 @@ def test_bands_simple(tmp_path):
146146
assert phonon_summary["config"]["bands"]
147147

148148

149+
def test_qpoints_simple(tmp_path):
150+
"""Test qpoints mode."""
151+
with chdir(tmp_path):
152+
file_prefix = "NaCl"
153+
qpoints_results = "NaCl-qpoints.hdf5"
154+
summary_path = "NaCl-phonons-summary.yml"
155+
156+
with open("QPOINTS", mode="w", encoding="utf8") as file:
157+
file.write("3\n0.0 0.0 0.0\n0.1 0.1 0.0\n0.2 0.2 0.2\n")
158+
159+
result = runner.invoke(
160+
app,
161+
[
162+
"phonons",
163+
"--struct",
164+
DATA_PATH / "NaCl.cif",
165+
"--arch",
166+
"mace_mp",
167+
"--qpoints",
168+
"--write-full",
169+
"--hdf5",
170+
"--file-prefix",
171+
file_prefix,
172+
],
173+
)
174+
assert result.exit_code == 0
175+
176+
assert qpoints_results.exists()
177+
with HDF5Open(qpoints_results, "r") as h5f:
178+
assert "dynamical_matrix" in h5f
179+
assert "eigenvector" in h5f
180+
assert "frequency" in h5f
181+
assert "masses" in h5f
182+
assert "qpoint" in h5f
183+
assert "reciprocal_lattice" in h5f
184+
185+
# Read phonons summary file
186+
assert summary_path.exists()
187+
with open(summary_path, encoding="utf8") as file:
188+
phonon_summary = yaml.safe_load(file)
189+
190+
assert "command" in phonon_summary
191+
assert "janus phonons" in phonon_summary["command"]
192+
assert "config" in phonon_summary
193+
assert phonon_summary["config"]["qpoints"]
194+
195+
149196
@pytest.mark.parametrize("compression", [None, "gzip", "lzf"])
150197
def test_hdf5(tmp_path, compression):
151198
"""Test saving force constants and bands to HDF5 in new directory."""

0 commit comments

Comments
 (0)