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