BornAgain  1.18.0
Simulate and fit neutron and x-ray scattering at grazing incidence
FitObjective.cpp
Go to the documentation of this file.
1 // ************************************************************************** //
2 //
3 // BornAgain: simulate and fit scattering at grazing incidence
4 //
5 //! @file Core/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 "Core/Fitting/FitStatus.h"
22 #include <stdexcept>
23 
25 {
26 public:
27  virtual ~IMetricWrapper();
28  virtual double compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const = 0;
29 };
30 
31 //! Metric wrapper for back-compaptibility with old scripts
33 {
34 public:
35  explicit ChiModuleWrapper(std::unique_ptr<IChiSquaredModule> module);
36  double compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const override;
37 
38 private:
39  std::unique_ptr<IChiSquaredModule> m_module;
40 };
41 
43 {
44 public:
45  explicit ObjectiveMetricWrapper(std::unique_ptr<ObjectiveMetric> module);
46  double compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const override;
47 
48 private:
49  std::unique_ptr<ObjectiveMetric> m_module;
50 };
51 
53 {
54  return [&callback](const Fit::Parameters& params) {
55  auto simulation = callback.build_simulation(params);
56  std::unique_ptr<Simulation> clone(simulation->clone());
57  delete simulation; // deleting Python object
58  return clone;
59  };
60 }
61 
63  : m_metric_module(
64  std::make_unique<ObjectiveMetricWrapper>(std::make_unique<PoissonLikeMetric>())),
65  m_fit_status(std::make_unique<FitStatus>(this))
66 {
67 }
68 
69 FitObjective::~FitObjective() = default;
70 
71 //! Constructs simulation/data pair for later fit.
72 //! @param builder: simulation builder capable of producing simulations
73 //! @param data: experimental data array
74 //! @param uncertainties: data uncertainties array
75 //! @param weight: weight of dataset in metric calculations
77  const OutputData<double>& data,
78  std::unique_ptr<OutputData<double>> uncertainties,
79  double weight)
80 {
81  m_fit_objects.emplace_back(builder, data, std::move(uncertainties), weight);
82 }
83 
85 {
86  run_simulations(params);
87  const double metric_value = m_metric_module->compute(m_fit_objects, params.size());
88  m_fit_status->update(params, metric_value);
89  return metric_value;
90 }
91 
92 std::vector<double> FitObjective::evaluate_residuals(const Fit::Parameters& params)
93 {
94  evaluate(params);
95 
96  std::vector<double> result = experimental_array(); // init result with experimental data values
97  const std::vector<double> sim_values = simulation_array();
98  std::transform(result.begin(), result.end(), sim_values.begin(), result.begin(),
99  [](double lhs, double rhs) { return lhs - rhs; });
100  return result;
101 }
102 
104 {
105  return std::accumulate(
106  m_fit_objects.begin(), m_fit_objects.end(), 0u,
107  [](size_t acc, auto& obj) -> size_t { return acc + obj.numberOfFitElements(); });
108 }
109 
110 //! Returns simulation result in the form of SimulationResult.
112 {
113  return dataPair(i_item).simulationResult();
114 }
115 
116 //! Returns experimental data in the form of SimulationResult.
118 {
119  return dataPair(i_item).experimentalData();
120 }
121 
122 //! Returns experimental data uncertainties in the form of SimulationResult.
124 {
125  return dataPair(i_item).uncertainties();
126 }
127 
128 //! Returns relative difference between simulation and experimental data
129 //! in the form of SimulationResult.
131 {
132  return dataPair(i_item).relativeDifference();
133 }
134 
135 //! Returns absolute value of difference between simulation and experimental data
136 //! in the form of SimulationResult.
138 {
139  return dataPair(i_item).absoluteDifference();
140 }
141 
142 //! Returns one dimensional array representing merged experimental data.
143 //! The area outside of the region of interest is not included, masked data is nullified.
144 std::vector<double> FitObjective::experimental_array() const
145 {
147 }
148 
149 //! Returns one dimensional array representing merged simulated intensities data.
150 //! The area outside of the region of interest is not included, masked data is nullified.
151 std::vector<double> FitObjective::simulation_array() const
152 {
154 }
155 
156 //! Returns one-dimensional array representing merged data uncertainties.
157 //! The area outside of the region of interest is not included, masked data is nullified.
158 std::vector<double> FitObjective::uncertainties() const
159 {
161 }
162 
163 //! Returns one-dimensional array representing merged user weights.
164 //! The area outside of the region of interest is not included, masked data is nullified.
165 std::vector<double> FitObjective::weights_array() const
166 {
168 }
169 
170 const SimDataPair& FitObjective::dataPair(size_t i_item) const
171 {
172  return m_fit_objects[check_index(i_item)];
173 }
174 
175 void FitObjective::initPrint(int every_nth)
176 {
177  m_fit_status->initPrint(every_nth);
178 }
179 
180 void FitObjective::initPlot(int every_nth, fit_observer_t observer)
181 {
182  m_fit_status->addObserver(every_nth, observer);
183 }
184 
185 void FitObjective::initPlot(int every_nth, PyObserverCallback& callback)
186 {
187  fit_observer_t observer = [&](const FitObjective& objective) { callback.update(objective); };
188  m_fit_status->addObserver(every_nth, observer);
189 }
190 
192 {
193  return m_fit_status->isCompleted();
194 }
195 
197 {
198  return m_fit_status->iterationInfo();
199 }
200 
202 {
203  return m_fit_status->minimizerResult();
204 }
205 
207 {
208  m_fit_status->finalize(result);
209 }
210 
212 {
213  return static_cast<unsigned>(m_fit_objects.size());
214 }
215 
217 {
218  m_fit_status->setInterrupted();
219 }
220 
222 {
223  return m_fit_status->isInterrupted();
224 }
225 
227 {
228  return iterationInfo().iterationCount() == 1;
229 }
230 
232 {
233  if (m_fit_status->isInterrupted())
234  throw std::runtime_error("Fitting was interrupted by the user.");
235 
236  if (m_fit_objects.empty())
237  throw std::runtime_error("FitObjective::run_simulations() -> Error. "
238  "No simulation/data defined.");
239 
240  for (auto& obj : m_fit_objects)
241  obj.runSimulation(params);
242 }
243 
245 {
246  std::cout << "Warning in FitObjective::setChiSquaredModule: setChiSquaredModule is deprecated "
247  "and will be removed in future versions. Please use "
248  "FitObjective::setObjectiveMetric instead."
249  << std::endl;
250 
251  std::unique_ptr<IChiSquaredModule> chi_module(module.clone());
252  m_metric_module = std::make_unique<ChiModuleWrapper>(std::move(chi_module));
253 }
254 
255 void FitObjective::setObjectiveMetric(std::unique_ptr<ObjectiveMetric> metric)
256 {
257  m_metric_module = std::make_unique<ObjectiveMetricWrapper>(std::move(metric));
258 }
259 
260 void FitObjective::setObjectiveMetric(const std::string& metric)
261 {
262  m_metric_module = std::make_unique<ObjectiveMetricWrapper>(
264 }
265 
266 void FitObjective::setObjectiveMetric(const std::string& metric, const std::string& norm)
267 {
269  std::make_unique<ObjectiveMetricWrapper>(ObjectiveMetricUtils::createMetric(metric, norm));
270 }
271 
272 //! Returns true if the specified DataPair element contains uncertainties
273 bool FitObjective::containsUncertainties(size_t i_item) const
274 {
275  return dataPair(i_item).containsUncertainties();
276 }
277 
278 //! Returns true if all the data pairs in FitObjective instance contain uncertainties
280 {
281  bool result = true;
282  for (size_t i = 0, size = fitObjectCount(); i < size; ++i)
283  result = result && dataPair(i).containsUncertainties();
284  return result;
285 }
286 
287 //! Returns available metrics and norms
289 {
291 }
292 
293 std::vector<double> FitObjective::composeArray(DataPairAccessor getter) const
294 {
295  const size_t n_objs = m_fit_objects.size();
296  if (n_objs == 0)
297  return {};
298  if (n_objs == 1)
299  return (m_fit_objects[0].*getter)();
300 
301  std::vector<double> result;
302  result.reserve(numberOfFitElements());
303  for (auto& pair : m_fit_objects) {
304  std::vector<double> array = (pair.*getter)();
305  std::move(array.begin(), array.end(), std::back_inserter(result));
306  }
307  return result;
308 }
309 
310 size_t FitObjective::check_index(size_t index) const
311 {
312  if (index >= m_fit_objects.size())
313  throw std::runtime_error("FitObjective::check_index() -> Index outside of range");
314  return index;
315 }
316 
317 // ------------------ metric wrappers -----------------------------
318 
320 
321 ChiModuleWrapper::ChiModuleWrapper(std::unique_ptr<IChiSquaredModule> module)
322  : IMetricWrapper(), m_module(std::move(module))
323 {
324  if (!m_module)
325  throw std::runtime_error("Error in ChiModuleWrapper: empty chi square module passed");
326 }
327 
328 double ChiModuleWrapper::compute(const std::vector<SimDataPair>& fit_objects, size_t n_pars) const
329 {
330  size_t n_points = 0;
331  double result = 0.0;
332  for (auto& obj : fit_objects) {
333  const auto sim_array = obj.simulation_array();
334  const auto exp_array = obj.experimental_array();
335  const auto weights = obj.user_weights_array();
336  const size_t n_elements = sim_array.size();
337  for (size_t i = 0; i < n_elements; ++i) {
338  double value = m_module->residual(sim_array[i], exp_array[i], weights[i]);
339  result += value * value;
340  }
341  n_points += n_elements;
342  }
343 
344  int fnorm = static_cast<int>(n_points) - static_cast<int>(n_pars);
345  if (fnorm <= 0)
346  throw std::runtime_error("Error in ChiModuleWrapper: Normalization shall be positive");
347 
348  return result / fnorm;
349 }
350 
351 ObjectiveMetricWrapper::ObjectiveMetricWrapper(std::unique_ptr<ObjectiveMetric> module)
352  : IMetricWrapper(), m_module(std::move(module))
353 {
354  if (!m_module)
355  throw std::runtime_error("Error in ObjectiveMetricWrapper: empty objective metric passed");
356 }
357 
358 double ObjectiveMetricWrapper::compute(const std::vector<SimDataPair>& fit_objects, size_t) const
359 {
360  // deciding whether to use uncertainties in metrics computation.
361  bool use_uncertainties = true;
362  for (auto& obj : fit_objects)
363  use_uncertainties = use_uncertainties && obj.containsUncertainties();
364 
365  double result = 0.0;
366  for (auto& obj : fit_objects)
367  result += m_module->compute(obj, use_uncertainties);
368  return result;
369 }
Defines class ChiSquaredModule.
Defines class FitObjective.
Defines class FitStatus.
std::function< void(const FitObjective &)> fit_observer_t
Definition: FitTypes.h:30
std::function< std::unique_ptr< Simulation >(const Fit::Parameters &)> simulation_builder_t
Definition: FitTypes.h:28
Defines ObjectiveMetric utilities and corresponding namespace.
Defines ObjectiveMetric classes.
Defines family of PyFittingCallbacks classes.
Defines class Simulation.
Metric wrapper for back-compaptibility with old scripts.
std::unique_ptr< IChiSquaredModule > m_module
ChiModuleWrapper(std::unique_ptr< IChiSquaredModule > module)
double compute(const std::vector< SimDataPair > &fit_objects, size_t n_pars) const override
Holds vector of SimDataPairs (experimental data and simulation results) for use in fitting.
Definition: FitObjective.h:34
void run_simulations(const Fit::Parameters &params)
SimulationResult uncertaintyData(size_t i_item=0) const
Returns experimental data uncertainties in the form of SimulationResult.
std::unique_ptr< IMetricWrapper > m_metric_module
Definition: FitObjective.h:141
bool isFirstIteration() const
virtual double evaluate(const Fit::Parameters &params)
IterationInfo iterationInfo() const
void addSimulationAndData(simulation_builder_t builder, const OutputData< double > &data, std::unique_ptr< OutputData< double >> uncertainties, double weight=1.0)
Constructs simulation/data pair for later fit.
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 finalize(const Fit::MinimizerResult &result)
Should be explicitely called on last iteration to notify all observers.
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
static simulation_builder_t simulationBuilder(PyBuilderCallback &callback)
void interruptFitting()
size_t check_index(size_t index) 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.
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.
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.
size_t numberOfFitElements() const
std::vector< double > experimental_array() const
Returns one dimensional array representing merged experimental data.
virtual ~FitObjective()
Fit::MinimizerResult minimizerResult() const
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
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.
virtual std::vector< double > evaluate_residuals(const Fit::Parameters &params)
std::vector< double > simulation_array() const
Returns one dimensional array representing merged simulated intensities data.
bool isCompleted() const
Contains status of the fitting (running, interupted etc) and all intermediate information which has t...
Definition: FitStatus.h:35
Result of minimization round.
A collection of fit parameters.
Definition: Parameters.h:28
size_t size() const
Definition: Parameters.cpp:51
Interface residual calculations.
virtual IChiSquaredModule * clone() const =0
clone method
virtual ~IMetricWrapper()
virtual double compute(const std::vector< SimDataPair > &fit_objects, size_t n_pars) const =0
Stores fit iteration info to track fit flow from various observers.
Definition: IterationInfo.h:26
unsigned iterationCount() const
Returns current number of minimizer iterations.
ObjectiveMetricWrapper(std::unique_ptr< ObjectiveMetric > module)
double compute(const std::vector< SimDataPair > &fit_objects, size_t n_pars) const override
std::unique_ptr< ObjectiveMetric > m_module
Implementation of metric with standard deviation , where is the simulated intensity.
Builds simulation object using a Python callable.
virtual Simulation * build_simulation(Fit::Parameters)
Observer for FitObjective based on Python callable.
virtual void update(const FitObjective &)
Holds pair of simulation/experimental data to fit.
Definition: SimDataPair.h:26
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.
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.
Definition: SimDataPair.cpp:98
bool containsUncertainties() const
Definition: SimDataPair.cpp:81
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.
Definition: SimDataPair.cpp:91
Wrapper around OutputData<double> that also provides unit conversions.
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.