diff --git a/.gitignore b/.gitignore index 42de640a..6f52fd35 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ *.dot *.png *.svg +*.xz ~* *~ .project diff --git a/docs/source/images/NaCl-bands.svg b/docs/source/images/NaCl-bands.svg new file mode 100644 index 00000000..8e0a52e2 --- /dev/null +++ b/docs/source/images/NaCl-bands.svg @@ -0,0 +1,15915 @@ + + + + + + + + 2024-10-22T13:31:49.384111 + image/svg+xml + + + Matplotlib v3.9.2, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/source/user_guide/command_line.rst b/docs/source/user_guide/command_line.rst index 55d16002..a7ea5b04 100644 --- a/docs/source/user_guide/command_line.rst +++ b/docs/source/user_guide/command_line.rst @@ -320,7 +320,8 @@ Calculate phonons with a 2x2x2 supercell, after geometry optimization (using the This will save the Phonopy parameters, including displacements and force constants, to ``NaCl-phonopy.yml`` and ``NaCl-force_constants.hdf5``, in addition to generating a log file, ``NaCl-phonons-log.yml``, and summary of inputs, ``NaCl-phonons-summary.yml``. -Additionally, the ``--bands`` option can be added to calculate the band structure and save the results to ``NaCl-auto_bands.yml``: +Additionally, the ``--bands`` option can be added to calculate the band structure +and save the results to a compressed yaml file, ``NaCl-auto_bands.yml.xz``: .. code-block:: bash @@ -348,6 +349,80 @@ Similar to Phonopy, the supercell matrix can be defined in three ways: For all options, run ``janus phonons --help``. +Band paths +++++++++++ + +By default, q-points along BZ high symmetry paths are generated using `SeeK-path `_, +but band paths can also be specified explicitly using the ``--paths`` option to specify a yaml file. + +.. code-block:: bash + + janus phonons --struct tests/data/NaCl.cif --bands --plot-to-file --paths tests/data/paths.yml + + +This will save the results in a compressed yaml file, ``NaCl-bands.yml.xz``, as well as the generated plot, ``NaCl-bands.svg``. + +The ``--paths`` file must include: + +- ``labels``, which label band segment points + +- ``paths``, which list reciprocal points in reduced coordinates to give the band paths + + - Multiple lists can be specified to define disconnected paths + +- ``npoints``, which gives the number of sampling points, including path ends, in each path segment + +These correspond to ``BAND_LABELS``, ``BAND``, and ``BAND_POINTS`` in `phonopy `_. + +For example: + +.. code-block:: yaml + + labels: + - $\Gamma$ + - $\mathrm{X}$ + - $\mathrm{U}$ + - $\mathrm{K}$ + - $\Gamma$ + - $\mathrm{L}$ + - $\mathrm{W}$ + - $\mathrm{X}$ + npoints: 101 + paths: + - - - 0.0 + - 0.0 + - 0.0 + - - 0.5 + - 0.0 + - 0.5 + - - 0.625 + - 0.25 + - 0.625 + - - - 0.375 + - 0.375 + - 0.75 + - - 0.0 + - 0.0 + - 0.0 + - - 0.5 + - 0.5 + - 0.5 + - - 0.5 + - 0.25 + - 0.75 + - - 0.5 + - 0.0 + - 0.5 + + +This defines two disconnected paths, one between :math:`{\Gamma}`, :math:`X` and :math:`U`, +and one between :math:`K`, :math:`{\Gamma}`, :math:`L`, :math:`W`, and :math:`X`, +with 101 sampling points for each path segment. + +.. image:: ../images/NaCl-bands.svg + :height: 700px + :align: center + Training and fine-tuning MLIPs ------------------------------ diff --git a/janus_core/calculations/phonons.py b/janus_core/calculations/phonons.py index b69c5ed6..0718908c 100644 --- a/janus_core/calculations/phonons.py +++ b/janus_core/calculations/phonons.py @@ -9,7 +9,12 @@ from numpy import ndarray import phonopy from phonopy.file_IO import write_force_constants_to_hdf5 +from phonopy.phonon.band_structure import ( + get_band_qpoints_and_path_connections, + get_band_qpoints_by_seekpath, +) from phonopy.structure.atoms import PhonopyAtoms +from yaml import safe_load from janus_core.calculations.base import BaseCalculation from janus_core.calculations.geom_opt import GeomOpt @@ -22,7 +27,7 @@ PathLike, PhononCalcs, ) -from janus_core.helpers.utils import none_to_dict, track_progress, write_table +from janus_core.helpers.utils import none_to_dict, track_progress class Phonons(BaseCalculation): @@ -71,6 +76,8 @@ class Phonons(BaseCalculation): three as the second row, etc. Default is 2. displacement : float Displacement for force constants calculation, in A. Default is 0.01. + displacement_kwargs : dict[str, Any] | None + Keyword arguments to pass to generate_displacements. Default is {}. mesh : tuple[int, int, int] Mesh for sampling. Default is (10, 10, 10). symmetrize : bool @@ -81,6 +88,16 @@ class Phonons(BaseCalculation): Default is False. minimize_kwargs : dict[str, Any] | None Keyword arguments to pass to geometry optimizer. Default is {}. + n_qpoints : int + Number of q-points to sample along generated path, including end points. + Unused if `qpoint_file` is specified. Default is 51. + qpoint_file : PathLike | None + Path to yaml file with info to generate a path of q-points for band structure. + Default is None. + dos_kwargs : dict[str, Any] | None + Keyword arguments to pass to run_total_dos. Default is {}. + pdos_kwargs : dict[str, Any] | None + Keyword arguments to pass to run_projected_dos. Default is {}. temp_min : float Start temperature for thermal properties calculations, in K. Default is 0.0. temp_max : float @@ -88,8 +105,7 @@ class Phonons(BaseCalculation): temp_step : float Temperature step for thermal properties calculations, in K. Default is 50.0. force_consts_to_hdf5 : bool - Whether to write force constants in hdf format or not. - Default is True. + Whether to write force constants in hdf format or not. Default is True. plot_to_file : bool Whether to plot various graphs as band stuctures, dos/pdos in svg. Default is False. @@ -115,7 +131,7 @@ class Phonons(BaseCalculation): ------- calc_force_constants(write_force_consts) Calculate force constants and optionally write results. - write_force_constants(phonopy_file, force_consts_to_hdf5 force_consts_file) + write_force_constants(phonopy_file, force_consts_to_hdf5, force_consts_file) Write results of force constants calculations. calc_bands(write_bands) Calculate band structure and optionally write and plot results. @@ -154,10 +170,15 @@ def __init__( calcs: MaybeSequence[PhononCalcs] = (), supercell: MaybeList[int] = 2, displacement: float = 0.01, + displacement_kwargs: dict[str, Any] | None = None, mesh: tuple[int, int, int] = (10, 10, 10), symmetrize: bool = False, minimize: bool = False, minimize_kwargs: dict[str, Any] | None = None, + n_qpoints: int = 51, + qpoint_file: PathLike | None = None, + dos_kwargs: dict[str, Any] | None = None, + pdos_kwargs: dict[str, Any] | None = None, temp_min: float = 0.0, temp_max: float = 1000.0, temp_step: float = 50.0, @@ -213,6 +234,8 @@ def __init__( three as the second row, etc. Default is 2. displacement : float Displacement for force constants calculation, in A. Default is 0.01. + displacement_kwargs : dict[str, Any] | None + Keyword arguments to pass to generate_displacements. Default is {}. mesh : tuple[int, int, int] Mesh for sampling. Default is (10, 10, 10). symmetrize : bool @@ -223,6 +246,16 @@ def __init__( Default is False. minimize_kwargs : dict[str, Any] | None Keyword arguments to pass to geometry optimizer. Default is {}. + n_qpoints : int + Number of q-points to sample along generated path, including end points. + Unused if `qpoint_file` is specified. Default is 51. + qpoint_file : PathLike | None + Path to yaml file with info to generate a path of q-points for band + structure. Default is None. + dos_kwargs : dict[str, Any] | None + Keyword arguments to pass to run_total_dos. Default is {}. + pdos_kwargs : dict[str, Any] | None + Keyword arguments to pass to run_projected_dos. Default is {}. temp_min : float Start temperature for thermal calculations, in K. Default is 0.0. temp_max : float @@ -230,8 +263,7 @@ def __init__( temp_step : float Temperature step for thermal calculations, in K. Default is 50.0. force_consts_to_hdf5 : bool - Whether to write force constants in hdf format or not. - Default is True. + Whether to write force constants in hdf format or not. Default is True. plot_to_file : bool Whether to plot various graphs as band stuctures, dos/pdos in svg. Default is False. @@ -246,14 +278,29 @@ def __init__( enable_progress_bar : bool Whether to show a progress bar during phonon calculations. Default is False. """ - (read_kwargs, minimize_kwargs) = none_to_dict((read_kwargs, minimize_kwargs)) + (read_kwargs, displacement_kwargs, minimize_kwargs, dos_kwargs, pdos_kwargs) = ( + none_to_dict( + ( + read_kwargs, + displacement_kwargs, + minimize_kwargs, + dos_kwargs, + pdos_kwargs, + ) + ) + ) self.calcs = calcs self.displacement = displacement + self.displacement_kwargs = displacement_kwargs self.mesh = mesh self.symmetrize = symmetrize self.minimize = minimize self.minimize_kwargs = minimize_kwargs + self.n_qpoints = n_qpoints + self.qpoint_file = qpoint_file + self.dos_kwargs = dos_kwargs + self.pdos_kwargs = pdos_kwargs self.temp_min = temp_min self.temp_max = temp_max self.temp_step = temp_step @@ -406,7 +453,10 @@ def calc_force_constants( ) phonon = phonopy.Phonopy(cell, supercell_matrix) - phonon.generate_displacements(distance=self.displacement) + phonon.generate_displacements( + distance=self.displacement, + **self.displacement_kwargs, + ) disp_supercells = phonon.supercells_with_displacements if self.enable_progress_bar: @@ -534,17 +584,44 @@ def write_bands( if save_plots is None: save_plots = self.plot_to_file - bands_file = self._build_filename("auto_bands.yml", filename=bands_file) - self.results["phonon"].auto_band_structure( - write_yaml=True, - filename=bands_file, + if self.qpoint_file: + bands_file = self._build_filename("bands.yml.xz", filename=bands_file) + + with open(self.qpoint_file, encoding="utf8") as file: + paths_info = safe_load(file) + + labels = paths_info["labels"] + num_q_points = sum(len(q) for q in paths_info["paths"]) + num_labels = len(labels) + assert ( + num_q_points == num_labels + ), "Number of labels is different to number of q-points specified" + + q_points, connections = get_band_qpoints_and_path_connections( + band_paths=paths_info["paths"], npoints=paths_info["npoints"] + ) + + else: + bands_file = self._build_filename("auto_bands.yml.xz", filename=bands_file) + q_points, labels, connections = get_band_qpoints_by_seekpath( + self.results["phonon"].primitive, self.n_qpoints + ) + + self.results["phonon"].run_band_structure( + paths=q_points, + path_connections=connections, + labels=labels, with_eigenvectors=self.write_full, with_group_velocities=self.write_full, ) + self.results["phonon"].write_yaml_band_structure( + filename=bands_file, + compression="lzma", + ) bplt = self.results["phonon"].plot_band_structure() if save_plots: - plot_file = self._build_filename("auto_bands.svg", filename=plot_file) + plot_file = self._build_filename("bands.svg", filename=plot_file) bplt.savefig(plot_file) def calc_thermal_props( @@ -610,23 +687,8 @@ def write_thermal_props(self, thermal_file: PathLike | None = None) -> None: Name of data file to save thermal properties. Default is inferred from `file_prefix`. """ - thermal_file = self._build_filename("thermal.dat", filename=thermal_file) - - data = { - "temperature": self.results["thermal_properties"]["temperatures"], - "Cv": self.results["thermal_properties"]["heat_capacity"], - "H": self.results["thermal_properties"]["free_energy"], - "S": self.results["thermal_properties"]["entropy"], - } - - with open(thermal_file, "w", encoding="utf8") as out: - write_table( - fmt="ascii", - file=out, - **data, - units={"temperature": "K", "Cv": "J/mol/K", "H": "eV", "S": "eV"}, - formats=dict.fromkeys(data, ".8f"), - ) + thermal_file = self._build_filename("thermal.yml", filename=thermal_file) + self.results["phonon"].write_yaml_thermal_properties(filename=thermal_file) def calc_dos( self, @@ -664,7 +726,7 @@ def calc_dos( self.tracker.start_task("DOS calculation") self.results["phonon"].run_mesh(mesh) - self.results["phonon"].run_total_dos() + self.results["phonon"].run_total_dos(**self.dos_kwargs) if self.logger: self.logger.info("DOS calculation complete") @@ -773,7 +835,7 @@ def calc_pdos( self.results["phonon"].run_mesh( mesh, with_eigenvectors=True, is_mesh_symmetry=False ) - self.results["phonon"].run_projected_dos() + self.results["phonon"].run_projected_dos(**self.pdos_kwargs) if self.logger: self.logger.info("PDOS calculation complete") diff --git a/janus_core/cli/phonons.py b/janus_core/cli/phonons.py index d1ee845f..85724fb8 100644 --- a/janus_core/cli/phonons.py +++ b/janus_core/cli/phonons.py @@ -15,9 +15,12 @@ Architecture, CalcKwargs, Device, + DisplacementKwargs, + DoSKwargs, LogPath, MinimizeKwargs, ModelPath, + PDoSKwargs, ReadKwargsLast, StructPath, Summary, @@ -46,6 +49,7 @@ def phonons( displacement: Annotated[ float, Option(help="Displacement for force constants calculation, in A.") ] = 0.01, + displacement_kwargs: DisplacementKwargs = None, mesh: Annotated[ tuple[int, int, int], Option(help="Mesh numbers along a, b, c axes.") ] = (10, 10, 10), @@ -53,8 +57,28 @@ def phonons( bool, Option(help="Whether to compute band structure."), ] = False, + n_qpoints: Annotated[ + int, + Option( + help=( + "Number of q-points to sample along generated path, including end " + "points. Unused if `qpoint_file` is specified" + ) + ), + ] = 51, + qpoint_file: Annotated[ + Optional[Path], + Option( + help=( + "Path to yaml file with info to generate a path of q-points for band " + "structure." + ) + ), + ] = None, dos: Annotated[bool, Option(help="Whether to calculate the DOS.")] = False, + dos_kwargs: DoSKwargs = None, pdos: Annotated[bool, Option(help="Whether to calculate the PDOS.")] = False, + pdos_kwargs: PDoSKwargs = None, thermal: Annotated[ bool, Option(help="Whether to calculate thermal properties.") ] = False, @@ -134,14 +158,26 @@ def phonons( matrix row-wise. displacement : float Displacement for force constants calculation, in A. Default is 0.01. + displacement_kwargs : Optional[dict[str, Any]] + Keyword arguments to pass to generate_displacements. Default is {}. mesh : tuple[int, int, int] Mesh for sampling. Default is (10, 10, 10). bands : bool Whether to calculate and save the band structure. Default is False. + n_qpoints : int + Number of q-points to sample along generated path, including end points. + Unused if `qpoint_file` is specified. Default is 51. + qpoint_file : Optional[PathLike] + Path to yaml file with info to generate a path of q-points for band structure. + Default is None. dos : bool Whether to calculate and save the DOS. Default is False. + dos_kwargs : Optional[dict[str, Any]] + Other keyword arguments to pass to run_total_dos. Default is {}. pdos : bool Whether to calculate and save the PDOS. Default is False. + pdos_kwargs : Optional[dict[str, Any]] + Other keyword arguments to pass to run_projected_dos. Default is {}. thermal : bool Whether to calculate thermal properties. Default is False. temp_min : float @@ -210,8 +246,22 @@ def phonons( # Check options from configuration file are all valid check_config(ctx) - read_kwargs, calc_kwargs, minimize_kwargs = parse_typer_dicts( - [read_kwargs, calc_kwargs, minimize_kwargs] + ( + displacement_kwargs, + read_kwargs, + calc_kwargs, + minimize_kwargs, + dos_kwargs, + pdos_kwargs, + ) = parse_typer_dicts( + [ + displacement_kwargs, + read_kwargs, + calc_kwargs, + minimize_kwargs, + dos_kwargs, + pdos_kwargs, + ] ) # Read only first structure by default and ensure only one image is read @@ -266,10 +316,15 @@ def phonons( "calcs": calcs, "supercell": supercell, "displacement": displacement, + "displacement_kwargs": displacement_kwargs, "mesh": mesh, "symmetrize": symmetrize, "minimize": minimize, "minimize_kwargs": minimize_kwargs, + "n_qpoints": n_qpoints, + "qpoint_file": qpoint_file, + "dos_kwargs": dos_kwargs, + "pdos_kwargs": pdos_kwargs, "temp_min": temp_min, "temp_max": temp_max, "temp_step": temp_step, diff --git a/janus_core/cli/types.py b/janus_core/cli/types.py index 31143a94..b37e8a70 100644 --- a/janus_core/cli/types.py +++ b/janus_core/cli/types.py @@ -159,6 +159,34 @@ def __str__(self) -> str: ), ] +DoSKwargs = Annotated[ + Optional[TyperDict], + Option( + parser=parse_dict_class, + help=( + """ + Keyword arguments to pass to run_total_dos. Must be passed as a dictionary + wrapped in quotes, e.g. "{'key' : value}". + """ + ), + metavar="DICT", + ), +] + +PDoSKwargs = Annotated[ + Optional[TyperDict], + Option( + parser=parse_dict_class, + help=( + """ + Keyword arguments to pass to run_projected_dos. Must be passed as a + dictionary wrapped in quotes, e.g. "{'key' : value}". + """ + ), + metavar="DICT", + ), +] + EnsembleKwargs = Annotated[ Optional[TyperDict], Option( @@ -173,6 +201,20 @@ def __str__(self) -> str: ), ] +DisplacementKwargs = Annotated[ + Optional[TyperDict], + Option( + parser=parse_dict_class, + help=( + """ + Keyword arguments to pass to generate_displacements. Must be passed as a + dictionary wrapped in quotes, e.g. "{'key' : value}". + """ + ), + metavar="DICT", + ), +] + PostProcessKwargs = Annotated[ Optional[TyperDict], Option( diff --git a/tests/data/paths.yml b/tests/data/paths.yml new file mode 100644 index 00000000..5d2fcd9e --- /dev/null +++ b/tests/data/paths.yml @@ -0,0 +1,11 @@ +labels: +- $\mathrm{K}$ +- $\Gamma$ +npoints: 11 +paths: +- - - 0.375 + - 0.375 + - 0.75 + - - 0.0 + - 0.0 + - 0.0 diff --git a/tests/test_phonons.py b/tests/test_phonons.py index f36bc625..1d03ebae 100644 --- a/tests/test_phonons.py +++ b/tests/test_phonons.py @@ -128,7 +128,7 @@ def test_symmetrize(tmp_path): phonons_1.calc_force_constants() phonons_2 = Phonons( - struct=single_point.struct.copy(), + struct=phonons_1.struct.copy(), write_results=False, minimize=True, minimize_kwargs={"fmax": 0.001}, diff --git a/tests/test_phonons_cli.py b/tests/test_phonons_cli.py index e15dd66a..b66c8d2a 100644 --- a/tests/test_phonons_cli.py +++ b/tests/test_phonons_cli.py @@ -2,6 +2,7 @@ from __future__ import annotations +import lzma from pathlib import Path import pytest @@ -26,7 +27,7 @@ def test_help(): def test_phonons(): """Test calculating force constants and band structure.""" phonopy_path = Path("./NaCl-phonopy.yml").absolute() - bands_path = Path("./NaCl-auto_bands.yml").absolute() + bands_path = Path("./NaCl-auto_bands.yml.xz").absolute() log_path = Path("./NaCl-phonons-log.yml").absolute() summary_path = Path("./NaCl-phonons-summary.yml").absolute() @@ -59,7 +60,7 @@ def test_phonons(): has_eigenvectors = False has_velocity = False - with open(bands_path, encoding="utf8") as file: + with lzma.open(bands_path, mode="rt") as file: for line in file: if "eigenvector" in line: has_eigenvectors = True @@ -89,7 +90,7 @@ def test_phonons(): def test_bands_simple(tmp_path): """Test calculating force constants and reduced bands information.""" file_prefix = tmp_path / "NaCl" - autoband_results = tmp_path / "NaCl-auto_bands.yml" + autoband_results = tmp_path / "NaCl-auto_bands.yml.xz" summary_path = tmp_path / "NaCl-phonons-summary.yml" result = runner.invoke( @@ -99,6 +100,8 @@ def test_bands_simple(tmp_path): "--struct", DATA_PATH / "NaCl.cif", "--bands", + "--n-qpoints", + 21, "--no-write-full", "--no-hdf5", "--file-prefix", @@ -108,9 +111,10 @@ def test_bands_simple(tmp_path): assert result.exit_code == 0 assert autoband_results.exists() - with open(autoband_results, encoding="utf8") as file: + with lzma.open(autoband_results, mode="rb") as file: bands = yaml.safe_load(file) assert "eigenvector" not in bands["phonon"][0]["band"][0] + assert bands["nqpoint"] == 126 # Read phonons summary file assert summary_path.exists() @@ -149,7 +153,7 @@ def test_hdf5(tmp_path): def test_thermal_props(tmp_path): """Test calculating thermal properties.""" file_prefix = tmp_path / "test" / "NaCl" - thermal_results = tmp_path / "test" / "NaCl-thermal.dat" + thermal_results = tmp_path / "test" / "NaCl-thermal.yml" result = runner.invoke( app, @@ -179,6 +183,8 @@ def test_dos(tmp_path): "--struct", DATA_PATH / "NaCl.cif", "--dos", + "--dos-kwargs", + "{'freq_min': -1, 'freq_max': 0}", "--no-hdf5", "--file-prefix", file_prefix, @@ -186,6 +192,9 @@ def test_dos(tmp_path): ) assert result.exit_code == 0 assert dos_results.exists() + lines = dos_results.read_text().splitlines() + assert lines[1].split()[0] == "-1.0000000000" + assert lines[-1].split()[0] == "0.0000000000" def test_pdos(tmp_path): @@ -200,6 +209,8 @@ def test_pdos(tmp_path): "--struct", DATA_PATH / "NaCl.cif", "--pdos", + "--pdos-kwargs", + "{'freq_min': -1, 'freq_max': 0, 'xyz_projection': True}", "--no-hdf5", "--file-prefix", file_prefix, @@ -207,6 +218,10 @@ def test_pdos(tmp_path): ) assert result.exit_code == 0 assert pdos_results.exists() + with open(pdos_results, encoding="utf8") as file: + lines = file.readlines() + assert lines[1].split()[0] == "-1.0000000000" + assert lines[-1].split()[0] == "0.0000000000" def test_plot(tmp_path): @@ -214,13 +229,13 @@ def test_plot(tmp_path): file_prefix = tmp_path / "NaCl" pdos_results = tmp_path / "NaCl-pdos.dat" dos_results = tmp_path / "NaCl-dos.dat" - autoband_results = tmp_path / "NaCl-auto_bands.yml" + autoband_results = tmp_path / "NaCl-auto_bands.yml.xz" summary_path = tmp_path / "NaCl-phonons-summary.yml" svgs = [ tmp_path / "NaCl-dos.svg", tmp_path / "NaCl-pdos.svg", tmp_path / "NaCl-bs-dos.svg", - tmp_path / "NaCl-auto_bands.svg", + tmp_path / "NaCl-bands.svg", ] result = runner.invoke( @@ -443,3 +458,83 @@ def test_no_carbon(tmp_path): with open(summary_path, encoding="utf8") as file: phonon_summary = yaml.safe_load(file) assert "emissions" not in phonon_summary + + +def test_displacement_kwargs(tmp_path): + """Test displacement_kwargs can be set.""" + file_prefix_1 = tmp_path / "NaCl_1" + file_prefix_2 = tmp_path / "NaCl_2" + displacement_file_1 = tmp_path / "NaCl_1-phonopy.yml" + displacement_file_2 = tmp_path / "NaCl_2-phonopy.yml" + + result = runner.invoke( + app, + [ + "phonons", + "--struct", + DATA_PATH / "NaCl.cif", + "--no-hdf5", + "--displacement-kwargs", + "{'is_plusminus': True}", + "--file-prefix", + file_prefix_1, + ], + ) + assert result.exit_code == 0 + + result = runner.invoke( + app, + [ + "phonons", + "--struct", + DATA_PATH / "NaCl.cif", + "--no-hdf5", + "--displacement-kwargs", + "{'is_plusminus': False}", + "--file-prefix", + file_prefix_2, + ], + ) + assert result.exit_code == 0 + + # Check parameters + with open(displacement_file_1, encoding="utf8") as file: + params = yaml.safe_load(file) + n_displacements_1 = len(params["displacements"]) + + assert n_displacements_1 == 4 + + with open(displacement_file_2, encoding="utf8") as file: + params = yaml.safe_load(file) + n_displacements_2 = len(params["displacements"]) + + assert n_displacements_2 == 2 + + +def test_paths(tmp_path): + """Test displacement_kwargs can be set.""" + file_prefix = tmp_path / "NaCl" + qpoint_file = DATA_PATH / "paths.yml" + band_results = tmp_path / "NaCl-bands.yml.xz" + + result = runner.invoke( + app, + [ + "phonons", + "--struct", + DATA_PATH / "NaCl.cif", + "--no-hdf5", + "--bands", + "--qpoint-file", + qpoint_file, + "--file-prefix", + file_prefix, + ], + ) + assert result.exit_code == 0 + + assert band_results.exists() + with lzma.open(band_results, mode="rb") as file: + bands = yaml.safe_load(file) + assert bands["nqpoint"] == 11 + assert bands["npath"] == 1