File: ISimulation.cpp

package info (click to toggle)
bornagain 23.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 103,948 kB
  • sloc: cpp: 423,131; python: 40,997; javascript: 11,167; awk: 630; sh: 318; ruby: 173; xml: 130; makefile: 51; ansic: 24
file content (263 lines) | stat: -rw-r--r-- 8,402 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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Sim/Simulation/ISimulation.cpp
//! @brief     Implements interface ISimulation.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Sim/Simulation/ISimulation.h"
#include "Base/Progress/ProgressHandler.h"
#include "Base/Util/Assert.h"
#include "Base/Util/StringUtil.h"
#include "Device/Data/Datafield.h"
#include "Param/Distrib/DistributionHandler.h"
#include "Resample/Option/SimulationOptions.h"
#include "Resample/Processed/ReSample.h"
#include "Sample/Multilayer/Sample.h"
#include "Sim/Background/IBackground.h"
#include <algorithm>
#include <gsl/gsl_errno.h>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <thread>

namespace {

size_t indexStep(size_t total_size, size_t n_handlers)
{
    ASSERT(total_size > 0);
    ASSERT(n_handlers > 0);
    size_t result = total_size / n_handlers;
    return total_size % n_handlers ? ++result : result;
}

size_t startIndex(size_t n_handlers, size_t current_handler, size_t n_elements)
{
    const size_t handler_size = indexStep(n_elements, n_handlers);
    const size_t start_index = current_handler * handler_size;
    if (start_index >= n_elements)
        return n_elements;
    return start_index;
}

size_t batchSize(size_t n_handlers, size_t current_handler, size_t n_elements)
{
    const size_t handler_size = indexStep(n_elements, n_handlers);
    const size_t start_index = current_handler * handler_size;
    if (start_index >= n_elements)
        return 0;
    return std::min(handler_size, n_elements - start_index);
}

} // namespace


//  ************************************************************************************************
//  class implementation
//  ************************************************************************************************

ISimulation::ISimulation(const Sample& sample)
    : m_sample(sample.clone())
    , m_options(std::make_unique<SimulationOptions>())
    , m_distribution_handler(std::make_unique<DistributionHandler>())
    , m_progress(std::make_unique<ProgressHandler>())
{
    ASSERT(m_sample);
}

ISimulation::~ISimulation() = default;

//... Setters:

void ISimulation::setBackground(const IBackground& bg)
{
    m_background.reset(bg.clone());
}

void ISimulation::subscribe(const std::function<bool(size_t)>& inform)
{
    ASSERT(m_progress);
    m_progress->subscribe(inform);
}

//! Initializes a progress monitor that prints to stdout.
void ISimulation::setTerminalProgressMonitor()
{
#ifndef SILENT_PROGRESS
    subscribe([](size_t percentage_done) -> bool {
        if (percentage_done < 100)
            std::cout << std::setprecision(2) << "\r... " << percentage_done << "%" << std::flush;
        else // wipe out
            std::cout << "\r... 100%\n";
        return true;
    });
#endif
}

const SimulationOptions& ISimulation::options() const
{
    ASSERT(m_options);
    return *m_options;
}

//... Executor:

//! Runs simulation with possible averaging over parameter distributions; returns result.
Datafield ISimulation::simulate()
{
    ASSERT(m_sample);
    const std::string errs = m_sample->validateAmbientSubstrate();
    if (!errs.empty())
        throw std::runtime_error("Invalid sample model: " + errs + ".");

    gsl_set_error_handler_off();

    prepareSimulation();
    m_cache = std::vector<double>(nOutChannels(), 0.);

    const ReSample re_sample = ReSample::make(*m_sample, options(), force_polarized());

    const size_t total_size = nElements();
    if (total_size == 0)
        throw std::runtime_error("No output pixels. All masked? Invalid axis?");
    size_t n_combinations = distributionHandler().nParamSamples();

    m_progress->reset();
    m_progress->setExpectedNTicks(n_combinations * total_size);

    // restrict calculation to current batch // TODO: clarify
    const size_t n_batches = m_options->getNumberOfBatches();
    const size_t current_batch = m_options->getCurrentBatch();
    const size_t batch_start = startIndex(n_batches, current_batch, total_size);
    const size_t batch_size = batchSize(n_batches, current_batch, total_size);
    ASSERT(batch_size);

    if (n_combinations == 1) {
        runSingleSimulation(re_sample, batch_start, batch_size, 1.);
    } else {
        // only GISAS
        initDistributionHandler();
        for (size_t index = 0; index < n_combinations; ++index) {
            double weight = distributionHandler().setParameterValues(index);
            runSingleSimulation(re_sample, batch_start, batch_size, weight);
        }
    }

    return packResult();
}

//... Getters:

std::vector<const INode*> ISimulation::nodeChildren() const
{
    std::vector<const INode*> result;
    if (m_sample)
        result << m_sample.get();
    return result;
}

const Sample* ISimulation::sample() const
{
    return m_sample.get();
}

const IBackground* ISimulation::background() const
{
    return m_background.get();
}

const std::vector<ParameterDistribution>& ISimulation::paramDistributions() const
{
    return m_distribution_handler->paramDistributions();
}

SimulationOptions& ISimulation::options()
{
    ASSERT(m_options);
    return *m_options;
}

//... Protected accessors:

ProgressHandler& ISimulation::progress()
{
    ASSERT(m_progress);
    return *m_progress;
}

DistributionHandler& ISimulation::distributionHandler()
{
    ASSERT(m_distribution_handler);
    return *m_distribution_handler;
}

//... Private executor:

//! Runs a single simulation with fixed parameter values.
//! If desired, the simulation is run in several threads.
void ISimulation::runSingleSimulation(const ReSample& re_sample, size_t batch_start,
                                      size_t batch_size, double weight)
{
    initScanElementVector();

    const size_t n_threads = m_options->getNumberOfThreads();
    ASSERT(n_threads > 0);

    if (n_threads == 1) {
        // Run computation in current thread.
        try {
            for (size_t i = 0; i < batch_size; ++i) {
                if (!m_progress->alive())
                    break;
                runComputation(re_sample, batch_start + i, weight);
            }
        } catch (const std::exception& ex) {
            throw std::runtime_error(std::string("Unexpected error in simulation:\n") + ex.what());
        }

    } else {
        // Launch computation threads.
        std::vector<std::unique_ptr<std::thread>> threads;
        std::vector<std::string> failure_messages;
        std::mutex mutex;
        for (size_t i_thread = 0; i_thread < n_threads; ++i_thread) {
            const size_t thread_start = batch_start + startIndex(n_threads, i_thread, batch_size);
            const size_t thread_size = batchSize(n_threads, i_thread, batch_size);
            if (thread_size == 0)
                break;
            threads.emplace_back(new std::thread(
                [this, &re_sample, &weight, &failure_messages, &mutex, thread_start, thread_size] {
                    try {
                        for (size_t i = 0; i < thread_size; ++i) {
                            if (!m_progress->alive())
                                break;
                            runComputation(re_sample, thread_start + i, weight);
                        }
                    } catch (const std::exception& ex) {
                        mutex.lock();
                        if (std::find(failure_messages.begin(), failure_messages.end(), ex.what())
                            == failure_messages.end())
                            failure_messages.emplace_back(ex.what());
                        mutex.unlock();
                    }
                }));
        }

        // Wait for threads to complete.
        for (auto& thread : threads)
            thread->join();

        // Check successful completion.
        if (!failure_messages.empty())
            throw std::runtime_error("Unexpected error(s) in simulation thread(s):\n"
                                     + Base::String::join(failure_messages, "\n"));
    }
}