Skip to content

Commit 8c4306f

Browse files
authored
Merge pull request #575 from sebproell/input-file-in-sections
Use InputFile::match_section
2 parents 118e694 + d04f25a commit 8c4306f

File tree

6 files changed

+120
-171
lines changed

6 files changed

+120
-171
lines changed

src/core/fem/src/condition/4C_fem_condition_definition.cpp

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,30 +64,27 @@ void Core::Conditions::ConditionDefinition::read(Core::IO::InputFile& input,
6464
all_of(specs_),
6565
});
6666

67-
for (const auto& fragment : input.in_section(section_name()))
67+
Core::IO::InputParameterContainer container;
68+
try
6869
{
69-
std::optional<Core::IO::InputParameterContainer> data;
70-
try
71-
{
72-
data = fragment.match(condition_spec);
73-
}
74-
catch (const Core::Exception& e)
75-
{
76-
FOUR_C_THROW("Failed to match condition specification in section '{}'. The error was:\n{}.",
77-
section_name().c_str(), e.what());
78-
}
79-
if (!data)
80-
{
81-
FOUR_C_THROW(
82-
"Failed to match condition specification in section '{}'.", sectionname_.c_str());
83-
}
70+
input.match_section(section_name(), container);
71+
}
72+
catch (const Core::Exception& e)
73+
{
74+
FOUR_C_THROW("Failed to match condition specification in section '{}'. The error was:\n{}.",
75+
section_name(), e.what());
76+
}
8477

78+
79+
for (const auto& condition_data :
80+
container.get_or<std::vector<Core::IO::InputParameterContainer>>(section_name(), {}))
81+
{
8582
// Read a one-based condition number but convert it to zero-based for internal use.
86-
const int dobjid = data->get<int>("E") - 1;
83+
const int dobjid = condition_data.get<int>("E") - 1;
8784

8885
std::shared_ptr<Core::Conditions::Condition> condition =
8986
std::make_shared<Core::Conditions::Condition>(dobjid, condtype_, buildgeometry_, gtype_);
90-
condition->parameters() = *std::move(data);
87+
condition->parameters() = condition_data;
9188

9289
//------------------------------- put condition in map of conditions
9390
cmap.insert(std::pair<int, std::shared_ptr<Core::Conditions::Condition>>(dobjid, condition));

src/core/io/src/4C_io_input_file.cpp

Lines changed: 40 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -309,18 +309,32 @@ namespace Core::IO
309309
std::all_of(section_name.begin() + 5, section_name.end(),
310310
[](const char c) { return std::isdigit(c); });
311311
}
312+
313+
bool is_legacy_section(const std::string& section_name) const
314+
{
315+
return std::ranges::any_of(
316+
legacy_section_names_, [&](const auto& name) { return name == section_name; });
317+
}
318+
319+
InputFile::FragmentIteratorRange in_section(const std::string& section_name) const
320+
{
321+
static const std::vector<InputFile::Fragment> empty;
322+
323+
if (!content_by_section_.contains(section_name))
324+
{
325+
return std::views::all(empty);
326+
}
327+
328+
// Take a const reference to the section content to match the return type.
329+
const auto& lines = content_by_section_.at(section_name).fragments;
330+
return std::views::all(lines);
331+
}
312332
};
313333

314334
} // namespace Internal
315335

316336
namespace
317337
{
318-
/**
319-
* Sections that contain at least this number of entries are considered huge and are only
320-
* available on rank 0.
321-
*/
322-
constexpr std::size_t huge_section_threshold = 10'000;
323-
324338
//! The different ways we want to handle sections in the input file.
325339
enum class SectionType
326340
{
@@ -720,31 +734,28 @@ namespace Core::IO
720734
// Communicate the dat content
721735
if (Core::Communication::my_mpi_rank(pimpl_->comm_) == 0)
722736
{
723-
// Temporarily move the sections that are not huge into a separate map.
724-
std::unordered_map<std::string, Internal::SectionContent> non_huge_sections;
737+
// Temporarily move the sections that we want to broadcast into a separate map.
738+
std::unordered_map<std::string, Internal::SectionContent> non_legacy_sections;
725739

726740
for (auto&& [section_name, content] : pimpl_->content_by_section_)
727741
{
728-
if (std::holds_alternative<Internal::SectionContent::DatContent>(content.content))
742+
if (std::holds_alternative<Internal::SectionContent::DatContent>(content.content) &&
743+
!pimpl_->is_legacy_section(section_name))
729744
{
730-
if (content.as_dat().lines.size() < huge_section_threshold)
731-
{
732-
non_huge_sections[section_name] = std::move(content);
733-
}
745+
non_legacy_sections[section_name] = std::move(content);
734746
}
735747
}
736748

737-
Core::Communication::broadcast(non_huge_sections, 0, pimpl_->comm_);
749+
Core::Communication::broadcast(non_legacy_sections, 0, pimpl_->comm_);
738750

739-
// Move the non-huge sections back into the main map.
740-
for (auto&& [section_name, content] : non_huge_sections)
751+
for (auto&& [section_name, content] : non_legacy_sections)
741752
{
742753
pimpl_->content_by_section_[section_name] = std::move(content);
743754
}
744755
}
745756
else
746757
{
747-
// Other ranks receive the non-huge sections.
758+
// Other ranks receive the non-legacy sections.
748759
Core::Communication::broadcast(pimpl_->content_by_section_, 0, pimpl_->comm_);
749760
}
750761

@@ -754,7 +765,7 @@ namespace Core::IO
754765
{
755766
ryml::Tree tree_with_small_sections = init_yaml_tree_with_exceptions();
756767
tree_with_small_sections.rootref() |= ryml::MAP;
757-
// Go through the tree and drop the huge sections from the tree.
768+
// Go through the tree and drop the legacy sections from the tree.
758769
for (auto file_node : pimpl_->yaml_tree_.rootref())
759770
{
760771
auto new_file_node = tree_with_small_sections.rootref().append_child();
@@ -763,8 +774,7 @@ namespace Core::IO
763774

764775
for (auto section_node : file_node.children())
765776
{
766-
if (section_node.is_map() ||
767-
(section_node.is_seq() && section_node.num_children() < huge_section_threshold))
777+
if (!pimpl_->is_legacy_section(to_string(section_node.key())))
768778
{
769779
// Copy the node to the new tree.
770780
auto new_section_node = new_file_node.append_child();
@@ -773,10 +783,8 @@ namespace Core::IO
773783
}
774784
}
775785

776-
std::stringstream ss;
777-
ss << tree_with_small_sections;
778-
std::string yaml_str = ss.str();
779-
Core::Communication::broadcast(yaml_str, /*root*/ 0, pimpl_->comm_);
786+
auto serialized_tree = ryml::emitrs_yaml<std::string>(tree_with_small_sections);
787+
Core::Communication::broadcast(serialized_tree, /*root*/ 0, pimpl_->comm_);
780788
}
781789
else
782790
{
@@ -836,48 +844,15 @@ namespace Core::IO
836844
}
837845
}
838846

839-
InputFile::FragmentIteratorRange InputFile::in_section(const std::string& section_name) const
840-
{
841-
static const std::vector<Fragment> empty;
842-
843-
// Early return in case the section does not exist at all.
844-
const bool known_somewhere = has_section(section_name);
845-
if (!known_somewhere)
846-
{
847-
return std::views::all(empty);
848-
}
849-
850-
const bool locally_known = pimpl_->content_by_section_.contains(section_name);
851-
const bool known_everywhere = Core::Communication::all_reduce<bool>(
852-
locally_known, [](const bool& r, const bool& in) { return r && in; }, pimpl_->comm_);
853-
854-
if (known_everywhere)
855-
{
856-
// Take a const reference to the section content to match the return type.
857-
const auto& lines = pimpl_->content_by_section_.at(section_name).fragments;
858-
return std::views::all(lines);
859-
}
860-
else
861-
// Distribute the content of the section to all ranks.
862-
{
863-
FOUR_C_ASSERT((!locally_known && (Core::Communication::my_mpi_rank(pimpl_->comm_) > 0)) ||
864-
(locally_known && (Core::Communication::my_mpi_rank(pimpl_->comm_) == 0)),
865-
"Implementation error: section should be known on rank 0 and unknown on others.");
866-
867-
auto& content = pimpl_->content_by_section_[section_name];
868-
Core::Communication::broadcast(content, 0, pimpl_->comm_);
869-
content.set_up_fragments();
870-
871-
const auto& lines = pimpl_->content_by_section_.at(section_name).fragments;
872-
return std::views::all(lines);
873-
}
874-
}
875-
876-
877847

878848
InputFile::FragmentIteratorRange InputFile::in_section_rank_0_only(
879849
const std::string& section_name) const
880850
{
851+
FOUR_C_ASSERT_ALWAYS(pimpl_->is_legacy_section(section_name),
852+
"You tried to process section '{}' on rank 0 only, but this feature is meant for special "
853+
"legacy sections. Please use match_section() instead.",
854+
section_name);
855+
881856
if (Core::Communication::my_mpi_rank(pimpl_->comm_) == 0 &&
882857
pimpl_->content_by_section_.contains(section_name))
883858
{
@@ -952,7 +927,7 @@ namespace Core::IO
952927
if (list_spec)
953928
{
954929
std::vector<InputParameterContainer> list_entries;
955-
for (const auto& line : in_section(section_name))
930+
for (const auto& line : pimpl_->in_section(section_name))
956931
{
957932
Core::IO::ValueParser parser{line.get_as_dat_style_string(),
958933
{.user_scope_message =
@@ -977,7 +952,7 @@ namespace Core::IO
977952
// Create a group in the dat file format by starting with the section name.
978953
std::stringstream flattened_dat;
979954
flattened_dat << std::quoted(section_name) << " ";
980-
for (const auto& line : in_section(section_name))
955+
for (const auto& line : pimpl_->in_section(section_name))
981956
{
982957
std::string line_str(line.get_as_dat_style_string());
983958
// Split the line into key-value according to the dat file format. Quote keys and values
@@ -1084,7 +1059,7 @@ namespace Core::IO
10841059
auto section = tree.rootref().append_child();
10851060
section << ryml::key(section_name);
10861061
section |= ryml::SEQ;
1087-
for (const auto& line : in_section(section_name))
1062+
for (const auto& line : pimpl_->in_section(section_name))
10881063
{
10891064
section.append_child() = ryml::csubstr(
10901065
line.get_as_dat_style_string().data(), line.get_as_dat_style_string().size());

src/core/io/src/4C_io_input_file.hpp

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,16 @@ namespace Core::IO
4040
* This class encapsulates input files independent of their format.
4141
*
4242
* Objects of this class read the content of a file and grant access to it. The input is not
43-
* yet interpreted in any way. Input files contain different sections. A section either contains
44-
* key-value pairs or a list of arbitrary lines of text. The content is accessed via the
45-
* in_section() function which return InputFile::Fragment objects. These hide what exactly is
46-
* contained and in which format. The content can be matched against an expected InputSpec to
47-
* extract meaningful data.
43+
* yet interpreted in any way. Input files contain different sections. The content can be matched
44+
* against an expected InputSpec via match_section() to extract meaningful data.
4845
*
4946
* Sections may appear in an arbitrary order and each section name must be unique. An exception is
5047
* the special section named "INCLUDES" which can contain a list of other files that should be
5148
* read in addition to the current file.
5249
*
5350
* Three file formats are supported: the custom .dat file format and the standard .yaml (or .yml)
5451
* and .json formats. The format of a file is detected based on its ending. If the ending is not
55-
* one of the above mentioned, the file is assumed to be in the .dat format.
52+
* one of the above-mentioned, the file is assumed to be in the .dat format.
5653
* Included files do not have to use the same format as the including file.
5754
*
5855
* The following example shows the structure of a .dat file:
@@ -86,12 +83,13 @@ namespace Core::IO
8683
* SECTION2:
8784
* - A line with content that is not a key-value pair.
8885
* - It can contain anything.
86+
* - We call these unstructured sections "legacy sections".
8987
* @endcode
9088
*
9189
*
92-
* @note The file is only read on rank 0 to save memory. Sections that are huge are only
93-
* distributed to other ranks if they are accessed through line_in_section(). If you only
94-
* want to read a section on rank 0, use in_section_rank_0_only().
90+
* @note The file is only read on rank 0 to save memory. All sections are broadcast to all other
91+
* ranks, except for the legacy sections (see the constructor). Legacy sections need to be
92+
* consumed on rank 0 with the help of in_section_rank_0_only().
9593
*/
9694
class InputFile
9795
{
@@ -213,31 +211,10 @@ namespace Core::IO
213211
*/
214212
[[nodiscard]] std::filesystem::path file_for_section(const std::string& section_name) const;
215213

216-
/**
217-
* Get a range of Fragments inside a section. A Fragment always contains meaningful content,
218-
* i.e., something other than whitespace or comments. The usual way to interact with the
219-
* Fragments is as follows:
220-
*
221-
* @code
222-
* for (const auto& input_fragment : input.in_section("section_name"))
223-
* {
224-
* auto parameters = fragment.match(spec);
225-
* }
226-
* @endcode
227-
*
228-
* @return A range of Fragments which encapsulate the content of the section. Use the
229-
* Fragment::match() function to extract the content.
230-
*
231-
* @note This is a collective call that needs to be called on all MPI ranks in the
232-
* communicator associated with this object. Depending on the section size, the content
233-
* might need to be distributed from rank 0 to all other ranks. This happens automatically.
234-
*/
235-
FragmentIteratorRange in_section(const std::string& section_name) const;
236214

237215
/**
238-
* This function is similar to in_section(), but it only returns the lines on rank 0 and
239-
* returns an empty range on all other ranks. This is useful for sections that might be huge
240-
* and are not processed on all ranks.
216+
* Returns the lines in a section on rank 0 and returns an empty range on all other ranks. This
217+
* only works for legacy sections.
241218
*/
242219
FragmentIteratorRange in_section_rank_0_only(const std::string& section_name) const;
243220

@@ -247,7 +224,7 @@ namespace Core::IO
247224
* that you do not need to pass an InputSpec to this function, since the InputFile object
248225
* already knows about the expected format of the section. Nevertheless, this function only
249226
* makes sense for sections with a known InputSpec. Legacy string sections cannot use this
250-
* function and must be processed with in_section() or in_section_rank_0_only().
227+
* function and must be processed with in_section_rank_0_only().
251228
*/
252229
void match_section(const std::string& section_name, InputParameterContainer& container) const;
253230

src/core/io/src/4C_io_input_parameter_container.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <map>
2222
#include <optional>
2323
#include <ostream>
24+
#include <ranges>
2425
#include <string>
2526
#include <typeindex>
2627
#include <vector>
@@ -50,6 +51,8 @@ namespace Core::IO
5051
*/
5152
using List = std::vector<InputParameterContainer>;
5253

54+
using GroupStorage = std::map<std::string, InputParameterContainer>;
55+
5356
/**
5457
* \brief Add @data to the container at the given key @name.
5558
*
@@ -68,6 +71,11 @@ namespace Core::IO
6871
*/
6972
[[nodiscard]] const InputParameterContainer& group(const std::string& name) const;
7073

74+
/**
75+
* Get a range view of all groups in the container.
76+
*/
77+
[[nodiscard]] auto groups() const;
78+
7179
/**
7280
* Check if a group with the name @p name exists.
7381
*/
@@ -266,6 +274,11 @@ namespace Core::IO::Internal::InputParameterContainerImplementation
266274
} // namespace Core::IO::Internal::InputParameterContainerImplementation
267275

268276

277+
inline auto Core::IO::InputParameterContainer::groups() const
278+
{
279+
return std::ranges::subrange(groups_);
280+
}
281+
269282

270283
template <typename T>
271284
const T& Core::IO::InputParameterContainer::get(const std::string& name) const

0 commit comments

Comments
 (0)