diff --git a/CHANGELOG b/CHANGELOG index 049d162bd1ab3ca3b77c77121752cc62199dd57e..ae083be77ffe70acee36cb395a646d119e72b115 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ BornAgain-1.18.99, ongoing development * Removed some interference functions constructors * Remove old R&T computations from API (now in Code/Legacy and Tests) * Python plot API entirely keyword based + * Add support for Q-Offset in QSpecScan > Fixes of unreported bugs: * Several GUI bugs that caused crashes * For alpha_i=0, set scattered intensity to 0 diff --git a/Core/Scan/QSpecScan.cpp b/Core/Scan/QSpecScan.cpp index ffb4c3487c77aa8924f27dd072ed37bcfe37ede2..305700f79dab26ec79e4b5dad4e52cb6be39eed9 100644 --- a/Core/Scan/QSpecScan.cpp +++ b/Core/Scan/QSpecScan.cpp @@ -41,6 +41,7 @@ QSpecScan::~QSpecScan() = default; QSpecScan* QSpecScan::clone() const { auto* result = new QSpecScan(*m_qs); result->setQResolution(*m_resolution); + result->setOffset(m_offset); return result; } @@ -52,7 +53,9 @@ QSpecScan::generateSimulationElements(const Instrument& instrument) const { std::vector<SpecularSimulationElement> result; result.reserve(qz.size()); for (size_t i = 0, size = qz.size(); i < size; ++i) - result.emplace_back(SpecularSimulationElement(-qz[i] / 2.0, instrument, qz[i] >= 0)); + result.emplace_back( + SpecularSimulationElement(-(qz[i] + m_offset) / 2.0, instrument, qz[i] >= 0)); + return result; } diff --git a/Core/Scan/QSpecScan.h b/Core/Scan/QSpecScan.h index 89bf72cfe5d5e744577b0df861cecf54758446c7..7838a73bcc9cc2f130a0f8011d0fe10632884cd3 100644 --- a/Core/Scan/QSpecScan.h +++ b/Core/Scan/QSpecScan.h @@ -83,6 +83,9 @@ public: void setAbsoluteQResolution(const IRangedDistribution& distr, const std::vector<double>& std_dev); + void setOffset(double offset) { m_offset = offset; } + double offset() const { return m_offset; } + private: void checkInitialization(); std::vector<double> generateQzVector() const; @@ -91,6 +94,8 @@ private: const std::unique_ptr<IAxis> m_qs; std::unique_ptr<ScanResolution> m_resolution; mutable std::vector<std::vector<ParameterSample>> m_q_res_cache; + + double m_offset = 0.; }; #endif // BORNAGAIN_CORE_SCAN_QSPECSCAN_H diff --git a/Examples/fit56_SpecularAdvanced/Pt_layer_fit.py b/Examples/fit56_SpecularAdvanced/Pt_layer_fit.py index 95ea478c3acba5e0b77e12ff4353923debbdce26..8438bd423b902b908401408829acb265c62c8883 100644 --- a/Examples/fit56_SpecularAdvanced/Pt_layer_fit.py +++ b/Examples/fit56_SpecularAdvanced/Pt_layer_fit.py @@ -57,9 +57,9 @@ def get_sample(params): def get_simulation(q_axis, parameters): - q_axis = q_axis + parameters["q_offset"] scan = ba.QSpecScan(q_axis) - + scan.setOffset( parameters["q_offset"] ) + n_sig = 4.0 n_samples = 25 @@ -84,12 +84,12 @@ def run_simulation(q_axis, fitParams): return simulation #.result() -def qr(result, q_offset=0): +def qr(result): """ helper function to return the q axis and reflectivity from simulation result """ - q = numpy.array(result.result().axis(ba.Axes.QSPACE)) - q_offset + q = numpy.array(result.result().axis(ba.Axes.QSPACE)) r = numpy.array(result.result().array(ba.Axes.QSPACE)) return q, r @@ -232,8 +232,7 @@ if __name__ == '__main__': paramsInitial = {d: v[0] for d, v in startParams.items()} qzs = numpy.linspace(qmin, qmax, scan_size) - q, r = qr(run_simulation(qzs, paramsInitial), - dict(paramsInitial, **fixedParams)["q_offset"]) + q, r = qr(run_simulation(qzs, paramsInitial)) data = get_Experimental_data(qmin, qmax) plot(q, r, data, f'PtLayerFit_initial.pdf', @@ -246,6 +245,5 @@ if __name__ == '__main__': print("Fit Result:") print(fitResult) - q, r = qr(run_simulation(qzs, fitParams=fitResult), - fitResult["q_offset"]) + q, r = qr(run_simulation(qzs, fitParams=fitResult)) plot(q, r, data, f'PtLayerFit_fit.pdf', dict(fitResult, **fixedParams)) diff --git a/Tests/UnitTests/Core/Fresnel/SpecularScanTest.cpp b/Tests/UnitTests/Core/Fresnel/SpecularScanTest.cpp index 4c35f9afbfea82b8cf6405e07a43eef74aa9f5e9..3df1c01142d516e9ef1f7443883e9a3163549c71 100644 --- a/Tests/UnitTests/Core/Fresnel/SpecularScanTest.cpp +++ b/Tests/UnitTests/Core/Fresnel/SpecularScanTest.cpp @@ -7,6 +7,8 @@ #include "Device/Instrument/Instrument.h" #include "Device/Resolution/ScanResolution.h" #include "Param/Distrib/RangedDistributions.h" +#include "Sample/Material/MaterialFactoryFuncs.h" +#include "Sample/Slice/Slice.h" #include "Tests/GTestWrapper/google_test.h" class SpecularScanTest : public ::testing::Test {}; @@ -123,6 +125,7 @@ TEST_F(SpecularScanTest, QScanInit) { EXPECT_EQ(scan.numberOfSimulationElements(), axis.size()); EXPECT_EQ(scan.footprintFactor(), nullptr); EXPECT_EQ(scan.footprint(0, 1), std::vector<double>{1.0}); + EXPECT_EQ(scan.offset(), 0.); EXPECT_THROW(scan.footprint(1, axis.size()), std::runtime_error); EXPECT_NO_THROW(scan.footprint(0, axis.size())); }; @@ -166,11 +169,13 @@ TEST_F(SpecularScanTest, AngularScanClone) { TEST_F(SpecularScanTest, QScanClone) { QSpecScan scan(std::vector<double>{0.1, 0.2, 0.3}); + scan.setOffset(2.22); std::unique_ptr<QSpecScan> scan_clone(scan.clone()); EXPECT_EQ(*scan_clone->coordinateAxis(), *scan.coordinateAxis()); EXPECT_NE(scan_clone->coordinateAxis(), scan.coordinateAxis()); EXPECT_EQ(scan_clone->footprintFactor(), nullptr); + EXPECT_EQ(scan_clone->offset(), scan.offset()); } TEST_F(SpecularScanTest, GenerateSimElements) { @@ -183,13 +188,26 @@ TEST_F(SpecularScanTest, GenerateSimElements) { for (size_t i = 0; i < sim_elements.size(); ++i) EXPECT_TRUE(sim_elements[i].isCalculated()); - QSpecScan scan2(std::vector<double>{0.0, 0.2, 0.3}); + const auto scan2_qvector = std::vector<double>{0.0, 0.2, 0.3}; + QSpecScan scan2(scan2_qvector); std::vector<SpecularSimulationElement> sim_elements2 = - scan.generateSimulationElements(instrument); + scan2.generateSimulationElements(instrument); EXPECT_EQ(sim_elements2.size(), scan2.numberOfSimulationElements()); EXPECT_EQ(scan2.numberOfSimulationElements(), 3u); for (size_t i = 0; i < sim_elements2.size(); ++i) EXPECT_TRUE(sim_elements2[i].isCalculated()); + + const double offset = 1.; + scan2.setOffset(offset); + std::vector<SpecularSimulationElement> sim_elements3 = + scan2.generateSimulationElements(instrument); + std::vector<Slice> slices; + slices.emplace_back(0., MaterialBySLD()); + for (size_t i = 0; i < sim_elements3.size(); ++i){ + const auto generatedKzs = sim_elements3[i].produceKz(slices); + EXPECT_EQ(generatedKzs[0].imag(), 0.); + EXPECT_EQ(2. * generatedKzs[0].real(), scan2_qvector[i] + offset); + } } TEST_F(SpecularScanTest, ErrorInput) { diff --git a/auto/Wrap/libBornAgainCore.py b/auto/Wrap/libBornAgainCore.py index cfecc37f6fe9182d06665a9092b1b78d89369dfe..41bfd1defca874425258fe94dfcce45a500d4b56 100644 --- a/auto/Wrap/libBornAgainCore.py +++ b/auto/Wrap/libBornAgainCore.py @@ -3448,6 +3448,14 @@ class QSpecScan(object): """ return _libBornAgainCore.QSpecScan_setAbsoluteQResolution(self, *args) + def setOffset(self, offset): + r"""setOffset(QSpecScan self, double offset)""" + return _libBornAgainCore.QSpecScan_setOffset(self, offset) + + def offset(self): + r"""offset(QSpecScan self) -> double""" + return _libBornAgainCore.QSpecScan_offset(self) + # Register QSpecScan in _libBornAgainCore: _libBornAgainCore.QSpecScan_swigregister(QSpecScan) diff --git a/auto/Wrap/libBornAgainCore_wrap.cpp b/auto/Wrap/libBornAgainCore_wrap.cpp index 7adfb9bbec23ffddc375fbf1566027c00dbffcab..4c09c1ce5fe7f533e6ac4df0be6e3201fd6b31c6 100644 --- a/auto/Wrap/libBornAgainCore_wrap.cpp +++ b/auto/Wrap/libBornAgainCore_wrap.cpp @@ -38690,6 +38690,58 @@ fail: } +SWIGINTERN PyObject *_wrap_QSpecScan_setOffset(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + QSpecScan *arg1 = (QSpecScan *) 0 ; + double arg2 ; + void *argp1 = 0 ; + int res1 = 0 ; + double val2 ; + int ecode2 = 0 ; + PyObject *swig_obj[2] ; + + if (!SWIG_Python_UnpackTuple(args, "QSpecScan_setOffset", 2, 2, swig_obj)) SWIG_fail; + res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_QSpecScan, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "QSpecScan_setOffset" "', argument " "1"" of type '" "QSpecScan *""'"); + } + arg1 = reinterpret_cast< QSpecScan * >(argp1); + ecode2 = SWIG_AsVal_double(swig_obj[1], &val2); + if (!SWIG_IsOK(ecode2)) { + SWIG_exception_fail(SWIG_ArgError(ecode2), "in method '" "QSpecScan_setOffset" "', argument " "2"" of type '" "double""'"); + } + arg2 = static_cast< double >(val2); + (arg1)->setOffset(arg2); + resultobj = SWIG_Py_Void(); + return resultobj; +fail: + return NULL; +} + + +SWIGINTERN PyObject *_wrap_QSpecScan_offset(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { + PyObject *resultobj = 0; + QSpecScan *arg1 = (QSpecScan *) 0 ; + void *argp1 = 0 ; + int res1 = 0 ; + PyObject *swig_obj[1] ; + double result; + + if (!args) SWIG_fail; + swig_obj[0] = args; + res1 = SWIG_ConvertPtr(swig_obj[0], &argp1,SWIGTYPE_p_QSpecScan, 0 | 0 ); + if (!SWIG_IsOK(res1)) { + SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "QSpecScan_offset" "', argument " "1"" of type '" "QSpecScan const *""'"); + } + arg1 = reinterpret_cast< QSpecScan * >(argp1); + result = (double)((QSpecScan const *)arg1)->offset(); + resultobj = SWIG_From_double(static_cast< double >(result)); + return resultobj; +fail: + return NULL; +} + + SWIGINTERN PyObject *QSpecScan_swigregister(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { PyObject *obj; if (!SWIG_Python_UnpackTuple(args, "swigregister", 1, 1, &obj)) return NULL; @@ -44550,6 +44602,8 @@ static PyMethodDef SwigMethods[] = { "Sets qz resolution values via IRangedDistribution and values of standard deviations. std_dev can be either single-valued or a numpy array. In the latter case the length of the array should coinside with the length of the qz-axis. \n" "\n" ""}, + { "QSpecScan_setOffset", _wrap_QSpecScan_setOffset, METH_VARARGS, "QSpecScan_setOffset(QSpecScan self, double offset)"}, + { "QSpecScan_offset", _wrap_QSpecScan_offset, METH_O, "QSpecScan_offset(QSpecScan self) -> double"}, { "QSpecScan_swigregister", QSpecScan_swigregister, METH_O, NULL}, { "QSpecScan_swiginit", QSpecScan_swiginit, METH_VARARGS, NULL}, { "delete_ISimulation", _wrap_delete_ISimulation, METH_O, "\n"