Skip to content
Snippets Groups Projects
Commit 80c72909 authored by Simon Oehrl's avatar Simon Oehrl
Browse files

Merge branch 'release/0.1.0'

parents 7d91e686 8ec076ad
Branches
Tags 0.1.0
No related merge requests found
Pipeline #163437 passed
Dockerfile
/build
/.vscode
__pycache__
\ No newline at end of file
stages:
- build
- run
# include:
# - project: 'vr-group/in-situ-pipeline/insite'
# ref: develop
# file: '/test-setup.yml'
docker:build:
stage: build
# variables:
# INSITE_NEST_MODULE_COMMIT: $CI_COMMIT_SHA
# api_test:
# extends: .api_test
deploy:develop:
tags:
- centos
- docker
- centos
only:
- develop
script:
- docker build -t insite-nest-module .
- docker build -t rwthvr/insite-nest-module:develop .
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- docker push rwthvr/insite-nest-module:develop
docker:run:
stage: run
deploy:master:
tags:
- centos
- docker
dependencies:
- docker:build
- centos
only:
- master
script:
- docker run --publish 8000:8000 insite-nest-module 20
- docker build -t rwthvr/insite-nest-module .
- VERSION=$(git tag --points-at $CI_COMMIT_SHA)
- >
[[ $VERSION =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]
- docker tag rwthvr/insite-nest-module rwthvr/insite-nest-module:$VERSION
- docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- docker push rwthvr/insite-nest-module
......@@ -105,6 +105,7 @@ project( ${MODULE_NAME} CXX )
# Add cpprestsdk
find_package(cpprestsdk REQUIRED)
find_package(libpqxx REQUIRED)
# Get the Python executable (for help generation).
execute_process(
......@@ -283,7 +284,7 @@ if ( BUILD_SHARED_LIBS )
install( TARGETS ${MODULE_NAME}_module
DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
target_link_libraries(${MODULE_NAME}_module PRIVATE cpprestsdk::cpprest)
target_link_libraries(${MODULE_NAME}_module PRIVATE cpprestsdk::cpprest libpqxx::pqxx_shared)
endif ()
# Build dynamic/static library for standard linking from NEST.
......@@ -298,6 +299,7 @@ set_target_properties( ${MODULE_NAME}_lib
COMPILE_FLAGS "${NEST_CXXFLAGS}"
LINK_FLAGS "${NEST_LIBS}"
OUTPUT_NAME ${MODULE_NAME} )
target_link_libraries(${MODULE_NAME}_lib PRIVATE cpprestsdk::cpprest libpqxx::pqxx_shared)
# Install library, header and sli init files.
install( TARGETS ${MODULE_NAME}_lib DESTINATION ${CMAKE_INSTALL_LIBDIR} )
......@@ -340,6 +342,11 @@ if ( ( NOT CMAKE_CROSSCOMPILING )
endif ()
configure_file(
${CMAKE_SOURCE_DIR}/examples/run_brunel_simulation.sh.in
${CMAKE_BINARY_DIR}/run_brunel_simulation.sh
@ONLY
)
message( "" )
message( "-------------------------------------------------------" )
......
......@@ -2,26 +2,44 @@ FROM ubuntu:latest
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
cmake g++ make ninja-build python3 python3-dev python3-pip python3-numpy python3-scipy python3-matplotlib \
git gsl-bin libgsl0-dev libltdl-dev libtool \
git gsl-bin libgsl0-dev libltdl-dev libtool netcat \
libboost-atomic-dev libboost-thread-dev libboost-system-dev libboost-date-time-dev libboost-regex-dev \
libboost-filesystem-dev libboost-random-dev libboost-chrono-dev libboost-serialization-dev \
libwebsocketpp-dev openssl libssl-dev ninja-build
libwebsocketpp-dev openssl libssl-dev ninja-build \
openmpi-bin libopenmpi-dev libpq-dev postgresql-server-dev-all
RUN pip3 install Cython
RUN git clone --single-branch --branch nestio https://github.com/jougs/nest-simulator.git nest
RUN git clone --single-branch --branch master https://github.com/nest/nest-simulator.git nest && \
cd nest && \
git checkout 5c0f41230dda9e4b99b8df89729ea43b340246ad && \
cd /
WORKDIR /nest-build
RUN cmake \
-G Ninja \
-Dwith-mpi=ON \
-DCMAKE_INSTALL_PREFIX=/nest-install \
-DCMAKE_BUILD_TYPE=Release \
/nest
RUN ninja && ninja install
RUN git clone --single-branch --branch v2.10.14 --recurse-submodules https://github.com/microsoft/cpprestsdk.git /cpprestsdk
WORKDIR /cpprestsdk-build
RUN cmake \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTS=OFF \
/cpprestsdk
RUN ninja && ninja install
RUN git clone --single-branch --branch 6.4.6 https://github.com/jtv/libpqxx.git /libpqxx
WORKDIR /libpqxx-build
RUN cmake \
-G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_TESTS=OFF \
/libpqxx
RUN ninja && ninja install
COPY . /insite
WORKDIR /insite-build
RUN cmake \
......@@ -30,4 +48,8 @@ RUN cmake \
-DCMAKE_BUILD_TYPE=Release \
/insite
RUN ninja && ninja install
ENTRYPOINT [ "/insite/examples/run.sh" ]
\ No newline at end of file
ENV PGPASSWORD=postgres
EXPOSE 8000
ENTRYPOINT "/insite-build/run_brunel_simulation.sh"
CMD 1000 2500 2
\ No newline at end of file
#include "data_storage.hpp"
#include <algorithm>
#include <cstring>
namespace insite {
......@@ -37,15 +38,34 @@ DataStorage::DataStorage(
// spikes_neurons_dataset_ =
// h5_file_->createDataSet("spikes/neurons", H5::PredType::NATIVE_UINT64,
// spikes_data_space, spikes_set_properties_);
buffered_spikes_.reserve(32 * 1024 * 1024 / sizeof(Spike)); // Reserve 32mb
SetCurrentSimulationTime(0.0);
}
void DataStorage::AddSpike(std::uint64_t simulation_step, std::uint64_t gid) {
void DataStorage::AddNeuronId(uint64_t neuron_id) {
std::unique_lock<std::mutex> lock(neuron_ids_mutex_);
const auto insert_position =
std::lower_bound(neuron_ids_.begin(), neuron_ids_.end(), neuron_id);
if (insert_position == neuron_ids_.end() || *insert_position != neuron_id) {
neuron_ids_.insert(insert_position, neuron_id);
}
}
std::vector<uint64_t> DataStorage::GetNeuronIds() {
std::unique_lock<std::mutex> lock(neuron_ids_mutex_);
std::vector<uint64_t> temp_neuron_ids = neuron_ids_;
return temp_neuron_ids;
}
void DataStorage::AddSpike(double simulation_time, std::uint64_t gid) {
std::unique_lock<std::mutex> lock(spike_mutex_);
constexpr auto spike_occured_before = [](const Spike& lhs, const Spike& rhs) {
return lhs.simulation_step < rhs.simulation_step;
return lhs.simulation_time < rhs.simulation_time;
};
const Spike spike {simulation_step, gid};
const auto equal_range = std::equal_range(buffered_spikes_.begin(), buffered_spikes_.end(), spike, spike_occured_before);
const Spike spike{simulation_time, gid};
const auto equal_range =
std::equal_range(buffered_spikes_.begin(), buffered_spikes_.end(), spike,
spike_occured_before);
for (auto i = equal_range.first; i != equal_range.second; ++i) {
if (i->gid == gid) {
return;
......@@ -91,4 +111,71 @@ void DataStorage::Flush() {
// buffered_spikes_.clear();
}
void DataStorage::AddMultimeterMeasurement(std::uint64_t device_id,
const std::string& attribute_name,
const double simulation_time,
const std::uint64_t gid,
const double value) {
std::unique_lock<std::mutex> lock(measurement_mutex_);
auto& measurement = buffered_measurements_[device_id][attribute_name];
auto& simulation_times = measurement.simulation_times;
auto& gids = measurement.gids;
auto& values = measurement.values;
auto time_iterator = std::lower_bound(
simulation_times.begin(), simulation_times.end(), simulation_time);
auto time_index = std::distance(simulation_times.begin(), time_iterator);
if (time_iterator == simulation_times.end() ||
*time_iterator != simulation_time) {
simulation_times.insert(time_iterator, simulation_time);
auto new_values =
std::vector<double>(simulation_times.size() * gids.size(), 0.0);
for (std::size_t t = 0; t < simulation_times.size(); ++t)
for (std::size_t g = 0; g < gids.size(); ++g)
if (t != time_index)
new_values[t * gids.size() + g] =
values[(t > time_index ? t - 1 : t) * gids.size() + g];
values = new_values;
}
auto gid_iterator = std::lower_bound(gids.begin(), gids.end(), gid);
auto gid_index = std::distance(gids.begin(), gid_iterator);
if (gid_iterator == gids.end() || *gid_iterator != gid) {
gids.insert(gid_iterator, gid);
auto new_values =
std::vector<double>(simulation_times.size() * gids.size(), 0.0);
for (std::size_t t = 0; t < simulation_times.size(); ++t)
for (std::size_t g = 0; g < gids.size(); ++g)
if (g != gid_index)
new_values[t * gids.size() + g] =
values[t * gids.size() + (g > gid_index ? g - 1 : g)];
values = new_values;
}
values[time_index * gids.size() + gid_index] = value;
}
std::unordered_map<std::uint64_t,
std::unordered_map<std::string, MultimeterMeasurements>>
DataStorage::GetMultimeterMeasurements() {
std::unique_lock<std::mutex> lock(measurement_mutex_);
auto measurements = buffered_measurements_;
return measurements;
}
void DataStorage::SetCurrentSimulationTime(double simulation_time) {
uint64_t simulation_time_int;
memcpy(&simulation_time_int, &simulation_time, sizeof(simulation_time_int));
current_simulation_time_ = simulation_time_int;
}
double DataStorage::GetCurrentSimulationTime() const {
const uint64_t simulation_time_int =current_simulation_time_;
double simulation_time;
memcpy(&simulation_time, &simulation_time_int, sizeof(simulation_time));
return simulation_time;
}
} // namespace insite
......@@ -3,35 +3,74 @@
#include <cstdint>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <atomic>
namespace insite {
struct Spike {
std::uint64_t simulation_step;
double simulation_time;
std::uint64_t gid;
};
static_assert(sizeof(Spike) == 2 * 8);
struct MultimeterInfo {
std::uint64_t device_id;
bool needs_update;
std::vector<std::string> double_attributes;
std::vector<std::string> long_attributes;
std::vector<std::uint64_t> gids;
};
struct MultimeterMeasurements {
std::vector<double> simulation_times;
std::vector<std::uint64_t> gids;
std::vector<double> values;
};
class DataStorage {
public:
DataStorage(const std::string& filename
/*, hsize_t time_chunk_size, hsize_t neuronids_chunk_size*/);
void AddSpike(std::uint64_t simulation_step, std::uint64_t gid);
void AddNeuronId(uint64_t neuron_ids);
std::vector<uint64_t> GetNeuronIds();
void AddSpike(double simulation_time, std::uint64_t gid);
std::vector<Spike> GetSpikes();
void Flush();
void AddMultimeterMeasurement(std::uint64_t device_id,
const std::string& attribute_name, const double simulation_time,
const std::uint64_t gid, const double value);
std::unordered_map<std::uint64_t, std::unordered_map<std::string,
MultimeterMeasurements>> GetMultimeterMeasurements();
void SetCurrentSimulationTime(double simulation_time);
double GetCurrentSimulationTime() const;
private:
// std::unique_ptr<H5::H5File> h5_file_;
std::mutex neuron_ids_mutex_;
std::vector<uint64_t> neuron_ids_;
// uint64_t flushed_spikes_count = 0;
std::vector<Spike> buffered_spikes_;
// H5::DataSet spikes_times_dataset_;
// H5::DataSet spikes_neurons_dataset_;
std::mutex spike_mutex_;
std::atomic_uint64_t current_simulation_time_;
// Device ID to attribute index to measurement map.
std::unordered_map<std::uint64_t, std::unordered_map<std::string,
MultimeterMeasurements>> buffered_measurements_;
std::mutex measurement_mutex_;
};
} // namespace insite
......
......@@ -44,12 +44,21 @@ References
# Import all necessary modules for simulation, analysis and plotting.
import nest
import nest.raster_plot
import signal
import math
import time
import sys
from numpy import exp
def sigint_handler(sig, frame):
print('Exiting...')
sys.exit(0)
signal.signal(signal.SIGINT, sigint_handler)
nest.Install("insitemodule")
nest.ResetKernel()
......@@ -81,11 +90,11 @@ epsilon = 0.1 # connection probability
# Definition of the number of neurons in the network and the number of neuron
# recorded from
order = 2500
order = int(sys.argv[2]) if len(
sys.argv) > 2 else 2500 # Should be square, otherwise the position grid becomes invalid
NE = 4 * order # number of excitatory neurons
NI = 1 * order # number of inhibitory neurons
N_neurons = NE + NI # number of neurons in total
N_rec = 4 # record from 50 neurons
###############################################################################
# Definition of connectivity parameter
......@@ -127,7 +136,7 @@ p_rate = 1000.0 * nu_ex * CE
# already processed simulation time as well as its percentage of the total
# simulation time.
nest.SetKernelStatus({"resolution": dt, "print_time": True,
nest.SetKernelStatus({"resolution": dt,
"overwrite_files": True})
print("Building network")
......@@ -148,21 +157,11 @@ nest.SetDefaults("poisson_generator", {"rate": p_rate})
# as the poisson generator and two spike detectors. The spike detectors will
# later be used to record excitatory and inhibitory spikes.
nodes_ex = nest.Create("iaf_psc_delta", NE)
nodes_in = nest.Create("iaf_psc_delta", NI)
nodes_ex = nest.Create("iaf_psc_delta", positions=nest.spatial.grid([int(math.sqrt(NE)), int(math.sqrt(NE))]))
nodes_in = nest.Create("iaf_psc_delta", positions=nest.spatial.grid([int(math.sqrt(NI)), int(math.sqrt(NI))]))
noise = nest.Create("poisson_generator")
espikes = nest.Create("spike_detector")
ispikes = nest.Create("spike_detector")
multimeter = nest.Create("multimeter")
multimeter2 = nest.Create("multimeter")
ascii_multimeter = nest.Create("multimeter")
###############################################################################
# Configuration of the spike detectors recording excitatory and inhibitory
# spikes using ``SetStatus``, which expects a list of node handles and a list
# of parameter dictionaries. Setting the property `record_to` to *"ascii"*
# ensures that the spikes will be recorded to a file, whose name starts with
# the string assigned to label.
nest.SetStatus(espikes, [{"label": "brunel-py-ex",
"record_to": "insite"}])
......@@ -170,11 +169,6 @@ nest.SetStatus(espikes, [{"label": "brunel-py-ex",
nest.SetStatus(ispikes, [{"label": "brunel-py-in",
"record_to": "insite"}])
nest.SetStatus(multimeter, {"record_from": ["V_m"], "record_to": "insite", })
nest.SetStatus(multimeter2, {"record_from": ["V_m"], "record_to": "insite", })
nest.SetStatus(ascii_multimeter, {"record_from": [
"V_m"], "record_to": "ascii", })
print("Connecting devices")
###############################################################################
......@@ -207,12 +201,8 @@ nest.Connect(noise, nodes_in, syn_spec="excitatory")
# Here the same shortcut for the specification of the synapse as defined
# above is used.
nest.Connect(nodes_ex[:N_rec], espikes, syn_spec="excitatory")
nest.Connect(nodes_in[:N_rec], ispikes, syn_spec="excitatory")
nest.Connect(multimeter, nodes_ex[:N_rec], syn_spec="excitatory")
nest.Connect(multimeter, nodes_in[:N_rec], syn_spec="excitatory")
nest.Connect(ascii_multimeter, nodes_ex[:N_rec], syn_spec="excitatory")
nest.Connect(ascii_multimeter, nodes_in[:N_rec], syn_spec="excitatory")
nest.Connect(nodes_ex, espikes, syn_spec="excitatory")
nest.Connect(nodes_in, ispikes, syn_spec="excitatory")
print("Connecting network")
......@@ -227,7 +217,8 @@ print("Excitatory connections")
# suffices to insert a string.
conn_params_ex = {'rule': 'fixed_indegree', 'indegree': CE}
nest.Connect(nodes_ex, nodes_ex + nodes_in, conn_params_ex, "excitatory")
nest.Connect(nodes_ex, nodes_ex, conn_params_ex, "excitatory")
nest.Connect(nodes_ex, nodes_in, conn_params_ex, "excitatory")
print("Inhibitory connections")
......@@ -238,7 +229,8 @@ print("Inhibitory connections")
# population defined above.
conn_params_in = {'rule': 'fixed_indegree', 'indegree': CI}
nest.Connect(nodes_in, nodes_ex + nodes_in, conn_params_in, "inhibitory")
nest.Connect(nodes_in, nodes_ex, conn_params_in, "inhibitory")
nest.Connect(nodes_in, nodes_in, conn_params_in, "inhibitory")
###############################################################################
# Storage of the time point after the buildup of the network in a variable.
......@@ -270,8 +262,8 @@ events_in = nest.GetStatus(ispikes, "n_events")[0]
# neurons recorded from and the simulation time. The multiplication by 1000.0
# converts the unit 1/ms to 1/s=Hz.
rate_ex = events_ex / simtime * 1000.0 / N_rec
rate_in = events_in / simtime * 1000.0 / N_rec
rate_ex = events_ex / simtime * 1000.0 / NE
rate_in = events_in / simtime * 1000.0 / NI
###############################################################################
# Reading out the number of connections established using the excitatory and
......@@ -300,3 +292,10 @@ print("Excitatory rate : %.2f Hz" % rate_ex)
print("Inhibitory rate : %.2f Hz" % rate_in)
print("Building time : %.2f s" % build_time)
print("Simulation time : %.2f s" % sim_time)
try:
input("Press Enter to quit...")
except EOFError:
print("Simulation finished, press ctrl+c to exit.")
while True:
time.sleep(1)
#!/bin/bash
source /nest-install/bin/nest_vars.sh
export LD_LIBRARY_PATH=$NEST_MODULE_PATH:/usr/local/lib/:$LD_LIBRARY_PATH
python3 /insite/examples/sim2.py $1
\ No newline at end of file
#!/bin/bash
source @NEST_INSTALL_PREFIX@/bin/nest_vars.sh
export LD_LIBRARY_PATH=$NEST_MODULE_PATH:/usr/local/lib/:$LD_LIBRARY_PATH
# python3 @CMAKE_SOURCE_DIR@/examples/brunel_simulation.py $1 $2 # Uncomment this to run normally
time mpirun -n $3 --mca btl_vader_single_copy_mechanism none --allow-run-as-root -x PYTHONPATH python3 @CMAKE_SOURCE_DIR@/examples/brunel_simulation.py $1 $2
\ No newline at end of file
# -*- coding: utf-8 -*-
#
# soundclick_example.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# NEST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NEST. If not, see <http://www.gnu.org/licenses/>.
'''
soundclick recording backend example
------------------------------------
Example PyNest script to demonstrate the soundclick recording backend.
Recorded spike events produce a clicking sound similar to that heard in
electrophysiological recordings. The recording backend makes use of the
SFML (Simple and Fast Multimedia Library) audio module.
This requires libsfml-dev to be installed. To create the illusion of a
realistic sound from an electrophysiological recording, the recording
backend slows down the simulation to biological real time.
'''
import nest
nest.Install("insitemodule")
population = nest.Create("izhikevich", 100)
multimeter = nest.Create("multimeter", 1)
ascii_multimeter = nest.Create("multimeter", 1)
nest.SetStatus(multimeter, {"record_from": ["V_m"], "record_to": "insite", })
nest.SetStatus(ascii_multimeter, {"record_from": [
"V_m"], "record_to": "ascii", })
nest.Connect(multimeter, population)
nest.Connect(ascii_multimeter, population)
# regular spiking
nest.SetStatus(population, {"a": 0.02,
"b": 0.2,
"c": -65.0,
"d": 8.0,
"U_m": 0.0,
"V_m": -75.0,
"I_e": 6.0})
nest.Simulate(4000)
nest.SetStatus(population, {"I_e": 0.0})
nest.Simulate(500)
# fast spiking
nest.SetStatus(population, {"a": 0.1,
"b": 0.2,
"c": -65.0,
"d": 2.0,
"U_m": 0.0,
"V_m": -75.0,
"I_e": 6.0})
nest.Simulate(4000)
nest.SetStatus(population, {"I_e": 0.0})
nest.Simulate(500)
# chattering
nest.SetStatus(population, {"a": 0.02,
"b": 0.2,
"c": -50.0,
"d": 2.0,
"U_m": 0.0,
"V_m": -75.0,
"I_e": 6.0})
nest.Simulate(4000)
#include "http_server.hpp"
#include <algorithm>
#include <iostream>
#include <regex>
#include <unordered_set>
#include "data_storage.hpp"
namespace insite {
HttpServer::HttpServer(web::http::uri address, DataStorage* storage)
: http_listener_{address}, storage_(storage) {
HttpServer::HttpServer(web::http::uri address, DataStorage* storage,
std::string database_uri)
: http_listener_{address},
storage_(storage),
database_uri_(database_uri) {
http_listener_.support([this](web::http::http_request request) {
if (request.method() == "GET" &&
request.relative_uri().path() == "/spikes") {
request.reply(GetSpikes(request));
} else if (request.method() == "GET" &&
request.relative_uri().path() == "/multimeter_measurement") {
request.reply(GetMultimeterMeasurement(request));
} else if (request.method() == "GET" &&
request.relative_uri().path() == "/current_simulation_time") {
request.reply(GetCurrentSimulationTime(request));
} else {
std::cerr << "Invalid request: " << request.to_string() << "\n";
request.reply(web::http::status_codes::NotFound);
......@@ -19,19 +34,170 @@ HttpServer::HttpServer(web::http::uri address, DataStorage* storage)
std::cout << "HTTP server is listening...\n";
}
web::http::http_response HttpServer::GetCurrentSimulationTime(
const web::http::http_request& request) {
web::http::http_response response(web::http::status_codes::OK);
web::json::value simulation_time = storage_->GetCurrentSimulationTime();
response.set_body(simulation_time);
return response;
}
web::http::http_response HttpServer::GetSpikes(
const web::http::http_request& request) {
const auto parameters = web::uri::split_query(request.request_uri().query());
web::http::http_response response(web::http::status_codes::OK);
const auto spikes = storage_->GetSpikes();
web::json::value gids = web::json::value::array(spikes.size());
web::json::value simulation_steps = web::json::value::array(spikes.size());
for (size_t i = 0; i < spikes.size(); ++i) {
gids[i] = spikes[i].gid;
simulation_steps[i] = spikes[i].simulation_step;
auto spikes = storage_->GetSpikes();
const auto from = parameters.find("from");
const auto to = parameters.find("to");
const auto population = parameters.find("population");
std::unordered_set<uint64_t> population_node_ids;
if (population != parameters.end()) {
pqxx::connection connection(database_uri_);
pqxx::work txn(connection);
const auto population_node_ids_result = txn.exec(
"SELECT id FROM nest_neuron WHERE nest_neuron.population_id = " +
population->second);
txn.commit();
population_node_ids.reserve(population_node_ids_result.size());
for (const auto& node_id : population_node_ids_result) {
population_node_ids.insert(node_id[0].as<uint64_t>());
}
spikes.erase(std::remove_if(spikes.begin(), spikes.end(), [&population_node_ids](const Spike& spike) {
return population_node_ids.count(spike.gid) == 0;
}), spikes.end());
}
const auto spike_happened_before = [](const Spike& spike,
double simulation_time) {
return spike.simulation_time < simulation_time;
};
auto spikes_begin = spikes.begin();
auto spikes_end = spikes.end();
if (from != parameters.end()) {
const auto from_number = std::stoll(from->second);
spikes_begin = std::lower_bound(spikes.begin(), spikes.end(), from_number,
spike_happened_before);
}
if (to != parameters.end()) {
const auto to_number = std::stoll(to->second);
spikes_end = std::lower_bound(spikes.begin(), spikes.end(), to_number,
spike_happened_before);
}
const auto element_count = spikes_end - spikes_begin;
web::json::value gids = web::json::value::array(element_count);
web::json::value simulation_times = web::json::value::array(element_count);
{
size_t index = 0;
for (auto spike = spikes_begin; spike != spikes_end; ++spike, ++index) {
gids[index] = spike->gid;
simulation_times[index] = spike->simulation_time;
}
}
response.set_body(web::json::value::object(
{{"simulation_steps", simulation_steps}, {"neuron_ids", gids}}));
{{"simulation_times", simulation_times}, {"gids", gids}}));
return response;
}
web::http::http_response HttpServer::GetMultimeterMeasurement(
const web::http::http_request& request) {
web::http::http_response response(web::http::status_codes::OK);
web::json::value body = web::json::value::object();
const auto parameters = web::uri::split_query(request.request_uri().query());
const auto parameter_multimeter_id = parameters.find("multimeter_id");
const auto parameter_attribute = parameters.find("attribute");
const auto parameter_from = parameters.find("from");
const auto parameter_to = parameters.find("to");
const auto parameter_gids = parameters.find("gids");
const auto parameter_offset = parameters.find("offset");
const auto parameter_limit = parameters.find("limit");
const auto multimeter_id = std::stoll(parameter_multimeter_id->second);
const auto attribute = parameter_attribute->second;
auto filter_gids = std::vector<std::uint64_t>();
if (parameter_gids != parameters.end()) {
std::regex regex{R"([\s,]+)"};
std::sregex_token_iterator it{parameter_gids->second.begin(),
parameter_gids->second.end(), regex, -1};
std::vector<std::string> filter_gid_strings{it, {}};
std::transform(filter_gid_strings.begin(), filter_gid_strings.end(),
std::back_inserter(filter_gids),
[](const std::string& str) { return std::stoll(str); });
}
std::cout << "Filter GID count: " << filter_gids.size() << "\n";
const auto measurements = storage_->GetMultimeterMeasurements();
if (measurements.find(multimeter_id) != measurements.end() &&
measurements.at(multimeter_id).find(attribute) !=
measurements.at(multimeter_id).end()) {
auto& measurement = measurements.at(multimeter_id).at(attribute);
auto& simulation_times = measurement.simulation_times;
auto& gids = measurement.gids;
auto& values = measurement.values;
auto simulation_times_begin =
parameter_from == parameters.end()
? simulation_times.begin()
: std::lower_bound(simulation_times.begin(), simulation_times.end(),
std::stoll(parameter_from->second));
auto simulation_times_end =
parameter_to == parameters.end()
? simulation_times.end()
: std::lower_bound(simulation_times.begin(), simulation_times.end(),
std::stoll(parameter_to->second));
if (parameter_offset != parameters.end())
simulation_times_begin += std::stoll(parameter_offset->second);
if (parameter_limit != parameters.end())
simulation_times_end =
simulation_times_begin + std::stoll(parameter_limit->second);
auto simulation_times_subset = std::vector<web::json::value>(
simulation_times_begin, simulation_times_end);
std::size_t simulation_start_index =
std::distance(simulation_times.begin(), simulation_times_begin);
std::size_t simulation_end_index =
std::distance(simulation_times.begin(), simulation_times_end);
auto gids_begin = filter_gids.empty() ? gids.begin() : filter_gids.begin();
auto gids_end = filter_gids.empty() ? gids.end() : filter_gids.end();
auto gids_subset = std::vector<web::json::value>(gids_begin, gids_end);
auto gid_indices = std::vector<std::size_t>();
if (!filter_gids.empty())
for (auto& filter_gid : filter_gids)
gid_indices.push_back(std::distance(
gids.begin(),
std::lower_bound(gids.begin(), gids.end(), filter_gid)));
else {
gid_indices.resize(gids.size());
std::iota(gid_indices.begin(), gid_indices.end(), 0);
}
auto values_subset = std::vector<web::json::value>(
simulation_times_subset.size() * gids_subset.size());
for (std::size_t t = 0, vt = simulation_start_index;
vt < simulation_end_index; ++t, ++vt)
for (std::size_t g = 0; g < gid_indices.size(); ++g)
values_subset[t * gids_subset.size() + g] =
values[vt * gids.size() + gid_indices[g]];
body["simulation_times"] = web::json::value::array(simulation_times_subset);
body["gids"] = web::json::value::array(gids_subset);
body["values"] = web::json::value::array(values_subset);
}
response.set_body(body);
return response;
}
} // namespace insite
......@@ -3,6 +3,7 @@
#include <cpprest/http_listener.h>
#include <string>
#include <pqxx/pqxx>
namespace insite {
......@@ -10,13 +11,19 @@ class DataStorage;
class HttpServer {
public:
HttpServer(web::http::uri address, DataStorage* storage);
HttpServer(web::http::uri address, DataStorage* storage, std::string database_uri);
private:
DataStorage* storage_;
web::http::experimental::listener::http_listener http_listener_;
DataStorage* storage_;
std::string database_uri_;
web::http::http_response GetCurrentSimulationTime(const web::http::http_request& request);
web::http::http_response GetSpikes(const web::http::http_request& request);
web::http::http_response GetMultimeterMeasurement(
const web::http::http_request& request);
};
} // namespace insite
......
#ifndef NEURON_INFO_HPP
#define NEURON_INFO_HPP
#include <vector>
// Includes from nest kernel:
#include <node_collection.h>
namespace insite {
struct NeuronInfo {
bool operator<(const NeuronInfo& that) const { return gid < that.gid; }
nest::index gid;
nest::NodeCollectionPTR gid_collection;
std::vector<double> position;
};
} // namespace insite
#endif
#include <stdexcept>
#include <string>
// Includes from libnestutil:
#include "compose.hpp"
// Includes from nestkernel:
#include "kernel_manager.h"
#include "recording_device.h"
#include "vp_manager_impl.h"
// Includes from topology:
#include "topology.h"
// Includes from sli:
#include "dictutils.h"
#include "recording_backend_insite.h"
namespace insite {
namespace {
std::string ReadDatabaseHost() {
std::ifstream host_file("database_host.txt");
if (host_file.is_open()) {
std::string database_host;
std::getline(host_file, database_host);
return database_host;
} else {
return "database";
}
}
} // namespace
RecordingBackendInsite::RecordingBackendInsite()
: data_storage_("tgest"),
http_server_("http://0.0.0.0:8000", &data_storage_) {}
database_connection_("postgresql://postgres@" + ReadDatabaseHost()),
http_server_("http://0.0.0.0:" + get_port_string(), &data_storage_, "postgresql://postgres@" + ReadDatabaseHost()) {
pqxx::work txn(database_connection_);
simulation_node_id_ = txn.exec1(
"INSERT INTO nest_simulation_node (address) "
"VALUES ('http://insite-nest-module:" +
get_port_string() +
"') "
"RETURNING id;")[0]
.as<int>();
std::cout << "Simulation node registered to database. Node ID: "
<< simulation_node_id_ << std::endl;
txn.commit();
}
RecordingBackendInsite::~RecordingBackendInsite() throw() {}
......@@ -29,6 +63,11 @@ void RecordingBackendInsite::finalize() {
void RecordingBackendInsite::enroll(const nest::RecordingDevice& device,
const DictionaryDatum& params) {
std::cout << "RecordingBackendInsite::enroll(" << device.get_label() << ")\n";
if (device.get_type() == nest::RecordingDevice::MULTIMETER) {
auto id = device.get_node_id();
multimeter_infos_.emplace(std::make_pair(id, MultimeterInfo{id, true}));
}
}
void RecordingBackendInsite::disenroll(const nest::RecordingDevice& device) {
......@@ -41,6 +80,37 @@ void RecordingBackendInsite::set_value_names(
const std::vector<Name>& double_value_names,
const std::vector<Name>& long_value_names) {
std::cout << "RecordingBackendInsite::set_value_names()\n";
if (device.get_type() == nest::RecordingDevice::MULTIMETER) {
auto& multimeter = multimeter_infos_.at(device.get_node_id());
std::stringstream multimeter_query;
multimeter_query << "INSERT INTO nest_multimeter (id, attributes) "
<< "VALUES (" << device.get_node_id() << ",\'{";
bool first = true;
for (auto& name : double_value_names) {
const auto& name_string = name.toString();
multimeter.double_attributes.push_back(name_string);
multimeter_query << (first ? "" : ",") << '\"' << name_string << "\"";
first = false;
}
for (auto& name : long_value_names) {
const auto& name_string = name.toString();
multimeter.long_attributes.push_back(name_string);
multimeter_query << (first ? "" : ",") << '\"' << name_string << "\"";
first = false;
}
multimeter_query << "}\') ON CONFLICT DO NOTHING;";
multimeter.needs_update = true;
pqxx::work txn(database_connection_);
txn.exec0(multimeter_query.str());
txn.commit();
}
}
void RecordingBackendInsite::prepare() {}
......@@ -57,15 +127,146 @@ void RecordingBackendInsite::post_run_hook() {
std::cout << "RecordingBackendInsite::post_run_hook()\n";
}
void RecordingBackendInsite::post_step_hook() {}
void RecordingBackendInsite::post_step_hook() {
// // Send simulation time
// {
// pqxx::work txn(database_connection_);
// txn.exec0(
// "UPDATE nest_simulation_node "
// "SET current_simulation_time = " +
// std::to_string(latest_simulation_time_) +
// ""
// "WHERE id = " +
// std::to_string(simulation_node_id_));
// txn.commit();
// }
data_storage_.SetCurrentSimulationTime(latest_simulation_time_);
if (new_neuron_infos_.size() > 0) {
std::stringstream neuron_query;
neuron_query << "INSERT INTO nest_neuron (id, simulation_node_id, "
"population_id, position) "
<< "VALUES ";
for (auto& neuron_info : new_neuron_infos_) {
const bool first = neuron_info.gid == new_neuron_infos_[0].gid;
if (!first) {
neuron_query << ",";
}
uint64_t population_id = 0;
for (const nest::NodeIDTriple& node_id_triple :
*neuron_info.gid_collection.get()) {
population_id ^= node_id_triple.node_id * 938831;
}
neuron_query << "(" << neuron_info.gid << "," << simulation_node_id_
<< "," << population_id % 0x800000;
const auto position_size = neuron_info.position.size();
if (position_size > 0) {
assert(position_size <= 3);
neuron_query << ",\'{";
for (size_t i = 0; i < position_size; ++i) {
if (i > 0) {
neuron_query << ",";
}
neuron_query << neuron_info.position[i];
}
neuron_query << "}\'";
} else {
neuron_query << ",NULL";
}
neuron_query << ")";
}
neuron_query << ";";
pqxx::work txn(database_connection_);
txn.exec0(neuron_query.str());
txn.commit();
neuron_infos_.insert(neuron_infos_.end(), new_neuron_infos_.begin(),
new_neuron_infos_.end());
std::sort(neuron_infos_.begin(), neuron_infos_.end());
new_neuron_infos_.clear();
}
// Send multimeter info
// for (auto& kvp : multimeter_infos_) {
// auto& multimeter = kvp.second;
// if (!multimeter.needs_update) continue;
// multimeter.needs_update = false;
// if (multimeter.gids.size() > 0) {
// std::stringstream neuron_multimeter_query;
// neuron_multimeter_query
// << "INSERT INTO nest_neuron_multimeter (neuron_id, multimeter_id) "
// << "VALUES ";
// for (const auto& neuron_id : multimeter.gids) {
// const bool first = neuron_id == multimeter.gids[0];
// neuron_multimeter_query << (first ? "" : ",") << "(" << neuron_id << ","
// << multimeter.device_id << ")";
// }
// neuron_multimeter_query << " ON CONFLICT DO NOTHING;";
// pqxx::work txn(database_connection_);
// txn.exec0(neuron_multimeter_query.str());
// txn.commit();
// }
// }
}
void RecordingBackendInsite::write(const nest::RecordingDevice& device,
const nest::Event& event,
const std::vector<double>& double_values,
const std::vector<long>& long_values) {
const auto sender_gid = event.get_sender_node_id();
const auto time_stamp = event.get_stamp().get_ms();
if (device.get_type() == nest::RecordingDevice::SPIKE_DETECTOR) {
data_storage_.AddSpike(event.get_stamp().get_steps(),
event.get_sender_gid());
data_storage_.AddSpike(time_stamp, sender_gid);
}
if (device.get_type() == nest::RecordingDevice::MULTIMETER) {
auto device_id = device.get_node_id();
auto& multimeter = multimeter_infos_.at(device_id);
auto& gids = multimeter.gids;
// If the measurement is from a GID we previously do not know, add.
if (!binary_search(gids.begin(), gids.end(), sender_gid)) {
gids.insert(std::lower_bound(gids.begin(), gids.end(), sender_gid),
sender_gid);
multimeter.needs_update = true;
}
for (std::size_t i = 0; i < double_values.size(); ++i)
data_storage_.AddMultimeterMeasurement(
device_id, multimeter.double_attributes[i], time_stamp, sender_gid,
double_values[i]);
for (std::size_t i = 0; i < long_values.size(); ++i)
data_storage_.AddMultimeterMeasurement(
device_id, multimeter.long_attributes[i], time_stamp, sender_gid,
double(long_values[i]));
}
latest_simulation_time_ = std::max(latest_simulation_time_, time_stamp);
NeuronInfo neuron_info;
neuron_info.gid = sender_gid;
if (!binary_search(neuron_infos_.begin(), neuron_infos_.end(), neuron_info) &&
!binary_search(new_neuron_infos_.begin(), new_neuron_infos_.end(),
neuron_info)) {
neuron_info.gid_collection = event.get_sender().get_nc();
const auto layer = nest::get_layer(neuron_info.gid_collection);
if (layer.get()) {
neuron_info.position = layer->get_position_vector(
neuron_info.gid_collection->find(sender_gid));
}
new_neuron_infos_.insert(
std::lower_bound(new_neuron_infos_.begin(), new_neuron_infos_.end(),
neuron_info),
neuron_info);
data_storage_.AddNeuronId(neuron_info.gid);
}
}
......@@ -92,4 +293,7 @@ void RecordingBackendInsite::get_device_status(
std::cout << "RecordingBackendInsite::get_device_status()\n";
}
std::string RecordingBackendInsite::get_port_string() const {
return std::to_string(8000 + nest::kernel().mpi_manager.get_rank());
}
} // namespace insite
#ifndef RECORDING_BACKEND_INSITE_H
#define RECORDING_BACKEND_INSITE_H
#include <unordered_map>
#include <cpprest/http_client.h>
#include <pqxx/pqxx>
#include "data_storage.hpp"
#include "http_server.hpp"
#include "recording_backend.h"
#include "nest_types.h"
#include "node_collection.h"
#include "neuron_info.hpp"
namespace insite {
......@@ -52,8 +59,16 @@ class RecordingBackendInsite : public nest::RecordingBackend {
DictionaryDatum& params) const override;
private:
std::string get_port_string() const;
DataStorage data_storage_;
pqxx::connection database_connection_;
HttpServer http_server_;
int simulation_node_id_;
std::vector<NeuronInfo> neuron_infos_;
std::vector<NeuronInfo> new_neuron_infos_;
std::unordered_map<nest::index, MultimeterInfo> multimeter_infos_;
double latest_simulation_time_ = 0;
};
} // namespace insite
......
import requests
def test_spikes(nest_simulation):
r = requests.get("http://localhost:8000/spikes")
spikes = r.json()
assert(len(spikes['gids']) == len(spikes['simulation_times']))
previous_time = 0
for time in spikes['simulation_times']:
assert(time >= previous_time)
previous_time = time
#!/bin/sh
TIMEOUT=15
QUIET=0
echoerr() {
if [ "$QUIET" -ne 1 ]; then printf "%s\n" "$*" 1>&2; fi
}
usage() {
exitcode="$1"
cat << USAGE >&2
Usage:
$cmdname host:port [-t timeout] [-- command args]
-q | --quiet Do not output any status messages
-t TIMEOUT | --timeout=timeout Timeout in seconds, zero for no timeout
-- COMMAND ARGS Execute command with args after the test finishes
USAGE
exit "$exitcode"
}
wait_for() {
for i in `seq $TIMEOUT` ; do
nc -z "$HOST" "$PORT" > /dev/null 2>&1
result=$?
if [ $result -eq 0 ] ; then
if [ $# -gt 0 ] ; then
exec "$@"
fi
exit 0
fi
sleep 1
done
echo "Operation timed out" >&2
exit 1
}
while [ $# -gt 0 ]
do
case "$1" in
*:* )
HOST=$(printf "%s\n" "$1"| cut -d : -f 1)
PORT=$(printf "%s\n" "$1"| cut -d : -f 2)
shift 1
;;
-q | --quiet)
QUIET=1
shift 1
;;
-t)
TIMEOUT="$2"
if [ "$TIMEOUT" = "" ]; then break; fi
shift 2
;;
--timeout=*)
TIMEOUT="${1#*=}"
shift 1
;;
--)
shift
break
;;
--help)
usage 0
;;
*)
echoerr "Unknown argument: $1"
usage 1
;;
esac
done
if [ "$HOST" = "" -o "$PORT" = "" ]; then
echoerr "Error: you need to provide a host and port to test."
usage 2
fi
wait_for "$@"
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment