/*
* universal_data_logger_impl.h
*
* 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/>.
*
*/
#include "universal_data_logger.h"
#include "nest_time.h"
#include "network.h"
#include "node.h"
template <typename HostNode>
nest::UniversalDataLogger<HostNode>::UniversalDataLogger(HostNode& host)
: host_(host),
data_loggers_()
{}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::reset()
{
for ( DLiter_ it = data_loggers_.begin() ; it != data_loggers_.end() ; ++it )
it->reset();
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::init()
{
for ( DLiter_ it = data_loggers_.begin() ; it != data_loggers_.end() ; ++it )
it->init();
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::record_data(long_t step)
{
for ( DLiter_ it = data_loggers_.begin() ; it != data_loggers_.end() ; ++it )
it->record_data(host_, step);
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::handle(const DataLoggingRequest& dlr)
{
const rport rport = dlr.get_rport();
assert(rport >= 1);
assert(static_cast<size_t>(rport) <= data_loggers_.size());
data_loggers_[rport-1].handle(host_, dlr);
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::DataLogger_::reset()
{
data_.clear();
next_rec_step_ = -1; // flag as uninitialized
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::DataLogger_::init()
{
if ( num_vars_ < 1 )
return; // not recording anything
// Next recording step is in current slice or beyond, indicates that
// buffer is properly initialized.
if ( next_rec_step_ >= Node::network()->get_slice_origin().get_steps() )
return;
// If we get here, the buffer has either never been initialized or has
// been dormant during a period when the host node was frozen. We then
// (re-)initialize.
data_.clear();
// store recording time in steps
rec_int_steps_ = recording_interval_.get_steps();
// set next recording step to first multiple of rec_int_steps_
// beyond current time, shifted one to left, since rec_step marks
// left of update intervals, and we want time stamps at right end of
// update interval to be multiples of recording interval
next_rec_step_ =
( Node::network()->get_time().get_steps() / rec_int_steps_ + 1 ) * rec_int_steps_ - 1;
// number of data points per slice
const long_t recs_per_slice =
static_cast<long_t>(std::ceil(Node::network()->get_min_delay()
/ static_cast<double>(rec_int_steps_)));
data_.resize(2,
DataLoggingReply::Container(recs_per_slice,
DataLoggingReply::Item(num_vars_)
)
);
next_rec_.resize(2); // just for safety's sake
next_rec_[0] = next_rec_[1] = 0; // start at beginning of buffer
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::DataLogger_::record_data(const HostNode& host, long_t step)
{
if ( num_vars_ < 1 || step < next_rec_step_ )
return;
const size_t wt = Node::network()->write_toggle();
assert(wt < next_rec_.size());
assert(wt < data_.size());
/* The following assertion may fire if the multimeter connected to
this logger is frozen. In that case, handle() is not called and
next_rec_[wt] never reset. The assert() prevents error propagation.
This is not an exception, since I consider the chance of users
freezing multimeters very slim.
See #464 for details.
*/
assert(next_rec_[wt] < data_[wt].size());
DataLoggingReply::Item& dest = data_[wt][next_rec_[wt]];
// set time stamp: step is left end of update interval, so add 1
dest.timestamp = Time::step(step + 1);
// obtain data through access functions, calling via pointer-to-member
for ( size_t j = 0 ; j < num_vars_ ; ++j )
dest.data[j] = ((host).*(node_access_[j]))();
next_rec_step_ += rec_int_steps_;
/* We just increment. Construction ensures that we cannot overflow,
and read-out resets.
Overflow is possible if the multimeter is frozen, see #464.
In that case, the assertion above will trigger.
*/
++next_rec_[wt];
}
template <typename HostNode>
void nest::UniversalDataLogger<HostNode>::DataLogger_::handle(HostNode& host,
const DataLoggingRequest& request)
{
if ( num_vars_ < 1 )
return; // nothing to do
// The following assertions will fire if the user forgot to call init()
// on the data logger.
assert(next_rec_.size() == 2);
assert(data_.size() == 2);
// get read toggle and start and end of slice
const size_t rt = Node::network()->read_toggle();
assert(not data_[rt].empty());
// Check if we have valid data, i.e., data with time stamps within the
// past time slice. This may not be the case if the node has been frozen.
// In that case, we still reset the recording marker, to prepare for the next
// round.
if ( data_[rt][0].timestamp <= Node::network()->get_previous_slice_origin() )
{
next_rec_[rt] = 0;
return;
}
// If recording interval and min_delay are not commensurable,
// the last entry of data_ will not contain useful data for every
// other slice. We mark this by time stamp -infinity.
// Applying this mark here is less work than initializing all time stamps
// to -infinity after each call to this function.
if ( next_rec_[rt] < data_[rt].size() )
data_[rt][next_rec_[rt]].timestamp = Time::neg_inf();
// now create reply event and rigg it
DataLoggingReply reply(data_[rt]);
// "clear" data
next_rec_[rt] = 0;
reply.set_sender(host);
reply.set_sender_gid(host.get_gid());
reply.set_receiver(request.get_sender());
reply.set_port(request.get_port());
// send it off
host.network()->send_to_node(reply);
}