PolyFEM
Loading...
Searching...
No Matches
Checkpoint.cpp
Go to the documentation of this file.
1#include "Checkpoint.hpp"
2
6
7#include <h5pp/h5pp.h>
8
9#include <chrono>
10
11#ifdef _WIN32
12#ifndef NOMINMAX
13#define NOMINMAX
14#endif
15#include <windows.h>
16#endif
17
18namespace fs = std::filesystem;
19
20namespace polyfem::io
21{
23 {
24 public:
25 explicit Impl(const fs::path &path) : file(path.string(), h5pp::FileAccess::REPLACE) {}
26 h5pp::File file;
27 };
28
29 namespace
30 {
31 fs::path temporary_checkpoint_path(const fs::path &path)
32 {
33 const auto stamp = std::chrono::high_resolution_clock::now().time_since_epoch().count();
34 return path.parent_path() / fmt::format(".{}.{}.tmp", path.filename().string(), stamp);
35 }
36
37 std::string resource_destination(const std::string &logical)
38 {
39 // Canonical identities are already normalized. Keep Windows drive
40 // names as components so dependencies on different drives cannot alias.
41 const size_t start = logical.find_first_not_of('/');
42 if (start == std::string::npos || logical == ".")
43 return "/resources/tree";
44 return "/resources/tree/" + logical.substr(start);
45 }
46
47 template <typename T>
48 std::pair<std::vector<T>, std::vector<long>> pack_ragged(const std::vector<std::vector<T>> &rows)
49 {
50 std::pair<std::vector<T>, std::vector<long>> packed;
51 packed.second.reserve(rows.size() + 1);
52 packed.second.push_back(0);
53 for (const auto &row : rows)
54 {
55 packed.first.insert(packed.first.end(), row.begin(), row.end());
56 packed.second.push_back(packed.first.size());
57 }
58 return packed;
59 }
60
61 Eigen::MatrixXi pack_padded(const std::vector<std::vector<int>> &rows)
62 {
63 int width = 0;
64 for (const auto &row : rows)
65 width = std::max(width, int(row.size()));
66 Eigen::MatrixXi packed = Eigen::MatrixXi::Constant(rows.size(), width, -1);
67 for (int i = 0; i < rows.size(); ++i)
68 for (int j = 0; j < rows[i].size(); ++j)
69 packed(i, j) = rows[i][j];
70 return packed;
71 }
72 } // namespace
73
74 CheckpointWriter::CheckpointWriter(const fs::path &path, const json &config, const CheckpointMetadata &metadata)
75 : path_(fs::absolute(path).lexically_normal()), temporary_path_(temporary_checkpoint_path(path_))
76 {
77 if (path_.empty())
78 log_and_throw_error("Checkpoint output path is empty.");
79 fs::create_directories(path_.parent_path());
80 impl_ = std::make_unique<Impl>(temporary_path_);
81 write_string("/config", config.dump());
82 write_long("/checkpoint/metadata/schema_version", metadata.schema_version);
83 write_string("/checkpoint/metadata/formulation", metadata.formulation);
84 write_long("/checkpoint/metadata/step", metadata.step);
85 write_double("/checkpoint/metadata/time", metadata.time);
86 write_double("/checkpoint/metadata/dt", metadata.dt);
87 write_long("/checkpoint/metadata/remaining_steps", metadata.remaining_steps);
88 write_long("/checkpoint/metadata/output_index", metadata.output_index);
89 write_string("/resources/root", "/");
90 }
91
93 {
94 impl_.reset();
95 if (!finalized_)
96 {
97 std::error_code error;
98 fs::remove(temporary_path_, error);
99 }
100 }
101
102 void CheckpointWriter::write_matrix(const std::string &path, const Eigen::MatrixXd &value) { impl_->file.writeDataset(value, path); }
103 void CheckpointWriter::write_int_matrix(const std::string &path, const Eigen::MatrixXi &value) { impl_->file.writeDataset(value.cast<int64_t>(), path); }
104 void CheckpointWriter::write_bytes(const std::string &path, const std::vector<unsigned char> &value) { impl_->file.writeDataset(value, path); }
105 void CheckpointWriter::write_vector(const std::string &path, const std::vector<double> &value) { impl_->file.writeDataset(value, path); }
106 void CheckpointWriter::write_int_vector(const std::string &path, const std::vector<int> &value) { impl_->file.writeDataset(value, path); }
107 void CheckpointWriter::write_long_vector(const std::string &path, const std::vector<long> &value) { impl_->file.writeDataset(value, path); }
108 void CheckpointWriter::write_string(const std::string &path, const std::string &value) { impl_->file.writeDataset(value, path); }
109 void CheckpointWriter::write_long(const std::string &path, const long value) { impl_->file.writeDataset(value, path); }
110 void CheckpointWriter::write_double(const std::string &path, const double value) { impl_->file.writeDataset(value, path); }
111 void CheckpointWriter::write_attribute(const std::string &path, const std::string &name, const long value) { impl_->file.writeAttribute(value, path, name); }
112 void CheckpointWriter::write_attribute(const std::string &path, const std::string &name, const std::string &value) { impl_->file.writeAttribute(value, path, name); }
113
114 void CheckpointWriter::write_mesh(const std::string &group, const mesh::Mesh &mesh)
115 {
116 write_mesh(group, mesh.to_mesh_data());
117 }
118
119 void CheckpointWriter::write_mesh(const std::string &group, const mesh::MeshData &data)
120 {
121 data.validate();
122 write_matrix(group + "/vertices", data.vertices);
123 write_int_matrix(group + "/cells", data.elements);
124 write_attribute(group, "schema_version", mesh::MESH_SCHEMA_VERSION);
125 write_attribute(group, "dimension", data.dimension());
126 write_attribute(group, "mesh_type", "fem");
127 write_attribute(group, "elements_are_ordered", long(data.elements_are_ordered));
128 if (!data.body_ids.empty())
129 write_int_vector(group + "/body_ids", data.body_ids);
130 if (!data.geometry_ids.empty())
131 write_int_vector(group + "/geometry_ids", data.geometry_ids);
132 if (!data.node_ids.empty())
133 write_int_vector(group + "/node_ids", data.node_ids);
134 if (!data.boundary_ids.empty())
135 {
136 write_int_matrix(group + "/boundary_elements", pack_padded(data.boundary_elements));
137 write_int_vector(group + "/boundary_ids", data.boundary_ids);
138 }
139 if (!data.higher_order_connectivity.empty())
140 {
141 const auto packed = pack_ragged(data.higher_order_connectivity);
142 write_matrix(group + "/higher_order_nodes", data.higher_order_nodes);
143 write_int_vector(group + "/higher_order_connectivity", packed.first);
144 write_long_vector(group + "/higher_order_offsets", packed.second);
145 }
146 if (!data.higher_order_weights.empty())
147 {
148 const auto packed = pack_ragged(data.higher_order_weights);
149 write_vector(group + "/higher_order_weights", packed.first);
150 write_long_vector(group + "/higher_order_weight_offsets", packed.second);
151 }
152 if (data.has_polyhedral_topology())
153 {
154 const auto faces = pack_ragged(data.faces);
155 const auto cell_faces = pack_ragged(data.cell_faces);
156 const auto orientations = pack_ragged(data.cell_face_orientations);
157 write_int_vector(group + "/faces", faces.first);
158 write_long_vector(group + "/face_offsets", faces.second);
159 write_int_vector(group + "/cell_faces", cell_faces.first);
160 write_long_vector(group + "/cell_face_offsets", cell_faces.second);
161 write_int_vector(group + "/cell_face_orientations", orientations.first);
162 std::vector<int> is_hex(data.cell_is_hex.size());
163 std::transform(data.cell_is_hex.begin(), data.cell_is_hex.end(), is_hex.begin(), [](const bool value) { return int(value); });
164 write_int_vector(group + "/cell_is_hex", is_hex);
165 write_matrix(group + "/cell_kernel_points", data.cell_kernel_points);
166 }
167 }
168
170 {
171 // Capture the manifest only once, after the completed step. Input reads
172 // during solver setup (e.g. constraints) must be included as well.
173 const std::vector<std::string> manifest = resources.accessed_resources();
174 write_string("/resources/manifest", json(manifest).dump());
175 impl_->file.deleteLink("/resources/root");
176 write_string("/resources/root", resources.canonical_path(""));
177 const auto *hdf5 = dynamic_cast<const HDF5IO *>(&resources);
178 for (const std::string &canonical : manifest)
179 {
180 const std::string destination = resource_destination(canonical);
181 // A previously copied group already contains its descendants.
182 if (impl_->file.linkExists(destination))
183 continue;
184 if (!resources.exists(canonical))
185 log_and_throw_error("Checkpoint dependency {} no longer exists.", resources.describe(canonical));
186 if (hdf5)
187 {
188 // HDF5 object copying preserves datatype, dataspace, attributes and
189 // group contents without converting numeric datasets to byte arrays.
190 impl_->file.copyLinkFromFile(destination, hdf5->file_path(), hdf5->resolve(canonical));
191 continue;
192 }
193 if (resources.is_group(canonical))
194 {
195 impl_->file.createGroup(destination);
196 continue;
197 }
198 auto input = resources.open(canonical, true);
199 const std::vector<unsigned char> contents{
200 std::istreambuf_iterator<char>(*input), std::istreambuf_iterator<char>()};
201 if (input->bad())
202 log_and_throw_error("Unable to embed dependency {}.", resources.describe(canonical));
203 write_bytes(destination, contents);
204 }
205 }
206
208 {
209 if (finalized_)
210 return;
211 impl_.reset();
212 std::error_code error;
213#ifdef _WIN32
214 // Both paths are in the same directory, so this is a rename with
215 // replacement, never a copy followed by deletion.
216 if (!MoveFileExW(temporary_path_.c_str(), path_.c_str(), MOVEFILE_REPLACE_EXISTING))
217 error = std::error_code(GetLastError(), std::system_category());
218#else
219 fs::rename(temporary_path_, path_, error);
220#endif
221 if (error)
222 log_and_throw_error("Unable to atomically publish checkpoint {}: {}", path_.string(), error.message());
223 finalized_ = true;
224 }
225
227 : path_(fs::absolute(path).lexically_normal()), io_(std::make_unique<HDF5IO>(path_))
228 {
229 const auto require = [&](const std::string &key) {
230 if (!io_->exists(key))
231 log_and_throw_error("Checkpoint {} is missing {}.", path_.string(), key);
232 };
233 for (const std::string &key : {
234 "/config", "/checkpoint/metadata/schema_version", "/checkpoint/metadata/formulation",
235 "/checkpoint/metadata/step", "/checkpoint/metadata/time", "/checkpoint/metadata/dt",
236 "/checkpoint/metadata/remaining_steps", "/checkpoint/metadata/output_index",
237 "/checkpoint/meshes/active", "/checkpoint/state", "/resources/root"})
238 require(key);
239 resources_ = std::make_unique<HDF5IO>(
240 path_, io_->read_string("/resources/root"), path_.parent_path(), "/resources/tree");
241 config_ = json::parse(io_->read_string("/config"));
242 metadata_.schema_version = read_long("/checkpoint/metadata/schema_version");
243 metadata_.formulation = read_string("/checkpoint/metadata/formulation");
244 metadata_.step = read_long("/checkpoint/metadata/step");
245 metadata_.time = read_double("/checkpoint/metadata/time");
246 metadata_.dt = read_double("/checkpoint/metadata/dt");
247 metadata_.remaining_steps = read_long("/checkpoint/metadata/remaining_steps");
248 metadata_.output_index = read_long("/checkpoint/metadata/output_index");
251 "Unsupported checkpoint schema {} in {}; expected {}.",
253 if (!(metadata_.dt > 0) || metadata_.step < 0 || metadata_.remaining_steps < 0)
254 log_and_throw_error("Checkpoint {} has invalid temporal metadata.", path_.string());
255 }
256
257 Eigen::MatrixXd CheckpointReader::read_matrix(const std::string &path) const { return io_->read_matrix(path); }
258 Eigen::MatrixXi CheckpointReader::read_int_matrix(const std::string &path) const { return io_->read_int_matrix(path); }
259 std::vector<double> CheckpointReader::read_vector(const std::string &path) const { return io_->read_double_vector(path); }
260 std::vector<int> CheckpointReader::read_int_vector(const std::string &path) const { return io_->read_int_vector(path); }
261 std::vector<long> CheckpointReader::read_long_vector(const std::string &path) const { return io_->read_long_vector(path); }
262 std::string CheckpointReader::read_string(const std::string &path) const { return io_->read_string(path); }
263 long CheckpointReader::read_long(const std::string &path) const { return io_->read_long_vector(path).at(0); }
264 double CheckpointReader::read_double(const std::string &path) const { return io_->read_double_vector(path).at(0); }
265
266 std::unique_ptr<mesh::Mesh> CheckpointReader::read_mesh(const std::string &path, const bool non_conforming) const
267 {
268 return mesh::MeshLoader(*io_).load_fem(path, non_conforming);
269 }
270} // namespace polyfem::io
std::vector< Eigen::VectorXi > faces
const std::filesystem::path & path() const
CheckpointMetadata metadata_
std::filesystem::path path_
std::unique_ptr< mesh::Mesh > read_mesh(const std::string &path, bool non_conforming=false) const
CheckpointReader(const std::filesystem::path &path)
long read_long(const std::string &path) const
double read_double(const std::string &path) const
Eigen::MatrixXd read_matrix(const std::string &path) const
std::unique_ptr< const ResourceIO > resources_
std::unique_ptr< const HDF5IO > io_
Eigen::MatrixXi read_int_matrix(const std::string &path) const
std::vector< int > read_int_vector(const std::string &path) const
std::string read_string(const std::string &path) const
std::vector< double > read_vector(const std::string &path) const
std::vector< long > read_long_vector(const std::string &path) const
std::unique_ptr< Impl > impl_
void write_matrix(const std::string &path, const Eigen::MatrixXd &value)
std::filesystem::path path_
void write_attribute(const std::string &path, const std::string &name, long value)
void write_double(const std::string &path, double value)
std::filesystem::path temporary_path_
void write_int_matrix(const std::string &path, const Eigen::MatrixXi &value)
void write_mesh(const std::string &group, const mesh::Mesh &mesh)
void write_bytes(const std::string &path, const std::vector< unsigned char > &value)
void write_long_vector(const std::string &path, const std::vector< long > &value)
CheckpointWriter(const std::filesystem::path &path, const json &config, const CheckpointMetadata &metadata)
void write_string(const std::string &path, const std::string &value)
void write_long(const std::string &path, long value)
void embed_resources(const ResourceIO &resources)
void write_int_vector(const std::string &path, const std::vector< int > &value)
void write_vector(const std::string &path, const std::vector< double > &value)
HDF5 logical paths always use POSIX separators and roots.
Read-only interface to a hierarchical collection of simulation resources.
std::vector< std::string > accessed_resources() const
A snapshot of canonical paths consumed so far, including solver setup.
virtual std::string canonical_path(const std::string &path) const =0
Absolute resource identity, independent of the active reader root.
virtual std::string describe(const std::string &path) const =0
virtual bool exists(const std::string &path) const =0
virtual std::unique_ptr< std::istream > open(const std::string &path, bool binary) const =0
virtual bool is_group(const std::string &path) const =0
Format-independent input used to construct a runtime Mesh.
Definition MeshData.hpp:12
Eigen::MatrixXd vertices
Definition MeshData.hpp:21
bool has_polyhedral_topology() const
Definition MeshData.hpp:19
Eigen::MatrixXd higher_order_nodes
Definition MeshData.hpp:33
std::vector< std::vector< int > > higher_order_connectivity
Definition MeshData.hpp:34
std::vector< int > node_ids
Definition MeshData.hpp:29
std::vector< int > geometry_ids
Definition MeshData.hpp:28
std::vector< std::vector< int > > boundary_elements
Definition MeshData.hpp:30
std::vector< std::vector< int > > faces
Optional topology for arbitrary polyhedral cells.
Definition MeshData.hpp:38
std::vector< int > body_ids
Definition MeshData.hpp:27
bool elements_are_ordered
Whether each element row carries a format-defined local vertex order.
Definition MeshData.hpp:25
std::vector< int > boundary_ids
Definition MeshData.hpp:31
Eigen::MatrixXi elements
Definition MeshData.hpp:22
void validate() const
Definition MeshData.cpp:7
Eigen::MatrixXd cell_kernel_points
Definition MeshData.hpp:42
std::vector< std::vector< int > > cell_faces
Definition MeshData.hpp:39
std::vector< std::vector< int > > cell_face_orientations
Definition MeshData.hpp:40
std::vector< std::vector< double > > higher_order_weights
Definition MeshData.hpp:35
std::vector< bool > cell_is_hex
Definition MeshData.hpp:41
Abstract mesh class to capture 2d/3d conforming and non-conforming meshes.
Definition Mesh.hpp:50
MeshData to_mesh_data() const
Capture the complete runtime mesh in the canonical, format-independent representation.
Definition Mesh.cpp:100
The only non-legacy decoder from a logical resource path to mesh data.
std::unique_ptr< Mesh > load_fem(const std::string &path, bool non_conforming=false) const
constexpr long CHECKPOINT_SCHEMA_VERSION
constexpr long MESH_SCHEMA_VERSION
The canonical typed mesh schema shared by HDF5 bundles and checkpoints.
nlohmann::json json
Definition Common.hpp:9
void log_and_throw_error(const std::string &msg)
Definition Logger.cpp:73