File: example_dream_04.cpp

package info (click to toggle)
tasmanian 8.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,852 kB
  • sloc: cpp: 34,523; python: 7,039; f90: 5,080; makefile: 224; sh: 64; ansic: 8
file content (172 lines) | stat: -rw-r--r-- 8,075 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include "Tasmanian.hpp"

using namespace std;

/*!
 * \internal
 * \file example_dream_04.cpp
 * \brief Examples for the Tasmanian DREAM module.
 * \author Miroslav Stoyanov
 * \ingroup TasmanianDREAMExamples
 *
 * Tasmanian DREAM Example 4
 * \endinternal
 */

/*!
 * \ingroup TasmanianDREAMExamples
 * \addtogroup TasmanianDREAMExamples4 Tasmanian DREAM module, example 4
 *
 * Example 4:
 * Given data that is the superposition of two exponential curves,
 * use Sparse Grid and Bayesian inference to identify the scale and growth of each curve.
 * Higher dimensions and bi-modal posterior will decrease the acceptance rate,
 * thus we need more samples than the previous example.
 */

//! \brief DREAM Example 4: signal decomposition, using bi-modal posterior distribution.
//! \ingroup TasmanianDREAMExamples4

//! \snippet DREAM/Examples/example_dream_04.cpp DREAM_Example_04 example
void dream_example_04(){
#ifndef __TASMANIAN_DOXYGEN_SKIP
//! [DREAM_Example_04 example]
#endif
    // using the default random engine, but must reset the random number generator
    srand((int) time(nullptr));

    // EXAMPLE 4:
    cout << "\n" << "---------------------------------------------------------------------------------------------------\n";
    cout << std::scientific; cout.precision(5);
    cout << "EXAMPLE 4: similar to Example 3, but the data is noisy and the multi-modal posterior comes\n"
         << "           from a symmetry in the model\n"
         << "           set inference problem: identify x_0, x_1, x_2 and x_3 (four) model parameters\n"
         << "                                  from noisy data\n"
         << "           model: f(x) = x_0*exp(x_1*t) + x_2*exp(x_3*t),  data: d = exp(t) + 0.4*exp(3*t)\n"
         << "           note the symmetry between x_1 and x_3 which results in a bi-modal posterior\n"
         << "           t in [0,1], t is discretized with 64 equidistant nodes\n"
         << "           likelihood is exp(-64 * (f(x) - d)^2)\n"
         << "           using a sparse grid to interpolate the model\n"
         << "     NOTE: 64 corresponds to the discretization error and the added noise\n" << endl;

    // multi-modal distributions require a lot more chains and samples
    int num_chains = 50;
    int num_burnup_iterations = 1000;
    int num_sample_iterations = 1000;
    // the total number of samples is num_chains * num_iterations
    int num_discrete_nodes = 64;

    // create a lambda function that represents the model
    // normally this would be a call to an external code
    auto model = [&](double x0, double x1, double x2, double x3, std::vector<double> &data)->
        void{
            double dt = 1.0 / ((double) data.size());
            double t = 0.5 * dt;
            for(auto &d : data){
                d = x0 * std::exp(x1 * t) + x2 * std::exp(x3 * t);
                t += dt;
            }
        };

    // create data corresponding to 1.0, 1.0, 0.4, 3.0
    std::vector<double> data(num_discrete_nodes);
    model(1.0, 1.0, 0.4, 3.0, data);

    // add noise to the data, use magnitude (standard deviation) of 1 / num_discrete_nodes
    // you can adjust the example to consider more/less noise
    TasDREAM::applyGaussianUpdate(data, 1.0 / ((double) num_discrete_nodes));

    auto grid = TasGrid::makeSequenceGrid(4, num_discrete_nodes, 15, // 15-th order polynomial
                                          TasGrid::type_iptotal, TasGrid::rule_leja);
    // set search interval x_0, x_2 in [0.2, 1.2], x_1, x_3 in [0.5, 4.0]
    std::vector<double> domain_a = {0.2, 0.5, 0.2, 0.5};
    std::vector<double> domain_b = {1.2, 4.0, 1.2, 4.0};
    grid.setDomainTransform(domain_a, domain_b);

    TasGrid::loadNeededValues<TasGrid::mode_sequential>(
        [&](std::vector<double> const &x, std::vector<double> &y, size_t)->
        void{
            model(x[0], x[1], x[2], x[3], y);
        },
        grid, 1);

    // when working with grids with large N, acceleration becomes very useful
    // if BLAS is enabled on compile time, the grid will use BLAS by default
    // GPU acceleration can be enabled here using:
    //   grid.enableAcceleration(TasGrid::accel_gpu_cuda);
    //   grid.setGPUID(0);

    // define the likelihood function and load the data
    // the discretization error and noise are both is 1.0 / num_discrete_nodes
    // the Gaussian formula adds another factor of 0.5 which cancels one
    TasDREAM::LikelihoodGaussIsotropic likely(1.0 / ((double) num_discrete_nodes), data);

    TasDREAM::TasmanianDREAM state(num_chains, grid); // get the dimensions from the sparse grid
    std::vector<double> initial_chains;
    TasDREAM::genUniformSamples(domain_a, domain_b, num_chains, initial_chains);
    state.setState(initial_chains); // use chains distributed uniformly over the domain

    // Call to Tasmanian DREAM Sampling algorithm
    TasDREAM::SampleDREAM<TasDREAM::logform>
                         (num_burnup_iterations, num_sample_iterations,
                          TasDREAM::posterior<TasDREAM::logform>
                                (grid,   // provide the model
                                 likely, // provide the likelihood
                                 TasDREAM::uniform_prior), // assume non-informative prior
                          grid.getDomainInside(),
                          state,
                          TasDREAM::dist_gaussian, 0.01, // independent update of magnitude 0.01
                          TasDREAM::const_percent<100> // use 100% of differential update
                          );

    // get the vector containing the sampling history
    const std::vector<double> &history = state.getHistory();

    // splitting the bimodal data is tricky,
    // add constraint that orders the rates
    double rate_low = 0.0,  scale_low = 0.0;
    double rate_high = 0.0, scale_high = 0.0;
    for(size_t i=0; i<history.size(); i+=4){
        // group the samples by looking at the second and fourth input
        if (history[i+1] < history[i+3]){
            scale_low  += history[i];
            rate_low   += history[i+1];
            scale_high += history[i+2];
            rate_high  += history[i+3];
        }else{
            scale_low  += history[i+2];
            rate_low   += history[i+3];
            scale_high += history[i];
            rate_high  += history[i+1];
        }
    }
    double num_samples = (double) (history.size() / 4);
    rate_low   /= num_samples;
    scale_low  /= num_samples;
    rate_high  /= num_samples;
    scale_high /= num_samples;

    // High dimensions and multiple modes reduce the acceptance rate,
    // and tuning sampling parameters such as the differential update magnitude
    // could have either positive or negative effects.
    // Low acceptance rate is undesirable (called poor mixing in literature)
    // and extremely low rate indicates ill-posed or incorrectly implemented problem.
    cout << "Acceptance rate: " << std::fixed << state.getAcceptanceRate() << "\n\n";
    cout << "High dimensions and multiple modes reduce the acceptance rate,\n"
         << "and sampling parameters (e.g., differential update magnitude) can affect it either way.\n\n";
    cout << "Inferred values (noise free case):\n";
    cout << " low   rate:" << setw(12) << std::fixed << rate_low
         << "   error:" << setw(12) << std::scientific << std::abs(rate_low - 1.0)   << "\n";
    cout << " low  scale:" << setw(12) << std::fixed << scale_low
         << "   error:" << setw(12) << std::scientific << std::abs(scale_low - 1.0)  << "\n\n";
    cout << " high  rate:" << setw(12) << std::fixed << rate_high
         << "   error:" << setw(12) << std::scientific << std::abs(rate_high - 3.0)  << "\n";
    cout << " high scale:" << setw(12) << std::fixed << scale_high
         << "   error:" << setw(12) << std::scientific << std::abs(scale_high - 0.4) << "\n\n";

    cout << "\n" << "---------------------------------------------------------------------------------------------------\n";

#ifndef __TASMANIAN_DOXYGEN_SKIP
//! [DREAM_Example_04 example]
#endif
}