qub_simulate.cpp.html |
mathcode2html
|
Source file: qub_simulate.cpp
|
Converted: Sun Aug 7 2016 at 13:45:52
|
This documentation file will
not reflect any later changes in the source file.
|
$$\phantom{******** If you see this on the webpage then the
browser could not locate *********}$$
$$\phantom{******** jsMath/easy/load.js or the variable root
is set wrong in this file *********}$$
$$\newcommand{\vector}[1]{\left[\begin{array}{c} #1 \end{array}\right]}$$
$$\newenvironment{matrix}{\left[\begin{array}{cccccccccc}} {\end{array}\right]}$$
$$\newcommand{\A}{{\cal A}}$$
$$\newcommand{\W}{{\cal W}}$$
/* Copyright 2008-2014 Research Foundation State University of New York */
/* This file is part of QUB Express. */
/* QUB Express 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 3 of the License, or */
/* (at your option) any later version. */
/* QUB Express 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, */
/* named LICENSE.txt, in the QUB Express program directory. If not, see */
/* <http://www.gnu.org/licenses/>. */
/*
*/
#ifdef _WIN32
#include <windows.h>
#include <time.h>
#include <process.h>
#define sleep Sleep
#define getpid _getpid
#else
#include <stdlib.h>
#include <unistd.h>
#define BOOL int
#define TRUE 1
#define FALSE 0
#endif
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
#include <math.h>
#include <string.h>
#include <vector>
#include <boost/tuple/tuple.hpp>
using boost::tuple;
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/exponential_distribution.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include "qub_simulate.h"
#include "max_ll_util.h"
#include "ublas_plus.h"
#include "ublas_matrixutil.h"
/*
Stimulus interval iterator
Each signal is either constant or sampled; constants have sample count = -1.
The iterator presents an array of values[nstimulus] at the current time,
and the count of samples until it changes.
*/
class stim_intervals
{
private:
int nsamp;
int nstim;
int *counts;
float **stims;
std::vector<int> stor_ixs;
int *ixs;
std::vector<int> stor_remain;
int *remain;
std::vector<double> stor_values;
public:
double *values;
int count;
stim_intervals(int nsample, int nstimulus, int *stim_counts, float **stimuli)
: nsamp( nsample ),
nstim( nstimulus ),
counts( stim_counts ),
stims( stimuli ),
stor_ixs( nstim ),
ixs( nstim ? &(stor_ixs[0]) : NULL ),
stor_remain( nstim ),
remain( NULL ), // stay NULL in case no stim // remain( nstim ? &(stor_remain[0]) : NULL ),
stor_values( nstim ),
values( nstim ? &(stor_values[0]) : NULL ),
count( 0 )
{
reset();
}
stim_intervals(const stim_intervals& other)
: nstim( other.nstim ),
counts( other.counts ),
stims( other.stims ),
stor_ixs( nstim ),
ixs( nstim ? &(stor_ixs[0]) : NULL ),
stor_remain( nstim ),
remain( (nstim && other.remain) ? &(stor_remain[0]) : NULL ),
stor_values( nstim ),
values( nstim ? &(stor_values[0]) : NULL ),
count( other.count )
{
if ( nstim ) {
memcpy(ixs, other.ixs, nstim*sizeof(int));
memcpy(remain, other.remain, nstim*sizeof(int));
memcpy(values, other.values, nstim*sizeof(int));
}
}
void operator=(const stim_intervals& other)
{
if (this == &other)
return;
bool resize = (nstim != other.nstim);
nstim = other.nstim;
counts = other.counts;
stims = other.stims;
if ( resize && nstim ) {
stor_ixs.resize( nstim );
ixs = &(stor_ixs[0]);
stor_remain.resize( nstim );
remain = other.remain ? &(stor_remain[0]) : NULL;
stor_values.resize( nstim );
values = &(stor_values[0]);
}
}
bool operator==(const stim_intervals& other) const {
if ( nstim != other.nstim || counts != other.counts )
return false;
for (int i=nstim-1; i>=0; --i)
if ( ixs[i] != other.ixs[i] )
return false;
return true;
}
bool operator!=(const stim_intervals& other) const { return ! (*this == other); }
void reset() {
if ( nstim == 0 )
return;
bool any_vary = false;
for (int i=0; i<nstim; ++i)
if ( counts[i] > 0 )
any_vary = true;
count = 1;
remain = &(stor_remain[0]);
for (int i=0; i<nstim; ++i) {
if ( counts[i] == 0 ) {
ixs[i] = 0;
values[i] = stims[i][0];
remain[i] = nsamp;
if ( any_vary )
++remain[i]; // the first ++(*this) will subtract one
} else {
ixs[i] = -1;
remain[i] = 1;
}
}
if ( any_vary )
++(*this);
else
count = nsamp;
}
operator bool () const {
return ( remain != NULL );
}
stim_intervals& operator++ ()
{
if ( remain == NULL )
return *this;
int min_remain = (1<<30);
for (int i=0; i<nstim; ++i) {
if ( remain[i] > count ) {
ixs[i] += count;
remain[i] -= count;
} else {
ixs[i] += count;
if ( ixs[i] < counts[i] ) {
float val = stims[i][ixs[i]];
values[i] = val;
int j = ixs[i]+1;
while ( (j<counts[i]) && (stims[i][j] == val) )
++j;
remain[i] = j - ixs[i];
}
else {
count = 0;
remain = NULL;
return *this;
}
}
if ( remain[i] < min_remain )
min_remain = remain[i];
}
count = min_remain;
//cerr << count << " x ";
//for (int i=1; i<nstim; ++i)
// cerr << values[i] << "\t";
//cerr << endl;
return *this;
}
};
/*
One Markov mechanism:
nstate:
st: state index of the most recently completed dwell
next_st: state it will go to after that (possibly next_st == st, if dur == maxdur)
dur: seconds spent in st (length of most recently completed dwell)
int next_event(Q, expo, uni, maxdur):
Q: matrix of rate constants
expo: boost::random::variate_generator, exponentially distributed
uni: boost::random::variate_generator, uniform between 0 and 1
maxdur: max seconds in a dwell; if it cuts an event short, st == next_st.
*/
class single_channel
{
public:
int nstate;
int st, next_st;
double dur;
single_channel(int _nstate, int start_state)
: nstate( _nstate ), next_st( start_state )
{}
template<class ExpoRand, class UniRand>
int next_event(matrix<double>& Q, ExpoRand& expo, UniRand& uni, double maxdur)
// returns exit_state
{
st = next_st;
double full_duration = expo() / (- Q(st, st));
if ( full_duration > maxdur ) {
dur = maxdur;
// next_st = st;
}
else {
dur = full_duration;
double staterand = uni() * -Q(st, st);
double tot = 0.0;
for (int i=0; i<nstate; ++i) {
if (i != st) {
tot += Q(st, i);
if ( tot > staterand ) {
next_st = i;
break;
}
}
}
}
return next_st;
}
};
// boost::random: exponential, normal, and uniform distributions
typedef boost::mt19937 base_generator_type;
typedef boost::uniform_int<> seed_distribution_type;
typedef boost::variate_generator<base_generator_type&, seed_distribution_type> seed_gen_type;
typedef boost::uniform_real<> uni_distribution_type;
typedef boost::variate_generator<base_generator_type&, uni_distribution_type> uni_gen_type;
typedef boost::exponential_distribution<> expo_distribution_type;
typedef boost::variate_generator<base_generator_type&, expo_distribution_type> expo_gen_type;
typedef boost::normal_distribution<> norm_distribution_type;
typedef boost::variate_generator<base_generator_type&, norm_distribution_type> norm_gen_type;
// library lifetime to avoid duplicate seeds
// func-lifetime generators with passed-in seed
// simulate{1}() use global generator (maybe better named)
base_generator_type g_generator( static_cast<boost::mt19937::result_type>( clock() + (int)getpid() ) );
// specialization for 1 channel
int qub_simulate_one(double sampling, int nstate, int *start_state, matrix<double>& P0,
matrix<double>& K0, matrix<double>& K1, matrix<int>& L, matrix<int>& V,
double base_amp, double base_std, double *exc_amp, double *exc_std,
QUBFAST_VAR_NOT_USED int *clazz, double *vRev,
int nsample, stim_intervals& stim, int v_signal, float *samples, int *states, int *counts,
int seed, StatusCallback pctCB, void *pctObj)
{
base_generator_type generator( static_cast<boost::mt19937::result_type>(seed) );
uni_gen_type uni_gen(generator, uni_distribution_type(0.0, 1.0));
expo_gen_type expo_gen(generator, expo_distribution_type(1.0));
norm_gen_type norm_gen(generator, norm_distribution_type(0.0, 1.0));
// pick random start state, if it's undefined (< 0)
if ( *start_state < 0 ) {
double rand = uni_gen(); // uniform in [0,1) since P0 sums to 1
double tot = P0(0,0);
*start_state = 0;
while ( (tot < rand) && (*start_state < nstate) )
tot += P0(0, ++(*start_state));
}
// set up mechanism and rate constants:
single_channel chan(nstate, *start_state);
matrix<double> Q(nstate, nstate);
int at = 0; // output sample index
int evt_at = 0; // output dwell index
double sam = 0.0; // fractional samples
double maxdur = 0.0; // seconds remaining before the stimulus changes (or segment ends)
for (; stim; ++stim) { // for each "count" samples of constant stimulus "values":
// update rate constants and seconds remaining
BuildQ(nstate, Q, K0, K1, L, V, stim.values);
maxdur = sampling * stim.count;
// fill with events
while ( maxdur > 0.0 ) {
chan.next_event(Q, expo_gen, uni_gen, maxdur);
maxdur -= chan.dur;
sam += chan.dur / sampling;
// chan.dur is not an exact multiple of sampling. Sometimes too short.
// See if it (cumulatively) crosses the threshold of one sample:
int evt_n = (int) (sam + 0.5);
if ( evt_n ) { // at least one sample long:
sam -= evt_n; // subtract from fractional accumulator
// write dwell
states[evt_at] = chan.st;
counts[evt_at] = evt_n;
++evt_at;
// write gaussian data
float amp, std;
if ( v_signal ) {
float dV = (float) (1e-3 * (stim.values[v_signal] - vRev[chan.st]));
amp = (float) (dV * exc_amp[chan.st]);
dV = (float) (dV * exc_std[chan.st]); // now it's scaled exc_std
std = (float) (sqrt(base_std*base_std + dV*dV));
} else {
amp = (float) exc_amp[chan.st];
std = (float) sqrt(base_std*base_std + exc_std[chan.st]*exc_std[chan.st]);
}
for (int i=0; i<evt_n; ++i)
samples[at+i] = (float) (base_amp + amp + norm_gen()*std);
at += evt_n;
// send progress and stop if requested
if ( pctCB(pctObj, at*1.0/nsample) ) {
//cerr << "break" << endl;
return evt_at;
}
}
}
}
*start_state = chan.next_st;
if ( at != nsample )
cerr << "wtf! at=" << at << ", nsample=" << nsample << endl;
return evt_at;
}
// specialization for n channels
int qub_simulate_multi(int nchannel, double sampling, int nstate, int *start_state, matrix<double>& P0,
matrix<double>& K0, matrix<double>& K1, matrix<int>& L, matrix<int>& V,
double base_amp, double base_std, double *exc_amp, double *exc_std,
QUBFAST_VAR_NOT_USED int *clazz, double *vRev,
int nsample, stim_intervals& stim, int v_signal, float *samples, int *states, int *counts,
int seed, QUBFAST_VAR_NOT_USED StatusCallback pctCB, QUBFAST_VAR_NOT_USED void *pctObj)
// states, counts for chans[0] only
{
base_generator_type generator( static_cast<boost::mt19937::result_type>(seed) );
uni_gen_type uni_gen(generator, uni_distribution_type(0.0, 1.0));
expo_gen_type expo_gen(generator, expo_distribution_type(1.0));
norm_gen_type norm_gen(generator, norm_distribution_type(0.0, 1.0));
// set up nchannel mechanisms and bookkeeping
std::vector<single_channel> chans;
std::vector<int> remain; // number of samples remaining in the current dwell
std::vector<double> sam; // fractional samples accumulated from prior dwells
for (int i=0; i<nchannel; ++i) {
// pick random start states, if they're undefined (< 0)
if ( start_state[i] < 0 ) {
double rand = uni_gen(); // uniform in [0,1) since P0 sums to 1
double tot = P0(0,0);
start_state[i] = 0;
while ( (tot < rand) && (start_state[i] < nstate) )
tot += P0(0, ++(start_state[i]));
}
chans.push_back(single_channel(nstate, start_state[i]));
remain.push_back(0); // need to get next event (no samples remaining)
sam.push_back(0.0); // no fractional samples accumulated yet
}
// one rate matrix for all mechanisms, and one set of output arrays. (states and counts are for mechanism 0 only.)
matrix<double> Q(nstate, nstate);
int at = 0;
int evt_at = 0;
double maxdur = 0.0;
double base_var = base_std*base_std; // you can add and subtract variance (not std)
std::vector<double> dV(nstate);
for (; stim; ++stim) { // for each "count" samples of constant stimulus "values":
// update rate constants and seconds remaining
BuildQ(nstate, Q, K0, K1, L, V, stim.values);
maxdur = sampling * stim.count;
if ( v_signal ) {
double V_here = stim.values[v_signal];
for (int i=0; i<nstate; ++i)
dV[i] = 1e-3 * (V_here - vRev[i]);
}
// fill with data points
for (int s=0; s<stim.count; ++s, maxdur-=sampling) {
// baseline and "instrumentation" noise, regardless of channels
double amp = base_amp;
double variance = base_var;
for (int i=0; i<nchannel; ++i) {
if ( ! remain[i] ) { // this channel's dwell is done:
while ( ! remain[i] ) { // accumulate events until it crosses the threshold of one sample:
chans[i].next_event(Q, expo_gen, uni_gen, maxdur);
sam[i] += chans[i].dur / sampling;
remain[i] = (int) (sam[i] + 0.5);
sam[i] -= remain[i];
}
if ( i == 0 ) { // record the event, mechanism 0 only.
states[evt_at] = chans[i].st;
counts[evt_at] = remain[i];
++evt_at;
}
}
// add amp and noise from this mechanism
if ( v_signal ) {
amp += dV[chans[i].st] * exc_amp[chans[i].st];
double scaled_std = dV[chans[i].st] * exc_std[chans[i].st];
variance += scaled_std * scaled_std;
} else {
amp += exc_amp[chans[i].st];
variance += exc_std[chans[i].st] * exc_std[chans[i].st];
}
--remain[i]; // count this sample
}
// output one gaussian datapoint. Should be equivalent to
// base_amp + base_std*norm_gen() + sum_i(exc_amp[ch[i].st] + exc_std[ch[i].st]*norm_gen())
// but faster.
samples[at++] = (float) (amp + sqrt(variance) * norm_gen());
}
}
// output states so you could call again to simulate the continuation
for (int i=0; i<nchannel; ++i)
start_state[i] = chans[i].next_st;
if ( at != nsample )
cerr << "wtf! at=" << at << ", nsample=" << nsample << endl;
return evt_at;
}
// returns event count
// P0 == NULL: P0 = QtoPe(Q)
// start_states[i] < 0: rand from P0
// start_states is updated to hold exit (next) states
// stim_counts[i] == 0: stimuli[i][0] holds constant value
extern "C" QUBFAST_API int qub_simulate(double sampling, int nchannel, int nstate, int *start_states, double *P0,
double *K0, double *K1, int *L, int *V,
double base_amp, double base_std, double *exc_amp, double *exc_std,
int nsample, int nstimulus, int *stim_counts, float **stimuli,
float *samples, int *states, int *counts,
StatusCallback pctCB, void *pctObj)
{
std::vector<int> clazz;
for (int i=0; i<nstate; ++i)
clazz.push_back(i);
seed_gen_type seed_gen(g_generator, seed_distribution_type(-(1<<31), 1<<31));
int seed = seed_gen();
return qub_simulate2(sampling, nchannel, nstate, start_states, P0, K0, K1, L, V,
base_amp, base_std, exc_amp, exc_std, &(clazz[0]), NULL,
nsample, nstimulus, stim_counts, stimuli, 0,
samples, states, counts, seed, pctCB, pctObj);
}
QUBFAST_API int qub_simulate2(double sampling, int nchannel, int nstate, int *start_states, double *P0,
double *K0, double *K1, int *L, int *V,
double base_amp, double base_std, double *exc_amp, double *exc_std,
int *clazz, double *vRev,
int nsample, int nstimulus, int *stim_counts, float **stimuli,
int v_signal,
float *samples, int *states, int *counts,
int seed, StatusCallback pctCB, void *pctObj)
/*
Simulates sampled and state-idealized HMM data and returns the number of dwells written into states and counts.
@param sampling: interval between samples, in seconds
@param nchannel: number of identical mechanisms' responses to sum together
@param nstate: number of model states (vertices)
@param start_states: array[nstate] of entry state indices, or -1 to pick fresh. On output, contains exit states.
@param P0: array[Nstate] of entry probabilities, or NULL
@param K0: row-major matrix, nstate x nstate; element (i,j) is the pre-exponential rate constant between states i and j
@param K1: row-major matrix, nstate x nstate; element (i,j) is the exponential rate constant between states i and j
@param L: row-major matrix, nstate x nstate; element (i,j) is the pre-exponential stimulus index, or 0
@param V: row-major matrix, nstate x nstate; element (i,j) is the exponential stimulus index, or 0
@param base_amp: constant baseline offset, amplitude of closed (class 0, black)
@param base_std: baseline std; instrument rather than channel noise
@param exc_amp: array[Nstate] of (state amp) - base_amp
@param exc_std: array[Nstate] of sqrt((state std)**2 - base_std**2)
@param clazz: class index of each state
@param vRev: reversal potential, in mV, of each state; or NULL if (! v_signal)
@param nsample: number of data points to return in samples
@param nstimulus: number of stimulus signals, plus signal 0 which is ignored (so at least 1)
@param stim_counts: number of datapoints in each signal; -1 if it's constant
@param stimuli: array[max(1,stim_counts[i])] of datapoints for each signal
@param v_signal: index of Voltage signal, if exc_amp is to be scaled by 1e-3*(Voltage-vRev)
@param samples: output array[nsample] for data points
@param states: output array[nsample] for idealized state index of each dwell
@param counts: output array[nsample] for number of samples in each dwell
@param seed: random seed; should be different each invocation
@param pctCB: callback pctCB(pctObj, complete), 0.0 <= complete <= 1.0; Return nonzero to stop the simulation.
@param pctObj: data pointer for the callback function, or NULL
*/
{
// copy into boost::matrix
matrix<double> _K0(nstate, nstate); matrix_from_array(_K0, K0, nstate, nstate);
matrix<double> _K1(nstate, nstate); matrix_from_array(_K1, K1, nstate, nstate);
matrix<int> _L (nstate, nstate); matrix_from_array(_L, L, nstate, nstate);
matrix<int> _V (nstate, nstate); matrix_from_array(_V, V, nstate, nstate);
// set up stimulus iterator
stim_intervals stim(nsample, nstimulus, stim_counts, stimuli);
// copy entry probabilities, or calculate equilibrium at initial stimulus
matrix<double> _P0(nstate, nstate);
if ( P0 ) {
matrix_from_array(_P0, P0, 1, nstate);
} else {
matrix<double> Q(nstate, nstate);
BuildQ(nstate, Q, _K0, _K1, _L, _V, stim.values);
QtoPe(Q, _P0);
}
if ( nchannel <= 1 ) {
return qub_simulate_one(sampling, nstate, start_states, _P0, _K0, _K1, _L, _V,
base_amp, base_std, exc_amp, exc_std, clazz, vRev,
nsample, stim, v_signal, samples, states, counts,
seed, pctCB, pctObj);
} else {
return qub_simulate_multi(nchannel, sampling, nstate, start_states, _P0, _K0, _K1, _L, _V,
base_amp, base_std, exc_amp, exc_std, clazz, vRev,
nsample, stim, v_signal, samples, states, counts,
seed, pctCB, pctObj);
}
}