19namespace fs = std::filesystem;
26 std::string normalize_logical(
const std::string &path)
28 const bool absolute = !
path.empty() &&
path.front() ==
'/';
29 std::vector<std::string> components;
30 std::istringstream input(path);
31 std::string component;
32 while (std::getline(input, component,
'/'))
34 if (component.empty() || component ==
".")
36 if (component ==
".." && !components.empty() && components.back() !=
"..")
37 components.pop_back();
38 else if (component !=
".." || !absolute)
39 components.push_back(component);
41 std::string result = absolute ?
"/" :
"";
42 for (
const auto &part : components)
44 if (!result.empty() && result.back() !=
'/')
48 return result.empty() ?
"." : result;
51 std::string join_logical(
const std::string &base,
const std::string &path)
53 return normalize_logical(!
path.empty() &&
path.front() ==
'/'
58 bool is_windows_absolute(
const std::string &path)
60 return path.size() >= 3
61 && ((
path[0] >=
'A' &&
path[0] <=
'Z') || (path[0] >=
'a' && path[0] <=
'z'))
62 && path[1] ==
':' && (path[2] ==
'/' || path[2] ==
'\\');
65 std::string windows_separators(std::string path)
67 std::replace(
path.begin(),
path.end(),
'\\',
'/');
71 std::string normalize_windows_absolute(
const std::string &path)
73 const std::string normalized = windows_separators(path);
74 return normalized.substr(0, 2) + normalize_logical(normalized.substr(2));
77 std::string relative_logical(
const std::string &path,
const std::string &base)
79 const auto split = [](
const std::string &value) {
80 std::vector<std::string> parts;
81 std::istringstream input(value);
83 while (std::getline(input, part,
'/'))
85 parts.push_back(part);
88 const auto target =
split(path), root =
split(base);
90 while (common < target.size() && common < root.size() && target[common] == root[common])
93 for (
size_t i = common; i < root.size(); ++i)
95 for (
size_t i = common; i < target.size(); ++i)
96 result += target[i] +
"/";
99 return result.empty() ?
"." : result;
102 template <
typename T>
103 std::vector<T> matrix_vector(
const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> &m)
107 return std::vector<T>(m.data(), m.data() + m.size());
110 template <
typename T>
111 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> read_file_matrix(
const fs::path &path)
113 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> result;
119 bool is_file_dataset(
const h5pp::DsetInfo &info)
121 const hid_t type = info.h5Type.value();
123 return H5Tget_class(type) == H5T_STRING
124 || (info.dsetRank.value() == 1 && H5Tget_class(type) == H5T_INTEGER
125 && H5Tget_size(type) == 1 && H5Tget_sign(type) == H5T_SGN_NONE);
128 template <
typename T>
129 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> read_numeric_matrix(
const h5pp::DsetInfo &info)
131 const std::string &
path = info.dsetPath.value();
132 const auto &
dims = info.dsetDims.value();
135 const hsize_t rows =
dims.empty() ? 1 :
dims[0];
136 const hsize_t cols =
dims.size() < 2 ? 1 :
dims[1];
137 const hsize_t limit = std::numeric_limits<Eigen::Index>::max();
138 if (rows > limit || cols > limit || (cols && rows > limit / cols))
140 Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> result(rows, cols);
141 const hid_t type = info.h5Type.value();
142 const H5T_class_t kind = H5Tget_class(type);
143 const auto read = [&](
auto scalar,
const hid_t memory_type) {
144 using Source =
decltype(scalar);
145 std::vector<Source> values(result.size());
146 if (!values.empty() && H5Dread(info.h5Dset.value(), memory_type, H5S_ALL, H5S_ALL, H5P_DEFAULT, values.data()) < 0)
148 for (Eigen::Index r = 0; r < result.rows(); ++r)
149 for (Eigen::Index c = 0; c < result.cols(); ++c)
151 const Source value = values[r * result.cols() + c];
152 if constexpr (std::is_integral_v<T> && std::is_integral_v<Source>)
154 if constexpr (std::is_signed_v<Source>)
156 if (value < std::numeric_limits<T>::lowest() || value > std::numeric_limits<T>::max())
159 else if (value > uint64_t(std::numeric_limits<T>::max()))
162 result(r, c) =
static_cast<T>(value);
165 if (kind == H5T_INTEGER && H5Tget_size(type) <=
sizeof(uint64_t))
167 if (H5Tget_sign(type) == H5T_SGN_NONE)
168 read(uint64_t(0), H5T_NATIVE_UINT64);
170 read(int64_t(0), H5T_NATIVE_INT64);
172 else if (kind == H5T_FLOAT && std::is_floating_point_v<T>)
173 read(
double(0), H5T_NATIVE_DOUBLE);
204 const std::string pattern = normalize_logical(input_pattern);
205 if (pattern.find_first_of(
"*?") == std::string::npos)
206 return exists(pattern) ? std::vector<std::string>{pattern} : std::vector<std::string>{};
207 const bool recursive = pattern.find(
"**") != std::string::npos;
208 const auto depth = std::count(pattern.begin(), pattern.end(),
'/');
209 std::string expression;
210 expression.reserve(pattern.size() * 2);
211 for (
size_t i = 0; i < pattern.size(); ++i)
213 const char c = pattern[i];
214 if (c ==
'*' && i + 1 < pattern.size() && pattern[i + 1] ==
'*')
217 if (i + 1 < pattern.size() && pattern[i + 1] ==
'/')
219 expression +=
"(?:[^/]+/)*";
226 expression +=
"[^/]*";
228 expression +=
"[^/]";
231 if (std::string(
".^$|()[]{}+\\").
find(c) != std::string::npos)
236 const std::regex matcher(
"^" + expression +
"$");
237 const size_t wildcard = pattern.find_first_of(
"*?");
238 const std::string prefix = wildcard == std::string::npos ? pattern : pattern.substr(0, wildcard);
239 const size_t slash = prefix.rfind(
'/');
240 const std::string root = slash == std::string::npos ?
"." : prefix.substr(0, slash);
242 std::vector<std::string> result;
243 std::vector<std::string> pending{root.empty() ?
"/" : root};
244 while (!pending.empty())
246 const std::string current = pending.back();
248 for (
const std::string &child :
list(current))
250 if (std::regex_match(child, matcher))
251 result.push_back(child);
252 if ((recursive || std::count(child.begin(), child.end(),
'/') < depth) &&
is_group(child))
253 pending.push_back(child);
256 std::sort(result.begin(), result.end());
262 auto in =
open(path,
true);
263 return std::string(std::istreambuf_iterator<char>(*in), std::istreambuf_iterator<char>());
268 fs::path candidate = root.empty() ? fs::current_path() : root;
269 std::error_code error;
270 if (fs::exists(candidate, error) && !fs::is_directory(candidate, error))
271 candidate = candidate.parent_path();
272 root_ = fs::absolute(candidate).lexically_normal();
288 return (p.is_absolute() ? p :
root_ / p).lexically_normal();
296 const fs::path directory =
resolve(path);
297 if (!fs::is_directory(directory))
299 std::vector<std::string> result;
300 const bool absolute_input = fs::path(path).is_absolute();
301 for (
const fs::directory_entry &entry : fs::directory_iterator(directory))
302 result.push_back(absolute_input ? entry.path().generic_string() : entry.path().lexically_relative(
root_).generic_string());
303 std::sort(result.begin(), result.end());
310 auto in = std::make_unique<std::ifstream>(
resolve(path), std::ios::in | (binary ? std::ios::binary : std::ios::openmode(0)));
319 return read_file_matrix<double>(
resolve(path));
325 return read_file_matrix<int>(
resolve(path));
333 return matrix_vector(read_file_matrix<long>(
resolve(path)));
360 explicit Impl(
const fs::path &path)
361 :
file(path.string(), h5pp::FileAccess::READONLY) {}
365 std::error_code error;
377 const fs::path &file,
378 const std::string &root,
379 const fs::path &host_directory,
380 const std::string &storage_root)
381 : impl_(std::make_shared<
Impl>(file)),
382 file_path_(fs::absolute(file).lexically_normal()),
383 root_(!storage_root.empty() && is_windows_absolute(root)
384 ? normalize_windows_absolute(root)
385 : join_logical(
"/", root)),
386 storage_root_(storage_root.empty() || storage_root ==
"/"
388 : join_logical(
"/", storage_root)),
389 host_directory_(host_directory.empty() ? file_path_.parent_path() : host_directory) {}
395 auto result = std::make_unique<HDF5IO>(
397 result->impl_ =
impl_;
408 const std::string normalized = windows_separators(path);
409 if (is_windows_absolute(normalized))
410 return normalize_windows_absolute(normalized);
411 if (!normalized.empty() && normalized.front() ==
'/')
412 return normalize_windows_absolute(
root_.substr(0, 2) + normalized);
413 return normalize_windows_absolute(
root_ +
"/" + normalized);
415 return join_logical(
root_, path);
423 : join_logical(
storage_root_, logical.front() ==
'/' ? logical.substr(1) : logical);
429 const std::string target =
resolve(path);
430 if (!
impl_->file.linkExists(target))
432 const auto info =
impl_->file.getLinkInfo(target);
433 return info.h5ObjType.has_value() && info.h5ObjType.value() == H5O_TYPE_GROUP;
440 const std::string parent =
resolve(path);
442 const bool absolute_input = (!path.empty() && path.front() ==
'/')
443 || (!
storage_root_.empty() && is_windows_absolute(
root_) && is_windows_absolute(path));
444 std::set<std::string> children;
445 const auto add = [&](
const std::vector<std::string> &
entries) {
446 for (
const std::string &entry :
entries)
448 std::string full = entry;
451 if (full.front() !=
'/')
452 full = join_logical(parent, full);
453 const std::string relative = relative_logical(normalize_logical(full), parent);
454 if (relative !=
"." && relative !=
".." && relative.compare(0, 3,
"../") != 0)
456 const std::string child = join_logical(logical_parent, relative.substr(0, relative.find(
'/')));
458 absolute_input ? child : relative_logical(child,
root_));
462 add(
impl_->file.findGroups(
"", parent));
463 add(
impl_->file.findDatasets(
"", parent));
464 return {children.begin(), children.end()};
470 const std::string key =
resolve(path);
471 const auto info =
impl_->file.getDatasetInfo(key);
472 if (!is_file_dataset(info))
474 if (H5Tget_class(info.h5Type.value()) == H5T_STRING)
475 return impl_->file.readDataset<std::string>(key);
476 const std::vector<unsigned char> bytes =
477 impl_->file.readDataset<std::vector<unsigned char>>(key);
478 return std::string(bytes.begin(), bytes.end());
481 std::unique_ptr<std::istream>
HDF5IO::open(
const std::string &path,
const bool)
const
483 return std::make_unique<std::istringstream>(
read_string(path));
489 const auto info =
impl_->file.getDatasetInfo(
resolve(path));
490 return is_file_dataset(info) ? read_file_matrix<double>(
materialize(path)) : read_numeric_matrix<double>(info);
496 const auto info =
impl_->file.getDatasetInfo(
resolve(path));
497 return is_file_dataset(info) ? read_file_matrix<int>(
materialize(path)) : read_numeric_matrix<int>(info);
513 const auto info =
impl_->file.getDatasetInfo(
resolve(path));
514 return matrix_vector(is_file_dataset(info) ? read_file_matrix<long>(
materialize(path)) : read_numeric_matrix<long>(info));
519 const std::string key =
resolve(path);
520 return impl_->file.attributeExists(std::string_view(key), std::string_view(name));
526 return impl_->file.readAttribute<
long>(
resolve(path), name);
532 return impl_->file.readAttribute<std::string>(
resolve(path), name);
538 return impl_->file.readAttribute<std::array<long, 2>>(
resolve(path), name);
544 const std::string key =
resolve(path);
545 std::lock_guard<std::mutex> lock(
impl_->mutex);
546 if (
const auto it =
impl_->materialized.find(key); it !=
impl_->materialized.end())
548 if (
impl_->temporary_directory.empty())
550 const auto stamp = std::chrono::high_resolution_clock::now().time_since_epoch().count();
551 for (
size_t attempt = 0;; ++attempt)
553 const fs::path candidate = fs::temp_directory_path() / fmt::format(
"polyfem-resources-{}-{}", stamp, attempt);
554 if (fs::create_directory(candidate))
556 impl_->temporary_directory = candidate;
563 const fs::path directory =
impl_->temporary_directory / std::to_string(
impl_->materialized.size());
564 fs::create_directory(directory);
565 const fs::path filename = fs::path(key).filename();
566 const fs::path output = directory / (filename.empty() ? fs::path(
"resource") : filename);
567 std::ofstream file(output, std::ios::binary);
569 file.write(contents.data(), contents.size());
572 impl_->materialized.emplace(key, output);
585 if (input.
config.contains(
"root_path") && input.
config[
"root_path"].is_string()
586 && !input.
config[
"root_path"].get<std::string>().empty())
590 input.
config.erase(
"root_path");
597 auto resources = std::make_unique<FileSystemIO>(path.parent_path());
598 LoadedInput result{json::parse(resources->read_string(path.filename().string())), std::move(resources)};
599 return apply_explicit_root(std::move(result));
604 auto resources = std::make_unique<FileSystemIO>(path.parent_path());
606 return apply_explicit_root(std::move(result));
611 auto resources = std::make_unique<HDF5IO>(path);
612 std::string config_path =
"/config";
613 if (!resources->exists(config_path))
615 config_path =
"/json";
616 if (!resources->exists(config_path))
618 logger().warn(
"HDF5 input {} uses deprecated /json; rename it to /config.", path.string());
620 LoadedInput result{json::parse(resources->read_string(config_path)), std::move(resources)};
621 return apply_explicit_root(std::move(result));
std::vector< Eigen::Triplet< double > > entries
std::filesystem::path host_directory_
bool has_attribute(const std::string &path, const std::string &name) const override
std::filesystem::path resolve(const std::string &path) const
std::filesystem::path root_
bool exists(const std::string &path) const override
long read_integer_attribute(const std::string &path, const std::string &name) const override
std::unique_ptr< const ResourceIO > with_root(const std::string &root) const override
std::string read_string_attribute(const std::string &path, const std::string &name) const override
std::vector< std::string > list(const std::string &path) const override
std::vector< int > read_int_vector(const std::string &path) const override
std::filesystem::path materialize(const std::string &path) const override
bool is_group(const std::string &path) const override
Eigen::MatrixXd read_matrix(const std::string &path) const override
std::vector< long > read_long_vector(const std::string &path) const override
std::unique_ptr< std::istream > open(const std::string &path, bool binary) const override
Eigen::MatrixXi read_int_matrix(const std::string &path) const override
std::vector< double > read_double_vector(const std::string &path) const override
const std::filesystem::path & host_directory() const override
Host directory used only as the base for ordinary filesystem outputs.
std::string describe(const std::string &path) const override
FileSystemIO(const std::filesystem::path &root, const std::filesystem::path &host_directory={})
std::array< long, 2 > read_shape_attribute(const std::string &path, const std::string &name) const override
std::map< std::string, fs::path > materialized
Impl(const fs::path &path)
fs::path temporary_directory
std::vector< int > read_int_vector(const std::string &path) const override
std::vector< long > read_long_vector(const std::string &path) const override
std::filesystem::path file_path_
bool has_attribute(const std::string &path, const std::string &name) const override
std::unique_ptr< std::istream > open(const std::string &path, bool binary) const override
std::string read_string(const std::string &path) const override
long read_integer_attribute(const std::string &path, const std::string &name) const override
std::string logical_resolve(const std::string &path) const
std::filesystem::path materialize(const std::string &path) const override
std::shared_ptr< Impl > impl_
Eigen::MatrixXd read_matrix(const std::string &path) const override
std::filesystem::path host_directory_
std::vector< std::string > list(const std::string &path) const override
bool is_group(const std::string &path) const override
bool exists(const std::string &path) const override
HDF5IO(const std::filesystem::path &file, const std::string &root="/", const std::filesystem::path &host_directory={}, const std::string &storage_root="")
std::string storage_root_
std::vector< double > read_double_vector(const std::string &path) const override
std::unique_ptr< const ResourceIO > with_root(const std::string &root) const override
std::array< long, 2 > read_shape_attribute(const std::string &path, const std::string &name) const override
std::string describe(const std::string &path) const override
Eigen::MatrixXi read_int_matrix(const std::string &path) const override
std::string resolve(const std::string &path) const
std::string read_string_attribute(const std::string &path, const std::string &name) const override
std::set< std::string > paths
std::vector< std::string > accessed_resources() const
A snapshot of canonical paths consumed so far, including solver setup.
void record_access(const std::string &path) const
virtual std::vector< std::string > list(const std::string &path) const =0
std::vector< std::string > glob(const std::string &pattern) const
Glob normalized logical paths using POSIX '*' and '?' wildcards.
virtual std::string read_string(const std::string &path) const
virtual std::string canonical_path(const std::string &path) const =0
Absolute resource identity, independent of the active reader root.
std::shared_ptr< AccessTracker > access_tracker_
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
LoadedInput load_json_input(const std::filesystem::path &path)
LoadedInput load_hdf5_input(const std::filesystem::path &path)
LoadedInput load_yaml_input(const std::filesystem::path &path)
bool read_matrix(const std::string &path, Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &mat)
Reads a matrix from a file. Determines the file format based on the path's extension.
json yaml_string_to_json(const std::string &yaml_str)
Convert YAML string to JSON.
int find(const Eigen::VectorXi &vec, int x)
std::vector< std::string > split(const std::string &str, const std::string &delimiters=" ")
spdlog::logger & logger()
Retrieves the current logger.
void log_and_throw_error(const std::string &msg)