From e7524f1f827373529d7ea5133ee2a9070c3041e6 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 May 2026 17:28:21 -0600 Subject: [PATCH 01/15] get compiling with new rummy version --- src/parameter_input.cpp | 26 ++++++++++++ src/parameter_input.hpp | 16 +++++++ src/parameter_parsers/rummy_parser.cpp | 59 ++++++++++++++++++++------ src/parameter_parsers/rummy_parser.hpp | 24 ++++++++--- src/parthenon_manager.cpp | 6 ++- src/parthenon_manager.hpp | 4 +- 6 files changed, 112 insertions(+), 23 deletions(-) diff --git a/src/parameter_input.cpp b/src/parameter_input.cpp index 4f772862d54a2..776a11e6209db 100644 --- a/src/parameter_input.cpp +++ b/src/parameter_input.cpp @@ -917,6 +917,32 @@ ParameterInput::GetBlockNamesWithPrefix(const std::string &prefix) const { return matching_blocks; } +//---------------------------------------------------------------------------------------- +//! \fn std::vector ParameterInput::GetBlocksOfClass() +// \brief Return all block names whose rummy class metadata equals `class_name`. + +std::vector +ParameterInput::GetBlocksOfClass(const std::string &class_name) const { + std::vector matching_blocks; + for (const auto &block : param_storage_) { + if (block.class_name == class_name) matching_blocks.push_back(block.name); + } + return matching_blocks; +} + +//---------------------------------------------------------------------------------------- +//! \fn void ParameterInput::SetBlockClassMetadata() +// \brief Record the rummy class/instance metadata for an existing block. + +void ParameterInput::SetBlockClassMetadata(const std::string &block, + const std::string &class_name, + const std::string &instance_name) { + auto it = block_index_.find(block); + if (it == block_index_.end()) return; + param_storage_[it->second].class_name = class_name; + param_storage_[it->second].instance_name = instance_name; +} + //---------------------------------------------------------------------------------------- //! \fn std::vector ParameterInput::GetParameterNames() // \brief Return all parameter names in the given block diff --git a/src/parameter_input.hpp b/src/parameter_input.hpp index 0c4eab8d8b194..4a36e34f5183f 100644 --- a/src/parameter_input.hpp +++ b/src/parameter_input.hpp @@ -194,6 +194,12 @@ struct Parameter { struct Block { std::string name; + // Optional metadata describing the rummy class backing this block. Only + // populated by the FullDeck parser (e.g. a `` + // header records class_name="output", instance_name="output1"). Empty + // for the SimpleDeck parser and the legacy text parser. + std::string class_name; + std::string instance_name; std::vector params; // Ordered storage (for iteration) std::unordered_map param_index; // Fast lookup within block (stores indices) @@ -238,8 +244,18 @@ class ParameterInput { // === QUERY INTERFACE (parser-agnostic) === std::vector GetBlockNames() const; std::vector GetBlockNamesWithPrefix(const std::string &prefix) const; + // Return the names of all blocks whose rummy class metadata matches + // `class_name`. Only meaningful for inputs parsed by the FullDeck parser; + // returns an empty vector for SimpleDeck/legacy inputs. + std::vector GetBlocksOfClass(const std::string &class_name) const; std::vector GetParameterNames(const std::string &block) const; + // Set the rummy class/instance metadata for a parsed block. Used by the + // rummy parser when populating from a FullDeck; ignored for callers that + // do not need class introspection. + void SetBlockClassMetadata(const std::string &block, const std::string &class_name, + const std::string &instance_name); + void ParameterDump(std::ostream &os); // TODO(JMM): Make this more general? void OutputParameterTable(std::ostream &os, diff --git a/src/parameter_parsers/rummy_parser.cpp b/src/parameter_parsers/rummy_parser.cpp index 16200a628559e..341fb91bffd67 100644 --- a/src/parameter_parsers/rummy_parser.cpp +++ b/src/parameter_parsers/rummy_parser.cpp @@ -21,7 +21,8 @@ #include #include -#include +#include +#include #include "parameter_input.hpp" #include "rummy_parser.hpp" @@ -72,18 +73,34 @@ Rummy::Card ParamValueToRummyCard(const std::string &suit, const std::string &na return Rummy::Card(suit, name, trimmed, ""); } -void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync) { - Rummy::Deck deck; +namespace { +// Construct a deck of the requested flavor. Returned via unique_ptr to the +// shared DeckBase API so the rest of the parser stays flavor-agnostic. +std::unique_ptr MakeDeck(RummyDeckType deck_type) { + switch (deck_type) { + case RummyDeckType::Full: + return std::make_unique(); + case RummyDeckType::Simple: + default: + return std::make_unique(); + } +} +} // namespace + +void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + RummyDeckType deck_type) { + auto deck = MakeDeck(deck_type); if (sync) { - SyncDeckFromStorage(pin, deck); + SyncDeckFromStorage(pin, *deck); } - deck.Build(ss); - AddRummyParameters(pin, deck); + deck->Build(ss); + AddRummyParameters(pin, *deck); } void LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, - const std::vector &mods, const bool is_restart) { - Rummy::Deck deck; + const std::vector &mods, const bool is_restart, + RummyDeckType deck_type) { + auto deck = MakeDeck(deck_type); const bool no_inputs = files.empty() && mods.empty(); if (no_inputs) { @@ -92,7 +109,7 @@ void LoadParameterFromRummy(ParameterInput &pin, const std::vector if (is_restart) { // If this is a restart, we need to sync the deck with the existing parameters - SyncDeckFromStorage(pin, deck); + SyncDeckFromStorage(pin, *deck); } // concatenate all input files and mods into a single stream for parsing @@ -111,14 +128,18 @@ void LoadParameterFromRummy(ParameterInput &pin, const std::vector contents << mod << " # From command line\n"; } - deck.Build(contents); + deck->Build(contents); - AddRummyParameters(pin, deck); + AddRummyParameters(pin, *deck); } -void AddRummyParameters(ParameterInput &pin, Rummy::Deck &deck) { +void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck) { static const std::regex kVectorCardPattern(R"(^(.+)\[(\d+)\]$)"); + // If the deck is a FullDeck, capture per-suit class metadata so callers can + // query blocks by their pips class via ParameterInput::GetBlocksOfClass. + auto *full_deck = dynamic_cast(&deck); + for (const auto &suit_name : deck.GetSuitsInOrder()) { const std::string &block_name = suit_name; const auto &suit_cards = deck.GetCardsInOrder(suit_name); @@ -153,6 +174,18 @@ void AddRummyParameters(ParameterInput &pin, Rummy::Deck &deck) { comment); } } + if (full_deck != nullptr) { + const std::string class_name = full_deck->GetClassName(suit_name); + if (!class_name.empty()) { + // Instance name is the last '/'-separated segment of the suit path. + std::string instance_name = suit_name; + const auto last_slash = suit_name.find_last_of('/'); + if (last_slash != std::string::npos) { + instance_name = suit_name.substr(last_slash + 1); + } + pin.SetBlockClassMetadata(block_name, class_name, instance_name); + } + } } } @@ -253,7 +286,7 @@ bool IsRummyFormat(const std::string &filename) { //---------------------------------------------------------------------------------------- //! \fn void ParameterInput::SyncDeckFromStorage() // \brief Seed the Rummy Deck from the current param_storage_ contents. -void SyncDeckFromStorage(ParameterInput &pin, Rummy::Deck &deck) { +void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck) { std::map> new_cards; std::vector new_suits; std::map> new_card_map; diff --git a/src/parameter_parsers/rummy_parser.hpp b/src/parameter_parsers/rummy_parser.hpp index 890b3f654a35d..bfad066bb844b 100644 --- a/src/parameter_parsers/rummy_parser.hpp +++ b/src/parameter_parsers/rummy_parser.hpp @@ -21,16 +21,26 @@ #include "parameter_input.hpp" -// Foward declare Rummy::Deck to avoid including the full header in this file +// Forward declare Rummy deck classes to avoid including full headers here. namespace Rummy { -class Deck; -} +class DeckBase; +class SimpleDeck; +class FullDeck; +} // namespace Rummy namespace parthenon { + +// Selects the rummy deck flavor used to parse input files at runtime. Simple +// is the legacy flat key=value parser; Full enables the pips-backed parser +// (control flow, user classes, instantiation headers, etc.). +enum class RummyDeckType { Simple, Full }; + void LoadParameterFromRummy(ParameterInput &input, const std::vector &files, - const std::vector &mods, const bool is_restart); -void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync); -void AddRummyParameters(ParameterInput &pin, Rummy::Deck &deck); -void SyncDeckFromStorage(ParameterInput &pin, Rummy::Deck &deck); + const std::vector &mods, const bool is_restart, + RummyDeckType deck_type = RummyDeckType::Simple); +void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + RummyDeckType deck_type = RummyDeckType::Simple); +void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck); +void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck); bool IsRummyFormat(const std::string &filename); bool IsRummyFormat(std::istream &is, const bool command_line); } // namespace parthenon diff --git a/src/parthenon_manager.cpp b/src/parthenon_manager.cpp index 9869e03112ba1..3c6829eabbe27 100644 --- a/src/parthenon_manager.cpp +++ b/src/parthenon_manager.cpp @@ -50,7 +50,8 @@ namespace fs = FS_NAMESPACE; namespace parthenon { -ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[]) { +ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], + RummyDeckType deck_type) { if (called_init_env_) { PARTHENON_THROW("ParthenonInitEnv called twice!"); } @@ -143,7 +144,8 @@ ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[]) { pinput = std::make_unique(); } if (is_rummy) { - LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart); + LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, + deck_type); } else { for (const auto &input_filename : arg.input_filenames) { pinput->ReadFile(input_filename); diff --git a/src/parthenon_manager.hpp b/src/parthenon_manager.hpp index 6c8e0d4f01d35..153e4b414b73a 100644 --- a/src/parthenon_manager.hpp +++ b/src/parthenon_manager.hpp @@ -30,6 +30,7 @@ #include "mesh/mesh.hpp" #include "outputs/restart.hpp" #include "parameter_input.hpp" +#include "parameter_parsers/rummy_parser.hpp" #include "utils/error_checking.hpp" #include "utils/utils.hpp" @@ -40,7 +41,8 @@ enum class ParthenonStatus { ok, complete, error }; class ParthenonManager { public: ParthenonManager() { app_input.reset(new ApplicationInput()); } - ParthenonStatus ParthenonInitEnv(int argc, char *argv[]); + ParthenonStatus ParthenonInitEnv(int argc, char *argv[], + RummyDeckType deck_type = RummyDeckType::Simple); void ParthenonInitPackagesAndMesh(std::optional forest_def = {}); ParthenonStatus ParthenonFinalize(); From 0837ce51d0b8d56e5984cf5375d4f7b918f726d7 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 31 May 2026 16:42:30 -0600 Subject: [PATCH 02/15] Update rummy --- external/rummy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/rummy b/external/rummy index 6734a06292b70..78239519ca221 160000 --- a/external/rummy +++ b/external/rummy @@ -1 +1 @@ -Subproject commit 6734a06292b70fc2cbb756e66a073fc7c060013c +Subproject commit 78239519ca2210197ed539e148fd8e918cc9f4e4 From 42cbebd53a5cc884ac703011dc80cf7e1f88e1d0 Mon Sep 17 00:00:00 2001 From: Adam Date: Sun, 31 May 2026 16:44:52 -0600 Subject: [PATCH 03/15] Add option to select input deck type in initenv --- src/parameter_parsers/rummy_parser.cpp | 88 +++++++++++++++++++++++--- src/parameter_parsers/rummy_parser.hpp | 24 +++++-- src/parthenon_manager.cpp | 25 +++++++- src/parthenon_manager.hpp | 10 ++- 4 files changed, 126 insertions(+), 21 deletions(-) diff --git a/src/parameter_parsers/rummy_parser.cpp b/src/parameter_parsers/rummy_parser.cpp index 341fb91bffd67..07b5c0a8eec4c 100644 --- a/src/parameter_parsers/rummy_parser.cpp +++ b/src/parameter_parsers/rummy_parser.cpp @@ -13,6 +13,8 @@ // This file was made in part with generative AI. #include +#include +#include #include #include #include @@ -74,22 +76,45 @@ Rummy::Card ParamValueToRummyCard(const std::string &suit, const std::string &na } namespace { -// Construct a deck of the requested flavor. Returned via unique_ptr to the -// shared DeckBase API so the rest of the parser stays flavor-agnostic. -std::unique_ptr MakeDeck(RummyDeckType deck_type) { + +std::unique_ptr MakeDeck(InputDeckType /*deck_type*/, + std::istream &schema_stream) { + std::string schema_text((std::istreambuf_iterator(schema_stream)), + std::istreambuf_iterator()); + auto schema = Rummy::Schema::FromString(schema_text); + return std::make_unique(Rummy::FullDeck::Mode::Strict, + std::move(schema)); +} + +// Construct a deck of the requested type +std::unique_ptr MakeDeck(InputDeckType deck_type, + const std::string &schema_path = "") { switch (deck_type) { - case RummyDeckType::Full: - return std::make_unique(); - case RummyDeckType::Simple: + case InputDeckType::RummyFullLoose: + return std::make_unique(Rummy::FullDeck::Mode::Loose); + case InputDeckType::RummyFullStrict: { + if (schema_path.empty()) { + PARTHENON_FAIL("InputDeckType::RummyFullStrict requires a non-empty schema_path"); + } + std::ifstream f(schema_path); + if (!f.is_open()) { + std::stringstream msg; + msg << "Could not open schema file '" << schema_path << "'"; + PARTHENON_FAIL(msg); + } + return MakeDeck(deck_type, static_cast(f)); + } + case InputDeckType::RummySimple: default: return std::make_unique(); } } + } // namespace void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, - RummyDeckType deck_type) { - auto deck = MakeDeck(deck_type); + InputDeckType deck_type, const std::string &schema_path) { + auto deck = MakeDeck(deck_type, schema_path); if (sync) { SyncDeckFromStorage(pin, *deck); } @@ -99,8 +124,8 @@ void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sy void LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, const std::vector &mods, const bool is_restart, - RummyDeckType deck_type) { - auto deck = MakeDeck(deck_type); + InputDeckType deck_type, const std::string &schema_path) { + auto deck = MakeDeck(deck_type, schema_path); const bool no_inputs = files.empty() && mods.empty(); if (no_inputs) { @@ -133,6 +158,49 @@ void LoadParameterFromRummy(ParameterInput &pin, const std::vector AddRummyParameters(pin, *deck); } +void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + InputDeckType deck_type, std::istream &schema_stream) { + auto deck = MakeDeck(deck_type, schema_stream); + if (sync) { + SyncDeckFromStorage(pin, *deck); + } + deck->Build(ss); + AddRummyParameters(pin, *deck); +} + +void LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, + const std::vector &mods, const bool is_restart, + InputDeckType deck_type, std::istream &schema_stream) { + auto deck = MakeDeck(deck_type, schema_stream); + + const bool no_inputs = files.empty() && mods.empty(); + if (no_inputs) { + return; + } + + if (is_restart) { + SyncDeckFromStorage(pin, *deck); + } + + std::stringstream contents; + for (const auto &file : files) { + std::ifstream input_file(file); + if (input_file.is_open()) { + contents << input_file.rdbuf() << "\n"; + } else { + std::stringstream msg; + msg << "Could not open file '" << file << "'"; + PARTHENON_FAIL(msg); + } + } + for (const auto &mod : mods) { + contents << mod << " # From command line\n"; + } + + deck->Build(contents); + AddRummyParameters(pin, *deck); +} + void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck) { static const std::regex kVectorCardPattern(R"(^(.+)\[(\d+)\]$)"); diff --git a/src/parameter_parsers/rummy_parser.hpp b/src/parameter_parsers/rummy_parser.hpp index bfad066bb844b..6ddc42ec3b3db 100644 --- a/src/parameter_parsers/rummy_parser.hpp +++ b/src/parameter_parsers/rummy_parser.hpp @@ -15,6 +15,7 @@ #ifndef PARAMETER_PARSERS_RUMMY_PARSER_HPP_ #define PARAMETER_PARSERS_RUMMY_PARSER_HPP_ +#include #include #include #include @@ -28,17 +29,26 @@ class SimpleDeck; class FullDeck; } // namespace Rummy namespace parthenon { +enum class InputDeckType { + Native = 0, + RummySimple = 1, + RummyFullLoose = 2, + RummyFullStrict = 3, + RummyFullSchema = 4, +}; -// Selects the rummy deck flavor used to parse input files at runtime. Simple -// is the legacy flat key=value parser; Full enables the pips-backed parser -// (control flow, user classes, instantiation headers, etc.). -enum class RummyDeckType { Simple, Full }; - void LoadParameterFromRummy(ParameterInput &input, const std::vector &files, const std::vector &mods, const bool is_restart, - RummyDeckType deck_type = RummyDeckType::Simple); + InputDeckType deck_type = InputDeckType::RummySimple, + const std::string &schema_path = ""); +void LoadParameterFromRummy(ParameterInput &input, const std::vector &files, + const std::vector &mods, const bool is_restart, + InputDeckType deck_type, std::istream &schema_stream); +void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + InputDeckType deck_type = InputDeckType::RummySimple, + const std::string &schema_path = ""); void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, - RummyDeckType deck_type = RummyDeckType::Simple); + InputDeckType deck_type, std::istream &schema_stream); void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck); void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck); bool IsRummyFormat(const std::string &filename); diff --git a/src/parthenon_manager.cpp b/src/parthenon_manager.cpp index 3c6829eabbe27..e3ae611b5304c 100644 --- a/src/parthenon_manager.cpp +++ b/src/parthenon_manager.cpp @@ -51,7 +51,21 @@ namespace fs = FS_NAMESPACE; namespace parthenon { ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], - RummyDeckType deck_type) { + InputDeckType deck_type, + const std::string &schema_path) { + return ParthenonInitEnvCore_(argc, argv, deck_type, schema_path, nullptr); +} + +ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], + InputDeckType deck_type, + std::istream &schema_stream) { + return ParthenonInitEnvCore_(argc, argv, deck_type, "", &schema_stream); +} + +ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], + InputDeckType deck_type, + const std::string &schema_path, + std::istream *schema_stream) { if (called_init_env_) { PARTHENON_THROW("ParthenonInitEnv called twice!"); } @@ -144,8 +158,13 @@ ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], pinput = std::make_unique(); } if (is_rummy) { - LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, - deck_type); + if (schema_stream != nullptr) { + LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, + deck_type, *schema_stream); + } else { + LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, + deck_type, schema_path); + } } else { for (const auto &input_filename : arg.input_filenames) { pinput->ReadFile(input_filename); diff --git a/src/parthenon_manager.hpp b/src/parthenon_manager.hpp index 153e4b414b73a..497a5791217ad 100644 --- a/src/parthenon_manager.hpp +++ b/src/parthenon_manager.hpp @@ -42,7 +42,10 @@ class ParthenonManager { public: ParthenonManager() { app_input.reset(new ApplicationInput()); } ParthenonStatus ParthenonInitEnv(int argc, char *argv[], - RummyDeckType deck_type = RummyDeckType::Simple); + InputDeckType deck_type = InputDeckType::Native, + const std::string &schema_path = ""); + ParthenonStatus ParthenonInitEnv(int argc, char *argv[], InputDeckType deck_type, + std::istream &schema_stream); void ParthenonInitPackagesAndMesh(std::optional forest_def = {}); ParthenonStatus ParthenonFinalize(); @@ -64,6 +67,11 @@ class ParthenonManager { bool called_init_env_ = false; bool called_init_packages_and_mesh_ = false; + // Shared implementation for both ParthenonInitEnv overloads. + ParthenonStatus ParthenonInitEnvCore_(int argc, char *argv[], InputDeckType deck_type, + const std::string &schema_path, + std::istream *schema_stream); + template void ReadSwarmVars_(const SP_Swarm &pswarm, const BlockList_t &block_list, const std::size_t count_on_rank, const std::size_t offset) { From ce33f8a47c2c7dba9e211cdfdbc146c90392fcf7 Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Fri, 24 Jul 2026 13:42:26 -0600 Subject: [PATCH 04/15] Updates for full deck support --- src/parameter_parsers/rummy_parser.cpp | 203 +++++++++++++++++-------- src/parameter_parsers/rummy_parser.hpp | 42 +++-- 2 files changed, 172 insertions(+), 73 deletions(-) diff --git a/src/parameter_parsers/rummy_parser.cpp b/src/parameter_parsers/rummy_parser.cpp index 07b5c0a8eec4c..06199a8d81993 100644 --- a/src/parameter_parsers/rummy_parser.cpp +++ b/src/parameter_parsers/rummy_parser.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -31,6 +32,18 @@ namespace parthenon { +InputDeckType ToInputDeckType(RummyMode mode) { + switch (mode) { + case RummyMode::Simple: + return InputDeckType::RummySimple; + case RummyMode::FullLoose: + return InputDeckType::RummyFullLoose; + case RummyMode::FullStrict: + return InputDeckType::RummyFullStrict; + } + PARTHENON_FAIL("Unknown RummyMode"); +} + //! \fn ParameterInput::ParamValue RummyCardToParamValue(const Rummy::Card &card) // \brief Convert a Rummy Card to a ParameterInput::ParamValue for storage in // ParameterInput. @@ -45,6 +58,35 @@ ParamValue RummyCardToParamValue(const Rummy::Card &card) { } } +UnresolvedScalar RummyCardToUnresolvedScalar(const Rummy::Card &card) { + if (card.isBool()) return card.Get(); + if (card.isString()) return card.Get(); + return UnresolvedString(card.GetString(std::numeric_limits::max_digits10)); +} + +UnresolvedVector RummyVectorToParamValue(const Rummy::DeckBase &deck, + const std::string &suit, + const std::string &name) { + UnresolvedVector result; + const auto &cards = deck.GetSuit(suit); + auto direct = cards.find(name); + if (direct != cards.end() && + direct->second.GetValue().type == pips::ValueType::VECTOR && + direct->second.GetValue().as.vector != nullptr) { + for (const auto &element : direct->second.GetValue().as.vector->elements) { + result.values.emplace_back(RummyCardToUnresolvedScalar(Rummy::Card("", name, element, ""))); + } + return result; + } + for (std::size_t i = 0;; ++i) { + const std::string indexed = name + "[" + std::to_string(i) + "]"; + auto it = cards.find(indexed); + if (it == cards.end()) break; + result.values.emplace_back(RummyCardToUnresolvedScalar(it->second)); + } + return result; +} + //! \fn Rummy::Card ParamValueToRummyCard(suit, name, v) // \brief Convert a scalar ParamValue to a Rummy::Card. Rummy::Card ParamValueToRummyCard(const std::string &suit, const std::string &name, @@ -69,7 +111,7 @@ Rummy::Card ParamValueToRummyCard(const std::string &suit, const std::string &na try { std::size_t pos; double d = std::stod(trimmed, &pos); - return Rummy::Card(suit, name, d, ""); + if (pos == trimmed.size()) return Rummy::Card(suit, name, d, ""); } catch (...) { } return Rummy::Card(suit, name, trimmed, ""); @@ -104,6 +146,9 @@ std::unique_ptr MakeDeck(InputDeckType deck_type, } return MakeDeck(deck_type, static_cast(f)); } + case InputDeckType::RummyFullSchema: + PARTHENON_FAIL("InputDeckType::RummyFullSchema is unsupported. Use " + "InputDeckOptions{..., RummyMode::FullStrict, schema_path}."); case InputDeckType::RummySimple: default: return std::make_unique(); @@ -112,24 +157,27 @@ std::unique_ptr MakeDeck(InputDeckType deck_type, } // namespace -void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, - InputDeckType deck_type, const std::string &schema_path) { +std::unique_ptr +LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + InputDeckType deck_type, const std::string &schema_path) { auto deck = MakeDeck(deck_type, schema_path); if (sync) { SyncDeckFromStorage(pin, *deck); } deck->Build(ss); AddRummyParameters(pin, *deck); + return deck; } -void LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, - const std::vector &mods, const bool is_restart, - InputDeckType deck_type, const std::string &schema_path) { +std::unique_ptr +LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, + const std::vector &mods, const bool is_restart, + InputDeckType deck_type, const std::string &schema_path) { auto deck = MakeDeck(deck_type, schema_path); const bool no_inputs = files.empty() && mods.empty(); if (no_inputs) { - return; + return deck; } if (is_restart) { @@ -137,103 +185,110 @@ void LoadParameterFromRummy(ParameterInput &pin, const std::vector SyncDeckFromStorage(pin, *deck); } - // concatenate all input files and mods into a single stream for parsing - std::stringstream contents; + std::vector sources; for (const auto &file : files) { std::ifstream input_file(file); if (input_file.is_open()) { - contents << input_file.rdbuf() << "\n"; + std::stringstream contents; + contents << input_file.rdbuf(); + sources.push_back( + {file, contents.str(), std::filesystem::path(file).parent_path().string()}); } else { std::stringstream msg; msg << "Could not open file '" << file << "'"; PARTHENON_FAIL(msg); } } - for (const auto &mod : mods) { - contents << mod << " # From command line\n"; + if (!mods.empty()) { + std::stringstream contents; + for (const auto &mod : mods) contents << mod << " # From command line\n"; + sources.push_back({"", contents.str(), ""}); } - - deck->Build(contents); - + deck->BuildSources(sources); AddRummyParameters(pin, *deck); + return deck; } -void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, - InputDeckType deck_type, std::istream &schema_stream) { +std::unique_ptr +LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + InputDeckType deck_type, std::istream &schema_stream) { auto deck = MakeDeck(deck_type, schema_stream); if (sync) { SyncDeckFromStorage(pin, *deck); } deck->Build(ss); AddRummyParameters(pin, *deck); + return deck; } -void LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, - const std::vector &mods, const bool is_restart, - InputDeckType deck_type, std::istream &schema_stream) { +std::unique_ptr +LoadParameterFromRummy(ParameterInput &pin, const std::vector &files, + const std::vector &mods, const bool is_restart, + InputDeckType deck_type, std::istream &schema_stream) { auto deck = MakeDeck(deck_type, schema_stream); const bool no_inputs = files.empty() && mods.empty(); if (no_inputs) { - return; + return deck; } if (is_restart) { SyncDeckFromStorage(pin, *deck); } - std::stringstream contents; + std::vector sources; for (const auto &file : files) { std::ifstream input_file(file); if (input_file.is_open()) { - contents << input_file.rdbuf() << "\n"; + std::stringstream contents; + contents << input_file.rdbuf(); + sources.push_back( + {file, contents.str(), std::filesystem::path(file).parent_path().string()}); } else { std::stringstream msg; msg << "Could not open file '" << file << "'"; PARTHENON_FAIL(msg); } } - for (const auto &mod : mods) { - contents << mod << " # From command line\n"; + if (!mods.empty()) { + std::stringstream contents; + for (const auto &mod : mods) contents << mod << " # From command line\n"; + sources.push_back({"", contents.str(), ""}); } - - deck->Build(contents); + deck->BuildSources(sources); AddRummyParameters(pin, *deck); + return deck; } void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck) { - static const std::regex kVectorCardPattern(R"(^(.+)\[(\d+)\]$)"); - // If the deck is a FullDeck, capture per-suit class metadata so callers can // query blocks by their pips class via ParameterInput::GetBlocksOfClass. auto *full_deck = dynamic_cast(&deck); for (const auto &suit_name : deck.GetSuitsInOrder()) { const std::string &block_name = suit_name; + std::string class_name; + std::string canonical_path; + if (full_deck != nullptr) { + class_name = full_deck->GetClassName(suit_name); + canonical_path = full_deck->GetCanonicalPath(suit_name); + } + std::string instance_name = suit_name; + const auto last_slash = suit_name.find_last_of('/'); + if (last_slash != std::string::npos) instance_name = suit_name.substr(last_slash + 1); + pin.AddParsedBlock(block_name, class_name, instance_name, canonical_path); const auto &suit_cards = deck.GetCardsInOrder(suit_name); for (const auto &card_name : suit_cards) { // match for vector if (deck.IsCardVector(suit_name, card_name)) { - std::vector comments; - auto elements = deck.GetVector(suit_name, card_name, comments); - std::string joined; - std::string joined_comments; - for (std::size_t i = 0; i < elements.size(); ++i) { - if (comments[i] != "") { - if (i > 0) { - joined_comments += " "; - } - joined_comments += comments[i]; - } - if (i > 0) { - joined += ","; - } - joined += elements[i]; - } - // Rummy stores comments without '#' std::string comment; - if (!joined_comments.empty()) comment = "# " + joined_comments; - pin.AddParsedParameter(block_name, card_name, UnresolvedString(joined), comment); + const auto &cards = deck.GetSuit(suit_name); + auto first = cards.find(card_name + "[0]"); + if (first == cards.end()) first = cards.find(card_name); + if (first != cards.end() && !first->second.GetComment().empty()) + comment = "# " + first->second.GetComment(); + pin.AddParsedParameter(block_name, card_name, + RummyVectorToParamValue(deck, suit_name, card_name), comment); } else { auto &card = deck.GetCard(suit_name, card_name); std::string comment; @@ -242,18 +297,6 @@ void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck) { comment); } } - if (full_deck != nullptr) { - const std::string class_name = full_deck->GetClassName(suit_name); - if (!class_name.empty()) { - // Instance name is the last '/'-separated segment of the suit path. - std::string instance_name = suit_name; - const auto last_slash = suit_name.find_last_of('/'); - if (last_slash != std::string::npos) { - instance_name = suit_name.substr(last_slash + 1); - } - pin.SetBlockClassMetadata(block_name, class_name, instance_name); - } - } } } @@ -303,12 +346,24 @@ bool IsRummyFormat(std::istream &is, const bool command_line) { } if (line.compare(first_char, 1, "<") == 0) { - if (line.size() > first_char + 2 && line.compare(first_char + 1, 2, "..") == 0) { + const auto close = line.find('>', first_char + 1); + const std::string header = line.substr(first_char + 1, + close == std::string::npos + ? std::string::npos + : close - first_char - 1); + if (header.rfind("./", 0) == 0 || header.rfind("../", 0) == 0 || + header.find('(') != std::string::npos) { return restore_and_return(true); } found_block = true; continue; } + const auto token_end = line.find_first_of(" \t{"); + const std::string token = line.substr(first_char, token_end - first_char); + if (token == "include" || token == "setattr" || token == "for" || token == "while" || + token == "if" || token == "fn" || token == "class") { + return restore_and_return(true); + } // Non-comment, non-blank content before the first block = Rummy global variable // Disable for command line modifications @@ -370,6 +425,14 @@ void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck) { new_cards[suit][card_name] = std::move(card); }; + auto register_suit = [&](const std::string &suit) { + if (new_cards.find(suit) == new_cards.end()) { + new_cards[suit] = {}; + new_suits.push_back(suit); + new_card_map[suit] = {}; + } + }; + for (const auto &block : pin.GetBlocks()) { // Collapse the block name into a Rummy suit: non-empty '/' segments joined by '/'. // A block that is only "/" (global scope) maps to suit "/". @@ -387,6 +450,8 @@ void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck) { if (!assembled.empty()) suit = assembled; } + register_suit(suit); + for (const auto ¶m : block.params) { // Vector variants expand to one card per element: name[0], name[1], ... if (std::holds_alternative>(param.value)) { @@ -413,6 +478,14 @@ void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck) { std::string cn = param.name + "[" + std::to_string(i) + "]"; register_card(suit, cn, Rummy::Card(suit, cn, vec[i], "")); } + } else if (std::holds_alternative(param.value)) { + const auto &vec = std::get(param.value).values; + for (size_t i = 0; i < vec.size(); ++i) { + std::string cn = param.name + "[" + std::to_string(i) + "]"; + ParamValue element = std::visit( + [](const auto &item) -> ParamValue { return item; }, vec[i]); + register_card(suit, cn, ParamValueToRummyCard(suit, cn, element)); + } } else { register_card(suit, param.name, ParamValueToRummyCard(suit, param.name, param.value)); @@ -421,6 +494,14 @@ void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck) { } deck.SeedGlobals(new_cards, new_suits, new_card_map); + if (auto *full_deck = dynamic_cast(&deck); full_deck != nullptr) { + for (const auto &block : pin.GetBlocks()) { + std::string suit = block.name; + while (!suit.empty() && suit.front() == '/') suit.erase(suit.begin()); + if (suit.empty()) suit = "/"; + full_deck->SeedSuitMetadata(suit, block.class_name, block.canonical_path); + } + } } } // namespace parthenon diff --git a/src/parameter_parsers/rummy_parser.hpp b/src/parameter_parsers/rummy_parser.hpp index 6ddc42ec3b3db..db5812045742c 100644 --- a/src/parameter_parsers/rummy_parser.hpp +++ b/src/parameter_parsers/rummy_parser.hpp @@ -37,18 +37,36 @@ enum class InputDeckType { RummyFullSchema = 4, }; -void LoadParameterFromRummy(ParameterInput &input, const std::vector &files, - const std::vector &mods, const bool is_restart, - InputDeckType deck_type = InputDeckType::RummySimple, - const std::string &schema_path = ""); -void LoadParameterFromRummy(ParameterInput &input, const std::vector &files, - const std::vector &mods, const bool is_restart, - InputDeckType deck_type, std::istream &schema_stream); -void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, - InputDeckType deck_type = InputDeckType::RummySimple, - const std::string &schema_path = ""); -void LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, - InputDeckType deck_type, std::istream &schema_stream); +enum class InputParserPolicy { Auto, NativeOnly, RummyOnly }; +enum class RummyMode { Simple, FullLoose, FullStrict }; + +// Explicit parser selection. Unlike the legacy InputDeckType API this keeps +// format detection separate from the Rummy implementation selected after a +// Rummy deck has been chosen. +struct InputDeckOptions { + InputParserPolicy parser = InputParserPolicy::Auto; + RummyMode rummy_mode = RummyMode::Simple; + std::string schema_path; +}; + +InputDeckType ToInputDeckType(RummyMode mode); + +std::unique_ptr +LoadParameterFromRummy(ParameterInput &input, const std::vector &files, + const std::vector &mods, const bool is_restart, + InputDeckType deck_type = InputDeckType::RummySimple, + const std::string &schema_path = ""); +std::unique_ptr +LoadParameterFromRummy(ParameterInput &input, const std::vector &files, + const std::vector &mods, const bool is_restart, + InputDeckType deck_type, std::istream &schema_stream); +std::unique_ptr +LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + InputDeckType deck_type = InputDeckType::RummySimple, + const std::string &schema_path = ""); +std::unique_ptr +LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, + InputDeckType deck_type, std::istream &schema_stream); void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck); void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck); bool IsRummyFormat(const std::string &filename); From 2ae0dac7aaf3888aa038d20e6590f11b30da39cd Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Fri, 24 Jul 2026 13:44:48 -0600 Subject: [PATCH 05/15] Check for native output naming convention and add support for aliased names --- src/outputs/output_parameters.hpp | 1 + src/outputs/outputs.cpp | 34 ++++++++++++++++++++++--------- src/outputs/outputs_package.cpp | 4 +++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/outputs/output_parameters.hpp b/src/outputs/output_parameters.hpp index fad0f40fe547c..cb93d20893a7c 100644 --- a/src/outputs/output_parameters.hpp +++ b/src/outputs/output_parameters.hpp @@ -40,6 +40,7 @@ struct OutputParameters { int block_number = 0; std::string block_name; + std::string state_key; std::string file_basename; int file_number_width; bool file_label_final; diff --git a/src/outputs/outputs.cpp b/src/outputs/outputs.cpp index 1d35462dab64a..8332e9624fb42 100644 --- a/src/outputs/outputs.cpp +++ b/src/outputs/outputs.cpp @@ -84,6 +84,17 @@ namespace parthenon { +namespace { +bool IsLegacyOutputBlock(const std::string &block_name) { + constexpr const char *prefix = "parthenon/output"; + if (block_name.rfind(prefix, 0) != 0) return false; + const std::string suffix = block_name.substr(std::char_traits::length(prefix)); + return !suffix.empty() && + std::all_of(suffix.begin(), suffix.end(), + [](unsigned char c) { return std::isdigit(c) != 0; }); +} +} // namespace + //---------------------------------------------------------------------------------------- // OutputType constructor @@ -111,14 +122,20 @@ Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { // `pinput` again here as we're actually processing (potentially even modifying) // `pinput`. auto output_blocks = pin->GetBlockNamesWithPrefix("parthenon/output"); + int named_output_ordinal = 0; for (const auto &block_name : output_blocks) { std::shared_ptr pnew_type; // the new output we will create bool restart = false; // we track restart outputs separately so we // need this temp variable to check OutputParameters op; // define temporary OutputParameters struct op.block_name = block_name; - const auto outn_str = block_name.substr(16); // 16 because counting starts at 0! - op.block_number = atoi(outn_str.c_str()); + const auto slash = block_name.find_last_of('/'); + const auto outn_str = + (slash == std::string::npos) ? block_name : block_name.substr(slash + 1); + op.state_key = outn_str; + const bool legacy_output = IsLegacyOutputBlock(block_name); + op.block_number = + legacy_output ? std::atoi(outn_str.c_str()) : named_output_ordinal++; auto *pfile_number = pkg->MutableParam(outn_str + "/file_number"); auto *plast_time = pkg->MutableParam(outn_str + "/last_time"); auto *plast_n = pkg->MutableParam(outn_str + "/last_n"); @@ -215,10 +232,8 @@ Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { op.include_in_final = pin->GetOrAddBoolean(op.block_name, "include_in_final", true, "include output when triggered on final signal"); - char define_id[10]; - std::snprintf(define_id, sizeof(define_id), "out%d", - op.block_number); // default id="outN" - op.file_id = pin->GetOrAddString(op.block_name, "id", define_id); + const std::string default_id = legacy_output ? "out" + op.state_key : op.state_key; + op.file_id = pin->GetOrAddString(op.block_name, "id", default_id); op.file_type = pin->GetString(op.block_name, "file_type", "output type"); // read ghost cell option @@ -547,10 +562,9 @@ void Outputs::MakeOutputs(Mesh *pm, ParameterInput *pin, SimTime *tm, void OutputType::UpdateNextOutput_(Mesh *pm, SimTime *tm) { output_params.file_number++; auto pkg = pm->packages.Get("Outputs"); - const auto outn_str = std::to_string(output_params.block_number); - auto *pfile_number = pkg->MutableParam(outn_str + "/file_number"); - auto *plast_time = pkg->MutableParam(outn_str + "/last_time"); - auto *plast_n = pkg->MutableParam(outn_str + "/last_n"); + auto *pfile_number = pkg->MutableParam(output_params.state_key + "/file_number"); + auto *plast_time = pkg->MutableParam(output_params.state_key + "/last_time"); + auto *plast_n = pkg->MutableParam(output_params.state_key + "/last_n"); *pfile_number = output_params.file_number; if (tm != nullptr) { // JMM: Do NOT use the current time to update these, as that can diff --git a/src/outputs/outputs_package.cpp b/src/outputs/outputs_package.cpp index e24c36e3e809f..192122ef84e87 100644 --- a/src/outputs/outputs_package.cpp +++ b/src/outputs/outputs_package.cpp @@ -42,7 +42,9 @@ std::shared_ptr Initialize(ParameterInput *pin) { // from restart files or are cleanly initialized). auto output_blocks = pin->GetBlockNamesWithPrefix("parthenon/output"); for (const auto &block_name : output_blocks) { - std::string outn = block_name.substr(16); // 16 because counting starts at 0! + const auto slash = block_name.find_last_of('/'); + const std::string outn = + (slash == std::string::npos) ? block_name : block_name.substr(slash + 1); // These will be updated later or restarted from int file_number = 0; From cf35d3f8aeed6b9f8079b5d89359d80bea771844 Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Fri, 24 Jul 2026 13:45:55 -0600 Subject: [PATCH 06/15] Trying out a different restart encoding. Hopefully this is not really needed, but for now it is fine --- src/outputs/parthenon_hdf5.cpp | 2 +- src/outputs/parthenon_opmd.cpp | 2 +- src/parameter_input.cpp | 298 ++++++++++++++++++++++++++++++--- src/parameter_input.hpp | 25 ++- 4 files changed, 295 insertions(+), 32 deletions(-) diff --git a/src/outputs/parthenon_hdf5.cpp b/src/outputs/parthenon_hdf5.cpp index c553e6e2288dd..cde47b73514fd 100644 --- a/src/outputs/parthenon_hdf5.cpp +++ b/src/outputs/parthenon_hdf5.cpp @@ -139,7 +139,7 @@ void PHDF5Output::WriteOutputFileImpl(Mesh *pm, ParameterInput *pin, SimTime *tm Kokkos::Profiling::pushRegion("write input"); // write input key-value pairs std::ostringstream oss; - pin->ParameterDump(oss); + pin->RestartDump(oss); // Mesh information const H5G input_group = MakeGroup(file, "/Input"); diff --git a/src/outputs/parthenon_opmd.cpp b/src/outputs/parthenon_opmd.cpp index c70cfc647aac6..5a2b84dab3668 100644 --- a/src/outputs/parthenon_opmd.cpp +++ b/src/outputs/parthenon_opmd.cpp @@ -509,7 +509,7 @@ void OpenPMDOutput::WriteOutputFileImpl(Mesh *pm, ParameterInput *pin, SimTime * PARTHENON_INSTRUMENT_REGION("write input"); // write input key-value pairs std::ostringstream oss; - pin->ParameterDump(oss); + pin->RestartDump(oss); it.setAttribute("InputFile", oss.str()); } diff --git a/src/parameter_input.cpp b/src/parameter_input.cpp index cf46683f1b59a..6341376aaf8a5 100644 --- a/src/parameter_input.cpp +++ b/src/parameter_input.cpp @@ -84,6 +84,170 @@ std::string SanitizeString(const std::string &input) { output.end()); return output; } + +namespace { +std::string HexEncode(const std::string &value) { + static constexpr char digits[] = "0123456789abcdef"; + std::string encoded; + encoded.reserve(value.size() * 2); + for (unsigned char c : value) { + encoded.push_back(digits[c >> 4]); + encoded.push_back(digits[c & 0x0f]); + } + return encoded; +} + +std::string HexDecode(const std::string &value) { + if (value.size() % 2 != 0) throw std::runtime_error("Invalid restart hex payload"); + auto nibble = [](char c) -> unsigned char { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + throw std::runtime_error("Invalid restart hex payload"); + }; + std::string decoded; + decoded.reserve(value.size() / 2); + for (std::size_t i = 0; i < value.size(); i += 2) + decoded.push_back(static_cast((nibble(value[i]) << 4) | nibble(value[i + 1]))); + return decoded; +} + +std::string ScalarTag(const UnresolvedScalar &value) { + return std::visit( + [](const auto &element) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) return "u"; + if constexpr (std::is_same_v) return "i"; + if constexpr (std::is_same_v) return "r"; + if constexpr (std::is_same_v) return "b"; + return "s"; + }, + value); +} + +std::string ScalarPayload(const UnresolvedScalar &value) { + return std::visit( + [](const auto &element) -> std::string { + using T = std::decay_t; + if constexpr (std::is_same_v) { + return element.value; + } else if constexpr (std::is_same_v) { + std::ostringstream os; + os << std::setprecision(std::numeric_limits::max_digits10) << element; + return os.str(); + } else if constexpr (std::is_same_v) { + return element ? "true" : "false"; + } else if constexpr (std::is_same_v) { + return element; + } else { + return std::to_string(element); + } + }, + value); +} + +UnresolvedScalar DecodeScalar(const std::string &tag, const std::string &payload) { + if (tag == "u") return UnresolvedString(payload); + if (tag == "i") return std::stoi(payload); + if (tag == "r") return static_cast(std::stod(payload)); + if (tag == "b") return payload == "true"; + if (tag == "s") return payload; + throw std::runtime_error("Unknown restart scalar tag"); +} + +std::pair EncodeRestartValue(const ParamValue &value) { + auto encode_vector = [](const auto &elements, const std::string &tag) { + std::string payload; + for (std::size_t i = 0; i < elements.size(); ++i) { + if (i > 0) payload += ","; + std::ostringstream element; + if constexpr (std::is_same_v::value_type, + Real>) + element << std::setprecision(std::numeric_limits::max_digits10); + element << elements[i]; + payload += HexEncode(element.str()); + } + return std::make_pair(tag, payload); + }; + if (std::holds_alternative(value)) + return {"u", HexEncode(std::get(value).value)}; + if (std::holds_alternative(value)) + return {"i", HexEncode(std::to_string(std::get(value)))}; + if (std::holds_alternative(value)) { + std::ostringstream os; + os << std::setprecision(std::numeric_limits::max_digits10) + << std::get(value); + return {"r", HexEncode(os.str())}; + } + if (std::holds_alternative(value)) + return {"b", HexEncode(std::get(value) ? "true" : "false")}; + if (std::holds_alternative(value)) + return {"s", HexEncode(std::get(value))}; + if (std::holds_alternative>(value)) + return encode_vector(std::get>(value), "vi"); + if (std::holds_alternative>(value)) + return encode_vector(std::get>(value), "vr"); + if (std::holds_alternative>(value)) + return encode_vector(std::get>(value), "vb"); + if (std::holds_alternative>(value)) + return encode_vector(std::get>(value), "vs"); + const auto &values = std::get(value).values; + std::string payload; + for (std::size_t i = 0; i < values.size(); ++i) { + if (i > 0) payload += ","; + payload += ScalarTag(values[i]) + ":" + HexEncode(ScalarPayload(values[i])); + } + return {"vu", payload}; +} + +ParamValue DecodeRestartValue(const std::string &tag, const std::string &payload) { + if (tag == "u" || tag == "i" || tag == "r" || tag == "b" || tag == "s") { + auto scalar = DecodeScalar(tag, HexDecode(payload)); + return std::visit([](const auto &item) -> ParamValue { return item; }, scalar); + } + std::vector fields; + std::stringstream stream(payload); + std::string field; + while (std::getline(stream, field, ',')) + if (!field.empty()) fields.push_back(field); + if (tag == "vi") { + std::vector v; + for (const auto &f : fields) + v.push_back(std::stoi(HexDecode(f))); + return v; + } + if (tag == "vr") { + std::vector v; + for (const auto &f : fields) + v.push_back(static_cast(std::stod(HexDecode(f)))); + return v; + } + if (tag == "vb") { + std::vector v; + for (const auto &f : fields) + v.push_back(HexDecode(f) == "true"); + return v; + } + if (tag == "vs") { + std::vector v; + for (const auto &f : fields) + v.push_back(HexDecode(f)); + return v; + } + if (tag == "vu") { + UnresolvedVector v; + for (const auto &f : fields) { + const auto colon = f.find(':'); + if (colon == std::string::npos) + throw std::runtime_error("Invalid restart vector payload"); + v.values.emplace_back( + DecodeScalar(f.substr(0, colon), HexDecode(f.substr(colon + 1)))); + } + return v; + } + throw std::runtime_error("Unknown restart value tag"); +} +} // namespace //---------------------------------------------------------------------------------------- // ParameterInput constructor @@ -128,9 +292,26 @@ void ParameterInput::LoadFromStream(std::istream &is) { [](char c) { return std::isspace(c) && c != ' '; }), line.end()); - if (line.empty()) continue; // skip blank line - first_char = line.find_first_not_of(" "); // skip white space - if (first_char == std::string::npos) continue; // line is all white space + if (line.empty()) continue; // skip blank line + first_char = line.find_first_not_of(" "); // skip white space + if (first_char == std::string::npos) continue; // line is all white space + if (line.compare(first_char, 2, "#@") == 0) { + std::istringstream directive(line.substr(first_char + 2)); + std::string kind; + directive >> kind; + if (kind == "block" && !block_name.empty()) { + std::string class_name, instance_name, canonical_path; + directive >> class_name >> instance_name >> canonical_path; + AddParsedBlock(block_name, HexDecode(class_name), HexDecode(instance_name), + HexDecode(canonical_path)); + } else if (kind == "param" && !block_name.empty()) { + std::string name, tag, payload; + directive >> name >> tag >> payload; + AddParsedParameter(block_name, HexDecode(name), DecodeRestartValue(tag, payload), + "# From restart metadata"); + } + continue; + } if (line.compare(first_char, 1, "#") == 0) continue; // skip comments if (line.compare(first_char, 9, "") == 0) break; // stop on @@ -278,8 +459,7 @@ Block *ParameterInput::FindOrAddBlock_(const std::string &name) { // Not found - create new block in vector and index it size_t new_idx = param_storage_.size(); - param_storage_.emplace_back( - Block{name, {}, {}}); // name, params vector, param_index map + param_storage_.emplace_back(Block{name, {}, {}, {}, {}}); block_index_[name] = new_idx; // Index it return ¶m_storage_[new_idx]; } @@ -756,6 +936,26 @@ void ParameterInput::ParameterDump(std::ostream &os) { os << "" << std::endl; // finish with par-end (useful in restart files) } +void ParameterInput::RestartDump(std::ostream &os) { + os << "#---------------------- PAR_RESTART_DUMP ----------------------" << std::endl; + os << "#@parthenon-restart-v1" << std::endl; + for (const auto &block : param_storage_) { + os << "<" << block.name << ">" << std::endl; + os << "#@block " << HexEncode(block.class_name) << " " + << HexEncode(block.instance_name) << " " << HexEncode(block.canonical_path) + << std::endl; + for (const auto ¶m : block.params) { + os << param.name << " = " << param.ToString() << param.comment << std::endl; + const auto [tag, payload] = EncodeRestartValue(param.value); + os << "#@param " << HexEncode(param.name) << " " << tag; + if (!payload.empty()) os << " " << payload; + os << std::endl; + } + } + os << "#---------------------- PAR_RESTART_DUMP ----------------------" << std::endl; + os << "" << std::endl; +} + void ParameterInput::OutputParameterTable(std::ostream &os, const std::regex &block_regex) const { // Loop through once and store in a map for lexicographic ordering @@ -832,6 +1032,21 @@ std::string Parameter::ToString() const { if (std::holds_alternative(value)) { ss << std::get(value).value; + } else if (std::holds_alternative(value)) { + const auto &vec = std::get(value).values; + for (size_t i = 0; i < vec.size(); ++i) { + if (i > 0) ss << ", "; + std::visit( + [&](const auto &element) { + using Element = std::decay_t; + if constexpr (std::is_same_v) { + ss << element.value; + } else { + ss << element; + } + }, + vec[i]); + } } else if (std::holds_alternative(value)) { ss << std::get(value); } else if (std::holds_alternative(value)) { @@ -912,6 +1127,18 @@ void ParameterInput::AddParsedParameter(const std::string &block, const std::str AddParameter_(block, name, value, comment); } +void ParameterInput::AddParsedBlock(const std::string &block, + const std::string &class_name, + const std::string &instance_name, + const std::string &canonical_path) { + PARTHENON_REQUIRE_THROWS(!parsing_finalized_, + "Can't add new blocks after parsing is resolved."); + auto *parsed_block = FindOrAddBlock_(block); + if (!class_name.empty()) parsed_block->class_name = class_name; + if (!instance_name.empty()) parsed_block->instance_name = instance_name; + if (!canonical_path.empty()) parsed_block->canonical_path = canonical_path; +} + //---------------------------------------------------------------------------------------- //! \fn void ParameterInput::FinalizeParsing() // \brief Finalize the parsing phase - no more parsing allowed (GetOrAdd/Set still work) @@ -940,7 +1167,8 @@ ParameterInput::GetBlockNamesWithPrefix(const std::string &prefix) const { std::vector matching_blocks; for (const auto &block : param_storage_) { - if (block.name.compare(0, prefix.length(), prefix) == 0) { + if ((block.canonical_path == prefix) || + (block.name.compare(0, prefix.length(), prefix) == 0)) { matching_blocks.push_back(block.name); } } @@ -967,11 +1195,13 @@ ParameterInput::GetBlocksOfClass(const std::string &class_name) const { void ParameterInput::SetBlockClassMetadata(const std::string &block, const std::string &class_name, - const std::string &instance_name) { + const std::string &instance_name, + const std::string &canonical_path) { auto it = block_index_.find(block); if (it == block_index_.end()) return; param_storage_[it->second].class_name = class_name; param_storage_[it->second].instance_name = instance_name; + if (!canonical_path.empty()) param_storage_[it->second].canonical_path = canonical_path; } //---------------------------------------------------------------------------------------- @@ -1029,9 +1259,12 @@ std::optional ParameterInput::GetFromStorage_(const std::string &block, return std::nullopt; // Not in storage } - // If it's an UnresolvedString, convert and cache - if (std::holds_alternative(param->value)) { - if (!param->original_string.has_value()) { + // Parser-preserved unresolved values convert on first use and then cache + // the requested concrete type. + if (std::holds_alternative(param->value) || + std::holds_alternative(param->value)) { + if (std::holds_alternative(param->value) && + !param->original_string.has_value()) { param->original_string = std::get(param->value); } T typed_val = ConvertParamValue(param->value, block, name); @@ -1103,19 +1336,39 @@ T ParameterInput::ConvertParamValue(const ParamValue &value, const std::string & return std::get(value); } + constexpr bool is_vector_type = + std::is_same_v> || std::is_same_v> || + std::is_same_v> || std::is_same_v>; + + if (std::holds_alternative(value)) { + if constexpr (is_vector_type) { + using ElemType = typename T::value_type; + T result; + for (const auto &element : std::get(value).values) { + ParamValue scalar = + std::visit([](const auto &item) -> ParamValue { return item; }, element); + result.push_back(ConvertParamValue(scalar, block, name)); + } + return result; + } + } + // If it's an unresolved string, convert it if (std::holds_alternative(value)) { const std::string &str_val = std::get(value).value; - constexpr bool is_vector_type = std::is_same_v> || - std::is_same_v> || - std::is_same_v> || - std::is_same_v>; - if constexpr (std::is_same_v) { - return stoi(str_val); + const std::string trimmed = SanitizeString(str_val); + std::size_t pos = 0; + int parsed = std::stoi(trimmed, &pos); + if (pos != trimmed.size()) throw std::invalid_argument("trailing characters"); + return parsed; } else if constexpr (std::is_same_v) { - return static_cast(atof(str_val.c_str())); + const std::string trimmed = SanitizeString(str_val); + std::size_t pos = 0; + Real parsed = static_cast(std::stod(trimmed, &pos)); + if (pos != trimmed.size()) throw std::invalid_argument("trailing characters"); + return parsed; } else if constexpr (std::is_same_v) { return stob(str_val); } else if constexpr (std::is_same_v) { @@ -1126,15 +1379,8 @@ T ParameterInput::ConvertParamValue(const ParamValue &value, const std::string & T result; for (const auto &field : fields) { - if constexpr (std::is_same_v) { - result.push_back(stoi(field)); - } else if constexpr (std::is_same_v) { - result.push_back(static_cast(atof(field.c_str()))); - } else if constexpr (std::is_same_v) { - result.push_back(stob(field)); - } else if constexpr (std::is_same_v) { - result.push_back(field); - } + result.push_back(ConvertParamValue(ParamValue(UnresolvedString(field)), + block, name)); } return result; } diff --git a/src/parameter_input.hpp b/src/parameter_input.hpp index 841c12f0fb399..d9064cb191e2a 100644 --- a/src/parameter_input.hpp +++ b/src/parameter_input.hpp @@ -36,6 +36,7 @@ #include #include #include // for std::forward, std::pair +#include #include #include "config.hpp" @@ -166,9 +167,17 @@ struct UnresolvedString { explicit UnresolvedString(std::string &&v) : value(std::move(v)) {} }; -// Build ParamValue variant from SupportedParamTypes + UnresolvedString -using ParamValue = - type_list_to_variant_t>; +// A parser-provided vector whose elements retain their original scalar kind. +// This avoids treating commas in string elements as separators while deferring +// conversion until the consumer requests a concrete vector type. +using UnresolvedScalar = std::variant; +struct UnresolvedVector { + std::vector values; +}; + +// Build ParamValue variant from SupportedParamTypes + parser-preserved values. +using ParamValue = type_list_to_variant_t, 0>>; // This can be used to tell the params infrastructure that the default // value of one parameter depends on another one @@ -220,6 +229,7 @@ struct Block { // for the SimpleDeck parser and the legacy text parser. std::string class_name; std::string instance_name; + std::string canonical_path; std::vector params; // Ordered storage (for iteration) std::unordered_map param_index; // Fast lookup within block (stores indices) @@ -257,6 +267,9 @@ class ParameterInput { void AddParsedParameter(const std::string &block, const std::string &name, const ParamValue &value, const std::string &comment = "# From parser"); + void AddParsedBlock(const std::string &block, const std::string &class_name = "", + const std::string &instance_name = "", + const std::string &canonical_path = ""); // Finalize the parsing phase - no more parsing allowed (but GetOrAdd/Set still work) void FinalizeParsing(); @@ -274,9 +287,13 @@ class ParameterInput { // rummy parser when populating from a FullDeck; ignored for callers that // do not need class introspection. void SetBlockClassMetadata(const std::string &block, const std::string &class_name, - const std::string &instance_name); + const std::string &instance_name, + const std::string &canonical_path = ""); void ParameterDump(std::ostream &os); + // Backward-readable restart serialization that additionally preserves + // parser metadata and unresolved/typed value representations. + void RestartDump(std::ostream &os); // TODO(JMM): Make this more general? void OutputParameterTable(std::ostream &os, const std::regex &block_regex = std::regex("(.*)")) const; From fb2a86657d39b397b10ab3defa0588fc24d60b6c Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Fri, 24 Jul 2026 13:46:36 -0600 Subject: [PATCH 07/15] Support for rummy full deck, make sure the deck persists --- src/parthenon_manager.cpp | 57 +++++++++++++++++++++++++++------------ src/parthenon_manager.hpp | 12 ++++++++- 2 files changed, 51 insertions(+), 18 deletions(-) diff --git a/src/parthenon_manager.cpp b/src/parthenon_manager.cpp index ade479e476ce5..0ff72cf752ceb 100644 --- a/src/parthenon_manager.cpp +++ b/src/parthenon_manager.cpp @@ -18,6 +18,9 @@ #include "parthenon_manager.hpp" +#include +#include + #include #include #include @@ -53,20 +56,37 @@ namespace fs = FS_NAMESPACE; namespace parthenon { +ParthenonManager::ParthenonManager() { app_input = std::make_unique(); } + +ParthenonManager::~ParthenonManager() = default; + +Rummy::FullDeck *ParthenonManager::GetRummyFullDeck() const { + return dynamic_cast(input_deck.get()); +} + ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], InputDeckType deck_type, const std::string &schema_path) { - return ParthenonInitEnvCore_(argc, argv, deck_type, schema_path, nullptr); + return ParthenonInitEnvCore_(argc, argv, deck_type, InputParserPolicy::Auto, + schema_path, nullptr); } ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], InputDeckType deck_type, std::istream &schema_stream) { - return ParthenonInitEnvCore_(argc, argv, deck_type, "", &schema_stream); + return ParthenonInitEnvCore_(argc, argv, deck_type, InputParserPolicy::Auto, "", + &schema_stream); +} + +ParthenonStatus ParthenonManager::ParthenonInitEnv(int argc, char *argv[], + const InputDeckOptions &options) { + return ParthenonInitEnvCore_(argc, argv, ToInputDeckType(options.rummy_mode), + options.parser, options.schema_path, nullptr); } ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], InputDeckType deck_type, + InputParserPolicy parser_policy, const std::string &schema_path, std::istream *schema_stream) { if (called_init_env_) { @@ -154,21 +174,23 @@ ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], pinput->LoadFromStream(is); } // Determine what parser to use - bool is_rummy = false; - for (const auto &input_filename : arg.input_filenames) { - if (IsRummyFormat(input_filename)) { - is_rummy = true; - break; - } - } - if (!is_rummy) { - for (const auto &mod : arg.modifiers) { - std::stringstream ss(mod); - if (IsRummyFormat(ss, true)) { + bool is_rummy = parser_policy == InputParserPolicy::RummyOnly; + if (parser_policy == InputParserPolicy::Auto) { + for (const auto &input_filename : arg.input_filenames) { + if (IsRummyFormat(input_filename)) { is_rummy = true; break; } } + if (!is_rummy) { + for (const auto &mod : arg.modifiers) { + std::stringstream ss(mod); + if (IsRummyFormat(ss, true)) { + is_rummy = true; + break; + } + } + } } // read the parameters @@ -177,13 +199,14 @@ ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], } if (is_rummy) { if (schema_stream != nullptr) { - LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, - deck_type, *schema_stream); + input_deck = LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, + arg.is_restart, deck_type, *schema_stream); } else { - LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, - deck_type, schema_path); + input_deck = LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, + arg.is_restart, deck_type, schema_path); } } else { + input_deck.reset(); for (const auto &input_filename : arg.input_filenames) { pinput->ReadFile(input_filename); } diff --git a/src/parthenon_manager.hpp b/src/parthenon_manager.hpp index 94127adc74714..faf415f7abcb6 100644 --- a/src/parthenon_manager.hpp +++ b/src/parthenon_manager.hpp @@ -40,12 +40,15 @@ enum class ParthenonStatus { ok, complete, error }; class ParthenonManager { public: - ParthenonManager() { app_input.reset(new ApplicationInput()); } + ParthenonManager(); + ~ParthenonManager(); ParthenonStatus ParthenonInitEnv(int argc, char *argv[], InputDeckType deck_type = InputDeckType::Native, const std::string &schema_path = ""); ParthenonStatus ParthenonInitEnv(int argc, char *argv[], InputDeckType deck_type, std::istream &schema_stream); + ParthenonStatus ParthenonInitEnv(int argc, char *argv[], + const InputDeckOptions &options); void ParthenonInitPackagesAndMesh(std::optional forest_def = {}); ParthenonStatus ParthenonFinalize(); @@ -61,6 +64,12 @@ class ParthenonManager { std::unique_ptr pmesh; std::unique_ptr restartReader; std::unique_ptr app_input; + // Retains full Rummy state (graphs and packed device functions) for the + // complete Parthenon lifetime. Null when the native parser is selected. + std::unique_ptr input_deck; + + Rummy::DeckBase *GetRummyDeck() const { return input_deck.get(); } + Rummy::FullDeck *GetRummyFullDeck() const; private: ArgParse arg; @@ -69,6 +78,7 @@ class ParthenonManager { // Shared implementation for both ParthenonInitEnv overloads. ParthenonStatus ParthenonInitEnvCore_(int argc, char *argv[], InputDeckType deck_type, + InputParserPolicy parser_policy, const std::string &schema_path, std::istream *schema_stream); From 2da4491907fe94bb603d44572c4d88094fe2d81d Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Fri, 24 Jul 2026 13:50:12 -0600 Subject: [PATCH 08/15] Update rummy --- external/rummy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/rummy b/external/rummy index 78239519ca221..ae986446edbb8 160000 --- a/external/rummy +++ b/external/rummy @@ -1 +1 @@ -Subproject commit 78239519ca2210197ed539e148fd8e918cc9f4e4 +Subproject commit ae986446edbb8b232300bbaa2091e2bbc4a51027 From 2f60ee4862e7d2b43546f6f91ee11d25d2289904 Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Sat, 25 Jul 2026 12:29:27 -0600 Subject: [PATCH 09/15] Fix legacy block number for output --- src/outputs/outputs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/outputs/outputs.cpp b/src/outputs/outputs.cpp index 8332e9624fb42..fa024a617b8aa 100644 --- a/src/outputs/outputs.cpp +++ b/src/outputs/outputs.cpp @@ -232,7 +232,8 @@ Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { op.include_in_final = pin->GetOrAddBoolean(op.block_name, "include_in_final", true, "include output when triggered on final signal"); - const std::string default_id = legacy_output ? "out" + op.state_key : op.state_key; + const std::string default_id = + legacy_output ? "out" + std::to_string(op.block_number) : op.state_key; op.file_id = pin->GetOrAddString(op.block_name, "id", default_id); op.file_type = pin->GetString(op.block_name, "file_type", "output type"); From 9deccb0f63290fcf5837e0ee1a8d08654deef021 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 27 Jul 2026 07:02:43 -0600 Subject: [PATCH 10/15] Working through rummy restarts --- src/outputs/parthenon_hdf5.cpp | 20 ++-- src/outputs/parthenon_opmd.cpp | 17 +++- src/outputs/restart.hpp | 8 ++ src/outputs/restart_hdf5.cpp | 17 ++++ src/outputs/restart_hdf5.hpp | 1 + src/outputs/restart_opmd.hpp | 11 +++ src/parameter_input.hpp | 10 ++ src/parameter_parsers/rummy_parser.cpp | 125 +++++++++++++++++-------- src/parameter_parsers/rummy_parser.hpp | 17 +++- src/parthenon_manager.cpp | 24 ++++- 10 files changed, 194 insertions(+), 56 deletions(-) diff --git a/src/outputs/parthenon_hdf5.cpp b/src/outputs/parthenon_hdf5.cpp index cde47b73514fd..83706ba139af7 100644 --- a/src/outputs/parthenon_hdf5.cpp +++ b/src/outputs/parthenon_hdf5.cpp @@ -45,6 +45,7 @@ #include "outputs/parthenon_xdmf.hpp" #include "outputs/restart.hpp" #include "pack/default_names.hpp" +#include "parameter_parsers/rummy_parser.hpp" #include "provenance.hpp" #include "utils/string_utils.hpp" @@ -137,14 +138,19 @@ void PHDF5Output::WriteOutputFileImpl(Mesh *pm, ParameterInput *pin, SimTime *tm Kokkos::Profiling::pushRegion("write Attributes"); { Kokkos::Profiling::pushRegion("write input"); - // write input key-value pairs - std::ostringstream oss; - pin->RestartDump(oss); - - // Mesh information const H5G input_group = MakeGroup(file, "/Input"); - - HDF5WriteAttribute("File", oss.str().c_str(), input_group); + if (const auto *deck = pin->GetRummyDeck(); deck != nullptr) { + const auto state = MakeRummyRestartState(*pin, *deck); + HDF5WriteAttribute("File", state.source, input_group); + HDF5WriteAttribute("InputParser", "rummy", input_group); + HDF5WriteAttribute("RummyMode", state.mode, input_group); + HDF5WriteAttribute("RummyStateVersion", state.version, input_group); + HDF5WriteAttribute("RummyState", state.source, input_group); + } else { + std::ostringstream oss; + pin->RestartDump(oss); + HDF5WriteAttribute("File", oss.str(), input_group); + } Kokkos::Profiling::popRegion(); // write input } // Input section diff --git a/src/outputs/parthenon_opmd.cpp b/src/outputs/parthenon_opmd.cpp index 5a2b84dab3668..1938362daba07 100644 --- a/src/outputs/parthenon_opmd.cpp +++ b/src/outputs/parthenon_opmd.cpp @@ -56,6 +56,7 @@ #include "outputs/outputs.hpp" #include "outputs/parthenon_opmd.hpp" #include "pack/default_names.hpp" +#include "parameter_parsers/rummy_parser.hpp" #include "parthenon_array_generic.hpp" #include "provenance.hpp" #include "utils/error_checking.hpp" @@ -507,10 +508,18 @@ void OpenPMDOutput::WriteOutputFileImpl(Mesh *pm, ParameterInput *pin, SimTime * // Then our own if (!is_slice) { PARTHENON_INSTRUMENT_REGION("write input"); - // write input key-value pairs - std::ostringstream oss; - pin->RestartDump(oss); - it.setAttribute("InputFile", oss.str()); + if (const auto *deck = pin->GetRummyDeck(); deck != nullptr) { + const auto state = MakeRummyRestartState(*pin, *deck); + it.setAttribute("InputFile", state.source); + it.setAttribute("InputParser", std::string("rummy")); + it.setAttribute("RummyMode", state.mode); + it.setAttribute("RummyStateVersion", state.version); + it.setAttribute("RummyState", state.source); + } else { + std::ostringstream oss; + pin->RestartDump(oss); + it.setAttribute("InputFile", oss.str()); + } } if (!is_slice) { diff --git a/src/outputs/restart.hpp b/src/outputs/restart.hpp index e61cfe2dd6d44..8f977d49e9074 100644 --- a/src/outputs/restart.hpp +++ b/src/outputs/restart.hpp @@ -98,6 +98,14 @@ class RestartReader { [[nodiscard]] virtual std::string GetInputString() const = 0; + struct RummyInputState { + bool present = false; + int version = 0; + std::string mode; + std::string source; + }; + [[nodiscard]] virtual RummyInputState GetRummyInputState() const = 0; + // Return output format version number. Return -1 if not existent. [[nodiscard]] virtual int GetOutputFormatVersion() const = 0; diff --git a/src/outputs/restart_hdf5.cpp b/src/outputs/restart_hdf5.cpp index 67780f54fd756..19970cfb19aa7 100644 --- a/src/outputs/restart_hdf5.cpp +++ b/src/outputs/restart_hdf5.cpp @@ -84,6 +84,23 @@ int RestartReaderHDF5::GetOutputFormatVersion() const { #endif // ENABLE_HDF5 } +RestartReader::RummyInputState RestartReaderHDF5::GetRummyInputState() const { +#ifndef ENABLE_HDF5 + PARTHENON_FAIL("Restart functionality is not available because HDF5 is disabled"); +#else + RummyInputState state; + const H5O input = H5O::FromHIDCheck(H5Oopen(fh_, "Input", H5P_DEFAULT)); + auto status = PARTHENON_HDF5_CHECK(H5Aexists(input, "InputParser")); + if (status <= 0 || GetAttr("Input", "InputParser") != "rummy") + return state; + state.present = true; + state.version = GetAttr("Input", "RummyStateVersion"); + state.mode = GetAttr("Input", "RummyMode"); + state.source = GetAttr("Input", "RummyState"); + return state; +#endif +} + RestartReaderHDF5::SparseInfo RestartReaderHDF5::GetSparseInfo() const { #ifndef ENABLE_HDF5 PARTHENON_FAIL("Restart functionality is not available because HDF5 is disabled"); diff --git a/src/outputs/restart_hdf5.hpp b/src/outputs/restart_hdf5.hpp index fe14cacb152f5..13d95e203c9af 100644 --- a/src/outputs/restart_hdf5.hpp +++ b/src/outputs/restart_hdf5.hpp @@ -55,6 +55,7 @@ class RestartReaderHDF5 : public RestartReader { [[nodiscard]] std::string GetInputString() const override { return GetAttr("Input", "File"); }; + [[nodiscard]] RummyInputState GetRummyInputState() const override; // Return output format version number. Return -1 if not existent. [[nodiscard]] int GetOutputFormatVersion() const override; diff --git a/src/outputs/restart_opmd.hpp b/src/outputs/restart_opmd.hpp index 953c440e9d70f..a21588d700c36 100644 --- a/src/outputs/restart_opmd.hpp +++ b/src/outputs/restart_opmd.hpp @@ -41,6 +41,17 @@ class RestartReaderOPMD : public RestartReader { [[nodiscard]] std::string GetInputString() const override { return it->getAttribute("InputFile").get(); }; + [[nodiscard]] RummyInputState GetRummyInputState() const override { + RummyInputState state; + if (!it->containsAttribute("InputParser") || + it->getAttribute("InputParser").get() != "rummy") + return state; + state.present = true; + state.version = it->getAttribute("RummyStateVersion").get(); + state.mode = it->getAttribute("RummyMode").get(); + state.source = it->getAttribute("RummyState").get(); + return state; + } // Return output format version number. Return -1 if not existent. [[nodiscard]] int GetOutputFormatVersion() const override; diff --git a/src/parameter_input.hpp b/src/parameter_input.hpp index d9064cb191e2a..d4e64b3f13b2e 100644 --- a/src/parameter_input.hpp +++ b/src/parameter_input.hpp @@ -49,6 +49,10 @@ #include "utils/type_list.hpp" #include "utils/utils.hpp" +namespace Rummy { +class DeckBase; +} + namespace parthenon { std::string SanitizeString(const std::string &input); @@ -294,6 +298,11 @@ class ParameterInput { // Backward-readable restart serialization that additionally preserves // parser metadata and unresolved/typed value representations. void RestartDump(std::ostream &os); + // Non-owning link to the input deck retained by ParthenonManager. Output + // writers use it to generate current Rummy restart state while RestartDump + // remains available for native and legacy restart compatibility. + void SetRummyDeck(Rummy::DeckBase *deck) { rummy_deck_ = deck; } + Rummy::DeckBase *GetRummyDeck() const { return rummy_deck_; } // TODO(JMM): Make this more general? void OutputParameterTable(std::ostream &os, const std::regex &block_regex = std::regex("(.*)")) const; @@ -506,6 +515,7 @@ class ParameterInput { std::unordered_map block_index_; // Fast O(1) block lookup (stores indices) bool parsing_finalized_ = false; // Track if parsing phase is complete + Rummy::DeckBase *rummy_deck_ = nullptr; std::string last_filename_; // last input file opened, to prevent duplicate reads // We will want to iterate through the record in lexicographic diff --git a/src/parameter_parsers/rummy_parser.cpp b/src/parameter_parsers/rummy_parser.cpp index 06199a8d81993..4328e8960ba76 100644 --- a/src/parameter_parsers/rummy_parser.cpp +++ b/src/parameter_parsers/rummy_parser.cpp @@ -128,6 +128,47 @@ std::unique_ptr MakeDeck(InputDeckType /*deck_type*/, std::move(schema)); } +std::unique_ptr MakeRestartDeck(InputDeckType deck_type) { + switch (deck_type) { + case InputDeckType::RummyFullLoose: + return std::make_unique(Rummy::FullDeck::Mode::Loose); + case InputDeckType::RummyFullStrict: + case InputDeckType::RummyFullSchema: + // Schema-generated declarations are embedded in the restart source. + return std::make_unique(Rummy::FullDeck::Mode::Strict); + case InputDeckType::RummySimple: + return std::make_unique(); + default: + PARTHENON_FAIL("A Rummy restart requires a Rummy input deck type"); + } +} + +std::vector +MakeSources(const std::string *restart_source, const std::vector &files, + const std::vector &mods) { + std::vector sources; + if (restart_source != nullptr) + sources.push_back({"", *restart_source, ""}); + for (const auto &file : files) { + std::ifstream input_file(file); + if (!input_file.is_open()) { + std::stringstream msg; + msg << "Could not open file '" << file << "'"; + PARTHENON_FAIL(msg); + } + std::stringstream contents; + contents << input_file.rdbuf(); + sources.push_back( + {file, contents.str(), std::filesystem::path(file).parent_path().string()}); + } + if (!mods.empty()) { + std::stringstream contents; + for (const auto &mod : mods) contents << mod << " # From command line\n"; + sources.push_back({"", contents.str(), ""}); + } + return sources; +} + // Construct a deck of the requested type std::unique_ptr MakeDeck(InputDeckType deck_type, const std::string &schema_path = "") { @@ -185,25 +226,7 @@ LoadParameterFromRummy(ParameterInput &pin, const std::vector &file SyncDeckFromStorage(pin, *deck); } - std::vector sources; - for (const auto &file : files) { - std::ifstream input_file(file); - if (input_file.is_open()) { - std::stringstream contents; - contents << input_file.rdbuf(); - sources.push_back( - {file, contents.str(), std::filesystem::path(file).parent_path().string()}); - } else { - std::stringstream msg; - msg << "Could not open file '" << file << "'"; - PARTHENON_FAIL(msg); - } - } - if (!mods.empty()) { - std::stringstream contents; - for (const auto &mod : mods) contents << mod << " # From command line\n"; - sources.push_back({"", contents.str(), ""}); - } + auto sources = MakeSources(nullptr, files, mods); deck->BuildSources(sources); AddRummyParameters(pin, *deck); return deck; @@ -236,30 +259,54 @@ LoadParameterFromRummy(ParameterInput &pin, const std::vector &file SyncDeckFromStorage(pin, *deck); } - std::vector sources; - for (const auto &file : files) { - std::ifstream input_file(file); - if (input_file.is_open()) { - std::stringstream contents; - contents << input_file.rdbuf(); - sources.push_back( - {file, contents.str(), std::filesystem::path(file).parent_path().string()}); - } else { - std::stringstream msg; - msg << "Could not open file '" << file << "'"; - PARTHENON_FAIL(msg); - } - } - if (!mods.empty()) { - std::stringstream contents; - for (const auto &mod : mods) contents << mod << " # From command line\n"; - sources.push_back({"", contents.str(), ""}); - } + auto sources = MakeSources(nullptr, files, mods); + deck->BuildSources(sources); + AddRummyParameters(pin, *deck); + return deck; +} + +std::unique_ptr +LoadParameterFromRummyRestart(ParameterInput &pin, const std::string &restart_source, + const std::vector &files, + const std::vector &mods, + InputDeckType deck_type) { + auto deck = MakeRestartDeck(deck_type); + auto sources = MakeSources(&restart_source, files, mods); deck->BuildSources(sources); AddRummyParameters(pin, *deck); return deck; } +InputDeckType RummyRestartModeToDeckType(const std::string &mode) { + if (mode == "simple") return InputDeckType::RummySimple; + if (mode == "full-loose") return InputDeckType::RummyFullLoose; + if (mode == "full-strict") return InputDeckType::RummyFullStrict; + PARTHENON_FAIL("Unsupported Rummy restart mode '" + mode + "'"); +} + +RummyRestartState MakeRummyRestartState(const ParameterInput &pin, + const Rummy::DeckBase &deck) { + RummyRestartState state; + std::ostringstream source; + if (const auto *full = dynamic_cast(&deck); full != nullptr) { + auto snapshot = *full; + SyncDeckFromStorage(pin, snapshot); + snapshot.SaveRestartState(source); + state.mode = full->GetMode() == Rummy::FullDeck::Mode::Strict ? "full-strict" + : "full-loose"; + } else if (const auto *simple = dynamic_cast(&deck); + simple != nullptr) { + auto snapshot = *simple; + SyncDeckFromStorage(pin, snapshot); + snapshot.SaveRestartState(source); + state.mode = "simple"; + } else { + PARTHENON_FAIL("Unsupported Rummy deck implementation in restart output"); + } + state.source = source.str(); + return state; +} + void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck) { // If the deck is a FullDeck, capture per-suit class metadata so callers can // query blocks by their pips class via ParameterInput::GetBlocksOfClass. @@ -409,7 +456,7 @@ bool IsRummyFormat(const std::string &filename) { //---------------------------------------------------------------------------------------- //! \fn void ParameterInput::SyncDeckFromStorage() // \brief Seed the Rummy Deck from the current param_storage_ contents. -void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck) { +void SyncDeckFromStorage(const ParameterInput &pin, Rummy::DeckBase &deck) { std::map> new_cards; std::vector new_suits; std::map> new_card_map; diff --git a/src/parameter_parsers/rummy_parser.hpp b/src/parameter_parsers/rummy_parser.hpp index db5812045742c..7901130296168 100644 --- a/src/parameter_parsers/rummy_parser.hpp +++ b/src/parameter_parsers/rummy_parser.hpp @@ -49,6 +49,13 @@ struct InputDeckOptions { std::string schema_path; }; +struct RummyRestartState { + static constexpr int VERSION = 1; + int version = VERSION; + std::string mode; + std::string source; +}; + InputDeckType ToInputDeckType(RummyMode mode); std::unique_ptr @@ -67,8 +74,16 @@ LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, std::unique_ptr LoadParameterFromRummy(ParameterInput &pin, std::istream &ss, const bool sync, InputDeckType deck_type, std::istream &schema_stream); +std::unique_ptr +LoadParameterFromRummyRestart(ParameterInput &pin, const std::string &restart_source, + const std::vector &files, + const std::vector &mods, + InputDeckType deck_type); +RummyRestartState MakeRummyRestartState(const ParameterInput &pin, + const Rummy::DeckBase &deck); +InputDeckType RummyRestartModeToDeckType(const std::string &mode); void AddRummyParameters(ParameterInput &pin, Rummy::DeckBase &deck); -void SyncDeckFromStorage(ParameterInput &pin, Rummy::DeckBase &deck); +void SyncDeckFromStorage(const ParameterInput &pin, Rummy::DeckBase &deck); bool IsRummyFormat(const std::string &filename); bool IsRummyFormat(std::istream &is, const bool command_line); } // namespace parthenon diff --git a/src/parthenon_manager.cpp b/src/parthenon_manager.cpp index 0ff72cf752ceb..e35dd1d26ab49 100644 --- a/src/parthenon_manager.cpp +++ b/src/parthenon_manager.cpp @@ -144,6 +144,7 @@ ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], // Populate the ParameterInput object. // If restart, then ParameterInput in the restart file takes precedence. + RestartReader::RummyInputState rummy_restart; if (arg.is_restart) { // Read input from restart file if (fs::path(arg.restart_filename).extension() == ".rhdf") { @@ -167,16 +168,20 @@ ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], PARTHENON_FAIL("Unsupported restart file format."); } - // Load input stream + rummy_restart = restartReader->GetRummyInputState(); pinput = std::make_unique(); - auto inputString = restartReader->GetInputString(); - std::istringstream is(inputString); - pinput->LoadFromStream(is); + if (!rummy_restart.present) { + auto inputString = restartReader->GetInputString(); + std::istringstream is(inputString); + pinput->LoadFromStream(is); + } } // Determine what parser to use bool is_rummy = parser_policy == InputParserPolicy::RummyOnly; if (parser_policy == InputParserPolicy::Auto) { + is_rummy = rummy_restart.present; for (const auto &input_filename : arg.input_filenames) { + if (is_rummy) break; if (IsRummyFormat(input_filename)) { is_rummy = true; break; @@ -198,13 +203,22 @@ ParthenonStatus ParthenonManager::ParthenonInitEnvCore_(int argc, char *argv[], pinput = std::make_unique(); } if (is_rummy) { - if (schema_stream != nullptr) { + if (rummy_restart.present) { + PARTHENON_REQUIRE_THROWS( + rummy_restart.version == RummyRestartState::VERSION, + "Unsupported Rummy restart state version " + + std::to_string(rummy_restart.version)); + input_deck = LoadParameterFromRummyRestart( + *pinput, rummy_restart.source, arg.input_filenames, arg.modifiers, + RummyRestartModeToDeckType(rummy_restart.mode)); + } else if (schema_stream != nullptr) { input_deck = LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, deck_type, *schema_stream); } else { input_deck = LoadParameterFromRummy(*pinput, arg.input_filenames, arg.modifiers, arg.is_restart, deck_type, schema_path); } + pinput->SetRummyDeck(input_deck.get()); } else { input_deck.reset(); for (const auto &input_filename : arg.input_filenames) { From 9babf9f1fdf799d7bdf27923c848a7ad2850d0a2 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 27 Jul 2026 07:03:49 -0600 Subject: [PATCH 11/15] Update Rummy --- external/rummy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/rummy b/external/rummy index ae986446edbb8..d3873404d5304 160000 --- a/external/rummy +++ b/external/rummy @@ -1 +1 @@ -Subproject commit ae986446edbb8b232300bbaa2091e2bbc4a51027 +Subproject commit d3873404d5304acbeb86ba9fcbd7b12206c934e2 From 652dbc0e7f374a7cdafc7c1933ee2492c0ef9371 Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Tue, 28 Jul 2026 16:16:52 -0600 Subject: [PATCH 12/15] Grab the output block number from the suffix --- src/outputs/outputs.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/outputs/outputs.cpp b/src/outputs/outputs.cpp index fa024a617b8aa..5bf5f3239f37d 100644 --- a/src/outputs/outputs.cpp +++ b/src/outputs/outputs.cpp @@ -85,13 +85,17 @@ namespace parthenon { namespace { -bool IsLegacyOutputBlock(const std::string &block_name) { +int IsLegacyOutputBlock(const std::string &block_name) { constexpr const char *prefix = "parthenon/output"; if (block_name.rfind(prefix, 0) != 0) return false; const std::string suffix = block_name.substr(std::char_traits::length(prefix)); - return !suffix.empty() && + const bool is_legacy = !suffix.empty() && std::all_of(suffix.begin(), suffix.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); + if (is_legacy) { + return std::atoi(suffix.c_str()); + } + return -1; } } // namespace @@ -133,9 +137,10 @@ Outputs::Outputs(Mesh *pm, ParameterInput *pin, SimTime *tm) { const auto outn_str = (slash == std::string::npos) ? block_name : block_name.substr(slash + 1); op.state_key = outn_str; - const bool legacy_output = IsLegacyOutputBlock(block_name); + const int legacy_number = IsLegacyOutputBlock(block_name); + const bool legacy_output = (legacy_number >= 0); op.block_number = - legacy_output ? std::atoi(outn_str.c_str()) : named_output_ordinal++; + legacy_output ? legacy_number : named_output_ordinal++; auto *pfile_number = pkg->MutableParam(outn_str + "/file_number"); auto *plast_time = pkg->MutableParam(outn_str + "/last_time"); auto *plast_n = pkg->MutableParam(outn_str + "/last_n"); From bbc8ad325a128c9453cc239a29df5ad9930c426c Mon Sep 17 00:00:00 2001 From: Adam Dempsey Date: Tue, 28 Jul 2026 16:23:25 -0600 Subject: [PATCH 13/15] If the variable is stored as a double, but parsed as an int, make sure they are the same before failing --- src/parameter_input.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/parameter_input.cpp b/src/parameter_input.cpp index 6341376aaf8a5..4ab3d4b8c8574 100644 --- a/src/parameter_input.cpp +++ b/src/parameter_input.cpp @@ -1361,7 +1361,11 @@ T ParameterInput::ConvertParamValue(const ParamValue &value, const std::string & const std::string trimmed = SanitizeString(str_val); std::size_t pos = 0; int parsed = std::stoi(trimmed, &pos); - if (pos != trimmed.size()) throw std::invalid_argument("trailing characters"); + if (pos != trimmed.size()) { + Real d_parsed = static_cast(std::stod(trimmed, &pos)); + if (pos != trimmed.size()) throw std::invalid_argument("trailing characters"); + if ( static_cast(parsed) != d_parsed) throw std::invalid_argument("Integer type parameter is not parsing correctly from the string value"); + } return parsed; } else if constexpr (std::is_same_v) { const std::string trimmed = SanitizeString(str_val); From 9c9926718b86d7fad224b88aa41914a1c1be47af Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 29 Jul 2026 07:23:22 -0600 Subject: [PATCH 14/15] Update rummy --- external/rummy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/rummy b/external/rummy index d3873404d5304..212d34dab0e85 160000 --- a/external/rummy +++ b/external/rummy @@ -1 +1 @@ -Subproject commit d3873404d5304acbeb86ba9fcbd7b12206c934e2 +Subproject commit 212d34dab0e85ffaf5c9b8f695fae681ffd87483 From 229ff54d4ee0de02c2c513fdafc7a1cbd9d951e6 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 29 Jul 2026 08:58:06 -0600 Subject: [PATCH 15/15] Remove encoding nonsense --- src/outputs/parthenon_hdf5.cpp | 2 +- src/outputs/parthenon_opmd.cpp | 2 +- src/parameter_input.cpp | 202 --------------------------------- src/parameter_input.hpp | 6 +- 4 files changed, 3 insertions(+), 209 deletions(-) diff --git a/src/outputs/parthenon_hdf5.cpp b/src/outputs/parthenon_hdf5.cpp index 83706ba139af7..49160ac0627b6 100644 --- a/src/outputs/parthenon_hdf5.cpp +++ b/src/outputs/parthenon_hdf5.cpp @@ -148,7 +148,7 @@ void PHDF5Output::WriteOutputFileImpl(Mesh *pm, ParameterInput *pin, SimTime *tm HDF5WriteAttribute("RummyState", state.source, input_group); } else { std::ostringstream oss; - pin->RestartDump(oss); + pin->ParameterDump(oss); HDF5WriteAttribute("File", oss.str(), input_group); } Kokkos::Profiling::popRegion(); // write input diff --git a/src/outputs/parthenon_opmd.cpp b/src/outputs/parthenon_opmd.cpp index 1938362daba07..7ff09154d1937 100644 --- a/src/outputs/parthenon_opmd.cpp +++ b/src/outputs/parthenon_opmd.cpp @@ -517,7 +517,7 @@ void OpenPMDOutput::WriteOutputFileImpl(Mesh *pm, ParameterInput *pin, SimTime * it.setAttribute("RummyState", state.source); } else { std::ostringstream oss; - pin->RestartDump(oss); + pin->ParameterDump(oss); it.setAttribute("InputFile", oss.str()); } } diff --git a/src/parameter_input.cpp b/src/parameter_input.cpp index 4ab3d4b8c8574..12213bf67d09e 100644 --- a/src/parameter_input.cpp +++ b/src/parameter_input.cpp @@ -56,7 +56,6 @@ #include #include #include -#include #include #include #include @@ -84,170 +83,6 @@ std::string SanitizeString(const std::string &input) { output.end()); return output; } - -namespace { -std::string HexEncode(const std::string &value) { - static constexpr char digits[] = "0123456789abcdef"; - std::string encoded; - encoded.reserve(value.size() * 2); - for (unsigned char c : value) { - encoded.push_back(digits[c >> 4]); - encoded.push_back(digits[c & 0x0f]); - } - return encoded; -} - -std::string HexDecode(const std::string &value) { - if (value.size() % 2 != 0) throw std::runtime_error("Invalid restart hex payload"); - auto nibble = [](char c) -> unsigned char { - if (c >= '0' && c <= '9') return c - '0'; - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - throw std::runtime_error("Invalid restart hex payload"); - }; - std::string decoded; - decoded.reserve(value.size() / 2); - for (std::size_t i = 0; i < value.size(); i += 2) - decoded.push_back(static_cast((nibble(value[i]) << 4) | nibble(value[i + 1]))); - return decoded; -} - -std::string ScalarTag(const UnresolvedScalar &value) { - return std::visit( - [](const auto &element) -> std::string { - using T = std::decay_t; - if constexpr (std::is_same_v) return "u"; - if constexpr (std::is_same_v) return "i"; - if constexpr (std::is_same_v) return "r"; - if constexpr (std::is_same_v) return "b"; - return "s"; - }, - value); -} - -std::string ScalarPayload(const UnresolvedScalar &value) { - return std::visit( - [](const auto &element) -> std::string { - using T = std::decay_t; - if constexpr (std::is_same_v) { - return element.value; - } else if constexpr (std::is_same_v) { - std::ostringstream os; - os << std::setprecision(std::numeric_limits::max_digits10) << element; - return os.str(); - } else if constexpr (std::is_same_v) { - return element ? "true" : "false"; - } else if constexpr (std::is_same_v) { - return element; - } else { - return std::to_string(element); - } - }, - value); -} - -UnresolvedScalar DecodeScalar(const std::string &tag, const std::string &payload) { - if (tag == "u") return UnresolvedString(payload); - if (tag == "i") return std::stoi(payload); - if (tag == "r") return static_cast(std::stod(payload)); - if (tag == "b") return payload == "true"; - if (tag == "s") return payload; - throw std::runtime_error("Unknown restart scalar tag"); -} - -std::pair EncodeRestartValue(const ParamValue &value) { - auto encode_vector = [](const auto &elements, const std::string &tag) { - std::string payload; - for (std::size_t i = 0; i < elements.size(); ++i) { - if (i > 0) payload += ","; - std::ostringstream element; - if constexpr (std::is_same_v::value_type, - Real>) - element << std::setprecision(std::numeric_limits::max_digits10); - element << elements[i]; - payload += HexEncode(element.str()); - } - return std::make_pair(tag, payload); - }; - if (std::holds_alternative(value)) - return {"u", HexEncode(std::get(value).value)}; - if (std::holds_alternative(value)) - return {"i", HexEncode(std::to_string(std::get(value)))}; - if (std::holds_alternative(value)) { - std::ostringstream os; - os << std::setprecision(std::numeric_limits::max_digits10) - << std::get(value); - return {"r", HexEncode(os.str())}; - } - if (std::holds_alternative(value)) - return {"b", HexEncode(std::get(value) ? "true" : "false")}; - if (std::holds_alternative(value)) - return {"s", HexEncode(std::get(value))}; - if (std::holds_alternative>(value)) - return encode_vector(std::get>(value), "vi"); - if (std::holds_alternative>(value)) - return encode_vector(std::get>(value), "vr"); - if (std::holds_alternative>(value)) - return encode_vector(std::get>(value), "vb"); - if (std::holds_alternative>(value)) - return encode_vector(std::get>(value), "vs"); - const auto &values = std::get(value).values; - std::string payload; - for (std::size_t i = 0; i < values.size(); ++i) { - if (i > 0) payload += ","; - payload += ScalarTag(values[i]) + ":" + HexEncode(ScalarPayload(values[i])); - } - return {"vu", payload}; -} - -ParamValue DecodeRestartValue(const std::string &tag, const std::string &payload) { - if (tag == "u" || tag == "i" || tag == "r" || tag == "b" || tag == "s") { - auto scalar = DecodeScalar(tag, HexDecode(payload)); - return std::visit([](const auto &item) -> ParamValue { return item; }, scalar); - } - std::vector fields; - std::stringstream stream(payload); - std::string field; - while (std::getline(stream, field, ',')) - if (!field.empty()) fields.push_back(field); - if (tag == "vi") { - std::vector v; - for (const auto &f : fields) - v.push_back(std::stoi(HexDecode(f))); - return v; - } - if (tag == "vr") { - std::vector v; - for (const auto &f : fields) - v.push_back(static_cast(std::stod(HexDecode(f)))); - return v; - } - if (tag == "vb") { - std::vector v; - for (const auto &f : fields) - v.push_back(HexDecode(f) == "true"); - return v; - } - if (tag == "vs") { - std::vector v; - for (const auto &f : fields) - v.push_back(HexDecode(f)); - return v; - } - if (tag == "vu") { - UnresolvedVector v; - for (const auto &f : fields) { - const auto colon = f.find(':'); - if (colon == std::string::npos) - throw std::runtime_error("Invalid restart vector payload"); - v.values.emplace_back( - DecodeScalar(f.substr(0, colon), HexDecode(f.substr(colon + 1)))); - } - return v; - } - throw std::runtime_error("Unknown restart value tag"); -} -} // namespace //---------------------------------------------------------------------------------------- // ParameterInput constructor @@ -295,23 +130,6 @@ void ParameterInput::LoadFromStream(std::istream &is) { if (line.empty()) continue; // skip blank line first_char = line.find_first_not_of(" "); // skip white space if (first_char == std::string::npos) continue; // line is all white space - if (line.compare(first_char, 2, "#@") == 0) { - std::istringstream directive(line.substr(first_char + 2)); - std::string kind; - directive >> kind; - if (kind == "block" && !block_name.empty()) { - std::string class_name, instance_name, canonical_path; - directive >> class_name >> instance_name >> canonical_path; - AddParsedBlock(block_name, HexDecode(class_name), HexDecode(instance_name), - HexDecode(canonical_path)); - } else if (kind == "param" && !block_name.empty()) { - std::string name, tag, payload; - directive >> name >> tag >> payload; - AddParsedParameter(block_name, HexDecode(name), DecodeRestartValue(tag, payload), - "# From restart metadata"); - } - continue; - } if (line.compare(first_char, 1, "#") == 0) continue; // skip comments if (line.compare(first_char, 9, "") == 0) break; // stop on @@ -936,26 +754,6 @@ void ParameterInput::ParameterDump(std::ostream &os) { os << "" << std::endl; // finish with par-end (useful in restart files) } -void ParameterInput::RestartDump(std::ostream &os) { - os << "#---------------------- PAR_RESTART_DUMP ----------------------" << std::endl; - os << "#@parthenon-restart-v1" << std::endl; - for (const auto &block : param_storage_) { - os << "<" << block.name << ">" << std::endl; - os << "#@block " << HexEncode(block.class_name) << " " - << HexEncode(block.instance_name) << " " << HexEncode(block.canonical_path) - << std::endl; - for (const auto ¶m : block.params) { - os << param.name << " = " << param.ToString() << param.comment << std::endl; - const auto [tag, payload] = EncodeRestartValue(param.value); - os << "#@param " << HexEncode(param.name) << " " << tag; - if (!payload.empty()) os << " " << payload; - os << std::endl; - } - } - os << "#---------------------- PAR_RESTART_DUMP ----------------------" << std::endl; - os << "" << std::endl; -} - void ParameterInput::OutputParameterTable(std::ostream &os, const std::regex &block_regex) const { // Loop through once and store in a map for lexicographic ordering diff --git a/src/parameter_input.hpp b/src/parameter_input.hpp index d4e64b3f13b2e..2da8dacd4df50 100644 --- a/src/parameter_input.hpp +++ b/src/parameter_input.hpp @@ -295,12 +295,8 @@ class ParameterInput { const std::string &canonical_path = ""); void ParameterDump(std::ostream &os); - // Backward-readable restart serialization that additionally preserves - // parser metadata and unresolved/typed value representations. - void RestartDump(std::ostream &os); // Non-owning link to the input deck retained by ParthenonManager. Output - // writers use it to generate current Rummy restart state while RestartDump - // remains available for native and legacy restart compatibility. + // writers use it to generate current Rummy restart state. void SetRummyDeck(Rummy::DeckBase *deck) { rummy_deck_ = deck; } Rummy::DeckBase *GetRummyDeck() const { return rummy_deck_; } // TODO(JMM): Make this more general?