diff --git a/Core/Instrument/IntensityDataFunctions.cpp b/Core/Instrument/IntensityDataFunctions.cpp index 338f267f81b70ce2047f491e4172dc160be052a5..4f354ca75ef08d0236c4285a7a1bfa536d3f8591 100644 --- a/Core/Instrument/IntensityDataFunctions.cpp +++ b/Core/Instrument/IntensityDataFunctions.cpp @@ -18,8 +18,26 @@ #include "FourierTransform.h" #include "IHistogram.h" #include "Numeric.h" +#include "SimulationResult.h" #include <math.h> +//! Returns sum of relative differences between each pair of elements: +//! (a, b) -> 2*abs(a - b)/(|a| + |b|) ( and zero if a=b=0 within epsilon ) +double IntensityDataFunctions::RelativeDifference(const SimulationResult& dat, + const SimulationResult& ref) +{ + if (dat.size() != ref.size()) + throw std::runtime_error("Error in IntensityDataFunctions::RelativeDifference: " + "different number of elements"); + if (dat.size()==0) return 0.0; + double sum_of_diff = 0.0; + for (size_t i=0; i<dat.size(); ++i) { + sum_of_diff += Numeric::get_relative_difference(dat[i], ref[i]); + } + return sum_of_diff / dat.size(); +} + + //! Returns relative difference between two data sets sum(dat[i] - ref[i])/ref[i]). double IntensityDataFunctions::getRelativeDifference(const OutputData<double>& dat, const OutputData<double>& ref) diff --git a/Core/Instrument/SimulationResult.cpp b/Core/Instrument/SimulationResult.cpp index c66490aecfec75f7217681cd47162eac415ccf11..faa83f6a8778a6601a5df5f4f06cb9f4018e0a28 100644 --- a/Core/Instrument/SimulationResult.cpp +++ b/Core/Instrument/SimulationResult.cpp @@ -69,6 +69,27 @@ Histogram2D* SimulationResult::histogram2d(AxesUnits units_type) const return new Histogram2D(*P_data); } +double& SimulationResult::operator[](size_t i) +{ + if (mP_data) return (*mP_data)[i]; + throw std::runtime_error("Error in SimulationResult::operator[]: " + "no data initialized"); +} + +const double& SimulationResult::operator[](size_t i) const +{ + if (mP_data) return (*mP_data)[i]; + throw std::runtime_error("Error in SimulationResult::operator[]: " + "no data initialized"); +} + +size_t SimulationResult::size() const +{ + if (mP_data) return mP_data->getAllocatedSize(); + throw std::runtime_error("Error in SimulationResult::size(): " + "no data initialized"); +} + PyObject* SimulationResult::array() const { return mP_data->getArray(); diff --git a/Core/Instrument/SimulationResult.h b/Core/Instrument/SimulationResult.h index 897c28a253f982edcf8e89555639e57a0677316c..4b6fbc2d04b6dcffeb019ce20ee29d53e16789a8 100644 --- a/Core/Instrument/SimulationResult.h +++ b/Core/Instrument/SimulationResult.h @@ -48,6 +48,11 @@ public: OutputData<double>* data(AxesUnits units_type = AxesUnits::DEFAULT) const; Histogram2D* histogram2d(AxesUnits units_type = AxesUnits::DEFAULT) const; + //! Data element access + double& operator[](size_t i); + const double& operator[](size_t i) const; + size_t size() const; + //! returns data as Python numpy array #ifdef BORNAGAIN_PYTHON PyObject* array() const; diff --git a/Core/Tools/Numeric.cpp b/Core/Tools/Numeric.cpp index dd9ffbb70e847724bf117443f6bab0e78f8705b8..2f8ff7c886ad807607fcf7e0e02bbc4f8e9773b1 100644 --- a/Core/Tools/Numeric.cpp +++ b/Core/Tools/Numeric.cpp @@ -28,17 +28,15 @@ bool areAlmostEqual(double a, double b, double tolerance) return std::abs(a-b) <= eps * std::max( tolerance*eps, std::max(1., tolerance)*std::abs(b) ); } -//! Returns the safe relative difference, which is |(a-b)/b| except in special cases. +//! Returns the safe relative difference, which is 2(|a-b|)/(|a|+|b|) except in special cases. double get_relative_difference(double a, double b) { constexpr double eps = std::numeric_limits<double>::epsilon(); + const double avg_abs = (std::abs(a) + std::abs(b))/2.0; // return 0.0 if relative error smaller than epsilon - if (std::abs(a-b) <= eps*std::abs(b)) + if (std::abs(a-b) <= eps*avg_abs) return 0.0; - // for small numbers, divide by epsilon (to avoid catastrophic cancellation) - if (std::abs(b) <= eps) - return std::abs((a-b)/eps); - return std::abs((a-b)/b); + return std::abs(a-b)/avg_abs; } } // Numeric namespace diff --git a/Tests/Functional/Python/PyCore/sliced_composition.py b/Tests/Functional/Python/PyCore/sliced_composition.py index e5779f6369467e442f2fb908f36ab375e759cbf4..265274227a5743517488ae57e982822549cae19e 100644 --- a/Tests/Functional/Python/PyCore/sliced_composition.py +++ b/Tests/Functional/Python/PyCore/sliced_composition.py @@ -42,11 +42,11 @@ class SlicedSpheresTest(unittest.TestCase): multi_layer.addLayer(substrate) return multi_layer - def get_intensity_data(self, particle_to_air=None, particle_to_substrate=None): + def get_result(self, particle_to_air=None, particle_to_substrate=None): sample = self.get_sample(particle_to_air, particle_to_substrate) simulation = utils.get_simulation_MiniGISAS(sample) simulation.runSimulation() - return simulation.getIntensityData() + return simulation.result() def get_composition(self, top_material, bottom_material): """ @@ -91,13 +91,13 @@ class SlicedSpheresTest(unittest.TestCase): # spherical particle sphere = ba.Particle(mParticle, ba.FormFactorFullSphere(sphere_radius)) - reference = self.get_intensity_data(sphere) + reference = self.get_result(sphere) # spherical composition composition = self.get_composition(mParticle, mParticle) - data = self.get_intensity_data(composition) + data = self.get_result(composition) - diff = ba.getRelativeDifference(data, reference) + diff = ba.RelativeDifference(data, reference) print(diff) self.assertLess(diff, 1e-10) @@ -109,13 +109,13 @@ class SlicedSpheresTest(unittest.TestCase): """ composition = self.get_composition(mParticle, mSubstrate) - reference = self.get_intensity_data(composition) + reference = self.get_result(composition) # spherical composition composition2 = self.get_composition_v2(mParticle, mSubstrate) - data = self.get_intensity_data(composition2) + data = self.get_result(composition2) - diff = ba.getRelativeDifference(data, reference) + diff = ba.RelativeDifference(data, reference) print(diff) self.assertLess(diff, 1e-10) @@ -131,14 +131,14 @@ class SlicedSpheresTest(unittest.TestCase): shift = bottom_cup_height # Scattering from empty multilayer - reference = self.get_intensity_data() + reference = self.get_result() # spherical composition composition = self.get_composition(mAmbience, mSubstrate) composition.setPosition(0, 0, -shift) - data = self.get_intensity_data(composition) + data = self.get_result(composition) - diff = ba.getRelativeDifference(data, reference) + diff = ba.RelativeDifference(data, reference) print(diff) self.assertLess(diff, 1e-10) @@ -155,14 +155,14 @@ class SlicedSpheresTest(unittest.TestCase): # spherical particle sphere = ba.Particle(mParticle, ba.FormFactorFullSphere(sphere_radius)) sphere.setPosition(0, 0, -shift) - reference = self.get_intensity_data(sphere) + reference = self.get_result(sphere) # spherical composition composition = self.get_composition(mParticle, mParticle) composition.setPosition(0, 0, -shift) - data = self.get_intensity_data(composition) + data = self.get_result(composition) - diff = ba.getRelativeDifference(data, reference) + diff = ba.RelativeDifference(data, reference) print(diff) self.assertLess(diff, 1e-10) @@ -177,33 +177,19 @@ class SlicedSpheresTest(unittest.TestCase): # truncated sphere on top of substrate with height 16nm truncatedSphere = ba.Particle(mParticle, ba.FormFactorTruncatedSphere(sphere_radius, sphere_radius*2 - bottom_cup_height)) - reference = self.get_intensity_data(truncatedSphere) + reference = self.get_result(truncatedSphere) # Particle composition, top part made of same material, as particle. Bottom part made of same material as substrate. composition = self.get_composition(mParticle, mSubstrate) composition_shift = bottom_cup_height composition.setPosition(0, 0, -composition_shift) - data = self.get_intensity_data(composition) + data = self.get_result(composition) - diff = ba.getRelativeDifference(data, reference) + diff = ba.RelativeDifference(data, reference) print(diff) self.assertLess(diff, 1e-10) - # def testExample(self): - # """ - # Temporary method to generate reference data - # """ - # m_top_cup = ba.HomogeneousMaterial("Ag", 1.245e-5, 5.419e-7) - # m_bottom_cup = ba.HomogeneousMaterial("Teflon", 2.900e-6, 6.019e-9) - # - # composition = self.get_composition(m_top_cup, m_bottom_cup) - # shift = 4*nm - # composition.setPosition(0, 0, -shift) - # data = self.get_intensity_data(composition) - # ba.IntensityDataIOFactory.writeIntensityData(data, "SlicedComposition.int") - - if __name__ == '__main__': unittest.main()