From 4864b0ba338763318ff16e9a9f4cc0b841951433 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 5 Aug 2026 03:01:13 +0300 Subject: [PATCH 01/12] Changed the visualizer, fixed sonata tags: #219 --- .pre-commit-config.yaml | 14 +- examples/mnist-client/CMakeLists.txt | 39 +- examples/mnist-client/main.cpp | 13 +- examples/mnist-learn/CMakeLists.txt | 8 +- examples/mnist-learn/main.cpp | 49 +- .../models/blifat/construct_network.cpp | 32 +- examples/mnist-learn/training.h | 8 +- knp/base-framework/CMakeLists.txt | 4 + .../impl/sonata/load_network.cpp | 10 +- .../sonata/types/resource_blifat_neuron.cpp | 89 ++- .../impl/visualizer/utils_creation.cpp | 267 ++++++++ .../impl/visualizer/utils_creation.h | 62 ++ .../impl/visualizer/utils_dot.cpp | 318 ++++++++++ .../impl/visualizer/utils_dot.h | 154 +++++ .../impl/visualizer/utils_graph.cpp | 135 ++++ .../impl/visualizer/utils_graph.h | 89 +++ .../impl/visualizer/visualize_network.cpp | 589 +++++------------- .../framework/visualizer/visualize_network.h | 236 ++++--- .../framework/visualize_network_test.cpp | 222 +++++++ 19 files changed, 1743 insertions(+), 595 deletions(-) create mode 100644 knp/base-framework/impl/visualizer/utils_creation.cpp create mode 100644 knp/base-framework/impl/visualizer/utils_creation.h create mode 100644 knp/base-framework/impl/visualizer/utils_dot.cpp create mode 100644 knp/base-framework/impl/visualizer/utils_dot.h create mode 100644 knp/base-framework/impl/visualizer/utils_graph.cpp create mode 100644 knp/base-framework/impl/visualizer/utils_graph.h create mode 100644 knp/tests/framework/visualize_network_test.cpp diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 141e321e..da8c5dcb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -133,7 +133,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/PyCQA/autoflake - rev: v2.3.1 + rev: v2.3.3 hooks: - id: autoflake exclude: &fixtures tests(/\w*)*/functional/|tests/input|doc/data/messages|tests(/\w*)*data/ @@ -145,7 +145,7 @@ repos: - --remove-unused-variables - repo: https://github.com/pycqa/pylint - rev: "v4.0.4" # pylint-2.14.5 + rev: "v4.0.6" # pylint-2.14.5 hooks: - id: pylint args: ["-rn", "-sn", "--rcfile=knp/python-framework/packaging/common/.pylintrc", "--fail-on=I", "--load-plugins=pylint.extensions.docparams"] @@ -172,12 +172,12 @@ repos: # language_version: python3 - repo: https://github.com/pre-commit/pre-commit - rev: v4.5.1 + rev: v4.6.1 hooks: - id: validate_manifest - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 + rev: v2.3.0 hooks: - id: mypy args: ["--config-file", "knp/.mypy.ini", "--install-types", "--explicit-package-bases", "--check-untyped-defs", "--scripts-are-modules", "--strict"] @@ -204,20 +204,20 @@ repos: - id: pyupgrade - repo: https://github.com/ambv/black - rev: "25.12.0" + rev: "26.5.1" hooks: - id: black args: ["-l", "120", "--skip-string-normalization"] - repo: https://github.com/DanielNoord/pydocstringformatter - rev: v0.7.5 + rev: v1.0.0 hooks: - id: pydocstringformatter exclude: *fixtures args: ["--max-summary-lines=2", "--linewrap-full-docstring"] - repo: https://github.com/PyCQA/bandit - rev: 1.9.2 + rev: 1.9.4 hooks: - id: bandit args: ["-r", "-lll"] diff --git a/examples/mnist-client/CMakeLists.txt b/examples/mnist-client/CMakeLists.txt index 03560746..c641dd51 100644 --- a/examples/mnist-client/CMakeLists.txt +++ b/examples/mnist-client/CMakeLists.txt @@ -1,19 +1,19 @@ -#[[ -© 2024 AO Kaspersky Lab - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -]] - +#[[ +© 2024 AO Kaspersky Lab + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]] + cmake_minimum_required(VERSION 3.25) set(CMAKE_CXX_STANDARD 17) @@ -45,4 +45,9 @@ add_executable(mnist-client process_data.cpp ) -target_link_libraries(mnist-client PRIVATE KNP::BaseFramework::Core ${OpenCV_LIBS} ${Boost_LIBRARIES}) +find_package(PkgConfig REQUIRED) +pkg_check_modules(GRAPHVIZ REQUIRED IMPORTED_TARGET libgvc libcgraph) + +target_include_directories(mnist-client PUBLIC ${GRAPHVIZ_INCLUDE_DIRS}) +target_link_libraries(mnist-client PUBLIC PkgConfig::GRAPHVIZ ) +target_link_libraries(mnist-client PRIVATE KNP::BaseFramework::Core ${OpenCV_LIBS} ${Boost_LIBRARIES} ) diff --git a/examples/mnist-client/main.cpp b/examples/mnist-client/main.cpp index 6b25e0e0..3642b6c9 100644 --- a/examples/mnist-client/main.cpp +++ b/examples/mnist-client/main.cpp @@ -31,7 +31,6 @@ #include "inference.h" - // Namespace for program options. namespace po = boost::program_options; @@ -74,15 +73,9 @@ int main(int argc, char **argv) // https://click.kaspersky.com/?hl=en-US&version=2.0&pid=KNP&link=online_help&helpid=235801 // https://click.kaspersky.com/?hl=en-US&version=2.0&pid=KNP&link=online_help&helpid=274991 knp::framework::Network network = knp::framework::sonata::load_network(network_path); - // Constructs a network graph. - const knp::framework::NetworkGraph net_graph(network); - // Prints descriptions of graph connections. - knp::framework::print_network_description(net_graph); - // Draws a subgraph in the OpenCV window. - // Press `Esc` to exit the OpenCV window. - knp::framework::position_network_test( - knp::framework::NetworkGraph(network), knp::framework::divide_graph_by_connectivity(net_graph)[0], - {1000, 700}); + + // knp::framework::set_saving_path("temp_test_dir"); + knp::framework::visualize_network(network); } // If `task=infer`, the function loads a network and runs inference. diff --git a/examples/mnist-learn/CMakeLists.txt b/examples/mnist-learn/CMakeLists.txt index b86476ae..7e503706 100644 --- a/examples/mnist-learn/CMakeLists.txt +++ b/examples/mnist-learn/CMakeLists.txt @@ -26,6 +26,8 @@ if (NOT TARGET KNP::BaseFramework::Core) endif() find_package(Boost ${KNP_BOOST_MIN_VERSION} COMPONENTS program_options REQUIRED) +find_package(PkgConfig REQUIRED) +pkg_check_modules(GRAPHVIZ REQUIRED IMPORTED_TARGET libgvc libcgraph) add_executable(${PROJECT_NAME} main.cpp @@ -40,4 +42,8 @@ add_executable(${PROJECT_NAME} models/altai/prepare_network_for_inference.cpp models/altai/spike_generators.cpp) target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}) -target_link_libraries(${PROJECT_NAME} PRIVATE KNP::BaseFramework::Core Boost::program_options) +target_link_libraries(${PROJECT_NAME} PRIVATE + KNP::BaseFramework::Core + Boost::program_options + ${Graphviz_LIBRARIES} + ) diff --git a/examples/mnist-learn/main.cpp b/examples/mnist-learn/main.cpp index 1d3d8ac6..384587f4 100644 --- a/examples/mnist-learn/main.cpp +++ b/examples/mnist-learn/main.cpp @@ -1,8 +1,6 @@ /** * @file main.cpp * @brief Example of training a MNIST network. - * @kaspersky_support D. Postnikov - * @date 03.02.2026 * @license Apache 2.0 * @copyright © 2026 AO Kaspersky Lab * @@ -19,8 +17,16 @@ * limitations under the License. */ +#include +#include +#include +#include +#include + #include +#include + #include "dataset.h" #include "evaluate_results.h" #include "inference.h" @@ -31,6 +37,7 @@ /** * @brief Run whole model. + * * @tparam Neuron Neuron type. * @param model_desc Model description. */ @@ -55,9 +62,11 @@ void run_model(const ModelDescription& model_desc) /** * @brief Main function. + * * @param argc Argument count. * @param argv Arguments value. - * @return Error code. + * + * @return Error code.source_node_idnode_population: 00000000-0000-0000-0000-000000000000 */ int main(int argc, char** argv) { @@ -71,22 +80,24 @@ int main(int argc, char** argv) std::cin.get(); std::cout << "Starting model." << std::endl; - // Starting model according to selected type. - switch (model_desc.type_) - { - case SupportedModelType::BLIFAT: - { - run_model(model_desc); - break; - } - case SupportedModelType::AltAI: - { - run_model(model_desc); - break; - } - default: - throw std::runtime_error("Unknown model type."); - } + + Dataset dataset = process_dataset(model_desc); + + AnnotatedNetwork network = construct_network(model_desc); + + + knp::framework::BackendLoader backend_loader; + auto backend = train_model(model_desc, dataset, network, backend_loader); + + if (!model_desc.model_saving_path_.empty()) save_network(model_desc, network); + + // knp::framework::set_saving_path("temp_test_dir"); + visualize_network(network.network_); + visualize_network(network.network_, backend); + visualize_bus(network.network_, backend); + + auto network_path = model_desc.model_saving_path_; + return EXIT_SUCCESS; } diff --git a/examples/mnist-learn/models/blifat/construct_network.cpp b/examples/mnist-learn/models/blifat/construct_network.cpp index 5179c708..f5be84d9 100644 --- a/examples/mnist-learn/models/blifat/construct_network.cpp +++ b/examples/mnist-learn/models/blifat/construct_network.cpp @@ -48,19 +48,19 @@ using ResourceNeuronData = knp::neuron_traits::neuron_parameters -AnnotatedNetwork construct_network(const ModelDescription &model_desc) +AnnotatedNetwork construct_network(const ModelDescription& model_desc) { AnnotatedNetwork result; @@ -187,7 +184,6 @@ AnnotatedNetwork construct_network(const Model // Add input_pop as WTA sender. result.data_.wta_data_.emplace_back().first.push_back(pops.input_pop_.uid_); - create_projections(result, constructor, pops); } diff --git a/examples/mnist-learn/training.h b/examples/mnist-learn/training.h index 5d4c959b..4c12ae36 100644 --- a/examples/mnist-learn/training.h +++ b/examples/mnist-learn/training.h @@ -26,10 +26,10 @@ #include #include -#include -#include #include #include +#include +#include #include #include #include @@ -100,6 +100,7 @@ void train_network( // Online Help link: https://click.kaspersky.com/?hl=en-US&version=2.0&pid=KNP&link=online_help&helpid=235849 knp::framework::Model model(std::move(network.network_)); + // knp::framework::Model model(network.network_); knp::framework::ModelLoader::InputChannelMap channel_map = build_channel_map_train(network, model, dataset); @@ -162,7 +163,7 @@ void train_network( * @param backend_loader Backend loader. */ template -void train_model( +std::shared_ptr train_model( const ModelDescription& model_desc, const Dataset& dataset, AnnotatedNetwork& network, knp::framework::BackendLoader& backend_loader) { @@ -170,4 +171,5 @@ void train_model( train_network(training_backend, network, model_desc, dataset); prepare_network_for_inference(training_backend, model_desc, network); + return training_backend; } diff --git a/knp/base-framework/CMakeLists.txt b/knp/base-framework/CMakeLists.txt index 0e590a46..6bf92b50 100644 --- a/knp/base-framework/CMakeLists.txt +++ b/knp/base-framework/CMakeLists.txt @@ -79,10 +79,14 @@ knp_add_library("${PROJECT_NAME}-core" impl/logging.cpp impl/visualizer/graph_physics.cpp impl/visualizer/visualize_network.cpp + impl/visualizer/utils_graph.cpp + impl/visualizer/utils_dot.cpp + impl/visualizer/utils_creation.cpp ${${PROJECT_NAME}_headers} ALIAS KNP::BaseFramework::Core LINK_PRIVATE spdlog::spdlog_header_only Boost::headers Boost::filesystem HighFive hdf5-static csv2 + ${GRAPHVIZ_LIBRARIES} ${OpenCV_LIBS} # Hack to build with CLang. ${ADD_LIBS} diff --git a/knp/base-framework/impl/sonata/load_network.cpp b/knp/base-framework/impl/sonata/load_network.cpp index b0ac757d..be65fdcd 100644 --- a/knp/base-framework/impl/sonata/load_network.cpp +++ b/knp/base-framework/impl/sonata/load_network.cpp @@ -99,16 +99,16 @@ std::vector load_populations(const fs::path &pop_h5 for (size_t i = 0; i < num_populations; ++i) { - const std::string proj_name = group.getObjectName(i); + const std::string pop_name = group.getObjectName(i); const int neuron_type = - group.getGroup(proj_name).getDataSet("node_type_id").read>()[0]; // One type only. + group.getGroup(pop_name).getDataSet("node_type_id").read>()[0]; // One type only. // Check if type is in type_file. if (neuron_type == get_neuron_type_id()) - result.emplace_back(load_population(group, proj_name)); + result.emplace_back(load_population(group, pop_name)); else if (neuron_type == get_neuron_type_id()) - result.emplace_back(load_population(group, proj_name)); + result.emplace_back(load_population(group, pop_name)); else if (neuron_type == get_neuron_type_id()) - result.emplace_back(load_population(group, proj_name)); + result.emplace_back(load_population(group, pop_name)); // TODO: Add other supported types or better use a template. } return result; diff --git a/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp b/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp index b487cbca..d6343221 100644 --- a/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp +++ b/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp @@ -20,12 +20,17 @@ */ #include +#include #include +#include #include #include #include +#include +#include + #include #include "../csv_content.h" @@ -45,6 +50,43 @@ std::string get_neuron_type_name &population, + HighFive::NodeTraits &population_group) +{ + core::TagMap tags = population.get_tags(); + + std::vector tags_values; + std::vector tags_keys; + + if (tags.exists("io_type")) + { + auto io_type_tag_value = std::any_cast(tags["io_type"]); + + if (io_type_tag_value == knp::core::tags::IOType::input) + { + tags_values.push_back("0"); + } + else if (io_type_tag_value == knp::core::tags::IOType::output) + { + tags_values.push_back("1"); + } + else + { + std::cout << "unknown io_type tag value" << std::endl; + } + tags_keys.push_back("io_type"); + } + auto name_tag_value = std::any_cast(tags["name"]); + tags_values.push_back(name_tag_value); + tags_keys.push_back("name"); + + population_group.createDataSet("tags_values", tags_values); + population_group.createDataSet("tags_keys", tags_keys); +} + template <> void add_population_to_h5>( HighFive::File &file_h5, const core::Population &population) @@ -70,6 +112,8 @@ void add_population_to_h5(population.size(), get_neuron_type_id())); + + add_tags_to_h5(population, population_group); auto group0 = population_group.createGroup("0"); // TODO: Need to check if all parameters are the same. If not, then save them into h5. @@ -141,6 +185,43 @@ void add_population_to_h5; +// Two arrays are uploaded: keys and tag values. +// They are tagged with the population in accordance with the TagMap structure. +void load_tags_to_population( + const HighFive::Group &nodes_group, const std::string &population_name, + core::Population &population) +{ + auto ds_tags_values = nodes_group.getGroup(population_name).getDataSet("tags_values"); + auto ds_tags_keys = nodes_group.getGroup(population_name).getDataSet("tags_keys"); + auto size = ds_tags_values.getElementCount(); + std::vector data_tags_values(size); + std::vector data_tags_keys(size); + ds_tags_values.read(data_tags_values); + ds_tags_keys.read(data_tags_keys); + + for (size_t i = 0; i < size; ++i) + { + auto key = data_tags_keys[i]; + auto value = data_tags_values[i]; + + if (key == "io_type") + { + switch (std::stoi(value)) + { + case 0: + population.get_tags()[key] = knp::core::tags::IOType::input; + break; + case 1: + population.get_tags()[key] = knp::core::tags::IOType::output; + break; + default: + std::cout << "unknown io_type tag value" << std::endl; + } + continue; + } + population.get_tags()[key] = value; + } +} template <> core::Population @@ -205,8 +286,12 @@ load_population( LOAD_NEURONS_PARAMETER(target, neuron_traits::BLIFATNeuron, additional_threshold_, dyn_group, group_size); const knp::core::UID uid{boost::lexical_cast(population_name)}; - return core::Population( + + auto result = core::Population( uid, [&target](size_t index) { return target[index]; }, group_size); -} + load_tags_to_population(nodes_group, population_name, result); + + return result; +} } // namespace knp::framework::sonata diff --git a/knp/base-framework/impl/visualizer/utils_creation.cpp b/knp/base-framework/impl/visualizer/utils_creation.cpp new file mode 100644 index 00000000..fd27eb52 --- /dev/null +++ b/knp/base-framework/impl/visualizer/utils_creation.cpp @@ -0,0 +1,267 @@ +#include "utils_creation.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "utils_dot.h" +#include "utils_graph.h" + +extern "C" +{ +#include +} + +/** + * @brief Create DOT file for static network + * + * @param file_name output file name + * @param graph network graph + */ +void create_dot_file_for_static_network(std::string& file_name, knp::framework::NetworkGraph& graph) +{ + std::ofstream out(file_name); + + if (!out.is_open()) + { + SPDLOG_ERROR("Cannot open file {} for writing", file_name); + return; + } + + write_header_and_styles(out); + for (size_t i = 0; i < graph.nodes_.size(); ++i) + { + auto node = graph.nodes_[i]; + //write_node_to_dot(out, i, node.name_, node.size_); + out << " " << i << " [label=\"population:\\n" + << node.name_ << "\\nsize: " << node.size_ << "\", color=\"" + << "\"];\n"; + } + for (const auto& edge : graph.edges_) + { + if (edge.index_from_ == -1) + { + // write_edge_to_dot(out, "00000000", edge.index_to_, edge.name_); + // out << " " << "00000000" << " -> " << edge.index_to_ << " [label=\"projection :\\n" << edge.name_ << + // "\"];\n"; + continue; + } + // write_edge_to_dot(out, edge.index_from_, edge.index_to_, edge.name_); + out << " " << edge.index_from_ << " -> " << edge.index_to_ << " [label=\"projection :\\n" + << edge.name_ << "\"];\n"; + } + out << "}\n"; + if (out.good()) + { + SPDLOG_INFO("The DOT file is saved in: {}", std::filesystem::absolute(file_name).string()); + } + else + { + SPDLOG_ERROR("Failed to write to file {}", file_name); + } +} + +/** + * @brief Create DOT file for bus messages + * + * @param file_name output DOT file name + * @param graph network graph + * @param backend shared pointer to backend + */ +void create_dot_file_for_bus( + const std::string& file_name, const knp::framework::NetworkGraph& graph, + std::shared_ptr& backend) +{ + auto [spike_messages, synaptic_messages] = get_bus(backend); + + std::ofstream out(file_name); + + write_bus_messeges_to_dot(spike_messages, synaptic_messages, graph, out); // spike/synaptic impact messeges + + SPDLOG_INFO("The DOT file is saved in: {}", std::filesystem::absolute(file_name).string()); +} + +/** + * @brief Create DOT file for dynamic network + * + * @param file_name output file name + * @param graph network graph + * @param backend shared pointer to backend + */ +void create_dot_file_for_dynamic_network( + const std::string& file_name, const knp::framework::NetworkGraph& graph, + std::shared_ptr& backend) +{ + const auto subs = backend->get_message_endpoint().get_endpoint_subscriptions(); + + constexpr size_t SPIKE_IDX = 0; // must be 0 + constexpr size_t SYNAPTIC_IMPACT_IDX = 1; // must be 1 + + std::map node_src; + std::map node_dst; + + std::set unique_senders_spike; + std::set unique_receivers_impact; + + std::set unique_nodes; + std::set unique_edges; + + + std::vector> spike_messages; // [sender, receiver] + std::vector> synaptic_messages; // [sender, receiver] + + + // take from bus nessosary maps + for (const auto& [key, sub_variant] : subs) + { + const auto& [type_idx, receiver_uid] = key; + + std::visit( + [&](const auto& sub) + { + const auto& senders = sub.get_senders(); + if (senders.empty()) return; + + if (type_idx == SPIKE_IDX) + { // receiver: Projection, sender: *Population (* - or modificator) // 0 --> + for (const auto& sender : senders) + { + spike_messages.push_back({sender, receiver_uid}); + unique_senders_spike.insert(sender); + node_src[receiver_uid] = sender; + // edge_from[sender] = receiver_uid; + + unique_nodes.insert(sender); + unique_edges.insert(receiver_uid); + } + } + else if (type_idx == SYNAPTIC_IMPACT_IDX) + { // receiver: *Population, sender: Projection (* - or modificator) // --> 0 + for (const auto& sender : senders) + { + synaptic_messages.push_back({sender, receiver_uid}); + unique_receivers_impact.insert(receiver_uid); + node_dst[sender] = receiver_uid; + // edge_to[receiver_uid] = sender; + unique_nodes.insert(receiver_uid); + unique_edges.insert(sender); + } + } + }, + sub_variant); + } + + + std::set modificators; + + + // modificator exist in senders SPIKE + // modificator doesn't exist in receivers SYNAPTIC_IMPACT + for (const auto& uid : unique_senders_spike) + { + if (unique_receivers_impact.find(uid) == unique_receivers_impact.end()) + { + modificators.insert(uid); + SPDLOG_INFO("Modificator: {}", std::string(uid)); + } + } + + //////////////////////////////////////// + + std::ofstream out(file_name); + std::string dynamic_color = "yellow"; + write_projections_and_populations_to_dot( + unique_nodes, unique_edges, modificators, out, dynamic_color, graph, node_src, + node_dst); // projections/populations from bus + + SPDLOG_INFO("The DOT file is saved in: {}", std::filesystem::absolute(file_name).string()); +} + +///////////////// dot to png ///////////////////// +/** + * @brief Convert DOT file to PNG image + * + * @param path_to_dot_file path to input DOT file + * @param path_to_png_file path to output PNG file + * + * @return boolean indicating success + */ +bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& path_to_png_file) +{ + // 1. Initialize the Graphviz context + auto gvc = std::unique_ptr(gvContext(), gvFreeContext); + if (!gvc) return false; + + // 2. Open and parse the DOT file + // auto fp = std::unique_ptr(fopen(path_to_dot_file.c_str(), "r"), fclose); + struct FileDeleter + { + void operator()(FILE* fp) const + { + if (fp) + { + fclose(fp); + } + } + }; + std::unique_ptr fp(fopen(path_to_dot_file.c_str(), "r")); + + if (!fp) + { + SPDLOG_ERROR("Error: couldn't open the DOT file: {}", std::filesystem::absolute(path_to_dot_file).string()); + return false; + } + + // 3. Read the DOT file + auto g = std::unique_ptr(agread(fp.get(), nullptr), agclose); + if (!g) + { + SPDLOG_ERROR("Error: couldn't read DOT file: {}", std::filesystem::absolute(path_to_dot_file).string()); + return false; + } + + // 4. Compute the layout using the "dot" engine + // gvLayoutJobs(gvc.get(), g.get()); + int layout_res = gvLayoutJobs(gvc.get(), g.get()); + if (layout_res != 0) + { + SPDLOG_ERROR("Layout computation failed"); + return false; + } + // gvLayout(gvc.get(), g.get(), "dot"); + + // 5. Render the layout into a PNG file + int result = gvRenderFilename(gvc.get(), g.get(), "png", path_to_png_file.c_str()); + + + // 6. Check the result and print the path to the PNG file + if (result == 0) + { + SPDLOG_INFO("The PNG file is saved in: {}", std::filesystem::absolute(path_to_png_file).string()); + return true; + } + else + { + SPDLOG_ERROR("PNG file rendering error"); + return false; + } +} + +/** + * @brief Create PNG file from DOT file + * + * @param dot_file path to DOT file + * @param png_file path to PNG file + */ +void create_png_file(const std::string& dot_file, const std::string& png_file) +{ + // system("dot -Tpng ./store_log_file3/graph.dot -o ./store_log_file3/graph.png"); + if (!convert_dot_to_png(dot_file, png_file)) SPDLOG_ERROR("Dot file conversion failed."); +} diff --git a/knp/base-framework/impl/visualizer/utils_creation.h b/knp/base-framework/impl/visualizer/utils_creation.h new file mode 100644 index 00000000..a31efe72 --- /dev/null +++ b/knp/base-framework/impl/visualizer/utils_creation.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include + +#include + +#include +#include + +extern "C" +{ +#include +} + + +/** + * @brief Create DOT file for static network + * + * @param file_name output file name + * @param graph network graph + */ +void create_dot_file_for_static_network(std::string& file_name, knp::framework::NetworkGraph& graph); + +/** + * @brief Create DOT file for bus messages + * + * @param file_name output DOT file name + * @param graph network graph + * @param backend shared pointer to backend + */ +void create_dot_file_for_bus( + const std::string& file_name, const knp::framework::NetworkGraph& graph, + std::shared_ptr& backend); + +/** + * @brief Create DOT file for dynamic network + * + * @param file_name output file name + * @param graph network graph + * @param backend shared pointer to backend + */ +void create_dot_file_for_dynamic_network( + const std::string& file_name, const knp::framework::NetworkGraph& graph, + std::shared_ptr& backend); + +/** + * @brief Convert DOT file to PNG image + * + * @param path_to_dot_file path to input DOT file + * @param path_to_png_file path to output PNG file + * + * @return boolean indicating success + */ +bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& path_to_png_file); + +/** + * @brief Create PNG file from DOT file + * + * @param dot_file path to DOT file + * @param png_file path to PNG file + */ +void create_png_file(const std::string& dot_file, const std::string& png_file); diff --git a/knp/base-framework/impl/visualizer/utils_dot.cpp b/knp/base-framework/impl/visualizer/utils_dot.cpp new file mode 100644 index 00000000..8cb0eb59 --- /dev/null +++ b/knp/base-framework/impl/visualizer/utils_dot.cpp @@ -0,0 +1,318 @@ +#include "utils_dot.h" + +#include + +#include + +#include +#include +#include +#include +#include + +#include "utils_graph.h" + + +/** + * @brief Write header and styles to DOT file + * + * @param out output file stream + * + * @details commentsed - for a connected graph + */ +void write_header_and_styles(std::ofstream& out) +{ + // digraph - directed + out << "digraph G {\n"; + + // for unconnected graphs this is needed: (+ change -> to --) + out << " layout=neato;\n"; // Change engine to neato (physical force model) + out << " pack=true;\n"; // Enable compact packing of independent components + out << " packmode=\"graph\";\n\n"; // Pack each subgroup as a separate mini-graph + // + + // Global design settings for the graph + out << " // Canvas settings\n"; + out << " bgcolor=\"#FAFAFA\";\n"; // Light gray soft background page + // out << " rankdir=LR;\n"; // Graph goes left to right + out << " splines=true;\n"; // Beautiful smooth curves for arrows + // out << " nodesep=0.5;\n"; // Distance between nodes + // out << " ranksep=0.6;\n\n"; // Distance between levels (layers) of the graph + out << " overlap=false;\n"; + out << " sep=\"+30\";\n"; + + out << " // Default node styling\n"; + out << " node [\n"; + out << " fontname=\"Helvetica,Arial,sans-serif\",\n"; // Modern font + out << " fontsize=11,\n"; + out << " shape=box,\n"; // Rectangular shape + out << " style=\"filled,rounded\",\n"; // Fill with color + rounded corners + out << " penwidth=0,\n"; // Remove rigid black border around node + out << " margin=\"0.2,0.1\"\n"; // Internal padding for text + out << " ];\n\n"; + + out << " // Default edge (arrow) styling\n"; + out << " edge [\n"; + out << " fontname=\"Helvetica,Arial,sans-serif\",\n"; + out << " fontsize=9,\n"; + out << " fontcolor=\"#555555\",\n"; // Soft gray color for text above arrows + out << " color=\"#B0BEC5\",\n"; // Beautiful steel color for the arrow itself + out << " penwidth=1.5,\n"; // Make line slightly thicker than default + out << " arrowsize=0.8\n"; // Slightly reduce arrowhead size + out << " ];\n\n"; +} + +/** + * @brief Write edge without source to DOT file + * + * @param out output file stream + * @param dst destination node UID + * @param i counter for ghost node + * @param edge_uid edge UID + * @param size edge size + * @param color edge color + * + * @details A graphviz can't draw an arrow from nowhere. It is necessary to make an invisible vertex. + */ +void write_edge_without_src( + std::ofstream& out, knp::core::UID const& dst, int const& i, knp::core::UID const& edge_uid, + std::string const& size, std::string const& color) +{ + auto num = std::to_string(i); + auto short_name = std::string(edge_uid).substr(0, 8); + + + out << " " + << "ghost_src_" << num << " [shape=point, style=invis" + << "];\n"; + + + out << " \"" + << "ghost_src_" << num << "\" -> " << std::string(dst) << "[style=\"dotted\", " + << "label=\"projection :\\n" + << short_name << "\\nsize:" << size << "\", color=\"" << color << "\"];\n"; +} + +/** + * @brief Write edge without destination to DOT file + * + * @param out output file stream + * @param src source node UID + * @param i counter for ghost node + * @param edge_uid edge UID + * @param size edge size + * @param color edge color + * + * @details A graphviz can't draw an arrow to nowhere. It is necessary to make an invisible vertex. + */ +void write_edge_without_dst( + std::ofstream& out, knp::core::UID const& src, int const& i, knp::core::UID const& edge_uid, + std::string const& size, std::string const& color) +{ + auto num = std::to_string(i); + auto short_name = std::string(edge_uid).substr(0, 8); + + out << " " + << "ghost_dst_" << num << " [shape=point, style=invis" + << "];\n"; + + out << " \"" << std::string(src) << "\" -> " + << "ghost_dst_" << num << "[style=\"dotted\", " + << "label=\"projection :\\n" + << short_name << "\\nsize:" << size << "\", color=\"" << color << "\"];\n"; +} + +/** + * @brief Write bus messages (spike messages and synaptic impact messages) to DOT file + * + * @param spike_messages vector of spike message pairs + * @param synaptic_messages vector of synaptic message pairs + * @param graph network graph + * @param out output file stream for DOT file + */ +void write_bus_messeges_to_dot( + std::vector> const& spike_messages, + std::vector> const& synaptic_messages, + knp::framework::NetworkGraph const& graph, std::ofstream& out) +{ + write_header_and_styles(out); + + for (auto spike_message : spike_messages) + { + auto spike_sender = spike_message.first; + auto spike_receiver = spike_message.second; + auto sender_name = std::string(spike_sender).substr(0, 8); + auto receiver_name = std::string(spike_receiver).substr(0, 8); + std::string sender_size = ""; + std::string receiver_size = ""; + + auto node_it = std::find_if( + graph.nodes_.begin(), graph.nodes_.end(), + [&spike_sender](auto const& node) { return node.uid_ == spike_sender; }); + if (node_it != graph.nodes_.end()) + { + sender_name = node_it->name_; + sender_size = std::to_string(node_it->size_); + } + + auto edge_it = std::find_if( + graph.edges_.begin(), graph.edges_.end(), + [&spike_receiver](const auto& edge) { return edge.uid_ == spike_receiver; }); + if (edge_it != graph.edges_.end()) + { + receiver_name = edge_it->name_; + receiver_size = std::to_string(edge_it->size_); + } + + auto index_from = std::string(spike_sender); + auto index_to = std::string(spike_receiver); + + // write nodes + out << " \"" << index_from << "\" [label=\"" << sender_name << "\"];" + << "# " << sender_size << "\n"; + out << " \"" << index_to << "\" [label=\"" << receiver_name << "\"];" + << "# " << receiver_size << "\n"; + + // write edge + out << " \"" << index_from << "\" -> \"" << index_to << "\" [label=\"Spike Message" + << "\"];\n"; + } + + for (auto synaptic_message : synaptic_messages) + { + auto synaptic_sender = synaptic_message.first; + auto synaptic_receiver = synaptic_message.second; + auto sender_name = std::string(synaptic_sender).substr(0, 8); + auto receiver_name = std::string(synaptic_receiver).substr(0, 8); + std::string sender_size = ""; + std::string receiver_size = ""; + + auto edge_it = std::find_if( + graph.edges_.begin(), graph.edges_.end(), + [&synaptic_sender](const auto& edge) { return edge.uid_ == synaptic_sender; }); + if (edge_it != graph.edges_.end()) + { + sender_name = edge_it->name_; + sender_size = std::to_string(edge_it->size_); + } + + auto node_it = std::find_if( + graph.nodes_.begin(), graph.nodes_.end(), + [&synaptic_receiver](const auto& node) { return node.uid_ == synaptic_receiver; }); + if (node_it != graph.nodes_.end()) + { + receiver_name = node_it->name_; + receiver_size = std::to_string(node_it->size_); + } + + auto index_from = std::string(synaptic_sender); + auto index_to = std::string(synaptic_receiver); + + // write nodes + out << " \"" << index_from << "\" [label=\"" << sender_name << "\"];" + << "# " << sender_size << "\n"; + out << " \"" << index_to << "\" [label=\"" << receiver_name << "\"];" + << "# " << receiver_size << "\n"; + + // write edge + out << " \"" << index_from << "\" -> \"" << index_to << "\" [label=\"Synaptic Impact Message" + << "\"];\n"; + } + + out << "}\n"; +} + +/** + * @brief Write projections and populations to DOT file + * + * @param unique_nodes set of unique node UIDs + * @param unique_edges set of unique edge UIDs + * @param modificators set of modificator UIDs + * @param out output file stream + * @param dynamic_color color for dynamic elements + * @param graph network graph + * @param node_src map of node sources + * @param node_dst map of node destinations + */ +void write_projections_and_populations_to_dot( + std::set const& unique_nodes, std::set const& unique_edges, + std::set& modificators, std::ofstream& out, std::string& dynamic_color, + knp::framework::NetworkGraph const& graph, std::map& node_src, + std::map& node_dst) +{ + write_header_and_styles(out); + + if (unique_nodes.empty() && unique_edges.empty()) + { + SPDLOG_WARN("No nodes or edges to visualize"); + out << "}\n"; + return; + } + + /////// write nodes to dot ////////// + for (const auto& unique_node : unique_nodes) + { + // check if it is modificator + if (modificators.find(unique_node) != modificators.end()) + { + write_node_to_dot( + out, std::string(unique_node), std::string(unique_node).substr(0, 8), "None", dynamic_color, + "Modificator"); + continue; + } + auto graph_node = get_graph_node_by_uid(unique_node, graph.nodes_); + auto node_name = get_node_name(unique_node, graph.nodes_); + auto graph_node_size = graph_node.size_; + write_node_to_dot(out, std::string(unique_node), node_name, graph_node_size); + } + + /////// write edges to dot ////////// + [[maybe_unused]] int num_invisible_src_nodes = 1; + [[maybe_unused]] int num_invisible_dst_nodes = 1; + for (const auto& unique_edge : unique_edges) + { + auto edge_size = get_proj_size(graph.edges_, unique_edge); + auto it1 = node_src.find(unique_edge); + if (it1 == node_src.end()) + { + SPDLOG_INFO("There is't node_src for this edge: {}", std::string(unique_edge)); + knp::core::UID dst; + try + { + dst = node_dst.at(unique_edge); + } + catch (const std::out_of_range& e) + { + SPDLOG_ERROR("ERROR: There is an edge without a source and a destination! Details: {}", e.what()); + } + //write node to dot (ghost_i -> dst) + write_edge_without_src(out, dst, num_invisible_dst_nodes, unique_edge, edge_size); + num_invisible_src_nodes++; + continue; + } + auto src = node_src.at(unique_edge); + + auto it2 = node_dst.find(unique_edge); + if (it2 == node_dst.end()) + { + SPDLOG_INFO("There is't node_dst for this edge: {}", std::string(unique_edge)); + //write node to dot (src -> ghost_j) + write_edge_without_dst(out, src, num_invisible_dst_nodes, unique_edge, edge_size); + num_invisible_dst_nodes++; + continue; + } + auto dst = node_dst.at(unique_edge); + + // check if the source is a modifier (change color of edge) + if (modificators.find(src) != modificators.end()) + { + write_edge_to_dot( + out, std::string(src), std::string(dst), std::string(unique_edge).substr(0, 8), edge_size, + dynamic_color); + continue; + } + write_edge_to_dot(out, std::string(src), std::string(dst), std::string(unique_edge).substr(0, 8), edge_size); + } + + out << "}\n"; +} diff --git a/knp/base-framework/impl/visualizer/utils_dot.h b/knp/base-framework/impl/visualizer/utils_dot.h new file mode 100644 index 00000000..8aa73312 --- /dev/null +++ b/knp/base-framework/impl/visualizer/utils_dot.h @@ -0,0 +1,154 @@ +#pragma once +#include +#include + +#include + +#include +#include +#include +#include +#include + +extern "C" +{ +#include +} + + +/** + * @brief Write header and styles to DOT file + * + * @param out output file stream + * + * @details commentsed - for a connected graph + */ +void write_header_and_styles(std::ofstream& out); + +/** + * @brief Write edge to DOT file + * + * @param out output file stream + * @param index_from source node UID + * @param index_to target node UID + * @param name edge name + * @param size edge size + * @param color edge color + * + * @details need to visualize dynamic + */ +template +void write_edge_to_dot( + std::ofstream& out, T index_from, T index_to, T name, T size, std::string const& color = "lightgray") +{ + // example of dot line: + // "bdd469c7-66f2-43d9-b751-fb1e20732d3d" -> "12fe2450-0f03-4d93-a1e0-8d2f7a9277ae" + // [label="Projection:\nfced41d5\nsize: 200", color="lightgray"]; + + out << " \"" << index_from << "\" -> \"" << index_to << "\" [label=\"Projection:\\n" + << name << "\\nsize: " << size << "\", color=\"" << color << "\"];\n"; +} + +/** + * @brief Write node to DOT file + * + * @param out output file stream + * @param node_uid node UID + * @param name node name + * @param size node size + * @param color node color + * @param type_node node type + * + * @details need to visualize dynamic + */ +template +void write_node_to_dot( + std::ofstream& out, std::string const& node_uid, std::string const& name, T size, + std::string const& color = "lightgray", std::string const& type_node = "Population") +{ + //examples: + // "bdd469c7-66f2-43d9-b751-fb1e20732d3d" [label="Modificator:\nbdd469c7", color="yellow"]; + // "48bebcd2-80f6-488c-bbe8-0627cebc8af7" [label="Population:\nOUTPUT\nsize: 10", color="lightgray"]; + + if constexpr (std::is_same_v) + { + out << " \"" << node_uid << "\" [label=\"" << type_node << ":\\n" + << name << "\\nsize: " << size << "\", color=\"" << color << "\"];\n"; + return; + } + + out << " \"" << node_uid << "\" [label=\"" << type_node << ":\\n" << name << "\", color=\"" << color << "\"];\n"; +} + +/** + * @brief Write edge without source to DOT file + * + * @param out output file stream + * @param dst destination node UID + * @param i counter for ghost node + * @param edge_uid edge UID + * @param size edge size + * @param color edge color + * + * @details A graphviz can't draw an arrow from nowhere. It is necessary to make an invisible vertex. + */ +void write_edge_without_src( + std::ofstream& out, knp::core::UID const& dst, int const& i, knp::core::UID const& edge_uid, + std::string const& size, std::string const& color = "lightgray"); + +/** + * @brief Write edge without destination to DOT file + * + * @param out output file stream + * @param src source node UID + * @param i counter for ghost node + * @param edge_uid edge UID + * @param size edge size + * @param color edge color + * + * @details A graphviz can't draw an arrow to nowhere. It is necessary to make an invisible vertex. + */ +void write_edge_without_dst( + std::ofstream& out, knp::core::UID const& src, int const& i, knp::core::UID const& edge_uid, + std::string const& size, std::string const& color = "lightgray"); + +/** + * @brief Convert DOT file to PNG image + * + * @param path_to_dot_file path to input DOT file + * @param path_to_png_file path to output PNG file + * + * @return boolean indicating success + */ +bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& path_to_png_file); + +/** + * @brief Write bus messages (spike messages and synaptic impact messages) to DOT file + * + * @param spike_messages vector of spike message pairs + * @param synaptic_messages vector of synaptic message pairs + * @param graph network graph + * @param out output file stream for DOT file + */ +void write_bus_messeges_to_dot( + std::vector> const& spike_messages, + std::vector> const& synaptic_messages, + knp::framework::NetworkGraph const& graph, std::ofstream& out); + +/** + * @brief Write projections and populations to DOT file + * + * @param unique_nodes set of unique node UIDs + * @param unique_edges set of unique edge UIDs + * @param modificators set of modificator UIDs + * @param out output file stream + * @param dynamic_color color for dynamic elements + * @param graph network graph + * @param node_src map of node sources + * @param node_dst map of node destinations + */ +void write_projections_and_populations_to_dot( + std::set const& unique_nodes, std::set const& unique_edges, + std::set& modificators, std::ofstream& out, std::string& dynamic_color, + knp::framework::NetworkGraph const& graph, std::map& node_src, + std::map& node_dst); diff --git a/knp/base-framework/impl/visualizer/utils_graph.cpp b/knp/base-framework/impl/visualizer/utils_graph.cpp new file mode 100644 index 00000000..516f7013 --- /dev/null +++ b/knp/base-framework/impl/visualizer/utils_graph.cpp @@ -0,0 +1,135 @@ +#include "utils_graph.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + + +/** + * @brief Get bus messages from backend subscriptions + * + * @param backend shared pointer to backend + * + * @return tuple of spike messages and synaptic messages + */ +using SpikeMessages = std::vector>; +using SynapticMessages = std::vector>; +using BusMessages = std::tuple; + +BusMessages get_bus(std::shared_ptr& backend) +{ + const auto subs = backend->get_message_endpoint().get_endpoint_subscriptions(); + + constexpr size_t SPIKE_IDX = 0; // must be 0 + constexpr size_t SYNAPTIC_IMPACT_IDX = 1; // must be 1 + + SpikeMessages spike_messages; // [sender, receiver] + SynapticMessages synaptic_messages; // [sender, receiver] + + + // take from bus nessosary maps + for (const auto& [key, sub_variant] : subs) + { + // const auto& [type_idx, receiver_uid] = key; + const auto& type_idx = key.first; + const auto& receiver_uid = key.second; + std::visit( + [&](const auto& sub) + { + const auto& senders = sub.get_senders(); + if (senders.empty()) + { + SPDLOG_WARN("Empty senders list for subscription"); + return; + } + + + if (type_idx == SPIKE_IDX) + { // receiver: Projection, sender: Population // 0 --> + std::transform( + senders.begin(), senders.end(), std::back_inserter(spike_messages), + [&receiver_uid](const auto& sender) { return std::make_pair(sender, receiver_uid); }); + } + else if (type_idx == SYNAPTIC_IMPACT_IDX) + { // receiver: Population, sender: Projection // --> 0 + std::transform( + senders.begin(), senders.end(), std::back_inserter(synaptic_messages), + [&receiver_uid](const auto& sender) { return std::make_pair(sender, receiver_uid); }); + } + }, + sub_variant); + } + + return {spike_messages, synaptic_messages}; +} + + +/** + * @brief Get node name by UID from network graph nodes + * + * @param node_uid UID of the node + * @param nodes vector of network graph nodes + * + * @return string node name + */ +std::string get_node_name(knp::core::UID node_uid, std::vector const& nodes) +{ + std::string node_name = std::string(node_uid).substr(0, 8); + // nodes from netGraph + auto node_it = + std::find_if(nodes.begin(), nodes.end(), [&node_uid](const auto& node) { return node.uid_ == node_uid; }); + if (node_it != nodes.end()) + { + node_name = node_it->name_; + } + return node_name; +} + +/** + * @brief Get graph node by UID from network graph nodes + * + * @param node_uid UID of the node + * @param nodes vector of network graph nodes + * + * @return NetworkGraph::Node object + */ +knp::framework::NetworkGraph::Node get_graph_node_by_uid( + knp::core::UID node_uid, std::vector const& nodes) +{ + auto node_it = + std::find_if(nodes.begin(), nodes.end(), [&node_uid](const auto& node) { return node.uid_ == node_uid; }); + if (node_it != nodes.end()) + { + return *node_it; + } + + SPDLOG_WARN("Node with UID {} not found in graph", std::string(node_uid)); + return knp::framework::NetworkGraph::Node{}; +} + +/** + * @brief Get projection size by UID from network graph edges + * + * @param edges vector of network graph edges + * @param edge_uid UID of the edge + * + * @return string size of the edge + */ +std::string get_proj_size(std::vector const& edges, const knp::core::UID& edge_uid) +{ + auto it = std::find_if( + edges.begin(), edges.end(), [&edge_uid](const auto& graph_edge) { return graph_edge.uid_ == edge_uid; }); + if (it != edges.end()) + { + return std::to_string(it->size_); + } + throw std::runtime_error("Edge not found"); +} diff --git a/knp/base-framework/impl/visualizer/utils_graph.h b/knp/base-framework/impl/visualizer/utils_graph.h new file mode 100644 index 00000000..657931d4 --- /dev/null +++ b/knp/base-framework/impl/visualizer/utils_graph.h @@ -0,0 +1,89 @@ +#pragma once +// #include +#include + +#include + +#include +#include +#include +#include +#include + +extern "C" +{ +#include +} + +/** + * @brief Get bus messages from backend subscriptions + * + * @param backend shared pointer to backend + * + * @return tuple of spike messages and synaptic messages + */ +std::tuple< + std::vector>, std::vector>> +get_bus(std::shared_ptr& backend); + +/** + * @brief Get entity name for any network object. If there is no name it's constructed from UID. + * + * @param pop population object + * + * @return string name of the population + */ +template +std::string get_population_name(const Entity& pop) +{ + const size_t uid_part_size = 8; + knp::core::TagMap tags = std::visit([](const auto& p) { return p.get_tags(); }, pop); + knp::core::UID uid = std::visit([](const auto& p) { return p.get_uid(); }, pop); + std::string name; + auto tag = tags.get_tag("name"); + if (tag.has_value()) + { + try + { + name = std::any_cast(tag); + } + catch (std::bad_any_cast& exc) + { + SPDLOG_WARN("Wrong name tag type."); + name = std::string{uid}.substr(0, uid_part_size); + } + } + if (name.empty()) name = std::string{uid}.substr(0, uid_part_size); + return name; +} + +/** + * @brief Get node name by UID from network graph nodes + * + * @param node_uid UID of the node + * @param nodes vector of network graph nodes + * + * @return string node name + */ +std::string get_node_name(knp::core::UID node_uid, std::vector const& nodes); + +/** + * @brief Get graph node by UID from network graph nodes + * + * @param node_uid UID of the node + * @param nodes vector of network graph nodes + * + * @return NetworkGraph::Node object + */ +knp::framework::NetworkGraph::Node get_graph_node_by_uid( + knp::core::UID node_uid, std::vector const& nodes); + +/** + * @brief Get projection size by UID from network graph edges + * + * @param edges vector of network graph edges + * @param edge_uid UID of the edge + * + * @return string size of the edge + */ +std::string get_proj_size(std::vector const& edges, const knp::core::UID& edge_uid); diff --git a/knp/base-framework/impl/visualizer/visualize_network.cpp b/knp/base-framework/impl/visualizer/visualize_network.cpp index 97cef63c..dc8b3399 100644 --- a/knp/base-framework/impl/visualizer/visualize_network.cpp +++ b/knp/base-framework/impl/visualizer/visualize_network.cpp @@ -1,519 +1,244 @@ -/** - * @file visualize_network.cpp - * @brief Functions for subgraph visualization. - * @kaspersky_support A. Vartenkov - * @date 26.07.2024 - * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ #include #include #include -#include -#include -#include - -#include -#include - -#include "graph_physics.h" - +#include -// TODO: Draw network as a set of subgraphs. -namespace knp::framework +extern "C" { +#include +} +#include -/** - * @brief Adjacency list is a representation of a graph where there is a list of adjacent nodes for each node. - * @see https://en.wikipedia.org/wiki/Adjacency_list. - */ -using AdjacencyList = std::vector>; - +#include "utils_creation.h" +#include "utils_dot.h" +#include "utils_graph.h" -/** - * @brief Parameters for network drawing. - */ -struct DrawingParameters +namespace knp::framework { - /** - * @brief Nodes will be drawn using this color. - */ - const cv::Scalar node_color{0, 0, 0}; - /** - * @brief Background color. - */ - const cv::Scalar back_color{255, 255, 255}; - /** - * @brief Color for graph edges. - */ - const cv::Scalar edge_color{0, 0, 255}; - /** - * @brief Size of a circular arrow pointing from a node to the same node. - */ - const int self_arrow_radius = 30; - /** - * @brief Node circle radius. - */ - const int node_radius = 10; - /** - * @brief Length of an arrow showing the projection target. - */ - const int arrow_len = 20; - /** - * @brief An arrow head is a triangle. Arrow width is back side length divided by `2 * arrow_len` (`1.0` for a right - * angle). - */ - const double arrow_width = 0.3; - /** - * @brief Minimal number of pixels between image edge and text. - */ - const int text_margin = 5; -}; - - /** - * @brief Get entity name for any network object. If there is no name it's constructed from UID. + * @brief Set directory for saving visualization files. + * + * @param dir directory path for saving. + * + * @details Set directory for saving visualization files. + * Directory "visualization" is being created and it contains two subdirectories for dot files and png files. + * If the directory does not exist, it will be created. + * If the first character is /, then the path is absolute. (format: /path/to/absolute/dir) + * if not, relative to the current directory. format: path/to/relative/dir) */ -template -std::string get_name(const Entity &pop) +void set_saving_path(std::string dir) { - const size_t uid_part_size = 8; - knp::core::TagMap tags = std::visit([](const auto &p) { return p.get_tags(); }, pop); - knp::core::UID uid = std::visit([](const auto &p) { return p.get_uid(); }, pop); - std::string name; - auto tag = tags.get_tag("name"); - if (tag.has_value()) + if (!dir.empty() && dir[0] != '/') { try { - name = std::any_cast(tag); + if (dir.back() == '/') dir.pop_back(); + + std::filesystem::path current_dir = std::filesystem::current_path(); + ConfigVisualizePathes::default_path = (current_dir / dir).string(); + } + catch (const std::filesystem::filesystem_error& e) + { + SPDLOG_ERROR("Failed to get current path: {}", e.what()); + ConfigVisualizePathes::default_path = dir; } - catch (std::bad_any_cast &exc) + catch (...) { - SPDLOG_WARN("Wrong name tag type."); + SPDLOG_ERROR("Unknown error getting current path"); + ConfigVisualizePathes::default_path = dir; } } - if (name.empty()) name = std::string{uid}.substr(0, uid_part_size); - return name; + else + { + knp::framework::ConfigVisualizePathes::default_path = dir; + } } - /** * @brief Build network graph from a network. * @param network source network for a graph. * @details Online Help link: https://click.kaspersky.com/?hl=en-US&version=2.0&pid=KNP&link=online_help&helpid=235801 */ -NetworkGraph::NetworkGraph(const knp::framework::Network &network) +NetworkGraph::NetworkGraph(const knp::framework::Network& network) { // Add populations as nodes. - for (const auto &pop : network.get_populations()) + for (const auto& pop : network.get_populations()) { - size_t pop_size = std::visit([](const auto &p) { return p.size(); }, pop); - knp::core::UID uid = std::visit([](const auto &p) { return p.get_uid(); }, pop); - nodes_.push_back(Node{pop_size, uid, get_name(pop), pop.index()}); + size_t pop_size = std::visit([](const auto& p) { return p.size(); }, pop); + knp::core::UID uid = std::visit([](const auto& p) { return p.get_uid(); }, pop); + nodes_.push_back(Node{pop_size, uid, get_population_name(pop), pop.index()}); } // Add projections as edges. - for (const auto &proj : network.get_projections()) + for (const auto& proj : network.get_projections()) { - size_t proj_size = std::visit([](const auto &p) { return p.size(); }, proj); - knp::core::UID uid = std::visit([](const auto &p) { return p.get_uid(); }, proj); - knp::core::UID uid_from = std::visit([](const auto &p) { return p.get_presynaptic(); }, proj); - knp::core::UID uid_to = std::visit([](const auto &p) { return p.get_postsynaptic(); }, proj); + size_t proj_size = std::visit([](const auto& p) { return p.size(); }, proj); + knp::core::UID uid = std::visit([](const auto& p) { return p.get_uid(); }, proj); + knp::core::UID uid_from = std::visit([](const auto& p) { return p.get_presynaptic(); }, proj); + knp::core::UID uid_to = std::visit([](const auto& p) { return p.get_postsynaptic(); }, proj); int id_from = -1, id_to = -1; for (size_t i = 0; i < nodes_.size(); ++i) { if (uid_from == nodes_[i].uid_) id_from = i; if (uid_to == nodes_[i].uid_) id_to = i; } - edges_.push_back(Edge{proj_size, id_from, id_to, uid, get_name(proj), proj.index()}); + edges_.push_back(Edge{proj_size, id_from, id_to, uid, get_population_name(proj), proj.index()}); } } - - /** - * @brief Convert network graph into adjacency list form. - * @param graph network graph built from a `Network` object. - * @return graph as an adjacency list. - * @see https://en.wikipedia.org/wiki/Adjacency_list. - * @note There is no input node in `NetworkGraph`, but an adjacency list must have one as the last node. + * @brief Print node and edge connections of a network graph. + * + * @param graph network graph. + * + * @details The function writes a textual description of each node (population) and its incoming and outgoing edges to + * `stdout`. It is primarily useful for debugging the connectivity extraction logic. (needed to static network) */ -AdjacencyList build_adjacency_list(const NetworkGraph &graph) +void print_network_description(const NetworkGraph& graph) { - AdjacencyList adj_list; - adj_list.resize(graph.nodes_.size() + 1); - for (const auto &edge : graph.edges_) - { - int index = edge.index_from_; - if (index < 0) index = static_cast(graph.nodes_.size()); - adj_list[index].push_back(edge.index_to_); - } - return adj_list; -} + // out to console + // for (size_t i = 0; i < graph.nodes_.size(); ++i) + // { + // std::cout << "Population #" << i << " of size " << graph.nodes_[i].size_ << ": receive from"; + // std::vector edges_to_i; + // std::vector edges_from_i; -/** - * @brief Draw a line with an arrow that is much nicer than basic OpenCV arrow. - * @param img image to modify. - * @param pt_from point from which the arrow starts. - * @param pt_to point to which the arrow points. - * @param len arrow length in pixels. - * @param width arrow width as a fraction of arrow length. - * @param margin arrow shift from the end of the line to the beginning by "margin" pixels. - * @param color line and arrow color. - */ -void draw_simple_arrow_line( - cv::Mat &img, const cv::Point2d &pt_from, const cv::Point2d &pt_to, int len, double width, int margin, - const cv::Scalar &color) -{ - cv::line(img, pt_from, pt_to, color); - cv::Point2d direction = pt_to - pt_from; - direction /= cv::norm(direction); - cv::Point2d op_direction{-direction.y, direction.x}; + // for (const auto& edge : graph.edges_) + // { + // if (edge.index_to_ == static_cast(i)) edges_to_i.push_back(edge.index_from_); + // if (edge.index_from_ == static_cast(i)) edges_from_i.push_back(edge.index_to_); + // } + // for (auto edge : edges_to_i) std::cout << " #" << edge; + // std::cout << "; send to"; + // for (auto edge : edges_from_i) std::cout << " #" << edge; + // std::cout << std::endl; + //} + //out to log info + for (size_t i = 0; i < graph.nodes_.size(); ++i) + { + std::string log_msg = fmt::format("Population #{} of size {},: receive from", i, graph.nodes_[i].size_); - cv::Point2d pt_arrow_end = pt_to - direction * margin; - cv::Point2d pt_arrow_back = pt_to - direction * (len + margin); - cv::Point2d pt_back_pos_1 = pt_arrow_back + width * len * op_direction; - cv::Point2d pt_back_pos_2 = pt_arrow_back - width * len * op_direction; - cv::fillConvexPoly(img, std::vector{pt_back_pos_1, pt_arrow_end, pt_back_pos_2}, color); -} + std::vector edges_to_i; + std::vector edges_from_i; -/** - * @brief Draw edges between selected nodes. - * @param out_img output image matrix. - * @param adj_list adjacency list for the whole network. - * @param nodes indexes of the nodes that were selected. - * @param points node positions. - * @param params drawing parameters. - */ -void draw_edges( - cv::Mat &out_img, const AdjacencyList &adj_list, const std::vector &nodes, - const std::vector &points, const DrawingParameters ¶ms) -{ - for (size_t i = 0; i < nodes.size(); ++i) - { - // This is a bit nonobvious. `adj_list` is a list for all nodes in the graph, - // while `i` is the position of node in subgraph. - // Position of node in the whole graph is `nodes[i]`. - for (size_t j = 0; j < adj_list[nodes[i]].size(); ++j) + for (const auto& edge : graph.edges_) { - // `i` is an index of a list element and index of a source element for the graph edge simultaneously. - auto target_node_iter = std::find(nodes.begin(), nodes.end(), adj_list[nodes[i]][j]); - if (target_node_iter == nodes.end()) continue; - - size_t target_point_index = target_node_iter - nodes.begin(); - if (target_point_index == i) - { - cv::circle( - out_img, points[i] - cv::Point2i(0, params.self_arrow_radius), params.self_arrow_radius, - params.edge_color); - continue; - } - const cv::Point2i &point_from = points[i]; - const cv::Point2i &point_to = points[target_point_index]; - draw_simple_arrow_line( - out_img, point_from, point_to, params.arrow_len, params.arrow_width, params.node_radius, - params.edge_color); + if (edge.index_to_ == static_cast(i)) edges_to_i.push_back(edge.index_from_); + if (edge.index_from_ == static_cast(i)) edges_from_i.push_back(edge.index_to_); } + for (auto edge : edges_to_i) log_msg += fmt::format(" #{}", edge); + log_msg += fmt::format("; send to"); + for (auto edge : edges_from_i) log_msg += fmt::format(" #{}", edge); + SPDLOG_INFO(log_msg); } } - -/** - * @brief Draw graph with with node names or IDs as graph legends. - * @param graph whole network graph. - * @param adj_list whole network adjacency list. - */ -cv::Mat draw_annotated_subgraph( - const NetworkGraph &graph, const AdjacencyList &adj_list, const std::vector &nodes, - const std::vector &points, const std::vector &inputs, const cv::Size &img_size, - const DrawingParameters ¶ms = DrawingParameters{}) -{ - // TODO: Draw graph of selected nodes, while the nodes are considered external if they are not included. - cv::Mat out_img{img_size, CV_8UC3, params.back_color}; - // Draw inputs (black arrow from above). - for (auto input : inputs) - { - auto target_node_iter = std::find(nodes.begin(), nodes.end(), input); - if (target_node_iter == nodes.end()) continue; - cv::Point2i point = points[target_node_iter - nodes.begin()]; - draw_simple_arrow_line( - out_img, point - cv::Point2i(0, 2 * params.arrow_len + params.node_radius), point, params.arrow_len, - params.arrow_width, params.node_radius, params.node_color); - } - - draw_edges(out_img, adj_list, nodes, points, params); - - // Draw nodes. - for (size_t i = 0; i < nodes.size(); ++i) - { - // Drawing. - const auto &point = points[i]; - cv::circle( - out_img, {static_cast(point.x), static_cast(point.y)}, params.node_radius, params.node_color, -1); - cv::Point2i text_start = point; - std::string name = graph.nodes_[i].name_; - int baseline = 0; - auto text_size = cv::getTextSize(name, cv::FONT_HERSHEY_SIMPLEX, 0.7, 2, &baseline); - text_start.x = std::max(params.text_margin, text_start.x - text_size.width / 2); - if (text_start.x + text_size.width / 2 > img_size.width - params.text_margin) - text_start.x = img_size.width - text_size.width - params.text_margin; - text_start.y += params.node_radius + params.text_margin + text_size.height; - if (text_start.y > img_size.height) text_start.y = point.y - params.node_radius - params.text_margin; - cv::putText(out_img, name, text_start, cv::FONT_HERSHEY_SIMPLEX, 0.7, {0, 0, 0}, 2); - } - return out_img; -} - - /** - * @brief Draw connected subgraph. It shouldn't have any connections to other parts of the graph, excluding inputs. - * @details The function draws a vertical black arrow if a node is connected to input. - * @param adj_list adjacency list of the whole graph. - * @param nodes nodes that are part of the subgraph. - * @param points node coordinates. - * @param inputs input nodes. - * @param img_size output image size. - * @param params graph drawing parameters. - * @return image of a drawn subgraph. + * @brief Visualize static network. + * + * @param network source network for visualization. + * + * @details Visualize network. + * The model is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -cv::Mat draw_subgraph( - const AdjacencyList &adj_list, const std::vector &nodes, const std::vector &points, - const std::vector &inputs, const cv::Size &img_size, const DrawingParameters ¶ms = DrawingParameters{}) +void visualize_network(const knp::framework::Network& network) { - cv::Mat out_img{img_size, CV_8UC3, params.back_color}; - // Draw inputs (black arrow from above). - for (auto input : inputs) - { - auto target_node_iter = std::find(nodes.begin(), nodes.end(), input); - if (target_node_iter == nodes.end()) continue; - cv::Point2i point = points[target_node_iter - nodes.begin()]; - draw_simple_arrow_line( - out_img, point - cv::Point2i(0, 2 * params.arrow_len + params.node_radius), point, params.arrow_len, - params.arrow_width, params.node_radius, params.node_color); - } - - draw_edges(out_img, adj_list, nodes, points, params); + knp::framework::NetworkGraph graph(network); + knp::framework::ConfigVisualizePathes file_info{}; - // Draw nodes. - for (size_t i = 0; i < nodes.size(); ++i) - { - // Drawing. - const auto &point = points[i]; - cv::circle( - out_img, {static_cast(point.x), static_cast(point.y)}, params.node_radius, params.node_color, -1); - } - return out_img; + create_dot_file_for_static_network(file_info.name_dot_file, graph); + create_png_file(file_info.name_dot_file, file_info.name_png_file); + print_network_description(graph); // description of static network } - -/** - * @brief Make a reverse adjacency list. It's used to quickly find "incoming" nodes. - * @param adj_list regular adjacency list. - * @return reversed adjacency list, where each node has a list of nodes it is adjacent to. - */ -AdjacencyList make_reverse_list(const AdjacencyList &adj_list) -{ - AdjacencyList rev_list; - rev_list.resize(adj_list.size()); - for (size_t count = 0; count < adj_list.size(); ++count) - { - for (auto val : adj_list[count]) rev_list[val].push_back(count); - } - return rev_list; -} - - /** - * @brief Find an independent subgraph inside a larger graph. - * @param adj_list adjacency list. - * @param rev_list reversed adjacency list. - * @param remaining_nodes set of nodes that are not currently in a subgraph. - * @param ignore_nodes indexes of nodes to ignore. - * @return connected subgraph of nodes. - * @see make_reverse_list + * @brief Visualize static network by model. + * + * @param model source model for visualization. + * + * @details Visualize network. + * The model is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -std::vector find_connected_set( - const AdjacencyList &adj_list, const AdjacencyList &rev_list, std::unordered_set &remaining_nodes, - const std::unordered_set &ignore_nodes = {}) +void visualize_network(const knp::framework::Model& model) { - auto cleaned_nodes = remaining_nodes; - for (const auto node : ignore_nodes) cleaned_nodes.erase(node); - if (cleaned_nodes.empty()) - { - remaining_nodes = {}; - return {}; - } - - // Select a node. - int node = *cleaned_nodes.begin(); - std::unordered_set processed_nodes; - std::unordered_set nonproc_nodes{node}; - // Go through all its connections and add nodes to the resulting set. - while (!nonproc_nodes.empty()) - { - int curr_node = *nonproc_nodes.begin(); - for (const AdjacencyList &curr_list : {adj_list, rev_list}) - { - // Add all the nodes a selected node is connected to. - const auto &out_nodes = curr_list[curr_node]; - for (auto node_out : out_nodes) - { - int n = static_cast(node_out); - if (n == curr_node) continue; - if (processed_nodes.find(n) != processed_nodes.end()) continue; // Node has already been processed. - if (ignore_nodes.find(n) != ignore_nodes.end()) continue; // Node is explicitly excluded. - nonproc_nodes.insert(n); - } - } - // Move current node from unprocessed to processed. - nonproc_nodes.erase(curr_node); - processed_nodes.insert(curr_node); - } - - for (int node_id : processed_nodes) remaining_nodes.erase(node_id); - - // Return connected nodes as a sorted vector. - std::vector result; - result.resize(processed_nodes.size()); - std::copy(processed_nodes.begin(), processed_nodes.end(), result.begin()); - std::sort(result.begin(), result.end()); - return result; + visualize_network(model.get_network()); } - /** - * @brief Find all independent components inside a graph. - * @param graph network graph. + * @brief Visualize dynamic network using backend. + * + * @param network source network for visualization. + * @param backend visualization backend. + * + * @details Visualize network. + * The network is needed to get the names of static nodes. + * The backend is needed to get all graphs connections from the bus. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -std::vector> divide_graph_by_connectivity(const NetworkGraph &graph) +void visualize_network(const knp::framework::Network& network, std::shared_ptr& backend) { - AdjacencyList adj_list = build_adjacency_list(graph); - AdjacencyList rev_list = make_reverse_list(adj_list); - std::unordered_set remaining_nodes; - std::vector> connected_sets; - std::unordered_set ignored_nodes{static_cast(adj_list.size() - 1)}; - for (size_t i = 0; i < adj_list.size(); ++i) remaining_nodes.insert(i); - - remaining_nodes.erase(static_cast(adj_list.size()) - 1); // delete input node - while (!remaining_nodes.empty()) - { - connected_sets.push_back(find_connected_set(adj_list, rev_list, remaining_nodes, ignored_nodes)); - } + knp::framework::NetworkGraph graph(network); + knp::framework::ConfigVisualizePathes file_info{"dynamic"}; - return connected_sets; + create_dot_file_for_dynamic_network(file_info.name_dot_file, graph, backend); + create_png_file(file_info.name_dot_file, file_info.name_png_file); } - - /** - * @brief Print network subset description. - * @param adj_list adjacency list. - * @param rev_list reversed adjacency list. - * @param nodes nodes from a connected subset. - * @see build_adjacency_list, make_reverse_list, and find_connected_set. - */ -void print_connected_subset( - const NetworkGraph &graph, const AdjacencyList &adj_list, const AdjacencyList &rev_list, - const std::vector &nodes) -{ - for (auto node : nodes) - { - std::cout << "Population #" << node << " of size " << graph.nodes_[node].size_ << ": receive from"; - for (auto node_from : rev_list[node]) - if (node_from == adj_list.size() - 1) - std::cout << " #INPUT"; - else - std::cout << " #" << node_from; - std::cout << "; send to"; - for (auto node_to : adj_list[node]) std::cout << " #" << node_to; - std::cout << std::endl; - } -} - - -/** - * @brief Print all connected subsets descriptions. - * @param graph network graph. + * @brief Visualize dynamic network using backend. + * + * @param model source model for visualization. + * @param backend visualization backend. + * + * @details Visualize network. + * The model is needed to get the names of static nodes. + * The backend is needed to get all graphs connections from the bus. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -void print_network_description(const NetworkGraph &graph) +void visualize_network(const knp::framework::Model& model, std::shared_ptr& backend) { - AdjacencyList adj_list = build_adjacency_list(graph); - AdjacencyList rev_list = make_reverse_list(adj_list); - auto connected_subsets = divide_graph_by_connectivity(graph); - for (const auto &subset : connected_subsets) - { - print_connected_subset(graph, adj_list, rev_list, subset); - std::cout << std::endl; - } + visualize_network(model.get_network(), backend); } - /** - * @brief Show the process of subgraph adjustment. - * @param graph full network graph. - * @param nodes all nodes that are contained in a subgraph. - * @param screen_size output window size. - * @param margin margin size in pixels. + * @brief Visualize the bus messages. + * + * @param network source network for bus visualization. + * @param backend visualization backend. + * + * @details Visualize the bus messages. The network is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -void position_network_test( - const NetworkGraph &graph, const std::vector &nodes, const cv::Size &screen_size, int margin) +void visualize_bus(const knp::framework::Network& network, std::shared_ptr& backend) { - cv::theRNG().state = std::time(nullptr); - AdjacencyList adj_list = build_adjacency_list(graph); - VisualGraph vis_graph(nodes, adj_list); - int key = 0; + knp::framework::NetworkGraph graph(network); + knp::framework::ConfigVisualizePathes file_info{"bus"}; - // Create inputs. - std::vector inputs; - inputs.reserve(adj_list.back().size()); - std::transform( - adj_list.back().begin(), adj_list.back().end(), std::back_inserter(inputs), - [](size_t v) { return static_cast(v); }); - // for (auto v : adj_list.back()) inputs.push_back(static_cast(v)); - - while (key != 27) - { - auto points = vis_graph.scale_graph(screen_size, margin); - cv::Mat img = draw_annotated_subgraph(graph, adj_list, nodes, points, inputs, screen_size); - cv::imshow("Graph", img); - key = cv::waitKey(50) & 255; - vis_graph.iterate(1); - } + create_dot_file_for_bus(file_info.name_dot_file, graph, backend); + create_png_file(file_info.name_dot_file, file_info.name_png_file); } - - /** - * @brief Calculate positions of nodes. - * @param graph full network graph. - * @param nodes all nodes that are contained in a subgraph. - * @param screen_size output window size. - * @param margin margin size in pixels. - * @param num_iterations number of iterations for graph positioning algorithm. - * @return node coordinates. + * @brief Visualize the bus messages. + * + * @param model source model for bus visualization. + * @param backend visualization backend. + * + * @details Visualize the bus messages. The model is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -std::vector position_network( - const NetworkGraph &graph, const std::vector &nodes, const cv::Size &screen_size, int margin, - int num_iterations) +void visualize_bus(const knp::framework::Model& model, std::shared_ptr& backend) { - VisualGraph vis_graph(nodes, build_adjacency_list(graph)); - vis_graph.iterate(num_iterations); - auto result = vis_graph.scale_graph(screen_size, margin); - return result; + visualize_bus(model.get_network(), backend); } } // namespace knp::framework diff --git a/knp/base-framework/include/knp/framework/visualizer/visualize_network.h b/knp/base-framework/include/knp/framework/visualizer/visualize_network.h index f0bba766..40730ba3 100644 --- a/knp/base-framework/include/knp/framework/visualizer/visualize_network.h +++ b/knp/base-framework/include/knp/framework/visualizer/visualize_network.h @@ -1,33 +1,11 @@ -/** - * @file visualize_network.h - * @brief Functions for graph visualization. - * @warning Most of the functions are not well-tested or stable yet. - * @date 26.07.2024 - * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - #pragma once +#include #include +#include #include #include -#include - - /** * @brief Framework namespace. */ @@ -37,10 +15,10 @@ namespace knp::framework /** * @brief Network description structure used for visualization. * - * @details The structure stores a flat list of population nodes and projection edges, together with their identifiers, - * names and types. It is constructed from a @ref Network object and then used by the visualizer to build adjacency lists, - * draw sub‑graphs and compute node positions. - * + * @details The structure stores a flat list of population nodes and projection edges, together with their identifiers, + * names and types. It is constructed from a @ref Network object and then used by the visualizer to build adjacency + * lists, draw sub‑graphs and compute node positions. + * * @note You can use this to check network structure. */ struct KNP_DECLSPEC NetworkGraph @@ -49,7 +27,7 @@ struct KNP_DECLSPEC NetworkGraph /** * @brief Description of a population node. * - * @details Each node corresponds to a population in the original network. The fields store the population size, its + * @details Each node corresponds to a population in the original network. The fields store the population size, its * unique identifier, a human‑readable name and the neuron type index (used only for drawing legends). */ struct Node @@ -77,6 +55,22 @@ struct KNP_DECLSPEC NetworkGraph */ // cppcheck-suppress unusedStructMember size_t type_; + + /** + * @brief A flag showing a dynamic or static node. + * + * @details It is needed for the graph extension. + */ + // cppcheck-suppress unusedStructMember + bool is_static = true; + + /** + * @brief A flag showing a visible or invisible node. + * + * @details It is needed for the graph extension. ( for drawing edges without src or dst ) + */ + // cppcheck-suppress unusedStructMember + bool is_invisible = false; }; /** @@ -88,8 +82,9 @@ struct KNP_DECLSPEC NetworkGraph /** * @brief Description of a projection edge. * - * @details An edge connects a source population (@p index_from_) to a target population (@p index_to_). It stores the - * projection size, its UID, a readable name and the synapse type index (used for color‑coding in the visualizer). + * @details An edge connects a source population (@p index_from_) to a target population (@p index_to_). It stores + * the projection size, its UID, a readable name and the synapse type index (used for color‑coding in the + * visualizer). */ struct Edge { @@ -128,6 +123,14 @@ struct KNP_DECLSPEC NetworkGraph */ // cppcheck-suppress unusedStructMember size_t type_; + + /** + * @brief A flag showing a dynamic or static node. + * + * @details It is needed for the graph extension. + */ + // cppcheck-suppress unusedStructMember + bool is_static = true; }; /** @@ -138,85 +141,156 @@ struct KNP_DECLSPEC NetworkGraph /** * @brief Build network graph from a network. - * + * * @param network source network for a graph. - * - * @details Populations are added as nodes and projections as edges. The constructor extracts UIDs, names and sizes from - * the network. + * + * @details Populations are added as nodes and projections as edges. The constructor extracts UIDs, names and sizes + * from the network. */ - explicit NetworkGraph(const knp::framework::Network &network); + explicit NetworkGraph(const knp::framework::Network& network); }; /** - * @brief Print node and edge connections of a network graph. + * @brief Print node and edge connections of a network graph (static). * * @param graph network graph. * - * @details The function writes a textual description of each node (population) and its incoming and outgoing edges to `stdout`. - * It is primarily useful for debugging the connectivity extraction logic. + * @details The function writes a textual description of each node (population) and its incoming and outgoing edges to + * `stdout`. It is primarily useful for debugging the connectivity extraction logic. */ -KNP_DECLSPEC void print_network_description(const NetworkGraph &graph); - +KNP_DECLSPEC void print_network_description(const NetworkGraph& graph); /** - * @brief Print whole network information. - * - * @param graph network graph. - * - * @note The output format is not intended for end‑users; it is a raw dump useful for developers. + * @brief Visualize static network. + * + * @param network source network for visualization. + * + * @details Visualize network. + * The model is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -KNP_DECLSPEC void print_modified_network_description(const NetworkGraph &graph); +KNP_DECLSPEC void visualize_network(const knp::framework::Network& network); +/** + * @brief Visualize static network by model. + * + * @param model source model for visualization. + * + * @details Visualize network. + * The model is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). + */ +KNP_DECLSPEC void visualize_network(const knp::framework::Model& model); /** - * @brief Divide a network graph into independent sub‑graphs. + * @brief Visualize dynamic network using backend. * - * @param graph network graph. + * @param network source network for visualization. + * @param backend visualization backend. + * + * @details Visualize network. + * The network is needed to get the names of static nodes. + * The backend is needed to get all graphs connections from the bus. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). + */ +KNP_DECLSPEC void visualize_network( + const knp::framework::Network& network, std::shared_ptr& backend); + +/** + * @brief Visualize dynamic network using backend. * - * @return vector of sub‑graphs, each represented by a list of node indexes. + * @param model source model for visualization. + * @param backend visualization backend. * - * @details The function builds an adjacency list, creates a reverse list for fast inbound look‑ups, and then repeatedly - * extracts maximal connected components (ignoring the artificial input node). The resulting sets are sorted for - * deterministic ordering. + * @details Visualize network. + * The model is needed to get the names of static nodes. + * The backend is needed to get all graphs connections from the bus. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). */ -KNP_DECLSPEC std::vector> divide_graph_by_connectivity(const NetworkGraph &graph); +KNP_DECLSPEC void visualize_network(const knp::framework::Model& model, std::shared_ptr& backend); +//TODO: +// KNP_DECLSPEC void visualize_bus(std::shared_ptr& backend); /** - * @brief Compute positions of nodes in a sub‑graph. + * @brief Visualize the bus messages. * - * @param graph full network graph. - * @param nodes indexes of the nodes that belong to the sub‑graph. - * @param screen_size output window size. - * @param margin border size for the network graph, in pixels. - * @param num_iterations number of iterations for the force‑directed layout algorithm. + * @param network source network for bus visualization. + * @param backend visualization backend. * - * @return coordinates of the nodes after layout. + * @details Visualize the bus messages. The network is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). + */ +KNP_DECLSPEC void visualize_bus(const knp::framework::Network& network, std::shared_ptr& backend); + +/** + * @brief Visualize the bus messages. * - * @details The function runs the physics‑based layout for @p num_iterations steps and then rescales the resulting positions - * to fit inside @p screen_size with the requested @p margin. + * @param model source model for bus visualization. + * @param backend visualization backend. + * + * @details Visualize the bus messages. The model is needed to get the names of static nodes. + * By default, it saves dot/png files of graph to the current directory (the visualization directory is being created). + * For the change save directory, use set_saving_path(directory). + */ +KNP_DECLSPEC void visualize_bus(const knp::framework::Model& model, std::shared_ptr& backend); + +/** + * @brief Configuration structure for visualization paths. */ -KNP_DECLSPEC std::vector position_network( - const NetworkGraph &graph, const std::vector &nodes, cv::Size screen_size, int margin, int num_iterations); +struct ConfigVisualizePathes +{ + std::string mode = "static"; + std::string name; + + inline static std::string default_path = "visualization_docs"; + std::string dir; + std::string name_dot_file; + std::string name_png_file; + + ConfigVisualizePathes() { init(); } + + explicit ConfigVisualizePathes(const std::string& mode_val) : mode(mode_val) { init(); } + +private: + void init() + { + name = "graph_" + mode; + dir = default_path; + name_dot_file = dir + "/dot_files/" + name + ".dot"; + name_png_file = dir + "/png_files/" + name + ".png"; + + try + { + std::filesystem::create_directories(dir + "/dot_files"); + std::filesystem::create_directories(dir + "/png_files"); + } + catch (const std::filesystem::filesystem_error& ex) + { + std::cerr << "Failed to create directories: " << ex.what() << std::endl; + } + } +}; /** - * @brief Visualize the iterative positioning of a sub‑graph. - * - * @param graph base network graph. - * @param nodes indexes of the nodes that belong to the sub‑graph. - * @param screen_size output image size. - * @param margin size of borders in pixels (default = 50). - * - * @details The function opens an OpenCV window and repeatedly: - * 1. Scales the current graph to the screen - * 2. Draws the annotated sub‑graph - * 3. Displays the image - * 4. Advances the physics simulation by one iteration. - * - * @note Press **Esc** to exit the visualization. + * @brief Set directory for saving visualization files. + * + * @param dir directory path for saving. + * + * @details Set directory for saving visualization files. + * Directory "visualization" is being created and it contains two subdirectories for dot files and png files. + * If the directory does not exist, it will be created. + * If the first character is /, then the path is absolute. (format: /path/to/absolute/dir) + * if not, relative to the current directory. format: path/to/relative/dir) */ -KNP_DECLSPEC void position_network_test( - const NetworkGraph &graph, const std::vector &nodes, const cv::Size &screen_size, int margin = 50); +KNP_DECLSPEC void set_saving_path(std::string dir); + + } // namespace knp::framework diff --git a/knp/tests/framework/visualize_network_test.cpp b/knp/tests/framework/visualize_network_test.cpp new file mode 100644 index 00000000..d8477031 --- /dev/null +++ b/knp/tests/framework/visualize_network_test.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using BLIFATParams = knp::neuron_traits::neuron_parameters; +using DeltaProjection = knp::core::Projection; +using Synapse = DeltaProjection::Synapse; + +// Helper function to create a simple network for testing +knp::framework::Network create_test_network() +{ + knp::framework::Network network; + + // Create two populations + auto pop1 = knp::framework::population::creators::make_random(5); + auto pop2 = knp::framework::population::creators::make_random(3); + + network.add_population(pop1); + network.add_population(pop2); + + // Connect them + auto proj_uid = network.connect_populations< + knp::synapse_traits::DeltaSynapse, knp::neuron_traits::BLIFATNeuron, knp::neuron_traits::BLIFATNeuron>( + pop1, pop2); + + return network; +} + +// Helper function to create a simple model for testing +knp::framework::Model create_test_model() +{ + knp::framework::Network network = create_test_network(); + return knp::framework::Model(std::move(network)); +} + +TEST(VisualizeNetworkSuite, NetworkGraphConstruction) +{ + knp::framework::Network network = create_test_network(); + knp::framework::NetworkGraph graph(network); + + // Check that we have correct number of nodes and edges + ASSERT_EQ(graph.nodes_.size(), 2); + ASSERT_EQ(graph.edges_.size(), 1); + + // Check node properties + ASSERT_EQ(graph.nodes_[0].size_, 5); + ASSERT_EQ(graph.nodes_[1].size_, 3); + + // Check edge properties + ASSERT_EQ(graph.edges_[0].size_, 15); // 5 * 3 = 15 synapses + ASSERT_EQ(graph.edges_[0].index_from_, 0); + ASSERT_EQ(graph.edges_[0].index_to_, 1); +} + +TEST(VisualizeNetworkSuite, NetworkGraphNodeAndEdgeAccess) +{ + knp::framework::Network network = create_test_network(); + knp::framework::NetworkGraph graph(network); + + // Test accessing nodes + ASSERT_FALSE(graph.nodes_.empty()); + ASSERT_EQ(graph.nodes_.size(), 2); + + // Test accessing edges + ASSERT_FALSE(graph.edges_.empty()); + ASSERT_EQ(graph.edges_.size(), 1); + + // Test node properties + const auto& node0 = graph.nodes_[0]; + const auto& node1 = graph.nodes_[1]; + + ASSERT_GT(node0.size_, 0); + ASSERT_GT(node1.size_, 0); + ASSERT_FALSE(node0.name_.empty()); + ASSERT_FALSE(node1.name_.empty()); + + // Test edge properties + const auto& edge = graph.edges_[0]; + ASSERT_GT(edge.size_, 0); + ASSERT_FALSE(edge.name_.empty()); + ASSERT_EQ(edge.index_from_, 0); + ASSERT_EQ(edge.index_to_, 1); +} + + +TEST(VisualizeNetworkSuite, StaticVisualizationFilesGeneration) +{ + knp::framework::Network network = create_test_network(); + knp::framework::NetworkGraph graph(network); + + // Set up paths for testing + std::string test_dir = "./test_visualization/"; + std::filesystem::remove_all(test_dir); + std::filesystem::create_directories(test_dir); + + // Change the default path for testing + knp::framework::set_saving_path(test_dir); + + // Generate dot file + knp::framework::ConfigVisualizePathes file_info{}; + std::string dot_file = file_info.name_dot_file; + std::string png_file = file_info.name_png_file; + + // Test that we can create dot file + try + { + // Verify that the graph has expected structure + ASSERT_EQ(graph.nodes_.size(), 2); + ASSERT_EQ(graph.edges_.size(), 1); + + // Verify that the paths are correctly formed + ASSERT_FALSE(dot_file.empty()); + ASSERT_FALSE(png_file.empty()); + + // Verify that directories exist + std::filesystem::path dot_dir = std::filesystem::path(dot_file).parent_path(); + std::filesystem::path png_dir = std::filesystem::path(png_file).parent_path(); + + ASSERT_TRUE(std::filesystem::exists(dot_dir)); + ASSERT_TRUE(std::filesystem::exists(png_dir)); + ASSERT_TRUE(std::filesystem::is_directory(dot_dir)); + ASSERT_TRUE(std::filesystem::is_directory(png_dir)); + + // Verify that the files don't exist yet (they will be created during visualization) + ASSERT_FALSE(std::filesystem::exists(dot_file)); + ASSERT_FALSE(std::filesystem::exists(png_file)); + + // Test actual visualization function to ensure files are created + knp::framework::visualize_network(network); + + // Verify that files were created + ASSERT_TRUE(std::filesystem::exists(dot_file)); + ASSERT_TRUE(std::filesystem::exists(png_file)); + + // Verify that files are not empty + ASSERT_GT(std::filesystem::file_size(dot_file), 0); + ASSERT_GT(std::filesystem::file_size(png_file), 0); + } + catch (const std::exception& e) + { + FAIL() << "Failed to generate visualization files: " << e.what(); + } +} + + +TEST(VisualizeNetworkSuite, DynamicVisualizationWithBackend) +{ + knp::framework::Network network = create_test_network(); + knp::framework::Model model(std::move(network)); + + // Create backend using the correct API from backend_loader_test.cpp + knp::framework::BackendLoader backend_loader; + auto backend = backend_loader.load(knp::testing::get_backend_path()); + + // Test that we can call visualization functions without crashing + try + { + // This should not crash + knp::framework::visualize_network(model.get_network(), backend); + knp::framework::visualize_bus(model.get_network(), backend); + } + catch (const std::exception& e) + { + FAIL() << "Dynamic visualization failed with exception: " << e.what(); + } +} + +TEST(VisualizeNetworkSuite, ModelVisualization) +{ + knp::framework::Model model = create_test_model(); + + // Test that we can visualize model directly + try + { + knp::framework::visualize_network(model); + // knp::framework::visualize_network(model, nullptr); // Test with null backend + } + catch (const std::exception& e) + { + FAIL() << "Model visualization failed with exception: " << e.what(); + } +} + + +TEST(VisualizeNetworkSuite, NetworkGraphPrintFunctions) +{ + knp::framework::Network network = create_test_network(); + knp::framework::NetworkGraph graph(network); + + // Test that print functions don't crash + try + { + // These functions write to stdout, so we just ensure they don't throw + knp::framework::print_network_description(graph); + } + catch (const std::exception& e) + { + FAIL() << "Print functions failed with exception: " << e.what(); + } +} + +TEST(VisualizeNetworkSuite, VisualizationPathConfiguration) +{ + // Test setting custom visualization paths + std::string custom_path = "/tmp/custom_visualization/"; + std::filesystem::create_directories(custom_path); + + knp::framework::set_saving_path(custom_path); + + // Verify default path was changed + knp::framework::ConfigVisualizePathes config; + ASSERT_EQ(config.dir, custom_path); +} From 66c3043e1cbfe6b0bb7211bba096f9cbf729c503 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 5 Aug 2026 13:08:13 +0300 Subject: [PATCH 02/12] rollback of the pre-commit version: #219 --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da8c5dcb..141e321e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -133,7 +133,7 @@ repos: - id: requirements-txt-fixer - repo: https://github.com/PyCQA/autoflake - rev: v2.3.3 + rev: v2.3.1 hooks: - id: autoflake exclude: &fixtures tests(/\w*)*/functional/|tests/input|doc/data/messages|tests(/\w*)*data/ @@ -145,7 +145,7 @@ repos: - --remove-unused-variables - repo: https://github.com/pycqa/pylint - rev: "v4.0.6" # pylint-2.14.5 + rev: "v4.0.4" # pylint-2.14.5 hooks: - id: pylint args: ["-rn", "-sn", "--rcfile=knp/python-framework/packaging/common/.pylintrc", "--fail-on=I", "--load-plugins=pylint.extensions.docparams"] @@ -172,12 +172,12 @@ repos: # language_version: python3 - repo: https://github.com/pre-commit/pre-commit - rev: v4.6.1 + rev: v4.5.1 hooks: - id: validate_manifest - repo: https://github.com/pre-commit/mirrors-mypy - rev: v2.3.0 + rev: v1.19.1 hooks: - id: mypy args: ["--config-file", "knp/.mypy.ini", "--install-types", "--explicit-package-bases", "--check-untyped-defs", "--scripts-are-modules", "--strict"] @@ -204,20 +204,20 @@ repos: - id: pyupgrade - repo: https://github.com/ambv/black - rev: "26.5.1" + rev: "25.12.0" hooks: - id: black args: ["-l", "120", "--skip-string-normalization"] - repo: https://github.com/DanielNoord/pydocstringformatter - rev: v1.0.0 + rev: v0.7.5 hooks: - id: pydocstringformatter exclude: *fixtures args: ["--max-summary-lines=2", "--linewrap-full-docstring"] - repo: https://github.com/PyCQA/bandit - rev: 1.9.4 + rev: 1.9.2 hooks: - id: bandit args: ["-r", "-lll"] From b3966bb26bcb4b6613c06da93f89912186e0ffa2 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 5 Aug 2026 14:31:09 +0300 Subject: [PATCH 03/12] rename files: #219 --- knp/base-framework/CMakeLists.txt | 6 +++--- .../visualizer/{utils_dot.cpp => dot_helpers.cpp} | 13 ++++--------- .../impl/visualizer/{utils_dot.h => dot_helpers.h} | 0 .../{utils_creation.cpp => generate_files.cpp} | 6 +++--- .../{utils_creation.h => generate_files.h} | 0 .../{utils_graph.cpp => graph_helpers.cpp} | 2 +- .../visualizer/{utils_graph.h => graph_helpers.h} | 1 - .../impl/visualizer/visualize_network.cpp | 6 +++--- 8 files changed, 14 insertions(+), 20 deletions(-) rename knp/base-framework/impl/visualizer/{utils_dot.cpp => dot_helpers.cpp} (97%) rename knp/base-framework/impl/visualizer/{utils_dot.h => dot_helpers.h} (100%) rename knp/base-framework/impl/visualizer/{utils_creation.cpp => generate_files.cpp} (99%) rename knp/base-framework/impl/visualizer/{utils_creation.h => generate_files.h} (100%) rename knp/base-framework/impl/visualizer/{utils_graph.cpp => graph_helpers.cpp} (99%) rename knp/base-framework/impl/visualizer/{utils_graph.h => graph_helpers.h} (98%) diff --git a/knp/base-framework/CMakeLists.txt b/knp/base-framework/CMakeLists.txt index 6bf92b50..3d469bd1 100644 --- a/knp/base-framework/CMakeLists.txt +++ b/knp/base-framework/CMakeLists.txt @@ -79,9 +79,9 @@ knp_add_library("${PROJECT_NAME}-core" impl/logging.cpp impl/visualizer/graph_physics.cpp impl/visualizer/visualize_network.cpp - impl/visualizer/utils_graph.cpp - impl/visualizer/utils_dot.cpp - impl/visualizer/utils_creation.cpp + impl/visualizer/graph_helpers.cpp + impl/visualizer/dot_helpers.cpp + impl/visualizer/generate_files.cpp ${${PROJECT_NAME}_headers} ALIAS KNP::BaseFramework::Core LINK_PRIVATE diff --git a/knp/base-framework/impl/visualizer/utils_dot.cpp b/knp/base-framework/impl/visualizer/dot_helpers.cpp similarity index 97% rename from knp/base-framework/impl/visualizer/utils_dot.cpp rename to knp/base-framework/impl/visualizer/dot_helpers.cpp index 8cb0eb59..ce5d1566 100644 --- a/knp/base-framework/impl/visualizer/utils_dot.cpp +++ b/knp/base-framework/impl/visualizer/dot_helpers.cpp @@ -1,4 +1,4 @@ -#include "utils_dot.h" +#include "dot_helpers.h" #include @@ -10,7 +10,7 @@ #include #include -#include "utils_graph.h" +#include "graph_helpers.h" /** @@ -29,7 +29,6 @@ void write_header_and_styles(std::ofstream& out) out << " layout=neato;\n"; // Change engine to neato (physical force model) out << " pack=true;\n"; // Enable compact packing of independent components out << " packmode=\"graph\";\n\n"; // Pack each subgroup as a separate mini-graph - // // Global design settings for the graph out << " // Canvas settings\n"; @@ -82,11 +81,9 @@ void write_edge_without_src( auto short_name = std::string(edge_uid).substr(0, 8); - out << " " - << "ghost_src_" << num << " [shape=point, style=invis" + out << " ghost_src_" << num << " [shape=point, style=invis" << "];\n"; - out << " \"" << "ghost_src_" << num << "\" -> " << std::string(dst) << "[style=\"dotted\", " << "label=\"projection :\\n" @@ -112,8 +109,7 @@ void write_edge_without_dst( auto num = std::to_string(i); auto short_name = std::string(edge_uid).substr(0, 8); - out << " " - << "ghost_dst_" << num << " [shape=point, style=invis" + out << " ghost_dst_" << num << " [shape=point, style=invis" << "];\n"; out << " \"" << std::string(src) << "\" -> " @@ -313,6 +309,5 @@ void write_projections_and_populations_to_dot( } write_edge_to_dot(out, std::string(src), std::string(dst), std::string(unique_edge).substr(0, 8), edge_size); } - out << "}\n"; } diff --git a/knp/base-framework/impl/visualizer/utils_dot.h b/knp/base-framework/impl/visualizer/dot_helpers.h similarity index 100% rename from knp/base-framework/impl/visualizer/utils_dot.h rename to knp/base-framework/impl/visualizer/dot_helpers.h diff --git a/knp/base-framework/impl/visualizer/utils_creation.cpp b/knp/base-framework/impl/visualizer/generate_files.cpp similarity index 99% rename from knp/base-framework/impl/visualizer/utils_creation.cpp rename to knp/base-framework/impl/visualizer/generate_files.cpp index fd27eb52..aec24778 100644 --- a/knp/base-framework/impl/visualizer/utils_creation.cpp +++ b/knp/base-framework/impl/visualizer/generate_files.cpp @@ -1,4 +1,4 @@ -#include "utils_creation.h" +#include "generate_files.h" #include @@ -11,8 +11,8 @@ #include #include -#include "utils_dot.h" -#include "utils_graph.h" +#include "dot_helpers.h" +#include "graph_helpers.h" extern "C" { diff --git a/knp/base-framework/impl/visualizer/utils_creation.h b/knp/base-framework/impl/visualizer/generate_files.h similarity index 100% rename from knp/base-framework/impl/visualizer/utils_creation.h rename to knp/base-framework/impl/visualizer/generate_files.h diff --git a/knp/base-framework/impl/visualizer/utils_graph.cpp b/knp/base-framework/impl/visualizer/graph_helpers.cpp similarity index 99% rename from knp/base-framework/impl/visualizer/utils_graph.cpp rename to knp/base-framework/impl/visualizer/graph_helpers.cpp index 516f7013..f1fa2f2f 100644 --- a/knp/base-framework/impl/visualizer/utils_graph.cpp +++ b/knp/base-framework/impl/visualizer/graph_helpers.cpp @@ -1,4 +1,4 @@ -#include "utils_graph.h" +#include "graph_helpers.h" #include #include diff --git a/knp/base-framework/impl/visualizer/utils_graph.h b/knp/base-framework/impl/visualizer/graph_helpers.h similarity index 98% rename from knp/base-framework/impl/visualizer/utils_graph.h rename to knp/base-framework/impl/visualizer/graph_helpers.h index 657931d4..0b5717df 100644 --- a/knp/base-framework/impl/visualizer/utils_graph.h +++ b/knp/base-framework/impl/visualizer/graph_helpers.h @@ -1,5 +1,4 @@ #pragma once -// #include #include #include diff --git a/knp/base-framework/impl/visualizer/visualize_network.cpp b/knp/base-framework/impl/visualizer/visualize_network.cpp index dc8b3399..996d9d50 100644 --- a/knp/base-framework/impl/visualizer/visualize_network.cpp +++ b/knp/base-framework/impl/visualizer/visualize_network.cpp @@ -12,9 +12,9 @@ extern "C" } #include -#include "utils_creation.h" -#include "utils_dot.h" -#include "utils_graph.h" +#include "generate_files.h" +#include "graph_helpers.h" + namespace knp::framework { From d1cf11479c1d1f3010e996d994fcab1d1d7edced Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 5 Aug 2026 15:54:01 +0300 Subject: [PATCH 04/12] returned the initial comment, removed the accidental simplification of the file: #219 --- examples/mnist-learn/main.cpp | 46 ++++++++++++++----- .../impl/visualizer/visualize_network.cpp | 20 ++++++++ .../framework/visualizer/visualize_network.h | 21 +++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/examples/mnist-learn/main.cpp b/examples/mnist-learn/main.cpp index 384587f4..30be851f 100644 --- a/examples/mnist-learn/main.cpp +++ b/examples/mnist-learn/main.cpp @@ -1,6 +1,8 @@ /** * @file main.cpp * @brief Example of training a MNIST network. + * @kaspersky_support D. Postnikov + * @date 03.02.2026 * @license Apache 2.0 * @copyright © 2026 AO Kaspersky Lab * @@ -50,10 +52,16 @@ void run_model(const ModelDescription& model_desc) // Online Help link: https://click.kaspersky.com/?hl=en-US&version=2.0&pid=KNP&link=online_help&helpid=243548 knp::framework::BackendLoader backend_loader; - train_model(model_desc, dataset, network, backend_loader); + auto backend = train_model(model_desc, dataset, network, backend_loader); if (!model_desc.model_saving_path_.empty()) save_network(model_desc, network); + + // knp::framework::set_saving_path("temp_test_dir"); + visualize_network(network.network_); + visualize_network(network.network_, backend); + visualize_bus(network.network_, backend); + auto inference_spikes = infer_model(model_desc, dataset, network, backend_loader); evaluate_results(inference_spikes, dataset); @@ -80,23 +88,39 @@ int main(int argc, char** argv) std::cin.get(); std::cout << "Starting model." << std::endl; + // Starting model according to selected type. + switch (model_desc.type_) + { + case SupportedModelType::BLIFAT: + { + run_model(model_desc); + break; + } + case SupportedModelType::AltAI: + { + run_model(model_desc); + break; + } + default: + throw std::runtime_error("Unknown model type."); + } - Dataset dataset = process_dataset(model_desc); + // Dataset dataset = process_dataset(model_desc); - AnnotatedNetwork network = construct_network(model_desc); + // AnnotatedNetwork network = construct_network(model_desc); - knp::framework::BackendLoader backend_loader; - auto backend = train_model(model_desc, dataset, network, backend_loader); + // knp::framework::BackendLoader backend_loader; + // auto backend = train_model(model_desc, dataset, network, backend_loader); - if (!model_desc.model_saving_path_.empty()) save_network(model_desc, network); + // if (!model_desc.model_saving_path_.empty()) save_network(model_desc, network); - // knp::framework::set_saving_path("temp_test_dir"); - visualize_network(network.network_); - visualize_network(network.network_, backend); - visualize_bus(network.network_, backend); + // // knp::framework::set_saving_path("temp_test_dir"); + // visualize_network(network.network_); + // visualize_network(network.network_, backend); + // visualize_bus(network.network_, backend); - auto network_path = model_desc.model_saving_path_; + // auto network_path = model_desc.model_saving_path_; return EXIT_SUCCESS; diff --git a/knp/base-framework/impl/visualizer/visualize_network.cpp b/knp/base-framework/impl/visualizer/visualize_network.cpp index 996d9d50..25c296f4 100644 --- a/knp/base-framework/impl/visualizer/visualize_network.cpp +++ b/knp/base-framework/impl/visualizer/visualize_network.cpp @@ -1,3 +1,23 @@ +/** + * @file visualize_network.cpp + * @brief Functions for subgraph visualization. + * @kaspersky_support A. Vartenkov + * @date 26.07.2024 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include #include diff --git a/knp/base-framework/include/knp/framework/visualizer/visualize_network.h b/knp/base-framework/include/knp/framework/visualizer/visualize_network.h index 40730ba3..70ea57dc 100644 --- a/knp/base-framework/include/knp/framework/visualizer/visualize_network.h +++ b/knp/base-framework/include/knp/framework/visualizer/visualize_network.h @@ -1,3 +1,24 @@ +/** + * @file visualize_network.h + * @brief Functions for graph visualization. + * @warning Most of the functions are not well-tested or stable yet. + * @date 26.07.2024 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include From 7ecf8a25b425dfe702a916e36e192231dcdbbe7e Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 5 Aug 2026 17:21:53 +0300 Subject: [PATCH 05/12] add initial comments: #219 --- .../impl/visualizer/dot_helpers.cpp | 21 ++++++++++++++++++ .../impl/visualizer/dot_helpers.h | 21 ++++++++++++++++++ .../impl/visualizer/generate_files.cpp | 21 ++++++++++++++++++ .../impl/visualizer/generate_files.h | 21 ++++++++++++++++++ .../impl/visualizer/graph_helpers.cpp | 21 ++++++++++++++++++ .../impl/visualizer/graph_helpers.h | 21 ++++++++++++++++++ .../framework/visualize_network_test.cpp | 22 ++++++++++++++++++- 7 files changed, 147 insertions(+), 1 deletion(-) diff --git a/knp/base-framework/impl/visualizer/dot_helpers.cpp b/knp/base-framework/impl/visualizer/dot_helpers.cpp index ce5d1566..47d67350 100644 --- a/knp/base-framework/impl/visualizer/dot_helpers.cpp +++ b/knp/base-framework/impl/visualizer/dot_helpers.cpp @@ -1,3 +1,24 @@ +/** + * @file dot_helpers.cpp + * @brief Help functions for writing a dot file. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "dot_helpers.h" #include diff --git a/knp/base-framework/impl/visualizer/dot_helpers.h b/knp/base-framework/impl/visualizer/dot_helpers.h index 8aa73312..ce487229 100644 --- a/knp/base-framework/impl/visualizer/dot_helpers.h +++ b/knp/base-framework/impl/visualizer/dot_helpers.h @@ -1,3 +1,24 @@ +/** + * @file dot_helpers.h + * @brief Help functions for writing a dot file. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include diff --git a/knp/base-framework/impl/visualizer/generate_files.cpp b/knp/base-framework/impl/visualizer/generate_files.cpp index aec24778..6ad0809b 100644 --- a/knp/base-framework/impl/visualizer/generate_files.cpp +++ b/knp/base-framework/impl/visualizer/generate_files.cpp @@ -1,3 +1,24 @@ +/** + * @file generate_files.cpp + * @brief Functions for generate dot/png files. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "generate_files.h" #include diff --git a/knp/base-framework/impl/visualizer/generate_files.h b/knp/base-framework/impl/visualizer/generate_files.h index a31efe72..b76d9431 100644 --- a/knp/base-framework/impl/visualizer/generate_files.h +++ b/knp/base-framework/impl/visualizer/generate_files.h @@ -1,3 +1,24 @@ +/** + * @file generate_files.h + * @brief Functions for generate dot/png files. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include diff --git a/knp/base-framework/impl/visualizer/graph_helpers.cpp b/knp/base-framework/impl/visualizer/graph_helpers.cpp index f1fa2f2f..d0c26328 100644 --- a/knp/base-framework/impl/visualizer/graph_helpers.cpp +++ b/knp/base-framework/impl/visualizer/graph_helpers.cpp @@ -1,3 +1,24 @@ +/** + * @file graph_helpers.cpp + * @brief Help functions for graph vizualize. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "graph_helpers.h" #include diff --git a/knp/base-framework/impl/visualizer/graph_helpers.h b/knp/base-framework/impl/visualizer/graph_helpers.h index 0b5717df..fd82c855 100644 --- a/knp/base-framework/impl/visualizer/graph_helpers.h +++ b/knp/base-framework/impl/visualizer/graph_helpers.h @@ -1,3 +1,24 @@ +/** + * @file graph_helpers.cpp + * @brief Help functions for graph vizualize. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include diff --git a/knp/tests/framework/visualize_network_test.cpp b/knp/tests/framework/visualize_network_test.cpp index d8477031..d9a68d51 100644 --- a/knp/tests/framework/visualize_network_test.cpp +++ b/knp/tests/framework/visualize_network_test.cpp @@ -1,3 +1,24 @@ +/** + * @file visualize_network_test.cpp + * @brief Functions for subgraph visualization. + * @kaspersky_support Kirill L. + * @date 05.08.2026 + * @license Apache 2.0 + * @copyright © 2024 AO Kaspersky Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include #include #include @@ -8,7 +29,6 @@ #include #include -#include #include using BLIFATParams = knp::neuron_traits::neuron_parameters; From be655a8f1c22e3335de77aaff57faf431aa583ab Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 5 Aug 2026 17:57:39 +0300 Subject: [PATCH 06/12] removed comments: #219 --- examples/mnist-learn/main.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/examples/mnist-learn/main.cpp b/examples/mnist-learn/main.cpp index 30be851f..539dda68 100644 --- a/examples/mnist-learn/main.cpp +++ b/examples/mnist-learn/main.cpp @@ -105,23 +105,5 @@ int main(int argc, char** argv) throw std::runtime_error("Unknown model type."); } - // Dataset dataset = process_dataset(model_desc); - - // AnnotatedNetwork network = construct_network(model_desc); - - - // knp::framework::BackendLoader backend_loader; - // auto backend = train_model(model_desc, dataset, network, backend_loader); - - // if (!model_desc.model_saving_path_.empty()) save_network(model_desc, network); - - // // knp::framework::set_saving_path("temp_test_dir"); - // visualize_network(network.network_); - // visualize_network(network.network_, backend); - // visualize_bus(network.network_, backend); - - // auto network_path = model_desc.model_saving_path_; - - return EXIT_SUCCESS; } From 7a1bb02796f5ea6769b2714d1aebb3cf1b288273 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Fri, 7 Aug 2026 13:42:13 +0300 Subject: [PATCH 07/12] small fixes: #219 --- examples/mnist-client/CMakeLists.txt | 4 +-- examples/mnist-client/main.cpp | 1 + examples/mnist-learn/CMakeLists.txt | 1 - examples/mnist-learn/main.cpp | 2 +- examples/mnist-learn/training.h | 1 - .../sonata/types/resource_blifat_neuron.cpp | 36 ++++++++----------- 6 files changed, 19 insertions(+), 26 deletions(-) diff --git a/examples/mnist-client/CMakeLists.txt b/examples/mnist-client/CMakeLists.txt index c641dd51..eb57c5b0 100644 --- a/examples/mnist-client/CMakeLists.txt +++ b/examples/mnist-client/CMakeLists.txt @@ -48,6 +48,6 @@ add_executable(mnist-client find_package(PkgConfig REQUIRED) pkg_check_modules(GRAPHVIZ REQUIRED IMPORTED_TARGET libgvc libcgraph) -target_include_directories(mnist-client PUBLIC ${GRAPHVIZ_INCLUDE_DIRS}) -target_link_libraries(mnist-client PUBLIC PkgConfig::GRAPHVIZ ) +target_include_directories(mnist-client PRIVATE ${GRAPHVIZ_INCLUDE_DIRS}) +target_link_libraries(mnist-client PRIVATE PkgConfig::GRAPHVIZ ) target_link_libraries(mnist-client PRIVATE KNP::BaseFramework::Core ${OpenCV_LIBS} ${Boost_LIBRARIES} ) diff --git a/examples/mnist-client/main.cpp b/examples/mnist-client/main.cpp index 3642b6c9..ecbd43ee 100644 --- a/examples/mnist-client/main.cpp +++ b/examples/mnist-client/main.cpp @@ -31,6 +31,7 @@ #include "inference.h" + // Namespace for program options. namespace po = boost::program_options; diff --git a/examples/mnist-learn/CMakeLists.txt b/examples/mnist-learn/CMakeLists.txt index 7e503706..67262070 100644 --- a/examples/mnist-learn/CMakeLists.txt +++ b/examples/mnist-learn/CMakeLists.txt @@ -45,5 +45,4 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${PROJECT_SOURCE_DIR}) target_link_libraries(${PROJECT_NAME} PRIVATE KNP::BaseFramework::Core Boost::program_options - ${Graphviz_LIBRARIES} ) diff --git a/examples/mnist-learn/main.cpp b/examples/mnist-learn/main.cpp index 539dda68..892ad412 100644 --- a/examples/mnist-learn/main.cpp +++ b/examples/mnist-learn/main.cpp @@ -74,7 +74,7 @@ void run_model(const ModelDescription& model_desc) * @param argc Argument count. * @param argv Arguments value. * - * @return Error code.source_node_idnode_population: 00000000-0000-0000-0000-000000000000 + * @return Error code. */ int main(int argc, char** argv) { diff --git a/examples/mnist-learn/training.h b/examples/mnist-learn/training.h index 4c12ae36..39674a7c 100644 --- a/examples/mnist-learn/training.h +++ b/examples/mnist-learn/training.h @@ -100,7 +100,6 @@ void train_network( // Online Help link: https://click.kaspersky.com/?hl=en-US&version=2.0&pid=KNP&link=online_help&helpid=235849 knp::framework::Model model(std::move(network.network_)); - // knp::framework::Model model(network.network_); knp::framework::ModelLoader::InputChannelMap channel_map = build_channel_map_train(network, model, dataset); diff --git a/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp b/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp index d6343221..2bf6ad1b 100644 --- a/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp +++ b/knp/base-framework/impl/sonata/types/resource_blifat_neuron.cpp @@ -57,36 +57,28 @@ void add_tags_to_h5( HighFive::NodeTraits &population_group) { core::TagMap tags = population.get_tags(); - std::vector tags_values; std::vector tags_keys; if (tags.exists("io_type")) { auto io_type_tag_value = std::any_cast(tags["io_type"]); - if (io_type_tag_value == knp::core::tags::IOType::input) - { - tags_values.push_back("0"); - } + tags_values.push_back("input"); else if (io_type_tag_value == knp::core::tags::IOType::output) - { - tags_values.push_back("1"); - } + tags_values.push_back("output"); else - { - std::cout << "unknown io_type tag value" << std::endl; - } + SPDLOG_ERROR("Unknown io_type tag value"); tags_keys.push_back("io_type"); } auto name_tag_value = std::any_cast(tags["name"]); tags_values.push_back(name_tag_value); tags_keys.push_back("name"); - population_group.createDataSet("tags_values", tags_values); population_group.createDataSet("tags_keys", tags_keys); } + template <> void add_population_to_h5>( HighFive::File &file_h5, const core::Population &population) @@ -185,6 +177,7 @@ void add_population_to_h5; + // Two arrays are uploaded: keys and tag values. // They are tagged with the population in accordance with the TagMap structure. void load_tags_to_population( @@ -206,17 +199,18 @@ void load_tags_to_population( if (key == "io_type") { - switch (std::stoi(value)) + if (value == "input") + { + population.get_tags()[key] = knp::core::tags::IOType::input; + break; + } + else if (value == "output") { - case 0: - population.get_tags()[key] = knp::core::tags::IOType::input; - break; - case 1: - population.get_tags()[key] = knp::core::tags::IOType::output; - break; - default: - std::cout << "unknown io_type tag value" << std::endl; + population.get_tags()[key] = knp::core::tags::IOType::output; + break; } + else + SPDLOG_ERROR("Unknown io_type tag value"); continue; } population.get_tags()[key] = value; From da95183216432832c0a2e0ddf185052c157c0064 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Fri, 7 Aug 2026 17:17:08 +0300 Subject: [PATCH 08/12] small fixes pt. 2: #219 --- .../impl/visualizer/dot_helpers.cpp | 239 +++++++++--------- .../impl/visualizer/dot_helpers.h | 126 ++++----- .../impl/visualizer/generate_files.cpp | 64 ++--- .../impl/visualizer/generate_files.h | 45 ++-- .../impl/visualizer/graph_helpers.cpp | 32 +-- .../impl/visualizer/graph_helpers.h | 36 +-- 6 files changed, 277 insertions(+), 265 deletions(-) diff --git a/knp/base-framework/impl/visualizer/dot_helpers.cpp b/knp/base-framework/impl/visualizer/dot_helpers.cpp index 47d67350..4e3f93b2 100644 --- a/knp/base-framework/impl/visualizer/dot_helpers.cpp +++ b/knp/base-framework/impl/visualizer/dot_helpers.cpp @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ * limitations under the License. */ + #include "dot_helpers.h" #include @@ -35,15 +36,15 @@ /** - * @brief Write header and styles to DOT file + * @brief Write header and styles to DOT file. * - * @param out output file stream + * @param out output file stream. * - * @details commentsed - for a connected graph + * @details commentsed - for a connected graph. */ void write_header_and_styles(std::ofstream& out) { - // digraph - directed + // Digraph - directed. out << "digraph G {\n"; // for unconnected graphs this is needed: (+ change -> to --) @@ -52,7 +53,7 @@ void write_header_and_styles(std::ofstream& out) out << " packmode=\"graph\";\n\n"; // Pack each subgroup as a separate mini-graph // Global design settings for the graph - out << " // Canvas settings\n"; + out << " // Canvas settings.\n"; out << " bgcolor=\"#FAFAFA\";\n"; // Light gray soft background page // out << " rankdir=LR;\n"; // Graph goes left to right out << " splines=true;\n"; // Beautiful smooth curves for arrows @@ -61,7 +62,7 @@ void write_header_and_styles(std::ofstream& out) out << " overlap=false;\n"; out << " sep=\"+30\";\n"; - out << " // Default node styling\n"; + out << " // Default node styling.\n"; out << " node [\n"; out << " fontname=\"Helvetica,Arial,sans-serif\",\n"; // Modern font out << " fontsize=11,\n"; @@ -71,7 +72,7 @@ void write_header_and_styles(std::ofstream& out) out << " margin=\"0.2,0.1\"\n"; // Internal padding for text out << " ];\n\n"; - out << " // Default edge (arrow) styling\n"; + out << " // Default edge (arrow) styling.\n"; out << " edge [\n"; out << " fontname=\"Helvetica,Arial,sans-serif\",\n"; out << " fontsize=9,\n"; @@ -82,15 +83,16 @@ void write_header_and_styles(std::ofstream& out) out << " ];\n\n"; } + /** - * @brief Write edge without source to DOT file + * @brief Write edge without source to DOT file. * - * @param out output file stream - * @param dst destination node UID - * @param i counter for ghost node - * @param edge_uid edge UID - * @param size edge size - * @param color edge color + * @param out output file stream. + * @param dst destination node UID. + * @param i counter for ghost node. + * @param edge_uid edge UID. + * @param size edge size. + * @param color edge color. * * @details A graphviz can't draw an arrow from nowhere. It is necessary to make an invisible vertex. */ @@ -101,7 +103,6 @@ void write_edge_without_src( auto num = std::to_string(i); auto short_name = std::string(edge_uid).substr(0, 8); - out << " ghost_src_" << num << " [shape=point, style=invis" << "];\n"; @@ -112,14 +113,14 @@ void write_edge_without_src( } /** - * @brief Write edge without destination to DOT file + * @brief Write edge without destination to DOT file. * - * @param out output file stream - * @param src source node UID - * @param i counter for ghost node - * @param edge_uid edge UID - * @param size edge size - * @param color edge color + * @param out output file stream. + * @param src source node UID. + * @param i counter for ghost node. + * @param edge_uid edge UID. + * @param size edge size. + * @param color edge color. * * @details A graphviz can't draw an arrow to nowhere. It is necessary to make an invisible vertex. */ @@ -139,123 +140,123 @@ void write_edge_without_dst( << short_name << "\\nsize:" << size << "\", color=\"" << color << "\"];\n"; } + /** - * @brief Write bus messages (spike messages and synaptic impact messages) to DOT file + * @brief Write message nodes and edges to DOT file. + * + * @param out output file stream. + * @param message vector of message pairs (sender, receiver). + * @param graph network graph containing nodes and edges information. + * @param label label for the edge (e.g., "Spike Message" or "Synapse Message"). * - * @param spike_messages vector of spike message pairs - * @param synaptic_messages vector of synaptic message pairs - * @param graph network graph - * @param out output file stream for DOT file + * @details This function writes nodes and edges for message flow visualization. + * For Spike Messages, sender is a node and receiver is an edge. + * For Synapse Messages, sender is an edge and receiver is a node. + * The function extracts names and sizes from the graph for proper labeling. */ -void write_bus_messeges_to_dot( - std::vector> const& spike_messages, - std::vector> const& synaptic_messages, - knp::framework::NetworkGraph const& graph, std::ofstream& out) +void writeMessageNodesAndEdge( + std::ostream& out, const std::vector>& messages, + knp::framework::NetworkGraph const& graph, const std::string& label) { - write_header_and_styles(out); - - for (auto spike_message : spike_messages) + for (auto message : messages) { - auto spike_sender = spike_message.first; - auto spike_receiver = spike_message.second; - auto sender_name = std::string(spike_sender).substr(0, 8); - auto receiver_name = std::string(spike_receiver).substr(0, 8); - std::string sender_size = ""; - std::string receiver_size = ""; - - auto node_it = std::find_if( - graph.nodes_.begin(), graph.nodes_.end(), - [&spike_sender](auto const& node) { return node.uid_ == spike_sender; }); - if (node_it != graph.nodes_.end()) + auto sender = message.first; + auto receiver = message.second; + auto sender_name = std::string(sender).substr(0, 8); + auto receiver_name = std::string(receiver).substr(0, 8); + std::string sender_size; + std::string receiver_size; + + if (label == "Spike Message") { - sender_name = node_it->name_; - sender_size = std::to_string(node_it->size_); + auto node_it = std::find_if( + graph.nodes_.begin(), graph.nodes_.end(), [&sender](auto const& node) { return node.uid_ == sender; }); + if (node_it != graph.nodes_.end()) + { + sender_name = node_it->name_; + sender_size = std::to_string(node_it->size_); + } + auto edge_it = std::find_if( + graph.edges_.begin(), graph.edges_.end(), + [&receiver](const auto& edge) { return edge.uid_ == receiver; }); + if (edge_it != graph.edges_.end()) + { + receiver_name = edge_it->name_; + receiver_size = std::to_string(edge_it->size_); + } } - - auto edge_it = std::find_if( - graph.edges_.begin(), graph.edges_.end(), - [&spike_receiver](const auto& edge) { return edge.uid_ == spike_receiver; }); - if (edge_it != graph.edges_.end()) + else if (label == "Synaptic Impact Message") { - receiver_name = edge_it->name_; - receiver_size = std::to_string(edge_it->size_); + auto edge_it = std::find_if( + graph.edges_.begin(), graph.edges_.end(), [&sender](const auto& edge) { return edge.uid_ == sender; }); + if (edge_it != graph.edges_.end()) + { + sender_name = edge_it->name_; + sender_size = std::to_string(edge_it->size_); + } + auto node_it = std::find_if( + graph.nodes_.begin(), graph.nodes_.end(), + [&receiver](const auto& node) { return node.uid_ == receiver; }); + if (node_it != graph.nodes_.end()) + { + receiver_name = node_it->name_; + receiver_size = std::to_string(node_it->size_); + } } - auto index_from = std::string(spike_sender); - auto index_to = std::string(spike_receiver); + auto index_from = std::string(sender); + auto index_to = std::string(receiver); - // write nodes + // Write node. out << " \"" << index_from << "\" [label=\"" << sender_name << "\"];" << "# " << sender_size << "\n"; out << " \"" << index_to << "\" [label=\"" << receiver_name << "\"];" << "# " << receiver_size << "\n"; - // write edge - out << " \"" << index_from << "\" -> \"" << index_to << "\" [label=\"Spike Message" - << "\"];\n"; + // Write edge. + out << " \"" << index_from << "\" -> \"" << index_to << "\" [label=\"" << label << "\"];\n"; } +} - for (auto synaptic_message : synaptic_messages) - { - auto synaptic_sender = synaptic_message.first; - auto synaptic_receiver = synaptic_message.second; - auto sender_name = std::string(synaptic_sender).substr(0, 8); - auto receiver_name = std::string(synaptic_receiver).substr(0, 8); - std::string sender_size = ""; - std::string receiver_size = ""; - - auto edge_it = std::find_if( - graph.edges_.begin(), graph.edges_.end(), - [&synaptic_sender](const auto& edge) { return edge.uid_ == synaptic_sender; }); - if (edge_it != graph.edges_.end()) - { - sender_name = edge_it->name_; - sender_size = std::to_string(edge_it->size_); - } - - auto node_it = std::find_if( - graph.nodes_.begin(), graph.nodes_.end(), - [&synaptic_receiver](const auto& node) { return node.uid_ == synaptic_receiver; }); - if (node_it != graph.nodes_.end()) - { - receiver_name = node_it->name_; - receiver_size = std::to_string(node_it->size_); - } - - auto index_from = std::string(synaptic_sender); - auto index_to = std::string(synaptic_receiver); - // write nodes - out << " \"" << index_from << "\" [label=\"" << sender_name << "\"];" - << "# " << sender_size << "\n"; - out << " \"" << index_to << "\" [label=\"" << receiver_name << "\"];" - << "# " << receiver_size << "\n"; +/** + * @brief Write bus messages (spike messages and synaptic impact messages) to DOT file. + * + * @param out output file stream for DOT file. + * @param spike_messages vector of spike message pairs. + * @param synaptic_messages vector of synaptic message pairs. + * @param graph network graph. + */ +void write_bus_messages_to_dot( + std::ofstream& out, std::vector> const& spike_messages, + std::vector> const& synaptic_messages, + knp::framework::NetworkGraph const& graph) +{ + write_header_and_styles(out); - // write edge - out << " \"" << index_from << "\" -> \"" << index_to << "\" [label=\"Synaptic Impact Message" - << "\"];\n"; - } + writeMessageNodesAndEdge(out, spike_messages, graph, "Spike Message"); + writeMessageNodesAndEdge(out, synaptic_messages, graph, "Synaptic Impact Message"); out << "}\n"; } + /** - * @brief Write projections and populations to DOT file + * @brief Write projections and populations to DOT file. * - * @param unique_nodes set of unique node UIDs - * @param unique_edges set of unique edge UIDs - * @param modificators set of modificator UIDs - * @param out output file stream - * @param dynamic_color color for dynamic elements - * @param graph network graph - * @param node_src map of node sources - * @param node_dst map of node destinations + * @param out output file stream. + * @param unique_nodes set of unique node UIDs. + * @param unique_edges set of unique edge UIDs. + * @param modificators set of modificator UIDs. + * @param dynamic_color color for dynamic elements. + * @param graph network graph. + * @param node_src map of node sources. + * @param node_dst map of node destinations. */ void write_projections_and_populations_to_dot( - std::set const& unique_nodes, std::set const& unique_edges, - std::set& modificators, std::ofstream& out, std::string& dynamic_color, - knp::framework::NetworkGraph const& graph, std::map& node_src, - std::map& node_dst) + std::ofstream& out, std::set const& unique_nodes, std::set const& unique_edges, + std::set& modificators, std::string& dynamic_color, knp::framework::NetworkGraph const& graph, + std::map& node_src, std::map& node_dst) { write_header_and_styles(out); @@ -266,7 +267,7 @@ void write_projections_and_populations_to_dot( return; } - /////// write nodes to dot ////////// + // Write nodes to dot. for (const auto& unique_node : unique_nodes) { // check if it is modificator @@ -283,7 +284,7 @@ void write_projections_and_populations_to_dot( write_node_to_dot(out, std::string(unique_node), node_name, graph_node_size); } - /////// write edges to dot ////////// + // Write edges to dot. [[maybe_unused]] int num_invisible_src_nodes = 1; [[maybe_unused]] int num_invisible_dst_nodes = 1; for (const auto& unique_edge : unique_edges) @@ -302,9 +303,9 @@ void write_projections_and_populations_to_dot( { SPDLOG_ERROR("ERROR: There is an edge without a source and a destination! Details: {}", e.what()); } - //write node to dot (ghost_i -> dst) + // Write node to dot (ghost_i -> dst). write_edge_without_src(out, dst, num_invisible_dst_nodes, unique_edge, edge_size); - num_invisible_src_nodes++; + ++num_invisible_src_nodes; continue; } auto src = node_src.at(unique_edge); @@ -313,14 +314,14 @@ void write_projections_and_populations_to_dot( if (it2 == node_dst.end()) { SPDLOG_INFO("There is't node_dst for this edge: {}", std::string(unique_edge)); - //write node to dot (src -> ghost_j) + // Write node to dot (src -> ghost_j). write_edge_without_dst(out, src, num_invisible_dst_nodes, unique_edge, edge_size); - num_invisible_dst_nodes++; + ++num_invisible_dst_nodes; continue; } auto dst = node_dst.at(unique_edge); - // check if the source is a modifier (change color of edge) + // Check if the source is a modifier (change color of edge). if (modificators.find(src) != modificators.end()) { write_edge_to_dot( diff --git a/knp/base-framework/impl/visualizer/dot_helpers.h b/knp/base-framework/impl/visualizer/dot_helpers.h index ce487229..b25fabf1 100644 --- a/knp/base-framework/impl/visualizer/dot_helpers.h +++ b/knp/base-framework/impl/visualizer/dot_helpers.h @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,9 @@ * limitations under the License. */ + #pragma once + #include #include @@ -30,7 +32,6 @@ #include #include #include - extern "C" { #include @@ -38,25 +39,26 @@ extern "C" /** - * @brief Write header and styles to DOT file + * @brief Write header and styles to DOT file. * - * @param out output file stream + * @param out output file stream. * - * @details commentsed - for a connected graph + * @details commentsed - for a connected graph. */ void write_header_and_styles(std::ofstream& out); + /** - * @brief Write edge to DOT file + * @brief Write edge to DOT file. * - * @param out output file stream - * @param index_from source node UID - * @param index_to target node UID - * @param name edge name - * @param size edge size - * @param color edge color + * @param out output file stream. + * @param index_from source node UID. + * @param index_to target node UID. + * @param name edge name. + * @param size edge size. + * @param color edge color. * - * @details need to visualize dynamic + * @details need to visualize dynamic. */ template void write_edge_to_dot( @@ -70,17 +72,18 @@ void write_edge_to_dot( << name << "\\nsize: " << size << "\", color=\"" << color << "\"];\n"; } + /** - * @brief Write node to DOT file + * @brief Write node to DOT file. * - * @param out output file stream - * @param node_uid node UID - * @param name node name - * @param size node size - * @param color node color - * @param type_node node type + * @param out output file stream. + * @param node_uid node UID. + * @param name node name. + * @param size node size. + * @param color node color. + * @param type_node node type. * - * @details need to visualize dynamic + * @details need to visualize dynamic. */ template void write_node_to_dot( @@ -101,15 +104,16 @@ void write_node_to_dot( out << " \"" << node_uid << "\" [label=\"" << type_node << ":\\n" << name << "\", color=\"" << color << "\"];\n"; } + /** - * @brief Write edge without source to DOT file + * @brief Write edge without source to DOT file. * - * @param out output file stream - * @param dst destination node UID - * @param i counter for ghost node - * @param edge_uid edge UID - * @param size edge size - * @param color edge color + * @param out output file stream. + * @param dst destination node UID. + * @param i counter for ghost node. + * @param edge_uid edge UID. + * @param size edge size. + * @param color edge color. * * @details A graphviz can't draw an arrow from nowhere. It is necessary to make an invisible vertex. */ @@ -117,15 +121,16 @@ void write_edge_without_src( std::ofstream& out, knp::core::UID const& dst, int const& i, knp::core::UID const& edge_uid, std::string const& size, std::string const& color = "lightgray"); + /** - * @brief Write edge without destination to DOT file + * @brief Write edge without destination to DOT file. * - * @param out output file stream - * @param src source node UID - * @param i counter for ghost node - * @param edge_uid edge UID - * @param size edge size - * @param color edge color + * @param out output file stream. + * @param src source node UID. + * @param i counter for ghost node. + * @param edge_uid edge UID. + * @param size edge size. + * @param color edge color. * * @details A graphviz can't draw an arrow to nowhere. It is necessary to make an invisible vertex. */ @@ -134,42 +139,43 @@ void write_edge_without_dst( std::string const& size, std::string const& color = "lightgray"); /** - * @brief Convert DOT file to PNG image + * @brief Convert DOT file to PNG image. * - * @param path_to_dot_file path to input DOT file - * @param path_to_png_file path to output PNG file + * @param path_to_dot_file path to input DOT file. + * @param path_to_png_file path to output PNG file. * * @return boolean indicating success */ bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& path_to_png_file); + /** - * @brief Write bus messages (spike messages and synaptic impact messages) to DOT file + * @brief Write bus messages (spike messages and synaptic impact messages) to DOT file. * - * @param spike_messages vector of spike message pairs - * @param synaptic_messages vector of synaptic message pairs - * @param graph network graph - * @param out output file stream for DOT file + * @param out output file stream for DOT file. + * @param spike_messages vector of spike message pairs. + * @param synaptic_messages vector of synaptic message pairs. + * @param graph network graph. */ -void write_bus_messeges_to_dot( - std::vector> const& spike_messages, +void write_bus_messages_to_dot( + std::ofstream& out, std::vector> const& spike_messages, std::vector> const& synaptic_messages, - knp::framework::NetworkGraph const& graph, std::ofstream& out); + knp::framework::NetworkGraph const& graph); + /** - * @brief Write projections and populations to DOT file - * - * @param unique_nodes set of unique node UIDs - * @param unique_edges set of unique edge UIDs - * @param modificators set of modificator UIDs - * @param out output file stream - * @param dynamic_color color for dynamic elements - * @param graph network graph - * @param node_src map of node sources - * @param node_dst map of node destinations + * @brief Write projections and populations to DOT file. + * + * @param out output file stream. + * @param unique_nodes set of unique node UIDs. + * @param unique_edges set of unique edge UIDs. + * @param modificators set of modificator UIDs. + * @param dynamic_color color for dynamic elements. + * @param graph network graph. + * @param node_src map of node sources. + * @param node_dst map of node destinations. */ void write_projections_and_populations_to_dot( - std::set const& unique_nodes, std::set const& unique_edges, - std::set& modificators, std::ofstream& out, std::string& dynamic_color, - knp::framework::NetworkGraph const& graph, std::map& node_src, - std::map& node_dst); + std::ofstream& out, std::set const& unique_nodes, std::set const& unique_edges, + std::set& modificators, std::string& dynamic_color, knp::framework::NetworkGraph const& graph, + std::map& node_src, std::map& node_dst); diff --git a/knp/base-framework/impl/visualizer/generate_files.cpp b/knp/base-framework/impl/visualizer/generate_files.cpp index 6ad0809b..2bdb8700 100644 --- a/knp/base-framework/impl/visualizer/generate_files.cpp +++ b/knp/base-framework/impl/visualizer/generate_files.cpp @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ * limitations under the License. */ + #include "generate_files.h" #include @@ -34,17 +35,17 @@ #include "dot_helpers.h" #include "graph_helpers.h" - extern "C" { #include } + /** - * @brief Create DOT file for static network + * @brief Create DOT file for static network. * - * @param file_name output file name - * @param graph network graph + * @param file_name output file name. + * @param graph network graph. */ void create_dot_file_for_static_network(std::string& file_name, knp::framework::NetworkGraph& graph) { @@ -89,12 +90,13 @@ void create_dot_file_for_static_network(std::string& file_name, knp::framework:: } } + /** - * @brief Create DOT file for bus messages + * @brief Create DOT file for bus messages. * - * @param file_name output DOT file name - * @param graph network graph - * @param backend shared pointer to backend + * @param file_name output DOT file name. + * @param graph network graph. + * @param backend shared pointer to backend. */ void create_dot_file_for_bus( const std::string& file_name, const knp::framework::NetworkGraph& graph, @@ -104,21 +106,21 @@ void create_dot_file_for_bus( std::ofstream out(file_name); - write_bus_messeges_to_dot(spike_messages, synaptic_messages, graph, out); // spike/synaptic impact messeges + write_bus_messages_to_dot(out, spike_messages, synaptic_messages, graph); // spike/synaptic impact messeges SPDLOG_INFO("The DOT file is saved in: {}", std::filesystem::absolute(file_name).string()); } + /** - * @brief Create DOT file for dynamic network + * @brief Create DOT file for dynamic network. * - * @param file_name output file name - * @param graph network graph - * @param backend shared pointer to backend + * @param path output path file name. + * @param graph network graph. + * @param backend shared pointer to backend. */ void create_dot_file_for_dynamic_network( - const std::string& file_name, const knp::framework::NetworkGraph& graph, - std::shared_ptr& backend) + const std::string& path, const knp::framework::NetworkGraph& graph, std::shared_ptr& backend) { const auto subs = backend->get_message_endpoint().get_endpoint_subscriptions(); @@ -179,12 +181,10 @@ void create_dot_file_for_dynamic_network( sub_variant); } - std::set modificators; - - // modificator exist in senders SPIKE - // modificator doesn't exist in receivers SYNAPTIC_IMPACT + // Modificator exist in senders SPIKE. + // Modificator doesn't exist in receivers SYNAPTIC_IMPACT. for (const auto& uid : unique_senders_spike) { if (unique_receivers_impact.find(uid) == unique_receivers_impact.end()) @@ -196,23 +196,24 @@ void create_dot_file_for_dynamic_network( //////////////////////////////////////// - std::ofstream out(file_name); + std::ofstream out(path); std::string dynamic_color = "yellow"; write_projections_and_populations_to_dot( - unique_nodes, unique_edges, modificators, out, dynamic_color, graph, node_src, + out, unique_nodes, unique_edges, modificators, dynamic_color, graph, node_src, node_dst); // projections/populations from bus - SPDLOG_INFO("The DOT file is saved in: {}", std::filesystem::absolute(file_name).string()); + SPDLOG_INFO("The DOT file is saved in: {}", std::filesystem::absolute(path).string()); } -///////////////// dot to png ///////////////////// + +// Dot to png. /** - * @brief Convert DOT file to PNG image + * @brief Convert DOT file to PNG image. * - * @param path_to_dot_file path to input DOT file - * @param path_to_png_file path to output PNG file + * @param path_to_dot_file path to input DOT file. + * @param path_to_png_file path to output PNG file. * - * @return boolean indicating success + * @return boolean indicating success. */ bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& path_to_png_file) { @@ -275,11 +276,12 @@ bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& } } + /** - * @brief Create PNG file from DOT file + * @brief Create PNG file from DOT file. * - * @param dot_file path to DOT file - * @param png_file path to PNG file + * @param dot_file path to DOT file. + * @param png_file path to PNG file. */ void create_png_file(const std::string& dot_file, const std::string& png_file) { diff --git a/knp/base-framework/impl/visualizer/generate_files.h b/knp/base-framework/impl/visualizer/generate_files.h index b76d9431..80cf5372 100644 --- a/knp/base-framework/impl/visualizer/generate_files.h +++ b/knp/base-framework/impl/visualizer/generate_files.h @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,49 +35,52 @@ extern "C" /** - * @brief Create DOT file for static network + * @brief Create DOT file for static network. * - * @param file_name output file name - * @param graph network graph + * @param file_name output file name. + * @param graph network graph. */ void create_dot_file_for_static_network(std::string& file_name, knp::framework::NetworkGraph& graph); + /** - * @brief Create DOT file for bus messages + * @brief Create DOT file for bus messages. * - * @param file_name output DOT file name - * @param graph network graph - * @param backend shared pointer to backend + * @param file_name output DOT file name. + * @param graph network graph. + * @param backend shared pointer to backend. */ void create_dot_file_for_bus( const std::string& file_name, const knp::framework::NetworkGraph& graph, std::shared_ptr& backend); + /** - * @brief Create DOT file for dynamic network + * @brief Create DOT file for dynamic network. * - * @param file_name output file name - * @param graph network graph - * @param backend shared pointer to backend + * @param path output path file name. + * @param graph network graph. + * @param backend shared pointer to backend. */ void create_dot_file_for_dynamic_network( - const std::string& file_name, const knp::framework::NetworkGraph& graph, - std::shared_ptr& backend); + const std::string& path, const knp::framework::NetworkGraph& graph, std::shared_ptr& backend); + /** - * @brief Convert DOT file to PNG image + * @brief Convert DOT file to PNG image. * - * @param path_to_dot_file path to input DOT file - * @param path_to_png_file path to output PNG file + * @param path_to_dot_file path to input DOT file. + * @param path_to_png_file path to output PNG file. * - * @return boolean indicating success + * @return boolean indicating success. */ bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& path_to_png_file); + /** - * @brief Create PNG file from DOT file + * @brief Create PNG file from DOT file. * - * @param dot_file path to DOT file - * @param png_file path to PNG file + * @param dot_file path to DOT file. + * @param png_file path to PNG file. */ void create_png_file(const std::string& dot_file, const std::string& png_file); diff --git a/knp/base-framework/impl/visualizer/graph_helpers.cpp b/knp/base-framework/impl/visualizer/graph_helpers.cpp index d0c26328..317ccf05 100644 --- a/knp/base-framework/impl/visualizer/graph_helpers.cpp +++ b/knp/base-framework/impl/visualizer/graph_helpers.cpp @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,11 +35,11 @@ /** - * @brief Get bus messages from backend subscriptions + * @brief Get bus messages from backend subscriptions. * - * @param backend shared pointer to backend + * @param backend shared pointer to backend. * - * @return tuple of spike messages and synaptic messages + * @return tuple of spike messages and synaptic messages. */ using SpikeMessages = std::vector>; using SynapticMessages = std::vector>; @@ -94,12 +94,12 @@ BusMessages get_bus(std::shared_ptr& backend) /** - * @brief Get node name by UID from network graph nodes + * @brief Get node name by UID from network graph nodes. * - * @param node_uid UID of the node - * @param nodes vector of network graph nodes + * @param node_uid UID of the node. + * @param nodes vector of network graph nodes. * - * @return string node name + * @return string node name. */ std::string get_node_name(knp::core::UID node_uid, std::vector const& nodes) { @@ -115,12 +115,12 @@ std::string get_node_name(knp::core::UID node_uid, std::vector const& nodes) @@ -137,12 +137,12 @@ knp::framework::NetworkGraph::Node get_graph_node_by_uid( } /** - * @brief Get projection size by UID from network graph edges + * @brief Get projection size by UID from network graph edges. * - * @param edges vector of network graph edges - * @param edge_uid UID of the edge + * @param edges vector of network graph edges. + * @param edge_uid UID of the edge. * - * @return string size of the edge + * @return string size of the edge. */ std::string get_proj_size(std::vector const& edges, const knp::core::UID& edge_uid) { diff --git a/knp/base-framework/impl/visualizer/graph_helpers.h b/knp/base-framework/impl/visualizer/graph_helpers.h index fd82c855..3019373b 100644 --- a/knp/base-framework/impl/visualizer/graph_helpers.h +++ b/knp/base-framework/impl/visualizer/graph_helpers.h @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,11 +36,11 @@ extern "C" } /** - * @brief Get bus messages from backend subscriptions + * @brief Get bus messages from backend subscriptions. * - * @param backend shared pointer to backend + * @param backend shared pointer to backend. * - * @return tuple of spike messages and synaptic messages + * @return tuple of spike messages and synaptic messages. */ std::tuple< std::vector>, std::vector>> @@ -49,9 +49,9 @@ get_bus(std::shared_ptr& backend); /** * @brief Get entity name for any network object. If there is no name it's constructed from UID. * - * @param pop population object + * @param pop population object. * - * @return string name of the population + * @return string name of the population. */ template std::string get_population_name(const Entity& pop) @@ -78,32 +78,32 @@ std::string get_population_name(const Entity& pop) } /** - * @brief Get node name by UID from network graph nodes + * @brief Get node name by UID from network graph nodes. * - * @param node_uid UID of the node - * @param nodes vector of network graph nodes + * @param node_uid UID of the node. + * @param nodes vector of network graph nodes. * - * @return string node name + * @return string node name. */ std::string get_node_name(knp::core::UID node_uid, std::vector const& nodes); /** - * @brief Get graph node by UID from network graph nodes + * @brief Get graph node by UID from network graph nodes. * - * @param node_uid UID of the node - * @param nodes vector of network graph nodes + * @param node_uid UID of the node. + * @param nodes vector of network graph nodes. * - * @return NetworkGraph::Node object + * @return NetworkGraph::Node object. */ knp::framework::NetworkGraph::Node get_graph_node_by_uid( knp::core::UID node_uid, std::vector const& nodes); /** - * @brief Get projection size by UID from network graph edges + * @brief Get projection size by UID from network graph edges. * - * @param edges vector of network graph edges - * @param edge_uid UID of the edge + * @param edges vector of network graph edges. + * @param edge_uid UID of the edge. * - * @return string size of the edge + * @return string size of the edge. */ std::string get_proj_size(std::vector const& edges, const knp::core::UID& edge_uid); From 21e796b0e8e76e51145d166ed485f5abe590fc50 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Fri, 7 Aug 2026 17:25:23 +0300 Subject: [PATCH 09/12] small fixes pt. 2.1: #219 --- knp/base-framework/impl/visualizer/dot_helpers.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/knp/base-framework/impl/visualizer/dot_helpers.cpp b/knp/base-framework/impl/visualizer/dot_helpers.cpp index 4e3f93b2..698b4e10 100644 --- a/knp/base-framework/impl/visualizer/dot_helpers.cpp +++ b/knp/base-framework/impl/visualizer/dot_helpers.cpp @@ -112,6 +112,7 @@ void write_edge_without_src( << short_name << "\\nsize:" << size << "\", color=\"" << color << "\"];\n"; } + /** * @brief Write edge without destination to DOT file. * From 41f73c2aa0c1d6429fb1bb13029bcc9bfd8e5eaf Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Fri, 7 Aug 2026 19:31:03 +0300 Subject: [PATCH 10/12] small fixes pt. 3: #219 --- .../impl/visualizer/dot_helpers.h | 1 + .../impl/visualizer/generate_files.cpp | 21 ++- .../impl/visualizer/graph_helpers.cpp | 30 ++-- .../impl/visualizer/graph_helpers.h | 6 +- .../framework/visualizer/visualize_network.h | 10 ++ .../framework/visualize_network_test.cpp | 153 +++++++++--------- 6 files changed, 114 insertions(+), 107 deletions(-) diff --git a/knp/base-framework/impl/visualizer/dot_helpers.h b/knp/base-framework/impl/visualizer/dot_helpers.h index b25fabf1..cf006ce0 100644 --- a/knp/base-framework/impl/visualizer/dot_helpers.h +++ b/knp/base-framework/impl/visualizer/dot_helpers.h @@ -138,6 +138,7 @@ void write_edge_without_dst( std::ofstream& out, knp::core::UID const& src, int const& i, knp::core::UID const& edge_uid, std::string const& size, std::string const& color = "lightgray"); + /** * @brief Convert DOT file to PNG image. * diff --git a/knp/base-framework/impl/visualizer/generate_files.cpp b/knp/base-framework/impl/visualizer/generate_files.cpp index 2bdb8700..23fa7dc4 100644 --- a/knp/base-framework/impl/visualizer/generate_files.cpp +++ b/knp/base-framework/impl/visualizer/generate_files.cpp @@ -124,8 +124,8 @@ void create_dot_file_for_dynamic_network( { const auto subs = backend->get_message_endpoint().get_endpoint_subscriptions(); - constexpr size_t SPIKE_IDX = 0; // must be 0 - constexpr size_t SYNAPTIC_IMPACT_IDX = 1; // must be 1 + constexpr size_t spike_idx = 0; // Must be 0 (Serial number of the type). + constexpr size_t synaptic_impact_idx = 1; // Must be 1 (Serial number of the type). std::map node_src; std::map node_dst; @@ -136,11 +136,9 @@ void create_dot_file_for_dynamic_network( std::set unique_nodes; std::set unique_edges; - std::vector> spike_messages; // [sender, receiver] std::vector> synaptic_messages; // [sender, receiver] - // take from bus nessosary maps for (const auto& [key, sub_variant] : subs) { @@ -151,9 +149,9 @@ void create_dot_file_for_dynamic_network( { const auto& senders = sub.get_senders(); if (senders.empty()) return; - - if (type_idx == SPIKE_IDX) - { // receiver: Projection, sender: *Population (* - or modificator) // 0 --> + if (spike_idx == type_idx) + { + // Receiver: Projection, sender: *Population (* - or modificator) (0 -->). for (const auto& sender : senders) { spike_messages.push_back({sender, receiver_uid}); @@ -165,8 +163,9 @@ void create_dot_file_for_dynamic_network( unique_edges.insert(receiver_uid); } } - else if (type_idx == SYNAPTIC_IMPACT_IDX) - { // receiver: *Population, sender: Projection (* - or modificator) // --> 0 + else if (type_idx == synaptic_impact_idx) + { + // Receiver: *Population, sender: Projection. (* - or modificator) (--> 0). for (const auto& sender : senders) { synaptic_messages.push_back({sender, receiver_uid}); @@ -194,8 +193,6 @@ void create_dot_file_for_dynamic_network( } } - //////////////////////////////////////// - std::ofstream out(path); std::string dynamic_color = "yellow"; write_projections_and_populations_to_dot( @@ -264,7 +261,7 @@ bool convert_dot_to_png(const std::string& path_to_dot_file, const std::string& // 6. Check the result and print the path to the PNG file - if (result == 0) + if (0 == result) { SPDLOG_INFO("The PNG file is saved in: {}", std::filesystem::absolute(path_to_png_file).string()); return true; diff --git a/knp/base-framework/impl/visualizer/graph_helpers.cpp b/knp/base-framework/impl/visualizer/graph_helpers.cpp index 317ccf05..9dfb75b8 100644 --- a/knp/base-framework/impl/visualizer/graph_helpers.cpp +++ b/knp/base-framework/impl/visualizer/graph_helpers.cpp @@ -45,12 +45,13 @@ using SpikeMessages = std::vector>; using SynapticMessages = std::vector>; using BusMessages = std::tuple; + BusMessages get_bus(std::shared_ptr& backend) { const auto subs = backend->get_message_endpoint().get_endpoint_subscriptions(); - constexpr size_t SPIKE_IDX = 0; // must be 0 - constexpr size_t SYNAPTIC_IMPACT_IDX = 1; // must be 1 + constexpr size_t spike_idx = 0; // Must be 0 (Serial number of the type). + constexpr size_t synaptic_impact_idx = 1; // Must be 1 (Serial number of the type). SpikeMessages spike_messages; // [sender, receiver] SynapticMessages synaptic_messages; // [sender, receiver] @@ -66,21 +67,17 @@ BusMessages get_bus(std::shared_ptr& backend) [&](const auto& sub) { const auto& senders = sub.get_senders(); - if (senders.empty()) + if (senders.empty()) return; + if (type_idx == spike_idx) { - SPDLOG_WARN("Empty senders list for subscription"); - return; - } - - - if (type_idx == SPIKE_IDX) - { // receiver: Projection, sender: Population // 0 --> + // Receiver: Projection, sender: *Population (* - or modificator) (0 -->). std::transform( senders.begin(), senders.end(), std::back_inserter(spike_messages), [&receiver_uid](const auto& sender) { return std::make_pair(sender, receiver_uid); }); } - else if (type_idx == SYNAPTIC_IMPACT_IDX) - { // receiver: Population, sender: Projection // --> 0 + else if (type_idx == synaptic_impact_idx) + { + // receiver: Population, sender: Projection (* - or modificator) (--> 0). std::transform( senders.begin(), senders.end(), std::back_inserter(synaptic_messages), [&receiver_uid](const auto& sender) { return std::make_pair(sender, receiver_uid); }); @@ -107,13 +104,11 @@ std::string get_node_name(knp::core::UID node_uid, std::vectorname_; - } + if (node_it != nodes.end()) node_name = node_it->name_; return node_name; } + /** * @brief Get graph node by UID from network graph nodes. * @@ -132,10 +127,11 @@ knp::framework::NetworkGraph::Node get_graph_node_by_uid( return *node_it; } - SPDLOG_WARN("Node with UID {} not found in graph", std::string(node_uid)); + SPDLOG_INFO("Node with UID {} not found in graph", std::string(node_uid)); return knp::framework::NetworkGraph::Node{}; } + /** * @brief Get projection size by UID from network graph edges. * diff --git a/knp/base-framework/impl/visualizer/graph_helpers.h b/knp/base-framework/impl/visualizer/graph_helpers.h index 3019373b..9ccbc93c 100644 --- a/knp/base-framework/impl/visualizer/graph_helpers.h +++ b/knp/base-framework/impl/visualizer/graph_helpers.h @@ -35,6 +35,7 @@ extern "C" #include } + /** * @brief Get bus messages from backend subscriptions. * @@ -46,6 +47,7 @@ std::tuple< std::vector>, std::vector>> get_bus(std::shared_ptr& backend); + /** * @brief Get entity name for any network object. If there is no name it's constructed from UID. * @@ -69,7 +71,7 @@ std::string get_population_name(const Entity& pop) } catch (std::bad_any_cast& exc) { - SPDLOG_WARN("Wrong name tag type."); + SPDLOG_ERROR("Wrong name tag type."); name = std::string{uid}.substr(0, uid_part_size); } } @@ -77,6 +79,7 @@ std::string get_population_name(const Entity& pop) return name; } + /** * @brief Get node name by UID from network graph nodes. * @@ -98,6 +101,7 @@ std::string get_node_name(knp::core::UID node_uid, std::vector const& nodes); + /** * @brief Get projection size by UID from network graph edges. * diff --git a/knp/base-framework/include/knp/framework/visualizer/visualize_network.h b/knp/base-framework/include/knp/framework/visualizer/visualize_network.h index 70ea57dc..20ed5f97 100644 --- a/knp/base-framework/include/knp/framework/visualizer/visualize_network.h +++ b/knp/base-framework/include/knp/framework/visualizer/visualize_network.h @@ -27,12 +27,14 @@ #include #include + /** * @brief Framework namespace. */ namespace knp::framework { + /** * @brief Network description structure used for visualization. * @@ -182,6 +184,7 @@ struct KNP_DECLSPEC NetworkGraph */ KNP_DECLSPEC void print_network_description(const NetworkGraph& graph); + /** * @brief Visualize static network. * @@ -194,6 +197,7 @@ KNP_DECLSPEC void print_network_description(const NetworkGraph& graph); */ KNP_DECLSPEC void visualize_network(const knp::framework::Network& network); + /** * @brief Visualize static network by model. * @@ -206,6 +210,7 @@ KNP_DECLSPEC void visualize_network(const knp::framework::Network& network); */ KNP_DECLSPEC void visualize_network(const knp::framework::Model& model); + /** * @brief Visualize dynamic network using backend. * @@ -221,6 +226,7 @@ KNP_DECLSPEC void visualize_network(const knp::framework::Model& model); KNP_DECLSPEC void visualize_network( const knp::framework::Network& network, std::shared_ptr& backend); + /** * @brief Visualize dynamic network using backend. * @@ -235,9 +241,11 @@ KNP_DECLSPEC void visualize_network( */ KNP_DECLSPEC void visualize_network(const knp::framework::Model& model, std::shared_ptr& backend); + //TODO: // KNP_DECLSPEC void visualize_bus(std::shared_ptr& backend); + /** * @brief Visualize the bus messages. * @@ -250,6 +258,7 @@ KNP_DECLSPEC void visualize_network(const knp::framework::Model& model, std::sha */ KNP_DECLSPEC void visualize_bus(const knp::framework::Network& network, std::shared_ptr& backend); + /** * @brief Visualize the bus messages. * @@ -262,6 +271,7 @@ KNP_DECLSPEC void visualize_bus(const knp::framework::Network& network, std::sha */ KNP_DECLSPEC void visualize_bus(const knp::framework::Model& model, std::shared_ptr& backend); + /** * @brief Configuration structure for visualization paths. */ diff --git a/knp/tests/framework/visualize_network_test.cpp b/knp/tests/framework/visualize_network_test.cpp index d9a68d51..ca2b3d8e 100644 --- a/knp/tests/framework/visualize_network_test.cpp +++ b/knp/tests/framework/visualize_network_test.cpp @@ -31,23 +31,25 @@ #include #include + using BLIFATParams = knp::neuron_traits::neuron_parameters; using DeltaProjection = knp::core::Projection; using Synapse = DeltaProjection::Synapse; -// Helper function to create a simple network for testing + +// Helper function to create a simple network for testing. knp::framework::Network create_test_network() { knp::framework::Network network; - // Create two populations + // Create two populations. auto pop1 = knp::framework::population::creators::make_random(5); auto pop2 = knp::framework::population::creators::make_random(3); network.add_population(pop1); network.add_population(pop2); - // Connect them + // Connect them. auto proj_uid = network.connect_populations< knp::synapse_traits::DeltaSynapse, knp::neuron_traits::BLIFATNeuron, knp::neuron_traits::BLIFATNeuron>( pop1, pop2); @@ -55,19 +57,21 @@ knp::framework::Network create_test_network() return network; } -// Helper function to create a simple model for testing + +// Helper function to create a simple model for testing. knp::framework::Model create_test_model() { knp::framework::Network network = create_test_network(); return knp::framework::Model(std::move(network)); } + TEST(VisualizeNetworkSuite, NetworkGraphConstruction) { knp::framework::Network network = create_test_network(); knp::framework::NetworkGraph graph(network); - // Check that we have correct number of nodes and edges + // Check that we have correct number of nodes and edges. ASSERT_EQ(graph.nodes_.size(), 2); ASSERT_EQ(graph.edges_.size(), 1); @@ -81,6 +85,7 @@ TEST(VisualizeNetworkSuite, NetworkGraphConstruction) ASSERT_EQ(graph.edges_[0].index_to_, 1); } + TEST(VisualizeNetworkSuite, NetworkGraphNodeAndEdgeAccess) { knp::framework::Network network = create_test_network(); @@ -130,45 +135,43 @@ TEST(VisualizeNetworkSuite, StaticVisualizationFilesGeneration) std::string dot_file = file_info.name_dot_file; std::string png_file = file_info.name_png_file; - // Test that we can create dot file - try - { - // Verify that the graph has expected structure - ASSERT_EQ(graph.nodes_.size(), 2); - ASSERT_EQ(graph.edges_.size(), 1); - - // Verify that the paths are correctly formed - ASSERT_FALSE(dot_file.empty()); - ASSERT_FALSE(png_file.empty()); - - // Verify that directories exist - std::filesystem::path dot_dir = std::filesystem::path(dot_file).parent_path(); - std::filesystem::path png_dir = std::filesystem::path(png_file).parent_path(); - - ASSERT_TRUE(std::filesystem::exists(dot_dir)); - ASSERT_TRUE(std::filesystem::exists(png_dir)); - ASSERT_TRUE(std::filesystem::is_directory(dot_dir)); - ASSERT_TRUE(std::filesystem::is_directory(png_dir)); - - // Verify that the files don't exist yet (they will be created during visualization) - ASSERT_FALSE(std::filesystem::exists(dot_file)); - ASSERT_FALSE(std::filesystem::exists(png_file)); - - // Test actual visualization function to ensure files are created - knp::framework::visualize_network(network); - - // Verify that files were created - ASSERT_TRUE(std::filesystem::exists(dot_file)); - ASSERT_TRUE(std::filesystem::exists(png_file)); - - // Verify that files are not empty - ASSERT_GT(std::filesystem::file_size(dot_file), 0); - ASSERT_GT(std::filesystem::file_size(png_file), 0); - } - catch (const std::exception& e) - { - FAIL() << "Failed to generate visualization files: " << e.what(); - } + // Test that we can create dot file. + EXPECT_NO_THROW( + [&]() + { + // Verify that the graph has expected structure. + ASSERT_EQ(graph.nodes_.size(), 2); + ASSERT_EQ(graph.edges_.size(), 1); + + // Verify that the paths are correctly formed. + ASSERT_FALSE(dot_file.empty()); + ASSERT_FALSE(png_file.empty()); + + // Verify that directories exist + std::filesystem::path dot_dir = std::filesystem::path(dot_file).parent_path(); + std::filesystem::path png_dir = std::filesystem::path(png_file).parent_path(); + + ASSERT_TRUE(std::filesystem::exists(dot_dir)); + ASSERT_TRUE(std::filesystem::exists(png_dir)); + ASSERT_TRUE(std::filesystem::is_directory(dot_dir)); + ASSERT_TRUE(std::filesystem::is_directory(png_dir)); + + // Verify that the files don't exist yet (they will be created during visualization). + ASSERT_FALSE(std::filesystem::exists(dot_file)); + ASSERT_FALSE(std::filesystem::exists(png_file)); + + // Test actual visualization function to ensure files are created. + knp::framework::visualize_network(network); + + // Verify that files were created + ASSERT_TRUE(std::filesystem::exists(dot_file)); + ASSERT_TRUE(std::filesystem::exists(png_file)); + + // Verify that files are not empty. + ASSERT_GT(std::filesystem::file_size(dot_file), 0); + ASSERT_GT(std::filesystem::file_size(png_file), 0); + }()) + << "Failed to generate visualization files"; } @@ -177,37 +180,34 @@ TEST(VisualizeNetworkSuite, DynamicVisualizationWithBackend) knp::framework::Network network = create_test_network(); knp::framework::Model model(std::move(network)); - // Create backend using the correct API from backend_loader_test.cpp + // Create backend using the correct API from backend_loader_test.cpp. knp::framework::BackendLoader backend_loader; auto backend = backend_loader.load(knp::testing::get_backend_path()); - // Test that we can call visualization functions without crashing - try - { - // This should not crash - knp::framework::visualize_network(model.get_network(), backend); - knp::framework::visualize_bus(model.get_network(), backend); - } - catch (const std::exception& e) - { - FAIL() << "Dynamic visualization failed with exception: " << e.what(); - } + // Test that we can call visualization functions without crashing. + EXPECT_NO_THROW( + [&]() + { + // This should not crash + knp::framework::visualize_network(model.get_network(), backend); + knp::framework::visualize_bus(model.get_network(), backend); + }()) + << "Dynamic visualization failed with exception"; } + TEST(VisualizeNetworkSuite, ModelVisualization) { knp::framework::Model model = create_test_model(); - // Test that we can visualize model directly - try - { - knp::framework::visualize_network(model); - // knp::framework::visualize_network(model, nullptr); // Test with null backend - } - catch (const std::exception& e) - { - FAIL() << "Model visualization failed with exception: " << e.what(); - } + // Test that we can visualize model directly. + EXPECT_NO_THROW( + [&]() + { + knp::framework::visualize_network(model); + // knp::framework::visualize_network(model, nullptr); // Test with null backend. + }()) + << "Model visualization failed with exception"; } @@ -217,26 +217,25 @@ TEST(VisualizeNetworkSuite, NetworkGraphPrintFunctions) knp::framework::NetworkGraph graph(network); // Test that print functions don't crash - try - { - // These functions write to stdout, so we just ensure they don't throw - knp::framework::print_network_description(graph); - } - catch (const std::exception& e) - { - FAIL() << "Print functions failed with exception: " << e.what(); - } + EXPECT_NO_THROW( + [&]() + { + // These functions write to stdout, so we just ensure they don't throw. + knp::framework::print_network_description(graph); + }()) + << "Print functions failed with exception"; } + TEST(VisualizeNetworkSuite, VisualizationPathConfiguration) { - // Test setting custom visualization paths + // Test setting custom visualization paths. std::string custom_path = "/tmp/custom_visualization/"; std::filesystem::create_directories(custom_path); knp::framework::set_saving_path(custom_path); - // Verify default path was changed + // Verify default path was changed. knp::framework::ConfigVisualizePathes config; ASSERT_EQ(config.dir, custom_path); } From 0ef6d8773e3ce6973183694cbd04491d61d9db6e Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Fri, 7 Aug 2026 19:37:13 +0300 Subject: [PATCH 11/12] small fixes pt. 3.1: #219 --- knp/tests/framework/visualize_network_test.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/knp/tests/framework/visualize_network_test.cpp b/knp/tests/framework/visualize_network_test.cpp index ca2b3d8e..6f6bb460 100644 --- a/knp/tests/framework/visualize_network_test.cpp +++ b/knp/tests/framework/visualize_network_test.cpp @@ -4,7 +4,7 @@ * @kaspersky_support Kirill L. * @date 05.08.2026 * @license Apache 2.0 - * @copyright © 2024 AO Kaspersky Lab + * @copyright © 2026 AO Kaspersky Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -188,7 +188,7 @@ TEST(VisualizeNetworkSuite, DynamicVisualizationWithBackend) EXPECT_NO_THROW( [&]() { - // This should not crash + // This should not crash. knp::framework::visualize_network(model.get_network(), backend); knp::framework::visualize_bus(model.get_network(), backend); }()) @@ -216,7 +216,7 @@ TEST(VisualizeNetworkSuite, NetworkGraphPrintFunctions) knp::framework::Network network = create_test_network(); knp::framework::NetworkGraph graph(network); - // Test that print functions don't crash + // Test that print functions don't crash. EXPECT_NO_THROW( [&]() { From 8f95fa0b59eb62da274f1b8f33f264077eb252d6 Mon Sep 17 00:00:00 2001 From: NeZero2020 Date: Wed, 12 Aug 2026 12:33:18 +0300 Subject: [PATCH 12/12] remove CV and unused code: #219 --- examples/mnist-client/CMakeLists.txt | 10 +--------- examples/mnist-client/process_data.cpp | 25 ------------------------- knp/base-framework/CMakeLists.txt | 3 --- 3 files changed, 1 insertion(+), 37 deletions(-) diff --git a/examples/mnist-client/CMakeLists.txt b/examples/mnist-client/CMakeLists.txt index eb57c5b0..c0b66208 100644 --- a/examples/mnist-client/CMakeLists.txt +++ b/examples/mnist-client/CMakeLists.txt @@ -19,14 +19,6 @@ cmake_minimum_required(VERSION 3.25) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) -find_package(OpenCV) -if (NOT OpenCV_FOUND) - message(WARNING "OpenCV was not found. Skipping MNIST example...") - return() -endif() - -include_directories(${OpenCV_INCLUDE_DIRS}) - if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.30") # Suppress Boost warning. cmake_policy(SET CMP0167 OLD) @@ -50,4 +42,4 @@ pkg_check_modules(GRAPHVIZ REQUIRED IMPORTED_TARGET libgvc libcgraph) target_include_directories(mnist-client PRIVATE ${GRAPHVIZ_INCLUDE_DIRS}) target_link_libraries(mnist-client PRIVATE PkgConfig::GRAPHVIZ ) -target_link_libraries(mnist-client PRIVATE KNP::BaseFramework::Core ${OpenCV_LIBS} ${Boost_LIBRARIES} ) +target_link_libraries(mnist-client PRIVATE KNP::BaseFramework::Core ${Boost_LIBRARIES} ) diff --git a/examples/mnist-client/process_data.cpp b/examples/mnist-client/process_data.cpp index a7cb5858..ccf1a9d5 100644 --- a/examples/mnist-client/process_data.cpp +++ b/examples/mnist-client/process_data.cpp @@ -27,9 +27,6 @@ #include #include -#include -#include - /** * @brief Filesystem namespace alias. @@ -37,28 +34,6 @@ namespace fs = std::filesystem; -/** - * @brief Visualize data instances. - * @param data vector of flattened images. - * @param size output image size. - */ -cv::Mat draw_data(const std::vector &data, const cv::Size &size) -{ - cv::Mat out_img(size, CV_8UC1); - for (int y = 0; y < size.height; ++y) - { - for (int x = 0; x < size.width; ++x) - { - out_img.at(cv::Point2i{x, y}) = data[size.width * y + x]; - } - } - cv::imshow("Data", out_img); - std::cout << cv::sum(out_img) << std::endl; - cv::waitKey(0); - return out_img; -} - - /** * @brief Turn data frames into spikes. * @param buf data frame. diff --git a/knp/base-framework/CMakeLists.txt b/knp/base-framework/CMakeLists.txt index 3d469bd1..0d408941 100644 --- a/knp/base-framework/CMakeLists.txt +++ b/knp/base-framework/CMakeLists.txt @@ -34,7 +34,6 @@ project(knp-base-framework VERSION "${KNP_VERSION}" LANGUAGES C CXX set(${PROJECT_NAME}_PUBLIC_INCLUDE_DIR "knp/framework") find_package(Boost ${KNP_BOOST_MIN_VERSION} COMPONENTS filesystem REQUIRED) -find_package(OpenCV REQUIRED) include(GNUInstallDirs) include(knp-functions) @@ -77,7 +76,6 @@ knp_add_library("${PROJECT_NAME}-core" impl/inference_evaluation/classification/processor.cpp impl/observer.cpp impl/logging.cpp - impl/visualizer/graph_physics.cpp impl/visualizer/visualize_network.cpp impl/visualizer/graph_helpers.cpp impl/visualizer/dot_helpers.cpp @@ -87,7 +85,6 @@ knp_add_library("${PROJECT_NAME}-core" LINK_PRIVATE spdlog::spdlog_header_only Boost::headers Boost::filesystem HighFive hdf5-static csv2 ${GRAPHVIZ_LIBRARIES} - ${OpenCV_LIBS} # Hack to build with CLang. ${ADD_LIBS} LINK_PUBLIC