void TPDE::allocate(vct &ibas, int iMO, int iMFCO, symbol &it, symbol &ix, symbol &iy, symbol &iz)
{
	bas=ibas;
	MO=iMO;
	MFCO=iMFCO;
	Nbas=bas.size();
	t=it;
	x=ix;
	y=iy;
	z=iz;
	zero_ders.remove_all();
	MOindex = (MO+1)*(MO+1)*(MO+1)*(MO+1)*Nbas;

	// 1D data
	data1D0 = new ex[MOindex];
	data1D1 = new ex[MOindex];
	for (int j=0;j<MOindex;j++)
	{
		data1D0[j] = 0;
		data1D1[j] = 0;
	}
	// 0D data
	data0D = 0;

	basders = new ex[MOindex];
	for (int i=0;i<MOindex;i++) basders[i] = 0;

	for (int bi=0;bi<Nbas;bi++)
	for (int o=0;o<=MO;o++) for (int i0=0;i0<=o;i0++) for (int i1=0;i1<=o;i1++) for (int i2=0;i2<=o;i2++) for (int i3=0;i3<=o;i3++) if (i0+i1+i2+i3==o)
		basders[ind(bi,i0,i1,i2,i3)] = GiNaC::diff(GiNaC::diff(GiNaC::diff(GiNaC::diff( bas[bi], t, i0 ), x, i1), y, i2), z, i3);

	for (int bi=0;bi<Nbas;bi++)
	for (int o=1;o<=MO;o++) for (int i0=0;i0<=o;i0++) for (int i1=0;i1<=o;i1++) for (int i2=0;i2<=o;i2++) for (int i3=0;i3<=o;i3++) if (i0+i1+i2+i3==o)
	{
		zero_ders.append( basders[ind(bi,i0,i1,i2,i3)] == 0 );
		zero_ders.append( pow( basders[ind(bi,i0,i1,i2,i3)], 2) == 0 );
	}

	for (int bi=0;bi<Nbas;bi++)
	for (int o=MO+1;o<=MO+1;o++)
	for (int i0=0;i0<=o;i0++)
	for (int i1=0;i1<=o;i1++)
	for (int i2=0;i2<=o;i2++)
	for (int i3=0;i3<=o;i3++)
	if (i0+i1+i2+i3==o)
	{
		zero_ders.append( GiNaC::diff(GiNaC::diff(GiNaC::diff(GiNaC::diff( bas[bi], t, i0 ), x, i1), y, i2), z, i3) == 0);
		zero_ders.append( pow (GiNaC::diff(GiNaC::diff(GiNaC::diff(GiNaC::diff( bas[bi], t, i0 ), x, i1), y, i2), z, i3), 2) == 0);
	}
	
	empty();
}

void TPDE::eat(ex expression)
{
	if (expression==0) return;

	ex rest=GiNaC::expand(expression);
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	{
		// treat squares
		ex co;
		co=GiNaC::coeff( rest, basders[ind(bi,i0,i1,i2,i3)], 2 );
		if (co!=0)
		{
			rest = GiNaC::expand(rest - co*pow(basders[ind(bi,i0,i1,i2,i3)],2) );
			if (2*oi<=MFCO) // trim higher derivatives
				data_w(bi,i0,i1,i2,i3, bi,i0,i1,i2,i3) += GiNaC::expand( subs( GiNaC::expand(co), zero_ders ));
		}

		co=GiNaC::coeff( rest, basders[ind(bi,i0,i1,i2,i3)] );
		if (co!=0)
		{
			rest = GiNaC::expand(rest - co*basders[ind(bi,i0,i1,i2,i3)] );
			// now decompose co into basders again
			for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
			for (int bj=0;bj<Nbas;bj++)
			{
				ex co2=GiNaC::coeff( co, basders[ind(bj,j0,j1,j2,j3)] );
				if (co2!=0)
				{
					co = GiNaC::expand(co - co2*basders[ind(bj,j0,j1,j2,j3)] );
					if (oi+oj<=MFCO) // trim higher derivatives
						data_w( bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) += GiNaC::expand( subs( GiNaC::expand(co2), zero_ders ));
				}
			}
			if (co!=0)
				data_w( bi,i0,i1,i2,i3 ) += GiNaC::expand( subs( GiNaC::expand(co), zero_ders ));
		}
	}
	if (rest!=0) data0D += GiNaC::expand( subs( GiNaC::expand( rest ), zero_ders ));
}

void TPDE::print(ostringstream &out)
{
	ostringstream sout;
	// 0D
	if (data0D!=0)
	{
		sout << "1" << endl;
		sout << "\t" << data0D << endl;
	}
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (data_r( bi,i0,i1,i2,i3 )!=0)
	{
		sout << basders[ind(bi,i0,i1,i2,i3)] << endl;
		sout << "\t" << data_r( bi,i0,i1,i2,i3 ) <<endl;
	}

	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (data_r( bi,i0,i1,i2,i3, bj,j0,j1,j2,j3 )!=0)
	{
		sout << basders[ind(bi,i0,i1,i2,i3)] << "  *  " << basders[ind(bj,j0,j1,j2,j3)] << endl;
		sout << "\t" << data_r( bi,i0,i1,i2,i3, bj,j0,j1,j2,j3 ) <<endl;
	}

	string str1 = sout.str();
	str1 = ReplaceString( str1, "(t,x,y,z)", "");
	str1 = ReplaceString( str1, "(t,\\x,\\y,\\z)", "");
	str1 = ReplaceString( str1, "fu", "u");
	str1 = ReplaceString( str1, "fv", "v");
	str1 = ReplaceString( str1, "fw", "w");
	str1 = ReplaceString( str1, "frho", "frh");
	str1 = ReplaceString( str1, "o", "omega");
	str1 = ReplaceString( str1, "frh", "rho");
	out << str1 << endl;
}

void TPDE::copy(TPDE &A)
{
	free();
	allocate(A.bas,A.MO,A.MFCO,A.t,A.x,A.y,A.z);
	for (int i=0;i<MOindex;i++)
	{
		data1D0[i] = A.data1D0[i];
		data1D1[i] = A.data1D1[i];
	}
	swapit = A.swapit;
	data0D = A.data0D;

	// TODO: check whether this works with std::vector
	v_data2D0 = A.v_data2D0;
	v_data2D1 = A.v_data2D1;
}


void TPDE::empty(ex **d)
{
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		d[ind(bi,i0,i1,i2,i3)][ind(bj,j0,j1,j2,j3)]=0;
}

void TPDE::empty(ex *d)
{
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		d[ind(bi,i0,i1,i2,i3)]=0;
}

void TPDE::diff_worker(int i)
{
	symbol dd=t;
	int d0=1,d1=0,d2=0,d3=0;
	switch (i)
	{
		case 1: dd=x; d0=0; d1=1; d2=0; d3=0; break; // x
		case 2: dd=y; d0=0; d1=0; d2=1; d3=0; break; // y
		case 3: dd=z; d0=0; d1=0; d2=0; d3=1; break; // z
	}

	swap();
	ex prev0D=data0D;
	v_data2D().clear();
	empty(data1D());
	data0D=0;
	// diff 0D
	if (prev0D!=0) eat( GiNaC::expand( GiNaC::diff( prev0D, dd ) ) );

	// diff 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	{
		ex cA=prev(bi,i0,i1,i2,i3);
		// differentiation of a product  D(cA * dA) = D(cA) dA + cA * D(dA)
		if (cA!=0)
		{
			cA = GiNaC::expand( cA );
			// cA * D(dA)
			if (oi<MO) data_w(bi,i0+d0,i1+d1,i2+d2,i3+d3) += cA;
			// D(cA) * dA
			cA = GiNaC::expand( GiNaC::diff( cA, dd) );
			if (cA!=0 && oi+1<= MFCO) // trim higher derivatives
			{
				for (int bk=0;bk<Nbas;bk++)
				{
					ex co2 = GiNaC::coeff( cA, basders[ind(bk,d0,d1,d2,d3)] );
					if (co2!=0) data_w( bi,i0,i1,i2,i3, bk,d0,d1,d2,d3 ) += GiNaC::expand( subs( GiNaC::expand(co2), zero_ders ));
				}
			}
		}
	}

	// diff 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	{
		//  D( coef * dA * dB ) =  coef * [ D(dA)*dB + dA*D(dB) ]_{case1} + [ D(coef) * dA*dB ]_{case2}
		//  case 1a
		if (ind(bi,i0,i1,i2,i3) == ind(bj,j0,j1,j2,j3)) // diagonal term D( dA^2 ) = 2 dA D(dA)
		{
			if (oi<MO && 2*oi+1 <= MFCO)
				data_w( bi,i0+d0,i1+d1,i2+d2,i3+d3, bj,j0,j1,j2,j3) += (ex)2*prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
		} else
		{
			if (oi<MO && oi+1+oj<= MFCO)
				data_w(bi,i0+d0,i1+d1,i2+d2,i3+d3, bj,j0,j1,j2,j3) += prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
			if (oj<MO && oi+oj+1<= MFCO)
				data_w(bi,i0,i1,i2,i3,  bj,j0+d0,j1+d1,j2+d2,j3+d3) += prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
		}
	}
}

void TPDE::diff(int i, int order)
{
	if (order<=0) return;
	for (int j=0;j<order;j++) diff_worker(i);
}

bool TPDE::get_der_index(ex der, int &bi, int &i0, int &i1, int &i2, int &i3)
{
	bi=0;i0=0;i1=0;i2=0;i3=0;
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bj=0;bj<Nbas;bj++)
	if ( basders[ind(bj,j0,j1,j2,j3)] == der )
	{
		bi=bj;i0=j0;i1=j1;i2=j2;i3=j3;
		return true;
	}
	return false;
}

void TPDE::mul(ex e)
{
	// 0D
	data0D *= e;
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (data_r(bi,i0,i1,i2,i3)!=0)
		data_w(bi,i0,i1,i2,i3) *= e;
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) *= e;
}

void TPDE::add(TPDE &A)
{
	if (MO != A.MO || Nbas != A.Nbas) 
	{
		cout << "ERROR: TPDE::add() incompatible structures in addition"<<endl;
		return;
	}
	// 0D
	data0D += A.data0D;
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (A.data_r(bi,i0,i1,i2,i3) != 0)
		data_w(bi,i0,i1,i2,i3) += A.data_r(bi,i0,i1,i2,i3);
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (A.data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) != 0)
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) += A.data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
}


void TPDE::expand()
{
	// 0D
	data0D = GiNaC::expand( data0D );
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		data_w(bi,i0,i1,i2,i3) = GiNaC::expand( data_r(bi,i0,i1,i2,i3) );
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = GiNaC::expand( data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) );
}

void TPDE::normal()
{
	// 0D
	data0D = GiNaC::expand( GiNaC::normal( data0D ));
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		data_w(bi,i0,i1,i2,i3) = GiNaC::expand( GiNaC::normal( data_r(bi,i0,i1,i2,i3) ));
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = GiNaC::expand( GiNaC::normal( data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) ));
}

bool TPDE::compare(TPDE &A)
{
	if (Nbas != A.Nbas || MO != A.MO) return false;

	// 0D
	if (data0D != A.data0D) return false;
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		if (data_r(bi,i0,i1,i2,i3) != A.data_r(bi,i0,i1,i2,i3) ) return false;
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) != A.data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) ) return false;

	return true; // the same
}

bool TPDE::is_empty()
{
	// 0D
	if (data0D!=0) return false;
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		if (data_r(bi,i0,i1,i2,i3)!=0) return false;
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0) return false;
	return true;
}


ex TPDE::raw()
{
	// 0D
	ex result=data0D;
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (data_r(bi,i0,i1,i2,i3)!=0)
		result += data_r(bi,i0,i1,i2,i3)*basders[ind(bi,i0,i1,i2,i3)];
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
		result += data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)*basders[ind(bi,i0,i1,i2,i3)]*basders[ind(bj,j0,j1,j2,j3)];
	return result;
}

void TPDE::save_raw(const char* dirname, const char* subdirname, const char*fmt, ... )
{
	char name[1024];
	va_list args;
	va_start(args, fmt);
	vsprintf(name, fmt, args);
	va_end(args);

	char fn[1000], dn[1000];
	mkdir(dirname,0777);
	sprintf(dn,"%s/%s",dirname,subdirname);
	mkdir(dn,0777);
	// raw output
	ostringstream sout;
	print(sout);
	sprintf(fn,"%s/%s/%s.txt",dirname,subdirname,name);


	ostringstream sout2;
	sout2 << raw() << "=0" << endl;

//	sout_all << "Complete PDE that can be directly imported to Maxima (for instance):" << endl;
//	sout_all << raw() << endl << endl;
//	sout_all << "List of all coefficients of each partial derivatives:" << endl;
//	sout_all << sout.str() << endl;

	string str1 = sout2.str();
//	str1 = ReplaceString( str1, "(t,x,y,z)", "");
//	str1 = ReplaceString( str1, "(t,\\x,\\y,\\z)", "");
	str1 = ReplaceString( str1, "cs", "c_s");
	str1 = ReplaceString( str1, "frho", "rho");
	str1 = ReplaceString( str1, "rho", "rh");
	str1 = ReplaceString( str1, "o", "omega");
	str1 = ReplaceString( str1, "fu", "u");
	str1 = ReplaceString( str1, "fv", "v");
	str1 = ReplaceString( str1, "fw", "w");
	str1 = ReplaceString( str1, "rh", "rho");

	ofstream fout(fn);
//	fout << str1;
	fout << "Complete expression of PDE that can be directly imported to Maxima:" << endl;
	fout << str1 << endl << endl;
	fout << "List of all coefficients of each partial derivatives:" << endl;
	fout << sout.str() << endl;
	fout.close();
}

void TPDE::save(const char* dirname, const char*fmt, ... )
//void TPDE::save(const char* dirname, const char*fmt, ... )
{
	char name[1024];
	va_list args;
	va_start(args, fmt);
	vsprintf(name, fmt, args);
	va_end(args);

	char fn[1000], dn[1000];
	mkdir(dirname,0777);
//	sprintf(dn,"%s/%s",dirname,subdirname);
//	mkdir(dn,0777);
//	sprintf(dn,"%s/%s/%s",dirname,subdirname,name);
//	mkdir(dn,0777);
	sprintf(dn,"%s/%s",dirname,name);
	mkdir(dn,0777);

	// 0D
	if (data0D!=0)
	{
		stringstream sfn;
		sfn << dn << "/coef_1";
		ostringstream ssn;
		ssn << data0D;
		ofstream fout(sfn.str());
		fout << replaceStringForExport(ssn.str()) <<endl;
		fout.close();
	}

	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (data_r(bi,i0,i1,i2,i3)!=0)
	{
		stringstream sfn;
		sfn << dn << "/coef_" << bas[bi] << "_";
		for (int j=0;j<i0;j++) sfn << "t";
		for (int j=0;j<i1;j++) sfn << "x";
		for (int j=0;j<i2;j++) sfn << "y";
		for (int j=0;j<i3;j++) sfn << "z";
		ostringstream ssn;
		ssn << data_r(bi,i0,i1,i2,i3);
		ofstream fout(sfn.str());
		fout << replaceStringForExport(ssn.str()) <<endl;
		fout.close();
	}
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
	{
		stringstream sfn;
		sfn << dn << "/coef_" << bas[bi] << "_";
		for (int j=0;j<i0;j++) sfn << "t";
		for (int j=0;j<i1;j++) sfn << "x";
		for (int j=0;j<i2;j++) sfn << "y";
		for (int j=0;j<i3;j++) sfn << "z";
		sfn << "__X__" << bas[bj] << "_";
		for (int j=0;j<j0;j++) sfn << "t";
		for (int j=0;j<j1;j++) sfn << "x";
		for (int j=0;j<j2;j++) sfn << "y";
		for (int j=0;j<j3;j++) sfn << "z";
		ostringstream ssn;
		ssn << data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
		ofstream fout(sfn.str());
		fout << replaceStringForExport(ssn.str()) <<endl;
		fout.close();
	}
}

string TPDE::replaceStringForExport(string s)
{
	string str1 = s;
	str1 = ReplaceString( str1, "(t,x,y,z)", "");
	str1 = ReplaceString( str1, "(t,\\x,\\y,\\z)", "");
	str1 = ReplaceString( str1, "fu", "u");
	str1 = ReplaceString( str1, "fv", "v");
	str1 = ReplaceString( str1, "fw", "w");
	str1 = ReplaceString( str1, "frho", "rho");
	str1 = ReplaceString( str1, "cs", "c_s");
	// hot fix!
	str1 = ReplaceString( str1, "omega_10", "omega_{10}");
	str1 = ReplaceString( str1, "omega_11", "omega_{11}");
	str1 = ReplaceString( str1, "omega_12", "omega_{12}");
	str1 = ReplaceString( str1, "omega_13", "omega_{13}");
	str1 = ReplaceString( str1, "omega_14", "omega_{14}");
	str1 = ReplaceString( str1, "omega_15", "omega_{15}");
	str1 = ReplaceString( str1, "omega_16", "omega_{16}");
	str1 = ReplaceString( str1, "omega_17", "omega_{17}");
	str1 = ReplaceString( str1, "omega_18", "omega_{18}");
	str1 = ReplaceString( str1, "omega_19", "omega_{19}");
	str1 = ReplaceString( str1, "omega_20", "omega_{20}");
	str1 = ReplaceString( str1, "omega_21", "omega_{21}");
	str1 = ReplaceString( str1, "omega_22", "omega_{22}");
	str1 = ReplaceString( str1, "omega_23", "omega_{23}");
	str1 = ReplaceString( str1, "omega_24", "omega_{24}");
	str1 = ReplaceString( str1, "omega_25", "omega_{25}");
	str1 = ReplaceString( str1, "omega_26", "omega_{26}");
	str1 = ReplaceString( str1, "omega_27", "omega_{27}");
	return str1;
}

string TPDE::derivative_tex(int bj, int j0, int j1, int j2, int j3)
{
	ostringstream s;
	s<<latex;
	int o=j0+j1+j2+j3;
	if (o!=0)
	{
		if (o==1)
			s << "\\frac{ \\partial "<< bas[bj] << "}";
		else
			s << "\\frac{ \\partial^"<< o <<" "<< bas[bj] << "}";
		s << "{";
		if (j0>0) { if (j0==1) s << "\\partial t"; else s << "\\partial t^{"<<j0<<"}"; }
		if (j1>0) { if (j1==1) s << "\\partial \\x"; else s << "\\partial \\x^{"<<j1<<"}"; }
		if (j2>0) { if (j2==1) s << "\\partial \\y"; else s << "\\partial \\y^{"<<j2<<"}"; }
		if (j3>0) { if (j3==1) s << "\\partial \\z"; else s << "\\partial \\z^{"<<j3<<"}"; }
		s << "}";
	}
	string str1 = s.str();
	str1 = ReplaceString( str1, "(t,x,y,z)", "");
	str1 = ReplaceString( str1, "(t,\\x,\\y,\\z)", "");
	return str1;
}

string TPDE::derivative_tex_short(int bj, int j0, int j1, int j2, int j3)
{
	ostringstream s;
	s<<latex;
	int o=j0+j1+j2+j3;
	if (o!=0)
	{
		s << "{";
		if (j0>0) { if (j0==1) s << "\\D_t"; else s << "\\D_t^{"<<j0<<"}"; }
		if (j1>0) { if (j1==1) s << "\\D_x"; else s << "\\D_x^{"<<j1<<"}"; }
		if (j2>0) { if (j2==1) s << "\\D_y"; else s << "\\D_y^{"<<j2<<"}"; }
		if (j3>0) { if (j3==1) s << "\\D_z"; else s << "\\D_z^{"<<j3<<"}"; }
		s << bas[bj] << "}";
	}
	string str1 = s.str();
	str1 = ReplaceString( str1, "(t,x,y,z)", "");
	str1 = ReplaceString( str1, "(t,\\x,\\y,\\z)", "");
	return str1;
}

// multiindex
string TPDE::derivative_tex_multiindex(int bj, int j0, int j1, int j2, int j3, int dim)
{
	ostringstream s;
	s<<latex;
	if (dim==1) s << "\\D_{("<<j0<<","<<j1<<")}"<<bas[bj];
	if (dim==2) s << "\\D_{("<<j0<<","<<j1<<","<<j2<<")}"<<bas[bj];
	if (dim==3) s << "\\D_{("<<j0<<","<<j1<<","<<j2<<","<<j3<<")}"<<bas[bj];
	string str1 = s.str();
	str1 = ReplaceString( str1, "(t,x,y,z)", "");
	str1 = ReplaceString( str1, "(t,\\x,\\y,\\z)", "");
	return str1;
}

void TPDE::save_tex(const char* dirname, const char* subdirname, const char*fmt, ... )
{
	char name[1024];
	va_list args;
	va_start(args, fmt);
	vsprintf(name, fmt, args);
	va_end(args);

	char fn[1000], dn[1000];
	mkdir(dirname,0777);
	sprintf(dn,"%s/%s",dirname,subdirname);
	mkdir(dn,0777);

	sprintf(fn,"%s/%s/%s.tex",dirname,subdirname,name);
	ofstream fout(fn);
	fout << latex;
	// header
	fout<< "\\begin{flushleft}" << endl;
	fout<< "\\noindent\n$\n";

	bool first=true;
	for (int ok=1;ok<=2*MO;ok++)
	for (int oi=1;oi<=MO;oi++)  for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) for (int i0=0;i0<=oi;i0++) if (i0+i1+i2+i3==oi)
	{
		// 1D terms
		if (oi==ok)
		for (int bi=0;bi<Nbas;bi++)
		{
			ex co = data_r(bi,i0,i1,i2,i3);
			if (co!=0)
			{
				if (first) first=false; else fout << "+" << endl;
				if (co!=1) fout << factorize_latex(co) << endl;
				fout << "{\\color{blue}"<< derivative_tex(bi, i0, i1, i2, i3) << "}"<<endl;
			}
		}

		// 2D terms
		for (int oj=1;oj<=MO;oj++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) for (int j0=0;j0<=oj;j0++) if (j0+j1+j2+j3==oj)
		if (oi+oj==ok)
		for (int bj=0;bj<Nbas;bj++)
		for (int bi=0;bi<Nbas;bi++)
		{
			ex co = data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
			if (co!=0)
			{
				if (first) first=false; else fout << "+" << endl;
				if (co!=1) fout << factorize_latex(co) << endl;
				if (ind(bi,i0,i1,i2,i3)==ind(bj,j0,j1,j2,j3))
					fout << "\\left({\\color{blue}"<<derivative_tex(bj, j0, j1, j2, j3)<< "}\\right)^2"<<endl;
				else
					fout << "{\\color{blue}"<< derivative_tex(bi, i0, i1, i2, i3) << " "<< derivative_tex(bj, j0, j1, j2, j3)<< "}"<<endl;
			}
		}
	}
	if (data0D!=0)
		fout << "= " << factorize_latex(-data0D) <<endl;
	 else
	 	fout << "=0";
	fout<< ".$\n\\end{flushleft}" << endl;
	fout.close();
}

void TPDE::save_tex2(const char* dirname, const char* subdirname, const char*fmt, ... )
{
	char name[1024];
	va_list args;
	va_start(args, fmt);
	vsprintf(name, fmt, args);
	va_end(args);

	char fn[1000], dn[1000];
	mkdir(dirname,0777);
	sprintf(dn,"%s/%s",dirname,subdirname);
	mkdir(dn,0777);

	char coefid[1024];
	sprintf(coefid,"%s_%s_%s_",dirname,subdirname,name); // unique id for hyperrefs

	ostringstream sout1, sout2; // sout2 is to store extra substitutions
	sout1 << latex;
	sout2 << latex;
	// header
	sout1<< "\\begin{flushleft}" << endl;
	sout1<< "\\noindent\n";
	
	int counter=0;

	bool first=true;
	for (int ok=1;ok<=2*MO;ok++)
	for (int oi=1;oi<=MO;oi++) for (int i3=0;i3<=oi;i3++)  for (int i2=0;i2<=oi;i2++) for (int i1=0;i1<=oi;i1++) for (int i0=0;i0<=oi;i0++) if (i0+i1+i2+i3==oi)
	{
		// 1D terms
		if (oi==ok)
		for (int bi=0;bi<Nbas;bi++)
		{
			ex co = data_r(bi,i0,i1,i2,i3);
			if (co!=0)
			{
				if (!is_a<GiNaC::add>(co) && isnegative(co))
				{
					co*=-1;
					sout1 << "$-$\n";
					if (first) first=false;
				} else
				{
					if (first) first=false; else sout1 << "$+$\n";
				}
				if (co!=1) factorize_latex(sout1,sout2,co,counter,coefid); else sout1 << "$";
				sout1 << "{\\color{blue}"<< derivative_tex(bi, i0, i1, i2, i3) << "}$\n";
			}
		}

		// 2D terms
		for (int oj=1;oj<=MO;oj++) for (int j3=0;j3<=oj;j3++) for (int j2=0;j2<=oj;j2++) for (int j1=0;j1<=oj;j1++) for (int j0=0;j0<=oj;j0++) if (j0+j1+j2+j3==oj)
		if (oi+oj==ok)
		for (int bj=0;bj<Nbas;bj++)
		for (int bi=0;bi<Nbas;bi++)
		{
			ex co = data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
			if (co!=0)
			{
				if (!is_a<GiNaC::add>(co) && isnegative(co))
				{
					co*=-1;
					sout1 << "$-$\n";
					if (first) first=false;
				} else
				{
					if (first) first=false; else sout1 << "$+$\n";
				}
				if (co!=1) factorize_latex(sout1,sout2,co,counter,coefid); else sout1 << "$";
				if (ind(bi,i0,i1,i2,i3)==ind(bj,j0,j1,j2,j3))
					sout1 << "\\left({\\color{blue}"<<derivative_tex(bj, j0, j1, j2, j3)<< "}\\right)^2$\n";
				else
					sout1 << "{\\color{blue}"<< derivative_tex(bi, i0, i1, i2, i3) << " "<< derivative_tex(bj, j0, j1, j2, j3)<< "}$\n";
			}
		}
	}
	if (data0D!=0)
	{
		sout1 << "$= ";
		factorize_latex(sout1,sout2,-data0D,counter,coefid);
	}
	else
	{
		sout1 << "$=0";
	}
	if (counter==0)
		sout1<< ".$\n";
	else
		sout1<< ",$\n\\vskip 1em\nwhere:\n";
	sout1<<"\\end{flushleft}\n";
	sprintf(fn,"%s/%s/%s.tex",dirname,subdirname,name);
	ofstream fout(fn);
	fout << latex;
	fout << sout1.str() << endl;
	// put coefs definitions if any
	if (sout2.str().length()>0)
	{
		fout << "\\begin{flushleft}" << endl;
		fout << "\\scriptsize"<<endl;
		fout << "\\noindent\n";
		fout << sout2.str();
		fout << "\\end{flushleft}" << endl;
	}
	fout.close();
}


bool TPDE::nonzero_coeff(int bk, int k0, int k1, int k2, int k3)
{
	// 1D
	if (data_r(bk,k0,k1,k2,k3)!=0) return true;
	// 2D
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bj=0;bj<Nbas;bj++)
	{
		if (ind(bk,k0,k1,k2,k3)<=ind(bj,j0,j1,j2,j3))
		if (data_r(bk,k0,k1,k2,k3, bj,j0,j1,j2,j3)!=0)
			return true;

		if (ind(bj,j0,j1,j2,j3)<=ind(bk,k0,k1,k2,k3))
		if (data_r(bj,j0,j1,j2,j3, bk,k0,k1,k2,k3)!=0)
			return true;
	}
	return false;
}


bool TPDE::replace_der_by_expr(int bk, int kt, int kx, int ky, int kz, TPDE &subsexpr)
{
	bool replaced_at_least_one=false;
	// case 1: check quadradic term
	ex c00 = data_r(bk,kt,kx,ky,kz, bk,kt,kx,ky,kz);
	if (c00!=0)
	{
		TPDE A;
		data_w(bk,kt,kx,ky,kz, bk,kt,kx,ky,kz) = 0;
		A.copy(subsexpr);
		A.mul(subsexpr); 	// A = loc_se^2
		A.mul(c00);		// A = c00 loc_se^2
		add(A);			// add A to this
		replaced_at_least_one=true;
	}
	// case 2: check the remaining terms
	// create B === coefs by basders[ind(bi,kt,kx,ky,kz)] without the quadratic term above
	TPDE B;
	B.allocate(bas, MO, MFCO, t, x, y, z); // copy of "this"
	// now select only entries that correspond to basders[ind(bk,kt,kx,ky,kz)] ... without the quadratic term obviously
	// 1D
	B.data0D = data_r(bk,kt,kx,ky,kz);
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (ind(bi,i0,i1,i2,i3) != ind(bk,kt,kx,ky,kz)) // this is not necessary, the quadratic term is zero anyway
		B.data_w(bi,i0,i1,i2,i3) = data_r(bi,i0,i1,i2,i3, bk,kt,kx,ky,kz) + data_r(bk,kt,kx,ky,kz, bi,i0,i1,i2,i3);
	// now zero these term in "this"
	// 1D
	data_w(bk,kt,kx,ky,kz) = 0;
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	{
		//if (data(bi,i0,i1,i2,i3, bk,kt,kx,ky,kz)!=0)
		data_w(bi,i0,i1,i2,i3, bk,kt,kx,ky,kz) = 0;
		//if (data(bk,kt,kx,ky,kz, bi,i0,i1,i2,i3)!=0)
		data_w(bk,kt,kx,ky,kz, bi,i0,i1,i2,i3) = 0;
	}

	if (!B.is_empty())
	{
		B.mul(subsexpr);
		// now add the result in "this"
		add(B);
		replaced_at_least_one=true;
	}

	return replaced_at_least_one;
}


void TPDE::mul(TPDE &A)
{
	// A = C0 + sum_k Ck dk + sum_k sum_l Ckl dk dl
	// B = D0 + sum_k Dk dk + sum_k sum_l Dkl dk dl
	// with C0=0=D0 !!!
	// so, A*B = sum_k sum_l Ck Dl dk dl only ;-)
	// so in multiplication, only Ck dk coefficients will be there because of truncation !!!
	swap();

	v_data2D().clear();
	empty(data1D());
	ex prev0D = data0D;

	// 0D * A.0D:
	data0D = prev0D*A.data0D;
	// 0D * A.1D + 1D * A.0D:
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		data_w(bi,i0,i1,i2,i3) = prev0D * A.data_r(bi,i0,i1,i2,i3) + prev(bi,i0,i1,i2,i3) * A.data0D;

	// 0D * A.2D + 2D * A.0D:
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (oi+oj<=MFCO)
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = prev0D * A.data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) + prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) * A.data0D;

	// 1D * A.1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (oi+oj<=MFCO)
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) += prev(bi,i0,i1,i2,i3) * A.data_r(bj,j0,j1,j2,j3);
}

void TPDE::swap()
{
	swapit = !swapit;
}

void TPDE::subs_polynomial1(ex target, TPDE& expression)
{
	ex expr = expression.raw();
//	swap();
	// now work with new clean coefs

//	TODO: now its brute force only
	ex src;
	ex result;

	src = raw();

	int it=0;
	// TREAT THIS CAREFULLY
	while (src.degree( target ) > 0)
	{
		it++;
//		cout << "before subs higher degree: " << src.degree( target ) << endl;
//	cout << "before subs: \n"<<src<<endl<<endl;
		result = GiNaC::expand( src.subs( target == expr ) );
//	cout << "after subs: \n"<<result<<endl<<endl;
//	result = GiNaC::expand( result.subs( target == expr ) );
//	result = GiNaC::expand( result.subs( target == expr ) );
//		cout << "after subs higher degree: " << src.degree( target ) << endl;
		empty();
		eat( result );

		// second 
		src = raw();
//		cout << "after eat raw subs higher degree: " << src.degree( target ) << endl;
		if (it>100) { cout << "failsafe triggered it " << it << endl; break; }
		break;
	}
}

void TPDE::subs_polynomial2(ex target, TPDE& expression)
{
	// this is what will be substituted for all targets
	ex expr = expression.raw();
	ex coef;
	ex coef_cleared;
	ex delta;
	// TODO: should we call 'swap()' here ???
//	swap();
	cout << "0D"<<endl;
	// 0D
	{
		coef = data0D;
		coef_cleared = GiNaC::expand( coef.subs( target == 0 ) );
		delta = GiNaC::expand( coef - coef_cleared );
		// delta will be eaten
		if (delta != 0)
		{
			data0D = coef_cleared;
			eat( delta.subs( target == expr) );
		}
	}
	// 1D
	cout << "1D"<<endl;
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (data_r(bi,i0,i1,i2,i3)!=0)
	{
//		result += data_r(bi,i0,i1,i2,i3)*basders[ind(bi,i0,i1,i2,i3)];
		coef = data_r(bi,i0,i1,i2,i3);
		coef_cleared = GiNaC::expand( coef.subs( target == 0 ) );
		delta = GiNaC::expand( coef - coef_cleared );
		if (delta != 0)
		{
			// delta will be eaten
			data_w(bi,i0,i1,i2,i3) = coef_cleared;
			eat( delta.subs( target == expr)*basders[ind(bi,i0,i1,i2,i3)] );
		}
	}
	// 2D
	cout << "2D"<<endl;
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
	{
//		result += data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)*basders[ind(bi,i0,i1,i2,i3)]*basders[ind(bj,j0,j1,j2,j3)];
		coef = data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
		coef_cleared = GiNaC::expand( coef.subs( target == 0 ) );
		delta = GiNaC::expand( coef - coef_cleared );
		// delta will be eaten
		if (delta != 0)
		{
			data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = coef_cleared;
			eat( delta.subs( target == expr)*basders[ind(bi,i0,i1,i2,i3)]*basders[ind(bj,j0,j1,j2,j3)] );
		}
	}
}


bool TPDE::subs_polynomial3_worker(ex target, TPDE& loc, ex& coef)
{
	// work with coef
	// check for correct degrees
	if (coef.ldegree(target)==0 && coef.degree(target)==0) return true; // nothing to do
	if (coef.ldegree(target)<0 || coef.degree(target)>2)
	{ 
		cout << "subs_polynomial3_worker error: incorrect degrees of polynomials to work with"<<endl;
		cout << "ldeg: "<<coef.ldegree(target)<<endl;
		cout << "hdeg: "<<coef.degree(target)<<endl;
		return false;
	}
	ex c0 = coef.coeff(target,0);
	ex c1 = (coef.degree(target)>=1) ? coef.coeff(target,0) : 0;
	ex c2 = (coef.degree(target)>=2) ? coef.coeff(target,1) : 0;
	coef = c0;
	if (c1!=0) // add /
	{
	}
	
	if (c2!=0)
	{
	}

//	cout << "coefs:"<<endl;
//	for (int i=0;i<=coef.degree(target);i++)
//	{
//		cout << "coef for i="<<i<<" is "<< coef.coeff(target,i) << endl;
//	}
	return true;
}

void TPDE::subs_polynomial3(ex target, TPDE& expression)
{
	const int MAXHDEG = 100;

	int ldeg = ldegree(target);
	int hdeg = degree(target);
	if (ldeg==0 && hdeg==0) return; // nothing to do
	if (ldeg<0)// || hdeg>2)
	{ 
		cout << "subs_polynomial3 error: incorrect degrees of polynomials to work with"<<endl;
		cout << "ldeg: "<<ldeg<<endl;
		cout << "hdeg: "<<hdeg<< " max_hdeg: "<<MAXHDEG<<endl;
		return;
	}
	// makes a copy of TPDE just to make sure we are not working on the same STRUCT ,,,
	TPDE loc;
	loc.copy(expression);
	// allocate TPDEs

	swap();
	// important!!!
        v_data2D().clear();
        empty(data1D());

	TPDE c[MAXHDEG]; // struct to store order 1 coefficient
	for (int i=0;i<hdeg;i++) c[i].allocate(bas, MO, MFCO, t, x, y, z);

	//cout << "0D"<<endl;
	// 0D
	{
		ex c0_data0D = data0D.coeff(target,0);
		for (int i=0;i<hdeg;i++)
			c[i].data0D = data0D.coeff(target,i+1);
		// rewrite
		data0D = c0_data0D;
	}
	// 1D
	//cout << "1D"<<endl;
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (prev(bi,i0,i1,i2,i3)!=0)
	{
		data_w(bi,i0,i1,i2,i3) = prev(bi,i0,i1,i2,i3).coeff(target,0);
		for (int i=0;i<hdeg;i++)
			c[i].data_w(bi,i0,i1,i2,i3) = prev(bi,i0,i1,i2,i3).coeff(target,i+1);
	}
	// 2D
	//cout << "2D"<<endl;
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
	{
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3).coeff(target,0);
		for (int i=0;i<hdeg;i++)
			c[i].data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = prev(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3).coeff(target,i+1);
	}
	// substitute
	TPDE loc2;
	for (int i=0;i<hdeg;i++)
	{
		//cout << "subs powers "<<i<<" of "<<hdeg-1<<endl;
		// produces power of loc
		if (i==0)
			loc2.copy(loc);
		else
			loc2.mul(loc);
		loc2.normal();
		// multiply coef * loc^power
		c[i].mul(loc2);
		// add the result
		add(c[i]);
	}

	normal();
}



// replace all coeffs by symbolic polynomial based on bas2
void TPDE::replace_coefs(int &sym_index, vct &bas2, lst  &sym_subs, const char *prefix="C")
{
	ex c;
	// 0D
	if (data0D != 0)
	{
		c = data0D;
		replace_coefs_expression(c, sym_index, bas2, sym_subs, prefix);
		data0D =c;
	}
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	if (data_r(bi,i0,i1,i2,i3)!=0)
	{
		c = data_r(bi,i0,i1,i2,i3);
		replace_coefs_expression(c, sym_index, bas2, sym_subs, prefix);
		data_w(bi,i0,i1,i2,i3) = c;
	}
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
	if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
	{
		c = data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3);
		replace_coefs_expression(c, sym_index, bas2, sym_subs, prefix);
		data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = c;
	}
}


int TPDE::ldegree(ex& term)
{
	ex c;
	int ld=100;
	// 0D
	ld = std::min(ld, data0D.ldegree(term));
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		ld = std::min(ld, data_r(bi,i0,i1,i2,i3).ldegree(term));
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		ld = std::min(ld, data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3).ldegree(term));
	return ld;
}

int TPDE::degree(ex& term)
{
	ex c;
	int ld=0;
	// 0D
	ld = std::max(ld, data0D.degree(term));
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
		ld = std::max(ld, data_r(bi,i0,i1,i2,i3).degree(term));
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
		ld = std::max(ld, data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3).degree(term));
	return ld;
}

void TPDE::load(const char* dn, symtab &table)
{
	if (!file_exists(dn))
	{
		cerr << dn <<" not found"<< endl;
		return;
	}

	char fn[1000];
	parser reader(table);	// optional

	// 0D
	{
		stringstream sfn;
		sfn << dn << "/coef_1";
		if (file_exists(sfn.str())) data0D = load_ex(sfn.str(), reader);
	}
	// 1D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int bi=0;bi<Nbas;bi++)
	{
		stringstream sfn;
		sfn << dn << "/coef_" << bas[bi] << "_";
		for (int j=0;j<i0;j++) sfn << "t";
		for (int j=0;j<i1;j++) sfn << "x";
		for (int j=0;j<i2;j++) sfn << "y";
		for (int j=0;j<i3;j++) sfn << "z";
		if (file_exists(sfn.str())) data_w(bi,i0,i1,i2,i3) = load_ex(sfn.str(), reader);
	}
	// 2D
	for (int oi=1;oi<=MO;oi++) for (int i0=0;i0<=oi;i0++) for (int i1=0;i1<=oi;i1++) for (int i2=0;i2<=oi;i2++) for (int i3=0;i3<=oi;i3++) if (i0+i1+i2+i3==oi)
	for (int oj=1;oj<=MO;oj++) for (int j0=0;j0<=oj;j0++) for (int j1=0;j1<=oj;j1++) for (int j2=0;j2<=oj;j2++) for (int j3=0;j3<=oj;j3++) if (j0+j1+j2+j3==oj)
	for (int bi=0;bi<Nbas;bi++)
	for (int bj=0;bj<Nbas;bj++)
	if (oi+oj<=MFCO) // trim higher orders
	if (ind(bi,i0,i1,i2,i3)<=ind(bj,j0,j1,j2,j3))
//	if (data_r(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3)!=0)
	{
		stringstream sfn;
		sfn << dn << "/coef_" << bas[bi] << "_";
		for (int j=0;j<i0;j++) sfn << "t";
		for (int j=0;j<i1;j++) sfn << "x";
		for (int j=0;j<i2;j++) sfn << "y";
		for (int j=0;j<i3;j++) sfn << "z";
		sfn << "__X__" << bas[bj] << "_";
		for (int j=0;j<j0;j++) sfn << "t";
		for (int j=0;j<j1;j++) sfn << "x";
		for (int j=0;j<j2;j++) sfn << "y";
		for (int j=0;j<j3;j++) sfn << "z";
		if (file_exists(sfn.str())) data_w(bi,i0,i1,i2,i3, bj,j0,j1,j2,j3) = load_ex(sfn.str(), reader);
//cout << "__X__" << dn << endl;
	}
}



void TPDE::free()
{
	v_data2D0.clear();
	v_data2D1.clear();

	if (data1D0) delete [] data1D0;
	if (data1D1) delete [] data1D1;
	if (basders) delete [] basders;
	zero_ders.remove_all();
}
