BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
ISimulation.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Core/Simulation/ISimulation.cpp
6 //! @brief Implements interface ISimulation.
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 
16 #include "Base/Utils/StringUtils.h"
24 #include <gsl/gsl_errno.h>
25 #include <thread>
26 
27 namespace {
28 
29 bool detHasSameDimensions(const IDetector& detector, const OutputData<double>& data)
30 {
31  if (data.rank() != detector.dimension())
32  return false;
33 
34  for (size_t i = 0; i < detector.dimension(); ++i)
35  if (data.axis(i).size() != detector.axis(i).size())
36  return false;
37 
38  return true;
39 }
40 
41 size_t getIndexStep(size_t total_size, size_t n_handlers)
42 {
43  ASSERT(total_size > 0);
44  ASSERT(n_handlers > 0);
45  size_t result = total_size / n_handlers;
46  return total_size % n_handlers ? ++result : result;
47 }
48 
49 size_t getStartIndex(size_t n_handlers, size_t current_handler, size_t n_elements)
50 {
51  const size_t handler_size = getIndexStep(n_elements, static_cast<size_t>(n_handlers));
52  const size_t start_index = current_handler * handler_size;
53  if (start_index >= n_elements)
54  return n_elements;
55  return start_index;
56 }
57 
58 size_t getNumberOfElements(size_t n_handlers, size_t current_handler, size_t n_elements)
59 {
60  const size_t handler_size = getIndexStep(n_elements, static_cast<size_t>(n_handlers));
61  const size_t start_index = current_handler * handler_size;
62  if (start_index >= n_elements)
63  return 0;
64  return std::min(handler_size, n_elements - start_index);
65 }
66 
67 void runComputations(std::vector<std::unique_ptr<IComputation>>& computations)
68 {
69  ASSERT(!computations.empty());
70 
71  if (computations.size() == 1) { // Running computation in current thread
72  auto& computation = computations.front();
73  computation->run();
74  if (computation->isCompleted())
75  return;
76  std::string message = computation->errorMessage();
77  throw std::runtime_error("Error in runComputations: ISimulation has "
78  "terminated unexpectedly with following error: "
79  "message.\n"
80  + message);
81  }
82 
83  // Running computations in several threads.
84  // The number of threads is equal to the number of computations.
85 
86  std::vector<std::unique_ptr<std::thread>> threads;
87 
88  // Run simulations in n threads.
89  for (auto& comp : computations)
90  threads.emplace_back(new std::thread([&comp]() { comp->run(); }));
91 
92  // Wait for threads to complete.
93  for (auto& thread : threads)
94  thread->join();
95 
96  // Check successful completion.
97  std::vector<std::string> failure_messages;
98  for (auto& comp : computations)
99  if (!comp->isCompleted())
100  failure_messages.push_back(comp->errorMessage());
101 
102  if (failure_messages.empty())
103  return;
104  throw std::runtime_error("Error in runComputations: "
105  "At least one simulation thread has terminated unexpectedly.\n"
106  "Messages: "
107  + StringUtils::join(failure_messages, " --- "));
108 }
109 
110 } // namespace
111 
112 // ************************************************************************************************
113 // class ISimulation
114 // ************************************************************************************************
115 
116 ISimulation::ISimulation(const Beam& beam, const MultiLayer& sample, const IDetector& detector)
117  : m_instrument(beam, detector)
118 {
119  setSample(sample);
120  initialize();
121 }
122 
123 #ifndef SWIG
124 ISimulation::ISimulation(const Beam& beam, const IDetector& detector) : m_instrument(beam, detector)
125 {
126  initialize();
127 }
128 #endif // SWIG
129 
131 {
132  initialize();
133 }
134 
136  : ICloneable()
137  , INode()
138  , m_options(other.m_options)
139  , m_progress(other.m_progress)
140  , m_sample_provider(other.m_sample_provider)
141  , m_distribution_handler(other.m_distribution_handler)
142  , m_instrument(other.instrument())
143 {
144  if (other.m_background)
145  setBackground(*other.m_background);
146  initialize();
147 }
148 
149 ISimulation::~ISimulation() = default;
150 
152 {
155 }
156 
157 #ifndef SWIG
158 void ISimulation::setInstrument(const Instrument& instrument_)
159 {
160  m_instrument = instrument_;
162 }
163 #endif // SWIG
164 
165 //! Initializes a progress monitor that prints to stdout.
167 {
168  m_progress.subscribe([](size_t percentage_done) -> bool {
169  if (percentage_done < 100)
170  std::cout << std::setprecision(2) << "\r... " << percentage_done << "%" << std::flush;
171  else // wipe out
172  std::cout << "\r... 100%\n";
173  return true;
174  });
175 }
176 
178 {
179  detector().setResolutionFunction(resolution_function);
180 }
181 
183 {
186  throw std::runtime_error(
187  "Error in ISimulation::prepareSimulation(): non-default materials of"
188  " several different types are used in the sample provided");
189  gsl_set_error_handler_off();
190 }
191 
192 //! Run simulation with possible averaging over parameter distributions
194 {
196 
197  const size_t total_size = numberOfSimulationElements();
198  size_t param_combinations = m_distribution_handler.getTotalNumberOfSamples();
199 
200  m_progress.reset();
201  m_progress.setExpectedNTicks(param_combinations * total_size);
202 
203  // restrict calculation to current batch
204  const size_t n_batches = m_options.getNumberOfBatches();
205  const size_t current_batch = m_options.getCurrentBatch();
206 
207  const size_t batch_start = getStartIndex(n_batches, current_batch, total_size);
208  const size_t batch_size = getNumberOfElements(n_batches, current_batch, total_size);
209  if (batch_size == 0)
210  return;
211 
212  if(param_combinations == 1)
213  runSingleSimulation(batch_start, batch_size, 1.);
214  else{
215  std::unique_ptr<ParameterPool> param_pool(createParameterTree());
216  for (size_t index = 0; index < param_combinations; ++index) {
217  double weight = m_distribution_handler.setParameterValues(param_pool.get(), index);
218  runSingleSimulation(batch_start, batch_size, weight);
219  }
221  }
224 }
225 
227 {
228  MPISimulation ompi;
229  ompi.runSimulation(this);
230 }
231 
232 //! The MultiLayer object will not be owned by the ISimulation object
234 {
236 }
237 
239 {
240  return m_sample_provider.sample();
241 }
242 
243 void ISimulation::setSampleBuilder(const std::shared_ptr<class ISampleBuilder>& sample_builder)
244 {
245  m_sample_provider.setBuilder(sample_builder);
246 }
247 
249 {
250  m_background.reset(bg.clone());
252 }
253 
254 std::vector<const INode*> ISimulation::getChildren() const
255 {
256  std::vector<const INode*> result;
257  result.push_back(&instrument());
259  if (m_background)
260  result.push_back(m_background.get());
261  return result;
262 }
263 
264 void ISimulation::addParameterDistribution(const std::string& param_name,
265  const IDistribution1D& distribution, size_t nbr_samples,
266  double sigma_factor, const RealLimits& limits)
267 {
268  ParameterDistribution par_distr(param_name, distribution, nbr_samples, sigma_factor, limits);
269  addParameterDistribution(par_distr);
270 }
271 
273 {
274  validateParametrization(par_distr);
276 }
277 
278 //! Runs a single simulation with fixed parameter values.
279 //! If desired, the simulation is run in several threads.
280 void ISimulation::runSingleSimulation(size_t batch_start, size_t batch_size, double weight)
281 {
283 
284  const size_t n_threads = m_options.getNumberOfThreads();
285  ASSERT(n_threads > 0);
286 
287  std::vector<std::unique_ptr<IComputation>> computations;
288 
289  for (size_t i_thread = 0; i_thread < n_threads;
290  ++i_thread) { // Distribute computations by threads
291  const size_t thread_start = batch_start + getStartIndex(n_threads, i_thread, batch_size);
292  const size_t thread_size = getNumberOfElements(n_threads, i_thread, batch_size);
293  if (thread_size == 0)
294  break;
295  computations.push_back(generateSingleThreadedComputation(thread_start, thread_size));
296  }
297  runComputations(computations);
298 
299  normalize(batch_start, batch_size);
300  addBackgroundIntensity(batch_start, batch_size);
301  addDataToCache(weight);
302 }
303 
304 //! Convert user data to SimulationResult object for later drawing in various axes units.
305 //! User data will be cropped to the ROI defined in the simulation, amplitudes in areas
306 //! corresponding to the masked areas of the detector will be set to zero.
307 
309  bool put_masked_areas_to_zero)
310 {
311  auto converter = UnitConverterUtils::createConverter(*this);
312  auto roi_data = UnitConverterUtils::createOutputData(*converter, converter->defaultUnits());
313 
314  if (roi_data->hasSameDimensions(data)) {
315  // data is already cropped to ROI
316  if (put_masked_areas_to_zero) {
317  detector().iterate(
318  [&](IDetector::const_iterator it) {
319  (*roi_data)[it.roiIndex()] = data[it.roiIndex()];
320  },
321  /*visit_masked*/ false);
322  } else {
323  roi_data->setRawDataVector(data.getRawDataVector());
324  }
325 
326  } else if (detHasSameDimensions(detector(), data)) {
327  // exp data has same shape as the detector, we have to put orig data to smaller roi map
328  detector().iterate(
329  [&](IDetector::const_iterator it) {
330  (*roi_data)[it.roiIndex()] = data[it.detectorIndex()];
331  },
332  /*visit_masked*/ !put_masked_areas_to_zero);
333 
334  } else {
335  throw std::runtime_error("FitObject::init_dataset() -> Error. Detector and exp data have "
336  "different shape.");
337  }
338 
339  return SimulationResult(*roi_data, *converter);
340 }
#define ASSERT(condition)
Definition: Assert.h:31
Defines a few helper functions.
Defines interface IBackground.
Defines interface IComputation.
Defines interface ISampleBuilder.
Defines interface ISimulation.
Defines class MPISimulation.
Defines helper functions for MultiLayer objects.
Defines class ParameterPool.
Declares utilities for unit converters.
An incident neutron or x-ray beam.
Definition: Beam.h:27
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:46
const IAxis & axis(size_t index) const
Definition: IDetector.cpp:56
void setResolutionFunction(const IResolutionFunction2D &resFunc)
Definition: IDetector.cpp:112
void iterate(std::function< void(const_iterator)> func, bool visit_masks=false) const
Definition: IDetector.cpp:197
Interface for one-dimensional distributions.
Definition: Distributions.h:34
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:126
void registerChild(INode *node)
Definition: INode.cpp:57
Interface providing two-dimensional resolution function.
Abstract base class of OffSpecularSimulation, GISASSimulation and SpecularSimulation.
Definition: ISimulation.h:38
void setSample(const MultiLayer &sample)
The MultiLayer object will not be owned by the ISimulation object.
Instrument m_instrument
Definition: ISimulation.h:160
std::vector< const INode * > getChildren() const
Returns a vector of children.
virtual void updateIntensityMap()
Definition: ISimulation.h:119
virtual ~ISimulation()
virtual void validateParametrization(const ParameterDistribution &) const
Checks the distribution validity for simulation.
Definition: ISimulation.h:139
void runSingleSimulation(size_t batch_start, size_t batch_size, double weight=1.0)
Runs a single simulation with fixed parameter values.
void initialize()
virtual size_t numberOfSimulationElements() const =0
Gets the number of elements this simulation needs to calculate.
virtual void addDataToCache(double weight)=0
void setDetectorResolutionFunction(const IResolutionFunction2D &resolution_function)
const MultiLayer * sample() const
void addParameterDistribution(const std::string &param_name, const IDistribution1D &distribution, size_t nbr_samples, double sigma_factor=0.0, const RealLimits &limits=RealLimits())
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.
SampleProvider m_sample_provider
Definition: ISimulation.h:158
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.
void runMPISimulation()
Run a simulation in a MPI environment.
const Instrument & instrument() const
Definition: ISimulation.h:55
void runSimulation()
Run a simulation, possibly averaged over parameter distributions.
SimulationOptions m_options
Definition: ISimulation.h:156
void setSampleBuilder(const std::shared_ptr< ISampleBuilder > &sample_builder)
void setInstrument(const Instrument &instrument_)
virtual void moveDataFromCache()=0
virtual void addBackgroundIntensity(size_t start_ind, size_t n_elements)=0
virtual void initSimulationElementVector()=0
Initializes the vector of ISimulation elements.
virtual void prepareSimulation()
Put into a clean state for running a simulation.
virtual SimulationResult result() const =0
Returns the results of the simulation in a format that supports unit conversion and export to numpy a...
std::unique_ptr< IBackground > m_background
Definition: ISimulation.h:161
IDetector & detector()
Definition: ISimulation.h:63
void setBackground(const IBackground &bg)
DistributionHandler m_distribution_handler
Definition: ISimulation.h:159
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.
ProgressHandler m_progress
Definition: ISimulation.h:157
void setTerminalProgressMonitor()
Initializes a progress monitor that prints to stdout.
virtual void transferResultsToIntensityMap()
Creates the appropriate data structure (e.g.
Definition: ISimulation.h:114
Assembles beam, detector and their relative positions with respect to the sample.
Definition: Instrument.h:32
void runSimulation(ISimulation *simulation)
Our sample model: a stack of layers one below the other.
Definition: MultiLayer.h:41
size_t rank() const
Returns number of dimensions.
Definition: OutputData.h:56
std::vector< T > getRawDataVector() const
Returns copy of raw data vector.
Definition: OutputData.h:334
const IAxis & axis(size_t serial_number) const
returns axis with given serial number
Definition: OutputData.h:318
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:24
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.
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.
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 ISimulation &simulation)
std::unique_ptr< OutputData< double > > createOutputData(const IUnitConverter &converter, Axes::Units units)
Returns zero-valued output data array in specified units.