|
Bertini 2 d882924 (2026-10-06 19:56 UTC)
Numerical algebraic geometry library (C++ core)
|
One structured output directory: definitions/ + history/ + derived views. More...
#include <output_directory.hpp>
Public Member Functions | |
| OutputDirectory (std::filesystem::path root) | |
| Open (creating if needed) the output directory at root, writing the self-documenting README.txt on first creation. | |
| std::filesystem::path const & | Root () const |
| The directory's root path. | |
| std::string | PutDefinition (std::string const &content, std::string const &kind, std::optional< std::string > external_id=std::nullopt, std::string const &label={}) |
| Store a definition content-addressed; returns its id. | |
| std::string | GetDefinition (std::string const &id) const |
| Read a definition's bytes by id (searched across all kinds). Throws if absent. | |
| bool | HasDefinition (std::string const &id) const |
| Whether a definition with this id is present (searched across all kinds). | |
| void | Append (boost::json::object const &record) |
| Append one record to this session's history file. | |
| void | Annotate (std::string const &run_id, std::int64_t index, std::string const &key, boost::json::value const &value) |
| Attach metadata to a recorded point: appends an annotation record {"kind":"annotation", "point":{"run","index"}, "key", "value"}. | |
| std::vector< boost::json::object > | Scan () const |
| Every record from every history file, in filename-then-line order. | |
| void | EnsureResultsFile (std::string const &run_id, boost::json::object const &header) |
| Ensure the run's results file exists, with its self-description header as line 1: {"kind":"results_header","schema",...,"run","ask"}. | |
| void | AppendResult (std::string const &run_id, boost::json::object const &record) |
| Append one payload record to the run's results file (flushed per record, so a kill loses at most the line in flight). | |
| std::vector< boost::json::object > | ResultsOf (std::string const &run_id) const |
| Every record in the run's results file, header line included, in order. | |
| void | RefreshIndex () const |
| (Re)write INDEX.txt: one line per run — when, paths done, op, target description, run id. | |
| std::string | Describe () const |
| One human line: how much is here (record/file/definition counts + path). | |
Static Public Member Functions | |
| static std::shared_ptr< OutputDirectory > | Shared (std::filesystem::path const &root) |
| The process-shared OutputDirectory at root: one instance – hence one session history file – per directory per process, however many solvers attach. | |
One structured output directory: definitions/ + history/ + derived views.
Construction ensures the directory skeleton and its self-documenting README.txt exist. One OutputDirectory instance = one writing session = one history file at a time (claimed exclusively on first append, and claimed afresh if that file is deleted while the instance lives). Through Shared(), one session spans the whole process.
|
explicit |
Open (creating if needed) the output directory at root, writing the self-documenting README.txt on first creation.
| void bertini::records::OutputDirectory::Annotate | ( | std::string const & | run_id, |
| std::int64_t | index, | ||
| std::string const & | key, | ||
| boost::json::value const & | value ) |
Attach metadata to a recorded point: appends an annotation record {"kind":"annotation", "point":{"run","index"}, "key", "value"}.
Annotations are the audit trail's margin notes – projection values, "this is the one I meant", classification flags. Readers (e.g. bertini.load) merge them beside the point they describe. Newest wins per (point, key).
| run_id | The run the point belongs to. |
| index | The point's path index within that run. |
| key | The annotation's name. |
| value | Any JSON value. |
| void bertini::records::OutputDirectory::Append | ( | boost::json::object const & | record | ) |
Append one record to this session's history file.
The file is claimed on first append with an exclusive-create, date-named YYYYMMDD_HHMMSS-pid<pid>[suffix].jsonl (one writer per file, ever); each record is one compact JSON line, flushed per append so a kill loses at most the line being written (which Scan tolerates). Appending a run header refreshes INDEX.txt.
| void bertini::records::OutputDirectory::AppendResult | ( | std::string const & | run_id, |
| boost::json::object const & | record ) |
Append one payload record to the run's results file (flushed per record, so a kill loses at most the line in flight).
Tracked paths append {"kind":"path","run","index","status",...} lines carrying the endpoint AND its per-path metadata – the treasure travels with its facts. The kind vocabulary is open: future operations may append other payload kinds.
| run_id | The run whose file receives the record. |
| record | The payload record. |
| std::string bertini::records::OutputDirectory::Describe | ( | ) | const |
One human line: how much is here (record/file/definition counts + path).
| void bertini::records::OutputDirectory::EnsureResultsFile | ( | std::string const & | run_id, |
| boost::json::object const & | header ) |
Ensure the run's results file exists, with its self-description header as line 1: {"kind":"results_header","schema",...,"run","ask"}.
The file lives at results/<2 hex>/<run id>.jsonl (sharded like definitions/). Creation is exclusive-create, so concurrent writers race benignly: exactly one writes the header. Idempotent – an existing file (a resumed run) is untouched. Written BEFORE the history run header that refers to it, so a reference never dangles.
| run_id | The run id (lowercase hex; anything else is refused – it becomes a path). |
| header | The self-description object written as line 1. |
| std::string bertini::records::OutputDirectory::GetDefinition | ( | std::string const & | id | ) | const |
Read a definition's bytes by id (searched across all kinds). Throws if absent.
| bool bertini::records::OutputDirectory::HasDefinition | ( | std::string const & | id | ) | const |
Whether a definition with this id is present (searched across all kinds).
| std::string bertini::records::OutputDirectory::PutDefinition | ( | std::string const & | content, |
| std::string const & | kind, | ||
| std::optional< std::string > | external_id = std::nullopt, | ||
| std::string const & | label = {} ) |
Store a definition content-addressed; returns its id.
| content | The definition's bytes (usually human-readable text). |
| kind | The human-navigable subfolder the definition lives in: "systems", "configs", or "givens" (an open vocabulary – any lowercase name works; these three are the ones bertini writes). Inside each kind, files shard by the id's first two hex characters so no directory grows unbounded. |
| external_id | If given, store under that id; otherwise the id is the SHA-256 of the bytes (self-verifying: sha256sum of the file reproduces its name). |
| label | Optional role label woven into the filename (e.g. "cli_input" gives given-cli_input-<digest>.txt), so a listing says what each file IS without opening it. Presentation only, never identity. |
Atomic (write-temp + rename) and idempotent: equal content lands at an equal path, so concurrent writers race benignly. Ids are resolved WITHOUT the kind (records reference bare ids); the kind is presentation, not identity.
| void bertini::records::OutputDirectory::RefreshIndex | ( | ) | const |
(Re)write INDEX.txt: one line per run — when, paths done, op, target description, run id.
Derived from the records; never crashes a solve (defensive against malformed records).
| std::vector< boost::json::object > bertini::records::OutputDirectory::ResultsOf | ( | std::string const & | run_id | ) | const |
Every record in the run's results file, header line included, in order.
Same tolerance rules as Scan: a torn final line is skipped, a torn interior line throws. An absent file (nothing recorded for this run) is an empty vector.
| run_id | The run to read. |
|
inline |
The directory's root path.
| std::vector< boost::json::object > bertini::records::OutputDirectory::Scan | ( | ) | const |
Every record from every history file, in filename-then-line order.
A torn FINAL line of a file is skipped (the crash-mid-append case); a torn interior line throws (real corruption). Records with unrecognized kinds are preserved.
|
static |
The process-shared OutputDirectory at root: one instance – hence one session history file – per directory per process, however many solvers attach.
Each instance claims its own session history file on first append, so anything constructing many writers in a loop (a parameter sweep attaching the ambient records to every solver, say) must share one instance: the per-second claim namespace is finite, and the history should not be shredded across a file per solve. Keyed by the weakly-canonical path.
The instance is held for the life of the process, not only while someone holds it: a sweep's solvers attach and release one after another, and a new instance per solve is a new session file per solve. A fresh instance replaces the held one when the directory has been deleted since, or when the caller is a different process (a forked child must not write into its parent's session file).
What callers receive is a lease on that instance, shared by everyone using the directory at the same time. When the last holder lets go, the instance closes its files at once, keeping its session (it reopens the same session file on its next record). So a directory nobody is writing to holds no open files: a process that records into thousands of directories does not run out of file handles, and on Windows, which cannot delete an open file, a finished solve's directory can be deleted while the process lives. Instances whose directory is gone and that nobody is using are dropped.
| root | The directory root (need not exist yet). |