@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 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.