diff --git a/.gitignore b/.gitignore index f4a71f8..04ad69d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,10 @@ CMakeDoxyfile.in CMakeDoxygenDefaults.cmake DartConfiguration.tcl __pycache__ +/.vscode +/.devcontainer +*.dSYM +testbracket +testmvmap +testdf +testgraph \ No newline at end of file diff --git a/CM-broken.txt b/CM-broken.txt new file mode 100644 index 0000000..583fb48 --- /dev/null +++ b/CM-broken.txt @@ -0,0 +1,106 @@ +# Copyright 2020 Lawrence Livermore National Security, LLC and other CLIPPy +# Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: MIT + +# Works with 3.11 and tested through 3.15 (not tested yet) +cmake_minimum_required(VERSION 3.14) +set(ALLOW_DUPLICATE_CUSTOM_TARGETS TRUE) + +project(CLIPPy + VERSION 0.2 + DESCRIPTION "Command Line Interface Plus Python" + LANGUAGES CXX) + +include(FetchContent) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Only do these if this is the main project, and not if it is included through add_subdirectory +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + # Let's ensure -std=c++xx instead of -std=g++xx + set(CMAKE_CXX_EXTENSIONS OFF) + + # Let's nicely support folders in IDE's + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + + # Testing only available if this is the main app + # Note this needs to be done in the main CMakeLists + # since it calls enable_testing, which must be in the + # main CMakeLists. + include(CTest) + + # Docs only available if this is the main app + find_package(Doxygen) + if(Doxygen_FOUND) + #add_subdirectory(docs) + else() + message(STATUS "Doxygen not found, not building docs") + endif() +endif() + +# +# Metall +# find_package(Metall QUIET) +# if (NOT Metall_FOUND) +# #set(METALL_WORK_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-work) +# set(METALL_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-src) +# set(METALL_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-build) +# set(METALL_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-install) +# FetchContent_Declare(Metall +# GIT_REPOSITORY https://github.com/LLNL/metall.git +# GIT_TAG master +# ) +# # SOURCE_DIR ${METALL_SOURCE_DIR} +# # BINARY_DIR ${METALL_BUILD_DIR} +# # CMAKE_ARGS -DINSTALL_HEADER_ONLY=ON -DCMAKE_INSTALL_PREFIX=${METALL_INSTALL_DIR} +# # ) +# # set(METALL_INCLUDE_DIR ${METALL_INSTALL_DIR}/include) +# FetchContent_MakeAvailable(Metall) +# endif () + +# find_package(Threads REQUIRED) + + +# +# Boost +# find_package(Boost 1.83 REQUIRED COMPONENTS) + +include(FetchContent) + +# Fetch Boost +FetchContent_Declare( + boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.83.0 # Replace with the desired Boost version + GIT_PROGRESS TRUE +) +FetchContent_MakeAvailable(boost) + +# Ensure submodules for Boost.JSON are initialized +add_subdirectory(${boost_SOURCE_DIR}/libs/json ${boost_BINARY_DIR}/boost-json) + + +### Require out-of-source builds +file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) +if(EXISTS "${LOC_PATH}") + message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") +endif() + +include_directories("${PROJECT_SOURCE_DIR}/include") + +option(TEST_WITH_SLURM "Run tests with Slurm" OFF) + +# Header-only library, so likely not have src dir +# add_subdirectory(src) + +# Testing & examples are only available if this is the main app +# Emergency override MODERN_CMAKE_BUILD_TESTING provided as well +if((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MODERN_CMAKE_BUILD_TESTING) AND BUILD_TESTING) + add_subdirectory(test) + # Example codes are here. + #add_subdirectory(examples) +endif() diff --git a/CM-working.txt b/CM-working.txt new file mode 100644 index 0000000..421b8cf --- /dev/null +++ b/CM-working.txt @@ -0,0 +1,109 @@ +# Copyright 2020 Lawrence Livermore National Security, LLC and other CLIPPy +# Project Developers. See the top-level COPYRIGHT file for details. +# +# SPDX-License-Identifier: MIT + +# Works with 3.11 and tested through 3.15 (not tested yet) +cmake_minimum_required(VERSION 3.14) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(ALLOW_DUPLICATE_CUSTOM_TARGETS TRUE) + +project(CLIPPy + VERSION 0.2 + DESCRIPTION "Command Line Interface Plus Python" + LANGUAGES CXX) + +include(FetchContent) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Only do these if this is the main project, and not if it is included through add_subdirectory +if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) + + set(CMAKE_CXX_STANDARD 20) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + # Let's ensure -std=c++xx instead of -std=g++xx + set(CMAKE_CXX_EXTENSIONS OFF) + + # Let's nicely support folders in IDE's + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + + # Testing only available if this is the main app + # Note this needs to be done in the main CMakeLists + # since it calls enable_testing, which must be in the + # main CMakeLists. + include(CTest) + + # Docs only available if this is the main app + find_package(Doxygen) + if(Doxygen_FOUND) + #add_subdirectory(docs) + else() + message(STATUS "Doxygen not found, not building docs") + endif() +endif() + +# +# Metall +# find_package(Metall QUIET) +# if (NOT Metall_FOUND) +# #set(METALL_WORK_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-work) +# set(METALL_SOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-src) +# set(METALL_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-build) +# set(METALL_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/metall-install) +# FetchContent_Declare(Metall +# GIT_REPOSITORY https://github.com/LLNL/metall.git +# GIT_TAG master +# ) +# # SOURCE_DIR ${METALL_SOURCE_DIR} +# # BINARY_DIR ${METALL_BUILD_DIR} +# # CMAKE_ARGS -DINSTALL_HEADER_ONLY=ON -DCMAKE_INSTALL_PREFIX=${METALL_INSTALL_DIR} +# # ) +# # set(METALL_INCLUDE_DIR ${METALL_INSTALL_DIR}/include) +# FetchContent_MakeAvailable(Metall) +# endif () + +# find_package(Threads REQUIRED) + + +# +# Boost +# find_package(Boost 1.83 REQUIRED COMPONENTS) + +# +# Boost +include(setup_boost) +prepare_fetchcontent_boost() +set(FETCHCONTENT_QUIET FALSE) +FetchContent_Declare( + Boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.83.0 + GIT_SUBMODULES ${BOOST_REQD_SUBMODULES} + GIT_PROGRESS TRUE + CONFIGURE_COMMAND "" # tell CMake it's not a cmake project +) +FetchContent_MakeAvailable(Boost) +get_boost_include_dirs() + + +### Require out-of-source builds +file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) +if(EXISTS "${LOC_PATH}") + message(FATAL_ERROR "You cannot build in a source directory (or any directory with a CMakeLists.txt file). Please make a build subdirectory. Feel free to remove CMakeCache.txt and CMakeFiles.") +endif() + +include_directories("${PROJECT_SOURCE_DIR}/include") + +option(TEST_WITH_SLURM "Run tests with Slurm" OFF) + +# Header-only library, so likely not have src dir +# add_subdirectory(src) + +# Testing & examples are only available if this is the main app +# Emergency override MODERN_CMAKE_BUILD_TESTING provided as well +if((CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME OR MODERN_CMAKE_BUILD_TESTING) AND BUILD_TESTING) + add_subdirectory(test) + # Example codes are here. + #add_subdirectory(examples) +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 932a91c..421b8cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ # Works with 3.11 and tested through 3.15 (not tested yet) cmake_minimum_required(VERSION 3.14) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") set(ALLOW_DUPLICATE_CUSTOM_TARGETS TRUE) project(CLIPPy @@ -18,7 +19,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Only do these if this is the main project, and not if it is included through add_subdirectory if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Let's ensure -std=c++xx instead of -std=g++xx @@ -67,7 +68,24 @@ endif() # # Boost -find_package(Boost 1.75 REQUIRED COMPONENTS) +# find_package(Boost 1.83 REQUIRED COMPONENTS) + +# +# Boost +include(setup_boost) +prepare_fetchcontent_boost() +set(FETCHCONTENT_QUIET FALSE) +FetchContent_Declare( + Boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.83.0 + GIT_SUBMODULES ${BOOST_REQD_SUBMODULES} + GIT_PROGRESS TRUE + CONFIGURE_COMMAND "" # tell CMake it's not a cmake project +) +FetchContent_MakeAvailable(Boost) +get_boost_include_dirs() + ### Require out-of-source builds file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" LOC_PATH) diff --git a/cmake/setup_boost.cmake b/cmake/setup_boost.cmake new file mode 100644 index 0000000..5b6b2e7 --- /dev/null +++ b/cmake/setup_boost.cmake @@ -0,0 +1,83 @@ +function(prepare_fetchcontent_boost) + set(BOOST_INCLUDE_LIBRARIES json PARENT_SCOPE) + set(BOOST_ENABLE_CMAKE ON PARENT_SCOPE) + + set(BOOST_REQD_SUBMODULES + "tools/cmake;" + "libs/assert;" + "libs/exception;" + "libs/throw_exception;" + "libs/static_assert;" + "libs/config;" + "libs/container;" + "libs/container_hash;" + "libs/utility;" + "libs/type_traits;" + "libs/move;" + "libs/tuple;" + "libs/variant2;" + "libs/detail;" + "libs/smart_ptr;" + "libs/integer;" + "libs/intrusive;" + "libs/io;" + # "libs/iostreams;" + "libs/describe;" + "libs/core;" + "libs/align;" + "libs/predef;" + "libs/preprocessor;" + "libs/system;" + "libs/winapi;" + "libs/mp11;" + "libs/json;" + # "libs/property_tree;" + # "libs/interprocess;" + # "libs/optional;" + # "libs/any;" + # "libs/type_index;" + # "libs/mpl;" + # "libs/multi_index;" + # "libs/serialization;" + # "libs/bind;" + # "libs/foreach;" + # "libs/iterator;" + # "libs/headers;" + # "libs/format;" + # "libs/range;" + # "libs/concept_check;" + # "libs/uuid;" + # "libs/random;" + # "libs/tti;" + # "libs/function_types;" + PARENT_SCOPE) +endfunction() + +function(get_boost_include_dirs) + list(APPEND BOOST_INCLUDE_DIRS + # ${Boost_SOURCE_DIR}/libs/interprocess/include + # ${Boost_SOURCE_DIR}/libs/property_tree/include + # ${Boost_SOURCE_DIR}/libs/optional/include + # ${Boost_SOURCE_DIR}/libs/any/include + # ${Boost_SOURCE_DIR}/libs/type_index/include + # ${Boost_SOURCE_DIR}/libs/mpl/include + # ${Boost_SOURCE_DIR}/libs/bind/include + # ${Boost_SOURCE_DIR}/libs/multi_index/include + # ${Boost_SOURCE_DIR}/libs/serialization/include + # ${Boost_SOURCE_DIR}/libs/foreach/include + # ${Boost_SOURCE_DIR}/libs/iterator/include + ${Boost_SOURCE_DIR}/libs/container/include + # ${Boost_SOURCE_DIR}/libs/unordered/include + # ${Boost_SOURCE_DIR}/libs/iostreams/include + ${Boost_SOURCE_DIR}/libs/system/include + ${Boost_SOURCE_DIR}/libs/describe/include + # ${Boost_SOURCE_DIR}/libs/format/include + # ${Boost_SOURCE_DIR}/libs/range/include + # ${Boost_SOURCE_DIR}/libs/concept_check/include + # ${Boost_SOURCE_DIR}/libs/uuid/include + # ${Boost_SOURCE_DIR}/libs/random/include + # ${Boost_SOURCE_DIR}/libs/tti/include + # ${Boost_SOURCE_DIR}/libs/function_types/include + ) + set(BOOST_INCLUDE_DIRS ${BOOST_INCLUDE_DIRS} PARENT_SCOPE) +endfunction() diff --git a/include/clippy/clippy.hpp b/include/clippy/clippy.hpp index 22ba8a0..60322d3 100644 --- a/include/clippy/clippy.hpp +++ b/include/clippy/clippy.hpp @@ -5,6 +5,7 @@ #pragma once +#include #include #include #include @@ -13,7 +14,6 @@ #include #include #include -#include #include "clippy-object.hpp" @@ -36,23 +36,23 @@ static constexpr bool LOG_JSON = false; namespace clippy { namespace { -template struct is_container { +template +struct is_container { enum { value = false, }; }; -template struct is_container> { +template +struct is_container> { enum { value = true, }; }; boost::json::value asContainer(boost::json::value val, bool requiresContainer) { - if (!requiresContainer) - return val; - if (val.is_array()) - return val; + if (!requiresContainer) return val; + if (val.is_array()) return val; boost::json::array res; @@ -76,10 +76,10 @@ struct BcastInput { } }; #endif -} // namespace +} // namespace class clippy { -public: + public: clippy(const std::string &name, const std::string &desc) { get_value(m_json_config, "method_name") = name; get_value(m_json_config, "desc") = desc; @@ -98,7 +98,8 @@ class clippy { ~clippy() { const bool requiresResponse = - !(m_json_return.is_null() && m_json_state.empty() && m_json_overwrite_args.empty() && m_json_selectors.is_null()); + !(m_json_return.is_null() && m_json_state.empty() && + m_json_overwrite_args.empty() && m_json_selectors.is_null()); if (requiresResponse) { int rank = 0; @@ -121,14 +122,14 @@ class clippy { } } - template void log(std::ofstream &logfile, const M &msg) { - if (LOG_JSON) - logfile << msg << std::flush; + template + void log(std::ofstream &logfile, const M &msg) { + if (LOG_JSON) logfile << msg << std::flush; } - template void log(const M &msg) { - if (!LOG_JSON) - return; + template + void log(const M &msg) { + if (!LOG_JSON) return; std::ofstream logfile{clippyLogFile, std::ofstream::app}; log(logfile, msg); @@ -159,11 +160,13 @@ class clippy { boost::json::value_from(default_val); } - void update_selectors(const std::map& map_selectors) { + void update_selectors( + const std::map &map_selectors) { m_json_selectors = boost::json::value_from(map_selectors); } - template void returns(const std::string &desc) { + template + void returns(const std::string &desc) { get_value(m_json_config, returns_key, "desc") = desc; } @@ -172,11 +175,10 @@ class clippy { m_returns_self = true; } - void return_self() { - m_returns_self = true; - } + void return_self() { m_returns_self = true; } - template void to_return(const T &value) { + template + void to_return(const T &value) { // if (detail::get_type_name() != // m_json_config[returns_key]["type"].get()) { // throw std::runtime_error("clippy::to_return(value): Invalid type."); @@ -184,7 +186,6 @@ class clippy { m_json_return = boost::json::value_from(value); } - void to_return(::clippy::object value) { m_json_return = std::move(value).json(); } @@ -202,7 +203,6 @@ class clippy { logfile << "<-hlp- " << m_json_config << std::endl; } - std::cout << m_json_config; return true; } @@ -239,7 +239,7 @@ class clippy { logfile << "<-hlp- " << m_json_config << std::endl; } - if(world.rank0()) { + if (world.rank0()) { std::cout << m_json_config; } return true; @@ -270,14 +270,16 @@ class clippy { } #endif /* WITH_YGM */ - template T get(const std::string &name) { + template + T get(const std::string &name) { static constexpr bool requires_container = is_container::value; - if (has_argument(name)) { // if the argument exists + if (has_argument(name)) { // if the argument exists + auto foo = get_value(m_json_input, name); return boost::json::value_to( asContainer(get_value(m_json_input, name), requires_container)); - } else { // it's an optional - // std::cout << "optional argument found: " + name << std::endl; + } else { // it's an optional + // std::cerr << "optional argument found: " + name << std::endl; return boost::json::value_to( asContainer(get_value(m_json_config, "args", name, "default_val"), requires_container)); @@ -288,11 +290,13 @@ class clippy { return has_value(m_json_input, state_key, name); } - template T get_state(const std::string &name) const { + template + T get_state(const std::string &name) const { return boost::json::value_to(get_value(m_json_input, state_key, name)); } - template void set_state(const std::string &name, T val) { + template + void set_state(const std::string &name, T val) { // if no state exists (= empty), then copy it from m_json_input if it exists // there; // otherwise just start with an empty state. @@ -319,23 +323,22 @@ class clippy { return false; } -private: + private: void write_response(std::ostream &os) const { // construct the response object boost::json::object json_response; // incl. the response if it has been set - if(m_returns_self) { + if (m_returns_self) { json_response["returns_self"] = true; } else if (!m_json_return.is_null()) json_response[returns_key] = m_json_return; // only communicate the state if it has been explicitly set. // no state -> no state update - if (!m_json_state.empty()) - json_response[state_key] = m_json_state; + if (!m_json_state.empty()) json_response[state_key] = m_json_state; - if(!m_json_selectors.is_null()) + if (!m_json_selectors.is_null()) json_response[selectors_key] = m_json_selectors; // only communicate the pass by reference arguments if explicitly set @@ -353,7 +356,8 @@ class clippy { // TODO: Warn/Check for unknown args } - template void add_optional_validator(const std::string &name) { + template + void add_optional_validator(const std::string &name) { if (m_input_validators.count(name) > 0) { std::stringstream ss; ss << "CLIPPy ERROR: Cannot have duplicate argument names: " << name @@ -363,7 +367,7 @@ class clippy { m_input_validators[name] = [name](const boost::json::value &j) { if (!j.get_object().contains(name)) { return; - } // Optional, only eval if present + } // Optional, only eval if present try { static constexpr bool requires_container = is_container::value; @@ -378,7 +382,8 @@ class clippy { }; } - template void add_required_validator(const std::string &name) { + template + void add_required_validator(const std::string &name) { if (m_input_validators.count(name) > 0) { throw std::runtime_error("Clippy:: Cannot have duplicate argument names"); } @@ -480,7 +485,7 @@ class clippy { boost::json::value m_json_selectors; boost::json::object m_json_state; boost::json::object m_json_overwrite_args; - bool m_returns_self=false; + bool m_returns_self = false; boost::json::object *m_json_input_state = nullptr; size_t m_next_position = 0; @@ -488,7 +493,7 @@ class clippy { std::map> m_input_validators; -public: + public: static constexpr const char *const state_key = "_state"; static constexpr const char *const selectors_key = "_selectors"; static constexpr const char *const returns_key = "returns"; @@ -496,7 +501,7 @@ class clippy { static constexpr const char *const class_desc_key = "class_desc"; }; -} // namespace clippy +} // namespace clippy namespace boost::json { void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, @@ -512,9 +517,9 @@ void tag_invoke(boost::json::value_from_tag, boost::json::value &jv, } } -std::vector> -tag_invoke(boost::json::value_to_tag>>, - const boost::json::value &jv) { +std::vector> tag_invoke( + boost::json::value_to_tag>>, + const boost::json::value &jv) { std::vector> value; auto &outer_array = jv.get_array(); @@ -555,4 +560,4 @@ std::vector> tag_invoke( return value; } -} // namespace boost::json +} // namespace boost::json diff --git a/include/clippy/selector.hpp b/include/clippy/selector.hpp new file mode 100644 index 0000000..af198ab --- /dev/null +++ b/include/clippy/selector.hpp @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include + +#include "boost/json.hpp" + +class selector { + std::string sel_str; + + std::vector dots; + + static std::vector make_dots(const std::string &sel_str) { + std::vector dots; + for (size_t i = 0; i < sel_str.size(); ++i) { + if (sel_str[i] == '.') { + dots.push_back(i); + } + } + dots.push_back(sel_str.size()); + return dots; + } + + public: + friend std::ostream &operator<<(std::ostream &os, const selector &sel); + friend void tag_invoke(boost::json::value_from_tag /*unused*/, + boost::json::value &v, const selector &sel); + explicit selector(const std::string &sel) + : sel_str(sel), dots(make_dots(sel_str)) {} + explicit selector(const char *sel) : selector(std::string(sel)) {} + selector(boost::json::object o) { + auto v = o.at("rule").as_object()["var"]; + sel_str = v.as_string().c_str(); + dots = make_dots(sel_str); + } + bool operator<(const selector &other) const { + return sel_str < other.sel_str; + } + operator std::string() { return sel_str; } + operator std::string() const { return sel_str; } + bool headeq(const std::string &comp) const { + return std::string_view(sel_str).substr(0, dots[0]) == comp; + } + std::optional tail() const { + if (dots.size() <= 1) { // remember that end of string is a dot + return std::nullopt; + } + return selector(sel_str.substr(dots[0] + 1)); + } +}; + +std::ostream &operator<<(std::ostream &os, const selector &sel) { + os << sel.sel_str; + return os; +} + +selector tag_invoke(boost::json::value_to_tag /*unused*/, + const boost::json::value &v) { + return v.as_object(); +} + +void tag_invoke(boost::json::value_from_tag /*unused*/, boost::json::value &v, + const selector &sel) { + std::cerr << "This should not be called." << std::endl; + // std::map o {}; + // o["expression_type"] = "jsonlogic"; + // o["rule"] = {{"var", sel.sel_str}}; + // v = {"expression_type": "jsonlogic", "rule": {"var": + // "node.degree"}}}sel.sel_str; +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9f56e11..0327a01 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,9 +10,11 @@ function ( add_test class_name method_name ) set(source "${method_name}.cpp") set(target "${class_name}_${method_name}") add_executable(${target} ${source}) - target_include_directories(${target} PRIVATE ${Boost_INCLUDE_DIRS}) - include_directories(${CMAKE_CURRENT_SOURCE_DIR}) + # target_include_directories(${target} PRIVATE ${Boost_INCLUDE_DIRS}) + include_directories(${CMAKE_CURRENT_SOURCE_DIR} include/) set_target_properties(${target} PROPERTIES OUTPUT_NAME "${method_name}" ) + target_include_directories(${target} PRIVATE ${PROJECT_SOURCE_DIR}/include ${BOOST_INCLUDE_DIRS} include/) + target_link_libraries(${target} PRIVATE Boost::json) endfunction() @@ -21,3 +23,5 @@ add_subdirectory(TestBag) add_subdirectory(TestSet) add_subdirectory(TestFunctions) add_subdirectory(TestSelector) +add_subdirectory(TestGraph) +add_subdirectory(TestDF) diff --git a/test/TestDF/CMakeLists.txt b/test/TestDF/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/TestDF/__init__.cpp b/test/TestDF/__init__.cpp new file mode 100644 index 0000000..8a4339d --- /dev/null +++ b/test/TestDF/__init__.cpp @@ -0,0 +1,32 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include + +#include "clippy/clippy.hpp" +#include "testdf.hpp" + +namespace boostjsn = boost::json; + +static const std::string method_name = "__init__"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Initializes a TestDF"}; + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + // testgraph needs to be convertible to json + testdf the_df{}; + clip.set_state(state_name, the_df); + std::map selectors; + clip.set_state(sel_state_name, selectors); + + return 0; +} diff --git a/test/TestDF/soonCMakeLists b/test/TestDF/soonCMakeLists new file mode 100644 index 0000000..aa9605d --- /dev/null +++ b/test/TestDF/soonCMakeLists @@ -0,0 +1,10 @@ +add_test(TestDF __init__) +add_test(TestDF __str__) +add_test(TestDF add_row) +add_test(TestDF add_col) +add_test(TestDF drop_col) +add_test(TestDF remove_if) +add_test(TestDF copy_col) +add_test(TestDF create_idx) +add_test(TestDF drop_idx) +add_test(TestDF describe) diff --git a/test/TestDF/testdf.cpp b/test/TestDF/testdf.cpp new file mode 100644 index 0000000..da9eefa --- /dev/null +++ b/test/TestDF/testdf.cpp @@ -0,0 +1,32 @@ +#include "testdf.hpp" + +#include + +int main() { + testdf d{}; + d.add_col("b1"); + d.add_col("i1"); + d.add_col("d1"); + d.add_col("s1"); + auto dt = d.dtypes(); + + std::cout << "dtypes:\n"; + for (auto [k, v] : dt) { + std::cout << k << ": " << v << std::endl; + } + + using variants = std::variant; + std::map row1{{"b1", true}, {"i1", 42}, {"d1", 3.14}}; + + d.add_row(row1); + + std::vector> myrows = { + {{"s1", "hello2"}, {"d1", 10.14}, {"i1", 11}, {"b1", false}}, + {{"s1", "world"}, {"d1", 11.71}, {"i1", 12}, {"b1", true}}}; + + d.add_rows(myrows); + + d.print(); + std::cout << "to_csv:\n"; + d.to_csv(); +} \ No newline at end of file diff --git a/test/TestDF/testdf.hpp b/test/TestDF/testdf.hpp new file mode 100644 index 0000000..c6c9f13 --- /dev/null +++ b/test/TestDF/testdf.hpp @@ -0,0 +1,186 @@ +#pragma once +#include "../include/mvmap.hpp" + +class testdf { + using variants = std::variant; + using idx_variants = std::variant; + using df_mvmap = mvmap::mvmap; + + df_mvmap data; + // std::optional>> + // index; + + public: + testdf() = default; + testdf(const df_mvmap &data) : data(data) {}; + + friend void tag_invoke(boost::json::value_from_tag /*unused*/, + boost::json::value &v, const testdf &df) { + std::optional idx_name; + // if (df.index.has_value()) { + // auto idx_pair = df.index.value(); + // idx_name = idx_pair.first; + // } + + v = {{"index_name", boost::json::value_from(idx_name)}, + {"data", boost::json::value_from(df.data)}}; + } + + friend testdf tag_invoke(boost::json::value_to_tag /*unused*/, + const boost::json::value &v) { + const auto &obj = v.as_object(); + df_mvmap data = boost::json::value_to(obj.at("data")); + auto idx_name_o = + boost::json::value_to>(obj.at("index_name")); + + testdf df{data}; + // if (idx_name_o.has_value()) { + // std::string idx_name = idx_name_o.value(); + // if (data.series_is_bool(idx_name)) { + // df.set_index(idx_name); + // } else if (data.series_is_string(idx_name)) { + // df.set_index(idx_name); + // } else if (data.series_is_int64_t(idx_name)) { + // df.set_index(idx_name); + // } else if (data.series_is_double(idx_name)) { + // df.set_index(idx_name); + // } else { + // std::cerr << "Unknown index type; ignoring" << std::endl; + // } + // } + + return df; + } + + // bool is_index(const std::string &idx) { + // if (!index.has_value()) { + // return false; + // } + // return index.value().first == idx; + // } + + // template + // bool set_index(const std::string &name) { + // if (data.has_series(name)) { + // return false; + // } + // auto ser_o = data.get_series(name); + // if (!ser_o.has_value()) { + // return false; + // } + // auto ser = ser_o.value(); + // index = std::make_pair(name, ser); + // // index.emplace(std::make_pair(name, ser)); + // return true; + // } + + // candidate function not viable: no known conversion from 'pair::type, typename + // __unwrap_ref_decay &>::type>' (aka 'pair::series_proxy>') to 'nullopt_t' for 1st argument + + // bool drop_index() { + // if (!index.has_value()) { + // return false; + // } + // index = std::nullopt; + // return true; + // } + + // bool drop_index(const std::string &name) { + // if (!is_index(name)) { + // return false; + // } + // index = std::nullopt; + // return true; + // } + + template + std::optional> add_col( + const std::string &name, const std::string &desc = "") { + return data.add_series(name, desc); + } + + void drop_col(const std::string &name) { + // drop_index(name); + return data.drop_series(name); + } + + template + void remove_if(F f) { + return data.remove_if(f); + } + + bool copy_col(const std::string &from, const std::string &to, + const std::optional &desc = std::nullopt) { + return data.copy_series(from, to, desc); + } + + std::vector> columns() { + return data.list_series(); + } + + std::map dtypes() { + std::map d{}; + for (auto [ser, _] : data.list_series()) { + if (data.series_is_double(ser)) { + d[ser] = "double"; + } else if (data.series_is_int64_t(ser)) { + d[ser] = "int64_t"; + } else if (data.series_is_string(ser)) { + d[ser] = "string"; + } else if (data.series_is_bool(ser)) { + d[ser] = "bool"; + } else { + d[ser] = "UNKNOWN"; + } + } + return d; + } + + void add_row(const std::map &row) { + return data.add_row(data.size(), row); + } + + void add_rows(const std::vector> &rows) { + for (const auto &row : rows) { + add_row(row); + } + } + + void print() { data.print(); } + + void to_csv() { + auto cols = columns(); + std::cout << "index,"; + for (auto col = cols.begin(); col != cols.end(); ++col) { + bool last = col == std::prev(cols.end()); + std::cout << col->first; + if (!last) { + std::cout << ","; + } + } + std::cout << std::endl; + + auto ks = data.keys(); + + for (const auto &k : ks) { + std::cout << k << ","; + for (auto col = cols.begin(); col != cols.end(); ++col) { + auto d = data.get_as_variant(col->first, k); + bool last = col == std::prev(cols.end()); + std::visit( + [&d, last](auto &&arg) { + std::cout << arg; + if (!last) { + std::cout << ","; + } + }, + d); + } + std::cout << std::endl; + } + } + void describe() { std::cout << "TestDF " << std::endl; } +}; diff --git a/test/TestGraph/CMakeLists.txt b/test/TestGraph/CMakeLists.txt new file mode 100644 index 0000000..defbc49 --- /dev/null +++ b/test/TestGraph/CMakeLists.txt @@ -0,0 +1,18 @@ +add_test(TestGraph __init__) +add_test(TestGraph __str__) +add_test(TestGraph add_edge) +add_test(TestGraph add_node) +add_test(TestGraph nv) +add_test(TestGraph ne) +add_test(TestGraph degree) +add_test(TestGraph add_series) +add_test(TestGraph connected_components) +add_test(TestGraph drop_series) +add_test(TestGraph copy_series) +add_test(TestGraph extrema) +add_test(TestGraph count) +add_custom_command( + TARGET TestGraph_nv POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_SOURCE_DIR}/meta.json + ${CMAKE_CURRENT_BINARY_DIR}/meta.json) diff --git a/test/TestGraph/__init__.cpp b/test/TestGraph/__init__.cpp new file mode 100644 index 0000000..34fc260 --- /dev/null +++ b/test/TestGraph/__init__.cpp @@ -0,0 +1,31 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "__init__"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Initializes a TestGraph"}; + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + // testgraph needs to be convertible to json + testgraph::testgraph the_graph; + clip.set_state(state_name, the_graph); + std::map selectors; + clip.set_state(sel_state_name, selectors); + + return 0; +} diff --git a/test/TestGraph/__str__.cpp b/test/TestGraph/__str__.cpp new file mode 100644 index 0000000..a0cd317 --- /dev/null +++ b/test/TestGraph/__str__.cpp @@ -0,0 +1,39 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "__str__"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Str method for TestGraph"}; + + clip.add_required_state(state_name, + "Internal container"); + + clip.returns("String of data."); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto the_graph = clip.get_state(state_name); + clip.set_state(state_name, the_graph); + + std::stringstream sstr; + sstr << "Graph with " << the_graph.nv() << " nodes and " << the_graph.ne() + << " edges"; + + clip.to_return(sstr.str()); + + return 0; +} diff --git a/test/TestGraph/add.cpp b/test/TestGraph/add.cpp new file mode 100644 index 0000000..481b290 --- /dev/null +++ b/test/TestGraph/add.cpp @@ -0,0 +1,57 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include +#include +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "add"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Adds a subselector"}; + clip.add_required("selector", "Parent Selector"); + clip.add_required("subname", "Description of new selector"); + clip.add_optional("desc", "Description", "EMPTY DESCRIPTION"); + clip.add_required_state>( + "selector_state", "Internal container"); + + if (clip.parse(argc, argv)) { + return 0; + } + + std::map sstate; + if (clip.has_state("selector_state")) { + sstate = + clip.get_state>("selector_state"); + } + + auto jo = clip.get("selector"); + std::string subname = clip.get("subname"); + std::string desc = clip.get("desc"); + + std::string parentname; + try { + if (jo["expression_type"].as_string() != std::string("jsonlogic")) { + std::cerr << " NOT A THINGY " << std::endl; + exit(-1); + } + parentname = jo["rule"].as_object()["var"].as_string().c_str(); + } catch (...) { + std::cerr << "!! ERROR !!" << std::endl; + exit(-1); + } + + sstate[parentname + "." + subname] = desc; + + clip.set_state("selector_state", sstate); + clip.update_selectors(sstate); + clip.return_self(); + + return 0; +} diff --git a/test/TestGraph/add_edge.cpp b/test/TestGraph/add_edge.cpp new file mode 100644 index 0000000..cc00d9f --- /dev/null +++ b/test/TestGraph/add_edge.cpp @@ -0,0 +1,35 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "add_edge"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Inserts (src, dst) into a TestGraph"}; + clip.add_required("src", "source node"); + clip.add_required("dst", "dest node"); + clip.add_required_state(state_name, + "Internal container"); + clip.returns_self(); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto src = clip.get("src"); + auto dst = clip.get("dst"); + auto the_graph = clip.get_state(state_name); + the_graph.add_edge(src, dst); + clip.set_state(state_name, the_graph); + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/add_node.cpp b/test/TestGraph/add_node.cpp new file mode 100644 index 0000000..b043660 --- /dev/null +++ b/test/TestGraph/add_node.cpp @@ -0,0 +1,33 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "add_node"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Inserts a node into a TestGraph"}; + clip.add_required("node", "node to insert"); + clip.add_required_state(state_name, + "Internal container"); + clip.returns_self(); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto node = clip.get("node"); + auto the_graph = clip.get_state(state_name); + the_graph.add_node(node); + clip.set_state(state_name, the_graph); + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/add_series.cpp b/test/TestGraph/add_series.cpp new file mode 100644 index 0000000..fb18c73 --- /dev/null +++ b/test/TestGraph/add_series.cpp @@ -0,0 +1,79 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include "clippy/clippy.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +namespace boostjsn = boost::json; + +static const std::string method_name = "add_series"; +static const std::string graph_state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Adds a subselector"}; + clip.add_required("parent_sel", "Parent Selector"); + clip.add_required("sub_sel", "Name of new selector"); + clip.add_optional("desc", "Description of new selector", ""); + + clip.add_required_state>( + sel_state_name, "Internal container for pending selectors"); + clip.add_required_state(graph_state_name, + "Internal state for the graph"); + + if (clip.parse(argc, argv)) { + return 0; + } + + std::string parstr = clip.get("parent_sel"); + auto parsel = selector{parstr}; + auto subsel = clip.get("sub_sel"); + auto desc = clip.get("desc"); + + std::string fullname = parstr + "." + subsel; + + // std::map selectors; + auto the_graph = clip.get_state(graph_state_name); + + if (parsel.headeq("edge")) { + if (the_graph.has_edge_series(subsel)) { + std::cerr << "!! ERROR: Selector name already exists in edge table !!" + << std::endl; + exit(-1); + } + } else if (parsel.headeq("node")) { + if (the_graph.has_node_series(subsel)) { + std::cerr << "!! ERROR: Selector name already exists in node table !!" + << std::endl; + exit(-1); + } + } else { + std::cerr + << "((!! ERROR: Parent must be either \"edge\" or \"node\" (received " + << parstr << ") !!)"; + exit(-1); + } + + if (clip.has_state(sel_state_name)) { + auto selectors = + clip.get_state>(sel_state_name); + if (selectors.contains(fullname)) { + std::cerr << "Warning: Selector name already exists; ignoring" + << std::endl; + } else { + selectors[fullname] = desc; + clip.set_state(sel_state_name, selectors); + clip.update_selectors(selectors); + clip.return_self(); + } + } + + return 0; +} diff --git a/test/TestGraph/bfs b/test/TestGraph/bfs new file mode 100755 index 0000000..fad136a Binary files /dev/null and b/test/TestGraph/bfs differ diff --git a/test/TestGraph/bfs.cpp b/test/TestGraph/bfs.cpp new file mode 100644 index 0000000..5b29f3e --- /dev/null +++ b/test/TestGraph/bfs.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +int main() { + std::vector> adj{{1}, {0, 2}, {1}, {4}, {3}}; + std::vector components(adj.size()); + std::iota(components.begin(), components.end(), 0); + + std::vector curr_level{0}; + std::vector next_level{}; + std::vector visited(adj.size(), false); + + long int u{}; + + while (!curr_level.empty()) { + u = curr_level.back(); + std::cout << "u = " << u << "\n"; + curr_level.pop_back(); + for (long int v : adj[u]) { + std::cout << " testing " << v << "\n"; + if (!visited[v]) { + std::cout << " visiting " << v << "\n"; + visited[v] = true; + components[v] = components[u]; + std::cout << " components[" << v << "] = " << components[u] << "\n"; + next_level.push_back(v); + } + std::cout << "swapping\n"; + std::swap(next_level, curr_level); + next_level.clear(); + } + } +} diff --git a/test/TestGraph/connected_components.cpp b/test/TestGraph/connected_components.cpp new file mode 100644 index 0000000..22421cc --- /dev/null +++ b/test/TestGraph/connected_components.cpp @@ -0,0 +1,124 @@ + + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "testgraph.hpp" + +static const std::string method_name = "connected_components"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{ + method_name, + "Populates a column containing the component id of each node in a graph"}; + clip.add_required( + "selector", + "Existing selector name into which the component id will be written"); + clip.add_required_state(state_name, + "Internal container"); + clip.add_required_state>( + sel_state_name, "Internal container for selectors"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto sel_json = clip.get("selector"); + + std::string sel; + try { + if (sel_json["expression_type"].as_string() != std::string("jsonlogic")) { + std::cerr << " NOT A THINGY " << std::endl; + exit(-1); + } + sel = sel_json["rule"].as_object()["var"].as_string().c_str(); + } catch (...) { + std::cerr << "!! ERROR !!" << std::endl; + exit(-1); + } + + if (!sel.starts_with("node.")) { + std::cerr << "Selector must be a node subselector" << std::endl; + return 1; + } + auto the_graph = clip.get_state(state_name); + + auto selectors = + clip.get_state>(sel_state_name); + if (!selectors.contains(sel)) { + std::cerr << "Selector not found" << std::endl; + return 1; + } + auto subsel = sel.substr(5); + if (the_graph.has_node_series(subsel)) { + std::cerr << "Selector already populated" << std::endl; + return 1; + } + + auto cc_o = the_graph.add_node_series(subsel, selectors.at(sel)); + if (!cc_o) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + + auto cc = cc_o.value(); + std::map ccmap; + + int64_t i = 0; + for (auto &node : the_graph.nodes()) { + ccmap[node] = i++; + } + std::vector> adj(the_graph.nv()); + the_graph.for_all_edges([&adj, &ccmap](auto edge, mvmap::locator /*unused*/) { + long i = ccmap[edge.first]; + long j = ccmap[edge.second]; + adj[i].push_back(j); + adj[j].push_back(i); + }); + + std::vector visited(the_graph.nv(), false); + std::vector components(the_graph.nv()); + std::iota(components.begin(), components.end(), 0); + + for (int64_t i = 0; i < the_graph.nv(); ++i) { + if (!visited[i]) { + std::queue q; + q.push(i); + while (!q.empty()) { + int64_t v = q.front(); + q.pop(); + visited[v] = true; + for (int64_t u : adj[v]) { + if (!visited[u]) { + q.push(u); + components[u] = components[i]; + } + } + } + } + } + + the_graph.for_all_nodes( + [&components, &ccmap, &cc](auto node, mvmap::locator /*unused*/) { + int64_t i = ccmap[node]; + cc[node] = components[i]; + }); + + clip.set_state(state_name, the_graph); + // clip.set_state(sel_state_name, selectors); + // clip.update_selectors(selectors); + + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/copy_series.cpp b/test/TestGraph/copy_series.cpp new file mode 100644 index 0000000..8c0f766 --- /dev/null +++ b/test/TestGraph/copy_series.cpp @@ -0,0 +1,92 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#include "clippy/clippy.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +namespace boostjsn = boost::json; + +static const std::string method_name = "copy_series"; +static const std::string graph_state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Copies a subselector to a new subselector"}; + clip.add_required("from_sel", "Source Selector"); + clip.add_required("to_sel", "Target Selector"); + + clip.add_required_state>( + sel_state_name, "Internal container for pending selectors"); + clip.add_required_state(graph_state_name, + "Internal state for the graph"); + + if (clip.parse(argc, argv)) { + return 0; + } + + auto from_selector = clip.get("from_sel"); + auto to_selector = clip.get("to_sel"); + + auto the_graph = clip.get_state(graph_state_name); + + bool edge_sel = false; // if false, then node selector + if (testgraph::testgraph::is_edge_selector(from_selector)) { + edge_sel = true; + } else if (!testgraph::testgraph::is_node_selector(from_selector)) { + std::cerr << "!! ERROR: from_selector must start with either \"edge\" or " + "\"node\" (received " + << from_selector << ") !!"; + exit(-1); + } + + if (the_graph.has_series(to_selector)) { + std::cerr << "!! ERROR: Selector name " << to_selector + << " already exists in graph !!" << std::endl; + exit(-1); + } + + if (clip.has_state(sel_state_name)) { + auto selectors = + clip.get_state>(sel_state_name); + if (selectors.contains(to_selector)) { + std::cerr << "Warning: Using unmanifested selector." << std::endl; + selectors.erase(to_selector); + } + auto from_selector_tail = from_selector.tail(); + auto to_selector_tail = to_selector.tail(); + if (!from_selector_tail.has_value() || !to_selector_tail.has_value()) { + std::cerr + << "!! ERROR: from_selector and to_selector must have content !!" + << std::endl; + exit(1); + } + from_selector = from_selector_tail.value(); + to_selector = to_selector_tail.value(); + if (edge_sel) { + if (!the_graph.copy_edge_series(from_selector, to_selector)) { + std::cerr << "!! ERROR: copy failed from " << from_selector << " to " + << to_selector << "!!" << std::endl; + exit(1); + }; + } else { + if (!the_graph.copy_node_series(from_selector, to_selector)) { + std::cerr << "!! ERROR: copy failed from " << from_selector << " to " + << to_selector << "!!" << std::endl; + exit(1); + }; + } + } + + clip.set_state(graph_state_name, the_graph); + clip.return_self(); + + return 0; +} diff --git a/test/TestGraph/count.cpp b/test/TestGraph/count.cpp new file mode 100644 index 0000000..2391e4c --- /dev/null +++ b/test/TestGraph/count.cpp @@ -0,0 +1,124 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +static const std::string method_name = "count"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, + "returns a map containing the count of values in a " + "series based on selector"}; + clip.add_required("selector", + "Existing selector name to calculate extrema"); + clip.add_required_state(state_name, + "Internal container"); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + auto sel_str = clip.get("selector"); + selector sel{sel_str}; + + auto the_graph = clip.get_state(state_name); + + bool is_edge_sel = testgraph::testgraph::is_edge_selector(sel); + bool is_node_sel = testgraph::testgraph::is_node_selector(sel); + + if (!is_edge_sel && !is_node_sel) { + std::cerr << "Selector must start with either \"edge\" or \"node\"" + << std::endl; + return 1; + } + + auto tailsel_opt = sel.tail(); + if (!tailsel_opt) { + std::cerr << "no tail" << std::endl; + return 1; + } + + auto tail_sel = tailsel_opt.value(); + if (is_edge_sel) { + if (the_graph.has_series(sel)) { + auto series = the_graph.get_edge_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else if (the_graph.has_series(sel)) { + auto series = the_graph.get_edge_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else if (the_graph.has_series(sel)) { + auto series = the_graph.get_edge_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else if (the_graph.has_series(sel)) { + auto series = the_graph.get_edge_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else { + std::cerr << "UNKNOWN TYPE" << std::endl; + return 1; + } + } else if (is_node_sel) { + if (the_graph.has_series(sel)) { + sel = tailsel_opt.value(); + auto series = the_graph.get_node_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else if (the_graph.has_series(sel)) { + auto series = the_graph.get_node_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else if (the_graph.has_series(sel)) { + auto series = the_graph.get_node_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else if (the_graph.has_series(sel)) { + auto series = the_graph.get_node_series(tail_sel); + if (series) { + clip.to_return>(series.value().count()); + } else { + clip.to_return>({}); + } + } else { + std::cerr << "UNKNOWN TYPE" << std::endl; + return 1; + } + } + clip.set_state(state_name, the_graph); + return 0; +} diff --git a/test/TestGraph/degree.cpp b/test/TestGraph/degree.cpp new file mode 100644 index 0000000..657556d --- /dev/null +++ b/test/TestGraph/degree.cpp @@ -0,0 +1,78 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +static const std::string method_name = "degree"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{ + method_name, + "Populates a column containing the degree of each node in a graph"}; + clip.add_required( + "selector", + "Existing selector name into which the degree will be written"); + clip.add_required_state(state_name, + "Internal container"); + clip.add_required_state>( + sel_state_name, "Internal container for pending selectors"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + selector sel = clip.get("selector"); + + if (!sel.headeq("node")) { + std::cerr << "Selector must be a node subselector" << std::endl; + return 1; + } + auto the_graph = clip.get_state(state_name); + + auto selectors = + clip.get_state>(sel_state_name); + if (!selectors.contains(sel)) { + std::cerr << "Selector not found" << std::endl; + return 1; + } + auto subsel = sel.tail().value(); + if (the_graph.has_node_series(subsel)) { + std::cerr << "Selector already populated" << std::endl; + return 1; + } + + auto deg_o = the_graph.add_node_series(subsel, "Degree"); + if (!deg_o) { + std::cerr << "Unable to manifest node series" << std::endl; + return 1; + } + + auto deg = deg_o.value(); + + the_graph.for_all_edges([°](auto edge, mvmap::locator /*unused*/) { + deg[edge.first]++; + if (edge.first != edge.second) { + deg[edge.second]++; + } + }); + + clip.set_state(state_name, the_graph); + clip.set_state(sel_state_name, selectors); + clip.update_selectors(selectors); + + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/drop_series.cpp b/test/TestGraph/drop_series.cpp new file mode 100644 index 0000000..3a6298d --- /dev/null +++ b/test/TestGraph/drop_series.cpp @@ -0,0 +1,66 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include "clippy/clippy.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +namespace boostjsn = boost::json; + +static const std::string method_name = "drop_series"; +static const std::string graph_state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; +static const std::string sel_name = "selector"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Drops a selector"}; + clip.add_required(sel_name, "Selector to drop"); + + clip.add_required_state>( + sel_state_name, "Internal container for pending selectors"); + clip.add_required_state(graph_state_name, + "Internal state for the graph"); + + if (clip.parse(argc, argv)) { + return 0; + } + + auto jo = clip.get(sel_name); + + selector sel = clip.get(sel_name); + + auto sel_state = + clip.get_state>(sel_state_name); + if (!sel_state.contains(sel)) { + std::cerr << "Selector name not found!" << std::endl; + exit(-1); + } + auto the_graph = clip.get_state(graph_state_name); + auto subsel = sel.tail().value(); + if (sel.headeq("edge")) { + if (the_graph.has_edge_series(subsel)) { + the_graph.drop_edge_series(sel); + } + } else if (sel.headeq("node")) { + if (the_graph.has_node_series(subsel)) { + the_graph.drop_node_series(subsel); + } + } else { + std::cerr << "Selector name must start with either \"edge.\" or \"node.\"" + << std::endl; + exit(-1); + } + sel_state.erase(sel); + clip.set_state(graph_state_name, the_graph); + clip.set_state(sel_state_name, sel_state); + clip.update_selectors(sel_state); + + return 0; +} diff --git a/test/TestGraph/extrema.cpp b/test/TestGraph/extrema.cpp new file mode 100644 index 0000000..cc13f1e --- /dev/null +++ b/test/TestGraph/extrema.cpp @@ -0,0 +1,155 @@ + +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include +#include +#include + +#include "clippy/clippy-eval.hpp" +#include "clippy/selector.hpp" +#include "testgraph.hpp" + +static const std::string method_name = "extrema"; +static const std::string state_name = "INTERNAL"; +static const std::string sel_state_name = "selectors"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, + "returns the extrema of a series based on selector"}; + clip.add_required("selector", + "Existing selector name to calculate extrema"); + clip.add_required_state(state_name, + "Internal container"); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + auto sel_str = clip.get("selector"); + selector sel{sel_str}; + + bool is_edge_sel = testgraph::testgraph::is_edge_selector(sel); + bool is_node_sel = testgraph::testgraph::is_node_selector(sel); + + if (!is_edge_sel && !is_node_sel) { + std::cerr << "Selector must start with either \"edge\" or \"node\"" + << std::endl; + return 1; + } + + auto tail_opt = sel.tail(); + if (!tail_opt) { + std::cerr << "Selector must have a tail" << std::endl; + return 1; + } + auto tail_sel = tail_opt.value(); + + auto the_graph = clip.get_state(state_name); + + if (is_edge_sel) { + clip.returns>>( + "min and max keys and values of the series"); + if (the_graph.has_edge_series(tail_sel)) { + auto series = the_graph.get_edge_series(tail_sel); + if (!series) { + std::cerr << "Edge series not found" << std::endl; + return 1; + } + auto series_val = series.value(); + auto [min_tup, max_tup] = series_val.extrema(); + + std::map> extrema; + if (min_tup) { + extrema["min"] = std::make_pair(std::get<1>(min_tup.value()), + std::get<0>(min_tup.value())); + } + + if (max_tup) { + extrema["max"] = std::make_pair(std::get<1>(max_tup.value()), + std::get<0>(max_tup.value())); + } + clip.to_return(extrema); + } else if (the_graph.has_edge_series(tail_sel)) { + auto series = the_graph.get_edge_series(tail_sel); + if (!series) { + std::cerr << "Edge series not found" << std::endl; + return 1; + } + auto series_val = series.value(); + auto [min_tup, max_tup] = series_val.extrema(); + + std::map> extrema; + if (min_tup) { + extrema["min"] = std::make_pair(std::get<1>(min_tup.value()), + std::get<0>(min_tup.value())); + } + + if (max_tup) { + extrema["max"] = std::make_pair(std::get<1>(max_tup.value()), + std::get<0>(max_tup.value())); + } + clip.to_return(extrema); + } else { + std::cerr << "Edge series is an invalid type" << std::endl; + return 1; + } + } else if (is_node_sel) { + if (the_graph.has_edge_series(tail_sel)) { + clip.returns>>( + "min and max keys and values of the series"); + + auto series = the_graph.get_node_series(tail_sel); + if (!series) { + std::cerr << "Edge series not found" << std::endl; + return 1; + } + + auto series_val = series.value(); + auto [min_tup, max_tup] = series_val.extrema(); + + std::map> extrema; + if (min_tup) { + extrema["min"] = std::make_pair(std::get<1>(min_tup.value()), + std::get<0>(min_tup.value())); + } + if (max_tup) { + extrema["max"] = std::make_pair(std::get<1>(max_tup.value()), + std::get<0>(max_tup.value())); + } + + clip.to_return(extrema); + } else if (the_graph.has_node_series(tail_sel)) { + clip.returns< + std::map>>( + "min and max keys and values of the series"); + + auto series = the_graph.get_node_series(tail_sel); + if (!series) { + return 1; + } + auto series_val = series.value(); + auto [min_tup, max_tup] = series_val.extrema(); + + std::map> extrema; + if (min_tup) { + extrema["min"] = std::make_pair(std::get<1>(min_tup.value()), + std::get<0>(min_tup.value())); + } + if (max_tup) { + extrema["max"] = std::make_pair(std::get<1>(max_tup.value()), + std::get<0>(max_tup.value())); + } + + clip.to_return(extrema); + } else { + std::cerr << "Node series is an invalid type" << std::endl; + return 1; + } + } + + clip.set_state(state_name, the_graph); + return 0; +} diff --git a/test/TestGraph/for_all_edges.cpp b/test/TestGraph/for_all_edges.cpp new file mode 100644 index 0000000..80ff892 --- /dev/null +++ b/test/TestGraph/for_all_edges.cpp @@ -0,0 +1,51 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy-eval.hpp" +#include "testgraph.hpp" +#include +#include +#include +#include +#include + +static const std::string method_name = "add_node"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, + "Adds a new column to a graph based on a lambda"}; + clip.add_required("name", "New column name"); + clip.add_required("expression", "Lambda Expression"); + clip.add_required_state(state_name, + "Internal container"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto name = clip.get("name"); + auto expression = clip.get("expression"); + auto the_graph = clip.get_state(state_name); + + // + // Expression here + auto apply_jl = [&expression](const testgraph::edge_t &value, + mvmap::locator loc) { + boost::json::object data; + data["src"] = value.first; + data["dst"] = value.second; + data["loc"] = boost::json::value_from(loc); + json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); + return json_logic::unpackValue(res); + }; + + the_graph.for_all_edges(apply_jl); + + clip.set_state(state_name, the_graph); + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/meta.json b/test/TestGraph/meta.json new file mode 100644 index 0000000..0af2980 --- /dev/null +++ b/test/TestGraph/meta.json @@ -0,0 +1,7 @@ +{ + "__doc__" : "A graph data structure", + "initial_selectors" : { + "edge" : "The edges of the graph", + "node": "The nodes of the graph" + } +} diff --git a/test/TestGraph/ne.cpp b/test/TestGraph/ne.cpp new file mode 100644 index 0000000..cb2ded3 --- /dev/null +++ b/test/TestGraph/ne.cpp @@ -0,0 +1,29 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "ne"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Returns the number of edges in the graph"}; + + clip.returns("Number of edges."); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto the_graph = clip.get_state(state_name); + + clip.to_return(the_graph.ne()); + return 0; +} diff --git a/test/TestGraph/nv.cpp b/test/TestGraph/nv.cpp new file mode 100644 index 0000000..fafd0d5 --- /dev/null +++ b/test/TestGraph/nv.cpp @@ -0,0 +1,29 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "nv"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Returns the number of nodes in the graph"}; + + clip.returns("Number of nodes."); + + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto the_graph = clip.get_state(state_name); + + clip.to_return(the_graph.nv()); + return 0; +} diff --git a/test/TestGraph/remove.cpp b/test/TestGraph/remove.cpp new file mode 100644 index 0000000..f24422b --- /dev/null +++ b/test/TestGraph/remove.cpp @@ -0,0 +1,36 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy.hpp" +#include "testgraph.hpp" +#include +#include +#include +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "remove_edge"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + + clippy::clippy clip{method_name, "Removes a string from a TestSet"}; + + clip.add_required("item", "Item to remove"); + clip.add_required_state>(state_name, "Internal container"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto item = clip.get("item"); + auto the_set = clip.get_state>(state_name); + the_set.erase(item); + clip.set_state(state_name, the_set); + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/remove_if.cpp b/test/TestGraph/remove_if.cpp new file mode 100644 index 0000000..628b061 --- /dev/null +++ b/test/TestGraph/remove_if.cpp @@ -0,0 +1,50 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy-eval.hpp" +#include +#include +#include +#include +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "remove_if"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Removes a string from a TestSet"}; + clip.add_required("expression", "Remove If Expression"); + clip.add_required_state>(state_name, "Internal container"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto expression = clip.get("expression"); + auto the_set = clip.get_state>(state_name); + + // + // Expression here + auto apply_jl = [&expression](int value) { + boostjsn::object data; + data["value"] = value; + json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); + return json_logic::unpackValue(res); + }; + + for (auto first = the_set.begin(), last = the_set.end(); first != last;) { + if (apply_jl(*first)) + first = the_set.erase(first); + else + ++first; + } + + clip.set_state(state_name, the_set); + clip.return_self(); + return 0; +} diff --git a/test/TestGraph/testconst.cpp b/test/TestGraph/testconst.cpp new file mode 100644 index 0000000..badd1f1 --- /dev/null +++ b/test/TestGraph/testconst.cpp @@ -0,0 +1,8 @@ +class Foo { + int x; + +public: + int get_x() const { return x; } + + int get_x() { return x + 1; } +}; diff --git a/test/TestGraph/testgraph.cpp b/test/TestGraph/testgraph.cpp new file mode 100644 index 0000000..03f4641 --- /dev/null +++ b/test/TestGraph/testgraph.cpp @@ -0,0 +1,48 @@ +#include "testgraph.hpp" + +#include + +int main() { + auto g = testgraph::testgraph{}; + + g.add_node("a"); + g.add_node("b"); + g.add_edge("a", "b"); + g.add_edge("b", "c"); + + assert(g.has_node("a")); + assert(g.has_node("b")); + assert(g.has_node("c")); + assert(!g.has_node("d")); + + assert(g.has_edge({"a", "b"})); + assert(g.has_edge({"b", "c"})); + assert(!g.has_edge({"a", "c"})); + + assert(g.out_degree("a") == 1); + assert(g.in_degree("a") == 0); + assert(g.in_degree("b") == 1); + assert(g.out_degree("b") == 1); + + auto colref = + g.add_node_series("color", "The color of the nodes"); + colref.value()["a"] = "blue"; + colref.value()["c"] = "red"; + + auto weightref = g.add_edge_series("weight", "edge weights"); + + weightref.value()[std::pair("a", "b")] = 5.5; + weightref.value()[std::pair("b", "c")] = 3.3; + + std::cout << "g.nv: " << g.nv() << ", g.ne: " << g.ne() << "\n"; + std::cout << boost::json::value_from(g) << "\n"; + + auto val = boost::json::value_from(g); + auto str = boost::json::serialize(val); + std::cout << "here it is: " << str << "\n"; + + auto g2 = boost::json::value_to(val); + auto e2 = weightref.value().extrema(); + std::cout << "extrema: " << std::get<0>(e2.first.value()) << ", " + << std::get<0>(e2.second.value()) << "\n"; +} diff --git a/test/TestGraph/testgraph.hpp b/test/TestGraph/testgraph.hpp new file mode 100644 index 0000000..f5027ea --- /dev/null +++ b/test/TestGraph/testgraph.hpp @@ -0,0 +1,222 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +#include "../include/mvmap.hpp" +#include "boost/json.hpp" + +namespace testgraph { +// map of (src, dst) : weight +using node_t = std::string; +using edge_t = std::pair; + +template +using sparsevec = std::map; + +// using variants = std::variant; +class testgraph { + using edge_mvmap = mvmap::mvmap; + using node_mvmap = mvmap::mvmap; + template + using edge_series_proxy = edge_mvmap::series_proxy; + template + using node_series_proxy = node_mvmap::series_proxy; + node_mvmap node_table; + edge_mvmap edge_table; + + public: + static inline bool is_edge_selector(const std::string &sel) { + return sel.starts_with("edge."); + } + + static inline bool is_node_selector(const std::string &sel) { + return sel.starts_with("node."); + } + + static inline bool is_valid_selector(const std::string &sel) { + return is_edge_selector(sel) || is_node_selector(sel); + } + + friend void tag_invoke(boost::json::value_from_tag /*unused*/, + boost::json::value &v, testgraph const &g) { + v = {{"node_table", boost::json::value_from(g.node_table)}, + {"edge_table", boost::json::value_from(g.edge_table)}}; + } + + friend testgraph tag_invoke(boost::json::value_to_tag /*unused*/, + const boost::json::value &v) { + const auto &obj = v.as_object(); + auto nt = boost::json::value_to(obj.at("node_table")); + auto et = boost::json::value_to(obj.at("edge_table")); + return {nt, et}; + } + testgraph() = default; + testgraph(node_mvmap nt, edge_mvmap et) + : node_table(std::move(nt)), edge_table(std::move(et)) {}; + + // this function requires that the "edge." prefix be removed from the name. + template + std::optional> add_edge_series( + const std::string &sel, const std::string &desc = "") { + return edge_table.add_series(sel, desc); + } + + template + std::optional> add_edge_series( + const std::string &sel, const edge_series_proxy &from, + const std::string &desc = "") { + return edge_table.add_series(sel, from, desc); + } + + void drop_edge_series(const std::string &sel) { edge_table.drop_series(sel); } + + // this function requires that the "node." prefix be removed from the name. + void drop_node_series(const std::string &sel) { node_table.drop_series(sel); } + + // this function requires that the "node." prefix be removed from the name. + template + std::optional> add_node_series( + const std::string &sel, const std::string &desc = "") { + return node_table.add_series(sel, desc); + } + + template + std::optional> add_node_series( + const std::string &sel, const node_series_proxy &from, + const std::string &desc = "") { + return node_table.add_series(sel, from, desc); + } + template + std::optional> get_edge_series(const std::string &sel) { + return edge_table.get_series(sel); + } + + bool copy_edge_series(const std::string &from, const std::string &to, + const std::optional &desc = std::nullopt) { + return edge_table.copy_series(from, to, desc); + } + + bool copy_node_series(const std::string &from, const std::string &to, + const std::optional &desc = std::nullopt) { + return node_table.copy_series(from, to, desc); + } + + template + std::optional> get_node_series(const std::string &sel) { + return node_table.get_series(sel); + } + + [[nodiscard]] size_t nv() const { return node_table.size(); } + [[nodiscard]] size_t ne() const { return edge_table.size(); } + + template + void for_all_edges(F f) { + edge_table.for_all(f); + } + template + void for_all_nodes(F f) { + node_table.for_all(f); + } + + [[nodiscard]] std::vector edges() const { + auto kv = edge_table.keys(); + return {kv.begin(), kv.end()}; + } + [[nodiscard]] std::vector nodes() const { + auto kv = node_table.keys(); + return {kv.begin(), kv.end()}; + } + + bool add_node(const node_t &node) { return node_table.add_key(node); }; + bool add_edge(const node_t &src, const node_t &dst) { + node_table.add_key(src); + node_table.add_key(dst); + return edge_table.add_key({src, dst}); + } + + bool has_node(const node_t &node) { return node_table.contains(node); }; + bool has_edge(const edge_t &edge) { return edge_table.contains(edge); }; + bool has_edge(const node_t &src, const node_t &dst) { + return edge_table.contains({src, dst}); + }; + + // strips the head off the std::string and passes the tail to the appropriate + // method. + [[nodiscard]] bool has_series(const std::string &sel) const { + auto tail = sel.substr(5); + + if (is_node_selector(sel)) { + return has_node_series(tail); + } + if (is_edge_selector(sel)) { + return has_edge_series(tail); + } + return false; + } + + template + [[nodiscard]] bool has_series(const std::string &sel) const { + auto tail = sel.substr(5); + + if (is_node_selector(sel)) { + return has_node_series(tail); + } + if (is_edge_selector(sel)) { + return has_edge_series(tail); + } + return false; + } + + // assumes sel has already been tail'ed. + [[nodiscard]] bool has_node_series(const std::string &sel) const { + return node_table.has_series(sel); + } + + template + [[nodiscard]] bool has_node_series(const std::string &sel) const { + return node_table.has_series(sel); + } + + // assumes sel has already been tail'ed. + [[nodiscard]] bool has_edge_series(const std::string &sel) const { + return edge_table.has_series(sel); + } + template + [[nodiscard]] bool has_edge_series(const std::string &sel) const { + return edge_table.has_series(sel); + } + + [[nodiscard]] std::vector out_neighbors(const node_t &node) const { + std::vector neighbors; + for (const auto &[src, dst] : edge_table.keys()) { + if (src == node) { + neighbors.emplace_back(dst); + } + } + return neighbors; + } + + [[nodiscard]] std::vector in_neighbors(const node_t &node) const { + std::vector neighbors; + for (const auto &[src, dst] : edge_table.keys()) { + if (dst == node) { + neighbors.emplace_back(src); + } + } + return neighbors; + } + + [[nodiscard]] size_t in_degree(const node_t &node) const { + return in_neighbors(node).size(); + } + [[nodiscard]] size_t out_degree(const node_t &node) const { + return out_neighbors(node).size(); + } + +}; // class testgraph + +} // namespace testgraph diff --git a/test/TestGraph/testlocator.cpp b/test/TestGraph/testlocator.cpp new file mode 100644 index 0000000..a91b02c --- /dev/null +++ b/test/TestGraph/testlocator.cpp @@ -0,0 +1,7 @@ +class locator { + int loc; +} + +int main() { + auto l = locator{5}; +} diff --git a/test/TestGraph/testmvmap.cpp b/test/TestGraph/testmvmap.cpp new file mode 100644 index 0000000..c040fd1 --- /dev/null +++ b/test/TestGraph/testmvmap.cpp @@ -0,0 +1,71 @@ +#include "mvmap.hpp" +#include +// #include +#include +#include +#include + +using mymap_t = mvmap::mvmap; +int main() { + + mymap_t m{}; + + m.add_series("weight"); + std::cout << "added series weight\n"; + m.add_series("name"); + std::cout << "added series name\n"; + + auto hmap = m.get_or_create_series("age"); + std::cout << "created hmap\n"; + + hmap["seth"] = 5; + std::cout << "added seth\n"; + hmap["roger"] = 8; + std::cout << "added roger\n"; + + assert(hmap["seth"] == 5); + assert(hmap["roger"] == 8); + + auto v = boost::json::value_from(m); + std::string j = boost::json::serialize(v); + std::cout << "j = " << j << '\n'; + auto jv = boost::json::parse(j); + std::cout << boost::json::serialize(jv) << "\n"; + auto n = boost::json::value_to(jv); + + std::cout << "created n\n"; + auto hmap2 = n.get_or_create_series("age"); + std::cout << "created hmap2\n"; + assert(hmap2["seth"] == 5); + assert(hmap2["roger"] == 8); + + size_t age_sum = 0; + hmap2.for_all([&age_sum](const auto &k, auto, auto &v) { age_sum += v; }); + + std::cout << "sum of ages = " << age_sum << "\n"; + assert(age_sum == 13); + + hmap2.remove_if([](const auto &k, auto, auto &v) { return v > 6; }); + + assert(hmap2.at("roger") == std::nullopt); + assert(hmap2.at("seth") == 5); + + age_sum = 0; + hmap2.for_all([&age_sum](const auto &k, auto, auto &v) { age_sum += v; }); + std::cout << "sum of ages = " << age_sum << "\n"; + assert(age_sum == 5); + hmap2["roger"] = 8; + + age_sum = 0; + hmap2.for_all([&age_sum](const auto &k, auto, auto &v) { age_sum += v; }); + + std::cout << "sum of ages = " << age_sum << "\n"; + assert(age_sum == 13); + n.remove_if([&hmap2](const auto &k, auto) { return hmap2[k] == 5; }); + + age_sum = 0; + hmap2.for_all([&age_sum](const auto &k, auto, auto &v) { age_sum += v; }); + + std::cout << "sum of ages = " << age_sum << "\n"; + assert(age_sum == 8); +} diff --git a/test/TestGraph/testselector.cpp b/test/TestGraph/testselector.cpp new file mode 100644 index 0000000..9b87720 --- /dev/null +++ b/test/TestGraph/testselector.cpp @@ -0,0 +1,20 @@ +#include "../../include/clippy/selector.hpp" +#include + +int main() { + selector s = selector("foo.bar.baz"); + + selector zzz = "foo.zoo.boo"; + + selector s2{"x.y.z"}; + std::cout << "s = " << s << "\n"; + assert(s.headeq("foo")); + assert(!s.headeq("bar")); + + auto val = boost::json::value_from(s); + auto str = boost::json::serialize(val); + + auto t = boost::json::value_to(val); + assert(t.headeq("foo")); + std::cout << str << "\n"; +} diff --git a/test/TestGraph/where.cpp b/test/TestGraph/where.cpp new file mode 100644 index 0000000..628b061 --- /dev/null +++ b/test/TestGraph/where.cpp @@ -0,0 +1,50 @@ +// Copyright 2021 Lawrence Livermore National Security, LLC and other CLIPPy +// Project Developers. See the top-level COPYRIGHT file for details. +// +// SPDX-License-Identifier: MIT + +#include "clippy/clippy-eval.hpp" +#include +#include +#include +#include +#include + +namespace boostjsn = boost::json; + +static const std::string method_name = "remove_if"; +static const std::string state_name = "INTERNAL"; + +int main(int argc, char **argv) { + clippy::clippy clip{method_name, "Removes a string from a TestSet"}; + clip.add_required("expression", "Remove If Expression"); + clip.add_required_state>(state_name, "Internal container"); + clip.returns_self(); + // no object-state requirements in constructor + if (clip.parse(argc, argv)) { + return 0; + } + + auto expression = clip.get("expression"); + auto the_set = clip.get_state>(state_name); + + // + // Expression here + auto apply_jl = [&expression](int value) { + boostjsn::object data; + data["value"] = value; + json_logic::ValueExpr res = json_logic::apply(expression["rule"], data); + return json_logic::unpackValue(res); + }; + + for (auto first = the_set.begin(), last = the_set.end(); first != last;) { + if (apply_jl(*first)) + first = the_set.erase(first); + else + ++first; + } + + clip.set_state(state_name, the_set); + clip.return_self(); + return 0; +} diff --git a/test/TestSelector/selector.hpp b/test/TestSelector/selector.hpp new file mode 100644 index 0000000..2da089f --- /dev/null +++ b/test/TestSelector/selector.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +namespace testgraph { +class selector { + std::string name; + +public: + selector(boost::json::object &jo) { + try { + if (jo["expression_type"].as_string() != std::string("jsonlogic")) { + std::cerr << " NOT A THINGY\n"; + exit(-1); + } + name = jo["rule"].as_object()["var"].as_string().c_str(); + } catch (...) { + std::cerr << "!! ERROR !!\n"; + exit(-1); + } + } + [[nodiscard]] std::string to_string() const { return name; } +}; + +} // namespace testgraph diff --git a/test/include/mvmap.hpp b/test/include/mvmap.hpp new file mode 100644 index 0000000..a3d54e3 --- /dev/null +++ b/test/include/mvmap.hpp @@ -0,0 +1,594 @@ +#pragma once +#include +// #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mvmap { +using index = uint64_t; + +class locator { + static const index INVALID_LOC = std::numeric_limits::max(); + index loc; + locator(index loc) : loc(loc) {}; + + public: + template + friend class mvmap; + friend void tag_invoke(boost::json::value_from_tag /*unused*/, + boost::json::value &v, locator l); + friend locator tag_invoke(boost::json::value_to_tag /*unused*/, + const boost::json::value &v); + locator() : loc(INVALID_LOC) {}; + [[nodiscard]] bool is_valid() const { return loc != INVALID_LOC; } + + void print() const { + if (is_valid()) { + std::cout << "locator: " << loc << std::endl; + } else { + std::cout << "locator: invalid" << std::endl; + } + } +}; +void tag_invoke(boost::json::value_from_tag /*unused*/, boost::json::value &v, + locator l) { + v = l.loc; +} + +locator tag_invoke(boost::json::value_to_tag /*unused*/, + const boost::json::value &v) { + return boost::json::value_to(v); +} +template +class mvmap { + template + using series = std::map; + using key_to_idx = std::map; + using idx_to_key = std::map; + using variants = std::variant; + + template + static void print_series(const series &s) { + for (auto el : s) { + std::cout << el.first << " -> " << el.second << std::endl; + } + } + + // A locator is an opaque handle to a key in a series. + + idx_to_key itk; + key_to_idx kti; + std::map...>> data; + std::map series_desc; + + public: + // A series_proxy is a reference to a series in an mvmap. + template + class series_proxy { + std::string m_id; + std::string m_desc; + key_to_idx &kti_r; + idx_to_key &itk_r; + series &series_r; + + using series_type = V; + + // returns true if there is an index assigned to a given key + bool has_idx_at_key(K k) const { return kti_r.contains(k); } + // returns true if there is a key assigned to a given locator + bool has_key_at_index(locator l) const { return itk_r.contains(l.loc); } + + // returns or creates the index for a key. + index get_idx(K k) { + if (!has_idx_at_key(k)) { + index i{kti_r.size()}; + kti_r[k] = i; + itk_r[i] = k; + return i; + } + return kti_r[k]; + } + + public: + series_proxy(std::string id, series &ser, mvmap &m) + : m_id(std::move(id)), kti_r(m.kti), itk_r(m.itk), series_r(ser) {} + + series_proxy(std::string id, const std::string &desc, series &ser, + mvmap &m) + : m_id(std::move(id)), + m_desc(desc), + kti_r(m.kti), + itk_r(m.itk), + series_r(ser) {} + + bool is_string_v() const { return std::is_same_v; } + bool is_double_v() const { return std::is_same_v; } + bool is_int64_t_v() const { return std::is_same_v; } + bool is_bool_v() const { return std::is_same_v; } + + std::string id() const { return m_id; } + std::string desc() const { return m_desc; } + V &operator[](K k) { return series_r[get_idx(k)]; } + const V &operator[](K k) const { return series_r[get_idx(k)]; } + + // this assumes the key exists. + V &operator[](locator l) { return series_r[l.loc]; } + const V &operator[](locator l) const { return series_r[l.loc]; } + + std::optional> at(locator l) { + if (!has_key_at_index(l) || !series_r.contains(l.loc)) { + return std::nullopt; + } + return series_r[l.loc]; + }; + std::optional> at(locator l) const { + if (!has_key_at_index(l) || !series_r.contains(l.loc)) { + return std::nullopt; + } + return series_r[l.loc]; + }; + + std::optional> at(K k) { + if (!has_idx_at_key(k) || !series_r.contains(get_idx(k))) { + return std::nullopt; + } + return series_r[get_idx(k)]; + }; + + std::optional> at(K k) const { + if (!has_idx_at_key(k) || !series_r.contains(get_idx(k))) { + return std::nullopt; + } + return series_r[get_idx(k)]; + }; + + // this will create the key/index if it doesn't exist. + locator get_loc(K k) { return locator(get_idx(k)); } + + // F takes (K key, locator, V value) + template + void for_all(F f) { + for (auto el : series_r) { + f(itk_r[el.first], locator(el.first), el.second); + } + }; + + template + void for_all(F f) const { + for (auto el : series_r) { + f(itk_r[el.first], locator(el.first), el.second); + } + }; + + // F takes (K key, locator, V value) + template + void remove_if(F f) { + auto indices_to_delete = std::vector{}; + for (auto el : series_r) { + if (f(itk_r[el.first], locator(el.first), el.second)) { + indices_to_delete.emplace_back(el.first); + } + } + + for (auto ltd : indices_to_delete) { + erase(locator(ltd)); + } + }; + + void erase(const locator &l) { + auto i = l.loc; + kti_r.erase(itk_r[i]); + itk_r.erase(i); + series_r.erase(i); + } + + // if the key doesn't exist, do nothing. + void erase(const K &k) { + if (!has_idx_at_key(k)) { + return; + } + auto i = kti_r[k]; + erase(locator(i)); + } + + // this returns the key for a given locator in a series, or nullopt if the + // locator is invalid. + std::optional> get_key( + const locator &l) const { + if (!has_key_at_index(l.loc)) { + return std::nullopt; + } + return itk_r[l.loc]; + } + + std::pair>, + std::optional>> + extrema() { + V min = std::numeric_limits::max(); + V max = std::numeric_limits::min(); + bool found_min = false; + bool found_max = false; + locator min_loc; + locator max_loc; + K min_key; + K max_key; + for_all([&min, &max, &found_min, &found_max, &min_loc, &max_loc, &min_key, + &max_key](auto k, auto l, auto v) { + if (v < min) { + min = v; + min_loc = l; + min_key = k; + found_min = true; + } + if (v > max) { + max = v; + max_loc = l; + max_key = k; + found_max = true; + } + }); + std::optional> min_opt, max_opt; + if (found_min) { + min_opt = std::make_tuple(min, min_key, min_loc); + } else { + min_opt = std::nullopt; + } + if (found_max) { + max_opt = std::make_tuple(max, max_key, max_loc); + } else { + max_opt = std::nullopt; + } + return std::make_pair(min_opt, max_opt); + } + + std::map count() { + std::map ct; + for_all([&ct](auto /*unused*/, auto /*unused*/, auto v) { ct[v]++; }); + return ct; + } + + void print() { + std::cout << "id: " << m_id << ", "; + std::cout << "desc: " << m_desc << ", "; + std::string dtype = "unknown"; + if (is_string_v()) { + dtype = "string"; + } else if (is_double_v()) { + dtype = "double"; + } else if (is_int64_t_v()) { + dtype = "int64_t"; + } else if (is_bool_v()) { + dtype = "bool"; + } + std::cout << "dtype: " << dtype << ", "; + // std::cout << "kti_r.size(): " << kti_r.size() << std::endl; + // std::cout << "itk_r.size(): " << itk_r.size() << std::endl; + std::cout << series_r.size() << " entries" << std::endl; + // std::cout << "elements: " << std::endl; + for (auto el : series_r) { + std::cout << " " << itk_r[el.first] << " -> " << el.second + << std::endl; + } + } + + }; // end of series + ///////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////// + mvmap(const idx_to_key &itk, const key_to_idx &kti, + const std::map...>> &data) + : itk(itk), kti(kti), data(data) {} + + mvmap() = default; + friend void tag_invoke(boost::json::value_from_tag /*unused*/, + boost::json::value &v, const mvmap &m) { + v = {{"itk", boost::json::value_from(m.itk)}, + {"kti", boost::json::value_from(m.kti)}, + {"data", boost::json::value_from(m.data)}}; + } + + friend mvmap tag_invoke( + boost::json::value_to_tag> /*unused*/, + const boost::json::value &v) { + const auto &obj = v.as_object(); + using index = uint64_t; + // template using series = std::map; + using key_to_idx = std::map; + using idx_to_key = std::map; + return {boost::json::value_to(obj.at("itk")), + boost::json::value_to(obj.at("kti")), + boost::json::value_to< + std::map...>>>( + obj.at("data"))}; + } + + [[nodiscard]] size_t size() const { return kti.size(); } + bool add_key(const K &k) { + if (kti.count(k) > 0) { + return false; + } + auto i = kti.size(); + kti[k] = i; + itk[i] = k; + return true; + } + + [[nodiscard]] std::vector> list_series() { + std::vector> ser_pairs; + for (auto el : series_desc) { + ser_pairs.push_back(el); + } + return ser_pairs; + } + + [[nodiscard]] bool has_series(const std::string &id) const { + return data.contains(id); + } + + template + [[nodiscard]] bool has_series(const std::string &id) const { + // std::cerr << "has_series: " << id << std::endl; + // std::cerr << "V = " << typeid(V).name() << std::endl; + // std::cerr << "data.contains(id): " << data.contains(id) << std::endl; + // if (data.contains(id)) { + // std::cerr << "std::holds_alternative>(data.at(id)): " + // << std::holds_alternative>(data.at(id)) << + // std::endl; + // } + return data.contains(id) && std::holds_alternative>(data.at(id)); + } + bool contains(const K &k) { return kti.contains(k); } + auto keys() const { return std::views::keys(kti); } + + void add_row(const K &key, + const std::map> &row) { + auto loc = locator(kti.size()); + for (const auto &el : row) { + if (!has_series(el.first)) { + continue; + } + std::visit( + [&el, &key, this](auto &ser) { + std::cout << "adding to series " << el.first << std::endl; + using variant_type = std::decay_tsecond)>; + auto sproxy = get_series>(el.first) + .value(); // this is a series_proxy + // TODO: make sure this actually sets itk and kti correctly. + sproxy[key] = std::get(el.second); + }, + data[el.first]); + } + } + + void rem_row(const K &key) { + auto index = kti[key]; + for (auto &el : data) { + std::visit([&key, &index, this](auto &ser) { ser.erase(index); }, + el.second); + } + + kti.erase(key); + itk.erase(index); + } + // adds a new column (series) to the mvmap and returns true. If already + // exists, return false + template + std::optional> add_series(const std::string &sel, + const std::string &desc = "") { + if (has_series(sel)) { + return std::nullopt; + } + data[sel] = series{}; + series_desc[sel] = desc; + return series_proxy(sel, desc, std::get>(data[sel]), *this); + } + + // copies an existing column (series) to a new (unmanifested) column and + // returns true. If the new column already exists, or if the existing column + // doesn't, return false. + bool copy_series(const std::string &from, const std::string &to, + const std::optional &desc = std::nullopt) { + if (has_series(to) || !has_series(from)) { + std::cerr << "copy_series failed from " << from << " to " << to + << std::endl; + return false; + } + // std::cerr << "copying series from " << from << " to " << to << std::endl; + data[to] = data[from]; + series_desc[to] = desc.has_value() ? desc.value() : series_desc[from]; + return true; + } + + template + std::optional> get_series(const std::string &sel) { + if (!has_series(sel)) { + // series doesn't exist or is of the wrong type. + return std::nullopt; + } + // return series_proxy(sel, this->series_desc.at(sel), + // std::get>(data[sel]), *this); + auto foo = series_desc[sel]; + return series_proxy(sel, foo, std::get>(data[sel]), *this); + } + + bool series_is_string(const std::string &sel) const { + return has_series(sel); + } + + bool series_is_double(const std::string &sel) const { + return has_series(sel); + } + + bool series_is_int64_t(const std::string &sel) const { + return has_series(sel); + } + + bool series_is_bool(const std::string &sel) const { + return has_series(sel); + } + + // std::optional> get_variant_series( + // const std::string &sel) { + // if (!has_series(sel)) { + // return std::nullopt; + // } + + // using vtype = decltype(data[sel]); + // return series_proxy(sel, this->series_desc.at(sel), + // data.at(sel), + // this); + // } + + void drop_series(const std::string &sel) { + if (!has_series(sel)) { + return; + } + data.erase(sel); + series_desc.erase(sel); + } + + std::optional get_as_variant(const std::string &sel, + const locator &loc) { + auto col = data[sel]; + std::optional val; + std::visit( + [&val, sel, loc, this](auto &ser) { + using T = std::decay_tsecond)>; + auto sproxy = get_series>(sel) + .value(); // this is a series_proxy + val = sproxy.at(loc); + }, + col); + return val; + // return data[sel][loc.loc]; + } + + std::optional get_as_variant(const std::string &sel, + const K &key) { + auto col = data[sel]; + std::optional val; + std::visit( + [&val, sel, key, this](auto &ser) { + using T = std::decay_tsecond)>; + auto sproxy = get_series>(sel) + .value(); // this is a series_proxy + val = sproxy.at(key); + }, + col); + return val; + } + + // F is a function that takes a key and a locator. + // Users will need to close over series_proxies that they want to use. + template + void for_all(F f) { + for (auto &idx : kti) { + f(idx.first, locator(idx.second)); + } + } + + template + void remove_if(F f) { + std::vector indices_to_delete; + for (auto &idx : kti) { + if (f(idx.first, locator(idx.second))) { + indices_to_delete.emplace_back(idx.second); + } + } + + for (auto &idx : indices_to_delete) { + kti.erase(itk[idx]); + itk.erase(idx); + for (auto &id_ser : data) { + std::visit([&idx](auto &ser) { ser.erase(idx); }, id_ser.second); + } + } + } + + void print() { + std::cout << "mvmap with " << data.size() << " series: " << std::endl; + for (auto &el : data) { + std::cout << "series " << el.first << ":" << std::endl; + std::visit( + [&el, this](auto &ser) { + using T = std::decay_tsecond)>; + auto sproxy = get_series>(el.first) + .value(); // this is a series_proxy + sproxy.print(); + }, + el.second); + + // std::cout << " second: " << el.second << std::endl; + // auto foo = get_variant_series(el.first).value(); + // foo.visit([](auto &ser) { print_series(ser); }); + // print_series(el.second); + } + } + + void print_cols(const std::vector &cols) { + for_all([this, &cols](auto key, auto loc) { + std::cout << key << " -> " << loc.loc; + for (auto &col : cols) { + auto v = get_as_variant(col, loc); + if (v.has_value()) { + std::cout << "at col " << col << ", v has a value\n"; + std::visit( + [&col](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + std::cout << " (str) " << col << ": " << arg; + } else if constexpr (std::is_same_v) { + std::cout << " (dbl) " << col << ": " << arg; + } else if constexpr (std::is_same_v) { + std::cout << " (int) " << col << ": " << arg; + } else if constexpr (std::is_same_v) { + std::cout << " (bool) " << col << ": " << arg; + } + }, + v.value()); + } + } + std::cout << std::endl; + }); + } + + std::map get_series_vals_at( + const K &key, const std::vector &cols) { + std::vector...>> proxies; + + for (auto &col : cols) { + if (has_series(col)) { + std::visit( + [this, &col, &proxies](auto &coldata) { + using T = std::decay_t::mapped_type; + auto sproxy = + get_series(col).value(); // this is a series_proxy + proxies.push_back(sproxy); + }, + data[col]); + } + } + + std::map row; + for (auto &sproxy : proxies) { + std::visit( + [&row, &key](auto &&arg) { + auto v = arg.at(key); + if (v.has_value()) { + row[arg.id()] = arg[key]; + } + }, + sproxy); + } + return row; + } +}; +}; // namespace mvmap diff --git a/test/include/testbracket.cpp b/test/include/testbracket.cpp new file mode 100644 index 0000000..96ac356 --- /dev/null +++ b/test/include/testbracket.cpp @@ -0,0 +1,17 @@ +#include +#include + +class Foo { + public: + int &operator[](int k) { return k; } + int &operator[](double k) { return int(k); } + int &operator[](std::string k) { return -99; } +}; + +int main() { + Foo f; + std::cout << f[1] << std::endl; + std::cout << f[1.1] << std::endl; + std::cout << f["hello"] << std::endl; + return 0; +} \ No newline at end of file diff --git a/test/include/testmvmap.cpp b/test/include/testmvmap.cpp new file mode 100644 index 0000000..3e9821d --- /dev/null +++ b/test/include/testmvmap.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include "mvmap.hpp" + +int main() { + mvmap::mvmap m; + + m.add_series("s1", "string1"); + m.add_series("d1", "double1"); + m.add_series("i1", "int1"); + m.add_series("b1", "bool1"); + + auto s1 = m.get_series("s1"); + s1.value()["k1"] = "hello"; + std::cout << "s1\n"; + s1.value().print(); + std::cout << "s1 done\n"; + auto s2 = m.get_series("i1"); + std::cout << "s2\n"; + s2.value()["k1"] = 42; + s2.value()["k2"] = 43; + std::cout << "s2 done\n"; + + std::cout << "m\n"; + m.print(); + std::cout << "m done\n"; + using variants = std::variant; + std::map myrow{ + {"s1", "hello"}, {"d1", 3.14}, {"i1", 99}, {"b1", true}}; + + std::cout << "adding rowkey\n"; + m.add_row("rowkey", myrow); + + std::map myrow_partial{ + {"d1", 2.22}, + {"i1", 22}, + }; + + std::cout << "adding partial\n"; + m.add_row("partial", myrow_partial); + std::cout << "m again\n"; + m.print(); + std::cout << "m done\n"; + + std::cout << "m cols\n"; + std::vector cols{"s1", "i1"}; + m.print_cols(cols); + + auto row = m.get_series_vals_at("partial", cols); + std::cout << "row\n"; + for (auto el : row) { + std::cout << el.first << " -> "; + std::visit( + [](auto &&arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + std::cout << " (str) " << arg; + } else if constexpr (std::is_same_v) { + std::cout << " (dbl) " << arg; + } else if constexpr (std::is_same_v) { + std::cout << " (int) " << arg; + } else if constexpr (std::is_same_v) { + std::cout << " (bool) " << arg; + } + }, + el.second); + std::cout << std::endl; + } + auto s1pxy = m.get_series("s1").value(); + std::cout << "row done\n"; +}; \ No newline at end of file