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

Add mesh CLI option and fix thermal mesh #307

Merged
merged 5 commits into from
Oct 3, 2024
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
6 changes: 3 additions & 3 deletions docs/source/user_guide/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ Calculate phonons with a 2x2x2 supercell, after geometry optimization (using the

.. code-block:: bash

janus phonons --struct tests/data/NaCl.cif --supercell 2x2x2 --minimize --arch mace_mp --model-path small
janus phonons --struct tests/data/NaCl.cif --supercell 2 2 2 --minimize --arch mace_mp --model-path small


This will save the Phonopy parameters, including displacements and force constants, to ``NaCl-phonopy.yml`` and ``NaCl-force_constants.hdf5``,
Expand All @@ -324,7 +324,7 @@ Additionally, the ``--bands`` option can be added to calculate the band structur

.. code-block:: bash

janus phonons --struct tests/data/NaCl.cif --supercell 2x2x2 --minimize --arch mace_mp --model-path small --bands
janus phonons --struct tests/data/NaCl.cif --supercell 2 2 2 --minimize --arch mace_mp --model-path small --bands


If you need eigenvectors and group velocities written, add the ``--write-full`` option. This will generate a much larger file, but can be used to visualise phonon modes.
Expand All @@ -333,7 +333,7 @@ Further calculations, including thermal properties, DOS, and PDOS, can also be c

.. code-block:: bash

janus phonons --struct tests/data/NaCl.cif --supercell 2x3x4 --dos --pdos --thermal --temp-start 0 --temp-end 300 --temp-step 50
janus phonons --struct tests/data/NaCl.cif --supercell 2 3 4 --dos --pdos --thermal --temp-start 0 --temp-end 300 --temp-step 50


This will create additional output files: ``NaCl-thermal.dat`` for the thermal properties (heat capacity, entropy, and free energy)
Expand Down
38 changes: 29 additions & 9 deletions janus_core/calculations/phonons.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class Phonons(BaseCalculation):
Size of supercell for calculation. Default is 2.
displacement : float
Displacement for force constants calculation, in A. Default is 0.01.
mesh : tuple[int, int, int]
Mesh for sampling. Default is (10, 10, 10).
symmetrize : bool
Whether to symmetrize force constants after calculation.
Default is False.
Expand Down Expand Up @@ -106,7 +108,7 @@ class Phonons(BaseCalculation):
Calculate band structure and optionally write and plot results.
write_bands(bands_file, save_plots, plot_file)
Write results of band structure calculations.
calc_thermal_props(write_thermal)
calc_thermal_props(mesh, write_thermal)
Calculate thermal properties and optionally write results.
write_thermal_props(thermal_file)
Write results of thermal properties calculations.
Expand Down Expand Up @@ -138,6 +140,7 @@ def __init__(
calcs: MaybeSequence[PhononCalcs] = (),
supercell: MaybeList[int] = 2,
displacement: float = 0.01,
mesh: tuple[int, int, int] = (10, 10, 10),
symmetrize: bool = False,
minimize: bool = False,
minimize_kwargs: Optional[dict[str, Any]] = None,
Expand Down Expand Up @@ -186,6 +189,8 @@ def __init__(
Size of supercell for calculation. Default is 2.
displacement : float
Displacement for force constants calculation, in A. Default is 0.01.
mesh : tuple[int, int, int]
Mesh for sampling. Default is (10, 10, 10).
symmetrize : bool
Whether to symmetrize force constants after calculations.
Default is False.
Expand Down Expand Up @@ -219,6 +224,7 @@ def __init__(

self.calcs = calcs
self.displacement = displacement
self.mesh = mesh
self.symmetrize = symmetrize
self.minimize = minimize
self.minimize_kwargs = minimize_kwargs
Expand Down Expand Up @@ -490,13 +496,18 @@ def write_bands(
bplt.savefig(plot_file)

def calc_thermal_props(
self, write_thermal: Optional[bool] = None, **kwargs
self,
mesh: Optional[tuple[int, int, int]] = None,
write_thermal: Optional[bool] = None,
**kwargs,
) -> None:
"""
Calculate thermal properties and optionally write results.

Parameters
----------
mesh : Optional[tuple[int, int, int]]
Mesh for sampling. Default is self.mesh.
write_thermal : Optional[bool]
Whether to write out thermal properties to file. Default is
self.write_results.
Expand All @@ -506,6 +517,9 @@ def calc_thermal_props(
if write_thermal is None:
write_thermal = self.write_results

if mesh is None:
mesh = self.mesh

# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
Expand All @@ -515,7 +529,7 @@ def calc_thermal_props(
self.logger.info("Starting thermal properties calculation")
self.tracker.start_task("Thermal calculation")

self.results["phonon"].run_mesh()
self.results["phonon"].run_mesh(mesh)
self.results["phonon"].run_thermal_properties(
t_step=self.temp_step, t_max=self.temp_max, t_min=self.temp_min
)
Expand Down Expand Up @@ -563,7 +577,7 @@ def write_thermal_props(self, thermal_file: Optional[PathLike] = None) -> None:
def calc_dos(
self,
*,
mesh: MaybeList[float] = (10, 10, 10),
mesh: Optional[tuple[int, int, int]] = None,
write_dos: Optional[bool] = None,
**kwargs,
) -> None:
Expand All @@ -572,8 +586,8 @@ def calc_dos(

Parameters
----------
mesh : MaybeList[float]
Mesh for sampling. Default is (10, 10, 10).
mesh : Optional[tuple[int, int, int]]
Mesh for sampling. Default is self.mesh.
write_dos : Optional[bool]
Whether to write out results to file. Default is True.
**kwargs
Expand All @@ -582,6 +596,9 @@ def calc_dos(
if write_dos is None:
write_dos = self.write_results

if mesh is None:
mesh = self.mesh

# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
Expand Down Expand Up @@ -665,7 +682,7 @@ def write_dos(
def calc_pdos(
self,
*,
mesh: MaybeList[float] = (10, 10, 10),
mesh: Optional[tuple[int, int, int]] = None,
write_pdos: Optional[bool] = None,
**kwargs,
) -> None:
Expand All @@ -674,8 +691,8 @@ def calc_pdos(

Parameters
----------
mesh : MaybeList[float]
Mesh for sampling. Default is (10, 10, 10).
mesh : Optional[tuple[int, int, int]]
Mesh for sampling. Default is self.mesh.
write_pdos : Optional[bool]
Whether to write out results to file. Default is self.write_results.
**kwargs
Expand All @@ -684,6 +701,9 @@ def calc_pdos(
if write_pdos is None:
write_pdos = self.write_results

if mesh is None:
mesh = self.mesh

# Calculate phonons if not already in results
if "phonon" not in self.results:
# Use general (self.write_results) setting for writing force constants
Expand Down
57 changes: 20 additions & 37 deletions janus_core/cli/phonons.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from janus_core.cli.utils import (
carbon_summary,
check_config,
dict_tuples_to_lists,
end_summary,
parse_typer_dicts,
save_struct_calc,
Expand All @@ -39,30 +40,22 @@ def phonons(
ctx: Context,
struct: StructPath,
supercell: Annotated[
str,
Option(help="Supercell lattice vectors in the form '1x2x3'."),
] = "2x2x2",
tuple[int, int, int], Option(help="Supercell lattice vectors.")
] = (2, 2, 2),
displacement: Annotated[
float,
Option(help="Displacement for force constants calculation, in A."),
float, Option(help="Displacement for force constants calculation, in A.")
] = 0.01,
mesh: Annotated[
tuple[int, int, int], Option(help="Mesh numbers along a, b, c axes.")
] = (10, 10, 10),
bands: Annotated[
bool,
Option(help="Whether to compute band structure."),
] = False,
dos: Annotated[
bool,
Option(help="Whether to calculate the DOS."),
] = False,
pdos: Annotated[
bool,
Option(
help="Whether to calculate the PDOS.",
),
] = False,
dos: Annotated[bool, Option(help="Whether to calculate the DOS.")] = False,
pdos: Annotated[bool, Option(help="Whether to calculate the PDOS.")] = False,
thermal: Annotated[
bool,
Option(help="Whether to calculate thermal properties."),
bool, Option(help="Whether to calculate thermal properties.")
] = False,
temp_min: Annotated[
float,
Expand All @@ -80,18 +73,14 @@ def phonons(
bool, Option(help="Whether to symmetrize force constants.")
] = False,
minimize: Annotated[
bool,
Option(
help="Whether to minimize structure before calculations.",
),
bool, Option(help="Whether to minimize structure before calculations.")
] = False,
fmax: Annotated[
float, Option(help="Maximum force for optimization convergence.")
] = 0.1,
minimize_kwargs: MinimizeKwargs = None,
hdf5: Annotated[
bool,
Option(help="Whether to save force constants in hdf5."),
bool, Option(help="Whether to save force constants in hdf5.")
] = True,
plot_to_file: Annotated[
bool,
Expand Down Expand Up @@ -133,11 +122,12 @@ def phonons(
Typer (Click) Context. Automatically set.
struct : Path
Path of structure to simulate.
supercell : str
Supercell lattice vectors. Must be passed in the form '1x2x3'. Default is
2x2x2.
supercell : tuple[int, int, int]
Supercell lattice vectors. Default is (2, 2, 2).
displacement : float
Displacement for force constants calculation, in A. Default is 0.01.
mesh : tuple[int, int, int]
Mesh for sampling. Default is (10, 10, 10).
alinelena marked this conversation as resolved.
Show resolved Hide resolved
bands : bool
Whether to calculate and save the band structure. Default is False.
dos : bool
Expand Down Expand Up @@ -209,17 +199,6 @@ def phonons(
raise ValueError("'fmax' must be passed through the --fmax option")
minimize_kwargs["fmax"] = fmax

try:
supercell = [int(x) for x in supercell.split("x")]
except ValueError as exc:
raise ValueError(
"Please pass lattice vectors as integers in the form 1x2x3"
) from exc

# Validate supercell list
if len(supercell) != 3:
raise ValueError("Please pass three lattice vectors in the form 1x2x3")

calcs = []
if bands:
calcs.append("bands")
Expand Down Expand Up @@ -247,6 +226,7 @@ def phonons(
"calcs": calcs,
"supercell": supercell,
"displacement": displacement,
"mesh": mesh,
"symmetrize": symmetrize,
"minimize": minimize,
"minimize_kwargs": minimize_kwargs,
Expand Down Expand Up @@ -283,6 +263,9 @@ def phonons(
log=log,
)

# Convert all tuples to list in inputs nested dictionary
dict_tuples_to_lists(inputs)

# Save summary information before calculations begin
start_summary(command="phonons", summary=summary, inputs=inputs)

Expand Down
16 changes: 16 additions & 0 deletions janus_core/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ def dict_paths_to_strs(dictionary: dict) -> None:
dictionary[key] = str(value)


def dict_tuples_to_lists(dictionary: dict) -> None:
"""
Recursively iterate over dictionary, converting tuple values to lists.

Parameters
----------
dictionary : dict
Dictionary to be converted.
"""
for key, value in dictionary.items():
if isinstance(value, dict):
dict_paths_to_strs(value)
elif isinstance(value, tuple):
dictionary[key] = list(value)


def dict_remove_hyphens(dictionary: dict) -> dict:
"""
Recursively iterate over dictionary, replacing hyphens with underscores in keys.
Expand Down
22 changes: 12 additions & 10 deletions tests/test_phonons_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,9 @@ def test_plot(tmp_path):
"--struct",
DATA_PATH / "NaCl.cif",
"--supercell",
"1x1x1",
1,
1,
1,
"--pdos",
"--dos",
"--bands",
Expand Down Expand Up @@ -268,7 +270,9 @@ def test_supercell(tmp_path):
"--struct",
DATA_PATH / "NaCl.cif",
"--supercell",
"1x2x3",
1,
2,
3,
"--no-hdf5",
"--file-prefix",
file_prefix,
Expand All @@ -285,10 +289,7 @@ def test_supercell(tmp_path):
assert params["supercell_matrix"] == [[1, 0, 0], [0, 2, 0], [0, 0, 3]]


test_data = ["2", "2.1x2.1x2.1", "2x2xa"]


@pytest.mark.parametrize("supercell", test_data)
@pytest.mark.parametrize("supercell", [(2,), (2, 2), (2, 2, "a"), ("2x2x2",)])
def test_invalid_supercell(supercell, tmp_path):
"""Test errors are raise for invalid supercells."""
file_prefix = tmp_path / "test"
Expand All @@ -300,13 +301,12 @@ def test_invalid_supercell(supercell, tmp_path):
"--struct",
DATA_PATH / "NaCl.cif",
"--supercell",
supercell,
*supercell,
"--file-prefix",
file_prefix,
],
)
assert result.exit_code == 1
assert isinstance(result.exception, ValueError)
assert result.exit_code == 1 or result.exit_code == 2


def test_minimize_kwargs(tmp_path):
Expand Down Expand Up @@ -379,7 +379,9 @@ def test_valid_traj_input(read_kwargs, tmp_path):
"--struct",
DATA_PATH / "NaCl-traj.xyz",
"--supercell",
"1x1x1",
1,
1,
1,
"--read-kwargs",
read_kwargs,
"--no-hdf5",
Expand Down