qubx_idealize.cpp.html mathcode2html   
 Source file:   qubx_idealize.cpp
 Converted:   Sun Aug 7 2016 at 13:47:27
 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/>.                                        */

/*

See also:

Up: Index

*/

#ifdef _WIN32
  #include <windows.h>
#else
  #include <stdlib.h>
  #define BOOL int
  #define TRUE 1
  #define FALSE 0
#endif

#include <string.h>
#include <iostream>
#include <fstream>

#define _USE_MATH_DEFINES
#include <math.h>
#include <string.h>
#include <set>
#include <map>
#include <vector>
#include <algorithm>

#include "qubx_idealize.h"
#include "../qubfast/max_ll_util.h"
#include "../qubfast/ublas_matrixutil.h"
#include "../qubfast/callbk_reportstream.h"
#include "../qubfast/running_mean.h"
#include "../qubfast/sampled_gaussian.h"
#include "../qubfast/qub_constraints.h"
#include "../qubfast/nmsimplex.h"
#include "../qubfast/qubx_model_storage.h"
#include "../qubfast/qub_kalman.h"

using std::cout;
using std::cerr;
using std::endl;
using std::set;
using std::map;
using std::pair;
using std::copy;
using std::min;
using std::max;

class qubx_idlz_data;


#define QUBX_IDLZ_AA_MAXITER 25
#define QUBX_IDLZ_AMPS_MAXITER 120

// like the telephone keypad:
#define UNSET_VALUE 86738.82583

#ifdef _WIN32
  #include "../qubfast/gettimeofday.h"
#else
  #include <sys/time.h>
#endif
void print_timestamp(const char *lbl)
{
  struct timeval tm;
  gettimeofday(&tm, NULL);
  cerr << "--- " << tm.tv_sec << " : " << (tm.tv_usec / 1000) << "." << (tm.tv_usec % 1000) << " --- " << lbl << endl;
}


typedef map<std::vector<double>, int> stimmap;
void mplex_idl(int Nsignal, int *dwellCounts, int **classeses, float **durationses,
	       double **ampses, int *out_dwellCount, int *out_classes, float *out_durations,
	       stimmap& stimcls_of)
{
  if ( ! Nsignal )
    return;
  // the key to stimmap
  std::vector<double> amps(1+Nsignal);
  // output
  int &Nd = *out_dwellCount;
  Nd = 0;
  // per-signal stuff
  std::vector<int> finger(Nsignal, 0);
  std::vector<int> clss(Nsignal);
  std::vector<float> remain(Nsignal);
  for (int i=0; i<Nsignal; ++i) {
    if ( ! dwellCounts[i] )
      return;
    clss[i] = classeses[i][0];
    remain[i] = durationses[i][0];
    if ( clss[i] >= 0 )
      amps[i+1] = ampses[i][ clss[i] ];
    else
      amps[i+1] = 0.0; // gap at beginning of stimulus: should complain and fail; instead silently defaulting to zero
  }
  while ( 1 ) {
    // until any signal ends
    bool done = false;
    for (int i=0; i<Nsignal; ++i)
      if ( finger[i] >= dwellCounts[i] )
	done = true;
    if ( done )
      break;
    // find signal with shortest remaining
    float tm = remain[0];
    //int changing = 0;
    for (int i=0; i<Nsignal; ++i) {
      if ( remain[i] < tm ) {
	tm = remain[i];
	//changing = i;
      }
    }
    // look up stimclass
    stimmap::iterator sti = stimcls_of.find(amps);
    if ( sti == stimcls_of.end() ) {
      int sc = (int) stimcls_of.size();
      stimcls_of[amps] = sc;
    }
    // append the event
    out_classes[Nd] = stimcls_of[amps];
    out_durations[Nd] = tm;
    ++Nd;
    // shorten all signals remain, and move the finger for any that are 0
    for (int i=0; i<Nsignal; ++i) {
      remain[i] -= tm;
      if ( remain[i] <= 0.0 ) {
	if ( (++finger[i]) < dwellCounts[i] ) {
	  remain[i] = durationses[i][ finger[i] ];
	  clss[i] = classeses[i][ finger[i] ];
	  if ( clss[i] >= 0 )
	    amps[i+1] = ampses[i][ clss[i] ];
	    // else: gap: keep the last event's amp
	}
      }
    }
  }
}


class qubx_idlz_data_piece
{
public:
  int stimclass;
  float *data; // pointer into mac_data->data, or NULL
  int *pt_cls; // pointer into mac_data->data_out, or NULL if count == 0
  float *baseline; // ""
  int count;
  int first;
  
  qubx_idlz_data_piece(int stimclass_, float *data_, int *pt_cls_, float *baseline_, int count_, int first_)
    : stimclass(stimclass_), data(data_), pt_cls(pt_cls_), baseline(baseline_), count(count_), first(first_)
  {}
  int count_dwells()
  {
    if ( ! pt_cls ) return 1;
    int n = 1;
    int c = pt_cls[0];
    for (int i=1; i<count; ++i)
      if ( pt_cls[i] != c ) {
	c = pt_cls[i];
	++n;
      }
    return n;
  }

  int get_dwells(int *ff, int *ll, int *cc)
  {
    if ( ! pt_cls ) {
      *ff = first;
      *ll = first + count - 1;
      *cc = -1;
      return 1;
    }
    int i_out = 0;
    int c = pt_cls[0];
    int f = first;
    int n = 1;
    ff[0] = f;
    cc[0] = c;
    for (int i=1; i<count; ++i)
      if ( pt_cls[i] == c ) {
	++n;
      } else {
	ll[i_out] = f + n - 1;
	++i_out;
	f = ff[i_out] = f + n;
	n = 1;
	c = pt_cls[i];
	cc[i_out] = c;
      }
    ll[i_out] = f + n - 1;
    return ( i_out + 1 );
  }
};

class qubx_idlz_data_seg
{
public:
  std::vector<qubx_idlz_data_piece> pieces;
  int first, count;
  std::vector<double> sc_frac;
  // algo responsibility:
  std::vector<double> amp, std;
  
  qubx_idlz_data_seg()
    : first(0), count(0)
  {}
};

class qubx_idlz_data
{
public:
  double sampling;
  int Nstim, Nsig, Ndata, data_cap;
  float *data;
  int *pt_cls;
  float *baseline;
  std::vector<qubx_idlz_data_seg> segments;
  std::vector<double> stimclasses;
  stimmap stimcls_of;
  std::vector<double> sc_frac;
  
  qubx_idlz_data(double sampling_, int Nstimsig_, int data_cap_)
    : sampling(sampling_), Nstim(0), Nsig(Nstimsig_+1), Ndata(0), data_cap(data_cap_)
    , data(new float[data_cap_]), pt_cls(new int[data_cap_]), baseline(new float[data_cap_])
  {
    memset(baseline, 0, data_cap_*sizeof(float));
  }
  
  virtual ~qubx_idlz_data()
  {
    delete [] data;
    delete [] pt_cls;
    delete [] baseline;
  }
  
  void reset(double sampling_, int Nstimsig, int newcap)
  {
    Nstim = Ndata = 0;
    sampling = sampling_;
    Nsig = Nstimsig + 1;
    segments.clear();
    stimclasses.clear();
    stimcls_of.clear();
    ensure_capacity(newcap);
  }
  
  void ensure_capacity(int newcap)
  {
    if ( data_cap < newcap ) {
      while ( data_cap < newcap )
	data_cap *= 2;
      float *newdata = new float[data_cap];
      int *new_pt_cls = new int[data_cap];
      float *new_base = new float[data_cap];
      memcpy(newdata, data, Ndata*sizeof(float));
      memcpy(new_pt_cls, pt_cls, Ndata*sizeof(int));
      memcpy(new_base, baseline, Ndata*sizeof(float));
      memset(new_base+Ndata, 0, (data_cap - Ndata) * sizeof(float));
      
      for (std::vector<qubx_idlz_data_seg>::iterator segi=segments.begin(); segi!=segments.end(); ++segi) {
	for (std::vector<qubx_idlz_data_piece>::iterator piece = segi->pieces.begin(); piece != segi->pieces.end(); ++piece) {
	  if ( piece->data ) {
	    piece->data = newdata + (piece->data - data);
	    piece->pt_cls = new_pt_cls + (piece->pt_cls - pt_cls);
	    piece->baseline = new_base + (piece->baseline - baseline);
	  }
	}
      }

      delete [] data;
      delete [] pt_cls;
      delete [] baseline;
      data = newdata;
      pt_cls = new_pt_cls;
      baseline = new_base;
      memset(baseline, 0, data_cap*sizeof(float));
    }
  }

  int append_segment(int first)
  {
    int ix = (int) segments.size();
    segments.resize(ix+1);
    segments[ix].first = first;
    return ix;
  }
  
  void append_chunk(int seg_ix, int Nd, float *I,
		    int *dwellCounts, int **classeses, float **durationses, double **ampses)
  {
    if ( I )
      ensure_capacity(Ndata + Nd);
    float *chunk_data = I ? (data + Ndata) : NULL;
    int *chunk_pt_cls = I ? (pt_cls + Ndata) : NULL;
    float *chunk_baseline = I ? (baseline + Ndata) : NULL;
    if ( I ) {
      float *di = chunk_data;
      for (int i=0; i<Nd; ++i, ++di)
	*di = I[i];
      Ndata += Nd;
    }
    
    if ( Nsig < 2 ) {
      segments[seg_ix].pieces.push_back(qubx_idlz_data_piece(0, chunk_data, chunk_pt_cls, chunk_baseline, Nd,
							     segments[seg_ix].first + segments[seg_ix].count));
      segments[seg_ix].count += Nd;
      return;
    }

    int max_mplex = 0;
    for (int i=0; i<(Nsig-1); ++i)
      max_mplex += dwellCounts[i];
    std::vector<int> mplex_clss(max_mplex);
    std::vector<float> mplex_durs(max_mplex);
    int mplex_count = 0;
    mplex_idl(Nsig-1, dwellCounts, classeses, durationses, ampses, &mplex_count, & mplex_clss[0], & mplex_durs[0], stimcls_of);
    
    double sampling_ms = sampling * 1e3;
    int counts_so_far = 0;
    int points_so_far = 0;
    for (int i=0; i<mplex_count; ++i) {
      int count = (int) (mplex_durs[i] / sampling_ms + 0.5);
      int points = I ? count : 0;
      segments[seg_ix].pieces.push_back(qubx_idlz_data_piece(mplex_clss[i],
						     points ? (chunk_data + points_so_far) : NULL,
						     points ? (chunk_pt_cls + points_so_far) : NULL,
						     points ? (chunk_baseline + points_so_far) : NULL,
						     count,
						     segments[seg_ix].first + segments[seg_ix].count + counts_so_far));
      counts_so_far += count;
      points_so_far += points;
    }
    segments[seg_ix].count += counts_so_far;
  }

  double* get_stimclasses()
  {
    if ( stimclasses.size() )
      return & stimclasses[0];
    
    Nstim = (int) stimcls_of.size();
    if ( ! Nstim )
      Nstim = 1;
    stimclasses.resize(Nstim*Nsig);
    for (stimmap::iterator stimi = stimcls_of.begin(); stimi != stimcls_of.end(); ++stimi) {
      int i = stimi->second * Nsig;
      copy(stimi->first.begin(), stimi->first.end(), & stimclasses[i]);
    }
    // calculate which fraction of points are in each stimclass, for each seg and whole data
    sc_frac.resize(Nstim);
    memset(& sc_frac[0], 0, Nstim*sizeof(double));
    int total = 0;
    for (std::vector<qubx_idlz_data_seg>::iterator segi=segments.begin(); segi!=segments.end(); ++segi) {
      segi->sc_frac.resize(Nstim);
      memset(& segi->sc_frac[0], 0, Nstim*sizeof(double));
      int seg_total = 0;
      for (std::vector<qubx_idlz_data_piece>::iterator piece = segi->pieces.begin(); piece != segi->pieces.end(); ++piece) {
	if ( piece->data ) {
	  seg_total += piece->count;
	  segi->sc_frac[piece->stimclass] += piece->count;
	}
      }
      if ( seg_total ) {
	for (int i=0; i<Nstim; ++i) {
	  sc_frac[i] += segi->sc_frac[i];
	  segi->sc_frac[i] /= seg_total;
	  total += seg_total;
	}
      }
    }
    if ( total )
      for (int i=0; i<Nstim; ++i)
	sc_frac[i] /= total;
    return & stimclasses[0];
  }
};




extern "C" MAXILL_API void* qubx_idlz_data_create(double sampling, int Nstimsig, int data_cap)
{
  return new qubx_idlz_data(sampling, Nstimsig, data_cap);
}

extern "C" MAXILL_API void qubx_idlz_data_free(void *data)
{
  delete (qubx_idlz_data *) data;
}

extern "C" MAXILL_API void qubx_idlz_data_reset(void *data, double sampling, int Nstimsig, int data_cap)
{
  ((qubx_idlz_data *) data)->reset(sampling, Nstimsig, data_cap);
}

extern "C" MAXILL_API int qubx_idlz_data_append_segment(void *data, int first)
{
  return ((qubx_idlz_data *) data)->append_segment(first);
}

extern "C" MAXILL_API void qubx_idlz_data_append_chunk(void *data, int seg_ix, int Nd,
								float *I, int *dwellCounts,
								int **classeses, float **durationses, double **ampses)
{
  ((qubx_idlz_data *) data)->append_chunk(seg_ix, Nd, I, dwellCounts, classeses, durationses, ampses);
}

extern "C" MAXILL_API double* qubx_idlz_data_get_stimclasses(void *data, int *Nstimclass)
// returns double[Nstimclass * Nsignal], Nsignal = Nstimsig + 1;
{
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  double *stimclasses = idldata->get_stimclasses();
  *Nstimclass = idldata->Nstim;
  return stimclasses;
}

extern "C" MAXILL_API double* qubx_idlz_data_get_stimclass_frac(void *data)
{
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  return & idldata->sc_frac[0];
}

extern "C" MAXILL_API int qubx_idlz_data_get_dwellcount(void *data, int iseg, int *ncls)
{
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  int count = 0;
  qubx_idlz_data_seg& seg = idldata->segments[iseg];
  int maxcls = -1;
  for (std::vector<qubx_idlz_data_piece>::iterator piece = seg.pieces.begin(); piece != seg.pieces.end(); ++piece) {
    count += piece->count_dwells();
    if ( piece->data )
      for (int i=0; i<piece->count; ++i)
	if ( maxcls < piece->pt_cls[i] )
	  maxcls = piece->pt_cls[i];
  }
  *ncls = maxcls + 1;
  return count;
}

extern "C" MAXILL_API void qubx_idlz_data_get_dwells(void *data, int iseg, int *ff, int *ll, int *cc, double *aa, double *ss)
{
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  int count = 0;
  qubx_idlz_data_seg& seg = idldata->segments[iseg];
  for (std::vector<qubx_idlz_data_piece>::iterator piece = seg.pieces.begin(); piece != seg.pieces.end(); ++piece)
    count += piece->get_dwells(ff+count, ll+count, cc+count);
  int maxcls = -1;
  for (int i=0; i<count; ++i)
    if ( cc[i] > maxcls )
      maxcls = cc[i];
  memcpy(aa, & seg.amp[0], (maxcls+1)*sizeof(double));
  memcpy(ss, & seg.std[0], (maxcls+1)*sizeof(double));
}

extern "C" MAXILL_API int qubx_idlz_data_get_baseline(void *data, int iseg, int offset, int count, float *baseline)
{
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  if ( (iseg < (int) idldata->segments.size()) && idldata->segments[iseg].pieces.size() ) {
    memcpy(baseline, idldata->segments[iseg].pieces[0].baseline+offset, count*sizeof(float));
    return count;
  } else {
    return 0;
  }
}

void qubx_idlz_set_seg_amps(qubx_stim_amps *ampm, qubx_idlz_data *data)
{
  qubx_model *model = ampm->model;

  for (std::vector<qubx_idlz_data_seg>::iterator segi=data->segments.begin(); segi!=data->segments.end(); ++segi) {
    matrix<double> sc_frac_per_cls(model->Nclass, ampm->Nstim);
    memset(& sc_frac_per_cls(0,0), 0, model->Nclass*ampm->Nstim*sizeof(double));
    std::vector<int> cls_total(model->Nclass, 0);
    for (std::vector<qubx_idlz_data_piece>::iterator piece=segi->pieces.begin(); piece!=segi->pieces.end(); ++piece) {
      if ( ! piece->data )
	continue;
      int sc = piece->stimclass;
      
      for (int i=0; i<piece->count; ++i) {
	int cls = piece->pt_cls[i];
	cls_total[cls] += 1;
	sc_frac_per_cls(cls, sc) += 1.0;
      }
    }
    for (int c=0; c<model->Nclass; ++c) {
      if ( cls_total[c] ) {
	for (int sc=0; sc<ampm->Nstim; ++sc) {
	  sc_frac_per_cls(c, sc) /= cls_total[c];
	}
      }
    }

    segi->amp.resize(model->Nclass);
    segi->std.resize(model->Nclass);
    for (int c=0; c<model->Nclass; ++c) {
      segi->amp[c] = UNSET_VALUE;
      double amp = 0.0;
      double std = 0.0;
      double weight = 0.0;
      for (int sc=0; sc<ampm->Nstim; ++sc) {
	double w = sc_frac_per_cls(c,sc);
	weight += w;
	amp += w * ampm->sc_amp[sc][c];
	std += w * ampm->sc_std[sc][c];
      }
      if ( weight ) {
	segi->amp[c] = amp;
	segi->std[c] = std;
      }
    }
  }
}


class qubx_idlz_halfamp_state
{
public:
  qubx_model *model;
  qubx_stim_amps *ampm;
  std::vector<double> thresh_v;
  std::vector<int> cls_ix_v;
  double *thresh;
  int *cls_ix;
  int cls_last;
  bool omp_stop;
  
  qubx_idlz_halfamp_state(qubx_model *model_, qubx_stim_amps *ampm_)
    : model(model_), ampm(ampm_), thresh_v(model_->Nclass), cls_ix_v(model_->Nclass),
      thresh(&(thresh_v[0])), cls_ix(&(cls_ix_v[0])), omp_stop(false)
  {}
  
  void setup(int sc)
  {
    double *amp = ampm->sc_amp[sc];
    
    std::vector< pair<double, int> > cls_amp_ix;
    for (int c=0; c<model->Nclass; ++c)
      cls_amp_ix.push_back( pair<double, int>(amp[c], c) );
    sort(cls_amp_ix.begin(), cls_amp_ix.end());
    for (int c=0; c<(model->Nclass-1); ++c) {
      thresh[c] = (cls_amp_ix[c+1].first + cls_amp_ix[c].first) / 2.0;
      cls_ix[c] = cls_amp_ix[c].second;
    }
    cls_last = cls_amp_ix[model->Nclass-1].second;
  }
  
  int get_cls(double current)
  {
    for (int c=0; c<(model->Nclass-1); ++c)
      if ( current < thresh[c] )
	return cls_ix[c];
    return cls_last;
  }
};

extern "C" MAXILL_API int qubx_idlz_halfamp(qubx_model *model, void *data, qubx_stim_amps *ampm)
{
  //print_timestamp("halfamp...");
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  qubx_idlz_halfamp_state state(model, ampm);

  model->progress = 0;
  int rtn = 0; // success

  #pragma omp parallel
  if ( model->IeqFv ) {
    int idata = 0;
    for (std::vector<qubx_idlz_data_seg>::iterator segi=idldata->segments.begin(); segi!=idldata->segments.end(); ++segi) {
      for (std::vector<qubx_idlz_data_piece>::iterator piece=segi->pieces.begin(); piece!=segi->pieces.end(); ++piece) {
	if ( ! piece->data )
	  continue;
	#pragma omp single
	{
	  state.setup(piece->stimclass);
	}
	
	#pragma omp for
	for (int i=0; i<piece->count; ++i)
	  piece->pt_cls[i] = state.get_cls(piece->data[i]);

	#pragma omp single
	{
	  idata += piece->count;
	  model->progress = (int) (idata * 100.0 / (double) idldata->Ndata);
	  state.omp_stop = state.omp_stop || model->stop_flag;
	}
	if ( state.omp_stop ) {
	  rtn = -10;
	  break;
	}
      }
    }
  } else {
    #pragma omp single
    {
      state.setup(0);
    }
    for (int block=0; block<idldata->Ndata; block += 65536) {
      int block_end = block + 65536;
      if ( idldata->Ndata < block_end )
	block_end = idldata->Ndata;
      #pragma omp single
      {
	model->progress = (int) (block * 100.0 / (double) idldata->Ndata);
	state.omp_stop = state.omp_stop || model->stop_flag;
      }
      if ( state.omp_stop ) {
	rtn = -10;
	break;
      }
      #pragma omp for
      for (int i=block; i<block_end; ++i)
	idldata->pt_cls[i] = state.get_cls(idldata->data[i]);
    }
  }
  
  qubx_idlz_set_seg_amps(ampm, idldata);

  //print_timestamp("done.");
  return rtn;
}



double qubx_idlz_skm_AA_func_simplex(void *obj, double *params);
double qubx_idlz_skm_amp_func_simplex(void *obj, double *params);

class qubx_idlz_skm_state
{
public:
  qubx_model *model;
  qubx_stim_amps *ampm;
  qubx_stim_rates *ratesm;
  qubx_idlz_data *data;
  qubx_model_storage *st;
  double sampling;
  double resolution;
  int b_reest_amps, b_reest_rates, b_track_baseline;
  double baseline_std;
  std::vector< pointy_matrix<double> > AA_log, AA_r;
  std::vector< pointy_col_vector<double> > sc_amp_r, sc_std_r;
  matrix<char> best_prior; // (t, state)
  int t, t0_seg;
  matrix<double> P0_prev;
  std::vector< matrix<double> > P_from;
  qub_kalman_const *kalman_const;
  std::vector<qub_kalman_state *> kalman_prev, kalman_from;
  matrix<float> best_baseline;
  SampledGaussian bl_measure;
  int prev_sc;
  std::vector<double> amp_of_state;
  std::vector< std::vector< SampledGaussian > > measure;
  bool omp_stop;
  double progress_iterweight, progress_before;

  qubx_idlz_skm_state(qubx_model *model_, qubx_stim_amps *ampm_, qubx_stim_rates *ratesm_, qubx_idlz_data *data_,
		      double resolution_, int reest_amps_, int reest_rates_, int track_baseline_, double baseline_std_)
    : model(model_), ampm(ampm_), ratesm(ratesm_), data(data_),
      st((qubx_model_storage*) model->storage), sampling(data->sampling), resolution(resolution_),
      b_reest_amps(reest_amps_), b_reest_rates(reest_rates_), b_track_baseline(track_baseline_), baseline_std(baseline_std_),
      best_prior(data->Ndata, model->Nstate), t(0),
      P0_prev(1, model->Nstate), P_from(model->Nstate),
      kalman_const(NULL), amp_of_state(model_->Nstate), measure(data->Nstim),
      omp_stop(false)
  {
    for (int i=0; i<model->Nstate; ++i)
      P_from[i].resize(1, model->Nstate);
    for (int sc=0; sc<data->Nstim; ++sc)
      measure[sc].resize(model->Nstate);

    AA_log.resize(data->Nstim);
    AA_r.resize(data->Nstim);
    sc_amp_r.resize(data->Nstim);
    sc_std_r.resize(data->Nstim);
    
    if ( model->Nstate ) {
      for (int sc=0; sc<data->Nstim; ++sc) {
	AA_log[sc].resize(model->Nstate, model->Nstate);
	AA_r[sc].resize(model->Nstate, model->Nstate);
	sc_amp_r[sc].resize(st->Nclass);
	sc_std_r[sc].resize(st->Nclass);
      }
      qubx_stim_rates_setup_QQ(ratesm);
      qubx_stim_rates_setup_AA(ratesm);
    }
    
    if ( b_track_baseline ) {
      kalman_const = qub_kalman_create(1, 1);
      // kalman_const.A[0][0] defaults to 1.0 (identity) deterministic process
      kalman_const->H[0][0] = 1.0; // baseline -> measurement
      kalman_const->Q[0][0] = baseline_std * baseline_std; // baseline variance
      kalman_const->R[0][0] = model->var0; // measurement variance
      for (int i=0; i<st->Nstate; ++i) {
	kalman_prev.push_back(qub_kalman_state_create(kalman_const));
	for (int j=0; j<st->Nstate; ++j)
	  kalman_from.push_back(qub_kalman_state_create(kalman_const));
      }
      best_baseline.resize(data->Ndata, model->Nstate);
      bl_measure.setup(0.0, baseline_std, resolution, 6.0, 1024, true, true);
    }
  }
  
  virtual ~qubx_idlz_skm_state()
  {
    for (size_t i=0; i<kalman_prev.size(); ++i)
      qub_kalman_state_free(kalman_prev[i]);
    for (size_t i=0; i<kalman_from.size(); ++i)
      qub_kalman_state_free(kalman_from[i]);
    if ( kalman_const )
      qub_kalman_free(kalman_const);
  }
  
  void setup(int sc)
  {
    for (int j=0; j<model->Nstate; ++j)
      measure[sc][j].setup(ampm->sc_amp[sc][model->clazz[j]],
			   ampm->sc_std[sc][model->clazz[j]],
			   resolution, 6.0, 1024, true, true);
    for (int i=0; i<model->Nstate; ++i)
      for (int j=0; j<model->Nstate; ++j)
	if ( ratesm->AA[sc][i][j] > 0.0 )
	  AA_log[sc].m(i,j) = log(ratesm->AA[sc][i][j]);
	else
	  AA_log[sc].m(i,j) = -1e2;
  }

  void load_sc_amps(int sc)
  {
    for (int i=0; i<model->Nstate; ++i)
      amp_of_state[i] = ampm->sc_amp[sc][model->clazz[i]];
  }
  
  void do_P0(int sc)
  {
    qubx_stim_rates_storage *ratesst = (qubx_stim_rates_storage *) ratesm->storage;
    prev_sc = sc;
    load_sc_amps(sc);
    if ( model->usePeq ) {
      QtoPe(ratesst->QQ[sc].m, P0_prev);
    } else {
      memcpy(&(P0_prev(0,0)), model->P0, model->Nstate * sizeof(double));
    }
    for (int i=0; i<model->Nstate; ++i)
      if ( P0_prev(0,i) > 0 )
	P0_prev(0,i) = log(P0_prev(0,i));
      else
	P0_prev(0,i) = -1e2;
  }
  
  void reset()
  {
    t = 0;
  }
    
  void start_seg(int sc)
  {
    do_P0(sc);
    t0_seg = t;
  }
  
  void first_sample_of_seg(double y, int s)
  {
    if ( b_track_baseline ) {
      double baseline_prior = y - amp_of_state[s];
      qub_kalman_state_reset(kalman_prev[s]);
      kalman_prev[s]->X[0] = baseline_prior;
      y -= kalman_prev[s]->X[0];
      best_baseline(t,s) = (float) kalman_prev[s]->X[0];
    }
    P0_prev(0,s) = measure[prev_sc][s](y); // TODO: += or =  ?
  }
  
  void next_sample_to_lattice(double y, int sc, int prev_s, int next_s)
  {
    double logP_baseline = 0.0;
    if ( b_track_baseline ) {
      double baseline_prior = y - amp_of_state[next_s];
      qub_kalman_state *into = kalman_from[prev_s*st->Nstate + next_s];
      qub_kalman_state_next_into(into, kalman_prev[prev_s], &baseline_prior);
      y -= into->X[0];
      logP_baseline = bl_measure(into->X[0] - kalman_prev[prev_s]->X[0]);
    }
    
    P_from[prev_s](0, next_s) = P0_prev(0,prev_s) + AA_log[prev_sc].m(prev_s, next_s) + measure[sc][next_s](y)
                                + logP_baseline;;
  }
  
  void lattice_to_pick(int next_s)
  {
    double best_P = P_from[0](0,next_s);
    int best_i = 0;
    for (int prev_s=1; prev_s<model->Nstate; ++prev_s) {
      if ( P_from[prev_s](0, next_s) > best_P ) {
	best_P = P_from[prev_s](0, next_s);
	best_i = prev_s;
      }
    }
    best_prior(t, next_s) = (char) best_i;
    P0_prev(0, next_s) = best_P;
    if ( b_track_baseline ) {
      qub_kalman_state_copy(kalman_prev[next_s], kalman_from[best_i*st->Nstate + next_s]);
      best_baseline(t, next_s) = (float) kalman_prev[next_s]->X[0];
    }
  }
  
  void end_sample(int sc)
  {
    prev_sc = sc;
    load_sc_amps(sc);
    ++t;
  }

  void skip_samples(int n, int sc)
  {
    // these numbers are too small for IEEE floating point, so we'll multiply with a proportional vector:
    double logshift = P0_prev(0,0);
    for (int i=1; i<model->Nstate; ++i)
      if ( logshift < P0_prev(0,i) )
	logshift = P0_prev(0,i);
    logshift = - logshift - 1;
    for (int i=0; i<model->Nstate; ++i)
      P0_prev(0,i) = exp(P0_prev(0, i) + logshift);
    
    qubx_stim_rates_storage *ratesst = (qubx_stim_rates_storage *) ratesm->storage;
    ratesst->setup_A(sc, n*sampling);
    P0_prev = prod(P0_prev, ratesst->A.m);
    
    for (int i=0; i<model->Nstate; ++i)
      P0_prev(0, i) = log(P0_prev(0, i)) - logshift;
  }
  
  double end_seg()
  {
    int s_final = 0;     // s = argmax_j( P0_prev(0,j) );
    double best_P = P0_prev(0, s_final);
    for (int j=1; j<model->Nstate; ++j) {
      if ( best_P < P0_prev(0, j) ) {
	best_P = P0_prev(0, j);
	s_final = j;
      }
    }
    int s = s_final;
    for (int tt=t-1; tt>t0_seg; --tt) {
      data->pt_cls[tt] = s;
      s = best_prior(tt, s);
    }
    data->pt_cls[t0_seg] = s;

    if ( b_track_baseline ) {
      int *ss = data->pt_cls + t-1;
      for (int tt=t-1; tt>t0_seg; --tt) {
	data->baseline[tt] = best_baseline(tt,*(ss--));
      }
      data->baseline[t0_seg] = best_baseline(t0_seg,s);
    }
    return best_P;
  }

  void avg_sc_AA()
  {
    for (int sc=0; sc<ratesm->Nstim; ++sc)
      if ( data->sc_frac[sc] )
	AA_r[sc].clear();
    
    matrix<int> sc_from_count(ratesm->Nstim, model->Nstate);
    sc_from_count.clear();
    
    for (std::vector<qubx_idlz_data_seg>::iterator segi=data->segments.begin(); segi!=data->segments.end(); ++segi) {
      for (std::vector<qubx_idlz_data_piece>::iterator piece=segi->pieces.begin(); piece!=segi->pieces.end(); ++piece) {
	if ( ! piece->data )
	  continue;
	int sc = piece->stimclass;
	int from = piece->pt_cls[0];
	for (int i=1; i<piece->count; ++i) {
	  AA_r[sc].m(from, piece->pt_cls[i]) += 1.0;
	  sc_from_count(sc, from) += 1;
	  from = piece->pt_cls[i];
	}
      }
    }
    for (int sc=0; sc<ratesm->Nstim; ++sc) {
      if ( data->sc_frac[sc] ) {
	for (int i=0; i<model->Nstate; ++i) {
	  if ( sc_from_count(sc, i) ) {
	    double scale = 1.0 / sc_from_count(sc, i);
	    for (int j=0; j<model->Nstate; ++j)
	      AA_r[sc].m(i,j) *= scale;
	  }
	}
      }
    }
  }
    
  bool reest_k() {
    avg_sc_AA();
    qubx_stim_rates_setup_constraints(ratesm);
    simplex(qubx_idlz_skm_AA_func_simplex, this, ratesm->cns->fPars, ratesm->cns->NfPar, 1e-9, 1.0, QUBX_IDLZ_AA_MAXITER);
    return true;
  }

  double AA_func_simplex(double *params)
  {
    memcpy(ratesm->cns->fPars, params, ratesm->cns->NfPar*sizeof(double));
    qubx_constrained_free_to_pars(ratesm->cns);
    qubx_stim_rates_pars_to_model(ratesm);
    return AA_eval();
  }

  double AA_eval()
  {
    double ssd = 0.0;
    for (int sc=0; sc<ratesm->Nstim; ++sc) {
      if ( data->sc_frac[sc] ) {
	double sc_ssd = 0.0;
	for (int i=0; i<model->Nstate; ++i) {
	  for (int j=0; j<model->Nstate; ++j) {
	    double diff = ratesm->AA[sc][i][j] - AA_r[sc].m(i,j);
	    sc_ssd += diff * diff;
	  }
	}
	ssd += data->sc_frac[sc] * sc_ssd;
      }
    }
    return ssd;
  }

  void avg_sc_amps()
  {
    if ( model->IeqFv && model->iVoltage ) {
      std::vector<RunningMean> avger(ampm->Nstim * model->Nclass);
      
      for (std::vector<qubx_idlz_data_seg>::iterator segi=data->segments.begin(); segi!=data->segments.end(); ++segi) {
	for (std::vector<qubx_idlz_data_piece>::iterator piece=segi->pieces.begin(); piece!=segi->pieces.end(); ++piece) {
	  if ( ! piece->data )
	    continue;
	  int sc_off = piece->stimclass * model->Nclass;
	  for (int i=0; i<piece->count; ++i)
	    avger[sc_off + piece->pt_cls[i]].add( piece->data[i] - piece->baseline[i] );
	}
      }
      for (int sc=0; sc<ampm->Nstim; ++sc) {
	int sc_off = sc * model->Nclass;
	for (int cls=0; cls<model->Nclass; ++cls) {
	  sc_amp_r[sc].p[cls] = avger[sc_off + cls].mean;
	  sc_std_r[sc].p[cls] = avger[sc_off + cls].std();
	}
      }
    } else { // no v-sensitive amps
      std::vector<RunningMean> avger(model->Nclass);
      for (int i=0; i<data->Ndata; ++i)
	avger[data->pt_cls[i]].add( data->data[i] - data->baseline[i] );
      for (int cls=0; cls<model->Nclass; ++cls) {
	sc_amp_r[0].p[cls] = avger[cls].mean;
	sc_std_r[0].p[cls] = avger[cls].std();
      }
    }
  }
  
  bool reest_amps() {
    avg_sc_amps();
    if ( model->IeqFv && model->iVoltage ) {
      qubx_stim_amps_model_to_pars(ampm);
      qubx_constrained_setup(ampm->cns);
      simplex(qubx_idlz_skm_amp_func_simplex, this, ampm->cns->fPars, ampm->cns->NfPar, 1e-9, 1.0, QUBX_IDLZ_AMPS_MAXITER);
    } else { // no v-sensitive amps: all collected in _r[sc=0]
      memcpy(st->amp.p, sc_amp_r[0].p, st->Nclass*sizeof(double));
      for (int c=0; c<st->Nclass; ++c)
	st->var.p[c] = sc_std_r[0].p[c] * sc_std_r[0].p[c];
      qubx_stim_amps_setup_scs(ampm);
    }
    return true;
  }
  
  double amp_eval(double *params)
  {
    memcpy(ampm->cns->fPars, params, ampm->cns->NfPar * sizeof(double));
    qubx_constrained_free_to_pars(ampm->cns);
    qubx_stim_amps_pars_to_model(ampm);
    qubx_stim_amps_setup_scs(ampm);
    
    double ssd = 0.0;
    for (int sc=0; sc<data->Nstim; ++sc) {
      if ( data->sc_frac[sc] ) {
	double sc_ssd = 0.0;
	for (int i=0; i<model->Nclass; ++i) {
	  double diff = ampm->sc_amp[sc][i] - sc_amp_r[sc].p[i];
	  sc_ssd += diff * diff;
	  diff = ampm->sc_std[sc][i] - sc_std_r[sc].p[i];
	  sc_ssd += diff * diff;
	}
	ssd += data->sc_frac[sc] * sc_ssd;
      }
    }
    return ssd;
  }
};

double qubx_idlz_skm_AA_func_simplex(void *obj, double *params)
{
  return ((qubx_idlz_skm_state *) obj)->AA_func_simplex(params);
}

double qubx_idlz_skm_amp_func_simplex(void *obj, double *params)
{
  return ((qubx_idlz_skm_state *) obj)->amp_eval(params);
}


double qubx_idlz_skm_once(qubx_model *model, qubx_idlz_data *idldata, qubx_idlz_skm_state& state)
{
  double ll = 0.0;
  int idata = 0;
  state.reset();
  
  //print_timestamp("setup sc...");
#pragma omp parallel reduction (+: ll)
  {
    #pragma omp for
    for (int sc=0; sc<idldata->Nstim; ++sc)
      state.setup(sc);
    
    //print_timestamp("seg/piece/point loop...");
    for (std::vector<qubx_idlz_data_seg>::iterator segi=idldata->segments.begin(); segi!=idldata->segments.end(); ++segi) {
      bool first_sample = true;
      for (std::vector<qubx_idlz_data_piece>::iterator piece=segi->pieces.begin(); piece!=segi->pieces.end(); ++piece) {
	if ( ! state.omp_stop ) {
	  int sc = piece->stimclass;
	  int n = piece->count;
	  if ( piece == segi->pieces.begin() ) {
	    #pragma omp single
	    {
	      state.start_seg(sc);
	    }
	  }
	  if ( ! piece->data ) {
	  #pragma omp single
	    {
	      state.skip_samples(n, sc);
	    }
	  } else if ( n ) {
	    int i = 0;
	    if ( first_sample ) {
	    #pragma omp for
	      for (int s=0; s<model->Nstate; ++s)
		state.first_sample_of_seg(piece->data[0], s);
	    #pragma omp single
	      {
		state.end_sample(sc);
		++idata;
	      }
	      i = 1;
	      first_sample = false;
	    }
	    for (; i<n && ! state.omp_stop; ++i) {
	      double y = piece->data[i];
	      #pragma omp for
	      for (int j=model->Nstate*model->Nstate-1; j>=0; --j) {
		state.next_sample_to_lattice(y, sc, j/model->Nstate, j%model->Nstate);
	      }  
	      #pragma omp for
	      for (int j=model->Nstate-1; j>=0; --j) {
		state.lattice_to_pick(j);
	      }
	      #pragma omp single
	      {
		state.end_sample(sc);
		state.omp_stop = state.omp_stop || model->stop_flag;
		++idata;
		if ( ! (idata % 16384) )
		  model->progress = (int) (state.progress_before + 100 * idata * state.progress_iterweight / idldata->Ndata);
	      }
	    }
	  }
	}
      }
      #pragma omp single
      {
	if ( ! state.omp_stop )
	  ll += state.end_seg();
      }
    }
  }

  if ( state.omp_stop ) {
    for (int i=0; i<idldata->Ndata; ++i)
      idldata->pt_cls[i] = 0;
  } else {
    // before converting state index to class, summarize the state-to-state transitions:
    if ( state.b_reest_rates ) {
      //print_timestamp("reestimating Ks...");
      state.reest_k();
    }
    
    //print_timestamp("recovering class sequence...");
    int *clazz = state.st->clazz.p;
    for (int i=0; i<idldata->Ndata; ++i) {
      //cerr << i << ": " << idldata->pt_cls[i] << endl;
      idldata->pt_cls[i] = clazz[idldata->pt_cls[i]];
    }
    
    // now get stats on class amp and std
    if ( state.b_reest_amps ) {
      //print_timestamp("reestimating amps...");
      state.reest_amps(); // bool std?);
    }
  }
  return ll;
}



extern "C" MAXILL_API int qubx_idlz_skm(qubx_model *model, void *data,
					qubx_stim_amps *ampm, qubx_stim_rates *ratesm, double resolution, int method,
					int reest_amps, int reest_rates, int track_baseline, double baseline_std,
					double *ll_out)
{
  *ll_out = 0.0;
  model->progress = 0;
  if ( model->stop_flag )
     return -10;

  //print_timestamp("setup skm...");
  
  qubx_idlz_data *idldata = (qubx_idlz_data *) data;
  qubx_idlz_skm_state state(model, ampm, ratesm, idldata, resolution, reest_amps, reest_rates, track_baseline, baseline_std);
  int rtn = 0; // success

  if ( method == 2/*SKM*/ ) {
    double prev_ll = -1e100;

    int maxiter = 7;
    state.progress_iterweight = 1.0 / maxiter;
    double conv_ll = 1e-8 * idldata->Ndata;
    for (int iter=0; iter<maxiter && !model->stop_flag; ++iter) {
      state.progress_before = iter * state.progress_iterweight;
      double ll = qubx_idlz_skm_once(model, idldata, state);
      model->progress = (int) (state.progress_before + state.progress_iterweight);
      cerr << "ll: " << ll << endl;
      if ( (ll - prev_ll) < conv_ll )
	break;
      *ll_out = prev_ll = ll;
    }
  } else {
    state.progress_iterweight = 1.0;
    state.progress_before = 0.0;
    *ll_out = qubx_idlz_skm_once(model, idldata, state);
  }

  if ( model->stop_flag ) {
    rtn = -10;
  } else {
    //print_timestamp("averaging seg amps...");
    qubx_idlz_set_seg_amps(ampm, idldata);
  }
  //print_timestamp("done.");
  return rtn;
}


// note: don't use openmp; it makes it slower (at least on my 2-core).