Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add yaml-cpp support for file config #1677

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ IncludeCategories:
- Regex: '^<(omp|cu|hip|oneapi|thrust|CL/|cooperative|mpi|nvToolsExt|Kokkos).*'
Priority: 2
SortPriority: 3
- Regex: '^<(nlohmann|gflags|gtest|sde_lib|papi).*'
- Regex: '^<(yaml-cpp|nlohmann|gflags|gtest|sde_lib|papi).*'
Priority: 4
- Regex: '<ginkgo/ginkgo.hpp>'
Priority: 6
Expand Down
16 changes: 15 additions & 1 deletion extensions/test/config/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
find_package(yaml-cpp 0.8.0 QUIET)
if(NOT yaml-cpp_FOUND)
message(STATUS "Fetching external yaml-cpp")
FetchContent_Declare(
yaml-cpp
GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git
GIT_TAG 0.8.0
)
FetchContent_MakeAvailable(yaml-cpp)
endif()
ginkgo_create_test(json_config ADDITIONAL_LIBRARIES nlohmann_json::nlohmann_json)
ginkgo_create_test(yaml_config ADDITIONAL_LIBRARIES yaml-cpp::yaml-cpp)

# prepare the testing file and generate location
configure_file("${Ginkgo_SOURCE_DIR}/extensions/test/config/file_location.hpp.in"
"${Ginkgo_BINARY_DIR}/extensions/test/config/file_location.hpp" @ONLY)
configure_file(test.json "${Ginkgo_BINARY_DIR}/extensions/test/config/test.json")
configure_file(test.json "${Ginkgo_BINARY_DIR}/extensions/test/config/test.json" COPYONLY)
configure_file(test.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/test.yaml" COPYONLY)
configure_file(alias.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/alias.yaml" COPYONLY)
configure_file(nested_alias.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/nested_alias.yaml" COPYONLY)
7 changes: 7 additions & 0 deletions extensions/test/config/alias.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
base: &base_config
key1: 123
base2: &base_config2
key2: test
test:
<<: [*base_config, *base_config2]
key3: true
7 changes: 7 additions & 0 deletions extensions/test/config/file_location.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ namespace config {

const char* location_test_json =
"@Ginkgo_BINARY_DIR@/extensions/test/config/test.json";
const char* location_test_yaml =
"@Ginkgo_BINARY_DIR@/extensions/test/config/test.yaml";
const char* location_alias_yaml =
"@Ginkgo_BINARY_DIR@/extensions/test/config/alias.yaml";
const char* location_nested_alias_yaml =
"@Ginkgo_BINARY_DIR@/extensions/test/config/nested_alias.yaml";



} // namespace config
Expand Down
9 changes: 9 additions & 0 deletions extensions/test/config/nested_alias.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
base: &base_config
key1: 123
base2: &base_config2
<<: *base_config
key2: test
test:
<<: *base_config2
key2: override
key3: true
6 changes: 6 additions & 0 deletions extensions/test/config/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
item: 4
array:
- 3.0
- 4.5
map:
bool: false
127 changes: 127 additions & 0 deletions extensions/test/config/yaml_config.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2017 - 2025 The Ginkgo authors
//
// SPDX-License-Identifier: BSD-3-Clause

#include <ostream>
#include <stdexcept>
#include <string>

#include <gtest/gtest.h>
#include <yaml-cpp/yaml.h>

#include <ginkgo/core/config/property_tree.hpp>
#include <ginkgo/extensions/config/yaml_config.hpp>

#include "core/test/utils.hpp"
#include "extensions/test/config/file_location.hpp"


TEST(YamlConfig, ThrowIfInvalid)
{
const char yaml[] = "test: null";
auto d = YAML::Load(yaml);

ASSERT_THROW(gko::ext::config::parse_yaml(d), std::runtime_error);
}


TEST(YamlConfig, ReadMap)
{
const char yaml[] = R"(
test: A
bool: true
)";
auto d = YAML::Load(yaml);

auto ptree = gko::ext::config::parse_yaml(d);

ASSERT_EQ(ptree.get_map().size(), 2);
ASSERT_EQ(ptree.get("test").get_string(), "A");
ASSERT_EQ(ptree.get("bool").get_boolean(), true);
}


TEST(YamlConfig, ReadArray)
{
const char yaml[] = R"(
- A
- B
- C
)";
auto d = YAML::Load(yaml);

auto ptree = gko::ext::config::parse_yaml(d);

ASSERT_EQ(ptree.get_array().size(), 3);
ASSERT_EQ(ptree.get(0).get_string(), "A");
ASSERT_EQ(ptree.get(1).get_string(), "B");
ASSERT_EQ(ptree.get(2).get_string(), "C");
}


TEST(YamlConfig, ReadInput)
{
const char yaml[] = R"(
item: 4
array:
- 3.0
- 4.5
map:
bool: false)";
auto d = YAML::Load(yaml);

auto ptree = gko::ext::config::parse_yaml(d);

auto& child_array = ptree.get("array").get_array();
auto& child_map = ptree.get("map").get_map();
ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get("item").get_integer(), 4);
ASSERT_EQ(child_array.size(), 2);
ASSERT_EQ(child_array.at(0).get_real(), 3.0);
ASSERT_EQ(child_array.at(1).get_real(), 4.5);
ASSERT_EQ(child_map.size(), 1);
ASSERT_EQ(child_map.at("bool").get_boolean(), false);
}


TEST(YamlConfig, ReadInputFromFile)
{
auto ptree =
gko::ext::config::parse_yaml_file(gko::ext::config::location_test_yaml);

auto& child_array = ptree.get("array").get_array();
auto& child_map = ptree.get("map").get_map();
ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get("item").get_integer(), 4);
ASSERT_EQ(child_array.size(), 2);
ASSERT_EQ(child_array.at(0).get_real(), 3.0);
ASSERT_EQ(child_array.at(1).get_real(), 4.5);
ASSERT_EQ(child_map.size(), 1);
ASSERT_EQ(child_map.at("bool").get_boolean(), false);
}


TEST(YamlConfig, ReadInputFromFileWithAlias)
{
auto yaml = YAML::LoadFile(gko::ext::config::location_alias_yaml);

auto ptree = gko::ext::config::parse_yaml(yaml["test"]);

ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get_map().at("key1").get_integer(), 123);
ASSERT_EQ(ptree.get_map().at("key2").get_string(), "test");
ASSERT_EQ(ptree.get_map().at("key3").get_boolean(), true);
}


TEST(YamlConfig, ReadInputFromFileWithNestedAliasAndOverwrite)
{
auto yaml = YAML::LoadFile(gko::ext::config::location_nested_alias_yaml);

auto ptree = gko::ext::config::parse_yaml(yaml["test"]);

ASSERT_EQ(ptree.get_map().size(), 3);
ASSERT_EQ(ptree.get_map().at("key1").get_integer(), 123);
ASSERT_EQ(ptree.get_map().at("key2").get_string(), "override");
ASSERT_EQ(ptree.get_map().at("key3").get_boolean(), true);
}
130 changes: 130 additions & 0 deletions include/ginkgo/extensions/config/yaml_config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-FileCopyrightText: 2017 - 2025 The Ginkgo authors
//
// SPDX-License-Identifier: BSD-3-Clause

#ifndef GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_
#define GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_

#include <iostream>
#include <stdexcept>
#include <string>

#include <yaml-cpp/yaml.h>

#include <ginkgo/core/config/property_tree.hpp>


namespace gko {
namespace ext {
namespace config {


/**
* parse_yaml takes a yaml-cpp node object to generate the property tree
* object
*/
inline gko::config::pnode parse_yaml(const YAML::Node& input)
{
auto parse_array = [](const auto& arr) {
gko::config::pnode::array_type nodes;
for (const auto& it : arr) {
nodes.emplace_back(parse_yaml(it));
}
return gko::config::pnode{nodes};
};
auto parse_map = [](const auto& map) {
gko::config::pnode::map_type nodes;
// use [] to get override behavior
for (YAML::const_iterator it = map.begin(); it != map.end(); ++it) {
std::string key = it->first.as<std::string>();
// yaml-cpp keeps the alias without resolving it when parsing.
// We resolve them here.
if (key == "<<") {
auto node = parse_yaml(it->second);
if (node.get_tag() == gko::config::pnode::tag_t::array) {
for (const auto& arr : node.get_array()) {
for (const auto& item : arr.get_map()) {
nodes[item.first] = item.second;
}
}
} else if (node.get_tag() == gko::config::pnode::tag_t::map) {
for (const auto& item : node.get_map()) {
nodes[item.first] = item.second;
}
} else {
std::runtime_error("can not handle this alias: " +
YAML::Dump(it->second));
}
} else {
nodes[key] = parse_yaml(it->second);
}
}
return gko::config::pnode{nodes};
};
// yaml-cpp does not have type check
auto parse_data = [](const auto& data) {
if (std::int64_t value;
YAML::convert<std::int64_t>::decode(data, value)) {
return gko::config::pnode{value};
}
if (bool value; YAML::convert<bool>::decode(data, value)) {
return gko::config::pnode{value};
}
if (double value; YAML::convert<double>::decode(data, value)) {
return gko::config::pnode{value};
}
if (std::string value;
YAML::convert<std::string>::decode(data, value)) {
return gko::config::pnode{value};
}
std::string content = YAML::Dump(data);
throw std::runtime_error(
"property_tree can not handle the node with content: " + content);
};

if (input.IsSequence()) {
return parse_array(input);
}
if (input.IsMap()) {
return parse_map(input);
}
return parse_data(input);
}


/**
* parse_yaml_file takes the yaml file to generate the property tree object
*
* @note Because YAML always needs a entry for reusing, there will be more than
* one entry when putting the anchors in the top level. It is unclear which
* entry is the actual solver to parse, so please use the parse_yaml function
* and specify the actual entry.
*
* for example,
* ```
* reuse: &reuse_config
* ...
* actual:
* << *reuse
* ...
* ```
* when passing the file to this function, `reuse` and `actual` are valid
* entries such that we can not randomly pick one as solver.
* ```
* // yaml is the object from the file
* auto solver_factory = parse_yaml(yaml["actual"]);
* ```
* By doing so, we know the `actual` entry is the solver to parse.
*/
inline gko::config::pnode parse_yaml_file(std::string filename)
{
return parse_yaml(YAML::LoadFile(filename));
}


} // namespace config
} // namespace ext
} // namespace gko


#endif // GKO_PUBLIC_EXTENSIONS_CONFIG_JSON_CONFIG_HPP_
Loading