Skip to content

Commit 03b6659

Browse files
committed
Added LIF neuron tests: KasperskyLab#188
1 parent ff71f52 commit 03b6659

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

knp/tests/framework/lif_test.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @file lif_test.cpp
3+
* @brief LIF neuron test.
4+
* @kaspersky_support David P.
5+
* @date 05.05.2026
6+
* @license Apache 2.0
7+
* @copyright © 2026 AO Kaspersky Lab
8+
*
9+
* Licensed under the Apache License, Version 2.0 (the "License");
10+
* you may not use this file except in compliance with the License.
11+
* You may obtain a copy of the License at
12+
*
13+
* http://www.apache.org/licenses/LICENSE-2.0
14+
*
15+
* Unless required by applicable law or agreed to in writing, software
16+
* distributed under the License is distributed on an "AS IS" BASIS,
17+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
* See the License for the specific language governing permissions and
19+
* limitations under the License.
20+
*/
21+
22+
23+
#include <knp/backends/cpu-single-threaded/backend.h>
24+
#include <knp/core/population.h>
25+
#include <knp/core/projection.h>
26+
#include <knp/framework/network.h>
27+
#include <knp/neuron-traits/lif.h>
28+
#include <knp/synapse-traits/delta.h>
29+
30+
#include <tests_common.h>
31+
32+
#include <algorithm>
33+
34+
35+
using Synapse = knp::synapse_traits::DeltaSynapse;
36+
37+
38+
namespace knp::testing
39+
{
40+
41+
class TestingBackendST : public knp::backends::single_threaded_cpu::SingleThreadedCPUBackend
42+
{
43+
public:
44+
TestingBackendST() = default;
45+
void _init() override { knp::backends::single_threaded_cpu::SingleThreadedCPUBackend::_init(); }
46+
};
47+
48+
using Population = knp::core::Population<knp::neuron_traits::LIFNeuron>;
49+
using Projection = knp::core::Projection<knp::synapse_traits::DeltaSynapse>;
50+
51+
} // namespace knp::testing
52+
53+
54+
struct NeuronLog
55+
{
56+
std::vector<float> potential_;
57+
std::vector<size_t> spikes_;
58+
};
59+
60+
61+
NeuronLog run_lif_neuron(
62+
const knp::neuron_traits::neuron_parameters<knp::neuron_traits::LIFNeuron> &neuron, size_t steps,
63+
const std::vector<float> &impacts = {}, const uint32_t num_neurons = 1, const uint32_t neuron_index = 0)
64+
{
65+
assert(num_neurons > neuron_index);
66+
const knp::core::UID pop_uid, in_uid, out_uid;
67+
knp::core::Population<knp::neuron_traits::LIFNeuron> population{
68+
pop_uid, [&neuron](size_t) { return neuron; }, num_neurons};
69+
knp::testing::TestingBackendST backend;
70+
backend.subscribe<knp::core::messaging::SynapticImpactMessage>(pop_uid, {in_uid});
71+
auto endpoint = backend.get_message_bus().create_endpoint();
72+
endpoint.subscribe<knp::core::messaging::SpikeMessage>(out_uid, {pop_uid});
73+
74+
backend.load_populations({population});
75+
backend._init();
76+
auto &pop = *backend.begin_populations();
77+
NeuronLog result;
78+
const auto &neuron_ref = std::get<knp::core::Population<knp::neuron_traits::LIFNeuron>>(pop)[neuron_index];
79+
for (size_t step = 0; step < steps; ++step)
80+
{
81+
const knp::core::messaging::MessageHeader header{in_uid, step};
82+
if (step < impacts.size())
83+
{
84+
knp::core::messaging::SynapticImpact impact{
85+
0, impacts[step], knp::synapse_traits::OutputType::EXCITATORY, 0, neuron_index};
86+
const knp::core::messaging::SynapticImpactMessage msg{
87+
header, knp::core::UID{false}, pop_uid, true, {impact}};
88+
endpoint.send_message(msg);
89+
}
90+
result.potential_.push_back(neuron_ref.potential_);
91+
backend._step();
92+
endpoint.receive_all_messages();
93+
auto out_msgs = endpoint.unload_messages<knp::core::messaging::SpikeMessage>(out_uid);
94+
if (!out_msgs.empty() && !out_msgs[0].neuron_indexes_.empty())
95+
{
96+
if (std::find(out_msgs[0].neuron_indexes_.begin(), out_msgs[0].neuron_indexes_.end(), neuron_index) !=
97+
out_msgs[0].neuron_indexes_.end())
98+
result.spikes_.push_back(step);
99+
}
100+
}
101+
return result;
102+
}
103+
104+
105+
TEST(LIFNeuron, NeuronPotentialLeakRev)
106+
{
107+
constexpr int starting_potential = 100;
108+
constexpr float leak_coefficient = 0.25F;
109+
constexpr size_t steps_amount = 10;
110+
auto base_neuron = knp::neuron_traits::neuron_parameters<knp::neuron_traits::LIFNeuron>{};
111+
base_neuron.activation_threshold_ = starting_potential + 1; // We don't want activations in this test.
112+
base_neuron.leak_coefficient_ = leak_coefficient;
113+
base_neuron.potential_ = starting_potential;
114+
115+
auto results = run_lif_neuron(base_neuron, steps_amount);
116+
117+
std::vector<float> expected_results;
118+
expected_results.reserve(steps_amount);
119+
expected_results.push_back(starting_potential);
120+
for (size_t power = 0; power < steps_amount - 1; ++power)
121+
{
122+
expected_results.push_back(expected_results[power] * leak_coefficient);
123+
}
124+
125+
ASSERT_EQ(results.potential_, expected_results);
126+
ASSERT_TRUE(results.spikes_.empty());
127+
}
128+
129+
130+
TEST(LIFNeuron, Threshold)
131+
{
132+
constexpr float leak_coefficient = 0.5F;
133+
constexpr float activation_threshold = 1.F;
134+
constexpr float potential = 3.F;
135+
constexpr size_t steps_amount = 3;
136+
auto base_neuron = knp::neuron_traits::neuron_parameters<knp::neuron_traits::LIFNeuron>{};
137+
base_neuron.leak_coefficient_ = leak_coefficient;
138+
base_neuron.activation_threshold_ = activation_threshold;
139+
base_neuron.potential_ = potential;
140+
auto result = run_lif_neuron(base_neuron, steps_amount);
141+
const std::vector<float> expected_potential{potential, 0, 0};
142+
const std::vector<size_t> expected_spikes{0};
143+
ASSERT_EQ(result.potential_, expected_potential);
144+
ASSERT_EQ(result.spikes_, expected_spikes);
145+
}
146+
147+
148+
TEST(LIFNeuron, ImpactsSpikes)
149+
{
150+
constexpr float leak_coefficient = 0.5F;
151+
constexpr float threshold = 6.F;
152+
constexpr size_t steps_amount = 6;
153+
auto base_neuron = knp::neuron_traits::neuron_parameters<knp::neuron_traits::LIFNeuron>{};
154+
base_neuron.leak_coefficient_ = leak_coefficient;
155+
base_neuron.activation_threshold_ = threshold;
156+
const std::vector<float> impacts{5.F, 5.F, 2.F, 3.F, 8.F};
157+
auto result = run_lif_neuron(base_neuron, steps_amount, impacts);
158+
const std::vector<float> expected_potential{0.F, 5.F, 0.F, 2.F, 4.F, 0.F};
159+
const std::vector<size_t> expected_spikes{1, 4};
160+
ASSERT_EQ(result.potential_, expected_potential);
161+
ASSERT_EQ(result.spikes_, expected_spikes);
162+
}
163+
164+
165+
TEST(LIFNeuron, RefractPeriod)
166+
{
167+
constexpr float leak_coefficient = 0.5F;
168+
constexpr float threshold = 6.F;
169+
constexpr size_t steps_amount = 7;
170+
auto base_neuron = knp::neuron_traits::neuron_parameters<knp::neuron_traits::LIFNeuron>{};
171+
base_neuron.leak_coefficient_ = leak_coefficient;
172+
base_neuron.activation_threshold_ = threshold;
173+
base_neuron.refract_period_ = 2;
174+
const std::vector<float> impacts{5.F, 5.F, 10.F, 10.F, 5.F, 5.F};
175+
auto result = run_lif_neuron(base_neuron, steps_amount, impacts);
176+
const std::vector<float> expected_potential{0.F, 5.F, 0.F, 0.F, 0.F, 5.F, 0.F};
177+
const std::vector<size_t> expected_spikes{1, 5};
178+
ASSERT_EQ(result.potential_, expected_potential);
179+
ASSERT_EQ(result.spikes_, expected_spikes);
180+
}

0 commit comments

Comments
 (0)