1 /*
2  * Copyright © 2004 Ondra Kamenik
3  * Copyright © 2019 Dynare Team
4  *
5  * This file is part of Dynare.
6  *
7  * Dynare is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Dynare is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Dynare.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 // Tensor polynomial evaluation.
22 
23 /* We need to evaluate a tensor polynomial of the form:
24 25    [g_x]_α₁ [x]^α₁ + [g_x²]_α₁α₂ [x]^α₁ [x]^α₂ + … + [g_xⁿ]_α₁…αₙ  ∏  [x]^αᵢ
26                                                                   ⁱ⁼¹
27    where x is a column vector.
28 
29    We have basically two options. The first is to use the formula above,
30    the second is to use a Horner-like formula:
31 
32     ⎡ ⎡                                                   ⎤ ⎤
33     ⎢ ⎢⎡                                  ⎤               ⎥ ⎥
34     ⎢…⎢⎢[g_xⁿ⁻¹] + [g_xⁿ]_α₁…αₙ₋₁αₙ [x]^αₙ⎥       [x]^αₙ₋₁⎥…⎥  [x]^α₁
35     ⎢ ⎢⎣                                  ⎦α₁…αₙ₋₁        ⎥ ⎥
36     ⎣ ⎣                                                   ⎦ ⎦α₁
37 
38    Alternatively, we can put the the polynomial into a more compact form
39 
40                ₙ ⎡1⎤αᵢ
41     [G]_α₁…αₙ  ∏ ⎢ ⎥
42               ⁱ⁼¹⎣x⎦
43 
44    Then the polynomial evaluation becomes just a matrix multiplication of the
45    vector power.
46 
47    Here we define the tensor polynomial as a container of full symmetry
48    tensors and add an evaluation methods. We have two sorts of
49    containers, folded and unfolded. For each type we declare two methods
50    implementing the above formulas. We define classes for the
51    compactification of the polynomial. The class derives from the tensor
52    and has a eval method. */
53 
54 #include "t_container.hh"
55 #include "fs_tensor.hh"
56 #include "rfs_tensor.hh"
57 #include "tl_static.hh"
58 #include "pascal_triangle.hh"
59 
60 #include <memory>
61 
62 /* Just to make the code nicer, we implement a Kronecker power of a
63    vector encapsulated in the following class. It has getNext() method
64    which returns either folded or unfolded row-oriented single column
65    Kronecker power of the vector according to the type of a dummy
66    argument. This allows us to use the type dependent code in templates
67    below.
68 
69    The implementation of the Kronecker power is that we maintain the last
70    unfolded power. If unfolded getNext() is called, we Kronecker multiply
71    the last power with a vector and return it. If folded getNext() is
72    called, we do the same plus we fold it.
73 
74    getNext() returns the vector for the first call (first power), the
75    second power is returned on the second call, and so on. */
76 
77 class PowerProvider
78 {
79   Vector origv;
80   std::unique_ptr<URSingleTensor> ut;
81   std::unique_ptr<FRSingleTensor> ft;
82   int nv;
83 public:
PowerProvider(const ConstVector & v)84   PowerProvider(const ConstVector &v)
85     : origv(v), nv(v.length())
86   {
87   }
88 
89   /*
90     We need to select getNext() implementation at compile type depending on a
91     type parameter.
92 
93     Unfortunately full specialization is not possible at class scope. This may
94     be a bug in GCC 6. See:
95     https://stackoverflow.com/questions/49707184/explicit-specialization-in-non-namespace-scope-does-not-compile-in-gcc
96 
97     Apply the workaround suggested in:
98     https://stackoverflow.com/questions/3052579/explicit-specialization-in-non-namespace-scope
99   */
100   template<typename T>
101   struct dummy { using type = T; };
102 
103   template<class T>
104   const T &
getNext()105   getNext()
106   {
107     return getNext(dummy<T>());
108   }
109 
110 private:
111   const URSingleTensor &getNext(dummy<URSingleTensor>);
112   const FRSingleTensor &getNext(dummy<FRSingleTensor>);
113 };
114 
115 /* The tensor polynomial is basically a tensor container which is more
116    strict on insertions. It maintains number of rows and number of
117    variables and allows insertions only of those tensors, which yield
118    these properties. The maximum dimension is maintained by insert()
119    method.
120 
121    So we re-implement insert() method and implement evalTrad()
122    (traditional polynomial evaluation) and horner-like evaluation
123    evalHorner().
124 
125    In addition, we implement derivatives of the polynomial and its evaluation.
126    The evaluation of a derivative is different from the evaluation of the whole
127    polynomial, simply because the evaluation of the derivatives is a tensor,
128    and the evaluation of the polynomial is a vector (zero dimensional tensor).
129    See documentation to TensorPolynomial::derivative() and
130    TensorPolynomial::evalPartially() for details. */
131 
132 template<class _Ttype, class _TGStype, class _Stype>
133 class TensorPolynomial : public TensorContainer<_Ttype>
134 {
135   int nr;
136   int nv;
137   int maxdim;
138   using _Tparent = TensorContainer<_Ttype>;
139 public:
TensorPolynomial(int rows,int vars)140   TensorPolynomial(int rows, int vars)
141     : TensorContainer<_Ttype>(1),
142       nr(rows), nv(vars), maxdim(0)
143   {
144   }
TensorPolynomial(const TensorPolynomial<_Ttype,_TGStype,_Stype> & tp,int k)145   TensorPolynomial(const TensorPolynomial<_Ttype, _TGStype, _Stype> &tp, int k)
146     : TensorContainer<_Ttype>(tp),
147       nr(tp.nr), nv(tp.nv), maxdim(0)
148   {
149     derivative(k);
150   }
TensorPolynomial(int first_row,int num,TensorPolynomial<_Ttype,_TGStype,_Stype> & tp)151   TensorPolynomial(int first_row, int num, TensorPolynomial<_Ttype, _TGStype, _Stype> &tp)
152     : TensorContainer<_Ttype>(first_row, num, tp),
153       nr(num), nv(tp.nv), maxdim(tp.maxdim)
154   {
155   }
156 
157   // TensorPolynomial contract constructor
158   /* This constructor takes a tensor polynomial
159               ₘ                  ⎡x⎤α₁…α
160       P(x,y)= ∑  [g_(xy)ᵏ]_α₁…αₖ ⎢ ⎥
161              ᵏ⁼⁰                 ⎣y⎦
162 
163      and for a given x it makes a polynomial
164 
165       Q(y)=P(x,y).
166 
167      The algorithm for each full symmetry (xy)ᵏ works with subtensors (slices)
168      of symmetry xⁱyʲ (with i+j=k), and contracts these subtensors with respect
169      to xⁱ to obtain a tensor of full symmetry yʲ. Since the column xⁱ is
170      calculated by PowerProvider we cycle for i=1,…,m. Then we have to add
171      everything for i=0.
172 
173      The code works as follows: For slicing purposes we need stack sizes ‘ss’
174      corresponing to lengths of x and y, and then identity ‘pp’ for unfolding a
175      symmetry of the slice to obtain stack coordinates of the slice. Then we do
176      the calculations for i=1,…,m and then for i=0. */
177 
TensorPolynomial(const TensorPolynomial<_Ttype,_TGStype,_Stype> & tp,const Vector & xval)178   TensorPolynomial(const TensorPolynomial<_Ttype, _TGStype, _Stype> &tp, const Vector &xval)
179     : TensorContainer<_Ttype>(1),
180       nr(tp.nrows()), nv(tp.nvars() - xval.length()), maxdim(0)
181   {
182     TL_RAISE_IF(nvars() < 0,
183                 "Length of xval too big in TensorPolynomial contract constructor");
184     IntSequence ss{xval.length(), nvars()};
185     IntSequence pp{0, 1};
186 
187     // do contraction for all i>0
188     /* Here we setup the PowerProvider, and cycle through i=1,…,m. Within the
189        loop we cycle through j=0,…,m-i. If there is a tensor with symmetry
190        (xy)ⁱ⁺ʲ in the original polynomial, we make its slice with symmetry
191        xⁱyʲ, and contractAndAdd() it to the tensor ‘ten’ in the ‘this’
192        polynomial with a symmetry yʲ.
193 
194        Note three things: First, the tensor ‘ten’ is either created and put
195        to ‘this’ container or just got from the container, this is done in
196        “initialize ‘ten’ of dimension ‘j’”. Second, the contribution to
197        the ‘ten’ tensor must be multiplied by
198        ⎛i+j⎞
199        ⎝ j ⎠, since there are exactly that number of slices of
200        (xy)ⁱ⁺ʲ of the symmetry xⁱyʲ and all must be added. Third,
201        the tensor ‘ten’ is fully symmetric and _TGStype::contractAndAdd()
202        works with general symmetry, that is why we have to in-place convert
203        fully syummetric ‘ten’ to a general symmetry tensor. */
204     PowerProvider pwp(xval);
205     for (int i = 1; i <= tp.maxdim; i++)
206       {
207         const _Stype &xpow = pwp.getNext<_Stype>();
208         for (int j = 0; j <= tp.maxdim-i; j++)
209           if (tp.check(Symmetry{i+j}))
210             {
211               // initialize ‘ten’ of dimension ‘j’
212               /* The pointer ‘ten’ is either a new tensor or got from ‘this’ container. */
213               _Ttype *ten;
214               if (_Tparent::check(Symmetry{j}))
215                 ten = &_Tparent::get(Symmetry{j});
216               else
217                 {
218                   auto ten_smart = std::make_unique<_Ttype>(nrows(), nvars(), j);
219                   ten_smart->zeros();
220                   ten = ten_smart.get();
221                   insert(std::move(ten_smart));
222                 }
223 
224               Symmetry sym{i, j};
225               IntSequence coor(pp.unfold(sym));
226               _TGStype slice(tp.get(Symmetry{i+j}), ss, coor, TensorDimens(sym, ss));
227               slice.mult(PascalTriangle::noverk(i+j, j));
228               _TGStype tmp(*ten);
229               slice.contractAndAdd(0, tmp, xpow);
230             }
231       }
232 
233     // do contraction for i=0
234     /* This is easy. The code is equivalent to “do contraction for all i>0” as
235        for i=0. The contraction here takes a form of a simple addition. */
236     for (int j = 0; j <= tp.maxdim; j++)
237       if (tp.check(Symmetry{j}))
238         {
239 
240           // initialize ‘ten’ of dimension ‘j’
241           /* Same code as above */
242           _Ttype *ten;
243           if (_Tparent::check(Symmetry{j}))
244             ten = &_Tparent::get(Symmetry{j});
245           else
246             {
247               auto ten_smart = std::make_unique<_Ttype>(nrows(), nvars(), j);
248               ten_smart->zeros();
249               ten = ten_smart.get();
250               insert(std::move(ten_smart));
251             }
252 
253           Symmetry sym{0, j};
254           IntSequence coor(pp.unfold(sym));
255           _TGStype slice(tp.get(Symmetry{j}), ss, coor, TensorDimens(sym, ss));
256           ten->add(1.0, slice);
257         }
258   }
259 
TensorPolynomial(const TensorPolynomial & tp)260   TensorPolynomial(const TensorPolynomial &tp)
261     : TensorContainer<_Ttype>(tp), nr(tp.nr), nv(tp.nv), maxdim(tp.maxdim)
262   {
263   }
264   int
nrows() const265   nrows() const
266   {
267     return nr;
268   }
269   int
nvars() const270   nvars() const
271   {
272     return nv;
273   }
274 
275   /* Here we cycle up to the maximum dimension, and if a tensor exists in
276      the container, then we multiply it with the Kronecker power of the
277      vector supplied by PowerProvider. */
278 
279   void
evalTrad(Vector & out,const ConstVector & v) const280   evalTrad(Vector &out, const ConstVector &v) const
281   {
282     if (_Tparent::check(Symmetry{0}))
283       out = _Tparent::get(Symmetry{0}).getData();
284     else
285       out.zeros();
286 
287     PowerProvider pp(v);
288     for (int d = 1; d <= maxdim; d++)
289       {
290         const _Stype &p = pp.getNext<_Stype>();
291         Symmetry cs{d};
292         if (_Tparent::check(cs))
293           {
294             const _Ttype &t = _Tparent::get(cs);
295             t.multaVec(out, p.getData());
296           }
297       }
298   }
299 
300   /* Here we construct by contraction ‘maxdim-1’ tensor first, and then
301      cycle. */
302 
303   void
evalHorner(Vector & out,const ConstVector & v) const304   evalHorner(Vector &out, const ConstVector &v) const
305   {
306     if (_Tparent::check(Symmetry{0}))
307       out = _Tparent::get(Symmetry{0}).getData();
308     else
309       out.zeros();
310 
311     if (maxdim == 0)
312       return;
313 
314     std::unique_ptr<_Ttype> last;
315     if (maxdim == 1)
316       last = std::make_unique<_Ttype>(_Tparent::get(Symmetry{1}));
317     else
318       last = std::make_unique<_Ttype>(_Tparent::get(Symmetry{maxdim}), v);
319     for (int d = maxdim-1; d >= 1; d--)
320       {
321         Symmetry cs{d};
322         if (_Tparent::check(cs))
323           {
324             const _Ttype &nt = _Tparent::get(cs);
325             last->add(1.0, ConstTwoDMatrix(nt));
326           }
327         if (d > 1)
328           last = std::make_unique<_Ttype>(*last, v);
329       }
330     last->multaVec(out, v);
331   }
332 
333   /* Before a tensor is inserted, we check for the number of rows, and
334      number of variables. Then we insert and update the ‘maxdim’. */
335 
336   void
insert(std::unique_ptr<_Ttype> t)337   insert(std::unique_ptr<_Ttype> t) override
338   {
339     TL_RAISE_IF(t->nrows() != nr,
340                 "Wrong number of rows in TensorPolynomial::insert");
341     TL_RAISE_IF(t->nvar() != nv,
342                 "Wrong number of variables in TensorPolynomial::insert");
343     if (maxdim < t->dimen())
344       maxdim = t->dimen();
345     TensorContainer<_Ttype>::insert(std::move(t));
346   }
347 
348   /* The polynomial takes the form
349 
350 351       ∑ 1/i! [g_yⁱ]_α₁…αᵢ [y]^α₁ … [y]^αᵢ
352      ⁱ⁼⁰
353 
354      where [g_yⁱ] are i-order derivatives of the polynomial. We assume that
355      1/i! [g_yⁱ] are items in the tensor container. This method differentiates
356      the polynomial by one order to yield:
357 
358 359       ∑ 1/i! [i·g_yⁱ]_α₁…αᵢ [y]^α₁ … [y]^αᵢ₋₁
360      ⁱ⁼¹
361 
362      where [i·1/i!·g_yⁱ] are put in the container.
363 
364      A polynomial can be derivative of some order, and the order cannot be
365      recognized from the object. That is why we need to input the order. */
366 
367   void
derivative(int k)368   derivative(int k)
369   {
370     for (int d = 1; d <= maxdim; d++)
371       if (_Tparent::check(Symmetry{d}))
372         {
373           _Ttype &ten = _Tparent::get(Symmetry{d});
374           ten.mult(static_cast<double>(std::max((d-k), 0)));
375         }
376   }
377 
378   /* Now let us suppose that we have an s-order derivative of a
379      polynomial whose i-order derivatives are [g_yⁱ], so we have
380 
381       ₙ                   ᵢ₋ₛ
382       ∑ 1/i! [g_yⁱ]_α₁…αᵢ  ∏ [y]^αₖ
383      ⁱ⁼ˢ                  ᵏ⁼¹
384 
385 
386      where 1/i! [g_yⁱ] are tensors in the container.
387 
388      This methods performs this evaluation. The result is an ‘s’ dimensional
389      tensor. Note that when combined with the method derivative(), they
390      evaluate a derivative of some order. For example a sequence of calls
391      g.derivative(0), g.derivative(1) and der=g.evalPartially(2, v)
392      calculates 2! multiple of the second derivative of g at v. */
393 
394   std::unique_ptr<_Ttype>
evalPartially(int s,const ConstVector & v)395   evalPartially(int s, const ConstVector &v)
396   {
397     TL_RAISE_IF(v.length() != nvars(),
398                 "Wrong length of vector for TensorPolynomial::evalPartially");
399 
400     auto res = std::make_unique<_Ttype>(nrows(), nvars(), s);
401     res->zeros();
402 
403     if (_Tparent::check(Symmetry{s}))
404       res->add(1.0, _Tparent::get(Symmetry{s}));
405 
406     for (int d = s+1; d <= maxdim; d++)
407       if (_Tparent::check(Symmetry{d}))
408         {
409           const _Ttype &ltmp = _Tparent::get(Symmetry{d});
410           auto last = std::make_unique<_Ttype>(ltmp);
411           for (int j = 0; j < d - s; j++)
412             {
413               auto newlast = std::make_unique<_Ttype>(*last, v);
414               last = std::move(newlast);
415             }
416           res->add(1.0, *last);
417         }
418 
419     return res;
420   }
421 };
422 
423 /* This just gives a name to unfolded tensor polynomial. */
424 
425 class FTensorPolynomial;
426 class UTensorPolynomial : public TensorPolynomial<UFSTensor, UGSTensor, URSingleTensor>
427 {
428 public:
UTensorPolynomial(int rows,int vars)429   UTensorPolynomial(int rows, int vars)
430     : TensorPolynomial<UFSTensor, UGSTensor, URSingleTensor>(rows, vars)
431   {
432   }
UTensorPolynomial(const UTensorPolynomial & up,int k)433   UTensorPolynomial(const UTensorPolynomial &up, int k)
434     : TensorPolynomial<UFSTensor, UGSTensor, URSingleTensor>(up, k)
435   {
436   }
437   UTensorPolynomial(const FTensorPolynomial &fp);
UTensorPolynomial(const UTensorPolynomial & tp,const Vector & xval)438   UTensorPolynomial(const UTensorPolynomial &tp, const Vector &xval)
439     : TensorPolynomial<UFSTensor, UGSTensor, URSingleTensor>(tp, xval)
440   {
441   }
UTensorPolynomial(int first_row,int num,UTensorPolynomial & tp)442   UTensorPolynomial(int first_row, int num, UTensorPolynomial &tp)
443     : TensorPolynomial<UFSTensor, UGSTensor, URSingleTensor>(first_row, num, tp)
444   {
445   }
446 };
447 
448 /* This just gives a name to folded tensor polynomial. */
449 
450 class FTensorPolynomial : public TensorPolynomial<FFSTensor, FGSTensor, FRSingleTensor>
451 {
452 public:
FTensorPolynomial(int rows,int vars)453   FTensorPolynomial(int rows, int vars)
454     : TensorPolynomial<FFSTensor, FGSTensor, FRSingleTensor>(rows, vars)
455   {
456   }
FTensorPolynomial(const FTensorPolynomial & fp,int k)457   FTensorPolynomial(const FTensorPolynomial &fp, int k)
458     : TensorPolynomial<FFSTensor, FGSTensor, FRSingleTensor>(fp, k)
459   {
460   }
461   FTensorPolynomial(const UTensorPolynomial &up);
FTensorPolynomial(const FTensorPolynomial & tp,const Vector & xval)462   FTensorPolynomial(const FTensorPolynomial &tp, const Vector &xval)
463     : TensorPolynomial<FFSTensor, FGSTensor, FRSingleTensor>(tp, xval)
464   {
465   }
FTensorPolynomial(int first_row,int num,FTensorPolynomial & tp)466   FTensorPolynomial(int first_row, int num, FTensorPolynomial &tp)
467     : TensorPolynomial<FFSTensor, FGSTensor, FRSingleTensor>(first_row, num, tp)
468   {
469   }
470 };
471 
472 /* The compact form of TensorPolynomial is in fact a full symmetry
473    tensor, with the number of variables equal to the number of variables
474    of the polynomial plus 1 for ‘1’. */
475 
476 template<class _Ttype, class _TGStype, class _Stype>
477 class CompactPolynomial : public _Ttype
478 {
479 public:
480   /* This constructor copies matrices from the given tensor polynomial to the
481      appropriate location in this matrix. It creates a dummy tensor ‘dum’ with
482      two variables (one corresponds to ‘1’, the other to x). The index goes
483      through this dummy tensor and the number of columns of the folded/unfolded
484      general symmetry tensor corresponding to the selections of ‘1’ or x given
485      by the index. Length of ‘1’ is one, and length of x is pol.nvars(). This
486      nvs information is stored in ‘dumnvs’. The symmetry of this general
487      symmetry dummy tensor ‘dumgs’ is given by a number of ones and x’s in the
488      index. We then copy the matrix, if it exists in the polynomial and
489      increase ‘offset’ for the following cycle. */
490 
CompactPolynomial(const TensorPolynomial<_Ttype,_TGStype,_Stype> & pol)491   CompactPolynomial(const TensorPolynomial<_Ttype, _TGStype, _Stype> &pol)
492     : _Ttype(pol.nrows(), pol.nvars()+1, pol.getMaxDim())
493   {
494     _Ttype::zeros();
495 
496     IntSequence dumnvs{1, pol.nvars()};
497 
498     int offset = 0;
499     _Ttype dum(0, 2, _Ttype::dimen());
500     for (Tensor::index i = dum.begin(); i != dum.end(); ++i)
501       {
502         int d = i.getCoor().sum();
503         Symmetry symrun{_Ttype::dimen()-d, d};
504         _TGStype dumgs(0, TensorDimens(symrun, dumnvs));
505         if (pol.check(Symmetry{d}))
506           {
507             TwoDMatrix subt(*this, offset, dumgs.ncols());
508             subt.add(1.0, pol.get(Symmetry{d}));
509           }
510         offset += dumgs.ncols();
511       }
512   }
513 
514   /* We create ‘x1’ to be a concatenation of ‘1’ and x, and then create
515      PowerProvider to make a corresponding power ‘xpow’ of ‘x1’, and finally
516      multiply this matrix with the power. */
517 
518   void
eval(Vector & out,const ConstVector & v) const519   eval(Vector &out, const ConstVector &v) const
520   {
521     TL_RAISE_IF(v.length()+1 != _Ttype::nvar(),
522                 "Wrong input vector length in CompactPolynomial::eval");
523     TL_RAISE_IF(out.length() != _Ttype::nrows(),
524                 "Wrong output vector length in CompactPolynomial::eval");
525 
526     Vector x1(v.length()+1);
527     Vector x1p(x1, 1, v.length());
528     x1p = v;
529     x1[0] = 1.0;
530 
531     if (_Ttype::dimen() == 0)
532       out = ConstVector(*this, 0);
533     else
534       {
535         PowerProvider pp(x1);
536         const _Stype &xpow = pp.getNext<_Stype>();
537         for (int i = 1; i < _Ttype::dimen(); i++)
538           xpow = pp.getNext<_Stype>();
539         multVec(0.0, out, 1.0, xpow);
540       }
541   }
542 };
543 
544 /* Specialization of the CompactPolynomial for unfolded tensor. */
545 class UCompactPolynomial : public CompactPolynomial<UFSTensor, UGSTensor, URSingleTensor>
546 {
547 public:
UCompactPolynomial(const UTensorPolynomial & upol)548   UCompactPolynomial(const UTensorPolynomial &upol)
549     : CompactPolynomial<UFSTensor, UGSTensor, URSingleTensor>(upol)
550   {
551   }
552 };
553 
554 /* Specialization of the CompactPolynomial for folded tensor. */
555 class FCompactPolynomial : public CompactPolynomial<FFSTensor, FGSTensor, FRSingleTensor>
556 {
557 public:
FCompactPolynomial(const FTensorPolynomial & fpol)558   FCompactPolynomial(const FTensorPolynomial &fpol)
559     : CompactPolynomial<FFSTensor, FGSTensor, FRSingleTensor>(fpol)
560   {
561   }
562 };
563