BornAgain  1.18.0
Simulate and fit neutron and x-ray scattering at grazing incidence
Simulation.cpp
Go to the documentation of this file.
1 // ************************************************************************** //
2 //
3 // BornAgain: simulate and fit scattering at grazing incidence
4 //
5 //! @file Core/Simulation/Simulation.cpp
6 //! @brief Implements class Simulation.
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************** //
14 
20 #include "Fit/Tools/StringUtils.h"
25 #include <gsl/gsl_errno.h>
26 #include <iomanip>
27 #include <iostream>
28 #include <thread>
29 
30 namespace
31 {
32 
33 bool detHasSameDimensions(const IDetector& detector, const OutputData<double>& data)
34 {
35  if (data.getRank() != detector.dimension())
36  return false;
37 
38  for (size_t i = 0; i < detector.dimension(); ++i)
39  if (data.getAxis(i).size() != detector.getAxis(i).size())
40  return false;
41 
42  return true;
43 }
44 
45 size_t getIndexStep(size_t total_size, size_t n_handlers)
46 {
47  ASSERT(total_size > 0);
48  ASSERT(n_handlers > 0);
49  size_t result = total_size / n_handlers;
50  return total_size % n_handlers ? ++result : result;
51 }
52 
53 size_t getStartIndex(size_t n_handlers, size_t current_handler, size_t n_elements)
54 {
55  const size_t handler_size = getIndexStep(n_elements, static_cast<size_t>(n_handlers));
56  const size_t start_index = current_handler * handler_size;
57  if (start_index >= n_elements)
58  return n_elements;
59  return start_index;
60 }
61 
62 size_t getNumberOfElements(size_t n_handlers, size_t current_handler, size_t n_elements)
63 {
64  const size_t handler_size = getIndexStep(n_elements, static_cast<size_t>(n_handlers));
65  const size_t start_index = current_handler * handler_size;
66  if (start_index >= n_elements)
67  return 0;
68  return std::min(handler_size, n_elements - start_index);
69 }
70 
71 void runComputations(std::vector<std::unique_ptr<IComputation>> computations)
72 {
73  ASSERT(!computations.empty());
74 
75  if (computations.size() == 1) { // Running computation in current thread
76  auto& computation = computations.front();
77  computation->run();
78  if (computation->isCompleted())
79  return;
80  std::string message = computation->errorMessage();
81  throw Exceptions::RuntimeErrorException("Error in runComputations: Simulation has "
82  "terminated unexpectedly with following error: "
83  "message.\n"
84  + message);
85  }
86 
87  // Running computations in several threads.
88  // The number of threads is equal to the number of computations.
89 
90  std::vector<std::unique_ptr<std::thread>> threads;
91 
92  // Run simulations in n threads.
93  for (auto& comp : computations)
94  threads.emplace_back(new std::thread([&comp]() { comp->run(); }));
95 
96  // Wait for threads to complete.
97  for (auto& thread : threads)
98  thread->join();
99 
100  // Check successful completion.
101  std::vector<std::string> failure_messages;
102  for (auto& comp : computations)
103  if (!comp->isCompleted())
104  failure_messages.push_back(comp->errorMessage());
105 
106  if (failure_messages.empty())
107  return;
109  "Error in runComputations: "
110  "At least one simulation thread has terminated unexpectedly.\n"
111  "Messages: "
112  + StringUtils::join(failure_messages, " --- "));
113 }
114 
115 } // namespace
116 
117 // ************************************************************************** //
118 // class Simulation
119 // ************************************************************************** //
120 
122 {
123  initialize();
124 }
125 
127  : ICloneable(), INode(), m_options(other.m_options), m_progress(other.m_progress),
128  m_sample_provider(other.m_sample_provider),
129  m_distribution_handler(other.m_distribution_handler), m_instrument(other.instrument())
130 {
131  if (other.m_background)
132  setBackground(*other.m_background);
133  initialize();
134 }
135 
136 Simulation::~Simulation() = default;
137 
139 {
142 }
143 
144 //! Initializes a progress monitor that prints to stdout.
146 {
147  m_progress.subscribe([](size_t percentage_done) -> bool {
148  if (percentage_done < 100)
149  std::cout << std::setprecision(2) << "\r... " << percentage_done << "%" << std::flush;
150  else // wipe out
151  std::cout << "\r... 100%\n";
152  return true;
153  });
154 }
155 
157 {
158  instrument().setDetectorResolutionFunction(resolution_function);
159 }
160 
162 {
164 }
165 
166 //! Sets the polarization analyzer characteristics of the detector
167 void Simulation::setAnalyzerProperties(const kvector_t direction, double efficiency,
168  double total_transmission)
169 {
170  instrument().setAnalyzerProperties(direction, efficiency, total_transmission);
171 }
172 
173 void Simulation::setBeamIntensity(double intensity)
174 {
175  instrument().setBeamIntensity(intensity);
176 }
177 
179 {
180  return instrument().getBeamIntensity();
181 }
182 
183 //! Sets the beam polarization according to the given Bloch vector
185 {
186  instrument().setBeamPolarization(bloch_vector);
187 }
188 
190 {
193  throw std::runtime_error(
194  "Error in Simulation::prepareSimulation(): non-default materials of"
195  " several different types are used in the sample provided");
196  gsl_set_error_handler_off();
197 }
198 
199 //! Run simulation with possible averaging over parameter distributions
201 {
203 
204  const size_t total_size = numberOfSimulationElements();
205  size_t param_combinations = m_distribution_handler.getTotalNumberOfSamples();
206 
207  m_progress.reset();
208  m_progress.setExpectedNTicks(param_combinations * total_size);
209 
210  // restrict calculation to current batch
211  const size_t n_batches = m_options.getNumberOfBatches();
212  const size_t current_batch = m_options.getCurrentBatch();
213 
214  const size_t batch_start = getStartIndex(n_batches, current_batch, total_size);
215  const size_t batch_size = getNumberOfElements(n_batches, current_batch, total_size);
216  if (batch_size == 0)
217  return;
218 
219  std::unique_ptr<ParameterPool> param_pool(createParameterTree());
220  for (size_t index = 0; index < param_combinations; ++index) {
221  double weight = m_distribution_handler.setParameterValues(param_pool.get(), index);
222  runSingleSimulation(batch_start, batch_size, weight);
223  }
227 }
228 
230 {
231  MPISimulation ompi;
232  ompi.runSimulation(this);
233 }
234 
235 void Simulation::setInstrument(const Instrument& instrument_)
236 {
237  m_instrument = instrument_;
239 }
240 
241 //! The MultiLayer object will not be owned by the Simulation object
243 {
245 }
246 
248 {
249  return m_sample_provider.sample();
250 }
251 
252 void Simulation::setSampleBuilder(const std::shared_ptr<class ISampleBuilder>& sample_builder)
253 {
254  m_sample_provider.setBuilder(sample_builder);
255 }
256 
258 {
259  m_background.reset(bg.clone());
261 }
262 
263 std::vector<const INode*> Simulation::getChildren() const
264 {
265  std::vector<const INode*> result;
266  result.push_back(&instrument());
268  if (m_background)
269  result.push_back(m_background.get());
270  return result;
271 }
272 
273 void Simulation::addParameterDistribution(const std::string& param_name,
274  const IDistribution1D& distribution, size_t nbr_samples,
275  double sigma_factor, const RealLimits& limits)
276 {
277  ParameterDistribution par_distr(param_name, distribution, nbr_samples, sigma_factor, limits);
278  addParameterDistribution(par_distr);
279 }
280 
282 {
283  validateParametrization(par_distr);
285 }
286 
287 //! Runs a single simulation with fixed parameter values.
288 //! If desired, the simulation is run in several threads.
289 void Simulation::runSingleSimulation(size_t batch_start, size_t batch_size, double weight)
290 {
292 
293  const size_t n_threads = m_options.getNumberOfThreads();
294  ASSERT(n_threads > 0);
295 
296  std::vector<std::unique_ptr<IComputation>> computations;
297 
298  for (size_t i_thread = 0; i_thread < n_threads;
299  ++i_thread) { // Distribute computations by threads
300  const size_t thread_start = batch_start + getStartIndex(n_threads, i_thread, batch_size);
301  const size_t thread_size = getNumberOfElements(n_threads, i_thread, batch_size);
302  if (thread_size == 0)
303  break;
304  computations.push_back(generateSingleThreadedComputation(thread_start, thread_size));
305  }
306  runComputations(std::move(computations));
307 
308  normalize(batch_start, batch_size);
309  addBackgroundIntensity(batch_start, batch_size);
310  addDataToCache(weight);
311 }
312 
313 //! Convert user data to SimulationResult object for later drawing in various axes units.
314 //! User data will be cropped to the ROI defined in the simulation, amplitudes in areas
315 //! corresponding to the masked areas of the detector will be set to zero.
316 
318  bool put_masked_areas_to_zero)
319 {
320  auto converter = UnitConverterUtils::createConverter(*this);
321  auto roi_data = UnitConverterUtils::createOutputData(*converter, converter->defaultUnits());
322 
323  const IDetector& detector = instrument().detector();
324 
325  if (roi_data->hasSameDimensions(data)) {
326  // data is already cropped to ROI
327  if (put_masked_areas_to_zero) {
328  detector.iterate(
329  [&](IDetector::const_iterator it) {
330  (*roi_data)[it.roiIndex()] = data[it.roiIndex()];
331  },
332  /*visit_masked*/ false);
333  } else {
334  roi_data->setRawDataVector(data.getRawDataVector());
335  }
336 
337  } else if (detHasSameDimensions(detector, data)) {
338  // exp data has same shape as the detector, we have to put orig data to smaller roi map
339  detector.iterate(
340  [&](IDetector::const_iterator it) {
341  (*roi_data)[it.roiIndex()] = data[it.detectorIndex()];
342  },
343  /*visit_masked*/ !put_masked_areas_to_zero);
344 
345  } else {
346  throw std::runtime_error("FitObject::init_dataset() -> Error. Detector and exp data have "
347  "different shape.");
348  }
349 
350  return SimulationResult(*roi_data, *converter);
351 }
#define ASSERT(condition)
Definition: Assert.h:26
Defines interface IBackground.
Defines interface IComputation.
Defines pure virtual base class ISampleBuilder.
Defines class MPISimulation.
Defines helper functions for MultiLayer objects.
Defines class MultiLayer.
Defines class ParameterPool.
Defines class Simulation.
Defines a few helper functions.
Declares utilities for unit converters.
void addParameterDistribution(const std::string &param_name, const IDistribution1D &distribution, size_t nbr_samples, double sigma_factor=0.0, const RealLimits &limits=RealLimits())
add a sampled parameter distribution
double setParameterValues(ParameterPool *p_parameter_pool, size_t index)
set the parameter values of the simulation object to a specific combination of values,...
void setParameterToMeans(ParameterPool *p_parameter_pool) const
Sets mean distribution values to the parameter pool.
size_t getTotalNumberOfSamples() const
get the total number of parameter value combinations (product of the individual sizes of each paramet...
virtual size_t size() const =0
retrieve the number of bins
Interface for a simulating the background signal.
Definition: IBackground.h:26
virtual IBackground * clone() const =0
Interface for polymorphic classes that should not be copied, except by explicit cloning.
Definition: ICloneable.h:25
Abstract detector interface.
Definition: IDetector.h:36
size_t dimension() const
Returns actual dimensionality of the detector (number of defined axes)
Definition: IDetector.cpp:44
const IAxis & getAxis(size_t index) const
Definition: IDetector.cpp:54
void iterate(std::function< void(const_iterator)> func, bool visit_masks=false) const
Definition: IDetector.cpp:201
Interface for one-dimensional distributions.
Definition: Distributions.h:33
Base class for tree-like structures containing parameterized objects.
Definition: INode.h:49
ParameterPool * createParameterTree() const
Creates new parameter pool, with all local parameters and those of its children.
Definition: INode.cpp:116
void registerChild(INode *node)
Definition: INode.cpp:58
Interface providing two-dimensional resolution function.
Assembles beam, detector and their relative positions with respect to the sample.
Definition: Instrument.h:34
void setAnalyzerProperties(const kvector_t direction, double efficiency, double total_transmission)
Sets the polarization analyzer characteristics of the detector.
Definition: Instrument.cpp:167
void setBeamIntensity(double intensity)
Definition: Instrument.cpp:106
double getBeamIntensity() const
Definition: Instrument.cpp:116
IDetector & detector()
Definition: Instrument.cpp:133
void setDetectorResolutionFunction(const IResolutionFunction2D &p_resolution_function)
Sets detector resolution function.
Definition: Instrument.cpp:72
void removeDetectorResolution()
Removes detector resolution function.
Definition: Instrument.cpp:77
void setBeamPolarization(const kvector_t bloch_vector)
Sets the beam's polarization according to the given Bloch vector.
Definition: Instrument.cpp:111
void runSimulation(Simulation *simulation)
Our sample model: a stack of layers one below the other.
Definition: MultiLayer.h:42
std::vector< T > getRawDataVector() const
Returns copy of raw data vector.
Definition: OutputData.h:335
size_t getRank() const
Returns number of dimensions.
Definition: OutputData.h:59
const IAxis & getAxis(size_t serial_number) const
returns axis with given serial number
Definition: OutputData.h:314
A parametric distribution function, for use with any model parameter.
void subscribe(ProgressHandler::Callback_t callback)
void setExpectedNTicks(size_t n)
Limits for a real fit parameter.
Definition: RealLimits.h:25
void setSample(const MultiLayer &multilayer)
void updateSample()
Generates new sample if sample builder defined.
const MultiLayer * sample() const
Returns current sample.
std::vector< const INode * > getChildren() const override
Returns a vector of children (const).
void setBuilder(const std::shared_ptr< ISampleBuilder > &sample_builder)
An iterator for SimulationArea.
unsigned getNumberOfBatches() const
unsigned getCurrentBatch() const
unsigned getNumberOfThreads() const
Wrapper around OutputData<double> that also provides unit conversions.
Pure virtual base class of OffSpecularSimulation, GISASSimulation and SpecularSimulation.
Definition: Simulation.h:38
virtual std::unique_ptr< IComputation > generateSingleThreadedComputation(size_t start, size_t n_elements)=0
Generate a single threaded computation for a given range of simulation elements.
virtual void moveDataFromCache()=0
SimulationOptions m_options
Definition: Simulation.h:152
virtual void validateParametrization(const ParameterDistribution &) const
Checks the distribution validity for simulation.
Definition: Simulation.h:135
Instrument m_instrument
Definition: Simulation.h:156
void setBackground(const IBackground &bg)
Definition: Simulation.cpp:257
const Instrument & instrument() const
Definition: Simulation.h:55
virtual void updateIntensityMap()
Definition: Simulation.h:115
void setTerminalProgressMonitor()
Initializes a progress monitor that prints to stdout.
Definition: Simulation.cpp:145
void removeDetectorResolutionFunction()
Definition: Simulation.cpp:161
void runMPISimulation()
Run a simulation in a MPI environment.
Definition: Simulation.cpp:229
void setSample(const MultiLayer &sample)
The MultiLayer object will not be owned by the Simulation object.
Definition: Simulation.cpp:242
virtual void initSimulationElementVector()=0
Initializes the vector of Simulation elements.
void setSampleBuilder(const std::shared_ptr< ISampleBuilder > &sample_builder)
Definition: Simulation.cpp:252
void runSimulation()
Run a simulation, possibly averaged over parameter distributions.
Definition: Simulation.cpp:200
void setAnalyzerProperties(const kvector_t direction, double efficiency, double total_transmission)
Sets the polarization analyzer characteristics of the detector.
Definition: Simulation.cpp:167
std::vector< const INode * > getChildren() const
Returns a vector of children (const).
Definition: Simulation.cpp:263
void initialize()
Definition: Simulation.cpp:138
virtual void prepareSimulation()
Put into a clean state for running a simulation.
Definition: Simulation.cpp:189
std::unique_ptr< IBackground > m_background
Definition: Simulation.h:157
virtual ~Simulation()
virtual void transferResultsToIntensityMap()
Creates the appropriate data structure (e.g.
Definition: Simulation.h:110
SampleProvider m_sample_provider
Definition: Simulation.h:154
const MultiLayer * sample() const
Definition: Simulation.cpp:247
double getBeamIntensity() const
Definition: Simulation.cpp:178
virtual void addDataToCache(double weight)=0
SimulationResult convertData(const OutputData< double > &data, bool put_masked_areas_to_zero=true)
Convert user data to SimulationResult object for later drawing in various axes units.
Definition: Simulation.cpp:317
DistributionHandler m_distribution_handler
Definition: Simulation.h:155
virtual void addBackgroundIntensity(size_t start_ind, size_t n_elements)=0
void setBeamIntensity(double intensity)
Definition: Simulation.cpp:173
virtual SimulationResult result() const =0
Returns the results of the simulation in a format that supports unit conversion and export to numpy a...
void addParameterDistribution(const std::string &param_name, const IDistribution1D &distribution, size_t nbr_samples, double sigma_factor=0.0, const RealLimits &limits=RealLimits())
Definition: Simulation.cpp:273
void runSingleSimulation(size_t batch_start, size_t batch_size, double weight=1.0)
Runs a single simulation with fixed parameter values.
Definition: Simulation.cpp:289
ProgressHandler m_progress
Definition: Simulation.h:153
void setBeamPolarization(const kvector_t bloch_vector)
Sets the beam polarization according to the given Bloch vector.
Definition: Simulation.cpp:184
virtual size_t numberOfSimulationElements() const =0
Gets the number of elements this simulation needs to calculate.
void setDetectorResolutionFunction(const IResolutionFunction2D &resolution_function)
Definition: Simulation.cpp:156
virtual void normalize(size_t start_ind, size_t n_elements)=0
Normalize the detector counts to beam intensity, to solid angle, and to exposure angle.
void setInstrument(const Instrument &instrument_)
Definition: Simulation.cpp:235
bool ContainsCompatibleMaterials(const MultiLayer &multilayer)
Returns true if the multilayer contains non-default materials of one type only.
std::string join(const std::vector< std::string > &joinable, const std::string &joint)
Returns string obtain by joining vector elements.
Definition: StringUtils.cpp:71
std::unique_ptr< IUnitConverter > createConverter(const Simulation &simulation)
std::unique_ptr< OutputData< double > > createOutputData(const IUnitConverter &converter, Axes::Units units)
Returns zero-valued output data array in specified units.
size_t getIndexStep(size_t total_size, size_t n_handlers)
Definition: Simulation.cpp:45
void runComputations(std::vector< std::unique_ptr< IComputation >> computations)
Definition: Simulation.cpp:71
size_t getStartIndex(size_t n_handlers, size_t current_handler, size_t n_elements)
Definition: Simulation.cpp:53
size_t getNumberOfElements(size_t n_handlers, size_t current_handler, size_t n_elements)
Definition: Simulation.cpp:62
bool detHasSameDimensions(const IDetector &detector, const OutputData< double > &data)
Definition: Simulation.cpp:33