@q $Id: ps_tensor.hweb 741 2006-05-09 11:12:46Z kamenik $ @>
@q Copyright 2004, Ondra Kamenik @>

@*2 Even more general symmetry tensor. Start of {\tt ps\_tensor.h} file.

Here we define an abstraction for a tensor, which has a general
symmetry, but the symmetry is not of what is modelled by
|Symmetry|. This kind of tensor comes to existence when we evaluate
something like:
$$\left[B_{y^2u^3}\right]_{\alpha_1\alpha_2\beta_1\beta_2\beta_3}=
\cdots+\left[g_{y^3}\right]_{\gamma_1\gamma_2\gamma_3}
\left[g_{yu}\right]^{\gamma_1}_{\alpha_1\beta_3}
\left[g_{yu}\right]^{\gamma_2}_{\alpha_2\beta_1}
\left[g_u\right]^{\gamma_3}_{\beta_2}+\cdots
$$ 
If the tensors are unfolded, we obtain a tensor
$$g_{y^3}\cdot\left(g_{yu}\otimes g_{yu}\otimes g_{u}\right)$$

Obviously, this tensor can have a symmetry not compatible with
ordering $\alpha_1\alpha_2\beta_1\beta_2\beta_3$, (in other words, not
compatible with symmetry $y^2u^3$). In fact, the indices are permuted.

This kind of tensor must be added to $\left[B_{y^2u^3}\right]$. Its
dimensions are the same as of $\left[B_{y^2u^3}\right]$, but some
coordinates are permuted. The addition is the only action we need to
do with the tensor.

Another application where this permuted symmetry tensor appears is a
slice of a fully symmetric tensor. If the symmetric dimension of the
tensor is partitioned to continuous parts, and we are interested only
in data with a given symmetry (permuted) of the partitions, then we
have the permuted symmetry tensor. For instance, if $x$ is partitioned
$x=[a,b,c,d]$, and having tensor $\left[f_{x^3}\right]$, one can d a
slice (subtensor) $\left[f_{aca}\right]$. The data of this tensor are
permuted of $\left[f_{a^c}\right]$.

Here we also define the folded version of permuted symmetry tensor. It
has permuted symmetry and is partially folded. One can imagine it as a
product of a few dimensions, each of them is folded and having a few
variables. The underlying variables are permuted. The product of such
dimensions is described by |PerTensorDimens2|. The tensor holding the
underlying data is |FPSTensor|.

@s SortIntSequence int
@s PerTensorDimens int
@s UPSTensor int
@s PerTensorDimens2 int
@s FPSTensor int
@s KronProdFoldStacks int

@c

#ifndef PS_TENSOR_H
#define PS_TENSOR_H

#include "tensor.h"
#include "gs_tensor.h"
#include "equivalence.h"
#include "permutation.h"
#include "kron_prod.h"
#include "sparse_tensor.h"

@<|SortIntSequence| class declaration@>;
@<|PerTensorDimens| class declaration@>;
@<|UPSTensor| class declaration@>;
@<|PerTensorDimens2| class declaration@>;
@<|FPSTensor| class declaration@>;

#endif

@ This is just a helper class for ordering a sequence on call stack.

@<|SortIntSequence| class declaration@>=
class SortIntSequence : public IntSequence {
public:@;
	SortIntSequence(const IntSequence& s)
		: IntSequence(s) {@+ sort();@+}
};


@ Here we declare a class describing dimensions of permuted symmetry
tensor. It inherits from |TensorDimens| and adds a permutation which
permutes |nvmax|. It has two constructors, each corresponds to a
context where the tensor appears.

The first constructor calculates the permutation from a given equivalence.

The second constructor corresponds to dimensions of a slice. Let us
take $\left[f_{aca}\right]$ as an example. First it calculates
|TensorDimens| of $\left[f_{a^c}\right]$, then it calculates a
permutation corresponding to ordering of $aca$ to $a^2c$, and applies
this permutation on the dimensions as the first constructor. The
constructor takes only stack sizes (lengths of $a$, $b$, $c$, and
$d$), and coordinates of picked partitions.

Note that inherited methods |calcUnfoldColumns| and |calcFoldColumns|
work, since number of columns is independent on the permutation, and
|calcFoldColumns| does not use changed |nvmax|, it uses |nvs|, so it
is OK.

@<|PerTensorDimens| class declaration@>=
class PerTensorDimens : public TensorDimens {
protected:@;
	Permutation per;
public:@;
	PerTensorDimens(const Symmetry& s, const IntSequence& nvars,
					const Equivalence& e)
		: TensorDimens(s, nvars), per(e)
		{@+ per.apply(nvmax);@+}
	PerTensorDimens(const TensorDimens& td, const Equivalence& e)
		: TensorDimens(td), per(e)
		{@+ per.apply(nvmax);@+}
	PerTensorDimens(const TensorDimens& td, const Permutation& p)
		: TensorDimens(td), per(p)
		{@+ per.apply(nvmax);@+}
	PerTensorDimens(const IntSequence& ss, const IntSequence& coor)
		: TensorDimens(ss, SortIntSequence(coor)), per(coor)
		{@+ per.apply(nvmax);@+}
	PerTensorDimens(const PerTensorDimens& td)
		: TensorDimens(td), per(td.per)@+ {}
	const PerTensorDimens& operator=(const PerTensorDimens& td)
		{@+ TensorDimens::operator=(td);@+ per = td.per;@+ return *this;@+}
	bool operator==(const PerTensorDimens& td)
		{@+ return TensorDimens::operator==(td) && per == td.per;@+}
	int tailIdentity() const
		{@+ return per.tailIdentity();@+}
	const Permutation& getPer() const
		{@+ return per;@+}
};

@ Here we declare the permuted symmetry unfolded tensor. It has
|PerTensorDimens| as a member. It inherits from |UTensor| which
requires to implement |fold| method. There is no folded counterpart,
so in our implementation we raise unconditional exception, and return
some dummy object (just to make it compilable without warnings).

The class has two sorts of constructors corresponding to a context where it
appears. The first constructs object from a given matrix, and
Kronecker product. Within the constructor, all the calculations are
performed. Also we need to define dimensions, these are the same of
the resulting matrix (in our example $\left[B_{y^2u^3}\right]$) but
permuted. The permutation is done in |PerTensorDimens| constructor.

The second type of constructor is slicing. It makes a slice from
|FSSparseTensor|. The slice is given by stack sizes, and coordinates of
picked stacks.

There are two algorithms for filling a slice of a sparse tensor. The
first |fillFromSparseOne| works well for more dense tensors, the
second |fillFromSparseTwo| is better for very sparse tensors. We
provide a static method, which decides what of the two algorithms is
better.

@<|UPSTensor| class declaration@>=
class UPSTensor : public UTensor {
	const PerTensorDimens tdims;
public:@;
	@<|UPSTensor| constructors from Kronecker product@>;
	UPSTensor(const FSSparseTensor& t, const IntSequence& ss,
			  const IntSequence& coor, const PerTensorDimens& ptd);
	UPSTensor(const UPSTensor& ut)
		: UTensor(ut), tdims(ut.tdims)@+ {}

	void increment(IntSequence& v) const;
	void decrement(IntSequence& v) const;
	FTensor& fold() const;

	int getOffset(const IntSequence& v) const;
	void addTo(FGSTensor& out) const;
	void addTo(UGSTensor& out) const;

	enum fill_method {first, second};
	static fill_method decideFillMethod(const FSSparseTensor& t);
private:@;
	int tailIdentitySize() const;
	void fillFromSparseOne(const FSSparseTensor& t, const IntSequence& ss,
						   const IntSequence& coor);
	void fillFromSparseTwo(const FSSparseTensor& t, const IntSequence& ss,
						   const IntSequence& coor);
};

@ Here we have four constructors making an |UPSTensor| from a product
of matrix and Kronecker product. The first constructs the tensor from
equivalence classes of the given equivalence in an order given by the
equivalence. The second does the same but with optimized
|KronProdAllOptim|, which has a different order of matrices than given
by the classes in the equivalence. This permutation is projected to
the permutation of the |UPSTensor|. The third, is the same as the
first, but the classes of the equivalence are permuted by the given
permutation. Finally, the fourth is the most general combination. It
allows for a permutation of equivalence classes, and for optimized
|KronProdAllOptim|, which permutes the permuted equivalence classes.

@<|UPSTensor| constructors from Kronecker product@>=
	UPSTensor(const TensorDimens& td, const Equivalence& e,
			  const ConstTwoDMatrix& a, const KronProdAll& kp)
		: UTensor(along_col, PerTensorDimens(td, e).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, e)
		{@+ kp.mult(a, *this);@+}
	UPSTensor(const TensorDimens& td, const Equivalence& e,
			  const ConstTwoDMatrix& a, const KronProdAllOptim& kp)
		: UTensor(along_col, PerTensorDimens(td, Permutation(e, kp.getPer())).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, Permutation(e, kp.getPer()))
		{@+ kp.mult(a, *this);@+}
	UPSTensor(const TensorDimens& td, const Equivalence& e, const Permutation& p,
			  const ConstTwoDMatrix& a, const KronProdAll& kp)
		: UTensor(along_col, PerTensorDimens(td, Permutation(e, p)).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, Permutation(e, p))
		{@+ kp.mult(a, *this);@+}
	UPSTensor(const TensorDimens& td, const Equivalence& e, const Permutation& p,
			  const ConstTwoDMatrix& a, const KronProdAllOptim& kp)
		: UTensor(along_col, PerTensorDimens(td, Permutation(e, Permutation(p, kp.getPer()))).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, Permutation(e, Permutation(p, kp.getPer())))
		{@+ kp.mult(a, *this);@+}

@ Here we define an abstraction for the tensor dimension with the
symmetry like $xuv\vert uv\vert xu\vert y\vert y\vert x\vert x\vert
y$. These symmetries come as induces symmetries of equivalence and
some outer symmetry. Thus the underlying variables are permuted. One
can imagine the dimensions as an unfolded product of dimensions which
consist of folded products of variables.

We inherit from |PerTensorDimens| since we need the permutation
implied by the equivalence. The new member are the induced symmetries
(symmetries of each folded dimensions) and |ds| which are sizes of the
dimensions. The number of folded dimensions is return by |numSyms|.

The object is constructed from outer tensor dimensions and from
equivalence with optionally permuted classes.

@<|PerTensorDimens2| class declaration@>=
class PerTensorDimens2 : public PerTensorDimens {
	InducedSymmetries syms;
	IntSequence ds;
public:@;
	PerTensorDimens2(const TensorDimens& td, const Equivalence& e,
					 const Permutation& p)
		: PerTensorDimens(td, Permutation(e, p)),
		  syms(e, p, td.getSym()),
		  ds(syms.size())
		{@+ setDimensionSizes();@+}
	PerTensorDimens2(const TensorDimens& td, const Equivalence& e)
		: PerTensorDimens(td, e),
		  syms(e, td.getSym()),
		  ds(syms.size())
		{@+ setDimensionSizes();@+}
	int numSyms() const
		{@+ return (int)syms.size();@+}
	const Symmetry& getSym(int i) const
		{@+ return syms[i];@+}
	int calcMaxOffset() const
		{@+ return ds.mult(); @+}
	int calcOffset(const IntSequence& coor) const;
	void print() const;
protected:@;
	void setDimensionSizes();
};

@ Here we define an abstraction of the permuted symmetry folded
tensor. It is needed in context of the Faa Di Bruno formula for folded
stack container multiplied with container of dense folded tensors, or
multiplied by one full symmetry sparse tensor.

For example, if we perform the Faa Di Bruno for $F=f(z)$, where
$z=[g(x,y,u,v), h(x,y,u), x, y]^T$, we get for one concrete
equivalence:
$$
\left[F_{x^4y^3u^3v^2}\right]=\ldots+
\left[f_{g^2h^2x^2y}\right]\left(
[g]_{xv}\otimes[g]_{u^2v}\otimes
[h]_{xu}\otimes[h]_{y^2}\otimes
\left[\vphantom{\sum}[I]_x\otimes[I]_x\right]\otimes
\left[\vphantom{\sum}[I]_y\right]
\right)
+\ldots
$$

The class |FPSTensor| represents the tensor at the right. Its
dimension corresponds to a product of 7 dimensions with the following
symmetries: $xv\vert u^v\vert xu\vert y^2\vert x\vert x\vert y$. Such
the dimension is described by |PerTensorDimens2|.

The tensor is constructed in a context of stack container
multiplication, so, it is constructed from dimensions |td| (dimensions
of the output tensor), stack product |sp| (implied symmetries picking
tensors from a stack container, here it is $z$), then a sorted integer
sequence of the picked stacks of the stack product (it is always
sorted, here it is $(0,0,1,1,2,2,3)$), then the tensor
$\left[f_{g^2h^2x^2y}\right]$ (its symmetry must be the same as
symmetry given by the |istacks|), and finally from the equivalence
with permuted classes.

We implement |increment| and |getOffset| methods, |decrement| and
|unfold| raise an exception. Also, we implement |addTo| method, which
adds the tensor data (partially unfolded) to folded general symmetry
tensor.

@<|FPSTensor| class declaration@>=
template<typename _Ttype> class StackProduct;

class FPSTensor : public FTensor {
	const PerTensorDimens2 tdims;
public:@;
	@<|FPSTensor| constructors@>;

	void increment(IntSequence& v) const;
	void decrement(IntSequence& v) const;
	UTensor& unfold() const;

	int getOffset(const IntSequence& v) const;
	void addTo(FGSTensor& out) const;
};

@ As for |UPSTensor|, we provide four constructors allowing for
combinations of permuting equivalence classes, and optimization of
|KronProdAllOptim|. These constructors multiply with dense general
symmetry tensor (coming from the dense container, or as a dense slice
of the full symmetry sparse tensor). In addition to these 4
constructors, we have one constructor multiplying with general
symmetry sparse tensor (coming as a sparse slice of the full symmetry
sparse tensor).

@<|FPSTensor| constructors@>=
	FPSTensor(const TensorDimens& td, const Equivalence& e,
			  const ConstTwoDMatrix& a, const KronProdAll& kp)
		: FTensor(along_col, PerTensorDimens(td, e).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, e)
		{@+ kp.mult(a, *this);@+}
	FPSTensor(const TensorDimens& td, const Equivalence& e,
			  const ConstTwoDMatrix& a, const KronProdAllOptim& kp)
		: FTensor(along_col, PerTensorDimens(td, Permutation(e, kp.getPer())).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, e, kp.getPer())
		{@+ kp.mult(a, *this);@+}
	FPSTensor(const TensorDimens& td, const Equivalence& e, const Permutation& p,
			  const ConstTwoDMatrix& a, const KronProdAll& kp)
		: FTensor(along_col, PerTensorDimens(td, Permutation(e, p)).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, e, p)
		{@+ kp.mult(a, *this);@+}
	FPSTensor(const TensorDimens& td, const Equivalence& e, const Permutation& p,
			  const ConstTwoDMatrix& a, const KronProdAllOptim& kp)
		: FTensor(along_col, PerTensorDimens(td, Permutation(e, Permutation(p, kp.getPer()))).getNVX(),
				  a.nrows(), kp.ncols(), td.dimen()), tdims(td, e, Permutation(p, kp.getPer()))
		{@+ kp.mult(a, *this);@+}

	FPSTensor(const TensorDimens& td, const Equivalence& e, const Permutation& p,
			  const GSSparseTensor& t, const KronProdAll& kp);

	FPSTensor(const FPSTensor& ft)
		: FTensor(ft), tdims(ft.tdims)@+ {}

@ End of {\tt ps\_tensor.h} file.
