StatBase::StatBase(long flags)
{
	Init(flags);
}

// TSSTAT.CPP
#include <slib-internal.h>
#pragma hdrstop
// Calculate Pearson correlation = cov(X, Y) / (sigma_X * sigma_Y)
// This routine efficiently computes the correlation in one pass of the
// data and makes use of the algorithm described in:
// B. P. Welford, "Note on a Method for Calculating Corrected Sums of
// Squares and Products", Technometrics, Vol 4, No 3, 1962.
// This paper derives a numerically stable recurrence to compute a sum of products
// with the relation
double gsl_stats_correlation(const double data1[], const size_t stride1, const double data2[], const size_t stride2, const size_t n)
{
	double sum_xsq = 0.0;
	double sum_ysq = 0.0;
	double sum_cross = 0.0;
	// using the above relation from Welford's paper
	double mean_x = data1[0 * stride1];
	double mean_y = data2[0 * stride2];
	for(size_t i = 1; i < n; ++i) {
		const double ratio = i / (i + 1.0);
		const double delta_x = data1[i * stride1] - mean_x;
		const double delta_y = data2[i * stride2] - mean_y;
		sum_xsq += delta_x * delta_x * ratio;
		sum_ysq += delta_y * delta_y * ratio;
		sum_cross += delta_x * delta_y * ratio;
		mean_x += delta_x / (i + 1.0);
		mean_y += delta_y / (i + 1.0);
	}
	double r = sum_cross / (sqrt(sum_xsq) * sqrt(sum_ysq));
	return r;
}

struct gsl_block {
	size_t size;
	double * data;
};

struct gsl_vector {
	size_t size;
	size_t stride;
	double * data;
	gsl_block * block;
	int owner;
};

struct gsl_vector_view {
	gsl_vector vector;
};

void gsl_vector_set(gsl_vector * v, const size_t i, double x)
{
#if GSL_RANGE_CHECK
	if(GSL_RANGE_COND(i >= v->size)) {
		GSL_ERROR_VOID ("index out of range", GSL_EINVAL);
	}
#endif
	v->data[i * v->stride] = x;
}

/*
gsl_stats_spearman()
  Compute Spearman rank correlation coefficient

Inputs: data1   - data1 vector
        stride1 - stride of data1
        data2   - data2 vector
        stride2 - stride of data2
        n       - number of elements in data1 and data2
        work    - additional workspace of size 2*n

Return: Spearman rank correlation coefficient
*/
/*double gsl_stats_spearman(const double data1[], const size_t stride1, const double data2[], const size_t stride2, const size_t n, double work[])
{
	size_t i;
	gsl_vector_view ranks1 = gsl_vector_view_array(&work[0], n);
	gsl_vector_view ranks2 = gsl_vector_view_array(&work[n], n);
	double r;
	for(i = 0; i < n; ++i) {
		gsl_vector_set(&ranks1.vector, i, data1[i * stride1]);
		gsl_vector_set(&ranks2.vector, i, data2[i * stride2]);
	}
	// sort data1 and update data2 at same time; compute rank of data1
	gsl_sort_vector2(&ranks1.vector, &ranks2.vector);
	compute_rank(&ranks1.vector);
	// now sort data2, updating ranks1 appropriately; compute rank of data2
	gsl_sort_vector2(&ranks2.vector, &ranks1.vector);
	compute_rank(&ranks2.vector);
	// compute correlation of rank vectors in double precision
	r = gsl_stats_correlation(ranks1.vector.data, ranks1.vector.stride, ranks2.vector.data, ranks2.vector.stride, n);
	return r;
}*/

StatBase::StatBase()
{
	Init(0);
}

StatBase::~StatBase()
{
}

int StatBase::Init(long flags /*=0*/)
{
	Flags = flags;
	Count = 0;
	IterCount = 0;
	memzero(Sum, sizeof(Sum));
	Min = SMathConst::Max;
	Max = SMathConst::Min;
	Exp = Var = 0.0;
	Kurtosis = 0.0;
	Skew = 0.0;
	Test_Z = 0.0;
	Test_ChSq = 0.0;
	Series.freeAll();
	return 1;
}

// @prototype
static double Lockes_Z_Test(const RealArray & x);

int StatBase::Finish()
{
	if(Count > 0) {
		Exp = Sum[0] / Count;
		Var = Sum[1] / Count - Exp * (2.0 * Sum[0] / Count - Exp);
		if(Flags & fExt) {
	    	Skew = (Sum[2] / Count) / pow(Sum[1] / Count, 1.5);
	    	Kurtosis = ((Sum[3] / Count) / fpowi(Sum[1] / Count, 2)) - 3.0; /* excess kurtosis */
		}
		if(Flags & fGammaTest) {
			Test_Z = Lockes_Z_Test(Series);
		}
	}
	else {
		Exp = 0.0;
		Var = 0.0;
	}
	return 1;
}

void StatBase::Step(const RealArray & rVl)
{
	const uint sc = rVl.getCount();
	Count += sc;
	IterCount += sc;
	for(uint i = 0; i < sc; i++) {
		const double val = rVl.at(i);
		SETMAX(Max, val);
		SETMIN(Min, val);
		Sum[0] += val;
		double sq = val * val;
		Sum[1] += sq;
		if(Flags & fExt) {
			Sum[2] += sq * val;
			Sum[3] += sq * sq;
		}
	}
	if(Flags & (fGammaTest|fGaussianTest|fStoreVals)) {
		Series.insertChunk(sc, &rVl);
	}
}

void StatBase::Step(double val)
{
	Count++;
	IterCount++;
	SETMAX(Max, val);
	SETMIN(Min, val);
	Sum[0] += val;
	double sq = val * val;
	Sum[1] += sq;
	if(Flags & fExt) {
		Sum[2] += sq * val;
		Sum[3] += sq * sq;
	}
	if(Flags & (fGammaTest|fGaussianTest|fStoreVals)) {
		Series.insert(&val);
	}
}

double StatBase::GetSum() const { return Sum[0]; }
double StatBase::GetVariance() const
{
	return (Count > 1 && Min != Max) ? (Var * static_cast<double>(Count) / static_cast<double>(Count - 1)) : 0.0; // @v10.7.1 (&& Min != Max)
}

double StatBase::GetStdDev() const
{
	return sqrt(GetVariance());
}

int StatBase::GetValue(long idx, double * pVal) const
{
	int    ok = -1;
	if(idx < Series.getCountI()) {
		ASSIGN_PTR(pVal, Series.at(idx));
		ok = 1;
	}
	return ok;
}

void StatBase::GetGammaParams(double * pAlpha, double * pBeta) const
{
	double beta = fdivnz(GetVariance(), GetExp());
	double alpha = fdivnz(GetExp(), beta);
	ASSIGN_PTR(pAlpha, alpha);
	ASSIGN_PTR(pBeta, beta);
}
static int real_kendall_tau(const RealArray & x, const RealArray & y, RPairArray & rXy, double *ptau, double *pz)
{
	assert(x.getCount() == y.getCount());
	double tau, nn1, s2, z;
	int    tt1, tx = 0, ty = 0;
	int    Tx = 0, Ty = 0;
	int    Tx2 = 0, Ty2 = 0;
	int    Tx25 = 0, Ty25 = 0;
	int    tie, N0, N1, S;
	uint   i;
	// populate sorter
	uint   j = 0;
	rXy.clear();
	for(i = 0; i < x.getCount(); i++) {
			RPair p;
			p.X = x.at(i);
			p.Y = y.at(i);
			rXy.insert(&p);
	}
	rXy.SortByX(); // sort pairs by x
	// make order counts
	N0 = N1 = 0;
	uint   nn = rXy.getCount();
	for(i = 0; i < nn; i++) {
		const RPair xy_i = rXy[i];
		double y_i = xy_i.Y;
		double x_i = xy_i.X;
		for(j = i+1; j < nn; j++) {
			const RPair & r_xy = rXy[j];
			if(r_xy.Y == y_i)
				Ty++;
			else if(r_xy.X != x_i) {
				if(r_xy.Y > y_i)
					N0++;
				else if(r_xy.Y < y_i)
					N1++;
			}
		}
		if(i > 0) {
			// account for ties in x
			tie = (x_i == rXy[i-1].X);
			if(tie)
				tx++;
			if(tx > 0 && (!tie || i == nn - 1)) {
				tx++;
				tt1 = tx * (tx - 1);
				Tx += tt1;
				Tx2 += tt1 * (tx - 2);
				Tx25 += tt1 * (2 * tx + 5);
				tx = 0;
			}
		}
	}
	if(Ty > 0) {
		// account for ties in y
		Ty = 0;
		rXy.SortByY();
		for(i = 1; i < nn; i++) {
			tie = (rXy[i].Y == rXy[i-1].Y);
			if(tie)
				ty++;
			if(ty > 0 && (!tie || i == nn - 1)) {
				ty++;
				tt1 = ty * (ty - 1);
				Ty += tt1;
				Ty2 += tt1 * (ty - 2);
				Ty25 += tt1 * (2 * ty + 5);
				ty = 0;
			}
		}
	}
	S = N0 - N1;
#if 0
	slfprintf_stderr("N0 = %d, N1 = %d, S = %d\n", N0, N1, S);
	slfprintf_stderr("Tx = %d, Ty = %d\n", Tx, Ty);
#endif
	nn1 = nn * (nn - 1.0);
	// normal approximation as in Shapiro and Chen, Journal of Quality Technology, 2001
	if(Tx == 0 && Ty == 0) {
		tau = 2 * S / nn1;
		s2 = (1.0/18) * nn1 * (2 * nn + 5);
	}
	else {
		double den = (nn1 - Tx) * (nn1 - Ty);
		tau = 2 * S / sqrt(den);
		s2 = (1.0/18) * (nn1 * (2 * nn + 5) - Tx25 - Ty25);
		if(Tx2 != 0 && Ty2 != 0)
			s2 += (1.0/(9*nn1*(nn-2))) * Tx2 * Ty2;
		if(Tx != 0 && Ty != 0)
			s2 += (1.0/(2*nn1)) * Tx * Ty;
	}
	z = (S - 1) / sqrt(s2);
	ASSIGN_PTR(ptau, tau);
	ASSIGN_PTR(pz, z);
	return 1;
}
// check for negative values, screen out missing values, and load x into an array for randomization
static int locke_shuffle_init(const RealArray & rX, RealArray & rResult)
{
	uint   i, m = 0;
	rResult.clear();
	for(i = 0; i < rX.getCount(); i++) {
		if(rX.at(i) < 0.0)
			return 0;
		else //if(!na(x[i]))
			m++;
	}
	if(m < 4) {
		return 0;
	}
	else {
		for(i = 0; i < rX.getCount(); i++) {
				rResult.insert(&rX.at(i));
		}
		return 1;
	}
}

IMPL_CMPCFUNC(lockes_test_rand, i1, i2)
{
	SRng * p_rng = static_cast<SRng *>(pExtraData);
	ulong r = p_rng ? p_rng->GetUniformInt(8096) : 0;
	return (r - 4097);
}
// @x: data series.
// Performs Charles Locke's nonparametric test for whether an
// empirical distribution (namely, that of @x over the range @t1 to @t2) is gamma.
// See C. Locke, "A Test for the Composite Hypothesis that a Population has a Gamma Distribution,"
// Commun. Statis.-Theor. Meth. A5(4), 351-364 (1976).  Also see Shapiro and Chen,
// Journal of Quality Technology 33(1), Jan 2001.
//   the z value for test, or #NADBL on error.
static double Lockes_Z_Test(const RealArray & x)
{
	double z = 0.0; // NADBL
	double zj;
	uint   m = x.getCount();
	uint   i, t;
	RealArray sx;
	SRng * p_rng = 0;
	if(locke_shuffle_init(x, sx)) {
		m /= 2;
		// dimention = m
		RealArray u;
		RealArray v;
		RPairArray uv;
		double zero = 0.0;
		for(i = 0; i < m; i++) {
			u.insert(&zero);
			v.insert(&zero);
		}
		z = 0.0;
		uint   nrepeat = 100;
		if(x.getCount() > 10000)
			nrepeat = 1;
		else if(x.getCount() > 1000)
			nrepeat = 10;
		// repeat the shuffling of the series NREPEAT times, since the
		// test statistic is sensitive to the ordering under the null
		p_rng = SRng::CreateInstance(SRng::algMT, 0);
		p_rng->Set(getcurtime_());
		for(uint j = 0; j < nrepeat; j++) {
			sx.sort(PTR_CMPCFUNC(lockes_test_rand), p_rng);
			t = 0;
			for(i = 0; i < m; i++) {
				u[i] = sx[t] + sx[t+1];
				v[i] = sx[t] / sx[t+1];
				double temp = sx[t+1] / sx[t];
				SETMAX(v[i], temp);
				t += 2;
			}
			real_kendall_tau(u, v, uv, 0, &zj);
			z += zj;
#if LOCKE_DEBUG
			printf("z[%d] = %g\n", j, zj);
#endif
		}
		z /= (double)nrepeat;
#if LOCKE_DEBUG
		slfprintf_stderr("Kendall's tau: average z = %g\n", z);
#endif
	}
	delete p_rng;
	return z;
}
TimSerStat::TimSerStat() : StatBase(0), P_Queue(0), P_AC_Add(0), P_AC_Mul(0)
{
	Init(0);
}

TimSerStat::~TimSerStat()
{
	delete P_Queue;
	delete P_AC_Add;
	delete P_AC_Mul;
}

int TimSerStat::Init(long flags)
{
	StatBase::Init(flags);
	NumLags = 0;
	ZDELETE(P_Queue);
	ZDELETE(P_AC_Add);
	ZDELETE(P_AC_Mul);
	return 1;
}

int TimSerStat::SetNumLags(long numLags)
{
	EXCEPTVAR(SLibError);
	int    ok = 1;
	NumLags = numLags;
	ZDELETE(P_Queue);
	ZDELETE(P_AC_Add);
	ZDELETE(P_AC_Mul);
	if(NumLags > 0) {
		size_t n = static_cast<size_t>(NumLags);
		THROW(P_Queue = new DblQueue(n));
		THROW(P_AC_Add = new double[n]);
		THROW(P_AC_Mul = new double[n]);
		memzero(P_AC_Add, sizeof(double) * n);
		memzero(P_AC_Mul, sizeof(double) * n);
	}
	CATCHZOK
	return ok;
}

int TimSerStat::Step(double val, int /*whiteSpace*/)
{
	StatBase::Step(val);
	if(NumLags > 0) {
		const uint c = P_Queue->getNumItems();
		for(uint i = 0; i < c; i++) {
			const  uint   lag_1 = c - i - 1;
			const  double v_i = P_Queue->get(i);
   	        P_AC_Add[lag_1] += (val + v_i);
	   	    P_AC_Mul[lag_1] += (val * v_i);
		}
		P_Queue->push(val);
	}
	return 1;
}

int TimSerStat::Finish()
{
	StatBase::Finish();
	if(NumLags > 0) {
		const double n_var = Count * Var;
		const double qexp = Exp * Exp;
		for(uint i = 0; i < P_Queue->getNumItems(); i++)
			P_AC_Add[i] = (qexp * (Count-i-1) + P_AC_Mul[i] - Exp * P_AC_Add[i]) / n_var;
		return 1;
	}
	else
		return -1;
}

uint TimSerStat::GetNumLags() const
{
	return P_Queue ? P_Queue->getNumItems() : 0;
}

double TimSerStat::GetAutocorrel(uint lag) const
{
	return (lag < GetNumLags()) ? P_AC_Add[lag] : 0L;
}
TimSerSpikes::TimSerSpikes() : Sign(0), Count(0), P_Spikes(0)
{
}

TimSerSpikes::~TimSerSpikes()
{
	delete P_Spikes;
}

int TimSerSpikes::Init(int sign)
{
	Sign = sign;
	Count = 0;
	MEMSZERO(Prev);
	P_Spikes = new SVector(sizeof(Spike));
	return P_Spikes ? 1 : (SLibError = SLERR_NOMEM, 0);
}

int TimSerSpikes::Step(long n, double val)
{
	Count++;
	if(Count > 2) {
		if(Sign >= 0)
			if(Prev[1].V >= Prev[0].V && Prev[1].V > val)
				if(!P_Spikes->insert(&Prev[1]))
					return 0;
		if(Sign <= 0)
			if(Prev[1].V <= Prev[0].V && Prev[1].V < val)
				if(!P_Spikes->insert(&Prev[1]))
					return 0;
		Prev[0] = Prev[1];
		Prev[1].V = val;
		Prev[1].N = n;
	}
	else {
		Prev[(size_t)Count-1].V = val;
		Prev[(size_t)Count-1].N = n;
	}
	return 1;
}

long TimSerSpikes::GetNumSpikes() const
{
	return SVectorBase::GetCount(P_Spikes);
}

double TimSerSpikes::GetSpike(long n, long * pN) const
{
	if(n < GetNumSpikes()) {
		Spike * p_spike = static_cast<Spike *>(P_Spikes->at((uint)n));
		ASSIGN_PTR(pN, p_spike->N);
		return p_spike->V;
	}
	return 0L;
}

struct _PRDINF { // @flat
	long   Distance;
	long   Count;
};

long TimSerSpikes::GetMostCommonDistance()
{
	uint   i = 0;
	_PRDINF prd, * p_prd;
	SVector prd_ary(sizeof(_PRDINF)); // @v9.8.12 SArray-->SVector
	for(i = 0; (int)i < GetNumSpikes() - 1; i++) {
		long s, n;
		uint idx = 0;
		GetSpike(i,   &s);
   		GetSpike(i+1, &n);
		prd.Distance = labs(s - n);
		if(prd_ary.lsearch(&prd, &idx, CMPF_LONG)) {
			p_prd = static_cast<_PRDINF *>(prd_ary.at(idx));
			p_prd->Distance = labs(s - n);
			p_prd->Count++;
		}
		else {
			prd.Distance = labs(s - n);
			prd.Count = 1;
			prd_ary.insert(&prd);
		}
	}
	long max_count = 0;
	long distance = 3; // default distance
	for(i = 0; prd_ary.enumItems(&i, (void **)&p_prd);)
		if(p_prd->Count > max_count) {
			distance = p_prd->Distance;
			max_count = p_prd->Count;
		}
	return distance;
}
STimeSeries::ValuVec::ValuVec(const char * pSymb, TYPEID typ, int fxPrec) : Typ(typ), SVector(stsize(typ), O_ARRAY), Symb(pSymb), FxPrec(fxPrec), Flags(0)
{
	assert(STimeSeries::VerifyValuVecType(typ));
	assert(!oneof2(Typ, T_FLOAT, T_DOUBLE) || FxPrec == 0);
}

STimeSeries::ValuVec::ValuVec() : Typ(T_DOUBLE), SVector(stsize(T_DOUBLE), O_ARRAY), FxPrec(0), Flags(0)
{
	assert(STimeSeries::VerifyValuVecType(Typ));
}

void FASTCALL STimeSeries::ValuVec::ConvertDoubleToInner(double outer, void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: *(double *)pInner = outer; break;
		case T_FLOAT:  *(float *)pInner = (float)outer; break;
		case T_INT32:
			if(FxPrec != 0)
				*(int32 *)pInner = (int32)R0(outer * fpow10i(FxPrec));
			else
				*(int32 *)pInner = (int32)outer;
			break;
		case T_INT64:
			if(FxPrec != 0)
				*(int64 *)pInner = (int64)R0(outer * fpow10i(FxPrec));
			else
				*(int64 *)pInner = (int64)outer;
			break;
		default:
			assert(0);
	}
}

void FASTCALL STimeSeries::ValuVec::ConvertFloatToInner(float outer, void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: *static_cast<double *>(pInner) = outer; break;
		case T_FLOAT:  *static_cast<float *>(pInner) = outer; break;
		case T_INT32:  *static_cast<int32 *>(pInner) = (FxPrec != 0) ? (int32)R0(outer * fpow10fi(FxPrec)) : (int32)outer; break;
		case T_INT64:  *static_cast<int64 *>(pInner) = (FxPrec != 0) ? (int64)R0(outer * fpow10fi(FxPrec)) : (int64)outer; break;
		default: assert(0);
	}
}

void FASTCALL STimeSeries::ValuVec::ConvertInt32ToInner(int32 outer, void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: *static_cast<double *>(pInner) = (double)outer; break;
		case T_FLOAT:  *static_cast<float *>(pInner) = (float)outer; break;
		case T_INT32:  *static_cast<int32 *>(pInner) = (FxPrec != 0) ? (int32)(outer * fpow10i(FxPrec)) : outer; break;
		case T_INT64:  *static_cast<int64 *>(pInner) = (FxPrec != 0) ? (int64)(outer * fpow10i(FxPrec)) : (int64)outer; break;
		default: assert(0);
	}
}

void FASTCALL STimeSeries::ValuVec::ConvertInt64ToInner(int64 outer, void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: *static_cast<double *>(pInner) = (double)outer; break;
		case T_FLOAT:  *static_cast<float *>(pInner) = (float)outer; break;
		case T_INT32:
			if(FxPrec != 0)
				*static_cast<int32 *>(pInner) = (int32)(outer * fpow10i(FxPrec));
			else
				*static_cast<int32 *>(pInner) = (int32)outer;
			break;
		case T_INT64:
			if(FxPrec != 0)
				*static_cast<int64 *>(pInner) = (int64)(outer * fpow10i(FxPrec));
			else
				*static_cast<int64 *>(pInner) = outer;
			break;
		default:
			assert(0);
	}
}

double FASTCALL STimeSeries::ValuVec::ConvertInnerToDouble(const void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: return *static_cast<const double *>(pInner);
		case T_FLOAT:  return *static_cast<const float *>(pInner);
		case T_INT32:  return FxPrec ? (((double)(*(int32 *)pInner)) / fpow10i(FxPrec)) : (double)*static_cast<const int32 *>(pInner);
		case T_INT64:  return FxPrec ? (((double)(*(int64 *)pInner)) / fpow10i(FxPrec)) : (double)*static_cast<const int64 *>(pInner);
		default: assert(0); return 0.0;
	}
}

float FASTCALL STimeSeries::ValuVec::ConvertInnerToFloat(const void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: return (float)*static_cast<const double *>(pInner);
		case T_FLOAT:  return *static_cast<const float *>(pInner);
		case T_INT32:  return FxPrec ? (((float)(*static_cast<const int32 *>(pInner))) / fpow10fi(FxPrec)) : (float)*static_cast<const int32 *>(pInner);
		case T_INT64:  return FxPrec ? (((float)(*static_cast<const int64 *>(pInner))) / fpow10fi(FxPrec)) : (float)*static_cast<const int64 *>(pInner);
		default: assert(0); return 0.0f;
	}
}

int32 FASTCALL STimeSeries::ValuVec::ConvertInnerToInt32(const void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: return (int32)*static_cast<const double *>(pInner);
		case T_FLOAT:  return (int32)*static_cast<const float *>(pInner);
		case T_INT32:  return FxPrec ? (int32)(((double)(*static_cast<const int32 *>(pInner))) / fpow10i(FxPrec)) : *static_cast<const int32 *>(pInner);
		case T_INT64:  return FxPrec ? (int32)(((double)(*static_cast<const int64 *>(pInner))) / fpow10i(FxPrec)) : (int32)*static_cast<const int64 *>(pInner);
		default: assert(0); return 0;
	}
}

int64 FASTCALL STimeSeries::ValuVec::ConvertInnerToInt64(const void * pInner) const
{
	switch(Typ) {
		case T_DOUBLE: return (int64)*static_cast<const double *>(pInner);
		case T_FLOAT:  return (int64)*static_cast<const float *>(pInner);
		case T_INT32:  return FxPrec ? (int64)(((double)(*static_cast<const int32 *>(pInner))) / fpow10i(FxPrec)) : (int64)*static_cast<const int32 *>(pInner);
		case T_INT64:  return FxPrec ? (int64)(((double)(*static_cast<const int64 *>(pInner))) / fpow10i(FxPrec)) : *static_cast<const int64 *>(pInner);
		default: assert(0); return 0;
	}
}

STimeSeries::STimeSeries() : Ver(1), Id(0), State(0)
{
	memzero(Reserve, sizeof(Reserve));
}

STimeSeries::STimeSeries(const STimeSeries & rS)
{
	Copy(rS);
}

STimeSeries::~STimeSeries()
{
}

STimeSeries & FASTCALL STimeSeries::operator = (const STimeSeries & rS)
{
	Copy(rS);
	return *this;
}

int FASTCALL STimeSeries::Copy(const STimeSeries & rS)
{
	int    ok = 1;
	Destroy();
	Ver = rS.Ver;
	Id = rS.Id;
	State = rS.State;
	Symb = rS.Symb;
	T = rS.T;
	TSCollection_Copy(VL, rS.VL);
	return ok;
}

void FASTCALL STimeSeries::SetSymb(const char * pSymb)
{
	Symb = pSymb;
}

const char * FASTCALL STimeSeries::GetSymb() const
{
	return Symb;
}

STimeSeries & STimeSeries::Z()
{
	for(uint i = 0; i < VL.getCount(); i++) {
		ValuVec * p_vv = VL.at(i);
		CALLPTRMEMB(p_vv, clear());
	}
	T.clear();
	return *this;
}

void STimeSeries::Destroy()
{
	VL.freeAll();
	T.freeAll();
}

int STimeSeries::SetupBySample(const STimeSeries * pSample)
{
	int    ok = 1;
	Destroy();
	if(pSample) {
		for(uint i = 0; i < pSample->VL.getCount(); i++) {
			const ValuVec * p_s_vec = pSample->VL.at(i);
			ValuVec * p_new_vec = 0;
			if(p_s_vec) {
				THROW(p_new_vec = new ValuVec(p_s_vec->Symb, p_s_vec->Typ, p_s_vec->FxPrec));
			}
			THROW(VL.insert(p_new_vec));
		}
	}
	CATCHZOK
	return ok;
}

uint STimeSeries::GetCount() const
{
	return T.getCount();
}

int FASTCALL STimeSeries::GetTime(uint itemIdx, SUniTime * pT) const
{
	int    ok = 1;
	if(itemIdx < T.getCount()) {
		ASSIGN_PTR(pT, T.at(itemIdx));
	}
	else
		ok = 0;
	return ok;
}

int FASTCALL STimeSeries::Swap(uint p1, uint p2)
{
	int    ok = 1;
	if(p1 == p2)
		ok = -1;
	else {
		THROW(T.swap(p1, p2));
		for(uint i = 0; i < VL.getCount(); i++) {
			ValuVec * p_vv = VL.at(i);
			if(p_vv)
				THROW(p_vv->swap(p1, p2));
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::AddItemFromSample(const STimeSeries & rSample, uint samplePos)
{
	int    ok = 1;
	uint   idx = 0;
	assert(rSample.VL.getCount() == VL.getCount());
	THROW(rSample.VL.getCount() == VL.getCount());
	THROW(AddItem(rSample.T.at(samplePos), &idx));
	for(uint i = 0; i < rSample.VL.getCount(); i++) {
		const ValuVec * p_s_vec = rSample.VL.at(i);
		ValuVec * p_vec = VL.at(i);
		assert((p_s_vec && p_vec) || (!p_s_vec && !p_vec));
		THROW((p_s_vec && p_vec) || (!p_s_vec && !p_vec));
		if(p_s_vec) {
			assert(p_s_vec->Typ == p_vec->Typ && p_s_vec->FxPrec == p_vec->FxPrec);
			THROW(p_s_vec->Typ == p_vec->Typ && p_s_vec->FxPrec == p_vec->FxPrec);
			THROW(Helper_SetValue(idx, p_vec, p_s_vec->at(samplePos)));
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::SearchEntry(const SUniTime & rUt, uint startPos, uint * pIdx) const
{
	int    ok = 0;
	for(uint i = startPos; !ok && i < T.getCount(); i++) {
		const SUniTime & r_ut = T.at(i);
		int cr = r_ut.IsEqual(rUt);
		if(oneof2(cr, SUniTime::cmprSureTrue, SUniTime::cmprUncertainTrue)) {
			ASSIGN_PTR(pIdx, i);
			ok = cr;
		}
	}
	return ok;
}

int STimeSeries::SearchFirstEntrySince(const SUniTime & rSince, uint * pIdx) const
{
	int    ok = 0;
	for(uint i = 0; !ok && i < T.getCount(); i++) {
		const SUniTime & r_ut = T.at(i);
		int    sq;
		const  int si = r_ut.Compare(rSince, &sq);
		if(si >= 0 && oneof2(sq, SUniTime::cqSure, SUniTime::cqUncertain)) {
			ASSIGN_PTR(pIdx, i);
			ok = 1;
		}
	}
	return ok;
}

int STimeSeries::SearchEntryReverse(const SUniTime & rUt, uint startPos, uint * pIdx) const
{
	int    ok = 0;
	uint   i = startPos;
	if(i) do {
		const SUniTime & r_ut = T.at(--i);
		const int cr = r_ut.IsEqual(rUt);
		if(oneof2(cr, SUniTime::cmprSureTrue, SUniTime::cmprUncertainTrue)) {
			ASSIGN_PTR(pIdx, i);
			ok = cr;
		}
	} while(!ok && i);
	return ok;
}

static IMPL_CMPFUNC(SUniTime, p1, p2)
{
	int sq;
	return static_cast<const SUniTime *>(p1)->Compare(*static_cast<const SUniTime *>(p2), &sq);
}

int STimeSeries::SearchEntryBinary(const SUniTime & rUt, uint * pIdx) const
{
	return T.bsearch(&rUt, pIdx, PTR_CMPFUNC(SUniTime));
}

STimeSeries::AppendStat::AppendStat() : AppendCount(0), UpdCount(0), SrcFldsCount(0),
	IntersectFldsCount(0), TmProfile(0), SpreadSum(0), SpreadCount(0), UpdCountVecIdx(-1)
{
}

int STimeSeries::AddItems(const STimeSeries & rSrc, AppendStat * pStat)
{
	int    ok = -1;
	SProfile::Measure pm;
	LAssocArray src_to_this_vl_assoc_list;
	const ValuVec * p_src_spread_vec = 0;
	if(pStat) {
		pStat->SrcFldsCount = rSrc.VL.getCount();
		pStat->AppendCount = 0;
		pStat->UpdCount = 0;
	}
	for(uint i = 0; i < rSrc.VL.getCount(); i++) {
		const ValuVec * p_s_vec = rSrc.VL.at(i);
		if(p_s_vec->Symb.NotEmpty()) {
			uint tidx = 0;
			if(pStat && p_s_vec->Symb.IsEqiAscii("spread"))
				p_src_spread_vec = p_s_vec;
			if(GetValueVecIndex(p_s_vec->Symb, &tidx)) {
				src_to_this_vl_assoc_list.Add(i+1, tidx+1);
			}
		}
	}
	if(pStat)
		pStat->IntersectFldsCount = src_to_this_vl_assoc_list.getCount();
	if(src_to_this_vl_assoc_list.getCount()) {
		SUniTime ut_last;
		SUniTime ut;
		SUniTime fut;
		int    skip_existance_checking = 0;
		if(GetCount())
			GetTime(GetCount()-1, &ut_last);
		else
			skip_existance_checking = 1;
		for(uint j = 0; j < rSrc.GetCount(); j++) {
			if(rSrc.GetTime(j, &ut)) {
				uint   ii = 0;
				int    mod = 0; // 1 - append, 2 - update
				const int  ser = skip_existance_checking ? 0 : SearchEntryBinary(ut, &ii);
				if(ser > 0) {
					GetTime(ii, &fut);
				}
				else {
					THROW(AddItem(ut, &ii));
				}
				for(uint k = 0; k < src_to_this_vl_assoc_list.getCount(); k++) {
					const LAssoc & r_asc = src_to_this_vl_assoc_list.at(k);
					double val = 0.0;
					THROW(rSrc.GetValue(j, r_asc.Key-1, &val));
					const long local_vec_idx = (r_asc.Val-1);
					if(ser > 0) {
						double ex_val = 0.0;
						THROW(GetValue(ii, local_vec_idx, &ex_val));
						if(ex_val != val) {
							THROW(SetValue(ii, local_vec_idx, val));
							mod = 2;
							if(pStat && (pStat->UpdCountVecIdx < 0 || pStat->UpdCountVecIdx == local_vec_idx))
								pStat->UpdCount++;
						}
					}
					else {
						THROW(SetValue(ii, local_vec_idx, val));
						if(p_src_spread_vec) {
							assert(pStat != 0); // Иначе p_src_spread_vec был бы нулевым (see above)
							const void * p_value_buf = p_src_spread_vec->at(j);
							if(p_value_buf) {
								int32 temp_val = p_src_spread_vec->ConvertInnerToInt32(p_value_buf);
								if(temp_val >= 0) {
									pStat->SpreadSum += temp_val;
									pStat->SpreadCount++;
								}
							}
						}
						mod = 1;
					}
				}
				assert(mod == 1 || ser > 0);
				if(pStat) {
					/*if(mod == 2)
						pStat->UpdCount++;
					else*/ if(mod == 1)
						pStat->AppendCount++;
				}
				ok = 1;
			}
		}
	}
	CATCHZOK
	if(pStat)
		pStat->TmProfile = pm.Get();
	return ok;
}

static IMPL_CMPFUNC(STimeSeriesIndex, p1, p2)
{
	int    si = 0;
	const  STimeSeries * p_ts = static_cast<const STimeSeries *>(pExtraData);
	if(p_ts) {
		SUniTime t1;
		SUniTime t2;
		if(p_ts->GetTime(*static_cast<const uint *>(p1), &t1) && p_ts->GetTime(*static_cast<const uint *>(p2), &t2)) {
			int sq;
			si = t1.Compare(t2, &sq);
		}
	}
	return si;
}

int STimeSeries::Sort(/*CompFunc fcmp, void * pExtraData*/ /*=0*/)
{
	int    ok = 1;
	const  uint tc = T.getCount();
	if(tc > 1) {
		uint i;
		TSVector <uint> pos_vec;
		for(i = 0; i < VL.getCount(); i++) {
			const ValuVec * p_vv = VL.at(i);
			if(p_vv) {
				THROW(p_vv->getCount() == tc);
			}
		}
		for(i = 0; i < tc; i++)
			pos_vec.insert(&i);
		assert(pos_vec.getCount() == tc);
		pos_vec.sort2(PTR_CMPFUNC(STimeSeriesIndex), this);
		assert(pos_vec.getCount() == tc);
		{
			STimeSeries ts_temp;
			THROW(ts_temp.SetupBySample(this));
			for(i = 0; i < tc; i++) {
				uint pos = pos_vec.at(i);
				THROW(ts_temp.AddItemFromSample(*this, pos));
			}
			Z();
			for(i = 0; i < tc; i++) {
				THROW(AddItemFromSample(ts_temp, i));
			}
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::Serialize(int dir, SBuffer & rBuf, SSerializeContext * pSCtx)
{
	int    ok = 1;
	THROW(pSCtx->Serialize(dir, Ver, rBuf));
	THROW(pSCtx->Serialize(dir, Id, rBuf));
	THROW(pSCtx->Serialize(dir, Symb, rBuf));
	THROW(pSCtx->SerializeBlock(dir, sizeof(Reserve), Reserve, rBuf, 0));
	THROW(pSCtx->Serialize(dir, &T, rBuf));
	{
		if(dir > 0) {
			uint32 c = VL.getCount();
			THROW(rBuf.Write(&c, sizeof(c)));
			for(uint i = 0; i < c; i++) {
				STimeSeries::ValuVec * p_item = VL.at(i);
				THROW(pSCtx->Serialize(dir, p_item->Typ, rBuf));
				THROW(pSCtx->Serialize(dir, p_item->FxPrec, rBuf));
				THROW(pSCtx->Serialize(dir, p_item->Flags, rBuf));
				THROW(pSCtx->Serialize(dir, p_item->Symb, rBuf));
				THROW(pSCtx->Serialize(dir, dynamic_cast<SVector *>(p_item), rBuf));
			}
		}
		else if(dir < 0) {
			VL.freeAll();
			SString c_symb;
			uint32 c = 0;
			THROW(rBuf.ReadV(&c, sizeof(c)));
			for(uint i = 0; i < c; i++) {
				TYPEID c_typ;
				int16  c_fx_prec;
				uint16 c_flags;
				THROW(pSCtx->Serialize(dir, c_typ, rBuf));
				THROW(pSCtx->Serialize(dir, c_fx_prec, rBuf));
				THROW(pSCtx->Serialize(dir, c_flags, rBuf));
				THROW(pSCtx->Serialize(dir, c_symb, rBuf));
				{
					STimeSeries::ValuVec * p_new_item = new STimeSeries::ValuVec(c_symb, c_typ, c_fx_prec);
					THROW(p_new_item);
					p_new_item->Flags = c_flags;
					THROW(pSCtx->Serialize(dir, dynamic_cast<SVector *>(p_new_item), rBuf));
					THROW(VL.insert(p_new_item));
				}
			}
		}
	}
	CATCHZOK
	return ok;
}

STimeSeries::ValuVec * FASTCALL STimeSeries::GetVecBySymb(const char * pSymb, uint * pVecIdx) const
{
	if(!isempty(pSymb)) {
		for(uint i = 0; i < VL.getCount(); i++) {
			ValuVec * p_vec = VL.at(i);
			if(p_vec && p_vec->GetSymb().IsEqiAscii(pSymb)) {
				ASSIGN_PTR(pVecIdx, i);
				return p_vec;
			}
		}
	}
	return (SLS.SetError(SLERR_MATH_TSVECBYSYMBNFOUND, pSymb), 0);
}

STimeSeries::ValuVec * FASTCALL STimeSeries::GetVecByIdx(uint vecIdx) const
{
	return (vecIdx < VL.getCount()) ? VL.at(vecIdx) : 0;
}

int STimeSeries::AddValueVec(const char * pSymb, TYPEID typ, int fxPrec, uint * pVecIdx)
{
	int    ok = 1;
	uint   vec_idx = 0;
	THROW(!GetVecBySymb(pSymb, 0));
	THROW(VerifyValuVecType(typ));
	{
		ValuVec * p_new_vec = new ValuVec(pSymb, typ, fxPrec);
		THROW(p_new_vec);
		vec_idx = VL.getCount();
		THROW(VL.insert(p_new_vec));
		ASSIGN_PTR(pVecIdx, vec_idx);
	}
	CATCHZOK
	return ok;
}

uint STimeSeries::GetValueVecCount() const
{
	return VL.getCount();
}

int  STimeSeries::GetValueVecParam(uint vecIdx, TYPEID * pTyp, SString * pSymb, int * pFxPrec, uint * pFlags) const
{
	int    ok = 0;
	if(vecIdx < VL.getCount()) {
		const STimeSeries::ValuVec * p_vec = VL.at(vecIdx);
		if(p_vec) {
			ASSIGN_PTR(pTyp, p_vec->Typ);
			ASSIGN_PTR(pSymb, p_vec->Symb);
			ASSIGN_PTR(pFxPrec, p_vec->FxPrec);
			ASSIGN_PTR(pFlags, p_vec->Flags);
			ok = 1;
		}
	}
	return ok;
}

int STimeSeries::GetValueVecIndex(const char * pSymb, uint * pIdx) const
{
	return BIN(GetVecBySymb(pSymb, pIdx));
}

int STimeSeries::AddItem(SUniTime tm, uint * pItemIdx)
{
	if(T.insert(&tm)) {
		ASSIGN_PTR(pItemIdx, T.getCount()-1);
		return 1;
	}
	else
		return 0;
}

int STimeSeries::Helper_SetValue(uint itemIdx, STimeSeries::ValuVec * pVec, const void * pValuePtr)
{
	assert(pValuePtr);
	int    ok = 1;
	THROW(pVec);
	THROW(itemIdx < T.getCount());
	{
		const uint org_vec_count = pVec->getCount();
		if(itemIdx < org_vec_count) {
			pVec->atPut(itemIdx, pValuePtr);
		}
		else {
			if(itemIdx > org_vec_count) {
				uint8 zero_value_buf[16];
				memzero(zero_value_buf, sizeof(zero_value_buf));
				for(uint i = 0; i < (itemIdx - org_vec_count); i++) {
					THROW(pVec->insert(zero_value_buf));
				}
			}
			assert(itemIdx == pVec->getCount());
			THROW(pVec->insert(pValuePtr));
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::SetValue(uint itemIdx, uint vecIdx, double value)
{
	uint8 value_buf[16];
	memzero(value_buf, sizeof(value_buf));
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec) {
		p_vec->ConvertDoubleToInner(value, value_buf);
		return Helper_SetValue(itemIdx, p_vec, value_buf);
	}
	else
		return 0;
}

int STimeSeries::SetValue(uint itemIdx, uint vecIdx, float value)
{
	uint8 value_buf[16];
	memzero(value_buf, sizeof(value_buf));
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec) {
		p_vec->ConvertFloatToInner(value, value_buf);
		return Helper_SetValue(itemIdx, p_vec, value_buf);
	}
	else
		return 0;
}

int STimeSeries::SetValue(uint itemIdx, uint vecIdx, int32 value)
{
	uint8 value_buf[16];
	memzero(value_buf, sizeof(value_buf));
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec) {
		p_vec->ConvertInt32ToInner(value, value_buf);
		return Helper_SetValue(itemIdx, p_vec, value_buf);
	}
	else
		return 0;
}

int STimeSeries::SetValue(uint itemIdx, uint vecIdx, int64 value)
{
	uint8 value_buf[16];
	memzero(value_buf, sizeof(value_buf));
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec) {
		p_vec->ConvertInt64ToInner(value, value_buf);
		return Helper_SetValue(itemIdx, p_vec, value_buf);
	}
	else
		return 0;
}

int STimeSeries::GetValue(uint itemIdx, uint vecIdx, double * pValue) const
{
	int    ok = 0;
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec && itemIdx < p_vec->getCount()) {
		if(pValue) {
			const void * p_value_buf = p_vec->at(itemIdx);
			*pValue = p_vec->ConvertInnerToDouble(p_value_buf);
		}
		ok = 1;
	}
	return ok;
}

int STimeSeries::GetValue(uint itemIdx, uint vecIdx, float * pValue) const
{
	int    ok = 0;
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec && itemIdx < p_vec->getCount()) {
		if(pValue) {
			const void * p_value_buf = p_vec->at(itemIdx);
			*pValue = p_vec->ConvertInnerToFloat(p_value_buf);
		}
		ok = 1;
	}
	return ok;
}

int STimeSeries::GetValue(uint itemIdx, uint vecIdx, int32 * pValue) const
{
	int    ok = 0;
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec && itemIdx < p_vec->getCount()) {
		if(pValue) {
			const void * p_value_buf = p_vec->at(itemIdx);
			*pValue = p_vec->ConvertInnerToInt32(p_value_buf);
		}
		ok = 1;
	}
	return ok;
}

int STimeSeries::GetValue(uint itemIdx, uint vecIdx, int64 * pValue) const
{
	int    ok = 0;
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
	if(p_vec && itemIdx < p_vec->getCount()) {
		if(pValue) {
			const void * p_value_buf = p_vec->at(itemIdx);
			*pValue = p_vec->ConvertInnerToInt64(p_value_buf);
		}
		ok = 1;
	}
	return ok;
}

STimeSeries::SnapshotEntry::SnapshotEntry() : Idx(0)
{
}

STimeSeries::SnapshotEntry & STimeSeries::SnapshotEntry::Z()
{
	Idx = 0;
	Tm.Z();
	Values.clear();
	return *this;
}

int STimeSeries::GetSnapshotEntry(uint idx, SnapshotEntry & rEntry) const
{
	int    ok = 1;
	THROW(idx < GetCount());
	rEntry.Idx = idx;
	rEntry.Tm = T.at(idx);
	rEntry.Values.clear();
	for(uint i = 0; i < VL.getCount(); i++) {
		const STimeSeries::ValuVec * p_vec = VL.at(i);
		THROW(p_vec);
		const void * p_value_buf = p_vec->at(idx);
		double value = p_vec->ConvertInnerToDouble(p_value_buf);
		THROW(rEntry.Values.Add(i, value, 0, 0));
	}
	CATCHZOK
	return ok;
}

int STimeSeries::SetSnapshotEntry(uint idx, const SnapshotEntry & rEntry)
{
	int    ok = 1;
	THROW(idx < GetCount());
	T.at(idx) = rEntry.Tm;
	for(uint i = 0; i < VL.getCount(); i++) {
		uint8 value_buf[16];
		STimeSeries::ValuVec * p_vec = VL.at(i);
		THROW(p_vec);
		memzero(value_buf, sizeof(value_buf));
		const double value = rEntry.Values.Get(i, 0);
		p_vec->ConvertDoubleToInner(value, value_buf);
		THROW(Helper_SetValue(idx, p_vec, value_buf));
	}
	CATCHZOK
	return ok;
}

int STimeSeries::AppendSnapshotEntry(const SnapshotEntry & rEntry)
{
	int    ok = 1;
	uint   idx = 0;
	THROW(AddItem(rEntry.Tm, &idx));
	for(uint i = 0; i < VL.getCount(); i++) {
		uint8 value_buf[16];
		STimeSeries::ValuVec * p_vec = VL.at(i);
		THROW(p_vec);
		memzero(value_buf, sizeof(value_buf));
		const double value = rEntry.Values.Get(i, 0);
		p_vec->ConvertDoubleToInner(value, value_buf);
		THROW(Helper_SetValue(idx, p_vec, value_buf));
	}
	CATCHZOK
	return ok;
}

int STimeSeries::GetLVect(uint vecIdx, uint startItemIdx, LVect & rV) const
{
	int    ok = -1;
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
    THROW(p_vec);
	if(rV.size() > 0) {
		const uint last_idx = startItemIdx+rV.size()-1;
		if(last_idx < p_vec->getCount()) {
			for(uint i = startItemIdx, j = 0; i <= last_idx; i++, j++) {
				const void * p_value_buf = p_vec->at(i);
				double value = p_vec->ConvertInnerToDouble(p_value_buf);
				THROW(rV.set(j, value));
			}
			ok = 1;
		}
	}
    CATCHZOK
	return ok;
}

int STimeSeries::GetTimeArray(uint startItemIdx, uint idxCount, DateTimeArray & rV) const
{
	rV.clear();
	int   ok = -1;
	if(idxCount) {
		const uint last_idx = (startItemIdx+idxCount-1);
		if(last_idx < T.getCount()) {
			for(uint i = startItemIdx; i <= last_idx; i++) {
				const SUniTime & r_ut = T.at(i);
				LDATETIME dtm;
				r_ut.Get(dtm);
				THROW(rV.add(dtm));
			}
			ok = 1;
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::GetRealArray(uint vecIdx, uint startItemIdx, uint idxCount, RealArray & rV) const
{
	rV.clear();
	int    ok = -1;
	STimeSeries::ValuVec * p_vec = GetVecByIdx(vecIdx);
    THROW(p_vec);
	if(idxCount) {
		const uint last_idx = (startItemIdx+idxCount-1);
		if(last_idx < p_vec->getCount()) {
			double buffer[128];
			uint   buffer_ptr = 0;
			for(uint i = startItemIdx; i <= last_idx; i++) {
				const void * p_value_buf = p_vec->at(i);
				double value = p_vec->ConvertInnerToDouble(p_value_buf);
				if(buffer_ptr == SIZEOFARRAY(buffer)) {
					THROW(rV.insertChunk(buffer_ptr, buffer));
					buffer_ptr = 0;
				}
				assert(buffer_ptr < SIZEOFARRAY(buffer));
				buffer[buffer_ptr++] = value;
			}
			if(buffer_ptr) {
				THROW(rV.insertChunk(buffer_ptr, buffer));
				buffer_ptr = 0;
			}
			ok = 1;
		}
	}
    CATCHZOK
	return ok;
}

STimeSeries::Stat::Stat(long flags) : StatBase(flags), State(0), DeltaAvg(0.0), LocalDevPtCount(0), Reserve(0), LocalDevAvg(0.0)
{
}

int STimeSeries::Analyze(const char * pVecSymb, uint firstIdx, uint count, Stat & rS) const
{
	int    ok = 1;
	uint   vec_idx = 0;
	STimeSeries::ValuVec * p_vec = GetVecBySymb(pVecSymb, &vec_idx);
	TSQueue <double> * p_local_dev_queue = 0;
	THROW(p_vec);
	assert(firstIdx >= 0 && firstIdx < GetCount());
	assert((firstIdx+count) <= GetCount());
	THROW(firstIdx >= 0 && firstIdx < GetCount());
	THROW((firstIdx+count) <= GetCount());
	{
		const uint _c = (firstIdx+count);
		StatBase stat_delta(0);
		StatBase stat_local_dev(0);
		if(rS.LocalDevPtCount > 0) {
			THROW(p_local_dev_queue = new TSQueue <double>(rS.LocalDevPtCount));
		}
		rS.State |= Stat::stSorted;
		SUniTime prev_utm;
		SUniTime utm;
		// @construction RealArray value_list;
		double prev_value = 0.0;
		for(uint i = firstIdx; i < _c; i++) {
			THROW(GetTime(i, &utm));
			if(i > firstIdx) {
				int   sq = 0;
				int   si = utm.Compare(prev_utm, &sq);
				if(si < 0)
					rS.State &= ~Stat::stSorted;
				else if(si == 0 && sq == SUniTime::cmprSureTrue)
					rS.State |= Stat::stHasTmDup;
			}
			{
				const void * p_value_buf = p_vec->at(i);
				double value = p_vec->ConvertInnerToDouble(p_value_buf);
				rS.Step(value);
				if(i > firstIdx) {
					double delta = (value - prev_value) / prev_value;
					stat_delta.Step(fabs(delta));
					if(rS.LocalDevPtCount > 0) {
						assert(p_local_dev_queue);
						p_local_dev_queue->push(value);
						const uint ldqc = p_local_dev_queue->getNumItems();
						if(ldqc >= static_cast<uint>(rS.LocalDevPtCount)) {
							assert(ldqc == static_cast<uint>(rS.LocalDevPtCount));
							StatBase temp_stat_local_dev(0);
#ifndef NDEBUG
							double ldq_values[64];
							memzero(ldq_values, sizeof(ldq_values));
#endif // NDEBUG
							for(uint k = 0; k < ldqc; k++) {
								const double qv = p_local_dev_queue->get(k);
#ifndef NDEBUG
								{
									ldq_values[k] = qv;
									const void * p_temp_value_buf = p_vec->at(i-rS.LocalDevPtCount+1+k);
									double temp_value = p_vec->ConvertInnerToDouble(p_temp_value_buf);
									assert(temp_value == qv);
								}
#endif // NDEBUG
								temp_stat_local_dev.Step(qv);
							}
							temp_stat_local_dev.Finish();
							if(temp_stat_local_dev.GetMax() == temp_stat_local_dev.GetMin())
								stat_local_dev.Step(0.0);
							else {
								const double tsld_stddev = temp_stat_local_dev.GetStdDev();
								if(feqeps(tsld_stddev, 0.0, 1E-10))
									stat_local_dev.Step(0.0);
								else {
									assert(tsld_stddev >= 0.0);
									stat_local_dev.Step(tsld_stddev);
								}
							}
						}
					}
				}
				prev_value = value;
			}
			prev_utm = utm;
		}
		stat_delta.Finish();
		stat_local_dev.Finish(); // @v10.7.1
		rS.Finish();
		rS.DeltaAvg = stat_delta.GetExp();
		rS.LocalDevAvg = stat_local_dev.GetExp(); // @v10.7.1
		/* @construction
		{
			struct MoveListBlock {
				MoveListBlock(uint step) : Step(step)
				{
					const double zero = 0.0;
					for(uint i = 0; i < Step; i++) {
						List.insert(&zero);
					}
				}
				void  Put(uint rawIdx, double value)
				{
				}
				const uint Step;
				RealArray List;
			};
			RealArray move_list_s60;
			RealArray move_list_s720;
			RealArray move_list_s1440;
			RealArray move_list_s2880;
			uint   last_pt_s60 = 0;
			uint   last_pt_s720 = 0;
			uint   last_pt_s1440 = 0;
			for(uint j = 0; j < value_list.getCount(); j++) {
			}
		}
		*/
	}
	CATCHZOK
	delete p_local_dev_queue;
	return ok;
}

int STimeSeries::Analyze(const char * pVecSymb, Stat & rS) const
{
	return Analyze(pVecSymb, 0, GetCount(), rS);
}

int STimeSeries::RemoveItem(uint idx)
{
	int    ok = -1;
	if(idx < GetCount()) {
		for(uint vec_idx = 0; vec_idx < VL.getCount(); vec_idx++) {
			ValuVec * p_vec = VL.at(vec_idx);
			if(p_vec) {
				THROW(p_vec->getCount() == GetCount());
				p_vec->atFree(idx);
			}
		}
		T.atFree(idx);
		ok = 1;
	}
	CATCHZOK
	return ok;
}

int STimeSeries::Repair(const char * pCriticalVecSymb)
{
	int    ok = -1;
	const  uint _c = GetCount();
	if(_c) {
		LongArray pos_to_remove;
		Sort();
		SUniTime prev_utm;
		SUniTime utm;
		double value = 0.0;
		double prev_value = 0.0;
		STimeSeries::ValuVec * p_vec = 0;
		uint   vec_idx = 0;
		if(!isempty(pCriticalVecSymb)) {
			THROW(p_vec = GetVecBySymb(pCriticalVecSymb, &vec_idx));
		}
		for(uint i = 0; i < _c; i++) {
			THROW(GetTime(i, &utm));
			if(p_vec) {
				const void * p_value_buf = p_vec->at(i);
				value = p_vec->ConvertInnerToDouble(p_value_buf);
			}
			if(i) {
				int   sq = 0;
				int   si = utm.Compare(prev_utm, &sq);
				if(si == 0 && sq == SUniTime::cmprSureTrue) {
					/*if(p_vec) {
						assert(feqeps(value, prev_value, 1e-6));
					}*/
					pos_to_remove.add(i);
				}
			}
			prev_utm = utm;
			prev_value = value;
		}
		{
			uint rp = pos_to_remove.getCount();
			if(rp) do {
				const uint idx_to_remove = static_cast<const uint>(pos_to_remove.get(--rp));
				THROW(RemoveItem(idx_to_remove));
				ok = 1;
			} while(rp);
		}
	}
	CATCHZOK
	return ok;
}

STimeSeries::AnalyzeFitParam::AnalyzeFitParam(uint distance, uint firstIdx, uint count) : Distance(distance), FirstIdx(firstIdx), IdxCount(count), Flags(0)
{
}

/*static*/int STimeSeries::AnalyzeFit(const DateTimeArray * pTimeVec, const RealArray & rData, const AnalyzeFitParam & rP, RealArray * pTrendList,
	RealArray * pSumSqList, RealArray * pCov00, RealArray * pCov01, RealArray * pCov11)
{
	CALLPTRMEMB(pTrendList, clear());
	CALLPTRMEMB(pSumSqList, clear());
	CALLPTRMEMB(pCov00, clear());
	CALLPTRMEMB(pCov01, clear());
	CALLPTRMEMB(pCov11, clear());
	int    ok = 1;
	{
		const  uint _c = rData.getCount();
		pTimeVec = 0; // @debug С целью сравнить результаты
		assert(!pTimeVec || pTimeVec->getCount() == _c); // @v11.1.11
		if(_c && rP.FirstIdx < _c) {
			const  uint ic = (rP.IdxCount == 0) ? _c : MIN(rP.FirstIdx + rP.IdxCount, _c);
			const  uint distance = rP.Distance;
			RealArray lss_rv_x;
			//RealArray lss_rv_y;
			const size_t chunk_size = 128;
			double trend_chunk[chunk_size];
			double sumsq_chunk[chunk_size];
			double cov00_chunk[chunk_size];
			double cov01_chunk[chunk_size];
			double cov11_chunk[chunk_size];
			size_t chunk_p = 0;
			if(!pTimeVec) {
				for(uint i = 0; i < distance; i++) {
					THROW(lss_rv_x.add(static_cast<double>(i+1)));
				}
			}
			assert(_c == ic);
			for(uint j = rP.FirstIdx; j < ic; j++) {
				double trend;
				double sumsq;
				double cov00, cov01, cov11;
				if(j >= distance) {
					LssLin lss;
					const uint _first_idx = j-distance+1;
					if(pTimeVec) { // @construction
						lss_rv_x.clear();
						const LDATETIME first_dtm = pTimeVec->at(_first_idx);
						for(uint i = _first_idx; i < _first_idx+distance; i++) {
							const LDATETIME dtm = pTimeVec->at(i);
							double _xval = static_cast<double>(diffdatetimesec(dtm, first_dtm));
							THROW(lss_rv_x.add(_xval));
						}
					}
					assert(lss_rv_x.getCount() == distance);
					lss.Solve(distance, static_cast<const double *>(lss_rv_x.dataPtr()), &rData.at(_first_idx));
					trend = lss.B;
					sumsq = lss.SumSq;
					cov00 = lss.Cov00;
					cov01 = lss.Cov01;
					cov11 = lss.Cov11;
				}
				else {
					trend = 0.0;
					sumsq = 0.0;
					cov00 = 0.0;
					cov01 = 0.0;
					cov11 = 0.0;
				}
				if(chunk_p >= chunk_size) {
					if(pTrendList) { THROW(pTrendList->insertChunk(chunk_p, trend_chunk)); }
					if(pSumSqList) { THROW(pSumSqList->insertChunk(chunk_p, sumsq_chunk)); }
					if(pCov00) { THROW(pCov00->insertChunk(chunk_p, cov00_chunk)); }
					if(pCov01) { THROW(pCov01->insertChunk(chunk_p, cov01_chunk)); }
					if(pCov11) { THROW(pCov11->insertChunk(chunk_p, cov11_chunk)); }
					chunk_p = 0;
				}
				trend_chunk[chunk_p] = trend;
				sumsq_chunk[chunk_p] = sumsq;
				cov00_chunk[chunk_p] = cov00;
				cov01_chunk[chunk_p] = cov01;
				cov11_chunk[chunk_p] = cov11;
				chunk_p++;
			}
			if(chunk_p) {
				if(pTrendList) { THROW(pTrendList->insertChunk(chunk_p, trend_chunk)); }
				if(pSumSqList) { THROW(pSumSqList->insertChunk(chunk_p, sumsq_chunk)); }
				if(pCov00) { THROW(pCov00->insertChunk(chunk_p, cov00_chunk)); }
				if(pCov01) { THROW(pCov01->insertChunk(chunk_p, cov01_chunk)); }
				if(pCov11) { THROW(pCov11->insertChunk(chunk_p, cov11_chunk)); }
			}
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::AnalyzeFit(const char * pVecSymb, const AnalyzeFitParam & rP, RealArray * pTrendList, RealArray * pSumSqList,
	RealArray * pCov00, RealArray * pCov01, RealArray * pCov11) const
{
	CALLPTRMEMB(pTrendList, clear());
	CALLPTRMEMB(pSumSqList, clear());
	CALLPTRMEMB(pCov00, clear());
	CALLPTRMEMB(pCov01, clear());
	CALLPTRMEMB(pCov11, clear());
	int    ok = 1;
	uint   vec_idx = 0;
	STimeSeries::ValuVec * p_vec = GetVecBySymb(pVecSymb, &vec_idx);
	THROW(p_vec);
	{
		const  uint _c = GetCount();
		if(_c && rP.FirstIdx < _c) {
			const  uint ic = (rP.IdxCount == 0) ? _c : MIN(rP.FirstIdx + rP.IdxCount, _c);
			RealArray lss_rv_y;
			DateTimeArray lss_tv_x;
			int gtr = GetTimeArray(0, ic, lss_tv_x); // @v11.1.11
			int gvr = GetRealArray(vec_idx, 0, ic, lss_rv_y);
			assert(lss_tv_x.getCount() == lss_rv_y.getCount()); // @v11.1.11
			THROW(STimeSeries::AnalyzeFit(&lss_tv_x, lss_rv_y, rP, pTrendList, pSumSqList, pCov00, pCov01, pCov11));
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::Helper_GetFrame(const ValuVec * pVec, uint startIdx, uint count, double diffScale, long normFlags, RealArray & rList) const
{
	rList.clear();
	int    ok = 1;
	const  bool is_diff_scale = (diffScale != 0.0);
	if(pVec && startIdx < pVec->getCount()) {
		const uint last_idx = MIN(startIdx+count, pVec->getCount());
		if(normFlags == 0) {
			for(uint i = startIdx; i < last_idx; i++) {
				const void * p_value_buf = pVec->at(i);
				double value = pVec->ConvertInnerToDouble(p_value_buf);
				THROW(rList.insert(&value));
			}
		}
		else {
			double base = 0.0;
			StatBase sb(StatBase::fStoreVals);
			for(uint i = startIdx; i < last_idx; i++) {
				const void * p_value_buf = pVec->at(i);
				double value = pVec->ConvertInnerToDouble(p_value_buf);
				sb.Step(value);
			}
			sb.Finish();
			const RealArray & r_sb_ser = sb.GetSeries();
			assert(r_sb_ser.getCount() == (last_idx-startIdx));
			if(normFlags & nfBaseAvg)
				base = sb.GetExp();
			else {
				base = r_sb_ser.at(0);
			}
			if(base == 0.0 && !(normFlags & nfZero)) {
			}
			else {
				for(uint j = 0; j < r_sb_ser.getCount(); j++) {
					double ex_value = r_sb_ser.at(j);
					double value;
					if(normFlags & nfZero) {
						value = ex_value - base;
						if(is_diff_scale)
							value *= diffScale;
					}
					else {
						value = ex_value / base;
					}
					THROW(rList.insert(&value));
				}
			}
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::GetFrame(const char * pVecSymb, uint startIdx, uint count, long normFlags, RealArray & rList) const
	{ return Helper_GetFrame(GetVecBySymb(pVecSymb, 0), startIdx, count, 0.0, normFlags, rList); }
int STimeSeries::GetFrame(const char * pVecSymb, uint startIdx, uint count, double diffScale, long normFlags, RealArray & rList) const
	{ return Helper_GetFrame(GetVecBySymb(pVecSymb, 0), startIdx, count, diffScale, normFlags, rList); }
int STimeSeries::GetFrame(uint vecIdx, uint startIdx, uint count, long normFlags, RealArray & rList) const
	{ return Helper_GetFrame(GetVecByIdx(vecIdx), startIdx, count, 0.0, normFlags, rList); }
int STimeSeries::GetFrame(uint vecIdx, uint startIdx, uint count, double diffScale, long normFlags, RealArray & rList) const
	{ return Helper_GetFrame(GetVecByIdx(vecIdx), startIdx, count, diffScale, normFlags, rList); }

int STimeSeries::GetChunkRecentCount(uint count, STimeSeries & rResult) const
{
	int    ok = -1;
	THROW(rResult.SetupBySample(this));
	if(count) {
		const  uint tsc = GetCount();
		if(tsc >= count) {
			for(uint i = (tsc-count); i < tsc; i++) {
				THROW(rResult.AddItemFromSample(*this, i));
			}
			ok = 1;
		}
	}
	CATCHZOK
	return ok;
}

int STimeSeries::GetChunkRecentSince(const SUniTime & rSince, const SUniTime * pTill, STimeSeries & rResult) const
{
	int    ok = -1;
	uint   first_idx = 0;
	THROW(rResult.SetupBySample(this));
	if(SearchFirstEntrySince(rSince, &first_idx) > 0) {
		const  uint tsc = GetCount();
		for(uint i = first_idx; i < tsc; i++) {
			if(pTill) {
				const SUniTime & r_ut = T.at(i);
				int    sq;
				int    si = r_ut.Compare(*pTill, &sq);
				if(si <= 0 && oneof2(sq, SUniTime::cqSure, SUniTime::cqUncertain)) {
					THROW(rResult.AddItemFromSample(*this, i));
				}
			}
			else { // } @v10.7.0
				THROW(rResult.AddItemFromSample(*this, i));
			}
		}
		ok = 1;
	}
	CATCHZOK
	return ok;
}
#if SLTEST_RUNNING // {

SLTEST_R(STimeSeries)
{
	int    ok = 1;
	SString temp_buf;
	SString src_file_name;
	SString test_file_name;
	SLS.QueryPath("testroot", src_file_name);
	src_file_name.SetLastSlash().Cat("data").SetLastSlash().Cat("ts-eurusd.csv");
	SLS.QueryPath("testroot", test_file_name);
	test_file_name.SetLastSlash().Cat("out").SetLastSlash().Cat("ts-eurusd.out");
	SFile f_in(src_file_name, SFile::mRead);
	if(f_in.IsValid()) {
		SString line_buf;
		StringSet ss_in(",");
		STimeSeries ts;

		LDATETIME dtm;
		double open = 0.0;
		double close = 0.0;
		long   tick_vol = 0;
		long   real_vol = 0;
		long   spread = 0;

		uint   vecidx_open = 0;
		uint   vecidx_close = 0;
		uint   vecidx_ticvol = 0;
		uint   vecidx_realvol = 0;
		uint   vecidx_spread = 0;
		THROW(SLTEST_CHECK_NZ(ts.AddValueVec("open", T_INT32, 5, &vecidx_open)));
		THROW(SLTEST_CHECK_NZ(ts.AddValueVec("close", T_INT32, 5, &vecidx_close)));
		THROW(SLTEST_CHECK_NZ(ts.AddValueVec("tick_volume", T_INT32, 0, &vecidx_ticvol)));
		THROW(SLTEST_CHECK_NZ(ts.AddValueVec("real_volume", T_INT32, 0, &vecidx_realvol)));
		THROW(SLTEST_CHECK_NZ(ts.AddValueVec("spread", T_INT32, 0, &vecidx_spread)));
		{
			uint8 sign[8];
			size_t actual_size = 0;
			if(f_in.Read(sign, 4, &actual_size) && actual_size == 4) {
				if(sign[0] == 0xEF && sign[1] == 0xBB && sign[2] == 0xBF)
					f_in.Seek(3);
				else
					f_in.Seek(0);
			}
		}
		while(f_in.ReadLine(line_buf)) {
			line_buf.Chomp().Strip();
			if(line_buf.NotEmpty()) {
				ss_in.setBuf(line_buf);
				dtm.Z();
				open = 0.0;
				close = 0.0;
				tick_vol = 0;
				real_vol = 0;
				spread = 0;
				for(uint ssp = 0, fldn = 0; ss_in.get(&ssp, temp_buf); fldn++) {
					switch(fldn) {
						case 0: strtodate(temp_buf, DATF_YMD, &dtm.d); break;
						case 1: strtotime(temp_buf, TIMF_HMS, &dtm.t); break;
						case 2: open = temp_buf.ToReal(); break;
						case 3: close = temp_buf.ToReal(); break;
						case 4: tick_vol = temp_buf.ToLong(); break;
						case 5: real_vol = temp_buf.ToLong(); break;
						case 6: spread = temp_buf.ToLong(); break;
					}
				}
				if(checkdate(&dtm) && close > 0.0) {
					SUniTime ut;
					ut.Set(dtm, SUniTime::indMin);
					uint   item_idx = 0;
					THROW(SLTEST_CHECK_NZ(ts.AddItem(ut, &item_idx)));
					THROW(SLTEST_CHECK_NZ(ts.SetValue(item_idx, vecidx_open, open)));
					THROW(SLTEST_CHECK_NZ(ts.SetValue(item_idx, vecidx_close, close)));
					THROW(SLTEST_CHECK_NZ(ts.SetValue(item_idx, vecidx_ticvol, tick_vol)));
					THROW(SLTEST_CHECK_NZ(ts.SetValue(item_idx, vecidx_realvol, real_vol)));
					THROW(SLTEST_CHECK_NZ(ts.SetValue(item_idx, vecidx_spread, spread)));
				}
			}
		}
		{
			STimeSeries dts;
			SBuffer sbuf; // serialize buf
			SBuffer cbuf; // compress buf
			SBuffer dbuf; // decompress buf
			SSerializeContext sctx;
			THROW(SLTEST_CHECK_NZ(ts.Serialize(+1, sbuf, &sctx)));
			{
				{
					SCompressor c(SCompressor::tZLib);
					THROW(SLTEST_CHECK_NZ(c.CompressBlock(sbuf.GetBuf(sbuf.GetRdOffs()), sbuf.GetAvailableSize(), cbuf, 0, 0)));
				}
				{
					SCompressor c(SCompressor::tZLib);
					THROW(SLTEST_CHECK_NZ(c.DecompressBlock(cbuf.GetBuf(cbuf.GetRdOffs()), cbuf.GetAvailableSize(), dbuf)));
				}
				SLTEST_CHECK_EQ(sbuf.GetAvailableSize(), dbuf.GetAvailableSize());
				SLTEST_CHECK_Z(memcmp(sbuf.GetBuf(sbuf.GetRdOffs()), dbuf.GetBuf(dbuf.GetRdOffs()), sbuf.GetAvailableSize()));
				THROW(SLTEST_CHECK_NZ(dts.Serialize(-1, dbuf, &sctx)));
			}
			{
				SFile f_out(test_file_name, SFile::mWrite);
				THROW(SLTEST_CHECK_NZ(f_out.IsValid()));
				for(uint i = 0; i < dts.GetCount(); i++) {
					SUniTime ut;
					dts.GetTime(i, &ut);
					ut.Get(dtm);
					THROW(SLTEST_CHECK_NZ(dts.GetValue(i, vecidx_open, &open)));
					THROW(SLTEST_CHECK_NZ(dts.GetValue(i, vecidx_close, &close)));
					THROW(SLTEST_CHECK_NZ(dts.GetValue(i, vecidx_ticvol, &tick_vol)));
					THROW(SLTEST_CHECK_NZ(dts.GetValue(i, vecidx_realvol, &real_vol)));
					THROW(SLTEST_CHECK_NZ(dts.GetValue(i, vecidx_spread, &spread)));
					line_buf.Z().Cat(dtm.d, DATF_ANSI|DATF_CENTURY).Comma().Cat(dtm.t, TIMF_HM).Comma().
						Cat(open, MKSFMTD(0, 5, 0)).Comma().Cat(close, MKSFMTD(0, 5, 0)).Comma().Cat(tick_vol).Comma().Cat(real_vol).Comma().Cat(spread).CR();
					THROW(SLTEST_CHECK_NZ(f_out.WriteLine(line_buf)));
				}
				f_out.Close();
				SLTEST_CHECK_LT(0L, SFile::Compare(src_file_name, test_file_name, 0));
			}
		}
	}
	CATCHZOK
	return ok;
}

#endif // } SLTEST_RUNNING


pbitset expand_and(pbitset b) const noexcept
		{
			pbitset ret;
			T v( m_bs);

			for (size_t i = 0; v != 0; v = v >> 1, ++i)
			{
				if (v & 1)
				{
					if (b.test(0))
						ret.set(i);
					b = b >> 1;
				}
			}
			return ret;
		}

// copyright-holders:Couriersud

#include "plib/pstonum.h"
#include "plib/pstrutil.h"

#include "nl_base.h"
#include "nl_factory.h"
#include "nlid_truthtable.h"

#include <bitset>
#include <cstdint>
#include <vector>

namespace netlist::devices {

	template<std::size_t m_NI, std::size_t m_NO>
	class NETLIB_NAME(truthtable_t) : public device_t
	{
	public:

		using type_t = typename plib::fast_type_for_bits<m_NO + m_NI>::type;

		static constexpr const std::size_t m_num_bits = m_NI;
		static constexpr const std::size_t m_size = (1 << (m_num_bits));
		static constexpr const type_t m_outmask = ((1 << m_NO) - 1);

		struct truthtable_t
		{
			truthtable_t()
			: m_timing_index{0}
			{}

			std::array<type_t, m_size> m_out_state;
			std::array<uint_least8_t, m_size * m_NO> m_timing_index;
			std::array<netlist_time, 16> m_timing_nt;
		};

		template <class C>
		nld_truthtable_t(C &owner, const pstring &name,
				const pstring &model,
				truthtable_t &ttp, const std::vector<pstring> &desc)
		: device_t(owner, name, model)
#if USE_TT_ALTERNATIVE
		, m_state(*this, "m_state", 0)
#endif
		, m_ign(*this, "m_ign", 0)
		, m_ttp(ttp)
		/* FIXME: the family should provide the names of the power-terminals! */
		, m_power_pins(*this)
		{
			m_activate = activate_delegate(& NETLIB_NAME(truthtable_t) :: incdec_active, this);
			set_hint_deactivate(true);
			init(desc);
		}

	private:
		void init(const std::vector<pstring> &desc);

		NETLIB_RESETI()
		{
			int active_outputs = 0;
			m_ign = 0;
#if USE_TT_ALTERNATIVE
			m_state = 0;
#endif
			for (std::size_t i = 0; i < m_NI; ++i)
			{
				m_I[i].activate();
#if USE_TT_ALTERNATIVE
				m_state |= (m_I[i]() << i);
#endif
			}
			for (auto &q : m_Q)
				if (q.has_net() && !exec().nlstate().core_terms(q.net()).empty())
					active_outputs++;
			set_active_outputs(active_outputs);
		}

		NETLIB_HANDLERI(inputs)
		{
#if USE_TT_ALTERNATIVE
			m_state = 0;
			for (std::size_t i = 0; i < m_NI; ++i)
			{
				m_state |= (m_I[i]() << i);
			}
#endif
			process<true>();
		}

#if USE_TT_ALTERNATIVE
		template <std::size_t N>
		void update_N() noexcept
		{
			m_state &= ~(1<<N);
			m_state |= (m_I[N]() << N);
			process<true>();
		}
#endif

		void incdec_active(bool a) noexcept
		{
			if (a)
			{
				process<false>();
			}
			else
			{
				for (std::size_t i = 0; i< m_NI; i++)
					m_I[i].inactivate();
				m_ign = (1<<m_NI)-1;
			}
		}

		template<bool doOUT>
		void process() noexcept
		{
			netlist_time_ext mt(netlist_time_ext::zero());
			type_t nstate(0);
			type_t ign(m_ign);

			if (doOUT)
			{
#if !USE_TT_ALTERNATIVE
				for (auto I = m_I.begin(); ign != 0; ign >>= 1, ++I)
					if (ign & 1)
						I->activate();
				for (std::size_t i = 0; i < m_NI; i++)
					nstate |= (m_I[i]() << i);
#else
				nstate = m_state;
				for (std::size_t i = 0; ign != 0; ign >>= 1, ++i)
				{
					if (ign & 1)
					{
						nstate &= ~(1 << i);
						m_I[i].activate();
						nstate |= (m_I[i]() << i);
					}
				}
#endif
			}
			else
				for (std::size_t i = 0; i < m_NI; i++)
				{
					m_I[i].activate();
					nstate |= (m_I[i]() << i);
					mt = std::max(this->m_I[i].net().next_scheduled_time(), mt);
				}

			const type_t outstate(m_ttp.m_out_state[nstate]);
			type_t out(outstate & m_outmask);

			m_ign = outstate >> m_NO;

			const auto *t(&m_ttp.m_timing_index[nstate * m_NO]);

			if (doOUT)
				this->push(out, t);
			else
			{
				const auto *tim = m_ttp.m_timing_nt.data();
				for (std::size_t i = 0; i < m_NO; ++i)
					m_Q[i].set_Q_time((out >> i) & 1, mt + tim[t[i]]);
			}

			ign = m_ign;
			for (auto I = m_I.begin(); ign != 0; ign >>= 1, ++I)
				if (ign & 1)
					I->inactivate();
#if USE_TT_ALTERNATIVE
			m_state = nstate;
#endif
		}

		template<typename T>
		void push(const T &v, const std::uint_least8_t * t)
		{
			if (m_NO >= 1) m_Q[0].push((v >> 0) & 1, m_ttp.m_timing_nt[t[0]]);
			if (m_NO >= 2) m_Q[1].push((v >> 1) & 1, m_ttp.m_timing_nt[t[1]]);
			if (m_NO >= 3) m_Q[2].push((v >> 2) & 1, m_ttp.m_timing_nt[t[2]]);
			if (m_NO >= 4) m_Q[3].push((v >> 3) & 1, m_ttp.m_timing_nt[t[3]]);
			if (m_NO >= 5) m_Q[4].push((v >> 4) & 1, m_ttp.m_timing_nt[t[4]]);
			if (m_NO >= 6) m_Q[5].push((v >> 5) & 1, m_ttp.m_timing_nt[t[5]]);
			if (m_NO >= 7) m_Q[6].push((v >> 6) & 1, m_ttp.m_timing_nt[t[6]]);
			if (m_NO >= 8) m_Q[7].push((v >> 7) & 1, m_ttp.m_timing_nt[t[7]]);
			for (std::size_t i = 8; i < m_NO; i++)
				m_Q[i].push((v >> i) & 1, m_ttp.m_timing_nt[t[i]]);
		}

		plib::static_vector<logic_input_t, m_NI> m_I;
		plib::static_vector<logic_output_t, m_NO> m_Q;

#if USE_TT_ALTERNATIVE
		state_var<type_t>   m_state;
#endif
		state_var<type_t>   m_ign;
		const truthtable_t  m_ttp;
		/* FIXME: the family should provide the names of the power-terminals! */
		nld_power_pins m_power_pins;
	};

	template <typename T>
	struct pbitset
	{
		using type = T;

		pbitset() noexcept : m_bs(0) { }
		pbitset(T v) noexcept : m_bs(v) { }

		pbitset &set() noexcept { *this = all_bits(); return *this; }
		pbitset &set(std::size_t bit) noexcept { m_bs |= (static_cast<T>(1) << bit); return *this; }
		pbitset &reset() noexcept { *this = no_bits(); return *this; }
		pbitset &reset(std::size_t bit) noexcept { m_bs &= ~(static_cast<T>(1) << bit); return *this; }

		pbitset flip() const noexcept { return pbitset(~m_bs); }
		pbitset flip(std::size_t bit) const noexcept { return pbitset(m_bs ^ (static_cast<T>(1) << bit)); }

		std::size_t count() const noexcept
		{
			std::size_t ret(0);
			for (T v = m_bs; v != 0; v = v >> 1)
			{
				ret += (v & 1);
			}
			return ret;
		}
		constexpr bool test(const std::size_t bit) const { return ((m_bs >> bit) & 1) == 1; }

		operator T&() noexcept  { return m_bs; }
		operator const T&() const noexcept { return m_bs; }
		constexpr T as_uint() const noexcept { return m_bs; }

		constexpr bool all() const noexcept { return *this == all_bits(); }

		/// \brief And all bits set with compressed bits from b
		/// \code
		/// \endcode
		/// \returns pbitset

		static constexpr pbitset all_bits() noexcept { return pbitset(~static_cast<T>(0)); }
		static constexpr pbitset no_bits() noexcept{ return pbitset(static_cast<T>(0)); }
	private:
		T m_bs;
	};

	using tt_bitset = pbitset<std::uint_least64_t>;

	struct packed_int
	{
		packed_int(void *data, std::size_t bits) noexcept
		: m_data(data)
		, m_size(bits)
		{}

		void set(size_t pos, std::uint_least64_t val) noexcept
		{
			switch (m_size)
			{
				case 8: static_cast<std::uint_least8_t  *>(m_data)[pos] = static_cast<std::uint_least8_t>(val); break;
				case 16: static_cast<std::uint_least16_t *>(m_data)[pos] = static_cast<std::uint_least16_t>(val); break;
				case 32: static_cast<std::uint_least32_t *>(m_data)[pos] = static_cast<std::uint_least32_t>(val); break;
				case 64: static_cast<std::uint_least64_t *>(m_data)[pos] = static_cast<std::uint_least64_t>(val); break;
				default: { }
			}
		}

		std::uint_least64_t operator[] (size_t pos) const noexcept
		{
			switch (m_size)
			{
				case 8: return static_cast<std::uint_least8_t  *>(m_data)[pos];
				case 16: return static_cast<std::uint_least16_t *>(m_data)[pos];
				case 32: return static_cast<std::uint_least32_t *>(m_data)[pos];
				case 64: return static_cast<std::uint_least64_t *>(m_data)[pos];
				default:
					return 0; //should never happen
			}
		}

		std::uint_least64_t mask() const noexcept
		{
			switch (m_size)
			{
				case 8: return static_cast<std::uint_least8_t>(-1);
				case 16: return static_cast<std::uint_least16_t>(-1);
				case 32: return static_cast<std::uint_least32_t>(-1);
				case 64: return static_cast<std::uint_least64_t>(-1);
				default:
					return 0; //should never happen
			}
		}
	private:
		void *m_data;
		size_t m_size;
	};

	struct truthtable_parser
	{
		truthtable_parser(unsigned NO, unsigned NI,
				packed_int outs, uint_least8_t *timing, netlist_time *timing_nt)
		: m_NO(NO)
		, m_NI(NI)
		, m_out_state(outs)
		, m_timing(timing)
		, m_timing_nt(timing_nt)
		, m_num_bits(m_NI)
		, m_size(1 << (m_num_bits))
		{
		}

		void parse(const std::vector<pstring> &truthtable);

	private:
		void parseline(unsigned cur, std::vector<pstring> list,
				tt_bitset state, std::uint_least64_t val, std::vector<uint_least8_t> &timing_index);

		tt_bitset calculate_ignored_inputs(tt_bitset state) const;

		unsigned m_NO;
		unsigned m_NI;
		packed_int m_out_state;
		uint_least8_t  *m_timing;
		netlist_time *m_timing_nt;

		const std::size_t m_num_bits;
		const std::size_t m_size;

	};

	template<std::size_t m_NI, std::size_t m_NO>
	void NETLIB_NAME(truthtable_t)<m_NI, m_NO>::init(const std::vector<pstring> &desc)
	{
		pstring header = desc[0];

		std::vector<pstring> io(plib::psplit(header,'|'));
		// checks
		nl_assert_always(io.size() == 2, "too many '|'");
		std::vector<pstring> inout(plib::psplit(io[0], ','));
		nl_assert_always(inout.size() == m_num_bits, "bitcount wrong");
		std::vector<pstring> outputs(plib::psplit(io[1], ','));
		nl_assert_always(outputs.size() == m_NO, "output count wrong");

#if !USE_TT_ALTERNATIVE
		for (std::size_t i=0; i < m_NI; i++)
		{
			inout[i] = plib::trim(inout[i]);
			m_I.emplace_back(*this, inout[i], nldelegate(&NETLIB_NAME(truthtable_t)<m_NI, m_NO> :: inputs, this));
		}
#else
		for (std::size_t i=0; i < m_NI; i++)
		{
			inout[i] = plib::trim(inout[i]);
		}
		if (0 < m_NI) m_I.emplace(0, *this, inout[0]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<0>, this));
		if (1 < m_NI) m_I.emplace(1, *this, inout[1]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<1>, this));
		if (2 < m_NI) m_I.emplace(2, *this, inout[2]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<2>, this));
		if (3 < m_NI) m_I.emplace(3, *this, inout[3]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<3>, this));
		if (4 < m_NI) m_I.emplace(4, *this, inout[4]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<4>, this));
		if (5 < m_NI) m_I.emplace(5, *this, inout[5]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<5>, this));
		if (6 < m_NI) m_I.emplace(6, *this, inout[6]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<6>, this));
		if (7 < m_NI) m_I.emplace(7, *this, inout[7]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<7>, this));
		if (8 < m_NI) m_I.emplace(8, *this, inout[8]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<8>, this));
		if (9 < m_NI) m_I.emplace(9, *this, inout[9]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<9>, this));
		if (10 < m_NI) m_I.emplace(10, *this, inout[10]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<10>, this));
		if (11 < m_NI) m_I.emplace(11, *this, inout[11]); //, nldelegate(&nld_truthtable_t<m_NI, m_NO>::update_N<11>, this));
#endif
		for (std::size_t i=0; i < m_NO; i++)
		{
			outputs[i] = plib::trim(outputs[i]);
			m_Q.emplace_back(*this, outputs[i]);
			// Connect output "Q" to input "_Q" if this exists
			// This enables timed state without having explicit state ....
			pstring tmp = "_" + outputs[i];
			const std::size_t idx = plib::container::indexof(inout, tmp);
			if (idx != plib::container::npos)
				connect(m_Q[i], m_I[idx]);
		}
		m_ign = 0;
	}

	template<unsigned m_NI, unsigned m_NO>
	class netlist_factory_truthtable_t : public factory::truthtable_base_element_t
	{
	public:
		netlist_factory_truthtable_t(const pstring &name,
			factory::properties &&props)
		: truthtable_base_element_t(name, std::move(props))
		{ }

		device_arena::unique_ptr<core_device_t> make_device(device_arena &pool, netlist_state_t &anetlist, const pstring &name) override
		{
			using tt_type = nld_truthtable_t<m_NI, m_NO>;

			if (!m_ttbl)
			{
				m_ttbl = plib::make_unique<typename nld_truthtable_t<m_NI, m_NO>::truthtable_t>(pool);
				truthtable_parser desc_s(m_NO, m_NI,
						packed_int(m_ttbl->m_out_state.data(), sizeof(m_ttbl->m_out_state[0]) * 8),
						m_ttbl->m_timing_index.data(), m_ttbl->m_timing_nt.data());

				desc_s.parse(m_desc);
			}

			return plib::make_unique<tt_type>(pool, anetlist, name, m_family_name, *m_ttbl, m_desc);
		}
	private:
		device_arena::unique_ptr<typename nld_truthtable_t<m_NI, m_NO>::truthtable_t> m_ttbl;
	};

	tt_bitset truthtable_parser::calculate_ignored_inputs(tt_bitset state) const
	{
		// Determine all inputs which may be ignored ...
		tt_bitset ignore = 0;
		for (std::size_t j=0; j<m_NI; j++)
		{
			// if changing the input directly doesn't change outputs we can ignore
			if (m_out_state[state] == m_out_state[tt_bitset(state).flip(j)])
				ignore.set(j);
		}

		// Check all permutations of ign
		// We have to remove those where the ignored inputs
		// may change the output
		tt_bitset bits = tt_bitset().set(ignore.count());

		std::vector<bool> t(bits);

		// loop over all combinations of bits set in ignore
		for (std::uint_least64_t j = 1; j < bits; j++)
		{
			tt_bitset tign = ignore.expand_and(j);
			t[j] = false;
			tt_bitset bitsk(tt_bitset().set(tign.count()));

			// now loop over all combinations of the bits set currently set

			for (std::uint_least64_t k=0; k < bitsk; k++)
			{
				tt_bitset b = tign.expand_and(k);
				// will any of the inputs ignored change the output if changed?
				if (m_out_state[state] != m_out_state[state ^ b])
				{
					t[j] = true;
					break;
				}
			}
		}

		// find the ignore mask without potential for change with the most bits

		size_t jb(0);
		tt_bitset jm(0);

		for (std::uint_least64_t j=1; j<bits; j++)
		{
			tt_bitset bj(j);
			size_t nb(bj.count());
			if (!t[j] && (nb > jb))
			{
				jb = nb;
				jm = bj;
			}
		}
		return ignore.expand_and(jm);
	}

	// parseline

	void truthtable_parser::parseline(unsigned cur, std::vector<pstring> list,
			tt_bitset state, std::uint_least64_t val, std::vector<uint_least8_t> &timing_index)
	{
		pstring elem = plib::trim(list[cur]);
		std::uint_least64_t start = 0;
		std::uint_least64_t end = 0;

		if (elem == "0")
		{
			start = 0;
			end = 0;
		}
		else if (elem == "1")
		{
			start = 1;
			end = 1;
		}
		else if (elem == "X")
		{
			start = 0;
			end = 1;
		}
		else
			nl_assert_always(false, "unknown input value (not 0, 1, or X)");
		for (std::uint_least64_t i = start; i <= end; i++)
		{
			tt_bitset nstate = state;
			if (i==1)
				nstate.set(cur);

			if (cur < m_num_bits - 1)
			{
				parseline(cur + 1, list, nstate, val, timing_index);
			}
			else
			{
				// cutoff previous inputs and outputs for ignore
				if (m_out_state[nstate] != m_out_state.mask() &&  m_out_state[nstate] != val)
					throw nl_exception(plib::pfmt("Error in truthtable: State {1:04} already set, {2} != {3}\n")
							.x(nstate.as_uint())(m_out_state[nstate])(val) );
				m_out_state.set(nstate, val);
				for (std::size_t j=0; j<m_NO; j++)
					m_timing[nstate * m_NO + j] = timing_index[j];
			}
		}
	}

	void truthtable_parser::parse(const std::vector<pstring> &truthtable)
	{
		unsigned line = 0;

		pstring ttline(truthtable[line]);
		line++;
		ttline = truthtable[line];
		line++;

		for (unsigned j=0; j < m_size; j++)
			m_out_state.set(j, tt_bitset::all_bits());

		for (int j=0; j < 16; j++)
			m_timing_nt[j] = netlist_time::zero();

		while (!ttline.empty())
		{
			std::vector<pstring> io(plib::psplit(ttline,'|'));
			// checks
			nl_assert_always(io.size() == 3, "io.count mismatch");
			std::vector<pstring> inout(plib::psplit(io[0], ','));
			nl_assert_always(inout.size() == m_num_bits, "number of bits not matching");
			std::vector<pstring> out(plib::psplit(io[1], ','));
			nl_assert_always(out.size() == m_NO, "output count not matching");
			std::vector<pstring> times(plib::psplit(io[2], ','));
			nl_assert_always(times.size() == m_NO, "timing count not matching");

			tt_bitset val = 0;
			std::vector<uint_least8_t> tindex;

			// FIXME: evaluation of outputs should be done in parseline to
			//        enable the use of inputs for output values, i.e. "I1" or "~I1"
			//  in addition to "0" and "1".

			for (unsigned j=0; j<m_NO; j++)
			{
				pstring outs = plib::trim(out[j]);
				if (outs == "1")
					val.set(j);
				else
					nl_assert_always(outs == "0", "Unknown value (not 0 or 1");
				// FIXME: error handling
				netlist_time t = netlist_time::from_nsec(plib::pstonum<std::int64_t>(plib::trim(times[j])));
				uint_least8_t k=0;
				while (m_timing_nt[k] != netlist_time::zero() && m_timing_nt[k] != t)
					k++;
				m_timing_nt[k] = t;
				tindex.push_back(k); //[j] = k;
			}

			parseline(0, inout, 0 , val, tindex);
			if (line < truthtable.size())
				ttline = truthtable[line];
			else
				ttline = "";
			line++;
		}

		// determine ignore mask by looping over all input combinations
		std::vector<tt_bitset> ign(m_size);
		for (tt_bitset &x : ign)
			x.set();

		for (std::uint_least64_t i=0; i < m_size; i++)
		{
			if (ign[i].all()) // not yet visited
			{
				tt_bitset tign = calculate_ignored_inputs(i);

				ign[i] = tign;

				// don't need to recalculate similar ones
				tt_bitset bitsk;
				bitsk.set(tign.count());

				for (std::uint_least64_t k=0; k < bitsk; k++)
				{
					tt_bitset b = tign.expand_and(k);
					ign[(i & tign.flip()) | b] = tign;
				}
			}
		}

		for (size_t i=0; i<m_size; i++)
		{
			if (m_out_state[i] == m_out_state.mask())
				throw nl_exception(plib::pfmt("truthtable: found element not set {1}\n").x(i) );
			m_out_state.set(i, m_out_state[i] | (ign[i] << m_NO));
		}
	}

} // namespace netlist::devices

namespace netlist::factory {

	truthtable_base_element_t::truthtable_base_element_t(const pstring &name,
		properties &&props)
	: factory::element_t(name, std::move(props))
	, m_family_name(config::DEFAULT_LOGIC_FAMILY())
	{
	}

	#define ENTRYY(n, m, s)    case (n * 100 + m): \
		{ using xtype = devices::netlist_factory_truthtable_t<n, m>; \
			auto cs=s; \
			ret = plib::make_unique<xtype, host_arena>(desc.name, std::move(cs)); } \
			break

	#define ENTRY(n, s) ENTRYY(n, 1, s); ENTRYY(n, 2, s); ENTRYY(n, 3, s); \
						ENTRYY(n, 4, s); ENTRYY(n, 5, s); ENTRYY(n, 6, s); \
						ENTRYY(n, 7, s); ENTRYY(n, 8, s); ENTRYY(n, 9, s); \
						ENTRYY(n, 10, s)

	host_arena::unique_ptr<truthtable_base_element_t> truthtable_create(tt_desc &desc, properties &&props)
	{
		host_arena::unique_ptr<truthtable_base_element_t> ret;

		switch (desc.ni * 100 + desc.no)
		{
			ENTRY(1, props);
			ENTRY(2, props);
			ENTRY(3, props);
			ENTRY(4, props);
			ENTRY(5, props);
			ENTRY(6, props);
			ENTRY(7, props);
			ENTRY(8, props);
			ENTRY(9, props);
			ENTRY(10, props);
			ENTRY(11, props);
			ENTRY(12, props);
			default:
				pstring msg = plib::pfmt("unable to create truthtable<{1},{2}>")(desc.ni)(desc.no);
				nl_assert_always(false, putf8string(msg).c_str());
		}
		ret->m_desc = desc.desc;
		ret->m_family_name = (!desc.family.empty() ? desc.family : pstring(config::DEFAULT_LOGIC_FAMILY()));

		return ret;
	}

} // namespace netlist::factory


WebSocketPerMessageDeflateCompressor::WebSocketPerMessageDeflateCompressor()
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        memset(&_deflateState, 0, sizeof(_deflateState));

        _deflateState.zalloc = Z_NULL;
        _deflateState.zfree = Z_NULL;
        _deflateState.opaque = Z_NULL;
#endif
    }

/*
 *  IXWebSocketPerMessageDeflateCodec.cpp
 *  Author: Benjamin Sergeant
 */

#include "IXWebSocketPerMessageDeflateCodec.h"

#include "IXWebSocketPerMessageDeflateOptions.h"
#include <cassert>
#include <string.h>

namespace
{
    // The passed in size (4) is important, without it the string litteral
    // is treated as a char* and the null termination (\x00) makes it
    // look like an empty string.
    const std::string kEmptyUncompressedBlock = std::string("\x00\x00\xff\xff", 4);
} // namespace

namespace ix
{
    // Compressor

    WebSocketPerMessageDeflateCompressor::~WebSocketPerMessageDeflateCompressor()
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        deflateEnd(&_deflateState);
#endif
    }

    bool WebSocketPerMessageDeflateCompressor::init(uint8_t deflateBits,
                                                    bool clientNoContextTakeOver)
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        int ret = deflateInit2(&_deflateState,
                               Z_DEFAULT_COMPRESSION,
                               Z_DEFLATED,
                               -1 * deflateBits,
                               4, // memory level 1-9
                               Z_DEFAULT_STRATEGY);

        if (ret != Z_OK) return false;

        _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;

        return true;
#else
        return false;
#endif
    }

    template<typename T>
    bool WebSocketPerMessageDeflateCompressor::endsWithEmptyUnCompressedBlock(const T& value)
    {
        if (kEmptyUncompressedBlock.size() > value.size()) return false;
        auto N = value.size();
        return value[N - 1] == kEmptyUncompressedBlock[3] &&
               value[N - 2] == kEmptyUncompressedBlock[2] &&
               value[N - 3] == kEmptyUncompressedBlock[1] &&
               value[N - 4] == kEmptyUncompressedBlock[0];
    }

    bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in, std::string& out)
    {
        return compressData(in, out);
    }

    bool WebSocketPerMessageDeflateCompressor::compress(const std::string& in,
                                                        std::vector<uint8_t>& out)
    {
        return compressData(in, out);
    }

    bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
                                                        std::string& out)
    {
        return compressData(in, out);
    }

    bool WebSocketPerMessageDeflateCompressor::compress(const std::vector<uint8_t>& in,
                                                        std::vector<uint8_t>& out)
    {
        return compressData(in, out);
    }

    template<typename T, typename S>
    bool WebSocketPerMessageDeflateCompressor::compressData(const T& in, S& out)
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        //    An endpoint uses the following algorithm to compress a message.
        //    1.  Compress all the octets of the payload of the message using
        //    2.  If the resulting data does not end with an empty DEFLATE block
        //        with no compression (the "BTYPE" bits are set to 00), append an
        //        empty DEFLATE block with no compression to the tail end.
        //    3.  Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end.
        //        After this step, the last octet of the compressed data contains
        //        (possibly part of) the DEFLATE header bits with the "BTYPE" bits
        size_t output;

        // Clear output
        out.clear();

        if (in.empty())
        {
            // The normal buffer size should be 6 but
            // we remove the 4 octets from the tail (#4)
            uint8_t buf[2] = {0x02, 0x00};
            out.push_back(buf[0]);
            out.push_back(buf[1]);

            return true;
        }

        _deflateState.avail_in = (uInt) in.size();
        _deflateState.next_in = (Bytef*) in.data();

        do
        {
            // Output to local buffer
            _deflateState.avail_out = (uInt) _compressBuffer.size();
            _deflateState.next_out = &_compressBuffer.front();

            deflate(&_deflateState, _flush);

            output = _compressBuffer.size() - _deflateState.avail_out;

            out.insert(out.end(), _compressBuffer.begin(), _compressBuffer.begin() + output);
        } while (_deflateState.avail_out == 0);

        if (endsWithEmptyUnCompressedBlock(out))
        {
            out.resize(out.size() - 4);
        }

        return true;
#else
        return false;
#endif
    }

    // Decompressor
    WebSocketPerMessageDeflateDecompressor::WebSocketPerMessageDeflateDecompressor()
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        memset(&_inflateState, 0, sizeof(_inflateState));

        _inflateState.zalloc = Z_NULL;
        _inflateState.zfree = Z_NULL;
        _inflateState.opaque = Z_NULL;
        _inflateState.avail_in = 0;
        _inflateState.next_in = Z_NULL;
#endif
    }

    WebSocketPerMessageDeflateDecompressor::~WebSocketPerMessageDeflateDecompressor()
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        inflateEnd(&_inflateState);
#endif
    }

    bool WebSocketPerMessageDeflateDecompressor::init(uint8_t inflateBits,
                                                      bool clientNoContextTakeOver)
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        int ret = inflateInit2(&_inflateState, -1 * inflateBits);

        if (ret != Z_OK) return false;

        _flush = (clientNoContextTakeOver) ? Z_FULL_FLUSH : Z_SYNC_FLUSH;

        return true;
#else
        return false;
#endif
    }

    bool WebSocketPerMessageDeflateDecompressor::decompress(const std::string& in, std::string& out)
    {
#ifdef IXWEBSOCKET_USE_ZLIB
        //    An endpoint uses the following algorithm to decompress a message.
        //    1.  Append 4 octets of 0x00 0x00 0xff 0xff to the tail end of the
        //        payload of the message.
        //    2.  Decompress the resulting data using DEFLATE.
        std::string inFixed(in);
        inFixed += kEmptyUncompressedBlock;

        _inflateState.avail_in = (uInt) inFixed.size();
        _inflateState.next_in = (unsigned char*) (const_cast<char*>(inFixed.data()));

        // Clear output
        out.clear();

        do
        {
            _inflateState.avail_out = (uInt) _compressBuffer.size();
            _inflateState.next_out = &_compressBuffer.front();

            int ret = inflate(&_inflateState, Z_SYNC_FLUSH);

            if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR)
            {
                return false; // zlib error
            }

            out.append(reinterpret_cast<char*>(&_compressBuffer.front()),
                       _compressBuffer.size() - _inflateState.avail_out);
        } while (_inflateState.avail_out == 0);

        return true;
#else
        return false;
#endif
    }
} // namespace ix


TSVolatility::TSVolatility( Prices& series, time_duration dtTau, time_duration dtTauPrime, double p, unsigned int n )
  : m_seriesSource( series ),
    m_dtTau( dtTau ),
    m_dtTauByTwo( microseconds( dtTau.total_microseconds() / 2 ) ),
    m_dtTauPrime( dtTauPrime ),
    m_p( p ),
    m_n( n ),
    m_tsDif( series, dtTauPrime ),
    m_tsNorm( m_tsDif, m_dtTauByTwo, n, p )
{
}

/************************************************************************
 * email: example@nnthink.com                                           *
 *                                                                      *
 * agreement.                                                           *
 *                                                                      *
 * See the file LICENSE.txt for redistribution information.             *
 ************************************************************************/

#include "stdafx.h"
#include "TSVolatility.h"

namespace ou { // One Unified
namespace tf { // TradeFrame
namespace hf { // high frequency

TSVolatility::TSVolatility( const TSVolatility& rhs )
  : m_dtTau( rhs.m_dtTau ), m_dtTauByTwo( rhs.m_dtTauByTwo ), m_dtTauPrime( rhs.m_dtTauPrime ),
  m_p( rhs.m_p ), m_n( rhs.m_n ), m_seriesSource( rhs.m_seriesSource ),
  m_tsDif( m_seriesSource, m_dtTauPrime ), m_tsNorm( m_tsDif, m_dtTauByTwo, m_n, m_p )
{
}

TSVolatility::~TSVolatility(void) {
}

void TSVolatility::HandleUpdate( const Price& price ) {
  Append( price );
}

} // namespace hf
} // namespace tf
} // namespace ou


cplus/github/Ayush7614/Daily-Coding-DS-ALGO-Practice/Operating System/CPU Scheduler/Round Robin.cpp

bool comparison(const Process &i, const Process &j)
{
    return (i.arrivalTime < j.arrivalTime)? true:false;
}

// TO RUN: g++ -o CPU-Scheduling CPU-Scheduling.cpp && ./CPU-Scheduling
//Test Case:
//RoundRobin 2 get_array A 3 0 B 4 7 C 9 0 D 0 2 E 7 6 exit calculate show exit

#include<iostream>
#include<cstring>
#include<vector>
#include<iomanip>
#include<algorithm>
#include<queue>

class Process
{
    public:
        char processID;
        float brustTime;
        float arrivalTime;
        float got_cpu_at;
        float completion_time;
        float waiting_time;
        float turn_around_time;
        float response_time;
        Process(char PID = '\0', float BT = 0, float AT = 0)
        {
            processID = PID;
            brustTime = BT;
            arrivalTime = AT;
            got_cpu_at = completion_time = waiting_time  = turn_around_time = response_time = -1;
        }
};

//This Function is used to sort the vector arr

class Scheduler
{
    protected:
        std::vector<Process> arr;
        std::vector<Process> sort()
        {
            std::vector<Process> Queue;
            std::vector<Process> vec = arr;
            std::sort(vec.begin(), vec.end(), comparison);
            for(int i=0; i<vec.size(); i++)
                Queue.push_back(vec[i]);
            return Queue;
        }
    public:
        void get_array();
        void show();
        virtual void calculate() = 0;
        virtual void printGantChart() = 0;
};

class RoundRobin : public Scheduler
{
    private:
        std::vector<Process> GantChart;
        std::queue<Process> Arrivals;
        int TimeQuantum;
    public:
        void calculate();
        RoundRobin(int TQ = 2){TimeQuantum = TQ;}
        void printGantChart()
        {
            for(int i=0; i<GantChart.size(); i++)
                std::cout<<GantChart[i].processID<<" ";
            std::cout<<"\n";
        }
};

int main()
{
    char command[20];
    Scheduler *Sc;
    int TQ;
    while(1)
    {
        std::cout<<"command: ";
        std::cin>>command;
        if(!strcmp(command, "exit"))
            break;
        else if(!strcmp(command, "RoundRobin"))
        {
            std::cout<<"Time Quantum (ms): ";
            std::cin>>TQ;
            Sc = new RoundRobin(TQ);
        }
        else if(!strcmp(command, "get_array"))
            Sc->get_array();
        else if(!strcmp(command, "show"))
            Sc->show();
        else if(!strcmp(command, "calculate"))
            Sc->calculate();
        else if(!strcmp(command, "GantChart"))
            Sc->printGantChart();
    }
    return 0;
}

void Scheduler::get_array()
{
    char PID;
    int BT, AT;
    std::string str;
    std::cout<<"Type exit to quit entering\n";
    std::cout<<"PID BT AT\n";
    while(1)
    {
        std::cin>>str;
        if(str == "exit")
            break;
        PID = str[0];
        std::cin>>BT>>AT;
        arr.push_back(*(new Process(PID, BT, AT)));
    }
}

void Scheduler::show()
{
    std::cout<<"PID"<<std::setw(4)<<std::right
             <<"BT"<<std::setw(5)<<std::right
             <<"AT"<<std::setw(5)<<std::right
             <<"CT"<<std::setw(5)<<std::right
             <<"WT"<<std::setw(5)<<std::right
             <<"TAT"<<std::setw(5)<<std::right
             <<"RT\n";
    for(int i=0; i<arr.size(); i++)
    {
        std::cout<<arr[i].processID<<std::setw(5)<<std::right
                <<arr[i].brustTime<<std::setw(5)<<std::right
                <<arr[i].arrivalTime<<std::setw(5)<<std::right
                <<arr[i].completion_time<<std::setw(5)<<std::right
                <<arr[i].waiting_time<<std::setw(5)<<std::right
                <<arr[i].turn_around_time<<std::setw(5)<<std::right
                <<arr[i].response_time<<"\n";
    }
}

void RoundRobin::calculate()
{
    int time = -1;
    std::vector<Process> ready_queue = sort();
    std::vector<Process> new_bt = arr;
    bool done = false;
    Process *front = NULL;
    while(!done)
    {
        done = true;
        time++;
        while(ready_queue.size() > 0 && ready_queue[0].arrivalTime <= time)
        {
            Arrivals.push(ready_queue[0]);
            ready_queue.erase(ready_queue.begin());
        }
        if(front) Arrivals.push(*front);
        front = NULL;
        if(!Arrivals.empty())//Arrivals.front() != NULL
        {
            if(Arrivals.front().brustTime > TimeQuantum)
            {
                front = &Arrivals.front();
                GantChart.push_back(*front);
                front->brustTime -= TimeQuantum;
                //in case Process faces cpu for 1st time
                if(front->got_cpu_at == -1) front->got_cpu_at = time;
                for(int i=0; i<arr.size(); i++)
                    if(Arrivals.front().processID == arr[i].processID)
                    {
                        new_bt[i].brustTime -= TimeQuantum;
                        break;
                    }
                time += TimeQuantum;
                Arrivals.pop();
            }
            else
            {
                GantChart.push_back(Arrivals.front());
                time += Arrivals.front().brustTime;
                Arrivals.front().brustTime = 0;
                for(int i=0; i<arr.size(); i++)
                    if(Arrivals.front().processID == arr[i].processID)
                    {
                        front = &arr[i];
                        new_bt[i].brustTime = 0;
                        break;
                    }
                front->completion_time = time;
                front->got_cpu_at = Arrivals.front().got_cpu_at;
                Arrivals.pop();
                front = NULL;
            }
            time--;
        }
        for(int i=0; i<new_bt.size(); i++)
            if(new_bt[i].brustTime > 0)
            {
                done = false;
                break;
            }
    }
    for(int i=0; i<arr.size(); i++)
    {
        arr[i].waiting_time = arr[i].completion_time - arr[i].brustTime - arr[i].arrivalTime;
        arr[i].turn_around_time = arr[i].waiting_time + arr[i].brustTime;
        arr[i].response_time = arr[i].got_cpu_at - arr[i].arrivalTime;
    }
}


void get_operator_from_registry_and_execute() {
  std::vector<torch::Tensor> output =
    helpers::get_operator_from_registry_and_execute<std::vector<torch::Tensor>>("custom::op", torch::ones(5), 2.0, 3);

  const auto manual = custom_op(torch::ones(5), 2.0, 3);

  TORCH_INTERNAL_ASSERT(output.size() == 3);
  for (size_t i = 0; i < output.size(); ++i) {
    TORCH_INTERNAL_ASSERT(output[i].allclose(torch::ones(5) * 2));
    TORCH_INTERNAL_ASSERT(output[i].allclose(manual[i]));
  }
}

#include <torch/script.h>
#include <torch/cuda.h>

#include "op.h"

#include <memory>
#include <string>
#include <vector>

#include <iostream>

namespace helpers {
template <typename Predicate>
void check_all_parameters(
    const torch::jit::Module& module,
    Predicate predicate) {
  for (at::Tensor parameter : module.parameters()) {
    AT_ASSERT(predicate(parameter));
  }
}

template<class Result, class... Args>
Result get_operator_from_registry_and_execute(const char* op_name, Args&&... args) {
  auto& ops = torch::jit::getAllOperatorsFor(
      torch::jit::Symbol::fromQualString(op_name));
  TORCH_INTERNAL_ASSERT(ops.size() == 1);

  auto& op = ops.front();
  TORCH_INTERNAL_ASSERT(op->schema().name() == op_name);

  torch::jit::Stack stack;
  torch::jit::push(stack, std::forward<Args>(args)...);
  op->getOperation()(&stack);

  TORCH_INTERNAL_ASSERT(1 == stack.size());
  return torch::jit::pop(stack).to<Result>();
}
} // namespace helpers

void get_autograd_operator_from_registry_and_execute() {
  torch::Tensor x = torch::randn({5,5}, torch::requires_grad());
  torch::Tensor y = torch::randn({5,5}, torch::requires_grad());
  torch::Tensor z = torch::randn({5,5}, torch::requires_grad());

  torch::Tensor output =
    helpers::get_operator_from_registry_and_execute<torch::Tensor>("custom::op_with_autograd", x, 2, y, c10::optional<torch::Tensor>());

  TORCH_INTERNAL_ASSERT(output.allclose(x + 2*y + x*y));
  auto go = torch::ones({}, torch::requires_grad());
  output.sum().backward(go, false, true);

  TORCH_INTERNAL_ASSERT(torch::allclose(x.grad(), y + torch::ones({5,5})));
  TORCH_INTERNAL_ASSERT(torch::allclose(y.grad(), x + torch::ones({5,5})*2));

  // Test with optional argument.
  at::zero_(x.mutable_grad());
  at::zero_(y.mutable_grad());
  output = helpers::get_operator_from_registry_and_execute<torch::Tensor>(
      "custom::op_with_autograd", x, 2, y, z);

  TORCH_INTERNAL_ASSERT(output.allclose(x + 2*y + x*y + z));
  go = torch::ones({}, torch::requires_grad());
  output.sum().backward(go, false, true);

  TORCH_INTERNAL_ASSERT(torch::allclose(x.grad(), y + torch::ones({5,5})));
  TORCH_INTERNAL_ASSERT(torch::allclose(y.grad(), x + torch::ones({5,5})*2));
  TORCH_INTERNAL_ASSERT(torch::allclose(z.grad(), torch::ones({5,5})));
}

void get_autograd_operator_from_registry_and_execute_in_nograd_mode() {
  at::AutoNonVariableTypeMode _var_guard(true);

  torch::Tensor x = torch::randn({5,5}, torch::requires_grad());
  torch::Tensor y = torch::randn({5,5}, torch::requires_grad());

  torch::Tensor output =
    helpers::get_operator_from_registry_and_execute<torch::Tensor>("custom::op_with_autograd", x, 2, y, c10::optional<torch::Tensor>());

  TORCH_INTERNAL_ASSERT(output.allclose(x + 2*y + x*y));
}

void load_serialized_module_with_custom_op_and_execute(
    const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);
  std::vector<torch::jit::IValue> inputs;
  inputs.push_back(torch::ones(5));
  auto output = module.forward(inputs).toTensor();

  AT_ASSERT(output.allclose(torch::ones(5) + 1));
}

void test_argument_checking_for_serialized_modules(
    const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);

  try {
    module.forward({torch::jit::IValue(1), torch::jit::IValue(2)});
    AT_ASSERT(false);
  } catch (const c10::Error& error) {
    AT_ASSERT(
        std::string(error.what_without_backtrace())
            .find("Expected at most 2 argument(s) for operator 'forward', "
                  "but received 3 argument(s)") == 0);
  }

  try {
    module.forward({torch::jit::IValue(5)});
    AT_ASSERT(false);
  } catch (const c10::Error& error) {
    AT_ASSERT(
        std::string(error.what_without_backtrace())
            .find("forward() Expected a value of type 'Tensor' "
                  "for argument 'input' but instead found type 'int'") == 0);
  }

  try {
    module.forward({});
    AT_ASSERT(false);
  } catch (const c10::Error& error) {
    AT_ASSERT(
        std::string(error.what_without_backtrace())
            .find("forward() is missing value for argument 'input'") == 0);
  }
}

void test_move_to_device(const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.device().is_cpu();
  });

  module.to(torch::kCUDA);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.device().is_cuda();
  });

  module.to(torch::kCPU);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.device().is_cpu();
  });
}

void test_move_to_dtype(const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);

  module.to(torch::kInt);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.dtype() == torch::kInt;
  });

  module.to(torch::kDouble);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.dtype() == torch::kDouble;
  });
}

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    std::cerr << "usage: test_custom_ops <path-to-exported-script-module>\n";
    return -1;
  }
  const std::string path_to_exported_script_module = argv[1];

  get_operator_from_registry_and_execute();
  get_autograd_operator_from_registry_and_execute();
  get_autograd_operator_from_registry_and_execute_in_nograd_mode();
  load_serialized_module_with_custom_op_and_execute(
      path_to_exported_script_module);
  test_argument_checking_for_serialized_modules(path_to_exported_script_module);
  test_move_to_dtype(path_to_exported_script_module);

  if (torch::cuda::device_count() > 0) {
    test_move_to_device(path_to_exported_script_module);
  }

  std::cout << "ok\n";
}


try {
    module.forward({});
    AT_ASSERT(false);
  } catch (const c10::Error& error) {
    AT_ASSERT(
        std::string(error.what_without_backtrace())
            .find("forward() is missing value for argument 'input'") == 0);
  }

#include <torch/script.h>
#include <torch/cuda.h>

#include "op.h"

#include <memory>
#include <string>
#include <vector>

#include <iostream>

namespace helpers {
template <typename Predicate>
void check_all_parameters(
    const torch::jit::Module& module,
    Predicate predicate) {
  for (at::Tensor parameter : module.parameters()) {
    AT_ASSERT(predicate(parameter));
  }
}

template<class Result, class... Args>
Result get_operator_from_registry_and_execute(const char* op_name, Args&&... args) {
  auto& ops = torch::jit::getAllOperatorsFor(
      torch::jit::Symbol::fromQualString(op_name));
  TORCH_INTERNAL_ASSERT(ops.size() == 1);

  auto& op = ops.front();
  TORCH_INTERNAL_ASSERT(op->schema().name() == op_name);

  torch::jit::Stack stack;
  torch::jit::push(stack, std::forward<Args>(args)...);
  op->getOperation()(&stack);

  TORCH_INTERNAL_ASSERT(1 == stack.size());
  return torch::jit::pop(stack).to<Result>();
}
} // namespace helpers

void get_operator_from_registry_and_execute() {
  std::vector<torch::Tensor> output =
    helpers::get_operator_from_registry_and_execute<std::vector<torch::Tensor>>("custom::op", torch::ones(5), 2.0, 3);

  const auto manual = custom_op(torch::ones(5), 2.0, 3);

  TORCH_INTERNAL_ASSERT(output.size() == 3);
  for (size_t i = 0; i < output.size(); ++i) {
    TORCH_INTERNAL_ASSERT(output[i].allclose(torch::ones(5) * 2));
    TORCH_INTERNAL_ASSERT(output[i].allclose(manual[i]));
  }
}

void get_autograd_operator_from_registry_and_execute() {
  torch::Tensor x = torch::randn({5,5}, torch::requires_grad());
  torch::Tensor y = torch::randn({5,5}, torch::requires_grad());
  torch::Tensor z = torch::randn({5,5}, torch::requires_grad());

  torch::Tensor output =
    helpers::get_operator_from_registry_and_execute<torch::Tensor>("custom::op_with_autograd", x, 2, y, c10::optional<torch::Tensor>());

  TORCH_INTERNAL_ASSERT(output.allclose(x + 2*y + x*y));
  auto go = torch::ones({}, torch::requires_grad());
  output.sum().backward(go, false, true);

  TORCH_INTERNAL_ASSERT(torch::allclose(x.grad(), y + torch::ones({5,5})));
  TORCH_INTERNAL_ASSERT(torch::allclose(y.grad(), x + torch::ones({5,5})*2));

  // Test with optional argument.
  at::zero_(x.mutable_grad());
  at::zero_(y.mutable_grad());
  output = helpers::get_operator_from_registry_and_execute<torch::Tensor>(
      "custom::op_with_autograd", x, 2, y, z);

  TORCH_INTERNAL_ASSERT(output.allclose(x + 2*y + x*y + z));
  go = torch::ones({}, torch::requires_grad());
  output.sum().backward(go, false, true);

  TORCH_INTERNAL_ASSERT(torch::allclose(x.grad(), y + torch::ones({5,5})));
  TORCH_INTERNAL_ASSERT(torch::allclose(y.grad(), x + torch::ones({5,5})*2));
  TORCH_INTERNAL_ASSERT(torch::allclose(z.grad(), torch::ones({5,5})));
}

void get_autograd_operator_from_registry_and_execute_in_nograd_mode() {
  at::AutoNonVariableTypeMode _var_guard(true);

  torch::Tensor x = torch::randn({5,5}, torch::requires_grad());
  torch::Tensor y = torch::randn({5,5}, torch::requires_grad());

  torch::Tensor output =
    helpers::get_operator_from_registry_and_execute<torch::Tensor>("custom::op_with_autograd", x, 2, y, c10::optional<torch::Tensor>());

  TORCH_INTERNAL_ASSERT(output.allclose(x + 2*y + x*y));
}

void load_serialized_module_with_custom_op_and_execute(
    const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);
  std::vector<torch::jit::IValue> inputs;
  inputs.push_back(torch::ones(5));
  auto output = module.forward(inputs).toTensor();

  AT_ASSERT(output.allclose(torch::ones(5) + 1));
}

void test_argument_checking_for_serialized_modules(
    const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);

  try {
    module.forward({torch::jit::IValue(1), torch::jit::IValue(2)});
    AT_ASSERT(false);
  } catch (const c10::Error& error) {
    AT_ASSERT(
        std::string(error.what_without_backtrace())
            .find("Expected at most 2 argument(s) for operator 'forward', "
                  "but received 3 argument(s)") == 0);
  }

  try {
    module.forward({torch::jit::IValue(5)});
    AT_ASSERT(false);
  } catch (const c10::Error& error) {
    AT_ASSERT(
        std::string(error.what_without_backtrace())
            .find("forward() Expected a value of type 'Tensor' "
                  "for argument 'input' but instead found type 'int'") == 0);
  }

}

void test_move_to_device(const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.device().is_cpu();
  });

  module.to(torch::kCUDA);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.device().is_cuda();
  });

  module.to(torch::kCPU);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.device().is_cpu();
  });
}

void test_move_to_dtype(const std::string& path_to_exported_script_module) {
  torch::jit::Module module =
      torch::jit::load(path_to_exported_script_module);

  module.to(torch::kInt);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.dtype() == torch::kInt;
  });

  module.to(torch::kDouble);

  helpers::check_all_parameters(module, [](const torch::Tensor& tensor) {
    return tensor.dtype() == torch::kDouble;
  });
}

int main(int argc, const char* argv[]) {
  if (argc != 2) {
    std::cerr << "usage: test_custom_ops <path-to-exported-script-module>\n";
    return -1;
  }
  const std::string path_to_exported_script_module = argv[1];

  get_operator_from_registry_and_execute();
  get_autograd_operator_from_registry_and_execute();
  get_autograd_operator_from_registry_and_execute_in_nograd_mode();
  load_serialized_module_with_custom_op_and_execute(
      path_to_exported_script_module);
  test_argument_checking_for_serialized_modules(path_to_exported_script_module);
  test_move_to_dtype(path_to_exported_script_module);

  if (torch::cuda::device_count() > 0) {
    test_move_to_device(path_to_exported_script_module);
  }

  std::cout << "ok\n";
}