#ifndef READCON_PLUS_PLUS_H
#define READCON_PLUS_PLUS_H
#pragma once
#if defined(__cplusplus) && __cplusplus < 201703L
#error "readcon-core.hpp requires C++17 or later"
#endif
#include <array>
#include <cmath>
#include <filesystem>
#include <iterator>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <vector>
#include "readcon-core.h"
namespace readcon {
struct Atom {
uint64_t atomic_number;
double x;
double y;
double z;
uint64_t atom_id;
double mass;
[[deprecated("Use fixed_x/fixed_y/fixed_z or fixed_mask() instead")]]
bool is_fixed;
bool fixed_x;
bool fixed_y;
bool fixed_z;
[[deprecated("Use velocity() (returns std::optional) instead")]]
double vx;
[[deprecated("Use velocity() (returns std::optional) instead")]]
double vy;
[[deprecated("Use velocity() (returns std::optional) instead")]]
double vz;
[[deprecated("Use velocity() (returns std::optional) instead")]]
bool has_velocity;
[[deprecated("Use force() (returns std::optional) instead")]]
double fx;
[[deprecated("Use force() (returns std::optional) instead")]]
double fy;
[[deprecated("Use force() (returns std::optional) instead")]]
double fz;
[[deprecated("Use force() (returns std::optional) instead")]]
bool has_forces;
double energy;
bool has_energy;
std::array<bool, 3> fixed_mask() const {
return {fixed_x, fixed_y, fixed_z};
}
std::optional<std::array<double, 3>> velocity() const {
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
if (has_velocity)
return std::array<double, 3>{vx, vy, vz};
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
return std::nullopt;
}
std::optional<std::array<double, 3>> force() const {
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
if (has_forces)
return std::array<double, 3>{fx, fy, fz};
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
return std::nullopt;
}
std::optional<double> energy_value() const {
if (has_energy)
return energy;
return std::nullopt;
}
};
class ConFrame;
class ConFrameWriter;
class ConFrameBuilder;
class ConFrameIterator {
public:
class Iterator {
public:
using iterator_category = std::input_iterator_tag;
using value_type = ConFrame;
using difference_type = std::ptrdiff_t;
using pointer = ConFrame *;
using reference = ConFrame &;
reference operator*();
pointer operator->();
Iterator &operator++();
bool operator==(const Iterator &other) const;
bool operator!=(const Iterator &other) const;
private:
friend class ConFrameIterator;
explicit Iterator(CConFrameIterator *iterator_ptr);
void fetch_next_frame();
CConFrameIterator *iterator_ptr_ = nullptr;
std::unique_ptr<ConFrame> current_frame_;
};
explicit ConFrameIterator(const std::filesystem::path &path);
Iterator begin();
Iterator end();
private:
struct IteratorDeleter {
void operator()(CConFrameIterator *ptr) const {
if (ptr) {
free_con_frame_iterator(ptr);
}
}
};
std::unique_ptr<CConFrameIterator, IteratorDeleter> iterator_ptr_;
};
class ConFrame {
public:
friend class ConFrameIterator::Iterator;
friend class ConFrameWriter;
friend class ConFrameBuilder;
friend ConFrame read_first_frame(const std::filesystem::path &);
friend std::vector<ConFrame> read_all_frames(const std::filesystem::path &);
ConFrame(const ConFrame &) = delete;
ConFrame &operator=(const ConFrame &) = delete;
ConFrame(ConFrame &&) = default;
ConFrame &operator=(ConFrame &&) = default;
const std::array<double, 3> &cell() const;
const std::array<double, 3> &angles() const;
const std::vector<Atom> &atoms() const;
const std::array<std::string, 2> &prebox_header() const;
const std::array<std::string, 2> &postbox_header() const;
bool has_velocities() const;
bool has_forces() const;
bool has_energies() const;
std::optional<size_t> atom_index_by_id(uint64_t atom_id) const;
uint32_t spec_version() const;
std::string metadata_json() const;
double energy() const;
uint64_t frame_index() const;
double time() const;
double timestep() const;
uint64_t neb_bead() const;
uint64_t neb_band() const;
std::optional<double> energy_opt() const;
std::optional<uint64_t> frame_index_opt() const;
std::optional<double> time_opt() const;
std::optional<double> timestep_opt() const;
std::optional<uint64_t> neb_bead_opt() const;
std::optional<uint64_t> neb_band_opt() const;
std::optional<std::string> potential_type() const;
const RKRConFrame *get_handle() const { return frame_handle_.get(); }
private:
struct FrameDeleter {
void operator()(RKRConFrame *ptr) const {
if (ptr)
free_rkr_frame(ptr);
}
};
explicit ConFrame(RKRConFrame *frame_handle);
std::unique_ptr<RKRConFrame, FrameDeleter> frame_handle_;
void cache_data() const;
mutable bool is_cached_ = false;
mutable std::vector<Atom> atoms_cache_;
mutable std::array<double, 3> cell_cache_;
mutable std::array<double, 3> angles_cache_;
mutable std::array<std::string, 2> prebox_header_cache_;
mutable std::array<std::string, 2> postbox_header_cache_;
mutable bool has_velocities_cache_ = false;
mutable bool has_forces_cache_ = false;
mutable bool has_energies_cache_ = false;
};
class ConFrameWriter {
public:
explicit ConFrameWriter(const std::filesystem::path &path,
uint8_t precision = 6);
void extend(const std::vector<ConFrame> &frames);
private:
struct WriterDeleter {
void operator()(RKRConFrameWriter *ptr) const {
if (ptr)
free_rkr_writer(ptr);
}
};
std::unique_ptr<RKRConFrameWriter, WriterDeleter> writer_handle_;
};
class ConFrameBuilder {
public:
ConFrameBuilder(const std::array<double, 3> &cell,
const std::array<double, 3> &angles,
const std::array<std::string, 2> &prebox = {"", ""},
const std::array<std::string, 2> &postbox = {"", ""});
~ConFrameBuilder();
ConFrameBuilder(const ConFrameBuilder &) = delete;
ConFrameBuilder &operator=(const ConFrameBuilder &) = delete;
ConFrameBuilder(ConFrameBuilder &&other) noexcept;
ConFrameBuilder &operator=(ConFrameBuilder &&other) noexcept;
ConFrameBuilder &set_metadata_json(const std::string &metadata_json);
ConFrameBuilder &set_scalar_metadata(const std::string &key, double value);
ConFrameBuilder &set_string_metadata(const std::string &key,
const std::string &value);
ConFrameBuilder &set_energy(double energy);
ConFrameBuilder &set_frame_index(uint64_t idx);
ConFrameBuilder &set_time(double time);
ConFrameBuilder &set_timestep(double dt);
ConFrameBuilder &set_neb_bead(uint64_t bead);
ConFrameBuilder &set_neb_band(uint64_t band);
ConFrameBuilder &add_atom(
const std::string &symbol, double x, double y, double z,
const std::array<bool, 3> &fixed, uint64_t atom_id, double mass,
std::optional<std::array<double, 3>> velocity = std::nullopt,
std::optional<std::array<double, 3>> force = std::nullopt);
ConFrameBuilder &add_atom(
const std::string &symbol, double x, double y, double z, bool is_fixed,
uint64_t atom_id, double mass,
std::optional<std::array<double, 3>> velocity = std::nullopt,
std::optional<std::array<double, 3>> force = std::nullopt);
ConFrameBuilder &with_velocity(const std::array<double, 3> &v);
ConFrameBuilder &with_force(const std::array<double, 3> &f);
ConFrameBuilder &with_energy(double energy);
ConFrame build();
private:
RKRConFrameBuilder *builder_handle_ = nullptr;
};
inline std::string status_message(RKRStatus status) {
const char *message = rkr_status_message(status);
return message ? std::string(message) : std::string("unknown status");
}
inline uint64_t symbol_to_z(const std::string &symbol) {
return rkr_symbol_to_z(symbol.c_str());
}
inline std::string z_to_symbol(uint64_t z) {
const char *symbol = rkr_z_to_symbol(z);
return symbol ? std::string(symbol) : std::string("X");
}
inline void throw_on_error(RKRStatus status, const std::string &operation) {
if (status != RKRStatus::RKR_STATUS_SUCCESS) {
throw std::runtime_error(operation + ": " + status_message(status));
}
}
inline ConFrame read_first_frame(const std::filesystem::path &path) {
RKRConFrame *handle = rkr_read_first_frame(path.string().c_str());
if (!handle) {
throw std::runtime_error("Failed to read first frame from: " +
path.string());
}
return ConFrame(handle);
}
inline std::vector<ConFrame> read_all_frames(const std::filesystem::path &path) {
size_t num_frames = 0;
RKRConFrame **handles = rkr_read_all_frames(path.string().c_str(), &num_frames);
if (!handles) {
throw std::runtime_error("Failed to read frames from: " +
path.string());
}
std::vector<ConFrame> frames;
frames.reserve(num_frames);
for (size_t i = 0; i < num_frames; ++i) {
frames.emplace_back(ConFrame(handles[i]));
}
for (size_t i = 0; i < num_frames; ++i) {
handles[i] = nullptr;
}
free_rkr_frame_array(handles, num_frames);
return frames;
}
inline ConFrameIterator::ConFrameIterator(const std::filesystem::path &path) {
CConFrameIterator *iter_ptr = read_con_file_iterator(path.string().c_str());
if (!iter_ptr) {
throw std::runtime_error("Failed to open .con file for iteration: " +
path.string());
}
iterator_ptr_.reset(iter_ptr);
}
inline ConFrameIterator::Iterator ConFrameIterator::begin() {
return Iterator(iterator_ptr_.get());
}
inline ConFrameIterator::Iterator ConFrameIterator::end() {
return Iterator(nullptr);
}
inline bool
ConFrameIterator::Iterator::operator==(const Iterator &other) const {
return current_frame_ == other.current_frame_;
}
inline bool
ConFrameIterator::Iterator::operator!=(const Iterator &other) const {
return !(*this == other);
}
inline ConFrameIterator::Iterator::Iterator(CConFrameIterator *iterator_ptr)
: iterator_ptr_(iterator_ptr) {
if (iterator_ptr_)
fetch_next_frame();
}
inline void ConFrameIterator::Iterator::fetch_next_frame() {
RKRConFrame *frame_handle = con_frame_iterator_next(iterator_ptr_);
if (frame_handle) {
current_frame_ = std::unique_ptr<ConFrame>(new ConFrame(frame_handle));
} else {
current_frame_ = nullptr;
}
}
inline ConFrame &ConFrameIterator::Iterator::operator*() {
return *current_frame_;
}
inline ConFrame *ConFrameIterator::Iterator::operator->() {
return current_frame_.get();
}
inline ConFrameIterator::Iterator &ConFrameIterator::Iterator::operator++() {
fetch_next_frame();
return *this;
}
inline ConFrame::ConFrame(RKRConFrame *frame_handle)
: frame_handle_(frame_handle) {}
inline void ConFrame::cache_data() const {
if (is_cached_) {
return;
}
CFrame *c_frame = rkr_frame_to_c_frame(frame_handle_.get());
if (!c_frame) {
throw std::runtime_error(
"Failed to extract CFrame from handle for caching.");
}
cell_cache_ = {c_frame->cell[0], c_frame->cell[1], c_frame->cell[2]};
angles_cache_ = {c_frame->angles[0], c_frame->angles[1],
c_frame->angles[2]};
has_velocities_cache_ = c_frame->has_velocities;
has_forces_cache_ = c_frame->has_forces;
has_energies_cache_ = c_frame->has_energies;
atoms_cache_.reserve(c_frame->num_atoms);
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif
for (size_t i = 0; i < c_frame->num_atoms; ++i) {
const CAtom &c_atom = c_frame->atoms[i];
atoms_cache_.emplace_back(
Atom{c_atom.atomic_number, c_atom.x, c_atom.y, c_atom.z,
c_atom.atom_id, c_atom.mass, c_atom.is_fixed,
c_atom.fixed_x, c_atom.fixed_y, c_atom.fixed_z,
c_atom.vx, c_atom.vy, c_atom.vz, c_atom.has_velocity,
c_atom.fx, c_atom.fy, c_atom.fz, c_atom.has_forces,
c_atom.energy, c_atom.has_energy});
}
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
free_c_frame(c_frame);
auto get_and_free_string =
[frame_handle = frame_handle_.get()](bool is_prebox, size_t index) {
char *c_str =
rkr_frame_get_header_line_cpp(frame_handle, is_prebox, index);
if (!c_str) {
return std::string();
}
std::string result(c_str);
rkr_free_string(c_str);
return result;
};
prebox_header_cache_[0] = get_and_free_string(true, 0);
prebox_header_cache_[1] = get_and_free_string(true, 1);
postbox_header_cache_[0] = get_and_free_string(false, 0);
postbox_header_cache_[1] = get_and_free_string(false, 1);
is_cached_ = true;
}
inline const std::array<double, 3> &ConFrame::cell() const {
cache_data();
return cell_cache_;
}
inline const std::array<double, 3> &ConFrame::angles() const {
cache_data();
return angles_cache_;
}
inline const std::vector<Atom> &ConFrame::atoms() const {
cache_data();
return atoms_cache_;
}
inline const std::array<std::string, 2> &ConFrame::prebox_header() const {
cache_data();
return prebox_header_cache_;
}
inline const std::array<std::string, 2> &ConFrame::postbox_header() const {
cache_data();
return postbox_header_cache_;
}
inline bool ConFrame::has_velocities() const {
cache_data();
return has_velocities_cache_;
}
inline bool ConFrame::has_forces() const {
cache_data();
return has_forces_cache_;
}
inline bool ConFrame::has_energies() const {
cache_data();
return has_energies_cache_;
}
inline std::optional<size_t>
ConFrame::atom_index_by_id(uint64_t atom_id) const {
uint64_t idx = rkr_frame_atom_index_by_id(frame_handle_.get(), atom_id);
if (idx == UINT64_MAX) {
return std::nullopt;
}
return static_cast<size_t>(idx);
}
inline uint32_t ConFrame::spec_version() const {
return rkr_frame_spec_version(frame_handle_.get());
}
inline std::string ConFrame::metadata_json() const {
char *json = rkr_frame_metadata_json(frame_handle_.get());
if (!json)
return "";
std::string result(json);
rkr_free_string(json);
return result;
}
inline double ConFrame::energy() const {
return rkr_frame_energy(frame_handle_.get());
}
inline uint64_t ConFrame::frame_index() const {
return rkr_frame_frame_index(frame_handle_.get());
}
inline double ConFrame::time() const {
return rkr_frame_time(frame_handle_.get());
}
inline double ConFrame::timestep() const {
return rkr_frame_timestep(frame_handle_.get());
}
inline uint64_t ConFrame::neb_bead() const {
return rkr_frame_neb_bead(frame_handle_.get());
}
inline uint64_t ConFrame::neb_band() const {
return rkr_frame_neb_band(frame_handle_.get());
}
inline std::optional<double> ConFrame::energy_opt() const {
double v = rkr_frame_energy(frame_handle_.get());
if (std::isnan(v))
return std::nullopt;
return v;
}
inline std::optional<uint64_t> ConFrame::frame_index_opt() const {
uint64_t v = rkr_frame_frame_index(frame_handle_.get());
if (v == UINT64_MAX)
return std::nullopt;
return v;
}
inline std::optional<double> ConFrame::time_opt() const {
double v = rkr_frame_time(frame_handle_.get());
if (std::isnan(v))
return std::nullopt;
return v;
}
inline std::optional<double> ConFrame::timestep_opt() const {
double v = rkr_frame_timestep(frame_handle_.get());
if (std::isnan(v))
return std::nullopt;
return v;
}
inline std::optional<uint64_t> ConFrame::neb_bead_opt() const {
uint64_t v = rkr_frame_neb_bead(frame_handle_.get());
if (v == UINT64_MAX)
return std::nullopt;
return v;
}
inline std::optional<uint64_t> ConFrame::neb_band_opt() const {
uint64_t v = rkr_frame_neb_band(frame_handle_.get());
if (v == UINT64_MAX)
return std::nullopt;
return v;
}
inline std::optional<std::string> ConFrame::potential_type() const {
char *p = rkr_frame_potential_type(frame_handle_.get());
if (!p)
return std::nullopt;
std::string s(p);
rkr_free_string(p);
return s;
}
inline ConFrameWriter::ConFrameWriter(const std::filesystem::path &path,
uint8_t precision) {
if (precision == 6) {
writer_handle_.reset(create_writer_from_path_c(path.string().c_str()));
} else {
writer_handle_.reset(
create_writer_from_path_with_precision_c(path.string().c_str(), precision));
}
if (!writer_handle_) {
throw std::runtime_error("Failed to create writer for file: " +
path.string());
}
}
inline void ConFrameWriter::extend(const std::vector<ConFrame> &frames) {
if (frames.empty())
return;
std::vector<const RKRConFrame *> handles;
handles.reserve(frames.size());
for (const auto &frame : frames) {
handles.push_back(frame.get_handle());
}
throw_on_error(
rkr_writer_extend(writer_handle_.get(), handles.data(), handles.size()),
"Failed to write multiple frames");
}
inline ConFrameBuilder::ConFrameBuilder(
const std::array<double, 3> &cell, const std::array<double, 3> &angles,
const std::array<std::string, 2> &prebox,
const std::array<std::string, 2> &postbox) {
builder_handle_ =
rkr_frame_new(cell.data(), angles.data(), prebox[0].c_str(),
prebox[1].c_str(), postbox[0].c_str(), postbox[1].c_str());
if (!builder_handle_) {
throw std::runtime_error("Failed to create frame builder.");
}
}
inline ConFrameBuilder::~ConFrameBuilder() {
if (builder_handle_) {
free_rkr_frame_builder(builder_handle_);
}
}
inline ConFrameBuilder::ConFrameBuilder(ConFrameBuilder &&other) noexcept
: builder_handle_(other.builder_handle_) {
other.builder_handle_ = nullptr;
}
inline ConFrameBuilder &
ConFrameBuilder::operator=(ConFrameBuilder &&other) noexcept {
if (this != &other) {
if (builder_handle_) {
free_rkr_frame_builder(builder_handle_);
}
builder_handle_ = other.builder_handle_;
other.builder_handle_ = nullptr;
}
return *this;
}
inline ConFrameBuilder &
ConFrameBuilder::add_atom(const std::string &symbol, double x, double y,
double z, const std::array<bool, 3> &fixed,
uint64_t atom_id, double mass,
std::optional<std::array<double, 3>> velocity,
std::optional<std::array<double, 3>> force) {
const double *vptr = velocity ? velocity->data() : nullptr;
const double *fptr = force ? force->data() : nullptr;
throw_on_error(rkr_frame_add_atom_full(builder_handle_, symbol.c_str(), x,
y, z, fixed[0], fixed[1], fixed[2],
atom_id, mass, vptr, fptr),
"Failed to add atom to frame builder");
return *this;
}
inline ConFrameBuilder &
ConFrameBuilder::add_atom(const std::string &symbol, double x, double y,
double z, bool is_fixed, uint64_t atom_id,
double mass,
std::optional<std::array<double, 3>> velocity,
std::optional<std::array<double, 3>> force) {
return add_atom(symbol, x, y, z,
{is_fixed, is_fixed, is_fixed}, atom_id, mass,
std::move(velocity), std::move(force));
}
inline ConFrameBuilder &
ConFrameBuilder::with_velocity(const std::array<double, 3> &v) {
throw_on_error(
rkr_frame_builder_set_last_velocity(builder_handle_, v.data()),
"Failed to attach velocity to last atom");
return *this;
}
inline ConFrameBuilder &
ConFrameBuilder::with_force(const std::array<double, 3> &f) {
throw_on_error(
rkr_frame_builder_set_last_force(builder_handle_, f.data()),
"Failed to attach force to last atom");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::with_energy(double energy) {
throw_on_error(
rkr_frame_builder_set_last_energy(builder_handle_, energy),
"Failed to attach per-atom energy to last atom");
return *this;
}
inline ConFrameBuilder &
ConFrameBuilder::set_metadata_json(const std::string &metadata_json) {
throw_on_error(rkr_frame_builder_set_metadata_json(builder_handle_,
metadata_json.c_str()),
"Failed to set builder metadata JSON");
return *this;
}
inline ConFrameBuilder &
ConFrameBuilder::set_scalar_metadata(const std::string &key, double value) {
throw_on_error(rkr_frame_builder_set_scalar_metadata(
builder_handle_, key.c_str(), value),
"Failed to set builder scalar metadata");
return *this;
}
inline ConFrameBuilder &
ConFrameBuilder::set_string_metadata(const std::string &key,
const std::string &value) {
throw_on_error(rkr_frame_builder_set_string_metadata(
builder_handle_, key.c_str(), value.c_str()),
"Failed to set builder string metadata");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::set_energy(double energy) {
throw_on_error(rkr_frame_builder_set_energy(builder_handle_, energy),
"Failed to set builder energy metadata");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::set_frame_index(uint64_t idx) {
throw_on_error(rkr_frame_builder_set_frame_index(builder_handle_, idx),
"Failed to set builder frame_index metadata");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::set_time(double time) {
throw_on_error(rkr_frame_builder_set_time(builder_handle_, time),
"Failed to set builder time metadata");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::set_timestep(double dt) {
throw_on_error(rkr_frame_builder_set_timestep(builder_handle_, dt),
"Failed to set builder timestep metadata");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::set_neb_bead(uint64_t bead) {
throw_on_error(rkr_frame_builder_set_neb_bead(builder_handle_, bead),
"Failed to set builder neb_bead metadata");
return *this;
}
inline ConFrameBuilder &ConFrameBuilder::set_neb_band(uint64_t band) {
throw_on_error(rkr_frame_builder_set_neb_band(builder_handle_, band),
"Failed to set builder neb_band metadata");
return *this;
}
inline ConFrame ConFrameBuilder::build() {
RKRConFrame *frame = rkr_frame_builder_build(builder_handle_);
builder_handle_ = nullptr; if (!frame) {
throw std::runtime_error("Failed to build frame from builder.");
}
return ConFrame(frame);
}
}
#endif