DECLARE_FUNCTION_4P(frho)	REGISTER_FUNCTION(frho, dummy().latex_name("\\rho"))
DECLARE_FUNCTION_4P(fu)		REGISTER_FUNCTION(fu, dummy().latex_name("\\ou"))
DECLARE_FUNCTION_4P(fv)		REGISTER_FUNCTION(fv, dummy().latex_name("\\ov"))
DECLARE_FUNCTION_4P(fw)		REGISTER_FUNCTION(fw, dummy().latex_name("\\ow"))

DECLARE_FUNCTION_4P(fmu0)	REGISTER_FUNCTION(fmu0, dummy().latex_name("\\mmA"))
DECLARE_FUNCTION_4P(fmu1)	REGISTER_FUNCTION(fmu1, dummy().latex_name("\\mmB"))
DECLARE_FUNCTION_4P(fmu2)	REGISTER_FUNCTION(fmu2, dummy().latex_name("\\mmC"))
DECLARE_FUNCTION_4P(fmu3)	REGISTER_FUNCTION(fmu3, dummy().latex_name("\\mmD"))
DECLARE_FUNCTION_4P(fmu4)	REGISTER_FUNCTION(fmu4, dummy().latex_name("\\mmE"))
DECLARE_FUNCTION_4P(fmu5)	REGISTER_FUNCTION(fmu5, dummy().latex_name("\\mmF"))
DECLARE_FUNCTION_4P(fmu6)	REGISTER_FUNCTION(fmu6, dummy().latex_name("\\mmG"))
DECLARE_FUNCTION_4P(fmu7)	REGISTER_FUNCTION(fmu7, dummy().latex_name("\\mmH"))
DECLARE_FUNCTION_4P(fmu8)	REGISTER_FUNCTION(fmu8, dummy().latex_name("\\mmI"))
DECLARE_FUNCTION_4P(fmu9)	REGISTER_FUNCTION(fmu9, dummy().latex_name("\\mmJ"))
DECLARE_FUNCTION_4P(fmu10)	REGISTER_FUNCTION(fmu10, dummy().latex_name("\\mmK"))
DECLARE_FUNCTION_4P(fmu11)	REGISTER_FUNCTION(fmu11, dummy().latex_name("\\mmL"))
DECLARE_FUNCTION_4P(fmu12)	REGISTER_FUNCTION(fmu12, dummy().latex_name("\\mmM"))
DECLARE_FUNCTION_4P(fmu13)	REGISTER_FUNCTION(fmu13, dummy().latex_name("\\mmN"))
DECLARE_FUNCTION_4P(fmu14)	REGISTER_FUNCTION(fmu14, dummy().latex_name("\\mmO"))
DECLARE_FUNCTION_4P(fmu15)	REGISTER_FUNCTION(fmu15, dummy().latex_name("\\mmP"))
DECLARE_FUNCTION_4P(fmu16)	REGISTER_FUNCTION(fmu16, dummy().latex_name("\\mmQ"))
DECLARE_FUNCTION_4P(fmu17)	REGISTER_FUNCTION(fmu17, dummy().latex_name("\\mmR"))
DECLARE_FUNCTION_4P(fmu18)	REGISTER_FUNCTION(fmu18, dummy().latex_name("\\mmS"))
DECLARE_FUNCTION_4P(fmu19)	REGISTER_FUNCTION(fmu19, dummy().latex_name("\\mmT"))
DECLARE_FUNCTION_4P(fmu20)	REGISTER_FUNCTION(fmu20, dummy().latex_name("\\mmU"))
DECLARE_FUNCTION_4P(fmu21)	REGISTER_FUNCTION(fmu21, dummy().latex_name("\\mmV"))
DECLARE_FUNCTION_4P(fmu22)	REGISTER_FUNCTION(fmu22, dummy().latex_name("\\mmW"))
DECLARE_FUNCTION_4P(fmu23)	REGISTER_FUNCTION(fmu23, dummy().latex_name("\\mmX"))
DECLARE_FUNCTION_4P(fmu24)	REGISTER_FUNCTION(fmu24, dummy().latex_name("\\mmY"))
DECLARE_FUNCTION_4P(fmu25)	REGISTER_FUNCTION(fmu25, dummy().latex_name("\\mmZ"))
DECLARE_FUNCTION_4P(fmu26)	REGISTER_FUNCTION(fmu26, dummy().latex_name("\\mmAA"))

template< typename LBM_TYPE >
CORE< LBM_TYPE >::CORE()  //: DEF(iD, iQ)
{
	Q=lbm.Q;
	D=lbm.getDim();

	sprintf(target_dir,"default");

	fun.rho = frho(lbm.t, lbm.x, lbm.y, lbm.z);
	fun.vel[0]=fu(lbm.t, lbm.x, lbm.y, lbm.z);
	fun.vel[1]=fv(lbm.t, lbm.x, lbm.y, lbm.z);
	fun.vel[2]=fw(lbm.t, lbm.x, lbm.y, lbm.z);

	fun.mu.push_back(fmu0(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu1(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu2(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu3(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu4(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu5(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu6(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu7(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu8(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu9(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu10(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu11(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu12(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu13(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu14(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu15(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu16(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu17(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu18(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu19(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu20(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu21(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu22(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu23(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu24(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu25(lbm.t, lbm.x, lbm.y, lbm.z));
	fun.mu.push_back(fmu26(lbm.t, lbm.x, lbm.y, lbm.z));
}



template< typename LBM_TYPE >
bool CORE< LBM_TYPE >::find_lowest_time_derivative(TPDE &expression, int basindex, int order, int &kt, int &kx, int &ky, int &kz)
{
	int k=order-1;
	for (int j0=1;j0<=k;j0++)
	for (int j3=0;(j3<=k && D==3) || (j3<=0);j3++)
	for (int j2=0;(j2<=k && D>=2) || (j2<=0);j2++)
	for (int j1=0;j1<=k;j1++)
	if (j0+j1+j2+j3<=order)
	if (expression.nonzero_coeff(basindex, j0,j1,j2,j3))
	{
		kt=j0;
		kx=j1;
		ky=j2;
		kz=j3;
		return true;
	}
	return false;
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::subs_time_derivatives_up_to_order(TPDE &expr, int basindex, TPDE *ders, int max_order)
{
	TPDE subsexpr;
	// substitute spatial derivatives of ders[i], i=0..order into expr
	for (int o=0;o<max_order;o++)
	{
		bool difference=true;
		while (difference)
		{
			int kt=0,kx=0,ky=0,kz=0;
			if (find_lowest_time_derivative(expr,basindex,ORDER+1,kt,kx,ky,kz))
			{
				if (kt==o+1)
				{
					// differentiate step-by-step with truncation is much faster than the version commented below
					subsexpr.copy(ders[o]);
					subsexpr.diff(1,kx);
					if (D>=2) subsexpr.diff(2,ky);
					if (D==3) subsexpr.diff(3,kz);
					difference = expr.replace_der_by_expr(basindex, kt, kx, ky, kz, subsexpr);
				} else break;
			} else break;
		}
	}
	expr.expand();
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::eliminate_all_spatial_orders_for_given_time_order(int basindex, int time_order, TPDE *ders)
{
	TPDE subsexpr;
	bool difference=true;
	int iter=0;
	while(difference)
	{
		int kt=0,kx=0,ky=0,kz=0;
		if (!find_lowest_time_derivative(ders[time_order],basindex,ORDER+1,kt,kx,ky,kz)) break;
//		cout << "iter"<<iter++<<": lowest order with time derivative found: basindex=" << basindex <<" kt="<<kt<<" kx="<<kx<<" ky="<<ky<<" kz="<<kz<< " time_order="<<time_order<<endl;
		log("    iter=%d: lowest time derivative order: basindex=%d der=(%d,%d,%d,%d) time_order=%d",iter++,basindex,kt,kx,ky,kz,time_order);
		fflush(stdout);

		if (kt <= time_order)
		{
			log("ERROR: lowest time derivative %d is lower or equal than the time_order %d", kt, time_order);
			cout << "DEBUG:"<<endl;
			ders[time_order].print();
			break;
		}
		if (kt > time_order+1 || kt > ORDER) break;

		subsexpr.copy(ders[time_order]);
		subsexpr.diff(1,kx);
		if (D>=2) subsexpr.diff(2,ky);
		if (D==3) subsexpr.diff(3,kz);
		difference = ders[time_order].replace_der_by_expr(basindex, kt, kx, ky, kz, subsexpr);
	}
	ders[time_order].expand();
}


template< typename LBM_TYPE >
void CORE< LBM_TYPE >::eliminate_all_spatial_orders_for_given_time_order(TPDE &target, int basindex, int time_order, TPDE &source)
{
	TPDE subsexpr;
	bool difference=true;
	int iter=0;
	while(difference)
	{
		int kt=0,kx=0,ky=0,kz=0;
		if (!find_lowest_time_derivative(target,basindex,ORDER+1,kt,kx,ky,kz)) break;
		log("    iter=%d: lowest time derivative order: basindex=%d der=(%d,%d,%d,%d) time_order=%d",iter++,basindex,kt,kx,ky,kz,time_order);

		if (kt <= time_order)
		{
			log("ERROR: lowest time derivative %d is lower or equal than the time_order %d", kt, time_order);
			cout << "DEBUG:"<<endl;
			target.print();
			break;
		}
		if (kt > time_order+1 || kt > ORDER) break;

		subsexpr.copy(source);
		subsexpr.diff(1,kx);
		if (D>=2) subsexpr.diff(2,ky);
		if (D==3) subsexpr.diff(3,kz);
		difference = target.replace_der_by_expr(basindex, kt, kx, ky, kz, subsexpr);
	}
	target.expand();
}

// index_offset = 1 or D+1 for NSE or ADE, respectively
template< typename LBM_TYPE >
void CORE< LBM_TYPE >::gauss_eliminate_derivatives_on_diagonal(int bi, vct& bas, TPDE*abs_rhs, int index_offset)
{
	lst sym_subs;
	int sym_subs_index=0;
	char sym_subs_prefix[200];
	sprintf(sym_subs_prefix, "c%d_",subs_lst_level++);

	TPDE aux;
	bool do_repeat=true;
	int it=0;
	while(do_repeat)
	{
		log("    Gaussian elimination: diagonal: row=%d: loop_restart (%d)",bi,it++);
		do_repeat=false;
		for (int o=1;o<=ORDER;o++)
		{
			for (int j0=0;j0<=ORDER;j0++)
			for (int j3=0;(j3<=ORDER && D==3) || (j3<=0);j3++)
			for (int j2=0;(j2<=ORDER && D>=2) || (j2<=0);j2++)
			for (int j1=0;j1<=ORDER;j1++)
			if (j0+j1+j2+j3==o)
			{
				if (abs_rhs[bi].nonzero_coeff(bi+index_offset, j0, j1, j2, j3))
				{
					log("        row=%d der=(%d,%d,%d,%d) order=%d",bi,j0,j1,j2,j3,o);
					aux.copy( abs_rhs[bi] );
					if (j0>0) aux.diff(0, j0);
					if (j1>0) aux.diff(1, j1);
					if (j2>0) aux.diff(2, j2);
					if (j3>0) aux.diff(3, j3);
					if (abs_rhs[bi].nonzero_coeff(bi+index_offset, j0, j1, j2, j3))
					{
						abs_rhs[bi].replace_der_by_expr(bi+index_offset,j0,j1,j2,j3,aux);
						// replace coeffs
						if (use_symbolic_coefs) abs_rhs[bi].replace_coefs(sym_subs_index, bas, sym_subs, sym_subs_prefix);
						do_repeat=true;
					}
				}
			}
			if (do_repeat) break;
		}
		abs_rhs[bi].normal();
	}
	// dump lst
	if (use_symbolic_coefs) if (sym_subs.nops()>0) dump_lst(target_dir,"subs","subs_list",dump_lst_count++, sym_subs);
}


template< typename LBM_TYPE >
void CORE< LBM_TYPE >::gauss_eliminate_derivatives_below_diagonal(int bi, vct& bas, TPDE*abs_rhs, int index_offset)
{
	lst sym_subs;
	int sym_subs_index=0;
	char sym_subs_prefix[200];
	sprintf(sym_subs_prefix, "c%d_",subs_lst_level++);

	const int N2=Q-index_offset;
	TPDE aux;
	bool do_repeat=true;
	// init
	int it=0;
	while(do_repeat)
	{
		log("    Gaussian elimination: below diagonal: row=%d: eliminating from %d to %d: loop_restart (%d)",bi,bi+1,N2-1,it++);
		do_repeat=false;
		for (int o=1;o<=ORDER;o++)
		{
			for (int j0=0;j0<=ORDER;j0++)
			for (int j3=0;(j3<=ORDER && D==3) || (j3<=0);j3++)
			for (int j2=0;(j2<=ORDER && D>=2) || (j2<=0);j2++)
			for (int j1=0;j1<=ORDER;j1++)
			if (j0+j1+j2+j3==o)
			{
				bool work_to_do=false;
				for (int j=bi+1;j<N2;j++)
				{
					if (abs_rhs[j].nonzero_coeff(bi+index_offset, j0, j1, j2, j3)) { work_to_do=true; break; }
				}
				if (work_to_do)
				{
					log("        row=%d der=(%d,%d,%d,%d) order=%d",bi,j0,j1,j2,j3,o);
					aux.copy( abs_rhs[bi] );
					if (j0>0) aux.diff(0, j0);
					if (j1>0) aux.diff(1, j1);
					if (j2>0) aux.diff(2, j2);
					if (j3>0) aux.diff(3, j3);

					for (int j=bi+1;j<N2;j++)
					if (abs_rhs[j].nonzero_coeff(bi+index_offset, j0, j1, j2, j3))
					{
						abs_rhs[j].replace_der_by_expr(bi+index_offset,j0,j1,j2,j3,aux);
						// replace coeffs
						if (use_symbolic_coefs) abs_rhs[j].replace_coefs(sym_subs_index, bas, sym_subs, sym_subs_prefix);
						do_repeat=true;
					}
				}
			}
			if (do_repeat) break;
		}
		for (int j=bi+1;j<N2;j++) abs_rhs[j].normal();
	}
	// dump lst
	if (use_symbolic_coefs) if (sym_subs.nops()>0) dump_lst(target_dir,"subs","subs_list",dump_lst_count++, sym_subs);
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::gauss_eliminate_derivatives_above_diagonal(int bi, vct& bas, TPDE*abs_rhs, int index_offset)
{
	lst sym_subs;
	int sym_subs_index=0;
	char sym_subs_prefix[200];
	sprintf(sym_subs_prefix, "c%d_",subs_lst_level++);

	const int N2=Q-index_offset;
	TPDE aux;
	bool do_repeat=true;
	// init
	int it=0;
	while(do_repeat)
	{
		log("    Gaussian elimination: above diagonal: row=%d: loop_restart (%d)",bi,it++);
		do_repeat=false;
		for (int o=1;o<=ORDER;o++)
		{
			for (int j0=0;j0<=ORDER;j0++)
			for (int j3=0;(j3<=ORDER && D==3) || (j3<=0);j3++)
			for (int j2=0;(j2<=ORDER && D>=2) || (j2<=0);j2++)
			for (int j1=0;j1<=ORDER;j1++)
			if (j0+j1+j2+j3==o)
			//if (j0+j1+j2+j3<=ORDER && j0+j1+j2+j3>0)
			{
				// browse all abs_rhs and test if we need to prepare a derivative to be replaced
				bool work_to_do=false;
				for (int j=0;j<bi;j++)
				{
					if (abs_rhs[j].nonzero_coeff(bi+index_offset, j0, j1, j2, j3)) { work_to_do=true; break; }
				}
				if (work_to_do)
				{
					log("        row=%d der=(%d,%d,%d,%d) order=%d",bi,j0,j1,j2,j3,o);
					aux.copy( abs_rhs[bi] );
					if (j0>0) aux.diff(0, j0);
					if (j1>0) aux.diff(1, j1);
					if (j2>0) aux.diff(2, j2);
					if (j3>0) aux.diff(3, j3);

					for (int j=0;j<bi;j++)
					if (abs_rhs[j].nonzero_coeff(bi+index_offset, j0, j1, j2, j3))
					{
						abs_rhs[j].replace_der_by_expr(bi+index_offset,j0,j1,j2,j3,aux);
						// replace coeffs
						if (use_symbolic_coefs) abs_rhs[j].replace_coefs(sym_subs_index, bas, sym_subs, sym_subs_prefix);
						do_repeat=true;
					}
				}
			}
			if (do_repeat) break;
		}
		for (int j=0;j<bi;j++) abs_rhs[j].normal();
	}
	// dump lst
	if (use_symbolic_coefs) if (sym_subs.nops()>0) dump_lst(target_dir,"subs","subs_list",dump_lst_count++, sym_subs);
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::gauss_eliminate_coefs_on_diagonal(int bi, vct& bas, TPDE*abs_rhs, int index_offset)
{
	abs_rhs[bi].subs_polynomial(bas[bi+index_offset], abs_rhs[bi]);
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::gauss_eliminate_coefs_below_diagonal(int bi, vct& bas, TPDE*abs_rhs, int index_offset)
{
	const int N2=Q-index_offset;
	for (int j=bi+1;j<N2;j++)
		abs_rhs[j].subs_polynomial(bas[bi+index_offset], abs_rhs[bi]);
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::gauss_eliminate_coefs_above_diagonal(int bi, vct& bas, TPDE*abs_rhs, int index_offset)
{
	for (int j=0;j<bi;j++)
		abs_rhs[j].subs_polynomial(bas[bi+index_offset], abs_rhs[bi]);
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::eliminate_higher_raw_moments(vct& bas, TPDE *pde, TPDE*abs_rhs, int Nc)
{
	const int Nn=Q-Nc;
	TPDE aux;
	for (int o=1;o<=ORDER;o++)
	{
		for (int i=0;i<Nn;i++)
		{
			for (int j0=0;j0<=ORDER;j0++)
			for (int j3=0;(j3<=ORDER && D==3) || (j3<=0);j3++)
			for (int j2=0;(j2<=ORDER && D>=2) || (j2<=0);j2++)
			for (int j1=0;j1<=ORDER;j1++)
			if (j0+j1+j2+j3==o)
			{
				// browse all abs_rhs and test if we need to prepare a derivative to be replaced
				bool work_to_do=false;
				for (int j=0;j<Nc;j++)
				{
					if (pde[j].nonzero_coeff(i+Nc, j0, j1, j2, j3)) { work_to_do=true; break; }
				}
				if (work_to_do)
				{
//					cout << "we have work to do for i="<<i<< " j0=" << j0 << " j1=" << j1 << " j2=" << j2 << " j3=" << j3 << " order=" <<o<<endl;
					log("    row=%d der=(%d,%d,%d,%d) order=%d",i,j0,j1,j2,j3,o);
					aux.copy( abs_rhs[i] );
					if (j0>0) aux.diff(0, j0);
					if (j1>0) aux.diff(1, j1);
					if (j2>0) aux.diff(2, j2);
					if (j3>0) aux.diff(3, j3);
					for (int j=0;j<Nc;j++)
					if (pde[j].nonzero_coeff(i+Nc, j0, j1, j2, j3))
						pde[j].replace_der_by_expr(i+Nc,j0,j1,j2,j3,aux);
				}
			}
		}
	}
	for (int j=0;j<Nn;j++) pde[j].normal();
}


// matrix-LBM generation of EPDE - virtualize for non-matrix-LBM types
// matrix A must be defined for this to work
template< typename LBM_TYPE >
void CORE< LBM_TYPE >::assemble_pdes(TPDE *pde)
{
	// allocate matrices E, I, M, Mi, C
	lbm.allocate();

	ex dl = lbm.dl;
	ex dt = lbm.dt;
	// speed-up fix: comment these two lines to get symbolic dl and dt
//	dl = (ex)1;
//	dt = (ex)1;

	matrix lhs_taylor = matrix(Q,1);
	matrix rhs_collision = matrix(Q,1);

	// fill rhs: spatial derivatives multiplied by M.E.(I-A).Minv and M.E.A.Minv
	for (int q=0;q<Q;q++)
	{
		lhs_taylor(q,0) = 0;
		rhs_collision(q,0) = 0;
	}

	matrix C;
	lbm.setC(C);

	// choice of directions
	for (int k=0;k<Q;k++)
	{
//		cout << "k "<<k<<endl;
		// prepare vectors with Taylor expanded quantities
		matrix mus = matrix(Q,1);

		// fill vectors
		for (int q=0;q<Q;q++)
		{
			ex der = lbm.get_moments(q, fun);
			ex der_eq = lbm.get_moments_eq(q, fun);

			// prepare Taylor vector for expansion in space (needs to be multiplied by the matrices afterwards)
			mus(q,0) = der;

			for (int l=1;l<=ORDER;l++)
			for (int j3=0;(j3<=l && D==3) || (j3<=0);j3++)
			for (int j2=0;(j2<=l && D>=2) || (j2<=0);j2++)
			for (int j1=0;j1<=l;j1++)
			for (int j0=0;j0<=l;j0++)
			if (j0+j1+j2+j3==l)
			{
				ex koef = pow(dl, j1+j2+j3)*pow(dt, j0);
				if (j0>0) koef /= factorial(j0);
				if (j1>0) koef /= factorial(j1);
				if (j2>0) koef /= factorial(j2);
				if (j3>0) koef /= factorial(j3);

				if (j1>0) koef *= (C(0,k)==0) ? 0 : pow(C(0,k),j1);
				if (j2>0) koef *= (C(1,k)==0) ? 0 : pow(C(1,k),j2);
				if (j3>0) koef *= (C(2,k)==0) ? 0 : pow(C(2,k),j3);

				// TODO: check the result of pow( -1, 3)
				mus(q,0) += koef * diff(diff(diff(diff( der, lbm.x, j1), lbm.y, j2), lbm.z, j3), lbm.t, j0);
			}
		}
		lhs_taylor = lhs_taylor.add( lbm.M.mul( lbm.E[k] ).mul( lbm.Mi ).mul(mus) );
	}


	// prepare moments for rhs
	// prepare vectors with Taylor expanded quantities
	matrix mus = matrix(Q,1);
	matrix mus_eq = matrix(Q,1);
	matrix dfs = matrix(Q,1);
	matrix dfs_eq = matrix(Q,1);

	// fill vectors
	for (int q=0;q<Q;q++)
	{
		ex der = lbm.get_moments(q, fun);
		ex der_eq = lbm.get_moments_eq(q, fun);
		// prepare Taylor vector for expansion in space (needs to be multiplied by the matrices afterwards)
		mus(q,0) = der;
		mus_eq(q,0) = der_eq;
	}
	// compute dfs and dfs_eq using Mi since mus = M dfs
	dfs = lbm.Mi.mul(mus);
	dfs_eq = lbm.Mi.mul(mus_eq);

	// rhs
	if (LBM_TYPE::is_matrix_lbm)
	{
		// apply matrices and produce the desired rhs
		matrix A;
		lbm.setA(A, fun.vel[0], fun.vel[1], fun.vel[2]);
		lbm.simplify_matrix(A);
		matrix I_A = lbm.I.sub(A); // TODO: use specially given matrix A based on Taylor-expanded velocities!!!!!!
		rhs_collision = rhs_collision.add( lbm.M.mul(I_A).mul(dfs) );
		rhs_collision = rhs_collision.add( lbm.M.mul(A).mul(dfs_eq) );
	} else
	{
		matrix dfs_star = matrix(Q,1); // post-collision RHS in terms of DFs
		lbm.collision(dfs_star, dfs, fun.vel[0], fun.vel[1], fun.vel[2], mus(0,0)); // note that rho = mus(0,0)
		rhs_collision = rhs_collision.add( lbm.M.mul(dfs_star) );
//		cout << "WARNING: non-matrix LBM specified but no code found...!"<<endl;
	}

	for (int q=0;q<Q;q++)
	{
		ex expr = expand( lhs_taylor(q,0) - rhs_collision(q,0) );
		pde[q].eat(expr);
	}
}

template< typename LBM_TYPE >
void CORE< LBM_TYPE >::compute_all()
{
	if (verbosity) log("compute %s",(lbm.pde_type==NSE) ? "NSE" : "ADE");

	// declaration of variables
	const int Nc= (lbm.pde_type==NSE) ? D+1 : 1;	// number of conserved quantities
	const int Nn= Q-Nc;				// number of nonconserved quantities
	int step=0, ord, sym_index=0;
	TPDE pde[Q], abs_rhs[Nn], a, aux, der[Nc][MAXO], pde_phys[Nc];
	vct bas;
	lst sym_subs;
	matrix W = matrix(Nc, Nc), Wi, W2 = matrix(Nn,Nn), W2i;

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: allocate conserved and nonconserved quantities", step++);
	if (lbm.pde_type==ADE)
	{
		bas.push_back( fun.rho );
		for (int q=Nc;q<Q;q++) bas.push_back( fun.mu[q] );
		// at the back
		bas.push_back( fun.vel[0] );
		if (D>=2) bas.push_back( fun.vel[1] );
		if (D>=3) bas.push_back( fun.vel[2] );
	}
	if (lbm.pde_type==NSE)
	{
		bas.push_back( fun.rho );
		bas.push_back( fun.vel[0] );
		if (D>=2) bas.push_back( fun.vel[1] ); 
		if (D>=3) bas.push_back( fun.vel[2] );
		for (int q=Nc;q<Q;q++) bas.push_back( fun.mu[q] );
	}

	for (int pi=0;pi<Q;pi++) pde[pi].allocate(bas, ORDER, MFCO, lbm.t, lbm.x, lbm.y, lbm.z);
	for (int pi=0;pi<Nn;pi++) abs_rhs[pi].allocate(bas, ORDER, MFCO, lbm.t, lbm.x, lbm.y, lbm.z);
	for (int k=0;k<MAXO;k++)
	for (int pi=0;pi<Nc;pi++)
		der[pi][k].allocate(bas, ORDER, MFCO, lbm.t, lbm.x, lbm.y, lbm.z);	// der stores Dt^order bas up to order=MAXO

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: assemble initial PDE", step++);
	assemble_pdes(pde);

	if (verbosity>1)
	for (int pi=0;pi<Q;pi++)
	{
		cout << "printing pde["<<pi<<"]:"<<endl;
		pde[pi].print();
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: eliminate zero-th order derivatives", step++);
	if (verbosity) log("    assemble W2 matrix");
	for (int i=0;i<Nn;i++)
	{
		ex expr=pde[i+Nc].data0D;
		ex remove=0;
		for (int j=0;j<Nn;j++)
		{
			ex c = coeff(expr, bas[j+Nc]);
			W2(i,j) = c;
			remove += c*bas[j+Nc];
		}
		pde[i+Nc].eat(-remove);
		if (verbosity>1)
		{
			cout << "main_pde["<<i+Nc<<"] printing after removing abs term"<<endl;
			pde[i+Nc].print();
		}
	}
	if (verbosity) log("    inversion of W2");
	W2i = inverse(W2);
	if (verbosity>1)
	{
		cout << "Matrix W2 = " << W2 << endl;
		cout << "matrix W2i="<<W2i<<endl<<endl;
	}
	// apply W2 inversion on Nn equations to express zero-th derivatives
	if (verbosity) log("    multiply -W2i*abs_rhs");
	for (int i=0;i<Nn;i++)
	{
		for (int j=0;j<Nn;j++)
		{
			a.copy(pde[j+Nc]);
			a.mul(-W2i(i,j));
			abs_rhs[i].add(a);
		}
		abs_rhs[i].normal();
	}
	if (verbosity>1)
	for (int pi=0;pi<Nn;pi++)
	{
		cout << "printing abs_rhs["<<pi<<"] (bas "<< bas[pi+Nc] <<"):"<<endl;
		abs_rhs[pi].print();
	}

	// symbolic substitution
	if (use_symbolic_coefs)
	{
		if (verbosity) log("    replace coefs by symbolic variables");
		// replace all coefs by a symbol
		for (int pi=0;pi<Nn;pi++) abs_rhs[pi].replace_coefs(sym_index, bas, sym_subs, "a");

		if (verbosity>1)
		{
			cout << "After symbolic_coefs subs"<<endl;
			for (int pi=0;pi<Nn;pi++)
			{
				cout << "printing abs_rhs["<<pi<<"] (bas "<< bas[pi+Nc] <<"):"<<endl;
				abs_rhs[pi].print();
			}
		}
	}
	if (use_symbolic_coefs) dump_lst(target_dir,"subs","subs_list",dump_lst_count++, sym_subs, true);

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: Gaussian elimination of nonconserved quantities", step++);
	// Gauss elimination
	for (int pi=0;pi<Nn;pi++)
	{
		// diagonal terms
		gauss_eliminate_coefs_on_diagonal(pi, bas, abs_rhs, Nc);
		gauss_eliminate_derivatives_on_diagonal(pi, bas, abs_rhs, Nc);
		// below diagonal
		gauss_eliminate_coefs_below_diagonal(pi, bas, abs_rhs, Nc);
		gauss_eliminate_derivatives_below_diagonal(pi, bas, abs_rhs, Nc);
	}
	// backward Gaus
	for (int pi=Nn-1;pi>=0;pi--)
	{
		gauss_eliminate_coefs_above_diagonal(pi, bas, abs_rhs, Nc);
		gauss_eliminate_derivatives_above_diagonal(pi, bas, abs_rhs, Nc);
	}
	if (verbosity>1)
	{
		cout << "after full Gauss elimination" << endl;
		for (int pi=0;pi<Nn;pi++)
		{
			cout << endl << "printing abs_rhs["<<pi<<"] (bas "<< bas[pi+Nc] <<"):"<<endl;
			abs_rhs[pi].print();
		}
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: elimination of higher order derivatives of conserved quantities", step++);
	eliminate_higher_raw_moments(bas, pde, abs_rhs, Nc);

	if (verbosity>1)
	{
		cout << "after elimination of first "<<Nc<<" eqs: "<<endl;
		for (int pi=0;pi<Nc;pi++)
		{
			cout << "printing pde["<<pi<<"]:"<<endl;
			pde[pi].print();
		}
	}


	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: elimination of higher order derivatives of conserved quantities", step++);

	if (verbosity) log("    assemble matrix W and its inverse");
	// this is actually very slow for dense matrices W !!!
	for (int i=0;i<Nc;i++)
	for (int j=0;j<Nc;j++)
	{
		W(i,j) = pde[i].data_r(j,1,0,0,0);
		pde[i].data_w(j,1,0,0,0)=0; // clear the coefficient that moved into W
	}
	Wi = inverse(W);
	if (verbosity>1)
	{
		cout << "matrix W="<<W<<endl<<endl;
		cout << "matrix Winv="<<Wi<<endl<<endl;
	}

	if (verbosity) log("    multiply -Wi*pde");
	// multiply -Winv*pde
	for (int i=0;i<Nc;i++)
	{
		for (int j=0;j<Nc;j++)
		{
			aux.copy(pde[j]);
			aux.mul(-Wi(i,j));
			der[i][0].add(aux);
		}
		der[i][0].normal();
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: eliminate all spatial derivatives up to time order=1", step++);
	ord=0;
	for (int i=0;i<Nc;i++)
	{
		// diagonal first
		eliminate_all_spatial_orders_for_given_time_order(der[i][ord], i, ord, der[i][ord]);
		for (int j=0;j<Nc;j++) if (i!=j)
		{
			eliminate_all_spatial_orders_for_given_time_order(der[j][ord], i, ord, der[i][ord]);
		}
		der[i][ord].normal();
		if (verbosity>1)
		{
			cout << "der["<<i<<"]["<<ord<<"].print():"<<endl;
			der[i][ord].print();
		}
	}

	bool work=true;
	while (work)
	{
		ord++;
		if (verbosity) log("step %d: eliminate all spatial derivatives of time derivatives of order=%d", step++,ord);
		for (int i=0;i<Nc;i++)
		{
			// time diff
			der[i][ord].copy(der[i][ord-1]);
			der[i][ord].diff(0,1);
		}

		for (int i=0;i<Nc;i++)
		for (int j=0;j<Nc;j++) 
			subs_time_derivatives_up_to_order(der[i][ord], j, der[j], ord);

		if (verbosity) log("    eliminate all spatial derivatives up to time order=%d",ord+1);
		for (int i=0;i<Nc;i++)
		{
			// diagonal first
			eliminate_all_spatial_orders_for_given_time_order(der[i][ord], i, ord, der[i][ord]);

			for (int j=0;j<Nc;j++) if (i!=j)
			{
				eliminate_all_spatial_orders_for_given_time_order(der[j][ord], i, ord, der[i][ord]);
			}
		}

		if (verbosity) log("    backward substitution");
		for (int k=0;k<ord;k++)
		for (int i=0;i<Nc;i++)
		for (int j=0;j<Nc;j++)
			subs_time_derivatives_up_to_order(der[i][k], j, der[j], ord+1);

		for (int i=0;i<Nc;i++) der[i][ord].normal();

		work=false;
		int kt,kx,ky,kz;
		for (int i=0;i<Nc;i++)
		for (int j=0;j<Nc;j++)
			if (find_lowest_time_derivative(der[i][0], j, ORDER+1,kt,kx,ky,kz)) work = true;
	}

	// now der[][] contains no time derivatives !!!
	if (verbosity>1)
	for (int q=0;q<Nc;q++)
	for (int o=0;o<ORDER;o++)
	{
		cout << endl << " der["<<q<<"]["<<o<<"] printing "<<endl;
		der[q][o].print();
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////
	///////////////////////////////////////////////////////////////////////////////////////////////////////

	if (verbosity) log("step %d: output and save",step++);

	for (int pi=0;pi<Nc;pi++) der[pi][0].normal();
	// mass conservation
	pde_phys[0].copy(der[0][0]);
	pde_phys[0].mul(-(ex)1);
	pde_phys[0].eat(diff(bas[0],lbm.t));

	for (int d=1;d<Nc;d++)
	{
		// momentum 0: pde_phys[1] = expand(loc_normal( bas[0]*(diff(bas[1],t) - der[1][0]) + bas[1]*(diff(bas[0],t) - der[0][0]))  );
		aux.copy(der[0][0]);
		aux.mul(-bas[d]);
		pde_phys[d].copy(der[d][0]);
		pde_phys[d].mul(-bas[0]);
		pde_phys[d].add(aux);
		pde_phys[d].eat( bas[0]*diff(bas[d], lbm.t) + bas[d]*diff(bas[0], lbm.t) );
	}

	for (int pi=0;pi<Nc;pi++)
	{
		pde_phys[pi].normal();
		pde_phys[pi].save(target_dir, "pde_%02d%s", pi,(use_symbolic_coefs)?"_subs":"");
		pde_phys[pi].subs_vels = subs_vels;
		pde_phys[pi].save_tex(target_dir, "spatial_EPDE_raw","pde_%02d", pi);
//		pde_phys[pi].save_raw(target_dir, "spatial_EPDE_raw","pde_%02d", pi);
	}
}
