Skip to content
Draft
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
18 changes: 12 additions & 6 deletions pyiron_potentialfit/atomistics/job/trainingcontainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def train_test_split(self, train_size, seed=None):
brk = int(len(self) * train_size)
if brk in (0, 1):
raise ValueError(
f"container not large enough to realize this split, only multiples of {1/len(self)} possible!"
f"container not large enough to realize this split, only multiples of {1/len(self)} possible!"
)

# somewhat inefficient, but probably good enough for normal training set sizes
Expand All @@ -362,8 +362,8 @@ def train_test_split(self, train_size, seed=None):
train_idx = idx[:brk]
test_idx = idx[brk:]
return (
self.sample(lambda f, i: i in train_idx),
self.sample(lambda f, i: i in test_idx),
self.sample(lambda f, i: i in train_idx),
self.sample(lambda f, i: i in test_idx),
)


Expand Down Expand Up @@ -633,9 +633,15 @@ def iter(self, *arrays, wrap_atoms=True):
"""
yield from self._container.iter(*arrays, wrap_atoms=wrap_atoms)

def train_test_split(self, train_size: float, seed=None,
run: bool = True,
train_name: str = None, test_name: str = None, project=None):
def train_test_split(
self,
train_size: float,
seed=None,
run: bool = True,
train_name: str = None,
test_name: str = None,
project=None,
):
"""
Split into two random sub sets for training and testing.

Expand Down
57 changes: 57 additions & 0 deletions pyiron_potentialfit/ml/potentialfit.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,20 @@ def predicted_data(self) -> FlattenedStorage:
def plot(self):
"""
Plots correlation and (training) error histograms.

See :class:`.PotentialPlots`.
"""
return PotentialPlots(self.training_data, self.predicted_data)

@property
def metrics(self):
"""
Calculate error (training) metrics.

See :class:`.PotentialMetrics`.
"""
return PotentialMetrics(self.training_data, self.predicted_data)

@abc.abstractmethod
def get_lammps_potential(self) -> pd.DataFrame:
"""
Expand Down Expand Up @@ -291,3 +302,49 @@ def force_angle_histogram(
"Angular Deviation of Force [" + ["rad", "deg"][angle_in_degrees] + "]"
)
plt.ylabel("Count")


class PotentialMetrics:
"""
Calculates various error metrics on training and test data.
"""

__slots__ = ("_true_data", "_predicted_data")

def __init__(self, true_data: TrainingStorage, predicted_data: FlattenedStorage):
self._true_data = true_data
self._predicted_data = predicted_data

def _rmse(self, a, b):
return np.sqrt(np.mean((a - b) ** 2))

def _mae(self, a, b):
return np.mean(np.abs(a - b))

@property
def energy_rmse(self):
N = self._true_data["length"]
return self._rmse(
self._true_data["energy"] / N, self._predicted_data["energy"] / N
)

@property
def energy_mae(self):
N = self._true_data["length"]
return self._mae(
self._true_data["energy"] / N, self._predicted_data["energy"] / N
)

@property
def force_rmse(self):
return self._rmse(
np.linalg.norm(self._true_data["forces"], axis=-1),
np.linalg.norm(self._predicted_data["forces"], axis=-1),
)

@property
def force_mae(self):
return self._mae(
np.linalg.norm(self._true_data["forces"], axis=-1),
np.linalg.norm(self._predicted_data["forces"], axis=-1),
)
2 changes: 1 addition & 1 deletion pyiron_potentialfit/spgfit/learn.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def energy_mae(j):
N = inpt.get_array("length")
train = np.squeeze(inpt.get_array("energy")) / N
pred = np.squeeze(j["output/training_efs"].to_object().get_array("energy")) / N
return np.abs(train-pred).mean()
return np.abs(train - pred).mean()


def energy_max(j):
Expand Down
11 changes: 8 additions & 3 deletions pyiron_potentialfit/spgfit/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,9 +857,10 @@ def main():
help="Retry the current step from scratch",
)
parser.add_argument(
"--export", type=str,
"--export",
type=str,
help="Optionally specify a directory where to dump POSCAR files with the generated structures after everything "
"is finished."
"is finished.",
)
args = parser.parse_args()

Expand Down Expand Up @@ -892,7 +893,11 @@ def main():
dir_path = os.path.join(args.export, cont.name)
os.makedirs(dir_path, exist_ok=True)
for i, s in enumerate(cont.iter_structures()):
s.write(os.path.join(dir_path, cont._container["identifier", i]) + ".POSCAR", format="vasp")
s.write(
os.path.join(dir_path, cont._container["identifier", i])
+ ".POSCAR",
format="vasp",
)


if __name__ == "__main__":
Expand Down