/*
* multimeter.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/>.
*
*/
#ifndef MULTIMETER_H
#define MULTIMETER_H
#include <vector>
#include "recording_device.h"
#include "name.h"
#include "node.h"
#include "connection.h"
#include "dictutils.h"
#include "exceptions.h"
#include "sibling_container.h"
/*BeginDocumentation
Name: multimeter - Device to record analog data from neurons.
Synopsis: multimeter Create
Description:
A multimeter records a user-defined set of state variables from connected nodes to memory,
file or stdout.
The multimeter must be configured with the list of variables to record
from, otherwise it will not record anything. The /recordables property
of a neuron model shows which quantities can be recorded with a multimeter.
A single multimeter should only record from neurons of the same basic
type (e.g. /iaf_cond_alpha and any user-defined models derived from it
using CopyModel). If the defaults or status dictionary of a model neuron
does not contain a /recordables entry, it is not ready for use with
multimeter.
By default, multimeters record values once per ms. Set the parameter /interval to
change this. The recording interval cannot be smaller than the resolution.
Results are returned in the /events entry of the status dictionary. For
each recorded quantity, a vector of doubles is returned. The vector has the
same name as the /recordable. If /withtime is set, times are given in the
/times vector in /events.
Accumulator mode:
Multimeter can operate in accumulator mode. In this case, values for all recorded
variables are added across all recorded nodes (but kept separate in time). This can
be useful to record average membrane potential in a population.
To activate accumulator mode, either set /to_accumulator to true, or set
/record_to [ /accumulator ]. In accumulator mode, you cannot record to file,
to memory, to screen, with GID or with weight. You must activate accumulator mode
before simulating. Accumulator data is never written to file. You must extract it
from the device using GetStatus.
Note:
- The set of variables to record and the recording interval must be set
BEFORE the multimeter is connected to any node, and cannot be changed
afterwards.
- A multimeter cannot be frozen.
- If you record with multimeter in accumulator mode and some of the nodes
you record from and others are not, data will only be collected from the
unfrozen nodes. Most likely, this will lead to confusing results, so
you should not use multimeter with frozen nodes.
Parameters:
The following parameters can be set in the status dictionary:
interval double - Recording interval in ms
record_from array - Array containing the names of variables to record
from, obtained from the /recordables entry of the
model from which one wants to record
Examples:
SLI ] /iaf_cond_alpha Create /n Set
SLI ] n /recordables get ==
[/V_m /g_ex /g_in /t_ref_remaining]
SLI ] /multimeter Create /mm Set
SLI ] mm << /interval 0.5 /record_from [/V_m /g_ex /g_in] >> SetStatus
SLI ] mm n Connect
SLI ] 10 Simulate
SLI ] mm /events get info
--------------------------------------------------
Name Type Value
--------------------------------------------------
g_ex doublevectortype <doublevectortype>
g_in doublevectortype <doublevectortype>
senders intvectortype <intvectortype>
times doublevectortype <doublevectortype>
t_ref_remaining doublevectortype <doublevectortype>
V_m doublevectortype <doublevectortype>
--------------------------------------------------
Total number of entries: 6
Sends: DataLoggingRequest
FirstVersion: 2009-04-01
Author: Hans Ekkehard Plesser
SeeAlso: Device, RecordingDevice
*/
namespace nest {
class Network;
/**
* General analog data recorder.
*
* This class is based on RecordingDevice and adds common
* functionality for devices sampling analog values at
* given time intervals. The user specifies which data
* are to be sampled at what interval.
*
* Sampling works in the way the the sampled node must store
* the relevant data for the most recent completed time slice
* and that the sampling device then sends a Request for data
* with a given time stamp.
*
* Start and stop are handled as follows: the first recorded
* data is with time stamp origin+start+1, the last recorded one
* that with time stamp origin+stop. Only such times are recorded
* for which (T-(origin+start)) mod interval is zero.
*
* The recording interval defaults to 1ms; this entails that
* the simulation resolution cannot be set to larger values than
* 1ms unless the analog recording device interval is set to at
* least that resolution.
*
* @note If you want to pick up values at every time stamp,
* you must set the interval to the simulation resolution.
*
* @todo Testing and Code Review:
* - performance: currently about 5% slower than plain voltmeter;
* but check asserts in universal_data_logger.
*
* @ingroup Devices
* @see UniversalDataLogger
*/
class Multimeter : public Node {
public:
Multimeter();
Multimeter(const Multimeter&);
/**
* @note Multimeters never have proxies, since they must
* sample their targets through local communication.
*/
bool has_proxies() const { return false; }
/**
* Import sets of overloaded virtual functions.
* We need to explicitly include sets of overloaded
* virtual functions into the current scope.
* According to the SUN C++ FAQ, this is the correct
* way of doing things, although all other compilers
* happily live without.
*/
using Node::handle;
port check_connection(Connection&, port);
void handle(DataLoggingReply&);
void get_status(DictionaryDatum &) const;
void set_status(const DictionaryDatum &) ;
protected:
void init_state_(Node const&);
void init_buffers_();
void calibrate();
void finalize();
/**
* Collect and output membrane potential information.
* This function pages all its targets at all pertinent sample
* points for membrane potential information and then outputs
* that information. The sampled nodes must provide data from
* the previous time slice.
*/
void update(Time const&, const long_t, const long_t);
private:
/** Indicate if recording device is active.
* The argument is the time stamp of the data requested,
* device is active if start_ < T <= stop_ and (T-start_)%interval_ == 0.
*/
bool is_active(Time const & T) const;
/**
* "Print" one value to file or screen, depending on settings in RecordingDevice.
* @note The default implementation supports only EntryTypes which
* RecordingDevice::print_value() can handle. Otherwise, specialization is
* required.
*/
void print_value_(const std::vector<double_t>&);
/**
* Add recorded data to dictionary.
* @note By default, only implemented for EntryType double, must
* otherwise be specialized.
* @param /events dictionary to be placed in properties dictionary
*/
void add_data_(DictionaryDatum&) const;
// ------------------------------------------------------------
RecordingDevice device_;
// ------------------------------------------------------------
struct Buffers_;
struct Parameters_ {
Time interval_; //!< recording interval, in ms
std::vector<Name> record_from_; //!< which data to record
Parameters_();
Parameters_(const Parameters_&);
void get(DictionaryDatum&) const;
void set(const DictionaryDatum&, const Buffers_&);
};
// ------------------------------------------------------------
struct State_ {
/** Recorded data.
* First dimension: time
* Second dimension: recorded variables
* @note In normal mode, data is stored as follows:
* For each recorded node, all data points for one time slice are put
* after one another in the first dimension. Each entry is a vector
* containing one element per recorded quantity.
* In accumulating mode, only one data point is stored per time step and
* values are added across nodes.
*/
std::vector<std::vector<double_t> > data_; //!< Recorded data
};
// ------------------------------------------------------------
struct Buffers_ {
/** Does this multimeter have targets?
* Placed here since it is implementation detail.
* @todo Ideally, one should be able to ask ConnectionManager.
*/
Buffers_();
bool has_targets_;
};
// ------------------------------------------------------------
struct Variables_ {
/** Flag active till first DataLoggingReply during an update() call processed.
* This flag is set to true by update() before dispatching the DataLoggingRequest
* event and is reset to false by handle() as soon as the first DataLoggingReply
* has been handled. This is needed when the Multimeter is running in accumulator
* mode.
*/
bool new_request_;
/** Index to first S_.data_ entry for currently processed request.
*
* This variable is set by the first DataLoggingReply arriving after
* a DataLoggingRequest has been sent out. Subsequently arriving
* replies use it to find the correct entries for accumulating data.
*/
size_t current_request_data_start_;
};
// ------------------------------------------------------------
Parameters_ P_;
State_ S_;
Buffers_ B_;
Variables_ V_;
};
inline
void nest::Multimeter::get_status(DictionaryDatum &d) const
{
// get the data from the device
device_.get_status(d);
// we need to add analog data to the events dictionary
DictionaryDatum dd = getValue<DictionaryDatum>(d, names::events);
add_data_(dd);
// if we are the device on thread 0, also get the data from the
// siblings on other threads
if (get_thread() == 0)
{
const SiblingContainer* siblings = network()->get_thread_siblings(get_gid());
std::vector<Node*>::const_iterator sibling;
for (sibling = siblings->begin() + 1; sibling != siblings->end(); ++sibling)
(*sibling)->get_status(d);
}
P_.get(d);
}
inline
void nest::Multimeter::set_status(const DictionaryDatum &d)
{
// protect Multimeter from being frozen
bool freeze = false;
if ( updateValue<bool>(d, names::frozen, freeze) && freeze )
throw BadProperty("Multimeter cannot be frozen.");
Parameters_ ptmp = P_;
ptmp.set(d, B_);
// Set properties in device. As a side effect, this will clear data_,
// if /clear_events set in d
device_.set_status(d, S_.data_);
P_ = ptmp;
}
} // Namespace
#endif