From ce6380b9564b83dec40d8bb2db06ebec15e93c8f Mon Sep 17 00:00:00 2001 From: Marius Mikucionis Date: Wed, 25 Jun 2025 16:25:26 +0200 Subject: [PATCH 01/21] Modernized CMake build scripts --- .gitignore | 3 +++ CMakeLists.txt | 29 +++++++++++++++++++---------- cmake/nlohmann_json.cmake | 29 +++++++++++++++++++++++++++++ src/CMakeLists.txt | 23 ++++++----------------- 4 files changed, 57 insertions(+), 27 deletions(-) create mode 100644 .gitignore create mode 100644 cmake/nlohmann_json.cmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..916b844 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +build +cmake-build-* diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f948b4..964c4fe 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,20 @@ -cmake_minimum_required(VERSION 3.7) +cmake_minimum_required(VERSION 3.28) +project(libstrategy VERSION 1.0.9 LANGUAGES CXX C) cmake_policy(SET CMP0048 NEW) include(ExternalProject) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD_EXTENSIONS OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif (NOT CMAKE_BUILD_TYPE) -set(CMAKE_POSITION_INDEPENDENT_CODE ON) -project(libstrategy VERSION 1.0.0 LANGUAGES CXX C) - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -Wall -Wpedantic -I -fPIC") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG -Wall -Wpedantic -fPIC") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) @@ -19,6 +22,7 @@ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(EXTERNAL_INSTALL_LOCATION ${CMAKE_BINARY_DIR}/external) +option(LIBSTRATEGY_OnlyLibrary "Build only as library." OFF) option(STRATEGY_GetDependencies "Fetch external dependencies from web." ON) if (STRATEGY_GetDependencies) @@ -28,11 +32,7 @@ if (STRATEGY_GetDependencies) CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release ) - ExternalProject_add(nlohmann_json - URL https://github.com/nlohmann/json/archive/v3.7.3.zip - URL_HASH SHA512=b47a07de9a071cce645a173d084df5dd31f7669154fc00f6c99e0506474d30e8376acaee1d3c79a50def4f25a36042951bfa4fca9a704687e59c368d05053158 - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION} -DJSON_BuildTests=OFF -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - ) + include(cmake/nlohmann_json.cmake) include_directories(${EXTERNAL_INSTALL_LOCATION}/include) link_directories(${EXTERNAL_INSTALL_LOCATION}/lib) @@ -46,7 +46,16 @@ if(Boost_ROOT) unset(Boost_ROOT) endif() -add_subdirectory(${CMAKE_SOURCE_DIR}/src/) +if(NOT LIBSTRATEGY_OnlyLibrary) + cmake_policy(SET CMP0069 NEW) + cmake_policy(SET CMP0074 NEW) + cmake_policy(SET CMP0167 NEW) # do not rely on FindBoost/CMake, use FindBoost from boost installation + + find_package(Boost 1.83 COMPONENTS program_options REQUIRED) + include_directories(${Boost_INCLUDE_DIR}) +endif() + +add_subdirectory(src) set(LIBSTRATEGY_BuildTests "Build tests of libstrategy" ON) if(BUILD_TESTING AND LIBSTRATEGY_BuildTests) diff --git a/cmake/nlohmann_json.cmake b/cmake/nlohmann_json.cmake new file mode 100644 index 0000000..7a7e8d5 --- /dev/null +++ b/cmake/nlohmann_json.cmake @@ -0,0 +1,29 @@ +find_package(nlohmann_json 3.12.0 QUIET) +if (nlohmann_json_FOUND) + message(STATUS "Found nlohmann_json: ${nlohmann_json_SOURCE_DIR}") +else (nlohmann_json_FOUND) + message(STATUS "Failed to find nlohmann_json, going to fetch from source") + set(JSON_BuildTests OFF CACHE BOOL "Build the unit tests when BUILD_TESTING is enabled.") + set(JSON_CI OFF CACHE BOOL "Enable CI build targets.") + set(JSON_Diagnostics OFF CACHE BOOL "Use extended diagnostic messages.") + set(JSON_Diagnostic_Positions OFF CACHE BOOL "Enable diagnostic positions.") + set(JSON_GlobalUDLs ON CACHE BOOL "Place user-defined string literals in the global namespace.") + set(JSON_ImplicitConversions ON CACHE BOOL "Enable implicit conversions.") + set(JSON_DisableEnumSerialization OFF CACHE BOOL "Disable default integer enum serialization.") + set(JSON_LegacyDiscardedValueComparison OFF CACHE BOOL "Enable legacy discarded value comparison.") + set(JSON_Install OFF CACHE BOOL "Install CMake targets during install step.") + set(JSON_MultipleHeaders ON CACHE BOOL "Use non-amalgamated version of the library.") + set(JSON_SystemInclude OFF CACHE BOOL "Include as system headers (skip for clang-tidy).") + set(FETCHCONTENT_QUIET ON) + set(FETCHCONTENT_UPDATES_DISCONNECTED ON) + include(FetchContent) + FetchContent_Declare(nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json + GIT_TAG v3.12.0 + GIT_SHALLOW TRUE # download specific revision only (git clone --depth 1) + GIT_PROGRESS TRUE # show download progress in Ninja + FIND_PACKAGE_ARGS NAMES nlohmann_json + USES_TERMINAL_DOWNLOAD TRUE) + FetchContent_MakeAvailable(nlohmann_json) + message(STATUS "Got nlohmann_json: ${nlohmann_json_SOURCE_DIR}") +endif (nlohmann_json_FOUND) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8ac6d1b..80e5c7b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,39 +1,28 @@ -cmake_minimum_required(VERSION 3.7) -project(libstrategy C CXX) -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_INCLUDE_CURRENT_DIR ON) -option(LIBSTRATEGY_OnlyLibrary "Build only as library." OFF) - -if(NOT LIBSTRATEGY_OnlyLibrary) - cmake_policy(SET CMP0069 NEW) - cmake_policy(SET CMP0074 NEW) - - find_package(Boost 1.66 COMPONENTS program_options REQUIRED) - include_directories(${Boost_INCLUDE_DIR}) -endif() +# set(CMAKE_INCLUDE_CURRENT_DIR ON) add_library(strategy SHARED ${HEADER_FILES} libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) +target_link_libraries(strategy PRIVATE nlohmann_json::nlohmann_json) if (STRATEGY_GetDependencies) add_dependencies(strategy ptrie nlohmann_json) endif (STRATEGY_GetDependencies) -target_include_directories (strategy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +#target_include_directories (strategy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) add_library(strategyStatic STATIC ${HEADER_FILES} libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) +target_link_libraries(strategyStatic PRIVATE nlohmann_json::nlohmann_json) if (STRATEGY_GetDependencies) add_dependencies(strategyStatic ptrie nlohmann_json) endif (STRATEGY_GetDependencies) -target_include_directories (strategyStatic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +#target_include_directories (strategyStatic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties(strategyStatic PROPERTIES OUTPUT_NAME strategy) if(NOT LIBSTRATEGY_OnlyLibrary) add_executable(z2s ${HEADER_FILES} main.cpp ZonotopStrategy.cpp SimpleTree.cpp) + target_link_libraries(z2s PRIVATE nlohmann_json::nlohmann_json ${Boost_LIBRARIES}) if (STRATEGY_GetDependencies) add_dependencies(z2s ptrie nlohmann_json) endif (STRATEGY_GetDependencies) - target_link_libraries(z2s PRIVATE stdc++fs ${Boost_LIBRARIES}) endif() - install(TARGETS strategy RUNTIME DESTINATION bin LIBRARY DESTINATION lib From f313ce14b482ccc60c8e4af2025d005d55ed4317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Wed, 25 Jun 2025 16:38:43 +0200 Subject: [PATCH 02/21] Fixed CMake scripts for Ubuntu-24.04 (cmake-3.28) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 964c4fe..6e2a10f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,10 +49,10 @@ endif() if(NOT LIBSTRATEGY_OnlyLibrary) cmake_policy(SET CMP0069 NEW) cmake_policy(SET CMP0074 NEW) - cmake_policy(SET CMP0167 NEW) # do not rely on FindBoost/CMake, use FindBoost from boost installation + # cmake_policy(SET CMP0167 NEW) # do not rely on FindBoost/CMake, use FindBoost from boost installation find_package(Boost 1.83 COMPONENTS program_options REQUIRED) - include_directories(${Boost_INCLUDE_DIR}) + # include_directories(${Boost_INCLUDE_DIR}) endif() add_subdirectory(src) From caa772c3f5b00ce66cb6504c25cada75d41ccab7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Wed, 25 Jun 2025 18:24:06 +0200 Subject: [PATCH 03/21] Simplified the cmake scripts --- .gitignore | 1 + CMakeLists.txt | 45 ++++++++------------------------------- README.md | 36 ++++++++++++++++++++++++++++++- cmake/nlohmann_json.cmake | 5 +++-- cmake/ptrie.cmake | 26 ++++++++++++++++++++++ src/CMakeLists.txt | 25 ++++++---------------- test/CMakeLists.txt | 19 ++++++----------- 7 files changed, 86 insertions(+), 71 deletions(-) create mode 100644 cmake/ptrie.cmake diff --git a/.gitignore b/.gitignore index 916b844..012dee2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *~ build cmake-build-* +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e2a10f..071ce86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,6 @@ cmake_minimum_required(VERSION 3.28) project(libstrategy VERSION 1.0.9 LANGUAGES CXX C) -cmake_policy(SET CMP0048 NEW) - -include(ExternalProject) - set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_EXTENSIONS OFF) @@ -15,50 +11,27 @@ if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release) endif (NOT CMAKE_BUILD_TYPE) -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g -Wall -Wpedantic -I -fPIC") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -DNDEBUG -Wall -Wpedantic -fPIC") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -set(EXTERNAL_INSTALL_LOCATION ${CMAKE_BINARY_DIR}/external) - -option(LIBSTRATEGY_OnlyLibrary "Build only as library." OFF) -option(STRATEGY_GetDependencies "Fetch external dependencies from web." ON) - -if (STRATEGY_GetDependencies) - ExternalProject_add(ptrie - URL https://github.com/petergjoel/ptrie/archive/v1.1.1.zip - URL_HASH SHA512=5435D6F8132D273B50ECCD721790D559F7DC67FFDADA75FEC315F4C53705199F31F1DDC80DDA0CB86F883DB27076FA6D324D28516B442FECCE3FFA51213AEF4E - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${EXTERNAL_INSTALL_LOCATION} -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release - ) - include(cmake/nlohmann_json.cmake) +option(LIBSTRATEGY_TESTS "libstrategy Unit Tests" ON) +option(LIBSTRATEGY_OnlyLibrary "Build only the library (no binary utilities)" OFF) - include_directories(${EXTERNAL_INSTALL_LOCATION}/include) - link_directories(${EXTERNAL_INSTALL_LOCATION}/lib) -else (STRATEGY_GetDependencies) - find_package(nlohmann_json 3.7.3 REQUIRED) - find_package(ptrie 1.1.1 REQUIRED) -endif (STRATEGY_GetDependencies) - -# Unset Boost_ROOT here if set, not needed for JSON or source of strategy -if(Boost_ROOT) - unset(Boost_ROOT) -endif() +include(cmake/ptrie.cmake) +include(cmake/nlohmann_json.cmake) if(NOT LIBSTRATEGY_OnlyLibrary) - cmake_policy(SET CMP0069 NEW) - cmake_policy(SET CMP0074 NEW) - # cmake_policy(SET CMP0167 NEW) # do not rely on FindBoost/CMake, use FindBoost from boost installation - + cmake_policy(SET CMP0069 NEW) # INTERPROCEDURAL_OPTIMIZATIONS flags + if (CMAKE_MAJOR_VERSION GREATER_EQUAL 3 AND CMAKE_MINOR_VERSION GREATER_EQUAL 30) + cmake_policy(SET CMP0167 NEW) # use FindBoost from boost installation (as opposed to CMake own) + endif () find_package(Boost 1.83 COMPONENTS program_options REQUIRED) - # include_directories(${Boost_INCLUDE_DIR}) endif() add_subdirectory(src) -set(LIBSTRATEGY_BuildTests "Build tests of libstrategy" ON) -if(BUILD_TESTING AND LIBSTRATEGY_BuildTests) +if (LIBSTRATEGY_TESTS) enable_testing() add_subdirectory(test) endif() diff --git a/README.md b/README.md index 8bff0df..0cceebd 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -# libstrategy \ No newline at end of file +# libstrategy + +Library for learning strategies. + +## Dependencies + +Library only: +- ptrie (will fetch if not installed) +- nlohmann_json (will fetch if not installed) + +Full project: +- Boost: program_options tests + +Ubuntu 24.04: +```shell +sudo apt install libboost-program-options-dev libboost-tests-dev +``` + +macOS: +```shell +brew install boost +``` + +## Compile and Install +```shell +``` + + +## Compile for Development + +```shell +cmake -B build +cmake --build build +ctest --test-dir build +``` \ No newline at end of file diff --git a/cmake/nlohmann_json.cmake b/cmake/nlohmann_json.cmake index 7a7e8d5..e792b7f 100644 --- a/cmake/nlohmann_json.cmake +++ b/cmake/nlohmann_json.cmake @@ -1,6 +1,7 @@ -find_package(nlohmann_json 3.12.0 QUIET) +find_package(nlohmann_json 3.7.3 QUIET) if (nlohmann_json_FOUND) - message(STATUS "Found nlohmann_json: ${nlohmann_json_SOURCE_DIR}") + get_target_property(nlohmann_json_INCLUDE_DIRS nlohmann_json::nlohmann_json INTERFACE_INCLUDE_DIRECTORIES) + message(STATUS "Found nlohmann_json: ${nlohmann_json_INCLUDE_DIRS}") else (nlohmann_json_FOUND) message(STATUS "Failed to find nlohmann_json, going to fetch from source") set(JSON_BuildTests OFF CACHE BOOL "Build the unit tests when BUILD_TESTING is enabled.") diff --git a/cmake/ptrie.cmake b/cmake/ptrie.cmake new file mode 100644 index 0000000..f189046 --- /dev/null +++ b/cmake/ptrie.cmake @@ -0,0 +1,26 @@ +find_package(ptrie 1.1.1 QUIET) +if (ptrie_FOUND) + get_target_property(ptrie_INCLUDE_DIRS ptrie::ptrie INTERFACE_INCLUDE_DIRECTORIES) + message(STATUS "Found ptrie: ${ptrie_INCLUDE_DIRS}") +else (ptrie_FOUND) + message(STATUS "Failed to find ptrie, going to fetch from source") + set(PTRIE_BuildTests OFF CACHE BOOL "Build the unit tests when BUILD_TESTING is enabled.") + set(PTRIE_BuildBenchmark OFF CACHE BOOL "Build the simple benchmark suite") + set(PTRIE_AddressSanitizer OFF CACHE BOOL "Enables address sanitization during compilation.") + set(PTRIE_GetDependencies OFF CACHE BOOL "Fetch external dependencies from web.") + set(FETCHCONTENT_QUIET ON) + set(FETCHCONTENT_UPDATES_DISCONNECTED ON) + include(FetchContent) + FetchContent_Declare(ptrie + GIT_REPOSITORY https://github.com/petergjoel/ptrie + GIT_TAG v1.1.1 + GIT_SHALLOW TRUE # download specific revision only (git clone --depth 1) + GIT_PROGRESS TRUE # show download progress in Ninja + FIND_PACKAGE_ARGS NAMES ptrie + USES_TERMINAL_DOWNLOAD TRUE) + FetchContent_MakeAvailable(ptrie) + message(STATUS "Got ptrie: ${ptrie_SOURCE_DIR}") + # Workaround until ptrie exports proper cmake config: + add_library(ptrie::ptrie INTERFACE IMPORTED GLOBAL) + target_include_directories(ptrie::ptrie INTERFACE ${ptrie_SOURCE_DIR}/src) +endif (ptrie_FOUND) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 80e5c7b..2d20931 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,26 +1,13 @@ -# set(CMAKE_INCLUDE_CURRENT_DIR ON) +add_library(strategy SHARED libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) +target_link_libraries(strategy PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json) -add_library(strategy SHARED ${HEADER_FILES} libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) -target_link_libraries(strategy PRIVATE nlohmann_json::nlohmann_json) -if (STRATEGY_GetDependencies) - add_dependencies(strategy ptrie nlohmann_json) -endif (STRATEGY_GetDependencies) -#target_include_directories (strategy PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -add_library(strategyStatic STATIC ${HEADER_FILES} libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) -target_link_libraries(strategyStatic PRIVATE nlohmann_json::nlohmann_json) -if (STRATEGY_GetDependencies) - add_dependencies(strategyStatic ptrie nlohmann_json) -endif (STRATEGY_GetDependencies) -#target_include_directories (strategyStatic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +add_library(strategyStatic STATIC libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) +target_link_libraries(strategyStatic PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json) set_target_properties(strategyStatic PROPERTIES OUTPUT_NAME strategy) if(NOT LIBSTRATEGY_OnlyLibrary) - add_executable(z2s ${HEADER_FILES} main.cpp ZonotopStrategy.cpp SimpleTree.cpp) - target_link_libraries(z2s PRIVATE nlohmann_json::nlohmann_json ${Boost_LIBRARIES}) - if (STRATEGY_GetDependencies) - add_dependencies(z2s ptrie nlohmann_json) - endif (STRATEGY_GetDependencies) + add_executable(z2s main.cpp ZonotopStrategy.cpp SimpleTree.cpp) + target_link_libraries(z2s PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json Boost::program_options) endif() install(TARGETS strategy diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3417e26..19009ba 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,18 +1,11 @@ - find_package (Boost COMPONENTS unit_test_framework REQUIRED) -include_directories (${TEST_SOURCE_DIR}/src - ${Boost_INCLUDE_DIRS} - ${libstrategy_SOURCE_DIR} - ) -add_definitions (-DBOOST_TEST_DYN_LINK) +include_directories (${CMAKE_SOURCE_DIR}/src) add_executable (unordered_load unordered_load.cpp) -add_executable (inconsistent_lookup inconsistent_lookup.cpp) - -target_link_libraries(unordered_load ${Boost_LIBRARIES} strategy) -target_link_libraries(inconsistent_lookup ${Boost_LIBRARIES} strategy) - +target_link_libraries(unordered_load PRIVATE strategy Boost::unit_test_framework) add_test(NAME unordered_load COMMAND unordered_load) + +add_executable (inconsistent_lookup inconsistent_lookup.cpp) +target_link_libraries(inconsistent_lookup PRIVATE strategy Boost::unit_test_framework) add_test(NAME inconsistent_lookup COMMAND inconsistent_lookup) -set_tests_properties(inconsistent_lookup PROPERTIES - ENVIRONMENT STRATEGY_DIR=${CMAKE_CURRENT_SOURCE_DIR}/strategies) +set_tests_properties(inconsistent_lookup PROPERTIES ENVIRONMENT STRATEGY_DIR=${CMAKE_CURRENT_SOURCE_DIR}/strategies) From 418b206529bb488ad7f2f19f6e0125af593db676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Wed, 25 Jun 2025 20:56:53 +0200 Subject: [PATCH 04/21] Added presets and README for quick build, test, install --- .gitignore | 2 + CMakePresets.json | 56 ++++++ README.md | 51 ++++-- cmake/CommonPresets.json | 383 +++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 2 +- 5 files changed, 479 insertions(+), 15 deletions(-) create mode 100644 CMakePresets.json create mode 100644 cmake/CommonPresets.json diff --git a/.gitignore b/.gitignore index 012dee2..7d90c9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ *~ build +build-* cmake-build-* +local .idea diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..c199215 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,56 @@ +{ + "version": 10, + "cmakeMinimumRequired": { + "major": 3, + "minor": 28, + "patch": 0 + }, + "include": ["cmake/CommonPresets.json"], + "configurePresets": [ + { + "name": "libonly", + "inherits": "multi", + "binaryDir": "${sourceDir}/build-libonly", + "cacheVariables": { + "LIBSTRATEGY_TESTS": "OFF" , + "LIBSTRATEGY_OnlyLibrary": "ON" + }, + "displayName": "Configure Library-Only for Debug and Release" + } + ], + "buildPresets": [ + { + "name": "libonly-release", + "inherits": "release", + "configurePreset": "libonly", + "displayName": "Build Library-Only for Debug" + }, + { + "name": "libonly-debug", + "inherits": "debug", + "configurePreset": "libonly", + "displayName": "Build Library-Only for Release" + } + ], + "testPresets": [], + "workflowPresets": [ + { + "name": "libonly", + "displayName": "Configure and Build Library-Only for Debug and Release (no testing)", + "steps": [ + { + "type": "configure", + "name": "libonly" + }, + { + "type": "build", + "name": "libonly-release" + }, + { + "type": "build", + "name": "libonly-debug" + } + ] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 0cceebd..b23451c 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,55 @@ Library for learning strategies. ## Dependencies -Library only: -- ptrie (will fetch if not installed) -- nlohmann_json (will fetch if not installed) +Library only requires: +- [ptrie](https://github.com/petergjoel/ptrie) (CMake will fetch automatically if not installed) +- [nlohmann_json](https://github.com/nlohmann/json) (CMake will fetch automatically if not installed) -Full project: -- Boost: program_options tests +Full project requires: +- [Boost](https://boost.org): [program_options](https://www.boost.org/library/latest/program_options/), [test](https://www.boost.org/library/latest/test/) -Ubuntu 24.04: +For Ubuntu 24.04 install build tools and library dependencies: ```shell -sudo apt install libboost-program-options-dev libboost-tests-dev +sudo apt install cmake ninja-build g++ libboost-program-options-dev libboost-tests-dev ``` -macOS: +For macOS install build tools and library dependencies: ```shell -brew install boost +brew install cmake ninja boost ``` ## Compile and Install +Inspect common workflow presets: ```shell +cmake --workflow --list-presets ``` +Run minimal preset to build the library with `Debug` and `Release` settings into `build-libonly/lib`: +```shell +cmake --workflow libonly +``` +Install the `Release` build of `build-libonly` into `$PWD/local` path: +```shell +cmake --install build-libonly --config Release --prefix $PWD/local +``` + +Configure, build and test for Development with Sanitizers (GCC/Clang/AppleClang): +```shell +cmake --workflow debug-san +``` -## Compile for Development +Configuration presets: +```shell +cmake --list-presets=configure +``` +Build presets: ```shell -cmake -B build -cmake --build build -ctest --test-dir build -``` \ No newline at end of file +cmake --list-presets=build +``` + +Test presets: +```shell +cmake --list-presets=test +``` + diff --git a/cmake/CommonPresets.json b/cmake/CommonPresets.json new file mode 100644 index 0000000..4cb48a6 --- /dev/null +++ b/cmake/CommonPresets.json @@ -0,0 +1,383 @@ +{ + "version": 10, + "cmakeMinimumRequired": { + "major": 3, + "minor": 28, + "patch": 0 + }, + "configurePresets": [ + { + "name": "default", + "binaryDir": "${sourceDir}/build", + "displayName": "Configure with Defaults (Debug)" + }, + { + "name": "multi", + "inherits": "default", + "binaryDir": "${sourceDir}/build-multi", + "generator": "Ninja Multi-Config", + "displayName": "Configure for Ninja Debug/Release" + }, + { + "name": "multi-san", + "inherits": "multi", + "binaryDir": "${sourceDir}/build-multi-san", + "environment": { + "CFLAGS": "-fsanitize=leak -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", + "CXXFLAGS": "-fsanitize=leak -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", + "LDFLAGS": "-fsanitize=leak -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer" + }, + "displayName": "Configure for Ninja Debug/Release with Sanitizers" + }, + { + "name": "gcc", + "inherits": "multi", + "binaryDir": "${sourceDir}/build-gcc", + "environment": { + "CMAKE_C_COMPILER": "gcc", + "CMAKE_CXX_COMPILER": "g++" + }, + "displayName": "Configure GCC for Ninja Debug/Release" + }, + { + "name": "clang", + "inherits": "multi", + "binaryDir": "${sourceDir}/build-clang", + "environment": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++" + }, + "displayName": "Configure Clang for Ninja Debug/Release" + } + ], + "buildPresets": [ + { + "name": "default", + "configurePreset": "default", + "displayName": "Build using default configuration" + }, + { + "name": "debug", + "configurePreset": "multi", + "configuration": "Debug", + "displayName": "Build with Debug" + }, + { + "name": "debug-san", + "inherits": "debug", + "configurePreset": "multi-san", + "displayName": "Build with Debug and Sanitizers" + }, + { + "name": "debug-gcc", + "inherits": "debug", + "configurePreset": "gcc", + "displayName": "Build with GCC Debug" + }, + { + "name": "debug-clang", + "inherits": "debug", + "configurePreset": "clang", + "displayName": "Build with Clang Debug" + }, + { + "name": "release", + "configurePreset": "multi", + "configuration": "Release", + "displayName": "Build with Release" + }, + { + "name": "release-san", + "inherits": "release", + "configurePreset": "multi-san", + "displayName": "Build with Release and Sanitizers" + }, + { + "name": "release-gcc", + "inherits": "release", + "configurePreset": "gcc", + "displayName": "Build with GCC Release" + }, + { + "name": "release-clang", + "inherits": "release", + "configurePreset": "clang", + "displayName": "Build with Clang Release" + } + ], + "testPresets": [ + { + "name": "default", + "configurePreset": "default", + "output": { "outputOnFailure": true }, + "execution": { "noTestsAction": "error", "stopOnFailure": true }, + "displayName": "Test with default options" + }, + { + "name": "debug", + "inherits": "default", + "configurePreset": "multi", + "configuration": "Debug", + "displayName": "Test the Debug" + }, + { + "name": "debug-san", + "inherits": "debug", + "configurePreset": "multi-san", + "displayName": "Test the Debug with Sanitizers" + }, + { + "name": "debug-gcc", + "inherits": "debug", + "configurePreset": "gcc", + "displayName": "Test the GCC Debug" + }, + { + "name": "debug-clang", + "inherits": "debug", + "configurePreset": "clang", + "displayName": "Test the Clang Debug" + }, + { + "name": "release", + "inherits": "default", + "configurePreset": "multi", + "configuration": "Release", + "displayName": "Test the Release" + }, + { + "name": "release-san", + "inherits": "release", + "configurePreset": "multi-san", + "displayName": "Test the Release with Sanitizers" + }, + { + "name": "release-gcc", + "inherits": "release", + "configurePreset": "gcc", + "displayName": "Test the GCC Release" + }, + { + "name": "release-clang", + "inherits": "release", + "configurePreset": "clang", + "displayName": "Test the Clang Release" + } + ], + "workflowPresets": [ + { + "name": "default", + "displayName": "Configure, Build and Test with Defaults (Debug)", + "steps": [ + { + "type": "configure", + "name": "default" + }, + { + "type": "build", + "name": "default" + }, + { + "type": "test", + "name": "default" + } + ] + }, + { + "name": "debug", + "displayName": "Configure, Build and Test with Debug", + "steps": [ + { + "type": "configure", + "name": "multi" + }, + { + "type": "build", + "name": "debug" + }, + { + "type": "test", + "name": "debug" + } + ] + }, + { + "name": "debug-san", + "displayName": "Configure, Build and Test with Debug and Sanitizers", + "steps": [ + { + "type": "configure", + "name": "multi-san" + }, + { + "type": "build", + "name": "debug-san" + }, + { + "type": "test", + "name": "debug-san" + } + ] + }, + { + "name": "debug-gcc", + "displayName": "Configure, Build and Test with Debug using GCC", + "steps": [ + { + "type": "configure", + "name": "gcc" + }, + { + "type": "build", + "name": "debug-gcc" + }, + { + "type": "test", + "name": "debug-gcc" + } + ] + }, + { + "name": "debug-clang", + "displayName": "Configure, Build and Test with Debug using Clang", + "steps": [ + { + "type": "configure", + "name": "clang" + }, + { + "type": "build", + "name": "debug-clang" + }, + { + "type": "test", + "name": "debug-clang" + } + ] + }, + { + "name": "release", + "displayName": "Configure, Build and Test with Release", + "steps": [ + { + "type": "configure", + "name": "multi" + }, + { + "type": "build", + "name": "release" + }, + { + "type": "test", + "name": "release" + } + ] + }, + { + "name": "release-san", + "displayName": "Configure, Build and Test with Release and Sanitizers", + "steps": [ + { + "type": "configure", + "name": "multi-san" + }, + { + "type": "build", + "name": "release-san" + }, + { + "type": "test", + "name": "release-san" + } + ] + }, + { + "name": "release-gcc", + "displayName": "Configure, Build and Test with Release using GCC", + "steps": [ + { + "type": "configure", + "name": "gcc" + }, + { + "type": "build", + "name": "release-gcc" + }, + { + "type": "test", + "name": "release-gcc" + } + ] + }, + { + "name": "release-clang", + "displayName": "Configure, Build and Test with Release using Clang", + "steps": [ + { + "type": "configure", + "name": "clang" + }, + { + "type": "build", + "name": "release-clang" + }, + { + "type": "test", + "name": "release-clang" + } + ] + }, + { + "name": "multi", + "displayName": "Configure, Build and Test with Debug and Release", + "steps": [ + { + "type": "configure", + "name": "multi" + }, + { + "type": "build", + "name": "debug" + }, + { + "type": "test", + "name": "debug" + }, + { + "type": "build", + "name": "release" + }, + { + "type": "test", + "name": "release" + } + ] + }, + { + "name": "multi-san", + "displayName": "Configure, Build and Test with Debug and Release and Sanitizers", + "steps": [ + { + "type": "configure", + "name": "multi-san" + }, + { + "type": "build", + "name": "debug-san" + }, + { + "type": "test", + "name": "debug-san" + }, + { + "type": "build", + "name": "release-san" + }, + { + "type": "test", + "name": "release-san" + } + ] + } + ] +} \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2d20931..9dc451c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -10,7 +10,7 @@ if(NOT LIBSTRATEGY_OnlyLibrary) target_link_libraries(z2s PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json Boost::program_options) endif() -install(TARGETS strategy +install(TARGETS strategy strategyStatic RUNTIME DESTINATION bin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) From ccf17eaf09ac0862b3f317721c467d48ebc747ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Wed, 25 Jun 2025 20:59:02 +0200 Subject: [PATCH 05/21] Minor fixes to README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b23451c..60c2a12 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Library for learning strategies. ## Dependencies -Library only requires: +Library-only build requires: - [ptrie](https://github.com/petergjoel/ptrie) (CMake will fetch automatically if not installed) - [nlohmann_json](https://github.com/nlohmann/json) (CMake will fetch automatically if not installed) @@ -13,7 +13,7 @@ Full project requires: For Ubuntu 24.04 install build tools and library dependencies: ```shell -sudo apt install cmake ninja-build g++ libboost-program-options-dev libboost-tests-dev +sudo apt install cmake ninja-build g++ libboost-program-options-dev libboost-test-dev ``` For macOS install build tools and library dependencies: From 4ab96f20196b96dfc08abbb213b7eca668b88b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Thu, 26 Jun 2025 14:43:49 +0200 Subject: [PATCH 06/21] Added back the compiler warnings and fixed them and some more readability cleanup --- CMakeLists.txt | 6 +- README.md | 21 +- cmake/warnings.cmake | 9 + src/SimpleTree.cpp | 2220 ++++++++++++++++------------------ src/SimpleTree.h | 262 ++-- src/ZonotopStrategy.cpp | 796 ++++++------ src/ZonotopStrategy.h | 102 +- src/errors.h | 33 +- test/inconsistent_lookup.cpp | 58 +- test/unordered_load.cpp | 570 +++++---- 10 files changed, 2024 insertions(+), 2053 deletions(-) create mode 100644 cmake/warnings.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 071ce86..8248888 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,13 +3,11 @@ project(libstrategy VERSION 1.0.9 LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_STANDARD_EXTENSIONS OFF) +set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if (NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE Release) -endif (NOT CMAKE_BUILD_TYPE) +include(cmake/warnings.cmake) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) diff --git a/README.md b/README.md index 60c2a12..2d2eed3 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,7 @@ brew install cmake ninja boost ``` ## Compile and Install -Inspect common workflow presets: -```shell -cmake --workflow --list-presets -``` - -Run minimal preset to build the library with `Debug` and `Release` settings into `build-libonly/lib`: +Run minimal compilation (just the library) with `Debug` and `Release` settings into `build-libonly/lib`: ```shell cmake --workflow libonly ``` @@ -36,22 +31,28 @@ Install the `Release` build of `build-libonly` into `$PWD/local` path: cmake --install build-libonly --config Release --prefix $PWD/local ``` -Configure, build and test for Development with Sanitizers (GCC/Clang/AppleClang): +## Other Presets +Inspect workflow presets: +```shell +cmake --workflow --list-presets +``` + +For example, configure, build and **test** for Development with **Sanitizers** (GCC/Clang/AppleClang): ```shell cmake --workflow debug-san ``` -Configuration presets: +Other configuration presets: ```shell cmake --list-presets=configure ``` -Build presets: +Other build presets: ```shell cmake --list-presets=build ``` -Test presets: +Other test presets: ```shell cmake --list-presets=test ``` diff --git a/cmake/warnings.cmake b/cmake/warnings.cmake new file mode 100644 index 0000000..409ab03 --- /dev/null +++ b/cmake/warnings.cmake @@ -0,0 +1,9 @@ +if (CMAKE_CXX_COMPILER_ID MATCHES GNU) + add_compile_options(-Wpedantic -Wall -Wextra) +elseif (CMAKE_CXX_COMPILER_ID MATCHES AppleClang) + add_compile_options(-Wpedantic -Wall -Wextra) +elseif (CMAKE_CXX_COMPILER_ID MATCHES Clang) + add_compile_options(-Wpedantic -Wall -Wextra) +elseif (CMAKE_CXX_COMPILER_ID MATCHES MSVC) + add_compile_options(/W4) +endif () \ No newline at end of file diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index b30ce34..fd25313 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -1,1010 +1,934 @@ /* * Copyright (C) 2020 Peter G. Jensen - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ - -/* +/* * File: SimpleTree.cpp * Author: Peter G. Jensen - * + * * Created on May 9, 2019, 10:21 PM */ #include "SimpleTree.h" #include "errors.h" +#include + +#include #include -#include -#include -#include #include +#include using json = nlohmann::json; -const std::vector& SimpleTree::actions() const -{ - return _actions; -} +const std::vector &SimpleTree::actions() const { return _actions; } -SimpleTree SimpleTree::parse(std::istream& input, bool simplify, bool subsumption, double accuracy) -{ - std::vector empty; - return parse(input, simplify, subsumption, accuracy, empty); +SimpleTree SimpleTree::parse(std::istream &input, bool simplify, + bool subsumption, double accuracy) { + auto empty = std::vector{}; + return parse(input, simplify, subsumption, accuracy, empty); } -SimpleTree SimpleTree::parse(std::istream& input, bool simplify, bool subsumption, double accuracy, std::vector& exactness) -{ - auto raw = json::parse(input); - if(!raw.is_object()) - throw base_error("Input JSON not well formatted"); +SimpleTree SimpleTree::parse(std::istream &input, + bool simplify [[maybe_unused]], bool subsumption, + double accuracy, std::vector &exactness) { + auto raw = json::parse(input); + if (!raw.is_object()) + throw base_error("Input JSON not well formatted"); - if(!raw["version"].is_number_float() || raw["version"].get() != 1.0) - throw base_error("Input version not supported"); + if (!raw["version"].is_number_float() || raw["version"].get() != 1.0) + throw base_error("Input version not supported"); - if(!raw["type"].is_string() || raw["type"].get() != "state->regressor") - throw base_error("Input type not supported"); + if (!raw["type"].is_string() || + raw["type"].get() != "state->regressor") + throw base_error("Input type not supported"); - if(!raw["representation"].is_string() || raw["representation"].get() != "map") - throw base_error("Input representation not supported"); - - if(!raw["actions"].is_object()) - throw base_error("Expected actions object"); - // store actions - SimpleTree tree; - if(tree._root == nullptr) - { + if (!raw["representation"].is_string() || + raw["representation"].get() != "map") + throw base_error("Input representation not supported"); + + if (!raw["actions"].is_object()) + throw base_error("Expected actions object"); + // store actions + SimpleTree tree; + if (tree._root == nullptr) { + tree._root = std::make_shared(); + tree._root->_limit = -std::numeric_limits::infinity(); + tree._root->_cost = std::numeric_limits::infinity(); + tree._root->_var = std::numeric_limits::max(); + } + for (const auto &[act_key, act_value] : raw["actions"].items()) { + auto id = std::stoi(act_key); + if (id >= static_cast(tree._actions.size())) + tree._actions.resize(id + 1); + tree._actions[id] = act_value.get(); + } + + if (!raw["statevars"].is_array()) + throw base_error("Expected statevars as array"); + tree._statevars = raw["statevars"].get>(); + + if (!raw["pointvars"].is_array()) + throw base_error("Expected pointvars as array"); + tree._pointvars = raw["pointvars"].get>(); + + if (!raw["regressors"].is_object()) + throw base_error("Expected regressors as object"); + auto minim = -1; + auto inf = json("inf"); // json string (whereas {"inf"} is json array!) + auto first_element = true; + for (const auto &[raw_key, raw_value] : raw["regressors"].items()) { + auto key = parse_key(raw_key); + if (key.size() != tree._statevars.size() && + (!tree._statevars.empty() || key[0] != 1.0)) { + std::cerr << raw_key << std::endl; + std::cerr << key.size() << std::endl; + std::cerr << tree._statevars.size() << std::endl; + throw base_error("Expected cardinality of key does not match cardinality " + "of statevars"); + } + if (tree._statevars.empty()) + key.clear(); + + if (!raw_value.is_object()) + throw base_error("Expected regressor-element as object"); + if (raw_value["type"].get() != "act->point->val") + throw base_error("Expected regressor-element of type act->point->val"); + if (raw_value["representation"].get() != "simpletree") + throw base_error("Expected regressor-element represented as simpletree"); + bool is_minimize = raw_value["minimize"].is_boolean() + ? raw_value["minimize"].get() + : raw_value["minimize"].get(); + if (!raw_value["regressor"].is_object()) + throw base_error("Expected regressor-sub-element as an object"); + if (minim == -1) + minim = is_minimize; + if (minim != is_minimize) + throw base_error( + "Expected all sub-regressors to have same minimization flag"); + if (first_element) + tree._root->_cost = + (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); + first_element = false; + // make sure all actions are mapped initially + for (size_t i = 0; i < tree._actions.size(); ++i) + tree._root->insert(key, inf, i, tree, 0, is_minimize, 0, exactness); + for (const auto &[reg_key, reg_value] : raw_value["regressor"].items()) { + auto action = std::stoi(reg_key); + if (action >= static_cast(tree._actions.size())) + throw base_error("Action-index is out of bounds"); + + if (tree._root == nullptr) { tree._root = std::make_shared(); tree._root->_limit = -std::numeric_limits::infinity(); tree._root->_cost = std::numeric_limits::infinity(); + tree._root->_cost = + (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); tree._root->_var = std::numeric_limits::max(); - } - for(auto it = raw["actions"].begin(); it != raw["actions"].end(); ++it) - { - auto id = atoi(it.key().c_str()); - if(id >= (int)tree._actions.size()) - tree._actions.resize(id+1); - tree._actions[id] = it.value().get(); - } - - if(!raw["statevars"].is_array()) - throw base_error("Expected statevars as array"); - tree._statevars = raw["statevars"].get>(); - - if(!raw["pointvars"].is_array()) - throw base_error("Expected pointvars as array"); - tree._pointvars = raw["pointvars"].get>(); - - - if(!raw["regressors"].is_object()) - throw base_error("Expected regressors as object"); - auto& regressors = raw["regressors"]; - int minim = -1; - typeof(raw) inf = "inf"; + } - bool first_element = true; - for(auto it = regressors.begin(); it != regressors.end(); ++it) - { - auto key = parse_key(it.key()); - if(key.size() != tree._statevars.size() && (tree._statevars.size() != 0 || key[0] != 1.0)) - { - std::cerr << it.key() << std::endl; - std::cerr << key.size() << std::endl; - std::cerr << tree._statevars.size() << std::endl; - throw base_error("Expected cardinality of key does not match cardinality of statevars"); - } - else if(tree._statevars.size() == 0) - key.clear(); - - - if(!it.value().is_object()) - throw base_error("Expected regressor-element as object"); - auto& reg = it.value(); - if(reg["type"].get() != "act->point->val") - throw base_error("Expected regressor-element of type act->point->val"); - if(reg["representation"].get() != "simpletree") - throw base_error("Expected regressor-element represented as simpletree"); - bool is_minimize = reg["minimize"].is_boolean() ? reg["minimize"].get() : reg["minimize"].get(); - if(!reg["regressor"].is_object()) - throw base_error("Expected regressor-sub-element as an object"); - if(minim == -1) - minim = is_minimize; - if(minim != is_minimize) - throw base_error("Expected all sub-regressors to have same minimization flag"); - if(first_element) - tree._root->_cost = (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); - first_element = false; - // make sure all actions are mapped initially - for(size_t i = 0; i < tree._actions.size(); ++i) - tree._root->insert(key, inf, i, tree, 0, is_minimize, 0, exactness); - for(auto iit = reg["regressor"].begin(); iit != reg["regressor"].end(); ++iit) - { - size_t action = atoi(iit.key().c_str()); - if(action >= tree._actions.size()) - throw base_error("Action-index is out of bounds"); - - if(tree._root == nullptr) - { - tree._root = std::make_shared(); - tree._root->_limit = -std::numeric_limits::infinity(); - tree._root->_cost = std::numeric_limits::infinity(); - tree._root->_cost = (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); - tree._root->_var = std::numeric_limits::max(); - } - - auto obj = iit.value(); - tree._root->insert(key, obj, action, tree, 0, is_minimize, accuracy, exactness); - } + tree._root->insert(key, reg_value, action, tree, 0, is_minimize, accuracy, + exactness); } - tree._is_minimization = minim != 0; - if(subsumption) tree._root->subsumption_reduction(minim, tree); - /*if(simplify) // disabled for now - { - nodemap_t nodemap; - if(tree._root) - tree._root = tree._root->simplify(true, nodemap, tree); - if(tree._root) - tree._root->_parent = nullptr; - }*/ - return tree; + } + tree._is_minimization = minim != 0; + if (subsumption) + tree._root->subsumption_reduction(minim, tree); + /*if(simplify) // disabled for now + { + nodemap_t nodemap; + if(tree._root) + tree._root = tree._root->simplify(true, nodemap, tree); + if(tree._root) + tree._root->_parent = nullptr; + }*/ + return tree; } -std::vector SimpleTree::parse_key(const std::string& key) { - size_t i = 0; - std::vector res; - if(key[i] != '(') - { - throw base_error("Key is incorrectly formatted"); - } - ++i; - while(i < key.size() && key[i] != ')') - { - std::string ok(key.begin() + i, key.end()); - std::istringstream ss(ok); - { - std::string num; - std::getline(ss, num, ','); - std::istringstream ns(num); - res.emplace_back(); - ns >> res.back(); - i += num.length() + 1; - } +std::vector SimpleTree::parse_key(const std::string &key) { + auto res = std::vector{}; + auto it = key.c_str(); + const auto end = it + key.size(); + if (it != end && *it != '(') { + throw base_error("Key is incorrectly formatted"); + } + ++it; + while (it != end) { + double number; + if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { + res.push_back(number); + it = p; + if (it != end) { + if (*it == ')') + break; + if (*it != ',') + throw base_error("expected comma-separated-numbers in key: " + key); + ++it; + } + } else { + throw base_error("invalid number in key: " + key); } - return res; + } + return res; } -std::pair SimpleTree::node_t::compute_min_max() { - double mincost = std::numeric_limits::infinity(); - double maxcost = -mincost; - if(_low) - { - auto r = _low->compute_min_max(); - mincost = std::min(r.first, mincost); - maxcost = std::max(r.second, maxcost); - } - if(_high) - { - auto r = _high->compute_min_max(); - mincost = std::min(r.first, mincost); - maxcost = std::max(r.second, maxcost); - } - - if(is_leaf() && !std::isinf(_cost)) - { - mincost = std::min(_cost, mincost); - maxcost = std::max(_cost, maxcost); - } - return std::make_pair(mincost, maxcost); +std::pair SimpleTree::node_t::compute_min_max() const { + double mincost = std::numeric_limits::infinity(); + double maxcost = -mincost; + if (_low) { + const auto [r_min, r_max] = _low->compute_min_max(); + mincost = std::min(r_min, mincost); + maxcost = std::max(r_max, maxcost); + } + if (_high) { + const auto [r_min, r_max] = _high->compute_min_max(); + mincost = std::min(r_min, mincost); + maxcost = std::max(r_max, maxcost); + } + + if (is_leaf() && !std::isinf(_cost)) { + mincost = std::min(_cost, mincost); + maxcost = std::max(_cost, maxcost); + } + return std::make_pair(mincost, maxcost); } -void SimpleTree::node_t::action_nodes(std::vector>& nodes, uint32_t low, uint32_t high, uint32_t varid) { - if(_var != varid) - { - if(!is_leaf() || !(std::isnan(_cost) || std::isinf(_cost))) - { - auto ptr = shared_from_this(); - auto lb = std::lower_bound(nodes.begin(), nodes.end(), ptr); - if(lb == nodes.end() || (*lb).get() != this) - nodes.insert(lb, ptr); - } - } - else - { - if(_low) - _low->action_nodes(nodes, low, _limit, varid); - if(_high) - _high->action_nodes(nodes, _limit+1, high, varid); +void SimpleTree::node_t::action_nodes(std::vector &nodes, + uint32_t low, uint32_t high, + uint32_t varid) { + if (_var != varid) { + if (!is_leaf() || !(std::isnan(_cost) || std::isinf(_cost))) { + auto ptr = shared_from_this(); + auto lb = std::lower_bound(nodes.begin(), nodes.end(), ptr); + if (lb == nodes.end() || lb->get() != this) + nodes.insert(lb, ptr); } + } else { + if (_low) + _low->action_nodes(nodes, low, _limit, varid); + if (_high) + _high->action_nodes(nodes, _limit + 1, high, varid); + } } -void SimpleTree::node_t::subsumption_reduction(bool minimization, SimpleTree& parent) -{ - if(_var < parent._statevars.size()) - { - if(_low) - _low->subsumption_reduction(minimization, parent); - if(_high) - _high->subsumption_reduction(minimization, parent); +void SimpleTree::node_t::subsumption_reduction(bool minimization, + SimpleTree &parent) { + if (_var < parent._statevars.size()) { + if (_low) + _low->subsumption_reduction(minimization, parent); + if (_high) + _high->subsumption_reduction(minimization, parent); + } else { + if (_var != parent._statevars.size()) { + return; } - else - { - if(_var != parent._statevars.size()) - { - return; + size_t prev_vals = std::numeric_limits::max(); + size_t p = 0; + while (true) { + ++p; + bool outer = false; + std::vector nodes; + action_nodes(nodes, 0, parent._actions.size() - 1, + parent._statevars.size()); + auto val = std::numeric_limits::infinity(); + if (!minimization) + val *= -1; + auto best = val; + auto worst = -val; + for (const auto &n : nodes) { + auto [min, max] = n->compute_min_max(); + if (std::isinf(min)) + continue; + assert(!std::isinf(max)); + if (minimization) { + val = std::min(val, max); + best = std::min(best, min); + worst = std::max(worst, max); + } else { + val = std::max(val, min); + best = std::max(best, max); + worst = std::min(worst, min); } - size_t prev_vals = std::numeric_limits::max(); - size_t p = 0; - while(true) - { - ++p; - bool outer = false; - std::vector> nodes; - action_nodes(nodes, 0, parent._actions.size()-1, parent._statevars.size()); - auto val = std::numeric_limits::infinity(); - if(!minimization) val *= -1; - auto best = val; - auto worst = -val; - for(auto& n : nodes) - { - auto mm = n->compute_min_max(); - if(std::isinf(mm.first)) continue; - assert(!std::isinf(mm.second)); - if(minimization) - { - val = std::min(val, mm.second); - best = std::min(best, mm.first); - worst = std::max(worst, mm.second); - } - else - { - val = std::max(val, mm.first); - best = std::max(best, mm.second); - worst = std::min(worst, mm.first); - } - } - std::vector> bounds(parent._pointvars.size(), std::make_pair(-std::numeric_limits::infinity(), std::numeric_limits::infinity())); - for(auto& n : nodes) - { - outer |= n->check_tiles(n.get(), nodes, bounds, val, best, worst, minimization, parent._statevars.size() + 1); - } - std::set> values; - { - std::vector> nodes(parent._actions.size()); - action_nodes(nodes, 0, parent._actions.size()-1, parent._statevars.size()); - for(auto& n : nodes) - { - if(n == nullptr) continue; - get_ranks(values, n.get()); - } - std::unordered_map replace; - for(auto& e : values) - { - if(replace.count(e.first) > 0) continue; - double id = replace.size(); - replace[e.first] = id; - // std::cerr << e.first << " : " << e.second << std::endl; - } - set_ranks(replace); - } - if(!outer && prev_vals <= values.size()) break; - prev_vals = values.size(); + } + std::vector> bounds( + parent._pointvars.size(), + std::make_pair(-std::numeric_limits::infinity(), + std::numeric_limits::infinity())); + for (const auto &n : nodes) { + outer |= n->check_tiles(n.get(), nodes, bounds, val, best, worst, + minimization, parent._statevars.size() + 1); + } + std::set> values; + { + auto nodes = std::vector(parent._actions.size()); + action_nodes(nodes, 0, parent._actions.size() - 1, + parent._statevars.size()); + for (auto &n : nodes) { + if (n == nullptr) + continue; + get_ranks(values, n.get()); + } + std::unordered_map replace; + for (auto &e : values) { + if (replace.count(e.first) > 0) + continue; + double id = replace.size(); + replace[e.first] = id; + // std::cerr << e.first << " : " << e.second << std::endl; } + set_ranks(replace); + } + if (!outer && prev_vals <= values.size()) + break; + prev_vals = values.size(); } + } } -void SimpleTree::node_t::set_ranks(std::unordered_map& values) { - if(is_leaf()) - { - if(std::isinf(_cost) || std::isnan(_cost)) return; - _cost = values[_cost]; - } - else - { - if(_low) _low->set_ranks(values); - if(_high) _high->set_ranks(values); - } +void SimpleTree::node_t::set_ranks(std::unordered_map &values) { + if (is_leaf()) { + if (std::isinf(_cost) || std::isnan(_cost)) + return; + _cost = values[_cost]; + } else { + if (_low) + _low->set_ranks(values); + if (_high) + _high->set_ranks(values); + } } +void SimpleTree::node_t::get_ranks( + std::set> &values, node_t *start) { + if (is_leaf()) { + if (std::isinf(_cost) || std::isnan(_cost)) + return; + values.emplace(_cost, start); + } else { + if (_low) + _low->get_ranks(values, start); + if (_high) + _high->get_ranks(values, start); + } +} -void SimpleTree::node_t::get_ranks(std::set >& values, node_t* start) -{ - if(is_leaf()) - { - if(std::isinf(_cost) || std::isnan(_cost)) return; - values.emplace(_cost, start); - } +bool SimpleTree::node_t::subsumes( + const std::vector> &bounds, + std::vector> &obounds, const double val, + const bool minimization, size_t offset, double &best, + std::pair &closest) const { + if (is_leaf()) { + // TODO: continue in other trees here, maybe combination is + // enough to prove subsumption! + if (_cost <= val) + closest.first = std::max(_cost, closest.first); + if (_cost >= val) + closest.second = std::min(_cost, closest.second); + if (minimization) + best = std::min(_cost, best); else + best = std::max(_cost, best); + if (minimization && _cost <= val) + return true; + if (!minimization && _cost >= val) + return true; + return false; + } else { + /*if(minimization && _maxcost <= val) { - if(_low) _low->get_ranks(values, start); - if(_high) _high->get_ranks(values, start); + best = std::min(_maxcost, best); + return true; } + else if(!minimization && _mincost >= val) + { + best = std::max(_mincost, best); + return true; + }*/ + assert(std::isinf(_cost)); + assert(_var >= offset); + assert(bounds[_var - offset].first <= bounds[_var - offset].second); + double org = _limit; + std::swap(obounds[_var - offset].second, org); + bool res = true; + if (bounds[_var - offset].first <= _limit && _low) + if (!_low->subsumes(bounds, obounds, val, minimization, offset, best, + closest)) + res = false; + std::swap(obounds[_var - offset].second, org); + std::swap(obounds[_var - offset].first, org); + if (bounds[_var - offset].second > _limit && _high) + if (!_high->subsumes(bounds, obounds, val, minimization, offset, best, + closest)) + res = false; + std::swap(obounds[_var - offset].first, org); + return res; + } } - -bool SimpleTree::node_t::subsumes(const std::vector >& bounds, std::vector >& obounds, const double val, const bool minimization, size_t offset, double& best, std::pair& closest) { - if(is_leaf()) - { - // TODO: continue in other trees here, maybe combination is - // enough to prove subsumption! - if(_cost <= val) closest.first = std::max(_cost, closest.first); - if(_cost >= val) closest.second = std::min(_cost, closest.second); - if(minimization) best = std::min(_cost, best); - else best = std::max(_cost, best); - if(minimization && _cost <= val) return true; - else if(!minimization && _cost >= val) return true; - return false; +bool SimpleTree::node_t::check_tiles( + node_t *start, std::vector &nodes, + std::vector> &bounds, double val, double best_val, + double worst_val, bool minimization, size_t offset) { + auto obounds = bounds; + if (is_leaf()) { + double best = + (minimization ? 1 : -1) * std::numeric_limits::infinity(); + _cost_bounds.first = -std::numeric_limits::infinity(); + _cost_bounds.second = std::numeric_limits::infinity(); + if (_cost == + (minimization ? 1 : -1) * std::numeric_limits::infinity()) + return false; + for (auto &n : nodes) { + if (n.get() == start || n == nullptr) + continue; + if (n->subsumes(bounds, obounds, _cost, minimization, offset, best, + _cost_bounds)) { + _cost = + (minimization ? 1 : -1) * std::numeric_limits::infinity(); + _cost_bounds.second = std::numeric_limits::infinity(); + return true; + } + assert(best <= best_val || minimization); // doing max + assert(best >= best_val || !minimization); // doing min } - else - { - /*if(minimization && _maxcost <= val) - { - best = std::min(_maxcost, best); - return true; - } - else if(!minimization && _mincost >= val) - { - best = std::max(_mincost, best); - return true; - }*/ - assert(std::isinf(_cost)); - assert(_var >= offset); - assert(bounds[_var-offset].first <= bounds[_var-offset].second); - double org = _limit; - std::swap(obounds[_var-offset].second, org); - bool res = true; - if(bounds[_var-offset].first <= _limit && _low) - if(!_low->subsumes(bounds, obounds, val, minimization, offset, best, closest)) - res = false; - std::swap(obounds[_var-offset].second, org); - std::swap(obounds[_var-offset].first, org); - if(bounds[_var-offset].second > _limit && _high) - if(!_high->subsumes(bounds, obounds, val, minimization, offset, best, closest)) - res = false; - std::swap(obounds[_var-offset].first, org); - return res; + assert(best <= best_val || minimization); // doing max + assert(best >= best_val || !minimization); // doing min + if ((minimization && best >= _cost) || (!minimization && best <= _cost)) { + // std::cerr << "BEST " << _cost << " MV " << minval << " BND " << + // _cost_bounds.first << ": " << _cost_bounds.second << std::endl; + _cost = best_val; + } else { + if (!std::isinf(_cost_bounds.first)) { + _cost = std::nextafter(_cost_bounds.first, + std::numeric_limits::infinity()); + if (!std::isinf(_cost_bounds.second) && + std::ceil(_cost) < _cost_bounds.second) + _cost = std::ceil(_cost); + } else if (!std::isinf(_cost_bounds.second)) { + _cost = std::nextafter(_cost_bounds.second, + -std::numeric_limits::infinity()); + } } -} + return false; + } -bool SimpleTree::node_t::check_tiles(node_t* start, std::vector >& nodes, std::vector >& bounds, double val, - double best_val, double worst_val, bool minimization, size_t offset) -{ - auto obounds = bounds; - if(is_leaf()) - { - double best = (minimization ? 1 : -1) * std::numeric_limits::infinity(); - _cost_bounds.first = -std::numeric_limits::infinity(); - _cost_bounds.second = std::numeric_limits::infinity(); - if(_cost == (minimization ? 1 : -1) * std::numeric_limits::infinity()) - return false; - for(auto& n : nodes) - { - if(n.get() == start || n == nullptr) continue; - if(n->subsumes(bounds, obounds, _cost, minimization, offset, best, _cost_bounds)) - { - _cost = (minimization ? 1 : -1) * std::numeric_limits::infinity(); - _cost_bounds.second = std::numeric_limits::infinity(); - return true; - } - assert(best <= best_val || minimization); // doing max - assert(best >= best_val || !minimization); // doing min - } - assert(best <= best_val || minimization); // doing max - assert(best >= best_val || !minimization); // doing min - if((minimization && best >= _cost) || - (!minimization && best <= _cost)) - { - //std::cerr << "BEST " << _cost << " MV " << minval << " BND " << _cost_bounds.first << ": " << _cost_bounds.second << std::endl; - _cost = best_val; - } - else - { - if(!std::isinf(_cost_bounds.first)) - { - _cost = std::nextafter(_cost_bounds.first, std::numeric_limits::infinity()); - if(!std::isinf(_cost_bounds.second) && std::ceil(_cost) < _cost_bounds.second) - _cost = std::ceil(_cost); - } - else if(!std::isinf(_cost_bounds.second)) - { - _cost = std::nextafter(_cost_bounds.second, -std::numeric_limits::infinity()); - } - } - return false; + if (!is_leaf()) { + double org = _limit; + auto &bnd = bounds[_var - offset]; + node_ptr switchnode = nullptr; + if (bnd.second <= _limit) { + switchnode = _low; } - - if(!is_leaf()) { - double org = _limit; - auto& bnd = bounds[_var- offset]; - std::shared_ptr switchnode = nullptr; - if(bnd.second <= _limit) - { - switchnode = _low; - } - if(bnd.first > _limit) - { - switchnode = _high; - } -/* std::cerr << "HANDLE " << std::endl; - print(std::cerr); - std::cerr << std::endl;*/ - bool res = false; - if(switchnode) - { - _var = switchnode->_var; - _limit = switchnode->_limit; - _cost = switchnode->_cost; - _low = switchnode->_low; - _high = switchnode->_high; - return check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); - } - else - { - std::swap(org, bnd.second); - if(_low) - res |= _low->check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); - std::swap(org, bnd.second); - std::swap(org, bnd.first); - if(_high) - res |= _high->check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); - std::swap(org, bnd.first); - } - _cost_bounds.first = std::max(_low->_cost_bounds.first, _high->_cost_bounds.first); - _cost_bounds.second = std::min(_low->_cost_bounds.second, _high->_cost_bounds.second); - // if both are leafs and can be merged. - if(_low->is_leaf() && _high->is_leaf() && _low->cost_intersect(*_high) && - std::isinf(_low->_cost) == std::isinf(_high->_cost)) - { - /*std::cerr << "[" << _low->_cost_bounds.first << ", " << _low->_cost_bounds.second << "]" << std::endl; - std::cerr << "[" << _high->_cost_bounds.first << ", " << _high->_cost_bounds.second << "]" << std::endl; - std::cerr << "[" << _cost_bounds.first << ", " << _cost_bounds.second << "]" << std::endl; - std::cerr << "FIXING " << std::endl; + if (bnd.first > _limit) { + switchnode = _high; + } + /* std::cerr << "HANDLE " << std::endl; print(std::cerr); std::cerr << std::endl;*/ - _cost = _low->midcost(*_high, best_val, worst_val); - //std::cerr << "NC " << _cost << std::endl; - _low = nullptr; - _high = nullptr; - _var = std::numeric_limits::max(); - _limit = std::numeric_limits::infinity(); - return true; - //std::cerr << "REMO 2" << std::endl; - } - - { - // check if one is leaf and sibiling has leaf on same side which - // can be merged: - double nc = 0; - std::shared_ptr node, other; - if(!_high->is_leaf() && - _high->_var == _var && - _high->_low->cost_intersect(*_low) && - _low->is_leaf() && - _high->_low->is_leaf() && - std::isinf(_low->_cost) == std::isinf(_high->_low->_cost)) - { - node = _high; - other = _high->_low; - nc = other->midcost(*_low, best_val, worst_val); - assert(!std::isnan(nc)); - } - else if( !_low->is_leaf() && - _low->_var == _var && - _low->_high->cost_intersect(*_high) && - _high->is_leaf() && - _low->_high->is_leaf() && - std::isinf(_high->_cost) == std::isinf(_low->_high->_cost)) - { - node = _low; - other = _low->_high; - nc = other->midcost(*_high, best_val, worst_val); - assert(!std::isnan(nc)); - } - - if(node) - { - _var = node->_var; - _limit = node->_limit; - _low = node->_low; - _high = node->_high; - other->_cost = nc; - // we could compute the values on the fly; but no time currently - check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); - return true; - } - - if(!_low->is_leaf() && !_high->is_leaf() && - _low->_var == _high->_var && _low->_var == _var && - _low->_high->is_leaf() && _high->_low->is_leaf() && - _low->_high->cost_intersect(*_high->_low) && - std::isinf(_low->_high->_cost) == std::isinf(_high->_low->_cost)) - { - assert(_high->_low->is_leaf()); - assert(_low->_high->is_leaf()); - _high->_low->_cost = _low->_high->midcost(*_high->_low, best_val, worst_val); - _var = _low->_var; - _limit = _low->_limit; - _low = _low->_low; - // we could compute the values on the fly; but no time currently - check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); - return true; - } - - } + bool res = false; + if (switchnode) { + _var = switchnode->_var; + _limit = switchnode->_limit; + _cost = switchnode->_cost; + _low = switchnode->_low; + _high = switchnode->_high; + return check_tiles(start, nodes, bounds, val, best_val, worst_val, + minimization, offset); + } else { + std::swap(org, bnd.second); + if (_low) + res |= _low->check_tiles(start, nodes, bounds, val, best_val, worst_val, + minimization, offset); + std::swap(org, bnd.second); + std::swap(org, bnd.first); + if (_high) + res |= _high->check_tiles(start, nodes, bounds, val, best_val, + worst_val, minimization, offset); + std::swap(org, bnd.first); + } + _cost_bounds.first = + std::max(_low->_cost_bounds.first, _high->_cost_bounds.first); + _cost_bounds.second = + std::min(_low->_cost_bounds.second, _high->_cost_bounds.second); + // if both are leafs and can be merged. + if (_low->is_leaf() && _high->is_leaf() && _low->cost_intersect(*_high) && + std::isinf(_low->_cost) == std::isinf(_high->_cost)) { + /*std::cerr << "[" << _low->_cost_bounds.first << ", " << + _low->_cost_bounds.second << "]" << std::endl; std::cerr << "[" << + _high->_cost_bounds.first << ", " << _high->_cost_bounds.second << "]" << + std::endl; std::cerr << "[" << _cost_bounds.first << ", " << + _cost_bounds.second << "]" << std::endl; std::cerr << "FIXING " << + std::endl; print(std::cerr); std::cerr << std::endl;*/ + _cost = _low->midcost(*_high, best_val, worst_val); + // std::cerr << "NC " << _cost << std::endl; + _low = nullptr; + _high = nullptr; + _var = std::numeric_limits::max(); + _limit = std::numeric_limits::infinity(); + return true; + // std::cerr << "REMO 2" << std::endl; } - return false; -} - -bool SimpleTree::node_t::cost_intersect(const node_t& other) const -{ - return _cost_bounds.first < other._cost_bounds.second && - other._cost_bounds.first < _cost_bounds.second; -} -double SimpleTree::node_t::midcost(const node_t& other, double minval, double maxval) const -{ - if(_cost == other._cost) return _cost; - assert(std::isinf(_cost) == std::isinf(other._cost)); - auto low = std::min(other._cost_bounds.second, _cost_bounds.second); - auto high = std::max(other._cost_bounds.first, _cost_bounds.first); - double c = std::ceil(low); - if(std::isinf(low)) - c = minval; - else if(std::isinf(high)) - c = maxval; - else { - c = std::round(low + high/2); - if(c <= low || c >= high) - c = (low + high)/2; + // check if one is leaf and sibiling has leaf on same side which + // can be merged: + double nc = 0; + node_ptr node, other; + if (!_high->is_leaf() && _high->_var == _var && + _high->_low->cost_intersect(*_low) && _low->is_leaf() && + _high->_low->is_leaf() && + std::isinf(_low->_cost) == std::isinf(_high->_low->_cost)) { + node = _high; + other = _high->_low; + nc = other->midcost(*_low, best_val, worst_val); + assert(!std::isnan(nc)); + } else if (!_low->is_leaf() && _low->_var == _var && + _low->_high->cost_intersect(*_high) && _high->is_leaf() && + _low->_high->is_leaf() && + std::isinf(_high->_cost) == std::isinf(_low->_high->_cost)) { + node = _low; + other = _low->_high; + nc = other->midcost(*_high, best_val, worst_val); + assert(!std::isnan(nc)); + } + + if (node) { + _var = node->_var; + _limit = node->_limit; + _low = node->_low; + _high = node->_high; + other->_cost = nc; + // we could compute the values on the fly; but no time currently + check_tiles(start, nodes, bounds, val, best_val, worst_val, + minimization, offset); + return true; + } + + if (!_low->is_leaf() && !_high->is_leaf() && _low->_var == _high->_var && + _low->_var == _var && _low->_high->is_leaf() && + _high->_low->is_leaf() && _low->_high->cost_intersect(*_high->_low) && + std::isinf(_low->_high->_cost) == std::isinf(_high->_low->_cost)) { + assert(_high->_low->is_leaf()); + assert(_low->_high->is_leaf()); + _high->_low->_cost = + _low->_high->midcost(*_high->_low, best_val, worst_val); + _var = _low->_var; + _limit = _low->_limit; + _low = _low->_low; + // we could compute the values on the fly; but no time currently + check_tiles(start, nodes, bounds, val, best_val, worst_val, + minimization, offset); + return true; + } } - return c; + } + return false; } +bool SimpleTree::node_t::cost_intersect(const node_t &other) const { + return _cost_bounds.first < other._cost_bounds.second && + other._cost_bounds.first < _cost_bounds.second; +} +double SimpleTree::node_t::midcost(const node_t &other, double minval, + double maxval) const { + if (_cost == other._cost) + return _cost; + assert(std::isinf(_cost) == std::isinf(other._cost)); + auto low = std::min(other._cost_bounds.second, _cost_bounds.second); + auto high = std::max(other._cost_bounds.first, _cost_bounds.first); + double c = std::ceil(low); + if (std::isinf(low)) + c = minval; + else if (std::isinf(high)) + c = maxval; + else { + c = std::round(low + high / 2); + if (c <= low || c >= high) + c = (low + high) / 2; + } + return c; +} +void SimpleTree::node_t::insert(std::vector &key, json &tree, + size_t action, SimpleTree &parent, + size_t prefix, bool minimize, double accuracy, + std::vector &exactness) { + if ((tree.is_number() || tree.is_string()) && key.size() < prefix) { + if (is_leaf()) { + auto nc = std::numeric_limits::infinity(); + if (!minimize) + nc *= -1; + if (tree.is_number()) + nc = tree.get(); + if (minimize) + _cost = std::isinf(_cost) ? nc : std::min(nc, _cost); + else + _cost = std::isinf(_cost) ? nc : std::max(nc, _cost); + if (accuracy != 0) + _cost = std::round(_cost / accuracy) * accuracy; + } else { + assert(false); + _low->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + } + consistent(key.size() + 1); + return; + } else if (is_leaf()) { + if (std::isinf(_limit)) { + // assert(std::isinf(_cost)); + assert(_var == std::numeric_limits::max()); + _low = std::make_shared(); + _high = std::make_shared(); + _low->_cost = _high->_cost = + (minimize ? 1 : -1) * std::numeric_limits::infinity(); + if (!std::isinf(_cost)) { + std::swap(_low->_cost, _cost); + _high->_cost = _low->_cost; + } -void SimpleTree::node_t::insert(std::vector& key, json& tree, size_t action, SimpleTree& parent, size_t prefix, bool minimize, double accuracy, std::vector& exactness) -{ - if((tree.is_number() || tree.is_string()) && key.size() < prefix) - { - if(is_leaf()) - { - auto nc = std::numeric_limits::infinity(); - if(!minimize) - nc *= -1; - if(tree.is_number()) - nc = tree.get(); - if(minimize) - _cost = std::isinf(_cost) ? nc : std::min(nc, _cost); - else - _cost = std::isinf(_cost) ? nc : std::max(nc, _cost); - if(accuracy != 0) - _cost = std::round(_cost/accuracy)*accuracy; - } - else - { - assert(false); - _low->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - } + _low->_parent = _high->_parent = this; + if (prefix == key.size()) { + _var = prefix; + _limit = action; + } else if (prefix < key.size()) { + _var = prefix; + _limit = key[prefix]; + } else { + const auto &tree_var = tree["var"]; + _var = key.size() + 1 + tree_var.get(); + auto ivar = tree_var.get(); + _limit = tree["bound"].get(); + if (exactness.size() > ivar && exactness[ivar] != 0) + _limit = std::round(_limit / exactness[ivar]) * exactness[ivar]; + } + consistent(key.size() + 1); + if (prefix <= key.size()) + _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, + exactness); + else { + _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, + accuracy, exactness); + _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, + accuracy, exactness); consistent(key.size() + 1); - return; + } + } else { + assert(!std::isinf(_cost)); + assert(_var != std::numeric_limits::max()); + assert(false); } - else if(is_leaf()) - { - if(std::isinf(_limit)) - { - //assert(std::isinf(_cost)); - assert(_var == std::numeric_limits::max()); - _low = std::make_shared(); - _high = std::make_shared(); - _low->_cost = _high->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); - if(!std::isinf(_cost)) - { - std::swap(_low->_cost, _cost); - _high->_cost = _low->_cost; - } + consistent(key.size() + 1); + return; + } - _low->_parent = _high->_parent = this; - if(prefix == key.size()) - { - _var = prefix; - _limit = action; - } - else if(prefix < key.size()) - { - _var = prefix; - _limit = key[prefix]; - } - else - { - _var = key.size() + 1 + tree["var"].get(); - auto ivar = tree["var"].get(); - _limit = tree["bound"].get(); - if(exactness.size() > ivar && exactness[ivar] != 0) - _limit = std::round(_limit/exactness[ivar])*exactness[ivar]; - } - consistent(key.size() + 1); - if(prefix <= key.size()) - _low->insert(key, tree, action, parent, prefix+1, minimize, accuracy, exactness); - else - { - _low->insert(key, tree["low"], action, parent, prefix+1, minimize, accuracy, exactness); - _high->insert(key, tree["high"], action, parent, prefix+1, minimize, accuracy, exactness); - consistent(key.size() + 1); - } - } - else - { - assert(!std::isinf(_cost)); - assert(_var != std::numeric_limits::max()); - assert(false); + if (prefix > key.size()) { + + if (size_t var = tree["var"].get() + 1 + key.size(); + var != _var) { + //_parent->replace(this, key, tree, action, parent, prefix); + assert(false); + } else { + size_t ivar = tree["var"].get(); + double bound = tree["bound"].get(); + if (exactness.size() > ivar && exactness[ivar] != 0) { + bound = std::round(bound / exactness[ivar]) * exactness[ivar]; + } + if (_limit == bound) { + _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, + accuracy, exactness); + _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, + accuracy, exactness); + } else { + if (_limit > bound) { + _low->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, + accuracy, exactness); + } else { + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, + accuracy, exactness); } - consistent(key.size() + 1); - return; + } } - - if(prefix > key.size()) - { - size_t var = tree["var"].get() + 1 + key.size(); - if(_var != var) - { - //_parent->replace(this, key, tree, action, parent, prefix); - assert(false); - } - else - { - size_t ivar = tree["var"].get(); - double bound = tree["bound"].get(); - if(exactness.size() > ivar && exactness[ivar] != 0) - { - bound = std::round(bound/exactness[ivar])*exactness[ivar]; - } - if(_limit == bound) - { - _low->insert(key, tree["low"], action, parent, prefix+1, minimize, accuracy, exactness); - _high->insert(key, tree["high"], action, parent, prefix+1, minimize, accuracy, exactness); - } - else - { - if(_limit > bound) - { - _low->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, accuracy, exactness); - } - else - { - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, accuracy, exactness); - } - } - } + consistent(key.size() + 1); + } else if (prefix == key.size()) { + if (_var != prefix) { + assert(false); + } else { + consistent(key.size() + 1); + if (_limit == action) { + _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, + exactness); consistent(key.size() + 1); - } - else if(prefix == key.size()) - { - if(_var != prefix) - { - assert(false); - } - else - { - consistent(key.size() + 1); - if(_limit == action) - { - _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, exactness); - consistent(key.size() + 1); - } + } else { + assert(_low->_var > prefix); + if (_limit > action) { + auto tmp = std::make_shared(); + tmp->_low = std::make_shared(); + tmp->_cost = tmp->_low->_cost = + (minimize ? 1 : -1) * std::numeric_limits::infinity(); + tmp->_high = shared_from_this(); + tmp->_limit = action; + tmp->_var = prefix; + if (_parent) { + tmp->_parent = _parent; + if (_parent->_high.get() == this) + _parent->_high = tmp; else - { - assert(_low->_var > prefix); - if(_limit > action) - { - auto tmp = std::make_shared(); - tmp->_low = std::make_shared(); - tmp->_cost = tmp->_low->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); - tmp->_high = shared_from_this(); - tmp->_limit = action; - tmp->_var = prefix; - if(_parent) - { - tmp->_parent = _parent; - if(_parent->_high.get() == this) - _parent->_high = tmp; - else - _parent->_low = tmp; - } - else - { - parent._root = tmp; - } - tmp->_low->_parent = tmp->_high->_parent = tmp.get(); - tmp->consistent(key.size() + 1); - tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - } - else if(_high->_var == prefix && _high->_limit < action) - { - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - consistent(key.size() + 1); - } - else if(_high->_var > prefix) - { - assert(std::isinf(_high->_cost)); - _high->_low = std::make_shared(); - _high->_high = std::make_shared(); - _high->_low->_cost = _high->_high->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); - _high->_limit = action; - _high->_var = prefix; - _high->_low->_parent = _high.get(); - _high->_high->_parent = _high.get(); - _high->_parent = this; - _high->consistent(key.size() + 1); - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - consistent(key.size() + 1); - } - else if(_high->_var == prefix && _high->_limit > action) - { - auto tmp = std::make_shared(); - tmp->_low = std::make_shared(); - tmp->_low->_cost = tmp->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); - tmp->_parent = this; - tmp->_high = _high; - _high = tmp; - tmp->_low->_parent = tmp.get(); - tmp->_high->_parent = tmp.get(); - tmp->_var = prefix; - tmp->_limit = action; - tmp->consistent(key.size() + 1); - tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - consistent(key.size() + 1); - } - else if(_high->_var == prefix && _high->_limit == action) - { - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - } - else - { - assert(false); - } - } + _parent->_low = tmp; + } else { + parent._root = tmp; + } + tmp->_low->_parent = tmp->_high->_parent = tmp.get(); + tmp->consistent(key.size() + 1); + tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + } else if (_high->_var == prefix && _high->_limit < action) { + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + consistent(key.size() + 1); + } else if (_high->_var > prefix) { + assert(std::isinf(_high->_cost)); + _high->_low = std::make_shared(); + _high->_high = std::make_shared(); + _high->_low->_cost = _high->_high->_cost = + (minimize ? 1 : -1) * std::numeric_limits::infinity(); + _high->_limit = action; + _high->_var = prefix; + _high->_low->_parent = _high.get(); + _high->_high->_parent = _high.get(); + _high->_parent = this; + _high->consistent(key.size() + 1); + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + consistent(key.size() + 1); + } else if (_high->_var == prefix && _high->_limit > action) { + auto tmp = std::make_shared(); + tmp->_low = std::make_shared(); + tmp->_low->_cost = tmp->_cost = + (minimize ? 1 : -1) * std::numeric_limits::infinity(); + tmp->_parent = this; + tmp->_high = _high; + _high = tmp; + tmp->_low->_parent = tmp.get(); + tmp->_high->_parent = tmp.get(); + tmp->_var = prefix; + tmp->_limit = action; + tmp->consistent(key.size() + 1); + tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + consistent(key.size() + 1); + } else if (_high->_var == prefix && _high->_limit == action) { + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + } else { + assert(false); } - consistent(key.size() + 1); + } } - else if(prefix < key.size()) - { - if(_var != prefix) - { - //_parent->replace(this, key, tree, action, parent, prefix); - assert(false); - } - else - { - if(_limit == key[prefix]) - { - _low->insert(key, tree, action, parent, prefix + (_low->_var != _var ? 1 : 0), minimize, accuracy, exactness); - } - else - { - const auto b = _limit < key[prefix]; - auto branch = (*this)[b]; - if(branch->_var != prefix) - { - // we need to inject a node here - auto next = std::make_shared(); - (*next)[false] = std::make_shared(); - (*next)[false]->_cost = next->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); - next->_parent = this; - next->_var = prefix; - next->_limit = key[prefix]; - // example - // original [0,10] | [11,20] made for element 10, - // we inject element 7, so we now get - // ([0-7] | [8-10] ) | [11,20 - // where the original low is moved to the high - // of the newly created node - (*next)[true] = branch; - (*this)[b] = next; - (*next)[true]->_parent = next.get(); - (*next)[false]->_parent = next.get(); - next->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - } - else branch->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); - } - } - consistent(key.size() + 1); + consistent(key.size() + 1); + } else if (prefix < key.size()) { + if (_var != prefix) { + //_parent->replace(this, key, tree, action, parent, prefix); + assert(false); + } else { + if (_limit == key[prefix]) { + _low->insert(key, tree, action, parent, + prefix + (_low->_var != _var ? 1 : 0), minimize, accuracy, + exactness); + } else { + const auto b = _limit < key[prefix]; + auto branch = (*this)[b]; + if (branch->_var != prefix) { + // we need to inject a node here + auto next = std::make_shared(); + (*next)[false] = std::make_shared(); + (*next)[false]->_cost = next->_cost = + (minimize ? 1 : -1) * std::numeric_limits::infinity(); + next->_parent = this; + next->_var = prefix; + next->_limit = key[prefix]; + // example + // original [0,10] | [11,20] made for element 10, + // we inject element 7, so we now get + // ([0-7] | [8-10] ) | [11,20 + // where the original low is moved to the high + // of the newly created node + (*next)[true] = branch; + (*this)[b] = next; + (*next)[true]->_parent = next.get(); + (*next)[false]->_parent = next.get(); + next->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + } else + branch->insert(key, tree, action, parent, prefix, minimize, accuracy, + exactness); + } } consistent(key.size() + 1); + } + consistent(key.size() + 1); } -void SimpleTree::node_t::consistent(size_t prefix) const -{ - return; +void SimpleTree::node_t::consistent(size_t prefix) const { + return; #ifndef NDEBUG - assert(_parent != this); - assert(_parent == nullptr || _parent->_low.get() == this || _parent->_high.get() == this); - assert(_low == nullptr || _low->_parent == this); - assert(_high == nullptr || _high->_parent == this); - if(_var == prefix-1 && _parent && _parent->_var == _var) - { - assert(_parent->_limit < _limit); - } - if(_var < prefix) assert(_parent == nullptr || _parent->_var <= _var); - if(_low) _low->consistent(prefix); - if(_high) _high->consistent(prefix); + assert(_parent != this); + assert(_parent == nullptr || _parent->_low.get() == this || + _parent->_high.get() == this); + assert(_low == nullptr || _low->_parent == this); + assert(_high == nullptr || _high->_parent == this); + if (_var == prefix - 1 && _parent && _parent->_var == _var) { + assert(_parent->_limit < _limit); + } + if (_var < prefix) + assert(_parent == nullptr || _parent->_var <= _var); + if (_low) + _low->consistent(prefix); + if (_high) + _high->consistent(prefix); #endif } - -double SimpleTree::value(const double* disc, const double* cont, uint32_t action) const { - if(_root) - return _root->value(disc, cont, action, _statevars.size()); - return std::numeric_limits::infinity(); +double SimpleTree::value(const double *disc, const double *cont, + uint32_t action) const { + if (_root) + return _root->value(disc, cont, action, _statevars.size()); + return std::numeric_limits::infinity(); } -double SimpleTree::node_t::value(const double* disc, const double* cont, uint32_t action, size_t ndisc) const { - if(is_leaf()) - return _cost; - if(_var < ndisc) - { - if(disc[_var] <= _limit) - return _low ? _low->value(disc, cont, action, ndisc) : _cost; - else - return _high ? _high->value(disc, cont, action, ndisc) : _cost; - } - else if(_var == ndisc) - { - if(action <= _limit) - return _low ? _low->value(disc, cont, action, ndisc) : _cost; - else - return _high ? _high->value(disc, cont, action, ndisc) : _cost; - } - else - { - if(cont[_var - (ndisc+1)] <= _limit) - return _low ? _low->value(disc, cont, action, ndisc) : _cost; - else - return _high ? _high->value(disc, cont, action, ndisc) : _cost; - } +double SimpleTree::node_t::value(const double *disc, const double *cont, + uint32_t action, size_t ndisc) const { + if (is_leaf()) + return _cost; + if (_var < ndisc) { + if (disc[_var] <= _limit) + return _low ? _low->value(disc, cont, action, ndisc) : _cost; + return _high ? _high->value(disc, cont, action, ndisc) : _cost; + } + if (_var == ndisc) { + if (action <= _limit) + return _low ? _low->value(disc, cont, action, ndisc) : _cost; + return _high ? _high->value(disc, cont, action, ndisc) : _cost; + } + if (cont[_var - (ndisc + 1)] <= _limit) + return _low ? _low->value(disc, cont, action, ndisc) : _cost; + return _high ? _high->value(disc, cont, action, ndisc) : _cost; } +SimpleTree::node_ptr SimpleTree::node_t::simplify(bool make_dd, + nodemap_t &nodemap, + SimpleTree &parent) { + if (_low) + _low = _low->simplify(make_dd, nodemap, parent); + if (_high) + _high = _high->simplify(make_dd, nodemap, parent); + if (_low) + _low->_parent = this; + if (_high) + _high->_parent = this; + if (_var < parent._statevars.size()) { + if (_low && _low->is_leaf() && std::isinf(_low->_cost)) + return _high; + if (_high && _high->is_leaf() && std::isinf(_high->_cost)) + return _low; + } - -std::shared_ptr SimpleTree::node_t::simplify(bool make_dd, nodemap_t& nodemap, SimpleTree& parent) { - if(_low) _low = _low->simplify(make_dd, nodemap, parent); - if(_high) _high = _high->simplify(make_dd, nodemap, parent); - if(_low) - _low->_parent = this; - if(_high) - _high->_parent = this; - if(_var < parent._statevars.size()) - { - if(_low && _low->is_leaf() && std::isinf(_low->_cost)) - return _high; - if(_high && _high->is_leaf() && std::isinf(_high->_cost)) - return _low; + assert(_low.get() != this); + assert(_high.get() != this); + if ((_low == nullptr || _low->is_leaf()) && + (_high == nullptr || _high->is_leaf())) { + auto lc = _low ? _low->_cost : _cost; + auto hc = _high ? _high->_cost : _cost; + if (lc == hc) { + _low = nullptr; + _high = nullptr; + _cost = lc; + _limit = std::numeric_limits::infinity(); + _var = std::numeric_limits::max(); + return shared_from_this(); } + } - assert(_low.get() != this); - assert(_high.get() != this); - if( (_low == nullptr || _low->is_leaf()) && - (_high == nullptr || _high->is_leaf())) - { - auto lc = _low ? _low->_cost : _cost; - auto hc = _high ? _high->_cost : _cost; - if(lc == hc) - { - _low = nullptr; - _high = nullptr; - _cost = lc; - _limit = std::numeric_limits::infinity(); - _var = std::numeric_limits::max(); - return shared_from_this(); - } - } + if (std::isinf(_limit) && (_low != nullptr || _high != nullptr)) { + assert(false); + return _low == nullptr ? _high : _low; + } - if(std::isinf(_limit) && (_low != nullptr || _high != nullptr)) - { - assert(false); - return _low == nullptr ? _high : _low; + // if comparison on same variable in on child and other child is leaf, then + // if the non-leaf child has a leaf-child in, we can merge. + { + if (_low && _low->is_leaf() && _high && + (!_high->is_leaf() || !std::isinf(_high->_cost)) && + _high->_var == _var && _high->_low && _high->_low->is_leaf() && + _high->_low->_cost == _low->_cost) { + return _high; } - - // if comparison on same variable in on child and other child is leaf, then - // if the non-leaf child has a leaf-child in, we can merge. - { - if(_low && _low->is_leaf() && - _high && (!_high->is_leaf() || !std::isinf(_high->_cost)) && - _high->_var == _var && _high->_low && _high->_low->is_leaf() && _high->_low->_cost == _low->_cost) - { - return _high; - } - if(_high && _high->is_leaf() && - _low && (!_low->is_leaf() || !std::isinf(_low->_cost)) && - _low->_var == _var && _low->_high && _low->_high->is_leaf() && _low->_high->_cost == _high->_cost) - { - return _low; - } + if (_high && _high->is_leaf() && _low && + (!_low->is_leaf() || !std::isinf(_low->_cost)) && _low->_var == _var && + _low->_high && _low->_high->is_leaf() && + _low->_high->_cost == _high->_cost) { + return _low; } - - + } - /*if(make_dd) - { - if(_high == nullptr) - return _low; - if(_low == nullptr) - return _high; - if(_low && _parent && !_low->is_leaf() && _high->is_leaf()) - { - auto cost = _high ? _high->_cost : _cost; - auto lcost = _low->_high ? _low->_high->_cost : _low->_cost; - if(cost == lcost && _low->_var == _var) - return _low; - } - if(_high && _parent && !_high->is_leaf() && _low->is_leaf()) - { - auto cost = _low ? _low->_cost : _cost; - auto hcost = _high->_low ? _high->_low->_cost : _high->_cost; - if(cost == hcost && _high->_var == _var) - return _high; - } - }*/ - if(_high != nullptr && _high == _low) - { - return _high; - } - if(make_dd) - { - auto& ptr = nodemap[*this]; - if(ptr == nullptr) - ptr = shared_from_this(); - return ptr; - } - return shared_from_this(); -} - -SimpleTree::signature_t::signature_t(const SimpleTree::node_t& node) -{ - if(node.is_leaf()) - { - _limit = node._limit; - _low = node._low.get(); - _high = node._high.get(); - _var = node._var; - } - else - { - _var = 0; - _low = nullptr; - _high = nullptr; - _limit = node._cost; - } -} - -bool SimpleTree::node_t::is_leaf() const { - return _low == nullptr && _high == nullptr; + /*if(make_dd) + { + if(_high == nullptr) + return _low; + if(_low == nullptr) + return _high; + if(_low && _parent && !_low->is_leaf() && _high->is_leaf()) + { + auto cost = _high ? _high->_cost : _cost; + auto lcost = _low->_high ? _low->_high->_cost : _low->_cost; + if(cost == lcost && _low->_var == _var) + return _low; + } + if(_high && _parent && !_high->is_leaf() && _low->is_leaf()) + { + auto cost = _low ? _low->_cost : _cost; + auto hcost = _high->_low ? _high->_low->_cost : _high->_cost; + if(cost == hcost && _high->_var == _var) + return _high; + } + }*/ + if (_high != nullptr && _high == _low) { + return _high; + } + if (make_dd) { + auto &ptr = nodemap[*this]; + if (ptr == nullptr) + ptr = shared_from_this(); + return ptr; + } + return shared_from_this(); } -/*void SimpleTree::node_t::rec_insert(double value, std::vector>& handled, std::vector >& bounds, bool minimize) { - auto nid = _var; - assert(nid < bounds.size()); - bool reset_low = false; - bool reset_high = false; - bool best = false; - if( std::isnan(_cost) || - (minimize && value < _cost) || +/*void SimpleTree::node_t::rec_insert(double value, +std::vector>& handled, std::vector >& bounds, bool minimize) { auto nid = _var; assert(nid < +bounds.size()); bool reset_low = false; bool reset_high = false; bool best = +false; if( std::isnan(_cost) || (minimize && value < _cost) || (!minimize && value > _cost)) { _cost = value; @@ -1013,7 +937,7 @@ bool SimpleTree::node_t::is_leaf() const { if(bounds[_var].second == _limit && !handled[_var].second) reset_high = handled[_var].second = true; if(bounds[_var].first == _limit && !handled[_var].first) - reset_low = handled[_var].first = true; + reset_low = handled[_var].first = true; if(bounds[_var].second <= _limit) { if(handled[_var].first && handled[_var].second) @@ -1041,7 +965,8 @@ bool SimpleTree::node_t::is_leaf() const { _low = std::make_shared(); _low->_var = nid; _low->_parent = this; - _low->_limit = (!handled[nid].first ? bounds[nid].first : bounds[nid].second); + _low->_limit = (!handled[nid].first ? bounds[nid].first : +bounds[nid].second); } _low->rec_insert(value, handled, bounds, minimize); if(reset_low) handled[_var].first = false; @@ -1075,7 +1000,8 @@ bool SimpleTree::node_t::is_leaf() const { _high = std::make_shared(); _high->_parent = this; _high->_var = nid; - _high->_limit = (!handled[nid].first ? bounds[nid].first : bounds[nid].second); + _high->_limit = (!handled[nid].first ? bounds[nid].first : +bounds[nid].second); } _high->rec_insert(value, handled, bounds, minimize); if(reset_low) handled[_var].first = false; @@ -1083,11 +1009,9 @@ bool SimpleTree::node_t::is_leaf() const { } else { - if(best && handled[_var].first && handled[_var].second && _var == handled.size()-1) { - _low = nullptr; - _high = nullptr; - if(reset_low) handled[_var].first = false; - if(reset_high) handled[_var].second = false; + if(best && handled[_var].first && handled[_var].second && _var == +handled.size()-1) { _low = nullptr; _high = nullptr; if(reset_low) +handled[_var].first = false; if(reset_high) handled[_var].second = false; } if(is_leaf() && !best) { @@ -1099,14 +1023,16 @@ bool SimpleTree::node_t::is_leaf() const { _low = std::make_shared(); _low->_var = nid; _low->_parent = this; - _low->_limit = (!handled[nid].first ? bounds[nid].first : bounds[nid].second); + _low->_limit = (!handled[nid].first ? bounds[nid].first : +bounds[nid].second); } if(_high == nullptr) { _high = std::make_shared(); _high->_parent = this; _high->_var = nid; - _high->_limit = (!handled[nid].first ? bounds[nid].first : bounds[nid].second); + _high->_limit = (!handled[nid].first ? bounds[nid].first : +bounds[nid].second); } _low->rec_insert(value, handled, bounds, minimize); @@ -1116,269 +1042,259 @@ bool SimpleTree::node_t::is_leaf() const { if(reset_high) handled[_var].second = false; }*/ -std::ostream& SimpleTree::print(std::ostream& stream) const -{ - _root->print(stream, 0); - return stream; +std::ostream &SimpleTree::print(std::ostream &stream) const { + _root->print(stream, 0); + return stream; } -std::ostream& SimpleTree::node_t::print(std::ostream& out, size_t tabs) const { - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "{\"var\":" << _var << ",\"bound\":" << _limit; - if(!std::isnan(_cost)) - { - out << ",\"value\":" << _cost; - } - if(_low) - { - out << ",\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "\"low\":\n"; - _low->print(out, tabs + 1); - } - if(_high) - { - out << ",\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "\"high\":\n"; - _high->print(out, tabs + 1); - } - out << "\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "}"; - return out; +std::ostream &SimpleTree::node_t::print(std::ostream &out, size_t tabs) const { + for (size_t i = 0; i < tabs; ++i) + out << "\t"; + out << "{\"var\":" << _var << ",\"bound\":" << _limit; + if (!std::isnan(_cost)) { + out << ",\"value\":" << _cost; + } + if (_low) { + out << ",\n"; + for (size_t i = 0; i < tabs; ++i) + out << "\t"; + out << "\"low\":\n"; + _low->print(out, tabs + 1); + } + if (_high) { + out << ",\n"; + for (size_t i = 0; i < tabs; ++i) + out << "\t"; + out << "\"high\":\n"; + _high->print(out, tabs + 1); + } + out << "\n"; + for (size_t i = 0; i < tabs; ++i) + out << "\t"; + out << "}"; + return out; } -std::ostream& SimpleTree::print_c(std::ostream& stream, std::string name) const -{ - if(_root == nullptr) - { - stream << "double " << name << "(unsigned int action, const double* disc, const double* vars)\n{\n"; - stream << "\treturn 0 ; \n}\n"; - return stream; - } - std::unordered_map _idnode; - std::unordered_map _nodeid; - _idnode[0] = _root.get(); - _nodeid[_root.get()] = 0; - - size_t lid = 0; - while(lid != _nodeid.size()) - { - auto n = _idnode[lid]; - if(!n->is_leaf()) - { - auto ln = n->_low.get(); - auto hn = n->_high.get(); - if(_nodeid.count(ln) == 0) - { - auto id = _idnode.size(); - _idnode[id] = ln; - _nodeid[ln] = id; - } - if(_nodeid.count(hn) == 0) - { - auto id = _idnode.size(); - _idnode[id] = hn; - _nodeid[hn] = id; - } - } - ++lid; - } - - stream << "const int " << name << "_nodes[] = {"; - for(size_t i = 0; i < lid; ++i) - { - if(i != 0) stream << ","; - auto n = _idnode[i]; - if(n->is_leaf()) - { - stream << "-1,-1,-1"; - } - else - { - stream << _nodeid[n->_low.get()] << ","; - stream << _nodeid[n->_high.get()] << ","; - stream << n->_var; - } +std::ostream &SimpleTree::print_c(std::ostream &os, + const std::string &name) const { + if (_root == nullptr) { + os << "double " << name + << "(unsigned int action, const double* disc, const double* vars)\n{\n"; + os << "\treturn 0 ; \n}\n"; + return os; + } + auto _idnode = std::unordered_map{}; + auto _nodeid = std::unordered_map{}; + _idnode[0] = _root.get(); + _nodeid[_root.get()] = 0; + + size_t lid = 0; + while (lid != _nodeid.size()) { + auto n = _idnode[lid]; + if (!n->is_leaf()) { + auto ln = n->_low.get(); + auto hn = n->_high.get(); + if (_nodeid.count(ln) == 0) { + auto id = _idnode.size(); + _idnode[id] = ln; + _nodeid[ln] = id; + } + if (_nodeid.count(hn) == 0) { + auto id = _idnode.size(); + _idnode[id] = hn; + _nodeid[hn] = id; + } } - stream << "};\n"; - auto mm = _root->compute_min_max(); - auto v = is_minimization() ? mm.second + 1 : mm.first -1; - stream << "const double " << name << "_values[] = {"; - for(size_t i = 0; i < lid; ++i) - { - if(i != 0) stream << ","; - auto n = _idnode[i]; - if(n->is_leaf()) - { - if(!std::isinf(n->_cost) && !std::isnan(n->_cost)) - stream << n->_cost; - else - stream << v; - } - else - { - stream << n->_limit; - } + ++lid; + } + + os << "const int " << name << "_nodes[] = {"; + for (size_t i = 0; i < lid; ++i) { + if (i != 0) + os << ","; + auto n = _idnode[i]; + if (n->is_leaf()) { + os << "-1,-1,-1"; + } else { + os << _nodeid[n->_low.get()] << ","; + os << _nodeid[n->_high.get()] << ","; + os << n->_var; } - stream << "};\n"; - stream << "double " << name << "(unsigned int action, const double* disc, const double* vars)\n{\n"; - //stream << "\t// Depth = " << _root->depth() << std::endl; - stream << "\t// Actions = " << _actions.size() << std::endl; - stream << "\t// Disc = " << _statevars.size() << std::endl; - stream << "\t// Cont = " << _pointvars.size() << std::endl; - stream << "\t// Nodes = " << lid << std::endl; - /*std::unordered_set printed; - std::vector toprint; - if(_root) - _root->print_c_nested(stream, _statevars.size(), 1, toprint, _root); - auto mm = _root->compute_min_max(); - auto v = is_minimization() ? mm.second + 1 : mm.first -1; - stream << "\treturn " << v << ";\n"; - for(auto n : toprint) - { - n->print_c(stream, 0, printed, 1); - stream << "\treturn " << v << ";\n"; + } + os << "};\n"; + const auto [mmin, mmax] = _root->compute_min_max(); + auto v = is_minimization() ? mmax + 1 : mmin - 1; + os << "const double " << name << "_values[] = {"; + for (size_t i = 0; i < lid; ++i) { + if (i != 0) + os << ","; + auto n = _idnode[i]; + if (n->is_leaf()) { + if (!std::isinf(n->_cost) && !std::isnan(n->_cost)) + os << n->_cost; + else + os << v; + } else { + os << n->_limit; } - stream << "\treturn " << v << ";\n"; - * */ - stream << "\tint ins = 0;\n\twhile(true) {\n"; - stream << "\t\tint l = " << name << "_nodes[ins*3]; int h = " << name << "_nodes[1+(ins*3)]; int v = " << name << "_nodes[2+(ins*3)];\n"; - stream << "\t\tif(v == -1) return " << name << "_values[ins];\n"; - stream << "\t\tdouble val = 0;\n"; - stream << "\t\tif(v == " << _statevars.size() << ") val = action;\n"; - stream << "\t\telse if(v > " << _statevars.size() << ") val = vars[v-" << (_statevars.size()+1) << "];\n"; - stream << "\t\telse val = disc[v];\n"; - stream << "\t\tif(val <= " << name << "_values[ins])\n"; - stream << "\t\t\tins = l;\n"; - stream << "\t\telse\n"; - stream << "\t\t\tins = h;\n"; - stream << "\t}\n"; - stream << "\treturn " << v << ";\n"; - stream << "}\n"; - return stream; + } + os << "};\n"; + os << "double " << name + << "(unsigned int action, const double* disc, const double* vars)\n{\n"; + // stream << "\t// Depth = " << _root->depth() << std::endl; + os << "\t// Actions = " << _actions.size() << std::endl; + os << "\t// Disc = " << _statevars.size() << std::endl; + os << "\t// Cont = " << _pointvars.size() << std::endl; + os << "\t// Nodes = " << lid << std::endl; + /*std::unordered_set printed; + std::vector toprint; + if(_root) + _root->print_c_nested(stream, _statevars.size(), 1, toprint, _root); + auto mm = _root->compute_min_max(); + auto v = is_minimization() ? mm.second + 1 : mm.first -1; + stream << "\treturn " << v << ";\n"; + for(auto n : toprint) + { + n->print_c(stream, 0, printed, 1); + stream << "\treturn " << v << ";\n"; + } + stream << "\treturn " << v << ";\n"; + * */ + os << "\tint ins = 0;\n\twhile(true) {\n"; + os << "\t\tint l = " << name << "_nodes[ins*3]; int h = " << name + << "_nodes[1+(ins*3)]; int v = " << name << "_nodes[2+(ins*3)];\n"; + os << "\t\tif(v == -1) return " << name << "_values[ins];\n"; + os << "\t\tdouble val = 0;\n"; + os << "\t\tif(v == " << _statevars.size() << ") val = action;\n"; + os << "\t\telse if(v > " << _statevars.size() << ") val = vars[v-" + << (_statevars.size() + 1) << "];\n"; + os << "\t\telse val = disc[v];\n"; + os << "\t\tif(val <= " << name << "_values[ins])\n"; + os << "\t\t\tins = l;\n"; + os << "\t\telse\n"; + os << "\t\t\tins = h;\n"; + os << "\t}\n"; + os << "\treturn " << v << ";\n"; + os << "}\n"; + return os; } size_t SimpleTree::node_t::depth() const { - if(_low && _high == nullptr) - return 1 + _low->depth(); - if(_high && _low == nullptr) - return 1 + _high->depth(); - if(_low == nullptr && _high == nullptr) - return 0; - return 1 + std::max(_low->depth(), _high->depth()); + if (_low && _high == nullptr) + return 1 + _low->depth(); + if (_high && _low == nullptr) + return 1 + _high->depth(); + if (_low == nullptr && _high == nullptr) + return 0; + return 1 + std::max(_low->depth(), _high->depth()); } +std::ostream & +SimpleTree::node_t::print_c(std::ostream &os, size_t disc, + std::unordered_set &printed, + size_t tabs) const { -std::ostream& SimpleTree::node_t::print_c(std::ostream& stream, size_t disc, std::unordered_set& printed, size_t tabs) const { - - if(printed.count(this) > 0) return stream; - printed.insert(this); - //for(size_t i = 0; i < tabs; ++i) stream << "\t"; - stream << "l" << this << ":\n"; - std::vector toprint; - if(is_leaf()) - { + if (printed.count(this) > 0) + return os; + printed.insert(this); + // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + os << "l" << this << ":\n"; + std::vector toprint; + if (is_leaf()) { // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; - if(!std::isinf(_cost)) - stream << "return " << _cost << ";" << std::endl; - else stream << "{}"; - return stream; - } - if(std::isinf(_limit)) - { - if(_low) - return _low->print_c(stream, disc, printed, tabs); - else if(_high) - return _high->print_c(stream, disc, printed, tabs); - return stream; - } - //for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; - if(_var == disc) - { - stream << "if(action <= " << _limit << ") "; - } + if (!std::isinf(_cost)) + os << "return " << _cost << ";" << std::endl; else - { - auto type = disc > _var ? "disc" : "vars"; - auto vid = disc > _var ? _var : _var - (disc+1); - stream << "if(" << type << "[" << vid << "] <= " << _limit << ") "; - } - if(_low) - _low->print_c_nested(stream, disc, tabs + 1, toprint, _low); + os << "{}"; + return os; + } + if (std::isinf(_limit)) { + if (_low) + return _low->print_c(os, disc, printed, tabs); + if (_high) + return _high->print_c(os, disc, printed, tabs); + return os; + } + // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; + if (_var == disc) { + os << "if(action <= " << _limit << ") "; + } else { + auto type = disc > _var ? "disc" : "vars"; + auto vid = disc > _var ? _var : _var - (disc + 1); + os << "if(" << type << "[" << vid << "] <= " << _limit << ") "; + } + if (_low) + _low->print_c_nested(os, disc, tabs + 1, toprint, _low); + else { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; else - { - if(!std::isinf(_cost)) - stream << "return " << _cost << ";"; - else stream << "{}"; - } - stream << "\n"; -// for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; - stream << "else "; - if(_high) - _high->print_c_nested(stream, disc, tabs + 1, toprint, _high); + os << "{}"; + } + os << "\n"; + // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; + os << "else "; + if (_high) + _high->print_c_nested(os, disc, tabs + 1, toprint, _high); + else { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; else - { - if(!std::isinf(_cost)) - stream << "return " << _cost << ";"; - else stream << "{}"; - } - stream << "\n"; - for(auto n : toprint) - n->print_c(stream, disc, printed, tabs); - return stream; + os << "{}"; + } + os << "\n"; + for (auto n : toprint) + n->print_c(os, disc, printed, tabs); + return os; } -std::ostream& SimpleTree::node_t::print_c_nested(std::ostream& stream, size_t disc, size_t tabs, std::vector& toprint, const std::shared_ptr& node) const -{ - assert(node.get() == this); - if(is_leaf() && std::isinf(_limit)) - { - if(!std::isinf(_cost)) - stream << "return " << _cost << ";"; - else stream << "{}"; - return stream; +std::ostream & +SimpleTree::node_t::print_c_nested(std::ostream &os, size_t disc, size_t tabs, + std::vector &toprint, + const node_ptr &node) const { + assert(node.get() == this); + if (is_leaf() && std::isinf(_limit)) { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; + return os; + } + if (node.use_count() == 1) { + os << "{\n"; + // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + if (_var == disc) { + os << "if(action <= " << _limit << ") "; + } else { + auto type = disc > _var ? "disc" : "vars"; + auto vid = disc > _var ? _var : _var - (disc + 1); + os << "if(" << type << "[" << vid << "] <= " << _limit << ") "; } - if(node.use_count() == 1) - { - stream << "{\n"; -// for(size_t i = 0; i < tabs; ++i) stream << "\t"; - if(_var == disc) - { - stream << "if(action <= " << _limit << ") "; - } - else - { - auto type = disc > _var ? "disc" : "vars"; - auto vid = disc > _var ? _var : _var - (disc+1); - stream << "if(" << type << "[" << vid << "] <= " << _limit << ") "; - } - if(_low) - _low->print_c_nested(stream, disc, tabs + 1, toprint, _low); - else - { - if(!std::isinf(_cost)) - stream << "return " << _cost << ";"; - else stream << "{}"; - } - stream << "\n"; - //for(size_t i = 0; i < tabs; ++i) stream << "\t"; - stream << "else "; - if(_high) - _high->print_c_nested(stream, disc, tabs + 1, toprint, _high); - else - { - if(!std::isinf(_cost)) - stream << "return " << _cost << ";"; - else stream << "{}"; - } - stream << "\n"; - //for(size_t i = 0; i < tabs-1; ++i) stream << "\t"; - stream << "}"; - return stream; + if (_low) + _low->print_c_nested(os, disc, tabs + 1, toprint, _low); + else { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; + } + os << "\n"; + // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + os << "else "; + if (_high) + _high->print_c_nested(os, disc, tabs + 1, toprint, _high); + else { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; } - stream << "goto l" << this << ";"; - toprint.push_back(this); - return stream; + os << "\n"; + // for(size_t i = 0; i < tabs-1; ++i) stream << "\t"; + os << "}"; + return os; + } + os << "goto l" << this << ";"; + toprint.push_back(this); + return os; } diff --git a/src/SimpleTree.h b/src/SimpleTree.h index bd9d5c6..019bfa7 100644 --- a/src/SimpleTree.h +++ b/src/SimpleTree.h @@ -1,21 +1,21 @@ /* * Copyright (C) 2020 Peter G. Jensen - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -/* +/* * File: SimpleTree.h * Author: Peter G. Jensen * @@ -25,131 +25,149 @@ #ifndef SIMPLETREE_H #define SIMPLETREE_H -#include +#include +#include // includes iostream :-( + +#include #include -#include -#include #include - -#include -#include +#include +#include class SimpleTree { public: - SimpleTree(const SimpleTree& orig) = default; - virtual ~SimpleTree() = default; - static SimpleTree parse(std::istream&, bool simplify = false, bool subsumption = false, double accuracy = 0); - static SimpleTree parse(std::istream&, bool simplify, bool subsumption, double accuracy, std::vector& exactness); - std::ostream& print(std::ostream& stream) const; - std::ostream& print_c(std::ostream& stream, std::string name) const; - double value(const double* disc, const double* cont, uint32_t action) const; - bool is_minimization() const { return _is_minimization; } - const std::vector &actions() const; - const std::vector &discrete_features() const { return _statevars; } - const std::vector &continous_features() const { return _pointvars; } + SimpleTree(const SimpleTree &orig) = default; + static SimpleTree parse(std::istream &, bool simplify = false, + bool subsumption = false, double accuracy = 0); + static SimpleTree parse(std::istream &, bool simplify, bool subsumption, + double accuracy, std::vector &exactness); + std::ostream &print(std::ostream &os) const; + std::ostream &print_c(std::ostream &os, const std::string &name) const; + double value(const double *disc, const double *cont, uint32_t action) const; + bool is_minimization() const { return _is_minimization; } + const std::vector &actions() const; + const std::vector &discrete_features() const { + return _statevars; + } + const std::vector &continous_features() const { + return _pointvars; + } + private: - - using json = nlohmann::json; - struct node_t; - struct signature_t { - uint32_t _var; - double _limit; - node_t* _low; - node_t* _high; - signature_t(const SimpleTree::node_t&); - } __attribute__((packed)); - friend struct ptrie::byte_iterator; + struct node_t; + using node_ptr = std::shared_ptr; + struct signature_t { + uint32_t _var{0}; + double _limit{0}; + node_t *_low{nullptr}; + node_t *_high{nullptr}; + } __attribute__((packed)); + friend struct ptrie::byte_iterator; + + using nodemap_t = ptrie::map; + using json = nlohmann::json; + SimpleTree() = default; + + static std::vector parse_key(const std::string &key); - using nodemap_t = ptrie::map>; - SimpleTree() = default; - - static std::vector parse_key(const std::string& key); - - struct node_t : public std::enable_shared_from_this { - uint32_t _var = std::numeric_limits::max(); - double _limit = -std::numeric_limits::infinity(); - double _cost = std::numeric_limits::infinity(); - std::pair _cost_bounds; - std::shared_ptr _low; - std::shared_ptr _high; - node_t* _parent; - void insert(std::vector& key, json& tree, size_t action, SimpleTree& parent, size_t prefix, bool minimize, double accuracy, std::vector& exactness); - std::ostream& print(std::ostream& out, size_t tabs = 0) const; - bool is_leaf() const; - std::shared_ptr simplify(bool make_dd, nodemap_t& nodemap, SimpleTree& parent); - void subsumption_reduction(bool minimization, SimpleTree& parent); - void action_nodes(std::vector>& nodes, uint32_t low, uint32_t high, uint32_t varid); - std::pair compute_min_max(); - bool check_tiles(node_t* start, std::vector>& , std::vector>& bounds, double val, double minval, double maxval, bool minimization, size_t offset); - bool subsumes(const std::vector>& bounds, std::vector>& obounds, const double val, const bool minimization, size_t offset, double& best, std::pair& closest); - void get_ranks(std::set>& values, node_t* start); - void set_ranks(std::unordered_map& values); - std::ostream& print_c(std::ostream& stream, size_t disc, std::unordered_set& printed, size_t tabs = 0) const; - std::ostream& print_c_nested(std::ostream& stream, size_t disc, size_t tabs, std::vector& toprint, const std::shared_ptr& node) const; - size_t depth() const; - double value(const double* disc, const double* cont, uint32_t action, size_t ndisc) const; - void consistent(size_t) const; - bool cost_intersect(const node_t& other) const; - double midcost(const node_t& other, double minval, double maxval) const; - bool operator==(const node_t& other) const - { - if(_limit != other._limit) - return false; - if(_var != other._var) - return false; - if(_low != other._low) - return false; - if(_high != other._high) - return false; - return true; - } - bool operator<(const node_t& other) const - { - if(_limit != other._limit) - return _limit < other._limit; - if(_var != other._var) - return _var < other._var; - if(_low != other._low) - return _low < other._low; - return _high < other._high; - } - std::shared_ptr& operator[](bool b) { - return b ? _high : _low; - } - }; - - std::vector _actions; - std::vector _statevars; - std::vector _pointvars; - std::shared_ptr _root; - bool _is_minimization = true; + struct node_t : std::enable_shared_from_this { + uint32_t _var = std::numeric_limits::max(); + double _limit = -std::numeric_limits::infinity(); + double _cost = std::numeric_limits::infinity(); + std::pair _cost_bounds; + node_ptr _low; + node_ptr _high; + node_t *_parent{nullptr}; + void insert(std::vector &key, json &tree, size_t action, + SimpleTree &parent, size_t prefix, bool minimize, + double accuracy, std::vector &exactness); + std::ostream &print(std::ostream &out, size_t tabs = 0) const; + bool is_leaf() const { return _low == nullptr && _high == nullptr; } + node_ptr simplify(bool make_dd, nodemap_t &nodemap, SimpleTree &parent); + void subsumption_reduction(bool minimization, SimpleTree &parent); + void action_nodes(std::vector &nodes, uint32_t low, uint32_t high, + uint32_t varid); + std::pair compute_min_max() const; + bool check_tiles(node_t *start, std::vector &, + std::vector> &bounds, double val, + double minval, double maxval, bool minimization, + size_t offset); + bool subsumes(const std::vector> &bounds, + std::vector> &obounds, double val, + bool minimization, size_t offset, double &best, + std::pair &closest) const; + void get_ranks(std::set> &values, + node_t *start); + void set_ranks(std::unordered_map &values); + std::ostream &print_c(std::ostream &os, size_t disc, + std::unordered_set &printed, + size_t tabs = 0) const; + std::ostream &print_c_nested(std::ostream &os, size_t disc, size_t tabs, + std::vector &toprint, + const node_ptr &node) const; + size_t depth() const; + double value(const double *disc, const double *cont, uint32_t action, + size_t ndisc) const; + void consistent(size_t) const; + bool cost_intersect(const node_t &other) const; + double midcost(const node_t &other, double minval, double maxval) const; + bool operator==(const node_t &other) const { + if (_limit != other._limit) + return false; + if (_var != other._var) + return false; + if (_low != other._low) + return false; + if (_high != other._high) + return false; + return true; + } + bool operator<(const node_t &other) const { + if (_limit != other._limit) + return _limit < other._limit; + if (_var != other._var) + return _var < other._var; + if (_low != other._low) + return _low < other._low; + return _high < other._high; + } + node_ptr &operator[](bool b) { return b ? _high : _low; } + operator signature_t() const { ///< support implicit conversion for map keys + if (is_leaf()) + return signature_t{_var, _limit, _low.get(), _high.get()}; + return signature_t{0, _cost, nullptr, nullptr}; + } + }; + + std::vector _actions; + std::vector _statevars; + std::vector _pointvars; + node_ptr _root; + bool _is_minimization = true; }; -namespace ptrie{ - template<> - struct byte_iterator { - static uchar& access(SimpleTree::signature_t* data, size_t id) - { - return ((uchar*)data)[id]; - } - - static const uchar& const_access(const SimpleTree::signature_t* data, size_t id) - { - return ((const uchar*)data)[id]; - } - - static constexpr size_t element_size() - { - return sizeof(size_t)*2+sizeof(double)+sizeof(uint32_t); - } - - static constexpr bool continious() - { - return true; - } - - // add read_blob, write_blob - }; -} -#endif /* SIMPLETREE_H */ +template <> struct ptrie::byte_iterator { + static uchar &access(SimpleTree::signature_t *data, size_t id) { + return reinterpret_cast(data)[id]; + } + static const uchar &const_access(const SimpleTree::signature_t *data, + size_t id) { + return reinterpret_cast(data)[id]; + } + + static constexpr size_t element_size() { + constexpr auto member_size = sizeof(SimpleTree::signature_t::_var) + + sizeof(SimpleTree::signature_t::_limit) + + sizeof(SimpleTree::signature_t::_high) + + sizeof(SimpleTree::signature_t::_low); + static_assert(sizeof(SimpleTree::signature_t) == member_size, + "tightly packed struct"); + return sizeof(SimpleTree::signature_t); + } + + static constexpr bool continious() { return true; } + + // add read_blob, write_blob +}; +#endif /* SIMPLETREE_H */ diff --git a/src/ZonotopStrategy.cpp b/src/ZonotopStrategy.cpp index 2b8406e..0e2631c 100644 --- a/src/ZonotopStrategy.cpp +++ b/src/ZonotopStrategy.cpp @@ -1,496 +1,466 @@ /* * Copyright (C) 2020 Peter G. Jensen - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -/* +/* * File: ZonotopStrategy.cpp * Author: Peter G. Jensen - * + * * Created on December 13, 2018, 2:54 PM */ #include "ZonotopStrategy.h" +#include "errors.h" #include #include -#include #include #include - - using json = nlohmann::json; -std::vector ZonotopStrategy::get_bounds(const std::string& zonotop, size_t& vars) -{ - size_t i = 0; - size_t vid = 0; - std::vector bounds; - while(i < zonotop.size() && zonotop[i] != ')') - { - if(zonotop[i] == '[') - { - double lower = 0, upper = 0; - // new element - ++i; - std::string ok(zonotop.begin() + i, zonotop.end()); - std::istringstream ss(ok); - { - std::string num; - std::getline(ss, num, ','); - std::istringstream ns(num); - ns >> lower; - i += num.length() + 1; - } - while(i < zonotop.size() && zonotop[i] == ' ') ++i; - { - std::string num; - std::getline(ss, num, ']'); - std::istringstream ns(num); - ns >> upper; - i += num.length() + 1; - } - bounds.emplace_back(lower, upper); - } +/// For printing a number of tab ('\t') characters. +struct Tabs { + size_t count; + friend std::ostream &operator<<(std::ostream &os, const Tabs &tabs) { + for (auto i = 0u; i < tabs.count; ++i) + os << '\t'; + return os; + } +}; + +std::vector +ZonotopStrategy::get_bounds(const std::string &zonotop, size_t &vars) { + auto i = size_t{0}; + auto vid = size_t{0}; + auto bounds = std::vector{}; + while (i < zonotop.size() && zonotop[i] != ')') { + if (zonotop[i] == '[') { + double lower = 0, upper = 0; + // new element + ++i; + std::string ok(zonotop.begin() + i, zonotop.end()); + std::istringstream ss(ok); + { + std::string num; + std::getline(ss, num, ','); + std::istringstream ns(num); + ns >> lower; + i += num.length() + 1; + } + while (i < zonotop.size() && zonotop[i] == ' ') ++i; + { + std::string num; + std::getline(ss, num, ']'); + std::istringstream ns(num); + ns >> upper; + i += num.length() + 1; + } + bounds.emplace_back(lower, upper); } - if(vars == 0) - vars = vid; - if(vars != vid) - { - throw base_error("Dimensionality of controller differs"); - } - return bounds; + ++i; + } + if (vars == 0) + vars = vid; + if (vars != vid) { + throw base_error("Dimensionality of controller differs"); + } + return bounds; } -ZonotopStrategy ZonotopStrategy::parse(std::istream& input) -{ - auto raw = json::parse(input); - ZonotopStrategy strategy; - size_t vars = 0; - if(raw.is_object()) - { - for(auto it = raw.begin(); it != raw.end(); ++it) - { - auto bounds = get_bounds(it.key(), vars); - - if(it.value().is_array()) - { - for(auto e : it.value()) - { - std::vector pat = e.get>(); - if(pat.size() > 0) - { - auto res = strategy._states.insert((unsigned char*)pat.data(), pat.size()*sizeof(uint16_t)); - strategy._max_length = std::max(strategy._max_length, pat.size()); - if(strategy.add(bounds, res.second)) - { - //std::cerr << "COL " << it.key() << " : " << e << std::endl; - //exit(-1); - } - } - } +ZonotopStrategy ZonotopStrategy::parse(std::istream &input) { + auto raw = json::parse(input); + auto strategy = ZonotopStrategy{}; + auto vars = size_t{0}; + if (raw.is_object()) { + for (auto &[raw_key, raw_value] : raw.items()) { + auto bounds = get_bounds(raw_key, vars); + if (raw_value.is_array()) { + for (auto e : raw_value) { + auto pat = e.get>(); + if (pat.size() > 0) { + auto res = strategy._states.insert((unsigned char *)pat.data(), + pat.size() * sizeof(uint16_t)); + strategy._max_length = std::max(strategy._max_length, pat.size()); + if (strategy.add(bounds, res.second)) { + // std::cerr << "COL " << it.key() << " : " << e << std::endl; + // exit(-1); } - else - { - throw base_error("Input JSON not well formatted"); - } + } } + } else { + throw base_error( + "Input JSON not well formatted (array field expected)"); + } } - else - { - throw base_error("Input JSON not well formatted"); - } - return strategy; + } else { + throw base_error("Input JSON not well formatted (object expected)"); + } + return strategy; } -bool ZonotopStrategy::add(std::vector& bounds, size_t state) -{ - if(_root == nullptr) - { - _root = std::make_shared(); - _root->_varid = 0; - _root->_limit = bounds[0]._lower; - } - std::vector> handled(bounds.size()); - return rec_insert(_root.get(), bounds, handled, state); +bool ZonotopStrategy::add(std::vector &bounds, size_t state) { + if (_root == nullptr) { + _root = std::make_shared(); + _root->_varid = 0; + _root->_limit = bounds[0]._lower; + } + auto handled = std::vector>(bounds.size()); + return rec_insert(_root.get(), bounds, handled, state); } -bool ZonotopStrategy::rec_insert(node_t* node, std::vector& bounds, std::vector>& handled, size_t state) { - auto nid = node->_varid; - assert(nid < bounds.size()); - bool reset = false; - bool col = false; - if(bounds[node->_varid]._upper <= node->_limit) - { - if(bounds[node->_varid]._upper == node->_limit) - reset = handled[node->_varid].second = true; - if(handled[node->_varid].first && handled[node->_varid].second) - { - if(node->_varid == bounds.size()-1) - { - auto lb = std::lower_bound(node->_low_patterns.begin(), node->_low_patterns.end(), state); - if(lb != node->_low_patterns.end() && *lb == state) - { - col = true; - } - else - { - node->_low_patterns.insert(lb, state); - try_merge(node, state); - } - return col; - } - ++nid; - assert(nid < bounds.size()); +bool ZonotopStrategy::rec_insert(node_t *node, std::vector &bounds, + std::vector> &handled, + size_t state) { + auto nid = node->_varid; + assert(nid < bounds.size()); + auto reset = false; + auto col = false; + if (bounds[node->_varid]._upper <= node->_limit) { + if (bounds[node->_varid]._upper == node->_limit) + reset = handled[node->_varid].second = true; + if (handled[node->_varid].first && handled[node->_varid].second) { + if (node->_varid == bounds.size() - 1) { + auto lb = std::lower_bound(node->_low_patterns.begin(), + node->_low_patterns.end(), state); + if (lb != node->_low_patterns.end() && *lb == state) { + col = true; + } else { + node->_low_patterns.insert(lb, state); + try_merge(node, state); } + return col; + } + ++nid; + assert(nid < bounds.size()); + } - if(node->_low == nullptr) - { - node->_low = std::make_shared(); - node->_low->_varid = nid; - node->_low->_parent = node; - node->_low->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - col |= rec_insert(node->_low.get(), bounds, handled, state); - if(reset) handled[node->_varid].second = false; + if (node->_low == nullptr) { + node->_low = std::make_shared(); + node->_low->_varid = nid; + node->_low->_parent = node; + node->_low->_limit = + (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); } - else if(bounds[node->_varid]._lower >= node->_limit) - { - if(bounds[node->_varid]._lower == node->_limit) - reset = handled[node->_varid].first = true; - if(handled[node->_varid].first && handled[node->_varid].second) - { - if(node->_varid == bounds.size()-1) - { - auto lb = std::lower_bound(node->_high_patterns.begin(), node->_high_patterns.end(), state); - if(lb != node->_high_patterns.end() && *lb == state) - { - col = true; - } - else - { - node->_high_patterns.insert(lb, state); - try_merge(node, state); - } - return col; - } - ++nid; - assert(nid < bounds.size()); + col |= rec_insert(node->_low.get(), bounds, handled, state); + if (reset) + handled[node->_varid].second = false; + } else if (bounds[node->_varid]._lower >= node->_limit) { + if (bounds[node->_varid]._lower == node->_limit) + reset = handled[node->_varid].first = true; + if (handled[node->_varid].first && handled[node->_varid].second) { + if (node->_varid == bounds.size() - 1) { + auto lb = std::lower_bound(node->_high_patterns.begin(), + node->_high_patterns.end(), state); + if (lb != node->_high_patterns.end() && *lb == state) { + col = true; + } else { + node->_high_patterns.insert(lb, state); + try_merge(node, state); } - - if(node->_high == nullptr) - { - node->_high = std::make_shared(); - node->_high->_parent = node; - node->_high->_varid = nid; - node->_high->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - col |= rec_insert(node->_high.get(), bounds, handled, state); - if(reset) handled[node->_varid].first = false; + return col; + } + ++nid; + assert(nid < bounds.size()); } - else - { - if(node->_low == nullptr) - { - node->_low = std::make_shared(); - node->_low->_varid = nid; - node->_low->_parent = node; - node->_low->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - if(node->_high == nullptr) - { - node->_high = std::make_shared(); - node->_high->_parent = node; - node->_high->_varid = nid; - node->_high->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - col |= rec_insert(node->_low.get(), bounds, handled, state); - col |= rec_insert(node->_high.get(), bounds, handled, state); + if (node->_high == nullptr) { + node->_high = std::make_shared(); + node->_high->_parent = node; + node->_high->_varid = nid; + node->_high->_limit = + (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); } - return col; + col |= rec_insert(node->_high.get(), bounds, handled, state); + if (reset) + handled[node->_varid].first = false; + } else { + if (node->_low == nullptr) { + node->_low = std::make_shared(); + node->_low->_varid = nid; + node->_low->_parent = node; + node->_low->_limit = + (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); + } + if (node->_high == nullptr) { + node->_high = std::make_shared(); + node->_high->_parent = node; + node->_high->_varid = nid; + node->_high->_limit = + (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); + } + + col |= rec_insert(node->_low.get(), bounds, handled, state); + col |= rec_insert(node->_high.get(), bounds, handled, state); + } + return col; } -void ZonotopStrategy::try_merge(node_t* node, size_t state) { - if(node == nullptr) return; - auto hlb = std::lower_bound(node->_high_patterns.begin(), node->_high_patterns.end(), state); - if(hlb != std::end(node->_high_patterns) && *hlb == state) - { - auto llb = std::lower_bound(node->_low_patterns.begin(), node->_low_patterns.end(), state); - if(llb != std::end(node->_low_patterns) && *llb == state) - { - node->_high_patterns.erase(hlb); - node->_low_patterns.erase(llb); - bool empty = (node->_high == nullptr && node->_low == nullptr && node->_high_patterns.empty() && node->_low_patterns.empty()); - auto parent = node->_parent; - if(node->_parent->_low.get() == node) - { - parent->_low_patterns.push_back(state); - if(empty) parent->_low = nullptr; - } - else - { - parent->_high_patterns.push_back(state); - if(empty) parent->_high = nullptr; - } - try_merge(parent, state); - } +void ZonotopStrategy::try_merge(node_t *node, size_t state) { + if (node == nullptr) + return; + auto hlb = std::lower_bound(node->_high_patterns.begin(), + node->_high_patterns.end(), state); + if (hlb != std::end(node->_high_patterns) && *hlb == state) { + auto llb = std::lower_bound(node->_low_patterns.begin(), + node->_low_patterns.end(), state); + if (llb != std::end(node->_low_patterns) && *llb == state) { + node->_high_patterns.erase(hlb); + node->_low_patterns.erase(llb); + bool empty = + (node->_high == nullptr && node->_low == nullptr && + node->_high_patterns.empty() && node->_low_patterns.empty()); + auto parent = node->_parent; + if (node->_parent->_low.get() == node) { + parent->_low_patterns.push_back(state); + if (empty) + parent->_low = nullptr; + } else { + parent->_high_patterns.push_back(state); + if (empty) + parent->_high = nullptr; + } + try_merge(parent, state); } + } } - -std::ostream& ZonotopStrategy::node_t::print(std::ostream& out, const ZonotopStrategy* parent, size_t tabs) const { - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "{\"var\":" << _varid << ",\"bound\":" << _limit; - auto buffer = std::make_unique(parent->_max_length); - if(!_low_patterns.empty()) - { - out << ",\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "\"low_patterns\":["; - bool fp = true; - for(auto p : _low_patterns) - { - if(!fp) - out << ","; - out << "["; - /*size_t length = parent->_states.unpack(p, (unsigned char*)buffer.get()); - bool fe = true; - for(size_t i = 0; i < length/sizeof(uint16_t); ++i) - { - if(!fe) - out << ","; - fe = false; - out << buffer[i]; - }*/ - out << p; - out << "]"; - } - out << "]"; - } - if(!_high_patterns.empty()) - { - out << ",\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "\"high_patterns\":["; - bool fp = true; - for(auto p : _high_patterns) - { - if(!fp) - out << ","; - out << "["; - size_t length = const_cast(parent)->_states.unpack(p, (unsigned char*)buffer.get()); - bool fe = true; - for(size_t i = 0; i < length/sizeof(uint16_t); ++i) - { - if(!fe) - out << ","; - fe = false; - out << buffer[i]; - } - out << "]"; - } - out << "]"; +std::ostream &ZonotopStrategy::node_t::print(std::ostream &os, + const ZonotopStrategy *parent, + size_t tabs) const { + os << Tabs{tabs}; + os << "{\"var\":" << _varid << ",\"bound\":" << _limit; + auto buffer = std::make_unique(parent->_max_length); + if (!_low_patterns.empty()) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"low_patterns\":["; + bool fp = true; + for (auto p : _low_patterns) { + if (!fp) + os << ","; + os << "["; + /*size_t length = parent->_states.unpack(p, (unsigned char*)buffer.get()); + bool fe = true; + for(size_t i = 0; i < length/sizeof(uint16_t); ++i) + { + if(!fe) + os << ","; + fe = false; + os << buffer[i]; + }*/ + os << p; + os << "]"; } - if(_low) - { - out << ",\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "\"low\":\n"; - _low->print(out, parent, tabs + 1); + os << "]"; + } + if (!_high_patterns.empty()) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"high_patterns\":["; + bool fp = true; + for (auto p : _high_patterns) { + if (!fp) + os << ","; + os << "["; + size_t length = const_cast(parent)->_states.unpack( + p, (unsigned char *)buffer.get()); + bool fe = true; + for (size_t i = 0; i < length / sizeof(uint16_t); ++i) { + if (!fe) + os << ","; + fe = false; + os << buffer[i]; + } + os << "]"; } - if(_high) - { - out << ",\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "\"high\":\n"; - _high->print(out, parent, tabs + 1); - } - out << "\n"; - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "}"; - return out; + os << "]"; + } + if (_low) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"low\":\n"; + _low->print(os, parent, tabs + 1); + } + if (_high) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"high\":\n"; + _high->print(os, parent, tabs + 1); + } + os << "\n"; + os << Tabs{tabs}; + os << "}"; + return os; } -void ZonotopStrategy::active(const double* sampel, bool* write) const { - //std::cerr << "LOOKUP [" << sampel[0] << ", " << sampel[1] << "]" << std::endl; - memset(write, 0, sizeof(bool)*_states.size()); - if(_root == nullptr) - return; - auto current = _root.get(); - int active = 0; -// _root->print(std::cerr, (ZonotopStrategy*)this); - while(current) - { - if(sampel[current->_varid] >= current->_limit) - { - for(auto p : current->_high_patterns) - { - write[p] = true; - //std::cerr << "ACTIVE " << p << std::endl; - } - active += current->_high_patterns.size(); - } - if(sampel[current->_varid] <= current->_limit) - { - for(auto p : current->_low_patterns) - { - write[p] = true; - //std::cerr << "ACTIVE " << p << std::endl; - } - active += current->_low_patterns.size(); - } - if(sampel[current->_varid] <= current->_limit && current->_low) - { - current = current->_low.get(); - continue; - } - if(sampel[current->_varid] >= current->_limit && current->_high) - { - current = current->_high.get(); - continue; - } - break; +void ZonotopStrategy::active(const double *sampel, bool *write) const { + // std::cerr << "LOOKUP [" << sampel[0] << ", " << sampel[1] << "]" << + // std::endl; + memset(write, 0, sizeof(bool) * _states.size()); + if (_root == nullptr) + return; + auto current = _root.get(); + int active = 0; + // _root->print(std::cerr, (ZonotopStrategy*)this); + while (current) { + if (sampel[current->_varid] >= current->_limit) { + for (auto p : current->_high_patterns) { + write[p] = true; + // std::cerr << "ACTIVE " << p << std::endl; + } + active += current->_high_patterns.size(); } - if(active == 0) - std::cerr << "No active for [" << sampel[0] << ", " << sampel[1] << "]" << std::endl; + if (sampel[current->_varid] <= current->_limit) { + for (auto p : current->_low_patterns) { + write[p] = true; + // std::cerr << "ACTIVE " << p << std::endl; + } + active += current->_low_patterns.size(); + } + if (sampel[current->_varid] <= current->_limit && current->_low) { + current = current->_low.get(); + continue; + } + if (sampel[current->_varid] >= current->_limit && current->_high) { + current = current->_high.get(); + continue; + } + break; + } + if (active == 0) + std::cerr << "No active for [" << sampel[0] << ", " << sampel[1] << "]" + << std::endl; } -int ZonotopStrategy::max_pattern_length() const { - return _max_length; -} +int ZonotopStrategy::max_pattern_length() const { return _max_length; } -int ZonotopStrategy::num_patterns() const { - return _states.size(); -} +int ZonotopStrategy::num_patterns() const { return _states.size(); } -int ZonotopStrategy::get_pattern(int el, int* write) { - auto buffer = std::make_unique(_max_length); - size_t length = _states.unpack(el, (unsigned char*)buffer.get()); - for(size_t i = 0; i < length/sizeof(uint16_t); ++i) - { - write[i] = buffer[i]; - } - return length/sizeof(uint16_t); +int ZonotopStrategy::get_pattern(int el, int *write) { + auto buffer = std::make_unique(_max_length); + size_t length = _states.unpack(el, (unsigned char *)buffer.get()); + for (size_t i = 0; i < length / sizeof(uint16_t); ++i) { + write[i] = buffer[i]; + } + return length / sizeof(uint16_t); } -std::ostream& ZonotopStrategy::print(std::ostream& stream) const { - if(_root) - _root->print(stream, this); - return stream; +std::ostream &ZonotopStrategy::print(std::ostream &stream) const { + if (_root) + _root->print(stream, this); + return stream; } double ZonotopStrategy::get_max(size_t dimen) const { - return _root->get_max(dimen); + return _root->get_max(dimen); } double ZonotopStrategy::get_min(size_t dimen) const { - return _root->get_min(dimen); + return _root->get_min(dimen); } double ZonotopStrategy::node_t::get_max(size_t dimen) const { - double maxval = _varid == dimen ? _limit : -std::numeric_limits::infinity(); - if(_high) - maxval = std::max(maxval, _high->get_max(dimen)); - if(_low) - maxval = std::max(maxval, _low->get_max(dimen)); - return maxval; + double maxval = + _varid == dimen ? _limit : -std::numeric_limits::infinity(); + if (_high) + maxval = std::max(maxval, _high->get_max(dimen)); + if (_low) + maxval = std::max(maxval, _low->get_max(dimen)); + return maxval; } double ZonotopStrategy::node_t::get_min(size_t dimen) const { - double minval = _varid == dimen ? _limit : std::numeric_limits::infinity(); - if(_high) - minval = std::min(minval, _high->get_min(dimen)); - if(_low) - minval = std::min(minval, _low->get_min(dimen)); - return minval; + double minval = + _varid == dimen ? _limit : std::numeric_limits::infinity(); + if (_high) + minval = std::min(minval, _high->get_min(dimen)); + if (_low) + minval = std::min(minval, _low->get_min(dimen)); + return minval; } - - -std::ostream& ZonotopStrategy::print_c(std::ostream& stream, std::string function_name) const -{ -/* stream << "const int " << function_name << "_patterns[][" << (_max_length + 1) << "] = {\n"; - bool first = true; - auto buffer = std::make_unique(_max_length); - for(size_t i = 0; i < _states.size(); ++i) - { - memset(buffer.get(), 0, max_pattern_length()*sizeof(uint16_t)); - auto size = _states.unpack(i, (unsigned char*)buffer.get()); - if(!first) - stream << ","; - stream << "{"; - bool bf = true; - for(int j = 0; j < max_pattern_length()+1; ++j) - { - if(!bf) - stream << ","; - if((size_t)j < size/sizeof(uint16_t)) - stream << buffer[j]; - else - stream << "-1"; - bf = false; - } - stream << "}\n"; - first = false; - } - stream << "};\n\n";*/ - stream << "bool " << function_name << "(const double* args, bool* patterns)\n"; - stream << "{\n"; - _root->print_c(stream, 1); - stream << "\treturn false;\n"; - stream << "}\n"; - return stream; +std::ostream &ZonotopStrategy::print_c(std::ostream &stream, + std::string function_name) const { + /* stream << "const int " << function_name << "_patterns[][" << + (_max_length + 1) << "] = {\n"; bool first = true; auto buffer = + std::make_unique(_max_length); for(size_t i = 0; i < + _states.size(); ++i) + { + memset(buffer.get(), 0, max_pattern_length()*sizeof(uint16_t)); + auto size = _states.unpack(i, (unsigned char*)buffer.get()); + if(!first) + stream << ","; + stream << "{"; + bool bf = true; + for(int j = 0; j < max_pattern_length()+1; ++j) + { + if(!bf) + stream << ","; + if((size_t)j < size/sizeof(uint16_t)) + stream << buffer[j]; + else + stream << "-1"; + bf = false; + } + stream << "}\n"; + first = false; + } + stream << "};\n\n";*/ + stream << "bool " << function_name + << "(const double* args, bool* patterns)\n"; + stream << "{\n"; + _root->print_c(stream, 1); + stream << "\treturn false;\n"; + stream << "}\n"; + return stream; } -std::ostream& ZonotopStrategy::node_t::print_c(std::ostream& out, size_t tabs) const { - if(_low != nullptr || !_low_patterns.empty()) - { - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "if(args[" << _varid << "] <= " << _limit << ") {\n"; - if(_low_patterns.size() > 0) - { - for(size_t i = 0; i < tabs+1; ++i) out << "\t"; - for(auto p : _low_patterns) - out << "patterns[" << p << "] = true; "; - out << "\n"; - } - if(_low) - _low->print_c(out, tabs + 1); - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "}\n"; +std::ostream &ZonotopStrategy::node_t::print_c(std::ostream &os, + size_t tabs) const { + if (_low != nullptr || !_low_patterns.empty()) { + os << Tabs{tabs}; + os << "if(args[" << _varid << "] <= " << _limit << ") {\n"; + if (_low_patterns.size() > 0) { + os << Tabs{tabs + 1}; + for (auto p : _low_patterns) + os << "patterns[" << p << "] = true; "; + os << "\n"; } + if (_low) + _low->print_c(os, tabs + 1); + os << Tabs{tabs}; + os << "}\n"; + } - if(_high != nullptr || !_high_patterns.empty()) - { - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "if(args[" << _varid << "] >= " << _limit << ") {\n"; - if(_high_patterns.size() > 0) - { - for(size_t i = 0; i < tabs+1; ++i) out << "\t"; - for(auto p : _high_patterns) - out << "patterns[" << p << "] = true; "; - out << "\n"; - } - if(_high) - _high->print_c(out, tabs + 1); - for(size_t i = 0; i < tabs; ++i) out << "\t"; - out << "}\n"; + if (_high != nullptr || !_high_patterns.empty()) { + os << Tabs{tabs}; + os << "if(args[" << _varid << "] >= " << _limit << ") {\n"; + if (_high_patterns.size() > 0) { + os << Tabs{tabs + 1}; + for (auto p : _high_patterns) + os << "patterns[" << p << "] = true; "; + os << "\n"; } - return out; + if (_high) + _high->print_c(os, tabs + 1); + os << Tabs{tabs}; + os << "}\n"; + } + return os; } diff --git a/src/ZonotopStrategy.h b/src/ZonotopStrategy.h index d5522ad..bf02d7b 100644 --- a/src/ZonotopStrategy.h +++ b/src/ZonotopStrategy.h @@ -1,21 +1,21 @@ /* * Copyright (C) 2020 Peter G. Jensen - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -/* +/* * File: ZonotopStrategy.h * Author: Peter G. Jensen * @@ -25,60 +25,60 @@ #ifndef ZONOTOPSTRATEGY_H #define ZONOTOPSTRATEGY_H +#include "SimpleTree.h" + #include -#include -#include -#include "SimpleTree.h" -#include "errors.h" +#include +#include class ZonotopStrategy { public: - ZonotopStrategy(ZonotopStrategy&&) = default; - virtual ~ZonotopStrategy() = default; - ZonotopStrategy& operator=(ZonotopStrategy&&) = default; - static ZonotopStrategy parse(std::istream&); - int num_patterns() const; - int max_pattern_length() const; - void active(const double* sample, bool* write) const; - int get_pattern(int el, int* write); + ZonotopStrategy(ZonotopStrategy &&) = default; + ZonotopStrategy &operator=(ZonotopStrategy &&) = default; + static ZonotopStrategy parse(std::istream &); + int num_patterns() const; + int max_pattern_length() const; + void active(const double *sample, bool *write) const; + int get_pattern(int el, int *write); + double get_min(size_t dimen) const; + double get_max(size_t dimen) const; + std::ostream &print(std::ostream &) const; + std::ostream &print_c(std::ostream &stream, std::string function_name) const; + +private: + ZonotopStrategy() = default; + ptrie::set_stable<> _states; + size_t _max_length = 0; + struct node_t; + using node_ptr = std::shared_ptr; + struct node_t : std::enable_shared_from_this { + uint32_t _varid = 0; + double _limit = 0; + node_ptr _low = nullptr; + node_ptr _high = nullptr; + std::vector _low_patterns; + std::vector _high_patterns; + node_t *_parent{nullptr}; + std::ostream &print(std::ostream &os, const ZonotopStrategy *parent, + size_t tabs = 0) const; + std::ostream &print_c(std::ostream &os, size_t tabs = 0) const; double get_min(size_t dimen) const; double get_max(size_t dimen) const; - std::ostream& print(std::ostream&) const; - std::ostream& print_c(std::ostream& stream, std::string function_name) const; -private: - ZonotopStrategy() = default; - ptrie::set_stable<> _states; - size_t _max_length = 0; - struct node_t { - uint32_t _varid = 0; - double _limit = 0; - std::shared_ptr _low = nullptr; - std::shared_ptr _high = nullptr; - std::vector _low_patterns; - std::vector _high_patterns; - node_t* _parent; - std::ostream& print(std::ostream& out, const ZonotopStrategy* parent, size_t tabs = 0) const; - std::ostream& print_c(std::ostream& out, size_t tabs = 0) const; - double get_min(size_t dimen) const; - double get_max(size_t dimen) const; - }; - std::shared_ptr _root; - struct bound_t { - double _lower = 0; - double _upper = 0; - bound_t() = default; - bound_t(const bound_t&) = default; - bound_t(bound_t&&) = default; - bound_t(double l, double u) - : _lower(l), _upper(u) {} - }; - static std::vector get_bounds(const std::string& zonotop, size_t& vars); - bool add(std::vector& bounds, size_t state); - bool rec_insert(node_t* node, std::vector& bounds, std::vector>& handled, size_t state); - void try_merge(node_t* node, size_t state); + }; + node_ptr _root; + struct bound_t { + double _lower = 0; + double _upper = 0; + bound_t() = default; + bound_t(double l, double u) : _lower{l}, _upper{u} {} + }; + static std::vector get_bounds(const std::string &zonotop, + size_t &vars); + bool add(std::vector &bounds, size_t state); + bool rec_insert(node_t *node, std::vector &bounds, + std::vector> &handled, size_t state); + void try_merge(node_t *node, size_t state); }; - #endif /* ZONOTOPSTRATEGY_H */ - diff --git a/src/errors.h b/src/errors.h index ad056ab..88face2 100644 --- a/src/errors.h +++ b/src/errors.h @@ -1,21 +1,21 @@ /* * Copyright (C) 2020 Peter G. Jensen - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. - * + * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ -/* +/* * File: errors.h * Author: Peter G. Jensen * @@ -25,26 +25,15 @@ #ifndef ERRORS_H #define ERRORS_H -struct base_error : public std::exception { - std::string _message; +struct base_error : std::logic_error { + using std::logic_error::logic_error; - explicit base_error(std::string m) - : _message(std::move(m)) { - } + virtual void print(std::ostream &os) const { os << what() << std::endl; } - const char *what() const noexcept override { - return _message.c_str(); - } - - virtual void print(std::ostream &os) const { - os << what() << std::endl; - } - - friend std::ostream &operator<<(std::ostream &os, const base_error &el) { - el.print(os); - return os; - } + friend std::ostream &operator<<(std::ostream &os, const base_error &el) { + el.print(os); + return os; + } }; #endif /* ERRORS_H */ - diff --git a/test/inconsistent_lookup.cpp b/test/inconsistent_lookup.cpp index c5ef33b..0e22e8c 100644 --- a/test/inconsistent_lookup.cpp +++ b/test/inconsistent_lookup.cpp @@ -17,44 +17,38 @@ #define BOOST_TEST_MODULE UnorderedLoad +#include "SimpleTree.h" + #include #include -#include "SimpleTree.h" - -BOOST_AUTO_TEST_CASE(DirectoryTest) -{ - BOOST_REQUIRE(getenv("STRATEGY_DIR")); -} +BOOST_AUTO_TEST_CASE(DirectoryTest) { BOOST_REQUIRE(getenv("STRATEGY_DIR")); } -BOOST_AUTO_TEST_CASE(Inconsistent1) -{ - std::string strategy = getenv("STRATEGY_DIR"); - strategy += "/inconsistent1.strategy"; - std::ifstream in(strategy); - auto tree = SimpleTree::parse(in, false, false); - double vars[] = {10}; - auto act18 = tree.value(vars,nullptr, 0); - auto act19 = tree.value(vars,nullptr, 1); - BOOST_REQUIRE_LT(act18, act19); +BOOST_AUTO_TEST_CASE(Inconsistent1) { + std::string strategy = getenv("STRATEGY_DIR"); + strategy += "/inconsistent1.strategy"; + std::ifstream in(strategy); + auto tree = SimpleTree::parse(in, false, false); + double vars[] = {10}; + auto act18 = tree.value(vars, nullptr, 0); + auto act19 = tree.value(vars, nullptr, 1); + BOOST_REQUIRE_LT(act18, act19); } -BOOST_AUTO_TEST_CASE(Inconsistent1Simplify) -{ - std::string strategy = getenv("STRATEGY_DIR"); - strategy += "/inconsistent1.strategy"; - std::ifstream in(strategy); - auto tree = SimpleTree::parse(in, true, false); - double vars[] = {10}; - BOOST_REQUIRE_LT(tree.value(vars,nullptr, 0), tree.value(vars,nullptr, 1)); +BOOST_AUTO_TEST_CASE(Inconsistent1Simplify) { + std::string strategy = getenv("STRATEGY_DIR"); + strategy += "/inconsistent1.strategy"; + std::ifstream in(strategy); + auto tree = SimpleTree::parse(in, true, false); + double vars[] = {10}; + BOOST_REQUIRE_LT(tree.value(vars, nullptr, 0), tree.value(vars, nullptr, 1)); } -BOOST_AUTO_TEST_CASE(Inconsistent1SimplifySubsumption) -{ - std::string strategy = getenv("STRATEGY_DIR"); - strategy += "/inconsistent1.strategy"; - std::ifstream in(strategy); - auto tree = SimpleTree::parse(in, true, true); - double vars[] = {10}; - BOOST_REQUIRE_LT(tree.value(vars,nullptr, 0), tree.value(vars,nullptr, 1)); +BOOST_AUTO_TEST_CASE(Inconsistent1SimplifySubsumption) { + std::string strategy = getenv("STRATEGY_DIR"); + strategy += "/inconsistent1.strategy"; + std::ifstream in(strategy); + auto tree = SimpleTree::parse(in, true, true); + double vars[] = {10}; + BOOST_REQUIRE_LT(tree.value(vars, nullptr, 0), tree.value(vars, nullptr, 1)); } diff --git a/test/unordered_load.cpp b/test/unordered_load.cpp index 4589c50..14e536a 100644 --- a/test/unordered_load.cpp +++ b/test/unordered_load.cpp @@ -17,262 +17,338 @@ #define BOOST_TEST_MODULE UnorderedLoad -#include - #include "SimpleTree.h" +#include -const std::string simple_unordered_strategy = "{\"version\":1.0,\"type\":\"state->regressor\",\"representation\":\"map\",\"actions\":{" -" \"0\":\"Controller._id4->Controller._id2 { 1, tau, minute_clock := TIME_OFFSET, initValues() }\"," -" \"1\":\"Controller._id2->Controller.Wait { 0 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 0, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"2\":\"Controller._id2->Controller.Wait { 1 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 1, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"3\":\"Controller._id2->Controller.Wait { 2 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 2, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"4\":\"Controller._id2->Controller.Wait { 3 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 3, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"5\":\"Controller._id2->Controller.Wait { 4 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 4, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"6\":\"Controller._id2->Controller.Wait { 5 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 5, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"7\":\"Controller._id2->Controller.Wait { 6 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 6, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"9\":\"Controller._id2->Controller.Wait { 8 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 8, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"10\":\"Controller._id2->Controller.Wait { 9 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 9, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"11\":\"Controller._id2->Controller.Wait { 10 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 10, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"12\":\"Controller._id2->Controller.Wait { 1, tau, consumed_power := 0, heat_produced := 0 }\"," -" \"13\":\"WAIT\"" -"},\"statevars\":[" -" \"round(minute_clock) \"" -"],\"pointvars\":[" -"],\"locationnames\":{" -" \"Fetch_Data.location\":{" -" \"0\":\"_id5\"" -" }," -" \"Room1.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Room2.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Room3.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Room4.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Optimization.location\":{" -" \"0\":\"_id1\"" -" }," -" \"Controller.location\":{" -" \"0\":\"_id2\"," -" \"1\":\"Wait\"," -" \"2\":\"_id4\"" -" }" -"},\"regressors\":{" -" \"(30)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 1.294636921229531," -" \"12\" : 3.091107032150207" -" }" -" }," -" \"(210)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 1.009248500699941," -" \"12\" : 1.028772620412916" -" }" -" }," -" \"(15)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 2.855407148131672," -" \"12\" : 1.352930166302546" -" }" -" }" -" }" -"}"; - - - - -const std::string unordered_strategy = "{\"version\":1.0,\"type\":\"state->regressor\",\"representation\":\"map\",\"actions\":{" -" \"0\":\"Controller._id4->Controller._id2 { 1, tau, minute_clock := TIME_OFFSET, initValues() }\"," -" \"1\":\"Controller._id2->Controller.Wait { 0 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 0, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"2\":\"Controller._id2->Controller.Wait { 1 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 1, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"3\":\"Controller._id2->Controller.Wait { 2 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 2, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"4\":\"Controller._id2->Controller.Wait { 3 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 3, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"5\":\"Controller._id2->Controller.Wait { 4 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 4, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"6\":\"Controller._id2->Controller.Wait { 5 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 5, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"7\":\"Controller._id2->Controller.Wait { 6 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 6, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"9\":\"Controller._id2->Controller.Wait { 8 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 8, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"10\":\"Controller._id2->Controller.Wait { 9 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 9, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"11\":\"Controller._id2->Controller.Wait { 10 >= 10 && any_on(), tau, consumed_power := 0.625000 + 0.187500 * 10, heat_produced := consumed_power * calculateCOP(), setMassFlow() }\"," -" \"12\":\"Controller._id2->Controller.Wait { 1, tau, consumed_power := 0, heat_produced := 0 }\"," -" \"13\":\"WAIT\"" -"},\"statevars\":[" -" \"round(minute_clock) \"" -"],\"pointvars\":[" -"],\"locationnames\":{" -" \"Fetch_Data.location\":{" -" \"0\":\"_id5\"" -" }," -" \"Room1.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Room2.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Room3.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Room4.location\":{" -" \"0\":\"_id0\"" -" }," -" \"Optimization.location\":{" -" \"0\":\"_id1\"" -" }," -" \"Controller.location\":{" -" \"0\":\"_id2\"," -" \"1\":\"Wait\"," -" \"2\":\"_id4\"" -" }" -"},\"regressors\":{" -" \"(195)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 1.767005809675129," -" \"12\" : 0.5472758743181302" -" }" -" }," -" \"(180)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 2.107357559876886," -" \"12\" : 0.841995896951961" -" }" -" }," -" \"(225)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 0.3895094003837855," -" \"12\" : 1.438914715299473" -" }" -" }," -" \"(120)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 3.562263183988571," -" \"12\" : 3.01531941566271" -" }" -" }," -" \"(90)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 4.595961684754502," -" \"12\" : 4.520572775155376" -" }" -" }," -" \"(75)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 5.200779712113728," -" \"12\" : 1.1120904838198" -" }" -" }," -" \"(165)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 2.422010195182813," -" \"12\" : 1.850311355224428" -" }" -" }," -" \"(240)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 0.1842626478060555," -" \"12\" : 0.5429048553483474" -" }" -" }," -" \"(150)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 1.802785410011689," -" \"12\" : 2.744820174794056" -" }" -" }," -" \"(135)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 4.082210512180363," -" \"12\" : 1.046474095029413" -" }" -" }," -" \"(105)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 3.506084076677214," -" \"12\" : 4.529894227895771" -" }" -" }," -" \"(45)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 2.923974662511487," -" \"12\" : 3.712782046133843" -" }" -" }," -" \"(30)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 1.294636921229531," -" \"12\" : 3.091107032150207" -" }" -" }," -" \"(210)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 1.009248500699941," -" \"12\" : 1.028772620412916" -" }" -" }," -" \"(15)\":" -" {\"type\":\"act->point->val\",\"representation\":\"simpletree\",\"minimize\":1,\"regressor\":" -" {" -" \"11\" : 2.855407148131672," -" \"12\" : 1.352930166302546" -" }" -" }" -" }" -"}"; - +const std::string simple_unordered_strategy = + "{\"version\":1.0,\"type\":\"state->regressor\",\"representation\":\"map\"," + "\"actions\":{" + " \"0\":\"Controller._id4->Controller._id2 { 1, tau, minute_clock := " + "TIME_OFFSET, initValues() }\"," + " \"1\":\"Controller._id2->Controller.Wait { 0 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 0, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"2\":\"Controller._id2->Controller.Wait { 1 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 1, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"3\":\"Controller._id2->Controller.Wait { 2 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 2, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"4\":\"Controller._id2->Controller.Wait { 3 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 3, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"5\":\"Controller._id2->Controller.Wait { 4 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 4, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"6\":\"Controller._id2->Controller.Wait { 5 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 5, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"7\":\"Controller._id2->Controller.Wait { 6 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 6, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"9\":\"Controller._id2->Controller.Wait { 8 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 8, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"10\":\"Controller._id2->Controller.Wait { 9 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 9, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"11\":\"Controller._id2->Controller.Wait { 10 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 10, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"12\":\"Controller._id2->Controller.Wait { 1, tau, consumed_power := " + "0, heat_produced := 0 }\"," + " \"13\":\"WAIT\"" + "},\"statevars\":[" + " \"round(minute_clock) \"" + "],\"pointvars\":[" + "],\"locationnames\":{" + " \"Fetch_Data.location\":{" + " \"0\":\"_id5\"" + " }," + " \"Room1.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room2.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room3.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room4.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Optimization.location\":{" + " \"0\":\"_id1\"" + " }," + " \"Controller.location\":{" + " \"0\":\"_id2\"," + " \"1\":\"Wait\"," + " \"2\":\"_id4\"" + " }" + "},\"regressors\":{" + " \"(30)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.294636921229531," + " \"12\" : 3.091107032150207" + " }" + " }," + " \"(210)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.009248500699941," + " \"12\" : 1.028772620412916" + " }" + " }," + " \"(15)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.855407148131672," + " \"12\" : 1.352930166302546" + " }" + " }" + " }" + "}"; +const std::string unordered_strategy = + "{\"version\":1.0,\"type\":\"state->regressor\",\"representation\":\"map\"," + "\"actions\":{" + " \"0\":\"Controller._id4->Controller._id2 { 1, tau, minute_clock := " + "TIME_OFFSET, initValues() }\"," + " \"1\":\"Controller._id2->Controller.Wait { 0 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 0, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"2\":\"Controller._id2->Controller.Wait { 1 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 1, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"3\":\"Controller._id2->Controller.Wait { 2 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 2, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"4\":\"Controller._id2->Controller.Wait { 3 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 3, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"5\":\"Controller._id2->Controller.Wait { 4 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 4, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"6\":\"Controller._id2->Controller.Wait { 5 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 5, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"7\":\"Controller._id2->Controller.Wait { 6 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 6, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"9\":\"Controller._id2->Controller.Wait { 8 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 8, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"10\":\"Controller._id2->Controller.Wait { 9 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 9, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"11\":\"Controller._id2->Controller.Wait { 10 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 10, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"12\":\"Controller._id2->Controller.Wait { 1, tau, consumed_power := " + "0, heat_produced := 0 }\"," + " \"13\":\"WAIT\"" + "},\"statevars\":[" + " \"round(minute_clock) \"" + "],\"pointvars\":[" + "],\"locationnames\":{" + " \"Fetch_Data.location\":{" + " \"0\":\"_id5\"" + " }," + " \"Room1.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room2.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room3.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room4.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Optimization.location\":{" + " \"0\":\"_id1\"" + " }," + " \"Controller.location\":{" + " \"0\":\"_id2\"," + " \"1\":\"Wait\"," + " \"2\":\"_id4\"" + " }" + "},\"regressors\":{" + " \"(195)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.767005809675129," + " \"12\" : 0.5472758743181302" + " }" + " }," + " \"(180)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.107357559876886," + " \"12\" : 0.841995896951961" + " }" + " }," + " \"(225)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 0.3895094003837855," + " \"12\" : 1.438914715299473" + " }" + " }," + " \"(120)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 3.562263183988571," + " \"12\" : 3.01531941566271" + " }" + " }," + " \"(90)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 4.595961684754502," + " \"12\" : 4.520572775155376" + " }" + " }," + " \"(75)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 5.200779712113728," + " \"12\" : 1.1120904838198" + " }" + " }," + " \"(165)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.422010195182813," + " \"12\" : 1.850311355224428" + " }" + " }," + " \"(240)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 0.1842626478060555," + " \"12\" : 0.5429048553483474" + " }" + " }," + " \"(150)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.802785410011689," + " \"12\" : 2.744820174794056" + " }" + " }," + " \"(135)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 4.082210512180363," + " \"12\" : 1.046474095029413" + " }" + " }," + " \"(105)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 3.506084076677214," + " \"12\" : 4.529894227895771" + " }" + " }," + " \"(45)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.923974662511487," + " \"12\" : 3.712782046133843" + " }" + " }," + " \"(30)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.294636921229531," + " \"12\" : 3.091107032150207" + " }" + " }," + " \"(210)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.009248500699941," + " \"12\" : 1.028772620412916" + " }" + " }," + " \"(15)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.855407148131672," + " \"12\" : 1.352930166302546" + " }" + " }" + " }" + "}"; -BOOST_AUTO_TEST_CASE(SimpleUnorderedKeyLoad) -{ - std::stringstream ss(simple_unordered_strategy); - auto strategy = SimpleTree::parse(ss, false, false, 0); - double disc[1] = {15.0}; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); +BOOST_AUTO_TEST_CASE(SimpleUnorderedKeyLoad) { + auto is = std::istringstream{simple_unordered_strategy}; + auto strategy = SimpleTree::parse(is, false, false, 0); + double disc[1] = {15.0}; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); - disc[0] = 210; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); + disc[0] = 210; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); - disc[0] = 30; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); + disc[0] = 30; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); } -BOOST_AUTO_TEST_CASE(UnorderedKeyLoad) -{ - std::stringstream ss(unordered_strategy); - auto strategy = SimpleTree::parse(ss, false, false, 0); - double disc[1] = {15.0}; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); +BOOST_AUTO_TEST_CASE(UnorderedKeyLoad) { + auto is = std::stringstream{unordered_strategy}; + auto strategy = SimpleTree::parse(is, false, false, 0); + double disc[1] = {15.0}; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); - disc[0] = 210; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); + disc[0] = 210; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); - disc[0] = 30; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); + disc[0] = 30; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); } From 346a66a31c316927c230b0bfe887775555c2f733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Fri, 27 Jun 2025 17:49:10 +0200 Subject: [PATCH 07/21] Refactoed key parsing to fallback to stream parsing in case from_char is not provided (AppleClang) --- src/SimpleTree.cpp | 85 +++++++++++++++++++++++++++++++--------- src/SimpleTree.h | 4 +- test/CMakeLists.txt | 4 ++ test/SimpleTree_test.cpp | 44 +++++++++++++++++++++ 4 files changed, 116 insertions(+), 21 deletions(-) create mode 100644 test/SimpleTree_test.cpp diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index fd25313..c9ae052 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -29,7 +29,9 @@ #include #include +#include // true_type for detecting from_chars #include +#include // declval for detecting from_chars #include using json = nlohmann::json; @@ -158,28 +160,73 @@ SimpleTree SimpleTree::parse(std::istream &input, return tree; } +template using void_t = std::void_t; + +/// C++17 compile-time test for presence of std::from_chars(const char*, const +/// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will +/// implement proper from_chars by then). History: C++17 introduced +/// std::from_chars, but STL vendors were late, then provided only integral +/// versions... +template +struct has_from_chars : std::false_type { +}; // primary template declaration (used when specializations fail) + +template +struct has_from_chars< + T, // template partial specialization + void_t< // tests if the following expression computes into a type: + decltype(std::from_chars(std::declval(), + std::declval(), + std::declval()))>> : std::true_type {}; + +template +constexpr auto has_from_chars_v = has_from_chars::value; + +static_assert(has_from_chars_v, + "cannot convert string into double fast"); + std::vector SimpleTree::parse_key(const std::string &key) { auto res = std::vector{}; - auto it = key.c_str(); - const auto end = it + key.size(); - if (it != end && *it != '(') { - throw base_error("Key is incorrectly formatted"); - } - ++it; - while (it != end) { - double number; - if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { - res.push_back(number); - it = p; - if (it != end) { - if (*it == ')') - break; - if (*it != ',') - throw base_error("expected comma-separated-numbers in key: " + key); - ++it; + if constexpr (has_from_chars_v) { // fast floating point parsing + auto it = key.c_str(); + const auto end = it + key.size(); + if (it == end || *it != '(') { + throw base_error("incorrectly formatted key ('(' expected): " + key); + } + ++it; + while (it != end && *it != ')') { + double number; + if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { + res.push_back(number); + it = p; + if (it != end && *it == ',') + ++it; + } else { + throw base_error("failed to parse number in key: " + key); } - } else { - throw base_error("invalid number in key: " + key); + } + if (it == end || *it != ')') { + throw base_error("incorrectly formatted key (')' expected): " + key); + } + } else { // fallback to slow stream parsing + auto is = std::istringstream{key}; + char c; + if (!is.get(c) || c != '(') { + throw base_error("incorrectly formatted key ('(' expected): " + key); + } + if (is && is.peek() == ')') + return res; + while (is) { + double number; + if (is >> number) + res.push_back(number); + else + throw base_error("failed to parse number in key: " + key); + if (is.get(c) && c != ',') + break; + } + if (c != ')') { + throw base_error("incorrectly formatted key (')' expected): " + key); } } return res; diff --git a/src/SimpleTree.h b/src/SimpleTree.h index 019bfa7..b3f32f2 100644 --- a/src/SimpleTree.h +++ b/src/SimpleTree.h @@ -41,6 +41,8 @@ class SimpleTree { bool subsumption = false, double accuracy = 0); static SimpleTree parse(std::istream &, bool simplify, bool subsumption, double accuracy, std::vector &exactness); + static std::vector parse_key(const std::string &key); + std::ostream &print(std::ostream &os) const; std::ostream &print_c(std::ostream &os, const std::string &name) const; double value(const double *disc, const double *cont, uint32_t action) const; @@ -68,8 +70,6 @@ class SimpleTree { using json = nlohmann::json; SimpleTree() = default; - static std::vector parse_key(const std::string &key); - struct node_t : std::enable_shared_from_this { uint32_t _var = std::numeric_limits::max(); double _limit = -std::numeric_limits::infinity(); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 19009ba..513a288 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,10 @@ find_package (Boost COMPONENTS unit_test_framework REQUIRED) include_directories (${CMAKE_SOURCE_DIR}/src) +add_executable (SimpleTree_test SimpleTree_test.cpp) +target_link_libraries(SimpleTree_test PRIVATE strategy Boost::unit_test_framework) +add_test(NAME SimpleTree_test COMMAND SimpleTree_test) + add_executable (unordered_load unordered_load.cpp) target_link_libraries(unordered_load PRIVATE strategy Boost::unit_test_framework) add_test(NAME unordered_load COMMAND unordered_load) diff --git a/test/SimpleTree_test.cpp b/test/SimpleTree_test.cpp new file mode 100644 index 0000000..e9ea1f6 --- /dev/null +++ b/test/SimpleTree_test.cpp @@ -0,0 +1,44 @@ +#define BOOST_TEST_MODULE UnorderedLoad + +#include "SimpleTree.h" +#include "errors.h" + +#include + +BOOST_AUTO_TEST_CASE(SimpleTreeParseKey) { + const auto res_blank = SimpleTree::parse_key("()"); + BOOST_CHECK(res_blank.empty()); + + const auto res_one = SimpleTree::parse_key("(1)"); + BOOST_REQUIRE_EQUAL(res_one.size(), 1); + BOOST_CHECK_EQUAL(res_one[0], 1); + + const auto res_two = SimpleTree::parse_key("(2,1)"); + BOOST_REQUIRE_EQUAL(res_two.size(), 2); + BOOST_CHECK_EQUAL(res_two[0], 2); + BOOST_CHECK_EQUAL(res_two[1], 1); + + const auto res_three = SimpleTree::parse_key("(3,2,1)"); + BOOST_REQUIRE_EQUAL(res_three.size(), 3); + BOOST_CHECK_EQUAL(res_three[0], 3); + BOOST_CHECK_EQUAL(res_three[1], 2); + BOOST_CHECK_EQUAL(res_three[2], 1); + + const auto res_float = SimpleTree::parse_key("(3.141)"); + BOOST_REQUIRE_EQUAL(res_float.size(), 1); + BOOST_CHECK_EQUAL(res_float[0], 3.141); + + const auto res_floats = SimpleTree::parse_key("(4.3,2.1)"); + BOOST_REQUIRE_EQUAL(res_floats.size(), 2); + BOOST_CHECK_EQUAL(res_floats[0], 4.3); + BOOST_CHECK_EQUAL(res_floats[1], 2.1); + + // a few negative tests: + BOOST_CHECK_THROW(SimpleTree::parse_key(""), base_error); + BOOST_CHECK_THROW(SimpleTree::parse_key("1"), base_error); + BOOST_CHECK_THROW(SimpleTree::parse_key(")"), base_error); + BOOST_CHECK_THROW(SimpleTree::parse_key("("), base_error); + BOOST_CHECK_THROW(SimpleTree::parse_key("(1"), base_error); + BOOST_CHECK_THROW(SimpleTree::parse_key("(2,"), base_error); + BOOST_CHECK_THROW(SimpleTree::parse_key("(2,1"), base_error); +} From e10eb0efd39ce5f65b929345dcf9aed5ea026724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Fri, 27 Jun 2025 17:51:14 +0200 Subject: [PATCH 08/21] Removed redundant static_assert (used for testing has_from_chars) --- src/SimpleTree.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index c9ae052..fc8d04e 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -182,9 +182,6 @@ struct has_from_chars< template constexpr auto has_from_chars_v = has_from_chars::value; -static_assert(has_from_chars_v, - "cannot convert string into double fast"); - std::vector SimpleTree::parse_key(const std::string &key) { auto res = std::vector{}; if constexpr (has_from_chars_v) { // fast floating point parsing From 50cc95db1febab55927ec074571bfc6db08ee4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Fri, 27 Jun 2025 18:02:53 +0200 Subject: [PATCH 09/21] Simplified dependency tracking in CMake (failed with cmake-4) --- src/CMakeLists.txt | 15 +++++++++------ test/CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9dc451c..a08a070 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,13 +1,16 @@ -add_library(strategy SHARED libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) -target_link_libraries(strategy PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json) +add_library(SimpleTree OBJECT SimpleTree.cpp) +target_link_libraries(SimpleTree PUBLIC ptrie::ptrie nlohmann_json::nlohmann_json) -add_library(strategyStatic STATIC libz2s.cpp ZonotopStrategy.cpp SimpleTree.cpp) -target_link_libraries(strategyStatic PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json) +add_library(strategy SHARED libz2s.cpp ZonotopStrategy.cpp) +target_link_libraries(strategy PUBLIC SimpleTree) + +add_library(strategyStatic STATIC libz2s.cpp ZonotopStrategy.cpp) +target_link_libraries(strategyStatic PUBLIC SimpleTree) set_target_properties(strategyStatic PROPERTIES OUTPUT_NAME strategy) if(NOT LIBSTRATEGY_OnlyLibrary) - add_executable(z2s main.cpp ZonotopStrategy.cpp SimpleTree.cpp) - target_link_libraries(z2s PUBLIC ptrie::ptrie PRIVATE nlohmann_json::nlohmann_json Boost::program_options) + add_executable(z2s main.cpp ZonotopStrategy.cpp) + target_link_libraries(z2s PUBLIC SimpleTree Boost::program_options) endif() install(TARGETS strategy strategyStatic diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 513a288..a934649 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,7 +2,7 @@ find_package (Boost COMPONENTS unit_test_framework REQUIRED) include_directories (${CMAKE_SOURCE_DIR}/src) add_executable (SimpleTree_test SimpleTree_test.cpp) -target_link_libraries(SimpleTree_test PRIVATE strategy Boost::unit_test_framework) +target_link_libraries(SimpleTree_test PRIVATE SimpleTree Boost::unit_test_framework) add_test(NAME SimpleTree_test COMMAND SimpleTree_test) add_executable (unordered_load unordered_load.cpp) From 30cc89281bc22808114b580b5487686ed4f3678a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Fri, 27 Jun 2025 18:22:26 +0200 Subject: [PATCH 10/21] Moved from_chars check --- src/SimpleTree.cpp | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index fc8d04e..2e9e2b1 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -27,13 +27,33 @@ #include -#include +#include // from_chars #include #include // true_type for detecting from_chars #include #include // declval for detecting from_chars #include +/// C++17 compile-time test for presence of std::from_chars(const char*, const +/// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will +/// implement proper from_chars by then). History: C++17 introduced +/// std::from_chars, but STL vendors were late, then provided only integral +/// versions... +template +struct has_from_chars : std::false_type { +}; // primary template declaration (used when specializations fail) + +template +struct has_from_chars< // template partial specialization + T, + std::void_t< // tests if the following expression computes into a type: + decltype(std::from_chars(std::declval(), + std::declval(), + std::declval()))>> : std::true_type {}; + +template +constexpr auto has_from_chars_v = has_from_chars::value; + using json = nlohmann::json; const std::vector &SimpleTree::actions() const { return _actions; } @@ -160,28 +180,6 @@ SimpleTree SimpleTree::parse(std::istream &input, return tree; } -template using void_t = std::void_t; - -/// C++17 compile-time test for presence of std::from_chars(const char*, const -/// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will -/// implement proper from_chars by then). History: C++17 introduced -/// std::from_chars, but STL vendors were late, then provided only integral -/// versions... -template -struct has_from_chars : std::false_type { -}; // primary template declaration (used when specializations fail) - -template -struct has_from_chars< - T, // template partial specialization - void_t< // tests if the following expression computes into a type: - decltype(std::from_chars(std::declval(), - std::declval(), - std::declval()))>> : std::true_type {}; - -template -constexpr auto has_from_chars_v = has_from_chars::value; - std::vector SimpleTree::parse_key(const std::string &key) { auto res = std::vector{}; if constexpr (has_from_chars_v) { // fast floating point parsing From ce155b737a44605ee07fd55697b18a903d5f2b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Fri, 27 Jun 2025 18:32:10 +0200 Subject: [PATCH 11/21] isolated checking for from_chars (AppleClang is such a headache) --- src/SimpleTree.cpp | 24 +----------------------- src/utilities.hpp | 28 ++++++++++++++++++++++++++++ test/CMakeLists.txt | 4 ++++ test/utilities_test.cpp | 25 +++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 23 deletions(-) create mode 100644 src/utilities.hpp create mode 100644 test/utilities_test.cpp diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index 2e9e2b1..2928f29 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -24,36 +24,14 @@ #include "SimpleTree.h" #include "errors.h" +#include "utilities.hpp" #include -#include // from_chars #include -#include // true_type for detecting from_chars #include -#include // declval for detecting from_chars #include -/// C++17 compile-time test for presence of std::from_chars(const char*, const -/// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will -/// implement proper from_chars by then). History: C++17 introduced -/// std::from_chars, but STL vendors were late, then provided only integral -/// versions... -template -struct has_from_chars : std::false_type { -}; // primary template declaration (used when specializations fail) - -template -struct has_from_chars< // template partial specialization - T, - std::void_t< // tests if the following expression computes into a type: - decltype(std::from_chars(std::declval(), - std::declval(), - std::declval()))>> : std::true_type {}; - -template -constexpr auto has_from_chars_v = has_from_chars::value; - using json = nlohmann::json; const std::vector &SimpleTree::actions() const { return _actions; } diff --git a/src/utilities.hpp b/src/utilities.hpp new file mode 100644 index 0000000..dd73be6 --- /dev/null +++ b/src/utilities.hpp @@ -0,0 +1,28 @@ +#ifndef UTILITIES_HPP +#define UTILITIES_HPP + +#include // from_chars +#include // true_type for detecting from_chars +#include // declval for detecting from_chars + +/// C++17 compile-time test for presence of std::from_chars(const char*, const +/// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will +/// implement proper from_chars by then). History: C++17 introduced +/// std::from_chars, but STL vendors were late, then provided only integral +/// versions... +template +struct has_from_chars : std::false_type { +}; // primary template declaration (used when specializations fail) + +template +struct has_from_chars< // template partial specialization + T, + std::void_t< // tests if the following expression computes into a type: + decltype(std::from_chars(std::declval(), + std::declval(), + std::declval()))>> : std::true_type {}; + +template +constexpr auto has_from_chars_v = has_from_chars::value; + +#endif // UTILITIES_HPP diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a934649..620da41 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,6 +1,10 @@ find_package (Boost COMPONENTS unit_test_framework REQUIRED) include_directories (${CMAKE_SOURCE_DIR}/src) +add_executable (utilities_test utilities_test.cpp) +add_test(NAME utilities_test COMMAND utilities_test) + + add_executable (SimpleTree_test SimpleTree_test.cpp) target_link_libraries(SimpleTree_test PRIVATE SimpleTree Boost::unit_test_framework) add_test(NAME SimpleTree_test COMMAND SimpleTree_test) diff --git a/test/utilities_test.cpp b/test/utilities_test.cpp new file mode 100644 index 0000000..9dd6313 --- /dev/null +++ b/test/utilities_test.cpp @@ -0,0 +1,25 @@ +#include "../src/utilities.hpp" +#include "utilities.hpp" + +#include + +template void test(const std::string &name) { + if constexpr (has_from_chars_v) { + std::cout << "from_chars(" << name << ") is supported\n"; + } else { + std::cout << "from_chars(" << name << ") is NOT supported\n"; + } +} + +int main() { + test("bool"); + test("char"); + test("int"); + test("unsigned int"); + test("long"); + test("unsigned long"); + test("long long"); + test("unsigned long long"); + test("float"); + test("double"); +} \ No newline at end of file From bb80d4ffd64e5ef55c9a1c616686d4bdd73a8e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Mon, 30 Jun 2025 07:56:54 +0200 Subject: [PATCH 12/21] Isolated parse_key into a separate header to debug AppleClang issues --- src/SimpleTree.cpp | 47 ---------------------------------- src/SimpleTree.h | 1 - src/errors.h | 9 ++----- src/utilities.hpp | 54 ++++++++++++++++++++++++++++++++++++++++ test/SimpleTree_test.cpp | 27 ++++++++++---------- 5 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index 2928f29..29ab3e7 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -158,53 +158,6 @@ SimpleTree SimpleTree::parse(std::istream &input, return tree; } -std::vector SimpleTree::parse_key(const std::string &key) { - auto res = std::vector{}; - if constexpr (has_from_chars_v) { // fast floating point parsing - auto it = key.c_str(); - const auto end = it + key.size(); - if (it == end || *it != '(') { - throw base_error("incorrectly formatted key ('(' expected): " + key); - } - ++it; - while (it != end && *it != ')') { - double number; - if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { - res.push_back(number); - it = p; - if (it != end && *it == ',') - ++it; - } else { - throw base_error("failed to parse number in key: " + key); - } - } - if (it == end || *it != ')') { - throw base_error("incorrectly formatted key (')' expected): " + key); - } - } else { // fallback to slow stream parsing - auto is = std::istringstream{key}; - char c; - if (!is.get(c) || c != '(') { - throw base_error("incorrectly formatted key ('(' expected): " + key); - } - if (is && is.peek() == ')') - return res; - while (is) { - double number; - if (is >> number) - res.push_back(number); - else - throw base_error("failed to parse number in key: " + key); - if (is.get(c) && c != ',') - break; - } - if (c != ')') { - throw base_error("incorrectly formatted key (')' expected): " + key); - } - } - return res; -} - std::pair SimpleTree::node_t::compute_min_max() const { double mincost = std::numeric_limits::infinity(); double maxcost = -mincost; diff --git a/src/SimpleTree.h b/src/SimpleTree.h index b3f32f2..f04e8f4 100644 --- a/src/SimpleTree.h +++ b/src/SimpleTree.h @@ -41,7 +41,6 @@ class SimpleTree { bool subsumption = false, double accuracy = 0); static SimpleTree parse(std::istream &, bool simplify, bool subsumption, double accuracy, std::vector &exactness); - static std::vector parse_key(const std::string &key); std::ostream &print(std::ostream &os) const; std::ostream &print_c(std::ostream &os, const std::string &name) const; diff --git a/src/errors.h b/src/errors.h index 88face2..a906f3c 100644 --- a/src/errors.h +++ b/src/errors.h @@ -25,15 +25,10 @@ #ifndef ERRORS_H #define ERRORS_H +#include + struct base_error : std::logic_error { using std::logic_error::logic_error; - - virtual void print(std::ostream &os) const { os << what() << std::endl; } - - friend std::ostream &operator<<(std::ostream &os, const base_error &el) { - el.print(os); - return os; - } }; #endif /* ERRORS_H */ diff --git a/src/utilities.hpp b/src/utilities.hpp index dd73be6..562e58f 100644 --- a/src/utilities.hpp +++ b/src/utilities.hpp @@ -1,9 +1,16 @@ #ifndef UTILITIES_HPP #define UTILITIES_HPP +#include "errors.h" + +/// Please avoid including this header into headers (include into cpp instead) +/// as it includes streams (instead of iosfwd) and slows down compilation. + #include // from_chars +#include // fallback if from_chars is not available #include // true_type for detecting from_chars #include // declval for detecting from_chars +#include /// C++17 compile-time test for presence of std::from_chars(const char*, const /// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will @@ -25,4 +32,51 @@ struct has_from_chars< // template partial specialization template constexpr auto has_from_chars_v = has_from_chars::value; +inline std::vector parse_key(const std::string &key) { + auto res = std::vector{}; + if constexpr (has_from_chars_v) { // fast floating point parsing + auto it = key.c_str(); + const auto end = it + key.size(); + if (it == end || *it != '(') { + throw base_error("incorrectly formatted key ('(' expected): " + key); + } + ++it; + while (it != end && *it != ')') { + double number; + if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { + res.push_back(number); + it = p; + if (it != end && *it == ',') + ++it; + } else { + throw base_error("failed to parse number in key: " + key); + } + } + if (it == end || *it != ')') { + throw base_error("incorrectly formatted key (')' expected): " + key); + } + } else { // fallback to slow stream parsing + auto is = std::istringstream{key}; + char c; + if (!is.get(c) || c != '(') { + throw base_error("incorrectly formatted key ('(' expected): " + key); + } + if (is && is.peek() == ')') + return res; + while (is) { + double number; + if (is >> number) + res.push_back(number); + else + throw base_error("failed to parse number in key: " + key); + if (is.get(c) && c != ',') + break; + } + if (c != ')') { + throw base_error("incorrectly formatted key (')' expected): " + key); + } + } + return res; +} + #endif // UTILITIES_HPP diff --git a/test/SimpleTree_test.cpp b/test/SimpleTree_test.cpp index e9ea1f6..4189330 100644 --- a/test/SimpleTree_test.cpp +++ b/test/SimpleTree_test.cpp @@ -2,43 +2,44 @@ #include "SimpleTree.h" #include "errors.h" +#include "utilities.hpp" #include BOOST_AUTO_TEST_CASE(SimpleTreeParseKey) { - const auto res_blank = SimpleTree::parse_key("()"); + const auto res_blank = parse_key("()"); BOOST_CHECK(res_blank.empty()); - const auto res_one = SimpleTree::parse_key("(1)"); + const auto res_one = parse_key("(1)"); BOOST_REQUIRE_EQUAL(res_one.size(), 1); BOOST_CHECK_EQUAL(res_one[0], 1); - const auto res_two = SimpleTree::parse_key("(2,1)"); + const auto res_two = parse_key("(2,1)"); BOOST_REQUIRE_EQUAL(res_two.size(), 2); BOOST_CHECK_EQUAL(res_two[0], 2); BOOST_CHECK_EQUAL(res_two[1], 1); - const auto res_three = SimpleTree::parse_key("(3,2,1)"); + const auto res_three = parse_key("(3,2,1)"); BOOST_REQUIRE_EQUAL(res_three.size(), 3); BOOST_CHECK_EQUAL(res_three[0], 3); BOOST_CHECK_EQUAL(res_three[1], 2); BOOST_CHECK_EQUAL(res_three[2], 1); - const auto res_float = SimpleTree::parse_key("(3.141)"); + const auto res_float = parse_key("(3.141)"); BOOST_REQUIRE_EQUAL(res_float.size(), 1); BOOST_CHECK_EQUAL(res_float[0], 3.141); - const auto res_floats = SimpleTree::parse_key("(4.3,2.1)"); + const auto res_floats = parse_key("(4.3,2.1)"); BOOST_REQUIRE_EQUAL(res_floats.size(), 2); BOOST_CHECK_EQUAL(res_floats[0], 4.3); BOOST_CHECK_EQUAL(res_floats[1], 2.1); // a few negative tests: - BOOST_CHECK_THROW(SimpleTree::parse_key(""), base_error); - BOOST_CHECK_THROW(SimpleTree::parse_key("1"), base_error); - BOOST_CHECK_THROW(SimpleTree::parse_key(")"), base_error); - BOOST_CHECK_THROW(SimpleTree::parse_key("("), base_error); - BOOST_CHECK_THROW(SimpleTree::parse_key("(1"), base_error); - BOOST_CHECK_THROW(SimpleTree::parse_key("(2,"), base_error); - BOOST_CHECK_THROW(SimpleTree::parse_key("(2,1"), base_error); + BOOST_CHECK_THROW(parse_key(""), base_error); + BOOST_CHECK_THROW(parse_key("1"), base_error); + BOOST_CHECK_THROW(parse_key(")"), base_error); + BOOST_CHECK_THROW(parse_key("("), base_error); + BOOST_CHECK_THROW(parse_key("(1"), base_error); + BOOST_CHECK_THROW(parse_key("(2,"), base_error); + BOOST_CHECK_THROW(parse_key("(2,1"), base_error); } From d2ac671a96a069b5e46290786d8746266acf468b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Mon, 30 Jun 2025 09:26:17 +0200 Subject: [PATCH 13/21] Fixed parse_key C++ standard and AppleClang insist that constexpr is evaluated at CT only when the condition depends on template parameters https://eel.is/c++draft/stmt.if#2 --- .clang-format | 177 +++ src/SimpleTree.cpp | 1957 ++++++++++++++++------------------ src/SimpleTree.h | 234 ++-- src/ZonotopStrategy.cpp | 745 +++++++------ src/ZonotopStrategy.h | 89 +- src/errors.h | 5 +- src/utilities.hpp | 131 +-- test/SimpleTree_test.cpp | 73 +- test/inconsistent_lookup.cpp | 49 +- test/unordered_load.cpp | 455 ++++---- test/utilities_test.cpp | 38 +- 11 files changed, 2028 insertions(+), 1925 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6b8b098 --- /dev/null +++ b/.clang-format @@ -0,0 +1,177 @@ +--- +Language: Cpp +# BasedOnStyle: Google +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: true +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +#IncludeBlocks: Regroup +IncludeBlocks: Preserve +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - 'c++' + - 'C++' + CanonicalDelimiter: '' + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: '' +ReflowComments: true +#SortIncludes: true +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: false +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 4 +UseTab: Never +--- +Language: Java +BasedOnStyle: Google +AlignTrailingComments: true +AllowShortFunctionsOnASingleLine: All +BinPackArguments: true +BreakAfterJavaFieldAnnotations: true +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +ColumnLimit: 120 +IndentWidth: 4 +ReflowComments: true +TabWidth: 4 +UseTab: Never +... diff --git a/src/SimpleTree.cpp b/src/SimpleTree.cpp index 29ab3e7..12b18e9 100644 --- a/src/SimpleTree.cpp +++ b/src/SimpleTree.cpp @@ -34,867 +34,803 @@ using json = nlohmann::json; -const std::vector &SimpleTree::actions() const { return _actions; } +const std::vector& SimpleTree::actions() const { return _actions; } -SimpleTree SimpleTree::parse(std::istream &input, bool simplify, - bool subsumption, double accuracy) { - auto empty = std::vector{}; - return parse(input, simplify, subsumption, accuracy, empty); +SimpleTree SimpleTree::parse(std::istream& input, bool simplify, bool subsumption, double accuracy) +{ + auto empty = std::vector{}; + return parse(input, simplify, subsumption, accuracy, empty); } -SimpleTree SimpleTree::parse(std::istream &input, - bool simplify [[maybe_unused]], bool subsumption, - double accuracy, std::vector &exactness) { - auto raw = json::parse(input); - if (!raw.is_object()) - throw base_error("Input JSON not well formatted"); +SimpleTree SimpleTree::parse(std::istream& input, bool simplify [[maybe_unused]], bool subsumption, double accuracy, + std::vector& exactness) +{ + auto raw = json::parse(input); + if (!raw.is_object()) + throw base_error("Input JSON not well formatted"); - if (!raw["version"].is_number_float() || raw["version"].get() != 1.0) - throw base_error("Input version not supported"); + if (!raw["version"].is_number_float() || raw["version"].get() != 1.0) + throw base_error("Input version not supported"); - if (!raw["type"].is_string() || - raw["type"].get() != "state->regressor") - throw base_error("Input type not supported"); + if (!raw["type"].is_string() || raw["type"].get() != "state->regressor") + throw base_error("Input type not supported"); - if (!raw["representation"].is_string() || - raw["representation"].get() != "map") - throw base_error("Input representation not supported"); + if (!raw["representation"].is_string() || raw["representation"].get() != "map") + throw base_error("Input representation not supported"); - if (!raw["actions"].is_object()) - throw base_error("Expected actions object"); - // store actions - SimpleTree tree; - if (tree._root == nullptr) { - tree._root = std::make_shared(); - tree._root->_limit = -std::numeric_limits::infinity(); - tree._root->_cost = std::numeric_limits::infinity(); - tree._root->_var = std::numeric_limits::max(); - } - for (const auto &[act_key, act_value] : raw["actions"].items()) { - auto id = std::stoi(act_key); - if (id >= static_cast(tree._actions.size())) - tree._actions.resize(id + 1); - tree._actions[id] = act_value.get(); - } + if (!raw["actions"].is_object()) + throw base_error("Expected actions object"); + // store actions + SimpleTree tree; + if (tree._root == nullptr) { + tree._root = std::make_shared(); + tree._root->_limit = -std::numeric_limits::infinity(); + tree._root->_cost = std::numeric_limits::infinity(); + tree._root->_var = std::numeric_limits::max(); + } + for (const auto& [act_key, act_value] : raw["actions"].items()) { + auto id = std::stoi(act_key); + if (id >= static_cast(tree._actions.size())) + tree._actions.resize(id + 1); + tree._actions[id] = act_value.get(); + } - if (!raw["statevars"].is_array()) - throw base_error("Expected statevars as array"); - tree._statevars = raw["statevars"].get>(); + if (!raw["statevars"].is_array()) + throw base_error("Expected statevars as array"); + tree._statevars = raw["statevars"].get>(); - if (!raw["pointvars"].is_array()) - throw base_error("Expected pointvars as array"); - tree._pointvars = raw["pointvars"].get>(); + if (!raw["pointvars"].is_array()) + throw base_error("Expected pointvars as array"); + tree._pointvars = raw["pointvars"].get>(); - if (!raw["regressors"].is_object()) - throw base_error("Expected regressors as object"); - auto minim = -1; - auto inf = json("inf"); // json string (whereas {"inf"} is json array!) - auto first_element = true; - for (const auto &[raw_key, raw_value] : raw["regressors"].items()) { - auto key = parse_key(raw_key); - if (key.size() != tree._statevars.size() && - (!tree._statevars.empty() || key[0] != 1.0)) { - std::cerr << raw_key << std::endl; - std::cerr << key.size() << std::endl; - std::cerr << tree._statevars.size() << std::endl; - throw base_error("Expected cardinality of key does not match cardinality " - "of statevars"); - } - if (tree._statevars.empty()) - key.clear(); + if (!raw["regressors"].is_object()) + throw base_error("Expected regressors as object"); + auto minim = -1; + auto inf = json("inf"); // json string (whereas {"inf"} is json array!) + auto first_element = true; + for (const auto& [raw_key, raw_value] : raw["regressors"].items()) { + auto key = parse_key(raw_key); + if (key.size() != tree._statevars.size() && (!tree._statevars.empty() || key[0] != 1.0)) { + std::cerr << raw_key << std::endl; + std::cerr << key.size() << std::endl; + std::cerr << tree._statevars.size() << std::endl; + throw base_error("Expected cardinality of key does not match cardinality " + "of statevars"); + } + if (tree._statevars.empty()) + key.clear(); - if (!raw_value.is_object()) - throw base_error("Expected regressor-element as object"); - if (raw_value["type"].get() != "act->point->val") - throw base_error("Expected regressor-element of type act->point->val"); - if (raw_value["representation"].get() != "simpletree") - throw base_error("Expected regressor-element represented as simpletree"); - bool is_minimize = raw_value["minimize"].is_boolean() - ? raw_value["minimize"].get() - : raw_value["minimize"].get(); - if (!raw_value["regressor"].is_object()) - throw base_error("Expected regressor-sub-element as an object"); - if (minim == -1) - minim = is_minimize; - if (minim != is_minimize) - throw base_error( - "Expected all sub-regressors to have same minimization flag"); - if (first_element) - tree._root->_cost = - (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); - first_element = false; - // make sure all actions are mapped initially - for (size_t i = 0; i < tree._actions.size(); ++i) - tree._root->insert(key, inf, i, tree, 0, is_minimize, 0, exactness); - for (const auto &[reg_key, reg_value] : raw_value["regressor"].items()) { - auto action = std::stoi(reg_key); - if (action >= static_cast(tree._actions.size())) - throw base_error("Action-index is out of bounds"); + if (!raw_value.is_object()) + throw base_error("Expected regressor-element as object"); + if (raw_value["type"].get() != "act->point->val") + throw base_error("Expected regressor-element of type act->point->val"); + if (raw_value["representation"].get() != "simpletree") + throw base_error("Expected regressor-element represented as simpletree"); + bool is_minimize = raw_value["minimize"].is_boolean() ? raw_value["minimize"].get() + : raw_value["minimize"].get(); + if (!raw_value["regressor"].is_object()) + throw base_error("Expected regressor-sub-element as an object"); + if (minim == -1) + minim = is_minimize; + if (minim != is_minimize) + throw base_error("Expected all sub-regressors to have same minimization flag"); + if (first_element) + tree._root->_cost = (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); + first_element = false; + // make sure all actions are mapped initially + for (size_t i = 0; i < tree._actions.size(); ++i) + tree._root->insert(key, inf, i, tree, 0, is_minimize, 0, exactness); + for (const auto& [reg_key, reg_value] : raw_value["regressor"].items()) { + auto action = std::stoi(reg_key); + if (action >= static_cast(tree._actions.size())) + throw base_error("Action-index is out of bounds"); - if (tree._root == nullptr) { - tree._root = std::make_shared(); - tree._root->_limit = -std::numeric_limits::infinity(); - tree._root->_cost = std::numeric_limits::infinity(); - tree._root->_cost = - (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); - tree._root->_var = std::numeric_limits::max(); - } + if (tree._root == nullptr) { + tree._root = std::make_shared(); + tree._root->_limit = -std::numeric_limits::infinity(); + tree._root->_cost = std::numeric_limits::infinity(); + tree._root->_cost = (is_minimize ? 1 : -1) * std::numeric_limits::infinity(); + tree._root->_var = std::numeric_limits::max(); + } - tree._root->insert(key, reg_value, action, tree, 0, is_minimize, accuracy, - exactness); + tree._root->insert(key, reg_value, action, tree, 0, is_minimize, accuracy, exactness); + } } - } - tree._is_minimization = minim != 0; - if (subsumption) - tree._root->subsumption_reduction(minim, tree); - /*if(simplify) // disabled for now - { - nodemap_t nodemap; - if(tree._root) - tree._root = tree._root->simplify(true, nodemap, tree); - if(tree._root) - tree._root->_parent = nullptr; - }*/ - return tree; + tree._is_minimization = minim != 0; + if (subsumption) + tree._root->subsumption_reduction(minim, tree); + /*if(simplify) // disabled for now + { + nodemap_t nodemap; + if(tree._root) + tree._root = tree._root->simplify(true, nodemap, tree); + if(tree._root) + tree._root->_parent = nullptr; + }*/ + return tree; } -std::pair SimpleTree::node_t::compute_min_max() const { - double mincost = std::numeric_limits::infinity(); - double maxcost = -mincost; - if (_low) { - const auto [r_min, r_max] = _low->compute_min_max(); - mincost = std::min(r_min, mincost); - maxcost = std::max(r_max, maxcost); - } - if (_high) { - const auto [r_min, r_max] = _high->compute_min_max(); - mincost = std::min(r_min, mincost); - maxcost = std::max(r_max, maxcost); - } +std::pair SimpleTree::node_t::compute_min_max() const +{ + double mincost = std::numeric_limits::infinity(); + double maxcost = -mincost; + if (_low) { + const auto [r_min, r_max] = _low->compute_min_max(); + mincost = std::min(r_min, mincost); + maxcost = std::max(r_max, maxcost); + } + if (_high) { + const auto [r_min, r_max] = _high->compute_min_max(); + mincost = std::min(r_min, mincost); + maxcost = std::max(r_max, maxcost); + } - if (is_leaf() && !std::isinf(_cost)) { - mincost = std::min(_cost, mincost); - maxcost = std::max(_cost, maxcost); - } - return std::make_pair(mincost, maxcost); + if (is_leaf() && !std::isinf(_cost)) { + mincost = std::min(_cost, mincost); + maxcost = std::max(_cost, maxcost); + } + return std::make_pair(mincost, maxcost); } -void SimpleTree::node_t::action_nodes(std::vector &nodes, - uint32_t low, uint32_t high, - uint32_t varid) { - if (_var != varid) { - if (!is_leaf() || !(std::isnan(_cost) || std::isinf(_cost))) { - auto ptr = shared_from_this(); - auto lb = std::lower_bound(nodes.begin(), nodes.end(), ptr); - if (lb == nodes.end() || lb->get() != this) - nodes.insert(lb, ptr); +void SimpleTree::node_t::action_nodes(std::vector& nodes, uint32_t low, uint32_t high, uint32_t varid) +{ + if (_var != varid) { + if (!is_leaf() || !(std::isnan(_cost) || std::isinf(_cost))) { + auto ptr = shared_from_this(); + auto lb = std::lower_bound(nodes.begin(), nodes.end(), ptr); + if (lb == nodes.end() || lb->get() != this) + nodes.insert(lb, ptr); + } + } else { + if (_low) + _low->action_nodes(nodes, low, _limit, varid); + if (_high) + _high->action_nodes(nodes, _limit + 1, high, varid); } - } else { - if (_low) - _low->action_nodes(nodes, low, _limit, varid); - if (_high) - _high->action_nodes(nodes, _limit + 1, high, varid); - } } -void SimpleTree::node_t::subsumption_reduction(bool minimization, - SimpleTree &parent) { - if (_var < parent._statevars.size()) { - if (_low) - _low->subsumption_reduction(minimization, parent); - if (_high) - _high->subsumption_reduction(minimization, parent); - } else { - if (_var != parent._statevars.size()) { - return; - } - size_t prev_vals = std::numeric_limits::max(); - size_t p = 0; - while (true) { - ++p; - bool outer = false; - std::vector nodes; - action_nodes(nodes, 0, parent._actions.size() - 1, - parent._statevars.size()); - auto val = std::numeric_limits::infinity(); - if (!minimization) - val *= -1; - auto best = val; - auto worst = -val; - for (const auto &n : nodes) { - auto [min, max] = n->compute_min_max(); - if (std::isinf(min)) - continue; - assert(!std::isinf(max)); - if (minimization) { - val = std::min(val, max); - best = std::min(best, min); - worst = std::max(worst, max); - } else { - val = std::max(val, min); - best = std::max(best, max); - worst = std::min(worst, min); - } - } - std::vector> bounds( - parent._pointvars.size(), - std::make_pair(-std::numeric_limits::infinity(), - std::numeric_limits::infinity())); - for (const auto &n : nodes) { - outer |= n->check_tiles(n.get(), nodes, bounds, val, best, worst, - minimization, parent._statevars.size() + 1); - } - std::set> values; - { - auto nodes = std::vector(parent._actions.size()); - action_nodes(nodes, 0, parent._actions.size() - 1, - parent._statevars.size()); - for (auto &n : nodes) { - if (n == nullptr) - continue; - get_ranks(values, n.get()); +void SimpleTree::node_t::subsumption_reduction(bool minimization, SimpleTree& parent) +{ + if (_var < parent._statevars.size()) { + if (_low) + _low->subsumption_reduction(minimization, parent); + if (_high) + _high->subsumption_reduction(minimization, parent); + } else { + if (_var != parent._statevars.size()) { + return; } - std::unordered_map replace; - for (auto &e : values) { - if (replace.count(e.first) > 0) - continue; - double id = replace.size(); - replace[e.first] = id; - // std::cerr << e.first << " : " << e.second << std::endl; + size_t prev_vals = std::numeric_limits::max(); + size_t p = 0; + while (true) { + ++p; + bool outer = false; + std::vector nodes; + action_nodes(nodes, 0, parent._actions.size() - 1, parent._statevars.size()); + auto val = std::numeric_limits::infinity(); + if (!minimization) + val *= -1; + auto best = val; + auto worst = -val; + for (const auto& n : nodes) { + auto [min, max] = n->compute_min_max(); + if (std::isinf(min)) + continue; + assert(!std::isinf(max)); + if (minimization) { + val = std::min(val, max); + best = std::min(best, min); + worst = std::max(worst, max); + } else { + val = std::max(val, min); + best = std::max(best, max); + worst = std::min(worst, min); + } + } + std::vector> bounds( + parent._pointvars.size(), + std::make_pair(-std::numeric_limits::infinity(), std::numeric_limits::infinity())); + for (const auto& n : nodes) { + outer |= n->check_tiles(n.get(), nodes, bounds, val, best, worst, minimization, + parent._statevars.size() + 1); + } + std::set> values; + { + auto nodes = std::vector(parent._actions.size()); + action_nodes(nodes, 0, parent._actions.size() - 1, parent._statevars.size()); + for (auto& n : nodes) { + if (n == nullptr) + continue; + get_ranks(values, n.get()); + } + std::unordered_map replace; + for (auto& e : values) { + if (replace.count(e.first) > 0) + continue; + double id = replace.size(); + replace[e.first] = id; + // std::cerr << e.first << " : " << e.second << std::endl; + } + set_ranks(replace); + } + if (!outer && prev_vals <= values.size()) + break; + prev_vals = values.size(); } - set_ranks(replace); - } - if (!outer && prev_vals <= values.size()) - break; - prev_vals = values.size(); } - } } -void SimpleTree::node_t::set_ranks(std::unordered_map &values) { - if (is_leaf()) { - if (std::isinf(_cost) || std::isnan(_cost)) - return; - _cost = values[_cost]; - } else { - if (_low) - _low->set_ranks(values); - if (_high) - _high->set_ranks(values); - } +void SimpleTree::node_t::set_ranks(std::unordered_map& values) +{ + if (is_leaf()) { + if (std::isinf(_cost) || std::isnan(_cost)) + return; + _cost = values[_cost]; + } else { + if (_low) + _low->set_ranks(values); + if (_high) + _high->set_ranks(values); + } } -void SimpleTree::node_t::get_ranks( - std::set> &values, node_t *start) { - if (is_leaf()) { - if (std::isinf(_cost) || std::isnan(_cost)) - return; - values.emplace(_cost, start); - } else { - if (_low) - _low->get_ranks(values, start); - if (_high) - _high->get_ranks(values, start); - } +void SimpleTree::node_t::get_ranks(std::set>& values, node_t* start) +{ + if (is_leaf()) { + if (std::isinf(_cost) || std::isnan(_cost)) + return; + values.emplace(_cost, start); + } else { + if (_low) + _low->get_ranks(values, start); + if (_high) + _high->get_ranks(values, start); + } } -bool SimpleTree::node_t::subsumes( - const std::vector> &bounds, - std::vector> &obounds, const double val, - const bool minimization, size_t offset, double &best, - std::pair &closest) const { - if (is_leaf()) { - // TODO: continue in other trees here, maybe combination is - // enough to prove subsumption! - if (_cost <= val) - closest.first = std::max(_cost, closest.first); - if (_cost >= val) - closest.second = std::min(_cost, closest.second); - if (minimization) - best = std::min(_cost, best); - else - best = std::max(_cost, best); - if (minimization && _cost <= val) - return true; - if (!minimization && _cost >= val) - return true; - return false; - } else { - /*if(minimization && _maxcost <= val) - { - best = std::min(_maxcost, best); - return true; +bool SimpleTree::node_t::subsumes(const std::vector>& bounds, + std::vector>& obounds, const double val, + const bool minimization, size_t offset, double& best, + std::pair& closest) const +{ + if (is_leaf()) { + // TODO: continue in other trees here, maybe combination is + // enough to prove subsumption! + if (_cost <= val) + closest.first = std::max(_cost, closest.first); + if (_cost >= val) + closest.second = std::min(_cost, closest.second); + if (minimization) + best = std::min(_cost, best); + else + best = std::max(_cost, best); + if (minimization && _cost <= val) + return true; + if (!minimization && _cost >= val) + return true; + return false; + } else { + /*if(minimization && _maxcost <= val) + { + best = std::min(_maxcost, best); + return true; + } + else if(!minimization && _mincost >= val) + { + best = std::max(_mincost, best); + return true; + }*/ + assert(std::isinf(_cost)); + assert(_var >= offset); + assert(bounds[_var - offset].first <= bounds[_var - offset].second); + double org = _limit; + std::swap(obounds[_var - offset].second, org); + bool res = true; + if (bounds[_var - offset].first <= _limit && _low) + if (!_low->subsumes(bounds, obounds, val, minimization, offset, best, closest)) + res = false; + std::swap(obounds[_var - offset].second, org); + std::swap(obounds[_var - offset].first, org); + if (bounds[_var - offset].second > _limit && _high) + if (!_high->subsumes(bounds, obounds, val, minimization, offset, best, closest)) + res = false; + std::swap(obounds[_var - offset].first, org); + return res; } - else if(!minimization && _mincost >= val) - { - best = std::max(_mincost, best); - return true; - }*/ - assert(std::isinf(_cost)); - assert(_var >= offset); - assert(bounds[_var - offset].first <= bounds[_var - offset].second); - double org = _limit; - std::swap(obounds[_var - offset].second, org); - bool res = true; - if (bounds[_var - offset].first <= _limit && _low) - if (!_low->subsumes(bounds, obounds, val, minimization, offset, best, - closest)) - res = false; - std::swap(obounds[_var - offset].second, org); - std::swap(obounds[_var - offset].first, org); - if (bounds[_var - offset].second > _limit && _high) - if (!_high->subsumes(bounds, obounds, val, minimization, offset, best, - closest)) - res = false; - std::swap(obounds[_var - offset].first, org); - return res; - } } -bool SimpleTree::node_t::check_tiles( - node_t *start, std::vector &nodes, - std::vector> &bounds, double val, double best_val, - double worst_val, bool minimization, size_t offset) { - auto obounds = bounds; - if (is_leaf()) { - double best = - (minimization ? 1 : -1) * std::numeric_limits::infinity(); - _cost_bounds.first = -std::numeric_limits::infinity(); - _cost_bounds.second = std::numeric_limits::infinity(); - if (_cost == - (minimization ? 1 : -1) * std::numeric_limits::infinity()) - return false; - for (auto &n : nodes) { - if (n.get() == start || n == nullptr) - continue; - if (n->subsumes(bounds, obounds, _cost, minimization, offset, best, - _cost_bounds)) { - _cost = - (minimization ? 1 : -1) * std::numeric_limits::infinity(); +bool SimpleTree::node_t::check_tiles(node_t* start, std::vector& nodes, + std::vector>& bounds, double val, double best_val, + double worst_val, bool minimization, size_t offset) +{ + auto obounds = bounds; + if (is_leaf()) { + double best = (minimization ? 1 : -1) * std::numeric_limits::infinity(); + _cost_bounds.first = -std::numeric_limits::infinity(); _cost_bounds.second = std::numeric_limits::infinity(); - return true; - } - assert(best <= best_val || minimization); // doing max - assert(best >= best_val || !minimization); // doing min - } - assert(best <= best_val || minimization); // doing max - assert(best >= best_val || !minimization); // doing min - if ((minimization && best >= _cost) || (!minimization && best <= _cost)) { - // std::cerr << "BEST " << _cost << " MV " << minval << " BND " << - // _cost_bounds.first << ": " << _cost_bounds.second << std::endl; - _cost = best_val; - } else { - if (!std::isinf(_cost_bounds.first)) { - _cost = std::nextafter(_cost_bounds.first, - std::numeric_limits::infinity()); - if (!std::isinf(_cost_bounds.second) && - std::ceil(_cost) < _cost_bounds.second) - _cost = std::ceil(_cost); - } else if (!std::isinf(_cost_bounds.second)) { - _cost = std::nextafter(_cost_bounds.second, - -std::numeric_limits::infinity()); - } + if (_cost == (minimization ? 1 : -1) * std::numeric_limits::infinity()) + return false; + for (auto& n : nodes) { + if (n.get() == start || n == nullptr) + continue; + if (n->subsumes(bounds, obounds, _cost, minimization, offset, best, _cost_bounds)) { + _cost = (minimization ? 1 : -1) * std::numeric_limits::infinity(); + _cost_bounds.second = std::numeric_limits::infinity(); + return true; + } + assert(best <= best_val || minimization); // doing max + assert(best >= best_val || !minimization); // doing min + } + assert(best <= best_val || minimization); // doing max + assert(best >= best_val || !minimization); // doing min + if ((minimization && best >= _cost) || (!minimization && best <= _cost)) { + // std::cerr << "BEST " << _cost << " MV " << minval << " BND " << + // _cost_bounds.first << ": " << _cost_bounds.second << std::endl; + _cost = best_val; + } else { + if (!std::isinf(_cost_bounds.first)) { + _cost = std::nextafter(_cost_bounds.first, std::numeric_limits::infinity()); + if (!std::isinf(_cost_bounds.second) && std::ceil(_cost) < _cost_bounds.second) + _cost = std::ceil(_cost); + } else if (!std::isinf(_cost_bounds.second)) { + _cost = std::nextafter(_cost_bounds.second, -std::numeric_limits::infinity()); + } + } + return false; } - return false; - } - if (!is_leaf()) { - double org = _limit; - auto &bnd = bounds[_var - offset]; - node_ptr switchnode = nullptr; - if (bnd.second <= _limit) { - switchnode = _low; - } - if (bnd.first > _limit) { - switchnode = _high; - } - /* std::cerr << "HANDLE " << std::endl; - print(std::cerr); - std::cerr << std::endl;*/ - bool res = false; - if (switchnode) { - _var = switchnode->_var; - _limit = switchnode->_limit; - _cost = switchnode->_cost; - _low = switchnode->_low; - _high = switchnode->_high; - return check_tiles(start, nodes, bounds, val, best_val, worst_val, - minimization, offset); - } else { - std::swap(org, bnd.second); - if (_low) - res |= _low->check_tiles(start, nodes, bounds, val, best_val, worst_val, - minimization, offset); - std::swap(org, bnd.second); - std::swap(org, bnd.first); - if (_high) - res |= _high->check_tiles(start, nodes, bounds, val, best_val, - worst_val, minimization, offset); - std::swap(org, bnd.first); - } - _cost_bounds.first = - std::max(_low->_cost_bounds.first, _high->_cost_bounds.first); - _cost_bounds.second = - std::min(_low->_cost_bounds.second, _high->_cost_bounds.second); - // if both are leafs and can be merged. - if (_low->is_leaf() && _high->is_leaf() && _low->cost_intersect(*_high) && - std::isinf(_low->_cost) == std::isinf(_high->_cost)) { - /*std::cerr << "[" << _low->_cost_bounds.first << ", " << - _low->_cost_bounds.second << "]" << std::endl; std::cerr << "[" << - _high->_cost_bounds.first << ", " << _high->_cost_bounds.second << "]" << - std::endl; std::cerr << "[" << _cost_bounds.first << ", " << - _cost_bounds.second << "]" << std::endl; std::cerr << "FIXING " << - std::endl; print(std::cerr); std::cerr << std::endl;*/ - _cost = _low->midcost(*_high, best_val, worst_val); - // std::cerr << "NC " << _cost << std::endl; - _low = nullptr; - _high = nullptr; - _var = std::numeric_limits::max(); - _limit = std::numeric_limits::infinity(); - return true; - // std::cerr << "REMO 2" << std::endl; - } + if (!is_leaf()) { + double org = _limit; + auto& bnd = bounds[_var - offset]; + node_ptr switchnode = nullptr; + if (bnd.second <= _limit) { + switchnode = _low; + } + if (bnd.first > _limit) { + switchnode = _high; + } + /* std::cerr << "HANDLE " << std::endl; + print(std::cerr); + std::cerr << std::endl;*/ + bool res = false; + if (switchnode) { + _var = switchnode->_var; + _limit = switchnode->_limit; + _cost = switchnode->_cost; + _low = switchnode->_low; + _high = switchnode->_high; + return check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); + } else { + std::swap(org, bnd.second); + if (_low) + res |= _low->check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); + std::swap(org, bnd.second); + std::swap(org, bnd.first); + if (_high) + res |= _high->check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); + std::swap(org, bnd.first); + } + _cost_bounds.first = std::max(_low->_cost_bounds.first, _high->_cost_bounds.first); + _cost_bounds.second = std::min(_low->_cost_bounds.second, _high->_cost_bounds.second); + // if both are leafs and can be merged. + if (_low->is_leaf() && _high->is_leaf() && _low->cost_intersect(*_high) && + std::isinf(_low->_cost) == std::isinf(_high->_cost)) { + /*std::cerr << "[" << _low->_cost_bounds.first << ", " << + _low->_cost_bounds.second << "]" << std::endl; std::cerr << "[" << + _high->_cost_bounds.first << ", " << _high->_cost_bounds.second << "]" << + std::endl; std::cerr << "[" << _cost_bounds.first << ", " << + _cost_bounds.second << "]" << std::endl; std::cerr << "FIXING " << + std::endl; print(std::cerr); std::cerr << std::endl;*/ + _cost = _low->midcost(*_high, best_val, worst_val); + // std::cerr << "NC " << _cost << std::endl; + _low = nullptr; + _high = nullptr; + _var = std::numeric_limits::max(); + _limit = std::numeric_limits::infinity(); + return true; + // std::cerr << "REMO 2" << std::endl; + } - { - // check if one is leaf and sibiling has leaf on same side which - // can be merged: - double nc = 0; - node_ptr node, other; - if (!_high->is_leaf() && _high->_var == _var && - _high->_low->cost_intersect(*_low) && _low->is_leaf() && - _high->_low->is_leaf() && - std::isinf(_low->_cost) == std::isinf(_high->_low->_cost)) { - node = _high; - other = _high->_low; - nc = other->midcost(*_low, best_val, worst_val); - assert(!std::isnan(nc)); - } else if (!_low->is_leaf() && _low->_var == _var && - _low->_high->cost_intersect(*_high) && _high->is_leaf() && - _low->_high->is_leaf() && - std::isinf(_high->_cost) == std::isinf(_low->_high->_cost)) { - node = _low; - other = _low->_high; - nc = other->midcost(*_high, best_val, worst_val); - assert(!std::isnan(nc)); - } + { + // check if one is leaf and sibiling has leaf on same side which + // can be merged: + double nc = 0; + node_ptr node, other; + if (!_high->is_leaf() && _high->_var == _var && _high->_low->cost_intersect(*_low) && _low->is_leaf() && + _high->_low->is_leaf() && std::isinf(_low->_cost) == std::isinf(_high->_low->_cost)) { + node = _high; + other = _high->_low; + nc = other->midcost(*_low, best_val, worst_val); + assert(!std::isnan(nc)); + } else if (!_low->is_leaf() && _low->_var == _var && _low->_high->cost_intersect(*_high) && + _high->is_leaf() && _low->_high->is_leaf() && + std::isinf(_high->_cost) == std::isinf(_low->_high->_cost)) { + node = _low; + other = _low->_high; + nc = other->midcost(*_high, best_val, worst_val); + assert(!std::isnan(nc)); + } - if (node) { - _var = node->_var; - _limit = node->_limit; - _low = node->_low; - _high = node->_high; - other->_cost = nc; - // we could compute the values on the fly; but no time currently - check_tiles(start, nodes, bounds, val, best_val, worst_val, - minimization, offset); - return true; - } + if (node) { + _var = node->_var; + _limit = node->_limit; + _low = node->_low; + _high = node->_high; + other->_cost = nc; + // we could compute the values on the fly; but no time currently + check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); + return true; + } - if (!_low->is_leaf() && !_high->is_leaf() && _low->_var == _high->_var && - _low->_var == _var && _low->_high->is_leaf() && - _high->_low->is_leaf() && _low->_high->cost_intersect(*_high->_low) && - std::isinf(_low->_high->_cost) == std::isinf(_high->_low->_cost)) { - assert(_high->_low->is_leaf()); - assert(_low->_high->is_leaf()); - _high->_low->_cost = - _low->_high->midcost(*_high->_low, best_val, worst_val); - _var = _low->_var; - _limit = _low->_limit; - _low = _low->_low; - // we could compute the values on the fly; but no time currently - check_tiles(start, nodes, bounds, val, best_val, worst_val, - minimization, offset); - return true; - } + if (!_low->is_leaf() && !_high->is_leaf() && _low->_var == _high->_var && _low->_var == _var && + _low->_high->is_leaf() && _high->_low->is_leaf() && _low->_high->cost_intersect(*_high->_low) && + std::isinf(_low->_high->_cost) == std::isinf(_high->_low->_cost)) { + assert(_high->_low->is_leaf()); + assert(_low->_high->is_leaf()); + _high->_low->_cost = _low->_high->midcost(*_high->_low, best_val, worst_val); + _var = _low->_var; + _limit = _low->_limit; + _low = _low->_low; + // we could compute the values on the fly; but no time currently + check_tiles(start, nodes, bounds, val, best_val, worst_val, minimization, offset); + return true; + } + } } - } - return false; + return false; } -bool SimpleTree::node_t::cost_intersect(const node_t &other) const { - return _cost_bounds.first < other._cost_bounds.second && - other._cost_bounds.first < _cost_bounds.second; +bool SimpleTree::node_t::cost_intersect(const node_t& other) const +{ + return _cost_bounds.first < other._cost_bounds.second && other._cost_bounds.first < _cost_bounds.second; } -double SimpleTree::node_t::midcost(const node_t &other, double minval, - double maxval) const { - if (_cost == other._cost) - return _cost; - assert(std::isinf(_cost) == std::isinf(other._cost)); - auto low = std::min(other._cost_bounds.second, _cost_bounds.second); - auto high = std::max(other._cost_bounds.first, _cost_bounds.first); - double c = std::ceil(low); - if (std::isinf(low)) - c = minval; - else if (std::isinf(high)) - c = maxval; - else { - c = std::round(low + high / 2); - if (c <= low || c >= high) - c = (low + high) / 2; - } - return c; +double SimpleTree::node_t::midcost(const node_t& other, double minval, double maxval) const +{ + if (_cost == other._cost) + return _cost; + assert(std::isinf(_cost) == std::isinf(other._cost)); + auto low = std::min(other._cost_bounds.second, _cost_bounds.second); + auto high = std::max(other._cost_bounds.first, _cost_bounds.first); + double c = std::ceil(low); + if (std::isinf(low)) + c = minval; + else if (std::isinf(high)) + c = maxval; + else { + c = std::round(low + high / 2); + if (c <= low || c >= high) + c = (low + high) / 2; + } + return c; } -void SimpleTree::node_t::insert(std::vector &key, json &tree, - size_t action, SimpleTree &parent, - size_t prefix, bool minimize, double accuracy, - std::vector &exactness) { - if ((tree.is_number() || tree.is_string()) && key.size() < prefix) { - if (is_leaf()) { - auto nc = std::numeric_limits::infinity(); - if (!minimize) - nc *= -1; - if (tree.is_number()) - nc = tree.get(); - if (minimize) - _cost = std::isinf(_cost) ? nc : std::min(nc, _cost); - else - _cost = std::isinf(_cost) ? nc : std::max(nc, _cost); - if (accuracy != 0) - _cost = std::round(_cost / accuracy) * accuracy; - } else { - assert(false); - _low->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - } - consistent(key.size() + 1); - return; - } else if (is_leaf()) { - if (std::isinf(_limit)) { - // assert(std::isinf(_cost)); - assert(_var == std::numeric_limits::max()); - _low = std::make_shared(); - _high = std::make_shared(); - _low->_cost = _high->_cost = - (minimize ? 1 : -1) * std::numeric_limits::infinity(); - if (!std::isinf(_cost)) { - std::swap(_low->_cost, _cost); - _high->_cost = _low->_cost; - } +void SimpleTree::node_t::insert(std::vector& key, json& tree, size_t action, SimpleTree& parent, size_t prefix, + bool minimize, double accuracy, std::vector& exactness) +{ + if ((tree.is_number() || tree.is_string()) && key.size() < prefix) { + if (is_leaf()) { + auto nc = std::numeric_limits::infinity(); + if (!minimize) + nc *= -1; + if (tree.is_number()) + nc = tree.get(); + if (minimize) + _cost = std::isinf(_cost) ? nc : std::min(nc, _cost); + else + _cost = std::isinf(_cost) ? nc : std::max(nc, _cost); + if (accuracy != 0) + _cost = std::round(_cost / accuracy) * accuracy; + } else { + assert(false); + _low->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + } + consistent(key.size() + 1); + return; + } else if (is_leaf()) { + if (std::isinf(_limit)) { + // assert(std::isinf(_cost)); + assert(_var == std::numeric_limits::max()); + _low = std::make_shared(); + _high = std::make_shared(); + _low->_cost = _high->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); + if (!std::isinf(_cost)) { + std::swap(_low->_cost, _cost); + _high->_cost = _low->_cost; + } - _low->_parent = _high->_parent = this; - if (prefix == key.size()) { - _var = prefix; - _limit = action; - } else if (prefix < key.size()) { - _var = prefix; - _limit = key[prefix]; - } else { - const auto &tree_var = tree["var"]; - _var = key.size() + 1 + tree_var.get(); - auto ivar = tree_var.get(); - _limit = tree["bound"].get(); - if (exactness.size() > ivar && exactness[ivar] != 0) - _limit = std::round(_limit / exactness[ivar]) * exactness[ivar]; - } - consistent(key.size() + 1); - if (prefix <= key.size()) - _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, - exactness); - else { - _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, - accuracy, exactness); - _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, - accuracy, exactness); + _low->_parent = _high->_parent = this; + if (prefix == key.size()) { + _var = prefix; + _limit = action; + } else if (prefix < key.size()) { + _var = prefix; + _limit = key[prefix]; + } else { + const auto& tree_var = tree["var"]; + _var = key.size() + 1 + tree_var.get(); + auto ivar = tree_var.get(); + _limit = tree["bound"].get(); + if (exactness.size() > ivar && exactness[ivar] != 0) + _limit = std::round(_limit / exactness[ivar]) * exactness[ivar]; + } + consistent(key.size() + 1); + if (prefix <= key.size()) + _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, exactness); + else { + _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, accuracy, exactness); + _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, accuracy, exactness); + consistent(key.size() + 1); + } + } else { + assert(!std::isinf(_cost)); + assert(_var != std::numeric_limits::max()); + assert(false); + } consistent(key.size() + 1); - } - } else { - assert(!std::isinf(_cost)); - assert(_var != std::numeric_limits::max()); - assert(false); + return; } - consistent(key.size() + 1); - return; - } - if (prefix > key.size()) { - - if (size_t var = tree["var"].get() + 1 + key.size(); - var != _var) { - //_parent->replace(this, key, tree, action, parent, prefix); - assert(false); - } else { - size_t ivar = tree["var"].get(); - double bound = tree["bound"].get(); - if (exactness.size() > ivar && exactness[ivar] != 0) { - bound = std::round(bound / exactness[ivar]) * exactness[ivar]; - } - if (_limit == bound) { - _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, - accuracy, exactness); - _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, - accuracy, exactness); - } else { - if (_limit > bound) { - _low->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, - accuracy, exactness); + if (prefix > key.size()) { + if (size_t var = tree["var"].get() + 1 + key.size(); var != _var) { + //_parent->replace(this, key, tree, action, parent, prefix); + assert(false); } else { - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, - accuracy, exactness); + size_t ivar = tree["var"].get(); + double bound = tree["bound"].get(); + if (exactness.size() > ivar && exactness[ivar] != 0) { + bound = std::round(bound / exactness[ivar]) * exactness[ivar]; + } + if (_limit == bound) { + _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, accuracy, exactness); + _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, accuracy, exactness); + } else { + if (_limit > bound) { + _low->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + _high->insert(key, tree["high"], action, parent, prefix + 1, minimize, accuracy, exactness); + } else { + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + _low->insert(key, tree["low"], action, parent, prefix + 1, minimize, accuracy, exactness); + } + } } - } - } - consistent(key.size() + 1); - } else if (prefix == key.size()) { - if (_var != prefix) { - assert(false); - } else { - consistent(key.size() + 1); - if (_limit == action) { - _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, - exactness); consistent(key.size() + 1); - } else { - assert(_low->_var > prefix); - if (_limit > action) { - auto tmp = std::make_shared(); - tmp->_low = std::make_shared(); - tmp->_cost = tmp->_low->_cost = - (minimize ? 1 : -1) * std::numeric_limits::infinity(); - tmp->_high = shared_from_this(); - tmp->_limit = action; - tmp->_var = prefix; - if (_parent) { - tmp->_parent = _parent; - if (_parent->_high.get() == this) - _parent->_high = tmp; - else - _parent->_low = tmp; - } else { - parent._root = tmp; - } - tmp->_low->_parent = tmp->_high->_parent = tmp.get(); - tmp->consistent(key.size() + 1); - tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - } else if (_high->_var == prefix && _high->_limit < action) { - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - consistent(key.size() + 1); - } else if (_high->_var > prefix) { - assert(std::isinf(_high->_cost)); - _high->_low = std::make_shared(); - _high->_high = std::make_shared(); - _high->_low->_cost = _high->_high->_cost = - (minimize ? 1 : -1) * std::numeric_limits::infinity(); - _high->_limit = action; - _high->_var = prefix; - _high->_low->_parent = _high.get(); - _high->_high->_parent = _high.get(); - _high->_parent = this; - _high->consistent(key.size() + 1); - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - consistent(key.size() + 1); - } else if (_high->_var == prefix && _high->_limit > action) { - auto tmp = std::make_shared(); - tmp->_low = std::make_shared(); - tmp->_low->_cost = tmp->_cost = - (minimize ? 1 : -1) * std::numeric_limits::infinity(); - tmp->_parent = this; - tmp->_high = _high; - _high = tmp; - tmp->_low->_parent = tmp.get(); - tmp->_high->_parent = tmp.get(); - tmp->_var = prefix; - tmp->_limit = action; - tmp->consistent(key.size() + 1); - tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - consistent(key.size() + 1); - } else if (_high->_var == prefix && _high->_limit == action) { - _high->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); + } else if (prefix == key.size()) { + if (_var != prefix) { + assert(false); } else { - assert(false); + consistent(key.size() + 1); + if (_limit == action) { + _low->insert(key, tree, action, parent, prefix + 1, minimize, accuracy, exactness); + consistent(key.size() + 1); + } else { + assert(_low->_var > prefix); + if (_limit > action) { + auto tmp = std::make_shared(); + tmp->_low = std::make_shared(); + tmp->_cost = tmp->_low->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); + tmp->_high = shared_from_this(); + tmp->_limit = action; + tmp->_var = prefix; + if (_parent) { + tmp->_parent = _parent; + if (_parent->_high.get() == this) + _parent->_high = tmp; + else + _parent->_low = tmp; + } else { + parent._root = tmp; + } + tmp->_low->_parent = tmp->_high->_parent = tmp.get(); + tmp->consistent(key.size() + 1); + tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + } else if (_high->_var == prefix && _high->_limit < action) { + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + consistent(key.size() + 1); + } else if (_high->_var > prefix) { + assert(std::isinf(_high->_cost)); + _high->_low = std::make_shared(); + _high->_high = std::make_shared(); + _high->_low->_cost = _high->_high->_cost = + (minimize ? 1 : -1) * std::numeric_limits::infinity(); + _high->_limit = action; + _high->_var = prefix; + _high->_low->_parent = _high.get(); + _high->_high->_parent = _high.get(); + _high->_parent = this; + _high->consistent(key.size() + 1); + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + consistent(key.size() + 1); + } else if (_high->_var == prefix && _high->_limit > action) { + auto tmp = std::make_shared(); + tmp->_low = std::make_shared(); + tmp->_low->_cost = tmp->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); + tmp->_parent = this; + tmp->_high = _high; + _high = tmp; + tmp->_low->_parent = tmp.get(); + tmp->_high->_parent = tmp.get(); + tmp->_var = prefix; + tmp->_limit = action; + tmp->consistent(key.size() + 1); + tmp->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + consistent(key.size() + 1); + } else if (_high->_var == prefix && _high->_limit == action) { + _high->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + } else { + assert(false); + } + } } - } - } - consistent(key.size() + 1); - } else if (prefix < key.size()) { - if (_var != prefix) { - //_parent->replace(this, key, tree, action, parent, prefix); - assert(false); - } else { - if (_limit == key[prefix]) { - _low->insert(key, tree, action, parent, - prefix + (_low->_var != _var ? 1 : 0), minimize, accuracy, - exactness); - } else { - const auto b = _limit < key[prefix]; - auto branch = (*this)[b]; - if (branch->_var != prefix) { - // we need to inject a node here - auto next = std::make_shared(); - (*next)[false] = std::make_shared(); - (*next)[false]->_cost = next->_cost = - (minimize ? 1 : -1) * std::numeric_limits::infinity(); - next->_parent = this; - next->_var = prefix; - next->_limit = key[prefix]; - // example - // original [0,10] | [11,20] made for element 10, - // we inject element 7, so we now get - // ([0-7] | [8-10] ) | [11,20 - // where the original low is moved to the high - // of the newly created node - (*next)[true] = branch; - (*this)[b] = next; - (*next)[true]->_parent = next.get(); - (*next)[false]->_parent = next.get(); - next->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - } else - branch->insert(key, tree, action, parent, prefix, minimize, accuracy, - exactness); - } + consistent(key.size() + 1); + } else if (prefix < key.size()) { + if (_var != prefix) { + //_parent->replace(this, key, tree, action, parent, prefix); + assert(false); + } else { + if (_limit == key[prefix]) { + _low->insert(key, tree, action, parent, prefix + (_low->_var != _var ? 1 : 0), minimize, accuracy, + exactness); + } else { + const auto b = _limit < key[prefix]; + auto branch = (*this)[b]; + if (branch->_var != prefix) { + // we need to inject a node here + auto next = std::make_shared(); + (*next)[false] = std::make_shared(); + (*next)[false]->_cost = next->_cost = (minimize ? 1 : -1) * std::numeric_limits::infinity(); + next->_parent = this; + next->_var = prefix; + next->_limit = key[prefix]; + // example + // original [0,10] | [11,20] made for element 10, + // we inject element 7, so we now get + // ([0-7] | [8-10] ) | [11,20 + // where the original low is moved to the high + // of the newly created node + (*next)[true] = branch; + (*this)[b] = next; + (*next)[true]->_parent = next.get(); + (*next)[false]->_parent = next.get(); + next->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + } else + branch->insert(key, tree, action, parent, prefix, minimize, accuracy, exactness); + } + } + consistent(key.size() + 1); } consistent(key.size() + 1); - } - consistent(key.size() + 1); } -void SimpleTree::node_t::consistent(size_t prefix) const { - return; +void SimpleTree::node_t::consistent(size_t prefix) const +{ + return; #ifndef NDEBUG - assert(_parent != this); - assert(_parent == nullptr || _parent->_low.get() == this || - _parent->_high.get() == this); - assert(_low == nullptr || _low->_parent == this); - assert(_high == nullptr || _high->_parent == this); - if (_var == prefix - 1 && _parent && _parent->_var == _var) { - assert(_parent->_limit < _limit); - } - if (_var < prefix) - assert(_parent == nullptr || _parent->_var <= _var); - if (_low) - _low->consistent(prefix); - if (_high) - _high->consistent(prefix); + assert(_parent != this); + assert(_parent == nullptr || _parent->_low.get() == this || _parent->_high.get() == this); + assert(_low == nullptr || _low->_parent == this); + assert(_high == nullptr || _high->_parent == this); + if (_var == prefix - 1 && _parent && _parent->_var == _var) { + assert(_parent->_limit < _limit); + } + if (_var < prefix) + assert(_parent == nullptr || _parent->_var <= _var); + if (_low) + _low->consistent(prefix); + if (_high) + _high->consistent(prefix); #endif } -double SimpleTree::value(const double *disc, const double *cont, - uint32_t action) const { - if (_root) - return _root->value(disc, cont, action, _statevars.size()); - return std::numeric_limits::infinity(); +double SimpleTree::value(const double* disc, const double* cont, uint32_t action) const +{ + if (_root) + return _root->value(disc, cont, action, _statevars.size()); + return std::numeric_limits::infinity(); } -double SimpleTree::node_t::value(const double *disc, const double *cont, - uint32_t action, size_t ndisc) const { - if (is_leaf()) - return _cost; - if (_var < ndisc) { - if (disc[_var] <= _limit) - return _low ? _low->value(disc, cont, action, ndisc) : _cost; - return _high ? _high->value(disc, cont, action, ndisc) : _cost; - } - if (_var == ndisc) { - if (action <= _limit) - return _low ? _low->value(disc, cont, action, ndisc) : _cost; +double SimpleTree::node_t::value(const double* disc, const double* cont, uint32_t action, size_t ndisc) const +{ + if (is_leaf()) + return _cost; + if (_var < ndisc) { + if (disc[_var] <= _limit) + return _low ? _low->value(disc, cont, action, ndisc) : _cost; + return _high ? _high->value(disc, cont, action, ndisc) : _cost; + } + if (_var == ndisc) { + if (action <= _limit) + return _low ? _low->value(disc, cont, action, ndisc) : _cost; + return _high ? _high->value(disc, cont, action, ndisc) : _cost; + } + if (cont[_var - (ndisc + 1)] <= _limit) + return _low ? _low->value(disc, cont, action, ndisc) : _cost; return _high ? _high->value(disc, cont, action, ndisc) : _cost; - } - if (cont[_var - (ndisc + 1)] <= _limit) - return _low ? _low->value(disc, cont, action, ndisc) : _cost; - return _high ? _high->value(disc, cont, action, ndisc) : _cost; } -SimpleTree::node_ptr SimpleTree::node_t::simplify(bool make_dd, - nodemap_t &nodemap, - SimpleTree &parent) { - if (_low) - _low = _low->simplify(make_dd, nodemap, parent); - if (_high) - _high = _high->simplify(make_dd, nodemap, parent); - if (_low) - _low->_parent = this; - if (_high) - _high->_parent = this; - if (_var < parent._statevars.size()) { - if (_low && _low->is_leaf() && std::isinf(_low->_cost)) - return _high; - if (_high && _high->is_leaf() && std::isinf(_high->_cost)) - return _low; - } - - assert(_low.get() != this); - assert(_high.get() != this); - if ((_low == nullptr || _low->is_leaf()) && - (_high == nullptr || _high->is_leaf())) { - auto lc = _low ? _low->_cost : _cost; - auto hc = _high ? _high->_cost : _cost; - if (lc == hc) { - _low = nullptr; - _high = nullptr; - _cost = lc; - _limit = std::numeric_limits::infinity(); - _var = std::numeric_limits::max(); - return shared_from_this(); +SimpleTree::node_ptr SimpleTree::node_t::simplify(bool make_dd, nodemap_t& nodemap, SimpleTree& parent) +{ + if (_low) + _low = _low->simplify(make_dd, nodemap, parent); + if (_high) + _high = _high->simplify(make_dd, nodemap, parent); + if (_low) + _low->_parent = this; + if (_high) + _high->_parent = this; + if (_var < parent._statevars.size()) { + if (_low && _low->is_leaf() && std::isinf(_low->_cost)) + return _high; + if (_high && _high->is_leaf() && std::isinf(_high->_cost)) + return _low; } - } - if (std::isinf(_limit) && (_low != nullptr || _high != nullptr)) { - assert(false); - return _low == nullptr ? _high : _low; - } + assert(_low.get() != this); + assert(_high.get() != this); + if ((_low == nullptr || _low->is_leaf()) && (_high == nullptr || _high->is_leaf())) { + auto lc = _low ? _low->_cost : _cost; + auto hc = _high ? _high->_cost : _cost; + if (lc == hc) { + _low = nullptr; + _high = nullptr; + _cost = lc; + _limit = std::numeric_limits::infinity(); + _var = std::numeric_limits::max(); + return shared_from_this(); + } + } - // if comparison on same variable in on child and other child is leaf, then - // if the non-leaf child has a leaf-child in, we can merge. - { - if (_low && _low->is_leaf() && _high && - (!_high->is_leaf() || !std::isinf(_high->_cost)) && - _high->_var == _var && _high->_low && _high->_low->is_leaf() && - _high->_low->_cost == _low->_cost) { - return _high; + if (std::isinf(_limit) && (_low != nullptr || _high != nullptr)) { + assert(false); + return _low == nullptr ? _high : _low; } - if (_high && _high->is_leaf() && _low && - (!_low->is_leaf() || !std::isinf(_low->_cost)) && _low->_var == _var && - _low->_high && _low->_high->is_leaf() && - _low->_high->_cost == _high->_cost) { - return _low; + // if comparison on same variable in on child and other child is leaf, then + // if the non-leaf child has a leaf-child in, we can merge. + { + if (_low && _low->is_leaf() && _high && (!_high->is_leaf() || !std::isinf(_high->_cost)) && + _high->_var == _var && _high->_low && _high->_low->is_leaf() && _high->_low->_cost == _low->_cost) { + return _high; + } + + if (_high && _high->is_leaf() && _low && (!_low->is_leaf() || !std::isinf(_low->_cost)) && _low->_var == _var && + _low->_high && _low->_high->is_leaf() && _low->_high->_cost == _high->_cost) { + return _low; + } } - } - /*if(make_dd) - { - if(_high == nullptr) - return _low; - if(_low == nullptr) - return _high; - if(_low && _parent && !_low->is_leaf() && _high->is_leaf()) - { - auto cost = _high ? _high->_cost : _cost; - auto lcost = _low->_high ? _low->_high->_cost : _low->_cost; - if(cost == lcost && _low->_var == _var) - return _low; - } - if(_high && _parent && !_high->is_leaf() && _low->is_leaf()) - { - auto cost = _low ? _low->_cost : _cost; - auto hcost = _high->_low ? _high->_low->_cost : _high->_cost; - if(cost == hcost && _high->_var == _var) - return _high; - } - }*/ - if (_high != nullptr && _high == _low) { - return _high; - } - if (make_dd) { - auto &ptr = nodemap[*this]; - if (ptr == nullptr) - ptr = shared_from_this(); - return ptr; - } - return shared_from_this(); + /*if(make_dd) + { + if(_high == nullptr) + return _low; + if(_low == nullptr) + return _high; + if(_low && _parent && !_low->is_leaf() && _high->is_leaf()) + { + auto cost = _high ? _high->_cost : _cost; + auto lcost = _low->_high ? _low->_high->_cost : _low->_cost; + if(cost == lcost && _low->_var == _var) + return _low; + } + if(_high && _parent && !_high->is_leaf() && _low->is_leaf()) + { + auto cost = _low ? _low->_cost : _cost; + auto hcost = _high->_low ? _high->_low->_cost : _high->_cost; + if(cost == hcost && _high->_var == _var) + return _high; + } + }*/ + if (_high != nullptr && _high == _low) { + return _high; + } + if (make_dd) { + auto& ptr = nodemap[*this]; + if (ptr == nullptr) + ptr = shared_from_this(); + return ptr; + } + return shared_from_this(); } /*void SimpleTree::node_t::rec_insert(double value, @@ -1015,259 +951,256 @@ bounds[nid].second); if(reset_high) handled[_var].second = false; }*/ -std::ostream &SimpleTree::print(std::ostream &stream) const { - _root->print(stream, 0); - return stream; +std::ostream& SimpleTree::print(std::ostream& stream) const +{ + _root->print(stream, 0); + return stream; } -std::ostream &SimpleTree::node_t::print(std::ostream &out, size_t tabs) const { - for (size_t i = 0; i < tabs; ++i) - out << "\t"; - out << "{\"var\":" << _var << ",\"bound\":" << _limit; - if (!std::isnan(_cost)) { - out << ",\"value\":" << _cost; - } - if (_low) { - out << ",\n"; +std::ostream& SimpleTree::node_t::print(std::ostream& out, size_t tabs) const +{ for (size_t i = 0; i < tabs; ++i) - out << "\t"; - out << "\"low\":\n"; - _low->print(out, tabs + 1); - } - if (_high) { - out << ",\n"; + out << "\t"; + out << "{\"var\":" << _var << ",\"bound\":" << _limit; + if (!std::isnan(_cost)) { + out << ",\"value\":" << _cost; + } + if (_low) { + out << ",\n"; + for (size_t i = 0; i < tabs; ++i) + out << "\t"; + out << "\"low\":\n"; + _low->print(out, tabs + 1); + } + if (_high) { + out << ",\n"; + for (size_t i = 0; i < tabs; ++i) + out << "\t"; + out << "\"high\":\n"; + _high->print(out, tabs + 1); + } + out << "\n"; for (size_t i = 0; i < tabs; ++i) - out << "\t"; - out << "\"high\":\n"; - _high->print(out, tabs + 1); - } - out << "\n"; - for (size_t i = 0; i < tabs; ++i) - out << "\t"; - out << "}"; - return out; + out << "\t"; + out << "}"; + return out; } -std::ostream &SimpleTree::print_c(std::ostream &os, - const std::string &name) const { - if (_root == nullptr) { - os << "double " << name - << "(unsigned int action, const double* disc, const double* vars)\n{\n"; - os << "\treturn 0 ; \n}\n"; - return os; - } - auto _idnode = std::unordered_map{}; - auto _nodeid = std::unordered_map{}; - _idnode[0] = _root.get(); - _nodeid[_root.get()] = 0; +std::ostream& SimpleTree::print_c(std::ostream& os, const std::string& name) const +{ + if (_root == nullptr) { + os << "double " << name << "(unsigned int action, const double* disc, const double* vars)\n{\n"; + os << "\treturn 0 ; \n}\n"; + return os; + } + auto _idnode = std::unordered_map{}; + auto _nodeid = std::unordered_map{}; + _idnode[0] = _root.get(); + _nodeid[_root.get()] = 0; - size_t lid = 0; - while (lid != _nodeid.size()) { - auto n = _idnode[lid]; - if (!n->is_leaf()) { - auto ln = n->_low.get(); - auto hn = n->_high.get(); - if (_nodeid.count(ln) == 0) { - auto id = _idnode.size(); - _idnode[id] = ln; - _nodeid[ln] = id; - } - if (_nodeid.count(hn) == 0) { - auto id = _idnode.size(); - _idnode[id] = hn; - _nodeid[hn] = id; - } + size_t lid = 0; + while (lid != _nodeid.size()) { + auto n = _idnode[lid]; + if (!n->is_leaf()) { + auto ln = n->_low.get(); + auto hn = n->_high.get(); + if (_nodeid.count(ln) == 0) { + auto id = _idnode.size(); + _idnode[id] = ln; + _nodeid[ln] = id; + } + if (_nodeid.count(hn) == 0) { + auto id = _idnode.size(); + _idnode[id] = hn; + _nodeid[hn] = id; + } + } + ++lid; } - ++lid; - } - os << "const int " << name << "_nodes[] = {"; - for (size_t i = 0; i < lid; ++i) { - if (i != 0) - os << ","; - auto n = _idnode[i]; - if (n->is_leaf()) { - os << "-1,-1,-1"; - } else { - os << _nodeid[n->_low.get()] << ","; - os << _nodeid[n->_high.get()] << ","; - os << n->_var; + os << "const int " << name << "_nodes[] = {"; + for (size_t i = 0; i < lid; ++i) { + if (i != 0) + os << ","; + auto n = _idnode[i]; + if (n->is_leaf()) { + os << "-1,-1,-1"; + } else { + os << _nodeid[n->_low.get()] << ","; + os << _nodeid[n->_high.get()] << ","; + os << n->_var; + } } - } - os << "};\n"; - const auto [mmin, mmax] = _root->compute_min_max(); - auto v = is_minimization() ? mmax + 1 : mmin - 1; - os << "const double " << name << "_values[] = {"; - for (size_t i = 0; i < lid; ++i) { - if (i != 0) - os << ","; - auto n = _idnode[i]; - if (n->is_leaf()) { - if (!std::isinf(n->_cost) && !std::isnan(n->_cost)) - os << n->_cost; - else - os << v; - } else { - os << n->_limit; + os << "};\n"; + const auto [mmin, mmax] = _root->compute_min_max(); + auto v = is_minimization() ? mmax + 1 : mmin - 1; + os << "const double " << name << "_values[] = {"; + for (size_t i = 0; i < lid; ++i) { + if (i != 0) + os << ","; + auto n = _idnode[i]; + if (n->is_leaf()) { + if (!std::isinf(n->_cost) && !std::isnan(n->_cost)) + os << n->_cost; + else + os << v; + } else { + os << n->_limit; + } } - } - os << "};\n"; - os << "double " << name - << "(unsigned int action, const double* disc, const double* vars)\n{\n"; - // stream << "\t// Depth = " << _root->depth() << std::endl; - os << "\t// Actions = " << _actions.size() << std::endl; - os << "\t// Disc = " << _statevars.size() << std::endl; - os << "\t// Cont = " << _pointvars.size() << std::endl; - os << "\t// Nodes = " << lid << std::endl; - /*std::unordered_set printed; - std::vector toprint; - if(_root) - _root->print_c_nested(stream, _statevars.size(), 1, toprint, _root); - auto mm = _root->compute_min_max(); - auto v = is_minimization() ? mm.second + 1 : mm.first -1; - stream << "\treturn " << v << ";\n"; - for(auto n : toprint) - { - n->print_c(stream, 0, printed, 1); - stream << "\treturn " << v << ";\n"; - } - stream << "\treturn " << v << ";\n"; - * */ - os << "\tint ins = 0;\n\twhile(true) {\n"; - os << "\t\tint l = " << name << "_nodes[ins*3]; int h = " << name - << "_nodes[1+(ins*3)]; int v = " << name << "_nodes[2+(ins*3)];\n"; - os << "\t\tif(v == -1) return " << name << "_values[ins];\n"; - os << "\t\tdouble val = 0;\n"; - os << "\t\tif(v == " << _statevars.size() << ") val = action;\n"; - os << "\t\telse if(v > " << _statevars.size() << ") val = vars[v-" - << (_statevars.size() + 1) << "];\n"; - os << "\t\telse val = disc[v];\n"; - os << "\t\tif(val <= " << name << "_values[ins])\n"; - os << "\t\t\tins = l;\n"; - os << "\t\telse\n"; - os << "\t\t\tins = h;\n"; - os << "\t}\n"; - os << "\treturn " << v << ";\n"; - os << "}\n"; - return os; -} - -size_t SimpleTree::node_t::depth() const { - if (_low && _high == nullptr) - return 1 + _low->depth(); - if (_high && _low == nullptr) - return 1 + _high->depth(); - if (_low == nullptr && _high == nullptr) - return 0; - return 1 + std::max(_low->depth(), _high->depth()); + os << "};\n"; + os << "double " << name << "(unsigned int action, const double* disc, const double* vars)\n{\n"; + // stream << "\t// Depth = " << _root->depth() << std::endl; + os << "\t// Actions = " << _actions.size() << std::endl; + os << "\t// Disc = " << _statevars.size() << std::endl; + os << "\t// Cont = " << _pointvars.size() << std::endl; + os << "\t// Nodes = " << lid << std::endl; + /*std::unordered_set printed; + std::vector toprint; + if(_root) + _root->print_c_nested(stream, _statevars.size(), 1, toprint, _root); + auto mm = _root->compute_min_max(); + auto v = is_minimization() ? mm.second + 1 : mm.first -1; + stream << "\treturn " << v << ";\n"; + for(auto n : toprint) + { + n->print_c(stream, 0, printed, 1); + stream << "\treturn " << v << ";\n"; + } + stream << "\treturn " << v << ";\n"; + * */ + os << "\tint ins = 0;\n\twhile(true) {\n"; + os << "\t\tint l = " << name << "_nodes[ins*3]; int h = " << name << "_nodes[1+(ins*3)]; int v = " << name + << "_nodes[2+(ins*3)];\n"; + os << "\t\tif(v == -1) return " << name << "_values[ins];\n"; + os << "\t\tdouble val = 0;\n"; + os << "\t\tif(v == " << _statevars.size() << ") val = action;\n"; + os << "\t\telse if(v > " << _statevars.size() << ") val = vars[v-" << (_statevars.size() + 1) << "];\n"; + os << "\t\telse val = disc[v];\n"; + os << "\t\tif(val <= " << name << "_values[ins])\n"; + os << "\t\t\tins = l;\n"; + os << "\t\telse\n"; + os << "\t\t\tins = h;\n"; + os << "\t}\n"; + os << "\treturn " << v << ";\n"; + os << "}\n"; + return os; } -std::ostream & -SimpleTree::node_t::print_c(std::ostream &os, size_t disc, - std::unordered_set &printed, - size_t tabs) const { - - if (printed.count(this) > 0) - return os; - printed.insert(this); - // for(size_t i = 0; i < tabs; ++i) stream << "\t"; - os << "l" << this << ":\n"; - std::vector toprint; - if (is_leaf()) { - // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; - if (!std::isinf(_cost)) - os << "return " << _cost << ";" << std::endl; - else - os << "{}"; - return os; - } - if (std::isinf(_limit)) { - if (_low) - return _low->print_c(os, disc, printed, tabs); - if (_high) - return _high->print_c(os, disc, printed, tabs); - return os; - } - // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; - if (_var == disc) { - os << "if(action <= " << _limit << ") "; - } else { - auto type = disc > _var ? "disc" : "vars"; - auto vid = disc > _var ? _var : _var - (disc + 1); - os << "if(" << type << "[" << vid << "] <= " << _limit << ") "; - } - if (_low) - _low->print_c_nested(os, disc, tabs + 1, toprint, _low); - else { - if (!std::isinf(_cost)) - os << "return " << _cost << ";"; - else - os << "{}"; - } - os << "\n"; - // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; - os << "else "; - if (_high) - _high->print_c_nested(os, disc, tabs + 1, toprint, _high); - else { - if (!std::isinf(_cost)) - os << "return " << _cost << ";"; - else - os << "{}"; - } - os << "\n"; - for (auto n : toprint) - n->print_c(os, disc, printed, tabs); - return os; +size_t SimpleTree::node_t::depth() const +{ + if (_low && _high == nullptr) + return 1 + _low->depth(); + if (_high && _low == nullptr) + return 1 + _high->depth(); + if (_low == nullptr && _high == nullptr) + return 0; + return 1 + std::max(_low->depth(), _high->depth()); } -std::ostream & -SimpleTree::node_t::print_c_nested(std::ostream &os, size_t disc, size_t tabs, - std::vector &toprint, - const node_ptr &node) const { - assert(node.get() == this); - if (is_leaf() && std::isinf(_limit)) { - if (!std::isinf(_cost)) - os << "return " << _cost << ";"; - else - os << "{}"; - return os; - } - if (node.use_count() == 1) { - os << "{\n"; - // for(size_t i = 0; i < tabs; ++i) stream << "\t"; +std::ostream& SimpleTree::node_t::print_c(std::ostream& os, size_t disc, std::unordered_set& printed, + size_t tabs) const +{ + if (printed.count(this) > 0) + return os; + printed.insert(this); + // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + os << "l" << this << ":\n"; + std::vector toprint; + if (is_leaf()) { + // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; + if (!std::isinf(_cost)) + os << "return " << _cost << ";" << std::endl; + else + os << "{}"; + return os; + } + if (std::isinf(_limit)) { + if (_low) + return _low->print_c(os, disc, printed, tabs); + if (_high) + return _high->print_c(os, disc, printed, tabs); + return os; + } + // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; if (_var == disc) { - os << "if(action <= " << _limit << ") "; + os << "if(action <= " << _limit << ") "; } else { - auto type = disc > _var ? "disc" : "vars"; - auto vid = disc > _var ? _var : _var - (disc + 1); - os << "if(" << type << "[" << vid << "] <= " << _limit << ") "; + auto type = disc > _var ? "disc" : "vars"; + auto vid = disc > _var ? _var : _var - (disc + 1); + os << "if(" << type << "[" << vid << "] <= " << _limit << ") "; } if (_low) - _low->print_c_nested(os, disc, tabs + 1, toprint, _low); + _low->print_c_nested(os, disc, tabs + 1, toprint, _low); else { - if (!std::isinf(_cost)) - os << "return " << _cost << ";"; - else - os << "{}"; + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; } os << "\n"; - // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + // for(size_t i = 0; i < tabs+1; ++i) stream << "\t"; os << "else "; if (_high) - _high->print_c_nested(os, disc, tabs + 1, toprint, _high); + _high->print_c_nested(os, disc, tabs + 1, toprint, _high); else { - if (!std::isinf(_cost)) - os << "return " << _cost << ";"; - else - os << "{}"; + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; } os << "\n"; - // for(size_t i = 0; i < tabs-1; ++i) stream << "\t"; - os << "}"; + for (auto n : toprint) + n->print_c(os, disc, printed, tabs); + return os; +} + +std::ostream& SimpleTree::node_t::print_c_nested(std::ostream& os, size_t disc, size_t tabs, + std::vector& toprint, const node_ptr& node) const +{ + assert(node.get() == this); + if (is_leaf() && std::isinf(_limit)) { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; + return os; + } + if (node.use_count() == 1) { + os << "{\n"; + // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + if (_var == disc) { + os << "if(action <= " << _limit << ") "; + } else { + auto type = disc > _var ? "disc" : "vars"; + auto vid = disc > _var ? _var : _var - (disc + 1); + os << "if(" << type << "[" << vid << "] <= " << _limit << ") "; + } + if (_low) + _low->print_c_nested(os, disc, tabs + 1, toprint, _low); + else { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; + } + os << "\n"; + // for(size_t i = 0; i < tabs; ++i) stream << "\t"; + os << "else "; + if (_high) + _high->print_c_nested(os, disc, tabs + 1, toprint, _high); + else { + if (!std::isinf(_cost)) + os << "return " << _cost << ";"; + else + os << "{}"; + } + os << "\n"; + // for(size_t i = 0; i < tabs-1; ++i) stream << "\t"; + os << "}"; + return os; + } + os << "goto l" << this << ";"; + toprint.push_back(this); return os; - } - os << "goto l" << this << ";"; - toprint.push_back(this); - return os; } diff --git a/src/SimpleTree.h b/src/SimpleTree.h index f04e8f4..c0ec46e 100644 --- a/src/SimpleTree.h +++ b/src/SimpleTree.h @@ -26,7 +26,7 @@ #define SIMPLETREE_H #include -#include // includes iostream :-( +#include // includes iostream :-( #include #include @@ -34,139 +34,129 @@ #include #include -class SimpleTree { +class SimpleTree +{ public: - SimpleTree(const SimpleTree &orig) = default; - static SimpleTree parse(std::istream &, bool simplify = false, - bool subsumption = false, double accuracy = 0); - static SimpleTree parse(std::istream &, bool simplify, bool subsumption, - double accuracy, std::vector &exactness); + SimpleTree(const SimpleTree& orig) = default; + static SimpleTree parse(std::istream&, bool simplify = false, bool subsumption = false, double accuracy = 0); + static SimpleTree parse(std::istream&, bool simplify, bool subsumption, double accuracy, + std::vector& exactness); - std::ostream &print(std::ostream &os) const; - std::ostream &print_c(std::ostream &os, const std::string &name) const; - double value(const double *disc, const double *cont, uint32_t action) const; - bool is_minimization() const { return _is_minimization; } - const std::vector &actions() const; - const std::vector &discrete_features() const { - return _statevars; - } - const std::vector &continous_features() const { - return _pointvars; - } + std::ostream& print(std::ostream& os) const; + std::ostream& print_c(std::ostream& os, const std::string& name) const; + double value(const double* disc, const double* cont, uint32_t action) const; + bool is_minimization() const { return _is_minimization; } + const std::vector& actions() const; + const std::vector& discrete_features() const { return _statevars; } + const std::vector& continous_features() const { return _pointvars; } private: - struct node_t; - using node_ptr = std::shared_ptr; - struct signature_t { - uint32_t _var{0}; - double _limit{0}; - node_t *_low{nullptr}; - node_t *_high{nullptr}; - } __attribute__((packed)); - friend struct ptrie::byte_iterator; + struct node_t; + using node_ptr = std::shared_ptr; + struct signature_t + { + uint32_t _var{0}; + double _limit{0}; + node_t* _low{nullptr}; + node_t* _high{nullptr}; + } __attribute__((packed)); + friend struct ptrie::byte_iterator; - using nodemap_t = ptrie::map; - using json = nlohmann::json; - SimpleTree() = default; + using nodemap_t = ptrie::map; + using json = nlohmann::json; + SimpleTree() = default; - struct node_t : std::enable_shared_from_this { - uint32_t _var = std::numeric_limits::max(); - double _limit = -std::numeric_limits::infinity(); - double _cost = std::numeric_limits::infinity(); - std::pair _cost_bounds; - node_ptr _low; - node_ptr _high; - node_t *_parent{nullptr}; - void insert(std::vector &key, json &tree, size_t action, - SimpleTree &parent, size_t prefix, bool minimize, - double accuracy, std::vector &exactness); - std::ostream &print(std::ostream &out, size_t tabs = 0) const; - bool is_leaf() const { return _low == nullptr && _high == nullptr; } - node_ptr simplify(bool make_dd, nodemap_t &nodemap, SimpleTree &parent); - void subsumption_reduction(bool minimization, SimpleTree &parent); - void action_nodes(std::vector &nodes, uint32_t low, uint32_t high, - uint32_t varid); - std::pair compute_min_max() const; - bool check_tiles(node_t *start, std::vector &, - std::vector> &bounds, double val, - double minval, double maxval, bool minimization, - size_t offset); - bool subsumes(const std::vector> &bounds, - std::vector> &obounds, double val, - bool minimization, size_t offset, double &best, - std::pair &closest) const; - void get_ranks(std::set> &values, - node_t *start); - void set_ranks(std::unordered_map &values); - std::ostream &print_c(std::ostream &os, size_t disc, - std::unordered_set &printed, - size_t tabs = 0) const; - std::ostream &print_c_nested(std::ostream &os, size_t disc, size_t tabs, - std::vector &toprint, - const node_ptr &node) const; - size_t depth() const; - double value(const double *disc, const double *cont, uint32_t action, - size_t ndisc) const; - void consistent(size_t) const; - bool cost_intersect(const node_t &other) const; - double midcost(const node_t &other, double minval, double maxval) const; - bool operator==(const node_t &other) const { - if (_limit != other._limit) - return false; - if (_var != other._var) - return false; - if (_low != other._low) - return false; - if (_high != other._high) - return false; - return true; - } - bool operator<(const node_t &other) const { - if (_limit != other._limit) - return _limit < other._limit; - if (_var != other._var) - return _var < other._var; - if (_low != other._low) - return _low < other._low; - return _high < other._high; - } - node_ptr &operator[](bool b) { return b ? _high : _low; } - operator signature_t() const { ///< support implicit conversion for map keys - if (is_leaf()) - return signature_t{_var, _limit, _low.get(), _high.get()}; - return signature_t{0, _cost, nullptr, nullptr}; - } - }; + struct node_t : std::enable_shared_from_this + { + uint32_t _var = std::numeric_limits::max(); + double _limit = -std::numeric_limits::infinity(); + double _cost = std::numeric_limits::infinity(); + std::pair _cost_bounds; + node_ptr _low; + node_ptr _high; + node_t* _parent{nullptr}; + void insert(std::vector& key, json& tree, size_t action, SimpleTree& parent, size_t prefix, + bool minimize, double accuracy, std::vector& exactness); + std::ostream& print(std::ostream& out, size_t tabs = 0) const; + bool is_leaf() const { return _low == nullptr && _high == nullptr; } + node_ptr simplify(bool make_dd, nodemap_t& nodemap, SimpleTree& parent); + void subsumption_reduction(bool minimization, SimpleTree& parent); + void action_nodes(std::vector& nodes, uint32_t low, uint32_t high, uint32_t varid); + std::pair compute_min_max() const; + bool check_tiles(node_t* start, std::vector&, std::vector>& bounds, + double val, double minval, double maxval, bool minimization, size_t offset); + bool subsumes(const std::vector>& bounds, + std::vector>& obounds, double val, bool minimization, size_t offset, + double& best, std::pair& closest) const; + void get_ranks(std::set>& values, node_t* start); + void set_ranks(std::unordered_map& values); + std::ostream& print_c(std::ostream& os, size_t disc, std::unordered_set& printed, + size_t tabs = 0) const; + std::ostream& print_c_nested(std::ostream& os, size_t disc, size_t tabs, std::vector& toprint, + const node_ptr& node) const; + size_t depth() const; + double value(const double* disc, const double* cont, uint32_t action, size_t ndisc) const; + void consistent(size_t) const; + bool cost_intersect(const node_t& other) const; + double midcost(const node_t& other, double minval, double maxval) const; + bool operator==(const node_t& other) const + { + if (_limit != other._limit) + return false; + if (_var != other._var) + return false; + if (_low != other._low) + return false; + if (_high != other._high) + return false; + return true; + } + bool operator<(const node_t& other) const + { + if (_limit != other._limit) + return _limit < other._limit; + if (_var != other._var) + return _var < other._var; + if (_low != other._low) + return _low < other._low; + return _high < other._high; + } + node_ptr& operator[](bool b) { return b ? _high : _low; } + operator signature_t() const + { ///< support implicit conversion for map keys + if (is_leaf()) + return signature_t{_var, _limit, _low.get(), _high.get()}; + return signature_t{0, _cost, nullptr, nullptr}; + } + }; - std::vector _actions; - std::vector _statevars; - std::vector _pointvars; - node_ptr _root; - bool _is_minimization = true; + std::vector _actions; + std::vector _statevars; + std::vector _pointvars; + node_ptr _root; + bool _is_minimization = true; }; -template <> struct ptrie::byte_iterator { - static uchar &access(SimpleTree::signature_t *data, size_t id) { - return reinterpret_cast(data)[id]; - } +template <> +struct ptrie::byte_iterator +{ + static uchar& access(SimpleTree::signature_t* data, size_t id) { return reinterpret_cast(data)[id]; } - static const uchar &const_access(const SimpleTree::signature_t *data, - size_t id) { - return reinterpret_cast(data)[id]; - } + static const uchar& const_access(const SimpleTree::signature_t* data, size_t id) + { + return reinterpret_cast(data)[id]; + } - static constexpr size_t element_size() { - constexpr auto member_size = sizeof(SimpleTree::signature_t::_var) + - sizeof(SimpleTree::signature_t::_limit) + - sizeof(SimpleTree::signature_t::_high) + - sizeof(SimpleTree::signature_t::_low); - static_assert(sizeof(SimpleTree::signature_t) == member_size, - "tightly packed struct"); - return sizeof(SimpleTree::signature_t); - } + static constexpr size_t element_size() + { + constexpr auto member_size = sizeof(SimpleTree::signature_t::_var) + sizeof(SimpleTree::signature_t::_limit) + + sizeof(SimpleTree::signature_t::_high) + sizeof(SimpleTree::signature_t::_low); + static_assert(sizeof(SimpleTree::signature_t) == member_size, "tightly packed struct"); + return sizeof(SimpleTree::signature_t); + } - static constexpr bool continious() { return true; } + static constexpr bool continious() { return true; } - // add read_blob, write_blob + // add read_blob, write_blob }; #endif /* SIMPLETREE_H */ diff --git a/src/ZonotopStrategy.cpp b/src/ZonotopStrategy.cpp index 0e2631c..22fc42b 100644 --- a/src/ZonotopStrategy.cpp +++ b/src/ZonotopStrategy.cpp @@ -34,433 +34,422 @@ using json = nlohmann::json; /// For printing a number of tab ('\t') characters. -struct Tabs { - size_t count; - friend std::ostream &operator<<(std::ostream &os, const Tabs &tabs) { - for (auto i = 0u; i < tabs.count; ++i) - os << '\t'; - return os; - } +struct Tabs +{ + size_t count; + friend std::ostream& operator<<(std::ostream& os, const Tabs& tabs) + { + for (auto i = 0u; i < tabs.count; ++i) + os << '\t'; + return os; + } }; -std::vector -ZonotopStrategy::get_bounds(const std::string &zonotop, size_t &vars) { - auto i = size_t{0}; - auto vid = size_t{0}; - auto bounds = std::vector{}; - while (i < zonotop.size() && zonotop[i] != ')') { - if (zonotop[i] == '[') { - double lower = 0, upper = 0; - // new element - ++i; - std::string ok(zonotop.begin() + i, zonotop.end()); - std::istringstream ss(ok); - { - std::string num; - std::getline(ss, num, ','); - std::istringstream ns(num); - ns >> lower; - i += num.length() + 1; - } - while (i < zonotop.size() && zonotop[i] == ' ') +std::vector ZonotopStrategy::get_bounds(const std::string& zonotop, size_t& vars) +{ + auto i = size_t{0}; + auto vid = size_t{0}; + auto bounds = std::vector{}; + while (i < zonotop.size() && zonotop[i] != ')') { + if (zonotop[i] == '[') { + double lower = 0, upper = 0; + // new element + ++i; + std::string ok(zonotop.begin() + i, zonotop.end()); + std::istringstream ss(ok); + { + std::string num; + std::getline(ss, num, ','); + std::istringstream ns(num); + ns >> lower; + i += num.length() + 1; + } + while (i < zonotop.size() && zonotop[i] == ' ') + ++i; + { + std::string num; + std::getline(ss, num, ']'); + std::istringstream ns(num); + ns >> upper; + i += num.length() + 1; + } + bounds.emplace_back(lower, upper); + } ++i; - { - std::string num; - std::getline(ss, num, ']'); - std::istringstream ns(num); - ns >> upper; - i += num.length() + 1; - } - bounds.emplace_back(lower, upper); } - ++i; - } - if (vars == 0) - vars = vid; - if (vars != vid) { - throw base_error("Dimensionality of controller differs"); - } - return bounds; + if (vars == 0) + vars = vid; + if (vars != vid) { + throw base_error("Dimensionality of controller differs"); + } + return bounds; } -ZonotopStrategy ZonotopStrategy::parse(std::istream &input) { - auto raw = json::parse(input); - auto strategy = ZonotopStrategy{}; - auto vars = size_t{0}; - if (raw.is_object()) { - for (auto &[raw_key, raw_value] : raw.items()) { - auto bounds = get_bounds(raw_key, vars); - if (raw_value.is_array()) { - for (auto e : raw_value) { - auto pat = e.get>(); - if (pat.size() > 0) { - auto res = strategy._states.insert((unsigned char *)pat.data(), - pat.size() * sizeof(uint16_t)); - strategy._max_length = std::max(strategy._max_length, pat.size()); - if (strategy.add(bounds, res.second)) { - // std::cerr << "COL " << it.key() << " : " << e << std::endl; - // exit(-1); +ZonotopStrategy ZonotopStrategy::parse(std::istream& input) +{ + auto raw = json::parse(input); + auto strategy = ZonotopStrategy{}; + auto vars = size_t{0}; + if (raw.is_object()) { + for (auto& [raw_key, raw_value] : raw.items()) { + auto bounds = get_bounds(raw_key, vars); + if (raw_value.is_array()) { + for (auto e : raw_value) { + auto pat = e.get>(); + if (pat.size() > 0) { + auto res = strategy._states.insert((unsigned char*)pat.data(), pat.size() * sizeof(uint16_t)); + strategy._max_length = std::max(strategy._max_length, pat.size()); + if (strategy.add(bounds, res.second)) { + // std::cerr << "COL " << it.key() << " : " << e << std::endl; + // exit(-1); + } + } + } + } else { + throw base_error("Input JSON not well formatted (array field expected)"); } - } } - } else { - throw base_error( - "Input JSON not well formatted (array field expected)"); - } + } else { + throw base_error("Input JSON not well formatted (object expected)"); } - } else { - throw base_error("Input JSON not well formatted (object expected)"); - } - return strategy; + return strategy; } -bool ZonotopStrategy::add(std::vector &bounds, size_t state) { - if (_root == nullptr) { - _root = std::make_shared(); - _root->_varid = 0; - _root->_limit = bounds[0]._lower; - } - auto handled = std::vector>(bounds.size()); - return rec_insert(_root.get(), bounds, handled, state); +bool ZonotopStrategy::add(std::vector& bounds, size_t state) +{ + if (_root == nullptr) { + _root = std::make_shared(); + _root->_varid = 0; + _root->_limit = bounds[0]._lower; + } + auto handled = std::vector>(bounds.size()); + return rec_insert(_root.get(), bounds, handled, state); } -bool ZonotopStrategy::rec_insert(node_t *node, std::vector &bounds, - std::vector> &handled, - size_t state) { - auto nid = node->_varid; - assert(nid < bounds.size()); - auto reset = false; - auto col = false; - if (bounds[node->_varid]._upper <= node->_limit) { - if (bounds[node->_varid]._upper == node->_limit) - reset = handled[node->_varid].second = true; - if (handled[node->_varid].first && handled[node->_varid].second) { - if (node->_varid == bounds.size() - 1) { - auto lb = std::lower_bound(node->_low_patterns.begin(), - node->_low_patterns.end(), state); - if (lb != node->_low_patterns.end() && *lb == state) { - col = true; - } else { - node->_low_patterns.insert(lb, state); - try_merge(node, state); +bool ZonotopStrategy::rec_insert(node_t* node, std::vector& bounds, + std::vector>& handled, size_t state) +{ + auto nid = node->_varid; + assert(nid < bounds.size()); + auto reset = false; + auto col = false; + if (bounds[node->_varid]._upper <= node->_limit) { + if (bounds[node->_varid]._upper == node->_limit) + reset = handled[node->_varid].second = true; + if (handled[node->_varid].first && handled[node->_varid].second) { + if (node->_varid == bounds.size() - 1) { + auto lb = std::lower_bound(node->_low_patterns.begin(), node->_low_patterns.end(), state); + if (lb != node->_low_patterns.end() && *lb == state) { + col = true; + } else { + node->_low_patterns.insert(lb, state); + try_merge(node, state); + } + return col; + } + ++nid; + assert(nid < bounds.size()); } - return col; - } - ++nid; - assert(nid < bounds.size()); - } - if (node->_low == nullptr) { - node->_low = std::make_shared(); - node->_low->_varid = nid; - node->_low->_parent = node; - node->_low->_limit = - (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - col |= rec_insert(node->_low.get(), bounds, handled, state); - if (reset) - handled[node->_varid].second = false; - } else if (bounds[node->_varid]._lower >= node->_limit) { - if (bounds[node->_varid]._lower == node->_limit) - reset = handled[node->_varid].first = true; - if (handled[node->_varid].first && handled[node->_varid].second) { - if (node->_varid == bounds.size() - 1) { - auto lb = std::lower_bound(node->_high_patterns.begin(), - node->_high_patterns.end(), state); - if (lb != node->_high_patterns.end() && *lb == state) { - col = true; - } else { - node->_high_patterns.insert(lb, state); - try_merge(node, state); + if (node->_low == nullptr) { + node->_low = std::make_shared(); + node->_low->_varid = nid; + node->_low->_parent = node; + node->_low->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); + } + col |= rec_insert(node->_low.get(), bounds, handled, state); + if (reset) + handled[node->_varid].second = false; + } else if (bounds[node->_varid]._lower >= node->_limit) { + if (bounds[node->_varid]._lower == node->_limit) + reset = handled[node->_varid].first = true; + if (handled[node->_varid].first && handled[node->_varid].second) { + if (node->_varid == bounds.size() - 1) { + auto lb = std::lower_bound(node->_high_patterns.begin(), node->_high_patterns.end(), state); + if (lb != node->_high_patterns.end() && *lb == state) { + col = true; + } else { + node->_high_patterns.insert(lb, state); + try_merge(node, state); + } + return col; + } + ++nid; + assert(nid < bounds.size()); } - return col; - } - ++nid; - assert(nid < bounds.size()); - } - if (node->_high == nullptr) { - node->_high = std::make_shared(); - node->_high->_parent = node; - node->_high->_varid = nid; - node->_high->_limit = - (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - col |= rec_insert(node->_high.get(), bounds, handled, state); - if (reset) - handled[node->_varid].first = false; - } else { - if (node->_low == nullptr) { - node->_low = std::make_shared(); - node->_low->_varid = nid; - node->_low->_parent = node; - node->_low->_limit = - (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } - if (node->_high == nullptr) { - node->_high = std::make_shared(); - node->_high->_parent = node; - node->_high->_varid = nid; - node->_high->_limit = - (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); - } + if (node->_high == nullptr) { + node->_high = std::make_shared(); + node->_high->_parent = node; + node->_high->_varid = nid; + node->_high->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); + } + col |= rec_insert(node->_high.get(), bounds, handled, state); + if (reset) + handled[node->_varid].first = false; + } else { + if (node->_low == nullptr) { + node->_low = std::make_shared(); + node->_low->_varid = nid; + node->_low->_parent = node; + node->_low->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); + } + if (node->_high == nullptr) { + node->_high = std::make_shared(); + node->_high->_parent = node; + node->_high->_varid = nid; + node->_high->_limit = (!handled[nid].first ? bounds[nid]._lower : bounds[nid]._upper); + } - col |= rec_insert(node->_low.get(), bounds, handled, state); - col |= rec_insert(node->_high.get(), bounds, handled, state); - } - return col; + col |= rec_insert(node->_low.get(), bounds, handled, state); + col |= rec_insert(node->_high.get(), bounds, handled, state); + } + return col; } -void ZonotopStrategy::try_merge(node_t *node, size_t state) { - if (node == nullptr) - return; - auto hlb = std::lower_bound(node->_high_patterns.begin(), - node->_high_patterns.end(), state); - if (hlb != std::end(node->_high_patterns) && *hlb == state) { - auto llb = std::lower_bound(node->_low_patterns.begin(), - node->_low_patterns.end(), state); - if (llb != std::end(node->_low_patterns) && *llb == state) { - node->_high_patterns.erase(hlb); - node->_low_patterns.erase(llb); - bool empty = - (node->_high == nullptr && node->_low == nullptr && - node->_high_patterns.empty() && node->_low_patterns.empty()); - auto parent = node->_parent; - if (node->_parent->_low.get() == node) { - parent->_low_patterns.push_back(state); - if (empty) - parent->_low = nullptr; - } else { - parent->_high_patterns.push_back(state); - if (empty) - parent->_high = nullptr; - } - try_merge(parent, state); +void ZonotopStrategy::try_merge(node_t* node, size_t state) +{ + if (node == nullptr) + return; + auto hlb = std::lower_bound(node->_high_patterns.begin(), node->_high_patterns.end(), state); + if (hlb != std::end(node->_high_patterns) && *hlb == state) { + auto llb = std::lower_bound(node->_low_patterns.begin(), node->_low_patterns.end(), state); + if (llb != std::end(node->_low_patterns) && *llb == state) { + node->_high_patterns.erase(hlb); + node->_low_patterns.erase(llb); + bool empty = (node->_high == nullptr && node->_low == nullptr && node->_high_patterns.empty() && + node->_low_patterns.empty()); + auto parent = node->_parent; + if (node->_parent->_low.get() == node) { + parent->_low_patterns.push_back(state); + if (empty) + parent->_low = nullptr; + } else { + parent->_high_patterns.push_back(state); + if (empty) + parent->_high = nullptr; + } + try_merge(parent, state); + } } - } } -std::ostream &ZonotopStrategy::node_t::print(std::ostream &os, - const ZonotopStrategy *parent, - size_t tabs) const { - os << Tabs{tabs}; - os << "{\"var\":" << _varid << ",\"bound\":" << _limit; - auto buffer = std::make_unique(parent->_max_length); - if (!_low_patterns.empty()) { - os << ",\n"; +std::ostream& ZonotopStrategy::node_t::print(std::ostream& os, const ZonotopStrategy* parent, size_t tabs) const +{ os << Tabs{tabs}; - os << "\"low_patterns\":["; - bool fp = true; - for (auto p : _low_patterns) { - if (!fp) - os << ","; - os << "["; - /*size_t length = parent->_states.unpack(p, (unsigned char*)buffer.get()); - bool fe = true; - for(size_t i = 0; i < length/sizeof(uint16_t); ++i) - { - if(!fe) - os << ","; - fe = false; - os << buffer[i]; - }*/ - os << p; - os << "]"; + os << "{\"var\":" << _varid << ",\"bound\":" << _limit; + auto buffer = std::make_unique(parent->_max_length); + if (!_low_patterns.empty()) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"low_patterns\":["; + bool fp = true; + for (auto p : _low_patterns) { + if (!fp) + os << ","; + os << "["; + /*size_t length = parent->_states.unpack(p, (unsigned char*)buffer.get()); + bool fe = true; + for(size_t i = 0; i < length/sizeof(uint16_t); ++i) + { + if(!fe) + os << ","; + fe = false; + os << buffer[i]; + }*/ + os << p; + os << "]"; + } + os << "]"; } - os << "]"; - } - if (!_high_patterns.empty()) { - os << ",\n"; - os << Tabs{tabs}; - os << "\"high_patterns\":["; - bool fp = true; - for (auto p : _high_patterns) { - if (!fp) - os << ","; - os << "["; - size_t length = const_cast(parent)->_states.unpack( - p, (unsigned char *)buffer.get()); - bool fe = true; - for (size_t i = 0; i < length / sizeof(uint16_t); ++i) { - if (!fe) - os << ","; - fe = false; - os << buffer[i]; - } - os << "]"; + if (!_high_patterns.empty()) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"high_patterns\":["; + bool fp = true; + for (auto p : _high_patterns) { + if (!fp) + os << ","; + os << "["; + size_t length = const_cast(parent)->_states.unpack(p, (unsigned char*)buffer.get()); + bool fe = true; + for (size_t i = 0; i < length / sizeof(uint16_t); ++i) { + if (!fe) + os << ","; + fe = false; + os << buffer[i]; + } + os << "]"; + } + os << "]"; } - os << "]"; - } - if (_low) { - os << ",\n"; - os << Tabs{tabs}; - os << "\"low\":\n"; - _low->print(os, parent, tabs + 1); - } - if (_high) { - os << ",\n"; + if (_low) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"low\":\n"; + _low->print(os, parent, tabs + 1); + } + if (_high) { + os << ",\n"; + os << Tabs{tabs}; + os << "\"high\":\n"; + _high->print(os, parent, tabs + 1); + } + os << "\n"; os << Tabs{tabs}; - os << "\"high\":\n"; - _high->print(os, parent, tabs + 1); - } - os << "\n"; - os << Tabs{tabs}; - os << "}"; - return os; + os << "}"; + return os; } -void ZonotopStrategy::active(const double *sampel, bool *write) const { - // std::cerr << "LOOKUP [" << sampel[0] << ", " << sampel[1] << "]" << - // std::endl; - memset(write, 0, sizeof(bool) * _states.size()); - if (_root == nullptr) - return; - auto current = _root.get(); - int active = 0; - // _root->print(std::cerr, (ZonotopStrategy*)this); - while (current) { - if (sampel[current->_varid] >= current->_limit) { - for (auto p : current->_high_patterns) { - write[p] = true; - // std::cerr << "ACTIVE " << p << std::endl; - } - active += current->_high_patterns.size(); - } - if (sampel[current->_varid] <= current->_limit) { - for (auto p : current->_low_patterns) { - write[p] = true; - // std::cerr << "ACTIVE " << p << std::endl; - } - active += current->_low_patterns.size(); - } - if (sampel[current->_varid] <= current->_limit && current->_low) { - current = current->_low.get(); - continue; - } - if (sampel[current->_varid] >= current->_limit && current->_high) { - current = current->_high.get(); - continue; +void ZonotopStrategy::active(const double* sampel, bool* write) const +{ + // std::cerr << "LOOKUP [" << sampel[0] << ", " << sampel[1] << "]" << + // std::endl; + memset(write, 0, sizeof(bool) * _states.size()); + if (_root == nullptr) + return; + auto current = _root.get(); + int active = 0; + // _root->print(std::cerr, (ZonotopStrategy*)this); + while (current) { + if (sampel[current->_varid] >= current->_limit) { + for (auto p : current->_high_patterns) { + write[p] = true; + // std::cerr << "ACTIVE " << p << std::endl; + } + active += current->_high_patterns.size(); + } + if (sampel[current->_varid] <= current->_limit) { + for (auto p : current->_low_patterns) { + write[p] = true; + // std::cerr << "ACTIVE " << p << std::endl; + } + active += current->_low_patterns.size(); + } + if (sampel[current->_varid] <= current->_limit && current->_low) { + current = current->_low.get(); + continue; + } + if (sampel[current->_varid] >= current->_limit && current->_high) { + current = current->_high.get(); + continue; + } + break; } - break; - } - if (active == 0) - std::cerr << "No active for [" << sampel[0] << ", " << sampel[1] << "]" - << std::endl; + if (active == 0) + std::cerr << "No active for [" << sampel[0] << ", " << sampel[1] << "]" << std::endl; } int ZonotopStrategy::max_pattern_length() const { return _max_length; } int ZonotopStrategy::num_patterns() const { return _states.size(); } -int ZonotopStrategy::get_pattern(int el, int *write) { - auto buffer = std::make_unique(_max_length); - size_t length = _states.unpack(el, (unsigned char *)buffer.get()); - for (size_t i = 0; i < length / sizeof(uint16_t); ++i) { - write[i] = buffer[i]; - } - return length / sizeof(uint16_t); +int ZonotopStrategy::get_pattern(int el, int* write) +{ + auto buffer = std::make_unique(_max_length); + size_t length = _states.unpack(el, (unsigned char*)buffer.get()); + for (size_t i = 0; i < length / sizeof(uint16_t); ++i) { + write[i] = buffer[i]; + } + return length / sizeof(uint16_t); } -std::ostream &ZonotopStrategy::print(std::ostream &stream) const { - if (_root) - _root->print(stream, this); - return stream; +std::ostream& ZonotopStrategy::print(std::ostream& stream) const +{ + if (_root) + _root->print(stream, this); + return stream; } -double ZonotopStrategy::get_max(size_t dimen) const { - return _root->get_max(dimen); -} +double ZonotopStrategy::get_max(size_t dimen) const { return _root->get_max(dimen); } -double ZonotopStrategy::get_min(size_t dimen) const { - return _root->get_min(dimen); -} +double ZonotopStrategy::get_min(size_t dimen) const { return _root->get_min(dimen); } -double ZonotopStrategy::node_t::get_max(size_t dimen) const { - double maxval = - _varid == dimen ? _limit : -std::numeric_limits::infinity(); - if (_high) - maxval = std::max(maxval, _high->get_max(dimen)); - if (_low) - maxval = std::max(maxval, _low->get_max(dimen)); - return maxval; +double ZonotopStrategy::node_t::get_max(size_t dimen) const +{ + double maxval = _varid == dimen ? _limit : -std::numeric_limits::infinity(); + if (_high) + maxval = std::max(maxval, _high->get_max(dimen)); + if (_low) + maxval = std::max(maxval, _low->get_max(dimen)); + return maxval; } -double ZonotopStrategy::node_t::get_min(size_t dimen) const { - double minval = - _varid == dimen ? _limit : std::numeric_limits::infinity(); - if (_high) - minval = std::min(minval, _high->get_min(dimen)); - if (_low) - minval = std::min(minval, _low->get_min(dimen)); - return minval; +double ZonotopStrategy::node_t::get_min(size_t dimen) const +{ + double minval = _varid == dimen ? _limit : std::numeric_limits::infinity(); + if (_high) + minval = std::min(minval, _high->get_min(dimen)); + if (_low) + minval = std::min(minval, _low->get_min(dimen)); + return minval; } -std::ostream &ZonotopStrategy::print_c(std::ostream &stream, - std::string function_name) const { - /* stream << "const int " << function_name << "_patterns[][" << - (_max_length + 1) << "] = {\n"; bool first = true; auto buffer = - std::make_unique(_max_length); for(size_t i = 0; i < - _states.size(); ++i) - { - memset(buffer.get(), 0, max_pattern_length()*sizeof(uint16_t)); - auto size = _states.unpack(i, (unsigned char*)buffer.get()); - if(!first) - stream << ","; - stream << "{"; - bool bf = true; - for(int j = 0; j < max_pattern_length()+1; ++j) - { - if(!bf) - stream << ","; - if((size_t)j < size/sizeof(uint16_t)) - stream << buffer[j]; - else - stream << "-1"; - bf = false; - } - stream << "}\n"; - first = false; - } - stream << "};\n\n";*/ - stream << "bool " << function_name - << "(const double* args, bool* patterns)\n"; - stream << "{\n"; - _root->print_c(stream, 1); - stream << "\treturn false;\n"; - stream << "}\n"; - return stream; +std::ostream& ZonotopStrategy::print_c(std::ostream& stream, std::string function_name) const +{ + /* stream << "const int " << function_name << "_patterns[][" << + (_max_length + 1) << "] = {\n"; bool first = true; auto buffer = + std::make_unique(_max_length); for(size_t i = 0; i < + _states.size(); ++i) + { + memset(buffer.get(), 0, max_pattern_length()*sizeof(uint16_t)); + auto size = _states.unpack(i, (unsigned char*)buffer.get()); + if(!first) + stream << ","; + stream << "{"; + bool bf = true; + for(int j = 0; j < max_pattern_length()+1; ++j) + { + if(!bf) + stream << ","; + if((size_t)j < size/sizeof(uint16_t)) + stream << buffer[j]; + else + stream << "-1"; + bf = false; + } + stream << "}\n"; + first = false; + } + stream << "};\n\n";*/ + stream << "bool " << function_name << "(const double* args, bool* patterns)\n"; + stream << "{\n"; + _root->print_c(stream, 1); + stream << "\treturn false;\n"; + stream << "}\n"; + return stream; } -std::ostream &ZonotopStrategy::node_t::print_c(std::ostream &os, - size_t tabs) const { - if (_low != nullptr || !_low_patterns.empty()) { - os << Tabs{tabs}; - os << "if(args[" << _varid << "] <= " << _limit << ") {\n"; - if (_low_patterns.size() > 0) { - os << Tabs{tabs + 1}; - for (auto p : _low_patterns) - os << "patterns[" << p << "] = true; "; - os << "\n"; +std::ostream& ZonotopStrategy::node_t::print_c(std::ostream& os, size_t tabs) const +{ + if (_low != nullptr || !_low_patterns.empty()) { + os << Tabs{tabs}; + os << "if(args[" << _varid << "] <= " << _limit << ") {\n"; + if (_low_patterns.size() > 0) { + os << Tabs{tabs + 1}; + for (auto p : _low_patterns) + os << "patterns[" << p << "] = true; "; + os << "\n"; + } + if (_low) + _low->print_c(os, tabs + 1); + os << Tabs{tabs}; + os << "}\n"; } - if (_low) - _low->print_c(os, tabs + 1); - os << Tabs{tabs}; - os << "}\n"; - } - if (_high != nullptr || !_high_patterns.empty()) { - os << Tabs{tabs}; - os << "if(args[" << _varid << "] >= " << _limit << ") {\n"; - if (_high_patterns.size() > 0) { - os << Tabs{tabs + 1}; - for (auto p : _high_patterns) - os << "patterns[" << p << "] = true; "; - os << "\n"; + if (_high != nullptr || !_high_patterns.empty()) { + os << Tabs{tabs}; + os << "if(args[" << _varid << "] >= " << _limit << ") {\n"; + if (_high_patterns.size() > 0) { + os << Tabs{tabs + 1}; + for (auto p : _high_patterns) + os << "patterns[" << p << "] = true; "; + os << "\n"; + } + if (_high) + _high->print_c(os, tabs + 1); + os << Tabs{tabs}; + os << "}\n"; } - if (_high) - _high->print_c(os, tabs + 1); - os << Tabs{tabs}; - os << "}\n"; - } - return os; + return os; } diff --git a/src/ZonotopStrategy.h b/src/ZonotopStrategy.h index bf02d7b..b6dd261 100644 --- a/src/ZonotopStrategy.h +++ b/src/ZonotopStrategy.h @@ -32,53 +32,54 @@ #include #include -class ZonotopStrategy { +class ZonotopStrategy +{ public: - ZonotopStrategy(ZonotopStrategy &&) = default; - ZonotopStrategy &operator=(ZonotopStrategy &&) = default; - static ZonotopStrategy parse(std::istream &); - int num_patterns() const; - int max_pattern_length() const; - void active(const double *sample, bool *write) const; - int get_pattern(int el, int *write); - double get_min(size_t dimen) const; - double get_max(size_t dimen) const; - std::ostream &print(std::ostream &) const; - std::ostream &print_c(std::ostream &stream, std::string function_name) const; - -private: - ZonotopStrategy() = default; - ptrie::set_stable<> _states; - size_t _max_length = 0; - struct node_t; - using node_ptr = std::shared_ptr; - struct node_t : std::enable_shared_from_this { - uint32_t _varid = 0; - double _limit = 0; - node_ptr _low = nullptr; - node_ptr _high = nullptr; - std::vector _low_patterns; - std::vector _high_patterns; - node_t *_parent{nullptr}; - std::ostream &print(std::ostream &os, const ZonotopStrategy *parent, - size_t tabs = 0) const; - std::ostream &print_c(std::ostream &os, size_t tabs = 0) const; + ZonotopStrategy(ZonotopStrategy&&) = default; + ZonotopStrategy& operator=(ZonotopStrategy&&) = default; + static ZonotopStrategy parse(std::istream&); + int num_patterns() const; + int max_pattern_length() const; + void active(const double* sample, bool* write) const; + int get_pattern(int el, int* write); double get_min(size_t dimen) const; double get_max(size_t dimen) const; - }; - node_ptr _root; - struct bound_t { - double _lower = 0; - double _upper = 0; - bound_t() = default; - bound_t(double l, double u) : _lower{l}, _upper{u} {} - }; - static std::vector get_bounds(const std::string &zonotop, - size_t &vars); - bool add(std::vector &bounds, size_t state); - bool rec_insert(node_t *node, std::vector &bounds, - std::vector> &handled, size_t state); - void try_merge(node_t *node, size_t state); + std::ostream& print(std::ostream&) const; + std::ostream& print_c(std::ostream& stream, std::string function_name) const; + +private: + ZonotopStrategy() = default; + ptrie::set_stable<> _states; + size_t _max_length = 0; + struct node_t; + using node_ptr = std::shared_ptr; + struct node_t : std::enable_shared_from_this + { + uint32_t _varid = 0; + double _limit = 0; + node_ptr _low = nullptr; + node_ptr _high = nullptr; + std::vector _low_patterns; + std::vector _high_patterns; + node_t* _parent{nullptr}; + std::ostream& print(std::ostream& os, const ZonotopStrategy* parent, size_t tabs = 0) const; + std::ostream& print_c(std::ostream& os, size_t tabs = 0) const; + double get_min(size_t dimen) const; + double get_max(size_t dimen) const; + }; + node_ptr _root; + struct bound_t + { + double _lower = 0; + double _upper = 0; + bound_t() = default; + bound_t(double l, double u): _lower{l}, _upper{u} {} + }; + static std::vector get_bounds(const std::string& zonotop, size_t& vars); + bool add(std::vector& bounds, size_t state); + bool rec_insert(node_t* node, std::vector& bounds, std::vector>& handled, + size_t state); + void try_merge(node_t* node, size_t state); }; #endif /* ZONOTOPSTRATEGY_H */ diff --git a/src/errors.h b/src/errors.h index a906f3c..f22baa0 100644 --- a/src/errors.h +++ b/src/errors.h @@ -27,8 +27,9 @@ #include -struct base_error : std::logic_error { - using std::logic_error::logic_error; +struct base_error : std::logic_error +{ + using std::logic_error::logic_error; }; #endif /* ERRORS_H */ diff --git a/src/utilities.hpp b/src/utilities.hpp index 562e58f..a72d6d0 100644 --- a/src/utilities.hpp +++ b/src/utilities.hpp @@ -3,80 +3,85 @@ #include "errors.h" -/// Please avoid including this header into headers (include into cpp instead) -/// as it includes streams (instead of iosfwd) and slows down compilation. +/** + * Avoid including this header into headers (include into cpp instead) + * as it includes streams (instead of iosfwd) and slows down compilation. + */ -#include // from_chars -#include // fallback if from_chars is not available -#include // true_type for detecting from_chars -#include // declval for detecting from_chars +#include // from_chars +#include // fallback if from_chars is not available +#include // true_type for detecting from_chars +#include // declval for detecting from_chars #include -/// C++17 compile-time test for presence of std::from_chars(const char*, const -/// char*, T&) Replace it with C++20 concepts later (or perhaps AppleClang will -/// implement proper from_chars by then). History: C++17 introduced -/// std::from_chars, but STL vendors were late, then provided only integral -/// versions... +/** C++17 compile-time test for presence of std::from_chars(const char*, const char*, T&) + * Replace it with C++20 concepts later (or perhaps AppleClang will implement proper from_chars by then). + * History: C++17 introduced std::from_chars, but STL vendors were late, then provided only integral versions... + */ template -struct has_from_chars : std::false_type { -}; // primary template declaration (used when specializations fail) +struct has_from_chars : std::false_type +{}; // primary template declaration (used when specializations fail) template -struct has_from_chars< // template partial specialization +struct has_from_chars< // template partial specialization T, - std::void_t< // tests if the following expression computes into a type: - decltype(std::from_chars(std::declval(), - std::declval(), - std::declval()))>> : std::true_type {}; + std::void_t< // tests if the following expression computes into a type: + decltype(std::from_chars(std::declval(), std::declval(), std::declval()))>> + : std::true_type +{}; template constexpr auto has_from_chars_v = has_from_chars::value; -inline std::vector parse_key(const std::string &key) { - auto res = std::vector{}; - if constexpr (has_from_chars_v) { // fast floating point parsing - auto it = key.c_str(); - const auto end = it + key.size(); - if (it == end || *it != '(') { - throw base_error("incorrectly formatted key ('(' expected): " + key); +/// Parses keys in a form "(number,number,number)" +template // has to be a template, otherwise AppleClang ignores constexpr +std::vector parse_key(const std::string& key) +{ + static_assert(std::is_arithmetic_v, "only numeric keys are supported"); + auto res = std::vector{}; + if constexpr (has_from_chars_v) { // fast floating point parsing + auto it = key.c_str(); + const auto end = it + key.size(); + if (it == end || *it != '(') { + throw base_error("incorrectly formatted key ('(' expected): " + key); + } + ++it; + while (it != end && *it != ')') { + double number; + if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { + res.push_back(number); + it = p; + if (it != end && *it == ',') + ++it; + } else { + throw base_error("failed to parse number in key: " + key); + } + } + if (it == end || *it != ')') { + throw base_error("incorrectly formatted key (')' expected): " + key); + } + } else { // fallback to slow stream parsing (AppleClang does not support from_chars) + auto is = std::istringstream{key}; + char c; + if (!is.get(c) || c != '(') { + throw base_error("incorrectly formatted key ('(' expected): " + key); + } + if (is && is.peek() == ')') + return res; + while (is) { + double number; + if (is >> number) + res.push_back(number); + else + throw base_error("failed to parse number in key: " + key); + if (is.get(c) && c != ',') + break; + } + if (c != ')') { + throw base_error("incorrectly formatted key (')' expected): " + key); + } } - ++it; - while (it != end && *it != ')') { - double number; - if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { - res.push_back(number); - it = p; - if (it != end && *it == ',') - ++it; - } else { - throw base_error("failed to parse number in key: " + key); - } - } - if (it == end || *it != ')') { - throw base_error("incorrectly formatted key (')' expected): " + key); - } - } else { // fallback to slow stream parsing - auto is = std::istringstream{key}; - char c; - if (!is.get(c) || c != '(') { - throw base_error("incorrectly formatted key ('(' expected): " + key); - } - if (is && is.peek() == ')') - return res; - while (is) { - double number; - if (is >> number) - res.push_back(number); - else - throw base_error("failed to parse number in key: " + key); - if (is.get(c) && c != ',') - break; - } - if (c != ')') { - throw base_error("incorrectly formatted key (')' expected): " + key); - } - } - return res; + return res; } -#endif // UTILITIES_HPP +#endif // UTILITIES_HPP diff --git a/test/SimpleTree_test.cpp b/test/SimpleTree_test.cpp index 4189330..b5fce7b 100644 --- a/test/SimpleTree_test.cpp +++ b/test/SimpleTree_test.cpp @@ -6,40 +6,41 @@ #include -BOOST_AUTO_TEST_CASE(SimpleTreeParseKey) { - const auto res_blank = parse_key("()"); - BOOST_CHECK(res_blank.empty()); - - const auto res_one = parse_key("(1)"); - BOOST_REQUIRE_EQUAL(res_one.size(), 1); - BOOST_CHECK_EQUAL(res_one[0], 1); - - const auto res_two = parse_key("(2,1)"); - BOOST_REQUIRE_EQUAL(res_two.size(), 2); - BOOST_CHECK_EQUAL(res_two[0], 2); - BOOST_CHECK_EQUAL(res_two[1], 1); - - const auto res_three = parse_key("(3,2,1)"); - BOOST_REQUIRE_EQUAL(res_three.size(), 3); - BOOST_CHECK_EQUAL(res_three[0], 3); - BOOST_CHECK_EQUAL(res_three[1], 2); - BOOST_CHECK_EQUAL(res_three[2], 1); - - const auto res_float = parse_key("(3.141)"); - BOOST_REQUIRE_EQUAL(res_float.size(), 1); - BOOST_CHECK_EQUAL(res_float[0], 3.141); - - const auto res_floats = parse_key("(4.3,2.1)"); - BOOST_REQUIRE_EQUAL(res_floats.size(), 2); - BOOST_CHECK_EQUAL(res_floats[0], 4.3); - BOOST_CHECK_EQUAL(res_floats[1], 2.1); - - // a few negative tests: - BOOST_CHECK_THROW(parse_key(""), base_error); - BOOST_CHECK_THROW(parse_key("1"), base_error); - BOOST_CHECK_THROW(parse_key(")"), base_error); - BOOST_CHECK_THROW(parse_key("("), base_error); - BOOST_CHECK_THROW(parse_key("(1"), base_error); - BOOST_CHECK_THROW(parse_key("(2,"), base_error); - BOOST_CHECK_THROW(parse_key("(2,1"), base_error); +BOOST_AUTO_TEST_CASE(SimpleTreeParseKey) +{ + const auto res_blank = parse_key("()"); + BOOST_CHECK(res_blank.empty()); + + const auto res_one = parse_key("(1)"); + BOOST_REQUIRE_EQUAL(res_one.size(), 1); + BOOST_CHECK_EQUAL(res_one[0], 1); + + const auto res_two = parse_key("(2,1)"); + BOOST_REQUIRE_EQUAL(res_two.size(), 2); + BOOST_CHECK_EQUAL(res_two[0], 2); + BOOST_CHECK_EQUAL(res_two[1], 1); + + const auto res_three = parse_key("(3,2,1)"); + BOOST_REQUIRE_EQUAL(res_three.size(), 3); + BOOST_CHECK_EQUAL(res_three[0], 3); + BOOST_CHECK_EQUAL(res_three[1], 2); + BOOST_CHECK_EQUAL(res_three[2], 1); + + const auto res_float = parse_key("(3.141)"); + BOOST_REQUIRE_EQUAL(res_float.size(), 1); + BOOST_CHECK_EQUAL(res_float[0], 3.141); + + const auto res_floats = parse_key("(4.3,2.1)"); + BOOST_REQUIRE_EQUAL(res_floats.size(), 2); + BOOST_CHECK_EQUAL(res_floats[0], 4.3); + BOOST_CHECK_EQUAL(res_floats[1], 2.1); + + // a few negative tests: + BOOST_CHECK_THROW(parse_key(""), base_error); + BOOST_CHECK_THROW(parse_key("1"), base_error); + BOOST_CHECK_THROW(parse_key(")"), base_error); + BOOST_CHECK_THROW(parse_key("("), base_error); + BOOST_CHECK_THROW(parse_key("(1"), base_error); + BOOST_CHECK_THROW(parse_key("(2,"), base_error); + BOOST_CHECK_THROW(parse_key("(2,1"), base_error); } diff --git a/test/inconsistent_lookup.cpp b/test/inconsistent_lookup.cpp index 0e22e8c..bd20e7a 100644 --- a/test/inconsistent_lookup.cpp +++ b/test/inconsistent_lookup.cpp @@ -24,31 +24,34 @@ BOOST_AUTO_TEST_CASE(DirectoryTest) { BOOST_REQUIRE(getenv("STRATEGY_DIR")); } -BOOST_AUTO_TEST_CASE(Inconsistent1) { - std::string strategy = getenv("STRATEGY_DIR"); - strategy += "/inconsistent1.strategy"; - std::ifstream in(strategy); - auto tree = SimpleTree::parse(in, false, false); - double vars[] = {10}; - auto act18 = tree.value(vars, nullptr, 0); - auto act19 = tree.value(vars, nullptr, 1); - BOOST_REQUIRE_LT(act18, act19); +BOOST_AUTO_TEST_CASE(Inconsistent1) +{ + std::string strategy = getenv("STRATEGY_DIR"); + strategy += "/inconsistent1.strategy"; + std::ifstream in(strategy); + auto tree = SimpleTree::parse(in, false, false); + double vars[] = {10}; + auto act18 = tree.value(vars, nullptr, 0); + auto act19 = tree.value(vars, nullptr, 1); + BOOST_REQUIRE_LT(act18, act19); } -BOOST_AUTO_TEST_CASE(Inconsistent1Simplify) { - std::string strategy = getenv("STRATEGY_DIR"); - strategy += "/inconsistent1.strategy"; - std::ifstream in(strategy); - auto tree = SimpleTree::parse(in, true, false); - double vars[] = {10}; - BOOST_REQUIRE_LT(tree.value(vars, nullptr, 0), tree.value(vars, nullptr, 1)); +BOOST_AUTO_TEST_CASE(Inconsistent1Simplify) +{ + std::string strategy = getenv("STRATEGY_DIR"); + strategy += "/inconsistent1.strategy"; + std::ifstream in(strategy); + auto tree = SimpleTree::parse(in, true, false); + double vars[] = {10}; + BOOST_REQUIRE_LT(tree.value(vars, nullptr, 0), tree.value(vars, nullptr, 1)); } -BOOST_AUTO_TEST_CASE(Inconsistent1SimplifySubsumption) { - std::string strategy = getenv("STRATEGY_DIR"); - strategy += "/inconsistent1.strategy"; - std::ifstream in(strategy); - auto tree = SimpleTree::parse(in, true, true); - double vars[] = {10}; - BOOST_REQUIRE_LT(tree.value(vars, nullptr, 0), tree.value(vars, nullptr, 1)); +BOOST_AUTO_TEST_CASE(Inconsistent1SimplifySubsumption) +{ + std::string strategy = getenv("STRATEGY_DIR"); + strategy += "/inconsistent1.strategy"; + std::ifstream in(strategy); + auto tree = SimpleTree::parse(in, true, true); + double vars[] = {10}; + BOOST_REQUIRE_LT(tree.value(vars, nullptr, 0), tree.value(vars, nullptr, 1)); } diff --git a/test/unordered_load.cpp b/test/unordered_load.cpp index 14e536a..22ec8f6 100644 --- a/test/unordered_load.cpp +++ b/test/unordered_load.cpp @@ -117,238 +117,239 @@ const std::string simple_unordered_strategy = " }" "}"; -const std::string unordered_strategy = - "{\"version\":1.0,\"type\":\"state->regressor\",\"representation\":\"map\"," - "\"actions\":{" - " \"0\":\"Controller._id4->Controller._id2 { 1, tau, minute_clock := " - "TIME_OFFSET, initValues() }\"," - " \"1\":\"Controller._id2->Controller.Wait { 0 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 0, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"2\":\"Controller._id2->Controller.Wait { 1 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 1, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"3\":\"Controller._id2->Controller.Wait { 2 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 2, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"4\":\"Controller._id2->Controller.Wait { 3 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 3, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"5\":\"Controller._id2->Controller.Wait { 4 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 4, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"6\":\"Controller._id2->Controller.Wait { 5 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 5, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"7\":\"Controller._id2->Controller.Wait { 6 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 6, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"9\":\"Controller._id2->Controller.Wait { 8 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 8, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"10\":\"Controller._id2->Controller.Wait { 9 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 9, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"11\":\"Controller._id2->Controller.Wait { 10 >= 10 && any_on(), tau, " - "consumed_power := 0.625000 + 0.187500 * 10, heat_produced := " - "consumed_power * calculateCOP(), setMassFlow() }\"," - " \"12\":\"Controller._id2->Controller.Wait { 1, tau, consumed_power := " - "0, heat_produced := 0 }\"," - " \"13\":\"WAIT\"" - "},\"statevars\":[" - " \"round(minute_clock) \"" - "],\"pointvars\":[" - "],\"locationnames\":{" - " \"Fetch_Data.location\":{" - " \"0\":\"_id5\"" - " }," - " \"Room1.location\":{" - " \"0\":\"_id0\"" - " }," - " \"Room2.location\":{" - " \"0\":\"_id0\"" - " }," - " \"Room3.location\":{" - " \"0\":\"_id0\"" - " }," - " \"Room4.location\":{" - " \"0\":\"_id0\"" - " }," - " \"Optimization.location\":{" - " \"0\":\"_id1\"" - " }," - " \"Controller.location\":{" - " \"0\":\"_id2\"," - " \"1\":\"Wait\"," - " \"2\":\"_id4\"" - " }" - "},\"regressors\":{" - " \"(195)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 1.767005809675129," - " \"12\" : 0.5472758743181302" - " }" - " }," - " \"(180)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 2.107357559876886," - " \"12\" : 0.841995896951961" - " }" - " }," - " \"(225)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 0.3895094003837855," - " \"12\" : 1.438914715299473" - " }" - " }," - " \"(120)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 3.562263183988571," - " \"12\" : 3.01531941566271" - " }" - " }," - " \"(90)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 4.595961684754502," - " \"12\" : 4.520572775155376" - " }" - " }," - " \"(75)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 5.200779712113728," - " \"12\" : 1.1120904838198" - " }" - " }," - " \"(165)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 2.422010195182813," - " \"12\" : 1.850311355224428" - " }" - " }," - " \"(240)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 0.1842626478060555," - " \"12\" : 0.5429048553483474" - " }" - " }," - " \"(150)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 1.802785410011689," - " \"12\" : 2.744820174794056" - " }" - " }," - " \"(135)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 4.082210512180363," - " \"12\" : 1.046474095029413" - " }" - " }," - " \"(105)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 3.506084076677214," - " \"12\" : 4.529894227895771" - " }" - " }," - " \"(45)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 2.923974662511487," - " \"12\" : 3.712782046133843" - " }" - " }," - " \"(30)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 1.294636921229531," - " \"12\" : 3.091107032150207" - " }" - " }," - " \"(210)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 1.009248500699941," - " \"12\" : 1.028772620412916" - " }" - " }," - " \"(15)\":" - " " - "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," - "\"minimize\":1,\"regressor\":" - " {" - " \"11\" : 2.855407148131672," - " \"12\" : 1.352930166302546" - " }" - " }" - " }" - "}"; +const std::string unordered_strategy = "{\"version\":1.0,\"type\":\"state->regressor\",\"representation\":\"map\"," + "\"actions\":{" + " \"0\":\"Controller._id4->Controller._id2 { 1, tau, minute_clock := " + "TIME_OFFSET, initValues() }\"," + " \"1\":\"Controller._id2->Controller.Wait { 0 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 0, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"2\":\"Controller._id2->Controller.Wait { 1 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 1, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"3\":\"Controller._id2->Controller.Wait { 2 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 2, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"4\":\"Controller._id2->Controller.Wait { 3 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 3, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"5\":\"Controller._id2->Controller.Wait { 4 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 4, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"6\":\"Controller._id2->Controller.Wait { 5 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 5, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"7\":\"Controller._id2->Controller.Wait { 6 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 6, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"9\":\"Controller._id2->Controller.Wait { 8 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 8, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"10\":\"Controller._id2->Controller.Wait { 9 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 9, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"11\":\"Controller._id2->Controller.Wait { 10 >= 10 && any_on(), tau, " + "consumed_power := 0.625000 + 0.187500 * 10, heat_produced := " + "consumed_power * calculateCOP(), setMassFlow() }\"," + " \"12\":\"Controller._id2->Controller.Wait { 1, tau, consumed_power := " + "0, heat_produced := 0 }\"," + " \"13\":\"WAIT\"" + "},\"statevars\":[" + " \"round(minute_clock) \"" + "],\"pointvars\":[" + "],\"locationnames\":{" + " \"Fetch_Data.location\":{" + " \"0\":\"_id5\"" + " }," + " \"Room1.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room2.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room3.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Room4.location\":{" + " \"0\":\"_id0\"" + " }," + " \"Optimization.location\":{" + " \"0\":\"_id1\"" + " }," + " \"Controller.location\":{" + " \"0\":\"_id2\"," + " \"1\":\"Wait\"," + " \"2\":\"_id4\"" + " }" + "},\"regressors\":{" + " \"(195)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.767005809675129," + " \"12\" : 0.5472758743181302" + " }" + " }," + " \"(180)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.107357559876886," + " \"12\" : 0.841995896951961" + " }" + " }," + " \"(225)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 0.3895094003837855," + " \"12\" : 1.438914715299473" + " }" + " }," + " \"(120)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 3.562263183988571," + " \"12\" : 3.01531941566271" + " }" + " }," + " \"(90)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 4.595961684754502," + " \"12\" : 4.520572775155376" + " }" + " }," + " \"(75)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 5.200779712113728," + " \"12\" : 1.1120904838198" + " }" + " }," + " \"(165)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.422010195182813," + " \"12\" : 1.850311355224428" + " }" + " }," + " \"(240)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 0.1842626478060555," + " \"12\" : 0.5429048553483474" + " }" + " }," + " \"(150)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.802785410011689," + " \"12\" : 2.744820174794056" + " }" + " }," + " \"(135)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 4.082210512180363," + " \"12\" : 1.046474095029413" + " }" + " }," + " \"(105)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 3.506084076677214," + " \"12\" : 4.529894227895771" + " }" + " }," + " \"(45)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.923974662511487," + " \"12\" : 3.712782046133843" + " }" + " }," + " \"(30)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.294636921229531," + " \"12\" : 3.091107032150207" + " }" + " }," + " \"(210)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 1.009248500699941," + " \"12\" : 1.028772620412916" + " }" + " }," + " \"(15)\":" + " " + "{\"type\":\"act->point->val\",\"representation\":\"simpletree\"," + "\"minimize\":1,\"regressor\":" + " {" + " \"11\" : 2.855407148131672," + " \"12\" : 1.352930166302546" + " }" + " }" + " }" + "}"; -BOOST_AUTO_TEST_CASE(SimpleUnorderedKeyLoad) { - auto is = std::istringstream{simple_unordered_strategy}; - auto strategy = SimpleTree::parse(is, false, false, 0); - double disc[1] = {15.0}; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); +BOOST_AUTO_TEST_CASE(SimpleUnorderedKeyLoad) +{ + auto is = std::istringstream{simple_unordered_strategy}; + auto strategy = SimpleTree::parse(is, false, false, 0); + double disc[1] = {15.0}; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); - disc[0] = 210; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); + disc[0] = 210; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); - disc[0] = 30; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); + disc[0] = 30; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); } -BOOST_AUTO_TEST_CASE(UnorderedKeyLoad) { - auto is = std::stringstream{unordered_strategy}; - auto strategy = SimpleTree::parse(is, false, false, 0); - double disc[1] = {15.0}; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); +BOOST_AUTO_TEST_CASE(UnorderedKeyLoad) +{ + auto is = std::stringstream{unordered_strategy}; + auto strategy = SimpleTree::parse(is, false, false, 0); + double disc[1] = {15.0}; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 2.855407148131672); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.352930166302546); - disc[0] = 210; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); + disc[0] = 210; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.009248500699941); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 1.028772620412916); - disc[0] = 30; - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); - BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); + disc[0] = 30; + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 11), 1.294636921229531); + BOOST_CHECK_EQUAL(strategy.value(disc, nullptr, 12), 3.091107032150207); } diff --git a/test/utilities_test.cpp b/test/utilities_test.cpp index 9dd6313..b0e3d04 100644 --- a/test/utilities_test.cpp +++ b/test/utilities_test.cpp @@ -1,25 +1,27 @@ -#include "../src/utilities.hpp" #include "utilities.hpp" #include -template void test(const std::string &name) { - if constexpr (has_from_chars_v) { - std::cout << "from_chars(" << name << ") is supported\n"; - } else { - std::cout << "from_chars(" << name << ") is NOT supported\n"; - } +template +void test(const std::string& name) +{ + if constexpr (has_from_chars_v) { + std::cout << "from_chars(" << name << ") is supported\n"; + } else { + std::cout << "from_chars(" << name << ") is NOT supported\n"; + } } -int main() { - test("bool"); - test("char"); - test("int"); - test("unsigned int"); - test("long"); - test("unsigned long"); - test("long long"); - test("unsigned long long"); - test("float"); - test("double"); +int main() +{ + test("bool"); + test("char"); + test("int"); + test("unsigned int"); + test("long"); + test("unsigned long"); + test("long long"); + test("unsigned long long"); + test("float"); + test("double"); } \ No newline at end of file From 2b0488ca4aa7e00b680e64edbae3ffcf2a2efbb0 Mon Sep 17 00:00:00 2001 From: Marius Mikucionis Date: Mon, 30 Jun 2025 09:38:28 +0200 Subject: [PATCH 14/21] Fixed parse?keys for sure now --- src/utilities.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utilities.hpp b/src/utilities.hpp index a72d6d0..00a10de 100644 --- a/src/utilities.hpp +++ b/src/utilities.hpp @@ -47,7 +47,7 @@ std::vector parse_key(const std::string& key) } ++it; while (it != end && *it != ')') { - double number; + T number; if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { res.push_back(number); it = p; From fc366a87d6f5fb4a948422a03169fb633755560f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Mon, 30 Jun 2025 10:24:58 +0200 Subject: [PATCH 15/21] Simplified parsing key --- src/utilities.hpp | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/utilities.hpp b/src/utilities.hpp index 00a10de..8ddb049 100644 --- a/src/utilities.hpp +++ b/src/utilities.hpp @@ -33,48 +33,43 @@ struct has_from_chars< // template partial specialization template constexpr auto has_from_chars_v = has_from_chars::value; -/// Parses keys in a form "(number,number,number)" +/// Parses numbers from a key in a form of "(number,number,number)" template // has to be a template, otherwise AppleClang ignores constexpr std::vector parse_key(const std::string& key) { static_assert(std::is_arithmetic_v, "only numeric keys are supported"); auto res = std::vector{}; + T number; // the number to parse into if constexpr (has_from_chars_v) { // fast floating point parsing auto it = key.c_str(); const auto end = it + key.size(); - if (it == end || *it != '(') { + if (it == end || *it != '(') throw base_error("incorrectly formatted key ('(' expected): " + key); - } - ++it; - while (it != end && *it != ')') { - T number; + if (*++it == ')') + return res; + while (it != end) { if (auto [p, ec] = std::from_chars(it, end, number); ec == std::errc()) { res.push_back(number); it = p; if (it != end && *it == ',') ++it; - } else { + else + break; + } else throw base_error("failed to parse number in key: " + key); - } } - if (it == end || *it != ')') { + if (it == end || *it != ')') throw base_error("incorrectly formatted key (')' expected): " + key); - } } else { // fallback to slow stream parsing (AppleClang does not support from_chars) auto is = std::istringstream{key}; char c; - if (!is.get(c) || c != '(') { + if (!is.get(c) || c != '(') throw base_error("incorrectly formatted key ('(' expected): " + key); - } if (is && is.peek() == ')') return res; - while (is) { - double number; - if (is >> number) - res.push_back(number); - else - throw base_error("failed to parse number in key: " + key); - if (is.get(c) && c != ',') + while (is >> number) { + res.push_back(number); + if (!is.get(c) || c != ',') break; } if (c != ')') { From 279655a8c603765e7d82da91730b7ea0b7aaffca Mon Sep 17 00:00:00 2001 From: Marius Mikucionis Date: Mon, 30 Jun 2025 10:28:15 +0200 Subject: [PATCH 16/21] Removed leak sanitizer as AppleClang does not support it and AddressSanitizer seems to cover leak checking --- cmake/CommonPresets.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/CommonPresets.json b/cmake/CommonPresets.json index 4cb48a6..dbccee6 100644 --- a/cmake/CommonPresets.json +++ b/cmake/CommonPresets.json @@ -23,9 +23,9 @@ "inherits": "multi", "binaryDir": "${sourceDir}/build-multi-san", "environment": { - "CFLAGS": "-fsanitize=leak -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", - "CXXFLAGS": "-fsanitize=leak -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", - "LDFLAGS": "-fsanitize=leak -fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer" + "CFLAGS": "-fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", + "CXXFLAGS": "-fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", + "LDFLAGS": "-fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer" }, "displayName": "Configure for Ninja Debug/Release with Sanitizers" }, @@ -380,4 +380,4 @@ ] } ] -} \ No newline at end of file +} From 2a3c95fd15c712ccf7a1f534dd31a8738225ed97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Mon, 30 Jun 2025 10:58:28 +0200 Subject: [PATCH 17/21] Added GH CI build script --- .github/workflows/build.yml | 154 ++++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..7be872b --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,154 @@ +--- +name: Build and Test +on: + push: + branches: [master] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + pull_request: + branches: [master] + types: [synchronize, opened, reopened, ready_for_review] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + workflow_dispatch: + +concurrency: + group: Server-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + ubuntu-2404: + runs-on: ubuntu-24.04 + env: + CMAKE_GENERATOR: Ninja + steps: + - name: Setup Runner Environment + run: | + uname -a + lsb_release -a + CORES=$(nproc) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - uses: actions/checkout@v4 + - name: Install Build Tools + run: | + sudo apt-get -qq update + sudo apt-get -qq install cmake ninja-build g++ libboost-program-options-dev libboost-test-dev + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san + + macOS-13: + runs-on: macos-13 + env: + CMAKE_GENERATOR: Ninja + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '14.3.1' + - name: Setup Runner Environment + run: | + uname -a + xcrun --show-sdk-version + CORES=$(sysctl -n hw.ncpu) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - uses: actions/checkout@v4 + + - name: Install Build Tools + run: | + brew update + brew install cmake ninja boost + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san + + macOS-14: + runs-on: macos-14 + env: + CMAKE_GENERATOR: Ninja + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.4.0' + - name: Setup Runner Environment + run: | + uname -a + xcrun --show-sdk-version + CORES=$(sysctl -n hw.ncpu) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - uses: actions/checkout@v4 + + - name: Install Build Tools + run: | + brew update + brew install cmake ninja boost + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san + + macOS-15: + runs-on: macos-15 + env: + CMAKE_GENERATOR: Ninja + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16.4.0' + - name: Setup Runner Environment + run: | + uname -a + xcrun --show-sdk-version + CORES=$(sysctl -n hw.ncpu) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - uses: actions/checkout@v4 + + - name: Install Build Tools + run: | + brew update + brew install cmake ninja boost + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san From 3ee3c29291336f4feb313e34061de9f306ed4241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Mon, 30 Jun 2025 11:37:50 +0200 Subject: [PATCH 18/21] Split GH CI scripts into multiple files --- .github/workflows/build-macos-13.yml | 56 +++++++++ .github/workflows/build-macos-14.yml | 56 +++++++++ .github/workflows/build-macos-15.yml | 56 +++++++++ .github/workflows/build-ubuntu-2404.yml | 53 ++++++++ .github/workflows/build.yml | 154 ------------------------ .gitignore | 8 +- 6 files changed, 225 insertions(+), 158 deletions(-) create mode 100644 .github/workflows/build-macos-13.yml create mode 100644 .github/workflows/build-macos-14.yml create mode 100644 .github/workflows/build-macos-15.yml create mode 100644 .github/workflows/build-ubuntu-2404.yml delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build-macos-13.yml b/.github/workflows/build-macos-13.yml new file mode 100644 index 0000000..ad0f54f --- /dev/null +++ b/.github/workflows/build-macos-13.yml @@ -0,0 +1,56 @@ +--- +name: Build for macOS-13 +on: + push: + branches: [master] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-macos-13.yml + pull_request: + branches: [master] + types: [synchronize, opened, reopened, ready_for_review] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-macos-13.yml + workflow_dispatch: + +jobs: + macOS-13: + runs-on: macos-13 + env: + CMAKE_GENERATOR: Ninja + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '14.3.1' + - name: Setup Runner Environment + run: | + uname -a + xcrun --show-sdk-version + CORES=$(sysctl -n hw.ncpu) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - name: Install Build Tools + run: | + brew update + brew install cmake ninja boost + - uses: actions/checkout@v4 + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san diff --git a/.github/workflows/build-macos-14.yml b/.github/workflows/build-macos-14.yml new file mode 100644 index 0000000..39e979c --- /dev/null +++ b/.github/workflows/build-macos-14.yml @@ -0,0 +1,56 @@ +--- +name: Build for macOS-14 +on: + push: + branches: [master] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-macos-14.yml + pull_request: + branches: [master] + types: [synchronize, opened, reopened, ready_for_review] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-macos-14.yml + workflow_dispatch: + +jobs: + macOS-14: + runs-on: macos-14 + env: + CMAKE_GENERATOR: Ninja + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.4.0' + - name: Setup Runner Environment + run: | + uname -a + xcrun --show-sdk-version + CORES=$(sysctl -n hw.ncpu) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - name: Install Build Tools + run: | + brew update + brew install cmake ninja boost + - uses: actions/checkout@v4 + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san diff --git a/.github/workflows/build-macos-15.yml b/.github/workflows/build-macos-15.yml new file mode 100644 index 0000000..61a3ef5 --- /dev/null +++ b/.github/workflows/build-macos-15.yml @@ -0,0 +1,56 @@ +--- +name: Build for macOS-15 +on: + push: + branches: [master] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-macos-15.yml + pull_request: + branches: [master] + types: [synchronize, opened, reopened, ready_for_review] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-macos-15.yml + workflow_dispatch: + +jobs: + macOS-15: + runs-on: macos-15 + env: + CMAKE_GENERATOR: Ninja + steps: + - uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '16.4.0' + - name: Setup Runner Environment + run: | + uname -a + xcrun --show-sdk-version + CORES=$(sysctl -n hw.ncpu) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - name: Install Build Tools + run: | + brew update + brew install cmake ninja boost + - uses: actions/checkout@v4 + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san diff --git a/.github/workflows/build-ubuntu-2404.yml b/.github/workflows/build-ubuntu-2404.yml new file mode 100644 index 0000000..73e5c87 --- /dev/null +++ b/.github/workflows/build-ubuntu-2404.yml @@ -0,0 +1,53 @@ +--- +name: Build for Ubuntu-24.04 +on: + push: + branches: [master] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-ubuntu-2404.yml + pull_request: + branches: [master] + types: [synchronize, opened, reopened, ready_for_review] + paths: + - src/** + - test/** + - cmake/** + - CMakeLists.txt + - CMakePresets.json + - .github/workflows/build-ubuntu-2404.yml + workflow_dispatch: + +jobs: + ubuntu-2404: + runs-on: ubuntu-24.04 + env: + CMAKE_GENERATOR: Ninja + steps: + - name: Setup Runner Environment + run: | + uname -a + lsb_release -a + CORES=$(nproc) + echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV + echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV + - name: Install Build Tools + run: | + sudo apt-get -qq update + sudo apt-get -qq install cmake ninja-build g++ libboost-program-options-dev libboost-test-dev + - uses: actions/checkout@v4 + - name: Configure Multi-Config + run: cmake --preset multi-san + - name: Build Debug + run: cmake --build --preset debug-san + - name: Test Debug + run: ctest --preset debug-san + - name: Build Release + run: cmake --build --preset release-san + - name: Test Release + run: ctest --preset release-san diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 7be872b..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,154 +0,0 @@ ---- -name: Build and Test -on: - push: - branches: [master] - paths: - - src/** - - test/** - - cmake/** - - CMakeLists.txt - - CMakePresets.json - pull_request: - branches: [master] - types: [synchronize, opened, reopened, ready_for_review] - paths: - - src/** - - test/** - - cmake/** - - CMakeLists.txt - - CMakePresets.json - workflow_dispatch: - -concurrency: - group: Server-${{ github.head_ref }} - cancel-in-progress: true - -jobs: - ubuntu-2404: - runs-on: ubuntu-24.04 - env: - CMAKE_GENERATOR: Ninja - steps: - - name: Setup Runner Environment - run: | - uname -a - lsb_release -a - CORES=$(nproc) - echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - name: Install Build Tools - run: | - sudo apt-get -qq update - sudo apt-get -qq install cmake ninja-build g++ libboost-program-options-dev libboost-test-dev - - name: Configure Multi-Config - run: cmake --preset multi-san - - name: Build Debug - run: cmake --build --preset debug-san - - name: Test Debug - run: ctest --preset debug-san - - name: Build Release - run: cmake --build --preset release-san - - name: Test Release - run: ctest --preset release-san - - macOS-13: - runs-on: macos-13 - env: - CMAKE_GENERATOR: Ninja - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '14.3.1' - - name: Setup Runner Environment - run: | - uname -a - xcrun --show-sdk-version - CORES=$(sysctl -n hw.ncpu) - echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - - name: Install Build Tools - run: | - brew update - brew install cmake ninja boost - - name: Configure Multi-Config - run: cmake --preset multi-san - - name: Build Debug - run: cmake --build --preset debug-san - - name: Test Debug - run: ctest --preset debug-san - - name: Build Release - run: cmake --build --preset release-san - - name: Test Release - run: ctest --preset release-san - - macOS-14: - runs-on: macos-14 - env: - CMAKE_GENERATOR: Ninja - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '15.4.0' - - name: Setup Runner Environment - run: | - uname -a - xcrun --show-sdk-version - CORES=$(sysctl -n hw.ncpu) - echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - - name: Install Build Tools - run: | - brew update - brew install cmake ninja boost - - name: Configure Multi-Config - run: cmake --preset multi-san - - name: Build Debug - run: cmake --build --preset debug-san - - name: Test Debug - run: ctest --preset debug-san - - name: Build Release - run: cmake --build --preset release-san - - name: Test Release - run: ctest --preset release-san - - macOS-15: - runs-on: macos-15 - env: - CMAKE_GENERATOR: Ninja - steps: - - uses: maxim-lobanov/setup-xcode@v1 - with: - xcode-version: '16.4.0' - - name: Setup Runner Environment - run: | - uname -a - xcrun --show-sdk-version - CORES=$(sysctl -n hw.ncpu) - echo "CMAKE_BUILD_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_PARALLEL_LEVEL=$CORES" >> $GITHUB_ENV - echo "CTEST_TEST_LOAD=$CORES" >> $GITHUB_ENV - - uses: actions/checkout@v4 - - - name: Install Build Tools - run: | - brew update - brew install cmake ninja boost - - name: Configure Multi-Config - run: cmake --preset multi-san - - name: Build Debug - run: cmake --build --preset debug-san - - name: Test Debug - run: ctest --preset debug-san - - name: Build Release - run: cmake --build --preset release-san - - name: Test Release - run: ctest --preset release-san diff --git a/.gitignore b/.gitignore index 7d90c9b..65b1f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *~ -build -build-* -cmake-build-* -local .idea +/build +/build-* +/cmake-build-* +/local From 3ada870b407d8c33668e3a14ec4bf63854967457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Mon, 30 Jun 2025 15:42:32 +0200 Subject: [PATCH 19/21] Fixed compiler settings in the cmake presets --- cmake/CommonPresets.json | 78 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/cmake/CommonPresets.json b/cmake/CommonPresets.json index dbccee6..b63b4ca 100644 --- a/cmake/CommonPresets.json +++ b/cmake/CommonPresets.json @@ -34,8 +34,8 @@ "inherits": "multi", "binaryDir": "${sourceDir}/build-gcc", "environment": { - "CMAKE_C_COMPILER": "gcc", - "CMAKE_CXX_COMPILER": "g++" + "CC": "gcc", + "CXX": "g++" }, "displayName": "Configure GCC for Ninja Debug/Release" }, @@ -44,10 +44,20 @@ "inherits": "multi", "binaryDir": "${sourceDir}/build-clang", "environment": { - "CMAKE_C_COMPILER": "clang", - "CMAKE_CXX_COMPILER": "clang++" + "CC": "clang", + "CXX": "clang++" }, "displayName": "Configure Clang for Ninja Debug/Release" + }, + { + "name": "clang-san", + "inherits": "multi-san", + "binaryDir": "${sourceDir}/build-clang-san", + "environment": { + "CC": "clang", + "CXX": "clang++" + }, + "displayName": "Configure Clang for Ninja Debug/Release with Sanitizers" } ], "buildPresets": [ @@ -80,6 +90,12 @@ "configurePreset": "clang", "displayName": "Build with Clang Debug" }, + { + "name": "debug-clang-san", + "inherits": "debug-san", + "configurePreset": "clang-san", + "displayName": "Build with Clang Debug and Sanitizers" + }, { "name": "release", "configurePreset": "multi", @@ -103,6 +119,12 @@ "inherits": "release", "configurePreset": "clang", "displayName": "Build with Clang Release" + }, + { + "name": "release-clang-san", + "inherits": "release-san", + "configurePreset": "clang-san", + "displayName": "Build with Clang Release and Sanitizers" } ], "testPresets": [ @@ -138,6 +160,12 @@ "configurePreset": "clang", "displayName": "Test the Clang Debug" }, + { + "name": "debug-clang-san", + "inherits": "debug-san", + "configurePreset": "clang-san", + "displayName": "Test the Clang Debug and Sanitizers" + }, { "name": "release", "inherits": "default", @@ -162,6 +190,12 @@ "inherits": "release", "configurePreset": "clang", "displayName": "Test the Clang Release" + }, + { + "name": "release-clang-san", + "inherits": "release-san", + "configurePreset": "clang-san", + "displayName": "Test the Clang Release with Sanitizers" } ], "workflowPresets": [ @@ -255,6 +289,24 @@ } ] }, + { + "name": "debug-clang-san", + "displayName": "Configure, Build and Test with Debug and Sanitizers using Clang", + "steps": [ + { + "type": "configure", + "name": "clang-san" + }, + { + "type": "build", + "name": "debug-clang-san" + }, + { + "type": "test", + "name": "debug-clang-san" + } + ] + }, { "name": "release", "displayName": "Configure, Build and Test with Release", @@ -327,6 +379,24 @@ } ] }, + { + "name": "release-clang-san", + "displayName": "Configure, Build and Test with Release and Sanitizers using Clang", + "steps": [ + { + "type": "configure", + "name": "clang-san" + }, + { + "type": "build", + "name": "release-clang-san" + }, + { + "type": "test", + "name": "release-clang-san" + } + ] + }, { "name": "multi", "displayName": "Configure, Build and Test with Debug and Release", From eaea662cc51ad9de810272e296d15c2e22fa9dbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Tue, 1 Jul 2025 07:59:25 +0200 Subject: [PATCH 20/21] Decreased CMake preset json version to 8 so that cmake-3.28 (Ubuntu) could read --- CMakePresets.json | 2 +- cmake/CommonPresets.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakePresets.json b/CMakePresets.json index c199215..0dd7936 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,5 +1,5 @@ { - "version": 10, + "version": 8, "cmakeMinimumRequired": { "major": 3, "minor": 28, diff --git a/cmake/CommonPresets.json b/cmake/CommonPresets.json index b63b4ca..9081855 100644 --- a/cmake/CommonPresets.json +++ b/cmake/CommonPresets.json @@ -1,5 +1,5 @@ { - "version": 10, + "version": 8, "cmakeMinimumRequired": { "major": 3, "minor": 28, From fa8ec81ce6c54f436ef05ffd72e419ea7c44ffd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Miku=C4=8Dionis?= Date: Tue, 1 Jul 2025 10:29:36 +0200 Subject: [PATCH 21/21] Added cmake script for sanitizers and set presets to use it instead --- CMakeLists.txt | 3 +- cmake/CommonPresets.json | 8 ++-- cmake/sanitizers.cmake | 99 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 cmake/sanitizers.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8248888..67c4927 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) include(cmake/warnings.cmake) +include(cmake/sanitizers.cmake) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) @@ -21,7 +22,7 @@ include(cmake/nlohmann_json.cmake) if(NOT LIBSTRATEGY_OnlyLibrary) cmake_policy(SET CMP0069 NEW) # INTERPROCEDURAL_OPTIMIZATIONS flags - if (CMAKE_MAJOR_VERSION GREATER_EQUAL 3 AND CMAKE_MINOR_VERSION GREATER_EQUAL 30) + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.30.0") cmake_policy(SET CMP0167 NEW) # use FindBoost from boost installation (as opposed to CMake own) endif () find_package(Boost 1.83 COMPONENTS program_options REQUIRED) diff --git a/cmake/CommonPresets.json b/cmake/CommonPresets.json index 9081855..0f6a6fe 100644 --- a/cmake/CommonPresets.json +++ b/cmake/CommonPresets.json @@ -22,10 +22,10 @@ "name": "multi-san", "inherits": "multi", "binaryDir": "${sourceDir}/build-multi-san", - "environment": { - "CFLAGS": "-fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", - "CXXFLAGS": "-fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer", - "LDFLAGS": "-fsanitize=undefined -fsanitize=address -fno-omit-frame-pointer" + "cacheVariables": { + "UBSAN" : { "type": "BOOL", "value": "ON"}, + "ASAN" : { "type": "BOOL", "value": "ON"}, + "SSP" : { "type": "BOOL", "value": "ON"} }, "displayName": "Configure for Ninja Debug/Release with Sanitizers" }, diff --git a/cmake/sanitizers.cmake b/cmake/sanitizers.cmake new file mode 100644 index 0000000..7471460 --- /dev/null +++ b/cmake/sanitizers.cmake @@ -0,0 +1,99 @@ +# Various sanitizers (runtime checks) for debugging +option(SSP "Stack Protection (GCC/Clang/ApplClang on Unix, MSVC on Windows)" OFF) +option(UBSAN "Undefined Behavior Sanitizer (GCC/Clang/AppleClang on Unix)" OFF) +option(ASAN "Address Sanitizer (GCC/Clang/AppleClang on Unix, MSVC on Windows)" OFF) +option(LSAN "Leak Sanitizer (GCC/Clang/AppleClang on Unix, MSVC on Windows)" OFF) +option(TSAN "Thread Sanitizer (GCC/Clang/AppleClang on Unix)" OFF) +option(RTC_C "Runtime Checks for Conversions (MSVC on Windows)" OFF) +option(RTC_S "Runtime Checks for Stack (MSVC on Windows)" OFF) +option(RTC_U "Runtime Checks for Uninitialized (MSVC on Windows)" OFF) + +if (SSP) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + # https://learn.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check + add_compile_options(/GS) + message(STATUS "Enabled Guard Stack") + else() + add_compile_options(-fstack-protector) + add_link_options(-fstack-protector) + message(STATUS "Enable Stack Protector") + endif () +endif(SSP) + +if (ASAN OR UBSAN OR LSAN OR TSAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + else() + add_compile_options(-fno-omit-frame-pointer) + add_link_options(-fno-omit-frame-pointer) + endif() +endif() + +if (UBSAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/fsanitize=undefined) + message(STATUS "Please see if MSVC supports undefined behavior sanitizer: https://learn.microsoft.com/en-us/cpp/sanitizers/asan") + else() + add_compile_options(-fsanitize=undefined) + add_link_options(-fsanitize=undefined) + endif() + message(STATUS "Enabled Undefined Behavior Sanitizer") +endif(UBSAN) + +if (ASAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/fsanitize=address) + else() + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) + endif() + message(STATUS "Enabled Address Sanitizer") +endif(ASAN) + +if (LSAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/fsanitize=leak) + else() + add_compile_options(-fsanitize=leak) + add_link_options(-fsanitize=leak) + endif() + message(STATUS "Enabled Leak Sanitizer") +endif(LSAN) + +if (TSAN) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/fsanitize=thread) + message(STATUS "Please see if MSVC supports thread sanitizer: https://learn.microsoft.com/en-us/cpp/sanitizers/asan") + else() + add_compile_options(-fsanitize=thread) + add_link_options(-fsanitize=thread) + endif() + message(STATUS "Enabled Thread Sanitizer") +endif(TSAN) + +if (RTC_C) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/RTCc) + add_compile_definitions(_ALLOW_RTCc_IN_STL) + message(STATUS "Enabled Runtime Check Conversions") + else() + message(WARNING "Runtime Check Conversions is not enabled for ${CMAKE_CXX_COMPILER_ID}") + endif() +endif(RTC_C) + +if (RTC_S) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/RTCs) + message(STATUS "Enabled Runtime Check Stack") + else() + message(WARNING "Runtime Check Stack is not enabled for ${CMAKE_CXX_COMPILER_ID}") + endif() +endif(RTC_S) + +if (RTC_U) + if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + add_compile_options(/RTCu) + message(STATUS "Enabled Runtime Check Uninitialized") + else() + message(WARNING "Runtime Check Uninitialized is not enabled for ${CMAKE_CXX_COMPILER_ID}") + endif() +endif(RTC_U)