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 1 commit 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
14 changes: 14 additions & 0 deletions 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.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/test.yaml")
configure_file(alias.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/alias.yaml")
configure_file(nested_alias.yaml "${Ginkgo_BINARY_DIR}/extensions/test/config/nested_alias.yaml")
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 - 2024 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);
}
114 changes: 114 additions & 0 deletions include/ginkgo/extensions/config/yaml_config.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: 2017 - 2024 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 the 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 {
std::string content = it->first.as<std::string>();
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. This function can not
* know which entry is the actual solver, so please use the parse_yaml function.
*/
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