#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
#
# _____ _ _ _ _____ _____
# | ____|__| | ___| |_ _____(_)___ ___| ___| ____|
# | _| / _` |/ _ \ \ \ /\ / / _ \ / __/ __| |_ | _|
# | |__| (_| | __/ |\ V V / __/ \__ \__ \ _| | |___
# |_____\__,_|\___|_| \_/\_/ \___|_|___/___/_| |_____|
#
#
# Unit of Strength of Materials and Structural Analysis
# University of Innsbruck,
# 2017 - today
#
# Matthias Neuner matthias.neuner@uibk.ac.at
#
# This file is part of EdelweissFE.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# The full text of the license can be found in the file LICENSE.md at
# the top level directory of EdelweissFE.
# ---------------------------------------------------------------------
"""Restart checkpoints: the one place that knows how a checkpoint file is laid out.
A checkpoint is an HDF5 file holding
- the attribute ``stepNumber``, the step the run was in, and ``numberOfIncrementsDone``, how many
increments of it were accepted;
- the model's state, written by :meth:`~edelweissfe.models.femodel.FEModel.writeRestart`;
- groups ``timestepper`` and ``solver`` with the time stepper's and the solver's state;
- a group ``stepActions`` with the state of every step action, by type and name -- including what
the steps before carried over, e.g. an accumulated load, and whether an action is still active;
- a group ``outputManagers`` with the state of every output manager of the step, by name;
- a group ``fieldOutputs`` with the history of every field output, and how much of its export file
belongs to the run.
Every component other than the model hands over its state as a mapping of arrays, through
``getRestartData`` and ``setRestartData``; most just declare which of their attributes it is (see
:mod:`~edelweissfe.utils.checkpointedstate`).
:func:`writeCheckpoint` writes one, the restart output manager calls it; :class:`ResumeCheckpoint`
reads one back, the driver uses it.
"""
import os
import h5py
from edelweissfe.utils.exceptions import RestartError
#: The layout of a checkpoint. Raise it whenever a checkpoint gains or changes state: a run resumes
#: only from checkpoints of its own layout.
CHECKPOINT_FORMAT_VERSION = 13
[docs]
def writeRestartDataOf(group: h5py.Group, entities: dict):
"""Store the restart data of each entity in its own subgroup, named after it.
Parameters
----------
group
The group to create the subgroups in.
entities
The entities, by name.
"""
for name, entity in entities.items():
entityGroup = group.create_group(name)
for entryName, entryValues in entity.getRestartData().items():
entityGroup.create_dataset(entryName, data=entryValues)
[docs]
def readRestartDataInto(group: h5py.Group, entities: dict, newEntitiesStartAfresh: bool = False):
"""Hand each entity the restart data :func:`writeRestartDataOf` stored for it.
Parameters
----------
group
The group holding the subgroups.
entities
The entities, by name.
newEntitiesStartAfresh
If True, an entity the checkpoint holds nothing for keeps its initial state (an output
manager added for the resumed run). Otherwise it is refused: it does not belong to the
checkpointed run.
"""
for name, entity in entities.items():
if name not in group:
if newEntitiesStartAfresh:
continue
raise RestartError("the checkpoint holds no state for {:}".format(name))
entity.setRestartData({entryName: values[()] for entryName, values in group[name].items()})
def _outputManagersOf(step) -> dict:
"""The output managers of a step, by name: those whose state a checkpoint of the step holds.
Parameters
----------
step
The step.
Returns
-------
dict
The output managers, by name.
"""
return {outputManager.name: outputManager for outputManager in step.outputManagers}
[docs]
def writeCheckpoint(fileName: str, model, step, serial: int):
"""Write a restart checkpoint of the converged state.
Written to a temporary file first, and renamed to ``fileName`` only once complete: a job killed
while writing leaves the previous checkpoint of that name intact, never a truncated one.
Parameters
----------
fileName
The file to write; replaced if it exists.
model
The model tree.
step
The current step, whose time stepper, solver, step actions, output managers and field
outputs write their own state.
serial
The number of this checkpoint in the sequence its writer writes, stored in the file: it
orders checkpoints independently of file times.
"""
temporaryFileName = fileName + ".tmp"
with h5py.File(temporaryFileName, "w") as f:
f.attrs["formatVersion"] = CHECKPOINT_FORMAT_VERSION
f.attrs["serial"] = serial
f.attrs["stepNumber"] = step.number
f.attrs["numberOfIncrementsDone"] = step.timeStepper.numberOfIncrementsDone()
model.writeRestart(f)
writeRestartDataOf(f, {"timestepper": step.timeStepper, "solver": step.solver})
stepActionsGroup = f.create_group("stepActions")
for actionType, actions in step.actions.items():
writeRestartDataOf(stepActionsGroup.create_group(actionType), actions)
writeRestartDataOf(f.create_group("outputManagers"), _outputManagersOf(step))
writeRestartDataOf(f.create_group("fieldOutputs"), step.fieldOutputController.fieldOutputs)
with open(temporaryFileName, "rb+") as written:
os.fsync(written.fileno())
os.replace(temporaryFileName, fileName)
[docs]
class ResumeCheckpoint:
"""A restart checkpoint opened for resuming a run, restored piece by piece as the run is set up.
The pieces are restored at different moments, because the objects they belong to are created at
different moments: the model first (:meth:`restoreModel`), the time stepper before the resumed
step begins (:meth:`restoreTimeStepper`), and the solver, the step actions, the output managers
and the field outputs of the step once it has begun (:meth:`restoreStep`). Close the checkpoint
right after that, see :meth:`close`.
Parameters
----------
fileName
The checkpoint to resume from.
"""
def __init__(self, fileName: str):
self.fileName = fileName
self._file = h5py.File(fileName, "r")
formatVersion = int(self._file.attrs.get("formatVersion", 1))
if formatVersion != CHECKPOINT_FORMAT_VERSION:
self._file.close()
raise RestartError(
"checkpoint {:} has format version {:}, this version of EdelweissFE reads version {:} "
"only: resume it with the version of EdelweissFE that wrote it, or run the analysis "
"from its start".format(fileName, formatVersion, CHECKPOINT_FORMAT_VERSION)
)
#: The step the checkpointed run was in; the steps before it are not solved again.
self.stepNumber = int(self._file.attrs["stepNumber"])
#: How many increments of that step were accepted when the checkpoint was written (see
#: :meth:`~edelweissfe.timesteppers.base.timestepperbase.TimeStepperBase.numberOfIncrementsDone`).
self.numberOfIncrementsDone = int(self._file.attrs["numberOfIncrementsDone"])
#: The model time the checkpoint was written at.
self.time = float(self._file.attrs["time"])
[docs]
def restoreModel(self, model, journal):
"""Restore the model's state, see :meth:`~edelweissfe.models.femodel.FEModel.readRestart`.
Parameters
----------
model
The model tree, rebuilt from the same input file.
journal
The journal, for the progress messages of the topology replay.
"""
model.readRestart(self._file, journal)
[docs]
def restoreTimeStepper(self, step):
"""Restore the progress of ``step``'s time stepper, before the step begins: it is what tells
the step that it is not at its start (see
:meth:`~edelweissfe.timesteppers.base.timestepperbase.TimeStepperBase.isAtStepStart`).
"""
readRestartDataInto(self._file, {"timestepper": step.timeStepper})
[docs]
def restoreStep(self, step):
"""Restore everything else ``step`` continues from, after it began as if cold: the solver's
state between increments, the state of the step actions -- which the skipped steps before it
never brought to their step end -- and the state of the step's output managers (Ensight's
file numbering, for instance). An output manager added for the resumed run starts afresh.
Parameters
----------
step
The resumed step.
"""
readRestartDataInto(self._file, {"solver": step.solver})
readRestartDataInto(self._file["outputManagers"], _outputManagersOf(step), newEntitiesStartAfresh=True)
readRestartDataInto(
self._file["fieldOutputs"], step.fieldOutputController.fieldOutputs, newEntitiesStartAfresh=True
)
for actionType, actions in step.actions.items():
if actions: # the collection creates an empty entry for every type it is asked about
readRestartDataInto(self._file["stepActions"][actionType], actions)
[docs]
def close(self):
"""Close the file. Nothing reads the checkpoint after :meth:`restoreStep`, and it must not
stay open: the restart output manager's ring buffer overwrites the oldest of its files,
which can be this very one, and HDF5 cannot truncate a file it still holds open. The
resumed run would then fail mid-step with "unable to truncate a file which is already
open", which reads like a solver failure and is not one."""
if self._file is not None:
self._file.close()
self._file = None