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 supercell_energies attributes in API #225

Merged
merged 5 commits into from
Apr 30, 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
261 changes: 190 additions & 71 deletions phono3py/api_phono3py.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import copy
from collections.abc import Sequence
from typing import Optional, Union
from typing import Literal, Optional, Union

import numpy as np
from phonopy.exception import ForceCalculatorRequiredError
Expand Down Expand Up @@ -610,7 +611,20 @@ def dataset(self):

@dataset.setter
def dataset(self, dataset):
self._dataset = dataset
if dataset is None:
self._dataset = None
elif "first_atoms" in dataset:
self._dataset = copy.deepcopy(dataset)
elif "displacements" in dataset:
self._dataset = {}
self.displacements = dataset["displacements"]
if "forces" in dataset:
self.forces = dataset["forces"]
if "supercell_energies" in dataset:
self.supercell_energies = dataset["supercell_energies"]
else:
raise RuntimeError("Data format of dataset is wrong.")

self._supercells_with_displacements = None
self._phonon_supercells_with_displacements = None

Expand Down Expand Up @@ -643,7 +657,21 @@ def phonon_dataset(self):

@phonon_dataset.setter
def phonon_dataset(self, dataset):
self._phonon_dataset = dataset
if dataset is None:
self._phonon_dataset = None
elif "first_atoms" in dataset:
self._phonon_dataset = copy.deepcopy(dataset)
elif "displacements" in dataset:
self._phonon_dataset = {}
self.phonon_displacements = dataset["displacements"]
if "forces" in dataset:
self.phonon_forces = dataset["forces"]
if "supercell_energies" in dataset:
self.phonon_supercell_energies = dataset["supercell_energies"]
else:
raise RuntimeError("Data format of dataset is wrong.")

self._phonon_supercells_with_displacements = None

@property
def band_indices(self):
Expand Down Expand Up @@ -822,45 +850,32 @@ def forces(self):
be the same order of supercells_with_displacements.

"""
dataset = self._dataset
if "forces" in dataset:
return dataset["forces"]
elif "first_atoms" in dataset:
num_scells = len(dataset["first_atoms"])
for disp1 in dataset["first_atoms"]:
num_scells += len(disp1["second_atoms"])
forces = np.zeros(
(num_scells, self._supercell.get_number_of_atoms(), 3),
dtype="double",
order="C",
)
i = 0
for disp1 in dataset["first_atoms"]:
forces[i] = disp1["forces"]
i += 1
for disp1 in dataset["first_atoms"]:
for disp2 in disp1["second_atoms"]:
forces[i] = disp2["forces"]
i += 1
return forces
else:
raise RuntimeError("displacement dataset has wrong format.")
return self._get_forces_energies(target="forces")

@forces.setter
def forces(self, forces_fc3):
forces = np.array(forces_fc3, dtype="double", order="C")
dataset = self._dataset
if "first_atoms" in dataset:
i = 0
for disp1 in dataset["first_atoms"]:
disp1["forces"] = forces[i]
i += 1
for disp1 in dataset["first_atoms"]:
for disp2 in disp1["second_atoms"]:
disp2["forces"] = forces[i]
i += 1
elif "displacements" in dataset or "forces" in dataset:
dataset["forces"] = forces
def forces(self, values):
self._set_forces_energies(values, target="forces")

@property
def supercell_energies(self):
"""Setter and getter of supercell energies in displacement dataset.

A set of supercell energies of displaced supercells. The order of
displaced supercells has to match with that in displacement dataset.
shape=(displaced supercells,)

getter : ndarray

setter : array_like
The order of supercells used for calculating supercell energies has
to be the same order of supercells_with_displacements.

"""
return self._get_forces_energies(target="supercell_energies")

@supercell_energies.setter
def supercell_energies(self, values):
self._set_forces_energies(values, target="supercell_energies")

@property
def phonon_displacements(self):
Expand Down Expand Up @@ -928,7 +943,7 @@ def phonon_displacements(self, displacements):

@property
def phonon_forces(self):
"""Setter and getter of forces in displacement dataset for fc2.
"""Setter and getter of forces in fc2 displacement dataset.

A set of atomic forces in displaced supercells. The order of
displaced supercells has to match with that in phonon displacement
Expand All @@ -942,39 +957,30 @@ def phonon_forces(self):
be the same order of phonon_supercells_with_displacements.

"""
if self._phonon_dataset is None:
raise RuntimeError("phonon_displacement_dataset does not exist.")

dataset = self._phonon_dataset
if "forces" in dataset:
return dataset["forces"]
elif "first_atoms" in dataset:
num_scells = len(dataset["first_atoms"])
forces = np.zeros(
(num_scells, self._phonon_supercell.get_number_of_atoms(), 3),
dtype="double",
order="C",
)
for i, disp1 in enumerate(dataset["first_atoms"]):
forces[i] = disp1["forces"]
return forces
else:
raise RuntimeError("displacement dataset has wrong format.")
return self._get_phonon_forces_energies(target="forces")

@phonon_forces.setter
def phonon_forces(self, forces_fc2):
if self._phonon_dataset is None:
raise RuntimeError("phonon_displacement_dataset does not exist.")
def phonon_forces(self, values):
self._set_phonon_forces_energies(values, target="forces")

forces = np.array(forces_fc2, dtype="double", order="C")
dataset = self._phonon_dataset
if "first_atoms" in dataset:
i = 0
for i, disp1 in enumerate(dataset["first_atoms"]):
disp1["forces"] = forces[i]
i += 1
elif "displacements" in dataset or "forces" in dataset:
dataset["forces"] = forces
@property
def phonon_supercell_energies(self):
"""Setter and getter of supercell energies in fc2 displacement dataset.

shape=(displaced supercells,)

getter : ndarray

setter : array_like
The order of supercells used for calculating supercell energies has
to be the same order of phonon_supercells_with_displacements.

"""
return self._get_phonon_forces_energies(target="supercell_energies")

@phonon_supercell_energies.setter
def phonon_supercell_energies(self, values):
self._set_phonon_forces_energies(values, target="supercell_energies")

@property
def phph_interaction(self):
Expand Down Expand Up @@ -2280,3 +2286,116 @@ def _extract_fc2_fc3_calculators(self, fc_calculator, fc_calculator_options, ord
_fc_calculator_options = None

return _fc_calculator, _fc_calculator_options

def _get_forces_energies(
self, target: Literal["forces", "supercell_energies"]
) -> Optional[np.ndarray]:
"""Return fc3 forces and supercell energies.

Return None if tagert data is not found rather than raising exception.

"""
if target in self._dataset: # type-2
return self._dataset[target]
elif "first_atoms" in self._dataset: # type-1
num_scells = len(self._dataset["first_atoms"])
for disp1 in self._dataset["first_atoms"]:
num_scells += len(disp1["second_atoms"])
if target == "forces":
values = np.zeros(
(num_scells, len(self._supercell), 3),
dtype="double",
order="C",
)
type1_target = "forces"
elif target == "supercell_energies":
values = np.zeros(num_scells, dtype="double")
type1_target = "supercell_energy"
count = 0
for disp1 in self._dataset["first_atoms"]:
values[count] = disp1[type1_target]
count += 1
for disp1 in self._dataset["first_atoms"]:
for disp2 in disp1["second_atoms"]:
values[count] = disp2[type1_target]
count += 1
return values
return None

def _set_forces_energies(
self, values, target: Literal["forces", "supercell_energies"]
):
if "first_atoms" in self._dataset: # type-1
count = 0
for disp1 in self._dataset["first_atoms"]:
if target == "forces":
disp1[target] = np.array(values[count], dtype="double", order="C")
elif target == "supercell_energies":
disp1["supercell_energy"] = float(values[count])
count += 1
for disp1 in self._dataset["first_atoms"]:
for disp2 in disp1["second_atoms"]:
if target == "forces":
disp2[target] = np.array(
values[count], dtype="double", order="C"
)
elif target == "supercell_energies":
disp2["supercell_energy"] = float(values[count])
count += 1
elif "displacements" in self._dataset or "forces" in self._dataset: # type-2
self._dataset[target] = np.array(values, dtype="double", order="C")
else:
raise RuntimeError("Set of FC3 displacements is not available.")

def _get_phonon_forces_energies(
self, target: Literal["forces", "supercell_energies"]
) -> Optional[np.ndarray]:
"""Return fc2 forces and supercell energies.

Return None if tagert data is not found rather than raising exception.

"""
if self._phonon_dataset is None:
raise RuntimeError("Dataset for fc2does not exist.")

if target in self._phonon_dataset: # type-2
return self._phonon_dataset[target]
elif "first_atoms" in self._phonon_dataset: # type-1
values = []
for disp in self._phonon_dataset["first_atoms"]:
if target == "forces":
if target in disp:
values.append(disp[target])
elif target == "supercell_energies":
if "supercell_energy" in disp:
values.append(disp["supercell_energy"])
if values:
return np.array(values, dtype="double", order="C")
return None

def _set_phonon_forces_energies(
self, values, target: Literal["forces", "supercell_energies"]
):
if self._phonon_dataset is None:
raise RuntimeError("Dataset for fc2 does not exist.")

if "first_atoms" in self._phonon_dataset:
for disp, v in zip(self._phonon_dataset["first_atoms"], values):
if target == "forces":
disp[target] = np.array(v, dtype="double", order="C")
elif target == "supercell_energies":
disp["supercell_energy"] = float(v)
elif "displacements" in self._phonon_dataset:
_values = np.array(values, dtype="double", order="C")
natom = len(self._phonon_supercell)
ndisps = len(self._phonon_dataset["displacements"])
if target == "forces" and (
_values.ndim != 3 or _values.shape != (ndisps, natom, 3)
):
raise RuntimeError(f"Array shape of input {target} is incorrect.")
elif target == "supercell_energies":
if _values.ndim != 1 or _values.shape != (ndisps,):
raise RuntimeError(f"Array shape of input {target} is incorrect.")
self._phonon_dataset[target] = _values
else:
raise RuntimeError("Set of FC2 displacements is not available.")
15 changes: 8 additions & 7 deletions phono3py/cui/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,13 +390,14 @@ def set_dataset_and_force_constants(
cutoff_pair_distance=cutoff_pair_distance,
log_level=log_level,
)
read_fc["fc2"] = _set_dataset_phonon_dataset_or_fc2(
ph3py,
ph3py_yaml=ph3py_yaml,
fc2_filename=fc2_filename,
forces_fc2_filename=forces_fc2_filename,
log_level=log_level,
)
if ph3py.phonon_supercell_matrix is not None:
read_fc["fc2"] = _set_dataset_phonon_dataset_or_fc2(
ph3py,
ph3py_yaml=ph3py_yaml,
fc2_filename=fc2_filename,
forces_fc2_filename=forces_fc2_filename,
log_level=log_level,
)

# Cases that dataset is in phono3py.yaml but not forces.
if ph3py.dataset is None:
Expand Down
Binary file added test/Si-111-222-fd.tar.xz
Binary file not shown.
Binary file added test/Si-111-222-rd.tar.xz
Binary file not shown.
Loading
Loading