our_knp/examples/knp/base-framework/impl/message_handlers.cpp
int 8f43927575 Initial import: KNP experiments (examples, viz, cats-dogs, reports)
examples/ is based on KasperskyLab/knp at 55470ed6 with local changes
to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:17:15 +03:00

166 lines
5.2 KiB
C++

/**
* @file message_handlers.cpp
* @brief Implementation of message handler functionality.
* @kaspersky_support A. Vartenkov
* @date 25.11.2024
* @license Apache 2.0
* @copyright © 2025 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 <knp/framework/message_handlers.h>
#include <unordered_set>
#include <utility>
/**
* @brief namespace for message modifier callables.
*/
namespace knp::framework::modifier
{
knp::core::messaging::SpikeData select_random_n(
knp::core::messaging::SpikeData &input, size_t n, std::mt19937 &random_engine)
{
if (input.size() <= n) return input;
std::uniform_int_distribution<size_t> distribution;
knp::core::messaging::SpikeData result;
for (size_t i = 0; i < n; ++i)
{
const size_t index = distribution(random_engine) % (input.size() - i);
result.push_back(input[index]);
std::swap(input[index], input[input.size() - 1 - i]);
}
return result;
}
knp::core::messaging::SpikeData KWtaRandomHandler::operator()(std::vector<knp::core::messaging::SpikeMessage> &messages)
{
if (messages.empty())
{
return {};
}
auto &msg = messages[0];
if (msg.neuron_indexes_.size() < num_winners_)
{
return msg.neuron_indexes_;
}
knp::core::messaging::SpikeData out_spikes = select_random_n(msg.neuron_indexes_, num_winners_, random_engine_);
return out_spikes;
}
knp::core::messaging::SpikeData GroupWtaRandomHandler::operator()(
const std::vector<knp::core::messaging::SpikeMessage> &messages)
{
if (messages.empty())
{
return {};
}
if (num_winners_ > group_borders_.size())
{
return messages[0].neuron_indexes_;
}
const auto &spikes = messages[0].neuron_indexes_;
if (spikes.empty())
{
return {};
}
std::vector<knp::core::messaging::SpikeData> spikes_per_group(group_borders_.size() + 1);
// Fill groups in.
for (const auto &spike : spikes)
{
const size_t group_index =
std::upper_bound(group_borders_.begin(), group_borders_.end(), spike) - group_borders_.begin();
spikes_per_group[group_index].push_back(spike);
}
// Sort groups by number of elements.
std::sort(
spikes_per_group.begin(), spikes_per_group.end(),
[](const auto &el1, const auto &el2) { return el1.size() > el2.size(); });
// Find all groups with the same number of spikes as the K-th one.
const auto &last_group = spikes_per_group[num_winners_ - 1];
auto group_interval = std::equal_range(
spikes_per_group.begin(), spikes_per_group.end(), last_group,
[](const auto &el1, const auto &el2) { return el1.size() > el2.size(); });
assert(group_interval.first - spikes_per_group.begin() + 1 >= 0);
assert(static_cast<size_t>(group_interval.first - spikes_per_group.begin() + 1) <= num_winners_);
// The approach could be more efficient, but I don't think it's necessary.
std::shuffle(group_interval.first, group_interval.second, random_engine_);
knp::core::messaging::SpikeData result;
for (size_t i = 0; i < num_winners_; ++i)
{
for (const auto &spike : spikes_per_group[i])
{
result.push_back(spike);
}
}
return result;
}
knp::core::messaging::SpikeData KWtaPerGroup::operator()(
const std::vector<knp::core::messaging::SpikeMessage> &messages)
{
if (messages.empty()) return {};
auto spikes = messages[0].neuron_indexes_;
if (spikes.empty()) return {};
std::vector<knp::core::messaging::SpikeData> spikes_per_group(group_borders_.size() + 1);
for (const auto &spike : spikes)
{
const size_t group_index =
std::upper_bound(group_borders_.begin(), group_borders_.end(), spike) - group_borders_.begin();
spikes_per_group[group_index].push_back(spike);
}
knp::core::messaging::SpikeData result;
result.reserve(group_borders_.size() * winners_per_group_);
for (auto &spike_group : spikes_per_group)
{
knp::core::messaging::SpikeData result_buf = select_random_n(spike_group, winners_per_group_, random_engine_);
result.insert(result.end(), result_buf.begin(), result_buf.end());
}
return result;
}
knp::core::messaging::SpikeData SpikeUnionHandler::operator()(
const std::vector<knp::core::messaging::SpikeMessage> &messages)
{
std::unordered_set<knp::core::messaging::SpikeIndex> spikes;
for (const auto &msg : messages)
{
spikes.insert(msg.neuron_indexes_.begin(), msg.neuron_indexes_.end());
}
knp::core::messaging::SpikeData result;
result.reserve(spikes.size());
std::copy(spikes.begin(), spikes.end(), std::back_inserter(result));
return result;
}
} // namespace knp::framework::modifier