Loading...
Searching...
No Matches
eonc::io Namespace Reference

Classes

struct  ConMetadataText
struct  ConMetadataValue

Enumerations

enum class  IoStatus : std::uint8_t {
  Ok = 0 , ReadError = 1 , WriteError = 2 , AppendError = 3 ,
  OpenError = 4 , InvalidArgument = 5
}
 Structured I/O result for the client surface (nanobind-friendly). More...

Functions

readcon::ConFrame matterToConFrame (Matter &m, const ConFrameMetadata *metadata=nullptr)
 Build a single stamped ConFrame from Matter (same builder as matter2con).
void set_write_con_forces (bool enabled) noexcept
 Whether written .con frames carry "Forces of Component" sections.
bool write_con_forces () noexcept
ConFrameMetadata metadata_from_frame (const readcon::ConFrame &frame)
 Extract known frame-level fields from a parsed readcon frame.
std::pair< std::array< double, 3 >, std::array< double, 3 > > cell_to_lengths_angles (const Matter &m)
void resetConAppendState ()
 Drop the per-path bookkeeping that lets append-mode writes skip re-parsing frames this process wrote.
IoStatus matter2con (Matter &m, std::string filename, bool append=false, const ConFrameMetadata *metadata=nullptr)
 Append a frame to a .con, or truncate and write one frame.
IoStatus con2matter (Matter &m, std::string filename)
IoStatus con2matter (Matter &m, const readcon::ConFrame &frame, ConFrameMetadata *out_metadata)
IoStatus matter2convel (Matter &m, std::string filename)
IoStatus convel2matter (Matter &m, std::string filename)
IoStatus matter2xyz (Matter &m, std::string filename, bool append=false)
 Write one extended-XYZ frame: Lattice= cell and 17-digit coordinates.
IoStatus writeTibble (Matter &m, std::string filename)
 Debug table (positions, optional cached forces). Not a structure format.
std::vector< readcon::ConFrame > buildNebPathFrames (const std::vector< std::shared_ptr< Matter > > &path, const std::vector< ConFrameMetadata > &metadata_per_image)
 Build NEB band ConFrames without writing (clone builder path of writeNebPath).
IoStatus writeConFrames (std::string filename, const std::vector< readcon::ConFrame > &frames)
 Write already-built ConFrames to a multi-frame .con (temp or durable).
IoStatus writeNebPath (std::string filename, const std::vector< std::shared_ptr< Matter > > &path, const std::vector< ConFrameMetadata > &metadata_per_image)
 Write a full NEB path as one multi-frame .con using ConFrameBuilder::clone().
void mirror_con_corpus (const std::string &path)
 Copy a con file into the run readcon-db corpus when libreadcon_db loads.
constexpr bool io_ok (IoStatus s) noexcept
constexpr const char * io_status_name (IoStatus s) noexcept
 Human-readable label for logging / bindings.

Enumeration Type Documentation

◆ IoStatus

enum class eonc::io::IoStatus : std::uint8_t
strong

Structured I/O result for the client surface (nanobind-friendly).

Prefer comparing to IoStatus::Ok rather than treating as bool.

Enumerator
Ok 
ReadError 
WriteError 
AppendError 
OpenError 
InvalidArgument 

Definition at line 29 of file ConFileIO.h.

29 : std::uint8_t {
30 Ok = 0,
31 ReadError = 1,
32 WriteError = 2,
33 AppendError = 3,
34 OpenError = 4,
36};

Function Documentation

◆ buildNebPathFrames()

std::vector< readcon::ConFrame > eonc::io::buildNebPathFrames ( const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< ConFrameMetadata > & metadata_per_image )
nodiscard

Build NEB band ConFrames without writing (clone builder path of writeNebPath).

Empty vector on invalid input.

Parameters
pathlength must equal metadata_per_image (endpoints included)

Definition at line 907 of file ConFileIO.cpp.

908 {
909 std::vector<readcon::ConFrame> frames;
910 if (path.empty() || path.size() != metadata_per_image.size()) {
912 "buildNebPathFrames: path/metadata size mismatch (path={}, meta={})",
913 path.size(), metadata_per_image.size());
914 return frames;
915 }
916 for (const auto &img : path) {
917 if (!img) {
918 EONC_LOG_ERROR("buildNebPathFrames: null Matter in path");
919 return {};
920 }
921 }
922
923 Matter &template_m = *path.front();
924 template_m.applyPeriodicBoundaryIfEnabled();
925
926 std::vector<uint64_t> atom_ids;
927 std::array<std::string, 2> prebox;
928 std::array<std::string, 2> postbox;
929 collect_ids_headers(template_m, atom_ids, prebox, postbox);
930
931 frames.reserve(path.size());
932 try {
933 auto seed = seed_builder(template_m, prebox, postbox, atom_ids);
934 for (size_t i = 0; i < path.size(); ++i) {
935 Matter &img = *path[i];
936 img.applyPeriodicBoundaryIfEnabled();
937 if (img.numberOfAtoms() != template_m.numberOfAtoms()) {
939 "buildNebPathFrames: image {} atom count {} != template {}", i,
940 img.numberOfAtoms(), template_m.numberOfAtoms());
941 return {};
942 }
943 auto builder = seed.clone();
944 apply_frame_metadata(builder, &metadata_per_image[i]);
945 apply_geometry(builder, img, /*with_velocities=*/false,
946 &metadata_per_image[i]);
947 frames.push_back(builder.build());
948 }
949 } catch (const std::exception &e) {
950 EONC_LOG_ERROR("buildNebPathFrames failed: {}", e.what());
951 return {};
952 }
953 return frames;
954}
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:261

◆ cell_to_lengths_angles()

std::pair< std::array< double, 3 >, std::array< double, 3 > > eonc::io::cell_to_lengths_angles ( const Matter & m)

Definition at line 530 of file ConFileIO.cpp.

530 {
531 const Matrix3d cell = m.getCell();
532 std::array<double, 3> lengths;
533 lengths[0] = cell.row(0).norm();
534 lengths[1] = cell.row(1).norm();
535 lengths[2] = cell.row(2).norm();
536 // CON header line 4 is alpha beta gamma:
537 // alpha = angle(b,c), beta = angle(a,c), gamma = angle(a,b).
538 std::array<double, 3> angles;
540 cell.row(1).dot(cell.row(2)), lengths[1] * lengths[2])) *
541 180.0 / eonc::helpers::pi;
543 cell.row(0).dot(cell.row(2)), lengths[0] * lengths[2])) *
544 180.0 / eonc::helpers::pi;
546 cell.row(0).dot(cell.row(1)), lengths[0] * lengths[1])) *
547 180.0 / eonc::helpers::pi;
548 return {lengths, angles};
549}
Eigen::Matrix< double, 3, 3, eOnStorageOrder > Matrix3d
Definition Eigen.h:35
Matrix3d getCell() const
Definition Matter.cpp:275
constexpr double pi
constexpr double safe_div(double num, double denom, double fallback=0.0)
Definition SafeMath.h:21
double safe_acos(double x)
Definition SafeMath.h:34

◆ con2matter() [1/2]

IoStatus eonc::io::con2matter ( Matter & m,
const readcon::ConFrame & frame,
ConFrameMetadata * out_metadata )
nodiscard

Definition at line 637 of file ConFileIO.cpp.

638 {
639 try {
640 const auto &atoms = frame.atoms();
641 const auto &lengths = frame.cell();
642 const auto &angles_deg = frame.angles();
643 const auto &prebox = frame.prebox_header();
644 const auto &postbox = frame.postbox_header();
645
646 m.headerCon[0] = prebox[0] + "\n";
647 m.headerCon[1] = prebox[1] + "\n";
648 m.headerCon[3] = postbox[0] + "\n";
649 m.headerCon[4] = postbox[1] + "\n";
650
651 double angles[3] = {angles_deg[0], angles_deg[1], angles_deg[2]};
652 if (angles[0] == 90.0 && angles[1] == 90.0 && angles[2] == 90.0) {
653 Matrix3d cell = Matrix3d::Zero();
654 cell(0, 0) = lengths[0];
655 cell(1, 1) = lengths[1];
656 cell(2, 2) = lengths[2];
657 m.setCell(cell);
658 } else {
659 angles[0] *= eonc::helpers::pi / 180.0;
660 angles[1] *= eonc::helpers::pi / 180.0;
661 angles[2] *= eonc::helpers::pi / 180.0;
662 const double alpha = angles[0];
663 const double beta = angles[1];
664 const double gamma = angles[2];
665
666 Matrix3d cell = Matrix3d::Zero();
667 cell(0, 0) = 1.0;
668 cell(1, 0) = cos(gamma);
669 cell(1, 1) = sin(gamma);
670 cell(2, 0) = cos(beta);
671 cell(2, 1) = (cos(alpha) - cell(1, 0) * cell(2, 0)) / cell(1, 1);
672 cell(2, 2) = eonc::safemath::safe_sqrt(1.0 - pow(cell(2, 0), 2) -
673 pow(cell(2, 1), 2));
674
675 cell(0, 0) *= lengths[0];
676 cell(1, 0) *= lengths[1];
677 cell(1, 1) *= lengths[1];
678 cell(2, 0) *= lengths[2];
679 cell(2, 1) *= lengths[2];
680 cell(2, 2) *= lengths[2];
681 m.setCell(cell);
682 }
683 m.headerCon[2] =
684 std::format("{} {} {}\n", angles_deg[0], angles_deg[1], angles_deg[2]);
685
686 const auto n = static_cast<Eigen::Index>(atoms.size());
687 m.resize(static_cast<long>(atoms.size()));
688
689 // Undo the species grouping the .con format imposes, so an index into
690 // Matter addresses the same atom as the matching row of mode.dat.
691 const std::vector<size_t> order = matter_order(atoms);
692 std::vector<long> file_to_matter(static_cast<size_t>(n));
693 for (Eigen::Index i = 0; i < n; ++i) {
694 file_to_matter[order[static_cast<size_t>(i)]] = static_cast<long>(i);
695 }
696 m.setFileToMatter(std::move(file_to_matter));
697
698 AtomMatrix positions = AtomMatrix::Zero(n, 3);
699 AtomMatrix forces = AtomMatrix::Zero(n, 3);
700 AtomMatrix velocities = AtomMatrix::Zero(n, 3);
701 VectorXd masses = VectorXd::Zero(n);
702 VectorXi atomic_nrs = VectorXi::Zero(n);
703 bool any_force = false;
704 bool any_velocity = false;
705
706 for (Eigen::Index i = 0; i < n; ++i) {
707 const auto &atom = atoms[order[static_cast<size_t>(i)]];
708 positions(i, 0) = atom.x;
709 positions(i, 1) = atom.y;
710 positions(i, 2) = atom.z;
711 masses(i) = atom.mass;
712 atomic_nrs(i) = static_cast<int>(atom.atomic_number);
713 const auto fixed = atom.fixed_mask();
714 m.setFixedMask(static_cast<long>(i), {fixed[0], fixed[1], fixed[2]});
715 m.setAtomIndex(static_cast<long>(i),
716 static_cast<std::int64_t>(atom.atom_id));
717
718 if (auto vel = atom.velocity()) {
719 any_velocity = true;
720 velocities(i, 0) = (*vel)[0];
721 velocities(i, 1) = (*vel)[1];
722 velocities(i, 2) = (*vel)[2];
723 }
724 if (auto force = atom.force()) {
725 any_force = true;
726 forces(i, 0) = (*force)[0];
727 forces(i, 1) = (*force)[1];
728 forces(i, 2) = (*force)[2];
729 }
730 }
731
732 m.setMasses(masses);
733 m.setAtomicNrs(atomic_nrs);
734 m.setPositions(positions);
735
736 if (any_velocity || frame.has_velocities()) {
737 m.setVelocities(velocities);
738 }
739
740 const auto meta = metadata_from_frame(frame);
741 if (out_metadata != nullptr) {
742 *out_metadata = meta;
743 }
744
745 // Trust file energy+forces only when both are present. Energy-only must not
746 // mark the pot clean with a zero force matrix (optimizer footgun). Prefer
747 // writing raw forces (friend) so fixed-atom components survive RT; then
748 // mark clean without setComputedPotential's net-force adjustment on zeros.
749 const bool has_force_section = any_force || frame.has_forces();
750 if (meta.energy && has_force_section) {
751 m.restoreFileForces(forces, true, *meta.energy);
752 } else if (has_force_section) {
753 m.restoreFileForces(forces, false, 0.0);
754 } else {
755 // Classic geometry-only files: always recompute pot (main-era behavior).
756 m.recomputePotential = true;
757 }
758
759 // setPositions already applied PBC when enabled; no second wrap here.
760 return IoStatus::Ok;
761 } catch (const std::exception &e) {
762 EONC_LOG_ERROR("Failed to convert frame to matter: {}", e.what());
763 return IoStatus::ReadError;
764 }
765}
Eigen::Matrix< double, Eigen::Dynamic, 3, eOnStorageOrder > AtomMatrix
Definition Eigen.h:37
void setFixedMask(long int atom, std::array< bool, 3 > mask)
Definition Matter.cpp:545
void setPositions(const AtomMatrix &pos)
Definition Matter.cpp:350
void setCell(const Matrix3d &newCell)
Definition Matter.cpp:277
void setAtomicNrs(const VectorXi &atmnrs)
Definition Matter.cpp:693
void setVelocities(const AtomMatrix &v)
Definition Matter.cpp:728
void setAtomIndex(long int atom, std::int64_t index)
Definition Matter.cpp:830
void resize(long int nAtoms)
Definition Matter.cpp:227
void restoreFileForces(const AtomMatrix &fileForces, bool trustEnergy, double energy)
Write .con forces without the fixed-atom mask setForces applies.
Definition Matter.cpp:834
bool recomputePotential
Definition Matter.h:343
void setMasses(const VectorXd &massesIn)
Definition Matter.cpp:488
std::array< std::string, 5 > headerCon
Definition Matter.h:349
void setFileToMatter(std::vector< long > map)
Definition Matter.h:300
ConFrameMetadata metadata_from_frame(const readcon::ConFrame &frame)
Extract known frame-level fields from a parsed readcon frame.
double safe_sqrt(double x)
Definition SafeMath.h:38

◆ con2matter() [2/2]

IoStatus eonc::io::con2matter ( Matter & m,
std::string filename )
nodiscard

Definition at line 622 of file ConFileIO.cpp.

622 {
623 filename = ensure_extension(std::move(filename), ".con");
624 try {
625 auto frame = readcon::read_first_frame(filename);
626 const IoStatus status = con2matter(m, frame, nullptr);
627 if (io_ok(status)) {
628 mirror_con_corpus(filename);
629 }
630 return status;
631 } catch (const std::exception &e) {
632 EONC_LOG_ERROR("Failed to read {}: {}", filename, e.what());
633 return IoStatus::ReadError;
634 }
635}
IoStatus con2matter(Matter &m, std::string filename)
IoStatus
Structured I/O result for the client surface (nanobind-friendly).
Definition ConFileIO.h:29
void mirror_con_corpus(const std::string &path)
Copy a con file into the run readcon-db corpus when libreadcon_db loads.
constexpr bool io_ok(IoStatus s) noexcept
Definition ConFileIO.h:38

◆ convel2matter()

IoStatus eonc::io::convel2matter ( Matter & m,
std::string filename )
nodiscard

Definition at line 787 of file ConFileIO.cpp.

787 {
788 filename = ensure_extension(std::move(filename), ".convel");
789 try {
790 auto frame = readcon::read_first_frame(filename);
791 const IoStatus status = con2matter(m, frame, nullptr);
792 if (io_ok(status)) {
793 mirror_con_corpus(filename);
794 }
795 return status;
796 } catch (const std::exception &e) {
797 EONC_LOG_ERROR("Failed to read convel {}: {}", filename, e.what());
798 return IoStatus::ReadError;
799 }
800}

◆ io_ok()

bool eonc::io::io_ok ( IoStatus s)
nodiscardconstexprnoexcept

Definition at line 38 of file ConFileIO.h.

38 {
39 return s == IoStatus::Ok;
40}

◆ io_status_name()

const char * eonc::io::io_status_name ( IoStatus s)
nodiscardconstexprnoexcept

Human-readable label for logging / bindings.

Definition at line 43 of file ConFileIO.h.

43 {
44 switch (s) {
45 case IoStatus::Ok:
46 return "ok";
48 return "read_error";
50 return "write_error";
52 return "append_error";
54 return "open_error";
56 return "invalid_argument";
57 }
58 return "unknown";
59}

◆ matter2con()

IoStatus eonc::io::matter2con ( Matter & m,
std::string filename,
bool append = false,
const ConFrameMetadata * metadata = nullptr )
nodiscard

Append a frame to a .con, or truncate and write one frame.

With append, an uncompressed target is extended in place: the new frame is serialized on its own and concatenated, so N appends cost N frame writes rather than N(N+1)/2. Earlier frames are never re-serialized, and the file is flushed before the call returns, so every intermediate state on disk is a complete multi-frame .con.

A target that eOn did not write, or that changed size or mtime since eOn last wrote it, is parsed once before anything is added; an unparseable target yields IoStatus::AppendError with its bytes untouched. Gzip and zstd targets keep the read-all-and-rewrite path because a compressed member cannot be extended in place.

Definition at line 556 of file ConFileIO.cpp.

557 {
558 filename = ensure_extension(std::move(filename), ".con");
559
561
562 const fs::path path(filename);
563 const auto compression =
564 readcon::ConFrameWriter::compression_from_extension(path);
565 const bool streamable =
566 compression == readcon::ConFrameWriter::Compression::None;
567
568 // The lock covers the stamp table and the read-then-extend window on one
569 // path. Nothing under it evaluates a potential: frame_from_matter reads
570 // cached energy and forces only while the pot is clean.
571 const std::lock_guard<std::mutex> guard(append_mutex());
572 const auto key = append_key(path);
573 const bool exists = fs::exists(path);
574 const bool concatenate = append && exists && streamable;
575
576 // ConFrame is move-only (no default ctor), so frames carries the new frame
577 // either way. A gzip or zstd target cannot be concatenated: readcon-core
578 // reads a single gzip member, so appended members would be invisible
579 // on read-back, and the streaming writers cannot be flushed frame by frame.
580 // Those targets keep the whole-file rewrite, which stays O(N) per append.
581 std::vector<readcon::ConFrame> frames;
582 if (append && exists && !streamable) {
583 try {
584 // Prefer read_all_frames (single ownership hand-off) over the iterator
585 // path for append rewrite; simpler lifetime for ASAN/CI envs.
586 frames = readcon::read_all_frames(path);
587 } catch (const std::exception &e) {
588 EONC_LOG_ERROR("Failed to append to {}: {}", filename, e.what());
590 }
591 } else if (concatenate && !tail_is_ours(key, path)) {
592 // Refuse to extend a file eOn cannot parse, matching the rewrite path:
593 // the target keeps its bytes and the caller sees AppendError. Frames this
594 // process wrote and nobody touched since need no such check.
595 try {
596 [[maybe_unused]] const auto existing = readcon::read_all_frames(path);
597 } catch (const std::exception &e) {
598 EONC_LOG_ERROR("Failed to append to {}: {}", filename, e.what());
600 }
601 }
602 try {
603 frames.push_back(frame_from_matter(m, metadata, /*with_velocities=*/false));
604 } catch (const std::exception &e) {
605 EONC_LOG_ERROR("Failed to build frame for {}: {}", filename, e.what());
607 }
608
609 const auto status = concatenate ? append_frames(path, frames, kConPrecision)
610 : write_frames(path, frames, kConPrecision);
611 if (io_ok(status)) {
612 remember_stamp(key, path);
613 if (!concatenate) {
614 mirror_con_corpus(path.string());
615 }
616 } else {
617 append_stamps().erase(key);
618 }
619 return status;
620}
void applyPeriodicBoundaryIfEnabled()
Apply MIC wrap when periodic boundaries are enabled (I/O path).
Definition Matter.h:322

◆ matter2convel()

IoStatus eonc::io::matter2convel ( Matter & m,
std::string filename )
nodiscard

Definition at line 767 of file ConFileIO.cpp.

767 {
768 filename = ensure_extension(std::move(filename), ".convel");
769
771
772 try {
773 auto frame = frame_from_matter(m, nullptr, /*with_velocities=*/true);
774 std::vector<readcon::ConFrame> frames;
775 frames.push_back(std::move(frame));
776 const IoStatus status = write_frames(filename, frames, kConvelPrecision);
777 if (io_ok(status)) {
778 mirror_con_corpus(filename);
779 }
780 return status;
781 } catch (const std::exception &e) {
782 EONC_LOG_ERROR("Failed to write convel {}: {}", filename, e.what());
784 }
785}

◆ matter2xyz()

IoStatus eonc::io::matter2xyz ( Matter & m,
std::string filename,
bool append = false )
nodiscard

Write one extended-XYZ frame: Lattice= cell and 17-digit coordinates.

append concatenates another frame. A tail that does not end in a newline gets one before the next atom count, so the frames stay separate. A target whose last frame has a different atom count yields InvalidArgument and is left unchanged. Chemfiles in readcon is ingress (XYZ/PDB/GRO into ConFrame), not XYZ egress, so this writer emits the Lattice= comment ASE and chemfiles already read.

Definition at line 802 of file ConFileIO.cpp.

802 {
803 filename = ensure_extension(std::move(filename), ".xyz");
804
806 const long n = m.numberOfAtoms();
807
808 if (append && fs::exists(filename)) {
809 std::error_code ec;
810 const auto sz = fs::file_size(filename, ec);
811 if (!ec && sz > 0) {
812 const auto prev = last_xyz_natoms(filename);
813 if (!prev) {
814 EONC_LOG_ERROR("matter2xyz: cannot parse existing {}", filename);
816 }
817 if (*prev != n) {
819 "matter2xyz: append atom count {} != last frame {} in {}", n, *prev,
820 filename);
822 }
823 }
824 }
825
826 std::ofstream out;
827 out.open(filename,
828 append ? (std::ios::out | std::ios::app | std::ios::binary)
829 : (std::ios::out | std::ios::trunc | std::ios::binary));
830 if (!out) {
831 EONC_LOG_ERROR("matter2xyz: cannot open {}", filename);
832 return IoStatus::OpenError;
833 }
834
835 // A frame header must start its own line. eOn's own frames end in a
836 // newline; a hand-written or foreign tail may not.
837 if (append && !ends_with_newline(filename)) {
838 out.put('\n');
839 }
840
841 const Matrix3d cell = m.getCell();
842 out << std::format(
843 "{}\nLattice=\"{:.17g} {:.17g} {:.17g} {:.17g} {:.17g} {:.17g} "
844 "{:.17g} {:.17g} {:.17g}\" Properties=species:S:1:pos:R:3 Generated "
845 "by eOn\n",
846 n, cell(0, 0), cell(0, 1), cell(0, 2), cell(1, 0), cell(1, 1), cell(1, 2),
847 cell(2, 0), cell(2, 1), cell(2, 2));
848 const AtomMatrix pos = m.getPositions();
849 for (long i = 0; i < n; ++i) {
850 out << std::format("{}\t{:.17g}\t{:.17g}\t{:.17g}\n",
851 symbol_for_z(m.getAtomicNr(i)), pos(i, 0), pos(i, 1),
852 pos(i, 2));
853 }
854 // Close before testing: the destructor's flush is where a full disk or a
855 // short write surfaces, and by then the state is gone.
856 out.close();
857 if (!out) {
858 EONC_LOG_ERROR("matter2xyz: failed to write {}", filename);
860 }
861 return IoStatus::Ok;
862}
const AtomMatrix & getPositions() const
Definition Matter.cpp:308
long int numberOfAtoms() const
Definition Matter.cpp:273
long getAtomicNr(long int atom) const
Definition Matter.cpp:495

◆ matterToConFrame()

readcon::ConFrame eonc::io::matterToConFrame ( Matter & m,
const ConFrameMetadata * metadata = nullptr )
nodiscard

Build a single stamped ConFrame from Matter (same builder as matter2con).

Does not write to disk.

Definition at line 500 of file ConFileIO.cpp.

501 {
502 return frame_from_matter(m, metadata, /*with_velocities=*/false);
503}

◆ metadata_from_frame()

ConFrameMetadata eonc::io::metadata_from_frame ( const readcon::ConFrame & frame)

Extract known frame-level fields from a parsed readcon frame.

Definition at line 513 of file ConFileIO.cpp.

513 {
514 ConFrameMetadata meta;
515 meta.energy = frame.energy_opt();
516 meta.frame_index = frame.frame_index_opt();
517 meta.time = frame.time_opt();
518 meta.timestep = frame.timestep_opt();
519 meta.neb_bead = frame.neb_bead_opt();
520 meta.neb_band = frame.neb_band_opt();
521 meta.potential_type = frame.potential_type();
522 const auto json = frame.metadata_json();
523 if (!json.empty() && json != "{}") {
524 meta.raw_json = json;
525 }
526 return meta;
527}
nlohmann::json json
std::optional< uint64_t > frame_index
Definition ConFileIO.h:72
std::optional< uint64_t > neb_band
Definition ConFileIO.h:77
std::optional< double > energy
Definition ConFileIO.h:73
std::optional< double > timestep
Definition ConFileIO.h:75
std::optional< double > time
Definition ConFileIO.h:74
std::optional< std::string > raw_json
Definition ConFileIO.h:81
std::optional< std::string > potential_type
Definition ConFileIO.h:78
std::optional< uint64_t > neb_bead
Definition ConFileIO.h:76

◆ mirror_con_corpus()

void eonc::io::mirror_con_corpus ( const std::string & path)

Copy a con file into the run readcon-db corpus when libreadcon_db loads.

Failure to load the library or to insert the blob does not change IoStatus.

Definition at line 138 of file ReadconDbMirror.cpp.

138 {
139#if !defined(_WIN32)
140 namespace fs = std::filesystem;
141 std::error_code ec;
142 if (!fs::is_regular_file(path, ec) || ec) {
143 return;
144 }
145 std::ifstream in(path);
146 if (!in) {
147 return;
148 }
149 std::ostringstream buf;
150 buf << in.rdbuf();
151 const std::string text = buf.str();
152 if (text.find_first_not_of(" \t\r\n") == std::string::npos) {
153 return;
154 }
155 fs::path canonical = fs::weakly_canonical(path, ec);
156 if (ec) {
157 canonical = fs::absolute(path);
158 }
159 const std::string resolved = canonical.string();
160 const std::size_t id = corpus_handle(corpus_dir(canonical));
161 if (id == static_cast<std::size_t>(-1)) {
162 return;
163 }
164 const std::uint64_t tid = fnv1a(resolved + '\0' + text);
165 std::uint32_t nframes = 0;
166 api().append_str(id, tid, text.c_str(), resolved.c_str(), &nframes);
167#else
168 (void)path;
169#endif
170}

◆ resetConAppendState()

void eonc::io::resetConAppendState ( )

Drop the per-path bookkeeping that lets append-mode writes skip re-parsing frames this process wrote.

Appends already re-parse a target whose size or mtime moved, so this only matters when a file is replaced with different content of the same size inside one filesystem timestamp tick.

Definition at line 551 of file ConFileIO.cpp.

551 {
552 const std::lock_guard<std::mutex> guard(append_mutex());
553 append_stamps().clear();
554}

◆ set_write_con_forces()

void eonc::io::set_write_con_forces ( bool enabled)
noexcept

Whether written .con frames carry "Forces of Component" sections.

Force sections enable force+energy co-loading on re-read (warm NEB restarts without re-evaluating the potential) but are not part of the classic con layout: external readers such as ASE's eon parser reject frames that carry them. Off unless [Main] write_con_forces enables it. A ConFrameMetadata.write_con_forces value, then the Matter Parameters field, override this process-wide flag for a single write. Reading force-bearing frames is always supported regardless of this flag.

Definition at line 505 of file ConFileIO.cpp.

505 {
506 g_write_con_forces.store(enabled, std::memory_order_relaxed);
507}

◆ write_con_forces()

bool eonc::io::write_con_forces ( )
nodiscardnoexcept

Definition at line 509 of file ConFileIO.cpp.

509 {
510 return g_write_con_forces.load(std::memory_order_relaxed);
511}

◆ writeConFrames()

IoStatus eonc::io::writeConFrames ( std::string filename,
const std::vector< readcon::ConFrame > & frames )
nodiscard

Write already-built ConFrames to a multi-frame .con (temp or durable).

Definition at line 956 of file ConFileIO.cpp.

957 {
958 if (frames.empty()) {
960 }
961 filename = ensure_extension(std::move(filename), ".con");
962 const fs::path path(filename);
963 const std::lock_guard<std::mutex> guard(append_mutex());
964 const auto key = append_key(path);
965 const auto status = write_frames(path, frames, kConPrecision);
966 if (io_ok(status)) {
967 remember_stamp(key, path);
968 mirror_con_corpus(path.string());
969 } else {
970 append_stamps().erase(key);
971 }
972 return status;
973}

◆ writeNebPath()

IoStatus eonc::io::writeNebPath ( std::string filename,
const std::vector< std::shared_ptr< Matter > > & path,
const std::vector< ConFrameMetadata > & metadata_per_image )
nodiscard

Write a full NEB path as one multi-frame .con using ConFrameBuilder::clone().

Seeds identity (symbols/fixed/mass/id/cell headers) from path[0] only, then for each image clones that template and bulk-updates positions/forces + metadata. Images must share atom count and topology with path[0] (standard NEB band invariant); heterogeneous multi-frame movies should not use this. Avoids re-reading the output file per image (legacy append path).

Parameters
pathlength must be numImages+2 (endpoints included)
metadata_per_imagelength must equal path.size()

Definition at line 975 of file ConFileIO.cpp.

977 {
978 auto frames = buildNebPathFrames(path, metadata_per_image);
979 if (frames.empty()) {
981 }
982 return writeConFrames(std::move(filename), frames);
983}
IoStatus writeConFrames(std::string filename, const std::vector< readcon::ConFrame > &frames)
Write already-built ConFrames to a multi-frame .con (temp or durable).
std::vector< readcon::ConFrame > buildNebPathFrames(const std::vector< std::shared_ptr< Matter > > &path, const std::vector< ConFrameMetadata > &metadata_per_image)
Build NEB band ConFrames without writing (clone builder path of writeNebPath).

◆ writeTibble()

IoStatus eonc::io::writeTibble ( Matter & m,
std::string fname )
nodiscard

Debug table (positions, optional cached forces). Not a structure format.

Definition at line 864 of file ConFileIO.cpp.

864 {
865 // Debug table, not a structure format. getForces()/getPotentialEnergy()
866 // run computePotential() on a dirty pot, which would charge a dump to
867 // the force-call count in results.dat. Cached values only; the header
868 // drops the columns it cannot fill. atmID is the CON column-5 id.
869 const bool have_forces = !m.needsForceUpdate();
870 if (!have_forces) {
872 "writeTibble: pot is dirty, {} omits the force and energy columns",
873 fname);
874 }
875 const AtomMatrix pos = m.getPositions();
876 std::ofstream out(fname);
877 if (!out) {
878 EONC_LOG_ERROR("writeTibble: cannot open {}", fname);
879 return IoStatus::OpenError;
880 }
881 out << (have_forces ? "x y z fx fy fz energy mass symbol atmID fixed\n"
882 : "x y z mass symbol atmID fixed\n");
883 const AtomMatrix fSys = have_forces ? m.getForces() : AtomMatrix();
884 const double eSys = have_forces ? m.getPotentialEnergy() : 0.0;
885 for (long idx = 0; idx < m.numberOfAtoms(); ++idx) {
886 out << std::format("{} {} {}", pos(idx, 0), pos(idx, 1), pos(idx, 2));
887 if (have_forces) {
888 out << std::format(" {} {} {} {}", fSys(idx, 0), fSys(idx, 1),
889 fSys(idx, 2), eSys);
890 }
891 const auto mask = m.getFixedMask(idx);
892 const int fixed_bits =
893 (mask[0] ? 1 : 0) | (mask[1] ? 2 : 0) | (mask[2] ? 4 : 0);
894 out << std::format(" {} {} {} {}\n", m.getMass(idx),
895 symbol_for_z(m.getAtomicNr(idx)), m.getAtomIndex(idx),
896 fixed_bits);
897 }
898 out.close();
899 if (!out) {
900 EONC_LOG_ERROR("writeTibble: failed to write {}", fname);
902 }
903 return IoStatus::Ok;
904}
#define EONC_LOG_WARNING(...)
Definition EonLogger.h:255
double getMass(long int atom) const
Definition Matter.cpp:478
double getPotentialEnergy() const
Definition Matter.cpp:554
bool needsForceUpdate() const
Whether forces need recomputation (positions changed since last eval).
Definition Matter.h:211
std::array< bool, 3 > getFixedMask(long int atom) const
Per-axis CON column-4 mask (bit0=x, bit1=y, bit2=z).
Definition Matter.cpp:520
std::int64_t getAtomIndex(long int atom) const
.con column-5 index (pre-grouping); public for I/O / bindings.
Definition Matter.cpp:826
const AtomMatrix & getForces() const
Definition Matter.cpp:412