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 <mp = _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