https://github.com/cran/rstpm2
Raw File
Tip revision: dbf475c5eff77d00481ede1a0ac227018b274bc1 authored by Mark Clements on 13 July 2022, 21:50:02 UTC
version 1.5.7
Tip revision: dbf475c
multistate.cpp
#include <RcppArmadillo.h>
#include <errno.h>
#include <math.h>
#include <boost/numeric/odeint.hpp>
#include <vector>
#include <algorithm>

/*
 *	Natural Splines
 *	---------------
 *	Here the end-conditions are determined by setting the second
 *	derivative of the spline at the end-points to equal to zero.
 *
 *	There are n-2 unknowns (y[i]'' at x[2], ..., x[n-1]) and n-2
 *	equations to determine them.  Either Choleski or Gaussian
 *	elimination could be used.
 */
static void
natural_spline(int n, double *x, double *y, double *b, double *c, double *d)
{
    int nm1, i;
    double t;
    x--; y--; b--; c--; d--;
    if(n < 2) {
	errno = EDOM;
	return;
    }
    if(n < 3) {
	t = (y[2] - y[1]);
	b[1] = t / (x[2]-x[1]);
	b[2] = b[1];
	c[1] = c[2] = d[1] = d[2] = 0.0;
	return;
    }
    nm1 = n - 1;
    /* Set up the tridiagonal system */
    /* b = diagonal, d = offdiagonal, c = right hand side */
    d[1] = x[2] - x[1];
    c[2] = (y[2] - y[1])/d[1];
    for( i=2 ; i<n ; i++) {
	d[i] = x[i+1] - x[i];
	b[i] = 2.0 * (d[i-1] + d[i]);
	c[i+1] = (y[i+1] - y[i])/d[i];
	c[i] = c[i+1] - c[i];
    }
    /* Gaussian elimination */
    for(i=3 ; i<n ; i++) {
	t = d[i-1]/b[i-1];
	b[i] = b[i] - t*d[i-1];
	c[i] = c[i] - t*c[i-1];
    }
    /* Backward substitution */
    c[nm1] = c[nm1]/b[nm1];
    for(i=n-2 ; i>1 ; i--)
	c[i] = (c[i]-d[i]*c[i+1])/b[i];
    /* End conditions */
    c[1] = c[n] = 0.0;
    /* Get cubic coefficients */
    b[1] = (y[2] - y[1])/d[1] - d[i] * c[2];
    c[1] = 0.0;
    d[1] = c[2]/d[1];
    b[n] = (y[n] - y[nm1])/d[nm1] + d[nm1] * c[nm1];
    for(i=2 ; i<n ; i++) {
	b[i] = (y[i+1]-y[i])/d[i] - d[i]*(c[i+1]+2.0*c[i]);
	d[i] = (c[i+1]-c[i])/d[i];
	c[i] = 3.0*c[i];
    }
    c[n] = 0.0;
    d[n] = 0.0;
    return;
}
/*
 *	Splines a la Forsythe Malcolm and Moler
 *	---------------------------------------
 *	In this case the end-conditions are determined by fitting
 *	cubic polynomials to the first and last 4 points and matching
 *	the third derivitives of the spline at the end-points to the
 *	third derivatives of these cubics at the end-points.
 */
static void
fmm_spline(int n, double *x, double *y, double *b, double *c, double *d)
{
    int nm1, i;
    double t;
    /* Adjustment for 1-based arrays */
    x--; y--; b--; c--; d--;
    if(n < 2) {
	errno = EDOM;
	return;
    }
    if(n < 3) {
	t = (y[2] - y[1]);
	b[1] = t / (x[2]-x[1]);
	b[2] = b[1];
	c[1] = c[2] = d[1] = d[2] = 0.0;
	return;
    }
    nm1 = n - 1;
    /* Set up tridiagonal system */
    /* b = diagonal, d = offdiagonal, c = right hand side */
    d[1] = x[2] - x[1];
    c[2] = (y[2] - y[1])/d[1];/* = +/- Inf	for x[1]=x[2] -- problem? */
    for(i=2 ; i<n ; i++) {
	d[i] = x[i+1] - x[i];
	b[i] = 2.0 * (d[i-1] + d[i]);
	c[i+1] = (y[i+1] - y[i])/d[i];
	c[i] = c[i+1] - c[i];
    }
    /* End conditions. */
    /* Third derivatives at x[0] and x[n-1] obtained */
    /* from divided differences */
    b[1] = -d[1];
    b[n] = -d[nm1];
    c[1] = c[n] = 0.0;
    if(n > 3) {
	c[1] = c[3]/(x[4]-x[2]) - c[2]/(x[3]-x[1]);
	c[n] = c[nm1]/(x[n] - x[n-2]) - c[n-2]/(x[nm1]-x[n-3]);
	c[1] = c[1]*d[1]*d[1]/(x[4]-x[1]);
	c[n] = -c[n]*d[nm1]*d[nm1]/(x[n]-x[n-3]);
    }
    /* Gaussian elimination */
    for(i=2 ; i<=n ; i++) {
	t = d[i-1]/b[i-1];
	b[i] = b[i] - t*d[i-1];
	c[i] = c[i] - t*c[i-1];
    }
    /* Backward substitution */
    c[n] = c[n]/b[n];
    for(i=nm1 ; i>=1 ; i--)
	c[i] = (c[i]-d[i]*c[i+1])/b[i];
    /* c[i] is now the sigma[i-1] of the text */
    /* Compute polynomial coefficients */
    b[n] = (y[n] - y[n-1])/d[n-1] + d[n-1]*(c[n-1]+ 2.0*c[n]);
    for(i=1 ; i<=nm1 ; i++) {
	b[i] = (y[i+1]-y[i])/d[i] - d[i]*(c[i+1]+2.0*c[i]);
	d[i] = (c[i+1]-c[i])/d[i];
	c[i] = 3.0*c[i];
    }
    c[n] = 3.0*c[n];
    d[n] = d[nm1];
    return;
}
/*
 *	Periodic Spline
 *	---------------
 *	The end conditions here match spline (and its derivatives)
 *	at x[1] and x[n].
 *
 *	Note: There is an explicit check that the user has supplied
 *	data with y[1] equal to y[n].
 */
static void
periodic_spline(int n, double *x, double *y, double *b, double *c, double *d)
{
    double s;
    int i, nm1;
    // double *e = (double *) R_alloc(n, sizeof(double));
    arma::vec ev(n);
    double* e = ev.memptr();
    /* Adjustment for 1-based arrays */
    x--; y--; b--; c--; d--; e--;
    if(n < 2 || y[1] != y[n]) {
	errno = EDOM;
	return;
    }
    if(n == 2) {
	b[1] = b[2] = c[1] = c[2] = d[1] = d[2] = 0.0;
	return;
    } else if (n == 3) {
	b[1] = b[2] = b[3] = -(y[1] - y[2])*(x[1] - 2*x[2] + x[3])/(x[3]-x[2])/(x[2]-x[1]);
	c[1] = -3*(y[1]-y[2])/(x[3]-x[2])/(x[2]-x[1]);
	c[2] = -c[1];
	c[3] = c[1];
	d[1] = -2*c[1]/3/(x[2]-x[1]);
	d[2] = -d[1]*(x[2]-x[1])/(x[3]-x[2]);
	d[3] = d[1];
	return;
    }
    /* else --------- n >= 4 --------- */
    nm1 = n-1;
    /* Set up the matrix system */
    /* A = diagonal	 B = off-diagonal  C = rhs */
#define A	b
#define B	d
#define C	c
    B[1]  = x[2] - x[1];
    B[nm1]= x[n] - x[nm1];
    A[1] = 2.0 * (B[1] + B[nm1]);
    C[1] = (y[2] - y[1])/B[1] - (y[n] - y[nm1])/B[nm1];
    for(i = 2; i < n; i++) {
	B[i] = x[i+1] - x[i];
	A[i] = 2.0 * (B[i] + B[i-1]);
	C[i] = (y[i+1] - y[i])/B[i] - (y[i] - y[i-1])/B[i-1];
    }
    /* Choleski decomposition */
#define L	b
#define M	d
#define E	e
    L[1] = sqrt(A[1]);
    E[1] = (x[n] - x[nm1])/L[1];
    s = 0.0;
    for(i = 1; i <= nm1 - 2; i++) {
	M[i] = B[i]/L[i];
	if(i != 1) E[i] = -E[i-1] * M[i-1] / L[i];
	L[i+1] = sqrt(A[i+1]-M[i]*M[i]);
	s = s + E[i] * E[i];
    }
    M[nm1-1] = (B[nm1-1] - E[nm1-2] * M[nm1-2])/L[nm1-1];
    L[nm1] = sqrt(A[nm1] - M[nm1-1]*M[nm1-1] - s);
    /* Forward Elimination */
#define Y	c
#define D	c
    Y[1] = D[1]/L[1];
    s = 0.0;
    for(i=2 ; i<=nm1-1 ; i++) {
	Y[i] = (D[i] - M[i-1]*Y[i-1])/L[i];
	s = s + E[i-1] * Y[i-1];
    }
    Y[nm1] = (D[nm1] - M[nm1-1] * Y[nm1-1] - s) / L[nm1];
#define X	c
    X[nm1] = Y[nm1]/L[nm1];
    X[nm1-1] = (Y[nm1-1] - M[nm1-1] * X[nm1])/L[nm1-1];
    for(i=nm1-2 ; i>=1 ; i--)
	X[i] = (Y[i] - M[i] * X[i+1] - E[i] * X[nm1])/L[i];
    /* Wrap around */
    X[n] = X[1];
    /* Compute polynomial coefficients */
    for(i=1 ; i<=nm1 ; i++) {
	s = x[i+1] - x[i];
	b[i] = (y[i+1]-y[i])/s - s*(c[i+1]+2.0*c[i]);
	d[i] = (c[i+1]-c[i])/s;
	c[i] = 3.0*c[i];
    }
    b[n] = b[1];
    c[n] = c[1];
    d[n] = d[1];
    return;
}
#undef A
#undef B
#undef C
#undef L
#undef M
#undef E
#undef Y
#undef D
#undef X
/* These were the public interfaces */
static void
spline_coef(int method, int n, double *x, double *y,
	    double *b, double *c, double *d)
{
    switch(method) {
    case 1:
    	periodic_spline(n, x, y, b, c, d);	break;
    case 2:
	natural_spline(n, x, y, b, c, d);	break;
    case 3:
	fmm_spline(n, x, y, b, c, d);	break;
    }
}
static void
spline_eval(int method, int nu, double *u, double *v,
	    int n, double *x, double *y, double *b, double *c, double *d)
{
/* Evaluate  v[l] := spline(u[l], ...),	    l = 1,..,nu, i.e. 0:(nu-1)
 * Nodes x[i], coef (y[i]; b[i],c[i],d[i]); i = 1,..,n , i.e. 0:(*n-1)
 */
    const int n_1 = n - 1;
    int i, j, k, l;
    double ul, dx, tmp;
    if(method == 1 && n > 1) { /* periodic */
    	dx = x[n_1] - x[0];
    	for(l = 0; l < nu; l++) {
    	    v[l] = fmod(u[l]-x[0], dx);
    	    if(v[l] < 0.0) v[l] += dx;
    	    v[l] += x[0];
    	}
    } else
      for(l = 0; l < nu; l++) v[l] = u[l];
    for(l = 0, i = 0; l < nu; l++) {
	ul = v[l];
	if(ul < x[i] || (i < n_1 && x[i+1] < ul)) {
	    /* reset i  such that  x[i] <= ul <= x[i+1] : */
	    i = 0;
	    j = n;
	    do {
		k = (i+j)/2;
		if(ul < x[k]) j = k;
		else i = k;
	    } while(j > i+1);
	}
	dx = ul - x[i];
	/* for natural splines extrapolate linearly left */
	tmp = (method == 2 && ul < x[0]) ? 0.0 : d[i];
	v[l] = y[i] + dx*(b[i] + dx*(c[i] + dx*tmp));
    }
}
class SplineCoef {
public:
  SplineCoef(arma::vec x, arma::vec y, int method = 2) : x(x), y(y), method(method) {
    int n = x.n_elem;
    // if(y.n_elem != x.n_elem) REprintf("inputs of different lengths");
    b.resize(n); c.resize(n); d.resize(n);
    b.zeros(); c.zeros(); d.zeros();
    if (method==1 && y(0) != y(n-1))
      y(n-1)=y(0);
    spline_coef(method, x.n_elem, x.memptr(), y.memptr(), b.memptr(), c.memptr(), d.memptr());
  }
  arma::vec eval(arma::vec xout) {
    int nu = xout.n_elem;
    arma::vec yout(nu);
    spline_eval(method, nu, xout.memptr(), yout.memptr(),
		x.n_elem, x.memptr(), y.memptr(), b.memptr(), c.memptr(), d.memptr());
    return yout;
  }
  double eval(double xout) {
    double yout;
    spline_eval(method, 1, &xout, &yout,
		x.n_elem, x.memptr(), y.memptr(), b.memptr(), c.memptr(), d.memptr());
    return yout;
  }
  static arma::vec eval(double xout, std::vector<SplineCoef>& z);
  arma::vec x, y, b, c, d;
  int method;
};
arma::vec SplineCoef::eval(double xout, std::vector<SplineCoef>& z)
  {
    arma::vec yout(z.size());
    for (size_t i=0; i<z.size(); i++)
      yout(i) = z[i].eval(xout);
    return yout;
  }
namespace boost { namespace numeric { namespace odeint {
      template <>
      struct is_resizeable<arma::vec>
      {
	typedef boost::true_type type;
	const static bool value = type::value;
      };
      template <>
      struct same_size_impl<arma::vec, arma::vec>
      {
	static bool same_size(const arma::vec& x, const arma::vec& y)
	{
	  return x.size() == y.size();
	}
      };
      template<>
      struct resize_impl<arma::vec, arma::vec>
      {
	static void resize(arma::vec& v1, const arma::vec& v2)
	{
	  v1.resize(v2.size());
	}
      };
    } } } // namespace boost::numeric::odeint
typedef arma::vec state_type;
typedef std::vector<state_type> vector_state_type;
typedef std::vector<double> vector_times;
struct push_back_state_and_time
{
    std::vector< state_type >& m_states;
    std::vector< double >& m_times;
    push_back_state_and_time( std::vector< state_type > &states , std::vector< double > &times )
    : m_states( states ) , m_times( times ) { }
    void operator()( const state_type &x , double t )
    {
        m_states.push_back( x );
        m_times.push_back( t );
    }
};
struct push_back_state
{
    std::vector< state_type >& m_states;
    push_back_state( std::vector< state_type > &states)
    : m_states( states ) { }
    void operator()( const state_type &x , double t )
    {
        m_states.push_back( x );
    }
};
struct Flow {
  size_t from, to;
  SplineCoef s;
  std::vector<SplineCoef> gradients;
  bool use_log;
};
// old version of libarmadillo-dev: mat.as_col() not available
arma::vec flatten(arma::mat m) {
  arma::vec v(m.n_cols*m.n_rows);
  for (size_t j=0; j<m.n_cols; j++)
    for (size_t i=0; i<m.n_rows; i++)
      v(j*m.n_rows+i) = m(i,j);
  return v;
}
struct StateComponents {
  arma::vec P, L;
  arma::mat gradP, gradL;
};
struct StateComponentsCombined {
  arma::vec times;
  arma::mat P, L;
  arma::cube gradP, gradL;
};
// forward declaration
class Markov {
public:
  typedef std::vector<Flow> Flows;
  Flows& flows;
  double minTm;
  size_t nStates, nGradients;
  StateComponents stateComponents(const state_type& x) {
    arma::vec P = x(arma::span(0,nStates-1));
    arma::vec L = x(arma::span(nStates,2*nStates-1));
    arma::mat gradP(arma::vec(x(arma::span(2*nStates,2*nStates+nStates*nGradients-1))).memptr(),
		    nStates, nGradients);
    arma::mat gradL(arma::vec(x(arma::span(2*nStates+nStates*nGradients,
					   2*nStates+2*nStates*nGradients-1))).memptr(),
		    nStates, nGradients);
    return {P, L, gradP, gradL};
  }
  Markov(Flows& flows, double minTm = 1.0e-8) : flows(flows), minTm(minTm) {
    nGradients = nStates = 0;
    for(size_t i=0; i<flows.size(); i++) {
      nGradients += flows[i].gradients.size();
      nStates = std::max(std::max(nStates,flows[i].from),flows[i].to);
    }
    nStates++;
  }
  void operator() ( const state_type &x , state_type &dxdt , const double t )
  {
    arma::vec rates(flows.size());
    arma::vec dPdt(nStates,arma::fill::zeros);
    arma::vec dLdt;
    arma::mat dGradPdt(nStates,nGradients,arma::fill::zeros);
    arma::mat dGradLdt;
    // destructure the state vector
    StateComponents c = stateComponents(x);
    // P (transition probabilities)
    for (size_t i=0; i<flows.size(); i++) {
      rates(i) = flows[i].s.eval(flows[i].use_log ? log(t<minTm ? minTm : t) : t);
      // if (flows[i].use_log) rates(i) = exp(rates(i));
      double delta = c.P(flows[i].from)*rates(i);
      dPdt(flows[i].to) += delta;
      dPdt(flows[i].from) -= delta;
    }
    // L (length of stay)
    dLdt = c.P;
    // gradients for P
    for (size_t i=0, start=0; i<flows.size(); start+=flows[i].gradients.size(), i++) {
      for (size_t j=0; j<flows[i].gradients.size(); j++) {
	double delta = c.gradP(flows[i].from,start+j)*rates(i)+
	  c.P(flows[i].from)*flows[i].gradients[j].eval(t);
	dGradPdt(flows[i].to,start+j) += delta;
	dGradPdt(flows[i].from,start+j) -= delta;
      }
    }
    // gradients for L
    dGradLdt = c.gradP;
    dxdt = arma::join_cols(dPdt,
			   arma::join_cols(dLdt,
					   arma::join_cols(flatten(dGradPdt),
							   flatten(dGradLdt))));
  }
};
StateComponentsCombined run(Markov &model, arma::vec p0, arma::vec times) {
  using namespace boost::numeric::odeint;
  vector_state_type states;
  // times, outtimes and vtimes all have the same data
  vector_times outtimes; // not strictly needed - we could use push_back_state()
  vector_times vtimes = arma::conv_to< vector_times >::from(times);
  state_type x = join_cols(p0, arma::zeros(model.nStates+2*model.nStates*model.nGradients));
  BOOST_STATIC_ASSERT( is_resizeable<state_type>::value == true );
  integrate_times(make_dense_output( 1.0e-10 , 1.0e-10 , runge_kutta_dopri5< state_type >() ),
		  model, x,
		  vtimes.begin(),
		  vtimes.end(),
		  1.0,
		  push_back_state_and_time(states, outtimes));
  size_t nx = states[0].size(), nTimes = times.size();
  // combine the results
  arma::mat combined(nTimes,nx);
  for (size_t i=0; i<nTimes; i++)
    combined(arma::span(i,i),arma::span(0,nx-1)) = states[i].t();
  // destructure the combined matrix
  arma::mat P = combined(arma::span::all,arma::span(0,model.nStates-1));
  arma::mat L = combined(arma::span::all,arma::span(model.nStates,2*model.nStates-1));
  arma::cube gradP(arma::mat(combined(arma::span::all,
				      arma::span(2*model.nStates,2*model.nStates+model.nStates*model.nGradients-1))).memptr(),
		   nTimes, model.nStates, model.nGradients);
  arma::cube gradL(arma::mat(combined(arma::span::all,
			    arma::span(2*model.nStates+model.nStates*model.nGradients,
				       2*model.nStates+2*model.nStates*model.nGradients-1))).memptr(),
		   nTimes, model.nStates, model.nGradients);
  return {times, P, L, gradP, gradL};
}
// Rcpp::List runMarkovODE(arma::vec y0, arma::vec times, arma::vec tlam, arma::mat lam, Rcpp::List gradients, arma::ivec from, arma::ivec to, Rcpp::LogicalVector use_logs, double minTm = 1.0e-8) {
RcppExport SEXP runMarkovODE(SEXP _y0, SEXP _times, SEXP _tlam, SEXP _lam, SEXP _gradients, SEXP _from, SEXP _to, SEXP _use_logs, SEXP _minTm) {
  using namespace Rcpp;
  arma::vec y0 = as<arma::vec>(_y0);
  arma::vec times = as<arma::vec>(_times);
  arma::vec tlam = as<arma::vec>(_tlam);
  arma::mat lam = as<arma::mat>(_lam);
  List gradients = as<List>(_gradients);
  arma::ivec from = as<arma::ivec>(_from);
  arma::ivec to = as<arma::ivec>(_to);
  LogicalVector use_logs = as<LogicalVector>(_use_logs);
  double minTm = as<double>(_minTm);
  Markov::Flows flows;
  for (size_t i=0; i<from.size(); i++) {
    SplineCoef coef = SplineCoef(use_logs[i] ? log(tlam) : tlam, lam.col(i));
    std::vector<SplineCoef> vgradients;
    arma::mat g = Rcpp::as<arma::mat>(gradients[i]);
    for (int j=0; j<gradients.size(); j++)
      vgradients.push_back(SplineCoef(tlam, g.col(j)));
    flows.push_back({size_t(from(i)), size_t(to(i)), coef, vgradients,
                     bool(use_logs[i])});
  }
  Markov markov(flows, minTm);
  auto report = run(markov, y0, times);
  return wrap(List::create(Named("times")=report.times,
			   Named("P")=report.P,
			   Named("L")=report.L,
			   Named("gradP")=report.gradP,
			   Named("gradL")=report.gradL));
}

RcppExport SEXP multistate_ddt(SEXP P_, SEXP Pu_, SEXP Q_, SEXP Qu_) {
  using namespace arma;
  // P(nstates,nobs), Pu(nstates,ncoef,nobs), Q(nstates,nstates,nobs), Qu(nstates*nstates,ncoef,nobs)
  mat P = Rcpp::as<mat>(P_);
  cube Pu = Rcpp::as<cube>(Pu_);
  cube Q = Rcpp::as<cube>(Q_);
  cube Qu = Rcpp::as<cube>(Qu_);
  int nstates = P.n_rows;
  int nobs=P.n_cols;
  int ncoef=Pu.n_cols;
  mat dPdt = P*0.0;   // nstates,nobs
  cube dPudt = Pu*0.0; // nstates,ncoef,nobs
  for(int i=0; i<nobs; i++) {
    mat Qi = Q.slice(i);
    rowvec Pi = conv_to<rowvec>::from(P.col(i));
    vec rs = sum(Qi,1);
    for (int j=0; j<nstates; j++)
      Qi(j,j) = -rs(j);
    dPdt.col(i) = conv_to<vec>::from(Pi*Qi); // is this valid?
    cube Qui = Qu(span::all, span::all, span(i,i));
    Qui.reshape(nstates,nstates,ncoef);
    mat Pui = Pu.slice(i);
    for (int j=0; j<ncoef; j++) {
      vec rs = sum(Qui.slice(j),1);
      for (int m=0; m<nstates; m++)
	Qui(m,m,j) = -rs(m);
    }
    dPudt.slice(i) = (Pui.t() * Qi).t();
    for(int j=0; j<ncoef; j++) {
      rowvec PiQuij = Pi * Qui.slice(j);
      for(int k=0; k<nstates; k++)
	dPudt(k,j,i) += PiQuij(k);
    }
  }
  return Rcpp::wrap(Rcpp::List::create(dPdt,dPudt));
}


class ExpM {
public:
  arma::mat Qmat;
  ExpM(arma::mat _Qmat) : Qmat(_Qmat) { }
  void operator() ( const state_type &x , state_type &dxdt , const double t )
  {
    dxdt = (x.t() * Qmat).t();
  }
};
RcppExport SEXP runExpM(SEXP _y0, SEXP _times, SEXP _Qmat) {
  using namespace Rcpp;
  arma::vec y0 = as<arma::vec>(_y0);
  arma::vec times = as<arma::vec>(_times);
  arma::mat Qmat = as<arma::mat>(_Qmat);
  ExpM model(Qmat);
  using namespace boost::numeric::odeint;
  vector_state_type states;
  // times, outtimes and vtimes all have the same data
  vector_times outtimes; // not strictly needed - we could use push_back_state()
  vector_times vtimes = arma::conv_to< vector_times >::from(times);
  BOOST_STATIC_ASSERT( is_resizeable<state_type>::value == true );
  integrate_times(make_dense_output( 1.0e-10 , 1.0e-10 , runge_kutta_dopri5< state_type >() ),
		  model, y0,
		  vtimes.begin(),
		  vtimes.end(),
		  1.0,
		  push_back_state_and_time(states, outtimes));
  size_t nx = states[0].size(), nTimes = times.size();
  // combine the results
  arma::mat combined(nTimes,nx);
  for (size_t i=0; i<nTimes; i++)
    combined(arma::span(i,i),arma::span(0,nx-1)) = states[i].t();
  return wrap(combined);
}
back to top