Add tests

This commit is contained in:
Atsushi Togo 2025-05-16 08:54:22 +09:00
parent 426a193729
commit cc0879dde0
7 changed files with 296 additions and 82 deletions

View File

@ -539,15 +539,15 @@ def run_pypolymlp_to_compute_forces(
)
cutoff_pair_distance = determine_cutoff_pair_distance(
fc_calculator,
fc_calculator_options,
cutoff_pair_distance,
random_displacements,
symfc_memory_size,
ph3py.supercell,
ph3py.primitive,
ph3py.symmetry,
log_level,
fc_calculator=fc_calculator,
fc_calculator_options=fc_calculator_options,
cutoff_pair_distance=cutoff_pair_distance,
random_displacements=random_displacements,
symfc_memory_size=symfc_memory_size,
supercell=ph3py.supercell,
primitive=ph3py.primitive,
symmetry=ph3py.symmetry,
log_level=log_level,
)
ph3py.generate_displacements(
distance=_displacement_distance,

View File

@ -91,15 +91,15 @@ def create_phono3py_supercells(
print("Displacement distance: %s" % distance)
cutoff_pair_distance = determine_cutoff_pair_distance(
settings.fc_calculator,
settings.fc_calculator_options,
settings.cutoff_pair_distance,
settings.random_displacements,
settings.symfc_memory_size,
ph3.supercell,
ph3.primitive,
ph3.symmetry,
log_level,
fc_calculator=settings.fc_calculator,
fc_calculator_options=settings.fc_calculator_options,
cutoff_pair_distance=settings.cutoff_pair_distance,
random_displacements=settings.random_displacements,
symfc_memory_size=settings.symfc_memory_size,
supercell=ph3.supercell,
primitive=ph3.primitive,
symmetry=ph3.symmetry,
log_level=log_level,
)
ph3.generate_displacements(
distance=distance,

View File

@ -95,7 +95,7 @@ from phono3py.file_IO import (
write_fc3_to_hdf5,
write_phonon_to_hdf5,
)
from phono3py.interface.fc_calculator import get_cutoff_pair_distance
from phono3py.interface.fc_calculator import determine_cutoff_pair_distance
from phono3py.interface.phono3py_yaml import Phono3pyYaml
from phono3py.phonon.grid import get_grid_point_from_address, get_ir_grid_points
from phono3py.phonon3.dataset import forces_in_dataset
@ -598,10 +598,10 @@ def _store_force_constants(ph3py: Phono3py, settings: Phono3pySettings, log_leve
load_fc2_and_fc3(ph3py, log_level=log_level)
cutoff_pair_distance = get_cutoff_pair_distance(
settings.fc_calculator,
settings.fc_calculator_options,
settings.cutoff_pair_distance,
cutoff_pair_distance = determine_cutoff_pair_distance(
fc_calculator=settings.fc_calculator,
fc_calculator_options=settings.fc_calculator_options,
cutoff_pair_distance=settings.cutoff_pair_distance,
)
(fc_calculator, fc_calculator_options) = get_fc_calculator_params(
settings.fc_calculator,

View File

@ -176,30 +176,6 @@ def update_cutoff_fc_calculator_options(
return fc_calc_opts
def get_cutoff_pair_distance(
fc_calculator: Optional[str],
fc_calculator_options: Optional[str],
cutoff_pair_distance: Optional[float],
) -> Optional[float]:
"""Return cutoff_pair_distance from settings."""
_, _fc_calculator_options = get_fc_calculator_params(
fc_calculator,
fc_calculator_options,
cutoff_pair_distance,
)
if cutoff_pair_distance is None:
cutoff = parse_symfc_options(
extract_fc2_fc3_calculators(_fc_calculator_options, 3), 3
).get("cutoff")
if cutoff is None:
_cutoff_pair_distance = None
else:
_cutoff_pair_distance = cutoff.get(3)
else:
_cutoff_pair_distance = cutoff_pair_distance
return _cutoff_pair_distance
def get_fc_calculator_params(
fc_calculator: Optional[str],
fc_calculator_options: Optional[str],
@ -235,24 +211,30 @@ def get_fc_calculator_params(
def determine_cutoff_pair_distance(
fc_calculator: Optional[str],
fc_calculator_options: Optional[str],
cutoff_pair_distance: Optional[float],
random_displacements: Optional[str],
symfc_memory_size: Optional[float],
supercell: PhonopyAtoms,
primitive: Primitive,
symmetry: Symmetry,
log_level: int,
fc_calculator: Optional[str] = None,
fc_calculator_options: Optional[str] = None,
cutoff_pair_distance: Optional[float] = None,
random_displacements: Optional[str] = None,
symfc_memory_size: Optional[float] = None,
supercell: Optional[PhonopyAtoms] = None,
primitive: Optional[Primitive] = None,
symmetry: Optional[Symmetry] = None,
log_level: int = 0,
) -> float:
"""Determine cutoff pair distance for displacements."""
_cutoff_pair_distance = get_cutoff_pair_distance(
_cutoff_pair_distance, _symfc_memory_size = _get_cutoff_pair_distance(
fc_calculator,
fc_calculator_options,
cutoff_pair_distance,
symfc_memory_size,
)
if random_displacements == "auto" and symfc_memory_size is not None:
symfc_options = {"memsize": {3: symfc_memory_size}}
if random_displacements == "auto" and _symfc_memory_size is not None:
if fc_calculator != "symfc":
raise RuntimeError(
"Estimation of cutoff_pair_distance by memory size is only "
"available for symfc calculator."
)
symfc_options = {"memsize": {3: _symfc_memory_size}}
update_symfc_cutoff_by_memsize(
symfc_options, supercell, primitive, symmetry, verbose=log_level > 0
)
@ -286,3 +268,32 @@ def _set_cutoff_in_fc_calculator_options(
print(f'Appended "{str_appended}" to fc_calculator_options for fc3.')
return f"{calc_opts_fc2}|{calc_opts_fc3}"
def _get_cutoff_pair_distance(
fc_calculator: Optional[str],
fc_calculator_options: Optional[str],
cutoff_pair_distance: Optional[float],
symfc_memory_size: Optional[float] = None,
) -> Optional[float]:
"""Return cutoff_pair_distance from settings."""
_, _fc_calculator_options = get_fc_calculator_params(
fc_calculator,
fc_calculator_options,
cutoff_pair_distance,
)
symfc_options = parse_symfc_options(
extract_fc2_fc3_calculators(_fc_calculator_options, 3), 3
)
_cutoff_pair_distance = cutoff_pair_distance
cutoff = symfc_options.get("cutoff")
if cutoff is not None:
_cutoff_pair_distance = cutoff.get(3)
_symfc_memory_size = symfc_memory_size
memsize = symfc_options.get("memsize")
if memsize is not None:
_symfc_memory_size = memsize.get(3)
return _cutoff_pair_distance, _symfc_memory_size

View File

@ -12,6 +12,7 @@ import h5py
import numpy as np
import pytest
import phono3py
from phono3py.cui.phono3py_script import main
cwd = pathlib.Path(__file__).parent
@ -22,24 +23,27 @@ cwd_called = pathlib.Path.cwd()
class MockArgs:
"""Mock args of ArgumentParser."""
filename: Optional[Sequence[os.PathLike]] = None
cell_filename: Optional[str] = None
conf_filename: Optional[os.PathLike] = None
fc_calculator: Optional[str] = None
fc_calculator_options: Optional[str] = None
fc_symmetry: bool = True
filename: Optional[Sequence[os.PathLike]] = None
force_sets_mode: bool = False
force_sets_to_forces_fc2_mode: bool = False
input_filename = None
input_output_filename = None
log_level: Optional[int] = None
output_yaml_filename: Optional[os.PathLike] = None
show_num_triplets: bool = False
write_grid_points: bool = False
fc_symmetry: bool = True
cell_filename: Optional[str] = None
is_bterta: Optional[bool] = None
mesh_numbers: Optional[Sequence] = None
mlp_params: Optional[str] = None
output_filename = None
output_yaml_filename: Optional[os.PathLike] = None
random_displacements: Optional[Union[int, str]] = None
show_num_triplets: bool = False
temperatures: Optional[Sequence] = None
use_pypolymlp: bool = False
input_filename = None
output_filename = None
input_output_filename = None
write_grid_points: bool = False
def __iter__(self):
"""Make self iterable to support in."""
@ -129,25 +133,36 @@ def test_phono3py_load_with_pypolymlp_si():
pytest.importorskip("pypolymlp", minversion="0.9.2")
pytest.importorskip("symfc")
# Create fc2.hdf5
argparse_control = _get_phono3py_load_args(
cwd / ".." / "phono3py_params_Si-111-222-rd.yaml.xz",
fc_calculator="symfc",
use_pypolymlp=True,
)
with pytest.raises(SystemExit) as excinfo:
main(**argparse_control)
assert excinfo.value.code == 0
# phono3py.yaml and fc2.hd5 are used in the next run. So they are not deleted.
for created_filename in ("fc3.hdf5", "phono3py_mlp_eval_dataset.yaml"):
for created_filename in ("phono3py.yaml", "fc2.hdf5", "fc3.hdf5"):
file_path = pathlib.Path(cwd_called / created_filename)
if file_path.exists():
file_path.unlink()
assert file_path.exists()
pathlib.Path(cwd_called / "fc3.hdf5").unlink()
# Create MLP (polymlp.yaml)
argparse_control = _get_phono3py_load_args(
cwd / ".." / "phono3py_params_Si-111-222-rd.yaml.xz",
use_pypolymlp=True,
)
with pytest.raises(SystemExit) as excinfo:
main(**argparse_control)
assert excinfo.value.code == 0
for created_filename in ("phono3py.yaml", "polymlp.yaml"):
file_path = pathlib.Path(cwd_called / created_filename)
assert file_path.exists()
# Create phono3py_mlp_eval_dataset.yaml
argparse_control = _get_phono3py_load_args(
cwd_called / "phono3py.yaml",
fc_calculator="symfc",
random_displacements="auto",
use_pypolymlp=True,
)
@ -155,6 +170,9 @@ def test_phono3py_load_with_pypolymlp_si():
main(**argparse_control)
assert excinfo.value.code == 0
ph3 = phono3py.load(cwd_called / "phono3py_mlp_eval_dataset.yaml")
assert len(ph3.displacements) == 4
for created_filename in (
"phono3py.yaml",
"fc2.hdf5",
@ -163,17 +181,123 @@ def test_phono3py_load_with_pypolymlp_si():
"phono3py_mlp_eval_dataset.yaml",
):
file_path = pathlib.Path(cwd_called / created_filename)
if file_path.exists():
file_path.unlink()
assert file_path.exists()
file_path.unlink()
def test_phono3py_load_with_pypolymlp_nacl():
"""Test phono3py-load script with pypolymlp using NaCl.
First run generates polymlp.yaml.
Second run uses polymlp.yaml.
"""
pytest.importorskip("pypolymlp", minversion="0.9.2")
pytest.importorskip("symfc")
# Stage1 (preparation)
argparse_control = _get_phono3py_load_args(
cwd / ".." / "phono3py_params_MgO-222rd-444rd.yaml.xz",
mlp_params="cutoff=4.0,gtinv_maxl=4 4,max_p=1,gtinv_order=2",
fc_calculator="symfc",
random_displacements="auto",
use_pypolymlp=True,
)
with pytest.raises(SystemExit) as excinfo:
main(**argparse_control)
assert excinfo.value.code == 0
ph3 = phono3py.load(cwd_called / "phono3py_mlp_eval_dataset.yaml")
assert len(ph3.displacements) == 16
for created_filename in (
"phono3py.yaml",
"fc2.hdf5",
"fc3.hdf5",
"polymlp.yaml",
"phono3py_mlp_eval_dataset.yaml",
):
file_path = pathlib.Path(cwd_called / created_filename)
assert file_path.exists()
for created_filename in (
"fc3.hdf5",
"phono3py_mlp_eval_dataset.yaml",
):
file_path = pathlib.Path(cwd_called / created_filename)
assert file_path.exists()
file_path.unlink()
# Stage2 (cutoff test)
argparse_control = _get_phono3py_load_args(
cwd / ".." / "phono3py.yaml",
fc_calculator="symfc",
fc_calculator_options="|cutoff=4.0",
random_displacements="auto",
use_pypolymlp=True,
)
with pytest.raises(SystemExit) as excinfo:
main(**argparse_control)
assert excinfo.value.code == 0
ph3 = phono3py.load(cwd_called / "phono3py_mlp_eval_dataset.yaml")
assert len(ph3.displacements) == 4
for created_filename in (
"phono3py.yaml",
"fc2.hdf5",
"fc3.hdf5",
"polymlp.yaml",
"phono3py_mlp_eval_dataset.yaml",
):
file_path = pathlib.Path(cwd_called / created_filename)
assert file_path.exists()
for created_filename in (
"fc3.hdf5",
"phono3py_mlp_eval_dataset.yaml",
):
file_path = pathlib.Path(cwd_called / created_filename)
assert file_path.exists()
file_path.unlink()
# Stage3 (memsize test)
argparse_control = _get_phono3py_load_args(
cwd / ".." / "phono3py.yaml",
fc_calculator="symfc",
fc_calculator_options="|memsize=0.05",
random_displacements="auto",
use_pypolymlp=True,
)
with pytest.raises(SystemExit) as excinfo:
main(**argparse_control)
assert excinfo.value.code == 0
ph3 = phono3py.load(cwd_called / "phono3py_mlp_eval_dataset.yaml")
assert len(ph3.displacements) == 8
for created_filename in (
"phono3py.yaml",
"fc2.hdf5",
"fc3.hdf5",
"polymlp.yaml",
"phono3py_mlp_eval_dataset.yaml",
):
file_path = pathlib.Path(cwd_called / created_filename)
assert file_path.exists()
file_path.unlink()
def _get_phono3py_load_args(
phono3py_yaml_filepath: Union[str, pathlib.Path],
fc_calculator: Optional[str] = None,
fc_calculator_options: Optional[str] = None,
load_phono3py_yaml: bool = True,
is_bterta: bool = False,
temperatures: Optional[Sequence] = None,
mesh_numbers: Optional[Sequence] = None,
mlp_params: Optional[str] = None,
random_displacements: Optional[Union[int, str]] = None,
temperatures: Optional[Sequence] = None,
use_pypolymlp: bool = False,
):
# Mock of ArgumentParser.args.
@ -181,21 +305,27 @@ def _get_phono3py_load_args(
mockargs = MockArgs(
filename=[phono3py_yaml_filepath],
fc_calculator=fc_calculator,
fc_calculator_options=fc_calculator_options,
is_bterta=is_bterta,
temperatures=temperatures,
mesh_numbers=mesh_numbers,
use_pypolymlp=use_pypolymlp,
log_level=1,
mesh_numbers=mesh_numbers,
mlp_params=mlp_params,
random_displacements=random_displacements,
temperatures=temperatures,
use_pypolymlp=use_pypolymlp,
)
else:
mockargs = MockArgs(
filename=[],
fc_calculator=fc_calculator,
fc_calculator_options=fc_calculator_options,
log_level=1,
cell_filename=phono3py_yaml_filepath,
is_bterta=is_bterta,
temperatures=temperatures,
mesh_numbers=mesh_numbers,
mlp_params=mlp_params,
random_displacements=random_displacements,
temperatures=temperatures,
use_pypolymlp=use_pypolymlp,
)

View File

@ -0,0 +1,73 @@
"""Tests of functions in fc_calculator."""
import pytest
from phonopy.structure.atoms import PhonopyAtoms
from phono3py.api_phono3py import Phono3py
from phono3py.interface.fc_calculator import determine_cutoff_pair_distance
def test_determine_cutoff_pair_distance() -> None:
"""Test determine_cutoff_pair_distance."""
cutoff = determine_cutoff_pair_distance(fc_calculator_options="|cutoff=4")
assert cutoff == pytest.approx(4.0)
cutoff = determine_cutoff_pair_distance(cutoff_pair_distance=5.0)
assert cutoff == pytest.approx(5.0)
cutoff = determine_cutoff_pair_distance(
fc_calculator_options="|cutoff=4", cutoff_pair_distance=5.0
)
assert cutoff == pytest.approx(4.0)
def test_determine_cutoff_pair_distance_with_memsize(aln_cell: PhonopyAtoms) -> None:
"""Test determine_cutoff_pair_distance estimated by memsize."""
pytest.importorskip("symfc")
ph3 = Phono3py(aln_cell, supercell_matrix=[3, 3, 2])
cutoff = determine_cutoff_pair_distance(
fc_calculator="symfc",
fc_calculator_options="|memsize=0.1",
random_displacements="auto",
supercell=ph3.supercell,
primitive=ph3.primitive,
symmetry=ph3.symmetry,
log_level=1,
)
assert cutoff == pytest.approx(3.2)
cutoff = determine_cutoff_pair_distance(
fc_calculator="symfc",
random_displacements="auto",
symfc_memory_size=0.2,
supercell=ph3.supercell,
primitive=ph3.primitive,
symmetry=ph3.symmetry,
log_level=1,
)
assert cutoff == pytest.approx(3.7)
cutoff = determine_cutoff_pair_distance(
fc_calculator="symfc",
fc_calculator_options="|memsize=0.1",
random_displacements="auto",
symfc_memory_size=0.2,
supercell=ph3.supercell,
primitive=ph3.primitive,
symmetry=ph3.symmetry,
log_level=1,
)
assert cutoff == pytest.approx(3.2)
with pytest.raises(RuntimeError):
cutoff = determine_cutoff_pair_distance(
fc_calculator="alm",
fc_calculator_options="|memsize=0.1",
random_displacements="auto",
symfc_memory_size=0.2,
supercell=ph3.supercell,
primitive=ph3.primitive,
symmetry=ph3.symmetry,
log_level=1,
)