Coverage Report

Created: 2025-08-11 06:28

/src/quantlib/ql/experimental/math/latentmodel.hpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
3
/*
4
 Copyright (C) 2008 Roland Lichters
5
 Copyright (C) 2014 Jose Aparicio
6
7
 This file is part of QuantLib, a free-software/open-source library
8
 for financial quantitative analysts and developers - http://quantlib.org/
9
10
 QuantLib is free software: you can redistribute it and/or modify it
11
 under the terms of the QuantLib license.  You should have received a
12
 copy of the license along with this program; if not, please email
13
 <quantlib-dev@lists.sf.net>. The license is also available online at
14
 <http://quantlib.org/license.shtml>.
15
16
 This program is distributed in the hope that it will be useful, but WITHOUT
17
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 FOR A PARTICULAR PURPOSE.  See the license for more details.
19
*/
20
21
#ifndef quantlib_latent_model_hpp
22
#define quantlib_latent_model_hpp
23
24
#include <ql/experimental/math/multidimquadrature.hpp>
25
#include <ql/experimental/math/multidimintegrator.hpp>
26
#include <ql/math/integrals/trapezoidintegral.hpp>
27
#include <ql/math/randomnumbers/randomsequencegenerator.hpp>
28
#include <ql/experimental/math/gaussiancopulapolicy.hpp>
29
#include <ql/experimental/math/tcopulapolicy.hpp>
30
#include <ql/math/randomnumbers/boxmullergaussianrng.hpp>
31
#include <ql/experimental/math/polarstudenttrng.hpp>
32
#include <ql/handle.hpp>
33
#include <ql/quote.hpp>
34
#include <vector>
35
36
/*! \file latentmodel.hpp
37
    \brief Generic multifactor latent variable model.
38
*/
39
40
namespace QuantLib {
41
42
    namespace detail {
43
        // havent figured out how to do this in-place
44
        struct multiplyV {
45
            std::vector<Real> operator()(Real d, std::vector<Real> v) 
46
0
            {
47
0
                std::transform(v.begin(), v.end(), v.begin(), 
48
0
                               [=](Real x) -> Real { return x * d; });
49
0
                return v;
50
0
            }
51
        };
52
    }
53
54
    //! \name Latent model direct integration facility.
55
    //@{
56
    /* Things trying to achieve here:
57
    1.- Unify the two branches of integrators in the library, they do not 
58
      hang from a common base class and here a common ptr for the 
59
      factory is needed.
60
    2.- Have a common signature for the integration call.
61
    3.- Factory construction so integrable latent models can choose the 
62
      integration algorithm separately.
63
    */
64
    class LMIntegration {
65
    public:
66
        // Interface with actual integrators:
67
        // integral of a scalar function
68
        virtual Real integrate(const std::function<Real (
69
            const std::vector<Real>& arg)>& f) const = 0;
70
        // integral of a vector function
71
        /* I had to use a different name, since the compiler does not
72
        recognise the overload; MSVC sees the argument as 
73
        std::function<Signature> in both cases....   
74
        I could do the as with the quadratures and have this as a template 
75
        function and spez for the vector case but I prefer to understand
76
        why the overload fails....
77
                    FIX ME
78
        */
79
        virtual std::vector<Real> integrateV(
80
            const std::function<std::vector<Real>  (
81
0
            const std::vector<Real>& arg)>& f) const {
82
0
            QL_FAIL("No vector integration provided");
83
0
        }
84
        virtual ~LMIntegration() = default;
85
    };
86
87
    //CRTP-ish for joining the integrations, class above to have the factory
88
    template <class I_T>
89
    class IntegrationBase : 
90
        public I_T, public LMIntegration {// diamond on 'integrate'
91
     // this class template always to be fully specialized:
92
     private:
93
       IntegrationBase() = default;
94
    };
95
    //@}
96
    
97
    // gcc reports value collision with heston engine (?!) thats why the name
98
    namespace LatentModelIntegrationType {
99
        typedef 
100
        enum LatentModelIntegrationType {
101
            #ifndef QL_PATCH_SOLARIS
102
            GaussianQuadrature,
103
            #endif
104
            Trapezoid
105
            // etc....
106
        } LatentModelIntegrationType;
107
    }
108
109
    #ifndef QL_PATCH_SOLARIS
110
111
    /* class template specializations. I havent use CRTP type cast directly
112
    because the signature of the integrators is different, grid integration
113
    needs the domain. */
114
    template<> class IntegrationBase<GaussianQuadMultidimIntegrator> : 
115
    public GaussianQuadMultidimIntegrator, public LMIntegration {
116
    public:
117
        IntegrationBase(Size dimension, Size order) 
118
0
        : GaussianQuadMultidimIntegrator(dimension, order) {}
119
0
        Real integrate(const std::function<Real(const std::vector<Real>& arg)>& f) const override {
120
0
            return GaussianQuadMultidimIntegrator::integrate<Real>(f);
121
0
        }
122
        std::vector<Real> integrateV(
123
            const std::function<std::vector<Real>(const std::vector<Real>& arg)>& f)
124
0
            const override {
125
0
            return GaussianQuadMultidimIntegrator::integrate<std::vector<Real>>(f);
126
0
        }
127
        ~IntegrationBase() override = default;
128
    };
129
130
    #endif
131
132
    template<> class IntegrationBase<MultidimIntegral> : 
133
        public MultidimIntegral, public LMIntegration {
134
    public:
135
        IntegrationBase(
136
            const std::vector<ext::shared_ptr<Integrator> >& integrators, 
137
            Real a, Real b) 
138
        : MultidimIntegral(integrators), 
139
0
          a_(integrators.size(),a), b_(integrators.size(),b) {}
140
0
        Real integrate(const std::function<Real(const std::vector<Real>& arg)>& f) const override {
141
0
            return MultidimIntegral::operator()(f, a_, b_);
142
0
        }
143
        // vector version here....
144
        ~IntegrationBase() override = default;
145
        const std::vector<Real> a_, b_;
146
    };
147
148
    // Intended to replace OneFactorCopula
149
150
    /*!
151
    \brief Generic multifactor latent variable model.\par
152
        In this model set up one considers latent (random) variables 
153
        \f$ Y_i \f$ described by:
154
        \f[
155
        \begin{array}{ccccc}
156
        Y_1 & = & \sum_k M_k a_{1,k} & + \sqrt{1-\sum_k a_{1,k}^2} Z_1 & 
157
            \sim \Phi_{Y_1}\nonumber \\
158
        ... & = &      ... & ...   & \nonumber \\
159
        Y_i & = & \sum_k M_k a_{i,k} & + \sqrt{1-\sum_k a_{i,k}^2} Z_i & 
160
            \sim \Phi_{Y_i}\nonumber \\
161
        ... & = &      ... & ...   & \nonumber \\
162
        Y_N & = & \sum_k M_k a_{N,k} & + \sqrt{1-\sum_k a_{N,k}^2} Z_N & 
163
            \sim \Phi_{Y_N}
164
        \end{array}
165
        \f]
166
        where the systemic \f$ M_k \f$ and idiosyncratic \f$ Z_i \f$ (this last 
167
        one known as error term in some contexts) random variables have 
168
        independent zero-mean unit-variance distributions. A restriction of the 
169
        model implemented here is that the N idiosyncratic variables all follow 
170
        the same probability law \f$ \Phi_Z(z)\f$ (but they are still 
171
        independent random variables) Also the model is normalized 
172
        so that: \f$-1\leq a_{i,k} \leq 1\f$ (technically the \f$Y_i\f$ are 
173
        convex linear combinations). The correlation between \f$Y_i\f$ and 
174
        \f$Y_j\f$ is then \f$\sum_k a_{i,k} a_{j,k}\f$. 
175
        \f$\Phi_{Y_i}\f$ denotes the cumulative distribution function of 
176
        \f$Y_i\f$ which in general differs for each latent variable.\par
177
        In its single factor set up this model is usually employed in derivative
178
        pricing and it is best to use it through integration of the desired 
179
        statistical properties of the model; in its multifactorial version (with
180
        typically around a dozen factors) it is used in the context of portfolio
181
        risk metrics; because of the number of variables it is best to opt for a
182
        simulation to compute model properties/magnitudes. 
183
        For this reason this class template provides a random factor sample 
184
        interface and an integration interface that will be instantiated by 
185
        derived concrete models as needed. The class is neutral on the 
186
        integration and random generation algorithms\par
187
        The latent variables are typically treated as unobservable magnitudes 
188
        and they serve to model one or several magnitudes related to them 
189
        through some function
190
        \f[
191
        \begin{array}{ccc}
192
        F_i(Y_i) & = & 
193
            F_i(\sum_k M_k a_{i,k} + \sqrt{1-\sum_k a_{i,k}^2} Z_i )\nonumber \\
194
        & = & F_i(M_1,..., M_k, ..., M_K, Z_i)
195
        \end{array}
196
        \f]
197
        The transfer function can have a more generic form: 
198
        \f$F_i(Y_1,....,Y_N)\f$ but here the model is restricted to a one to 
199
        one relation between the latent variables and the modelled ones. Also 
200
        it is assumed that \f$F_i(y_i; \tau)\f$ is monotonic in \f$y_i\f$; it 
201
        can then be inverted and the relation of the cumulative probability of 
202
        \f$F_i\f$ and \f$Y_i\f$ is simple:
203
        \f[
204
        \int_{\infty}^b \phi_{F_i} df = 
205
            \int_{\infty}^{F_i^{-1}(b)} \phi_{Y_i} dy
206
        \f]
207
        If  \f$t\f$ is some value of the functional or modelled variable, 
208
        \f$y\f$ is mapped to \f$t\f$ such that percentiles match, i.e. 
209
        \f$F_Y(y)=Q_i(t)\f$ or \f$y=F_Y^{-1}(Q_i(t))\f$.
210
        The class provides an integration facility of arbitrary functions 
211
        dependent on the model states. It also provides random number generation
212
        interfaces for usage of the model in monte carlo simulations.\par
213
        Now let \f$\Phi_Z(z)\f$ be the cumulated distribution function of (all 
214
        equal as mentioned) \f$Z_i\f$. For a given realization of \f$M_k\f$, 
215
        this determines the distribution of \f$y\f$:
216
        \f[
217
        Prob \,(Y_i < y|M_k) = \Phi_Z \left( \frac{y-\sum_k a_{i,k}\,M_k}
218
            {\sqrt{1-\sum_k a_{i,k}^2}}\right)
219
        \qquad
220
        \mbox{or}
221
        \qquad
222
        Prob \,(t_i < t|M) = \Phi_Z \left( \frac
223
            {F_{Y_{i}}^{-1}(Q_i(t))-\sum_k a_{i,k}\,M_k}
224
            {\sqrt{1-\sum_k a_{i,k}^2}}
225
        \right)
226
        \f]
227
        The distribution functions of \f$ M_k, Z_i \f$ are specified in
228
        specific copula template classes. The distribution function 
229
        of \f$ Y_i \f$ is then given by the convolution
230
        \f[
231
        F_{Y_{i}}(y) = Prob\,(Y_i<y) = 
232
        \int_{-\infty}^\infty\,\cdots\,\int_{-\infty}^{\infty}\:
233
        D_Z(z)\,\prod_k D_{M_{k}}(m_k) \quad
234
        \Theta \left(y - \sum_k a_{i,k}m_k - 
235
            \sqrt{1-\sum_k a_{i,k}^2}\,z\right)\,d\bar{m}\,dz,
236
        \qquad
237
        \Theta (x) = \left\{
238
        \begin{array}{ll}
239
        1 & x \geq 0 \\
240
        0 & x < 0
241
        \end{array}\right.
242
        \f]
243
        where \f$ D_Z(z) \f$ and \f$ D_M(m) \f$ are the probability
244
        densities of \f$ Z\f$ and \f$ M, \f$ respectively.\par
245
        This convolution can also be written
246
        \f[
247
        F_{Y_{i}}(y) = Prob \,(Y_i < y) =
248
        \int_{-\infty}^\infty\,\cdots\,\int_{-\infty}^{\infty} 
249
            D_{M_{k}}(m_k)\,dm_k\:
250
        \int_{-\infty}^{g(y,\vec{a},\vec{m})} D_Z(z)\,dz, \qquad
251
        g(y,\vec{a},\vec{m}) = \frac{y - \sum_k a_{i,k}m_k}
252
            {\sqrt{1-\sum_k a_{i,k}^2}}, \qquad \sum_k a_{i,k}^2 < 1
253
        \f]
254
        In general, \f$ F_{Y_{i}}(y) \f$ needs to be computed numerically.\par
255
        The policy class template separates the copula function (the 
256
        distributions involved) and the functionality (i.e. what the latent  
257
        model represents: a default probability, a recovery...). Since the  
258
        copula methods for the 
259
        probabilities are to be called repeatedly from an integration or a MC 
260
        simulation, virtual tables are avoided and template parameter mechnics 
261
        is preferred.\par
262
        There is nothing at this level enforncing the requirement 
263
        on the factor distributions to be of zero mean and unit variance. Thats 
264
        the user responsibility and the model fails to behave correctly if it 
265
        is not the case.\par
266
        Derived classes should implement a modelled magnitude (default time, 
267
        etc) and will provide probability distributions and conditional values.
268
        They could also provide functionality for the parameter inversion 
269
        problem, the (e.g.) time at which the modeled variable first takes a 
270
        given value. This problem has solution/sense depending on the transfer 
271
        function \f$F_i(Y_i)\f$ characteristics.
272
273
        To make direct integration and simulation time efficient virtual 
274
        functions have been avoided in accessing methods in the copula policy 
275
        and in the sampling of the random factors
276
    */
277
    template <class copulaPolicyImpl>
278
    class LatentModel 
279
        : public virtual Observer , public virtual Observable 
280
    {//observer if factors as quotes
281
    public:
282
      void update() override;
283
      //! \name Copula interface.
284
      //@{
285
      typedef copulaPolicyImpl copulaType;
286
      /*! Cumulative probability of the \f$ Y_i \f$ modelled latent random
287
          variable to take a given value.
288
      */
289
      Probability cumulativeY(Real val, Size iVariable) const {
290
          return copula_.cumulativeY(val, iVariable);
291
        }
292
        //! Cumulative distribution of Z, the idiosyncratic/error factors.
293
        Probability cumulativeZ(Real z) const {
294
            return copula_.cumulativeZ(z);
295
        }
296
        //! Density function of M, the market/systemic factors.
297
        Probability density(const std::vector<Real>& m) const {
298
            #if defined(QL_EXTRA_SAFETY_CHECKS)
299
                QL_REQUIRE(m.size() == nFactors_, 
300
                    "Factor size must match that of model.");
301
            #endif
302
            return copula_.density(m);
303
        }
304
        //! Inverse cumulative distribution of the systemic factor iFactor.
305
        Real inverseCumulativeDensity(Probability p, Size iFactor) const {
306
            return copula_.inverseCumulativeDensity(p, iFactor);
307
        }
308
        /*! Inverse cumulative value of the i-th random latent variable with a 
309
         given probability. */
310
        Real inverseCumulativeY(Probability p, Size iVariable) const {
311
            return copula_.inverseCumulativeY(p, iVariable);
312
        }
313
        /*! Inverse cumulative value of the idiosyncratic variable with a given 
314
        probability. */
315
        Real inverseCumulativeZ(Probability p) const {
316
            return copula_.inverseCumulativeZ(p);
317
        }
318
        /*! All factor cumulative inversion. Used in integrations and sampling.
319
            Inverts all the cumulative random factors probabilities in the 
320
            model. These are all the systemic factors plus all the idiosyncratic
321
            ones, so the size of the inversion is the number of systemic factors
322
            plus the number of latent modelled variables*/
323
        std::vector<Real> allFactorCumulInverter(const std::vector<Real>& probs) const {
324
            return copula_.allFactorCumulInverter(probs);
325
        }
326
        //@}
327
328
        /*! The value of the latent variable Y_i conditional to
329
            (given) a set of values of the factors.
330
331
            The passed allFactors vector contains values for all the
332
            independent factors in the model (systemic and
333
            idiosyncratic, in that order). A full sample is required,
334
            i.e. all the idiosyncratic values are expected to be
335
            present even if only the relevant one is used.
336
        */
337
        Real latentVarValue(const std::vector<Real>& allFactors, 
338
                            Size iVar) const 
339
        {
340
            return std::inner_product(factorWeights_[iVar].begin(), 
341
                // systemic term:
342
                factorWeights_[iVar].end(), allFactors.begin(),
343
                // idiosyncratic term:
344
                Real(allFactors[numFactors()+iVar] * idiosyncFctrs_[iVar]));
345
        }
346
        // \to do write variants of the above, although is the most common case
347
348
        const copulaType& copula() const {
349
            return copula_;
350
        }
351
352
353
    //  protected:
354
        //! \name Latent model random factor number generator facility.
355
        //@{
356
        /*!  Allows generation or random samples of the latent variable. 
357
358
            Generates samples of all the factors in the latent model according 
359
            to the given copula as random sequence. The default implementation 
360
            given uses the inversion in the copula policy (which must be 
361
            present).
362
            USNG is expected to be a uniform sequence generator in the default 
363
            implementation. 
364
        */
365
        /*
366
            Several (very different) usages make the spez non trivial
367
            The final goal is to obtain a sequence generator of the factor 
368
            samples, several routes are possible depending on the algorithms:
369
            
370
            1.- URNG -> Sequence Gen -> CopulaInversion  
371
              e.g.: CopulaInversion(RandomSequenceGenerator<MersenneTwisterRNG>)
372
            2.- PseudoRSG ------------> CopulaInversion
373
              e.g.: CopulaInversion(SobolRSG)
374
            3.- URNG -> SpecificMapping -> Sequence Gen  (bypasses the copula 
375
                for performance)
376
              e.g.: RandomSequenceGenerator<BoxMullerGaussianRng<
377
                MersenneTwisterRNG> > 
378
            
379
            Notice that the order the three algorithms involved (uniform gen, 
380
            sequence construction, distribution mapping) is not always the same.
381
            (in fact there could be some other ways to generate but these are 
382
            the ones in the library now.)
383
            Difficulties arise when wanting to use situation 3.- whith a generic
384
            RNG, leaving it unspecified
385
            
386
            Derived classes might specialize (on the copula
387
            type) to another type of generator if a more efficient algorithm 
388
            that the distribution inversion is available; rewritig then the 
389
            nextSequence method for a particular copula implementation.
390
            Some combinations of generators might make no sense, while it 
391
            could be possible to block template classes corresponding to those
392
            cases its not done (yet?) (e.g. a BoxMuller under a TCopula.)
393
            Dimensionality coherence (between the generator and the copula) 
394
            should have been checked by the client code.
395
            In multithread usage the sequence generator is expect to be already
396
            in position.
397
            To sample the latent variable itself users should call 
398
            LatentModel::latentVarValue with these samples.
399
        */
400
        // Cant use InverseCumulativeRsg since the inverse there has to return a
401
        //   real number and here a vector is needed, the function inverted here
402
        //   is multivalued.
403
        template <class USNG, 
404
            // dummy template parameter to allow for 'full' specialization of 
405
            // inner class without specialization of the outer.
406
            bool = true>
407
        class FactorSampler {
408
        public:
409
            typedef Sample<std::vector<Real> > sample_type;
410
            explicit FactorSampler(const copulaType& copula, 
411
                BigNatural seed = 0) 
412
            : sequenceGen_(copula.numFactors(), seed), // base case construction
413
              x_(std::vector<Real>(copula.numFactors()), 1.0),
414
              copula_(copula) { }
415
            /*! Returns a sample of the factor set \f$ M_k\,Z_i\f$. 
416
            This method has the vocation of being specialized at particular 
417
            types of the copula with a more efficient inversion to generate the 
418
            random variables modelled (e.g. Box-Muller for a gaussian).
419
            Here a default implementation is provided based directly on the 
420
            inversion of the cumulative distribution from the copula.
421
            Care has to be taken in potential specializations that the generator
422
            algorithm is compatible with an eventual concurrence of the 
423
            simulations.
424
             */
425
            const sample_type& nextSequence() const {
426
                typename USNG::sample_type sample =
427
                    sequenceGen_.nextSequence();
428
                x_.value = copula_.allFactorCumulInverter(sample.value);
429
                return x_;
430
            }
431
        private:
432
            USNG sequenceGen_;// copy, we might be mutithreaded
433
            mutable sample_type x_;
434
            // no copies
435
            const copulaType& copula_;
436
        };
437
        //@}
438
    protected:
439
        /* \todo Move integrator traits like number of quadrature points, 
440
        integration domain dimensions, etc to the copula through a static 
441
        member function. Since they depend on the nature of the probability 
442
        density distribution thats where they belong.
443
        This is why theres one factory per copula policy template parameter 
444
        (even if this is not used...yet)
445
        */
446
        class IntegrationFactory {
447
        public:
448
            static ext::shared_ptr<LMIntegration> createLMIntegration(
449
                Size dimension, 
450
                LatentModelIntegrationType::LatentModelIntegrationType type = 
451
                    #ifndef QL_PATCH_SOLARIS
452
                    LatentModelIntegrationType::GaussianQuadrature)
453
                    #else
454
                    LatentModelIntegrationType::Trapezoid)
455
                    #endif
456
            {
457
                switch(type) {
458
                    #ifndef QL_PATCH_SOLARIS
459
                    case LatentModelIntegrationType::GaussianQuadrature:
460
                        return 
461
                            ext::make_shared<
462
                            IntegrationBase<GaussianQuadMultidimIntegrator> >(
463
                                dimension, 25);
464
                    #endif
465
                    case LatentModelIntegrationType::Trapezoid:
466
                        {
467
                        std::vector<ext::shared_ptr<Integrator> > integrals;
468
                        integrals.reserve(dimension);
469
                        for(Size i=0; i<dimension; i++)
470
                            integrals.push_back(
471
                            ext::make_shared<TrapezoidIntegral<Default> >(
472
                                1.e-4, 20));
473
                        /* This integration domain is tailored for the T 
474
                        distribution; it is too wide for normals or Ts of high
475
                        order. 
476
                        \todo This needs to be solved by having the copula to 
477
                        provide the integration traits for any integration 
478
                        algorithm since it is the copula that knows the relevant
479
                        domain for its density distributions. Also to be able to
480
                        block integrations which will fail; like a quadrature  
481
                        here in some cases.
482
                        */
483
                        return 
484
                          ext::make_shared<IntegrationBase<MultidimIntegral> >
485
                               (integrals, -35., 35.);
486
                        }
487
                    default:
488
                        QL_FAIL("Unknown latent model integration type.");
489
                }
490
            }
491
        private:
492
          IntegrationFactory() = default;
493
        };
494
        //@}
495
496
497
    public:
498
        // model size, number of latent variables modelled
499
        Size size() const {return nVariables_;}
500
        //! Number of systemic factors.
501
        Size numFactors() const {return nFactors_;}
502
        //! Number of total free random factors; systemic and idiosyncratic.
503
        Size numTotalFactors() const { return nVariables_ + nFactors_; }
504
505
        /*! Constructs a LM with an arbitrary number of latent variables
506
          and factors given by the dimensions of the passed matrix.
507
            @param factorsWeights Ordering is factorWeights_[iVar][iFactor]
508
            @param ini Initialization variables. Trait type from the copula 
509
              policy to allow for static policies (this solution needs to be 
510
              revised, possibly drop the static policy and create a policy 
511
              member in LatentModel)
512
        */
513
        explicit LatentModel(
514
            const std::vector<std::vector<Real> >& factorsWeights, 
515
            const typename copulaType::initTraits& ini = 
516
                typename copulaType::initTraits());
517
        /*! Constructs a LM with an arbitrary number of latent variables 
518
          depending only on one random factor but contributing to each latent
519
          variable through different weights.
520
            @param factorsWeight Ordering is factorWeights_[iVariable]
521
            @param ini Initialization variables. Trait type from the copula 
522
              policy to allow for static policies (this solution needs to be 
523
              revised, possibly drop the static policy and create a policy 
524
              member in LatentModel)
525
        */
526
        explicit LatentModel(const std::vector<Real>& factorsWeight,
527
            const typename copulaType::initTraits& ini = 
528
                typename copulaType::initTraits());
529
        /*! Constructs a LM with an arbitrary number of latent variables 
530
          depending only on one random factor with the same weight for all
531
          latent variables.
532
533
            correlSqr is the weight, same for all.
534
535
            ini is a trait type from the copula policy, to allow for
536
            static policies (this solution needs to be revised,
537
            possibly drop the static policy and create a policy member
538
            in LatentModel)
539
        */
540
        explicit LatentModel(Real correlSqr,
541
                             Size nVariables,
542
                             const typename copulaType::initTraits& ini = typename copulaType::initTraits());
543
        /*! Constructs a LM with an arbitrary number of latent variables 
544
          depending only on one random factor with the same weight for all
545
          latent variables. The weight is observed and this constructor is
546
          intended to be used when the model relates to a market value.
547
548
            singleFactorCorrel is the weight/mkt-factor, same for all.
549
550
            ini is a trait type from the copula policy, to allow for
551
            static policies (this solution needs to be revised,
552
            possibly drop the static policy and create a policy member
553
            in LatentModel)
554
        */
555
        explicit LatentModel(const Handle<Quote>& singleFactorCorrel,
556
            Size nVariables,
557
            const typename copulaType::initTraits& ini = 
558
                typename copulaType::initTraits());
559
560
        //! Provides values of the factors \f$ a_{i,k} \f$ 
561
        const std::vector<std::vector<Real> >& factorWeights() const {
562
            return factorWeights_;
563
        }
564
        //! Provides values of the normalized idiosyncratic factors \f$ Z_i \f$
565
        const std::vector<Real>& idiosyncFctrs() const {return idiosyncFctrs_;}
566
567
        //! Latent variable correlations:
568
        Real latentVariableCorrel(Size iVar1, Size iVar2) const {
569
            // true for any normalized combination
570
            Real init = (iVar1 == iVar2 ? 
571
                idiosyncFctrs_[iVar1] * idiosyncFctrs_[iVar1] : Real(0.));
572
            return std::inner_product(factorWeights_[iVar1].begin(), 
573
                factorWeights_[iVar1].end(), factorWeights_[iVar2].begin(), 
574
                    init);
575
        }
576
        //! \name Integration facility interface
577
        //@{
578
        /*! Integrates an arbitrary scalar function over the density domain(i.e.
579
         computes its expected value).
580
        */
581
        Real integratedExpectedValue(
582
            const std::function<Real(const std::vector<Real>& v1)>& f) const {
583
            // function composition: composes the integrand with the density 
584
            //   through a product.
585
            return integration()->integrate(
586
                [&](const std::vector<Real>& x){ return copula_.density(x) * f(x); });
587
        }
588
        /*! Integrates an arbitrary vector function over the density domain(i.e.
589
         computes its expected value).
590
        */
591
        std::vector<Real> integratedExpectedValueV(
592
            // const std::function<std::vector<Real>(
593
            const std::function<std::vector<Real>(
594
                const std::vector<Real>& v1)>& f ) const {
595
            detail::multiplyV M;
596
            return integration()->integrateV(//see note in LMIntegrators base class
597
                [&](const std::vector<Real>& x){ return M(copula_.density(x), f(x)); });
598
        }
599
    protected:
600
        // Integrable models must provide their integrator.
601
        // Arguable, not having the integration in the LM class saves that 
602
        //   memory but have an entry in the VT... 
603
0
        virtual const ext::shared_ptr<LMIntegration>& integration() const {
604
0
            QL_FAIL("Integration non implemented in Latent model.");
605
0
        }
606
        //@}
607
608
        // Ordering is: factorWeights_[iVariable][iFactor]
609
        mutable std::vector<std::vector<Real> > factorWeights_;
610
        /* This is a duplicated value from the data above chosen for memory 
611
        reasons.
612
        I have opted for this one value redundant memory rather than have the 
613
        memory load of the observable in all factors. Typically Latent models 
614
        are used in two very different ways: with many factors and not linked 
615
        to a market observable (typical matrix size above is of tens of 
616
        thousands entries) or with just one observable value and the matrix is 
617
        just a scalar. Otherwise, to remove the redundancy, the matrix 
618
        factorWeights_ should be one of Quotes Handles.
619
        Yet it is not entirely true that quotes might be used only in pricing, 
620
        think sensitivity analysis....
621
        \todo Reconsider this, see how expensive truly is.
622
        */
623
        mutable Handle<Quote> cachedMktFactor_;
624
625
        // updated only by correlation observability and constructors.
626
        // \sqrt{1-\sum_k \beta_{i,k}^2} the addition being along the factors. 
627
        // It has therefore the size of the basket. Cached for perfomance
628
        mutable std::vector<Real> idiosyncFctrs_;
629
        //! Number of systemic factors.
630
        mutable Size nFactors_;//matches idiosyncFctrs_[0].size();i=0 or any
631
        //! Number of latent model variables, idiosyncratic terms or model dim
632
        mutable Size nVariables_;// matches idiosyncFctrs_.size() 
633
634
        mutable copulaType copula_;
635
    };
636
637
638
639
640
    // Defines ----------------------------------------------------------------
641
642
#ifndef __DOXYGEN__
643
644
    template <class Impl>
645
    LatentModel<Impl>::LatentModel(
646
        const std::vector<std::vector<Real> >& factorWeights,
647
        const typename Impl::initTraits& ini)
648
    : factorWeights_(factorWeights),
649
      nFactors_(factorWeights[0].size()), 
650
      nVariables_(factorWeights.size()), copula_(factorWeights, ini)
651
    {
652
        for(Size i=0; i<factorWeights.size(); i++) {
653
            idiosyncFctrs_.push_back(std::sqrt(1.-
654
                    std::inner_product(factorWeights[i].begin(), 
655
                factorWeights[i].end(), 
656
                factorWeights[i].begin(), Real(0.))));
657
            // while at it, check sizes are coherent:
658
            QL_REQUIRE(factorWeights[i].size() == nFactors_, 
659
                "Name " << i << " provides a different number of factors");
660
        }
661
    }
662
663
    template <class Impl>
664
    LatentModel<Impl>::LatentModel(
665
        const std::vector<Real>& factorWeights,
666
        const typename Impl::initTraits& ini)
667
    : nFactors_(1),
668
      nVariables_(factorWeights.size())
669
    {
670
        for (Real factorWeight : factorWeights)
671
            factorWeights_.emplace_back(1, factorWeight);
672
        for (Real factorWeight : factorWeights)
673
            idiosyncFctrs_.push_back(std::sqrt(1. - factorWeight * factorWeight));
674
        //convert row to column vector....
675
        copula_ = copulaType(factorWeights_, ini);
676
    }
677
678
    template <class Impl>
679
    LatentModel<Impl>::LatentModel(
680
        const Real correlSqr,
681
        Size nVariables,
682
        const typename Impl::initTraits& ini)
683
0
    : factorWeights_(nVariables, std::vector<Real>(1, correlSqr)),
684
0
      idiosyncFctrs_(nVariables, 
685
0
        std::sqrt(1.-correlSqr*correlSqr)),
686
0
      nFactors_(1), 
687
0
      nVariables_(nVariables),
688
0
      copula_(factorWeights_, ini)
689
0
    { }
690
691
    template <class Impl>
692
    LatentModel<Impl>::LatentModel(
693
        const Handle<Quote>& singleFactorCorrel,
694
        Size nVariables,
695
        const typename Impl::initTraits& ini)
696
    : factorWeights_(nVariables, std::vector<Real>(1, 
697
        std::sqrt(singleFactorCorrel->value()))),
698
      cachedMktFactor_(singleFactorCorrel),
699
      idiosyncFctrs_(nVariables, 
700
        std::sqrt(1.-singleFactorCorrel->value())),
701
      nFactors_(1), 
702
      nVariables_(nVariables),
703
      copula_(factorWeights_, ini)
704
    {
705
        registerWith(cachedMktFactor_);
706
    }
707
708
#endif
709
710
    template <class Impl>
711
0
    void LatentModel<Impl>::update() {
712
        /* only registration with the single market correl quote. If we get 
713
        register with something else remember that the quote stores correlation
714
        and the model need factor values; which for one factor models are the
715
        square root of the correlation.
716
        */
717
0
        factorWeights_ = std::vector<std::vector<Real> >(nVariables_, 
718
0
            std::vector<Real>(1, std::sqrt(cachedMktFactor_->value())));
719
0
        idiosyncFctrs_ = std::vector<Real>(nVariables_, 
720
0
            std::sqrt(1.-cachedMktFactor_->value()));
721
0
        copula_ = copulaType(factorWeights_, copula_.getInitTraits());
722
0
        notifyObservers();
723
0
    }
724
725
#ifndef __DOXYGEN__
726
727
    //----Template partial specializations of the random FactorSampler--------
728
    /*
729
    Notice that while the default template needs a sequence generator the 
730
    specializations need a number generator. This is forced at the time the 
731
    concrete policy class is used in the template parameter, if it has been 
732
    specialized it needs the sample type typedef to match at compilation. 
733
    
734
    Notice here the outer class template is specialized only, leaving the inner
735
    generator still a class template. Apparently old versions of gcc (3.x) bug 
736
    on this one not recognizing the specialization.
737
    */
738
    /*! \brief  Specialization for direct Gaussian Box-Muller generation.\par
739
    The implementation of Box-Muller in the library is the rejection variant so
740
    do not use it within a multithreaded simulation.
741
    */
742
    template<class TC> template<class URNG, bool dummy>
743
    class LatentModel<TC>
744
        ::FactorSampler <RandomSequenceGenerator<BoxMullerGaussianRng<URNG> > ,
745
            dummy> {
746
        typedef URNG urng_type;
747
    public:
748
        //Size below must be == to the numb of factors idiosy + systemi
749
        typedef Sample<std::vector<Real> > sample_type;
750
        explicit FactorSampler(const GaussianCopulaPolicy& copula,
751
                               BigNatural seed = 0) 
752
        : boxMullRng_(copula.numFactors(), 
753
            BoxMullerGaussianRng<urng_type>(urng_type(seed))){ }
754
        const sample_type& nextSequence() const {
755
                return boxMullRng_.nextSequence();
756
        }
757
    private:
758
        RandomSequenceGenerator<BoxMullerGaussianRng<urng_type> > boxMullRng_;
759
    };
760
761
    /*! \brief Specialization for direct T samples generation.\par
762
    The PolarT is a rejection algorithm so do not use it within a 
763
    multithreaded simulation.
764
    The RandomSequenceGenerator class does not admit heterogeneous 
765
    distribution samples so theres a trick here since the template parameter is 
766
    not what it is used internally.
767
    */
768
    template<class TC> template<class URNG, bool dummy>//uniform number expected
769
    class LatentModel<TC>
770
        ::FactorSampler<RandomSequenceGenerator<PolarStudentTRng<URNG> > , 
771
            dummy> {
772
        typedef URNG urng_type;
773
    public:
774
        typedef Sample<std::vector<Real> > sample_type;
775
        explicit FactorSampler(const TCopulaPolicy& copula, BigNatural seed = 0)
776
        : sequence_(std::vector<Real> (copula.numFactors()), 1.0),
777
          urng_(seed) {
778
            // 1 == urng.dimension() is enforced by the sample type
779
            const std::vector<Real>& varF = copula.varianceFactors();
780
            for (Real i : varF) // ...use back inserter lambda
781
                trng_.push_back(PolarStudentTRng<urng_type>(2. / (1. - i * i), urng_));
782
        }
783
        const sample_type& nextSequence() const {
784
            Size i=0;
785
            for(; i<trng_.size(); i++)//systemic samples plus one idiosyncratic
786
                sequence_.value[i] = trng_[i].next().value;
787
            for(; i<sequence_.value.size(); i++)//rest of idiosyncratic samples
788
                sequence_.value[i] = trng_.back().next().value;
789
            return sequence_;
790
        }
791
    private:
792
        mutable sample_type sequence_;
793
        urng_type urng_;
794
        mutable std::vector<PolarStudentTRng<urng_type> > trng_;
795
    };
796
797
#endif
798
799
}                    
800
801
802
#endif