BornAgain  1.19.79
Open-source research software to simulate and fit neutron and x-ray reflectometry and grazing-incidence small-angle scattering
FitObjective.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file Sim/Fitting/FitObjective.cpp
6 //! @brief Implements class FitObjective.
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/Util/Assert.h"
18 #include "Sim/Fitting/FitStatus.h"
25 #include <algorithm>
26 #include <iostream>
27 #include <stdexcept>
28 
29 namespace {
30 
31 simulation_builder_t simulationBuilder(const PyBuilderCallback& callback)
32 {
33  return [&callback](const mumufit::Parameters& params) {
34  return std::unique_ptr<ISimulation>(callback.build_simulation(params));
35  };
36 }
37 
38 } // namespace
39 
40 // ************************************************************************************************
41 // definition of auxiliary class hierarchy IMetricWrapper
42 // ************************************************************************************************
43 
44 class IMetricWrapper {
45 public:
46  virtual ~IMetricWrapper() = default;
47  virtual double compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const = 0;
48 };
49 
50 //! Metric wrapper for back-compaptibility with old scripts
51 class ChiModuleWrapper : public IMetricWrapper {
52 public:
53  explicit ChiModuleWrapper(std::unique_ptr<IChiSquaredModule> module);
54  double compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const override;
55 
56 private:
57  std::unique_ptr<IChiSquaredModule> m_module;
58 };
59 
60 class ObjectiveMetricWrapper : public IMetricWrapper {
61 public:
62  explicit ObjectiveMetricWrapper(std::unique_ptr<ObjectiveMetric> module);
63  double compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const override;
64 
65 private:
66  std::unique_ptr<ObjectiveMetric> m_module;
67 };
68 
69 // ************************************************************************************************
70 // implementation of auxiliary class hierarchy IMetricWrapper
71 // ************************************************************************************************
72 
73 ChiModuleWrapper::ChiModuleWrapper(std::unique_ptr<IChiSquaredModule> module)
74  : m_module(std::move(module))
75 {
76  ASSERT(m_module);
77 }
78 
79 double ChiModuleWrapper::compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const
80 {
81  size_t n_points = 0;
82  double result = 0.0;
83  for (const auto& obj : fit_objects) {
84  const auto sim_array = obj.simulation_array();
85  const auto exp_array = obj.experimental_array();
86  const auto weights = obj.user_weights_array();
87  const size_t n_elements = sim_array.size();
88  for (size_t i = 0; i < n_elements; ++i) {
89  double value = m_module->residual(sim_array[i], exp_array[i], weights[i]);
90  result += value * value;
91  }
92  n_points += n_elements;
93  }
94 
95  int fnorm = static_cast<int>(n_points) - static_cast<int>(n_pars);
96  if (fnorm <= 0)
97  throw std::runtime_error("Error in ChiModuleWrapper: Normalization shall be positive");
98 
99  return result / fnorm;
100 }
101 
102 ObjectiveMetricWrapper::ObjectiveMetricWrapper(std::unique_ptr<ObjectiveMetric> module)
103  : m_module(std::move(module))
104 {
105  ASSERT(m_module);
106 }
107 
108 double ObjectiveMetricWrapper::compute(const std::vector<SimDataPair>& fit_objects, size_t) const
109 {
110  // deciding whether to use uncertainties in metrics computation.
111  bool use_uncertainties = true;
112  for (const auto& obj : fit_objects)
113  use_uncertainties = use_uncertainties && obj.containsUncertainties();
114 
115  double result = 0.0;
116  for (const auto& obj : fit_objects)
117  result += m_module->compute(obj, use_uncertainties);
118  return result;
119 }
120 
121 // ************************************************************************************************
122 // implementation of class FitObjective
123 // ************************************************************************************************
124 
126  : m_metric_module(
127  std::make_unique<ObjectiveMetricWrapper>(std::make_unique<PoissonLikeMetric>()))
128  , m_fit_status(std::make_unique<FitStatus>(this))
129 {
130 }
131 
132 FitObjective::~FitObjective() = default;
133 
134 //! Constructs simulation/data pair for later fit.
135 //! @param builder: simulation builder capable of producing simulations
136 //! @param data: experimental data array
137 //! @param stdv: data uncertainties array
138 //! @param weight: weight of dataset in metric calculations
139 //!
140 //! Should be private, but is used in several tests.
142  const Datafield& data,
143  std::unique_ptr<Datafield>&& stdv, double weight)
144 {
145  m_fit_objects.emplace_back(builder, data, std::move(stdv), weight);
146 }
147 
149  const std::vector<std::vector<double>>& data, double weight)
150 {
151  execAddSimulationAndData(simulationBuilder(callback), *DataUtils::Array::createPField2D(data),
152  nullptr, weight);
153 }
154 
156  const std::vector<double>& data, double weight)
157 {
158  execAddSimulationAndData(simulationBuilder(callback), *DataUtils::Array::createPField1D(data),
159  nullptr, weight);
160 }
161 
163  const std::vector<std::vector<double>>& data,
164  const std::vector<std::vector<double>>& stdv, double weight)
165 {
166  execAddSimulationAndData(simulationBuilder(callback), *DataUtils::Array::createPField2D(data),
167  DataUtils::Array::createPField2D(stdv), weight);
168 }
169 
171  const std::vector<double>& data,
172  const std::vector<double>& stdv, double weight)
173 {
174  execAddSimulationAndData(simulationBuilder(callback), *DataUtils::Array::createPField1D(data),
175  DataUtils::Array::createPField1D(stdv), weight);
176 }
177 
179 {
180  execSimulations(params);
181  const double metric_value = m_metric_module->compute(m_fit_objects, params.size());
182  m_fit_status->update(params, metric_value);
183  return metric_value;
184 }
185 
186 std::vector<double> FitObjective::evaluate_residuals(const mumufit::Parameters& params)
187 {
188  evaluate(params);
189 
190  std::vector<double> result = experimental_array(); // init result with experimental data values
191  const std::vector<double> sim_values = simulation_array();
192  std::transform(result.begin(), result.end(), sim_values.begin(), result.begin(),
193  [](double lhs, double rhs) { return lhs - rhs; });
194  return result;
195 }
196 
197 //! Returns simulation result in the form of SimulationResult.
199 {
200  return dataPair(i_item).simulationResult();
201 }
202 
203 //! Returns experimental data in the form of SimulationResult.
205 {
206  return dataPair(i_item).experimentalData();
207 }
208 
209 //! Returns experimental data uncertainties in the form of SimulationResult.
211 {
212  return dataPair(i_item).uncertainties();
213 }
214 
215 //! Returns relative difference between simulation and experimental data
216 //! in the form of SimulationResult.
218 {
219  return dataPair(i_item).relativeDifference();
220 }
221 
222 //! Returns absolute value of difference between simulation and experimental data
223 //! in the form of SimulationResult.
225 {
226  return dataPair(i_item).absoluteDifference();
227 }
228 
229 //! Returns one-dimensional array representing merged experimental data.
230 //! The area outside of the region of interest is not included, masked data is nullified.
231 std::vector<double> FitObjective::experimental_array() const
232 {
234 }
235 
236 //! Returns one-dimensional array representing merged simulated intensities data.
237 //! The area outside of the region of interest is not included, masked data is nullified.
238 std::vector<double> FitObjective::simulation_array() const
239 {
241 }
242 
243 //! Returns one-dimensional array representing merged data uncertainties.
244 //! The area outside of the region of interest is not included, masked data is nullified.
245 std::vector<double> FitObjective::uncertainties() const
246 {
248 }
249 
250 //! Returns one-dimensional array representing merged user weights.
251 //! The area outside of the region of interest is not included, masked data is nullified.
252 std::vector<double> FitObjective::weights_array() const
253 {
255 }
256 
257 const SimDataPair& FitObjective::dataPair(size_t i_item) const
258 {
259  return m_fit_objects.at(i_item);
260 }
261 
262 void FitObjective::initPrint(int every_nth)
263 {
264  m_fit_status->initPrint(every_nth);
265 }
266 
267 void FitObjective::initPlot(int every_nth, fit_observer_t&& observer)
268 {
269  m_fit_status->addObserver(every_nth, std::move(observer));
270 }
271 
272 void FitObjective::initPlot(int every_nth, PyObserverCallback& callback)
273 {
274  fit_observer_t observer = [&](const FitObjective& objective) { callback.update(objective); };
275  m_fit_status->addObserver(every_nth, std::move(observer));
276 }
277 
279 {
280  return m_fit_status->isCompleted();
281 }
282 
284 {
285  return m_fit_status->iterationInfo();
286 }
287 
289 {
290  return m_fit_status->minimizerResult();
291 }
292 
294 {
295  m_fit_status->finalize(result);
296 }
297 
299 {
300  return static_cast<unsigned>(m_fit_objects.size());
301 }
302 
304 {
305  m_fit_status->setInterrupted();
306 }
307 
309 {
310  return m_fit_status->isInterrupted();
311 }
312 
314 {
315  return iterationInfo().iterationCount() == 1;
316 }
317 
319 {
320  if (m_fit_status->isInterrupted())
321  throw std::runtime_error("Fitting was interrupted by the user.");
322 
323  if (m_fit_objects.empty())
324  throw std::runtime_error("FitObjective::execSimulations() -> Error. "
325  "No simulation/data defined.");
326 
327  for (auto& obj : m_fit_objects)
328  obj.execSimulation(params);
329 }
330 
332 {
333  std::cout << "Warning in FitObjective::setChiSquaredModule: setChiSquaredModule is deprecated "
334  "and will be removed in future versions. Please use "
335  "FitObjective::setObjectiveMetric instead."
336  << std::endl;
337 
338  std::unique_ptr<IChiSquaredModule> chi_module(module.clone());
339  m_metric_module = std::make_unique<ChiModuleWrapper>(std::move(chi_module));
340 }
341 
342 void FitObjective::setObjectiveMetric(std::unique_ptr<ObjectiveMetric> metric)
343 {
344  m_metric_module = std::make_unique<ObjectiveMetricWrapper>(std::move(metric));
345 }
346 
347 void FitObjective::setObjectiveMetric(const std::string& metric)
348 {
349  m_metric_module = std::make_unique<ObjectiveMetricWrapper>(
351 }
352 
353 void FitObjective::setObjectiveMetric(const std::string& metric, const std::string& norm)
354 {
356  std::make_unique<ObjectiveMetricWrapper>(ObjectiveMetricUtils::createMetric(metric, norm));
357 }
358 
359 //! Returns true if the specified DataPair element contains uncertainties
360 bool FitObjective::containsUncertainties(size_t i_item) const
361 {
362  return dataPair(i_item).containsUncertainties();
363 }
364 
365 //! Returns true if all the data pairs in FitObjective instance contain uncertainties
367 {
368  bool result = true;
369  for (size_t i = 0, size = fitObjectCount(); i < size; ++i)
370  result = result && dataPair(i).containsUncertainties();
371  return result;
372 }
373 
374 //! Returns available metrics and norms
376 {
378 }
379 
380 std::vector<double> FitObjective::composeArray(DataPairAccessor getter) const
381 {
382  const size_t n_objs = m_fit_objects.size();
383  if (n_objs == 0)
384  return {};
385  if (n_objs == 1)
386  return (m_fit_objects[0].*getter)();
387 
388  std::vector<double> result;
389  for (const auto& pair : m_fit_objects) {
390  std::vector<double> array = (pair.*getter)();
391  std::move(array.begin(), array.end(), std::back_inserter(result));
392  }
393  return result;
394 }
Defines the macro ASSERT.
#define ASSERT(condition)
Definition: Assert.h:45
Defines class ChiSquaredModule.
Defines class FitObjective.
Defines class FitStatus.
std::function< void(const FitObjective &)> fit_observer_t
Definition: FitTypes.h:36
std::function< std::unique_ptr< ISimulation >(const mumufit::Parameters &)> simulation_builder_t
Definition: FitTypes.h:34
Defines interface ISimulation.
Defines class MinimizerResult.
Defines ObjectiveMetric utilities and corresponding namespace.
Defines ObjectiveMetric classes.
Defines family of PyFittingCallbacks classes.
Defines class SimDataPair.
Stores radiation power per bin.
Definition: Datafield.h:30
Holds vector of SimDataPairs (experimental data and simulation results) for use in fitting....
Definition: FitObjective.h:42
SimulationResult uncertaintyData(size_t i_item=0) const
Returns experimental data uncertainties in the form of SimulationResult.
virtual std::vector< double > evaluate_residuals(const mumufit::Parameters &params)
std::unique_ptr< IMetricWrapper > m_metric_module
Definition: FitObjective.h:141
bool isFirstIteration() const
IterationInfo iterationInfo() const
bool allPairsHaveUncertainties() const
Returns true if all the data pairs in FitObjective instance contain uncertainties.
SimulationResult relativeDifference(size_t i_item=0) const
Returns relative difference between simulation and experimental data in the form of SimulationResult.
void execAddSimulationAndData(const simulation_builder_t &builder, const Datafield &data, std::unique_ptr< Datafield > &&stdv, double weight=1.0)
Constructs simulation/data pair for later fit.
void initPlot(int every_nth, PyObserverCallback &callback)
Initializes observer callback to be called on every_nth fit iteration.
std::vector< SimDataPair > m_fit_objects
Definition: FitObjective.h:140
virtual double evaluate(const mumufit::Parameters &params)
void interruptFitting()
mumufit::MinimizerResult minimizerResult() const
void setObjectiveMetric(const std::string &metric)
std::vector< double > composeArray(DataPairAccessor getter) const
std::vector< double > uncertainties() const
Returns one-dimensional array representing merged data uncertainties. The area outside of the region ...
SimulationResult experimentalData(size_t i_item=0) const
Returns experimental data in the form of SimulationResult.
unsigned fitObjectCount() const
void setChiSquaredModule(const IChiSquaredModule &module)
static std::string availableMetricOptions()
Returns available metrics and norms.
bool containsUncertainties(size_t i_item) const
Returns true if the specified DataPair element contains uncertainties.
std::vector< double >(SimDataPair::*)() const DataPairAccessor
Definition: FitObjective.h:136
SimulationResult simulationResult(size_t i_item=0) const
Returns simulation result in the form of SimulationResult.
void initPrint(int every_nth)
Initializes printing to standard output on every_nth fit iteration.
std::vector< double > weights_array() const
Returns one-dimensional array representing merged user weights. The area outside of the region of int...
std::vector< double > experimental_array() const
Returns one-dimensional array representing merged experimental data. The area outside of the region o...
virtual ~FitObjective()
void finalize(const mumufit::MinimizerResult &result)
Should be explicitly called on last iteration to notify all observers.
SimulationResult absoluteDifference(size_t i_item=0) const
Returns absolute value of difference between simulation and experimental data in the form of Simulati...
bool isInterrupted() const
void execSimulations(const mumufit::Parameters &params)
std::unique_ptr< FitStatus > m_fit_status
Definition: FitObjective.h:142
const SimDataPair & dataPair(size_t i_item=0) const
Returns a reference to i-th SimDataPair.
std::vector< double > simulation_array() const
Returns one-dimensional array representing merged simulated intensities data. The area outside of the...
void addSimulationAndData(const PyBuilderCallback &callback, const std::vector< std::vector< double >> &data, double weight=1.0)
Constructs simulation/data pair for later fit.
bool isCompleted() const
Contains status of the fitting (running, interupted etc) and all intermediate information which has t...
Definition: FitStatus.h:39
Interface residual calculations.
IChiSquaredModule * clone() const override=0
clone method
Stores fit iteration info to track fit flow from various observers. Used in context of FitObjective.
Definition: IterationInfo.h:25
unsigned iterationCount() const
Returns current number of minimizer iterations.
Implementation of metric with standard deviation , where is the simulated intensity....
Builds simulation object using a Python callable. Base class to wrap Python callable and pass it to C...
virtual ISimulation * build_simulation(const mumufit::Parameters &) const
Observer for FitObjective based on Python callable. Base class to wrap Python callable and pass it to...
virtual void update(const FitObjective &)
Holds pair of simulation/experimental data to fit.
Definition: SimDataPair.h:30
std::vector< double > experimental_array() const
Returns the flattened experimental data cut to the ROI area.
SimulationResult absoluteDifference() const
Returns the absolute difference between simulated and experimental data cut to the ROI area.
std::vector< double > user_weights_array() const
Returns a flat array of user weights cut to the ROI area.
std::vector< double > uncertainties_array() const
Returns the flattened experimental uncertainties cut to the ROI area. If no uncertainties are availab...
std::vector< double > simulation_array() const
Returns the flattened simulated intensities cut to the ROI area.
SimulationResult uncertainties() const
Returns the data uncertainties cut to the ROI area If no uncertainties present, returns zero-filled S...
SimulationResult experimentalData() const
Returns the experimental data cut to the ROI area.
bool containsUncertainties() const
SimulationResult relativeDifference() const
Returns the relative difference between simulated and experimental data cut to the ROI area.
SimulationResult simulationResult() const
Returns the result of last computed simulation.
Wrapper around Datafield that also provides unit conversions.
Result of minimization round.
A collection of fit parameters.
Definition: Parameters.h:26
size_t size() const
Definition: Parameters.cpp:51
std::unique_ptr< Datafield > createPField1D(const std::vector< double > &vec)
Definition: ArrayUtils.cpp:32
std::unique_ptr< Datafield > createPField2D(const std::vector< std::vector< double >> &vec)
Definition: ArrayUtils.cpp:40
std::string defaultNormName()
Returns default norm name.
std::string availableMetricOptions()
Prints available metric options.
std::unique_ptr< ObjectiveMetric > createMetric(const std::string &metric)
Creates the specified metric with the default norm.