diff --git a/.style.yapf b/.style.yapf index c4ab74edd9f275d378e4173d8eb1f878ecf196ce..4c5b87caa85efbc416ce9e6585255bec33a4d66c 100644 --- a/.style.yapf +++ b/.style.yapf @@ -1,5 +1,5 @@ [style] based_on_style = pep8 -column_limit = 100 +column_limit = 140 allow_split_before_dict_value = false each_dict_entry_on_separate_line = true diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ee6787358e40a47df956fe695ff1718f1610675..c93404e5b9323b6ab73bdc3ef4aa4d4cefd9ef80 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,11 @@ set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/buildconfig/CMake") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) -option(ENABLE_MANTIDPLOT "Enable Qt4-based gui & components" ON) +option(ENABLE_MANTIDPLOT "Enable Qt4-based gui & components" OFF) option(ENABLE_WORKBENCH "Enable Qt5-based gui & components" ON) +if(ENABLE_MANTIDPLOT) + message( FATAL_ERROR "MantidPlot has been removed from the source code") +endif() set(CPACK_INSTALL_CMAKE_PROJECTS "${CMAKE_BINARY_DIR}" "Mantid" "ALL" "/") @@ -56,6 +59,13 @@ set_property(CACHE CPACK_PACKAGE_SUFFIX unstable "") # empty string and release are treated as the same thing +if(CPACK_PACKAGE_SUFFIX) + # Camelcase version of suffix for Windows/Mac + string(TOUPPER ${CPACK_PACKAGE_SUFFIX} CPACK_PACKAGE_SUFFIX_CAMELCASE) + string(SUBSTRING ${CPACK_PACKAGE_SUFFIX_CAMELCASE} 0 1 CPACK_PACKAGE_SUFFIX_CAMELCASE) + string(SUBSTRING ${CPACK_PACKAGE_SUFFIX} 1 -1 _tmp_suffix_remainder) + string(CONCAT CPACK_PACKAGE_SUFFIX_CAMELCASE ${CPACK_PACKAGE_SUFFIX_CAMELCASE} ${_tmp_suffix_remainder}) +endif() # Set package name here set(CPACK_PACKAGE_NAME "mantid${CPACK_PACKAGE_SUFFIX}") @@ -69,23 +79,8 @@ include(Eigen) include(Span) # Set ParaView information since later items depend on it - # VATES flag. Requires ParaView -option(MAKE_VATES "Switch for compiling the Vates project") -if(ENABLE_MANTIDPLOT AND MAKE_VATES) - find_package(ParaView) - if(ParaView_FOUND) - add_definitions(-DMAKE_VATES) - # Poco::File throws an exception when a backslash is used. - string(REPLACE "\\" - "/" - ParaView_DIR - ${ParaView_DIR}) - endif() -endif() -if(ENABLE_MANTIDPLOT) - include(ParaViewSetup) -endif() +option(MAKE_VATES "Switch for compiling the Vates project" Off) if( MSVC ) include ( MSVCSetup ) @@ -115,42 +110,13 @@ if(COVERALLS) coveralls_turn_on_coverage() endif() -# Qt/Qwt/PyQt/sip -if(ENABLE_MANTIDPLOT) - set(QT_USE_IMPORTED_TARGETS ON) - find_package(Qt4 - COMPONENTS QtCore - QtDesigner - QtGui - QtOpenGL - QtScript - QtSvg - QtXml - REQUIRED) - find_package(QScintillaQt4 REQUIRED) - find_package(Qwt5 REQUIRED) - if(QWT5_VERSION VERSION_LESS 5.0 - OR QWT5_VERSION VERSION_EQUAL 6.0 - OR QWT5_VERSION VERSION_GREATER 6.0) - message(FATAL_ERROR "Qwt version 5 is required, found: ${QWT5_VERSION}") - endif() - - if(UNIX_DIST MATCHES "RedHatEnterprise" AND UNIX_RELEASE MATCHES "7\..*") - set(USE_PRIVATE_SIPPYQT4 ON) - include(ExternalSipPyQt4) - else() - find_package(PyQt4 REQUIRED) - find_package(SIP REQUIRED) - endif() - separate_arguments(PYQT4_SIP_FLAGS) -endif() - if(ENABLE_WORKBENCH) find_package(Qt5 COMPONENTS Core Gui Widgets OpenGL + Test REQUIRED) if(Qt5_FOUND) message(STATUS "Found Qt ${Qt5_VERSION}: ${Qt5_DIR}") @@ -187,33 +153,6 @@ include_directories(Framework/NexusGeometry/inc) set(CORE_MANTIDLIBS Kernel HistogramData Indexing Beamline Geometry API Types) -if(ENABLE_MANTIDPLOT AND MAKE_VATES) - - if(NOT APPLE) - list(APPEND CPACK_INSTALL_CMAKE_PROJECTS - "${ParaView_DIR}" - "ParaView Runtime Libs" - "Runtime" - "${INBUNDLE}/") - list(APPEND CPACK_INSTALL_CMAKE_PROJECTS - "${ParaView_DIR}" - "VTK Runtime Libs" - "RuntimeLibraries" - "${INBUNDLE}/") - list(APPEND CPACK_INSTALL_CMAKE_PROJECTS - "${ParaView_DIR}" - "HDF5 Core Library" - "libraries" - "${INBUNDLE}/") - list(APPEND CPACK_INSTALL_CMAKE_PROJECTS - "${ParaView_DIR}" - "HDF5 HL Library" - "hllibraries" - "${INBUNDLE}/") - endif() - -endif(ENABLE_MANTIDPLOT AND MAKE_VATES) - if(UNIX) # Experimental feature. Unix only at this point. option(UNITY_BUILD @@ -227,7 +166,7 @@ endif() add_custom_target(AllTests) add_dependencies(AllTests FrameworkTests) -if(ENABLE_MANTIDPLOT OR ENABLE_WORKBENCH) +if(ENABLE_WORKBENCH) add_custom_target(GUITests) add_dependencies(check GUITests) # Collect all tests together @@ -235,10 +174,6 @@ if(ENABLE_MANTIDPLOT OR ENABLE_WORKBENCH) add_subdirectory(qt) endif() -if(ENABLE_MANTIDPLOT) - add_subdirectory(MantidPlot) -endif() - add_subdirectory(scripts) # Docs requirements @@ -287,11 +222,6 @@ endif() # Installation settings -if(ENABLE_MANTIDPLOT) - # N.B. INBUNDLE variable is empty except on Mac (set in DarwinSetup.cmake) - install(DIRECTORY installers/colormaps/ DESTINATION ${INBUNDLE}colormaps) -endif() - # Install the files (.desktop and icon) to create a menu items if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") function(uppercase_first_letter output_var input) @@ -331,17 +261,6 @@ if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(CPACK_PACKAGE_SUFFIX) uppercase_first_letter(_app_name_suffix ${CPACK_PACKAGE_SUFFIX}) endif() - if(ENABLE_MANTIDPLOT) - set(_icon_filename ${IMAGES_DIR}/mantidplot) - if(_icon_suffix) - set(_icon_filename ${_icon_filename}${_icon_suffix}) - endif() - install_desktop_files(mantidplot${CPACK_PACKAGE_SUFFIX}.desktop - "Mantid Plot ${_app_name_suffix}" - ${CMAKE_INSTALL_PREFIX}/bin/MantidPlot - ${_icon_filename}.png - mantidplot${CPACK_PACKAGE_SUFFIX}.png ) - endif() if(ENABLE_WORKBENCH) set(_icon_filename ${IMAGES_DIR}/mantid_workbench) @@ -394,10 +313,6 @@ if(ENABLE_CPACK) "OCE-draw,OCE-foundation,OCE-modeling,OCE-ocaf,OCE-visualization," "poco-crypto,poco-data,poco-mysql,poco-sqlite,poco-odbc,poco-util,poco-xml,poco-zip,poco-net,poco-netssl,poco-foundation," "hdf,hdf5,jsoncpp >= 0.7.0") - if(ENABLE_MANTIDPLOT) - set(CPACK_RPM_PACKAGE_REQUIRES - "${CPACK_RPM_PACKAGE_REQUIRES},qt4 >= 4.2,qscintilla,qwt5-qt4,qwtplot3d-qt4") - endif() if(ENABLE_WORKBENCH) set(CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},qt5-qtbase,qscintilla-qt5") @@ -411,11 +326,6 @@ if(ENABLE_CPACK) "python36-six,python36-PyYAML,python36-requests,python36-toml," "python36-ipython,python36-ipython-notebook" ) - if(ENABLE_MANTIDPLOT) - set(CPACK_RPM_PACKAGE_REQUIRES - "${CPACK_RPM_PACKAGE_REQUIRES},python36-QtPy," - "python36-matplotlib-qt4,python36-ipython-gui") - endif() if(ENABLE_WORKBENCH) set(CPACK_RPM_PACKAGE_REQUIRES "${CPACK_RPM_PACKAGE_REQUIRES},python36-qt5,python36-QtPy," @@ -447,19 +357,6 @@ if(ENABLE_CPACK) "librdkafka1,librdkafka++1," "libpocofoundation${POCO_SOLIB_VERSION},libpocoutil${POCO_SOLIB_VERSION},libpoconet${POCO_SOLIB_VERSION},libpoconetssl${POCO_SOLIB_VERSION},libpococrypto${POCO_SOLIB_VERSION},libpocoxml${POCO_SOLIB_VERSION}" ) - if(ENABLE_MANTIDPLOT) - set(CPACK_DEBIAN_PACKAGE_DEPENDS - "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqtcore4 (>= 4.2)," - "libqtgui4 (>= 4.2)," - "libqt4-opengl (>= 4.2)," - "libqt4-xml (>= 4.2)," - "libqt4-svg (>= 4.2)," - "libqt4-qt3support (>= 4.2)," - "qt4-dev-tools," - "libqwt5-qt4," - "libqwtplot3d-qt4-0v5," - "libqtwebkit4") - endif() if("${UNIX_CODENAME}" STREQUAL "bionic") set(CPACK_DEBIAN_PACKAGE_DEPENDS @@ -472,7 +369,7 @@ if(ENABLE_CPACK) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqscintilla2-qt5-13") endif() - else() + elseif ( "${UNIX_DIST}" MATCHES "Ubuntu" ) message(WARNING "Unsupported Debian-like OS: ${UNIX_CODENAME}. Packaging is unlikely to work correctly.") endif() # python requirements @@ -490,10 +387,6 @@ if(ENABLE_CPACK) "python3-yaml," "ipython3-qtconsole") # transitional package for bionic - if(ENABLE_MANTIDPLOT) - set(CPACK_DEBIAN_PACKAGE_DEPENDS - "${CPACK_DEBIAN_PACKAGE_DEPENDS},python3-pyqt4") - endif() if(ENABLE_WORKBENCH) set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},python3-pyqt5,python3-psutil") diff --git a/Framework/API/inc/MantidAPI/CompositeFunction.h b/Framework/API/inc/MantidAPI/CompositeFunction.h index 79b00612d1ff083ac2fa7f2c5b5b5af5e4567447..859532d1ae25c09ec6832c063bacf1bee1f1ab52 100644 --- a/Framework/API/inc/MantidAPI/CompositeFunction.h +++ b/Framework/API/inc/MantidAPI/CompositeFunction.h @@ -107,8 +107,12 @@ public: [[nodiscard]] bool isExplicitlySet(size_t i) const override; /// Get the fitting error for a parameter [[nodiscard]] double getError(size_t i) const override; + /// Get the fitting error for a parameter by name + [[nodiscard]] double getError(const std::string &name) const override; /// Set the fitting error for a parameter void setError(size_t i, double err) override; + /// Set the fitting error for a parameter by name + void setError(const std::string &name, double err) override; /// Value of i-th active parameter. Override this method to make fitted /// parameters different from the declared [[nodiscard]] double activeParameter(size_t i) const override; diff --git a/Framework/API/inc/MantidAPI/FunctionGenerator.h b/Framework/API/inc/MantidAPI/FunctionGenerator.h index 883c111f8c89cd5835a2ca352d388dff154df681..c1ca9fb218260ed8c10dd274a3ebf85667fcd69e 100644 --- a/Framework/API/inc/MantidAPI/FunctionGenerator.h +++ b/Framework/API/inc/MantidAPI/FunctionGenerator.h @@ -65,8 +65,12 @@ public: bool isExplicitlySet(size_t i) const override; /// Get the fitting error for a parameter double getError(size_t i) const override; + /// Get the fitting error for a parameter by name + double getError(const std::string &name) const override; /// Set the fitting error for a parameter void setError(size_t i, double err) override; + /// Set the fitting error for a parameter by name + void setError(const std::string &name, double err) override; /// Return parameter index from a parameter reference. size_t getParameterIndex(const ParameterReference &ref) const override; diff --git a/Framework/API/inc/MantidAPI/FunctionParameterDecorator.h b/Framework/API/inc/MantidAPI/FunctionParameterDecorator.h index 7ee3c88ceaa4483e6c1657f7f99837907e02c162..0037863168b65485cdf227490cead04c717a9722 100644 --- a/Framework/API/inc/MantidAPI/FunctionParameterDecorator.h +++ b/Framework/API/inc/MantidAPI/FunctionParameterDecorator.h @@ -75,8 +75,12 @@ public: bool isExplicitlySet(size_t i) const override; /// Get the fitting error for a parameter of decorated function. double getError(size_t i) const override; + /// Get the fitting error for a parameter of decorated function by name. + double getError(const std::string &name) const override; /// Set the fitting error for a parameter of decorated function. void setError(size_t i, double err) override; + /// Set the fitting error for a parameter of decorated function by name. + void setError(const std::string &name, double err) override; /// Return parameter index of decorated function from a parameter reference. /// Usefull for constraints and ties in composite functions. diff --git a/Framework/API/inc/MantidAPI/IFunction.h b/Framework/API/inc/MantidAPI/IFunction.h index 6749c4e4e041a28c07f5c37239fcbd182c6eccbc..18160216d5cde8107e5cfea383e4dbd5098c4b55 100644 --- a/Framework/API/inc/MantidAPI/IFunction.h +++ b/Framework/API/inc/MantidAPI/IFunction.h @@ -44,6 +44,19 @@ class Workspace; class MatrixWorkspace; class FunctionHandler; +/** + * Attribute visitor structure supporting lambda expressions + * Example usage: AttributeLambdaVisitor{[](const int val) {...}, [] (const + * double val) {}} would create a visitor capable of "visiting" an integer and + * double attribute* + * It functions by inheriting the () operator defined in each lambda + */ +template struct AttributeLambdaVisitor : Ts... { + using Ts::operator()...; +}; +template +AttributeLambdaVisitor(Ts...)->AttributeLambdaVisitor; + /** This is an interface to a fitting function - a semi-abstarct class. Functions derived from IFunction can be used with the Fit algorithm. IFunction defines the structure of a fitting funtion. @@ -250,6 +263,10 @@ public: template T apply(ConstAttributeVisitor &v) const { return boost::apply_visitor(v, m_data); } + /// Apply a lambda visitor + template void apply(AttributeLambdaVisitor &v) { + boost::apply_visitor(v, m_data); + } /// Returns type of the attribute std::string type() const; @@ -401,8 +418,12 @@ public: virtual bool isExplicitlySet(size_t i) const = 0; /// Get the fitting error for a parameter virtual double getError(size_t i) const = 0; + /// Get the fitting error for a parameter by name + virtual double getError(const std::string &name) const = 0; /// Set the fitting error for a parameter virtual void setError(size_t i, double err) = 0; + /// Set the fitting error for a parameter by name + virtual void setError(const std::string &name, double err) = 0; /// Check if a parameter i is fixed [[nodiscard]] bool isFixed(size_t i) const; @@ -539,10 +560,10 @@ public: std::shared_ptr> getCovarianceMatrix() const { return m_covar; } - /// Set the chi^2 - void setChiSquared(double chi2) { m_chiSquared = chi2; } - /// Get the chi^2 - [[nodiscard]] double getChiSquared() const { return m_chiSquared; } + /// Set the reduced chi^2 + void setReducedChiSquared(double chi2) { m_chiSquared = chi2; } + /// Get the reduced chi^2 + [[nodiscard]] double getReducedChiSquared() const { return m_chiSquared; } /// Set the parallel hint void setParallel(bool on) { diff --git a/Framework/API/inc/MantidAPI/IPeakFunction.h b/Framework/API/inc/MantidAPI/IPeakFunction.h index 11740974f7a854a1b1490b613a6f106b8bfa514d..514771ae237a442de1c336892149f9738610d7cd 100644 --- a/Framework/API/inc/MantidAPI/IPeakFunction.h +++ b/Framework/API/inc/MantidAPI/IPeakFunction.h @@ -61,6 +61,15 @@ public: /// Get name of parameter that is associated to centre. std::string getCentreParameterName() const; + /// Get the name of the parameter that is changed when the + /// fwhm is changed. By default this returns an empty string (unless + /// overridden in the individual functions) as some functions change two + /// params when the fwhm is set and others don't have a width (delta + /// func).This is intended for the BackToBackExponential-based peaks where + /// the width parameter (S) can be set in the intsurment parameter file and + /// this needs to be checked when a peak is added. + virtual std::string getWidthParameterName() const { return ""; } + /// Fix a parameter or set up a tie such that value returned /// by intensity() is constant during fitting. /// @param isDefault :: If true fix intensity by default: diff --git a/Framework/API/inc/MantidAPI/IndexProperty.h b/Framework/API/inc/MantidAPI/IndexProperty.h index 8df71f9d56051693d7f1722440c0a04d45bac9a4..e9bae459f24dd4c9431409f2d259efb1a3446a72 100644 --- a/Framework/API/inc/MantidAPI/IndexProperty.h +++ b/Framework/API/inc/MantidAPI/IndexProperty.h @@ -36,7 +36,8 @@ public: Kernel::IValidator_sptr(new Kernel::NullValidator)); IndexProperty(const IndexProperty &) = default; - IndexProperty &operator=(const IndexProperty &) = default; + // Copy assignment is deleted since there are reference type members. + IndexProperty &operator=(const IndexProperty &) = delete; IndexProperty *clone() const override; diff --git a/Framework/API/inc/MantidAPI/MatrixWorkspace.h b/Framework/API/inc/MantidAPI/MatrixWorkspace.h index 450676fdc7330cbc8b72abe102d2adba95e87169..2848870cc21a3ab9818b44c1b97f71d7625821bf 100644 --- a/Framework/API/inc/MantidAPI/MatrixWorkspace.h +++ b/Framework/API/inc/MantidAPI/MatrixWorkspace.h @@ -121,6 +121,9 @@ public: bool hasGroupedDetectors() const; + /// Returns true if the workspace is ragged (has differently sized spectra). + virtual bool isRaggedWorkspace() const = 0; + /// Get the footprint in memory in bytes. size_t getMemorySize() const override; virtual size_t getMemorySizeForXAxes() const; @@ -132,6 +135,10 @@ public: /// This throws an exception if the lengths are not identical across the /// spectra. virtual std::size_t blocksize() const = 0; + /// Returns the number of bins for a given histogram index. + virtual std::size_t getNumberBins(const std::size_t &index) const = 0; + /// Returns the maximum number of bins in a workspace (works on ragged data). + virtual std::size_t getMaxNumberBins() const = 0; /// Returns the number of histograms in the workspace virtual std::size_t getNumberHistograms() const = 0; @@ -144,7 +151,7 @@ public: Types::Core::DateAndTime getLastPulseTime() const; /// Returns the y index which corresponds to the X Value provided - std::size_t yIndexOfX(const double xValue, const std::size_t = 0, + std::size_t yIndexOfX(const double xValue, const std::size_t &index = 0, const double tolerance = 0.0) const; //---------------------------------------------------------------------- @@ -579,11 +586,10 @@ protected: private: std::size_t binIndexOfValue(Mantid::HistogramData::HistogramX const &xValues, - double const &xValue, bool const &ascendingOrder, - double const &tolerance) const; - std::size_t xIndexOfValue(Mantid::HistogramData::HistogramX const &xValues, - double const &xValue, - double const &tolerance) const; + const double xValue, + const bool ascendingOrder) const; + std::size_t xIndexOfValue(const Mantid::HistogramData::HistogramX &xValues, + const double xValue, const double tolerance) const; MatrixWorkspace *doClone() const override = 0; MatrixWorkspace *doCloneEmpty() const override = 0; diff --git a/Framework/API/inc/MantidAPI/ParamFunction.h b/Framework/API/inc/MantidAPI/ParamFunction.h index 8dcc9670dea3f10c7672474ba21ab96c06cf4f26..553594f2ea9f3ad0ec76b6d2d1e94676657da2ac 100644 --- a/Framework/API/inc/MantidAPI/ParamFunction.h +++ b/Framework/API/inc/MantidAPI/ParamFunction.h @@ -64,8 +64,12 @@ public: bool isExplicitlySet(size_t i) const override; /// Get the fitting error for a parameter double getError(size_t i) const override; + /// Get the fitting error for a parameter by name + double getError(const std::string &name) const override; /// Set the fitting error for a parameter void setError(size_t i, double err) override; + /// Set the fitting error for a parameter by name + void setError(const std::string &name, double err) override; /// Return parameter index from a parameter reference. Usefull for constraints /// and ties in composite functions diff --git a/Framework/API/inc/MantidAPI/PrecompiledHeader.h b/Framework/API/inc/MantidAPI/PrecompiledHeader.h index 1cd6fa20bd7393880be81493ec5118d215d687ed..6b1bac7c006852d7ef85f88a22b631b85e7a9402 100644 --- a/Framework/API/inc/MantidAPI/PrecompiledHeader.h +++ b/Framework/API/inc/MantidAPI/PrecompiledHeader.h @@ -7,33 +7,15 @@ #pragma once // Mantid +#include "MantidAPI/DllConfig.h" #include "MantidGeometry/IComponent.h" #include "MantidGeometry/IDetector.h" #include "MantidGeometry/Instrument.h" -#include "MantidKernel/ArrayProperty.h" -#include "MantidKernel/DateAndTime.h" -#include "MantidKernel/DynamicFactory.h" -#include "MantidKernel/Exception.h" -#include "MantidKernel/IPropertyManager.h" -#include "MantidKernel/Logger.h" -#include "MantidKernel/Quat.h" -#include "MantidKernel/System.h" -#include "MantidKernel/V3D.h" // STL -#include -#include -#include -#include -#include -#include -#include -#include // Boost #include -#include // Poco #include -#include diff --git a/Framework/API/inc/MantidAPI/Run.h b/Framework/API/inc/MantidAPI/Run.h index 5e571df6878c26037adf3288424f1fca58dc4635..d87ccca47079720fd96f2312f2e94ad93b593a18 100644 --- a/Framework/API/inc/MantidAPI/Run.h +++ b/Framework/API/inc/MantidAPI/Run.h @@ -76,19 +76,36 @@ public: /// Returns the vector of bin boundaries std::vector getBinBoundaries() const; - /// Set the gonoimeter & read the values from the logs if told to do so + /// Set a single gonoimeter & read the average values from the logs if told to + /// do so void setGoniometer(const Geometry::Goniometer &goniometer, const bool useLogValues); - /** @return A reference to the const Goniometer object for this run */ - inline const Geometry::Goniometer &getGoniometer() const { - return *m_goniometer; - } - /** @return A reference to the non-const Goniometer object for this run */ - inline Geometry::Goniometer &mutableGoniometer() { return *m_goniometer; } - - // Retrieve the goniometer rotation matrix + /// Set the gonoimeters using the individual values + void setGoniometers(const Geometry::Goniometer &goniometer); + /// Return reference to the first const Goniometer object for this run + const Geometry::Goniometer &getGoniometer() const; + + /// Return reference to the first non-const Goniometer object for this run + Geometry::Goniometer &mutableGoniometer(); + + /// Retrieve the first goniometer rotation matrix const Kernel::Matrix &getGoniometerMatrix() const; + /// Return reference to a const Goniometer object for the run + const Geometry::Goniometer &getGoniometer(const size_t index) const; + /// Return reference to a non-const Goniometer object for the run + Geometry::Goniometer &mutableGoniometer(const size_t index); + /// Get the number of goniometers in the Run + size_t getNumGoniometers() const; + /// Retrieve the a goniometer rotation matrix + const Kernel::Matrix &getGoniometerMatrix(const size_t index) const; + /// Append a goniometer to the run + size_t addGoniometer(const Geometry::Goniometer &goniometer); + /// Clear all goniometers on the Run + void clearGoniometers(); + /// Get vector of all goniometer matrices in the Run + const std::vector> getGoniometerMatrices() const; + /// Save the run to a NeXus file with a given group name void saveNexus(::NeXus::File *file, const std::string &group, bool keepOpen = false) const override; @@ -97,17 +114,20 @@ public: bool keepOpen = false) override; private: - /// Calculate the gonoimeter matrix - void calculateGoniometerMatrix(); + /// Calculate the average gonoimeter matrix + void calculateAverageGoniometerMatrix(); + /// Calculate the gonoimeter matrices from logs + void calculateGoniometerMatrices(Geometry::Goniometer goniometer); /// Goniometer for this run - std::unique_ptr m_goniometer; + std::vector> m_goniometers; /// A set of histograms that can be stored here for future reference std::vector m_histoBins; /// Adds all the time series in from one property manager into another void mergeMergables(Mantid::Kernel::PropertyManager &sum, const Mantid::Kernel::PropertyManager &toAdd); + void copyGoniometers(const Run &other); }; } // namespace API } // namespace Mantid diff --git a/Framework/API/inc/MantidAPI/WorkspaceHistory.h b/Framework/API/inc/MantidAPI/WorkspaceHistory.h index ba6360a9b0a608168bf1d331ef47613da4cf0d3e..e1bec128a13b285e6a5f31478184888fa59b06c0 100644 --- a/Framework/API/inc/MantidAPI/WorkspaceHistory.h +++ b/Framework/API/inc/MantidAPI/WorkspaceHistory.h @@ -40,8 +40,9 @@ public: virtual ~WorkspaceHistory() = default; /// Copy constructor WorkspaceHistory(const WorkspaceHistory &) = default; - /// Deleted copy assignment operator - WorkspaceHistory &operator=(const WorkspaceHistory &) = default; + /// Deleted copy assignment operator since m_environment has no copy + /// assignment. + WorkspaceHistory &operator=(const WorkspaceHistory &) = delete; /// Retrieve the algorithm history list const AlgorithmHistories &getAlgorithmHistories() const; /// Retrieve the environment history diff --git a/Framework/API/src/Algorithm.cpp b/Framework/API/src/Algorithm.cpp index 157f97e8706a14ddbee804aca26e2a232806c734..50557308740d9618243d88baa9262a3da3afdb06 100644 --- a/Framework/API/src/Algorithm.cpp +++ b/Framework/API/src/Algorithm.cpp @@ -639,9 +639,15 @@ bool Algorithm::executeInternal() { } // Throw because something was invalid if (numErrors > 0) { + std::stringstream msg; + msg << "Some invalid Properties found: [ "; + for (auto &error : errors) { + msg << error.first << " "; + } + msg << "]"; notificationCenter().postNotification( new ErrorNotification(this, "Some invalid Properties found")); - throw std::runtime_error("Some invalid Properties found"); + throw std::runtime_error(msg.str()); } } } diff --git a/Framework/API/src/CompositeFunction.cpp b/Framework/API/src/CompositeFunction.cpp index 3868d7751a35aec9aef46011e47cd4165b6e599b..b9e0742c3803f62c22cc797a98174bbe7f7983f6 100644 --- a/Framework/API/src/CompositeFunction.cpp +++ b/Framework/API/src/CompositeFunction.cpp @@ -278,7 +278,9 @@ bool CompositeFunction::hasAttribute(const std::string &name) const { return true; } const auto [attributeName, index] = parseName(name); - return m_functions[index]->hasAttribute(attributeName); + return index < m_functions.size() + ? m_functions[index]->hasAttribute(attributeName) + : false; } catch (std::invalid_argument &) { return false; } @@ -422,6 +424,16 @@ double CompositeFunction::getError(size_t i) const { return m_functions[iFun]->getError(i - m_paramOffsets[iFun]); } +/** + * Get the fitting error for a parameter by name. + * @param name :: The name of the parameter. + * @return value of the requested named parameter + */ +double CompositeFunction::getError(const std::string &name) const { + const auto [parameterName, index] = parseName(name); + return getFunction(index)->getError(parameterName); +} + /** * Set the fitting error for a parameter * @param i :: The index of a parameter @@ -432,6 +444,16 @@ void CompositeFunction::setError(size_t i, double err) { m_functions[iFun]->setError(i - m_paramOffsets[iFun], err); } +/** + * Sets the fitting error to a parameter by name. + * @param name :: The name of the parameter. + * @param err :: The error value to set + */ +void CompositeFunction::setError(const std::string &name, double err) { + auto [parameterName, index] = parseName(name); + getFunction(index)->setError(parameterName, err); +} + /// Value of i-th active parameter. Override this method to make fitted /// parameters different from the declared double CompositeFunction::activeParameter(size_t i) const { diff --git a/Framework/API/src/FunctionGenerator.cpp b/Framework/API/src/FunctionGenerator.cpp index 4fecd35eb92c00ffd5dcc6eabf9cbb133bc5169c..00b27e2d3b0268bb1c02e62fecdbd3bde2ebd5d3 100644 --- a/Framework/API/src/FunctionGenerator.cpp +++ b/Framework/API/src/FunctionGenerator.cpp @@ -151,6 +151,12 @@ double FunctionGenerator::getError(size_t i) const { } } +/// Get the fitting error for a parameter by name +double FunctionGenerator::getError(const std::string &name) const { + auto index = parameterIndex(name); + return getError(index); +} + /// Set the fitting error for a parameter void FunctionGenerator::setError(size_t i, double err) { if (i < m_nOwnParams) { @@ -161,6 +167,12 @@ void FunctionGenerator::setError(size_t i, double err) { } } +/// Set the fitting error for a parameter by name +void FunctionGenerator::setError(const std::string &name, double err) { + auto index = parameterIndex(name); + setError(index, err); +} + /// Change status of parameter void FunctionGenerator::setParameterStatus(size_t i, IFunction::ParameterStatus status) { diff --git a/Framework/API/src/FunctionParameterDecorator.cpp b/Framework/API/src/FunctionParameterDecorator.cpp index fade9e016835532c88877cc23fd9427cbe4012ae..d76c55abd5a489235c7dbaaa5f2cac15f4f2dd7d 100644 --- a/Framework/API/src/FunctionParameterDecorator.cpp +++ b/Framework/API/src/FunctionParameterDecorator.cpp @@ -159,12 +159,22 @@ double FunctionParameterDecorator::getError(size_t i) const { return m_wrappedFunction->getError(i); } +double FunctionParameterDecorator::getError(const std::string &name) const { + auto index = parameterIndex(name); + return getError(index); +} + void FunctionParameterDecorator::setError(size_t i, double err) { throwIfNoFunctionSet(); return m_wrappedFunction->setError(i, err); } +void FunctionParameterDecorator::setError(const std::string &name, double err) { + auto index = parameterIndex(name); + setError(index, err); +} + size_t FunctionParameterDecorator::getParameterIndex( const ParameterReference &ref) const { throwIfNoFunctionSet(); diff --git a/Framework/API/src/MatrixWorkspace.cpp b/Framework/API/src/MatrixWorkspace.cpp index 9265a47de9b001b78509ee280398273f5045f016..98207ed1e98fecf2285760d8f04e0b09b1c8ad22 100644 --- a/Framework/API/src/MatrixWorkspace.cpp +++ b/Framework/API/src/MatrixWorkspace.cpp @@ -1374,11 +1374,11 @@ Types::Core::DateAndTime MatrixWorkspace::getLastPulseTime() const { * @param xValue :: The X value to search for * @param index :: The index within the workspace to search within (default = 0) * @param tolerance :: The tolerance to accept between the passed xValue and the - * stored value (default = 0.0) + * stored value (default = 0.0). Used for point data only. * @returns The index corresponding to the X value provided */ std::size_t MatrixWorkspace::yIndexOfX(const double xValue, - const std::size_t index, + const std::size_t &index, const double tolerance) const { if (index >= getNumberHistograms()) throw std::out_of_range("MatrixWorkspace::yIndexOfX - Index out of range."); @@ -1388,17 +1388,21 @@ std::size_t MatrixWorkspace::yIndexOfX(const double xValue, const auto minX = ascendingOrder ? xValues.front() : xValues.back(); const auto maxX = ascendingOrder ? xValues.back() : xValues.front(); - if (xValue < minX) - throw std::out_of_range("MatrixWorkspace::yIndexOfX - X value is lower" - " than the lowest in the current range."); - else if (xValue > maxX) - throw std::out_of_range("MatrixWorkspace::yIndexOfX - X value is greater" - " than the highest in the current range."); + if (isHistogramDataByIndex(index)) { + UNUSED_ARG(tolerance); + + if (xValue < minX || xValue > maxX) + throw std::out_of_range("MatrixWorkspace::yIndexOfX - X value is out of " + "the range of the min and max bin edges."); + + return binIndexOfValue(xValues, xValue, ascendingOrder); + } else { + if (xValue < minX - tolerance || xValue > maxX + tolerance) + throw std::out_of_range("MatrixWorkspace::yIndexOfX - X value is out of " + "range for this point data."); - if (isHistogramDataByIndex(index)) - return binIndexOfValue(xValues, xValue, ascendingOrder, tolerance); - else return xIndexOfValue(xValues, xValue, tolerance); + } } /** @@ -1406,17 +1410,15 @@ std::size_t MatrixWorkspace::yIndexOfX(const double xValue, * @param xValues :: The histogram to search * @param xValue :: The X value to search for * @param ascendingOrder :: True if the order of the xValues is ascending - * @param tolerance :: The tolerance to accept between the passed xValue and the - * stored value (default = 0.0) * @returns An index to the bin containing X */ -std::size_t MatrixWorkspace::binIndexOfValue( - HistogramData::HistogramX const &xValues, double const &xValue, - bool const &ascendingOrder, double const &tolerance) const { +std::size_t +MatrixWorkspace::binIndexOfValue(HistogramData::HistogramX const &xValues, + const double xValue, + const bool ascendingOrder) const { std::size_t hops; if (ascendingOrder) { - auto lowerIter = - std::lower_bound(xValues.cbegin(), xValues.cend(), xValue - tolerance); + auto lowerIter = std::lower_bound(xValues.cbegin(), xValues.cend(), xValue); // If we are pointing at the first value then we want to be in the first bin if (lowerIter == xValues.cbegin()) @@ -1424,12 +1426,10 @@ std::size_t MatrixWorkspace::binIndexOfValue( hops = std::distance(xValues.cbegin(), lowerIter); } else { - auto lowerIter = std::upper_bound(xValues.crbegin(), xValues.crend(), - xValue + tolerance); + auto lowerIter = + std::lower_bound(xValues.crbegin(), xValues.crend(), xValue); - if (lowerIter == xValues.crend()) - --lowerIter; - else if (lowerIter == xValues.crbegin()) + if (lowerIter == xValues.crbegin()) ++lowerIter; hops = xValues.size() - std::distance(xValues.crbegin(), lowerIter); @@ -1448,9 +1448,9 @@ std::size_t MatrixWorkspace::binIndexOfValue( * @returns The index of the X value */ std::size_t -MatrixWorkspace::xIndexOfValue(HistogramData::HistogramX const &xValues, - double const &xValue, - double const &tolerance) const { +MatrixWorkspace::xIndexOfValue(const HistogramData::HistogramX &xValues, + const double xValue, + const double tolerance) const { auto const iter = std::find_if(xValues.cbegin(), xValues.cend(), [&xValue, &tolerance](double const &value) { return std::abs(xValue - value) <= tolerance; diff --git a/Framework/API/src/MultipleFileProperty.cpp b/Framework/API/src/MultipleFileProperty.cpp index 7df5dfb6c7d8def8085ff5911ed17cd201b29536..8bac1f46d4c27c1736cdd909ecdf1b876678167b 100644 --- a/Framework/API/src/MultipleFileProperty.cpp +++ b/Framework/API/src/MultipleFileProperty.cpp @@ -309,7 +309,7 @@ MultipleFileProperty::setValueAsMultipleFiles(const std::string &propValue) { // existing) file within a token, but which has unexpected zero padding, // or some other anomaly. if (VectorHelper::flattenVector(f).empty()) - f.emplace_back(std::vector(1, plusTokenString)); + f.emplace_back(1, plusTokenString); if (plusTokenStrings.size() > 1) { // See [3] in header documentation. Basically, for reasons of diff --git a/Framework/API/src/ParamFunction.cpp b/Framework/API/src/ParamFunction.cpp index 66b2b282edab56dc13d64b68a5df86d1a99562b2..d37b1fd26c3aba5f46ee6dd35335575e7fa8e6db 100644 --- a/Framework/API/src/ParamFunction.cpp +++ b/Framework/API/src/ParamFunction.cpp @@ -188,7 +188,7 @@ std::string ParamFunction::parameterDescription(size_t i) const { } /** - * Get the fitting error for a parameter + * Get the fitting error for a parameter. * @param i :: The index of a parameter * @return :: the error */ @@ -198,7 +198,27 @@ double ParamFunction::getError(size_t i) const { } /** - * Set the fitting error for a parameter + * Get the fitting error for a parameter by name. + * @param name :: The name of a parameter + * @return :: the error + */ +double ParamFunction::getError(const std::string &name) const { + auto it = std::find(m_parameterNames.cbegin(), m_parameterNames.cend(), name); + if (it == m_parameterNames.cend()) { + std::ostringstream msg; + msg << "ParamFunction tries to get error of non-existing parameter (" + << name << ") " + << "to function " << this->name(); + msg << "\nAllowed parameters: "; + for (const auto ¶meterName : m_parameterNames) + msg << parameterName << ", "; + throw std::invalid_argument(msg.str()); + } + return m_errors[static_cast(it - m_parameterNames.begin())]; +} + +/** + * Set the fitting error for a parameter. * @param i :: The index of a parameter * @param err :: The error value to set */ @@ -207,6 +227,26 @@ void ParamFunction::setError(size_t i, double err) { m_errors[i] = err; } +/** + * Get the fitting error for a parameter by name. + * @param name :: The name of a parameter + * @param err :: The error value to set + */ +void ParamFunction::setError(const std::string &name, double err) { + auto it = std::find(m_parameterNames.cbegin(), m_parameterNames.cend(), name); + if (it == m_parameterNames.cend()) { + std::ostringstream msg; + msg << "ParamFunction tries to set error of non-existing parameter (" + << name << ") " + << "to function " << this->name(); + msg << "\nAllowed parameters: "; + for (const auto ¶meterName : m_parameterNames) + msg << parameterName << ", "; + throw std::invalid_argument(msg.str()); + } + m_errors[static_cast(it - m_parameterNames.begin())] = err; +} + /** * Declare a new parameter. To used in the implementation'c constructor. * @param name :: The parameter name. diff --git a/Framework/API/src/Run.cpp b/Framework/API/src/Run.cpp index 3b0bcfed25816a89616dc44b87cbe94374a9d676..0ad5b09814d242413a6ef13fcf708b7ddc2110c0 100644 --- a/Framework/API/src/Run.cpp +++ b/Framework/API/src/Run.cpp @@ -35,6 +35,7 @@ const std::string ADDABLE[ADDABLES] = { "monitor2_counts", "monitor3_counts", "monitor4_counts", "monitor5_counts"}; /// Name of the goniometer log when saved to a NeXus file const char *GONIOMETER_LOG_NAME = "goniometer"; +const char *GONIOMETERS_LOG_NAME = "goniometers"; /// Name of the stored histogram bins log when saved to NeXus const char *HISTO_BINS_LOG_NAME = "processed_histogram_bins"; const char *PEAK_RADIUS_GROUP = "peak_radius"; @@ -45,26 +46,33 @@ const char *OUTER_BKG_RADIUS_GROUP = "outer_bkg_radius"; Kernel::Logger g_log("Run"); } // namespace -Run::Run() : m_goniometer(std::make_unique()) {} +Run::Run() { + m_goniometers.clear(); + m_goniometers.push_back(std::make_unique()); +} -Run::Run(const Run &other) - : LogManager(other), - m_goniometer(std::make_unique(*other.m_goniometer)), - m_histoBins(other.m_histoBins) {} +Run::Run(const Run &other) : LogManager(other), m_histoBins(other.m_histoBins) { + this->copyGoniometers(other); +} // Defined as default in source for forward declaration with std::unique_ptr. Run::~Run() = default; Run &Run::operator=(const Run &other) { LogManager::operator=(other); - m_goniometer = std::make_unique(*other.m_goniometer); + copyGoniometers(other); m_histoBins = other.m_histoBins; return *this; } bool Run::operator==(const Run &other) { - return *m_goniometer == *other.m_goniometer && - LogManager::operator==(other) && + if (m_goniometers.size() != other.m_goniometers.size()) + return false; + for (size_t i = 0; i < m_goniometers.size(); i++) { + if (*m_goniometers[i] != *other.m_goniometers[i]) + return false; + } + return LogManager::operator==(other) && this->m_histoBins == other.m_histoBins; } @@ -75,8 +83,7 @@ std::shared_ptr Run::clone() { for (auto property : this->m_manager->getProperties()) { clone->addProperty(property->clone()); } - clone->m_goniometer = - std::make_unique(*this->m_goniometer); + clone->copyGoniometers(const_cast(*this)); clone->m_histoBins = this->m_histoBins; return clone; } @@ -315,27 +322,51 @@ std::vector Run::getBinBoundaries() const { */ size_t Run::getMemorySize() const { size_t total = LogManager::getMemorySize(); - total += sizeof(*m_goniometer); + total += sizeof(Geometry::Goniometer) * m_goniometers.size(); total += m_histoBins.size() * sizeof(double); return total; } +/** @return A reference to the const Goniometer object for this run */ +const Geometry::Goniometer &Run::getGoniometer() const { + return *m_goniometers[0]; +} + +/** @return A reference to the non-const Goniometer object for this run */ +Geometry::Goniometer &Run::mutableGoniometer() { return *m_goniometers[0]; } + //----------------------------------------------------------------------------------------------- /** - * Set the gonoimeter & optionally read the values from the logs - * @param goniometer :: A refernce to a goniometer + * Set the gonoimeter & optionally read the average values from the logs + * @param goniometer :: A reference to a goniometer * @param useLogValues :: If true, recalculate the goniometer using the log * values */ void Run::setGoniometer(const Geometry::Goniometer &goniometer, const bool useLogValues) { - auto old = std::move(m_goniometer); + auto old = std::move(m_goniometers); try { - m_goniometer = std::make_unique(goniometer); + m_goniometers.emplace_back( + std::make_unique(goniometer)); if (useLogValues) - calculateGoniometerMatrix(); + calculateAverageGoniometerMatrix(); + } catch (std::runtime_error &) { + m_goniometers = std::move(old); + throw; + } +} + +//----------------------------------------------------------------------------------------------- +/** + * Set the gonoimeter & read the individual values from the logs + * @param goniometer :: A reference to a goniometer + */ +void Run::setGoniometers(const Geometry::Goniometer &goniometer) { + auto old = std::move(m_goniometers); + try { + calculateGoniometerMatrices(goniometer); } catch (std::runtime_error &) { - m_goniometer = std::move(old); + m_goniometers = std::move(old); throw; } } @@ -344,12 +375,84 @@ void Run::setGoniometer(const Geometry::Goniometer &goniometer, * previously set Goniometer object as well as the angles * loaded in the run (if any). * - * As of now, it uses the MEAN angle. - * * @return 3x3 double rotation matrix */ const Mantid::Kernel::DblMatrix &Run::getGoniometerMatrix() const { - return m_goniometer->getR(); + return getGoniometerMatrix(0); +} + +//----------------------------------------------------------------------------------------------- +/// @return the number of goniometers's in this Run +size_t Run::getNumGoniometers() const { return m_goniometers.size(); } + +//----------------------------------------------------------------------------------------------- +/** Add a new Goniometer to this Run + * + * @param goniometer :: goniometer to add + * @return the index at which it was added + */ +size_t Run::addGoniometer(const Geometry::Goniometer &goniometer) { + m_goniometers.emplace_back( + std::make_unique(goniometer)); + return m_goniometers.size() - 1; +} + +//----------------------------------------------------------------------------------------------- +/// Remove all goniometers on the Run +void Run::clearGoniometers() { m_goniometers.clear(); } + +//----------------------------------------------------------------------------------------------- +/** Get the Goniometer for the given run index + * + * @param index :: index of the run to get. + * @return goniometer + */ +const Geometry::Goniometer &Run::getGoniometer(const size_t index) const { + if (index >= m_goniometers.size()) + throw std::out_of_range( + "Run::getGoniometer() const: index is out of range."); + return *m_goniometers[index]; +} + +//----------------------------------------------------------------------------------------------- +/** Get the non-const Goniometer for the given run index + * + * @param index :: index of the run to get. + * @return goniometer + */ +Geometry::Goniometer &Run::mutableGoniometer(const size_t index) { + if (index >= m_goniometers.size()) + throw std::out_of_range( + "Run::getGoniometer() const: index is out of range."); + return *m_goniometers[index]; +} + +/** Get the gonoimeter rotation matrix, calculated using the + * previously set Goniometer object as well as the angles + * loaded in the run (if any). + * + * @param index :: index of the run to get. + * @return 3x3 double rotation matrix + */ +const Mantid::Kernel::DblMatrix & +Run::getGoniometerMatrix(const size_t index) const { + if (index >= m_goniometers.size()) + throw std::out_of_range( + "Run::getGoniometer() const: index is out of range."); + return m_goniometers[index]->getR(); +} + +/** Get a vector of all the gonoimeter rotation matries + * + * @return vector of 3x3 double rotation matrix + */ +const std::vector> Run::getGoniometerMatrices() const { + std::vector> goniometers; + goniometers.reserve(m_goniometers.size()); + for (auto it = m_goniometers.begin(); it != m_goniometers.end(); ++it) { + goniometers.emplace_back((*it)->getR()); + } + return goniometers; } //-------------------------------------------------------------------------------------------- @@ -363,7 +466,16 @@ void Run::saveNexus(::NeXus::File *file, const std::string &group, LogManager::saveNexus(file, group, true); // write the goniometer - m_goniometer->saveNexus(file, GONIOMETER_LOG_NAME); + if (m_goniometers.size() == 1) + m_goniometers[0]->saveNexus(file, GONIOMETER_LOG_NAME); + else if (m_goniometers.size() > 1) { + file->makeGroup(GONIOMETERS_LOG_NAME, "NXcollection", true); + file->writeData("num_goniometer", int(m_goniometers.size())); + for (size_t i = 0; i < m_goniometers.size(); i++) { + m_goniometers[i]->saveNexus(file, "goniometer" + std::to_string(i)); + } + file->closeGroup(); + } // write the histogram bins, if there are any if (!m_histoBins.empty()) { @@ -417,9 +529,20 @@ void Run::loadNexus(::NeXus::File *file, const std::string &group, file->getEntries(entries); LogManager::loadNexus(file, entries); for (const auto &name_class : entries) { - if (name_class.second == "NXpositioner") { + if (name_class.first == GONIOMETER_LOG_NAME) { // Goniometer class - m_goniometer->loadNexus(file, name_class.first); + m_goniometers[0]->loadNexus(file, name_class.first); + } else if (name_class.first == GONIOMETERS_LOG_NAME) { + file->openGroup(name_class.first, "NXcollection"); + int num_goniometer; + file->readData("num_goniometer", num_goniometer); + m_goniometers.clear(); + m_goniometers.reserve(num_goniometer); + for (int i = 0; i < num_goniometer; i++) { + m_goniometers.emplace_back(std::make_unique()); + m_goniometers[i]->loadNexus(file, "goniometer" + std::to_string(i)); + } + file->closeGroup(); } else if (name_class.first == HISTO_BINS_LOG_NAME) { file->openGroup(name_class.first, "NXdata"); file->readData("value", m_histoBins); @@ -470,11 +593,11 @@ void Run::loadNexus(::NeXus::File *file, const std::string &group, //----------------------------------------------------------------------------------------------------------------------- /** - * Calculate the goniometer matrix + * Calculate the average goniometer matrix */ -void Run::calculateGoniometerMatrix() { - for (size_t i = 0; i < m_goniometer->getNumberAxes(); ++i) { - const std::string axisName = m_goniometer->getAxis(i).name; +void Run::calculateAverageGoniometerMatrix() { + for (size_t i = 0; i < m_goniometers[0]->getNumberAxes(); ++i) { + const std::string axisName = m_goniometers[0]->getAxis(i).name; const double minAngle = getLogAsSingleValue(axisName, Kernel::Math::Minimum); const double maxAngle = @@ -511,7 +634,40 @@ void Run::calculateGoniometerMatrix() { "1\',Axis1='chi,0,0,1,1',Axis2='phi,0,1,0,1')"); } } - m_goniometer->setRotationAngle(i, angle); + m_goniometers[0]->setRotationAngle(i, angle); + } +} + +/** + * Calculate the goniometer matrixes from logs + * @param goniometer goniometer with axes names to use + */ +void Run::calculateGoniometerMatrices(Geometry::Goniometer goniometer) { + if (goniometer.getNumberAxes() == 0) + throw std::runtime_error( + "Run::calculateGoniometerMatrices must include axes for goniometer"); + + const size_t num_log_values = + getTimeSeriesProperty(goniometer.getAxis(0).name)->size(); + + m_goniometers.clear(); + m_goniometers.reserve(num_log_values); + + for (size_t i = 0; i < num_log_values; ++i) + m_goniometers.emplace_back( + std::make_unique(goniometer)); + + for (size_t i = 0; i < goniometer.getNumberAxes(); ++i) { + const auto angles = + getTimeSeriesProperty(goniometer.getAxis(i).name) + ->valuesAsVector(); + if (angles.size() != num_log_values) + throw std::runtime_error("Run::calculateGoniometerMatrices different " + "number of log entries between axes"); + + for (size_t j = 0; j < num_log_values; ++j) { + m_goniometers[j]->setRotationAngle(i, angles[j]); + } } } @@ -540,5 +696,16 @@ void Run::mergeMergables(Mantid::Kernel::PropertyManager &sum, } } +//----------------------------------------------------------------------------------------------- +/** Copy the goniometers from another + * @param other :: other workspace to copy */ +void Run::copyGoniometers(const Run &other) { + m_goniometers.clear(); + m_goniometers.reserve(other.m_goniometers.size()); + for (const auto &goniometer : other.m_goniometers) { + auto new_goniometer = std::make_unique(*goniometer); + m_goniometers.emplace_back(std::move(new_goniometer)); + } +} } // namespace API } // namespace Mantid diff --git a/Framework/API/src/TextAxis.cpp b/Framework/API/src/TextAxis.cpp index c9823c556337827a461085ef9d63d3b449f0809b..b3b35a435caaa4a6086f93908b50e407a5572c0b 100644 --- a/Framework/API/src/TextAxis.cpp +++ b/Framework/API/src/TextAxis.cpp @@ -10,6 +10,7 @@ #include "MantidAPI/TextAxis.h" #include "MantidKernel/EmptyValues.h" #include "MantidKernel/Exception.h" +#include "MantidKernel/VectorHelper.h" namespace Mantid { namespace API { @@ -71,7 +72,13 @@ void TextAxis::setValue(const std::size_t &index, const double &value) { * Returns the value that has been passed to it as a size_t */ size_t TextAxis::indexOfValue(const double value) const { - return static_cast(value); + std::vector spectraNumbers; + spectraNumbers.reserve(length()); + for (size_t i = 0; i < length(); i++) { + spectraNumbers.emplace_back(static_cast(i)); + } + return Mantid::Kernel::VectorHelper::indexOfValueFromCenters(spectraNumbers, + value); } /** Check if two axis defined as spectra or numeric axis are equivalent diff --git a/Framework/API/test/CompositeFunctionTest.h b/Framework/API/test/CompositeFunctionTest.h index f050d248f1e0b51c39d0c1c8d03d6435b8dab187..f5911272f17b49233df0ef29f140a098d09f4500 100644 --- a/Framework/API/test/CompositeFunctionTest.h +++ b/Framework/API/test/CompositeFunctionTest.h @@ -43,11 +43,20 @@ public: ~CompositeFunctionTest_MocMatrixWorkspace() override {} + bool isRaggedWorkspace() const override { return false; } + // Section required for iteration /// Returns the number of single indexable items in the workspace std::size_t size() const override { return m_spectra.size() * m_blocksize; } /// Returns the size of each block of data returned by the dataY accessors std::size_t blocksize() const override { return m_blocksize; } + /// Returns the number of bins for a given histogram index. + std::size_t getNumberBins(const std::size_t &index) const override { + UNUSED_ARG(index); + return m_blocksize; + } + /// Returns the maximum number of bins in a workspace (works on ragged data). + std::size_t getMaxNumberBins() const override { return m_blocksize; } /// Returns the number of histograms in the workspace std::size_t getNumberHistograms() const override { return m_spectra.size(); } @@ -1420,4 +1429,20 @@ public: TS_ASSERT_EQUALS(mfun->attributeName(2), "f1.GaussAttribute"); TS_ASSERT_EQUALS(mfun->attributeName(3), "f2.CubicAttribute"); } + + void test_setError_with_name() { + auto mfun = std::make_unique(); + auto gauss = std::make_shared>(); + auto background = std::make_shared>(); + auto cubic = std::make_shared>(); + + mfun->addFunction(background); + mfun->addFunction(gauss); + mfun->addFunction(cubic); + + mfun->setError(0, 1.0); + TS_ASSERT_EQUALS(mfun->getError(0), 1.0); + mfun->setError("f1.s", 5.0); + TS_ASSERT_EQUALS(mfun->getError("f1.s"), 5.0); + } }; diff --git a/Framework/API/test/FunctionParameterDecoratorTest.h b/Framework/API/test/FunctionParameterDecoratorTest.h index 476ccd1aca1c6aed270eab9708d365a7cdbb6616..919ee1fc4584cc94b1c4432aa91ef83fe3a3bac6 100644 --- a/Framework/API/test/FunctionParameterDecoratorTest.h +++ b/Framework/API/test/FunctionParameterDecoratorTest.h @@ -222,9 +222,10 @@ public: FunctionParameterDecorator_sptr fn = getFunctionParameterDecoratorGaussian(); IFunction_sptr decoratedFunction = fn->getDecoratedFunction(); - TS_ASSERT_THROWS_NOTHING(fn->setError(0, 3.0)); + TS_ASSERT_THROWS_NOTHING(fn->setError("PeakCentre", 4.0)); TS_ASSERT_EQUALS(fn->getError(0), 3.0); + TS_ASSERT_EQUALS(fn->getError("PeakCentre"), 4.0); for (size_t i = 0; i < fn->nParams(); ++i) { TS_ASSERT_EQUALS(fn->getError(i), decoratedFunction->getError(i)); } diff --git a/Framework/API/test/IFunctionTest.h b/Framework/API/test/IFunctionTest.h index b390908a2ef732f993fad75c875c86110ebaa116..5f52dfcbe6bde80fbf2eb1f62f5b05af96339258 100644 --- a/Framework/API/test/IFunctionTest.h +++ b/Framework/API/test/IFunctionTest.h @@ -54,7 +54,9 @@ public: std::string parameterDescription(std::size_t) const override { return ""; } bool isExplicitlySet(std::size_t) const override { return true; } double getError(std::size_t) const override { return 0.0; } + double getError(const std::string &) const override { return 0.0; } void setError(std::size_t, double) override {} + void setError(const std::string &, double) override {} size_t getParameterIndex(const Mantid::API::ParameterReference &ref) const override { if (ref.getLocalFunction() == this && ref.getLocalIndex() < nParams()) { diff --git a/Framework/API/test/MatrixWorkspaceTest.h b/Framework/API/test/MatrixWorkspaceTest.h index 983afc7c39fd2052adeb1828938a40a76419e0cf..82a3feb255990e2a00da3dedf9930fff1fcf547b 100644 --- a/Framework/API/test/MatrixWorkspaceTest.h +++ b/Framework/API/test/MatrixWorkspaceTest.h @@ -1784,7 +1784,7 @@ public: std::vector const xValues{5.3, 4.3, 3.3, 2.3}; auto const workspace = getWorkspaceWithPopulatedX(1, 4, 3, xValues); - TS_ASSERT_EQUALS(workspace.yIndexOfX(4.3), 0); + TS_ASSERT_EQUALS(workspace.yIndexOfX(4.3), 1); } void @@ -1855,7 +1855,7 @@ public: std::vector const xValues{4.0, 3.0, 2.0, 1.0}; auto const workspace = getWorkspaceWithPopulatedX(1, 4, 3, xValues); - TS_ASSERT_EQUALS(workspace.yIndexOfX(3.0), 0); + TS_ASSERT_EQUALS(workspace.yIndexOfX(3.0), 1); } void diff --git a/Framework/API/test/ParamFunctionAttributeHolderTest.h b/Framework/API/test/ParamFunctionAttributeHolderTest.h index a4b04297de3b5bb4ea33cbf8366e373051517186..10cbbf6b9ded7ff8dab6a1650e6de61ebd393537 100644 --- a/Framework/API/test/ParamFunctionAttributeHolderTest.h +++ b/Framework/API/test/ParamFunctionAttributeHolderTest.h @@ -87,4 +87,13 @@ public: TS_ASSERT_EQUALS(attrNames.at(1), "Att2"); TS_ASSERT_EQUALS(attrNames.at(2), "Att3"); } + + void test_setError_with_name() { + FakeParamFunctionAttributeHolder funct; + funct.initialize(); + funct.setError(0, 1.0); + TS_ASSERT_EQUALS(funct.getError(0), 1.0); + funct.setError("Par1", 5.0); + TS_ASSERT_EQUALS(funct.getError("Par1"), 5.0); + } }; diff --git a/Framework/API/test/RunTest.h b/Framework/API/test/RunTest.h index 44a65ab9cc6a4a7aac4f97161c09c35f5b792a76..1b9b4fe0f656c20d9e5d3c186d976a2e22c5349a 100644 --- a/Framework/API/test/RunTest.h +++ b/Framework/API/test/RunTest.h @@ -427,6 +427,48 @@ public: TS_ASSERT_EQUALS(runCopy.getGoniometer().getNumberAxes(), 3); } + void test_multiple_goniometers() { + Run runInfo; + TS_ASSERT_EQUALS(runInfo.getNumGoniometers(), 1); + + DblMatrix rotation(3, 3, true); + rotation[0][0] = cos(0.5); + rotation[0][2] = sin(0.5); + rotation[2][0] = -sin(0.5); + rotation[2][2] = cos(0.5); + Goniometer goniometer(rotation); + + auto index = runInfo.addGoniometer(goniometer); + + TS_ASSERT_EQUALS(runInfo.getNumGoniometers(), 2); + TS_ASSERT_EQUALS(index, 1); + + TS_ASSERT_EQUALS(runInfo.getGoniometer(0), Goniometer()); + TS_ASSERT_EQUALS(runInfo.getGoniometer(1), goniometer); + TS_ASSERT_EQUALS(runInfo.getGoniometerMatrix(0), DblMatrix(3, 3, true)); + TS_ASSERT_EQUALS(runInfo.getGoniometerMatrix(1), rotation); + + std::vector matrices = runInfo.getGoniometerMatrices(); + TS_ASSERT_EQUALS(matrices.size(), 2); + TS_ASSERT_EQUALS(matrices[0], DblMatrix(3, 3, true)); + TS_ASSERT_EQUALS(matrices[1], rotation); + + Run runInfo2; + TS_ASSERT_DIFFERS(runInfo, runInfo2); + runInfo2.addGoniometer(Goniometer()); + TS_ASSERT_DIFFERS(runInfo, runInfo2); + runInfo2.clearGoniometers(); + runInfo2.addGoniometer(Goniometer()); + runInfo2.addGoniometer(goniometer); + TS_ASSERT_EQUALS(runInfo, runInfo2); + + runInfo.clearGoniometers(); + TS_ASSERT_EQUALS(runInfo.getNumGoniometers(), 0); + + TS_ASSERT_THROWS(runInfo.getGoniometer(0), const std::out_of_range &); + TS_ASSERT_THROWS(runInfo.getGoniometerMatrix(0), const std::out_of_range &); + } + void addTimeSeriesEntry(Run &runInfo, const std::string &name, double val) { TimeSeriesProperty *tsp; tsp = new TimeSeriesProperty(name); @@ -572,6 +614,33 @@ public: TS_ASSERT(run3.hasProperty("double_val")); } + /** Save and load to NXS file */ + void test_nexus_multiple_goniometer() { + NexusTestHelper th(true); + th.createFile("RunTest2.nxs"); + + Run run1; + addTimeSeriesEntry(run1, "phi", 12.3); + addTimeSeriesEntry(run1, "chi", 45.6); + addTimeSeriesEntry(run1, "omega", 78.9); + addTimeSeriesEntry(run1, "proton_charge", 78.9); + + Goniometer gm; + gm.makeUniversalGoniometer(); + run1.setGoniometer(gm, true); + run1.addGoniometer(run1.getGoniometer(0)); + run1.addGoniometer(Goniometer()); + + run1.saveNexus(th.file.get(), "logs"); + th.file->openGroup("logs", "NXgroup"); + + // ---- Now re-load the same and compare ------ + th.reopenFile(); + Run run2; + run2.loadNexus(th.file.get(), "logs"); + TS_ASSERT_EQUALS(run2, run1); + } + void test_setGoniometerWithLogsUsesTimeSeriesAverage() { Run run; @@ -595,6 +664,53 @@ public: TS_ASSERT_EQUALS(gm.getAxis(2).angle, 45.6); } + void test_setGoniometersWithLogsUsesTimeSeries() { + Run run; + + auto omega = std::make_unique>("omega"); + omega->addValue("2018-01-01T00:00:00", 0.0); + omega->addValue("2018-01-01T01:00:00", 90.0); + omega->addValue("2018-01-01T02:00:00", 0.0); + run.addProperty(omega.release()); + + auto chi = std::make_unique>("chi"); + chi->addValue("2018-01-01T00:00:00", 0.0); + chi->addValue("2018-01-01T01:00:00", 0.0); + chi->addValue("2018-01-01T02:00:00", 0.0); + run.addProperty(chi.release()); + + addTimeSeriesEntry(run, "phi", 0.0); + + Goniometer gm; + gm.makeUniversalGoniometer(); + TS_ASSERT_THROWS(run.setGoniometers(gm), const std::runtime_error &); + + auto phi = std::make_unique>("phi"); + phi->addValue("2018-01-01T00:00:00", 0.0); + phi->addValue("2018-01-01T01:00:00", 0.0); + phi->addValue("2018-01-01T02:00:00", 90.0); + run.addProperty(phi.release(), true); + + run.setGoniometers(gm); + + TS_ASSERT_EQUALS(run.getNumGoniometers(), 3); + + gm = run.getGoniometer(0); + TS_ASSERT_DELTA(gm.getAxis(0).angle, 0.0, 1e-7); + TS_ASSERT_DELTA(gm.getAxis(1).angle, 0.0, 1e-7); + TS_ASSERT_DELTA(gm.getAxis(2).angle, 0.0, 1e-7); + + gm = run.getGoniometer(1); + TS_ASSERT_DELTA(gm.getAxis(0).angle, 90.0, 1e-7); + TS_ASSERT_DELTA(gm.getAxis(1).angle, 0.0, 1e-7); + TS_ASSERT_DELTA(gm.getAxis(2).angle, 0.0, 1e-7); + + gm = run.getGoniometer(2); + TS_ASSERT_DELTA(gm.getAxis(0).angle, 0.0, 1e-7); + TS_ASSERT_DELTA(gm.getAxis(1).angle, 0.0, 1e-7); + TS_ASSERT_DELTA(gm.getAxis(2).angle, 90.0, 1e-7); + } + /** Check for loading the old way of saving proton_charge */ void test_legacy_nexus() { NexusTestHelper th(true); diff --git a/Framework/API/test/TextAxisTest.h b/Framework/API/test/TextAxisTest.h index dcf87120c6824cbbeb2e1c2627eae9ed7d9f5bd5..2693b5c9f0920a6b3b9e8839bb345d9315a4afce 100644 --- a/Framework/API/test/TextAxisTest.h +++ b/Framework/API/test/TextAxisTest.h @@ -132,6 +132,7 @@ public: void test_indexOfValue_Returns_Input_As_Index() { TextAxis ta(2); TS_ASSERT_EQUALS(static_cast(1.5), ta.indexOfValue(1.5)); - TS_ASSERT_EQUALS(static_cast(-1), ta.indexOfValue(-1.5)); + TS_ASSERT_THROWS(ta.indexOfValue(-1.5), const std::out_of_range &); + TS_ASSERT_THROWS(ta.indexOfValue(5), const std::out_of_range &); } }; diff --git a/Framework/Algorithms/CMakeLists.txt b/Framework/Algorithms/CMakeLists.txt index 65057fcabeadd94d8050d8bcbfd5e70ec453013a..a91c33e8e6fbec1cb28011bf786e0d203f2e0dd3 100644 --- a/Framework/Algorithms/CMakeLists.txt +++ b/Framework/Algorithms/CMakeLists.txt @@ -72,6 +72,8 @@ set(SRC_FILES src/CopyLogs.cpp src/CopySample.cpp src/CorelliCrossCorrelate.cpp + src/CorelliCalibrationApply.cpp + src/CorelliCalibrationDatabase.cpp src/CorrectKiKf.cpp src/CorrectTOFAxis.cpp src/CorrectToFile.cpp @@ -92,6 +94,7 @@ set(SRC_FILES src/CreateWorkspace.cpp src/CropToComponent.cpp src/CropWorkspace.cpp + src/CropWorkspaceRagged.cpp src/CrossCorrelate.cpp src/CuboidGaugeVolumeAbsorption.cpp src/CylinderAbsorption.cpp @@ -324,7 +327,8 @@ set(SRC_FILES src/WienerSmooth.cpp src/WorkflowAlgorithmRunner.cpp src/WorkspaceJoiners.cpp - src/XDataConverter.cpp) + src/XDataConverter.cpp + src/XrayAbsorptionCorrection.cpp) set(INC_FILES inc/MantidAlgorithms/AbsorptionCorrection.h @@ -400,6 +404,8 @@ set(INC_FILES inc/MantidAlgorithms/CopyLogs.h inc/MantidAlgorithms/CopySample.h inc/MantidAlgorithms/CorelliCrossCorrelate.h + inc/MantidAlgorithms/CorelliCalibrationApply.h + inc/MantidAlgorithms/CorelliCalibrationDatabase.h inc/MantidAlgorithms/CorrectKiKf.h inc/MantidAlgorithms/CorrectTOFAxis.h inc/MantidAlgorithms/CorrectToFile.h @@ -420,6 +426,7 @@ set(INC_FILES inc/MantidAlgorithms/CreateWorkspace.h inc/MantidAlgorithms/CropToComponent.h inc/MantidAlgorithms/CropWorkspace.h + inc/MantidAlgorithms/CropWorkspaceRagged.h inc/MantidAlgorithms/CrossCorrelate.h inc/MantidAlgorithms/CuboidGaugeVolumeAbsorption.h inc/MantidAlgorithms/CylinderAbsorption.h @@ -660,7 +667,8 @@ set(INC_FILES inc/MantidAlgorithms/WienerSmooth.h inc/MantidAlgorithms/WorkflowAlgorithmRunner.h inc/MantidAlgorithms/WorkspaceJoiners.h - inc/MantidAlgorithms/XDataConverter.h) + inc/MantidAlgorithms/XDataConverter.h + inc/MantidAlgorithms/XrayAbsorptionCorrection.h) set(SRC_UNITY_IGNORE_FILES src/AlignDetectors.cpp @@ -748,6 +756,8 @@ set(TEST_FILES CopyLogsTest.h CopySampleTest.h CorelliCrossCorrelateTest.h + CorelliCalibrationApplyTest.h + CorelliCalibrationDatabaseTest.h CorrectKiKfTest.h CorrectTOFAxisTest.h CorrectToFileTest.h @@ -767,6 +777,7 @@ set(TEST_FILES CreateWorkspaceTest.h CropToComponentTest.h CropWorkspaceTest.h + CropWorkspaceRaggedTest.h CrossCorrelateTest.h CuboidGaugeVolumeAbsorptionTest.h CylinderAbsorptionTest.h @@ -983,7 +994,8 @@ set(TEST_FILES WienerSmoothTest.h WorkflowAlgorithmRunnerTest.h WorkspaceCreationHelperTest.h - WorkspaceGroupTest.h) + WorkspaceGroupTest.h + XrayAbsorptionCorrectionTest.h) set(TEST_PY_FILES test/NormaliseToUnityTest.py) diff --git a/Framework/Algorithms/inc/MantidAlgorithms/AddAbsorptionWeightedPathLengths.h b/Framework/Algorithms/inc/MantidAlgorithms/AddAbsorptionWeightedPathLengths.h index c09f0a60817555f7005a3af12f5b3ec1d5b3265e..ce81bbb2b7bd1ffb22766bdaa9d90af32cd0be61 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/AddAbsorptionWeightedPathLengths.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/AddAbsorptionWeightedPathLengths.h @@ -41,7 +41,7 @@ public: /// Algorithm's version for identification int version() const override { return 1; }; const std::vector seeAlso() const override { - return {"MonteCarloAbsorption, SetSample, SaveReflections"}; + return {"MonteCarloAbsorption", "SetSample", "SaveReflections"}; } /// Algorithm's category for identification const std::string category() const override { return "Crystal\\Peaks"; } diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CalculateEfficiency2.h b/Framework/Algorithms/inc/MantidAlgorithms/CalculateEfficiency2.h index c93dbcf677c3a8d055ed9521e4273b019e289a80..7ceefca00bb14fb8a6d1a511105f9c0746e87bd1 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/CalculateEfficiency2.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/CalculateEfficiency2.h @@ -11,6 +11,7 @@ //---------------------------------------------------------------------- #include "MantidAPI/Algorithm.h" #include "MantidAPI/MatrixWorkspace_fwd.h" +#include "MantidAPI/WorkspaceGroup_fwd.h" #include "MantidAlgorithms/DllConfig.h" namespace { @@ -77,6 +78,11 @@ private: void init() override; std::map validateInputs() override; void exec() override; + bool processGroups() override; + + API::MatrixWorkspace_sptr calculateEfficiency(API::MatrixWorkspace_sptr, + double startProgress = 0.0, + double stepProgress = 1.0); /// Sum all detectors, excluding monitors and masked detectors SummedResults sumUnmaskedAndDeadPixels(const API::MatrixWorkspace &workspace); @@ -84,10 +90,13 @@ private: void averageAndNormalizePixels(API::MatrixWorkspace &workspace, const SummedResults &results); + API::MatrixWorkspace_sptr mergeGroup(API::WorkspaceGroup &); + void validateGroupInput(); + /// Minimum efficiency. Pixels with lower efficiency will be masked double m_minThreshold{0.}; /// Maximum efficiency. Pixels with higher efficiency will be masked - double m_maxThreshold{0.}; + double m_maxThreshold{2.}; }; } // namespace Algorithms diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CorelliCalibrationApply.h b/Framework/Algorithms/inc/MantidAlgorithms/CorelliCalibrationApply.h new file mode 100644 index 0000000000000000000000000000000000000000..8c3611f3e9d6b05aee5a90eee24b5d5b87bab451 --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/CorelliCalibrationApply.h @@ -0,0 +1,65 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2014 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/ITableWorkspace.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAlgorithms/CorelliCalibrationDatabase.h" +#include "MantidAlgorithms/DllConfig.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidDataObjects/TableWorkspace.h" + +namespace Mantid { +namespace Algorithms { +/** + * @brief Apply calibration table for Corelli Powder Diffraction + * + */ +class MANTID_ALGORITHMS_DLL CorelliCalibrationApply : public API::Algorithm { +public: + /// Algorithm's name for identification + const std::string name() const override { return "CorelliCalibrationApply"; }; + + /// Summary of algorithm's purpose + const std::string summary() const override { + return "Apply Corelli calibration results onto input workspace"; + }; + + /// Algorithm's version, overriding a virtual method + int version() const override { return 1; }; + + /// Algorithm's category, overriding a virtual method + const std::string category() const override { + return "Diffraction\\Calibration"; + }; + + /// Extra help info + const std::vector seeAlso() const override { + return {"CorelliPowderCalibrationGenerate", "CorelliCalibrationDatabase", + "CorelliPowderCalibrationLoad"}; + }; + +private: + /// Overwrites Algorithm method. Does nothing at present + void init() override; + + /// Overwrites Algorithm method + void exec() override; + + /// Private validator for inputs + std::map validateInputs() override; + + /// Members + API::MatrixWorkspace_sptr ws; + DataObjects::TableWorkspace_sptr calTable; +}; + +} // namespace Algorithms + +} // namespace Mantid diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CorelliCalibrationDatabase.h b/Framework/Algorithms/inc/MantidAlgorithms/CorelliCalibrationDatabase.h new file mode 100644 index 0000000000000000000000000000000000000000..f6224a688913b3569362ef3bedcc99da03027799 --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/CorelliCalibrationDatabase.h @@ -0,0 +1,198 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2014 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAlgorithms/DllConfig.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidDataObjects/TableWorkspace.h" + +namespace Mantid { +namespace Algorithms { + +namespace CorelliCalibration { +static const std::vector calibrationTableColumnNames{ + "ComponentName", "Xposition", "Yposition", + "Zposition", "XdirectionCosine", "YdirectionCosine", + "ZdirectionCosine", "RotationAngle"}; +static const std::vector calibrationTableColumnTypes{ + "str", "double", "double", "double", + "double", "double", "double", "double"}; + +/// Structure to handle all the calibration component positions +struct ComponentPosition { + double x; + double y; + double z; + double xCosine; + double yCosine; + double zCosine; + double rotAngle; + + bool equalTo(const ComponentPosition &otherpos, double abstol) { + if (fabs(x - otherpos.x) >= abstol || fabs(y - otherpos.y) >= abstol || + fabs(z - otherpos.z) >= abstol || + fabs(xCosine - otherpos.xCosine) >= abstol || + fabs(yCosine - otherpos.yCosine) >= abstol || + fabs(zCosine - otherpos.zCosine) >= abstol || + fabs(rotAngle - otherpos.rotAngle) >= abstol) { + return false; + } + return true; + } +}; + +/// Class containing static and member methods to work with calibration table +/// workspaces +class MANTID_ALGORITHMS_DLL CalibrationTableHandler { +public: + /// Load a single-component calibration table + static DataObjects::TableWorkspace_sptr + loadComponentCalibrationTable(const std::string &filename, + const std::string &tablewsname); + + /// Check whether a TableWorkspace is a valid Corelli geometry calibration + /// table for all components + static bool + isValidCalibrationTableWorkspace(DataObjects::TableWorkspace_sptr calibws, + std::string &errormsg); + + /// Create a calibration TableWorkspace from scratch for either single + /// component or full set of components + static DataObjects::TableWorkspace_sptr + createCalibrationTableWorkspace(const std::string &wsname, bool iscomponent); + + /// Get the calibration of a component + ComponentPosition + getComponentCalibratedPosition(const std::string &component); + + /// Append a new row to single component calibration table + static void appendCalibration(DataObjects::TableWorkspace_sptr tablews, + const std::string &datestamp, + ComponentPosition &pos); + + /// Get the last entry (latest update) of a compoent calibrated position + static ComponentPosition getLatestCalibratedPosition( + DataObjects::TableWorkspace_sptr componentcaltable); + + /// Get the calibration position in the table (component table or full + /// calibration table) + static ComponentPosition + getCalibratedPosition(DataObjects::TableWorkspace_sptr componentcaltable, + size_t rownumber); + + /// Constructor + CalibrationTableHandler(); + + /// Set calibration table file + void setCalibrationTable(DataObjects::TableWorkspace_sptr calibws); + + /// Get component name from the table + std::vector getComponentNames(); + /// Get calibration workspace + DataObjects::TableWorkspace_sptr getCalibrationWorkspace() { + return mCalibWS; + } + + /// Save a single component in the calibration workspace + DataObjects::TableWorkspace_sptr + saveCompomentDatabase(const std::string &datestamp, + const std::string &component, + const std::string &filename); + /// Save the calibration table (of a single date) + void saveCalibrationTable(const std::string &filename); + +private: + DataObjects::TableWorkspace_sptr mCalibWS; + bool isSingleComponentTable; +}; + +} // namespace CorelliCalibration + +/** CorelliCalibrationDatabase: blablabla TODO + */ +class MANTID_ALGORITHMS_DLL CorelliCalibrationDatabase : public API::Algorithm { +public: + const std::string name() const override { + return "CorelliCalibrationDatabase"; + }; + int version() const override { return 1; }; + const std::string category() const override { + return "Diffraction\\Calibration"; + }; + + /// Extra help info + const std::vector seeAlso() const override { + return {"CorelliPowderCalibrationCreate", "CorelliPowderCalibrationLoad", + "CorelliCalibrationApply"}; + }; + + const std::string summary() const override { + return "Save calibrated components' positions and orientations to " + "database."; + }; + + /// get standard component calibration database (CSV) file name + static inline std::string + corelliComponentDatabaseName(const std::string &componentname, + const std::string &directory); + /// get standard date-base calibration database (CSV) file name + static inline std::string + corelliCalibrationDatabaseName(const std::string &datestamp, + const std::string &directory); + + static std::string convertTimeStamp(std::string run_start_time); + + /// Check whether a given file does exist + static inline bool isFileExist(const std::string &filepath); + + /// Join two string for a new path + static inline std::string joinPath(const std::string directory, + const std::string basename); + + /// Retrieve the bank level components names in order + static std::vector + retrieveInstrumentComponents(API::MatrixWorkspace_sptr ws); + +private: + std::map validateInputs() override; + void init() override; + void exec() override; + + /// append the newly calibration to each component csv file + void updateComponentDatabaseFiles( + const std::string &calibdbdir, + std::map &calibwsmap); + /// Load data file if necessary and possible: component_caibws_map + void loadNonCalibratedComponentDatabase( + const std::string &calibddir, + std::map &calibwsmap); + /// Create output full set calibration workspace + void createOutputCalibrationTable( + std::map &calibwsmap, + std::vector orderedcomponents); + // Create the summary CSV file + void saveCalibrationTable(const std::string &calibdbdir); + /// Set up a component name - TableWorkspace map for single component + /// calibration + void setComponentMap( + std::vector componentnames, + std::map &compmap); + + /// Input workspace where the calibration is from + API::MatrixWorkspace_sptr mInputWS; + /// Input calibration worksapce + DataObjects::TableWorkspace_sptr mInputCalibrationTableWS; + /// Output calibration worksapce (merged with previous calibrated data) + DataObjects::TableWorkspace_sptr mOutputWS; + /// Date stamp: YYYYMMDD + std::string mDateStamp; +}; + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CreateGroupingWorkspace.h b/Framework/Algorithms/inc/MantidAlgorithms/CreateGroupingWorkspace.h index 9807639354c56d9f42d8552b349edb55690a459f..198bbe6b1aa4ed22089840d5bcbeb841284a4b6d 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/CreateGroupingWorkspace.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/CreateGroupingWorkspace.h @@ -8,6 +8,7 @@ #include "MantidAPI/Algorithm.h" #include "MantidAlgorithms/DllConfig.h" +#include "MantidGeometry/Instrument.h" namespace Mantid { namespace Algorithms { @@ -45,6 +46,8 @@ private: std::map validateInputs() override; /// Run the algorithm void exec() override; + + Mantid::Geometry::Instrument_const_sptr getInstrument(); }; } // namespace Algorithms diff --git a/Framework/Algorithms/inc/MantidAlgorithms/CropWorkspaceRagged.h b/Framework/Algorithms/inc/MantidAlgorithms/CropWorkspaceRagged.h new file mode 100644 index 0000000000000000000000000000000000000000..d3c29ed202a62ad52c0cfbc644113cef7ee9818b --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/CropWorkspaceRagged.h @@ -0,0 +1,56 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2008 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/DistributedAlgorithm.h" +#include "MantidAlgorithms/DllConfig.h" + +namespace Mantid { +namespace Algorithms { +/** Extracts a 'block' from a workspace and places it in a new workspace. + (Or, to look at it another way, lops bins or spectra off a workspace). + + Required Properties: +
    +
  • InputWorkspace - The name of the Workspace2D to take as input.
  • +
  • OutputWorkspace - The name under which to store the output workspace. +
  • XMin - The X values to start the cropping the spectra at
  • +
  • XMax - The X values to end the cropping the spectra at
  • +
+ +*/ +class MANTID_ALGORITHMS_DLL CropWorkspaceRagged + : public API::DistributedAlgorithm { +public: + /// Algorithm's name + const std::string name() const override { return "CropWorkspaceRagged"; } + /// Summary of algorithms purpose + const std::string summary() const override { + return "Crops each spectrum of a workspace and produces a new " + "workspace."; + } + + /// Algorithm's version + int version() const override { return (1); } + const std::vector seeAlso() const override { + return {"CropWorkspace"}; + } + /// Algorithm's category for identification + const std::string category() const override { + return "Transforms\\Splitting"; + } + +private: + /// Initialisation code + void init() override; + /// Execution code + void exec() override; + std::map validateInputs() override; +}; + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/inc/MantidAlgorithms/FitPeaks.h b/Framework/Algorithms/inc/MantidAlgorithms/FitPeaks.h index c8b17dedc919e21bd73f29242b3b9ebd59eed44f..e0fbfc132b04989d70062028a35311e7b2132d23 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/FitPeaks.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/FitPeaks.h @@ -122,7 +122,8 @@ private: /// fit peaks in a same spectrum void fitSpectrumPeaks( size_t wi, const std::vector &expected_peak_centers, - const std::shared_ptr &fit_result); + const std::shared_ptr &fit_result, + std::vector> &lastGoodPeakParameters); /// fit background bool fitBackground(const size_t &ws_index, @@ -134,7 +135,7 @@ private: double fitIndividualPeak(size_t wi, const API::IAlgorithm_sptr &fitter, const double expected_peak_center, const std::pair &fitwindow, - const bool observe_peak_params, + const bool estimate_peak_width, const API::IPeakFunction_sptr &peakfunction, const API::IBackgroundFunction_sptr &bkgdfunc); @@ -145,7 +146,7 @@ private: const API::MatrixWorkspace_sptr &dataws, size_t wsindex, double xmin, double xmax, const double &expected_peak_center, - bool observe_peak_shape, bool estimate_background); + bool estimate_peak_width, bool estimate_background); double fitFunctionMD(API::IFunction_sptr fit_function, const API::MatrixWorkspace_sptr &dataws, size_t wsindex, @@ -206,12 +207,12 @@ private: size_t &peak_center_index, double &peak_height); /// Observe peak width - double observePeakWidth(const HistogramData::Histogram &histogram, - API::FunctionValues &bkgd_values, size_t ipeak, - size_t istart, size_t istop); + double observePeakFwhm(const HistogramData::Histogram &histogram, + API::FunctionValues &bkgd_values, size_t ipeak, + size_t istart, size_t istop); /// Process the result from fitting a single peak - void processSinglePeakFitResult( + bool processSinglePeakFitResult( size_t wsindex, size_t peakindex, const double cost, const std::vector &expected_peak_positions, const FitPeaksAlgorithm::FitFunction &fitfunction, diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h b/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h index aa46946a5183c933090ac4b8875ef41a146e7a8f..e4affc14c7eab18ad1b6a29457044ab7041dc17b 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/PDCalibration.h @@ -44,7 +44,8 @@ private: void createCalTableFromExisting(); void createCalTableNew(); void createInformationWorkspaces(); - std::function getDSpacingToTof(const detid_t detid); + std::function + getDSpacingToTof(const std::set &detIds); std::vector dSpacingWindows(const std::vector ¢res, const double widthMax); std::vector getTOFminmax(const double difc, const double difa, @@ -73,8 +74,8 @@ private: API::ITableWorkspace_sptr m_peakHeightTable{nullptr}; std::vector m_peaksInDspacing; std::map m_detidToRow; - double m_tofMin{0.}; - double m_tofMax{0.}; + double m_tofMin{0.}; // first bin boundary when rebinning in TOF (user input) + double m_tofMax{0.}; // last bin boundary when rebinning in TOF (user input) double m_tzeroMin{0.}; double m_tzeroMax{0.}; double m_difaMin{0.}; diff --git a/Framework/Algorithms/inc/MantidAlgorithms/PrecompiledHeader.h b/Framework/Algorithms/inc/MantidAlgorithms/PrecompiledHeader.h index 34dfc059e702fd003fa8b448e7fed9012b3285f1..553eded3f0c1df7a6890b95735f519d0e67dc429 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/PrecompiledHeader.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/PrecompiledHeader.h @@ -7,8 +7,15 @@ #pragma once #include "MantidAPI/Algorithm.h" +#include "MantidAPI/MatrixWorkspace.h" #include "MantidAlgorithms/DllConfig.h" -#include "MantidKernel/Exception.h" + +// STL +// No speed up from including STL headers for some reason // Boost -#include +#include +#include +#include +#include +#include diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h index 15d460cd505306fb387eaa2f21e0f9afd656fbb4..9e090711f2f0f9ed5a2c000c05bb5f5a4871a0ee 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/IMCInteractionVolume.h @@ -27,6 +27,9 @@ struct ComponentScatterPoint { Kernel::V3D scatterPoint; }; +using TrackPair = std::tuple, + std::shared_ptr>; + /** Defines a base class for objects describing a volume where interactions of Tracks and Objects can take place. @@ -34,14 +37,9 @@ struct ComponentScatterPoint { class MANTID_ALGORITHMS_DLL IMCInteractionVolume { public: virtual ~IMCInteractionVolume() = default; - virtual bool calculateBeforeAfterTrack( + virtual TrackPair calculateBeforeAfterTrack( Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos, - const Kernel::V3D &endPos, Geometry::Track &beforeScatter, - Geometry::Track &afterScatter, MCInteractionStatistics &stats) const = 0; - virtual double calculateAbsorption(const Geometry::Track &beforeScatter, - const Geometry::Track &afterScatter, - double lambdaBefore, - double lambdaAfter) const = 0; + const Kernel::V3D &endPos, MCInteractionStatistics &stats) const = 0; virtual const Geometry::BoundingBox &getBoundingBox() const = 0; virtual const Geometry::BoundingBox getFullBoundingBox() const = 0; virtual void setActiveRegion(const Geometry::BoundingBox ®ion) = 0; diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h index b55a589082fec14b3dabe2402c80fb43ce73bd2c..b479cf6249b9d81a0d751fcd38aa558fe7f4a93e 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SampleCorrections/MCInteractionVolume.h @@ -39,15 +39,9 @@ public: const Geometry::BoundingBox &getBoundingBox() const override; const Geometry::BoundingBox getFullBoundingBox() const override; - virtual bool calculateBeforeAfterTrack( + virtual TrackPair calculateBeforeAfterTrack( Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos, - const Kernel::V3D &endPos, Geometry::Track &beforeScatter, - Geometry::Track &afterScatter, - MCInteractionStatistics &stats) const override; - virtual double calculateAbsorption(const Geometry::Track &beforeScatter, - const Geometry::Track &afterScatter, - double lambdaBefore, - double lambdaAfter) const override; + const Kernel::V3D &endPos, MCInteractionStatistics &stats) const override; ComponentScatterPoint generatePoint(Kernel::PseudoRandomNumberGenerator &rng) const; void setActiveRegion(const Geometry::BoundingBox ®ion) override; diff --git a/Framework/Algorithms/inc/MantidAlgorithms/XrayAbsorptionCorrection.h b/Framework/Algorithms/inc/MantidAlgorithms/XrayAbsorptionCorrection.h new file mode 100644 index 0000000000000000000000000000000000000000..fbf9c4a1e56608a994e9628e8b29371b12477d9b --- /dev/null +++ b/Framework/Algorithms/inc/MantidAlgorithms/XrayAbsorptionCorrection.h @@ -0,0 +1,71 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +//------------------------------------------------------------------------------ +// Includes +//------------------------------------------------------------------------------ +#include "MantidAPI/Algorithm.h" +#include "MantidAlgorithms/InterpolationOption.h" +#include "MantidAlgorithms/SampleCorrections/IBeamProfile.h" +#include "MantidAlgorithms/SampleCorrections/MCAbsorptionStrategy.h" +#include "MantidAlgorithms/SampleCorrections/MCInteractionVolume.h" +#include "MantidAlgorithms/SampleCorrections/SparseWorkspace.h" + +namespace Mantid { +namespace API { +class Sample; +} +namespace Geometry { +class Instrument; +} + +namespace Algorithms { +class DetectorGridDefinition; +class MCInteractionVolume; + +/** + *Calculates attenuation of xrays due to absorption in a sample. + */ +class MANTID_ALGORITHMS_DLL XrayAbsorptionCorrection : public API::Algorithm { +public: + /// Algorithm's name + const std::string name() const override { return "XrayAbsorptionCorrection"; } + /// Algorithm's version + int version() const override { return 1; } + /// Algorithm's category for identification + const std::string category() const override { + return "CorrectionFunctions\\AbsorptionCorrections"; + } + /// Summary of algorithms purpose + const std::string summary() const override { + return "Calculates attenuation of xrays produced by negative muons due" + " to absorption in a sample for elemental analysis "; + } + const std::vector seeAlso() const override { + return {"SetSample", "SetSampleMaterial"}; + } + +protected: + double degreesToRadians(double degrees); + + Kernel::V3D calculateDetectorPos(double const detectorAngle, + double detectorDistance); + + std::vector + calculateMuonPos(API::MatrixWorkspace_sptr &muonProfile, + API::MatrixWorkspace_sptr inputWS, double detectorDistance); + + std::vector normaliseMuonIntensity(MantidVec muonIntensity); + +private: + void init() override; + void exec() override; + std::map validateInputs() override; +}; +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp b/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp index e8275f9b557e53a2a91c6c09c1f5012785199da3..38cb0dc6276dab8096fe543b696cd42b59a60f91 100644 --- a/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp +++ b/Framework/Algorithms/src/AddAbsorptionWeightedPathLengths.cpp @@ -18,6 +18,7 @@ #include "MantidGeometry/Instrument/ReferenceFrame.h" #include "MantidGeometry/Instrument/SampleEnvironment.h" #include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/EnabledWhenProperty.h" #include "MantidKernel/Material.h" #include "MantidKernel/MersenneTwister.h" @@ -51,6 +52,9 @@ void AddAbsorptionWeightedPathLengths::init() { "InputWorkspace", "", Direction::InOut), "An input/output peaks workspace that the path distances will be added " "to."); + declareProperty( + "UseSinglePath", false, + "Use a single path with a scatter point at the sample position"); auto positiveInt = std::make_shared>(); positiveInt->setLower(1); declareProperty("EventsPerPoint", DEFAULT_NEVENTS, positiveInt, @@ -65,6 +69,15 @@ void AddAbsorptionWeightedPathLengths::init() { "If a scattering point cannot be generated by increasing " "this value then there is most likely a problem with " "the sample geometry."); + setPropertySettings("SeedValue", + std::make_unique( + "UseSinglePath", ePropertyCriterion::IS_DEFAULT)); + setPropertySettings("EventsPerPoint", + std::make_unique( + "UseSinglePath", ePropertyCriterion::IS_DEFAULT)); + setPropertySettings("MaxScatterPtAttempts", + std::make_unique( + "UseSinglePath", ePropertyCriterion::IS_DEFAULT)); } std::map @@ -114,23 +127,46 @@ void AddAbsorptionWeightedPathLengths::exec() { maxScatterPtAttempts, true); const int seed = getProperty("SeedValue"); - MersenneTwister rng(seed); + PARALLEL_FOR_IF(Kernel::threadSafe(*inputWS)) for (int i = 0; i < npeaks; ++i) { + PARALLEL_START_INTERUPT_REGION IPeak &peak = inputWS->getPeak(i); auto peakWavelength = peak.getWavelength(); std::vector lambdas{peakWavelength}, absFactors(NLAMBDA), absFactorErrors(NLAMBDA); - MCInteractionStatistics detStatistics(peak.getDetectorID(), - inputWS->sample()); + bool useSinglePath = getProperty("UseSinglePath"); + if (useSinglePath) { + auto inst = inputWS->getInstrument(); + const auto sourcePos = inst->getSource()->getPos(); + const auto samplePos = inst->getSample()->getPos(); + const auto reverseBeamDir = normalize(samplePos - sourcePos); + + const IObject *sampleShape = &(inputWS->sample().getShape()); + + Track beforeScatter(samplePos, reverseBeamDir); + sampleShape->interceptSurface(beforeScatter); + const auto detDir = normalize(peak.getDetPos() - samplePos); + Track afterScatter(samplePos, detDir); + sampleShape->interceptSurface(afterScatter); + + absFactors[0] = beforeScatter.calculateAttenuation(lambdas[0]) * + afterScatter.calculateAttenuation(lambdas[0]); + + } else { + MersenneTwister rng(seed + int(i)); + MCInteractionStatistics detStatistics(peak.getDetectorID(), + inputWS->sample()); - strategy.calculate(rng, peak.getDetectorPosition(), lambdas, peakWavelength, - absFactors, absFactorErrors, detStatistics); + strategy.calculate(rng, peak.getDetectorPosition(), lambdas, + peakWavelength, absFactors, absFactorErrors, + detStatistics); - if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG)) { - g_log.debug(detStatistics.generateScatterPointStats()); + if (g_log.is(Kernel::Logger::Priority::PRIO_DEBUG)) { + g_log.debug(detStatistics.generateScatterPointStats()); + } } double mu = inputWS->sample().getMaterial().attenuationCoefficient( @@ -139,7 +175,9 @@ void AddAbsorptionWeightedPathLengths::exec() { peak.setAbsorptionWeightedPathLength(absWeightedPathLength * 100); // cm prog.report(reportMsg); + PARALLEL_END_INTERUPT_REGION } + PARALLEL_CHECK_INTERUPT_REGION } /** diff --git a/Framework/Algorithms/src/CalculateDIFC.cpp b/Framework/Algorithms/src/CalculateDIFC.cpp index c7b71d41c3aaca2f6035028c54f5d7158c9e0382..3e1b904cdf2d2deb1c90e45337abe9de560ce516 100644 --- a/Framework/Algorithms/src/CalculateDIFC.cpp +++ b/Framework/Algorithms/src/CalculateDIFC.cpp @@ -39,7 +39,7 @@ void calculateFromOffset(API::Progress &progress, } } -// look through the columns of detid and difc and put it into the +// look through the columns of detid and difc and copy theminto the // SpecialWorkspace2D void calculateFromTable(API::Progress &progress, DataObjects::SpecialWorkspace2D &outputWs, @@ -100,15 +100,15 @@ void CalculateDIFC::init() { declareProperty(std::make_unique>( "CalibrationWorkspace", "", Direction::Input, Mantid::API::PropertyMode::Optional), - "Optional: A OffsetsWorkspace containing the calibration " - "offsets. This cannot be specified with an " - "OffsetsWorkspace."); + "Optional: A TableWorkspace containing the DIFC values, " + "which will be copied. This property cannot be set in " + "conjunction with property OffsetsWorkspace."); declareProperty(std::make_unique>( "OffsetsWorkspace", "", Direction::Input, Mantid::API::PropertyMode::Optional), "Optional: A OffsetsWorkspace containing the calibration " - "offsets. This cannot be specified with a " - "CalibrationWorkspace."); + "offsets. This property cannot be set in conjunction with " + "property CalibrationWorkspace."); } std::map CalculateDIFC::validateInputs() { @@ -139,6 +139,8 @@ void CalculateDIFC::exec() { API::MatrixWorkspace_sptr outputWs = getProperty("OutputWorkspace"); if ((!bool(inputWs == outputWs)) || + // SpecialWorkspace2D is a Workspace2D where each spectrum + // has one detector pixel, one X-value, and one Y-value. (!bool(std::dynamic_pointer_cast(outputWs)))) { outputWs = std::dynamic_pointer_cast( std::make_shared(inputWs->getInstrument())); @@ -153,7 +155,8 @@ void CalculateDIFC::exec() { if (bool(calibWs)) { calculateFromTable(progress, *outputSpecialWs, *calibWs); } else { - // this method handles calculating from instrument geometry as well + // this method handles calculating from instrument geometry as well, + // and even when OffsetsWorkspace hasn't been set const auto &detectorInfo = inputWs->detectorInfo(); calculateFromOffset(progress, *outputSpecialWs, offsetsWs.get(), detectorInfo); diff --git a/Framework/Algorithms/src/CalculateDynamicRange.cpp b/Framework/Algorithms/src/CalculateDynamicRange.cpp index 6ad9aedc409b5db9dc8f48b16b5b957cff859549..77f28f2e4c75d1794792d237038d2898b71b28bd 100644 --- a/Framework/Algorithms/src/CalculateDynamicRange.cpp +++ b/Framework/Algorithms/src/CalculateDynamicRange.cpp @@ -78,7 +78,7 @@ void CalculateDynamicRange::calculateQMinMax( const std::string &compName = "") { const auto &spectrumInfo = workspace->spectrumInfo(); double min = std::numeric_limits::max(), - max = std::numeric_limits::min(); + max = std::numeric_limits::lowest(); // PARALLEL_FOR_NO_WSP_CHECK does not work with range-based for so NOLINT this // block PARALLEL_FOR_NO_WSP_CHECK() diff --git a/Framework/Algorithms/src/CalculateEfficiency2.cpp b/Framework/Algorithms/src/CalculateEfficiency2.cpp index 30f3461bd970943a16cd3cbd956d95096f4ea9b6..e460d32e18a94fc24b513db2e4e985300f66ef94 100644 --- a/Framework/Algorithms/src/CalculateEfficiency2.cpp +++ b/Framework/Algorithms/src/CalculateEfficiency2.cpp @@ -6,16 +6,8 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidAlgorithms/CalculateEfficiency2.h" #include "MantidAPI/SpectrumInfo.h" -#include "MantidAPI/WorkspaceFactory.h" -#include "MantidDataObjects/EventList.h" +#include "MantidAPI/WorkspaceGroup.h" #include "MantidDataObjects/EventWorkspace.h" -#include "MantidGeometry/ICompAssembly.h" -#include "MantidGeometry/IDTypes.h" -#include "MantidGeometry/IDetector.h" -#include "MantidGeometry/Instrument.h" -#include "MantidGeometry/Instrument/Detector.h" -#include "MantidGeometry/Instrument/RectangularDetector.h" -#include "MantidKernel/ArrayProperty.h" #include "MantidKernel/BoundedValidator.h" #include #include @@ -38,6 +30,7 @@ const static std::string INPUT_WORKSPACE{"InputWorkspace"}; const static std::string OUTPUT_WORKSPACE{"OutputWorkspace"}; const static std::string MIN_THRESHOLD{"MinThreshold"}; const static std::string MAX_THRESHOLD{"MaxThreshold"}; +const static std::string MERGE_GROUP{"MergeGroup"}; } // namespace PropertyNames namespace { // anonymous @@ -76,12 +69,13 @@ static void applyBadPixelThreshold(MatrixWorkspace &outputWS, * */ void CalculateEfficiency2::init() { - declareProperty(std::make_unique>( + declareProperty(std::make_unique>( PropertyNames::INPUT_WORKSPACE, "", Direction::Input), "The workspace containing the flood data"); declareProperty( std::make_unique>(PropertyNames::OUTPUT_WORKSPACE, "", - Direction::Output), + Direction::Output, + PropertyMode::Optional), "The name of the workspace to be created as the output of the algorithm"); auto positiveDouble = std::make_shared>(); @@ -90,17 +84,49 @@ void CalculateEfficiency2::init() { "Minimum threshold for a pixel to be considered"); declareProperty(PropertyNames::MAX_THRESHOLD, 2.0, positiveDouble->clone(), "Maximum threshold for a pixel to be considered"); + + declareProperty( + PropertyNames::MERGE_GROUP, false, + "Whether to merge entries when WorkspaceGroup is specified as input."); } std::map CalculateEfficiency2::validateInputs() { std::map result; + // Files from time-of-flight instruments must be integrated in Lambda before // using this algorithm + + auto oneBinMsg = "Input workspace must have only one bin. Consider " + "integrating the input over all the bins."; + Workspace_const_sptr ws1 = getProperty(PropertyNames::INPUT_WORKSPACE); MatrixWorkspace_const_sptr inputWS = - getProperty(PropertyNames::INPUT_WORKSPACE); - if (inputWS->blocksize() > 1) - result[PropertyNames::INPUT_WORKSPACE] = - "Input workspace must have only one bin"; + std::dynamic_pointer_cast(ws1); + if (inputWS == nullptr) { + WorkspaceGroup_const_sptr inputGroup = + std::dynamic_pointer_cast(ws1); + if (inputGroup != nullptr) { + for (auto entryNo = 0; entryNo < inputGroup->getNumberOfEntries(); + ++entryNo) { + auto const entry = std::static_pointer_cast( + inputGroup->getItem(entryNo)); + if (entry->blocksize() > 1) { + result[PropertyNames::INPUT_WORKSPACE] = oneBinMsg; + break; + } + } + } else { + result[PropertyNames::INPUT_WORKSPACE] = + "The input property must be either MatrixWorkspace or a " + "WorkspaceGroup containing MatrixWorkspaces"; + } + } else if (inputWS->blocksize() > 1) { + result[PropertyNames::INPUT_WORKSPACE] = oneBinMsg; + } + + if (getPropertyValue(PropertyNames::OUTPUT_WORKSPACE) == "") { + result[PropertyNames::OUTPUT_WORKSPACE] = + "The output workspace name must be specified."; + } // get the thresholds once to error check and use in the main function m_minThreshold = getProperty("MinThreshold"); @@ -118,12 +144,22 @@ std::map CalculateEfficiency2::validateInputs() { * */ void CalculateEfficiency2::exec() { - // create the output workspace from the input + Workspace_sptr ws1 = getProperty(PropertyNames::INPUT_WORKSPACE); + auto inputWS = std::static_pointer_cast(ws1); + auto outputWS = calculateEfficiency(inputWS); + setProperty(PropertyNames::OUTPUT_WORKSPACE, outputWS); + progress(1.0, "Done!"); +} + +API::MatrixWorkspace_sptr +CalculateEfficiency2::calculateEfficiency(MatrixWorkspace_sptr inputWorkspace, + double startProgress, + double stepProgress) { + + // create the output workspace from the input, while NOT preserving events auto childAlg = createChildAlgorithm("RebinToWorkspace", 0.0, 0.1); - childAlg->setPropertyValue("WorkspaceToRebin", - getPropertyValue(PropertyNames::INPUT_WORKSPACE)); - childAlg->setPropertyValue("WorkspaceToMatch", - getPropertyValue(PropertyNames::INPUT_WORKSPACE)); + childAlg->setProperty("WorkspaceToRebin", inputWorkspace); + childAlg->setProperty("WorkspaceToMatch", inputWorkspace); childAlg->setPropertyValue("OutputWorkspace", getPropertyValue(PropertyNames::OUTPUT_WORKSPACE)); childAlg->setProperty("PreserveEvents", false); @@ -133,33 +169,84 @@ void CalculateEfficiency2::exec() { // Loop over spectra and sum all the counts to get normalization // Skip monitors and masked detectors // returns tuple with (sum, err, npixels) - progress(0.1, "Computing the counts."); + progress(startProgress + 0.1 * stepProgress, "Computing the counts."); auto counts = sumUnmaskedAndDeadPixels(*outputWS); if (counts.nPixels == 0) { throw std::runtime_error("No pixels being used for calculation"); } - progress(0.3, "Normalising the detectors."); + progress(startProgress + 0.3 * stepProgress, "Normalising the detectors."); averageAndNormalizePixels(*outputWS, counts); - progress(0.5, "Applying bad pixel threshold."); + progress(startProgress + 0.5 * stepProgress, "Applying bad pixel threshold."); applyBadPixelThreshold(*outputWS, m_minThreshold, m_maxThreshold); // do it again only using the pixels that are within the threshold - progress(0.7, "Computing the counts."); + progress(startProgress + 0.7 * stepProgress, "Computing the counts."); counts = sumUnmaskedAndDeadPixels(*outputWS); if (counts.nPixels == 0) { throw std::runtime_error("All pixels are outside of the threshold values"); } - progress(0.9, "Normalising the detectors."); + progress(startProgress + 0.9 * stepProgress, "Normalising the detectors."); averageAndNormalizePixels(*outputWS, counts); - setProperty(PropertyNames::OUTPUT_WORKSPACE, outputWS); + return outputWS; +} + +/** + * Explicitly calls validateInputs and throws runtime error in case + * of issues in the input properties. + * + */ +void CalculateEfficiency2::validateGroupInput() { + auto results = validateInputs(); + for (const auto &result : results) { + throw std::runtime_error("Issue in " + result.first + + " property: " + result.second); + } +} + +/** + * Process groups and merge entries if MergeGroup property is set. + * + * @return A boolean true if execution was sucessful, false otherwise + */ +bool CalculateEfficiency2::processGroups() { + + // if run as a script, validateInputs will not be triggered + // for the processGroups, so properties will be validated manually + validateGroupInput(); + + Workspace_sptr ws1 = getProperty(PropertyNames::INPUT_WORKSPACE); + WorkspaceGroup_sptr inputWS = std::static_pointer_cast(ws1); + + const bool mergeGroups = getProperty(PropertyNames::MERGE_GROUP); + if (mergeGroups) { + auto mergedWS = mergeGroup(*inputWS); + auto outputWS = calculateEfficiency(mergedWS); + setProperty(PropertyNames::OUTPUT_WORKSPACE, outputWS); + } else { + auto outputGroup = std::make_shared(); + auto const nEntries = inputWS->getNumberOfEntries(); + auto const stepProgress = 1.0 / nEntries; + for (auto entryNo = 0; entryNo < nEntries; ++entryNo) { + auto entryWS = std::static_pointer_cast( + inputWS->getItem(entryNo)); + auto const startProgress = static_cast(entryNo) / nEntries; + auto outputWS = calculateEfficiency(entryWS, startProgress, stepProgress); + outputGroup->addWorkspace(outputWS); + } + const std::string groupName = + getPropertyValue(PropertyNames::OUTPUT_WORKSPACE); + AnalysisDataService::Instance().addOrReplace(groupName, outputGroup); + setProperty(PropertyNames::OUTPUT_WORKSPACE, outputGroup); + } progress(1.0, "Done!"); + return true; } -/* +/** * Sum up all the unmasked detector pixels. * * @param workspace: workspace where all the wavelength bins have been grouped @@ -231,5 +318,67 @@ void CalculateEfficiency2::averageAndNormalizePixels( << "; error = " << averageE << "\n"; } +/** + * Merges the input workspace group and tries to remove masked spectra + * and replace the masked value with an average of counts from non-masked + * entries. + * + * @param input: workspace group to be merged + * + * @returns merged workspace group + */ +API::MatrixWorkspace_sptr +CalculateEfficiency2::mergeGroup(API::WorkspaceGroup &input) { + auto nEntries = input.getNumberOfEntries(); + auto mergeRuns = createChildAlgorithm("MergeRuns"); + mergeRuns->setProperty("InputWorkspaces", input.getName()); + mergeRuns->executeAsChildAlg(); + Workspace_sptr mergedWs = mergeRuns->getProperty("OutputWorkspace"); + + auto createSingleAlg = createChildAlgorithm("CreateSingleValuedWorkspace"); + createSingleAlg->setProperty("DataValue", static_cast(nEntries)); + createSingleAlg->executeAsChildAlg(); + MatrixWorkspace_sptr normWs = createSingleAlg->getProperty("OutputWorkspace"); + + auto divideAlg = createChildAlgorithm("Divide"); + divideAlg->setProperty("LHSWorkspace", mergedWs); + divideAlg->setProperty("RHSWorkspace", normWs); + divideAlg->executeAsChildAlg(); + MatrixWorkspace_sptr mergedNormalisedWs = + divideAlg->getProperty("OutputWorkspace"); + + auto spectrumInfo = mergedNormalisedWs->spectrumInfo(); + PARALLEL_FOR_IF(Kernel::threadSafe(*mergedNormalisedWs)) + for (auto spectrumNo = 0; + spectrumNo < static_cast(mergedNormalisedWs->getNumberHistograms()); + ++spectrumNo) { + if (spectrumInfo.isMasked(spectrumNo)) { + auto &detDataY = mergedNormalisedWs->mutableY(spectrumNo); + auto &detDataErr = mergedNormalisedWs->mutableE(spectrumNo); + auto dataY = 0.0; + auto dataE = 0.0; + auto nonMaskedEntries = 0; + for (auto entryNo = 0; entryNo < nEntries; ++entryNo) { + MatrixWorkspace_sptr entry = + std::static_pointer_cast( + input.getItem(entryNo)); + auto spectrumInfoEntry = entry->spectrumInfo(); + if (!spectrumInfoEntry.isMasked(spectrumNo)) { + dataY += entry->readY(spectrumNo)[0]; + dataE += pow(entry->readE(spectrumNo)[0], 2); // propagate errors + nonMaskedEntries++; + } + } + if (nonMaskedEntries != 0) { + + spectrumInfo.setMasked(spectrumNo, false); + detDataY.front() = dataY / nonMaskedEntries; + detDataErr.front() = sqrt(dataE) / nonMaskedEntries; + } + } + } + return mergedNormalisedWs; +} + } // namespace Algorithms } // namespace Mantid diff --git a/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp b/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp index 955496e6e61a7b4e1af55c0ee39ec14ad48f7f2e..3417c43b262daedffa95b5c09e2977d3113e7a34 100644 --- a/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp +++ b/Framework/Algorithms/src/CalculatePlaczekSelfScattering.cpp @@ -31,7 +31,7 @@ getSampleSpeciesInfo(const API::MatrixWorkspace_const_sptr &ws) { const double xSection = ws->sample().getMaterial().totalScatterXSection(); const double bSqrdBar = xSection / (4.0 * M_PI); - for (const Kernel::Material::Material::FormulaUnit element : formula) { + for (const auto &element : formula) { const std::map atomMap{ {"mass", element.atom->mass}, {"stoich", element.multiplicity}, @@ -194,4 +194,4 @@ void CalculatePlaczekSelfScattering::exec() { } } // namespace Algorithms -} // namespace Mantid \ No newline at end of file +} // namespace Mantid diff --git a/Framework/Algorithms/src/CompareWorkspaces.cpp b/Framework/Algorithms/src/CompareWorkspaces.cpp index 3c6db246ddae666f655b7e2a059535732c84d739..8fa8576c7071700aa343fcf29f44b6eb01794766 100644 --- a/Framework/Algorithms/src/CompareWorkspaces.cpp +++ b/Framework/Algorithms/src/CompareWorkspaces.cpp @@ -19,6 +19,7 @@ #include "MantidDataObjects/PeaksWorkspace.h" #include "MantidDataObjects/TableWorkspace.h" #include "MantidGeometry/Crystal/IPeak.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidKernel/Unit.h" #include "MantidParallel/Communicator.h" @@ -827,11 +828,21 @@ bool CompareWorkspaces::checkSpectraMap(const MatrixWorkspace_const_sptr &ws1, } //------------------------------------------------------------------------------------------------ -/// Checks that the instruments match -/// @param ws1 :: the first workspace -/// @param ws2 :: the second workspace -/// @retval true The instruments match -/// @retval false The instruments do not match +/* @brief Checks that the instruments match + * + * @details the following checks are performed: + * - instrument name + * - positions and rotations of detectors + * - mask of detectors + * - position of the source and sample + * - instrument parameters + * + * @param ws1 :: the first workspace + * @param ws2 :: the second workspace + * @retval true The instruments match + * + * @retval false The instruments do not match + */ bool CompareWorkspaces::checkInstrument( const API::MatrixWorkspace_const_sptr &ws1, const API::MatrixWorkspace_const_sptr &ws2) { @@ -850,6 +861,18 @@ bool CompareWorkspaces::checkInstrument( return false; } + if (!ws1->componentInfo().hasEquivalentSource(ws2->componentInfo())) { + recordMismatch("Source mismatch: either one workspace has a source and the " + "other does not, or the sources are at different positions"); + return false; + } + + if (!ws1->componentInfo().hasEquivalentSample(ws2->componentInfo())) { + recordMismatch("Sample mismatch: either one workspace has a sample and the " + "other does not, or the samples are at different positions"); + return false; + } + const Geometry::ParameterMap &ws1_parmap = ws1->constInstrumentParameters(); const Geometry::ParameterMap &ws2_parmap = ws2->constInstrumentParameters(); @@ -867,7 +890,7 @@ bool CompareWorkspaces::checkInstrument( } //------------------------------------------------------------------------------------------------ -/// Checks that the masking matches +/// Checks that the bin masking matches /// @param ws1 :: the first workspace /// @param ws2 :: the second workspace /// @retval true The masking matches diff --git a/Framework/Algorithms/src/ConvertAxesToRealSpace.cpp b/Framework/Algorithms/src/ConvertAxesToRealSpace.cpp index db55a1a3b265b45c64b4f634c2dd764841e9e93b..551bdf3b72b64dd6d702ec35b83edb825236a4e2 100644 --- a/Framework/Algorithms/src/ConvertAxesToRealSpace.cpp +++ b/Framework/Algorithms/src/ConvertAxesToRealSpace.cpp @@ -94,7 +94,7 @@ void ConvertAxesToRealSpace::exec() { axisVector[1].label = vAxis; axisVector[1].bins = getProperty("NumberVerticalBins"); for (int axisIndex = 0; axisIndex < 2; ++axisIndex) { - axisVector[axisIndex].max = std::numeric_limits::min(); + axisVector[axisIndex].max = std::numeric_limits::lowest(); axisVector[axisIndex].min = std::numeric_limits::max(); } diff --git a/Framework/Algorithms/src/CopyDataRange.cpp b/Framework/Algorithms/src/CopyDataRange.cpp index 937157d7afd4107a315d30cfc303d4425e8c5465..246c686995aa27351dd37495a6e31a1736107e09 100644 --- a/Framework/Algorithms/src/CopyDataRange.cpp +++ b/Framework/Algorithms/src/CopyDataRange.cpp @@ -42,10 +42,8 @@ void copyDataRange(const MatrixWorkspace_const_sptr &inputWorkspace, int const &specMin, int const &specMax, double const &xMin, double const &xMax, int yInsertionIndex, int const &xInsertionIndex) { - auto const xMinIndex = - static_cast(inputWorkspace->yIndexOfX(xMin, 0, 0.000001)); - auto const xMaxIndex = - static_cast(inputWorkspace->yIndexOfX(xMax, 0, 0.000001)); + auto const xMinIndex = static_cast(inputWorkspace->yIndexOfX(xMin, 0)); + auto const xMaxIndex = static_cast(inputWorkspace->yIndexOfX(xMax, 0)); copyDataRange(inputWorkspace, std::move(destWorkspace), specMin, specMax, xMinIndex, xMaxIndex, yInsertionIndex, xInsertionIndex); @@ -96,11 +94,15 @@ void CopyDataRange::init() { declareProperty("EndWorkspaceIndex", EMPTY_INT(), positiveInt, "The index denoting the end of the spectra range."); - declareProperty("XMin", EMPTY_DBL(), anyDouble, - "An X value that is within the first (lowest X value) bin"); + declareProperty( + "XMin", EMPTY_DBL(), anyDouble, + "An X value that is equal to the lowest point to copy (point data), or " + "an X value that is within the first bin to copy (histogram data)."); - declareProperty("XMax", EMPTY_DBL(), anyDouble, - "An X value that is in the highest X value bin"); + declareProperty( + "XMax", EMPTY_DBL(), anyDouble, + "An X value that is equal to the highest point to copy (point data), or " + "an X value that is within the last bin to copy (histogram data)."); declareProperty("InsertionYIndex", 0, positiveInt, "The index denoting the histogram position for the start of " diff --git a/Framework/Algorithms/src/CorelliCalibrationApply.cpp b/Framework/Algorithms/src/CorelliCalibrationApply.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e82da6c77ce03e9e7d2641e7e454ce67af79dca9 --- /dev/null +++ b/Framework/Algorithms/src/CorelliCalibrationApply.cpp @@ -0,0 +1,191 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + + +#include "MantidAlgorithms/CorelliCalibrationApply.h" +#include "MantidAPI/FileProperty.h" +#include "MantidAPI/ITableWorkspace.h" +#include "MantidAPI/InstrumentValidator.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/DetectorInfo.h" +#include "MantidGeometry/Objects/IObject.h" +#include "MantidKernel/Logger.h" + +namespace Mantid { +namespace Algorithms { + +using namespace Kernel; +using namespace API; +using namespace DataObjects; +namespace { +Logger logger("CorelliCalibrationApply"); +} + +DECLARE_ALGORITHM(CorelliCalibrationApply) + +/** + * @brief Initialization + * + */ +void CorelliCalibrationApply::init() { + + // InputWorkspace + // [Input, Required, MatrixWorkspace or EventsWorkspace] + // workspace to which the calibration should be applied + auto wsValidator = std::make_shared(); + declareProperty(std::make_unique>( + "Workspace", "", Direction::InOut, + PropertyMode::Mandatory, wsValidator), + "CORELLI workspace to calibrate"); + + // CalibrationTable + // [Input, Mandatory, TableWorkspace] + // workspace resulting from uploading + declareProperty( + std::make_unique>( + "CalibrationTable", "", Direction::Input, PropertyMode::Mandatory), + "TableWorkspace containing calibration table"); +} + +/** + * @brief Validate algorithm inputs + * + * @return std::map + */ +std::map CorelliCalibrationApply::validateInputs() { + std::map issues; + ws = getProperty("Workspace"); + + // 1_check: input workspace is from CORELLI + if (ws->getInstrument()->getName() != "CORELLI") { + issues["Workspace"] = "CORELLI only algorithm, aborting"; + } + + // 2_check: headers of calibration table + calTable = getProperty("CalibrationTable"); + auto refCalTableHeader = CorelliCalibration::calibrationTableColumnNames; + std::vector colnames = calTable->getColumnNames(); + if (colnames.size() != refCalTableHeader.size()) { + issues["CalibrationTable"] = + "Headers of input calibration table does not match required"; + } + for (size_t i = 0; i < colnames.size(); i++) { + if (colnames[i] != refCalTableHeader[i]) { + issues["CalibrationTable"] = "Header mismatch at " + std::to_string(i); + break; + } + } + + return issues; +} + +/** + * @brief Executes the algorithm. + * + */ +void CorelliCalibrationApply::exec() { + g_log.notice() << "Start applying CORELLI calibration\n"; + + // Parse input arguments + ws = getProperty("Workspace"); + auto wsName = ws->getName(); + + calTable = getProperty("CalibrationTable"); + const auto componentNames = calTable->getColumn(0); + const auto x_poss = calTable->getColumn(1); + const auto y_poss = calTable->getColumn(2); + const auto z_poss = calTable->getColumn(3); + const auto rotxs = calTable->getColumn(4); + const auto rotys = calTable->getColumn(5); + const auto rotzs = calTable->getColumn(6); + const auto rotangs = calTable->getColumn(7); // unit: degrees + + /** + Translate each component in the instrument + [moderator, sample-position, bank1,.. bank92] + NOTE: + 1. moderator: this is often called "source" in other settings, but CORELLI + instrument uses the term moderator for it. + 2. sample-position: this is often referred to as "sample" in other + instrument definition, but the CORELLI instrument uses + a more verbose version here. + 3. the tranlation vector calcuated in upstream is the absolute coordinates + of each component (in lab frame), therefore we need to toggle the option + RelativePosition off here. + */ + g_log.notice() << "Translating each component using given Calibration table"; + auto moveAlg = createChildAlgorithm("MoveInstrumentComponent"); + moveAlg->initialize(); + moveAlg->setProperty("Workspace", wsName); + for (size_t row_num = 0; row_num < calTable->rowCount(); row_num++) { + moveAlg->setProperty("ComponentName", + componentNames->cell(row_num)); + moveAlg->setProperty("X", x_poss->cell(row_num)); + moveAlg->setProperty("Y", y_poss->cell(row_num)); + moveAlg->setProperty("Z", z_poss->cell(row_num)); + // [IMPORTANT] + // The position data from calibration table are ABSOLUTE values (lab frame) + moveAlg->setProperty("RelativePosition", false); + moveAlg->execute(); + } + + /** + Rotate each component in the instrument + IMPORTANT NOTE: + 0. The upstream calculation is a minimization routine that searches the + detector position by minimizaing DIFC, therefore the reported values + are ABSOLUTE values. + 1. In Mantid, the same component, let's take bank42 as an example, can have + different reference frameworks depending how it is called. + - bank42: the reference framework is the lab, where the origin (0,0,0) + is the sample location; + - bank42/sixteenpack: the reference framework is the sixteenpack, where + the origin is the geometry center of the + sixteenpack. + Given that the rotation angle is calucated w.r.t. sixteenpack, we need + to make sure that the provided table has the correct component names + defined in it. + 2. The Algorithm, RotateInstrumentComponent, actually does the rotation in + three steps: + - translate the component to the origin of the defined reference + framework (lab for bank42, and sixteenpack for bank42/sixteenpack) + - perform the rotation using given rotation axis and rotation angle + (in degrees) + - translate the component back to its starting position + More information can be found in the documentation of the algorithm. + */ + g_log.notice() << "Rotating each component using given Calibration table"; + auto rotateAlg = createChildAlgorithm("RotateInstrumentComponent"); + rotateAlg->initialize(); + rotateAlg->setProperty("Workspace", wsName); + for (size_t row_num = 0; row_num < calTable->rowCount(); row_num++) { + if (abs(rotangs->cell(row_num)) < 1e-8) { + continue; + } + rotateAlg->setProperty("ComponentName", + componentNames->cell(row_num)); + rotateAlg->setProperty("X", rotxs->cell(row_num)); + rotateAlg->setProperty("Y", rotys->cell(row_num)); + rotateAlg->setProperty("Z", rotzs->cell(row_num)); + rotateAlg->setProperty("Angle", rotangs->cell(row_num)); + // [IMPORTANT] + // The rotation required here has to be the RELATIVE rotation angle in + // degrees + rotateAlg->setProperty("RelativeRotation", false); + rotateAlg->execute(); + } + + // Config output + setProperty("Workspace", ws); + + g_log.notice() << "Finished applying CORELLI calibration\n"; +} + +} // namespace Algorithms + +} // namespace Mantid diff --git a/Framework/Algorithms/src/CorelliCalibrationDatabase.cpp b/Framework/Algorithms/src/CorelliCalibrationDatabase.cpp new file mode 100644 index 0000000000000000000000000000000000000000..19d3cb23d6a98218070a460e34d49c14c9f26a34 --- /dev/null +++ b/Framework/Algorithms/src/CorelliCalibrationDatabase.cpp @@ -0,0 +1,762 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAlgorithms/CorelliCalibrationDatabase.h" +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/FileProperty.h" +#include "MantidAPI/ITableWorkspace.h" +#include "MantidAPI/InstrumentValidator.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidAPI/TableRow.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidAPI/WorkspaceUnitValidator.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidGeometry/IComponent.h" +#include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/muParser_Silent.h" +#include "MantidKernel/CompositeValidator.h" +#include "MantidKernel/MandatoryValidator.h" +#include "MantidKernel/PhysicalConstants.h" +#include "MantidKernel/TimeSeriesProperty.h" + +#include +#include +#include +#include +#include +#include + +namespace Mantid { +namespace Algorithms { + +using namespace Kernel; +using namespace API; +using namespace Geometry; +using namespace DataObjects; +using Types::Core::DateAndTime; + +namespace CorelliCalibration { + +//----------------------------------------------------------------------------- +/** + * @brief CalibrationTableHandler constructor + */ +CalibrationTableHandler::CalibrationTableHandler() + : mCalibWS{nullptr}, isSingleComponentTable{false} {} + +//----------------------------------------------------------------------------- +/** + * @brief Create an empty calibration table (multi-component) or an emtpy + * history-of-compoment-positions table + * + * @param wsname: workspace name. If we pass a name, then register the + * table in the Analysis Data Service + * @param iscomponent: if True, then create a history-of-compoment-positions + * table, otherwise a calibration table + * + * @return shared pointer to the table workspace + */ +DataObjects::TableWorkspace_sptr +CalibrationTableHandler::createCalibrationTableWorkspace( + const std::string &wsname, bool iscomponent) { + + // Create table workspace + ITableWorkspace_sptr itablews = WorkspaceFactory::Instance().createTable(); + + // Add to ADS if workspace name is given + if (wsname.size() > 0) { + AnalysisDataService::Instance().addOrReplace(wsname, itablews); + } + + TableWorkspace_sptr tablews = + std::dynamic_pointer_cast(itablews); + + // Set up columns + if (iscomponent) + tablews->addColumn("str", "YYYMMDD"); // first column as date stamp + else { + tablews->addColumn("str", "ComponentName"); // first column as date stamp + } + for (size_t i = 1; i < CorelliCalibration::calibrationTableColumnNames.size(); + ++i) { + std::string colname = CorelliCalibration::calibrationTableColumnNames[i]; + std::string type = CorelliCalibration::calibrationTableColumnTypes[i]; + tablews->addColumn(type, colname); + } + + return tablews; +} + +//----------------------------------------------------------------------------- +/** + * @brief Check whether a TableWorkspace is a valid Corelli calibration table + * for all components. + * + * @details Cheks performed are correct number of columns and columns names + * + * @param calibws: Calibration table workspace + * @param errormsg: (output) error message + * @return + */ +bool CalibrationTableHandler::isValidCalibrationTableWorkspace( + DataObjects::TableWorkspace_sptr calibws, std::string &errormsg) { + // Check columns of + std::vector colNames = calibws->getColumnNames(); + + bool valid = true; + + if (colNames.size() != + CorelliCalibration::calibrationTableColumnNames.size()) { + // column numbers mismatch + std::stringstream errorss; + errorss << "Calibration table workspace requires " + << CorelliCalibration::calibrationTableColumnNames.size() + << " columns. Input workspace " << calibws->getName() << " get " + << calibws->getColumnNames().size() << "instead."; + errormsg = errorss.str(); + valid = false; + } else { + + // Check columns one by one + for (size_t i = 0; i < colNames.size(); ++i) { + if (colNames[i] != CorelliCalibration::calibrationTableColumnNames[i]) { + std::stringstream errorss; + errorss << i << "-th column is supposed to be " + << CorelliCalibration::calibrationTableColumnNames[i] + << ", but instead in TableWorkspace " << calibws->getName() + << " it is " << colNames[i]; + errormsg = errorss.str(); + valid = false; + break; + } + } + } + + return valid; +} + +//----------------------------------------------------------------------------- +/** + * @brief Append a new dated position to the table of history of positions + * + * @param tablews: table workspace + * @param datestamp: a day-stamp with format YYYYMMDD + * @param pos: location and orientation of the component + */ +void CalibrationTableHandler::appendCalibration( + DataObjects::TableWorkspace_sptr tablews, const std::string &datestamp, + ComponentPosition &pos) { + // check + if (tablews->columnCount() != calibrationTableColumnNames.size()) { + throw std::runtime_error( + "Single component calibration table workspace is not correct."); + } + + // Append a new row + Mantid::API::TableRow sourceRow = tablews->appendRow(); + // Date and positions + sourceRow << datestamp << pos.x << pos.y << pos.z << pos.xCosine + << pos.yCosine << pos.zCosine << pos.rotAngle; +} + +/** + * @brief Set a valid calibration table to the handler. This cannot be a + * table for history-of-component-positions, but a calibration table. + * @param calibws + */ +void CalibrationTableHandler::setCalibrationTable( + DataObjects::TableWorkspace_sptr calibws) { + + std::string errmsg{""}; + + if (!isValidCalibrationTableWorkspace(calibws, errmsg)) + throw std::runtime_error(errmsg); + + // Set + mCalibWS = calibws; +} + +/** + * @brief Get component names of the table workspace + * + * @details the component names are the first column of the table + * + * @throws std::runtime_error for single-component tables + * + * @return names as a vector or strings + */ +std::vector CalibrationTableHandler::getComponentNames() { + + // Check workspace type + if (isSingleComponentTable) + throw std::runtime_error("TableWorkspace contains a single component's " + "calibration in various dates"); + + std::vector names = mCalibWS->getColVector(0); + + return names; +} + +/** + * @brief extract the location and orientation for one of the components + * in a calibration table + * @param component + * @return + */ +ComponentPosition CalibrationTableHandler::getComponentCalibratedPosition( + const std::string &component) { + // Check + if (!mCalibWS) + throw std::runtime_error("Calibration workspace has not been set up yet."); + + // Get the row number of the specified component + size_t row_number = mCalibWS->rowCount(); + for (size_t i = 0; i < row_number; ++i) { + if (mCalibWS->cell(i, 0) == component) { + row_number = i; + break; + } + } + // Check + if (row_number == mCalibWS->rowCount()) + throw std::runtime_error("Specified component does not exist"); + + // Get the values + ComponentPosition pos; + pos.x = mCalibWS->cell(row_number, 1); + pos.y = mCalibWS->cell(row_number, 2); + pos.z = mCalibWS->cell(row_number, 3); + pos.xCosine = mCalibWS->cell(row_number, 4); + pos.yCosine = mCalibWS->cell(row_number, 5); + pos.zCosine = mCalibWS->cell(row_number, 6); + pos.rotAngle = mCalibWS->cell(row_number, 7); + + return pos; +} + +/** + * @brief Load a single-component database file to a table workspace of + * history of positions for the component + * + * @details the single-component calibration file contains the history of + * positions for said component + * + * @param filename + * @package tablewsname : name for TableWorkspace the file is loaded to + * @return shared pointer to the table workspace + */ +DataObjects::TableWorkspace_sptr +CalibrationTableHandler::loadComponentCalibrationTable( + const std::string &filename, const std::string &tablewsname) { + // Get algorithm handler + IAlgorithm_sptr loadAsciiAlg = + AlgorithmFactory::Instance().create("LoadAscii", 2); + // Set parameters + if (tablewsname.size() == 0) { + throw std::runtime_error( + "Failed to load ASCII as OutputWorkspace name is empty string."); + } + loadAsciiAlg->initialize(); + loadAsciiAlg->setPropertyValue("Filename", filename); + loadAsciiAlg->setPropertyValue("OutputWorkspace", tablewsname); + loadAsciiAlg->setPropertyValue("Separator", "CSV"); + loadAsciiAlg->setPropertyValue("CommentIndicator", "#"); + loadAsciiAlg->execute(); + // Convert to TableWorkspace + TableWorkspace_sptr tablews = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve(tablewsname)); + + return tablews; +} + +//----------------------------------------------------------------------------- +/** + * @brief Save a specific component to database (csv) file + * + * @details Extract the position of a specific component from the full + * calibration table, and append to a database file. If the database file + * does not exits, then create it. + * + * @param datestamp: YYYYMMDD date stamp + * @param component: component name + * @param filename: full path of the database file for the specific component + * + * @returns table of history of positions for the specific component + */ +TableWorkspace_sptr +CalibrationTableHandler::saveCompomentDatabase(const std::string &datestamp, + const std::string &component, + const std::string &filename) { + + std::string tablewsname = component + "_" + datestamp; + + // Load the database file for the specific component to a table workspace + // if extant, otherwise instantiate an empty table + TableWorkspace_sptr compcaltable = nullptr; + if (boost::filesystem::exists(filename)) { + compcaltable = loadComponentCalibrationTable(filename, tablewsname); + } else { + compcaltable = createCalibrationTableWorkspace(tablewsname, true); + } + + // Append a new row to the table containing the hisotry of positions for + // the specific component + ComponentPosition componentpos = getComponentCalibratedPosition(component); + appendCalibration(compcaltable, datestamp, componentpos); + + // save the updated history of positions to the database file. Will overwrite + // the file if extant + // Note: only version 2 of SaveAscii can work with TableWorkspace + IAlgorithm_sptr saveAsciiAlg = + AlgorithmFactory::Instance().create("SaveAscii", 2); + saveAsciiAlg->initialize(); + saveAsciiAlg->setProperty("InputWorkspace", compcaltable); + saveAsciiAlg->setProperty("Filename", filename); + saveAsciiAlg->setPropertyValue("CommentIndicator", "#"); + saveAsciiAlg->setPropertyValue("Separator", "CSV"); + saveAsciiAlg->setProperty("ColumnHeader", true); + saveAsciiAlg->setProperty("AppendToFile", + false); // always overwrite original file + // run + saveAsciiAlg->execute(); + + return compcaltable; +} + +//----------------------------------------------------------------------------- +/** + * @brief Save full calibration table to a database (CSV) file + * @param filename + */ +void CalibrationTableHandler::saveCalibrationTable( + const std::string &filename) { + // create algorithm: only version 2 of SaveAscii can work with TableWorkspace + IAlgorithm_sptr saveAsciiAlg = + AlgorithmFactory::Instance().create("SaveAscii", 2); + saveAsciiAlg->initialize(); + saveAsciiAlg->setProperty("InputWorkspace", mCalibWS); + saveAsciiAlg->setProperty("Filename", filename); + saveAsciiAlg->setPropertyValue("CommentIndicator", "#"); + saveAsciiAlg->setPropertyValue("Separator", "CSV"); + saveAsciiAlg->setProperty("ColumnHeader", true); + // run + saveAsciiAlg->execute(); +} + +//----------------------------------------------------------------------------- +ComponentPosition CalibrationTableHandler::getLatestCalibratedPosition( + DataObjects::TableWorkspace_sptr componentcaltable) { + + size_t num_rows = componentcaltable->rowCount(); + ComponentPosition pos = CalibrationTableHandler::getCalibratedPosition( + componentcaltable, num_rows - 1); + + return pos; +} + +//----------------------------------------------------------------------------- +ComponentPosition CalibrationTableHandler::getCalibratedPosition( + DataObjects::TableWorkspace_sptr componentcaltable, size_t rownumber) { + // Get the values + ComponentPosition pos; + + pos.x = componentcaltable->cell(rownumber, 1); + pos.y = componentcaltable->cell(rownumber, 2); + pos.z = componentcaltable->cell(rownumber, 3); + pos.xCosine = componentcaltable->cell(rownumber, 4); + pos.yCosine = componentcaltable->cell(rownumber, 5); + pos.zCosine = componentcaltable->cell(rownumber, 6); + pos.rotAngle = componentcaltable->cell(rownumber, 7); + + return pos; +} + +} // namespace CorelliCalibration + +// Register the algorithm into the AlgorithmFactory +DECLARE_ALGORITHM(CorelliCalibrationDatabase) + +/** Initialize the algorithm's properties. + */ +void CorelliCalibrationDatabase::init() { + auto wsValidator = std::make_shared(); + wsValidator->add(); + + // Input MatrixWorkspace which the calibration run is from + declareProperty(std::make_unique>( + "InputWorkspace", "", Direction::Input, wsValidator), + "Workspace containing the day-stamp of the calibration"); + + // Input calibration patch TableWorkspace + declareProperty(std::make_unique>( + "InputCalibrationPatchWorkspace", "", Direction::Input), + "Table workspace containing calibrated positions and " + "orientations for a subset of the banks"); + + // Output directory + declareProperty(std::make_unique("DatabaseDirectory", "", + FileProperty::Directory), + "The directory that the database (csv) files are saved to"); + + // Optional output calibration TableWorkspace + declareProperty(std::make_unique>( + "OutputWorkspace", "", Direction::Output), + "Table workspace containing calibrated positions and " + "orientations for all banks"); +} + +// Validate inputs workspace first. +std::map +CorelliCalibrationDatabase::validateInputs() { + std::map errors; + + mInputWS = getProperty("InputWorkspace"); + + // check for null pointers - this is to protect against workspace groups + if (!mInputWS) { + return errors; + } + + // This algorithm will only work for CORELLI, check for CORELLI. + if (mInputWS->getInstrument()->getName() != "CORELLI") + errors["InputWorkspace"] = "This Algorithm will only work for Corelli."; + // Must include start_time + else if (!mInputWS->run().hasProperty("start_time") && + !mInputWS->run().hasProperty("run_start")) + errors["InputWorkspace"] = "Workspace is missing property start_time."; + + // check for calibration patch table workspace + mInputCalibrationTableWS = getProperty("InputCalibrationPatchWorkspace"); + if (!mInputCalibrationTableWS) { + errors["InputCalibrationPatchWorkspace"] = + "Input calibration patch workspace is not specified"; + return errors; + } + // Check columns + else { + std::string error_msg{""}; + bool isvalid = CorelliCalibration::CalibrationTableHandler:: + isValidCalibrationTableWorkspace(mInputCalibrationTableWS, error_msg); + + if (!isvalid) { + errors["InputCalibrationPatchWorkspace"] = error_msg; + } + } + + return errors; +} + +//----------------------------------------------------------------------------- +/** Execute the algorithm. + */ +void CorelliCalibrationDatabase::exec() { + // parse input + if (!mInputWS) + throw std::runtime_error("input workspace not specified"); + if (!mInputCalibrationTableWS) + throw std::runtime_error("input calibration workspace not specified"); + + std::string calibDatabaseDir = getProperty("DatabaseDirectory"); + + // map for (component name, component calibration workspace + std::vector orderedcomponents = + retrieveInstrumentComponents(mInputWS); + + std::map component_caibws_map; + setComponentMap(orderedcomponents, component_caibws_map); + + // Update component CSV files + updateComponentDatabaseFiles(calibDatabaseDir, component_caibws_map); + + // Load data file if necessary and possible: component_caibws_map + loadNonCalibratedComponentDatabase(calibDatabaseDir, component_caibws_map); + + // Create summary calibration workspace: input: component_caibws_map output: + // new calibration workspace + createOutputCalibrationTable(component_caibws_map, orderedcomponents); + + // Create the summary CSV file + saveCalibrationTable(calibDatabaseDir); + + // Clean up memory + for (auto &[compname, calibws] : component_caibws_map) { + if (calibws) { + g_log.debug() << "Removing " << compname + << "calibration table from ADS\n"; + AnalysisDataService::Instance().remove(calibws->getName()); + } + } + + // output + setProperty("OutputWorkspace", mOutputWS); +} + +/** + * @brief update component database files from table + * @param calibdbdir: directory where dababase files are stored + * @param calibwsmap: in/out map for bank and calibration tableworkspace in ADS + */ +void CorelliCalibrationDatabase::updateComponentDatabaseFiles( + const std::string &calibdbdir, + std::map &calibwsmap) { + // Date stamp + std::string timestampstr{""}; + if (mInputWS->run().hasProperty("start_time")) { + // string value from start_time + timestampstr = mInputWS->run().getProperty("start_time")->value(); + } else { + // string value from run_start if start_time does not exist. + // with input workspace validate, there must be at least one exist + // between start_time and run_start + timestampstr = mInputWS->run().getProperty("run_start")->value(); + } + // convert + mDateStamp = convertTimeStamp(timestampstr); + + // Handler + CorelliCalibration::CalibrationTableHandler handler = + CorelliCalibration::CalibrationTableHandler(); + handler.setCalibrationTable(mInputCalibrationTableWS); + + // Loop over all the components that have been calibrated in the calibration + // table + size_t num_rows = mInputCalibrationTableWS->rowCount(); + for (size_t i = 0; i < num_rows; ++i) { + // get component name + std::string compname = + mInputCalibrationTableWS->cell(i, 0); + // get file name + std::string compdbname = corelliComponentDatabaseName(compname, calibdbdir); + // save + TableWorkspace_sptr comptablews = + handler.saveCompomentDatabase(mDateStamp, compname, compdbname); + // add the map + calibwsmap[compname] = comptablews; + g_log.debug() << "Component " << compname << " is updated to " << compdbname + << " and saved to " << comptablews->getName() << "\n"; + } +} + +/** + * @brief Load database file of certain banks if they do not appear in mCalibWS + * @param calibdbdir: calibration database directory + * @param calibwsmap: map from component name to calibration table workspace + */ +void CorelliCalibrationDatabase::loadNonCalibratedComponentDatabase( + const std::string &calibdbdir, + std::map &calibwsmap) { + // go through all the components + for (auto &[componentname, componentcaltable] : calibwsmap) { + // check whether the calibration workspace has been loaded + if (componentcaltable) // skip if it has been loaded + continue; + + // locate the file + std::string compdbname = + corelliComponentDatabaseName(componentname, calibdbdir); + if (!isFileExist(compdbname)) // skip if the file does not exist + { + // skip if the database file does not exist + g_log.debug() << "Component " << componentname + << ": No database file is found at " << compdbname << "\n"; + continue; + } + + // load the database (csv) file and set + TableWorkspace_sptr loaded_compcalibws = CorelliCalibration:: + CalibrationTableHandler::loadComponentCalibrationTable( + compdbname, componentname + "_" + mDateStamp); + calibwsmap[componentname] = loaded_compcalibws; + + g_log.debug() << "Component " << componentname << " is loaded from " + << compdbname << " and saved to " + << loaded_compcalibws->getName() << "\n"; + } +} + +// Create summary calibration workspace: input: component_caibws_map output: +// new calibration workspace +void CorelliCalibrationDatabase::createOutputCalibrationTable( + std::map &calibwsmap, + std::vector orderedcomponents) { + // Create an empty calibration table without setting to analysis data service + mOutputWS = CorelliCalibration::CalibrationTableHandler:: + createCalibrationTableWorkspace("", false); + CorelliCalibration::CalibrationTableHandler handler = + CorelliCalibration::CalibrationTableHandler(); + handler.setCalibrationTable(mOutputWS); + + for (auto componentname : orderedcomponents) { + auto componentcaltable = calibwsmap[componentname]; + if (componentcaltable) { + // only take care of calibrated components (before and now) + auto lastpos = CorelliCalibration::CalibrationTableHandler:: + getLatestCalibratedPosition(componentcaltable); + handler.appendCalibration(mOutputWS, componentname, lastpos); + } + } +} + +// Create the summary CSV file +// File name exampe: corelli_instrument_20202015.csv +void CorelliCalibrationDatabase::saveCalibrationTable( + const std::string &calibdbdir) { + // file name + std::string filename = "corelli_instrument_" + mDateStamp + ".csv"; + filename = joinPath(calibdbdir, filename); + + // Call the handler: set and save + CorelliCalibration::CalibrationTableHandler handler = + CorelliCalibration::CalibrationTableHandler(); + handler.setCalibrationTable(mOutputWS); + handler.saveCalibrationTable(filename); +} + +//----------------------------------------------------------------------------- +/** + * @brief A static method to convert Mantid datetime string to YYYYMMDD format + * @param run_start_time: str as run start time in format of YYYY-MM-DDTHH:MM:SS + * @return + */ +std::string +CorelliCalibrationDatabase::convertTimeStamp(std::string run_start_time) { + // Get the first sub string by + std::string date_str = run_start_time.substr(0, run_start_time.find("T")); + + // Separate year date and time + std::string year = date_str.substr(0, date_str.find("-")); + std::string monthday = date_str.substr( + date_str.find("-") + 1, date_str.size()); // +1 to ignore delimit '-' + std::string month = monthday.substr(0, monthday.find("-")); + std::string day = monthday.substr(monthday.find("-") + 1, + monthday.size()); // +1 to ignore delimit + std::string datestamp = year + month + day; + + return datestamp; +} + +//----------------------------------------------------------------------------- +/** + * @brief Compose a standard full path of a component CSV file + * + * @details names for bank components follow the pattern bankXX/sixteenpack + * and we drop the "/sixteenpack" suffix when composing the name of the CSV file + * + * @param componentname + * @param directory + * @return + */ +std::string CorelliCalibrationDatabase::corelliComponentDatabaseName( + const std::string &componentname, const std::string &directory) { + + // drop the suffix "/sixteenpack" if found in the component name + std::string shortName = componentname; + std::string suffix{"/sixteenpack"}; + size_t pos = componentname.find(suffix); + if (pos != std::string::npos) + shortName.erase(pos, suffix.length()); + + std::string basename = shortName + ".csv"; + std::string filename = joinPath(directory, basename); + + return filename; +} + +//----------------------------------------------------------------------------- +/** + * @brief Compose a standard full path of a calibration table CSV file + * @param datestamp + * @param directory + * @return + */ +std::string CorelliCalibrationDatabase::corelliCalibrationDatabaseName( + const std::string &datestamp, const std::string &directory) { + std::string basename = datestamp + ".csv"; + std::string filename = joinPath(directory, basename); + return filename; +} + +//----------------------------------------------------------------------------- +/** + * @brief Check whether a file does exist + * @param filepath + * @return + */ +bool CorelliCalibrationDatabase::isFileExist(const std::string &filepath) { + + // TODO - replace by std::filesystem::exists(filename) until C++17 is properly + // supported + return boost::filesystem::exists(filepath); +} + +//----------------------------------------------------------------------------- +/** + * @brief Join directory and base file name for a full path + * @param directory + * @param basename + * @return + */ +std::string CorelliCalibrationDatabase::joinPath(const std::string directory, + const std::string basename) { + boost::filesystem::path dir(directory); + boost::filesystem::path file(basename); + boost::filesystem::path fullpath = dir / file; + + return fullpath.string(); +} + +/** + * @brief Set up component map + * + * @details keys of this map are "moderator", "sample-position", + * "bank1/sixteenpack",..,"bank91/sixteenpack" + * @param componentnames + * @param compmap + */ +void CorelliCalibrationDatabase::setComponentMap( + std::vector componentnames, + std::map &compmap) { + // Add entries + for (auto compname : componentnames) + compmap[compname] = nullptr; +} + +/** + * @brief Retrieve the names of certain intrument compoments + * + * @details names of interest are are "moderator", "sample-position", + * "bank1/sixteenpack",..,"bank91/sixteenpack" + * + * @param ws : Any workspace containing instrument + * @return : vector including of all the compoments in the order as + * moderator, sample-position, bank1, bank2, ... + */ +std::vector +CorelliCalibrationDatabase::retrieveInstrumentComponents( + MatrixWorkspace_sptr ws) { + // Get access to instrument information + const auto &component_info = ws->componentInfo(); + + // Init output + std::vector componentnames = {"moderator", "sample-position"}; + + // Loop over all the components for bankX/sixteenpack + const size_t num_components = component_info.size(); + for (size_t i = 0; i < num_components; ++i) { + std::string compname = component_info.name(i); + // a component starts with bank must be a bank + if (compname.compare(0, 4, "bank") == 0) { + componentnames.push_back(compname + "/sixteenpack"); + } + } + + return componentnames; +} + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/src/CreateDetectorTable.cpp b/Framework/Algorithms/src/CreateDetectorTable.cpp index b48ad38b891eeed5c76c02b3e553aab1bfe9a2f9..a466b80a733a5c3e0b757e64283fb12139007ddd 100644 --- a/Framework/Algorithms/src/CreateDetectorTable.cpp +++ b/Framework/Algorithms/src/CreateDetectorTable.cpp @@ -183,23 +183,23 @@ createDetectorTableWorkspace(const MatrixWorkspace_sptr &ws, std::vector> createColumns(const bool isScanning, const bool includeData, const bool calcQ) { std::vector> colNames; - colNames.emplace_back(std::make_pair("double", "Index")); - colNames.emplace_back(std::make_pair("int", "Spectrum No")); - colNames.emplace_back(std::make_pair("str", "Detector ID(s)")); + colNames.emplace_back("double", "Index"); + colNames.emplace_back("int", "Spectrum No"); + colNames.emplace_back("str", "Detector ID(s)"); if (isScanning) - colNames.emplace_back(std::make_pair("str", "Time Indexes")); + colNames.emplace_back("str", "Time Indexes"); if (includeData) { - colNames.emplace_back(std::make_pair("double", "Data Value")); - colNames.emplace_back(std::make_pair("double", "Data Error")); + colNames.emplace_back("double", "Data Value"); + colNames.emplace_back("double", "Data Error"); } - colNames.emplace_back(std::make_pair("double", "R")); - colNames.emplace_back(std::make_pair("double", "Theta")); + colNames.emplace_back("double", "R"); + colNames.emplace_back("double", "Theta"); if (calcQ) { - colNames.emplace_back(std::make_pair("double", "Q")); + colNames.emplace_back("double", "Q"); } - colNames.emplace_back(std::make_pair("double", "Phi")); - colNames.emplace_back(std::make_pair("str", "Monitor")); + colNames.emplace_back("double", "Phi"); + colNames.emplace_back("str", "Monitor"); return colNames; } @@ -299,7 +299,8 @@ void populateTable(ITableWorkspace_sptr &t, const MatrixWorkspace_sptr &ws, double q = UnitConversion::convertToElasticQ(usignTheta, efixed); colValues << q; } catch (std::runtime_error &) { - colValues << "No Efixed"; + // No Efixed + colValues << std::nan(""); } } } diff --git a/Framework/Algorithms/src/CreateGroupingWorkspace.cpp b/Framework/Algorithms/src/CreateGroupingWorkspace.cpp index 9ce38b939b79c47ec099e91c057bb842133a3646..4dd70b3e002a05cce483e65cc948316c019592d2 100644 --- a/Framework/Algorithms/src/CreateGroupingWorkspace.cpp +++ b/Framework/Algorithms/src/CreateGroupingWorkspace.cpp @@ -21,10 +21,244 @@ #include #include +using namespace Mantid; +using namespace Mantid::API; +using namespace Mantid::Geometry; + namespace { Mantid::Kernel::Logger g_log("CreateGroupingWorkspace"); + +void removeSpacesFromString(std::string &str) { + str.erase(std::remove_if(str.begin(), str.end(), isspace), str.end()); +} + +/** Adds the elements of the second vector onto the end of the first vector. + * + * @param vec The vector to be extended. + * @param extension The vector to emplace onto the end of vec. + */ +void extendVectorBy(std::vector &vec, + const std::vector &extension) { + vec.reserve(vec.size() + std::distance(extension.cbegin(), extension.cend())); + vec.insert(vec.end(), extension.cbegin(), extension.cend()); +} + +/** Splits a string by the provided delimiter characters. Erases any empty + * sub-strings. + * + * @param str The string to be split. + * @param delimiter The characters to split the string by. + * @returns :: A vector of sub-strings resulting from the split. + */ +std::vector splitStringBy(const std::string &str, + const std::string &delimiter) { + std::vector subStrings; + boost::split(subStrings, str, boost::is_any_of(delimiter)); + subStrings.erase(std::remove_if(subStrings.begin(), subStrings.end(), + [](const std::string &subString) { + return subString.empty(); + }), + subStrings.cend()); + return subStrings; +} + +/** Returns true if a string contains a specific separator. + * + * @param str The string to search for the separator. + * @param separator The separator to search for. + * @returns :: True if the string contains the specified separator. + */ +bool hasSeparator(const std::string &str, const std::string &separator) { + return str.find(separator) != std::string::npos; +} + +/** Returns a vector of detector IDs (in string format) contained within the + * lower and upper limits of a range. + * + * @param lower The lowest detector ID. + * @param upper The highest detector ID. + * @returns :: A vector of detector IDs (in string format). + */ +std::vector getDetectorRangeFromLimits(int lower, int upper) { + std::vector detectorIds; + detectorIds.reserve(static_cast(upper - lower + 1)); + for (auto i = lower; i <= upper; ++i) + detectorIds.emplace_back(std::to_string(i)); + return detectorIds; +} + +/** Splits a grouping string by the colon separator, and then fully expands the + * group. + * + * @param groupString The grouping string containing a ':' separator. + * @returns :: The expanded vector of groups. + */ +std::vector groupsFromColonRange(const std::string &groupString) { + const auto splitByColon = splitStringBy(groupString, ":"); + if (splitByColon.size() > 2) + throw std::runtime_error("Expected a single colon separator."); + + if (splitByColon.size() == 2) + return getDetectorRangeFromLimits(std::stoi(splitByColon[0]), + std::stoi(splitByColon[1])); + return splitByColon; +} + +/** Expands the grouping strings that contain a ':' separator. For example the + * string '2:5' means the detector IDs 2, 3, 4 and 5 should be in their own + * individual groups. This means we must expand this string. + * + * @param groupsToExpand The grouping strings to check and expand. + * @returns :: A vector of expanded grouping strings. + */ +std::vector +expandGroupsWithColonSeparator(const std::vector &groupsToExpand) { + std::vector expandedGroupStrings; + for (const auto &groupString : groupsToExpand) + extendVectorBy(expandedGroupStrings, groupsFromColonRange(groupString)); + return expandedGroupStrings; +} + +/** Maps a single detector ID to a group ID if the detector ID is found in the + * vector of allowed ID's. + * + * @param allowedDetectorIDs The detector IDs which are allowed for the given + * instrument. + * @param detectorIDToGroup The mapping of detector IDs to group IDs. + * @param detectorID The ID of the detector to map to the group ID. + * @param groupID The ID of the group to map to the detector ID. + */ +void addDetectorToGroup(const std::vector &allowedDetectorIDs, + std::map &detectorIDToGroup, + int detectorID, int groupID) { + const auto iter = std::find(allowedDetectorIDs.cbegin(), + allowedDetectorIDs.cend(), detectorID); + if (iter == allowedDetectorIDs.cend()) + throw std::runtime_error("The Detector ID '" + std::to_string(detectorID) + + "' is not valid for this instrument component."); + + detectorIDToGroup[detectorID] = groupID; +} + +/** Adds the detector IDs from a grouping string containing a dash to the Group + * ID map. For example the string '2-5' means detector IDs 2, 3, 4 and 5 + * should be mapped to the same group ID. + * + * @param allowedDetectorIDs The detector IDs which are allowed for the given + * instrument. + * @param detectorIDToGroup The mapping of detector IDs to group IDs. + * @param groupString The string which contains the '-' separator. + * @param groupID The ID of the group to map to the detector IDs. + */ +void addDashSeparatedDetectorIDsToSameGroup( + const std::vector &allowedDetectorIDs, + std::map &detectorIDToGroup, const std::string &groupString, + int groupID) { + const auto splitByDash = splitStringBy(groupString, "-"); + + if (splitByDash.size() < 2) + throw std::runtime_error("Expected at least one dash separator."); + else if (splitByDash.size() > 2) + throw std::runtime_error("Expected a single dash separator."); + + for (auto i = std::stoi(splitByDash[0]); i <= std::stoi(splitByDash[1]); ++i) + addDetectorToGroup(allowedDetectorIDs, detectorIDToGroup, i, groupID); +} + +/** Adds the detector IDs from a grouping string containing a plus to the Group + * ID map. For example the string '2+3+4+5' means detector IDs 2, 3, 4 and 5 + * should be mapped to the same group ID. + * + * @param allowedDetectorIDs The detector IDs which are allowed for the given + * instrument. + * @param detectorIDToGroup The mapping of detector IDs to group IDs. + * @param groupString The string which contains the '+' separator. + * @param groupID The ID of the group to map to the detector IDs. + */ +void addPlusSeparatedDetectorIDsToSameGroup( + const std::vector &allowedDetectorIDs, + std::map &detectorIDToGroup, const std::string &groupString, + int groupID) { + const auto splitByPlus = splitStringBy(groupString, "+"); + if (splitByPlus.size() < 2) + throw std::runtime_error("Expected at least one plus separator."); + + for (const auto &id : splitByPlus) + addDetectorToGroup(allowedDetectorIDs, detectorIDToGroup, std::stoi(id), + groupID); +} + +/** Gets the detector IDs within the component of a given instrument. + * + * @param instrument A pointer to instrument. + * @param componentName Name of component in instrument. + * @returns :: A vector of Detector IDs. + */ +std::vector +getAllowedDetectorIDs(const Instrument_const_sptr &instrument, + const std::string &componentName) { + std::vector detectors; + detectors.reserve(instrument->getNumberDetectors()); + instrument->getDetectorsInBank(detectors, componentName); + + std::vector detectorIDs; + detectorIDs.reserve(detectors.size()); + std::transform( + detectors.cbegin(), detectors.cend(), std::back_inserter(detectorIDs), + [](const IDetector_const_sptr &detector) { return detector->getID(); }); + return detectorIDs; +} + +/** Creates a mapping between Detector IDs and Group IDs from several grouping + * strings already split by the comma ',' separator. At this stage the ':' + * separated strings have also already been expanded. + * + * @param allowedDetectorIDs The detector IDs which are allowed for the given + * instrument. + * @param groupingStrings The grouping strings to split and map. + * @returns :: Map of detector IDs to group IDs. + */ +std::map +mapGroupingStringsToGroupIDs(const std::vector &allowedDetectorIDs, + const std::vector &groupingStrings) { + std::map detectorIDToGroup; + for (auto j = 0; j < static_cast(groupingStrings.size()); ++j) { + if (hasSeparator(groupingStrings[j], "+")) + addPlusSeparatedDetectorIDsToSameGroup( + allowedDetectorIDs, detectorIDToGroup, groupingStrings[j], j + 1); + else if (hasSeparator(groupingStrings[j], "-")) + addDashSeparatedDetectorIDsToSameGroup( + allowedDetectorIDs, detectorIDToGroup, groupingStrings[j], j + 1); + else + addDetectorToGroup(allowedDetectorIDs, detectorIDToGroup, + std::stoi(groupingStrings[j]), j + 1); + } + return detectorIDToGroup; +} + +/** Creates a mapping between Detector IDs and Group IDs using a custom grouping + * string + * + * @param instrument A pointer to instrument. + * @param componentName Name of component in instrument. + * @param customGroupingString The string used to specify the grouping. + * @returns :: Map of detector IDs to group IDs. + */ +std::map +makeGroupingByCustomString(const Instrument_const_sptr &instrument, + const std::string &componentName, + std::string &customGroupingString) { + removeSpacesFromString(customGroupingString); + + const auto detectorIDs = getAllowedDetectorIDs(instrument, componentName); + const auto groupStrings = + expandGroupsWithColonSeparator(splitStringBy(customGroupingString, ",")); + + return mapGroupingStringsToGroupIDs(detectorIDs, groupStrings); } +} // namespace + namespace Mantid { namespace Algorithms { @@ -92,6 +326,11 @@ void CreateGroupingWorkspace::init() { std::make_shared>(0, INT_MAX), "Used to distribute the detectors of a given component into " "a fixed number of groups"); + declareProperty( + "CustomGroupingString", "", + "This takes a comma separated list of grouped detector IDs. An example " + "of the syntax is 1,2+3,4-6,7:10. The documentation page for this " + "algorithm gives a full explanation of this syntax."); declareProperty("ComponentName", "", "Specify the instrument component to " "group into a fixed number of groups"); @@ -111,6 +350,7 @@ void CreateGroupingWorkspace::init() { setPropertyGroup("MaxRecursionDepth", groupby); setPropertyGroup("FixedGroupCount", groupby); setPropertyGroup("ComponentName", groupby); + setPropertyGroup("CustomGroupingString", groupby); // output properties declareProperty("NumberGroupedSpectraResult", EMPTY_INT(), @@ -164,6 +404,18 @@ std::map CreateGroupingWorkspace::validateInputs() { result["ComponentName"] = msg; } + std::string customGroupingString = getPropertyValue("CustomGroupingString"); + std::string componentName = getPropertyValue("ComponentName"); + + if (!componentName.empty() && !customGroupingString.empty()) { + try { + (void)makeGroupingByCustomString(getInstrument(), componentName, + customGroupingString); + } catch (const std::runtime_error &ex) { + result["CustomGroupingString"] = ex.what(); + } + } + return result; } @@ -369,6 +621,7 @@ void CreateGroupingWorkspace::exec() { std::string GroupNames = getPropertyValue("GroupNames"); std::string grouping = getPropertyValue("GroupDetectorsBy"); int numGroups = getProperty("FixedGroupCount"); + std::string customGroupingString = getPropertyValue("CustomGroupingString"); std::string componentName = getPropertyValue("ComponentName"); // Some validation @@ -396,21 +649,7 @@ void CreateGroupingWorkspace::exec() { bool sortnames = false; - // ---------- Get the instrument one of 3 ways --------------------------- - Instrument_const_sptr inst; - if (inWS) { - inst = inWS->getInstrument(); - } else { - Algorithm_sptr childAlg = createChildAlgorithm("LoadInstrument", 0.0, 0.2); - MatrixWorkspace_sptr tempWS = std::make_shared(); - childAlg->setProperty("Workspace", tempWS); - childAlg->setPropertyValue("Filename", InstrumentFilename); - childAlg->setProperty("RewriteSpectraMap", - Mantid::Kernel::OptionalBool(true)); - childAlg->setPropertyValue("InstrumentName", InstrumentName); - childAlg->executeAsChildAlg(); - inst = tempWS->getInstrument(); - } + const auto inst = getInstrument(); // Validation for 2_4Grouping input used only for SNAP if (inst->getName() != "SNAP" && grouping == "2_4Grouping") { @@ -476,6 +715,15 @@ void CreateGroupingWorkspace::exec() { else if ((numGroups > 0) && !componentName.empty()) detIDtoGroup = makeGroupingByNumGroups(componentName, numGroups, inst, prog); + else if (!customGroupingString.empty() && !componentName.empty()) { + try { + detIDtoGroup = + makeGroupingByCustomString(inst, componentName, customGroupingString); + } catch (const std::runtime_error &ex) { + g_log.error(ex.what()); + return; + } + } g_log.information() << detIDtoGroup.size() << " entries in the detectorID-to-group map.\n"; @@ -511,5 +759,27 @@ void CreateGroupingWorkspace::exec() { } } +Instrument_const_sptr CreateGroupingWorkspace::getInstrument() { + MatrixWorkspace_sptr inputWorkspace = getProperty("InputWorkspace"); + std::string instrumentName = getPropertyValue("InstrumentName"); + std::string instrumentFilename = getPropertyValue("InstrumentFilename"); + + Instrument_const_sptr instrument; + if (inputWorkspace) { + instrument = inputWorkspace->getInstrument(); + } else { + Algorithm_sptr childAlg = createChildAlgorithm("LoadInstrument", 0.0, 0.2); + MatrixWorkspace_sptr tempWS = std::make_shared(); + childAlg->setProperty("Workspace", tempWS); + childAlg->setPropertyValue("Filename", instrumentFilename); + childAlg->setProperty("RewriteSpectraMap", + Mantid::Kernel::OptionalBool(true)); + childAlg->setPropertyValue("InstrumentName", instrumentName); + childAlg->executeAsChildAlg(); + instrument = tempWS->getInstrument(); + } + return instrument; +} + } // namespace Algorithms } // namespace Mantid diff --git a/Framework/Algorithms/src/CropWorkspaceRagged.cpp b/Framework/Algorithms/src/CropWorkspaceRagged.cpp new file mode 100644 index 0000000000000000000000000000000000000000..059933a165530db591792249149bc54cd056301c --- /dev/null +++ b/Framework/Algorithms/src/CropWorkspaceRagged.cpp @@ -0,0 +1,166 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAlgorithms/CropWorkspaceRagged.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidKernel/ArrayProperty.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/MandatoryValidator.h" + +namespace { +std::vector getSubVector(Mantid::MantidVec &data, + const int64_t &lowerIndex, + const int64_t &upperIndex) { + auto low = std::next(data.begin(), lowerIndex); + auto up = std::next(data.begin(), upperIndex); + // get new vectors + std::vector newData(low, up); + return newData; +} + +} // namespace + +namespace Mantid { +namespace Algorithms { + +using namespace Kernel; +using namespace API; + +DECLARE_ALGORITHM(CropWorkspaceRagged) + +/// Init function +void CropWorkspaceRagged::init() { + declareProperty(std::make_unique>( + "InputWorkspace", "", Direction::Input), + "The input workspace"); + declareProperty(std::make_unique>( + "OutputWorkspace", "", Direction::Output), + "Name to be given to the cropped workspace."); + + auto required = std::make_shared>>(); + declareProperty(std::make_unique>("XMin", required), + "The value(s) to start the cropping from. Should be either a " + "single value or a list."); + declareProperty(std::make_unique>("XMax", required), + "The value(s) to end the cropping at. Should be either a " + "single value or a list."); +} + +/// Input validation +std::map CropWorkspaceRagged::validateInputs() { + std::map issues; + MatrixWorkspace_sptr ws = getProperty("InputWorkspace"); + auto numSpectra = ws->getNumberHistograms(); + std::vector xMin = getProperty("XMin"); + std::vector xMax = getProperty("XMax"); + if (xMin.size() == 0 || (xMin.size() != numSpectra && xMin.size() > 1)) { + issues["XMin"] = "XMin must be a single value or one value per sepctrum."; + } + if (xMax.size() == 0 || (xMax.size() > 1 && xMax.size() != numSpectra)) { + issues["XMax"] = "XMax must be a single value or one value per sepctrum."; + } + if (xMin.size() == 1 && xMax.size() == 1 && xMin[0] > xMax[0]) { + issues["XMax"] = "XMax must be greater than XMin."; + } else if (xMin.size() == 1 && xMax.size() > 1) { + for (auto max : xMax) { + if (max < xMin[0]) { + issues["XMax"] = "XMax must be greater than XMin."; + return issues; + } + } + } else if (xMin.size() > 1 && xMax.size() == 1) { + for (auto min : xMin) { + if (min > xMax[0]) { + issues["XMin"] = "XMin must be less than XMax."; + return issues; + } + } + } else if (xMin.size() > 1 && xMax.size() > 1) { + for (size_t k = 0; k < xMin.size(); k++) { + if (xMin[k] > xMax[k]) { + issues["XMin"] = "XMin must be less than XMax."; + return issues; + } + } + } + return issues; +} // namespace Algorithms + +/// Exec function +void CropWorkspaceRagged::exec() { + MatrixWorkspace_sptr ws = getProperty("InputWorkspace"); + auto numSpectra = ws->getNumberHistograms(); + // clone ws to copy logs etc. + MatrixWorkspace_sptr outputWS = ws->clone(); + + std::vector xMin = getProperty("XMin"); + std::vector xMax = getProperty("XMax"); + if (xMin.size() == 1) { + auto value = xMin[0]; + xMin.assign(numSpectra, value); + } + if (xMax.size() == 1) { + auto value = xMax[0]; + xMax.assign(numSpectra, value); + } + + // Its easier to work with point data -> index is same for x, y, E + MatrixWorkspace_sptr tmp = outputWS; + bool histogram = false; + if (outputWS->isHistogramData()) { + auto alg = createChildAlgorithm("ConvertToPointData"); + alg->initialize(); + alg->setRethrows(true); + alg->setProperty("InputWorkspace", outputWS); + alg->setProperty("OutputWorkspace", outputWS); + alg->execute(); + tmp = alg->getProperty("OutputWorkspace"); + histogram = true; + } + PARALLEL_FOR_IF(Kernel::threadSafe(*tmp, *outputWS)) + for (int64_t i = 0; i < int64_t(numSpectra); ++i) { + PARALLEL_START_INTERUPT_REGION + auto points = tmp->points(i); + auto &dataX = outputWS->dataX(i); + auto &dataY = outputWS->dataY(i); + auto &dataE = outputWS->dataE(i); + + // get iterators for cropped region using points + auto low = std::lower_bound(points.begin(), points.end(), xMin[i]); + auto up = std::upper_bound(points.begin(), points.end(), xMax[i]); + // convert to index + int64_t lowerIndex = std::distance(points.begin(), low); + int64_t upperIndex = std::distance(points.begin(), up); + + // get new vectors + std::vector newY = getSubVector(dataY, lowerIndex, upperIndex); + std::vector newE = getSubVector(dataE, lowerIndex, upperIndex); + if (histogram && upperIndex + (size_t)1 <= dataX.size()) { + // the offset adds one to the upper index for histograms + // only use the offset if the end is cropped + upperIndex += 1; + } + std::vector newX = getSubVector(dataX, lowerIndex, upperIndex); + + // resize the data + dataX.resize(newX.size()); + dataY.resize(newY.size()); + dataE.resize(newE.size()); + + // update the data + outputWS->mutableX(i) = std::move(newX); + outputWS->mutableY(i) = std::move(newY); + outputWS->mutableE(i) = std::move(newE); + PARALLEL_END_INTERUPT_REGION + } + PARALLEL_CHECK_INTERUPT_REGION + + setProperty("OutputWorkspace", outputWS); +} + +} // namespace Algorithms +} // namespace Mantid diff --git a/Framework/Algorithms/src/DetectorEfficiencyCor.cpp b/Framework/Algorithms/src/DetectorEfficiencyCor.cpp index 13069ae0536b7c8e5e4ebf13a6902b5e195cbb21..277e18d856e2c23ed897e14ca3b02704b77d8f32 100644 --- a/Framework/Algorithms/src/DetectorEfficiencyCor.cpp +++ b/Framework/Algorithms/src/DetectorEfficiencyCor.cpp @@ -223,7 +223,7 @@ void DetectorEfficiencyCor::correctForEfficiency( const auto &detectorInfo = m_inputWS->detectorInfo(); const auto &spectrumDefinition = spectrumInfo.spectrumDefinition(spectraIn); - for (const auto index : spectrumDefinition) { + for (const auto &index : spectrumDefinition) { const auto detIndex = index.first; const auto &det_member = detectorInfo.detector(detIndex); Parameter_sptr par = diff --git a/Framework/Algorithms/src/FitPeaks.cpp b/Framework/Algorithms/src/FitPeaks.cpp index 30b54c57e0ff3ef2ccc49f8f377bc2348e6d167d..ed63591b05d8a5fa5c5af508f48708c1f644dd7e 100644 --- a/Framework/Algorithms/src/FitPeaks.cpp +++ b/Framework/Algorithms/src/FitPeaks.cpp @@ -11,6 +11,7 @@ #include "MantidAPI/Axis.h" #include "MantidAPI/CompositeFunction.h" #include "MantidAPI/CostFunctionFactory.h" +#include "MantidAPI/FrameworkManager.h" #include "MantidAPI/FuncMinimizerFactory.h" #include "MantidAPI/FunctionFactory.h" #include "MantidAPI/FunctionProperty.h" @@ -21,6 +22,8 @@ #include "MantidDataObjects/TableWorkspace.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidDataObjects/WorkspaceCreation.h" +#include "MantidGeometry/IDetector.h" +#include "MantidGeometry/Instrument/Detector.h" #include "MantidHistogramData/EstimatePolynomial.h" #include "MantidHistogramData/Histogram.h" #include "MantidHistogramData/HistogramBuilder.h" @@ -41,6 +44,7 @@ using namespace Mantid::API; using namespace Mantid::DataObjects; using namespace Mantid::HistogramData; using namespace Mantid::Kernel; +using namespace Mantid::Geometry; using Mantid::HistogramData::Histogram; using namespace std; @@ -259,8 +263,11 @@ void FitPeaks::init() { // properties about fitting range and criteria declareProperty(PropertyNames::START_WKSP_INDEX, EMPTY_INT(), "Starting workspace index for fit"); - declareProperty(PropertyNames::STOP_WKSP_INDEX, EMPTY_INT(), - "Last workspace index to fit (which is included)"); + declareProperty( + PropertyNames::STOP_WKSP_INDEX, EMPTY_INT(), + "Last workspace index to fit (which is included). " + "If a value larger than the workspace index of last spectrum, " + "then the workspace index of last spectrum is used."); // properties about peak positions to fit declareProperty( @@ -514,12 +521,13 @@ void FitPeaks::exec() { // process inputs processInputs(); - // create output workspaces + // create output workspace: fitted peak positions generateOutputPeakPositionWS(); - // generateFittedParametersValueWorkspace(); + // create output workspace: fitted peaks' parameters values generateFittedParametersValueWorkspaces(); + // create output workspace: calculated from fitted peak and background generateCalculatedPeaksWS(); // fit peaks @@ -791,7 +799,7 @@ void FitPeaks::processInputPeakCenters() { API::MatrixWorkspace_const_sptr peakcenterws = getProperty(PropertyNames::PEAK_CENTERS_WKSP); if (!peakcenterws) - g_log.error("There is no peak center workspace"); + g_log.notice("Peak centers are not specified by peak center workspace"); std::string peakpswsname = getPropertyValue(PropertyNames::PEAK_CENTERS_WKSP); if ((!m_peakCenters.empty()) && peakcenterws == nullptr) { @@ -805,9 +813,9 @@ void FitPeaks::processInputPeakCenters() { m_peakCenterWorkspace = getProperty(PropertyNames::PEAK_CENTERS_WKSP); // number of peaks to fit! m_numPeaksToFit = m_peakCenterWorkspace->x(0).size(); - g_log.warning() << "Input peak center workspace: " - << m_peakCenterWorkspace->x(0).size() << ", " - << m_peakCenterWorkspace->y(0).size() << "\n"; + g_log.debug() << "Input peak center workspace: " + << m_peakCenterWorkspace->x(0).size() << ", " + << m_peakCenterWorkspace->y(0).size() << "\n"; // check matrix worksapce for peak positions const size_t peak_center_ws_spectra_number = @@ -938,37 +946,46 @@ FitPeaks::fitPeaks() { std::vector> fit_result_vector(num_fit_result); + const int nThreads = FrameworkManager::Instance().getNumOMPThreads(); + size_t chunkSize = num_fit_result / nThreads; + // cppcheck-suppress syntaxError PRAGMA_OMP(parallel for schedule(dynamic, 1) ) - for (auto wi = static_cast(m_startWorkspaceIndex); - wi <= static_cast(m_stopWorkspaceIndex); ++wi) { - + for (int ithread = 0; ithread < nThreads; ithread++) { PARALLEL_START_INTERUPT_REGION - - // peaks to fit - std::vector expected_peak_centers = - getExpectedPeakPositions(static_cast(wi)); - - // initialize output for this - size_t numfuncparams = - m_peakFunction->nParams() + m_bkgdFunction->nParams(); - std::shared_ptr fit_result = - std::make_shared(m_numPeaksToFit, - numfuncparams); - - fitSpectrumPeaks(static_cast(wi), expected_peak_centers, - fit_result); - - PARALLEL_CRITICAL(FindPeaks_WriteOutput) { - writeFitResult(static_cast(wi), expected_peak_centers, - fit_result); - fit_result_vector[wi - m_startWorkspaceIndex] = fit_result; + auto iws_begin = + m_startWorkspaceIndex + chunkSize * static_cast(ithread); + auto iws_end = (ithread == nThreads - 1) ? m_stopWorkspaceIndex + 1 + : iws_begin + chunkSize; + + // vector to store fit params for last good fit to each peak + std::vector> lastGoodPeakParameters( + m_numPeaksToFit, std::vector(m_peakFunction->nParams(), 0.0)); + + for (auto wi = iws_begin; wi < iws_end; ++wi) { + // peaks to fit + std::vector expected_peak_centers = + getExpectedPeakPositions(static_cast(wi)); + + // initialize output for this + size_t numfuncparams = + m_peakFunction->nParams() + m_bkgdFunction->nParams(); + std::shared_ptr fit_result = + std::make_shared(m_numPeaksToFit, + numfuncparams); + + fitSpectrumPeaks(static_cast(wi), expected_peak_centers, + fit_result, lastGoodPeakParameters); + + PARALLEL_CRITICAL(FindPeaks_WriteOutput) { + writeFitResult(static_cast(wi), expected_peak_centers, + fit_result); + fit_result_vector[wi - m_startWorkspaceIndex] = fit_result; + } + prog.report(); } - prog.report(); - PARALLEL_END_INTERUPT_REGION } - PARALLEL_CHECK_INTERUPT_REGION return fit_result_vector; @@ -976,8 +993,8 @@ FitPeaks::fitPeaks() { namespace { /// Supported peak profiles for observation -std::vector supported_peak_profiles{"Gaussian", "Lorentzian", - "PseudoVoigt", "Voigt"}; +std::vector supported_peak_profiles{ + "Gaussian", "Lorentzian", "PseudoVoigt", "Voigt", "BackToBackExponential"}; double numberCounts(const Histogram &histogram) { double total = 0.; @@ -1025,7 +1042,9 @@ double numberCounts(const Histogram &histogram, const double xmin, */ void FitPeaks::fitSpectrumPeaks( size_t wi, const std::vector &expected_peak_centers, - const std::shared_ptr &fit_result) { + const std::shared_ptr &fit_result, + std::vector> &lastGoodPeakParameters) { + // Spectrum contains very weak signal: do not proceed and return if (numberCounts(m_inputMatrixWS->histogram(wi)) <= m_minPeakHeight) { for (size_t i = 0; i < fit_result->getNumberPeaks(); ++i) fit_result->setBadRecord(i, -1.); @@ -1055,12 +1074,13 @@ void FitPeaks::fitSpectrumPeaks( peak_fitter->setProperty("CostFunction", m_costFunction); peak_fitter->setProperty("CalcErrors", true); - // store the peak fit parameters once one works - bool foundAnyPeak = false; - std::vector lastGoodPeakParameters(peakfunction->nParams(), 0.); - const double x0 = m_inputMatrixWS->histogram(wi).x().front(); const double xf = m_inputMatrixWS->histogram(wi).x().back(); + + // Copy result from last fitting and set flag + std::vector localPrevGoodResults(m_peakFunction->nParams(), 0.0); + bool neighborPeakSameSpectrum = false; + for (size_t fit_index = 0; fit_index < m_numPeaksToFit; ++fit_index) { // convert fit index to peak index (in ascending order) size_t peak_index(fit_index); @@ -1071,11 +1091,77 @@ void FitPeaks::fitSpectrumPeaks( for (size_t i = 0; i < bkgdfunction->nParams(); ++i) bkgdfunction->setParameter(0, 0.); - // set the peak parameters from last good fit - override peak center - for (size_t i = 0; i < lastGoodPeakParameters.size(); ++i) - peakfunction->setParameter(i, lastGoodPeakParameters[i]); double expected_peak_pos = expected_peak_centers[peak_index]; - peakfunction->setCentre(expected_peak_pos); + + // Determine how to set the starting value parameters + + // Determine whether to set starting parameter from fitted value + // of same peak but different spectrum + bool samePeakCrossSpectrum = + (lastGoodPeakParameters[peak_index].size() > + static_cast( + std::count_if(lastGoodPeakParameters[peak_index].begin(), + lastGoodPeakParameters[peak_index].end(), + [&](auto const &val) { return val <= 1e-10; }))); + // Check whether current spectrum's pixel (detector ID) is close to its + // previous spectrum's pixel (detector ID). + try { + if (wi > 0 && samePeakCrossSpectrum) { + // First spectrum or discontinuous detector ID: do not start from same + // peak of last spectrum + std::shared_ptr pdetector = + std::dynamic_pointer_cast( + m_inputMatrixWS->getDetector(wi - 1)); + std::shared_ptr cdetector = + std::dynamic_pointer_cast( + m_inputMatrixWS->getDetector(wi)); + + // If they do have detector ID + if (pdetector && cdetector) { + auto prev_id = pdetector->getID(); + auto curr_id = cdetector->getID(); + if (prev_id + 1 != curr_id) + samePeakCrossSpectrum = false; + } else { + samePeakCrossSpectrum = false; + } + + } else { + // first spectrum in the workspace: no peak's fitting result to copy + // from + samePeakCrossSpectrum = false; + } + } catch (const std::runtime_error &) { + // workspace does not have detector ID set: there is no guarantee that the + // adjacent spectra can have similar peak profiles + samePeakCrossSpectrum = false; + } + + // Set starting values of the peak function + if (samePeakCrossSpectrum) { // somePeakFit + // Get from local best result + for (size_t i = 0; i < lastGoodPeakParameters[peak_index].size(); ++i) { + peakfunction->setParameter(i, lastGoodPeakParameters[peak_index][i]); + } + + // reset center though - don't know before hand which element this is + peakfunction->setCentre(expected_peak_pos); + + } else if (neighborPeakSameSpectrum) { + // set the peak parameters from last good fit to that peak + for (size_t i = 0; i < localPrevGoodResults.size(); ++i) { + peakfunction->setParameter(i, localPrevGoodResults[i]); + } + + // reset center though - don't know before hand which element this is + peakfunction->setCentre(expected_peak_pos); + } else { + // set matrix workspace so reads necessary params from .xml file after + // need to set center first + peakfunction->setCentre(expected_peak_pos); + peakfunction->setMatrixWorkspace(m_inputMatrixWS, wi, 0.0, 0.0); + peakfunction->clearTies(); // let S,A,B of B2B exp be refined + } double cost(DBL_MAX); if (expected_peak_pos <= x0 || expected_peak_pos >= xf) { @@ -1086,10 +1172,17 @@ void FitPeaks::fitSpectrumPeaks( std::pair peak_window_i = getPeakFitWindow(wi, peak_index); - bool observe_peak_params = - decideToEstimatePeakParams(!foundAnyPeak, peakfunction); + // Decide whether to estimate peak width by observation + bool observe_peak_width = + decideToEstimatePeakParams(!samePeakCrossSpectrum, peakfunction); + // + if (peakfunction->name() == "BackToBackExponential") { + if (neighborPeakSameSpectrum) { + observe_peak_width = false; + } + } - if (observe_peak_params && + if (observe_peak_width && m_peakWidthEstimateApproach == EstimatePeakWidth::NoEstimation) { g_log.warning( "Peak width can be estimated as ZERO. The result can be wrong"); @@ -1099,14 +1192,17 @@ void FitPeaks::fitSpectrumPeaks( // point) cost = fitIndividualPeak(wi, peak_fitter, expected_peak_pos, peak_window_i, - observe_peak_params, peakfunction, bkgdfunction); + observe_peak_width, peakfunction, bkgdfunction); if (cost < 1e7) { // assume it worked and save out the result - foundAnyPeak = true; - for (size_t i = 0; i < lastGoodPeakParameters.size(); ++i) - lastGoodPeakParameters[i] = peakfunction->getParameter(i); + // reset the flag such that there is at a peak fit in this spectrum + neighborPeakSameSpectrum = true; + // copy values + for (size_t i = 0; i < lastGoodPeakParameters[peak_index].size(); ++i) { + lastGoodPeakParameters[peak_index][i] = peakfunction->getParameter(i); + localPrevGoodResults[i] = peakfunction->getParameter(i); + } } } - foundAnyPeak = true; // process fitting result FitPeaksAlgorithm::FitFunction fit_function; @@ -1169,8 +1265,9 @@ bool FitPeaks::decideToEstimatePeakParams( * @param fitfunction :: pointer to function to retrieve information from * @param fit_result :: (output) PeakFitResult instance to set the fitting * result to + * @return :: whether the peak fiting is good or not */ -void FitPeaks::processSinglePeakFitResult( +bool FitPeaks::processSinglePeakFitResult( size_t wsindex, size_t peakindex, const double cost, const std::vector &expected_peak_positions, const FitPeaksAlgorithm::FitFunction &fitfunction, @@ -1269,7 +1366,7 @@ void FitPeaks::processSinglePeakFitResult( // chi2 fit_result->setRecord(peakindex, adjust_cost, peak_pos, fitfunction); - return; + return good_fit; } //---------------------------------------------------------------------------------------------- @@ -1292,7 +1389,6 @@ void FitPeaks::calculateFittedPeaks( for (auto iws = static_cast(m_startWorkspaceIndex); iws <= static_cast(m_stopWorkspaceIndex); ++iws) { PARALLEL_START_INTERUPT_REGION - // get a copy of peak function and background function IPeakFunction_sptr peak_function = std::dynamic_pointer_cast(m_peakFunction->clone()); @@ -1318,10 +1414,9 @@ void FitPeaks::calculateFittedPeaks( bkgd_function->setParameter(iparam, fit_result_i->getParameterValue( ipeak, num_peakfunc_params + iparam)); - // use domain and function to calcualte // get the range of start and stop to construct a function domain - const auto &vec_x = m_fittedPeakWS->x(static_cast(iws)); + const auto &vec_x = m_fittedPeakWS->points(static_cast(iws)); std::pair peakwindow = getPeakFitWindow(static_cast(iws), ipeak); auto start_x_iter = @@ -1343,9 +1438,10 @@ void FitPeaks::calculateFittedPeaks( // copy over the values size_t istart = static_cast(start_x_iter - vec_x.begin()); size_t istop = static_cast(stop_x_iter - vec_x.begin()); - for (size_t yindex = istart; yindex < istop; ++yindex) + for (size_t yindex = istart; yindex < istop; ++yindex) { m_fittedPeakWS->dataY(static_cast(iws))[yindex] = values.getCalculated(yindex - istart); + } } // END-FOR (ipeak) PARALLEL_END_INTERUPT_REGION } // END-FOR (iws) @@ -1354,49 +1450,6 @@ void FitPeaks::calculateFittedPeaks( return; } -namespace { -// calculate the moments about the mean -vector calculateMomentsAboutMean(const Histogram &histogram, - const double mean, - FunctionValues &bkgd_values, - size_t start_index, - size_t stop_index) { - // Calculate second moment about the mean should be the variance. Assume that - // the peak center is correct - const auto &x_vec = histogram.points(); - const auto &y_vec = histogram.y(); - - const size_t numPoints = - min(start_index - stop_index, bkgd_values.size()) - 1; - - double zeroth = 0.; // total counts - double first = 0.; // peak center - double second = 0.; // variance = sigma * sigma - - // zeroth and first don't require the mean - for (size_t i = 0; i < numPoints; ++i) { - // integrate using Newton's method - const double y_adj = - .5 * (y_vec[start_index + i] + y_vec[start_index + i + 1]) - - .5 * (bkgd_values.getCalculated(i) + bkgd_values.getCalculated(i + 1)); - if (y_adj <= 0.) - continue; // just skip to the next - const double dx = x_vec[start_index + i + 1] - x_vec[start_index + i]; - const double x_adj = x_vec[start_index + i] - mean; - - zeroth += y_adj * dx; - first += x_adj * y_adj * dx; - second += x_adj * x_adj * y_adj * dx; - } - // need to normalize by the total to get the right position because - // method of moments assumes a distribution normalized to one - first = first / zeroth; - second = second / zeroth; - - return vector{zeroth, first, second}; -} -} // namespace - //---------------------------------------------------------------------------------------------- /** Estimate background: There are two methods that will be tried. * First, algorithm FindPeakBackground will be tried; @@ -1477,18 +1530,20 @@ int FitPeaks::estimatePeakParameters( // use values from background to locate FWHM peakfunction->setHeight(peak_height); + // FIXME - there are multiple occasions in FitPeaks that setCentre is called. + // Is there any way to centralize this? peakfunction->setCentre(peak_center); // Estimate FHWM (peak width) if (observe_peak_width && m_peakWidthEstimateApproach != EstimatePeakWidth::NoEstimation) { - double peak_width = observePeakWidth( + double peak_fwhm = observePeakFwhm( histogram, bkgd_values, peak_center_index, start_index, stop_index); - - // proper factor for gaussian - const double CONVERSION = 1.; // 2. * std::sqrt(2.); - if (peak_width > 0.) { // TODO promote to property? - peakfunction->setFwhm(CONVERSION * peak_width); + if (peak_fwhm > 0.0) { + g_log.warning() << "Peak function " << peakfunction->name() + << " still set estimated FWHM" + << "\n"; + peakfunction->setFwhm(peak_fwhm); } } @@ -1574,32 +1629,40 @@ int FitPeaks::observePeakCenter(const Histogram &histogram, * @param istop :: array index for the right boundary of the peak * @return peak width as double */ -double FitPeaks::observePeakWidth(const Histogram &histogram, - FunctionValues &bkgd_values, size_t ipeak, - size_t istart, size_t istop) { - double peak_width(-0.); +double FitPeaks::observePeakFwhm(const Histogram &histogram, + FunctionValues &bkgd_values, size_t ipeak, + size_t istart, size_t istop) { + double peak_fwhm(-0.); if (m_peakWidthEstimateApproach == EstimatePeakWidth::InstrumentResolution) { // width from guessing from delta(D)/D const double peak_center = histogram.points()[ipeak]; - peak_width = peak_center * m_peakWidthPercentage; + peak_fwhm = peak_center * m_peakWidthPercentage; } else if (m_peakWidthEstimateApproach == EstimatePeakWidth::Observation) { - const auto moments = calculateMomentsAboutMean( - histogram, histogram.points()[ipeak], bkgd_values, istart, istop); - - // not enough total weight in range - if (moments[0] < m_minPeakHeight) - peak_width = -1.; // set to bad value - else - peak_width = std::sqrt(moments[2]); - + // estimate fwhm from area assuming Gaussian (more robust than using + // moments which overestimates variance by factor ~5 depending on background + // estimation over window much wider than peak) + const auto &x_vec = histogram.points(); + const auto &y_vec = histogram.y(); + const size_t numPoints = std::min(istart - istop, bkgd_values.size()) - 1; + + double area = 0.0; + // integrate using Newton's method + for (size_t i = 0; i < numPoints; ++i) { + const auto yavg = + 0.5 * (y_vec[istart + i] - bkgd_values.getCalculated(i) + + y_vec[istart + i + 1] - bkgd_values.getCalculated(i + 1)); + const auto dx = x_vec[istart + i + 1] - x_vec[istart + i]; + area += yavg * dx; + } + peak_fwhm = 2 * std::sqrt(M_LN2 / M_PI) * area / y_vec[ipeak]; } else { // get from last peak or from input! throw std::runtime_error( "This case for observing peak width is not supported."); } - return peak_width; + return peak_fwhm; } //---------------------------------------------------------------------------------------------- @@ -1660,7 +1723,7 @@ double FitPeaks::fitIndividualPeak(size_t wi, const API::IAlgorithm_sptr &fitter, const double expected_peak_center, const std::pair &fitwindow, - const bool observe_peak_params, + const bool estimate_peak_width, const API::IPeakFunction_sptr &peakfunction, const API::IBackgroundFunction_sptr &bkgdfunc) { double cost(DBL_MAX); @@ -1674,12 +1737,12 @@ FitPeaks::fitIndividualPeak(size_t wi, const API::IAlgorithm_sptr &fitter, // fit peak with high background! cost = fitFunctionHighBackground(fitter, fitwindow, wi, expected_peak_center, - observe_peak_params, peakfunction, bkgdfunc); + estimate_peak_width, peakfunction, bkgdfunc); } else { // fit peak and background cost = fitFunctionSD(fitter, peakfunction, bkgdfunc, m_inputMatrixWS, wi, fitwindow.first, fitwindow.second, - expected_peak_center, observe_peak_params, true); + expected_peak_center, estimate_peak_width, true); } return cost; @@ -1695,7 +1758,7 @@ double FitPeaks::fitFunctionSD( const IAlgorithm_sptr &fit, const API::IPeakFunction_sptr &peak_function, const API::IBackgroundFunction_sptr &bkgd_function, const API::MatrixWorkspace_sptr &dataws, size_t wsindex, double xmin, - double xmax, const double &expected_peak_center, bool observe_peak_shape, + double xmax, const double &expected_peak_center, bool estimate_peak_width, bool estimate_background) { std::stringstream errorid; errorid << "(WorkspaceIndex=" << wsindex @@ -1714,7 +1777,7 @@ double FitPeaks::fitFunctionSD( // Estimate peak profile parameter peak_function->setCentre(expected_peak_center); // set expected position first int result = estimatePeakParameters(histogram, peak_window, peak_function, - bkgd_function, observe_peak_shape); + bkgd_function, estimate_peak_width); if (result != GOOD) { peak_function->setCentre(expected_peak_center); if (result == NOSIGNAL || result == LOWPEAK) { diff --git a/Framework/Algorithms/src/MaskBinsFromWorkspace.cpp b/Framework/Algorithms/src/MaskBinsFromWorkspace.cpp index 4939fa7cee3793d05950a347befbdae7993f56f7..40d611b52f56f806328de41b8396c5f6afe8d03e 100644 --- a/Framework/Algorithms/src/MaskBinsFromWorkspace.cpp +++ b/Framework/Algorithms/src/MaskBinsFromWorkspace.cpp @@ -53,7 +53,7 @@ void MaskBinsFromWorkspace::exec() { // in the input workspace if (maskedWS->hasMaskedBins(0)) { const auto maskedBins = maskedWS->maskedBins(0); - for (const auto &wi : m_indexSet) { + for (const auto wi : m_indexSet) { for (const auto &maskedBin : maskedBins) { outputWS->flagMasked(wi, maskedBin.first, maskedBin.second); } diff --git a/Framework/Algorithms/src/PDCalibration.cpp b/Framework/Algorithms/src/PDCalibration.cpp index 23ca373d4107733adc447850d25b109040435dcb..ce76071512dbd111c7eea8eb75d06b0cfa24f442 100644 --- a/Framework/Algorithms/src/PDCalibration.cpp +++ b/Framework/Algorithms/src/PDCalibration.cpp @@ -10,6 +10,7 @@ #include "MantidAPI/IEventList.h" #include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/Run.h" +#include "MantidAPI/SpectrumInfo.h" #include "MantidAPI/TableRow.h" #include "MantidAPI/WorkspaceGroup.h" #include "MantidDataObjects/EventWorkspace.h" @@ -70,42 +71,55 @@ const auto isNonZero = [](const double value) { return value != 0.; }; /// private inner class class PDCalibration::FittedPeaks { public: + /** + * Find the bins with non-zero counts and with minimum and maximum TOF + * + * @param wksp :: Input signal workspace + * @param wkspIndex :: workspace index + * + * @throws runtime_error :: more than one detector is associated to the + * workspace index + */ FittedPeaks(const API::MatrixWorkspace_const_sptr &wksp, const std::size_t wkspIndex) { this->wkspIndex = wkspIndex; // convert workspace index into detector id const auto &spectrum = wksp->getSpectrum(wkspIndex); - const auto &detIds = spectrum.getDetectorIDs(); - if (detIds.size() != 1) { - throw std::runtime_error("Summed pixels is not currently supported"); - } - this->detid = *(detIds.begin()); + this->detid = spectrum.getDetectorIDs(); const auto &X = spectrum.x(); const auto &Y = spectrum.y(); tofMin = X.front(); tofMax = X.back(); - // determine tof min supported by the workspace + // determine tof min supported by the workspace (non-zero counts) size_t minIndex = 0; // want to store value for (; minIndex < Y.size(); ++minIndex) { if (isNonZero(Y[minIndex])) { tofMin = X[minIndex]; - break; + break; // we found the first bin with non-zero counts } } - // determine tof max supported by the workspace + // determine tof max supported by the workspace (non-zero counts) size_t maxIndex = Y.size() - 1; for (; maxIndex > minIndex; --maxIndex) { if (isNonZero(Y[maxIndex])) { tofMax = X[maxIndex]; - break; + break; // we found the last bin with non-zero counts } } } + /** + * Store the positions of the peak centers, as well as the left and right + * fit ranges for each peak, in TOF units + * + * @param peaksInD :: peak centers, in d-spacing + * @param peaksInDWindows :: left and right fit ranges for each peak + * @param toTof :: function converting from d-spacing to TOF + */ void setPositions(const std::vector &peaksInD, const std::vector &peaksInDWindows, const std::function &toTof) { @@ -126,12 +140,13 @@ public: } std::size_t wkspIndex; - detid_t detid; - double tofMin; - double tofMax; - std::vector inTofPos; + std::set detid; + double tofMin; // TOF of bin with minimum TOF and non-zero counts + double tofMax; // TOF of bin with maximum TOF and non-zero counts + std::vector inTofPos; // peak centers, in TOF + // left and right fit ranges for each peak center, in TOF std::vector inTofWindows; - std::vector inDPos; + std::vector inDPos; // peak centers, in d-spacing }; //---------------------------------------------------------------------------------------------- @@ -210,16 +225,15 @@ void PDCalibration::init() { mustBePositive->setLower(0.0); declareProperty( "PeakWindow", 0.1, mustBePositive, - "The maximum window (in d space) around peak to look for peak."); + "The maximum window (in d -pace) to the left and right of the " + "nominal peak center to look for the peak"); std::vector modes{"DIFC", "DIFC+TZERO", "DIFC+TZERO+DIFA"}; auto min = std::make_shared>(); min->setLower(1e-3); declareProperty("PeakWidthPercent", EMPTY_DBL(), min, - "The estimated peak width as a " - "percentage of the d-spacing " - "of the center of the peak. This is the same as the width in " - "time-of-flight."); + "The estimated peak width as a percent of the peak" + "center value, in d-spacing or TOF units."); declareProperty("MinimumPeakHeight", 2., "Minimum peak height such that all the fitted peaks with " @@ -231,9 +245,9 @@ void PDCalibration::init() { declareProperty( "ConstrainPeakPositions", false, - "If true peak position will be constrained by estimated positions " + "If true, peak centers will be constrained by estimated positions " "(highest Y value position) and " - "the peak width either estimted by observation or calculate."); + "the peak width will either be estimated by observation or calculated."); declareProperty("CalibrationParameters", "DIFC", std::make_shared(modes), @@ -242,12 +256,14 @@ void PDCalibration::init() { declareProperty( std::make_unique>("TZEROrange"), "Range for allowable TZERO from calibration (default is all)"); + declareProperty(std::make_unique>("DIFArange"), - "Range for allowable DIFA from calibration (default is all)"); + "Range for allowable DIFA from calibration (default " + "is all)"); declareProperty(std::make_unique>( "OutputCalibrationTable", "", Direction::Output), - "An output workspace containing the Calibration Table"); + "Output table workspace containing the calibration"); declareProperty(std::make_unique>( "DiagnosticWorkspaces", "", Direction::Output), @@ -306,14 +322,22 @@ std::map PDCalibration::validateInputs() { namespace { +/// @return ``true`` if an input table contains a column termed "dasid" bool hasDasIDs(const API::ITableWorkspace_const_sptr &table) { const auto columnNames = table->getColumnNames(); return (std::find(columnNames.begin(), columnNames.end(), std::string("dasid")) != columnNames.end()); } -/// @return Conversion factor or 1. if it is unknown +/** + * Conversion factor between fit-function width and FWHM. Factors are calculated + * for "Gaussian" and "Lorentzian" functions. For other functions, a value + * of 1.0 is returned + * + * @param peakshape :: name of the fitting function + */ double getWidthToFWHM(const std::string &peakshape) { + // could we use actual peak function here? if (peakshape == "Gaussian") { return 2 * std::sqrt(2. * std::log(2.)); } else if (peakshape == "Lorentzian") { @@ -368,7 +392,7 @@ void PDCalibration::exec() { } m_peaksInDspacing = getProperty("PeakPositions"); - // Sort peak positions, requried for correct peak window calculations + // Sort peak positions, required for correct peak window calculations std::sort(m_peaksInDspacing.begin(), m_peaksInDspacing.end()); const double peakWindowMaxInDSpacing = getProperty("PeakWindow"); @@ -400,10 +424,11 @@ void PDCalibration::exec() { .empty())) { //"PreviousCalibrationTable" createCalTableFromExisting(); } else { - createCalTableNew(); + createCalTableNew(); // calculates "difc" values from instrument geometry } createInformationWorkspaces(); + // Initialize the mask workspace, will all detectors masked by default std::string maskWSName = getPropertyValue("OutputCalibrationTable"); maskWSName += "_mask"; declareProperty(std::make_unique>( @@ -425,7 +450,9 @@ void PDCalibration::exec() { } auto NUMHIST = static_cast(m_uncalibratedWS->getNumberHistograms()); - // create TOF peak centers workspace + // A pair of workspaces, one containing the nominal peak centers in TOF units, + // the other containing the left and right fitting ranges around each nominal + // peak center, also in TOF units. This for each pixel of the instrument auto matrix_pair = createTOFPeakCenterFitWindowWorkspaces( m_uncalibratedWS, peakWindowMaxInDSpacing); API::MatrixWorkspace_sptr tof_peak_center_ws = matrix_pair.first; @@ -438,6 +465,8 @@ void PDCalibration::exec() { const std::string diagnostic_prefix = getPropertyValue("DiagnosticWorkspaces"); + // Refine the position of the peak centers starting from the nominal peak + // centers and fitting them against a peak fit function (e.g. a Gaussian) auto algFitPeaks = createChildAlgorithm("FitPeaks", .2, .7); algFitPeaks->setLoggingOffset(3); @@ -468,8 +497,20 @@ void PDCalibration::exec() { algFitPeaks->setProperty("RawPeakParameters", false); // Analysis output + // If using a Gaussian peak shape plus a constant background, then + // OutputPeakParametersWorkspace is a table with columns: + // wsindex_0 peakindex_0 centre width height intensity A0 chi2 + // ... + // wsindex_0 peakindex_N centre width height intensity A0 chi2 + // ... + // ... + // wsindex_M peakindex_N centre width height intensity algFitPeaks->setPropertyValue("OutputPeakParametersWorkspace", diagnostic_prefix + "_fitparam"); + // contains the same intensities as input m_uncalibratedWS except within + // the fitting range of each successfully fitted peak. Within this range, + // the actual intensities are replaced with the values resulting from + // evaluating the peak function (e.g. a Gaussian peak function) algFitPeaks->setPropertyValue("FittedPeaksWorkspace", diagnostic_prefix + "_fitted"); @@ -496,172 +537,207 @@ void PDCalibration::exec() { API::Progress prog(this, 0.7, 1.0, NUMHIST); + // calculate fitting ranges to the left and right of each nominal peak + // center, in d-spacing units const auto windowsInDSpacing = dSpacingWindows(m_peaksInDspacing, peakWindowMaxInDSpacing); - // cppcheck-suppress syntaxError - PRAGMA_OMP(parallel for schedule(dynamic, 1) ) - for (int wkspIndex = 0; wkspIndex < NUMHIST; ++wkspIndex) { - PARALLEL_START_INTERUPT_REGION - if (isEvent && uncalibratedEWS->getSpectrum(wkspIndex).empty()) { - prog.report(); - continue; - } - - // object to hold the information about the peak positions, detid, and wksp - // index - PDCalibration::FittedPeaks peaks(m_uncalibratedWS, wkspIndex); - auto toTof = getDSpacingToTof(peaks.detid); - peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof); - - // includes peaks that aren't used in the fit - const size_t numPeaks = m_peaksInDspacing.size(); - std::vector tof_vec_full(numPeaks, std::nan("")); - std::vector d_vec; - std::vector tof_vec; - std::vector width_vec_full(numPeaks, std::nan("")); - std::vector height_vec_full(numPeaks, std::nan("")); - std::vector height2; // the square of the peak height - // for (size_t i = 0; i < fittedTable->rowCount(); ++i) { - const size_t rowNumInFitTableOffset = wkspIndex * numPeaks; - for (size_t peakIndex = 0; peakIndex < numPeaks; ++peakIndex) { - size_t rowIndexInFitTable = rowNumInFitTableOffset + peakIndex; - - // check indices in PeaksTable - if (fittedTable->getRef("wsindex", rowIndexInFitTable) != wkspIndex) - throw std::runtime_error("workspace index mismatch!"); - if (fittedTable->getRef("peakindex", rowIndexInFitTable) != - static_cast(peakIndex)) - throw std::runtime_error( - "peak index mismatch but workspace index matched"); - - // get the effective peak parameters - const double centre = - fittedTable->getRef("centre", rowIndexInFitTable); - const double width = - fittedTable->getRef("width", rowIndexInFitTable); - const double height = - fittedTable->getRef("height", rowIndexInFitTable); - const double chi2 = - fittedTable->getRef("chi2", rowIndexInFitTable); - - // check chi-square - if (chi2 > maxChiSquared || chi2 < 0.) { - continue; - } - - // rule out of peak with wrong position - if (peaks.inTofWindows[2 * peakIndex] >= centre || - peaks.inTofWindows[2 * peakIndex + 1] <= centre) { - continue; - } - - // check height: make sure 0 is smaller than 0 - if (height < minPeakHeight + 1.E-15) { - continue; - } + // get spectrum info to check workspace index correpsonds to a valid spectrum + const auto &spectrumInfo = m_uncalibratedWS->spectrumInfo(); - // background value - double back_intercept = - fittedTable->getRef("A0", rowIndexInFitTable); - double back_slope = 0.; - double back_quad = 0.; - switch (backgroundType[0]) { - case 'Q': // Quadratic - back_quad = fittedTable->getRef( - "A2", rowIndexInFitTable); // fall through - case 'L': // Linear - back_slope = fittedTable->getRef("A1", rowIndexInFitTable); - } - double background = - back_intercept + back_slope * centre + back_quad * centre * centre; - - // ban peaks that are not outside of error bars for the background - if (height < 0.5 * std::sqrt(height + background)) { - continue; - } - d_vec.emplace_back(m_peaksInDspacing[peakIndex]); - tof_vec.emplace_back(centre); - height2.emplace_back(height * height); - tof_vec_full[peakIndex] = centre; - width_vec_full[peakIndex] = width; - height_vec_full[peakIndex] = height; - } - - maskWS->setMasked(peaks.detid, d_vec.size() < 2); - if (d_vec.size() < 2) { // not enough peaks were found - continue; - } else { - double difc = 0., t0 = 0., difa = 0.; - fitDIFCtZeroDIFA_LM(d_vec, tof_vec, height2, difc, t0, difa); - - const auto rowIndexOutputPeaks = m_detidToRow[peaks.detid]; - double chisq = 0.; - auto converter = - Kernel::Diffraction::getTofToDConversionFunc(difc, difa, t0); - for (std::size_t i = 0; i < numPeaks; ++i) { - if (std::isnan(tof_vec_full[i])) - continue; - const double dspacing = converter(tof_vec_full[i]); - const double temp = m_peaksInDspacing[i] - dspacing; - chisq += (temp * temp); - m_peakPositionTable->cell(rowIndexOutputPeaks, i + 1) = - dspacing; - m_peakWidthTable->cell(rowIndexOutputPeaks, i + 1) = - WIDTH_TO_FWHM * converter(width_vec_full[i]); - m_peakHeightTable->cell(rowIndexOutputPeaks, i + 1) = - height_vec_full[i]; - } - m_peakPositionTable->cell(rowIndexOutputPeaks, - m_peaksInDspacing.size() + 1) = chisq; - m_peakPositionTable->cell(rowIndexOutputPeaks, - m_peaksInDspacing.size() + 2) = - chisq / static_cast(numPeaks - 1); - - setCalibrationValues(peaks.detid, difc, difa, t0); - } - prog.report(); - - PARALLEL_END_INTERUPT_REGION - } - PARALLEL_CHECK_INTERUPT_REGION - - // sort the calibration workspaces - m_calibrationTable = sortTableWorkspace(m_calibrationTable); - setProperty("OutputCalibrationTable", m_calibrationTable); - - // fix-up the diagnostic workspaces - m_calibrationTable = sortTableWorkspace(m_peakPositionTable); - m_calibrationTable = sortTableWorkspace(m_peakWidthTable); - m_calibrationTable = sortTableWorkspace(m_peakHeightTable); - - // a derived table from the position and width - auto resolutionWksp = calculateResolutionTable(); - - // set the diagnostic workspaces out - auto diagnosticGroup = std::make_shared(); - // add workspaces calculated by FitPeaks - API::AnalysisDataService::Instance().addOrReplace( - diagnostic_prefix + "_fitparam", fittedTable); - diagnosticGroup->addWorkspace(fittedTable); - API::AnalysisDataService::Instance().addOrReplace( - diagnostic_prefix + "_fitted", calculatedWS); - diagnosticGroup->addWorkspace(calculatedWS); - - // add workspaces calculated by PDCalibration - API::AnalysisDataService::Instance().addOrReplace( - diagnostic_prefix + "_dspacing", m_peakPositionTable); - diagnosticGroup->addWorkspace(m_peakPositionTable); - API::AnalysisDataService::Instance().addOrReplace( - diagnostic_prefix + "_width", m_peakWidthTable); - diagnosticGroup->addWorkspace(m_peakWidthTable); - API::AnalysisDataService::Instance().addOrReplace( - diagnostic_prefix + "_height", m_peakHeightTable); - diagnosticGroup->addWorkspace(m_peakHeightTable); - API::AnalysisDataService::Instance().addOrReplace( - diagnostic_prefix + "_resolution", resolutionWksp); - diagnosticGroup->addWorkspace(resolutionWksp); - setProperty("DiagnosticWorkspaces", diagnosticGroup); + // Scan the table containing the fit parameters for every peak, retrieve the + // parameters for peaks that were successfully fitting, then use this info + // to obtain difc, difa, and tzero for each pixel + // cppcheck-suppress syntaxError + PRAGMA_OMP(parallel for schedule(dynamic, 1)) + for (int wkspIndex = 0; wkspIndex < NUMHIST; ++wkspIndex) { + PARALLEL_START_INTERUPT_REGION + if ((isEvent && uncalibratedEWS->getSpectrum(wkspIndex).empty()) || + !spectrumInfo.hasDetectors(wkspIndex) || + spectrumInfo.isMonitor(wkspIndex)) { + prog.report(); + continue; + } + + // object to hold the information about the peak positions, detid, and wksp + // index + PDCalibration::FittedPeaks peaks(m_uncalibratedWS, wkspIndex); + auto toTof = getDSpacingToTof(peaks.detid); + peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof); + + // includes peaks that aren't used in the fit + // The following data structures will hold information for the peaks + // found in the current spectrum + const size_t numPeaks = m_peaksInDspacing.size(); + // TOF of fitted peak centers, default `nan` for failed fitted peaks + std::vector tof_vec_full(numPeaks, std::nan("")); + std::vector d_vec; // nominal peak centers of fitted peaks + std::vector tof_vec; // TOF of fitted peak centers only + // width of fitted peak centers, default `nan` for failed fitted peaks + std::vector width_vec_full(numPeaks, std::nan("")); + // height of fitted peak centers, default `nan` for failed fitted peaks + std::vector height_vec_full(numPeaks, std::nan("")); + std::vector height2; // the square of the peak height + // for (size_t i = 0; i < fittedTable->rowCount(); ++i) { + const size_t rowNumInFitTableOffset = wkspIndex * numPeaks; + // We assumed that the current spectrum contains peaks near the nominal + // peak centers. Now we check how many peaks we actually found + for (size_t peakIndex = 0; peakIndex < numPeaks; ++peakIndex) { + size_t rowIndexInFitTable = rowNumInFitTableOffset + peakIndex; + + // check indices in PeaksTable + if (fittedTable->getRef("wsindex", rowIndexInFitTable) != wkspIndex) + throw std::runtime_error("workspace index mismatch!"); + if (fittedTable->getRef("peakindex", rowIndexInFitTable) != + static_cast(peakIndex)) + throw std::runtime_error( + "peak index mismatch but workspace index matched"); + + // get the effective peak parameters + const double centre = + fittedTable->getRef("centre", rowIndexInFitTable); + const double width = + fittedTable->getRef("width", rowIndexInFitTable); + const double height = + fittedTable->getRef("height", rowIndexInFitTable); + const double chi2 = + fittedTable->getRef("chi2", rowIndexInFitTable); + + // check chi-square + if (chi2 > maxChiSquared || chi2 < 0.) { + continue; // peak fit deemed as failure + } + + // rule out of peak with wrong position. `centre` should be within its + // left and right window ranges + if (peaks.inTofWindows[2 * peakIndex] >= centre || + peaks.inTofWindows[2 * peakIndex + 1] <= centre) { + continue; // peak fit deemed as failure + } + + // check height: make sure 0 is smaller than 0 + if (height < minPeakHeight + 1.E-15) { + continue; // peak fit deemed as failure + } + + // background value at the fitted peak center + double back_intercept = + fittedTable->getRef("A0", rowIndexInFitTable); + double back_slope = 0.; + double back_quad = 0.; + switch (backgroundType[0]) { + case 'Q': // Quadratic + back_quad = fittedTable->getRef( + "A2", rowIndexInFitTable); // fall through + case 'L': // Linear + back_slope = fittedTable->getRef("A1", rowIndexInFitTable); + } + double background = + back_intercept + back_slope * centre + back_quad * centre * centre; + + // ban peaks that are not outside of error bars for the background + if (height < 0.5 * std::sqrt(height + background)) { + continue; // peak fit deemed as failure + } + // the peak fit was a success. Collect info + d_vec.emplace_back(m_peaksInDspacing[peakIndex]); + tof_vec.emplace_back(centre); + height2.emplace_back(height * height); + tof_vec_full[peakIndex] = centre; + width_vec_full[peakIndex] = width; + height_vec_full[peakIndex] = height; + } + + // mask a detector if less than two peaks were fitted successfully + maskWS->setMasked(peaks.detid, d_vec.size() < 2); + + if (d_vec.size() < 2) { // not enough peaks were found + continue; + } else { + // obtain difc, difa, and t0 by fitting the nominal peak center + // positions, in d-spacing against the fitted peak center positions, in + // TOF units. + double difc = 0., t0 = 0., difa = 0.; + fitDIFCtZeroDIFA_LM(d_vec, tof_vec, height2, difc, t0, difa); + for (auto iter = peaks.detid.begin(); iter != peaks.detid.end(); + ++iter) { + auto det = *iter; + const auto rowIndexOutputPeaks = m_detidToRow[det]; + // chisq represent the deviations between the nominal peak positions + // and the peak positions using the GSAS formula with optimized difc, + // difa, and tzero + double chisq = 0.; + // `converter` if a function that returns a d-spacing for an input TOF + auto converter = + Kernel::Diffraction::getTofToDConversionFunc(difc, difa, t0); + for (std::size_t i = 0; i < numPeaks; ++i) { + if (std::isnan(tof_vec_full[i])) + continue; + // Find d-spacing using the GSAS formula with optimized difc, difa, + // t0 for the TOF of the current peak's center. + const double dspacing = converter(tof_vec_full[i]); + // `temp` is residual between the nominal position in d-spacing for + // the current peak, and the fitted position in d-spacing + const double temp = m_peaksInDspacing[i] - dspacing; + chisq += (temp * temp); + m_peakPositionTable->cell(rowIndexOutputPeaks, i + 1) = + dspacing; + m_peakWidthTable->cell(rowIndexOutputPeaks, i + 1) = + WIDTH_TO_FWHM * converter(width_vec_full[i]); + m_peakHeightTable->cell(rowIndexOutputPeaks, i + 1) = + height_vec_full[i]; + } + m_peakPositionTable->cell( + rowIndexOutputPeaks, m_peaksInDspacing.size() + 1) = chisq; + m_peakPositionTable->cell(rowIndexOutputPeaks, + m_peaksInDspacing.size() + 2) = + chisq / static_cast(numPeaks - 1); + + setCalibrationValues(det, difc, difa, t0); + } + } + prog.report(); + + PARALLEL_END_INTERUPT_REGION + } + PARALLEL_CHECK_INTERUPT_REGION + + // sort the calibration tables by increasing detector ID + m_calibrationTable = sortTableWorkspace(m_calibrationTable); + setProperty("OutputCalibrationTable", m_calibrationTable); + + // fix-up the diagnostic workspaces + m_peakPositionTable = sortTableWorkspace(m_peakPositionTable); + m_peakWidthTable = sortTableWorkspace(m_peakWidthTable); + m_peakHeightTable = sortTableWorkspace(m_peakHeightTable); + + // a derived table from the position and width + auto resolutionWksp = calculateResolutionTable(); + + // set the diagnostic workspaces out + auto diagnosticGroup = std::make_shared(); + // add workspaces calculated by FitPeaks + API::AnalysisDataService::Instance().addOrReplace( + diagnostic_prefix + "_fitparam", fittedTable); + diagnosticGroup->addWorkspace(fittedTable); + API::AnalysisDataService::Instance().addOrReplace( + diagnostic_prefix + "_fitted", calculatedWS); + diagnosticGroup->addWorkspace(calculatedWS); + + // add workspaces calculated by PDCalibration + API::AnalysisDataService::Instance().addOrReplace( + diagnostic_prefix + "_dspacing", m_peakPositionTable); + diagnosticGroup->addWorkspace(m_peakPositionTable); + API::AnalysisDataService::Instance().addOrReplace( + diagnostic_prefix + "_width", m_peakWidthTable); + diagnosticGroup->addWorkspace(m_peakWidthTable); + API::AnalysisDataService::Instance().addOrReplace( + diagnostic_prefix + "_height", m_peakHeightTable); + diagnosticGroup->addWorkspace(m_peakHeightTable); + API::AnalysisDataService::Instance().addOrReplace( + diagnostic_prefix + "_resolution", resolutionWksp); + diagnosticGroup->addWorkspace(resolutionWksp); + setProperty("DiagnosticWorkspaces", diagnosticGroup); } namespace { // anonymous namespace @@ -714,9 +790,24 @@ double gsl_costFunction(const gsl_vector *v, void *peaks) { return errsum; } -// returns the errsum, the conversion parameters are done by in/out parameters -// to the function -// if the fit fails it returns 0. +/** + * Linear regression of the nominal peak center positions, in d-spacing + * against the fitted peak center positions, in TOF units. + * + * The equation to carry out the fit depends on the number of fit parameter + * selected: + * one fit parameter: TOF = DIFC * d + * two fit parameter: TOF = DIFC * d + TZERO + * three fit parameters: TOF = DIFC * d + TZERO + DIFA * d^2 + * + * @param peaks :: array with structure (numpeaks, numfitparms, tof_1, + * ...tof_numpeaks, d1,...d_numpeaks, height^2_1,...height^2_numpeaks) + * @param difc :: will store optimized DIFC fit parameter + * @param t0 :: will store optimized TZERO fit parameter + * @param difa :: will store optimized DIFA fit parameter + + * @return cummulative error of the fit, zero if the fit fails + */ double fitDIFCtZeroDIFA(std::vector &peaks, double &difc, double &t0, double &difa) { const auto numParams = static_cast(peaks[1]); @@ -784,12 +875,25 @@ double fitDIFCtZeroDIFA(std::vector &peaks, double &difc, double &t0, gsl_vector_free(fitParams); gsl_vector_free(stepSizes); gsl_multimin_fminimizer_free(minimizer); - return errsum; } } // end of anonymous namespace +/** + * Fit the nominal peak center positions, in d-spacing against the fitted + * peak center positions, in TOF units. We use the GSAS formula: + * TOF = DIFC∗d + DIFA∗d^2 + TZERO + * + * No fitting is performed if the number of peaks is less than two. + * + * @param d :: nominal peak center positions, in d-spacing units + * @param tof :: fitted peak center positions, in TOF units + * @param height2 :: square of the fitted peak heights + * @param difc :: output optimized DIFC parameter + * @param t0 :: output optimized TZERO parameter + * @param difa :: output optimized DIFA parameter + */ void PDCalibration::fitDIFCtZeroDIFA_LM(const std::vector &d, const std::vector &tof, const std::vector &height2, @@ -805,9 +909,12 @@ void PDCalibration::fitDIFCtZeroDIFA_LM(const std::vector &d, size_t maxParams = std::min(numPeaks - 1, m_numberMaxParams); // this must have the same layout as the unpacking in gsl_costFunction above + // `peaks` has the following structure for a fit session with three peaks + // and two fit parameters: + // 3, 2, tof1, tof2, tof3, d1, d2, d3, h21, h22, h23 std::vector peaks(numPeaks * 3 + 2, 0.); peaks[0] = static_cast(d.size()); - peaks[1] = 1.; // number of parameters to fit + peaks[1] = 1.; // number of parameters to fit. Initialize to just one for (size_t i = 0; i < numPeaks; ++i) { peaks[i + 2] = tof[i]; peaks[i + 2 + numPeaks] = d[i]; @@ -824,21 +931,27 @@ void PDCalibration::fitDIFCtZeroDIFA_LM(const std::vector &d, // save the best values so far double best_errsum = std::numeric_limits::max(); - double best_difc = 0.; + double best_difc = difc_start; double best_t0 = 0.; double best_difa = 0.; - // loop over possible number of parameters + // loop over possible number of parameters, doing up to three sequential fits. + // We first start with equation TOF = DIFC * d and obtain + // optimized DIFC which serves as initial guess for next fit with equation + // TOF = DIFC * d + TZERO, obtaining optimized DIFC and TZERO which serve as + // initial guess for final fit TOF = DIFC * d + DIFA * d^2 + TZERO. for (size_t numParams = 1; numParams <= maxParams; ++numParams) { peaks[1] = static_cast(numParams); - double difc_local = difc_start; - double t0_local = 0.; - double difa_local = 0.; + double difc_local = best_difc; + double t0_local = best_t0; + double difa_local = best_difa; double errsum = fitDIFCtZeroDIFA(peaks, difc_local, t0_local, difa_local); if (errsum > 0.) { // normalize by degrees of freedom errsum = errsum / static_cast(numPeaks - numParams); // save the best and forget the rest + // the selected (DIFC, DIFA, TZERO) correspond to those of the fit that + // minimizes errsum. It doesn't have to be the last fit. if (errsum < best_errsum) { if (difa_local > m_difaMax || difa_local < m_difaMin) continue; // unphysical fit @@ -860,6 +973,17 @@ void PDCalibration::fitDIFCtZeroDIFA_LM(const std::vector &d, } } +/** + * Fitting ranges to the left and right of peak center + * + * Example: if centres = [1, 3, 11] and widthMax=3, then this function returns + * [(3-1)/2, (3-1)/2, (3-1)/2, 3, 3, 3] + * + * @param centres :: peak centers, in d-spacing + * @param widthMax :: maximum allowable fit range + * @return array containing left and right ranges for first peak, left and right + * for second peak, and so on. + */ vector PDCalibration::dSpacingWindows(const std::vector ¢res, const double widthMax) { @@ -897,14 +1021,32 @@ PDCalibration::dSpacingWindows(const std::vector ¢res, return windows; } +/** + * Return a function that converts from d-spacing to TOF units for a particular + * pixel, evaulating the GSAS conversion formula: + * TOF = DIFC∗d + DIFA∗d^2 + TZERO + * + * @param detIds :: set of detector IDs + */ std::function -PDCalibration::getDSpacingToTof(const detid_t detid) { - auto rowNum = m_detidToRow[detid]; +PDCalibration::getDSpacingToTof(const std::set &detIds) { // to start this is the old calibration values - const double difa = m_calibrationTable->getRef("difa", rowNum); - const double difc = m_calibrationTable->getRef("difc", rowNum); - const double tzero = m_calibrationTable->getRef("tzero", rowNum); + double difc = 0.; + double difa = 0.; + double tzero = 0.; + for (auto detId : detIds) { + auto rowNum = m_detidToRow[detId]; + difc += m_calibrationTable->getRef("difc", rowNum); + difa += m_calibrationTable->getRef("difa", rowNum); + tzero += m_calibrationTable->getRef("tzero", rowNum); + } + if (detIds.size() > 1) { + double norm = 1. / static_cast(detIds.size()); + difc = norm * difc; + difa = norm * difa; + tzero = norm * tzero; + } return Kernel::Diffraction::getDToTofConversionFunc(difc, difa, tzero); } @@ -912,6 +1054,7 @@ PDCalibration::getDSpacingToTof(const detid_t detid) { void PDCalibration::setCalibrationValues(const detid_t detid, const double difc, const double difa, const double tzero) { + auto rowNum = m_detidToRow[detid]; // detid is already there @@ -928,6 +1071,14 @@ void PDCalibration::setCalibrationValues(const detid_t detid, const double difc, m_calibrationTable->cell(rowNum, 5 + hasDasIdsOffset) = tofMinMax[1]; } +/** + * Adjustment of TofMin and TofMax values, to ensure positive values of + * d-spacing when converting from TOF to d-spacing using the GSAS equation + * TOF = DIFC∗d + DIFA∗d^2 + TZERO + * See calcTofMin and calcTofMax for adjustments to TofMin and TofMax. + * + * @return two-item array containing adjusted TofMin and TofMax values + */ vector PDCalibration::getTOFminmax(const double difc, const double difa, const double tzero) { vector tofminmax(2); @@ -956,6 +1107,7 @@ MatrixWorkspace_sptr PDCalibration::load(const std::string &filename) { return std::dynamic_pointer_cast(workspace); } +/// load input workspace and rebin with parameters "TofBinning" provided by User MatrixWorkspace_sptr PDCalibration::loadAndBin() { m_uncalibratedWS = getProperty("InputWorkspace"); return rebin(m_uncalibratedWS); @@ -975,6 +1127,19 @@ API::MatrixWorkspace_sptr PDCalibration::rebin(API::MatrixWorkspace_sptr wksp) { return wksp; } +/** + * Read a calibration table workspace provided by user, or load from a file + * provided by User + * + * Every pixel may need adjustment of TofMin and TofMax values, to ensure + * positive values of d-spacing when converting from TOF to d-spacing using + * the GSAS equation TOF = DIFC∗d + DIFA∗d^2 + TZERO. See calcTofMin and + * calcTofMax for adjustments + * to TofMin and TofMax. + * + * The output calibration has columns "detid", "difc", "difa", "tzero", + * "tofmin", and "tofmax", and possible additional columns "dasid" and "offset" + */ void PDCalibration::createCalTableFromExisting() { API::ITableWorkspace_sptr calibrationTableOld = getProperty("PreviousCalibrationTable"); @@ -1026,6 +1191,7 @@ void PDCalibration::createCalTableFromExisting() { if (m_hasDasIds) newRow << calibrationTableOld->getRef("dasid", rowNum); + // adjust tofmin and tofmax for this pixel const auto tofMinMax = getTOFminmax(calibrationTableOld->getRef("difc", rowNum), calibrationTableOld->getRef("difa", rowNum), @@ -1035,6 +1201,12 @@ void PDCalibration::createCalTableFromExisting() { } } +/** + * Initialize the calibration table workspace. The output calibration + * has columns "detid", "difc", "difa", "tzero", "tofmin", and "tofmax". + * "difc" from the instrument geometry: m_n * (L1 + L2) * 2 * sin(theta) / h + * "difa", "tzero", and "tofmin" set to zero, "tofmax" to DBL_MAX + */ void PDCalibration::createCalTableNew() { // create new calibraion table for when an old one isn't loaded // using the signal workspace and CalculateDIFC @@ -1057,7 +1229,7 @@ void PDCalibration::createCalTableNew() { setProperty("OutputCalibrationTable", m_calibrationTable); const detid2index_map allDetectors = - difcWS->getDetectorIDToWorkspaceIndexMap(true); + difcWS->getDetectorIDToWorkspaceIndexMap(false); // copy over the values auto it = allDetectors.begin(); @@ -1076,8 +1248,13 @@ void PDCalibration::createCalTableNew() { } } +/** + * Table workspaces where the first column is the detector ID and subsequent + * columns are termed "@x.xxxxx" where x.xxxxx are the peak positions of the + * "PeakPositions" input property array (the nominal peak center positions) + */ void PDCalibration::createInformationWorkspaces() { - // table for the fitted location of the various peaks + // table for the fitted location of the various peaks, in d-spacing units m_peakPositionTable = std::make_shared(); m_peakWidthTable = std::make_shared(); m_peakHeightTable = std::make_shared(); @@ -1147,10 +1324,8 @@ API::MatrixWorkspace_sptr PDCalibration::calculateResolutionTable() { pos); } } - if (resolution.empty()) { - resolutionWksp->setValue(detId, 0., - 0.); // instrument view doesn't like nan + resolutionWksp->setValue(detId, 0., 0.); // instview doesn't like nan } else { // calculate the mean const double mean = @@ -1168,6 +1343,7 @@ API::MatrixWorkspace_sptr PDCalibration::calculateResolutionTable() { return resolutionWksp; } +/// sort the calibration table according increasing values in column "detid" API::ITableWorkspace_sptr PDCalibration::sortTableWorkspace(API::ITableWorkspace_sptr &table) { auto alg = createChildAlgorithm("SortTableWorkspace"); @@ -1181,13 +1357,27 @@ PDCalibration::sortTableWorkspace(API::ITableWorkspace_sptr &table) { return table; } -/// NEW: convert peak positions in dSpacing to peak centers workspace +/** + * A pair of workspaces, one containing the nominal peak centers in TOF units, + * the other containing the left and right fitting ranges around each nominal + * peak center, also in TOF units + * + * Because each pixel has a different set of difc, difa, and tzero values, + * the position of the nominal peak centers (and the fitting ranges) + * will be different when expressed in TOF units. Thus, they have to be + * calculated for each pixel. + * + * @param dataws :: input signal workspace + * @param peakWindowMaxInDSpacing:: The maximum window (in d -pace) to the + * left and right of the nominal peak center to look for the peak + */ std::pair PDCalibration::createTOFPeakCenterFitWindowWorkspaces( const API::MatrixWorkspace_sptr &dataws, const double peakWindowMaxInDSpacing) { - // calculate from peaks in dpsacing to peak fit window in dspacing + // calculate fitting ranges to the left and right of each nominal peak + // center, in d-spacing units const auto windowsInDSpacing = dSpacingWindows(m_peaksInDspacing, peakWindowMaxInDSpacing); @@ -1197,7 +1387,7 @@ PDCalibration::createTOFPeakCenterFitWindowWorkspaces( << windowsInDSpacing[2 * i + 1] << std::endl; } - // create workspaces + // create workspaces for nominal peak centers and fit ranges size_t numspec = dataws->getNumberHistograms(); size_t numpeaks = m_peaksInDspacing.size(); MatrixWorkspace_sptr peak_pos_ws = @@ -1213,7 +1403,10 @@ PDCalibration::createTOFPeakCenterFitWindowWorkspaces( PARALLEL_START_INTERUPT_REGION // calculatePositionWindowInTOF PDCalibration::FittedPeaks peaks(dataws, static_cast(iws)); + // toTof is a function that converts from d-spacing to TOF for a particular + // pixel auto toTof = getDSpacingToTof(peaks.detid); + // setpositions initializes peaks.inTofPos and peaks.inTofWindows peaks.setPositions(m_peaksInDspacing, windowsInDSpacing, toTof); peak_pos_ws->setPoints(iws, peaks.inTofPos); peak_window_ws->setPoints(iws, peaks.inTofWindows); diff --git a/Framework/Algorithms/src/RunCombinationHelpers/SampleLogsBehaviour.cpp b/Framework/Algorithms/src/RunCombinationHelpers/SampleLogsBehaviour.cpp index 03a6bf849742076c03dbb30636c356afaaa3c0cd..407dbe8dffc33655e38e47093295db0834b38d0b 100644 --- a/Framework/Algorithms/src/RunCombinationHelpers/SampleLogsBehaviour.cpp +++ b/Framework/Algorithms/src/RunCombinationHelpers/SampleLogsBehaviour.cpp @@ -484,8 +484,7 @@ void SampleLogsBehaviour::updateSumProperty(double addeeWSNumericValue, /** * Perform the update for a time series property, adding a new value to the - *existing time series property. Skipped if the time series log entry is in the - *addeeWS. + *existing time series property. * * @param addeeWS the workspace being merged * @param outWS the workspace the others are merged into @@ -494,21 +493,21 @@ void SampleLogsBehaviour::updateSumProperty(double addeeWSNumericValue, void SampleLogsBehaviour::updateTimeSeriesProperty(MatrixWorkspace &addeeWS, MatrixWorkspace &outWS, const std::string &name) { + auto timeSeriesProp = outWS.run().getTimeSeriesProperty(name); try { - // If this already exists we do not need to do anything, Time Series Logs - // are combined when adding workspaces. - addeeWS.run().getTimeSeriesProperty(name); + const auto addeeTimeSeries = + addeeWS.run().getTimeSeriesProperty(name); + timeSeriesProp->merge(addeeTimeSeries); } catch (std::invalid_argument &) { - auto timeSeriesProp = outWS.run().getTimeSeriesProperty(name); Types::Core::DateAndTime startTime = addeeWS.run().startTime(); double value = addeeWS.run().getLogAsSingleValue(name); timeSeriesProp->addValue(startTime, value); - // Remove this to supress a warning, we will put it back after adding the - // workspaces in MergeRuns - const Property *addeeWSProperty = addeeWS.run().getProperty(name); - m_addeeLogMap.emplace_back( - std::shared_ptr(addeeWSProperty->clone())); } + // Remove this to supress a warning, we will put it back after adding the + // workspaces in MergeRuns + const Property *addeeWSProperty = addeeWS.run().getProperty(name); + m_addeeLogMap.emplace_back( + std::shared_ptr(addeeWSProperty->clone())); } /** diff --git a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp index e7df46dad272dcf7e5511a068c7924c68e716d9c..7aadade448cb072f860db9caa30a2325fc02a861 100644 --- a/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp +++ b/Framework/Algorithms/src/SampleCorrections/MCAbsorptionStrategy.cpp @@ -86,17 +86,17 @@ void MCAbsorptionStrategy::calculate(Kernel::PseudoRandomNumberGenerator &rng, wgtM2(attenuationFactors.size()); for (size_t i = 0; i < m_nevents; ++i) { - Geometry::Track beforeScatter; - Geometry::Track afterScatter; + std::shared_ptr beforeScatter; + std::shared_ptr afterScatter; for (int j = 0; j < nbins; ++j) { size_t attempts(0); do { bool success = false; if (m_regenerateTracksForEachLambda || j == 0) { const auto neutron = m_beamProfile.generatePoint(rng, scatterBounds); - success = m_scatterVol.calculateBeforeAfterTrack( - rng, neutron.startPos, finalPos, beforeScatter, afterScatter, - stats); + std::tie(success, beforeScatter, afterScatter) = + m_scatterVol.calculateBeforeAfterTrack(rng, neutron.startPos, + finalPos, stats); } else { success = true; } @@ -112,8 +112,8 @@ void MCAbsorptionStrategy::calculate(Kernel::PseudoRandomNumberGenerator &rng, } else { // elastic case already initialized } - const double wgt = m_scatterVol.calculateAbsorption( - beforeScatter, afterScatter, lambdaIn, lambdaOut); + const double wgt = beforeScatter->calculateAttenuation(lambdaIn) * + afterScatter->calculateAttenuation(lambdaOut); attenuationFactors[j] += wgt; // increment standard deviation using Welford algorithm double delta = wgt - wgtMean[j]; diff --git a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp index c9924a73eca3796522691f6cab58e29cd03501b6..7c4d94d5c63f64b596eadd83902d67b663954114 100644 --- a/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp +++ b/Framework/Algorithms/src/SampleCorrections/MCInteractionVolume.cpp @@ -160,17 +160,15 @@ ComponentScatterPoint MCInteractionVolume::generatePoint( * @param startPos Origin of the initial track * @param endPos Final position of neutron after scattering (assumed to be * outside of the "volume") - * @param beforeScatter Out parameter to return generated before scatter track - * @param afterScatter Out parameter to return generated after scatter track * @param stats A statistics class to hold the statistics on the generated * tracks such as the scattering angle and count of scatter points generated in * each sample or environment part - * @return Whether before/after tracks were successfully generated + * @return A tuple containing a flag to indicate whether before/after tracks + * were successfully generated and (if yes) the before/after tracks */ -bool MCInteractionVolume::calculateBeforeAfterTrack( +TrackPair MCInteractionVolume::calculateBeforeAfterTrack( Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos, - const Kernel::V3D &endPos, Geometry::Track &beforeScatter, - Geometry::Track &afterScatter, MCInteractionStatistics &stats) const { + const Kernel::V3D &endPos, MCInteractionStatistics &stats) const { // Generate scatter point. If there is an environment present then // first select whether the scattering occurs on the sample or the // environment. The attenuation for the path leading to the scatter point @@ -183,56 +181,29 @@ bool MCInteractionVolume::calculateBeforeAfterTrack( stats.UpdateScatterPointCounts(scatterPos.componentIndex, false); const auto toStart = normalize(startPos - scatterPos.scatterPoint); - beforeScatter = Track(scatterPos.scatterPoint, toStart); - int nlinks = m_sample->interceptSurface(beforeScatter); + auto beforeScatter = + std::make_shared(scatterPos.scatterPoint, toStart); + int nlinks = m_sample->interceptSurface(*beforeScatter); if (m_env) { - nlinks += m_env->interceptSurfaces(beforeScatter); + nlinks += m_env->interceptSurfaces(*beforeScatter); } // This should not happen but numerical precision means that it can // occasionally occur with tracks that are very close to the surface if (nlinks == 0) { - return false; + return {false, nullptr, nullptr}; } stats.UpdateScatterPointCounts(scatterPos.componentIndex, true); // Now track to final destination const V3D scatteredDirec = normalize(endPos - scatterPos.scatterPoint); - afterScatter = Track(scatterPos.scatterPoint, scatteredDirec); - m_sample->interceptSurface(afterScatter); + auto afterScatter = + std::make_shared(scatterPos.scatterPoint, scatteredDirec); + m_sample->interceptSurface(*afterScatter); if (m_env) { - m_env->interceptSurfaces(afterScatter); + m_env->interceptSurfaces(*afterScatter); } stats.UpdateScatterAngleStats(toStart, scatteredDirec); - return true; -} - -/** - * Calculate the attenuation correction factor the volume given a before - * and after track. - * @param beforeScatter Before scatter track - * @param afterScatter After scatter track - * @param lambdaBefore Lambda before scattering - * @param lambdaAfter Lambda after scattering - * @return Absorption factor - */ -double MCInteractionVolume::calculateAbsorption(const Track &beforeScatter, - const Track &afterScatter, - double lambdaBefore, - double lambdaAfter) const { - - // Function to calculate total attenuation for a track - auto calculateAttenuation = [](const Track &path, double lambda) { - double factor(1.0); - for (const auto &segment : path) { - const double length = segment.distInsideObject; - const auto &segObj = *(segment.object); - factor *= segObj.material().attenuation(length, lambda); - } - return factor; - }; - - return calculateAttenuation(beforeScatter, lambdaBefore) * - calculateAttenuation(afterScatter, lambdaAfter); + return {true, beforeScatter, afterScatter}; } } // namespace Algorithms diff --git a/Framework/Algorithms/src/XrayAbsorptionCorrection.cpp b/Framework/Algorithms/src/XrayAbsorptionCorrection.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1e07aa7ecfac5c47c33da1851469b5444024ac17 --- /dev/null +++ b/Framework/Algorithms/src/XrayAbsorptionCorrection.cpp @@ -0,0 +1,222 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidAlgorithms/XrayAbsorptionCorrection.h" +#include "MantidAPI/Sample.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidAPI/WorkspaceUnitValidator.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/CompositeValidator.h" +#include "MantidKernel/Material.h" +#include "MantidKernel/VectorHelper.h" +#include +#include + +using namespace Mantid::API; +using namespace Mantid::Geometry; +using namespace Mantid::Kernel; +using Mantid::DataObjects::Workspace2D; + +namespace { +// Angle in degrees +constexpr double DEFAULT_ANGLE = 45.0; +// distance in cm +constexpr double DEFAULT_DETECTOR_DISTANCE = 10.0; +constexpr double ConversionFrom_cm_to_m = 0.01; + +} // namespace + +namespace Mantid { +namespace Algorithms { + +DECLARE_ALGORITHM(XrayAbsorptionCorrection) + +/** + * Initialize the algorithm + */ +void XrayAbsorptionCorrection::init() { + + auto wsValidator = std::make_shared(); + + declareProperty(std::make_unique>( + "InputWorkspace", "", Direction::Input, wsValidator), + "The name of the input workspace."); + + declareProperty( + std::make_unique>("MuonImplantationProfile", "", + Direction::Input, wsValidator), + "The name of the Muon Implantation Profile."); + + declareProperty(std::make_unique>("OutputWorkspace", "", + Direction::Output), + "The name to use for the output workspace."); + + auto positiveDouble = std::make_shared>(); + positiveDouble->setLower(0); + declareProperty("DetectorAngle", DEFAULT_ANGLE, positiveDouble, + "Angle in degrees between beam and Detector." + "Range of normal values for detectors are : " + "Ge1 : 90-180 , Ge2 : 270-360 , Ge3 : 0 - 90 , Ge4 " + ": 180 -270.", + Direction::Input); + + declareProperty("DetectorDistance", DEFAULT_DETECTOR_DISTANCE, positiveDouble, + "Distance in cm between detector and sample.", + Direction::Input); +} + +std::map XrayAbsorptionCorrection::validateInputs() { + MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace"); + MatrixWorkspace_sptr muonProfile = getProperty("MuonImplantationProfile"); + std::map issues; + if (!inputWS->sample().getShape().hasValidShape()) { + issues["InputWorkspace"] = "Input workspace does not have a Sample"; + } + auto material = inputWS->sample().getShape().material(); + if (!material.hasValidXRayAttenuationProfile()) { + issues["InputWorkspace"] = + "Input workspace does not have a Xray Attenuation profile"; + } + if (muonProfile->getNumberHistograms() != 1) { + issues["MuonImplantationProfile"] = + "Muon Implantation profile must have only one spectrum"; + } + return issues; +} + +/** + * Converts angles in degrees to angles in radians. + * @param degrees double of angle in degrees + * @return A double of angle in radians + */ +double XrayAbsorptionCorrection::degreesToRadians(double degrees) { + return M_PI * (degrees / 180.0); +} + +/** + * Calculates the position of the detector. + * @param detectorAngle double of angle of detector + * @param detectorDistance distance betweeen sample and detector + * @return V3D object of position of detector + */ +Kernel::V3D +XrayAbsorptionCorrection::calculateDetectorPos(double const detectorAngle, + double detectorDistance) { + detectorDistance = detectorDistance * ConversionFrom_cm_to_m; + double x = detectorDistance / std::tan(degreesToRadians(detectorAngle)); + if (detectorAngle > 180.0) { + detectorDistance = -detectorDistance; + } + Kernel::V3D detectorPos = {x, 0.0, detectorDistance}; + return detectorPos; +} + +/** + * normalise moun intensity to 1. + * @param muonIntensity A MantidVec which contains the intensity of muons as a + * function of depth + * @return A vector of doubles which contains the normalised muon intensity + */ +std::vector +XrayAbsorptionCorrection::normaliseMuonIntensity(MantidVec muonIntensity) { + + double sum_of_elems = + std::accumulate(muonIntensity.begin(), muonIntensity.end(), 0.0); + + std::transform(muonIntensity.begin(), muonIntensity.end(), + muonIntensity.begin(), + [sum_of_elems](double d) { return d / sum_of_elems; }); + return muonIntensity; +} + +/** + * Calculate the muon implantation position in the sample. + * @param muonProfile A reference to the muon profile workspace + * @param inputWS A reference to the input workspace + * @param detectorDistance a double representing the distance from the sample to + * detector + * @return A vector of V3D objects that represent the position of muons + */ +std::vector XrayAbsorptionCorrection::calculateMuonPos( + API::MatrixWorkspace_sptr &muonProfile, API::MatrixWorkspace_sptr inputWS, + double detectorDistance) { + const MantidVec muonDepth = muonProfile->readX(0); + Kernel::V3D const muonPoint = {0.0, 0.0, detectorDistance}; + Kernel::V3D toStart = {0.0, 0.0, -1.0}; + const Geometry::IObject *shape = &inputWS->sample().getShape(); + Geometry::Track muonPath = Geometry::Track(muonPoint, toStart); + shape->interceptSurface(muonPath); + if (muonPath.count() == 0) { + throw std::runtime_error("No valid solution, check shape parameters, Muon " + "depth profile and detector distance"); + } + Kernel::V3D intersection = muonPath.cbegin()->entryPoint; + double sampleDepth = intersection[2]; + std::vector muonPos; + for (auto depth : muonDepth) { + /* Muon implantation position are at x = 0 and y = 0 and z position is + variable*/ + Kernel::V3D pos = {0.0, 0.0, + sampleDepth - (depth * ConversionFrom_cm_to_m)}; + muonPos.push_back(pos); + } + return muonPos; +} + +/** + * Execution + */ +void XrayAbsorptionCorrection::exec() { + MatrixWorkspace_sptr inputWS = getProperty("InputWorkspace"); + MatrixWorkspace_sptr outputWS = inputWS->clone(); + IAlgorithm_sptr convtoPoints = createChildAlgorithm("ConvertToPointData"); + convtoPoints->setProperty("InputWorkspace", inputWS); + convtoPoints->execute(); + MatrixWorkspace_sptr pointDataWS = + convtoPoints->getProperty("OutputWorkspace"); + + MatrixWorkspace_sptr muonProfile = getProperty("MuonImplantationProfile"); + MantidVec muonIntensity = muonProfile->readY(0); + std::vector normalisedMuonIntensity = + normaliseMuonIntensity(muonIntensity); + double detectorAngle = getProperty("DetectorAngle"); + double detectorDistance = getProperty("DetectorDistance"); + Kernel::V3D detectorPos = + calculateDetectorPos(detectorAngle, detectorDistance); + std::vector muonPos = + calculateMuonPos(muonProfile, inputWS, detectorDistance); + + for (size_t j = 0; j < inputWS->getNumberHistograms(); j++) { + auto &yData = outputWS->mutableY(j); + MantidVec xData = pointDataWS->readX(j); + for (size_t i = 0; i < xData.size(); i++) { + double totalFactor{0}; + for (size_t k = 0; k < normalisedMuonIntensity.size(); k++) { + Kernel::V3D pos = muonPos[k]; + Kernel::V3D detectorDirection = normalize(detectorPos - pos); + Geometry::Track xrayPath = Geometry::Track(pos, detectorDirection); + const Geometry::IObject *sampleShape = &inputWS->sample().getShape(); + sampleShape->interceptSurface(xrayPath); + double factor{1.0}; + if (xrayPath.count() == 0) { + throw std::runtime_error("No valid solution, check shape parameters " + ", detector disatance and angle"); + } + for (auto &link : xrayPath) { + double distInObject = link.distInsideObject; + factor = factor * link.object->material().xRayAttenuation( + distInObject, xData[i]); + } + totalFactor += (normalisedMuonIntensity[k] * factor); + } + yData[i] = totalFactor; + } + } + setProperty("OutputWorkspace", std::move(outputWS)); +} + +} // namespace Algorithms +} // namespace Mantid \ No newline at end of file diff --git a/Framework/Algorithms/test/AddAbsorptionWeightedPathLengthsTest.h b/Framework/Algorithms/test/AddAbsorptionWeightedPathLengthsTest.h index 87359f2eb9024007de17acb464281b1502a3bcef..741ff99f07d2a51baa8c7a7d024f054ef49e79d9 100644 --- a/Framework/Algorithms/test/AddAbsorptionWeightedPathLengthsTest.h +++ b/Framework/Algorithms/test/AddAbsorptionWeightedPathLengthsTest.h @@ -138,6 +138,27 @@ public: TS_ASSERT_THROWS_NOTHING(alg.setProperty("EventsPerPoint", 1000)); TS_ASSERT_THROWS(alg.execute(), const std::runtime_error &); } + void test_single_path() { + using namespace Mantid::Kernel; + const int NPEAKS = 10; + // this sets up a sample with a spherical shape of radius = 1mm + auto peaksWS = WorkspaceCreationHelper::createPeaksWorkspace(NPEAKS); + auto shape = + ComponentCreationHelper::createSphere(0.001, {0, 0, 0}, "sample-shape"); + peaksWS->mutableSample().setShape(shape); + setMaterialToVanadium(peaksWS); + + Mantid::Algorithms::AddAbsorptionWeightedPathLengths alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()); + TS_ASSERT(alg.isInitialized()); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", peaksWS)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("UseSinglePath", true)); + TS_ASSERT_THROWS_NOTHING(alg.execute();); + + Mantid::Geometry::IPeak &peak = peaksWS->getPeak(0); + const double delta(1e-06); + TS_ASSERT_DELTA(0.2, peak.getAbsorptionWeightedPathLength(), delta); + } private: void setTestInstrument(std::shared_ptr peaksWS) { diff --git a/Framework/Algorithms/test/CalculateEfficiency2Test.h b/Framework/Algorithms/test/CalculateEfficiency2Test.h index b1a5b75720a2458b655de1322729a05017a7f47e..7c3410c7d4229fd123e76a2aa57fa4038c5695be 100644 --- a/Framework/Algorithms/test/CalculateEfficiency2Test.h +++ b/Framework/Algorithms/test/CalculateEfficiency2Test.h @@ -10,15 +10,18 @@ #include "MantidAPI/AnalysisDataService.h" #include "MantidAPI/Axis.h" #include "MantidAPI/SpectrumInfo.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidAPI/WorkspaceGroup.h" #include "MantidAlgorithms/CalculateEfficiency2.h" +#include "MantidDataHandling/Load.h" #include "MantidDataHandling/LoadSpice2D.h" #include "MantidDataHandling/MoveInstrumentComponent.h" +#include "MantidKernel/ConfigService.h" #include "MantidKernel/EmptyValues.h" #include "MantidKernel/Unit.h" #include "MantidTestHelpers/SANSInstrumentCreationHelper.h" #include - using namespace Mantid; using namespace Mantid::API; using namespace Mantid::Kernel; @@ -68,6 +71,69 @@ public: convertToEvents->execute(); } } + /* + * Generate fake data workspace group for which we know what the result should + * be + */ + void setUpWorkspaceGroup() { + inputWS = "sampledata"; + + std::vector toGroup; + auto wsName = inputWS + "_1"; + Mantid::DataObjects::Workspace2D_sptr ws1 = + SANSInstrumentCreationHelper::createSANSInstrumentWorkspace(wsName); + toGroup.emplace_back(wsName); + + wsName = inputWS + "_2"; + Mantid::DataObjects::Workspace2D_sptr ws2 = + SANSInstrumentCreationHelper::createSANSInstrumentWorkspace(wsName); + toGroup.emplace_back(wsName); + + // Set up the X bin for the monitor channels + for (int i = 0; i < SANSInstrumentCreationHelper::nMonitors; i++) { + auto &X = ws1->mutableX(i); + X[0] = 1; + X[1] = 2; + auto &X2 = ws2->mutableX(i); + X2 = X; + } + + for (int ix = 0; ix < SANSInstrumentCreationHelper::nBins; ix++) { + for (int iy = 0; iy < SANSInstrumentCreationHelper::nBins; iy++) { + int i = ix * SANSInstrumentCreationHelper::nBins + iy + + SANSInstrumentCreationHelper::nMonitors; + auto &X = ws1->mutableX(i); + auto &Y = ws1->mutableY(i); + auto &E = ws1->mutableE(i); + X[0] = 1; + X[1] = 2; + Y[0] = 1.5; + E[0] = 0.1; + auto &X2 = ws2->mutableX(i); + auto &Y2 = ws2->mutableY(i); + auto &E2 = ws2->mutableE(i); + X2 = X; + Y2[0] = 1.0; + E2[0] = 0.2; + } + } + // mask certain spectra to test merging + auto info1 = ws1->spectrumInfo(); + auto info2 = ws2->spectrumInfo(); + info1.setMasked(0, true); + info2.setMasked(0, true); + info1.setMasked(1, true); + info2.setMasked(2, true); + info1.setMasked(4, true); + info2.setMasked(4, true); + + auto groupAlg = AlgorithmManager::Instance().create("GroupWorkspaces"); + groupAlg->initialize(); + groupAlg->setAlwaysStoreInADS(true); + groupAlg->setProperty("InputWorkspaces", toGroup); + groupAlg->setProperty("OutputWorkspace", inputWS); + groupAlg->execute(); + } void testName() { TS_ASSERT_EQUALS(correction.name(), "CalculateEfficiency") } @@ -224,6 +290,103 @@ public: Mantid::API::AnalysisDataService::Instance().remove(outputWS); } + void testProcessGroupsMerge() { + setUpWorkspaceGroup(); + if (!correction.isInitialized()) + correction.initialize(); + + const std::string outputWS("result"); + TS_ASSERT_THROWS_NOTHING( + correction.setPropertyValue("InputWorkspace", inputWS)); + TS_ASSERT_THROWS_NOTHING( + correction.setPropertyValue("OutputWorkspace", outputWS)) + TS_ASSERT_THROWS_NOTHING(correction.setProperty("MergeGroup", true)) + TS_ASSERT_THROWS_NOTHING(correction.execute()) + TS_ASSERT(correction.isExecuted()) + + Mantid::API::Workspace_const_sptr ws_out; + TS_ASSERT_THROWS_NOTHING( + ws_out = + Mantid::API::AnalysisDataService::Instance().retrieve(outputWS)); + auto ws2d_out = std::static_pointer_cast(ws_out); + + double tolerance(1e-02); + const auto &oSpecInfo = ws2d_out->spectrumInfo(); + // spectrum not masked in any input + TS_ASSERT_DELTA(ws2d_out->x(3)[0], 1.0, tolerance); + TS_ASSERT_DELTA(ws2d_out->x(3)[1], 2.0, tolerance); + TS_ASSERT_DELTA(ws2d_out->y(3)[0], 1.0, tolerance); + TS_ASSERT(!oSpecInfo.isMasked(3)); + + // spectra masked in one but not the other + TS_ASSERT_DELTA(ws2d_out->y(1)[0], 1.0, tolerance); + TS_ASSERT_DELTA(ws2d_out->e(1)[0], 0.0, tolerance); + TS_ASSERT(!oSpecInfo.isMasked(1)); + TS_ASSERT_DELTA(ws2d_out->y(2)[0], 1.5, tolerance); + TS_ASSERT_DELTA(ws2d_out->e(2)[0], 0.1, tolerance); + TS_ASSERT(!oSpecInfo.isMasked(2)); + + // the first and last spectra should stay masked + TS_ASSERT(oSpecInfo.isMasked(0)); + TS_ASSERT(oSpecInfo.isMasked(4)); + + Mantid::API::AnalysisDataService::Instance().remove(inputWS); + Mantid::API::AnalysisDataService::Instance().remove(outputWS); + } + + void testProcessGroupsIndividual() { + setUpWorkspaceGroup(); + if (!correction.isInitialized()) + correction.initialize(); + + const std::string outputWS("result"); + TS_ASSERT_THROWS_NOTHING( + correction.setPropertyValue("InputWorkspace", inputWS)); + TS_ASSERT_THROWS_NOTHING( + correction.setPropertyValue("OutputWorkspace", outputWS)) + TS_ASSERT_THROWS_NOTHING(correction.setProperty("MergeGroup", false)) + TS_ASSERT_THROWS_NOTHING(correction.execute()) + TS_ASSERT(correction.isExecuted()) + + Mantid::API::Workspace_const_sptr ws_out; + TS_ASSERT_THROWS_NOTHING( + ws_out = + Mantid::API::AnalysisDataService::Instance().retrieve(outputWS)); + auto wsGroup_out = std::static_pointer_cast(ws_out); + + double tolerance(1e-02); + auto nEntries = wsGroup_out->getNumberOfEntries(); + TS_ASSERT_EQUALS(nEntries, 2) + for (auto entryNo = 0; entryNo < nEntries; entryNo++) { + auto entry = std::static_pointer_cast( + wsGroup_out->getItem(entryNo)); + const auto &oSpecInfo = entry->spectrumInfo(); + // spectrum not masked in any input + TS_ASSERT_DELTA(entry->x(3)[0], 1, tolerance); + TS_ASSERT_DELTA(entry->x(3)[1], 2, tolerance); + TS_ASSERT_DELTA(entry->y(3)[0], 1.0, tolerance); + TS_ASSERT(!oSpecInfo.isMasked(3)); + + // spectra masked in one but not the other, should stay masked + if (entryNo == 0) { + TS_ASSERT(oSpecInfo.isMasked(1)); + TS_ASSERT_DELTA(entry->y(2)[0], 1.0, tolerance); + TS_ASSERT_DELTA(entry->e(2)[0], 0.067, tolerance); + TS_ASSERT(!oSpecInfo.isMasked(2)); + } else { + TS_ASSERT_DELTA(entry->y(1)[0], 1.0, tolerance); + TS_ASSERT_DELTA(entry->e(1)[0], 0.0, tolerance); + TS_ASSERT(!oSpecInfo.isMasked(1)); + TS_ASSERT(oSpecInfo.isMasked(2)); + } + // the first and last spectra should stay masked + TS_ASSERT(oSpecInfo.isMasked(0)); + TS_ASSERT(oSpecInfo.isMasked(4)); + } + Mantid::API::AnalysisDataService::Instance().remove(inputWS); + Mantid::API::AnalysisDataService::Instance().remove(outputWS); + } + /* * Function that will validate results against known results found with * "standard" HFIR reduction package. diff --git a/Framework/Algorithms/test/CompareWorkspacesTest.h b/Framework/Algorithms/test/CompareWorkspacesTest.h index 05c26affb52591aaa3402903765a155b782538f3..bc0044309df884931fdcf390f1d32a5d635e98bd 100644 --- a/Framework/Algorithms/test/CompareWorkspacesTest.h +++ b/Framework/Algorithms/test/CompareWorkspacesTest.h @@ -22,8 +22,10 @@ #include "MantidDataObjects/PeaksWorkspace.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" #include "MantidKernel/UnitFactory.h" #include "MantidKernel/V3D.h" +#include "MantidTestHelpers/InstrumentCreationHelper.h" #include "MantidTestHelpers/MDEventsTestHelper.h" #include "MantidTestHelpers/WorkspaceCreationHelper.h" @@ -31,6 +33,7 @@ using namespace Mantid::Algorithms; using namespace Mantid::API; using namespace Mantid::DataObjects; using namespace Mantid::Geometry; +using Mantid::Kernel::V3D; class CompareWorkspacesTest : public CxxTest::TestSuite { public: @@ -825,14 +828,16 @@ public: void testDifferentInstruments() { if (!checker.isInitialized()) checker.initialize(); - - Mantid::API::MatrixWorkspace_sptr ws2 = - WorkspaceCreationHelper::create2DWorkspace123(2, 2); - Mantid::Geometry::Instrument_sptr instrument( - new Mantid::Geometry::Instrument("different")); - ws2->setInstrument(instrument); - - TS_ASSERT_THROWS_NOTHING(checker.setProperty("Workspace1", ws1)); + Workspace2D_sptr ws = + WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument( + 1, 2, false, false, true, "original", false); + AnalysisDataService::Instance().addOrReplace("original", ws); + // test different names + Workspace2D_sptr ws2 = + WorkspaceCreationHelper::create2DWorkspaceWithFullInstrument( + 1, 2, false, false, true, "distorted", false); + AnalysisDataService::Instance().addOrReplace("distorted", ws2); + TS_ASSERT_THROWS_NOTHING(checker.setProperty("Workspace1", ws)); TS_ASSERT_THROWS_NOTHING(checker.setProperty("Workspace2", ws2)); TS_ASSERT(checker.execute()); @@ -846,6 +851,30 @@ public: // Same, using the !Mantid::API::equals() function TS_ASSERT((!Mantid::API::equals(ws1, ws2))); + + // test different source position + Workspace2D_sptr ws3 = ws->clone(); // shared to unique ptr conversion + TS_ASSERT_THROWS_NOTHING(checker.setProperty("Workspace2", ws3)); + auto &info3 = ws3->mutableComponentInfo(); + info3.setPosition(info3.source(), info3.sourcePosition() + V3D(0, 0, 1e-6)); + TS_ASSERT(checker.execute()); + TS_ASSERT_DIFFERS(checker.getPropertyValue("Result"), PROPERTY_VALUE_TRUE); + table = AnalysisDataService::Instance().retrieveWS( + "compare_msgs"); + TS_ASSERT(table->cell(0, 0).find("Source mismatch") != + std::string::npos); + + // Compare different sample position + Workspace2D_sptr ws4 = ws->clone(); // shared to unique ptr conversion + TS_ASSERT_THROWS_NOTHING(checker.setProperty("Workspace2", ws4)); + auto &info4 = ws4->mutableComponentInfo(); + info4.setPosition(info4.sample(), info4.samplePosition() + V3D(0, 0, 1e-6)); + TS_ASSERT(checker.execute()); + TS_ASSERT_DIFFERS(checker.getPropertyValue("Result"), PROPERTY_VALUE_TRUE); + table = AnalysisDataService::Instance().retrieveWS( + "compare_msgs"); + TS_ASSERT(table->cell(0, 0).find("Sample mismatch") != + std::string::npos); } void testDifferentParameterMaps() { diff --git a/Framework/Algorithms/test/ConjoinXRunsTest.h b/Framework/Algorithms/test/ConjoinXRunsTest.h index 7e0abf7ee8e31de31750f67defd6be8a13fd3e74..cff40e52ef4b04c3d822d1b772f2773477aef311 100644 --- a/Framework/Algorithms/test/ConjoinXRunsTest.h +++ b/Framework/Algorithms/test/ConjoinXRunsTest.h @@ -117,9 +117,9 @@ public: group.execute(); m_testee.setProperty("InputWorkspaces", "group"); m_testee.setProperty("OutputWorkspace", "out"); - TS_ASSERT_THROWS_EQUALS(m_testee.execute(), const std::runtime_error &e, - std::string(e.what()), - "Some invalid Properties found"); + TS_ASSERT_THROWS_EQUALS( + m_testee.execute(), const std::runtime_error &e, std::string(e.what()), + "Some invalid Properties found: [ InputWorkspaces ]"); } void testWSWithoutDxValues() { diff --git a/Framework/Algorithms/test/CorelliCalibrationApplyTest.h b/Framework/Algorithms/test/CorelliCalibrationApplyTest.h new file mode 100644 index 0000000000000000000000000000000000000000..163eb63225b6a1dff5f84259f691e4758947ee0f --- /dev/null +++ b/Framework/Algorithms/test/CorelliCalibrationApplyTest.h @@ -0,0 +1,163 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/IAlgorithm.h" +#include "MantidAPI/ITableWorkspace.h" +#include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/SpectrumInfo.h" +#include "MantidAPI/TableRow.h" +#include "MantidAPI/Workspace.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidAlgorithms/CorelliCalibrationApply.h" +#include "MantidGeometry/Instrument/ComponentInfo.h" +#include "MantidGeometry/Instrument/DetectorInfo.h" +#include "MantidGeometry/Objects/IObject.h" +#include "MantidKernel/Logger.h" + +#include + +using namespace Mantid::Algorithms; +using namespace Mantid::API; +using namespace Mantid::Kernel; +using namespace Mantid::DataObjects; + +/// +/// The base class CxxTest doc is available at +/// http://cxxtest.com/guide.html +class CorelliCalibrationApplyTest : public CxxTest::TestSuite { +public: + void testName() { + CorelliCalibrationApply corelliPCA; + TS_ASSERT_EQUALS(corelliPCA.name(), "CorelliCalibrationApply"); + } + + void testInit() { + CorelliCalibrationApply crlCalApp; + crlCalApp.initialize(); + TS_ASSERT(crlCalApp.isInitialized()); + } + + void testValidateWS() { + // get a mock workspace with wrong instrument name + IAlgorithm_sptr lei = + AlgorithmFactory::Instance().create("LoadEmptyInstrument", 1); + lei->initialize(); + lei->setPropertyValue("Filename", "NOW4_Definition.xml"); + lei->setPropertyValue("OutputWorkspace", "wrongTypeWs"); + lei->setPropertyValue("MakeEventWorkspace", "1"); + lei->execute(); + auto ws = AnalysisDataService::Instance().retrieveWS( + "wrongTypeWs"); + + // get a mock calTable + std::string calTableName = "correctCalTable"; + auto calTable = createTestCalibrationTableWorkspace(calTableName); + + // setup alg + CorelliCalibrationApply alg; + alg.initialize(); + alg.setPropertyValue("Workspace", "wrongTypeWs"); + alg.setPropertyValue("CalibrationTable", calTableName); + + // ensure that an exception is thrown here + TS_ASSERT_THROWS(alg.execute(), const std::runtime_error &); + } + + void testValidateCalTable() { + // get a mock workspace with correct instrument name + auto ws = createTestEventWorkspace(); + + // get a mock calTable with wrong header + std::string calTableName = "wrongCalTable"; + auto calTable = createTestCalibrationTableWorkspace(calTableName); + calTable->removeColumn("Xposition"); + + // setup alg + CorelliCalibrationApply alg; + alg.initialize(); + alg.setPropertyValue("Workspace", "correctWs"); + alg.setPropertyValue("CalibrationTable", calTableName); + + // ensure that an exception is thrown here + TS_ASSERT_THROWS(alg.execute(), const std::runtime_error &); + } + + void testExec() { + // setup input workspace + auto ws = createTestEventWorkspace(); + + // get a correct mock calibration table + std::string calTableName = "correctCalTable"; + auto calTable = createTestCalibrationTableWorkspace(calTableName); + + // setup alg + CorelliCalibrationApply alg; + alg.initialize(); + alg.setPropertyValue("Workspace", "correctWs"); + alg.setPropertyValue("CalibrationTable", calTableName); + + // make sure no exception is thrown here + TS_ASSERT_THROWS_NOTHING(alg.execute()) + } + +private: + EventWorkspace_sptr createTestEventWorkspace() { + // Name of the output workspace. + std::string outWSName("correctWs"); + + IAlgorithm_sptr lei = + AlgorithmFactory::Instance().create("LoadEmptyInstrument", 1); + lei->initialize(); + lei->setPropertyValue("Filename", "CORELLI_Definition.xml"); + lei->setPropertyValue("OutputWorkspace", outWSName); + lei->setPropertyValue("MakeEventWorkspace", "1"); + lei->execute(); + + EventWorkspace_sptr ws; + ws = AnalysisDataService::Instance().retrieveWS(outWSName); + return ws; + } + + TableWorkspace_sptr + createTestCalibrationTableWorkspace(const std::string &outWSName) { + + ITableWorkspace_sptr itablews = WorkspaceFactory::Instance().createTable(); + AnalysisDataService::Instance().addOrReplace(outWSName, itablews); + + TableWorkspace_sptr tablews = + std::dynamic_pointer_cast(itablews); + TS_ASSERT(tablews); + + // Set up columns + for (size_t i = 0; + i < CorelliCalibration::calibrationTableColumnNames.size(); i++) { + std::string colname = CorelliCalibration::calibrationTableColumnNames[i]; + std::string type = CorelliCalibration::calibrationTableColumnTypes[i]; + tablews->addColumn(type, colname); + } + + // append rows + Mantid::API::TableRow sourceRow = tablews->appendRow(); + sourceRow << "moderator" << 0. << 0. << -15.560 << 0. << 0. << 0. << 0.; + Mantid::API::TableRow sampleRow = tablews->appendRow(); + sampleRow << "sample-position" << 0.0001 << -0.0002 << 0.003 << 0. << 0. + << 0. << 0.; + Mantid::API::TableRow bank1Row = tablews->appendRow(); + bank1Row << "bank1" << 0.9678 << 0.0056 << 0.0003 << 0.4563 << -0.9999 + << 0.3424 << 5.67; + // bank42 is at the x-axis (transverse of beam direction) + // rotating 180 should reverse its bottom pixel (1) and top pixel (256) + TableRow bank42Row = tablews->appendRow(); + bank42Row << "bank42" << 0. << 0. << 0. << 1. << 0. << 0. << 180.; + + return tablews; + } +}; diff --git a/Framework/Algorithms/test/CorelliCalibrationDatabaseTest.h b/Framework/Algorithms/test/CorelliCalibrationDatabaseTest.h new file mode 100644 index 0000000000000000000000000000000000000000..1512f321faff55d50f7080f71db9dfdb9650c8f9 --- /dev/null +++ b/Framework/Algorithms/test/CorelliCalibrationDatabaseTest.h @@ -0,0 +1,435 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/Axis.h" +#include "MantidAPI/ITableWorkspace.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/TableRow.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidAlgorithms/CorelliCalibrationDatabase.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidKernel/DateAndTime.h" +#include "MantidKernel/TimeSeriesProperty.h" +#include +#include + +using Mantid::Algorithms::CorelliCalibrationDatabase; +using namespace Mantid::API; +using namespace Mantid::DataObjects; +using namespace Mantid::Kernel; +using Mantid::Types::Core::DateAndTime; +using Mantid::Types::Event::TofEvent; +using namespace Mantid::Algorithms; + +class CorelliCalibrationDatabaseTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static CorelliCalibrationDatabaseTest *createSuite() { + return new CorelliCalibrationDatabaseTest(); + } + static void destroySuite(CorelliCalibrationDatabaseTest *suite) { + delete suite; + } + + //----------------------------------------------------------------------------- + void test_Init() { + CorelliCalibrationDatabase alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + } + + //----------------------------------------------------------------------------- + /** + * @brief Test basic file IO library methods + */ + void test_file_io() { + // create directory + std::string test_dir{"TestCorelliCalibrationX"}; + boost::filesystem::create_directory(test_dir); + TS_ASSERT(boost::filesystem::is_directory(test_dir)); + + // clean + boost::filesystem::remove_all(test_dir); + } + + //----------------------------------------------------------------------------- + /** + * @brief Test algorithm to convert datetime string to date stamp + */ + void test_timestamp_conversion() { + std::string yyyymmdd = + CorelliCalibrationDatabase::convertTimeStamp("2018-02-20T12:57:17"); + TS_ASSERT_EQUALS(yyyymmdd, "20180220"); + } + + //----------------------------------------------------------------------------- + /** + * @brief Test ComponentPosition + */ + void test_component() { + // compare: + CorelliCalibration::ComponentPosition pos1{0., 0., 0., 20., 30., 40., 50}; + CorelliCalibration::ComponentPosition pos2{0., 0., 0., 20., 30., 40., 50}; + CorelliCalibration::ComponentPosition pos3{0., 0., 0., 20.003, + 30., 40., 50}; + TS_ASSERT(pos1.equalTo(pos2, 1E-7)); + TS_ASSERT(!pos1.equalTo(pos3, 1E-7)); + } + + //----------------------------------------------------------------------------- + /** + * @brief Test CalibrationWworkspaceHandler + */ + void test_calibration_workspace_handler() { + // Create a correct calibration worksapce + std::string outwsname("CorelliCalibrationDatabaseTest_TableWS2"); + TableWorkspace_sptr calib_ws = + createTestCalibrationTableWorkspace(outwsname); + + // Create an incorrect calibration workspace + std::string wrongwsname{"CorelliCalibrationDatabaseTest_TableWS_Wrong"}; + TableWorkspace_sptr calib_wrong_ws = + createIncorrectTestCalibrationTableWorkspace(wrongwsname); + + // Init CalibrationTableHandler instance + CorelliCalibration::CalibrationTableHandler calib_handler = + CorelliCalibration::CalibrationTableHandler(); + + // Expect to fail set a wrong + TS_ASSERT_THROWS_ANYTHING( + calib_handler.setCalibrationTable(calib_wrong_ws)); + // Shall not throw + TS_ASSERT_THROWS_NOTHING(calib_handler.setCalibrationTable(calib_ws)); + + // Test method to retrieve components names (rows) + std::vector componentnames = calib_handler.getComponentNames(); + std::vector expectednames{"moderator", "sample-position", + "bank1/sixteenpack"}; + TS_ASSERT_EQUALS(componentnames.size(), expectednames.size()); + for (size_t i = 0; i < 3; ++i) + TS_ASSERT_EQUALS(componentnames[i], expectednames[i]); + + // Test: get component names + std::vector compNames = calib_handler.getComponentNames(); + TS_ASSERT_EQUALS(compNames.size(), 3); + + // Test: get component calibrated positions + CorelliCalibration::ComponentPosition goldsourcepos{0., 0., -15.560, 0., + 0., 0., 0.}; + CorelliCalibration::ComponentPosition testsourcepos = + calib_handler.getComponentCalibratedPosition("moderator"); + TS_ASSERT(testsourcepos.equalTo(goldsourcepos, 1.E-10)); + + CorelliCalibration::ComponentPosition goldbank1pos{ + 0.9678, 0.0056, 0.0003, 0.4563, -0.9999, 0.3424, 5.67}; + CorelliCalibration::ComponentPosition testbank1pos = + calib_handler.getComponentCalibratedPosition("bank1/sixteenpack"); + TS_ASSERT(testbank1pos.equalTo(goldbank1pos, 1.E-10)); + + // Test: save calibration table + // component file: name, remove file if it does exist, save and check file + // existence + const std::string testcalibtablefilename{"/tmp/testsourcedb2.csv"}; + boost::filesystem::remove(testcalibtablefilename); + calib_handler.saveCalibrationTable(testcalibtablefilename); + TS_ASSERT(boost::filesystem::exists(testcalibtablefilename)); + // load file and check + TableWorkspace_sptr duptable = + loadCSVtoTable(testcalibtablefilename, "DuplicatedSource"); + TS_ASSERT_EQUALS(duptable->rowCount(), 3); + TS_ASSERT_DELTA(duptable->cell(2, 6), 0.3424, 0.00001); + + // Test: save single component file + const std::string testsamplecalfilename{"/tmp/testsampledb2.csv"}; + boost::filesystem::remove(testsamplecalfilename); + // save + calib_handler.saveCompomentDatabase("20201117", "sample-position", + testsamplecalfilename); + TS_ASSERT(boost::filesystem::exists(testsamplecalfilename)); + + // load + TableWorkspace_sptr dupsampletable = + calib_handler.loadComponentCalibrationTable(testsamplecalfilename, + "TestSampleCalib1"); + // check row number and value + TS_ASSERT_EQUALS(dupsampletable->rowCount(), 1); + TS_ASSERT_DELTA(dupsampletable->cell(0, 2), -0.0002, 0.000001); + + // Clean + AnalysisDataService::Instance().remove(outwsname); + } + + //----------------------------------------------------------------------------- + /** + * @brief Test main features required by the algorithm + * CorelliCalibrationDatabase + */ + void test_exec() { + + // Create the test environment + // create directory database + std::string calibdir{"/tmp/TestCorelliCalibration1117"}; + // clean previous + boost::filesystem::remove_all(calibdir); + // create data base + boost::filesystem::create_directory(calibdir); + // create a previously generated database file + // will create the following files: + // moderator.csv, sample-position.csv, bank2.csv, bank42.csv + std::vector banks{"moderator", "sample-position", "bank2", + "bank42"}; + create_existing_database_files(calibdir, banks); + + // Create workspaces + EventWorkspace_sptr input_ws = createTestEventWorkspace(); + // Name of the output calibration workspace + std::string outwsname("CorelliCalibrationDatabaseTest_CombinedTableWS"); + TableWorkspace_sptr calib_ws = + createTestCalibrationTableWorkspace(outwsname); + TS_ASSERT(input_ws); + TS_ASSERT(calib_ws); + + // Init algorithm + CorelliCalibrationDatabase alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()); + + // Set up + TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", input_ws)); + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("InputCalibrationPatchWorkspace", calib_ws)); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("DatabaseDirectory", calibdir)); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("OutputWorkspace", outwsname)); + + // Execute + TS_ASSERT_THROWS_NOTHING(alg.execute()); + TS_ASSERT(alg.isExecuted()); + + // Verifying results + // Output 3: the combined calibration workspace + TS_ASSERT(AnalysisDataService::Instance().doesExist(outwsname)); + TableWorkspace_sptr combinedcalibws = + std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve(outwsname)); + TS_ASSERT(combinedcalibws); + // shall be 5 components + TS_ASSERT_EQUALS(combinedcalibws->rowCount(), 5); + TS_ASSERT_EQUALS(combinedcalibws->cell(1, 0), + "sample-position"); + TS_ASSERT_EQUALS(combinedcalibws->cell(2, 0), + "bank1/sixteenpack"); + TS_ASSERT_EQUALS(combinedcalibws->cell(4, 0), + "bank42/sixteenpack"); + + // Output 2: search the saved output calibration file + boost::filesystem::path pdir(calibdir); + boost::filesystem::path pbase("corelli_instrument_20201117.csv"); + boost::filesystem::path ptodaycalfile = pdir / pbase; + std::string todaycalfile = ptodaycalfile.string(); + TS_ASSERT(boost::filesystem::exists(todaycalfile)); + // load and compare + // ... ... + + // Output 1: check all the files + std::vector compnames{"moderator", "sample-position", "bank1", + "bank2", "bank42"}; + std::vector expectedrows{2, 2, 1, 1, 1}; + for (size_t i = 0; i < 5; ++i) { + verify_component_files(calibdir, compnames[i], expectedrows[i]); + } + + // Clean memory + // Remove workspace from the data service. + // AnalysisDataService::Instance().remove(outWSName); + } + +private: + /** + * @brief Create testing CORELLI event workspace + * @return + */ + EventWorkspace_sptr createTestEventWorkspace() { + // Name of the output workspace. + std::string outWSName("CorelliCalibrationDatabaseTest_matrixWS"); + + IAlgorithm_sptr lei = + AlgorithmFactory::Instance().create("LoadEmptyInstrument", 1); + lei->initialize(); + lei->setPropertyValue("Filename", "CORELLI_Definition.xml"); + lei->setPropertyValue("OutputWorkspace", + "CorelliCalibrationDatabaseTest_OutputWS"); + lei->setPropertyValue("MakeEventWorkspace", "1"); + lei->execute(); + + EventWorkspace_sptr ws; + ws = AnalysisDataService::Instance().retrieveWS( + "CorelliCalibrationDatabaseTest_OutputWS"); + + // Add property start_time + ws->mutableRun().addProperty("start_time", + "2020-11-17T12:57:17", "", true); + + return ws; + } + + /** + * @brief Create Test Calibration TableWorkspace + * + * @details this table would be the output of CorelliPowderCalibrationCreate + * + * @param outWSName + * @return + */ + TableWorkspace_sptr + createTestCalibrationTableWorkspace(const std::string &outWSName) { + + ITableWorkspace_sptr itablews = WorkspaceFactory::Instance().createTable(); + AnalysisDataService::Instance().addOrReplace(outWSName, itablews); + + TableWorkspace_sptr tablews = + std::dynamic_pointer_cast(itablews); + TS_ASSERT(tablews); + + // Set up columns + for (size_t i = 0; + i < CorelliCalibration::calibrationTableColumnNames.size(); ++i) { + std::string colname = CorelliCalibration::calibrationTableColumnNames[i]; + std::string type = CorelliCalibration::calibrationTableColumnTypes[i]; + tablews->addColumn(type, colname); + } + + std::cout << "[DEBUG 0] Table workspace rows: " << tablews->rowCount() + << "\n"; + + // append rows + Mantid::API::TableRow sourceRow = tablews->appendRow(); + sourceRow << "moderator" << 0. << 0. << -15.560 << 0. << 0. << 0. << 0.; + Mantid::API::TableRow sampleRow = tablews->appendRow(); + sampleRow << "sample-position" << 0.0001 << -0.0002 << 0.003 << 0. << 0. + << 0. << 0.; + Mantid::API::TableRow bank1Row = tablews->appendRow(); + bank1Row << "bank1/sixteenpack" << 0.9678 << 0.0056 << 0.0003 << 0.4563 + << -0.9999 << 0.3424 << 5.67; + + std::cout << "[DEBUG 1] Table workspace rows: " << tablews->rowCount() + << "\n"; + + return tablews; + } + + /** + * @brief Create an incompatile table workspace for algorithm to throw + * exception + * @param outWSName + * @return + */ + TableWorkspace_sptr + createIncorrectTestCalibrationTableWorkspace(const std::string &outWSName) { + // Create table workspace + ITableWorkspace_sptr itablews = WorkspaceFactory::Instance().createTable(); + AnalysisDataService::Instance().addOrReplace(outWSName, itablews); + + TableWorkspace_sptr tablews = + std::dynamic_pointer_cast(itablews); + TS_ASSERT(tablews); + + // Set up columns + for (size_t i = 0; + i < CorelliCalibration::calibrationTableColumnNames.size() - 1; ++i) { + std::string colname = CorelliCalibration::calibrationTableColumnNames[i]; + std::string type = CorelliCalibration::calibrationTableColumnTypes[i]; + tablews->addColumn(type, colname); + } + + // append rows + Mantid::API::TableRow sourceRow = tablews->appendRow(); + sourceRow << "moderator" << 0. << 0. << -15.560 << 0. << 0. << 0.; + Mantid::API::TableRow sampleRow = tablews->appendRow(); + sampleRow << "sample-position" << 0.0001 << -0.0002 << 0.003 << 0. << 0. + << 0.; + Mantid::API::TableRow bank1Row = tablews->appendRow(); + bank1Row << "bank1" << 0.9678 << 0.0056 << 0.0003 << 0.4563 << -0.9999 + << 0.3424; + + return tablews; + } + + TableWorkspace_sptr loadCSVtoTable(const std::string &csvname, + const std::string &tablewsname) { + IAlgorithm_sptr loadAsciiAlg = + AlgorithmFactory::Instance().create("LoadAscii", 2); + loadAsciiAlg->initialize(); + loadAsciiAlg->setPropertyValue("Filename", csvname); + loadAsciiAlg->setPropertyValue("OutputWorkspace", tablewsname); + loadAsciiAlg->setPropertyValue("Separator", "CSV"); + loadAsciiAlg->setPropertyValue("CommentIndicator", "#"); + loadAsciiAlg->execute(); + + TableWorkspace_sptr tablews = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve(tablewsname)); + + return tablews; + } + + /** + * @brief Create some existing database (csv) files + */ + void create_existing_database_files(const std::string &calibdir, + std::vector &banks) { + + boost::filesystem::path dir(calibdir); + + for (auto bankname : banks) { + // create full path database name + std::string basename = bankname + ".csv"; + boost::filesystem::path basepath(basename); + boost::filesystem::path fullpath = dir / basename; + std::string filename = fullpath.string(); + // write file + std::ofstream bankofs(filename, std::ofstream::out); + bankofs + << "# YYYMMDD , Xposition , Yposition , Zposition , XdirectionCosine " + ", YdirectionCosine , ZdirectionCosine , RotationAngle\n"; + bankofs << "# str , double , double , double , double , double , double " + ", double\n"; + bankofs << "20120321,0.0001,-0.0002,0.003,0,-23.3,98.02,0"; + bankofs.close(); + } + } + + /** + * @brief verify single component file existence and check some specific value + * @param calfiledir + * @param component + * @param expectedrecordsnumber + */ + void verify_component_files(const std::string &calfiledir, + const std::string &component, + size_t expectedrecordsnumber) { + // Create full file path + boost::filesystem::path pdir(calfiledir); + boost::filesystem::path pbase(component + ".csv"); + boost::filesystem::path pcompcalfile = pdir / pbase; + std::string compcalfile = pcompcalfile.string(); + + // Assert file existence + TS_ASSERT(boost::filesystem::exists(compcalfile)); + + // Load table + TableWorkspace_sptr tablews = + loadCSVtoTable(compcalfile, "CorelliVerify_" + component); + + // Check records + TS_ASSERT_EQUALS(tablews->rowCount(), expectedrecordsnumber); + } +}; diff --git a/Framework/Algorithms/test/CreateGroupingWorkspaceTest.h b/Framework/Algorithms/test/CreateGroupingWorkspaceTest.h index 79cd24af8cb0243ba5620f5d8da59a1ce06b1c4d..31d5225e85e0998e7ecb7d6ee689606139ae6a5c 100644 --- a/Framework/Algorithms/test/CreateGroupingWorkspaceTest.h +++ b/Framework/Algorithms/test/CreateGroupingWorkspaceTest.h @@ -196,6 +196,35 @@ public: AnalysisDataService::Instance().remove(outWSName); } + void + test_creating_a_grouping_workpsace_will_work_when_using_a_custom_grouping_string() { + auto &ads = AnalysisDataService::Instance(); + + const std::string outputWS("CreateGroupingWorkspaceTest_OutputWS"); + + CreateGroupingWorkspace alg; + alg.initialize(); + alg.setPropertyValue("InstrumentName", "IRIS"); + alg.setPropertyValue("ComponentName", "graphite"); + alg.setPropertyValue("CustomGroupingString", "3-5,6+7,8:10"); + alg.setPropertyValue("OutputWorkspace", outputWS); + TS_ASSERT_THROWS_NOTHING(alg.execute();); + TS_ASSERT(alg.isExecuted()); + + const auto groupingWorkspace = ads.retrieveWS(outputWS); + TS_ASSERT_DELTA(groupingWorkspace->dataY(0)[0], 1.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(1)[0], 1.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(2)[0], 1.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(3)[0], 2.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(4)[0], 2.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(5)[0], 3.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(6)[0], 4.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(7)[0], 5.0, 0.000001); + TS_ASSERT_DELTA(groupingWorkspace->dataY(8)[0], 0.0, 0.000001); + + ads.remove(outputWS); + } + void test_exec_WithFixedGroups_FailOnGroupsGreaterThanDet() { // Name of the output workspace. std::string outWSName("CreateGroupingWorkspaceTest_OutputWS_fail"); diff --git a/Framework/Algorithms/test/CropWorkspaceRaggedTest.h b/Framework/Algorithms/test/CropWorkspaceRaggedTest.h new file mode 100644 index 0000000000000000000000000000000000000000..c4eb7addf436c590d69dc3f07a3b74aab3915c0d --- /dev/null +++ b/Framework/Algorithms/test/CropWorkspaceRaggedTest.h @@ -0,0 +1,308 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidAlgorithms/ConvertToPointData.h" +#include "MantidAlgorithms/CropWorkspaceRagged.h" +#include "MantidDataObjects/Workspace2D.h" +#include + +using namespace Mantid; +using namespace Mantid::API; +using namespace Mantid::Algorithms; +using namespace Mantid::DataObjects; +using namespace Mantid::Kernel; + +class CropWorkspaceRaggedTest : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static CropWorkspaceRaggedTest *createSuite() { + return new CropWorkspaceRaggedTest(); + } + static void destroySuite(CropWorkspaceRaggedTest *suite) { delete suite; } + + void createInputWorkspace() { + std::string name = "toCrop"; + // Set up a small workspace for testing + Workspace_sptr space = WorkspaceFactory::Instance().create( + "Workspace2D", m_numberOfSpectra, m_numberOfYPoints + 1, + m_numberOfYPoints); + m_ws = std::dynamic_pointer_cast(space); + std::vector ydata, errors; + for (int j = 0; j < m_numberOfSpectra; ++j) { + ydata.clear(); + errors.clear(); + for (int k = 0; k < m_numberOfYPoints; ++k) { + ydata.push_back(double(k + 1)); + errors.push_back(sqrt(double(k + 1))); + m_ws->dataX(j)[k] = k; + } + m_ws->dataY(j) = ydata; + m_ws->dataE(j) = errors; + } + } + + void setUp() override { + m_numberOfYPoints = 15; + m_numberOfSpectra = 5; + createInputWorkspace(); + } + void test_Name() { TS_ASSERT_EQUALS(m_alg.name(), "CropWorkspaceRagged"); } + + void test_Version() { TS_ASSERT_EQUALS(m_alg.version(), 1); } + + void test_Init() { + return; + TS_ASSERT_THROWS_NOTHING(m_alg.initialize()); + TS_ASSERT(m_alg.isInitialized()); + } + + void test_NoInputs() { + if (!m_alg.isInitialized()) + m_alg.initialize(); + + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + void test_XMinLarger() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "5.")); + + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "10.")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + void test_XMinListBug() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "10")); + + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1.,2.,3.,20.,5.")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + void test_XMaxListBug() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1.")); + + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("XMax", "10.,20.,30.,0.4,50.")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + void test_ListsBug() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1.,2.,3.,20.,5.")); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("XMax", "10.,20.,30.,0.4,50.")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + void test_TooFewXMins() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "5.")); + + TS_ASSERT_THROWS(m_alg.setPropertyValue("XMin", ""), + const std::invalid_argument &); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1,2")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + + void test_TooFewXMaxs() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1.")); + + TS_ASSERT_THROWS(m_alg.setPropertyValue("XMax", ""), + const std::invalid_argument &); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11,12")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + + void test_TooManyMins() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11.")); + + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1,2,3,4,5,6")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + + void test_TooManyXMaxs() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "1.")); + + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("XMax", "11,12,13,14,15,16")); + TS_ASSERT_THROWS(m_alg.execute(), const std::runtime_error &); + } + + void test_SingleValueCrop() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "2.")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + for (int spec = 0; spec < m_numberOfSpectra; spec++) { + TS_ASSERT_DELTA(out->readX(spec)[0], 2.0, 1e-6); + TS_ASSERT_DELTA(out->readY(spec)[0], 3.0, 1e-6); + TS_ASSERT_DELTA(out->readE(spec)[0], 1.732051, 1e-6); // sqrt(3) + + TS_ASSERT_DELTA(out->readX(spec).back(), 11.0, 1e-6); + TS_ASSERT_DELTA(out->readY(spec).back(), 11.0, 1e-6); + TS_ASSERT_DELTA(out->readE(spec).back(), 3.316625, 1e-6); // sqrt(11) + } + } + + void test_MinListCrop() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + std::vector xMin{2., 5., 6., 7., 1.}; + + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("XMin", "2., 5., 6., 7., 1.")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + for (int spec = 0; spec < m_numberOfSpectra; spec++) { + TS_ASSERT_DELTA(out->readX(spec)[0], xMin[spec], 1e-6); + TS_ASSERT_DELTA(out->readY(spec)[0], xMin[spec] + 1., 1e-6); + TS_ASSERT_DELTA(out->readE(spec)[0], sqrt(xMin[spec] + 1.), 1e-6); + + TS_ASSERT_DELTA(out->readX(spec).back(), 11.0, 1e-6); + TS_ASSERT_DELTA(out->readY(spec).back(), 11.0, 1e-6); + TS_ASSERT_DELTA(out->readE(spec).back(), 3.316625, 1e-6); // sqrt(11) + } + } + void test_MaxListCrop() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + std::vector xMax{12., 13., 11., 8., 9.}; + + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "2.")); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("XMax", "12, 13, 11, 8, 9")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + for (int spec = 0; spec < m_numberOfSpectra; spec++) { + TS_ASSERT_DELTA(out->readX(spec)[0], 2.0, 1e-6); + TS_ASSERT_DELTA(out->readY(spec)[0], 3.0, 1e-6); + TS_ASSERT_DELTA(out->readE(spec)[0], sqrt(3.), 1e-6); + + TS_ASSERT_DELTA(out->readX(spec).back(), xMax[spec], 1e-6); + TS_ASSERT_DELTA(out->readY(spec).back(), xMax[spec], 1e-6); + TS_ASSERT_DELTA(out->readE(spec).back(), sqrt(xMax[spec]), 1e-6); + } + } + + void test_preservesHist() { + TS_ASSERT(m_ws->isHistogramData()); + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "2.")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + TS_ASSERT(out->isHistogramData()); + } + + void test_preservesPoints() { + ConvertToPointData alg; + alg.initialize(); + alg.setRethrows(true); + alg.setProperty("InputWorkspace", m_ws); + alg.setProperty("OutputWorkspace", "points"); + alg.execute(); + MatrixWorkspace_sptr points = + AnalysisDataService::Instance().retrieveWS("points"); + + TS_ASSERT_EQUALS(points->isHistogramData(), false); + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", points)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "2.")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + TS_ASSERT_EQUALS(out->isHistogramData(), false); + } + + void test_xMaxMoreThanData() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "2.")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "111")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + + for (int spec = 0; spec < m_numberOfSpectra; spec++) { + TS_ASSERT_DELTA(out->readX(spec)[0], 2.0, 1e-6); + TS_ASSERT_DELTA(out->readY(spec)[0], 3.0, 1e-6); + TS_ASSERT_DELTA(out->readE(spec)[0], 1.732051, 1e-6); // sqrt(3) + + TS_ASSERT_DELTA(out->readX(spec).back(), m_ws->readX(spec).back(), 1e-6); + TS_ASSERT_DELTA(out->readY(spec).back(), m_ws->readY(spec).back(), 1e-6); + TS_ASSERT_DELTA(out->readE(spec).back(), m_ws->readE(spec).back(), 1e-6); + } + } + + void test_xMinLessThanData() { + TS_ASSERT_THROWS_NOTHING(m_alg.setProperty("InputWorkspace", m_ws)); + TS_ASSERT_THROWS_NOTHING( + m_alg.setPropertyValue("OutputWorkspace", "nothing")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMin", "-2.")); + TS_ASSERT_THROWS_NOTHING(m_alg.setPropertyValue("XMax", "11")); + TS_ASSERT_THROWS_NOTHING(m_alg.execute()); + + MatrixWorkspace_sptr out = + AnalysisDataService::Instance().retrieveWS("nothing"); + for (int spec = 0; spec < m_numberOfSpectra; spec++) { + TS_ASSERT_DELTA(out->readX(spec)[0], m_ws->readX(spec)[0], 1e-6); + TS_ASSERT_DELTA(out->readY(spec)[0], m_ws->readY(spec)[0], 1e-6); + TS_ASSERT_DELTA(out->readE(spec)[0], m_ws->readE(spec)[0], 1e-6); + + TS_ASSERT_DELTA(out->readX(spec).back(), 11.0, 1e-6); + TS_ASSERT_DELTA(out->readY(spec).back(), 11.0, 1e-6); + TS_ASSERT_DELTA(out->readE(spec).back(), 3.316625, 1e-6); // sqrt(11) + } + } + +private: + Workspace2D_sptr m_ws; + CropWorkspaceRagged m_alg; + int m_numberOfSpectra; + int m_numberOfYPoints; +}; diff --git a/Framework/Algorithms/test/FitPeaksTest.h b/Framework/Algorithms/test/FitPeaksTest.h index 8beb684f31a1187eb3f457f2e1ae136caa1b04ab..9942d73e96bf9715f0224409da1cceaaaa757a93 100644 --- a/Framework/Algorithms/test/FitPeaksTest.h +++ b/Framework/Algorithms/test/FitPeaksTest.h @@ -66,12 +66,12 @@ public: void test_singlePeaksPartialSpectra() { // Generate input workspace const std::string data_ws_name("Test1Data"); - createTestData(data_ws_name); + genearteTestDataGaussian(data_ws_name); // Generate peak and background parameters std::vector peakparnames; std::vector peakparvalues; - gen_PeakParameters(peakparnames, peakparvalues); + createBackToBackExponentialParameters(peakparnames, peakparvalues); // create a 1-value peak index vector for peak (0) at X=5 std::vector peak_index_vec; @@ -142,13 +142,16 @@ public: * @brief test_multiPeaksMultiSpectra */ void test_multiPeaksMultiSpectra() { + // run serially so values don't depend on no. cores etc. + FrameworkManager::Instance().setNumOMPThreads(1); + // set up parameters with starting value std::vector peakparnames; std::vector peakparvalues; createGuassParameters(peakparnames, peakparvalues); // Generate input workspace - createTestData(m_inputWorkspaceName); + genearteTestDataGaussian(m_inputWorkspaceName); // initialize algorithm to test FitPeaks fitpeaks; @@ -219,7 +222,6 @@ public: double ws1peak0_width = param_ws->cell(2, 4); TS_ASSERT_DELTA(ws1peak0_height, 4., 1E-6); TS_ASSERT_DELTA(ws1peak0_width, 0.17, 1E-6); - double ws1peak1_height = param_ws->cell(3, 2); double ws1peak1_width = param_ws->cell(3, 4); TS_ASSERT_DELTA(ws1peak1_height, 2., 1E-6); @@ -242,6 +244,8 @@ public: AnalysisDataService::Instance().remove("PeakPositionsWS"); AnalysisDataService::Instance().remove("FittedPeaksWS"); AnalysisDataService::Instance().remove("PeakParametersWS"); + + FrameworkManager::Instance().setNumOMPThreadsToConfigValue(); } //---------------------------------------------------------------------------------------------- @@ -249,13 +253,16 @@ public: * @brief test_effectivePeakParameters */ void test_effectivePeakParameters() { + // run serially so values don't depend on no. cores etc. + FrameworkManager::Instance().setNumOMPThreads(1); + // set up parameters with starting value std::vector peakparnames; std::vector peakparvalues; createGuassParameters(peakparnames, peakparvalues); // Generate input workspace - createTestData(m_inputWorkspaceName); + genearteTestDataGaussian(m_inputWorkspaceName); // initialize algorithm to test FitPeaks fitpeaks; @@ -350,172 +357,8 @@ public: AnalysisDataService::Instance().remove("PeakPositionsWS"); AnalysisDataService::Instance().remove("FittedPeaksWS"); AnalysisDataService::Instance().remove("PeakParametersWS"); - } - - //---------------------------------------------------------------------------------------------- - /** Test on single peak on partial spectra - */ - void Ntest_singlePeakMultiSpectra() { - // Generate input workspace - // std::string input_ws_name = loadVulcanHighAngleData(); - - // Generate peak and background parameters - std::vector peakparnames; - std::vector peakparvalues; - gen_PeakParameters(peakparnames, peakparvalues); - - // Initialize FitPeak - FitPeaks fitpeaks; - - fitpeaks.initialize(); - TS_ASSERT(fitpeaks.isInitialized()); - - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("InputWorkspace", m_inputWorkspaceName)); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("StartWorkspaceIndex", 19990)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("StopWorkspaceIndex", 20000)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("PeakCenters", "1.0758")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("FitWindowLeftBoundary", "1.05")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("FitWindowRightBoundary", "1.15")); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("PeakRanges", "0.02")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("PeakParameterValues", peakparvalues)); - - fitpeaks.setProperty("OutputWorkspace", "PeakPositionsWS3"); - fitpeaks.setProperty("OutputPeakParametersWorkspace", "PeakParametersWS3"); - fitpeaks.setProperty("FittedPeaksWorkspace", "FittedPeaksWS3"); - - fitpeaks.execute(); - TS_ASSERT(fitpeaks.isExecuted()); - - // check output workspaces - TS_ASSERT( - API::AnalysisDataService::Instance().doesExist("PeakPositionsWS3")); - TS_ASSERT( - API::AnalysisDataService::Instance().doesExist("PeakParametersWS3")); - TS_ASSERT(API::AnalysisDataService::Instance().doesExist("FittedPeaksWS3")); - - // about the parameters - API::MatrixWorkspace_sptr peak_params_ws = - std::dynamic_pointer_cast( - AnalysisDataService::Instance().retrieve("PeakParametersWS3")); - TS_ASSERT(peak_params_ws); - TS_ASSERT_EQUALS(peak_params_ws->getNumberHistograms(), 5); - TS_ASSERT_EQUALS(peak_params_ws->histogram(0).x().size(), 10); - return; - } - - void Ntest_singlePeakMultiSpectraPseudoVoigt() { - // Generate input workspace - // std::string input_ws_name = loadVulcanHighAngleData(); - - // Generate peak and background parameters - std::vector peakparvalues{0.5}; - - // Initialize FitPeak - FitPeaks fitpeaks; - - fitpeaks.initialize(); - TS_ASSERT(fitpeaks.isInitialized()); - - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("InputWorkspace", m_inputWorkspaceName)); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("StartWorkspaceIndex", 19990)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("StopWorkspaceIndex", 20000)); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("PeakFunction", "PseudoVoigt")); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("PeakCenters", "1.0758")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("FitWindowLeftBoundary", "1.05")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("FitWindowRightBoundary", "1.15")); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("PeakRanges", "0.02")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("PeakParameterValues", peakparvalues)); - - fitpeaks.setProperty("OutputWorkspace", "PeakPositionsWS3"); - fitpeaks.setProperty("OutputPeakParametersWorkspace", "PeakParametersWS3"); - fitpeaks.setProperty("FittedPeaksWorkspace", "FittedPeaksWS3"); - - fitpeaks.execute(); - TS_ASSERT(fitpeaks.isExecuted()); - - // check output workspaces - TS_ASSERT( - API::AnalysisDataService::Instance().doesExist("PeakPositionsWS3")); - TS_ASSERT( - API::AnalysisDataService::Instance().doesExist("PeakParametersWS3")); - TS_ASSERT(API::AnalysisDataService::Instance().doesExist("FittedPeaksWS3")); - - // about the parameters - API::MatrixWorkspace_sptr peak_params_ws = - std::dynamic_pointer_cast( - AnalysisDataService::Instance().retrieve("PeakParametersWS3")); - TS_ASSERT(peak_params_ws); - TS_ASSERT_EQUALS(peak_params_ws->getNumberHistograms(), 5); - TS_ASSERT_EQUALS(peak_params_ws->histogram(0).x().size(), 10); - - return; - } - - //---------------------------------------------------------------------------------------------- - /** Test on init and setup - */ - void Ntest_SingleSpectrum3Peaks() { - // Generate input workspace - // std::string input_ws_name = loadVulcanHighAngleData(); - - // Generate peak and background parameters - std::vector peakparnames; - std::vector peakparvalues; - gen_PeakParameters(peakparnames, peakparvalues); - - // Initialize FitPeak - FitPeaks fitpeaks; - - fitpeaks.initialize(); - TS_ASSERT(fitpeaks.isInitialized()); - - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("InputWorkspace", m_inputWorkspaceName)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("StartWorkspaceIndex", 6468)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("StopWorkspaceIndex", 24900)); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("PeakCenters", "1.0758, 0.89198")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("FitWindowLeftBoundary", "1.05, 0.87")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("FitWindowRightBoundary", "1.15, 0.92")); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("PeakRanges", "0.02, 0.015")); - TS_ASSERT_THROWS_NOTHING( - fitpeaks.setProperty("PeakParameterValues", peakparvalues)); - - fitpeaks.setProperty("OutputWorkspace", "PeakPositionsWS2"); - fitpeaks.setProperty("OutputPeakParametersWorkspace", "PeakParametersWS2"); - fitpeaks.setProperty("FittedPeaksWorkspace", "FittedPeaksWS2"); - - fitpeaks.execute(); - TS_ASSERT(fitpeaks.isExecuted()); - - TS_ASSERT( - API::AnalysisDataService::Instance().doesExist("PeakPositionsWS2")); - - TS_ASSERT(API::AnalysisDataService::Instance().doesExist("FittedPeaksWS2")); - API::MatrixWorkspace_sptr fitted_data_ws = - std::dynamic_pointer_cast( - API::AnalysisDataService::Instance().retrieve("FittedPeaksWS2")); - TS_ASSERT(fitted_data_ws); - - // API::MatrixWorkspace_const_sptr fitted_data_ws = - // fitpeaks.getProperty("FittedPeaksWS2"); - TS_ASSERT_EQUALS(fitted_data_ws->getNumberHistograms(), 24900); - - return; + FrameworkManager::Instance().setNumOMPThreadsToConfigValue(); } //---------------------------------------------------------------------------------------------- @@ -694,20 +537,140 @@ public: } //---------------------------------------------------------------------------------------------- - /** Test on VULCAN's data including 2 different starting value of peak - * profiles + /** Test fitting Back-to-back exponential with single peak on multiple spectra + * Test includes + * 1. fit high angle detector spectra from 7 to 15 (StopWorkspace is included) + * 2. fit 1 peak at d = 1.0758 */ - void test_multiple_peak_profiles() { + void test_singlePeakMultiSpectraBackToBackExp() { // Generate input workspace - std::string input_ws_name = loadVulcanHighAngleData(); + std::string input_ws_name = generateTestDataBackToBackExponential(); + // Specify output workspaces names + std::string peak_pos_ws_name("PeakPositionsB2BsPmS"); + std::string param_ws_name("PeakParametersB2BsPmS"); + std::string model_ws_name("ModelB2BsPmS"); + + // Generate peak and background parameters + std::vector peakparnames; + std::vector peakparvalues; + createBackToBackExponentialParameters(peakparnames, peakparvalues, false); + + // Input data workspace contains 16 spectra + auto inputWS = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve(input_ws_name)); + TS_ASSERT(inputWS); + TS_ASSERT_EQUALS(inputWS->getNumberHistograms(), 16); + + size_t start_ws_index = 7; + size_t stop_ws_index = 15; + + // Initialize FitPeak + FitPeaks fitpeaks; + + fitpeaks.initialize(); + TS_ASSERT(fitpeaks.isInitialized()); + + TS_ASSERT_THROWS_NOTHING( + fitpeaks.setProperty("InputWorkspace", input_ws_name)); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty( + "StartWorkspaceIndex", static_cast(start_ws_index))); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty( + "StopWorkspaceIndex", static_cast(stop_ws_index))); + TS_ASSERT_THROWS_NOTHING( + fitpeaks.setProperty("PeakFunction", "BackToBackExponential")); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("BackgroundType", "Linear")); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("PeakCenters", "1.0758")); + TS_ASSERT_THROWS_NOTHING( + fitpeaks.setProperty("FitWindowBoundaryList", "1.05, 1.11")); + TS_ASSERT_THROWS_NOTHING( + fitpeaks.setProperty("PeakParameterNames", peakparnames)); + TS_ASSERT_THROWS_NOTHING( + fitpeaks.setProperty("PeakParameterValues", peakparvalues)); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("FitFromRight", true)); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("HighBackground", false)); + + fitpeaks.setProperty("OutputWorkspace", peak_pos_ws_name); + fitpeaks.setProperty("OutputPeakParametersWorkspace", param_ws_name); + fitpeaks.setProperty("FittedPeaksWorkspace", model_ws_name); + + fitpeaks.execute(); + TS_ASSERT(fitpeaks.isExecuted()); + + // check output workspaces + TS_ASSERT(API::AnalysisDataService::Instance().doesExist(peak_pos_ws_name)); + TS_ASSERT(API::AnalysisDataService::Instance().doesExist(param_ws_name)); + TS_ASSERT(API::AnalysisDataService::Instance().doesExist(model_ws_name)); + + // About the parameters + API::MatrixWorkspace_sptr peak_pos_ws = + std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve(peak_pos_ws_name)); + // Check peak values + // Only the number of selected spectra will be recorded + size_t expectedNumSpectra = stop_ws_index - start_ws_index + 1; + + TS_ASSERT(peak_pos_ws); + TS_ASSERT_EQUALS(peak_pos_ws->getNumberHistograms(), expectedNumSpectra); + TS_ASSERT_EQUALS(peak_pos_ws->histogram(0).y().size(), 1); + // verify fitted peak position values + for (size_t i = 0; i < expectedNumSpectra; ++i) { + TS_ASSERT_DELTA(peak_pos_ws->histogram(i).y()[0], 1.0758, 1.2E-3); + } + + // Get the table workspace + auto param_table = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve(param_ws_name)); + TS_ASSERT(param_table); + + // Columns: 0. wsindex, 1. peakindex, 2. I, 3. A, 4. B, 5. X0, 6. S, 7. + // A0, 8. A1, 9. chi2 + std::vector colnames = param_table->getColumnNames(); + TS_ASSERT_EQUALS(colnames.size(), 10); + + // Verify intensity: between 90 and 150 + for (size_t i = 0; i < expectedNumSpectra; ++i) { + TS_ASSERT(param_table->cell(i, 2) > 90. && + param_table->cell(i, 2) < 150); + } + // workspace index 10, 11 and 12 are smallest with output index at 4, 5 and + // 6 + TS_ASSERT(param_table->cell(0, 2) > + param_table->cell(4, 2)); + TS_ASSERT(param_table->cell(8, 2) > + param_table->cell(5, 2)); + + // Clean up + AnalysisDataService::Instance().remove(input_ws_name); + AnalysisDataService::Instance().remove(peak_pos_ws_name); + AnalysisDataService::Instance().remove(param_ws_name); + AnalysisDataService::Instance().remove(model_ws_name); + + return; + } + + //---------------------------------------------------------------------------------------------- + /** Test to fit multiple peaks on multiple spectra with back-to-back + * exponential convoluted with Gaussian + */ + void test_multiPeaksMultiSpectraBackToBackExp() { + // Generate input workspace + std::string input_ws_name = generateTestDataBackToBackExponential(); API::MatrixWorkspace_sptr input_ws = std::dynamic_pointer_cast( AnalysisDataService::Instance().retrieve(input_ws_name)); + // Specify output workspaces names + std::string peak_pos_ws_name("PeakPositionsB2BmPmS"); + std::string param_ws_name("PeakParametersB2BmPmS"); + std::string model_ws_name("ModelB2BmPmS"); + // Generate peak and background parameters std::vector peakparnames; std::vector peakparvalues; - gen_PeakParameters(peakparnames, peakparvalues); + createBackToBackExponentialParameters(peakparnames, peakparvalues); + + size_t min_ws_index = 7; + size_t max_ws_index = 15; // Initialize FitPeak FitPeaks fitpeaks; @@ -717,16 +680,18 @@ public: TS_ASSERT_THROWS_NOTHING( fitpeaks.setProperty("InputWorkspace", input_ws_name)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("StartWorkspaceIndex", 0)); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("StopWorkspaceIndex", 5)); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty( + "StartWorkspaceIndex", static_cast(min_ws_index))); + TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty( + "StopWorkspaceIndex", static_cast(max_ws_index))); TS_ASSERT_THROWS_NOTHING( fitpeaks.setProperty("PeakFunction", "BackToBackExponential")); TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("BackgroundType", "Linear")); TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty( - "PeakCenters", "0.6867, 0.728299, 0.89198, 1.0758")); - TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty( - "FitWindowBoundaryList", - "0.67, 0.709, 0.71, 0.76, 0.87, 0.92, 1.05, 1.15")); + "PeakCenters", "0.728299, 0.817, 0.89198, 1.0758")); + TS_ASSERT_THROWS_NOTHING( + fitpeaks.setProperty("FitWindowBoundaryList", + "0.71, 0.76, 0.80, 0.84, 0.87, 0.91, 1.05, 1.11")); TS_ASSERT_THROWS_NOTHING( fitpeaks.setProperty("PeakParameterNames", peakparnames)); TS_ASSERT_THROWS_NOTHING( @@ -734,57 +699,88 @@ public: TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("FitFromRight", true)); TS_ASSERT_THROWS_NOTHING(fitpeaks.setProperty("HighBackground", false)); - fitpeaks.setProperty("OutputWorkspace", "PeakPositionsWS2"); - fitpeaks.setProperty("OutputPeakParametersWorkspace", "PeakParametersWS2"); - fitpeaks.setProperty("FittedPeaksWorkspace", "FittedPeaksWS2"); + fitpeaks.setProperty("OutputWorkspace", peak_pos_ws_name); + fitpeaks.setProperty("OutputPeakParametersWorkspace", param_ws_name); + fitpeaks.setProperty("FittedPeaksWorkspace", model_ws_name); fitpeaks.execute(); TS_ASSERT(fitpeaks.isExecuted()); - // Check outputs + // Verify the existence of output workspaces bool peak_pos_ws_exist, peak_param_ws_exist, fitted_ws_exist; API::MatrixWorkspace_sptr peak_pos_ws = - CheckAndRetrieveMatrixWorkspace("PeakPositionsWS2", &peak_pos_ws_exist); - API::MatrixWorkspace_sptr fitted_ws = - CheckAndRetrieveMatrixWorkspace("FittedPeaksWS2", &fitted_ws_exist); - API::ITableWorkspace_sptr peak_param_ws = CheckAndRetrieveTableWorkspace( - "PeakParametersWS2", &peak_param_ws_exist); + CheckAndRetrieveMatrixWorkspace(peak_pos_ws_name, &peak_pos_ws_exist); + API::MatrixWorkspace_sptr model_ws = + CheckAndRetrieveMatrixWorkspace(model_ws_name, &fitted_ws_exist); + API::ITableWorkspace_sptr peak_param_ws = + CheckAndRetrieveTableWorkspace(param_ws_name, &peak_param_ws_exist); + + size_t num_histograms = max_ws_index - min_ws_index + 1; + // Verify peaks' positions if (peak_pos_ws_exist) { - // workspace for peak positions from fitted value - TS_ASSERT_EQUALS(peak_pos_ws->getNumberHistograms(), 6); - HistogramData::HistogramY peak_pos_0 = peak_pos_ws->histogram(0).y(); - HistogramData::HistogramE pos_error_0 = peak_pos_ws->histogram(0).e(); - TS_ASSERT_DELTA(peak_pos_0[0], -4, - 0.0000001); // peak is out of data range - TS_ASSERT(pos_error_0[0] > 1E20); - TS_ASSERT(pos_error_0[3] < 100.); + // workspace for peak positions from fitted value: must be a 16 x 4 + // workspace2D + TS_ASSERT_EQUALS(peak_pos_ws->getNumberHistograms(), num_histograms); + TS_ASSERT_EQUALS(peak_pos_ws->histogram(0).y().size(), 4); + + // Expected value: with one peak that cannot be fit + std::vector exp_positions{-4, 0.81854, 0.89198, 1.0758}; + + for (size_t ih = 0; ih < num_histograms; ++ih) { + // Get histogram + HistogramData::HistogramY peak_pos_i = peak_pos_ws->histogram(ih).y(); + HistogramData::HistogramE pos_error_i = peak_pos_ws->histogram(ih).e(); + // Check value + for (auto ip = 0; ip < 4; ++ip) { + + // FIXME - the exact reason why only this peak gets failed with + // fitting has not been discovered yet. It is related to the starting + // values and fit window. Hopefully the future improved fitting + // algorithm will solve this issue. + if (ih == 5 && ip == 2) { + continue; + } + + // Check fitted peak positions with the expected positions + TS_ASSERT_DELTA(peak_pos_i[ip], exp_positions[ip], 0.0012); + if (peak_pos_i[ip] > 0) { + // a good fit: require error less than 100 + TS_ASSERT(pos_error_i[ip] < 150.); + } else { + // failed fit + TS_ASSERT(pos_error_i[ip] > 1E20); + } + } + } } + // Verify calcualated model if (fitted_ws_exist) { // workspace for calculated peaks from fitted data - TS_ASSERT_EQUALS(fitted_ws->getNumberHistograms(), + TS_ASSERT_EQUALS(model_ws->getNumberHistograms(), input_ws->getNumberHistograms()); } if (peak_param_ws_exist) { // workspace for calcualted peak parameters - TS_ASSERT_EQUALS(peak_param_ws->rowCount(), 4 * 6); + TS_ASSERT_EQUALS(peak_param_ws->rowCount(), (4 * num_histograms)); // check third spectrum size_t iws = 2; + // peak is out of range. zero intensity double peak_intensity_2_0 = peak_param_ws->cell(iws * 4, 2); TS_ASSERT_DELTA(peak_intensity_2_0, 0., 1.E-20); double peak_intensity_2_2 = peak_param_ws->cell(iws * 4 + 2, 2); - TS_ASSERT_DELTA(peak_intensity_2_2, 213.03, 0.03); + TS_ASSERT_DELTA(peak_intensity_2_2, 46.77, 2.0); double peak_intensity_2_3 = peak_param_ws->cell(iws * 4 + 3, 2); - TS_ASSERT_DELTA(peak_intensity_2_3, 1161.78, 4.0); + TS_ASSERT_DELTA(peak_intensity_2_3, 146.44, 2.0); } // clean AnalysisDataService::Instance().remove(input_ws_name); - AnalysisDataService::Instance().remove("PeakPositionsWS2"); - AnalysisDataService::Instance().remove("FittedPeaksWS2"); - AnalysisDataService::Instance().remove("PeakParametersWS2"); + AnalysisDataService::Instance().remove(peak_pos_ws_name); + AnalysisDataService::Instance().remove(param_ws_name); + AnalysisDataService::Instance().remove(model_ws_name); return; } @@ -801,7 +797,7 @@ public: createGuassParameters(peakparnames, peakparvalues); // Generate input workspace - createTestData(m_inputWorkspaceName); + genearteTestDataGaussian(m_inputWorkspaceName); // initialize algorithm to test FitPeaks fitpeaks; @@ -881,46 +877,10 @@ public: AnalysisDataService::Instance().remove("FitErrorsWS"); } - //---------------------------------------------------------------------------------------------- - /** Generate peak parameters for Back-to-back exponential convoluted by - * Gaussian - * FitPeak(InputWorkspace='diamond_high_res_d', OutputWorkspace='peak0_19999', - * ParameterTableWorkspace='peak0_19999_Param', WorkspaceIndex=19999, - * PeakFunctionType='BackToBackExponential', PeakParameterNames='I,A,B,X0,S', - * PeakParameterValues='2.5e+06,5400,1700,1.07,0.000355', - * FittedPeakParameterValues='129.407,-1.82258e+06,-230935,1.06065,-0.0154214', - * BackgroundParameterNames='A0,A1', BackgroundParameterValues='0,0', - * FittedBackgroundParameterValues='3694.92,-3237.13', FitWindow='1.05,1.14', - * PeakRange='1.06,1.09', - * MinGuessedPeakWidth=10, MaxGuessedPeakWidth=20, GuessedPeakWidthStep=1, - * PeakPositionTolerance=0.02) - */ - void gen_PeakParameters(vector &parnames, vector &parvalues) { - parnames.clear(); - parvalues.clear(); - - parnames.emplace_back("I"); - parvalues.emplace_back(2.5e+06); - - parnames.emplace_back("A"); - parvalues.emplace_back(5400); - - parnames.emplace_back("B"); - parvalues.emplace_back(1700); - - parnames.emplace_back("X0"); - parvalues.emplace_back(1.07); - - parnames.emplace_back("S"); - parvalues.emplace_back(0.000355); - - return; - } - //-------------------------------------------------------------------------------------------------------------- /** generate a peak center workspace compatible to the workspace created by - * createTestData(), which will 3 spectra and at most 2 elements for each - * sepctrum + * genearteTestDataGaussian(), which will 3 spectra and at most 2 elements for + * each sepctrum * @brief genPeakCenterWorkspace * @param peak_index_vec :: a vector of integer as 0 or 1. 0 for peak center * @param workspace_name @@ -941,9 +901,6 @@ public: WorkspaceCreationHelper::create2DWorkspaceWithValuesAndXerror( nhist, nbins, ishist, xval, yval, eval, dxval, maskedws)); - std::cout << "Center workspace has " << center_ws->readX(0).size() - << " bins" - << "\n"; for (size_t i = 0; i < center_ws->getNumberHistograms(); ++i) { for (size_t j = 0; j < peak_index_vec.size(); ++j) { const int peak_index = peak_index_vec[j]; @@ -959,7 +916,7 @@ public: //-------------------------------------------------------------------------------------------------------------- /** create a fit window workspace compatibel to the workspace created by - * createTestData() + * genearteTestDataGaussian() * @brief genFitWindowWorkspace :: generate a matrix work for peak fitting * window * @param peak_index_vec :: vector for peak indexes @@ -993,10 +950,10 @@ public: * ws-index = 0: peak 0 @ 5.00; peak 1 @ 10.00 * ws-index = 1: peak 0 @ 5.01; peak 1 @ 9.98 * ws-index = 2: peak 0 @ 5.03; peak 1 @ 10.02 - * @brief createTestData + * @brief genearteTestDataGaussian * @param workspacename */ - void createTestData(const std::string &workspacename) { + void genearteTestDataGaussian(const std::string &workspacename) { // ---- Create the simple workspace ------- size_t num_spec = 3; @@ -1015,22 +972,22 @@ public: std::transform(xvals.cbegin(), xvals.cend(), WS->mutableY(0).begin(), [](const double x) { return exp(-0.5 * pow((x - 10) / 0.1, 2)) + - 2.0 * exp(-0.5 * pow((x - 5) / 0.15, 2)) + 1.E-10; + 2.0 * exp(-0.5 * pow((x - 5) / 0.15, 2)) + 1; }); const auto &yvals = WS->histogram(0).y(); std::transform(yvals.cbegin(), yvals.cend(), WS->mutableE(0).begin(), - [](const double y) { return sqrt(y); }); + [](const double y) { return 0.2 * sqrt(y); }); if (num_spec > 1) { const auto &xvals1 = WS->points(1); std::transform(xvals1.cbegin(), xvals1.cend(), WS->mutableY(1).begin(), [](const double x) { return 2. * exp(-0.5 * pow((x - 9.98) / 0.12, 2)) + - 4.0 * exp(-0.5 * pow((x - 5.01) / 0.17, 2)); + 4.0 * exp(-0.5 * pow((x - 5.01) / 0.17, 2)) + 1; }); const auto &yvals1 = WS->histogram(1).y(); std::transform(yvals1.cbegin(), yvals1.cend(), WS->mutableE(1).begin(), - [](const double y) { return sqrt(y); }); + [](const double y) { return 0.2 * sqrt(y); }); } if (num_spec > 2) { @@ -1038,20 +995,14 @@ public: std::transform(xvals2.cbegin(), xvals2.cend(), WS->mutableY(2).begin(), [](const double x) { return 10 * exp(-0.5 * pow((x - 10.02) / 0.14, 2)) + - 3.0 * exp(-0.5 * pow((x - 5.03) / 0.19, 2)); + 3.0 * exp(-0.5 * pow((x - 5.03) / 0.19, 2)) + 1; }); const auto &yvals2 = WS->histogram(2).y(); std::transform(yvals2.cbegin(), yvals2.cend(), WS->mutableE(2).begin(), - [](const double y) { return sqrt(y); }); + [](const double y) { return 0.2 * sqrt(y); }); } AnalysisDataService::Instance().addOrReplace(workspacename, WS); - - // auto vecx = WS->x(2); - // auto vecy = WS->y(2); - // for (size_t i = 0; i < vecx.size(); ++i) - // std::cout << vecx[i] << "\t" << vecy[i] << "\n"; - return; } @@ -1077,7 +1028,7 @@ public: * exponenential convoluted * by Gaussian */ - std::string loadVulcanHighAngleData() { + std::string generateTestDataBackToBackExponential() { DataHandling::LoadNexusProcessed loader; loader.initialize(); @@ -1096,6 +1047,49 @@ public: return "diamond_3peaks"; } + //---------------------------------------------------------------------------------------------- + /** Generate peak parameters for Back-to-back exponential convoluted by + * Gaussian + * FitPeak(InputWorkspace='diamond_high_res_d', OutputWorkspace='peak0_19999', + * ParameterTableWorkspace='peak0_19999_Param', WorkspaceIndex=19999, + * PeakFunctionType='BackToBackExponential', PeakParameterNames='I,A,B,X0,S', + * PeakParameterValues='2.5e+06,5400,1700,1.07,0.000355', + * FittedPeakParameterValues='129.407,-1.82258e+06,-230935,1.06065,-0.0154214', + * BackgroundParameterNames='A0,A1', BackgroundParameterValues='0,0', + * FittedBackgroundParameterValues='3694.92,-3237.13', FitWindow='1.05,1.14', + * PeakRange='1.06,1.09', + * MinGuessedPeakWidth=10, MaxGuessedPeakWidth=20, GuessedPeakWidthStep=1, + * PeakPositionTolerance=0.02) + */ + void + createBackToBackExponentialParameters(vector &parnames, + vector &parvalues, + bool include_pos_intensity = true) { + parnames.clear(); + parvalues.clear(); + + if (include_pos_intensity) { + parnames.emplace_back("I"); + parvalues.emplace_back(2.5e+06); + } + + parnames.emplace_back("A"); + parvalues.emplace_back(5400); + + parnames.emplace_back("B"); + parvalues.emplace_back(1700); + + if (include_pos_intensity) { + parnames.emplace_back("X0"); + parvalues.emplace_back(1.07); + } + + parnames.emplace_back("S"); + parvalues.emplace_back(0.000355); + + return; + } + //---------------------------------------------------------------------------------------------- /** Check whether a workspace exists or not * @brief CheckAndRetrieveMatrixWorkspace diff --git a/Framework/Algorithms/test/MCAbsorptionStrategyTest.h b/Framework/Algorithms/test/MCAbsorptionStrategyTest.h index 03bdb8c9399482f48383c9025f31472b2b7d167d..c5341017d13136a90ff1f519d5154987aced67e4 100644 --- a/Framework/Algorithms/test/MCAbsorptionStrategyTest.h +++ b/Framework/Algorithms/test/MCAbsorptionStrategyTest.h @@ -17,6 +17,7 @@ #include "MantidDataObjects/Histogram1D.h" #include "MantidGeometry/Instrument/ReferenceFrame.h" #include "MantidGeometry/Objects/BoundingBox.h" +#include "MantidGeometry/Objects/Track.h" #include "MantidKernel/Logger.h" #include "MantidKernel/WarningSuppressions.h" #include "MonteCarloTesting.h" @@ -168,22 +169,35 @@ public: .Times(1) .WillOnce(ReturnRef(emptyBoundingBox)); - EXPECT_CALL(testInteractionVolume, - calculateBeforeAfterTrack(_, _, _, _, _, _)) + auto beforeScatter = std::make_shared(); + auto afterScatter = std::make_shared(); + auto ReturnTracksAndTrue = [beforeScatter, afterScatter](auto &, auto &, + auto &, auto &) { + return std::tuple{true, beforeScatter, afterScatter}; + }; + auto ReturnNullTracksAndFalse = + [beforeScatter, afterScatter](auto &, auto &, auto &, auto &) { + return std::tuple{false, nullptr, nullptr}; + }; + EXPECT_CALL(testInteractionVolume, calculateBeforeAfterTrack(_, _, _, _)) .Times(Exactly(6)) - .WillOnce(Return(true)) - .WillOnce(Return(false)) - .WillOnce(Return(true)) - .WillOnce(Return(true)) - .WillOnce(Return(true)) - .WillOnce(Return(true)); - EXPECT_CALL(testInteractionVolume, calculateAbsorption(_, _, _, _)) + .WillOnce(Invoke(ReturnTracksAndTrue)) + .WillOnce(Invoke(ReturnNullTracksAndFalse)) + .WillOnce(Invoke(ReturnTracksAndTrue)) + .WillOnce(Invoke(ReturnTracksAndTrue)) + .WillOnce(Invoke(ReturnTracksAndTrue)) + .WillOnce(Invoke(ReturnTracksAndTrue)); + + EXPECT_CALL(*beforeScatter, calculateAttenuation(_)) .Times(Exactly(5)) .WillOnce(Return(1.0)) .WillOnce(Return(2.0)) .WillOnce(Return(3.0)) .WillOnce(Return(4.0)) .WillOnce(Return(5.0)); + EXPECT_CALL(*afterScatter, calculateAttenuation(_)) + .Times(Exactly(5)) + .WillRepeatedly(Return(1.0)); testStrategy.calculate(rng, {0., 0., 0.}, {1.0}, 0., attenuationFactors, attenuationFactorErrors, trackStatistics); TS_ASSERT_EQUALS(attenuationFactors[0], 3.0); @@ -242,23 +256,24 @@ private: class MockMCInteractionVolume final : public IMCInteractionVolume { public: GNU_DIAG_OFF_SUGGEST_OVERRIDE - MOCK_CONST_METHOD6(calculateBeforeAfterTrack, - bool(Mantid::Kernel::PseudoRandomNumberGenerator &rng, - const Mantid::Kernel::V3D &startPos, - const Mantid::Kernel::V3D &endPos, - Mantid::Geometry::Track &beforeScatter, - Mantid::Geometry::Track &afterScatter, - MCInteractionStatistics &stats)); - MOCK_CONST_METHOD4(calculateAbsorption, - double(const Mantid::Geometry::Track &beforeScatter, - const Mantid::Geometry::Track &afterScatter, - double lambdaBefore, double lambdaAfter)); + MOCK_CONST_METHOD4(calculateBeforeAfterTrack, + Mantid::Algorithms::TrackPair( + Mantid::Kernel::PseudoRandomNumberGenerator &rng, + const Mantid::Kernel::V3D &startPos, + const Mantid::Kernel::V3D &endPos, + MCInteractionStatistics &stats)); MOCK_CONST_METHOD0(getBoundingBox, Mantid::Geometry::BoundingBox &()); MOCK_CONST_METHOD0(getFullBoundingBox, const Mantid::Geometry::BoundingBox()); MOCK_METHOD1(setActiveRegion, void(const Mantid::Geometry::BoundingBox &)); GNU_DIAG_ON_SUGGEST_OVERRIDE }; + class MockTrack final : public Mantid::Geometry::Track { + public: + GNU_DIAG_OFF_SUGGEST_OVERRIDE + MOCK_CONST_METHOD1(calculateAttenuation, double(double)); + GNU_DIAG_ON_SUGGEST_OVERRIDE + }; Mantid::Kernel::Logger g_log{"MCAbsorptionStrategyTest"}; }; diff --git a/Framework/Algorithms/test/MCInteractionVolumeTest.h b/Framework/Algorithms/test/MCInteractionVolumeTest.h index 5b196b617848571f94d143326bafcdd2c9ca86c3..b05ac1ed6a8082ec7c82ce247244f62b024280c0 100644 --- a/Framework/Algorithms/test/MCInteractionVolumeTest.h +++ b/Framework/Algorithms/test/MCInteractionVolumeTest.h @@ -60,36 +60,15 @@ public: auto sample = createTestSample(TestSampleType::SolidSphere); MCInteractionVolume interactor(sample); - Track beforeScatter; - Track afterScatter; MCInteractionStatistics trackStatistics(-1, sample); - interactor.calculateBeforeAfterTrack(rng, startPos, endPos, beforeScatter, - afterScatter, trackStatistics); + auto [success, beforeScatter, afterScatter] = + interactor.calculateBeforeAfterTrack(rng, startPos, endPos, + trackStatistics); + TS_ASSERT(success); TestGeneratedTracks(startPos, endPos, beforeScatter, afterScatter, sample.getShape()); } - void test_Solid_Sample_Gives_Expected_Absorption() { - - auto sample = createTestSample(TestSampleType::SolidSphere); - MCInteractionVolume interactor(sample); - const double lambdaBefore(2.5), lambdaAfter(3.5); - // use tracks generated by previous test - Track beforeScatter({-0.05, -0.05, -0.05}, - {-0.999343185, 0.025624184, 0.025624184}); - beforeScatter.addLink({-0.05, -0.05, -0.05}, - {-0.071481137, -0.049449202, -0.049449202}, - 0.021495255, sample.getShape()); - Track afterScatter({-0.05, -0.05, -0.05}, - {0.417472754, 0.417472755, 0.807113993}); - afterScatter.addLink({-0.05, -0.05, -0.05}, - {0.024407241, 0.024407241, 0.093853999}, 0.021495255, - sample.getShape()); - const double factor = interactor.calculateAbsorption( - beforeScatter, afterScatter, lambdaBefore, lambdaAfter); - TS_ASSERT_DELTA(0.0028357258, factor, 1e-8); - } - void test_Sample_With_Hole_Gives_Expected_Tracks() { using Mantid::Kernel::V3D; @@ -104,11 +83,10 @@ public: .WillRepeatedly(Return(0.25)); MCInteractionVolume interactor(sample); - Track beforeScatter; - Track afterScatter; MCInteractionStatistics trackStatistics(-1, sample); - interactor.calculateBeforeAfterTrack(rng, startPos, endPos, beforeScatter, - afterScatter, trackStatistics); + auto [success, beforeScatter, afterScatter] = + interactor.calculateBeforeAfterTrack(rng, startPos, endPos, + trackStatistics); TestGeneratedTracks(startPos, endPos, beforeScatter, afterScatter, sample.getShape()); TS_ASSERT(Mock::VerifyAndClearExpectations(&rng)); @@ -117,41 +95,18 @@ public: EXPECT_CALL(rng, nextValue()) .Times(Exactly(3)) .WillRepeatedly(Return(0.75)); - interactor.calculateBeforeAfterTrack(rng, startPos, endPos, beforeScatter, - afterScatter, trackStatistics); + std::tie(success, beforeScatter, afterScatter) = + interactor.calculateBeforeAfterTrack(rng, startPos, endPos, + trackStatistics); TestGeneratedTracks(startPos, endPos, beforeScatter, afterScatter, sample.getShape()); } - void test_Sample_With_Hole_Gives_Expected_Absorption() { - auto sample = createTestSample(TestSampleType::Annulus); - MCInteractionVolume interactor(sample); - const double lambdaBefore(2.5), lambdaAfter(3.5); - // use tracks generated by previous test - Track beforeScatter({-0.075, -0.075, -0.0375}, - {-0.999052621, 0.038924128, 0.019462064}); - beforeScatter.addLink({-0.075, -0.075, -0.0375}, - {-0.131142366, -0.072812635, -0.036406318}, - 0.056195605, sample.getShape()); - Track afterScatter({-0.075, -0.075, -0.0375}, - {0.999184480, 0.036115102, 0.018057551}); - afterScatter.addLink({-0.075, -0.075, -0.0375}, - {-0.066490895, -0.074692442, -0.037346221}, - 0.008516050, sample.getShape()); - afterScatter.addLink({0.071709799, -0.069697236, -0.034848618}, - {0.133981248, -0.067446460, -0.033723230}, 0.209151816, - sample.getShape()); - const double factorSeg1 = interactor.calculateAbsorption( - beforeScatter, afterScatter, lambdaBefore, lambdaAfter); - TS_ASSERT_DELTA(0.030489479, factorSeg1, 1e-8); - } - void test_Sample_And_Environment_Can_Scatter_In_All_Segments() { using Mantid::Kernel::V3D; // Testing inputs const V3D startPos(-2.0, 0.0, 0.0), endPos(2.0, 0.0, 0.0); - const double lambdaBefore(2.5), lambdaAfter(3.5); auto sample = createTestSample(TestSampleType::SamplePlusContainer); MockRNG rng; @@ -167,11 +122,10 @@ public: MCInteractionVolume interactor(sample); interactor.setActiveRegion(sample.getEnvironment().boundingBox()); - Track beforeScatter; - Track afterScatter; MCInteractionStatistics trackStatistics(-1, sample); - interactor.calculateBeforeAfterTrack(rng, startPos, endPos, beforeScatter, - afterScatter, trackStatistics); + auto [success, beforeScatter, afterScatter] = + interactor.calculateBeforeAfterTrack(rng, startPos, endPos, + trackStatistics); TestGeneratedTracks(startPos, endPos, beforeScatter, afterScatter, sample.getEnvironment().getContainer()); TS_ASSERT(Mock::VerifyAndClearExpectations(&rng)); @@ -183,60 +137,14 @@ public: EXPECT_CALL(rng, nextValue()) .Times(Exactly(3)) .WillRepeatedly(Return(0.25)); // RandomPoint::bounded r1, r2, r3 - interactor.calculateBeforeAfterTrack(rng, startPos, endPos, beforeScatter, - afterScatter, trackStatistics); + std::tie(success, beforeScatter, afterScatter) = + interactor.calculateBeforeAfterTrack(rng, startPos, endPos, + trackStatistics); TestGeneratedTracks(startPos, endPos, beforeScatter, afterScatter, sample.getShape()); - const double factorSample = interactor.calculateAbsorption( - beforeScatter, afterScatter, lambdaBefore, lambdaAfter); - TS_ASSERT_DELTA(0.73100698, factorSample, 1e-8); TS_ASSERT(Mock::VerifyAndClearExpectations(&rng)); } - void test_Sample_And_Environment_Gives_Expected_Absorption() { - auto sample = createTestSample(TestSampleType::SamplePlusContainer); - MCInteractionVolume interactor(sample); - const double lambdaBefore(2.5), lambdaAfter(3.5); - // use tracks generated by previous test - // scatter in segment can - Track beforeScatter({-0.0048, 0, 0}, {-1, 0, 0}); - beforeScatter.addLink({-0.0048, 0, 0}, {-0.005, 0, 0}, 0.0002, - sample.getEnvironment().getContainer().getShape()); - Track afterScatter({-0.0048, 0, 0}, {1, 0, 0}); - afterScatter.addLink({-0.0048, 0, 0}, {-0.0046, 0, 0}, 0.0002, - sample.getEnvironment().getContainer().getShape()); - afterScatter.addLink({-0.0046, 0, 0}, {0.0046, 0, 0}, 0.0094, - sample.getShape()); - afterScatter.addLink({0.0046, 0, 0}, {0.005, 0, 0}, 0.0098, - sample.getEnvironment().getContainer().getShape()); - const double factorContainer = interactor.calculateAbsorption( - beforeScatter, afterScatter, lambdaBefore, lambdaAfter); - TS_ASSERT_DELTA(0.69223681, factorContainer, 1e-8); - - // scatter in sample - Track beforeScatterSample({-0.0025, -0.0025, -0.0125}, - {-0.999979637, 0.001251539, 0.006257695}); - beforeScatterSample.addLink({-0.0025, -0.0025, -0.0125}, - {-0.003862450, -0.002498295, -0.012491474}, - 0.001362478, sample.getShape()); - beforeScatterSample.addLink( - {-0.003862450, -0.002498295, -0.012491474}, - {-0.004331450, -0.002497708, -0.012488539}, 0.001831487, - sample.getEnvironment().getContainer().getShape()); - Track afterScatterSample({-0.0025, -0.0025, -0.0125}, - {0.999979739, 0.001248414, 0.006242071}); - afterScatterSample.addLink({-0.0025, -0.0025, -0.0125}, - {0.003866481, -0.002492052, -0.012460259}, - 0.006366610, sample.getShape()); - afterScatterSample.addLink( - {0.003866481, -0.002492052, -0.012460259}, - {0.004335042, -0.002491467, -0.012457334}, 0.006835181, - sample.getEnvironment().getContainer().getShape()); - const double factorSample = interactor.calculateAbsorption( - beforeScatterSample, afterScatterSample, lambdaBefore, lambdaAfter); - TS_ASSERT_DELTA(0.73100698, factorSample, 1e-8); - } - void test_Construction_With_Env_But_No_Sample_Shape_Does_Not_Throw_Error() { Mantid::API::Sample sample; auto kit = createTestKit(); @@ -269,12 +177,9 @@ public: rng.setSeed(1); const size_t maxTries(1); MCInteractionVolume interactor(sample, maxTries); - Track beforeScatter; - Track afterScatter; MCInteractionStatistics trackStatistics(-1, sample); - TS_ASSERT_THROWS(interactor.calculateBeforeAfterTrack( - rng, startPos, endPos, beforeScatter, afterScatter, - trackStatistics), + TS_ASSERT_THROWS(interactor.calculateBeforeAfterTrack(rng, startPos, endPos, + trackStatistics), const std::runtime_error &); } @@ -368,19 +273,19 @@ private: Mantid::Kernel::Logger g_log{"MCInteractionVolumeTest"}; void TestGeneratedTracks(const V3D startPos, const V3D endPos, - const Track &beforeScatter, - const Track &afterScatter, + const std::shared_ptr beforeScatter, + const std::shared_ptr &afterScatter, const Mantid::Geometry::IObject &shape) { // check the generated tracks share the same start point (the scatter point) - TS_ASSERT_EQUALS(beforeScatter.startPoint(), afterScatter.startPoint()); - TS_ASSERT_EQUALS(shape.isValid(beforeScatter.startPoint()), true); + TS_ASSERT_EQUALS(beforeScatter->startPoint(), afterScatter->startPoint()); + TS_ASSERT_EQUALS(shape.isValid(beforeScatter->startPoint()), true); // check that the generated tracks intersect the supplied startPos and // endPos - const V3D scatterToEnd = endPos - afterScatter.startPoint(); - TS_ASSERT_EQUALS(afterScatter.direction(), + const V3D scatterToEnd = endPos - afterScatter->startPoint(); + TS_ASSERT_EQUALS(afterScatter->direction(), Mantid::Kernel::normalize(scatterToEnd)); - const V3D scatterToStart = startPos - beforeScatter.startPoint(); - TS_ASSERT_EQUALS(beforeScatter.direction(), + const V3D scatterToStart = startPos - beforeScatter->startPoint(); + TS_ASSERT_EQUALS(beforeScatter->direction(), Mantid::Kernel::normalize(scatterToStart)); } }; diff --git a/Framework/Algorithms/test/MuonWorkspaceCreationHelperTest.h b/Framework/Algorithms/test/MuonWorkspaceCreationHelperTest.h index b5c1053761445c668b0eac978eb850e6ae723eba..c54b1e6ea3b3ee0aedeee8b3aa4dd500823fdf90 100644 --- a/Framework/Algorithms/test/MuonWorkspaceCreationHelperTest.h +++ b/Framework/Algorithms/test/MuonWorkspaceCreationHelperTest.h @@ -251,6 +251,25 @@ public: TS_ASSERT_DELTA(deadTimeTable->getColumn(1)->toDouble(4), 0.005, 0.0001); } + void test_createTimeZeroTable_empty_for_incorrect_length() { + std::vector timeZeros = {0.5, 1.0}; + ITableWorkspace_sptr timeZeroTable = createTimeZeroTable(3, timeZeros); + + TS_ASSERT_EQUALS(timeZeroTable->columnCount(), 1); + TS_ASSERT_EQUALS(timeZeroTable->rowCount(), 0); + } + + void test_createTimeZeroTable_correct_value() { + std::vector timeZeros = {0.5, 1.0, 1.5}; + ITableWorkspace_sptr timeZeroTable = createTimeZeroTable(3, timeZeros); + + TS_ASSERT_EQUALS(timeZeroTable->columnCount(), 1); + TS_ASSERT_EQUALS(timeZeroTable->rowCount(), 3); + TS_ASSERT_DELTA(timeZeroTable->getColumn(0)->toDouble(0), 0.5, 0.01); + TS_ASSERT_DELTA(timeZeroTable->getColumn(0)->toDouble(1), 1.0, 0.01); + TS_ASSERT_DELTA(timeZeroTable->getColumn(0)->toDouble(2), 1.5, 0.01); + } + void test_createWorkspaceWithInstrumentandRun_run_number_and_instrument_set_correctly() { MatrixWorkspace_sptr ws = diff --git a/Framework/Algorithms/test/PDCalibrationTest.h b/Framework/Algorithms/test/PDCalibrationTest.h index f1f1dfdc37f4e64fd3d6c14dfc258327b0766ad9..ea34a13546c09bee5211224ef12c0f99e4fbc518 100644 --- a/Framework/Algorithms/test/PDCalibrationTest.h +++ b/Framework/Algorithms/test/PDCalibrationTest.h @@ -13,14 +13,19 @@ #include "MantidAPI/FrameworkManager.h" #include "MantidAPI/ITableWorkspace.h" #include "MantidAPI/MatrixWorkspace.h" +#include "MantidAlgorithms/ConvertToMatrixWorkspace.h" #include "MantidAlgorithms/CreateSampleWorkspace.h" +#include "MantidAlgorithms/CropWorkspace.h" #include "MantidAlgorithms/PDCalibration.h" +#include "MantidDataHandling/GroupDetectors2.h" #include "MantidDataHandling/MoveInstrumentComponent.h" #include "MantidDataHandling/RotateInstrumentComponent.h" #include "MantidDataObjects/TableColumn.h" #include "MantidKernel/Diffraction.h" +using Mantid::Algorithms::ConvertToMatrixWorkspace; using Mantid::Algorithms::CreateSampleWorkspace; +using Mantid::Algorithms::CropWorkspace; using Mantid::Algorithms::PDCalibration; using Mantid::API::AnalysisDataService; using Mantid::API::FrameworkManager; @@ -29,6 +34,7 @@ using Mantid::API::ITableWorkspace_sptr; using Mantid::API::MatrixWorkspace; using Mantid::API::MatrixWorkspace_const_sptr; using Mantid::API::Workspace_sptr; +using Mantid::DataHandling::GroupDetectors2; using Mantid::DataHandling::MoveInstrumentComponent; using Mantid::DataHandling::RotateInstrumentComponent; @@ -338,6 +344,122 @@ public: Mantid::API::AnalysisDataService::Instance().remove(prefix + "cal"); Mantid::API::AnalysisDataService::Instance().remove(prefix + "cal_mask"); } + + // Crop workspace so that final peak is evaluated over a range that includes + // the last bin (stop regression out of range bug for histo workspaces) + void test_exec_difc_histo() { + // convert to histo + ConvertToMatrixWorkspace convMatWS; + convMatWS.initialize(); + convMatWS.setPropertyValue("InputWorkspace", "PDCalibrationTest_WS"); + convMatWS.setPropertyValue("OutputWorkspace", "PDCalibrationTest_WS"); + convMatWS.execute(); + // crop + std::string xmax = "15104"; // only keep TOF < xmax + CropWorkspace cropWS; + cropWS.initialize(); + cropWS.setPropertyValue("InputWorkspace", "PDCalibrationTest_WS"); + cropWS.setPropertyValue("OutputWorkspace", "PDCalibrationTest_WS"); + cropWS.setPropertyValue("XMin", "300"); + cropWS.setPropertyValue("XMax", xmax); + cropWS.execute(); + + // setup the peak postions based on transformation from detID=155 + std::vector dValues(PEAK_TOFS.size()); + std::transform( + PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(), + Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.)); + + const std::string prefix{"PDCalibration_difc"}; + + PDCalibration alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("InputWorkspace", "PDCalibrationTest_WS")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty( + "TofBinning", std::to_string(TOF_BINNING[0]) + "," + + std::to_string(TOF_BINNING[1]) + "," + xmax)); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("OutputCalibrationTable", prefix + "cal")); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("DiagnosticWorkspaces", prefix + "diag")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("PeakPositions", dValues)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + TS_ASSERT(alg.isExecuted()); + + // test that the difc values are the same as for event + ITableWorkspace_sptr calTable = + AnalysisDataService::Instance().retrieveWS(prefix + + "cal"); + + TS_ASSERT(calTable); + + Mantid::DataObjects::TableColumn_ptr col0 = calTable->getColumn(0); + std::vector detIDs = col0->data(); + + // since the wksp was calculated in TOF, all DIFC end up being the same + size_t index = + std::find(detIDs.begin(), detIDs.end(), 155) - detIDs.begin(); + TS_ASSERT_EQUALS(calTable->cell(index, 0), 155); // detid + TS_ASSERT_DELTA(calTable->cell(index, 1), DIFC_155, .01); // difc + TS_ASSERT_EQUALS(calTable->cell(index, 2), 0); // difa + TS_ASSERT_EQUALS(calTable->cell(index, 3), 0); // tzero + + index = std::find(detIDs.begin(), detIDs.end(), 195) - detIDs.begin(); + TS_ASSERT_EQUALS(calTable->cell(index, 0), 195); // detid + TS_ASSERT_DELTA(calTable->cell(index, 1), DIFC_155, .01); // difc + TS_ASSERT_EQUALS(calTable->cell(index, 2), 0); // difa + TS_ASSERT_EQUALS(calTable->cell(index, 3), 0); // tzero + } + + void test_exec_grouped_detectors() { + // group detectors + GroupDetectors2 groupDet; + groupDet.initialize(); + groupDet.setPropertyValue("InputWorkspace", "PDCalibrationTest_WS"); + groupDet.setPropertyValue("OutputWorkspace", "PDCalibrationTest_WS"); + groupDet.setPropertyValue("DetectorList", "100,101,102,103"); + groupDet.execute(); + + // setup the peak postions based on transformation from detID=155 + std::vector dValues(PEAK_TOFS.size()); + std::transform( + PEAK_TOFS.begin(), PEAK_TOFS.end(), dValues.begin(), + Mantid::Kernel::Diffraction::getTofToDConversionFunc(DIFC_155, 0., 0.)); + + const std::string prefix{"PDCalibration_difc"}; + + PDCalibration alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("InputWorkspace", "PDCalibrationTest_WS")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("TofBinning", TOF_BINNING)); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("OutputCalibrationTable", prefix + "cal")); + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("DiagnosticWorkspaces", prefix + "diag")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("PeakPositions", dValues)); + TS_ASSERT_THROWS_NOTHING(alg.execute()); + TS_ASSERT(alg.isExecuted()); + + ITableWorkspace_sptr calTable = + AnalysisDataService::Instance().retrieveWS(prefix + + "cal"); + TS_ASSERT(calTable); + Mantid::DataObjects::TableColumn_ptr col0 = calTable->getColumn(0); + std::vector detIDs = col0->data(); + // test that the cal table has the same difc value for grouped dets + size_t index = + std::find(detIDs.begin(), detIDs.end(), 100) - detIDs.begin(); + TS_ASSERT_DELTA(calTable->cell(index + 1, 1), + calTable->cell(index, 1), 1E-5); // det 101 + TS_ASSERT_DELTA(calTable->cell(index + 2, 1), + calTable->cell(index, 1), 1E-5); // det 102 + TS_ASSERT_DELTA(calTable->cell(index + 3, 1), + calTable->cell(index, 1), 1E-5); // det 103 + } }; class PDCalibrationTestPerformance : public CxxTest::TestSuite { // TODO diff --git a/Framework/Algorithms/test/PrecompiledHeader.h b/Framework/Algorithms/test/PrecompiledHeader.h index e14daaa4f1d5053efece08402bc690a263b877a6..73f0e084060216c886620ac03bb110a071bb0c52 100644 --- a/Framework/Algorithms/test/PrecompiledHeader.h +++ b/Framework/Algorithms/test/PrecompiledHeader.h @@ -11,6 +11,7 @@ // Mantid #include "MantidAPI/Algorithm.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" // STL #include diff --git a/Framework/Algorithms/test/RebinByTimeBaseTest.h b/Framework/Algorithms/test/RebinByTimeBaseTest.h index 56087a238aaa041114deb6033f82da9384fe68c5..96bc7d71523de7eddf71865b672f4ae8fc1dd9f7 100644 --- a/Framework/Algorithms/test/RebinByTimeBaseTest.h +++ b/Framework/Algorithms/test/RebinByTimeBaseTest.h @@ -100,7 +100,10 @@ public: MOCK_METHOD0(resetAllXToSingleBin, void()); MOCK_METHOD0(clearMRU, void()); MOCK_CONST_METHOD0(clearMRU, void()); + MOCK_CONST_METHOD0(isRaggedWorkspace, bool()); MOCK_CONST_METHOD0(blocksize, std::size_t()); + MOCK_CONST_METHOD1(getNumberBins, std::size_t(const std::size_t &)); + MOCK_CONST_METHOD0(getMaxNumberBins, std::size_t()); MOCK_CONST_METHOD0(size, std::size_t()); MOCK_CONST_METHOD0(getNumberHistograms, std::size_t()); MOCK_METHOD1(getSpectrum, Mantid::API::IEventList &(const std::size_t)); diff --git a/Framework/Algorithms/test/SampleLogsBehaviourTest.h b/Framework/Algorithms/test/SampleLogsBehaviourTest.h index 3cc82415ad85894cd16e19cfb99b924ee337d026..8f87fa348ff97750bbf8a976cc6bae60d0115e29 100644 --- a/Framework/Algorithms/test/SampleLogsBehaviourTest.h +++ b/Framework/Algorithms/test/SampleLogsBehaviourTest.h @@ -10,6 +10,7 @@ #include "MantidAlgorithms/RunCombinationHelpers/SampleLogsBehaviour.h" #include "MantidDataHandling/LoadParameterFile.h" #include "MantidTestHelpers/WorkspaceCreationHelper.h" +#include "MantidTypes/Core/DateAndTime.h" #include #include "MantidKernel/TimeSeriesProperty.h" @@ -19,6 +20,7 @@ using namespace Mantid::API; using namespace Mantid::DataHandling; using namespace Mantid::Kernel; using namespace WorkspaceCreationHelper; +using namespace Mantid::Types::Core; class SampleLogsBehaviourTest : public CxxTest::TestSuite { public: @@ -170,7 +172,8 @@ public: TS_ASSERT_EQUALS(base->getLog("A")->units(), "A_unit") } - void test_time_series_unit() { + void test_time_series_from_scalars() { + // Tests when no time series logs are merged into a time series log Logger log("testLog"); auto ws = createWorkspace(2.65, 1.56, 8.55, "2018-11-30T16:17:01"); auto base = createWorkspace(4.5, 3.2, 7.9, "2018-11-30T16:17:03"); @@ -188,6 +191,48 @@ public: TS_ASSERT_EQUALS(base->getLog("B")->units(), "B_unit") } + void test_time_series_merge() { + // Tests when two time series logs are merged + Logger log("testLog"); + auto ws1 = createWorkspaceWithTimeSeriesLog(2.65, "2018-11-30T16:17:01"); + auto ws2 = createWorkspaceWithTimeSeriesLog(3.15, "2018-11-30T16:25:01"); + SampleLogsBehaviour::SampleLogNames sampleLogNames; + sampleLogNames.sampleLogsTimeSeries = "A"; + SampleLogsBehaviour sbh = SampleLogsBehaviour(ws1, log, sampleLogNames); + TS_ASSERT_THROWS_NOTHING(sbh.mergeSampleLogs(ws2, ws1)); + const std::string A = ws1->run().getLogData("A")->value(); + TS_ASSERT_EQUALS(A, + "2018-Nov-30 16:17:01 2.65\n2018-Nov-30 16:25:01 3.15\n") + } + + void test_time_series_merge_with_scalar() { + // Tests when one time series log is merged with a scalar log as time series + Logger log("testLog"); + auto ws1 = createWorkspaceWithTimeSeriesLog(4.47, "2018-11-30T16:17:01"); + auto ws2 = createWorkspace(2.65, 1.56, 8.55, "2018-11-30T16:25:01"); + SampleLogsBehaviour::SampleLogNames sampleLogNames; + sampleLogNames.sampleLogsTimeSeries = "A"; + SampleLogsBehaviour sbh = SampleLogsBehaviour(ws1, log, sampleLogNames); + TS_ASSERT_THROWS_NOTHING(sbh.mergeSampleLogs(ws2, ws1)); + const std::string A = ws1->run().getLogData("A")->value(); + TS_ASSERT_EQUALS(A, + "2018-Nov-30 16:17:01 4.47\n2018-Nov-30 16:25:01 2.65\n") + } + + MatrixWorkspace_sptr + createWorkspaceWithTimeSeriesLog(const double A, const std::string &time) { + MatrixWorkspace_sptr ws = create2DWorkspaceWithFullInstrument( + 3, 3, true, false, true, m_instrName); + DateAndTime dateTime; + dateTime.setFromISO8601(time); + std::vector series; + series.emplace_back(dateTime); + TimeSeriesProperty *ts = + new TimeSeriesProperty("A", series, std::vector({A})); + ws->mutableRun().addLogData(ts); + return ws; + } + MatrixWorkspace_sptr createWorkspace(const double A, const double B, const double C, const std::string &time = "") { diff --git a/Framework/Algorithms/test/XrayAbsorptionCorrectionTest.h b/Framework/Algorithms/test/XrayAbsorptionCorrectionTest.h new file mode 100644 index 0000000000000000000000000000000000000000..396279a4e22025fa72df71b58f9cb570a808039e --- /dev/null +++ b/Framework/Algorithms/test/XrayAbsorptionCorrectionTest.h @@ -0,0 +1,166 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once +#include "MantidAPI/Algorithm.h" +#include "MantidAlgorithms/CompareWorkspaces.h" +#include "MantidAlgorithms/XrayAbsorptionCorrection.h" +#include "MantidGeometry/Instrument/SampleEnvironment.h" +#include "MantidGeometry/Objects/IObject.h" +#include "MantidKernel/AttenuationProfile.h" +#include "MantidKernel/Interpolation.h" +#include "MantidKernel/Material.h" +#include "MantidKernel/VectorHelper.h" +#include "MantidTestHelpers/ComponentCreationHelper.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" +#include +#include +#include + +using namespace Mantid; +using namespace Algorithms; +class XrayAbsorptionCorrectionTest : public CxxTest::TestSuite { + +public: + static XrayAbsorptionCorrectionTest *createSuite() { + return new XrayAbsorptionCorrectionTest(); + } + static void destroySuite(XrayAbsorptionCorrectionTest *suite) { + delete suite; + } + void test_CalculateDetectorPos() { + TestableXrayAbsorptionCorrection alg; + Kernel::V3D pos = alg.calculateDetectorPos(45, 10); + Kernel::V3D correctPos = {0.1, 0, 0.1}; + + TS_ASSERT_DELTA(pos[0], correctPos[0], 0.001); + TS_ASSERT_DELTA(pos[1], correctPos[1], 0.001); + TS_ASSERT_DELTA(pos[2], correctPos[2], 0.001); + } + void test_CalculateMuonPos() { + TestableXrayAbsorptionCorrection alg; + API::MatrixWorkspace_sptr muonProfile = createWorkspace(1.0); + API::MatrixWorkspace_sptr inputWS = createWorkspaceWithDummyShape(20.0); + std::vector muonPos = + alg.calculateMuonPos(muonProfile, inputWS, 100.0); + double z = 1.0; + for (auto x : muonPos) { + z -= 0.01; + TS_ASSERT_DELTA(x[0], 0, 1.0e-6); + TS_ASSERT_DELTA(x[1], 0, 1.0e-6); + TS_ASSERT_DELTA(x[2], z, 1.0e-6); + } + } + void test_NormaliseMounIntensity() { + TestableXrayAbsorptionCorrection alg; + API::MatrixWorkspace_sptr muonProfile = createWorkspace(1); + std::vector normalisedIntensity = + alg.normaliseMuonIntensity(muonProfile->readY(0)); + for (auto intensity : normalisedIntensity) { + TS_ASSERT_DELTA(intensity, 0.1, 1.0e-6); + } + } + + void test_exec_with_no_shape() { + API::MatrixWorkspace_sptr muonProfile = createWorkspace(1); + API::MatrixWorkspace_sptr inputWS = createWorkspace(1); + Algorithms::XrayAbsorptionCorrection algo; + algo.initialize(); + algo.setProperty("InputWorkspace", inputWS); + algo.setProperty("MuonImplantationProfile", muonProfile); + algo.setProperty("OutputWorkspace", "outputWS"); + algo.setProperty("DetectorDistance", 10.0); + algo.setProperty("DetectorAngle", 45.0); + TS_ASSERT_THROWS(algo.execute(), const std::runtime_error &); + } + + void test_exec_with_valid_shape() { + API::MatrixWorkspace_sptr muonProfile = createWorkspace(100.0); + auto &muonDepth = muonProfile->mutableX(0); + for (size_t i = 0; i < muonDepth.size(); i++) { + muonDepth[i] = 100.0; + } + API::MatrixWorkspace_sptr inputWS = createWorkspaceWithDummyShape(20.0); + Algorithms::XrayAbsorptionCorrection algo; + algo.initialize(); + algo.setProperty("InputWorkspace", inputWS); + algo.setProperty("MuonImplantationProfile", muonProfile); + algo.setProperty("OutputWorkspace", "outputWS"); + algo.setProperty("DetectorDistance", 1000.0); + algo.setProperty("DetectorAngle", 45.0); + algo.execute(); + API::MatrixWorkspace_sptr outputWS = algo.getProperty("OutputWorkspace"); + auto &yData = inputWS->mutableY(0); + for (size_t i = 0; i < yData.size(); i++) { + yData[i] = std::exp(-1); + } + API::IAlgorithm_sptr comparison = + algo.createChildAlgorithm("CompareWorkspaces"); + comparison->setProperty("Workspace1", inputWS); + comparison->setProperty("Workspace2", "outputWS"); + comparison->setProperty("Tolerance", 1.0e-05); + comparison->setProperty("ToleranceRelErr", true); + comparison->execute(); + bool result = comparison->getProperty("Result"); + TS_ASSERT(result); + } + + void test_exec_with_non_valid_shape() { + API::MatrixWorkspace_sptr muonProfile = createWorkspace(100); + API::MatrixWorkspace_sptr inputWS = + createWorkspaceWithDummyShape(20, false); + Algorithms::XrayAbsorptionCorrection algo; + algo.initialize(); + algo.setProperty("InputWorkspace", inputWS); + algo.setProperty("MuonImplantationProfile", muonProfile); + algo.setProperty("OutputWorkspace", "outputWS"); + algo.setProperty("DetectorDistance", 10.0); + algo.setProperty("DetectorAngle", 45.0); + TS_ASSERT_THROWS(algo.execute(), std::runtime_error); + } + +private: + API::MatrixWorkspace_sptr + createWorkspaceWithDummyShape(double value, + bool hasXrayAttenuationProfile = true) { + API::MatrixWorkspace_sptr inputWS = + WorkspaceCreationHelper::create1DWorkspaceConstant(10, value, 0.0, + true); + + Kernel::Material sampleMaterial; + Kernel::AttenuationProfile sampleProfile; + sampleProfile.setAttenuationCoefficient(1.0, 1.0); + sampleProfile.setAttenuationCoefficient(10.0, 1.0); + sampleProfile.setAttenuationCoefficient(100.0, 1.0); + sampleProfile.setAttenuationCoefficient(1000.0, 1.0); + sampleMaterial.setXRayAttenuationProfile(sampleProfile); + + auto shape = ComponentCreationHelper::createSphere(1.0, {0.0, 0.0, 0.0}, + "sample-shape"); + + inputWS->mutableSample().setShape(shape); + if (hasXrayAttenuationProfile) { + auto shapeWithMaterial = std::shared_ptr( + inputWS->sample().getShape().cloneWithMaterial(sampleMaterial)); + inputWS->mutableSample().setShape(shapeWithMaterial); + } + return inputWS; + } + + API::MatrixWorkspace_sptr createWorkspace(double value) { + + API::MatrixWorkspace_sptr inputWS = + WorkspaceCreationHelper::create1DWorkspaceConstant(10, value, 0.0, + true); + + return inputWS; + } + +private: + class TestableXrayAbsorptionCorrection : public XrayAbsorptionCorrection { + friend class XrayAbsorptionCorrectionTest; + }; +}; diff --git a/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h b/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h index c00e1a5558f9d7769aab867a197b1f7dc9ca009c..5ef939dd412bafbff4cc6722ea0c2f1ee1d4870d 100644 --- a/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h +++ b/Framework/Beamline/inc/MantidBeamline/ComponentInfo.h @@ -124,7 +124,9 @@ public: bool hasDetectorInfo() const; void setDetectorInfo(DetectorInfo *detectorInfo); bool hasSource() const; + bool hasEquivalentSource(const ComponentInfo &other) const; bool hasSample() const; + bool hasEquivalentSample(const ComponentInfo &other) const; const Eigen::Vector3d &sourcePosition() const; const Eigen::Vector3d &samplePosition() const; size_t source() const; diff --git a/Framework/Beamline/src/ComponentInfo.cpp b/Framework/Beamline/src/ComponentInfo.cpp index 0f8454dd63e0db333f10a4948f41aa0d543b697f..3bff21b8a1a2e8a21af372f1670e2e17b4428d01 100644 --- a/Framework/Beamline/src/ComponentInfo.cpp +++ b/Framework/Beamline/src/ComponentInfo.cpp @@ -1,3 +1,4 @@ + // Mantid Repository : https://github.com/mantidproject/mantid // // Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, @@ -479,8 +480,42 @@ void ComponentInfo::setDetectorInfo(DetectorInfo *detectorInfo) { bool ComponentInfo::hasSource() const { return m_sourceIndex >= 0; } +/* + * @brief Check the sources of two componentInfo objects coincide + * + * @details check both objects either lack or have a source. If the latter, + * check their positions differ by less than 1 nm = 1e-9 m. + * + * @returns true if sources are equivalent + */ +bool ComponentInfo::hasEquivalentSource(const ComponentInfo &other) const { + if (this->hasSource() != other.hasSource()) + return false; // one has a source while the other does not + if (this->hasSource() && other.hasSource()) { + return (this->sourcePosition() - other.sourcePosition()).norm() < 1e-9; + } + return true; +} + bool ComponentInfo::hasSample() const { return m_sampleIndex >= 0; } +/* + * @brief Check the samples of two componentInfo objects coincide + * + * @details check both objects either lack or have a sample. If the latter, + * check their positions differ by less than 1 nm = 1e-9 m. + * + * @returns true if sources are equivalent + */ +bool ComponentInfo::hasEquivalentSample(const ComponentInfo &other) const { + if (this->hasSample() != other.hasSample()) + return false; // one has a source while the other does not + if (this->hasSample() && other.hasSample()) { + return (this->samplePosition() - other.samplePosition()).norm() < 1e-9; + } + return true; +} + const Eigen::Vector3d &ComponentInfo::sourcePosition() const { if (!hasSource()) { throw std::runtime_error("Source component has not been specified"); diff --git a/Framework/Crystal/CMakeLists.txt b/Framework/Crystal/CMakeLists.txt index 022144e09cd26f4a517ec879bf6636397e3ea69e..2708edd22fc61e9387a9e94faee3c5553e423839 100644 --- a/Framework/Crystal/CMakeLists.txt +++ b/Framework/Crystal/CMakeLists.txt @@ -52,6 +52,8 @@ set(SRC_FILES src/PredictPeaks.cpp src/PredictSatellitePeaks.cpp src/SCDCalibratePanels.cpp + src/SCDCalibratePanels2.cpp + src/SCDCalibratePanels2ObjFunc.cpp src/SCDPanelErrors.cpp src/SaveHKL.cpp src/SaveIsawPeaks.cpp @@ -128,6 +130,8 @@ set(INC_FILES inc/MantidCrystal/PredictPeaks.h inc/MantidCrystal/PredictSatellitePeaks.h inc/MantidCrystal/SCDCalibratePanels.h + inc/MantidCrystal/SCDCalibratePanels2.h + inc/MantidCrystal/SCDCalibratePanels2ObjFunc.h inc/MantidCrystal/SCDPanelErrors.h inc/MantidCrystal/SaveHKL.h inc/MantidCrystal/SaveIsawPeaks.h @@ -196,6 +200,7 @@ set(TEST_FILES PredictFractionalPeaksTest.h PredictPeaksTest.h PredictSatellitePeaksTest.h + SCDCalibratePanels2Test.h SCDCalibratePanelsTest.h SaveHKLTest.h SaveIsawPeaksTest.h @@ -227,6 +232,9 @@ if(COVERALLS) endforeach(loop_var) endif() +# Add a precompiled header where they are supported +enable_precompiled_headers(inc/MantidCrystal/PrecompiledHeader.h SRC_FILES) + # Add the target for this directory add_library(Crystal ${SRC_FILES} ${INC_FILES}) # Set the name of the generated library diff --git a/Framework/Crystal/inc/MantidCrystal/IndexPeaks.h b/Framework/Crystal/inc/MantidCrystal/IndexPeaks.h index 19eb28901042229264f2f12a3de70da970d943a9..afb804495e2a9fad688ce497ecb6934342236cbb 100644 --- a/Framework/Crystal/inc/MantidCrystal/IndexPeaks.h +++ b/Framework/Crystal/inc/MantidCrystal/IndexPeaks.h @@ -19,7 +19,9 @@ class MANTID_CRYSTAL_DLL IndexPeaks : public API::Algorithm { public: const std::string name() const override { return "IndexPeaks"; } const std::string summary() const override { - return "Index the peaks using the UB from the sample."; + return "Index the peaks using the UB from the sample. Leave MaxOrder at " + "default value 0 to get this value, and ModVectors (unless " + "overridden) from the sample."; } int version() const override { return 1; } const std::vector seeAlso() const override { diff --git a/MantidPlot/src/Mantid/MatrixWorkspaceCurve.cpp b/Framework/Crystal/inc/MantidCrystal/PrecompiledHeader.h similarity index 77% rename from MantidPlot/src/Mantid/MatrixWorkspaceCurve.cpp rename to Framework/Crystal/inc/MantidCrystal/PrecompiledHeader.h index 4fa8f41e6759e8dc0baf31aa67538caae8c37f1b..37102be45582cbc39b7cda212bb6a52943e49b50 100644 --- a/MantidPlot/src/Mantid/MatrixWorkspaceCurve.cpp +++ b/Framework/Crystal/inc/MantidCrystal/PrecompiledHeader.h @@ -4,3 +4,9 @@ // NScD Oak Ridge National Laboratory, European Spallation Source, // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidCrystal/DllConfig.h" + +// STL diff --git a/Framework/Crystal/inc/MantidCrystal/SCDCalibratePanels2.h b/Framework/Crystal/inc/MantidCrystal/SCDCalibratePanels2.h new file mode 100644 index 0000000000000000000000000000000000000000..3d2b711a23507d8635086acf23b774169190bbc1 --- /dev/null +++ b/Framework/Crystal/inc/MantidCrystal/SCDCalibratePanels2.h @@ -0,0 +1,137 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" +#include "MantidCrystal/DllConfig.h" +#include "MantidDataObjects/PeaksWorkspace.h" +#include "MantidDataObjects/TableWorkspace.h" + +#include + +namespace Mantid { +namespace Crystal { + +/** SCDCalibratePanels2 : + * Using input peakworkspace with indexation results to calibrate each + * individual panels. + * The target calibration properties include: + * - T0: micro seconds + * time for proton to travel from reactor to target to generate neutron + * - L1: meters + * distance between target and sample + * - L2: meters (also known as z_shift) + * distance between sample and the center of each panel + * - Rot: degrees + * Euler angles (xyz ) + * + * Spirit successor of ISAW and its reincarnation: SCDCalibratePanels + */ +class MANTID_CRYSTAL_DLL SCDCalibratePanels2 : public Mantid::API::Algorithm { +public: + /// Algorithm's name for identification + const std::string name() const override { return "SCDCalibratePanels"; } + + /// Summary of algorithm's purpose + const std::string summary() const override { + return "Panel parameters and L1 are optimized to " + "minimize errors between theoretical and actual q values for the " + "peaks"; + } + + /// Algorithm's version, overriding a virtual method + int version() const override { return 2; } + + /// Algorithm's category, overriding a virtual method + const std::string category() const override { return "Crystal\\Corrections"; } + + /// Extra help info + const std::vector seeAlso() const override { + return {"CalculateUMatrix"}; + } + +private: + /// Overwrites Algorithm method. Does nothing at present + void init() override; + + /// Overwrites Algorithm method + void exec() override; + + /// Private validator for inputs + std::map validateInputs() override; + + /// Private function dedicated for parsing lattice constant + void parseLatticeConstant( + std::shared_ptr pws); + + /// Private function for getting names of banks to be calibrated + void getBankNames(std::shared_ptr pws); + + /// Private function for calibrating T0 + void optimizeT0(std::shared_ptr pws); + + /// Private function for calibrating L1 + void optimizeL1(std::shared_ptr pws); + + /// Private function for calibrating banks + void optimizeBanks(std::shared_ptr pws); + + /// Helper functions for adjusting T0 for all peaks + void adjustT0(double dT0, DataObjects::PeaksWorkspace_sptr &pws); + + /// Helper functions for adjusting components + void adjustComponent(double dx, double dy, double dz, double rvx, double rvy, + double rvz, double rang, std::string cmptName, + DataObjects::PeaksWorkspace_sptr &pws); + + /// Generate a Table workspace to store the calibration results + DataObjects::TableWorkspace_sptr + generateCalibrationTable(std::shared_ptr &instrument); + + /// Save to xml file for Mantid to load + void saveXmlFile(const std::string &FileName, + boost::container::flat_set &AllBankNames, + std::shared_ptr &instrument); + + /// Save to ISAW type det calibration output for backward compatiblity + void saveIsawDetCal(const std::string &filename, + boost::container::flat_set &AllBankName, + std::shared_ptr &instrument, + double T0); + + /// Save the calibration table to a CSV file + void saveCalibrationTable(const std::string &FileName, + DataObjects::TableWorkspace_sptr &tws); + + /// unique vars for a given instance of calibration + double m_a, m_b, m_c, m_alpha, m_beta, m_gamma; + double m_T0 = 0.0; + double m_tolerance_translation = 1e-4; // meters + double m_tolerance_rotation = 1e-3; // degree + // The following bounds are set based on information provided by the + // CORELLI team + double m_bank_translation_bounds = 5e-2; // meter + double m_bank_rotation_bounds = 5.0; // degree + double m_source_translation_bounds = 0.1; // meter + bool LOGCHILDALG{false}; + const int MINIMUM_PEAKS_PER_BANK{6}; + + // Column names and types + const std::string calibrationTableColumnNames[8] = { + "ComponentName", "Xposition", "Yposition", + "Zposition", "XdirectionCosine", "YdirectionCosine", + "ZdirectionCosine", "RotationAngle"}; + const std::string calibrationTableColumnTypes[8] = { + "str", "double", "double", "double", + "double", "double", "double", "double"}; + + boost::container::flat_set m_BankNames; + Mantid::DataObjects::TableWorkspace_sptr mCaliTable; +}; + +} // namespace Crystal +} // namespace Mantid diff --git a/Framework/Crystal/inc/MantidCrystal/SCDCalibratePanels2ObjFunc.h b/Framework/Crystal/inc/MantidCrystal/SCDCalibratePanels2ObjFunc.h new file mode 100644 index 0000000000000000000000000000000000000000..133347a5a485c9b6dad1d8393ab871c5244d0aa6 --- /dev/null +++ b/Framework/Crystal/inc/MantidCrystal/SCDCalibratePanels2ObjFunc.h @@ -0,0 +1,56 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/IFunction1D.h" +#include "MantidAPI/ParamFunction.h" +#include "MantidAPI/Workspace_fwd.h" +#include "MantidCrystal/DllConfig.h" +#include "MantidKernel/V3D.h" + +namespace Mantid { +namespace Crystal { + +/** SCDCalibratePanels2ObjFunc : TODO: DESCRIPTION + */ +class MANTID_CRYSTAL_DLL SCDCalibratePanels2ObjFunc : public API::ParamFunction, + public API::IFunction1D { +public: + /// Constructor for init input vector + SCDCalibratePanels2ObjFunc(); + + /// overwrite based class name + std::string name() const override { return "SCDCalibratePanels2ObjFunc"; } + + /// set category + const std::string category() const override { return "General"; } + + /// base objective function + void function1D(double *out, const double *xValues, + const size_t order) const override; + +private: + /// temp workspace holder + mutable std::shared_ptr m_ws; + mutable std::string m_cmpt; + + const bool LOGCHILDALG{false}; + const Mantid::Kernel::V3D UNSET_HKL{0, 0, 0}; + const double PI{3.1415926535897932384626433832795028841971693993751058209}; + + /// helper functions + void moveInstruentComponentBy(double deltaX, double deltaY, double deltaZ, + std::string componentName, + const API::Workspace_sptr &ws) const; + + void rotateInstrumentComponentBy(double rotVx, double rotVy, double rotVz, + double rotAng, std::string componentName, + const API::Workspace_sptr &ws) const; +}; + +} // namespace Crystal +} // namespace Mantid diff --git a/Framework/Crystal/inc/MantidCrystal/SaveHKL.h b/Framework/Crystal/inc/MantidCrystal/SaveHKL.h index cd708f0c4e04e29fd017aafec80d0871fe98ccf7..18461d468393596a0dd0f040f79c431ff38609f1 100644 --- a/Framework/Crystal/inc/MantidCrystal/SaveHKL.h +++ b/Framework/Crystal/inc/MantidCrystal/SaveHKL.h @@ -32,7 +32,7 @@ public: /// Algorithm's version for identification int version() const override { return 1; } const std::vector seeAlso() const override { - return {"LoadHKL"}; + return {"LoadHKL", "SaveHKLCW"}; } /// Algorithm's category for identification const std::string category() const override { diff --git a/Framework/Crystal/src/IndexPeaks.cpp b/Framework/Crystal/src/IndexPeaks.cpp index 55fe977b5a713344a9eb92f49ed275a342468b67..2ca5907c0135ca530151613b63fccf7576218662 100644 --- a/Framework/Crystal/src/IndexPeaks.cpp +++ b/Framework/Crystal/src/IndexPeaks.cpp @@ -416,11 +416,12 @@ std::map IndexPeaks::validateInputs() { PeaksWorkspace_sptr ws = this->getProperty(Prop::PEAKSWORKSPACE); try { ws->sample().getOrientedLattice(); - } catch (std::runtime_error &exc) { - helpMsgs[Prop::PEAKSWORKSPACE] = exc.what(); + } catch (std::runtime_error &) { + helpMsgs[Prop::PEAKSWORKSPACE] = "No UB Matrix defined in the lattice."; + return helpMsgs; } - // get all runs which have peaksin the table + // get all runs which have peaks in the table const bool commonUB = this->getProperty(Prop::COMMONUB); if (commonUB) { const auto &allPeaks = ws->getPeaks(); @@ -437,6 +438,31 @@ std::map IndexPeaks::validateInputs() { }; }; + const auto args = Prop::IndexPeaksArgs::parse(*this); + const bool isSave = this->getProperty(Prop::SAVEMODINFO); + const bool isMOZero = (args.satellites.maxOrder == 0); + bool isAllVecZero = true; + for (size_t vecNo = 0; vecNo < args.satellites.modVectors.size(); vecNo++) { + if (args.satellites.modVectors[vecNo] != V3D(0.0, 0.0, 0.0)) { + isAllVecZero = false; + } + } + if (isMOZero && !isAllVecZero) { + helpMsgs["MaxOrder"] = + "Max Order cannot be zero if a Modulation Vector has been supplied."; + } + if (!isMOZero && isAllVecZero) { + helpMsgs["ModVector1"] = + "At least one Modulation Vector must be supplied if Max Order set."; + } + if (isSave && isAllVecZero) { + helpMsgs[Prop::SAVEMODINFO] = "Modulation info cannot be saved with no " + "valid Modulation Vectors supplied."; + } + if (isSave && isMOZero) { + helpMsgs["MaxOrder"] = + "Modulation info cannot be saved with Max Order = 0."; + } return helpMsgs; } diff --git a/Framework/Crystal/src/OptimizeLatticeForCellType.cpp b/Framework/Crystal/src/OptimizeLatticeForCellType.cpp index 0921cf5ccc2691d3f6aeef431b1b2f025ee30a64..fbdea5ba4683341c7b0e65229e856b762eb60465 100644 --- a/Framework/Crystal/src/OptimizeLatticeForCellType.cpp +++ b/Framework/Crystal/src/OptimizeLatticeForCellType.cpp @@ -100,7 +100,7 @@ void OptimizeLatticeForCellType::exec() { if (perRun) { std::vector> criteria; // Sort by run number - criteria.emplace_back(std::pair("runnumber", true)); + criteria.emplace_back("runnumber", true); ws->sort(criteria); const std::vector &peaks_all = ws->getPeaks(); int run = 0; diff --git a/Framework/Crystal/src/PeakAlgorithmHelpers.cpp b/Framework/Crystal/src/PeakAlgorithmHelpers.cpp index 733185e6deec69a6aa01a4843e7b1eeed16a030e..8fa7d3910dfcc89648b33a225c197e068b4b0736 100644 --- a/Framework/Crystal/src/PeakAlgorithmHelpers.cpp +++ b/Framework/Crystal/src/PeakAlgorithmHelpers.cpp @@ -170,8 +170,8 @@ generateOffsetVectors(const std::vector &modVectors, for (auto p = -maxOrder; p <= maxOrder; ++p) { if (m == 0 && n == 0 && p == 0) continue; - offsets.emplace_back(std::make_tuple( - m, n, p, modVector0 * m + modVector1 * n + modVector2 * p)); + offsets.emplace_back( + m, n, p, modVector0 * m + modVector1 * n + modVector2 * p); } } } @@ -188,13 +188,13 @@ generateOffsetVectors(const std::vector &modVectors, V3D offset{modVector * order}; switch (i) { case 0: - offsets.emplace_back(std::make_tuple(order, 0, 0, std::move(offset))); + offsets.emplace_back(order, 0, 0, std::move(offset)); break; case 1: - offsets.emplace_back(std::make_tuple(0, order, 0, std::move(offset))); + offsets.emplace_back(0, order, 0, std::move(offset)); break; case 2: - offsets.emplace_back(std::make_tuple(0, 0, order, std::move(offset))); + offsets.emplace_back(0, 0, order, std::move(offset)); break; } } diff --git a/Framework/Crystal/src/PeakIntensityVsRadius.cpp b/Framework/Crystal/src/PeakIntensityVsRadius.cpp index 9d30cdd1a072c4b49dc749fba55d49aed8a080f1..10a7d9b337ccb03f08e1c1d11b9d501b69b79121 100644 --- a/Framework/Crystal/src/PeakIntensityVsRadius.cpp +++ b/Framework/Crystal/src/PeakIntensityVsRadius.cpp @@ -187,9 +187,11 @@ void PeakIntensityVsRadius::exec() { progStep *double(step + 1), false); alg->setProperty("InputWorkspace", inWS); alg->setProperty("PeaksWorkspace", peaksWS); - alg->setProperty("PeakRadius", radius); - alg->setProperty("BackgroundOuterRadius", OuterRadius); - alg->setProperty("BackgroundInnerRadius", InnerRadius); + alg->setProperty>("PeakRadius", {radius}); + alg->setProperty>("BackgroundOuterRadius", + {OuterRadius}); + alg->setProperty>("BackgroundInnerRadius", + {InnerRadius}); alg->setPropertyValue("OutputWorkspace", "__tmp__PeakIntensityVsRadius"); alg->execute(); if (alg->isExecuted()) { diff --git a/Framework/Crystal/src/PredictPeaks.cpp b/Framework/Crystal/src/PredictPeaks.cpp index 514cfc0738be7be0a63836008d6b73c873c90bfa..473f13abfac12d3f0933c0fea12e42df60764a04 100644 --- a/Framework/Crystal/src/PredictPeaks.cpp +++ b/Framework/Crystal/src/PredictPeaks.cpp @@ -88,6 +88,23 @@ void PredictPeaks::init() { std::make_unique( "CalculateGoniometerForCW", IS_NOT_DEFAULT)); + declareProperty("InnerGoniometer", false, + "Whether the goniometer to be calculated is the most inner " + "(phi) or most outer (omega)"); + setPropertySettings("InnerGoniometer", + std::make_unique( + "CalculateGoniometerForCW", + Mantid::Kernel::ePropertyCriterion::IS_NOT_DEFAULT)); + + declareProperty("FlipX", false, + "Used when calculating goniometer angle if the q_lab x value " + "should be negative, hence the detector of the other side " + "(right) of the beam"); + setPropertySettings("FlipX", + std::make_unique( + "CalculateGoniometerForCW", + Mantid::Kernel::ePropertyCriterion::IS_NOT_DEFAULT)); + declareProperty(std::make_unique>("MinAngle", -180, Direction::Input), "Minimum goniometer rotation angle"); @@ -196,7 +213,7 @@ void PredictPeaks::exec() { // Sort peaks by run number so that peaks with equal goniometer matrices are // adjacent std::vector> criteria; - criteria.emplace_back(std::pair("RunNumber", true)); + criteria.emplace_back("RunNumber", true); peaksWS->sort(criteria); @@ -304,20 +321,25 @@ void PredictPeaks::exec() { } double angleMin = getProperty("MinAngle"); double angleMax = getProperty("MaxAngle"); + bool innerGoniometer = getProperty("InnerGoniometer"); + bool flipX = getProperty("FlipX"); for (auto &possibleHKL : possibleHKLs) { - Geometry::Goniometer goniometer; + Geometry::Goniometer goniometer(gonioVec.front()); V3D q_sample = ub * possibleHKL * (2.0 * M_PI * m_qConventionFactor); - goniometer.calcFromQSampleAndWavelength(q_sample, wavelength); - double angle = goniometer.getEulerAngles()[0]; + goniometer.calcFromQSampleAndWavelength(q_sample, wavelength, flipX, + innerGoniometer); + std::vector angles = goniometer.getEulerAngles("YZY"); + double angle = innerGoniometer ? angles[2] : angles[0]; if (!std::isfinite(angle) || angle < angleMin || angle > angleMax) continue; DblMatrix orientedUB = goniometer.getR() * ub; V3D q_lab = orientedUB * possibleHKL; double lambda = (2.0 * q_lab.Z()) / (q_lab.norm2()); if (std::abs(wavelength - lambda) < 0.01) { - g_log.information() << "Found goniometer rotation to be " - << goniometer.getEulerAngles()[0] - << " degrees for HKL = " << possibleHKL << "\n"; + g_log.information() + << "Found goniometer rotation to be in YZY convention [" + << angles[0] << ", " << angles[1] << ". " << angles[2] + << "] degrees for Q sample = " << q_sample << "\n"; calculateQAndAddToOutput(possibleHKL, orientedUB, goniometer.getR()); ++allowedPeakCount; } @@ -360,8 +382,8 @@ void PredictPeaks::exec() { // Sort peaks by run number so that peaks with equal goniometer matrices are // adjacent std::vector> criteria; - criteria.emplace_back(std::pair("RunNumber", true)); - criteria.emplace_back(std::pair("BankName", true)); + criteria.emplace_back("RunNumber", true); + criteria.emplace_back("BankName", true); m_pw->sort(criteria); auto &peaks = m_pw->getPeaks(); diff --git a/Framework/Crystal/src/PredictSatellitePeaks.cpp b/Framework/Crystal/src/PredictSatellitePeaks.cpp index 609bc035dd2413188ad214b7f64054d9410902ca..1678cfbea707d24145f646d908bd61e08e650a76 100644 --- a/Framework/Crystal/src/PredictSatellitePeaks.cpp +++ b/Framework/Crystal/src/PredictSatellitePeaks.cpp @@ -231,11 +231,11 @@ void PredictSatellitePeaks::exec() { // Sort peaks by run number so that peaks with equal goniometer matrices are // adjacent std::vector> criteria; - criteria.emplace_back(std::pair("RunNumber", true)); - criteria.emplace_back(std::pair("BankName", true)); - criteria.emplace_back(std::pair("h", true)); - criteria.emplace_back(std::pair("k", true)); - criteria.emplace_back(std::pair("l", true)); + criteria.emplace_back("RunNumber", true); + criteria.emplace_back("BankName", true); + criteria.emplace_back("h", true); + criteria.emplace_back("k", true); + criteria.emplace_back("l", true); outPeaks->sort(criteria); for (int i = 0; i < static_cast(outPeaks->getNumberPeaks()); ++i) { @@ -317,11 +317,11 @@ void PredictSatellitePeaks::exec_peaks() { // Sort peaks by run number so that peaks with equal goniometer matrices are // adjacent std::vector> criteria; - criteria.emplace_back(std::pair("RunNumber", true)); - criteria.emplace_back(std::pair("BankName", true)); - criteria.emplace_back(std::pair("h", true)); - criteria.emplace_back(std::pair("k", true)); - criteria.emplace_back(std::pair("l", true)); + criteria.emplace_back("RunNumber", true); + criteria.emplace_back("BankName", true); + criteria.emplace_back("h", true); + criteria.emplace_back("k", true); + criteria.emplace_back("l", true); outPeaks->sort(criteria); for (int i = 0; i < static_cast(outPeaks->getNumberPeaks()); ++i) { diff --git a/Framework/Crystal/src/SCDCalibratePanels2.cpp b/Framework/Crystal/src/SCDCalibratePanels2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3c5c046367b50b0cf55e8916710e2e36cc0e06f5 --- /dev/null +++ b/Framework/Crystal/src/SCDCalibratePanels2.cpp @@ -0,0 +1,915 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + + +#include "MantidCrystal/SCDCalibratePanels2.h" +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/ConstraintFactory.h" +#include "MantidAPI/ExperimentInfo.h" +#include "MantidAPI/FileProperty.h" +#include "MantidAPI/FunctionFactory.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/Sample.h" +#include "MantidAPI/TableRow.h" +#include "MantidAPI/WorkspaceFactory.h" +#include "MantidCrystal/SCDCalibratePanels2ObjFunc.h" +#include "MantidDataObjects/PeaksWorkspace.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidGeometry/Crystal/OrientedLattice.h" +#include "MantidKernel/BoundedValidator.h" +#include "MantidKernel/Logger.h" +#include +#include +#include + +namespace Mantid { +namespace Crystal { + +using namespace Mantid::API; +using namespace Mantid::DataObjects; +using namespace Mantid::Geometry; +using namespace Mantid::Kernel; + +/// Config logger +namespace { +Logger logger("SCDCalibratePanels2"); +} + +DECLARE_ALGORITHM(SCDCalibratePanels2) + +/** + * @brief Initialization + * + */ +void SCDCalibratePanels2::init() { + // Input peakworkspace + declareProperty(std::make_unique>( + "PeakWorkspace", "", Kernel::Direction::Input), + "Workspace of Indexed Peaks"); + + // Lattice constant group + auto mustBePositive = std::make_shared>(); + mustBePositive->setLower(0.0); + // NOTE: + // Build serve does not allow a,b,c,alpha,beta,gamma as names, hence + // the awkward naming here. + // + // Stacktrace + // ConfigService-[Information] Unable to locate directory at: + // /etc/mantid/instrument ConfigService-[Information] This is Mantid + // version 5.1.1-2-rc.1 revision ge9b2c62 ConfigService-[Information] running + // on ndw1457 starting 2021-01-18T01:51Z ConfigService-[Information] + // Properties file(s) loaded: /opt/mantidunstable/bin/Mantid.properties, + // /home/abc/.mantid/Mantid.user.properties ConfigService-[Information] Unable + // to locate directory at: /etc/mantid/instrument FrameworkManager-[Notice] + // Welcome to Mantid 5.1.1-2-rc.1 FrameworkManager-[Notice] Please cite: + // http://dx.doi.org/10.1016/j.nima.2014.07.029 and this release: + // http://dx.doi.org/10.5286/Software/Mantid5.1.1 + // FrameworkManager-[Information] Instrument updates disabled - cannot update + // instrument definitions. FrameworkManager-[Information] Version check + // disabled. SCDCalibratePanels(v2) property (a) violates conventions + // SCDCalibratePanels(v2) property (b) violates conventions + // SCDCalibratePanels(v2) property (c) violates conventions + // SCDCalibratePanels(v2) property (alpha) violates conventions + // SCDCalibratePanels(v2) property (beta) violates conventions + // SCDCalibratePanels(v2) property (gamma) violates conventions + // RESULT|iteration time_taken|1 0.15 + // RESULT|time_taken|0.15 + // Found 6 errors. Coding conventions found at + // http://www.mantidproject.org/Mantid_Standards RESULT|memory footprint + // increase|8.5703125 + // + declareProperty("a", EMPTY_DBL(), mustBePositive, + "Lattice Parameter a (Leave empty to use lattice constants " + "in peaks workspace)"); + declareProperty("b", EMPTY_DBL(), mustBePositive, + "Lattice Parameter b (Leave empty to use lattice constants " + "in peaks workspace)"); + declareProperty("c", EMPTY_DBL(), mustBePositive, + "Lattice Parameter c (Leave empty to use lattice constants " + "in peaks workspace)"); + declareProperty("alpha", EMPTY_DBL(), mustBePositive, + "Lattice Parameter alpha in degrees (Leave empty to use " + "lattice constants in peaks workspace)"); + declareProperty("beta", EMPTY_DBL(), mustBePositive, + "Lattice Parameter beta in degrees (Leave empty to use " + "lattice constants in peaks workspace)"); + declareProperty("gamma", EMPTY_DBL(), mustBePositive, + "Lattice Parameter gamma in degrees (Leave empty to use " + "lattice constants in peaks workspace)"); + const std::string LATTICE("Lattice Constants"); + setPropertyGroup("a", LATTICE); + setPropertyGroup("b", LATTICE); + setPropertyGroup("c", LATTICE); + setPropertyGroup("alpha", LATTICE); + setPropertyGroup("beta", LATTICE); + setPropertyGroup("gamma", LATTICE); + + // Calibration options group + declareProperty("CalibrateT0", false, "Calibrate the T0 (initial TOF)"); + declareProperty("CalibrateL1", true, + "Change the L1(source to sample) distance"); + declareProperty("CalibrateBanks", true, + "Calibrate position and orientation of each bank."); + // TODO: + // - add support to ignore edge pixels (EdgePixels) + // - add support for composite panels like SNAP (CalibrateSNAPPanels) + // - add support for calibration panels with non-standard size + // (ChangePanelSize) + // Once the core functionality of calibration is done, we can consider + // adding the following control calibration parameters. + const std::string PARAMETERS("Calibration Parameters"); + setPropertyGroup("CalibrateT0", PARAMETERS); + setPropertyGroup("CalibrateL1", PARAMETERS); + setPropertyGroup("CalibrateBanks", PARAMETERS); + + // Output options group + declareProperty(std::make_unique>( + "OutputWorkspace", "", Direction::Output), + "The workspace containing the calibration table."); + const std::vector detcalExts{".DetCal", ".Det_Cal"}; + declareProperty( + std::make_unique("DetCalFilename", "SCDCalibrate2.DetCal", + FileProperty::OptionalSave, detcalExts), + "Path to an ISAW-style .detcal file to save."); + declareProperty( + std::make_unique("XmlFilename", "SCDCalibrate2.xml", + FileProperty::OptionalSave, ".xml"), + "Path to an Mantid .xml description(for LoadParameterFile) file to " + "save."); + declareProperty( + std::make_unique("CSVFilename", "SCDCalibrate2.csv", + FileProperty::OptionalSave, ".csv"), + "Path to an .csv file which contains the Calibration Table"); + // TODO: + // - add option to store intermedia calibration results for additional + // analysis if needed + const std::string OUTPUT("Output"); + setPropertyGroup("OutputWorkspace", OUTPUT); + setPropertyGroup("DetCalFilename", OUTPUT); + setPropertyGroup("XmlFilename", OUTPUT); + setPropertyGroup("CSVFilename", OUTPUT); + + // Add new section for advanced control of the calibration/optimization + declareProperty("ToleranceOfTranslation", 5e-4, mustBePositive, + "Translations in meters found below this value will be set " + "to 0"); + declareProperty("ToleranceOfReorientation", 5e-2, mustBePositive, + "Reorientation (rotation) angles in degress found below " + "this value will be set to 0"); + declareProperty( + "TranslationSearchRadius", 5e-2, mustBePositive, + "This is the search radius when calibrating component translations " + "using optimization. For CORELLI instrument, most panels will shift " + "within 5cm, therefore the search radius is set to 5e-2."); + declareProperty( + "RotationSearchRadius", 5.0, mustBePositive, + "This is the search radius when calibrating component orientations " + "using optimization. For CORELLI instrument, most panels will wobble " + "within 5 degrees, therefore the default values is set to 5 here."); + declareProperty( + "SourceShiftSearchRadius", 0.1, mustBePositive, + "This is the search radius when calibrating source shift, L1, using " + "optimization. For CORELLI instrument, the source shift is often " + "within 10 cm, therefore the default value is set to 0.1."); + declareProperty("VerboseOutput", false, + "Toggle of child algorithm console output."); + // grouping into one category + const std::string ADVCNTRL("AdvancedControl"); + setPropertyGroup("ToleranceOfTranslation", ADVCNTRL); + setPropertyGroup("ToleranceOfReorientation", ADVCNTRL); + setPropertyGroup("TranslationSearchRadius", ADVCNTRL); + setPropertyGroup("RotationSearchRadius", ADVCNTRL); + setPropertyGroup("SourceShiftSearchRadius", ADVCNTRL); + setPropertyGroup("VerboseOutput", ADVCNTRL); +} + +/** + * @brief validate inputs + * + * @return std::map + */ +std::map SCDCalibratePanels2::validateInputs() { + std::map issues; + + // T0 calibration is not ready for production, raise an error + // if requested by user + bool calibrateT0 = getProperty("CalibrateT0"); + if (calibrateT0) + issues["CalibrateT0"] = "Caliration of T0 is not ready for production, " + "please set it to False to continue"; + + return issues; +} + +/** + * @brief execute calibration + * + */ +void SCDCalibratePanels2::exec() { + // parse all inputs + PeaksWorkspace_sptr m_pws = getProperty("PeakWorkspace"); + + parseLatticeConstant(m_pws); + + bool calibrateT0 = getProperty("CalibrateT0"); + bool calibrateL1 = getProperty("CalibrateL1"); + bool calibrateBanks = getProperty("CalibrateBanks"); + + const std::string DetCalFilename = getProperty("DetCalFilename"); + const std::string XmlFilename = getProperty("XmlFilename"); + const std::string CSVFilename = getProperty("CSVFilename"); + + // parsing advance control parameters + m_tolerance_translation = getProperty("ToleranceOfTranslation"); + m_tolerance_rotation = getProperty("ToleranceOfReorientation"); + m_bank_translation_bounds = getProperty("TranslationSearchRadius"); + m_bank_rotation_bounds = getProperty("RotationSearchRadius"); + m_source_translation_bounds = getProperty("SourceShiftSearchRadius"); + LOGCHILDALG = getProperty("VerboseOutput"); + + // STEP_0: sort the peaks + std::vector> criteria{{"BankName", true}}; + m_pws->sort(criteria); + + // STEP_1: preparation + // get names of banks that can be calibrated + getBankNames(m_pws); + + // STEP_2: optimize T0,L1,L2,etc. + if (calibrateT0) + optimizeT0(m_pws); + if (calibrateL1) + optimizeL1(m_pws); + if (calibrateBanks) + optimizeBanks(m_pws); + + // STEP_3: generate a table workspace to save the calibration results + Instrument_sptr instCalibrated = + std::const_pointer_cast(m_pws->getInstrument()); + TableWorkspace_sptr tablews = generateCalibrationTable(instCalibrated); + + // STEP_4: Write to disk if required + if (!XmlFilename.empty()) + saveXmlFile(XmlFilename, m_BankNames, instCalibrated); + + if (!DetCalFilename.empty()) + saveIsawDetCal(DetCalFilename, m_BankNames, instCalibrated, m_T0); + + if (!CSVFilename.empty()) + saveCalibrationTable(CSVFilename, tablews); + + // STEP_4: Cleanup +} + +/// ------------------------------------------- /// +/// Core functions for Calibration&Optimizatoin /// +/// ------------------------------------------- /// + +/** + * @brief adjusting the deltaT0 to match the qSample_calculated and + * qSameple_measured + * + * @note this function currently only returns dT0=0, and the reason + * is still unkown. + * + * @param pws + */ +void SCDCalibratePanels2::optimizeT0(std::shared_ptr pws) { + // tl;dr; mocking a matrix workspace to store the target Qvectors + // details + // The optimizer we are going to use is the Fit algorithm, which is + // designed to match one histogram with another target one. + // So what we are doing here is creating a fake histogram out of the + // qSamples so that Fit algorithm can take it in and start optimizing. + // The same goes for optimizeL1 and optimizeBanks. + int npks = pws->getNumberPeaks(); + MatrixWorkspace_sptr t0ws = std::dynamic_pointer_cast( + WorkspaceFactory::Instance().create( + "Workspace2D", // use workspace 2D to mock a histogram + 1, // one vector + 3 * npks, // X :: anything is fine + 3 * npks)); // Y :: flattened Q vector + // setting values to t0ws + auto &measured = t0ws->getSpectrum(0); + auto &xv = measured.mutableX(); + auto &yv = measured.mutableY(); + auto &ev = measured.mutableE(); + + for (int i = 0; i < npks; ++i) { + const Peak &pk = pws->getPeak(i); + V3D qv = pk.getQSampleFrame(); + + for (int j = 0; j < 3; ++j) { + xv[i * 3 + j] = i * 3 + j; + yv[i * 3 + j] = qv[j]; + ev[i * 3 + j] = 1; + } + } + + // create child Fit alg to optimize T0 + IAlgorithm_sptr fitT0_alg = createChildAlgorithm("Fit", -1, -1, false); + //-- obj func def + // dl;dr; + // Fit algorithm requires a IFunction1D to fit + // details + // Fit algorithm requires a class derived from IFunction1D as its + // input, so we have to implement the objective function as a separate + // class just to get Fit serving as an optimizer. + // For this particular case, we are constructing an objective function + // based on IFunction1D that outputs a fake histogram consist of + // qSample calucated based on perturbed instrument positions and + // orientations. + std::ostringstream fun_str; + fun_str << "name=SCDCalibratePanels2ObjFunc,Workspace=" << pws->getName() + << ",ComponentName=none"; + //-- bounds&constraints def + std::ostringstream tie_str; + tie_str << "DeltaX=0.0,DeltaY=0.0,DeltaZ=0.0,Theta=1.0,Phi=0.0," + "DeltaRotationAngle=0.0"; + //-- set&go + fitT0_alg->setPropertyValue("Function", fun_str.str()); + fitT0_alg->setProperty("Ties", tie_str.str()); + fitT0_alg->setProperty("InputWorkspace", t0ws); + fitT0_alg->setProperty("CreateOutput", true); + fitT0_alg->setProperty("Output", "fit"); + fitT0_alg->executeAsChildAlg(); + //-- parse output + double chi2OverDOF = fitT0_alg->getProperty("OutputChi2overDoF"); + ITableWorkspace_sptr rst = fitT0_alg->getProperty("OutputParameters"); + double dT0_optimized = rst->getRef("Value", 6); + // update T0 for all peaks + adjustT0(dT0_optimized, pws); + + //-- log + g_log.notice() << "-- Fit T0 results using " << npks << " peaks:\n" + << " dT0: " << dT0_optimized << " \n" + << " chi2/DOF = " << chi2OverDOF << "\n"; +} + +/** + * @brief + * + * @param pws + */ +void SCDCalibratePanels2::optimizeL1(std::shared_ptr pws) { + // cache starting L1 position + double original_L1 = -pws->getInstrument()->getSource()->getPos().Z(); + int npks = pws->getNumberPeaks(); + MatrixWorkspace_sptr l1ws = std::dynamic_pointer_cast( + WorkspaceFactory::Instance().create( + "Workspace2D", // use workspace 2D to mock a histogram + 1, // one vector + 3 * npks, // X :: anything is fine + 3 * npks)); // Y :: flattened Q vector + + auto &measured = l1ws->getSpectrum(0); + auto &xv = measured.mutableX(); + auto &yv = measured.mutableY(); + auto &ev = measured.mutableE(); + + for (int i = 0; i < npks; ++i) { + const Peak &pk = pws->getPeak(i); + V3D qv = pk.getQSampleFrame(); + for (int j = 0; j < 3; ++j) { + xv[i * 3 + j] = i * 3 + j; + yv[i * 3 + j] = qv[j]; + ev[i * 3 + j] = 1; + } + } + + // fit algorithm for the optimization of L1 + IAlgorithm_sptr fitL1_alg = createChildAlgorithm("Fit", -1, -1, false); + //-- obj func def + std::ostringstream fun_str; + fun_str << "name=SCDCalibratePanels2ObjFunc,Workspace=" << pws->getName() + << ",ComponentName=moderator"; + //-- bounds&constraints def + std::ostringstream tie_str; + tie_str << "DeltaX=0.0,DeltaY=0.0,Theta=1.0,Phi=0.0,DeltaRotationAngle=0.0," + "DeltaT0=" + << m_T0; + //-- set and go + fitL1_alg->setPropertyValue("Function", fun_str.str()); + fitL1_alg->setProperty("Ties", tie_str.str()); + fitL1_alg->setProperty("InputWorkspace", l1ws); + fitL1_alg->setProperty("CreateOutput", true); + fitL1_alg->setProperty("Output", "fit"); + fitL1_alg->executeAsChildAlg(); + //-- parse output + double chi2OverDOF = fitL1_alg->getProperty("OutputChi2overDoF"); + ITableWorkspace_sptr rst = fitL1_alg->getProperty("OutputParameters"); + double dL1_optimized = rst->getRef("Value", 2); + adjustComponent(0.0, 0.0, dL1_optimized, 1.0, 0.0, 0.0, 0.0, + pws->getInstrument()->getSource()->getName(), pws); + + //-- log + g_log.notice() << "-- Fit L1 results using " << npks << " peaks:\n" + << " dL1: " << dL1_optimized << " \n" + << " L1 " << original_L1 << " -> " + << -pws->getInstrument()->getSource()->getPos().Z() << " \n" + << " chi2/DOF = " << chi2OverDOF << "\n"; +} + +/** + * @brief Calibrate the position and rotation of each Bank, one at a time + * + * @param pws + */ +void SCDCalibratePanels2::optimizeBanks(std::shared_ptr pws) { + + PARALLEL_FOR_IF(Kernel::threadSafe(*pws)) + for (int i = 0; i < static_cast(m_BankNames.size()); ++i) { + PARALLEL_START_INTERUPT_REGION + // prepare local copies to work with + const std::string bankname = *std::next(m_BankNames.begin(), i); + + //-- step 0: extract peaks that lies on the current bank + // NOTE: We are cloning the whole pws, then subtracting + // those that are not on the current bank. + PeaksWorkspace_sptr pwsBanki = pws->clone(); + const std::string pwsBankiName = "_pws_" + bankname; + AnalysisDataService::Instance().addOrReplace(pwsBankiName, pwsBanki); + std::vector &allPeaks = pwsBanki->getPeaks(); + auto notMyPeaks = std::remove_if( + allPeaks.begin(), allPeaks.end(), + [&bankname](const Peak &pk) { return pk.getBankName() != bankname; }); + allPeaks.erase(notMyPeaks, allPeaks.end()); + + // Do not attempt correct panels with less than 6 peaks as the system will + // be under-determined + int nBankPeaks = pwsBanki->getNumberPeaks(); + if (nBankPeaks < MINIMUM_PEAKS_PER_BANK) { + g_log.notice() << "-- Bank " << bankname << " have only " << nBankPeaks + << " (<" << MINIMUM_PEAKS_PER_BANK + << ") Peaks, skipping\n"; + continue; + } + + //-- step 1: prepare a mocked workspace with QSample as its yValues + MatrixWorkspace_sptr wsBankCali = + std::dynamic_pointer_cast( + WorkspaceFactory::Instance().create( + "Workspace2D", // use workspace 2D to mock a histogram + 1, // one vector + 3 * nBankPeaks, // X :: anything is fine + 3 * nBankPeaks)); // Y :: flattened Q vector + auto &measured = wsBankCali->getSpectrum(0); + auto &xv = measured.mutableX(); + auto &yv = measured.mutableY(); + auto &ev = measured.mutableE(); + // TODO: non-uniform weighting (ev) will be implemented at a later date + for (int i = 0; i < nBankPeaks; ++i) { + const Peak &pk = pwsBanki->getPeak(i); + V3D qv = pk.getQSampleFrame(); + for (int j = 0; j < 3; ++j) { + xv[i * 3 + j] = i * 3 + j; + yv[i * 3 + j] = qv[j]; + ev[i * 3 + j] = 1; + } + } + + //-- step 2&3: invoke fit to find both traslation and rotation + IAlgorithm_sptr fitBank_alg = createChildAlgorithm("Fit", -1, -1, false); + //---- setup obj fun def + std::ostringstream fun_str; + fun_str << "name=SCDCalibratePanels2ObjFunc,Workspace=" << pwsBankiName + << ",ComponentName=" << bankname; + //---- bounds&constraints def + std::ostringstream tie_str; + tie_str << "DeltaT0=" << m_T0; + std::ostringstream constraint_str; + double brb = std::abs(m_bank_rotation_bounds); + constraint_str << "0.0setPropertyValue("Function", fun_str.str()); + fitBank_alg->setProperty("Ties", tie_str.str()); + fitBank_alg->setProperty("Constraints", constraint_str.str()); + fitBank_alg->setProperty("InputWorkspace", wsBankCali); + fitBank_alg->setProperty("CreateOutput", true); + fitBank_alg->setProperty("Output", "fit"); + fitBank_alg->executeAsChildAlg(); + //---- cache results + double chi2OverDOF = fitBank_alg->getProperty("OutputChi2overDoF"); + ITableWorkspace_sptr rstFitBank = + fitBank_alg->getProperty("OutputParameters"); + double dx = rstFitBank->getRef("Value", 0); + double dy = rstFitBank->getRef("Value", 1); + double dz = rstFitBank->getRef("Value", 2); + double theta = rstFitBank->getRef("Value", 3); + double phi = rstFitBank->getRef("Value", 4); + double rotang = rstFitBank->getRef("Value", 5); + + //-- step 4: update the instrument with optimization results + // if the fit results are above the tolerance/threshold + std::string bn = bankname; + if (pws->getInstrument()->getName().compare("CORELLI") == 0) + bn.append("/sixteenpack"); + if ((std::abs(dx) < m_tolerance_translation) && + (std::abs(dy) < m_tolerance_translation) && + (std::abs(dz) < m_tolerance_translation) && + (std::abs(rotang) < m_tolerance_rotation)) { + // skip the adjustment of the component as it is juat noise + g_log.notice() << "-- Fit " << bn + << " results below tolerance, skippping\n"; + } else { + double rvx = sin(theta) * cos(phi); + double rvy = sin(theta) * sin(phi); + double rvz = cos(theta); + adjustComponent(dx, dy, dz, rvx, rvy, rvz, rotang, bn, pws); + g_log.notice() << "-- Fit " << bn << " results using " << nBankPeaks + << " peaks:\n " + << " d(x,y,z) = (" << dx << "," << dy << "," << dz + << ")\n" + << " rotang(rx,ry,rz) =" << rotang << "(" << rvx << "," + << rvy << "," << rvz << ")\n" + << " chi2/DOF = " << chi2OverDOF << "\n"; + } + + // -- cleanup + AnalysisDataService::Instance().remove(pwsBankiName); + PARALLEL_END_INTERUPT_REGION + } + PARALLEL_CHECK_INTERUPT_REGION +} + +/// ---------------- /// +/// helper functions /// +/// ---------------- /// + +/** + * @brief get lattice constants from either inputs or the + * input peak workspace + * + */ +void SCDCalibratePanels2::parseLatticeConstant( + std::shared_ptr pws) { + m_a = getProperty("a"); + m_b = getProperty("b"); + m_c = getProperty("c"); + m_alpha = getProperty("alpha"); + m_beta = getProperty("beta"); + m_gamma = getProperty("gamma"); + // if any one of the six lattice constants is missing, try to get + // one from the workspace + if ((m_a == EMPTY_DBL() || m_b == EMPTY_DBL() || m_c == EMPTY_DBL() || + m_alpha == EMPTY_DBL() || m_beta == EMPTY_DBL() || + m_gamma == EMPTY_DBL()) && + (pws->sample().hasOrientedLattice())) { + OrientedLattice lattice = pws->mutableSample().getOrientedLattice(); + m_a = lattice.a(); + m_b = lattice.b(); + m_c = lattice.c(); + m_alpha = lattice.alpha(); + m_beta = lattice.beta(); + m_gamma = lattice.gamma(); + } +} + +/** + * @brief Gather names for bank for calibration + * + * @param pws + */ +void SCDCalibratePanels2::getBankNames(std::shared_ptr pws) { + int npeaks = static_cast(pws->getNumberPeaks()); + for (int i = 0; i < npeaks; ++i) { + std::string bname = pws->getPeak(i).getBankName(); + if (bname != "None") + m_BankNames.insert(bname); + } +} + +/** + * @brief shift T0 for both peakworkspace and all peaks + * + * @param dT0 + * @param pws + */ +void SCDCalibratePanels2::adjustT0(double dT0, PeaksWorkspace_sptr &pws) { + // update the T0 record in peakworkspace + Mantid::API::Run &run = pws->mutableRun(); + double T0 = 0.0; + if (run.hasProperty("T0")) { + T0 = run.getPropertyValueAsType("T0"); + } + T0 += dT0; + run.addProperty("T0", T0, true); + + // update wavelength of each peak using new T0 + for (int i = 0; i < pws->getNumberPeaks(); ++i) { + Peak &pk = pws->getPeak(i); + Units::Wavelength wl; + wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0, + pk.getInitialEnergy(), 0.0); + pk.setWavelength(wl.singleFromTOF(pk.getTOF() + dT0)); + } +} + +/** + * @brief adjust instrument component position and orientation + * + * @param dx + * @param dy + * @param dz + * @param rvx + * @param rvy + * @param rvz + * @param rang + * @param cmptName + * @param pws + */ +void SCDCalibratePanels2::adjustComponent( + double dx, double dy, double dz, double rvx, double rvy, double rvz, + double rang, std::string cmptName, DataObjects::PeaksWorkspace_sptr &pws) { + + // orientation + IAlgorithm_sptr rot_alg = Mantid::API::AlgorithmFactory::Instance().create( + "RotateInstrumentComponent", -1); + rot_alg->initialize(); + rot_alg->setChild(true); + rot_alg->setLogging(LOGCHILDALG); + rot_alg->setProperty("Workspace", pws); + rot_alg->setProperty("ComponentName", cmptName); + rot_alg->setProperty("X", rvx); + rot_alg->setProperty("Y", rvy); + rot_alg->setProperty("Z", rvz); + rot_alg->setProperty("Angle", rang); + rot_alg->setProperty("RelativeRotation", true); + rot_alg->executeAsChildAlg(); + + // translation + IAlgorithm_sptr mv_alg = Mantid::API::AlgorithmFactory::Instance().create( + "MoveInstrumentComponent", -1); + mv_alg->initialize(); + mv_alg->setChild(true); + mv_alg->setLogging(LOGCHILDALG); + mv_alg->setProperty("Workspace", pws); + mv_alg->setProperty("ComponentName", cmptName); + mv_alg->setProperty("X", dx); + mv_alg->setProperty("Y", dy); + mv_alg->setProperty("Z", dz); + mv_alg->setProperty("RelativePosition", true); + mv_alg->executeAsChildAlg(); +} + +/** + * @brief Generate a tableworkspace to store the calibration results + * + * @param instrument :: calibrated instrument + * @return DataObjects::TableWorkspace_sptr + */ +DataObjects::TableWorkspace_sptr SCDCalibratePanels2::generateCalibrationTable( + std::shared_ptr &instrument) { + g_log.notice() << "Generate a TableWorkspace to store calibration results.\n"; + + // Create table workspace + ITableWorkspace_sptr itablews = WorkspaceFactory::Instance().createTable(); + TableWorkspace_sptr tablews = + std::dynamic_pointer_cast(itablews); + + for (int i = 0; i < 8; ++i) + tablews->addColumn(calibrationTableColumnTypes[i], + calibrationTableColumnNames[i]); + + // The first row is always the source + IComponent_const_sptr source = instrument->getSource(); + V3D sourceRelPos = source->getRelativePos(); + Mantid::API::TableRow sourceRow = tablews->appendRow(); + // NOTE: source should not have any rotation, so we pass a zero + // rotation with a fixed axis + sourceRow << instrument->getSource()->getName() << sourceRelPos.X() + << sourceRelPos.Y() << sourceRelPos.Z() << 1.0 << 0.0 << 0.0 << 0.0; + + // Loop through banks and set row values + for (auto bankName : m_BankNames) { + // CORELLLI instrument has one extra layer that pack tubes into + // banks, which is what we need here + if (instrument->getName().compare("CORELLI") == 0) + bankName.append("/sixteenpack"); + + std::shared_ptr bank = + instrument->getComponentByName(bankName); + + Quat relRot = bank->getRelativeRot(); + V3D pos1 = bank->getRelativePos(); + + // Calculate cosines using relRot + double deg, xAxis, yAxis, zAxis; + relRot.getAngleAxis(deg, xAxis, yAxis, zAxis); + + // Append a new row + Mantid::API::TableRow bankRow = tablews->appendRow(); + // Row and positions + bankRow << bankName << pos1.X() << pos1.Y() << pos1.Z() << xAxis << yAxis + << zAxis << deg; + } + + g_log.notice() << "finished generating tables\n"; + setProperty("OutputWorkspace", tablews); + + return tablews; +} + +/** + * Saves the new instrument to an xml file that can be used with the + * LoadParameterFile Algorithm. If the filename is empty, nothing gets + * done. + * + * @param FileName The filename to save this information to + * + * @param AllBankNames The names of the banks in each group whose values + * are to be saved to the file + * + * @param instrument The instrument with the new values for the banks + * in Groups + * + * TODO: + * - Need to find a way to add the information regarding calibrated T0 + */ +void SCDCalibratePanels2::saveXmlFile( + const std::string &FileName, + boost::container::flat_set &AllBankNames, + std::shared_ptr &instrument) { + g_log.notice() << "Generating xml tree" + << "\n"; + + using boost::property_tree::ptree; + ptree root; + ptree parafile; + + // configure root node + parafile.put(".instrument", instrument->getName()); + parafile.put(".valid-from", + instrument->getValidFromDate().toISO8601String()); + + // configure and add each bank + for (auto bankName : AllBankNames) { + // Prepare data for node + if (instrument->getName().compare("CORELLI") == 0) + bankName.append("/sixteenpack"); + + std::shared_ptr bank = + instrument->getComponentByName(bankName); + + Quat relRot = bank->getRelativeRot(); + std::vector relRotAngles = relRot.getEulerAngles("XYZ"); + V3D pos1 = bank->getRelativePos(); + // TODO: no handling of scaling for now, will add back later + double scalex = 1.0; + double scaley = 1.0; + + // prepare node + ptree bank_root; + ptree bank_dx, bank_dy, bank_dz; + ptree bank_dx_val, bank_dy_val, bank_dz_val; + ptree bank_drotx, bank_droty, bank_drotz; + ptree bank_drotx_val, bank_droty_val, bank_drotz_val; + ptree bank_sx, bank_sy; + ptree bank_sx_val, bank_sy_val; + + // add data to node + bank_dx_val.put(".val", pos1.X()); + bank_dy_val.put(".val", pos1.Y()); + bank_dz_val.put(".val", pos1.Z()); + bank_dx.put(".name", "x"); + bank_dy.put(".name", "y"); + bank_dz.put(".name", "z"); + + bank_drotx_val.put(".val", relRotAngles[0]); + bank_droty_val.put(".val", relRotAngles[1]); + bank_drotz_val.put(".val", relRotAngles[2]); + bank_drotx.put(".name", "rotx"); + bank_droty.put(".name", "roty"); + bank_drotz.put(".name", "rotz"); + + bank_sx_val.put(".val", scalex); + bank_sy_val.put(".val", scaley); + bank_sx.put(".name", "scalex"); + bank_sy.put(".name", "scaley"); + + bank_root.put(".name", bankName); + + // configure structure + bank_dx.add_child("value", bank_dx_val); + bank_dy.add_child("value", bank_dy_val); + bank_dz.add_child("value", bank_dz_val); + + bank_drotx.add_child("value", bank_drotx_val); + bank_droty.add_child("value", bank_droty_val); + bank_drotz.add_child("value", bank_drotz_val); + + bank_sx.add_child("value", bank_sx_val); + bank_sy.add_child("value", bank_sy_val); + + bank_root.add_child("parameter", bank_drotx); + + bank_root.add_child("parameter", bank_droty); + bank_root.add_child("parameter", bank_drotz); + bank_root.add_child("parameter", bank_dx); + bank_root.add_child("parameter", bank_dy); + bank_root.add_child("parameter", bank_dz); + bank_root.add_child("parameter", bank_sx); + bank_root.add_child("parameter", bank_sy); + + parafile.add_child("component-link", bank_root); + } + + // get L1 info for source + ptree src; + ptree src_dx, src_dy, src_dz; + ptree src_dx_val, src_dy_val, src_dz_val; + // -- get positional data from source + IComponent_const_sptr source = instrument->getSource(); + V3D sourceRelPos = source->getRelativePos(); + // -- add date to node + src_dx_val.put(".val", sourceRelPos.X()); + src_dy_val.put(".val", sourceRelPos.Y()); + src_dz_val.put(".val", sourceRelPos.Z()); + src_dx.put(".name", "x"); + src_dy.put(".name", "y"); + src_dz.put(".name", "z"); + src.put(".name", source->getName()); + + src_dx.add_child("value", src_dx_val); + src_dy.add_child("value", src_dy_val); + src_dz.add_child("value", src_dz_val); + src.add_child("parameter", src_dx); + src.add_child("parameter", src_dy); + src.add_child("parameter", src_dz); + + parafile.add_child("component-link", src); + + // give everything to root + root.add_child("parameter-file", parafile); + // write the xml tree to disk + g_log.notice() << "\tSaving parameter file as " << FileName << "\n"; + boost::property_tree::write_xml( + FileName, root, std::locale(), + boost::property_tree::xml_writer_settings(' ', 2)); +} + +/** + * Really this is the operator SaveIsawDetCal but only the results of the given + * banks are saved. L1 and T0 are also saved. + * + * @param filename -The name of the DetCal file to save the results to + * @param AllBankName -the set of the NewInstrument names of the banks(panels) + * @param instrument -The instrument with the correct panel geometries + * and initial path length + * @param T0 -The time offset from the DetCal file + */ +void SCDCalibratePanels2::saveIsawDetCal( + const std::string &filename, + boost::container::flat_set &AllBankName, + std::shared_ptr &instrument, double T0) { + g_log.notice() << "Saving DetCal file in " << filename << "\n"; + + // create a workspace to pass to SaveIsawDetCal + const size_t number_spectra = instrument->getNumberDetectors(); + Workspace2D_sptr wksp = std::dynamic_pointer_cast( + WorkspaceFactory::Instance().create("Workspace2D", number_spectra, 2, 1)); + wksp->setInstrument(instrument); + wksp->rebuildSpectraMapping(true /* include monitors */); + + // convert the bank names into a vector + std::vector banknames(AllBankName.begin(), AllBankName.end()); + + // call SaveIsawDetCal + API::IAlgorithm_sptr alg = createChildAlgorithm("SaveIsawDetCal"); + alg->setProperty("InputWorkspace", wksp); + alg->setProperty("Filename", filename); + alg->setProperty("TimeOffset", T0); + alg->setProperty("BankNames", banknames); + alg->executeAsChildAlg(); +} + +/** + * @brief Save the CORELLI calibration table into a CSV file + * + * @param FileName + * @param tws + */ +void SCDCalibratePanels2::saveCalibrationTable( + const std::string &FileName, DataObjects::TableWorkspace_sptr &tws) { + API::IAlgorithm_sptr alg = createChildAlgorithm("SaveAscii"); + alg->setProperty("InputWorkspace", tws); + alg->setProperty("Filename", FileName); + alg->setPropertyValue("CommentIndicator", "#"); + alg->setPropertyValue("Separator", "CSV"); + alg->setProperty("ColumnHeader", true); + alg->setProperty("AppendToFile", false); + alg->executeAsChildAlg(); +} + +} // namespace Crystal +} // namespace Mantid diff --git a/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2fee56c596d52b2f4180118db1c1a19b6359aa6c --- /dev/null +++ b/Framework/Crystal/src/SCDCalibratePanels2ObjFunc.cpp @@ -0,0 +1,231 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + + +#include "MantidCrystal/SCDCalibratePanels2ObjFunc.h" +#include "MantidAPI/Algorithm.h" +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/FunctionFactory.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/Sample.h" +#include "MantidDataObjects/PeaksWorkspace.h" +#include "MantidGeometry/Crystal/OrientedLattice.h" +#include "MantidGeometry/Instrument.h" + +#include +#include + +namespace Mantid { +namespace Crystal { + +using namespace Mantid::API; +using namespace Mantid::CurveFitting; +using namespace Mantid::DataObjects; +using namespace Mantid::Geometry; +using namespace Mantid::Kernel; + +namespace { +// static logger +Logger g_log("SCDCalibratePanels2ObjFunc"); +} // namespace + +DECLARE_FUNCTION(SCDCalibratePanels2ObjFunc) + +/// ---------------/// +/// Core functions /// +/// ---------------/// +SCDCalibratePanels2ObjFunc::SCDCalibratePanels2ObjFunc() { + // parameters + declareParameter("DeltaX", 0.0, "relative shift along X"); + declareParameter("DeltaY", 0.0, "relative shift along Y"); + declareParameter("DeltaZ", 0.0, "relative shift along Z"); + // rotation axis is defined as (1, theta, phi) + // https://en.wikipedia.org/wiki/Spherical_coordinate_system + declareParameter("Theta", PI / 4, "Polar coordinates theta in radians"); + declareParameter("Phi", PI / 4, "Polar coordinates phi in radians"); + // rotation angle + declareParameter("DeltaRotationAngle", 0.0, + "angle of relative rotation in degree"); + declareParameter("DeltaT0", 0.0, "delta of TOF"); + + // attributes + declareAttribute("Workspace", Attribute("")); + declareAttribute("ComponentName", Attribute("")); +} + +/** + * @brief Evalute the objective function with given feature vector X + * + * @param out :: Q_calculated, which means yValues should be set to + * Q_measured when setting up the Fit algorithm + * @param xValues :: feature vector [shiftx3, rotx3, T0] + * @param order :: dimensionality of feature vector + */ +void SCDCalibratePanels2ObjFunc::function1D(double *out, const double *xValues, + const size_t order) const { + // Get the feature vector component (numeric type) + //-- delta in translation + const double dx = getParameter("DeltaX"); + const double dy = getParameter("DeltaY"); + const double dz = getParameter("DeltaZ"); + //-- delta in rotation/orientation as angle axis pair + // using polar coordinates to ensure a unit vector + // (r, theta, phi) where r=1 + const double theta = getParameter("Theta"); + const double phi = getParameter("Phi"); + // compute the rotation axis + double vx = sin(theta) * cos(phi); + double vy = sin(theta) * sin(phi); + double vz = cos(theta); + // + const double drotang = getParameter("DeltaRotationAngle"); + + //-- delta in TOF + const double dT0 = getParameter("DeltaT0"); + //-- NOTE: given that these components are never used as + // one vector, there is no need to construct a + // xValues + UNUSED_ARG(xValues); + UNUSED_ARG(order); + + // Get workspace and component name (string type) + m_ws = AnalysisDataService::Instance().retrieveWS( + getAttribute("Workspace").asString()); + m_cmpt = getAttribute("ComponentName").asString(); + + // Special adjustment for CORELLI + PeaksWorkspace_sptr pws = std::dynamic_pointer_cast(m_ws); + Instrument_sptr inst = + std::const_pointer_cast(pws->getInstrument()); + if (inst->getName().compare("CORELLI") == 0 && m_cmpt != "moderator") + m_cmpt.append("/sixteenpack"); + + // NOTE: Since the feature vectors are all deltas with respect to the starting + // position, + // we need to only operate on a copy instead of the original to avoid + // changing the base value + std::shared_ptr calc_ws = m_ws->clone(); + + // NOTE: when optimizing T0, a none component will be passed in. + if (m_cmpt != "none/sixteenpack") { + // rotation + // NOTE: moderator should not be reoriented + rotateInstrumentComponentBy(vx, vy, vz, drotang, m_cmpt, calc_ws); + + // translation + moveInstruentComponentBy(dx, dy, dz, m_cmpt, calc_ws); + } + + // generate a flatten Q_sampleframe from calculated ws (by moving instrument + // component) so that a direct comparison can be performed between measured + // and calculated + PeaksWorkspace_sptr calc_pws = + std::dynamic_pointer_cast(calc_ws); + Instrument_sptr calc_inst = + std::const_pointer_cast(calc_pws->getInstrument()); + + // NOTE: We are not sure if the PeaksWorkspace level T0 + // if going go affect the peak.getTOF + Mantid::API::Run &run = calc_pws->mutableRun(); + double T0 = 0.0; + if (run.hasProperty("T0")) { + T0 = run.getPropertyValueAsType("T0"); + } + T0 += dT0; + run.addProperty("T0", T0, true); + + for (int i = 0; i < calc_pws->getNumberPeaks(); ++i) { + const Peak pk = calc_pws->getPeak(i); + + V3D hkl = + V3D(boost::math::iround(pk.getH()), boost::math::iround(pk.getK()), + boost::math::iround(pk.getL())); + if (hkl == UNSET_HKL) + throw std::runtime_error("Found unindexed peak in input workspace!"); + + // construct the out vector (Qvectors) + Units::Wavelength wl; + V3D calc_qv; + // somehow calibration results works better with direct method + // but moderator requires the strange in-and-out way + if (m_cmpt != "moderator") { + wl.initialize(pk.getL1(), pk.getL2(), pk.getScattering(), 0, + pk.getInitialEnergy(), 0.0); + // create a peak with shifted wavelength + Peak calc_pk(calc_inst, pk.getDetectorID(), + wl.singleFromTOF(pk.getTOF() + dT0), hkl, + pk.getGoniometerMatrix()); + calc_qv = calc_pk.getQSampleFrame(); + } else { + Peak calc_pk(calc_inst, pk.getDetectorID(), pk.getWavelength(), hkl, + pk.getGoniometerMatrix()); + wl.initialize(calc_pk.getL1(), calc_pk.getL2(), calc_pk.getScattering(), + 0, calc_pk.getInitialEnergy(), 0.0); + // adding the TOF shift here + calc_pk.setWavelength(wl.singleFromTOF(pk.getTOF() + dT0)); + calc_qv = calc_pk.getQSampleFrame(); + } + + // get the updated/calculated q vector in sample frame and set it to out + // V3D calc_qv = calc_pk.getQSampleFrame(); + for (int j = 0; j < 3; ++j) + out[i * 3 + j] = calc_qv[j]; + } +} + +// -------/// +// Helper /// +// -------/// + +/** + * @brief Translate the component of given workspace by delta_(x, y, z) + * + * @param deltaX :: The shift along the X-axis in m + * @param deltaY :: The shift along the Y-axis in m + * @param deltaZ :: The shift along the Z-axis in m + * @param componentName :: string representation of a component + * @param ws :: input workspace (mostly peaksworkspace) + */ +void SCDCalibratePanels2ObjFunc::moveInstruentComponentBy( + double deltaX, double deltaY, double deltaZ, std::string componentName, + const API::Workspace_sptr &ws) const { + // move instrument is really fast, even with zero input + IAlgorithm_sptr mv_alg = Mantid::API::AlgorithmFactory::Instance().create( + "MoveInstrumentComponent", -1); + mv_alg->initialize(); + mv_alg->setChild(true); + mv_alg->setLogging(LOGCHILDALG); + mv_alg->setProperty("Workspace", ws); + mv_alg->setProperty("ComponentName", componentName); + mv_alg->setProperty("X", deltaX); + mv_alg->setProperty("Y", deltaY); + mv_alg->setProperty("Z", deltaZ); + mv_alg->setProperty("RelativePosition", true); + mv_alg->executeAsChildAlg(); +} + +void SCDCalibratePanels2ObjFunc::rotateInstrumentComponentBy( + double rotVx, double rotVy, double rotVz, double rotAng, + std::string componentName, const API::Workspace_sptr &ws) const { + // rotate + IAlgorithm_sptr rot_alg = Mantid::API::AlgorithmFactory::Instance().create( + "RotateInstrumentComponent", -1); + // + rot_alg->initialize(); + rot_alg->setChild(true); + rot_alg->setLogging(LOGCHILDALG); + rot_alg->setProperty("Workspace", ws); + rot_alg->setProperty("ComponentName", componentName); + rot_alg->setProperty("X", rotVx); + rot_alg->setProperty("Y", rotVy); + rot_alg->setProperty("Z", rotVz); + rot_alg->setProperty("Angle", rotAng); + rot_alg->setProperty("RelativeRotation", true); + rot_alg->executeAsChildAlg(); +} + +} // namespace Crystal +} // namespace Mantid diff --git a/Framework/Crystal/src/SCDPanelErrors.cpp b/Framework/Crystal/src/SCDPanelErrors.cpp index 19c2570941e844bf955281a5db37094b4f36650d..6dbd04977f117af09ae71df9bd3d88461943c0e9 100644 --- a/Framework/Crystal/src/SCDPanelErrors.cpp +++ b/Framework/Crystal/src/SCDPanelErrors.cpp @@ -189,14 +189,14 @@ void SCDPanelErrors::eval(double xshift, double yshift, double zshift, scaley, m_bank, cloned); auto inputP = std::dynamic_pointer_cast(cloned); - IAlgorithm_sptr alg = - Mantid::API::AlgorithmFactory::Instance().create("IndexPeaks", -1); - alg->initialize(); - alg->setChild(true); - alg->setLogging(false); - alg->setProperty("PeaksWorkspace", inputP); - alg->setProperty("Tolerance", 0.15); - alg->execute(); + // IAlgorithm_sptr alg = + // Mantid::API::AlgorithmFactory::Instance().create("IndexPeaks", -1); + // alg->initialize(); + // alg->setChild(true); + // alg->setLogging(false); + // alg->setProperty("PeaksWorkspace", inputP); + // alg->setProperty("Tolerance", 0.15); + // alg->execute(); auto inst = inputP->getInstrument(); Geometry::OrientedLattice lattice = inputP->mutableSample().getOrientedLattice(); diff --git a/Framework/Crystal/src/SaveHKL.cpp b/Framework/Crystal/src/SaveHKL.cpp index cf7b118ac0fd08d12c07a3790304b0b41ddc61b0..179739523c40d13efa27bfccff96bd1556a82dc1 100644 --- a/Framework/Crystal/src/SaveHKL.cpp +++ b/Framework/Crystal/src/SaveHKL.cpp @@ -113,7 +113,7 @@ void SaveHKL::exec() { if (peaksW != m_ws) peaksW = m_ws->clone(); auto inst = peaksW->getInstrument(); - std::vector peaks = peaksW->getPeaks(); + std::vector &peaks = peaksW->getPeaks(); double scaleFactor = getProperty("ScalePeaks"); double dMin = getProperty("MinDSpacing"); double wlMin = getProperty("MinWavelength"); @@ -124,10 +124,10 @@ void SaveHKL::exec() { int widthBorder = getProperty("WidthBorder"); int decimalHKL = getProperty("HKLDecimalPlaces"); bool cosines = getProperty("DirectionCosines"); - Kernel::DblMatrix UB(3, 3); + OrientedLattice ol; if (cosines) { if (peaksW->sample().hasOrientedLattice()) { - UB = peaksW->sample().getOrientedLattice().getUB(); + ol = peaksW->sample().getOrientedLattice(); } else { // Find OrientedLattice std::string fileUB = getProperty("UBFilename"); @@ -140,6 +140,7 @@ void SaveHKL::exec() { double val; // Read the ISAW UB matrix + Kernel::DblMatrix UB; for (size_t row = 0; row < 3; row++) { for (size_t col = 0; col < 3; col++) { s = getWord(in, true); @@ -151,6 +152,7 @@ void SaveHKL::exec() { } readToEndOfLine(in, true); } + ol.setUB(UB); } } @@ -165,27 +167,7 @@ void SaveHKL::exec() { qSign = 1.0; std::fstream out; - bool append = getProperty("AppendFile"); - if (append && Poco::File(filename.c_str()).exists()) { - IAlgorithm_sptr load_alg = createChildAlgorithm("LoadHKL"); - load_alg->setPropertyValue("Filename", filename); - load_alg->setProperty("OutputWorkspace", "peaks"); - load_alg->executeAsChildAlg(); - // Get back the result - DataObjects::PeaksWorkspace_sptr ws2 = - load_alg->getProperty("OutputWorkspace"); - ws2->setInstrument(inst); - - IAlgorithm_sptr plus_alg = createChildAlgorithm("CombinePeaksWorkspaces"); - plus_alg->setProperty("LHSWorkspace", peaksW); - plus_alg->setProperty("RHSWorkspace", ws2); - plus_alg->executeAsChildAlg(); - // Get back the result - peaksW = plus_alg->getProperty("OutputWorkspace"); - out.open(filename.c_str(), std::ios::out); - } else { - out.open(filename.c_str(), std::ios::out); - } + out.open(filename.c_str(), std::ios::out); // We cannot assume the peaks have bank type detector modules, so we have a // string to check this @@ -236,18 +218,6 @@ void SaveHKL::exec() { m_amu = getProperty("LinearAbsorptionCoef"); // in 1/cm m_power_th = getProperty("PowerLambda"); // in cm - // Function to calculate total attenuation for a track - auto calculateAttenuation = [](const Track &path, double lambda) { - double factor(1.0); - for (const auto &segment : path) { - const double length = segment.distInsideObject; - const auto &segObj = *(segment.object); - const auto &segMat = segObj.material(); - factor *= segMat.attenuation(length, lambda); - } - return factor; - }; - API::Run &run = peaksW->mutableRun(); double radius = getProperty("Radius"); // in cm @@ -258,10 +228,6 @@ void SaveHKL::exec() { radius = run.getPropertyValueAsType("Radius"); } - const auto sourcePos = inst->getSource()->getPos(); - const auto samplePos = inst->getSample()->getPos(); - const auto reverseBeamDir = normalize(samplePos - sourcePos); - const Material &sampleMaterial = peaksW->sample().getMaterial(); const IObject *sampleShape{nullptr}; // special case of sphere else assume the sample shape and material have been @@ -282,6 +248,17 @@ void SaveHKL::exec() { peaksW->mutableSample().setShape(sphere); } else if (peaksW->sample().getShape().hasValidShape()) { sampleShape = &(peaksW->sample().getShape()); + // if AddAbsorptionWeightedPathLengths has already been run on the workspace + // then keep those tbar values + if (std::all_of(peaks.cbegin(), peaks.cend(), [](auto &p) { + return p.getAbsorptionWeightedPathLength() == 0; + })) { + IAlgorithm_sptr alg = + createChildAlgorithm("AddAbsorptionWeightedPathLengths"); + alg->setProperty("InputWorkspace", peaksW); + alg->setProperty("UseSinglePath", true); + alg->executeAsChildAlg(); + } } if (correctPeaks) { @@ -387,19 +364,10 @@ void SaveHKL::exec() { // keep original method if radius is provided transmission = absorbSphere(radius, scattering, lambda, tbar); } else if (sampleShape) { - Track beforeScatter(samplePos, reverseBeamDir); - sampleShape->interceptSurface(beforeScatter); - const auto detDir = normalize(p.getDetPos() - samplePos); - Track afterScatter(samplePos, detDir); - sampleShape->interceptSurface(afterScatter); - - transmission = calculateAttenuation(beforeScatter, lambda) * - calculateAttenuation(afterScatter, lambda); - const auto &mat = sampleShape->material(); - const auto mu = - (mat.totalScatterXSection() + mat.absorbXSection(lambda)) * - mat.numberDensity(); - tbar = -std::log(transmission) / mu; + tbar = p.getAbsorptionWeightedPathLength(); + transmission = + exp(-tbar * 0.01 * + sampleShape->material().attenuationCoefficient(lambda)); } // Anvred write from Art Schultz/ @@ -521,43 +489,21 @@ void SaveHKL::exec() { out << std::setw(8) << std::fixed << std::setprecision(5) << lambda; out << std::setw(8) << std::fixed << std::setprecision(5) << tbar; Kernel::DblMatrix oriented = p.getGoniometerMatrix(); - Kernel::DblMatrix orientedIPNS(3, 3); - V3D dir_cos_1, dir_cos_2; - - orientedIPNS[0][0] = oriented[0][0]; - orientedIPNS[0][1] = oriented[0][2]; - orientedIPNS[0][2] = oriented[0][1]; - orientedIPNS[1][0] = oriented[2][0]; - orientedIPNS[1][1] = oriented[2][2]; - orientedIPNS[1][2] = oriented[2][1]; - orientedIPNS[2][0] = oriented[1][0]; - orientedIPNS[2][1] = oriented[1][2]; - orientedIPNS[2][2] = oriented[1][1]; - Kernel::DblMatrix orientedUB = UB * orientedIPNS; - double l2 = p.getL2(); - V3D R_reverse_incident = V3D(-l2, 0., 0.); - V3D R_IPNS; - double twoth = p.getScattering(); + + auto U = ol.getU(); + auto RU = oriented * U; + RU.Transpose(); + + // This is the reverse incident beam + V3D reverse_incident = + inst->getSource()->getPos() - inst->getSample()->getPos(); + reverse_incident.normalize(); // This is the scattered beam direction V3D dir = p.getDetPos() - inst->getSample()->getPos(); + dir.normalize(); - // "Azimuthal" angle: project the scattered beam direction onto the - // XY plane, and calculate the angle between that and the +X axis - // (right-handed) - double az = atan2(dir.Y(), dir.X()); - R_IPNS[0] = std::cos(twoth) * l2; - R_IPNS[1] = std::cos(az) * std::sin(twoth) * l2; - R_IPNS[2] = std::sin(az) * std::sin(twoth) * l2; - - for (int k = 0; k < 3; ++k) { - V3D q_abc_star = - V3D(orientedUB[k][0], orientedUB[k][1], orientedUB[k][2]); - double length_q_abc_star = q_abc_star.norm(); - dir_cos_1[k] = R_reverse_incident.scalar_prod(q_abc_star) / - (l2 * length_q_abc_star); - dir_cos_2[k] = - R_IPNS.scalar_prod(q_abc_star) / (l2 * length_q_abc_star); - } + V3D dir_cos_1 = RU * reverse_incident; + V3D dir_cos_2 = RU * dir; for (int k = 0; k < 3; ++k) { out << std::setw(9) << std::fixed << std::setprecision(5) @@ -632,6 +578,26 @@ void SaveHKL::exec() { badPeaks.emplace_back(static_cast(*it)); } peaksW->removePeaks(std::move(badPeaks)); + + bool append = getProperty("AppendFile"); + if (append && Poco::File(filename.c_str()).exists()) { + IAlgorithm_sptr load_alg = createChildAlgorithm("LoadHKL"); + load_alg->setPropertyValue("Filename", filename); + load_alg->setProperty("OutputWorkspace", "peaks"); + load_alg->executeAsChildAlg(); + // Get back the result + DataObjects::PeaksWorkspace_sptr ws2 = + load_alg->getProperty("OutputWorkspace"); + ws2->setInstrument(inst); + + IAlgorithm_sptr plus_alg = createChildAlgorithm("CombinePeaksWorkspaces"); + plus_alg->setProperty("LHSWorkspace", peaksW); + plus_alg->setProperty("RHSWorkspace", ws2); + plus_alg->executeAsChildAlg(); + // Get back the result + peaksW = plus_alg->getProperty("OutputWorkspace"); + } + setProperty("OutputWorkspace", peaksW); } // namespace Crystal diff --git a/Framework/Crystal/src/SaveLauenorm.cpp b/Framework/Crystal/src/SaveLauenorm.cpp index ef1a6830270e3f0fbff40ff03ff482eb2e61961d..32943393a298c8b475f72c85cad4e34b885469e7 100644 --- a/Framework/Crystal/src/SaveLauenorm.cpp +++ b/Framework/Crystal/src/SaveLauenorm.cpp @@ -119,16 +119,16 @@ void SaveLauenorm::exec() { // We must sort the peaks std::vector> criteria; if (type.compare(0, 2, "Ba") == 0) - criteria.emplace_back(std::pair("BankName", true)); + criteria.emplace_back("BankName", true); else if (type.compare(0, 2, "Ru") == 0) - criteria.emplace_back(std::pair("RunNumber", true)); + criteria.emplace_back("RunNumber", true); else { - criteria.emplace_back(std::pair("RunNumber", true)); - criteria.emplace_back(std::pair("BankName", true)); + criteria.emplace_back("RunNumber", true); + criteria.emplace_back("BankName", true); } - criteria.emplace_back(std::pair("h", true)); - criteria.emplace_back(std::pair("k", true)); - criteria.emplace_back(std::pair("l", true)); + criteria.emplace_back("h", true); + criteria.emplace_back("k", true); + criteria.emplace_back("l", true); ws->sort(criteria); std::vector peaks = ws->getPeaks(); diff --git a/Framework/Crystal/src/SetGoniometer.cpp b/Framework/Crystal/src/SetGoniometer.cpp index fe39fdc8d05f453495750132bbc7f8b4dbbcde5e..39c669cb3c0da8e4cac9c5f55b1e0beb9eb87518 100644 --- a/Framework/Crystal/src/SetGoniometer.cpp +++ b/Framework/Crystal/src/SetGoniometer.cpp @@ -56,6 +56,9 @@ void SetGoniometer::init() { propName.str(), "", Direction::Input), propName.str() + axisHelp); } + declareProperty("Average", true, + "Use the average value of the log, if false a separate " + "goniometer will be created for each value in the logs"); } /** Execute the algorithm. @@ -158,7 +161,10 @@ void SetGoniometer::exec() { // All went well, copy the goniometer into it. It will throw if the log values // cannot be found try { - ei->mutableRun().setGoniometer(gon, true); + if (getProperty("Average")) + ei->mutableRun().setGoniometer(gon, true); + else + ei->mutableRun().setGoniometers(gon); } catch (std::runtime_error &) { g_log.error("No log values for goniometers"); } diff --git a/Framework/Crystal/src/SortPeaksWorkspace.cpp b/Framework/Crystal/src/SortPeaksWorkspace.cpp index 8596b33f05a38d0603653858d85dd60667481209..830f9c5ecdae860c6bdf2f834240a65babdeb7ba 100644 --- a/Framework/Crystal/src/SortPeaksWorkspace.cpp +++ b/Framework/Crystal/src/SortPeaksWorkspace.cpp @@ -70,7 +70,7 @@ void SortPeaksWorkspace::exec() { // Perform the sorting. std::vector sortCriteria; - sortCriteria.emplace_back(std::make_pair(columnToSortBy, sortAscending)); + sortCriteria.emplace_back(columnToSortBy, sortAscending); outputWS->sort(sortCriteria); setProperty("OutputWorkspace", outputWS); } diff --git a/Framework/Crystal/src/StatisticsOfPeaksWorkspace.cpp b/Framework/Crystal/src/StatisticsOfPeaksWorkspace.cpp index 6d3f4d38ce3bbd58e5f2bc3d37e815d466c85105..c0be22b3d5314920f97de0c8fe4fadc83daad681 100644 --- a/Framework/Crystal/src/StatisticsOfPeaksWorkspace.cpp +++ b/Framework/Crystal/src/StatisticsOfPeaksWorkspace.cpp @@ -118,13 +118,13 @@ void StatisticsOfPeaksWorkspace::exec() { // We must sort the peaks std::vector> criteria; if (sortType.compare(0, 2, "Re") == 0) - criteria.emplace_back(std::pair("DSpacing", false)); + criteria.emplace_back("DSpacing", false); else if (sortType.compare(0, 2, "Ru") == 0) - criteria.emplace_back(std::pair("RunNumber", true)); - criteria.emplace_back(std::pair("BankName", true)); - criteria.emplace_back(std::pair("h", true)); - criteria.emplace_back(std::pair("k", true)); - criteria.emplace_back(std::pair("l", true)); + criteria.emplace_back("RunNumber", true); + criteria.emplace_back("BankName", true); + criteria.emplace_back("h", true); + criteria.emplace_back("k", true); + criteria.emplace_back("l", true); ws->sort(criteria); // ========================================= diff --git a/Framework/Crystal/test/IndexPeaksTest.h b/Framework/Crystal/test/IndexPeaksTest.h index 99fecd6cdc20b4b11a6ea48f64c88087da095269..1d09abde678ffda969cd6c6b1affb578909b4d87 100644 --- a/Framework/Crystal/test/IndexPeaksTest.h +++ b/Framework/Crystal/test/IndexPeaksTest.h @@ -487,22 +487,76 @@ public: // --------------------------- Failure tests ----------------------------- - void test_workspace_with_no_oriented_lattice_gives_validateInputs_error() { - const auto peaksWS = WorkspaceCreationHelper::createPeaksWorkspace(1); - IndexPeaks alg; - alg.initialize(); - alg.setProperty("PeaksWorkspace", peaksWS); + std::shared_ptr setup_validate_inputs_test_alg() { + const auto peaksWS = createTestPeaksWorkspaceWithSatellites(); + auto alg = std::make_shared(); + alg->initialize(); + alg->setProperty("PeaksWorkspace", peaksWS); + return alg; + } - auto helpMsgs = alg.validateInputs(); + void + assert_helpmsgs_error_from_validate_inputs(std::shared_ptr alg, + std::string prop, + std::string err_substring) { + auto helpMsgs = alg->validateInputs(); - const auto valueIter = helpMsgs.find("PeaksWorkspace"); + const auto valueIter = helpMsgs.find(prop); TS_ASSERT(valueIter != helpMsgs.cend()) if (valueIter != helpMsgs.cend()) { const auto msg = valueIter->second; - TS_ASSERT(msg.find("OrientedLattice") != std::string::npos) + TS_ASSERT(msg.find(err_substring) != std::string::npos) } } + void + test_inputs_with_save_mod_and_zero_max_order_gives_validateInputs_error() { + auto alg = setup_validate_inputs_test_alg(); + alg->setProperty("SaveModulationInfo", true); + + assert_helpmsgs_error_from_validate_inputs(alg, "MaxOrder", + "Modulation info"); + } + + void + test_inputs_with_save_mod_and_empty_mod_vectors_gives_validateInputs_error() { + auto alg = setup_validate_inputs_test_alg(); + alg->setProperty("SaveModulationInfo", true); + + assert_helpmsgs_error_from_validate_inputs(alg, "SaveModulationInfo", + "no valid Modulation"); + } + + void + test_inputs_with_max_order_set_and_empty_mod_vectors_gives_validateInputs_error() { + auto alg = setup_validate_inputs_test_alg(); + alg->setProperty("MaxOrder", 3); // arbitary non-zero max order + + assert_helpmsgs_error_from_validate_inputs(alg, "ModVector1", + "At least one Modulation"); + } + + void + test_inputs_with_zero_max_order_and_valid_mod_vector_gives_validateInputs_error() { + auto alg = setup_validate_inputs_test_alg(); + alg->setProperty( + "ModVector1", + std::vector{1.0, 2.0, 3.0}); // arbitary non-zero mod vector + + assert_helpmsgs_error_from_validate_inputs(alg, "MaxOrder", + "cannot be zero"); + } + + void test_workspace_with_no_oriented_lattice_gives_validateInputs_error() { + const auto peaksWS = WorkspaceCreationHelper::createPeaksWorkspace(1); + auto alg = std::make_shared(); + alg->initialize(); + alg->setProperty("PeaksWorkspace", peaksWS); + + assert_helpmsgs_error_from_validate_inputs(alg, "PeaksWorkspace", + "No UB Matrix"); + } + void test_negative_max_order_throws_error() { IndexPeaks alg; alg.initialize(); diff --git a/Framework/Crystal/test/SCDCalibratePanels2Test.h b/Framework/Crystal/test/SCDCalibratePanels2Test.h new file mode 100644 index 0000000000000000000000000000000000000000..8c29f7fbf9a386b761292a20c59e3c5cf25acd54 --- /dev/null +++ b/Framework/Crystal/test/SCDCalibratePanels2Test.h @@ -0,0 +1,876 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + + +// NOTE: Generating a synthetic peakworkspace for testing is very time +// consuming, therefore only one wholesome unittest with calibration +// is enabled for regression test. +// To test other testing targets, change the lead word from +// run_ to test_ +// to run them within the ctest framework. +// You might need to do one at a time to avoid ctest timeout error +// locally. + +// DEVNOTE: +// - cos, sin func uses radians +// - Quat class uses degrees + +#pragma once + +#include "MantidAPI/AlgorithmManager.h" +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/ExperimentInfo.h" +#include "MantidAPI/Run.h" +#include "MantidAPI/Sample.h" +#include "MantidCrystal/SCDCalibratePanels2.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidGeometry/Crystal/CrystalStructure.h" +#include "MantidKernel/Logger.h" +#include "MantidKernel/Unit.h" + +#include +#include +#include +#include +#include + +using namespace Mantid::API; +using namespace Mantid::Crystal; +using namespace Mantid::DataObjects; +using namespace Mantid::Geometry; +using namespace Mantid::Kernel; + +namespace { +/// static logger +Logger g_log("SCDCalibratePanels2Test"); +} // namespace + +class SCDCalibratePanels2Test : public CxxTest::TestSuite { +public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static SCDCalibratePanels2Test *createSuite() { + return new SCDCalibratePanels2Test(); + } + static void destroySuite(SCDCalibratePanels2Test *suite) { delete suite; } + + // ----------------- // + // ----- Setup ----- // + // ----------------- // + /** + * @brief Construct a new SCDCalibratePanels2Test object + * + */ + SCDCalibratePanels2Test() + : wsname("wsSCDCalibratePanels2Test"), + pwsname("pwsSCDCalibratePanels2Test"), + tmppwsname("tmppwsSCDCalibratePanels2Test"), // fixed workspace name + bank_xtop("bank73/sixteenpack"), // + bank_xcenter("bank12/sixteenpack"), // + bank_xbottom("bank11/sixteenpack"), // + bank_yright("bank59/sixteenpack"), // + bank_yleft("bank58/sixteenpack"), // + bank_ytop("bank88/sixteenpack"), // + bank_ybottom("bank26/sixteenpack"), // + silicon_a(5.431), silicon_b(5.431), silicon_c(5.431), // angstrom + silicon_alpha(90), silicon_beta(90), silicon_gamma(90), // degree + silicon_cs(CrystalStructure("5.431 5.431 5.431", "F d -3 m", + "Si 0 0 0 1.0 0.02")), + dspacing_min(1.0), dspacing_max(10.0), // + wavelength_min(0.1), wavelength_max(10), // + omega_step(3.0), // + TOLERANCE_L(5e-4), // this calibration has intrinsic accuracy limit of + // 1mm for translation + TOLERANCE_R(1e-5), // this calibration has intrinsic accuracy limit of + // 0.1 deg for rotation + LOGCHILDALG(false) { + // NOTE: + // The MAGIC PIECE, basically we need to let AlgorithmFactory + // to load a non-related algorithm, then somehow AlgorithmFactory + // can find the Fit algorithm for the remaining test + std::shared_ptr darkmagic = + AlgorithmFactory::Instance().create("LoadIsawPeaks", 1); + darkmagic->initialize(); + darkmagic->setLogging(false); // don't really care its output + darkmagic->setPropertyValue("Filename", "Peaks5637.integrate"); + darkmagic->setPropertyValue("OutputWorkspace", "TOPAZ_5637"); + TS_ASSERT(darkmagic->execute()); + + m_ws = generateSimulatedWorkspace(); + } + + // ---------------------- // + // ----- Unit Tests ----- // + // ---------------------- // + + /** + * @brief test name + * + */ + void test_Name() { + SCDCalibratePanels2 alg; + TS_ASSERT_EQUALS(alg.name(), "SCDCalibratePanels"); + } + + /** + * @brief test init + * + */ + void test_Init() { + SCDCalibratePanels2 alg; + alg.initialize(); + TS_ASSERT(alg.isInitialized()); + } + + /** + * @brief Trivial case where all components are in ideal/starting position + * Therefore the calibration results should be close to a zero + * vector. + * + * NOTE: Change the name from run_Null_Case to test_Null_Case to run it + * within the ctest framework. + */ + void run_Null_Case() { + g_log.notice() << "test: !Null case!\n"; + + // Generate unique temp files + auto filenamebase = boost::filesystem::temp_directory_path(); + filenamebase /= boost::filesystem::unique_path("nullcase_%%%%%%%%"); + + g_log.notice() << "-- generate simulated workspace\n"; + MatrixWorkspace_sptr ws = m_ws->clone(); + MatrixWorkspace_sptr wsraw = ws->clone(); + + // Trivial case, no component undergoes any affine transformation + g_log.notice() << "-- trivial case, no components moved\n"; + + g_log.notice() << "-- generate peaks\n"; + PeaksWorkspace_sptr pws = generateSimulatedPeaksWorkspace(ws); + PeaksWorkspace_sptr pwsref = pws->clone(); + + // Pretend we don't know the answer + g_log.notice() << "-- reset instrument positions&orientations\n"; + pws->setInstrument(wsraw->getInstrument()); + + // Perform the calibration + g_log.notice() << "-- start calibration\n"; + runCalibration(filenamebase.string(), pws, false, true, true); + + // Check if the calibration returns the same instrument as we put in + g_log.notice() << "-- validate calibration output\n"; + TS_ASSERT(validateCalibrationResults(pwsref, wsraw, filenamebase.string())); + + // Cleanup + doCleanup(); + } + + /** + * @brief Only adjust T0 + * + * NOTE: calibration of T0 shift always returns 0, therefore this + * test should not be turned on until the issue is resolved. + */ + void run_T0_Shift() { + g_log.notice() << "test: !T0 Shift!\n"; + + // prescribed shift + const double dT0 = 11; // micro seconds + + // Generate unique temp files + auto filenamebase = boost::filesystem::temp_directory_path(); + filenamebase /= boost::filesystem::unique_path("changeT0_%%%%%%%%"); + + g_log.notice() << "-- generate simulated workspace\n"; + MatrixWorkspace_sptr ws = m_ws->clone(); + MatrixWorkspace_sptr wsraw = ws->clone(); + + // Trivial case, no component undergoes any affine transformation + g_log.notice() << "-- generate peaks\n"; + PeaksWorkspace_sptr pws = generateSimulatedPeaksWorkspace(ws); + // -- add T0 property + pws->mutableRun().addProperty("T0", 0.0, true); + // -- make the twin + PeaksWorkspace_sptr pwsref = pws->clone(); + + // Adjust T0 + adjustT0(dT0, pws); + + // reset Workspace level of T0 + g_log.notice() << "--Reset workspace T0 \n" + << pws->mutableRun().getPropertyValueAsType("T0") + << "->" + << pwsref->mutableRun().getPropertyValueAsType("T0") + << "\n"; + pws->mutableRun().addProperty( + "T0", pwsref->mutableRun().getPropertyValueAsType("T0"), true); + + // NOTE: Toggle this back to see the per peak based impact due to dT0 + // for (int i = 0; i < pws->getNumberPeaks(); ++i) { + // Peak pk_before = pwsref->getPeak(i); + // Peak pk_after = pws->getPeak(i); + // g_log.notice() << "--Peak_" << i << " with dT0=" << dT0 << "\n" + // << " L1:" << pk_before.getL1() << "->" << + // pk_after.getL1() + // << "\n" + // << " L2:" << pk_before.getL2() << "->" << + // pk_after.getL2() + // << "\n" + // << " 2theta:" << pk_before.getScattering() << "->" + // << pk_after.getScattering() << "\n" + // << " initialEnergy:" << pk_before.getInitialEnergy() + // << "->" << pk_after.getInitialEnergy() << "\n" + // << " TOF:" << pk_before.getTOF() << "->" + // << pk_after.getTOF() << "\n" + // << " wavelength:" << pk_before.getWavelength() << "->" + // << pk_after.getWavelength() << "\n" + // << " hkl:" << pk_before.getH() << pk_before.getK() + // << pk_before.getL() << "->" << pk_after.getH() + // << pk_after.getK() << pk_after.getL() << "\n" + // << " q:" << pk_before.getQSampleFrame() << "->" + // << pk_after.getQSampleFrame() << "\n"; + // } + + // T0 got absorbed into peak wavelength, therefore there is no need to reset + // the instrument + // Perform the calibration + g_log.notice() << "-- start calibration\n"; + runCalibration(filenamebase.string(), pws, true, false, false); + } + + /** + * @brief Single variant case where only global var is adjusted. + * + * NOTE: technically we should also check T0, but the client, CORELLI + * team does not seem to care about using T0, therefore we are + * not implmenting T0 calibration here. + * + * NOTE: change the name from run_L1_Shift to test_L1_Shift to run + * it within the ctest framework + */ + void run_L1_Shift() { + g_log.notice() << "test: !Source Shift (L1 change)!\n"; + + g_log.notice() << "Tolerance of Distance (meter) :" << TOLERANCE_L << "\n"; + + // prescribed shift + // NOTE: the common range for dL1 is +-10cm + const double dL1 = boost::math::constants::e() / 100; + + // Generate unique temp files + // Generate unique temp files + auto filenamebase = boost::filesystem::temp_directory_path(); + filenamebase /= boost::filesystem::unique_path("changeL1_%%%%%%%%"); + + g_log.notice() << "-- generate simulated workspace\n"; + MatrixWorkspace_sptr ws = m_ws->clone(); + MatrixWorkspace_sptr wsraw = ws->clone(); + + // move source + adjustComponent(0.0, 0.0, dL1, 1.0, 0.0, 0.0, 0.0, + ws->getInstrument()->getSource()->getName(), ws); + + g_log.notice() << "-- generate peaks\n"; + PeaksWorkspace_sptr pws = generateSimulatedPeaksWorkspace(ws); + PeaksWorkspace_sptr pwsref = pws->clone(); + + // Pretend we don't know the answer + g_log.notice() << "-- reset instrument positions&orientations\n"; + g_log.notice() << " * before reset L1 = " + << pws->getInstrument()->getSource()->getPos().Z() << "\n"; + pws->setInstrument(wsraw->getInstrument()); + g_log.notice() << " * after reset L1 = " + << pws->getInstrument()->getSource()->getPos().Z() << "\n"; + + // Perform the calibration + g_log.notice() << "-- start calibration\n"; + runCalibration(filenamebase.string(), pws, false, true, false); + + // Check if the calibration returns the same instrument as we put in + g_log.notice() << "-- validate calibration output\n"; + TS_ASSERT(validateCalibrationResults(pwsref, wsraw, filenamebase.string())); + + // this is just for documentation purpose, the validation func above + // is better for robust testing + double L1_prescribed = ws->getInstrument()->getSource()->getPos().Z(); + double L1_calibrated = pws->getInstrument()->getSource()->getPos().Z(); + double dl = std::abs(L1_prescribed - L1_calibrated); + g_log.notice() << "-- |L1_prescribed-L1_calibrated| = " << dl << "\n"; + + // Cleanup + doCleanup(); + } + + /** + * @brief Moving (rotation and translation) single panel + * + */ + void run_bank_moved() { + g_log.notice() << "test: !single bank moved!\n"; + + g_log.notice() << "Tolerance of Distance (meter) :" << TOLERANCE_L << "\n"; + g_log.notice() << "Tolerance of Rotation (degree) :" << TOLERANCE_R << "\n"; + + // prescribed shift + // NOTE: the common range for dx, dy ,dz is +-5cm + double dx = 1.1e-2; + double dy = -0.9e-2; + double dz = 1.5e-2; + + // prescribed rotation + double theta = PI / 3; + double phi = PI / 8; + double rvx = sin(theta) * cos(phi); + double rvy = sin(theta) * sin(phi); + double rvz = cos(theta); + double ang = 1.414; // degrees + + // Generate unique temp files + auto filenamebase = boost::filesystem::temp_directory_path(); + filenamebase /= boost::filesystem::unique_path("panelMove_%%%%%%%%"); + + g_log.notice() << "-- generate simulated workspace\n"; + MatrixWorkspace_sptr ws = m_ws->clone(); + MatrixWorkspace_sptr wsraw = ws->clone(); + + g_log.notice() << "-- for x(top) - bank73\n" + << " translated by (" << dx << "," << dy << "," << dz + << ")\n" + << " rotated by " << ang << "@(" << rvx << "," << rvy + << "," << rvz << ")\n"; + adjustComponent(dx, dy, dz, rvx, rvy, rvz, ang, bank_xtop, ws); + + g_log.notice() << "-- generate peaks\n"; + PeaksWorkspace_sptr pws = generateSimulatedPeaksWorkspace(ws); + PeaksWorkspace_sptr pwsref = pws->clone(); + + // Pretend we don't know the answer + g_log.notice() << "-- reset instrument positions&orientations\n"; + g_log.notice() + << " * before reset x(top) - bank73:\n" + << " pos(abs) = " + << pws->getInstrument()->getComponentByName(bank_xtop)->getPos() << "\n" + << " quat(rel) = " + << pws->getInstrument()->getComponentByName(bank_xtop)->getRelativeRot() + << "\n"; + + pws->setInstrument(wsraw->getInstrument()); + g_log.notice() + << " * after reset x(top) - bank73:\n" + << " pos(abs) = " + << pws->getInstrument()->getComponentByName(bank_xtop)->getPos() << "\n" + << " quat(rel) = " + << pws->getInstrument()->getComponentByName(bank_xtop)->getRelativeRot() + << "\n"; + + // Perform the calibration + g_log.notice() << "-- start calibration\n"; + runCalibration(filenamebase.string(), pws, false, false, true); + + // Check if the calibration returns the same instrument as we put in + g_log.notice() << "-- validate calibration output\n"; + TS_ASSERT(validateCalibrationResults(pwsref, wsraw, filenamebase.string())); + + // Cleanup + doCleanup(); + } + + /** + * @brief Everything goes + * + * NOTE: not enough peaks on the y_panels, so we have to work with only the + * x_panels + */ + void test_Exec() { + g_log.notice() << "test: !calibrate L1 and two panels at the same time!\n"; + + g_log.notice() << "Tolerance of Distance (meter) :" << TOLERANCE_L << "\n"; + g_log.notice() << "Tolerance of Rotation (degree) :" << TOLERANCE_R << "\n"; + + // ----------------------------- // + // ----- Precribe movement ----- // + // ----------------------------- // + //-- source + const double dL1 = boost::math::constants::e() / 100; + //-- xtop + double dx1 = 1.1e-2; + double dy1 = -0.9e-2; + double dz1 = 1.5e-2; + double theta1 = PI / 3; + double phi1 = PI / 8; + double rvx1 = sin(theta1) * cos(phi1); + double rvy1 = sin(theta1) * sin(phi1); + double rvz1 = cos(theta1); + double ang1 = 1.414; // degrees + //-- xbottom + double dx2 = 0.5e-2; + double dy2 = 1.3e-2; + double dz2 = -1.9e-2; + double theta2 = PI / 4; + double phi2 = PI / 3; + double rvx2 = sin(theta2) * cos(phi2); + double rvy2 = sin(theta2) * sin(phi2); + double rvz2 = cos(theta2); + double ang2 = 2.13; // degrees + + // ----------------------------------- // + // ----- Generate Synthetic Data ----- // + // ----------------------------------- // + // Generate unique temp files + auto filenamebase = boost::filesystem::temp_directory_path(); + filenamebase /= boost::filesystem::unique_path("testExec_%%%%%%%%"); + g_log.notice() << "Base name is: " << filenamebase << "\n"; + + g_log.notice() << "-- generate simulated workspace\n"; + MatrixWorkspace_sptr ws = m_ws->clone(); + MatrixWorkspace_sptr wsraw = ws->clone(); + + // Source + adjustComponent(0.0, 0.0, dL1, 1.0, 0.0, 0.0, 0.0, + ws->getInstrument()->getSource()->getName(), ws); + g_log.notice() << "--Shift source by dL1 = " << dL1 << "\n"; + + // Bank73 + adjustComponent(dx1, dy1, dz1, rvx1, rvy1, rvz1, ang1, bank_xtop, ws); + g_log.notice() << "-- for x(top) - bank73\n" + << " translated by (" << dx1 << "," << dy1 << "," << dz1 + << ")\n" + << " rotated by " << ang1 << "@(" << rvx1 << "," << rvy1 + << "," << rvz1 << ")\n"; + + // Bank11 + adjustComponent(dx2, dy2, dz2, rvx2, rvy2, rvz2, ang2, bank_xbottom, ws); + g_log.notice() << "-- for x(bottom) - bank11\n" + << " translated by (" << dx2 << "," << dy2 << "," << dz2 + << ")\n" + << " rotated by " << ang2 << "@(" << rvx2 << "," << rvy2 + << "," << rvz2 << ")\n"; + + g_log.notice() << "-- generate peaks\n"; + PeaksWorkspace_sptr pws = generateSimulatedPeaksWorkspace(ws); + PeaksWorkspace_sptr pwsref = pws->clone(); + + g_log.notice() << "-- reset instrument positions&orientations\n"; + pws->setInstrument(wsraw->getInstrument()); + + // Perform the calibration + g_log.notice() << "-- start calibration\n"; + runCalibration(filenamebase.string(), pws, false, true, true); + + // Check if the calibration returns the same instrument as we put in + g_log.notice() << "-- validate calibration output\n"; + TS_ASSERT(validateCalibrationResults(pwsref, wsraw, filenamebase.string())); + + // Cleanup + doCleanup(); + } + +private: + // ---------------------------- // + // ----- Helper Functions ----- // + // -----------------------------// + + /** + * @brief generate a simulated workspace for each testing case + * + * @return MatrixWorkspace_sptr + */ + MatrixWorkspace_sptr generateSimulatedWorkspace() { + + // create simulated workspace + IAlgorithm_sptr csws_alg = + AlgorithmFactory::Instance().create("CreateSimulationWorkspace", 1); + csws_alg->initialize(); + csws_alg->setLogging(LOGCHILDALG); + csws_alg->setProperty("Instrument", "CORELLI"); + csws_alg->setProperty("BinParams", "1,100,10000"); + csws_alg->setProperty("UnitX", "TOF"); + csws_alg->setProperty("OutputWorkspace", wsname); + csws_alg->execute(); + TS_ASSERT(csws_alg->isExecuted()); + + // set UB + IAlgorithm_sptr sub_alg = AlgorithmFactory::Instance().create("SetUB", 1); + sub_alg->initialize(); + sub_alg->setLogging(LOGCHILDALG); + sub_alg->setProperty("Workspace", wsname); + sub_alg->setProperty("u", "1,0,0"); + sub_alg->setProperty("v", "0,1,0"); + sub_alg->setProperty("a", silicon_a); + sub_alg->setProperty("b", silicon_b); + sub_alg->setProperty("c", silicon_c); + sub_alg->setProperty("alpha", silicon_alpha); + sub_alg->setProperty("beta", silicon_beta); + sub_alg->setProperty("gamma", silicon_gamma); + sub_alg->execute(); + TS_ASSERT(sub_alg->isExecuted()); + + MatrixWorkspace_sptr ws = + AnalysisDataService::Instance().retrieveWS(wsname); + + auto &sample = ws->mutableSample(); + sample.setCrystalStructure(silicon_cs); + + return ws; + } + + /** + * @brief populate peaks for the post adjustment simulated workspace + * + * @return PeaksWorkspace_sptr + */ + PeaksWorkspace_sptr generateSimulatedPeaksWorkspace(MatrixWorkspace_sptr ws) { + // prepare the algs pointer + IAlgorithm_sptr sg_alg = + AlgorithmFactory::Instance().create("SetGoniometer", 1); + IAlgorithm_sptr pp_alg = + AlgorithmFactory::Instance().create("PredictPeaks", 1); + IAlgorithm_sptr cpw_alg = + AlgorithmFactory::Instance().create("CombinePeaksWorkspaces", 1); + + // generate peaks for a range of omega values + for (double omega = 0; omega <= 180; omega = omega + omega_step) { + std::ostringstream os; + os << omega << ",0,1,0,1"; + + // set the SetGoniometer + sg_alg->initialize(); + sg_alg->setLogging(LOGCHILDALG); + sg_alg->setProperty("Workspace", ws); + sg_alg->setProperty("Axis0", os.str()); + sg_alg->execute(); + + // predict peak positions + pp_alg->initialize(); + pp_alg->setLogging(LOGCHILDALG); + pp_alg->setProperty("InputWorkspace", ws); + pp_alg->setProperty("WavelengthMin", wavelength_min); + pp_alg->setProperty("wavelengthMax", wavelength_max); + pp_alg->setProperty("MinDSpacing", dspacing_min); + pp_alg->setProperty("MaxDSpacing", dspacing_max); + pp_alg->setProperty("ReflectionCondition", "All-face centred"); + + if (omega < omega_step) { + pp_alg->setProperty("OutputWorkspace", pwsname); + pp_alg->execute(); + } else { + pp_alg->setProperty("OutputWorkspace", tmppwsname); + pp_alg->execute(); + + // add the peaks to output peaks workspace + cpw_alg->initialize(); + cpw_alg->setLogging(LOGCHILDALG); + cpw_alg->setProperty("LHSWorkspace", tmppwsname); + cpw_alg->setProperty("RHSWorkspace", pwsname); + cpw_alg->setProperty("OutputWorkspace", pwsname); + cpw_alg->execute(); + } + } + + return AnalysisDataService::Instance().retrieveWS(pwsname); + } + + /** + * @brief Adjust the position of a component through translation and rotation + * + * @param dx + * @param dy + * @param dz + * @param rvx x-component of rotation axis + * @param rvy y-component of rotation axis + * @param rvz z-component of rotation axis + * @param drotang rotation angle + * @param cmptName + * @param ws + */ + void adjustComponent(double dx, double dy, double dz, double rvx, double rvy, + double rvz, double drotang, std::string cmptName, + MatrixWorkspace_sptr ws) { + + // rotation + IAlgorithm_sptr rot_alg = Mantid::API::AlgorithmFactory::Instance().create( + "RotateInstrumentComponent", -1); + rot_alg->initialize(); + rot_alg->setLogging(LOGCHILDALG); + rot_alg->setProperty("Workspace", ws); + rot_alg->setProperty("ComponentName", cmptName); + rot_alg->setProperty("X", rvx); + rot_alg->setProperty("Y", rvy); + rot_alg->setProperty("Z", rvz); + rot_alg->setProperty("Angle", drotang); + rot_alg->setProperty("RelativeRotation", true); + rot_alg->executeAsChildAlg(); + + // translation + IAlgorithm_sptr mv_alg = Mantid::API::AlgorithmFactory::Instance().create( + "MoveInstrumentComponent", -1); + mv_alg->initialize(); + mv_alg->setLogging(LOGCHILDALG); + mv_alg->setProperty("Workspace", ws); + mv_alg->setProperty("ComponentName", cmptName); + mv_alg->setProperty("X", dx); + mv_alg->setProperty("Y", dy); + mv_alg->setProperty("Z", dz); + mv_alg->setProperty("RelativePosition", true); + mv_alg->executeAsChildAlg(); + } + + /** + * @brief shift T0 for both peakworkspace and all peaks + * + * @param dT0 + * @param pws + */ + void adjustT0(double dT0, PeaksWorkspace_sptr pws) { + // update the T0 record in peakworkspace + pws->mutableRun().addProperty( + "T0", pws->mutableRun().getPropertyValueAsType("T0") + dT0, + true); + + // update wavelength of each peak using new T0 + for (int i = 0; i < pws->getNumberPeaks(); ++i) { + Peak &pk = pws->getPeak(i); + V3D hkl = + V3D(boost::math::iround(pk.getH()), boost::math::iround(pk.getK()), + boost::math::iround(pk.getL())); + + // make a standalone peak to calculate q vectors + Peak tmppk(pws->getInstrument(), pk.getDetectorID(), pk.getWavelength(), + hkl, pk.getGoniometerMatrix()); + Units::Wavelength wl; + wl.initialize(tmppk.getL1(), tmppk.getL2(), tmppk.getScattering(), 0, + tmppk.getInitialEnergy(), 0.0); + tmppk.setWavelength(wl.singleFromTOF(pk.getTOF() + dT0)); + + // only passing the q vector, not the energy info that relates T0 + pk.setQSampleFrame(tmppk.getQSampleFrame()); + } + } + + /** + * @brief Run the calibration algorithm + * + * @param filenameBase + * @param pws + * @param calibrateT0 + * @param calibrateL1 + * @param calibrateBanks + */ + void runCalibration(const std::string filenameBase, PeaksWorkspace_sptr pws, + bool calibrateT0, bool calibrateL1, bool calibrateBanks) { + // generate isaw, xml, and csv filename + const std::string isawFilename = filenameBase + ".DetCal"; + const std::string xmlFilename = filenameBase + ".xml"; + const std::string csvFilename = filenameBase + ".csv"; + + // execute the calibration + SCDCalibratePanels2 alg; + alg.initialize(); + alg.setProperty("PeakWorkspace", pws); + alg.setProperty("a", silicon_a); + alg.setProperty("b", silicon_b); + alg.setProperty("c", silicon_c); + alg.setProperty("alpha", silicon_alpha); + alg.setProperty("beta", silicon_beta); + alg.setProperty("gamma", silicon_gamma); + alg.setProperty("CalibrateT0", calibrateT0); + alg.setProperty("CalibrateL1", calibrateL1); + alg.setProperty("CalibrateBanks", calibrateBanks); + alg.setProperty("OutputWorkspace", "caliTableTest"); + alg.setProperty("DetCalFilename", isawFilename); + alg.setProperty("XmlFilename", xmlFilename); + alg.setProperty("CSVFilename", csvFilename); + alg.execute(); + TS_ASSERT(alg.isExecuted()); + } + + /** + * @brief validate the calibration results by comparing component + * positions from reference Peakworkspace and workspace adjusted + * using calibration output (xml) + * + * @param refpws + * @param refws + * @param xmlFileName + * @return true + * @return false + */ + bool validateCalibrationResults(PeaksWorkspace_sptr refpws, + MatrixWorkspace_sptr refws, + const std::string &fileName) { + // Test using xml parameter file (default) + const std::string xmlFileName = fileName + ".xml"; + + g_log.notice() << "Using Paramter file: " << xmlFileName << "\n"; + // Adjust components in reference workspace using calibration results + IAlgorithm_sptr lpf_alg = + AlgorithmFactory::Instance().create("LoadParameterFile", 1); + lpf_alg->initialize(); + lpf_alg->setLogging(LOGCHILDALG); + lpf_alg->setProperty("Workspace", refws); + lpf_alg->setProperty("Filename", xmlFileName); + lpf_alg->execute(); + + // Test using DetCal paramter file (debug) + // const std::string isawFileName = fileName + ".DetCal"; + // IAlgorithm_sptr lpf_alg = + // AlgorithmFactory::Instance().create("LoadIsawDetCal", 1); + // lpf_alg->initialize(); + // lpf_alg->setLogging(LOGCHILDALG); + // lpf_alg->setProperty("InputWorkspace", refws); + // lpf_alg->setProperty("Filename", fileName); + // lpf_alg->execute(); + + // compare each bank + // -- get the names + boost::container::flat_set BankNames; + for (int i = 0; i < refpws->getNumberPeaks(); ++i) { + std::string bname = refpws->getPeak(i).getBankName(); + if (bname != "None") { + BankNames.insert(bname); + } + } + // -- perform per bank comparison + Instrument_sptr inst1 = std::const_pointer_cast( + refws->getInstrument()); // based on calibration + Instrument_sptr inst2 = std::const_pointer_cast( + refpws->getInstrument()); // reference one + + bool sameInstrument = true; + for (auto bankname : BankNames) { + // update bankname for CORELLI + if (refpws->getInstrument()->getName().compare("CORELLI") == 0) + bankname.append("/sixteenpack"); + + if (!compareComponent(inst1, inst2, bankname)) { + g_log.error() << "--" << bankname << " mismatch\n"; + sameInstrument = false; + } + } + + // all banks are the same, now the source check will make the call + if (!compareComponent(inst1, inst2, inst1->getSource()->getName())) { + g_log.error() << "-- " << inst1->getSource()->getName() << " mismatch\n"; + sameInstrument = false; + } + + return sameInstrument; + } + + /** + * @brief compare if two components to see if they have similar + * translation and rotation + * + * @param instr1 + * @param instr2 + * @param componentName + * @return true + * @return false + */ + bool compareComponent(std::shared_ptr &instr1, + std::shared_ptr &instr2, + std::string componentName) { + + std::shared_ptr cmpt1 = + instr1->getComponentByName(componentName); + std::shared_ptr cmpt2 = + instr2->getComponentByName(componentName); + + V3D p1 = cmpt1->getRelativePos(); + V3D p2 = cmpt2->getRelativePos(); + + Quat q1 = cmpt1->getRelativeRot(); + Quat q2 = cmpt2->getRelativeRot(); + + q2.inverse(); + Quat dq = q1 * q2; + double dang = (2 * acos(dq.real()) / PI * 180); + dang = dang > 180 ? 360 - dang : dang; + + double dx = std::abs(p1.X() - p2.X()); + double dy = std::abs(p1.Y() - p2.Y()); + double dz = std::abs(p1.Z() - p2.Z()); + + bool sameComponent; + if (dx > TOLERANCE_L || dy > TOLERANCE_L || dz > TOLERANCE_L || + dang > TOLERANCE_R) { + sameComponent = false; + } else { + sameComponent = true; + } + + if (!sameComponent) + g_log.notice() << std::setprecision(8) << "--Component " << componentName + << "\n" + << " cali: " << p1 << "\n" + << " ref: " << p2 << "\n" + << " dx = " << dx << "\n" + << " dy = " << dy << "\n" + << " dz = " << dz << "\n" + << " cali: " << cmpt1->getRelativeRot() << "\n" + << " ref: " << cmpt2->getRelativeRot() << "\n" + << " misorientation = " << dang << "\n"; + + return sameComponent; + } + + /** + * @brief remove all workspace memory after one test is done + * + */ + void doCleanup() { + AnalysisDataService::Instance().remove(pwsname); + AnalysisDataService::Instance().remove(tmppwsname); + } + + // ------------------- // + // ----- members ----- // + // ------------------- // + // workspace names + const std::string wsname; + const std::string pwsname; + const std::string tmppwsname; + + MatrixWorkspace_sptr m_ws; + + // bank&panel names selected for testing + // batch_1: high order zone selection + const std::string bank_xtop; + const std::string bank_xcenter; + const std::string bank_xbottom; + + // batch_2: low order zone selection + // NOTE: limited reflections from experiment, often + // considered as a chanllegening case + const std::string bank_yright; + const std::string bank_yleft; + const std::string bank_ytop; + const std::string bank_ybottom; + + // lattice constants of silicon + const double silicon_a; + const double silicon_b; + const double silicon_c; + const double silicon_alpha; + const double silicon_beta; + const double silicon_gamma; + + // silicon crystal structure + CrystalStructure silicon_cs; + + // constants that select the recriprocal space + const double dspacing_min; + const double dspacing_max; + const double wavelength_min; + const double wavelength_max; + const double omega_step; + + // check praramerter + const double TOLERANCE_L; // distance + const double TOLERANCE_R; // rotation angle + const bool LOGCHILDALG; // whether to show individual alg log + const double PI{3.1415926535897932384626433832795028841971693993751058209}; +}; diff --git a/Framework/Crystal/test/SaveHKLTest.h b/Framework/Crystal/test/SaveHKLTest.h index 4a27bef55b9681cb30c8ccd02ce6b55a2a36e3e0..2ec3fa3e8c890e64adb85298af857e3e58bad060 100644 --- a/Framework/Crystal/test/SaveHKLTest.h +++ b/Framework/Crystal/test/SaveHKLTest.h @@ -6,17 +6,24 @@ // SPDX - License - Identifier: GPL - 3.0 + #pragma once +#include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/FrameworkManager.h" #include "MantidAPI/Run.h" #include "MantidAPI/Sample.h" #include "MantidCrystal/SaveHKL.h" +#include "MantidDataHandling/LoadInstrument.h" #include "MantidDataObjects/Peak.h" #include "MantidDataObjects/PeaksWorkspace.h" +#include "MantidGeometry/Crystal/OrientedLattice.h" #include "MantidGeometry/IDTypes.h" +#include "MantidGeometry/Instrument/Goniometer.h" #include "MantidGeometry/Objects/ShapeFactory.h" #include "MantidKernel/Material.h" #include "MantidKernel/System.h" +#include "MantidKernel/TimeSeriesProperty.h" #include "MantidKernel/Timer.h" #include "MantidTestHelpers/ComponentCreationHelper.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" #include #include #include @@ -31,6 +38,15 @@ using namespace Mantid::PhysicalConstants; class SaveHKLTest : public CxxTest::TestSuite { public: + // This pair of boilerplate methods prevent the suite being created statically + // This means the constructor isn't called when running other tests + static SaveHKLTest *createSuite() { return new SaveHKLTest(); } + static void destroySuite(SaveHKLTest *suite) { delete suite; } + + // create FrameworkManager so that MantidAlgorithms.dll is loaded and + // AddAbsorptionWeightedPathLengths algorithm is registered + SaveHKLTest() { FrameworkManager::Instance(); } + void test_Init() { SaveHKL alg; TS_ASSERT_THROWS_NOTHING(alg.initialize()) @@ -87,6 +103,121 @@ public: expectedTbar, expectedTransmission); } + void test_save_with_direction_cosines_DEMAND() { + // This test compares the direction cosines calculated with + // SaveHKL to values from other software + auto ws = std::make_shared(); + + // create dummy workspace to use with LoadInstrument + auto dummyWS = WorkspaceCreationHelper::create2DWorkspace(1, 1); + auto expinfo = std::dynamic_pointer_cast(dummyWS); + auto &run = expinfo->mutableRun(); + Types::Core::DateAndTime startTime(0); + auto twotheta = std::make_unique>("2theta"); + twotheta->addValue(startTime, 58.0595); + run.addLogData(std::move(twotheta)); + auto det_trans = std::make_unique>("det_trans"); + det_trans->addValue(startTime, 399.9955); + run.addLogData(std::move(det_trans)); + + AnalysisDataService::Instance().addOrReplace("dummy_direction_cosine_test", + dummyWS); + Mantid::DataHandling::LoadInstrument loader; + loader.initialize(); + loader.setPropertyValue("InstrumentName", "HB3A"); + loader.setPropertyValue("Workspace", "dummy_direction_cosine_test"); + loader.setPropertyValue("RewriteSpectraMap", "False"); + loader.execute(); + + Goniometer gon; + gon.pushAxis("omega", 0, 1, 0, 29.0295, -1); + gon.pushAxis("chi", 0, 0, 1, 15.1168, -1); + gon.pushAxis("phi", 0, 1, 0, 4.7395, -1); + run.setGoniometer(gon, false); + + ws->setInstrument(dummyWS->getInstrument()); + ws->mutableRun().setGoniometer(run.getGoniometer().getR(), false); + + Mantid::Kernel::DblMatrix UBMatrix({-0.009884, -0.016780, 0.115725, + 0.112280, 0.002840, 0.011331, -0.005899, + 0.081084, 0.023625}); + auto lattice = std::make_unique(); + lattice->setUB(UBMatrix); + ExperimentInfo_sptr ei = std::dynamic_pointer_cast(ws); + ei->mutableSample().setOrientedLattice(std::move(lattice)); + + V3D hkl(1, -2, 5); + V3D qSampleFrame = UBMatrix * hkl * 2 * M_PI; + Peak p(ws->getInstrument(), qSampleFrame, run.getGoniometer().getR()); + p.setHKL(hkl); + p.setRunNumber(1000); + p.setBankName("bank1"); + p.setIntensity(1.0); + p.setSigmaIntensity(1.0); + p.setBinCount(1.0); + ws->addPeak(p); + + std::string outfile = "./SaveHKLTest_direction_cosine.hkl"; + SaveHKL alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING(alg.setProperty("InputWorkspace", ws)); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Filename", outfile)); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("DirectionCosines", true)); + + TS_ASSERT_THROWS_NOTHING(alg.execute();); + TS_ASSERT(alg.isExecuted()); + + // Get the file + outfile = alg.getPropertyValue("Filename"); + TS_ASSERT(Poco::File(outfile).exists()); + + std::ifstream in(outfile.c_str()); + double h, k, l, fsw, sigmafsq, histnum, wl, tbar, dir_cos_1_x, dir_cos_2_x, + dir_cos_1_y, dir_cos_2_y, dir_cos_1_z, dir_cos_2_z, dsp, col, row; + in >> h >> k >> l >> fsw >> sigmafsq >> histnum >> wl >> tbar >> + dir_cos_1_x >> dir_cos_2_x >> dir_cos_1_y >> dir_cos_2_y >> + dir_cos_1_z >> dir_cos_2_z >> dsp >> col >> row; + in.close(); + + TS_ASSERT_EQUALS(h, -1); + TS_ASSERT_EQUALS(k, 2); + TS_ASSERT_EQUALS(l, -5); + TS_ASSERT_EQUALS(fsw, 1); + TS_ASSERT_EQUALS(sigmafsq, 1); + TS_ASSERT_EQUALS(histnum, 1); + TS_ASSERT_DELTA(wl, 1.55025, 1e-4); + TS_ASSERT_EQUALS(tbar, 0); + // compare to direction cosines produced with other software + TS_ASSERT_DELTA(dir_cos_1_x, -0.03516, 1e-4); + TS_ASSERT_DELTA(dir_cos_1_y, -0.71004, 1e-4); + TS_ASSERT_DELTA(dir_cos_1_z, -0.70328, 1e-4); + TS_ASSERT_DELTA(dir_cos_2_x, -0.13886, 1e-4); + TS_ASSERT_DELTA(dir_cos_2_y, 0.96643, 1e-4); + TS_ASSERT_DELTA(dir_cos_2_z, -0.21619, 1e-4); + TS_ASSERT_EQUALS(dsp, 1000); + TS_ASSERT_EQUALS(col, 0); + TS_ASSERT_EQUALS(row, 0); + + // compare direction cosine to direct calculation + auto RU = ws->run().getGoniometer().getR() * + ws->sample().getOrientedLattice().getU(); + RU.Transpose(); + V3D dir_cos_1 = RU * V3D(0, 0, -1); + auto peaks_pos = ws->getPeak(0).getDetPos(); + peaks_pos.normalize(); + V3D dir_cos_2 = RU * peaks_pos; + TS_ASSERT_DELTA(dir_cos_1_x, dir_cos_1[0], 1e-4); + TS_ASSERT_DELTA(dir_cos_1_y, dir_cos_1[1], 1e-4); + TS_ASSERT_DELTA(dir_cos_1_z, dir_cos_1[2], 1e-4); + TS_ASSERT_DELTA(dir_cos_2_x, dir_cos_2[0], 1e-4); + TS_ASSERT_DELTA(dir_cos_2_y, dir_cos_2[1], 1e-4); + TS_ASSERT_DELTA(dir_cos_2_z, dir_cos_2[2], 1e-4); + + if (Poco::File(outfile).exists()) + Poco::File(outfile).remove(); + } + private: PeaksWorkspace_sptr createTestPeaksWorkspace(int numRuns, size_t numBanks, size_t numPeaksPerBank) { diff --git a/Framework/Crystal/test/SetGoniometerTest.h b/Framework/Crystal/test/SetGoniometerTest.h index 1fd24eeefb748ed9d69fa6d20b6df33423a793b7..67b83370b8d3166f07ad4813317d66ebf091e796 100644 --- a/Framework/Crystal/test/SetGoniometerTest.h +++ b/Framework/Crystal/test/SetGoniometerTest.h @@ -117,6 +117,68 @@ public: TS_ASSERT_EQUALS(gon.getAxis(2).angle, 45); AnalysisDataService::Instance().remove("SetGoniometerTest_ws"); } + + void test_multiple_goniometers() { + Workspace2D_sptr ws = WorkspaceCreationHelper::create2DWorkspace(10, 10); + AnalysisDataService::Instance().addOrReplace("SetMutipleGoniometerTest_ws", + ws); + FrameworkManager::Instance().exec( + "AddTimeSeriesLog", 8, "Workspace", "SetMutipleGoniometerTest_ws", + "Name", "angle1", "Time", "2010-01-01T00:00:00", "Value", "0.0"); + FrameworkManager::Instance().exec( + "AddTimeSeriesLog", 8, "Workspace", "SetMutipleGoniometerTest_ws", + "Name", "angle1", "Time", "2010-01-01T00:01:00", "Value", "90.0"); + + FrameworkManager::Instance().exec( + "AddTimeSeriesLog", 8, "Workspace", "SetMutipleGoniometerTest_ws", + "Name", "angle2", "Time", "2010-01-01T00:00:00", "Value", "90.0"); + + FrameworkManager::Instance().exec( + "AddTimeSeriesLog", 8, "Workspace", "SetMutipleGoniometerTest_ws", + "Name", "angle2", "Time", "2010-01-01T00:01:00", "Value", "0.0"); + + SetGoniometer alg; + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("Workspace", "SetMutipleGoniometerTest_ws")); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Axis0", "angle1, 1,0,0,1")); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Axis1", "angle2, 0,1,0,1")); + TS_ASSERT_THROWS_NOTHING(alg.setProperty("Average", false)); + TS_ASSERT_THROWS_NOTHING(alg.execute();); + TS_ASSERT(alg.isExecuted()); // no log values + + // Check the results + TS_ASSERT_EQUALS(ws->run().getNumGoniometers(), 2); + + const Goniometer &gon = ws->mutableRun().getGoniometer(0); + TS_ASSERT_EQUALS(gon.getNumberAxes(), 2); + + TS_ASSERT_EQUALS(gon.getAxis(0).name, "angle1"); + TS_ASSERT_EQUALS(gon.getAxis(0).rotationaxis, V3D(1, 0, 0)); + TS_ASSERT_EQUALS(gon.getAxis(0).sense, 1); + TS_ASSERT_EQUALS(gon.getAxis(0).angle, 0.0); + + TS_ASSERT_EQUALS(gon.getAxis(1).name, "angle2"); + TS_ASSERT_EQUALS(gon.getAxis(1).rotationaxis, V3D(0, 1, 0)); + TS_ASSERT_EQUALS(gon.getAxis(1).sense, 1); + TS_ASSERT_EQUALS(gon.getAxis(1).angle, 90.0); + + const Goniometer &gon2 = ws->mutableRun().getGoniometer(1); + TS_ASSERT_EQUALS(gon2.getNumberAxes(), 2); + + TS_ASSERT_EQUALS(gon2.getAxis(0).name, "angle1"); + TS_ASSERT_EQUALS(gon2.getAxis(0).rotationaxis, V3D(1, 0, 0)); + TS_ASSERT_EQUALS(gon2.getAxis(0).sense, 1); + TS_ASSERT_EQUALS(gon2.getAxis(0).angle, 90.0); + + TS_ASSERT_EQUALS(gon2.getAxis(1).name, "angle2"); + TS_ASSERT_EQUALS(gon2.getAxis(1).rotationaxis, V3D(0, 1, 0)); + TS_ASSERT_EQUALS(gon2.getAxis(1).sense, 1); + TS_ASSERT_EQUALS(gon2.getAxis(1).angle, 0.0); + AnalysisDataService::Instance().remove("SetMultipleGoniometerTest_ws"); + } + void test_universal() { Workspace2D_sptr ws = WorkspaceCreationHelper::create2DWorkspace(10, 10); AnalysisDataService::Instance().addOrReplace("SetUnivGoniometerTest_ws", diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/PlotPeakByLogValue.h b/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/PlotPeakByLogValue.h index 17fd0bcfc2dac2a427ec3201c3320a695c208e45..81f7cb3741e4b2c62c207570b61924c5b5abebc4 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/PlotPeakByLogValue.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/Algorithms/PlotPeakByLogValue.h @@ -61,6 +61,7 @@ public: private: // Overridden Algorithm methods void init() override; + std::map validateInputs() override; void exec() override; /// Set any WorkspaceIndex attributes in the fitting function @@ -70,7 +71,8 @@ private: std::shared_ptr runSingleFit(bool createFitOutput, bool outputCompositeMembers, bool outputConvolvedMembers, const API::IFunction_sptr &ifun, - const InputSpectraToFit &data, double startX, double endX); + const InputSpectraToFit &data, double startX, double endX, + const std::string &exclude); double calculateLogValue(const std::string &logName, const InputSpectraToFit &data); @@ -100,6 +102,9 @@ private: std::string getMinimizerString(const std::string &wsName, const std::string &wsIndex); + /// Create a vector of linked exclude starts and ends + std::vector getExclude(const size_t numSpectra); + /// Base name of output workspace std::string m_baseName; diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/BackToBackExponential.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/BackToBackExponential.h index ca8bae1103f6d913edfeb88fd571dae49f808e36..49b464e4ffbd6c19c0a4927e86a303805685b354 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/BackToBackExponential.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/BackToBackExponential.h @@ -52,6 +52,7 @@ public: void setIntensity(const double newIntensity) override { setParameter("I", newIntensity); } + std::string getWidthParameterName() const override { return "S"; } /// overwrite IFunction base class methods std::string name() const override { return "BackToBackExponential"; } diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h index ddc9dac5810481aa486768e60da34364552a99ab..aeb7782028cfce3475d9d9a2478cc537a57fbb9c 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/CrystalFieldFunction.h @@ -61,8 +61,12 @@ public: bool isExplicitlySet(size_t i) const override; /// Get the fitting error for a parameter double getError(size_t i) const override; + /// Get the fitting error for a parameter by name + double getError(const std::string &name) const override; /// Set the fitting error for a parameter void setError(size_t i, double err) override; + /// Set the fitting error for a parameter by name + void setError(const std::string &name, double err) override; /// Return parameter index from a parameter reference. size_t getParameterIndex(const API::ParameterReference &ref) const override; diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/DynamicKuboToyabe.h b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/DynamicKuboToyabe.h index 709c65ac95006309466b35a399d948a790145970..b8bdbb9cb42440fad04b8b31306efc4bb8fe68d3 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/Functions/DynamicKuboToyabe.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/Functions/DynamicKuboToyabe.h @@ -36,21 +36,9 @@ public: std::string name() const override { return "DynamicKuboToyabe"; } const std::string category() const override { return "Muon\\MuonGeneric"; } - /// Returns the number of attributes associated with the function - size_t nAttributes() const override { return 1; } - - /// Returns a list of attribute names - std::vector getAttributeNames() const override; - - /// Return a value of attribute attName - Attribute getAttribute(const std::string &attName) const override; - /// Set a value to attribute attName void setAttribute(const std::string &attName, const Attribute &) override; - /// Check if attribute attName exists - bool hasAttribute(const std::string &attName) const override; - protected: void function1D(double *out, const double *xValues, const size_t nData) const override; diff --git a/Framework/CurveFitting/inc/MantidCurveFitting/PrecompiledHeader.h b/Framework/CurveFitting/inc/MantidCurveFitting/PrecompiledHeader.h index bb1dde3eae6c55db3dc5a71019a001135abe2476..8bbdcb0e02987e26a59fbd28a31a7043d5f4e586 100644 --- a/Framework/CurveFitting/inc/MantidCurveFitting/PrecompiledHeader.h +++ b/Framework/CurveFitting/inc/MantidCurveFitting/PrecompiledHeader.h @@ -6,6 +6,7 @@ // SPDX - License - Identifier: GPL - 3.0 + #pragma once -#include -#include -#include +#include "MantidAPI/IFunction1D.h" +#include "MantidCurveFitting/DllConfig.h" + +// STL diff --git a/Framework/CurveFitting/src/Algorithms/EstimateFitParameters.cpp b/Framework/CurveFitting/src/Algorithms/EstimateFitParameters.cpp index e36bbc271dca9860b85a904e6ea205aa0eb48401..52eee0b1350d12eb9b42b36d421b49f4708a63cc 100644 --- a/Framework/CurveFitting/src/Algorithms/EstimateFitParameters.cpp +++ b/Framework/CurveFitting/src/Algorithms/EstimateFitParameters.cpp @@ -230,7 +230,7 @@ void runCrossEntropy( for (auto &range : ranges) { auto mean = (range.first + range.second) / 2; auto sigma = std::fabs(range.first - range.second) / 2; - distributionParams.emplace_back(std::make_pair(mean, sigma)); + distributionParams.emplace_back(mean, sigma); } auto nParams = costFunction.nParams(); @@ -401,7 +401,7 @@ void EstimateFitParameters::execConcrete() { } // Use the lower and upper bounds of the constraint to set the range // of a generator with uniform distribution. - ranges.emplace_back(std::make_pair(boundary->lower(), boundary->upper())); + ranges.emplace_back(boundary->lower(), boundary->upper()); } // Number of parameters could have changed costFunction->reset(); diff --git a/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp b/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp index 722a07db8e398c133b56a009e951de0c4b5d42fa..dff570d38b8da3c6aeeffe873d59fcd31b4e478b 100644 --- a/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp +++ b/Framework/CurveFitting/src/Algorithms/LeBailFit.cpp @@ -1767,7 +1767,7 @@ void LeBailFit::setupRandomWalkStrategyFromTable( giter->second.emplace_back(parname); } else { // First instance in the new group. - m_MCGroups.emplace(group, vector{parname}); + m_MCGroups.emplace(group, std::initializer_list{parname}); } // 3. Set up MC parameters, A0, A1, non-negative diff --git a/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp b/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp index abe1d35bd41bc05ceb40c31b612d3d0335f0a4d8..941826dd92563e3ca2d4f6b4757a5f27a2cb44e3 100644 --- a/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp +++ b/Framework/CurveFitting/src/Algorithms/PlotPeakByLogValue.cpp @@ -157,7 +157,13 @@ void PlotPeakByLogValue::init() { declareProperty(std::make_unique>("Exclude", ""), "A list of pairs of real numbers, defining the regions to " - "exclude from the fit."); + "exclude from the fit for all spectra."); + + declareProperty( + std::make_unique>("ExcludeMultiple", ""), + "A list of Exclusion ranges, defining the regions to " + "exclude from the fit for each spectra. Must have the " + "same number of sets as the number of the spectra."); declareProperty("IgnoreInvalidData", false, "Flag to ignore infinities, NaNs and data with zero errors."); @@ -168,6 +174,21 @@ void PlotPeakByLogValue::init() { "OutputStatus and the OutputChiSquared"); } +std::map PlotPeakByLogValue::validateInputs() { + std::map errors; + std::string inputList = getPropertyValue("Input"); + int default_wi = getProperty("WorkspaceIndex"); + int default_spec = getProperty("Spectrum"); + const std::vector wsNames = + makeNames(inputList, default_wi, default_spec); + std::vector excludeList = getProperty("ExcludeMultiple"); + if (!excludeList.empty() && excludeList.size() != wsNames.size()) { + errors["ExcludeMultiple"] = + "ExcludeMultiple must be the same size has the number of spectra."; + } + return errors; +} + /** * Executes the algorithm */ @@ -190,6 +211,7 @@ void PlotPeakByLogValue::exec() { m_baseName = getPropertyValue("OutputWorkspace"); std::vector startX = getProperty("StartX"); std::vector endX = getProperty("EndX"); + std::vector exclude = getExclude(wsNames.size()); bool isDataName = false; // if true first output column is of type string and // is the data source name @@ -258,15 +280,15 @@ void PlotPeakByLogValue::exec() { if (startX.size() == 0) { fit = runSingleFit(createFitOutput, outputCompositeMembers, outputConvolvedMembers, ifun, data, EMPTY_DBL(), - EMPTY_DBL()); + EMPTY_DBL(), exclude[i]); } else if (startX.size() == 1) { - fit = - runSingleFit(createFitOutput, outputCompositeMembers, - outputConvolvedMembers, ifun, data, startX[0], endX[0]); + fit = runSingleFit(createFitOutput, outputCompositeMembers, + outputConvolvedMembers, ifun, data, startX[0], endX[0], + exclude[i]); } else { - fit = - runSingleFit(createFitOutput, outputCompositeMembers, - outputConvolvedMembers, ifun, data, startX[i], endX[i]); + fit = runSingleFit(createFitOutput, outputCompositeMembers, + outputConvolvedMembers, ifun, data, startX[i], endX[i], + exclude[i]); } ifun = fit->getProperty("Function"); @@ -428,7 +450,8 @@ PlotPeakByLogValue::createResultsTable(const std::string &logName, std::shared_ptr PlotPeakByLogValue::runSingleFit( bool createFitOutput, bool outputCompositeMembers, bool outputConvolvedMembers, const IFunction_sptr &ifun, - const InputSpectraToFit &data, double startX, double endX) { + const InputSpectraToFit &data, double startX, double endX, + const std::string &exclude) { g_log.debug() << "Fitting " << data.ws->getName() << " index " << data.i << " with \n"; g_log.debug() << ifun->asString() << '\n'; @@ -438,7 +461,6 @@ std::shared_ptr PlotPeakByLogValue::runSingleFit( if (createFitOutput) wsBaseName = data.name + "_" + spectrum_index; - const std::vector exclude = this->getProperty("Exclude"); bool histogramFit = this->getPropertyValue("EvaluationType") == "Histogram"; bool ignoreInvalidData = this->getProperty("IgnoreInvalidData"); @@ -540,6 +562,22 @@ std::string PlotPeakByLogValue::getMinimizerString(const std::string &wsName, return format; } +std::vector +PlotPeakByLogValue::getExclude(const size_t numSpectra) { + std::string exclude = getPropertyValue("Exclude"); + std::vector excludeList = getProperty("ExcludeMultiple"); + if (excludeList.empty()) { + std::vector excludeVector; + excludeVector.reserve(numSpectra); + for (size_t i = 0; i < numSpectra; i++) { + excludeVector.emplace_back(exclude); + } + return excludeVector; + } else { + return excludeList; + } +} + } // namespace Algorithms } // namespace CurveFitting } // namespace Mantid diff --git a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp index 4950361308f7fa6e5b61dace3e5f2e6371fa305e..ef8370e95e95d3fae4187d7d886b8d4ad8af9e48 100644 --- a/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp +++ b/Framework/CurveFitting/src/Algorithms/QENSFitSequential.cpp @@ -504,6 +504,12 @@ void QENSFitSequential::init() { "A list of pairs of real numbers, defining the regions to " "exclude from the fit."); + declareProperty( + std::make_unique>("ExcludeMultiple", ""), + "A list of Exclusion ranges, defining the regions to " + "exclude from the fit for each spectra. Must have the " + "same number of sets as the number of the spectra."); + declareProperty("IgnoreInvalidData", false, "Flag to ignore infinities, NaNs and data with zero errors."); @@ -812,6 +818,8 @@ void QENSFitSequential::renameGroupWorkspace( ITableWorkspace_sptr QENSFitSequential::performFit(const std::string &input, const std::string &output) { const std::vector exclude = getProperty("Exclude"); + const std::vector excludeMultiple = + getProperty("ExcludeMultiple"); const bool convolveMembers = getProperty("ConvolveMembers"); const bool outputCompositeMembers = getProperty("OutputCompositeMembers"); const bool passWsIndex = getProperty("PassWSIndexToFunction"); @@ -827,6 +835,7 @@ ITableWorkspace_sptr QENSFitSequential::performFit(const std::string &input, plotPeaks->setProperty("StartX", getPropertyValue("StartX")); plotPeaks->setProperty("EndX", getPropertyValue("EndX")); plotPeaks->setProperty("Exclude", exclude); + plotPeaks->setProperty("ExcludeMultiple", excludeMultiple); plotPeaks->setProperty("IgnoreInvalidData", ignoreInvalidData); plotPeaks->setProperty("FitType", "Sequential"); plotPeaks->setProperty("CreateOutput", true); diff --git a/Framework/CurveFitting/src/Algorithms/QENSFitSimultaneous.cpp b/Framework/CurveFitting/src/Algorithms/QENSFitSimultaneous.cpp index f2b49731d188565a7e79ba8468ffc434e1cd951a..cd83ec1d32ccd970a605b494f60071ae47402c18 100644 --- a/Framework/CurveFitting/src/Algorithms/QENSFitSimultaneous.cpp +++ b/Framework/CurveFitting/src/Algorithms/QENSFitSimultaneous.cpp @@ -220,7 +220,7 @@ getUniqueWorkspaceNames(std::vector &&workspaceNames) { std::set uniqueNames(workspaceNames.begin(), workspaceNames.end()); workspaceNames.assign(uniqueNames.begin(), uniqueNames.end()); - return workspaceNames; + return std::move(workspaceNames); } auto getNumericAxisValueReader(std::size_t axisIndex) { diff --git a/Framework/CurveFitting/src/CostFunctions/CostFuncFitting.cpp b/Framework/CurveFitting/src/CostFunctions/CostFuncFitting.cpp index e263c5e99b4e38dde9f3dd230449d63b9176c5ea..aa6ac688e1e05580179670a0ba488ae726ac273e 100644 --- a/Framework/CurveFitting/src/CostFunctions/CostFuncFitting.cpp +++ b/Framework/CurveFitting/src/CostFunctions/CostFuncFitting.cpp @@ -201,7 +201,8 @@ void CostFuncFitting::calFittingErrors(const GSLMatrix &covar, double chi2) { } } m_function->setCovarianceMatrix(covarMatrix); - m_function->setChiSquared(chi2); + m_function->setReducedChiSquared( + chi2 / static_cast((m_values->size() - np))); } /** diff --git a/Framework/CurveFitting/src/Functions/BackToBackExponential.cpp b/Framework/CurveFitting/src/Functions/BackToBackExponential.cpp index b0c1dde0c3b0230b1cd56f8acda15d59d5554253..bcfb2d46c3a57957c863f2843be73577d7050854 100644 --- a/Framework/CurveFitting/src/Functions/BackToBackExponential.cpp +++ b/Framework/CurveFitting/src/Functions/BackToBackExponential.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace Mantid { namespace CurveFitting { @@ -41,17 +41,15 @@ void BackToBackExponential::init() { } /** - * Get approximate height of the peak: function value at X0. + * Get approximate height (maximum value) of the peak (not at X0) */ double BackToBackExponential::height() const { - double x0 = getParameter(3); - std::vector vec(1, x0); - FunctionDomain1DVector domain(vec); - FunctionValues values(domain); - - function(domain, values); - - return values[0]; + // height = I * ln2 /(FWHM - ln2 * S) + const double I = getParameter(0); + const double s = getParameter(4); + const auto width = fwhm(); + const auto h = I * M_LN2 / (width - M_LN2 * s); + return h; } /** @@ -64,7 +62,7 @@ void BackToBackExponential::setHeight(const double h) { setParameter(0, 1e-6); h0 = height(); } - double area = getParameter(0); // == I + double area = getParameter(0); // ==I area *= h / h0; if (area <= 0.0) { area = 1e-6; @@ -78,14 +76,37 @@ void BackToBackExponential::setHeight(const double h) { /** * Get approximate peak width. */ -double BackToBackExponential::fwhm() const { return 2 * getParameter("S"); } +double BackToBackExponential::fwhm() const { + // intrinsic width of B2B exp + auto s = getParameter("S"); + auto w0 = expWidth(); + // Gaussian and B2B exp widths don't add in quadrature. The following + // tends to gaussian at large S and at S=0 is equal to the intrinsic width of + // the B2B exp (good to <3% for typical params) + return w0 * exp(-0.5 * M_LN2 * s / w0) + 2 * sqrt(2 * M_LN2) * s; +} /** * Set new peak width approximately. * @param w :: New value for the width. */ void BackToBackExponential::setFwhm(const double w) { - setParameter("S", w / 2.0); + // get height so can reset after changing S + const auto h0 = height(); + const auto w0 = expWidth(); + if (w > w0) { + const auto a = 0.5 * M_LN2; + const auto b = 2 * sqrt(2 * M_LN2); + // calculate new value of S (from solving eq in fwhm func) + setParameter( + "S", w0 * (gsl_sf_lambert_W0(-(a / b) * exp(-(a / b) * (w / w0))) / a + + (w / w0) / b)); + } else { + // set to some small number relative to w0 + setParameter("S", 1e-6); + } + // reset height + setHeight(h0); } void BackToBackExponential::function1D(double *out, const double *xValues, diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp index 220d2f320b5f0ba3d0eb26c3710e2a45cfc18eef..df58f06f09e0a349bfd71fcf1c735d729e05f885 100644 --- a/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp +++ b/Framework/CurveFitting/src/Functions/CrystalFieldFunction.cpp @@ -319,6 +319,20 @@ double CrystalFieldFunction::getError(size_t i) const { } } +/// Get the fitting error for a parameter +double CrystalFieldFunction::getError(const std::string &name) const { + auto index = parameterIndex(name); + checkSourceFunction(); + checkTargetFunction(); + if (index < m_nControlParams) { + return m_control.getError(index); + } else if (index < m_nControlSourceParams) { + return m_source->getError(index - m_nControlParams); + } else { + return m_target->getError(index - m_nControlSourceParams); + } +} + /// Set the fitting error for a parameter void CrystalFieldFunction::setError(size_t i, double err) { checkSourceFunction(); @@ -332,6 +346,20 @@ void CrystalFieldFunction::setError(size_t i, double err) { } } +/// Set the fitting error for a parameter +void CrystalFieldFunction::setError(const std::string &name, double err) { + auto index = parameterIndex(name); + checkSourceFunction(); + checkTargetFunction(); + if (index < m_nControlParams) { + m_control.setError(index, err); + } else if (index < m_nControlSourceParams) { + m_source->setError(index - m_nControlParams, err); + } else { + m_target->setError(index - m_nControlSourceParams, err); + } +} + /// Change status of parameter void CrystalFieldFunction::setParameterStatus( size_t i, IFunction::ParameterStatus status) { diff --git a/Framework/CurveFitting/src/Functions/CrystalFieldPeakUtils.cpp b/Framework/CurveFitting/src/Functions/CrystalFieldPeakUtils.cpp index c50b79f50ad0c4f95a402811910b5ec640ebaa5e..4edc4536095bad0c8dac3d10713f4f938e037b7b 100644 --- a/Framework/CurveFitting/src/Functions/CrystalFieldPeakUtils.cpp +++ b/Framework/CurveFitting/src/Functions/CrystalFieldPeakUtils.cpp @@ -263,7 +263,8 @@ void updatePeak(API::IPeakFunction &peak, double centre, double intensity, } } else { peak.setHeight(0.0); - peak.fixAllActive(fixByDefault); + peak.fixIntensity(fixByDefault); + peak.fixCentre(fixByDefault); } } @@ -315,11 +316,15 @@ size_t updateSpectrumFunction(API::CompositeFunction &spectrum, } // If there are any peaks above the maxNPeaks, ignore them // but don't remove + const bool fixByDefault = true; for (size_t i = maxNPeaks; i < nFunctions - iFirst; ++i) { auto fun = spectrum.getFunction(i + iFirst); auto &peak = dynamic_cast(*fun); const auto fwhm = peak.fwhm(); - ignorePeak(peak, fwhm); + peak.setHeight(0.0); + peak.setFwhm(fwhm); + peak.fixIntensity(fixByDefault); + peak.fixCentre(fixByDefault); } return nGoodPeaks; } diff --git a/Framework/CurveFitting/src/Functions/CubicSpline.cpp b/Framework/CurveFitting/src/Functions/CubicSpline.cpp index 61f59611f069b3246d5ada9a99adb6d5b656971c..680f6d7e1065067b988fdb7eb676c40860803e09 100644 --- a/Framework/CurveFitting/src/Functions/CubicSpline.cpp +++ b/Framework/CurveFitting/src/Functions/CubicSpline.cpp @@ -106,7 +106,7 @@ void CubicSpline::setupInput(boost::scoped_array &x, std::vector pairs; pairs.reserve(n); for (int i = 0; i < n; ++i) { - pairs.emplace_back(std::make_pair(x[i], y[i])); + pairs.emplace_back(x[i], y[i]); } std::sort(pairs.begin(), pairs.end(), @@ -179,9 +179,9 @@ void CubicSpline::calculateSpline(double *out, const double *xValues, } } - // warn user than some values wern't calculated + // inform user that some values weren't calculated if (outOfRange) { - g_log.warning() + g_log.information() << "Some x values where out of range and will not be calculated.\n"; } } @@ -247,9 +247,9 @@ void CubicSpline::calculateDerivative(double *out, const double *xValues, out[i] = xDeriv; } - // warn user that some values weren't calculated + // inform user that some values weren't calculated if (outOfRange) { - g_log.warning() + g_log.information() << "Some x values where out of range and will not be calculated.\n"; } } diff --git a/Framework/CurveFitting/src/Functions/DynamicKuboToyabe.cpp b/Framework/CurveFitting/src/Functions/DynamicKuboToyabe.cpp index 53bef9698355b28da863677132456d158a76a76e..3c5926543e8f27bd6f682f86d74c2fc96afdd22b 100644 --- a/Framework/CurveFitting/src/Functions/DynamicKuboToyabe.cpp +++ b/Framework/CurveFitting/src/Functions/DynamicKuboToyabe.cpp @@ -5,6 +5,7 @@ // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + #include "MantidCurveFitting/Functions/DynamicKuboToyabe.h" + #include "MantidAPI/FunctionFactory.h" #include "MantidAPI/Jacobian.h" #include "MantidAPI/MatrixWorkspace.h" @@ -18,15 +19,14 @@ namespace Mantid { namespace CurveFitting { namespace Functions { +using namespace API; using namespace CurveFitting; - using namespace Kernel; -using namespace API; - DECLARE_FUNCTION(DynamicKuboToyabe) void DynamicKuboToyabe::init() { + declareAttribute("BinWidth", Attribute(m_eps)); declareParameter("Asym", 0.2, "Amplitude at time 0"); declareParameter("Delta", 0.2, "Local field"); declareParameter("Field", 0.0, "External field"); @@ -319,29 +319,6 @@ void DynamicKuboToyabe::setActiveParameter(size_t i, double value) { setParameter(i, fabs(value), false); } -//---------------------------------------------------------------------------------------------- -/** Get Attribute names - * @return A list of attribute names - */ -std::vector DynamicKuboToyabe::getAttributeNames() const { - return {"BinWidth"}; -} - -//---------------------------------------------------------------------------------------------- -/** Get Attribute - * @param attName :: Attribute name. If it is not "eps" an exception is thrown. - * @return a value of attribute attName - */ -API::IFunction::Attribute -DynamicKuboToyabe::getAttribute(const std::string &attName) const { - - if (attName == "BinWidth") { - return Attribute(m_eps); - } - throw std::invalid_argument("DynamicKuboToyabe: Unknown attribute " + - attName); -} - //---------------------------------------------------------------------------------------------- /** Set Attribute * @param attName :: The attribute name. If it is not "eps" exception is thrown. @@ -377,6 +354,7 @@ void DynamicKuboToyabe::setAttribute(const std::string &attName, init(); } m_eps = newVal; + IFunction::setAttribute(attName, Attribute(m_eps)); } else { throw std::invalid_argument("DynamicKuboToyabe: Unknown attribute " + @@ -384,14 +362,6 @@ void DynamicKuboToyabe::setAttribute(const std::string &attName, } } -//---------------------------------------------------------------------------------------------- -/** Check if attribute attName exists - * @param attName :: The attribute name. - */ -bool DynamicKuboToyabe::hasAttribute(const std::string &attName) const { - return attName == "BinWidth"; -} - } // namespace Functions } // namespace CurveFitting } // namespace Mantid diff --git a/Framework/CurveFitting/src/IMWDomainCreator.cpp b/Framework/CurveFitting/src/IMWDomainCreator.cpp index acfe257d42e76dcaa2a3316affe96a687ec6f19d..7af1cfa40dde7beb7acfc36996c5519040750af4 100644 --- a/Framework/CurveFitting/src/IMWDomainCreator.cpp +++ b/Framework/CurveFitting/src/IMWDomainCreator.cpp @@ -28,6 +28,7 @@ #include "MantidKernel/Matrix.h" #include +#include namespace Mantid { namespace CurveFitting { @@ -412,6 +413,15 @@ void IMWDomainCreator::addFunctionValuesToWS( const std::shared_ptr &resultValues) const { const size_t nData = resultValues->size(); resultValues->zeroCalculated(); + // Confidence bands are calculated based on the example in + // www.astro.rug.nl/software/kapteyn/kmpfittutorial.html#confidence-and-prediction-intervals, + // which references J.Wolberg, Data Analysis Using the Method of Least + // Squares, 2006, Springer. + // Here we asusme a confidence band of 1 sigma + double sigma = 1; + double prob = std::erf(sigma / sqrt(2)); + // critical value for t distribution + double alpha = (1 + prob) / 2; // Function value function->function(*domain, *resultValues); @@ -458,14 +468,19 @@ void IMWDomainCreator::addFunctionValuesToWS( E[k] = s; } - double chi2 = function->getChiSquared(); + double chi2Reduced = function->getReducedChiSquared(); + size_t dof = nData - nParams; auto &yValues = ws->mutableY(wsIndex); auto &eValues = ws->mutableE(wsIndex); + double T = 1.0; + if (dof != 0) { + boost::math::students_t dist(static_cast(dof)); + T = boost::math::quantile(dist, alpha); + } for (size_t i = 0; i < nData; i++) { yValues[i] = resultValues->getCalculated(i); - eValues[i] = std::sqrt(E[i] * chi2); + eValues[i] = T * std::sqrt(E[i] * chi2Reduced); } - } else { // otherwise use the parameter errors which is OK for uncorrelated // parameters diff --git a/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp b/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp index 27131c9e368846f4e59b7ba2bed2fb44b79a27e2..d799ee25070f863582279578b7482e36a4356e44 100644 --- a/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp +++ b/Framework/CurveFitting/src/TableWorkspaceDomainCreator.cpp @@ -30,6 +30,8 @@ #include "MantidKernel/BoundedValidator.h" #include "MantidKernel/EmptyValues.h" +#include + namespace Mantid { namespace CurveFitting { @@ -465,6 +467,15 @@ void TableWorkspaceDomainCreator::addFunctionValuesToWS( const std::shared_ptr &resultValues) const { const size_t nData = resultValues->size(); resultValues->zeroCalculated(); + // Confidence bands are calculated based on the example in + // www.astro.rug.nl/software/kapteyn/kmpfittutorial.html#confidence-and-prediction-intervals, + // which references J.Wolberg, Data Analysis Using the Method of Least + // Squares, 2006, Springer. + // Here we asusme a confidence band of 1 sigma + double sigma = 1; + double prob = std::erf(sigma / sqrt(2)); + // critical value for t distribution + double alpha = (1 + prob) / 2; // Function value function->function(*domain, *resultValues); @@ -511,12 +522,18 @@ void TableWorkspaceDomainCreator::addFunctionValuesToWS( E[k] = s; } - double chi2 = function->getChiSquared(); + double chi2Reduced = function->getReducedChiSquared(); + size_t dof = nData - nParams; auto &yValues = ws->mutableY(wsIndex); auto &eValues = ws->mutableE(wsIndex); + double T = 1.0; + if (dof != 0) { + boost::math::students_t dist(static_cast(dof)); + T = boost::math::quantile(dist, alpha); + } for (size_t i = 0; i < nData; i++) { yValues[i] = resultValues->getCalculated(i); - eValues[i] = std::sqrt(E[i] * chi2); + eValues[i] = T * std::sqrt(E[i] * chi2Reduced); } } else { diff --git a/Framework/CurveFitting/test/Algorithms/ConvolutionFitSequentialTest.h b/Framework/CurveFitting/test/Algorithms/ConvolutionFitSequentialTest.h index 78e821f0e47dc18dd3c8ac0c2ea547a57a374aa0..3173a88121c8bee8ee693f2d1d7937d339d9b089 100644 --- a/Framework/CurveFitting/test/Algorithms/ConvolutionFitSequentialTest.h +++ b/Framework/CurveFitting/test/Algorithms/ConvolutionFitSequentialTest.h @@ -184,7 +184,6 @@ public: // Check oringal Log was copied correctly auto &memberRun = matrixMember->mutableRun(); auto &originalRun = redWs->mutableRun(); - auto foo = memberRun.getLogData(); TS_ASSERT_EQUALS(memberRun.getLogData().at(3)->value(), originalRun.getLogData().at(1)->value()); diff --git a/Framework/CurveFitting/test/Algorithms/FitPowderDiffPeaksTest.h b/Framework/CurveFitting/test/Algorithms/FitPowderDiffPeaksTest.h index 701f79f2d0dc6fba4ccbdd89dee455b2dce3e765..b56535ac5945348c49de85fa59acd72499c6299d 100644 --- a/Framework/CurveFitting/test/Algorithms/FitPowderDiffPeaksTest.h +++ b/Framework/CurveFitting/test/Algorithms/FitPowderDiffPeaksTest.h @@ -449,12 +449,10 @@ public: TS_ASSERT_EQUALS(peakdataws->x(0).rawData(), peakdataws->x(2).rawData()); TS_ASSERT_EQUALS(peakdataws->x(0).rawData(), peakdataws->x(3).rawData()); TS_ASSERT_EQUALS(peakdataws->x(0).rawData(), peakdataws->x(4).rawData()); - TS_ASSERT_DELTA(peakdataws->y(0)[0], 0.4302, 0.0001); - TS_ASSERT_DELTA(peakdataws->y(2)[0], 0.4302, 0.0001); - TS_ASSERT_DELTA(peakdataws->y(0)[500], 0.4163, 0.0001); - TS_ASSERT_DELTA(peakdataws->y(2)[500], 0.4163, 0.0001); - TS_ASSERT_DELTA(peakdataws->y(0)[1000], 0.4331, 0.0001); - TS_ASSERT_DELTA(peakdataws->y(2)[1000], 0.4331, 0.0001); + // checking difference = data in regions not fitted + TS_ASSERT_DELTA(peakdataws->y(0)[0], peakdataws->y(2)[0], 0.0001); + TS_ASSERT_DELTA(peakdataws->y(0)[500], peakdataws->y(2)[500], 0.0001); + TS_ASSERT_DELTA(peakdataws->y(0)[1100], peakdataws->y(2)[1100], 0.0001); // Output Bragg peaks parameters DataObjects::TableWorkspace_sptr outbraggws = @@ -463,12 +461,12 @@ public: TS_ASSERT(outbraggws); if (!outbraggws) { return; - } - TS_ASSERT_EQUALS(outbraggws->rowCount(), 11); + } // checking chisq for some peaks + TS_ASSERT_EQUALS(outbraggws->rowCount(), 12); TS_ASSERT_EQUALS(outbraggws->columnCount(), 10); - TS_ASSERT_DELTA(outbraggws->Double(0, 9), 1.83, 0.3); - TS_ASSERT_DELTA(outbraggws->Double(4, 9), 0.44, 0.01); - TS_ASSERT_DELTA(outbraggws->Double(8, 9), 0.52, 0.3); + TS_ASSERT_DELTA(outbraggws->Double(0, 9), 0.47, 0.1); + TS_ASSERT_DELTA(outbraggws->Double(5, 9), 0.57, 0.1); + TS_ASSERT_DELTA(outbraggws->Double(9, 9), 0.52, 0.1); AnalysisDataService::Instance().remove("DataWorkspace"); AnalysisDataService::Instance().remove("PeakParameters"); diff --git a/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h b/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h index 12372de8fbc0f8d5aac2b8d9560cfdbbb3a0f7c2..235a2f25211266a81ee95ce52b5f9163b5b3dbf5 100644 --- a/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h +++ b/Framework/CurveFitting/test/Algorithms/PlotPeakByLogValueTest.h @@ -568,19 +568,31 @@ public: AnalysisDataService::Instance().clear(); } - void test_exclude_range() { - HistogramData::Points points{-2, -1, 0, 1, 2}; - HistogramData::Counts counts(points.size(), 0.0); - // This value should be excluded. - counts.mutableData()[2] = 10.0; - MatrixWorkspace_sptr ws(DataObjects::create( - 1, HistogramData::Histogram(points, counts)) - .release()); - AnalysisDataService::Instance().addOrReplace("InputWS", ws); + void test_single_exclude_range_single_Spectra() { + createData(); + + PlotPeakByLogValue alg; + alg.initialize(); + alg.setPropertyValue("Input", "PlotPeakGroup_0"); + alg.setPropertyValue("Exclude", "-0.5, 0.5"); + alg.setPropertyValue("OutputWorkspace", "PlotPeakResult"); + alg.setProperty("CreateOutput", true); + alg.setPropertyValue("Function", "name=FlatBackground,A0=2"); + alg.setPropertyValue("MaxIterations", "50"); + alg.execute(); + + TS_ASSERT(alg.isExecuted()); + + deleteData(); + WorkspaceCreationHelper::removeWS("PlotPeakResult"); + } + + void test_single_exclude_range_multiple_Spectra() { + createData(); PlotPeakByLogValue alg; alg.initialize(); - alg.setPropertyValue("Input", "InputWS,i0"); + alg.setPropertyValue("Input", "PlotPeakGroup_0;PlotPeakGroup_1"); alg.setPropertyValue("Exclude", "-0.5, 0.5"); alg.setPropertyValue("OutputWorkspace", "PlotPeakResult"); alg.setProperty("CreateOutput", true); @@ -589,7 +601,30 @@ public: alg.execute(); TS_ASSERT(alg.isExecuted()); - AnalysisDataService::Instance().remove("InputWS"); + + deleteData(); + WorkspaceCreationHelper::removeWS("PlotPeakResult"); + } + + void test_multiple_exclude_range_multiple_Spectra() { + createData(); + std::vector excludeRanges; + excludeRanges.emplace_back("-0.5, 0.0"); + excludeRanges.emplace_back("0.5, 1.5"); + PlotPeakByLogValue alg; + alg.initialize(); + alg.setPropertyValue("Input", "PlotPeakGroup_0;PlotPeakGroup_1"); + alg.setProperty("ExcludeMultiple", excludeRanges); + alg.setPropertyValue("OutputWorkspace", "PlotPeakResult"); + alg.setProperty("CreateOutput", true); + alg.setPropertyValue("Function", "name=FlatBackground,A0=2"); + alg.setPropertyValue("MaxIterations", "50"); + alg.execute(); + + TS_ASSERT(alg.isExecuted()); + + deleteData(); + WorkspaceCreationHelper::removeWS("PlotPeakResult"); } void test_startX_single_value() { diff --git a/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h b/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h index 95a6bc3e17a1b83ef5ed058e25b3f9e33c3c3a39..e6f0c29ae60340ba4bf8aeeef2cc616c0ef48cf7 100644 --- a/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h +++ b/Framework/CurveFitting/test/Functions/CrystalFieldSpectrumTest.h @@ -752,8 +752,8 @@ public: TS_ASSERT_DELTA(fun->getParameter(46), 0, 1e-2); TS_ASSERT_DELTA(fun->getParameter(48), 1.1, 1e-2); TS_ASSERT(fun->isActive(36)); - TS_ASSERT(!fun->isActive(39)); - TS_ASSERT(!fun->isActive(42)); + TS_ASSERT(fun->isActive(39)); + TS_ASSERT(fun->isActive(42)); TS_ASSERT(!fun->isActive(45)); TS_ASSERT(!fun->isActive(48)); } @@ -787,7 +787,7 @@ public: TS_ASSERT(fun->isActive(36)); TS_ASSERT(!fun->isActive(39)); - TS_ASSERT(!fun->isActive(42)); + TS_ASSERT(fun->isActive(42)); TS_ASSERT(!fun->isActive(45)); TS_ASSERT(!fun->isActive(48)); @@ -832,7 +832,7 @@ public: TS_ASSERT(fun->isActive(36)); TS_ASSERT(!fun->isActive(39)); - TS_ASSERT(!fun->isActive(42)); + TS_ASSERT(fun->isActive(42)); TS_ASSERT(!fun->isActive(45)); TS_ASSERT(!fun->isActive(48)); diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadEventNexus.h b/Framework/DataHandling/inc/MantidDataHandling/LoadEventNexus.h index c4cf7bab0f1e75bc429bb55da4082d2d3fcd2cfc..090ff29c9ffc7c2b7a2aca341384f191219983f8 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadEventNexus.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadEventNexus.h @@ -100,6 +100,14 @@ public: bool returnpulsetimes, int &nPeriods, std::unique_ptr> &periodLog); + template + static std::shared_ptr runLoadNexusLogs( + const std::string &nexusfilename, T localWorkspace, Algorithm &alg, + bool returnpulsetimes, int &nPeriods, + std::unique_ptr> &periodLog, + const std::vector &allow_list, + const std::vector &block_list); + static void checkForCorruptedPeriods( std::unique_ptr> tempPeriodLog, std::unique_ptr> &periodLog, diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadILLDiffraction.h b/Framework/DataHandling/inc/MantidDataHandling/LoadILLDiffraction.h index f8ba124029141c2e317099d7c4e9d8a91ea0d527..dabe498c41a19b24f0153381628863752b017b3c 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadILLDiffraction.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadILLDiffraction.h @@ -64,7 +64,7 @@ private: void fillDataScanMetaData(const NeXus::NXDouble &); void fillMovingInstrumentScan(const NeXus::NXUInt &, const NeXus::NXDouble &); void fillStaticInstrumentScan(const NeXus::NXUInt &, const NeXus::NXDouble &, - const NeXus::NXFloat &); + const double &); std::vector getAbsoluteTimes(const NeXus::NXDouble &) const; @@ -80,7 +80,7 @@ private: getScannedVaribleByPropertyName(const NeXus::NXDouble &scan, const std::string &propertyName) const; - void initStaticWorkspace(); + void initStaticWorkspace(const std::string &start_time); void initMovingWorkspace(const NeXus::NXDouble &scan, const std::string &start_time); diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadILLPolarizedDiffraction.h b/Framework/DataHandling/inc/MantidDataHandling/LoadILLPolarizedDiffraction.h index 92af4137d3ce88fbc5d0adf37c1eb756bade00a0..5836f566a9c93d80ac0cf1f2274a42af54fe810d 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadILLPolarizedDiffraction.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadILLPolarizedDiffraction.h @@ -46,7 +46,7 @@ private: void loadData(); void loadMetaData(); - void loadInstrument(API::MatrixWorkspace_sptr); + void loadInstrument(API::MatrixWorkspace_sptr, const std::string &); std::vector loadTwoThetaDetectors(const API::MatrixWorkspace_sptr, const NeXus::NXEntry &, const int); std::vector loadBankParameters(const API::MatrixWorkspace_sptr, diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h b/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h index f663f58ddf13c641aaa8164ed051335c546e186d..869f21894000c4c17b908fe76b2d6c6972f7ed2f 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadILLSANS.h @@ -59,18 +59,18 @@ private: const std::string &); void initWorkSpace(NeXus::NXEntry &, const std::string &); + void initWorkSpaceD11B(NeXus::NXEntry &, const std::string &); + void initWorkSpaceD22B(NeXus::NXEntry &, const std::string &); void initWorkSpaceD33(NeXus::NXEntry &, const std::string &); void initWorkSpaceD16(NeXus::NXEntry &, const std::string &); void createEmptyWorkspace(const size_t, const size_t); - size_t loadDataIntoWorkspaceFromMonitors(NeXus::NXEntry &firstEntry, - size_t firstIndex = 0); - size_t loadDataIntoWorkspaceFromVerticalTubes(NeXus::NXInt &, - const std::vector &, - size_t); + size_t loadDataFromMonitors(NeXus::NXEntry &firstEntry, + size_t firstIndex = 0); + size_t loadDataFromTubes(NeXus::NXInt &, const std::vector &, size_t); void runLoadInstrument(); void moveDetectorsD33(const DetectorPosition &); - void moveDetectorDistance(double, const std::string &); + void moveDetectorDistance(double distance, const std::string &componentName); void moveDetectorHorizontal(double, const std::string &); void moveDetectorVertical(double, const std::string &); Kernel::V3D getComponentPosition(const std::string &componentName); @@ -81,7 +81,7 @@ private: void adjustTOF(); void moveSource(); - LoadHelper m_loader; ///< Load helper for metadata + LoadHelper m_loadHelper; ///< Load helper for metadata std::string m_instrumentName; ///< Name of the instrument std::vector m_supportedInstruments; ///< List of supported instruments @@ -90,10 +90,9 @@ private: std::string m_resMode; ///< Resolution mode for D11 and D22 bool m_isTOF; ///< TOF or monochromatic flag double m_sourcePos; ///< Source Z (for D33 TOF) - bool m_isD16Omega; + bool m_isD16Omega; ///< Data come from a D16 omega scan flag void setFinalProperties(const std::string &filename); - void setPixelSize(); std::vector getVariableTimeBinning(const NeXus::NXEntry &, const std::string &, const NeXus::NXInt &, diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadMuonStrategy.h b/Framework/DataHandling/inc/MantidDataHandling/LoadMuonStrategy.h index 2c232ea3293c5d794aec2d96037bb924c735b3e0..fb963af45a3b01203ab3282e4f669f32bcdcd4c3 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadMuonStrategy.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadMuonStrategy.h @@ -14,6 +14,11 @@ namespace Mantid { namespace DataHandling { class LoadMuonNexusV2NexusHelper; + +// Create time zero table +DataObjects::TableWorkspace_sptr +createTimeZeroTable(const size_t numSpec, const std::vector &timeZeros); + class DLLExport LoadMuonStrategy { public: // Constructor @@ -31,6 +36,8 @@ public: virtual API::Workspace_sptr loadDetectorGrouping() const = 0; // Load dead time table virtual API::Workspace_sptr loadDeadTimeTable() const = 0; + // Get time zero table + virtual API::Workspace_sptr getTimeZeroTable() = 0; protected: // Create grouping table diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadNexusLogs.h b/Framework/DataHandling/inc/MantidDataHandling/LoadNexusLogs.h index 1e78cbf9398361a54c0d8df7fac77cb8082f7b0a..5634c38b4c7deff08023aad80afa17167296aa76 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadNexusLogs.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadNexusLogs.h @@ -74,7 +74,9 @@ private: */ void loadLogs(::NeXus::File &file, const std::string &absolute_entry_name, const std::string &entry_class, - const std::shared_ptr &workspace) const; + const std::shared_ptr &workspace, + const std::vector &allow_list, + const std::vector &block_list) const; /** * Load an NXlog entry diff --git a/Framework/DataHandling/inc/MantidDataHandling/LoadRawHelper.h b/Framework/DataHandling/inc/MantidDataHandling/LoadRawHelper.h index d94295453beb2632d2e6d763b5d1a16341b660b7..2a1816b7bb0ed6573da28a40efc0e5e5af1967bf 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/LoadRawHelper.h +++ b/Framework/DataHandling/inc/MantidDataHandling/LoadRawHelper.h @@ -10,8 +10,12 @@ #include "MantidAPI/Run.h" #include "MantidAPI/WorkspaceGroup_fwd.h" #include "MantidDataHandling/ISISRunLogs.h" -#include "MantidDataObjects/Workspace2D.h" +#include "MantidDataObjects/Workspace2D_fwd.h" +#include "MantidGeometry/IDTypes.h" +#include "MantidHistogramData/HistogramX.h" #include "MantidKernel/FileDescriptor.h" +#include "MantidTypes/Core/DateAndTime.h" +#include "MantidTypes/SpectrumDefinition.h" #include #include @@ -20,6 +24,10 @@ class ISISRAW; class ISISRAW2; +namespace Poco { +class Path; +} + namespace Mantid { namespace API { class SpectrumDetectorMapping; @@ -27,13 +35,8 @@ class SpectrumDetectorMapping; namespace DataHandling { /** @class LoadRawHelper DataHandling/LoadRawHelper.h - -Helper class for LoadRaw algorithms. - - -@author Sofia Antony, ISIS,RAL -@date 14/04/2010 -*/ + * Helper class for LoadRaw algorithms. + */ class DLLExport LoadRawHelper : public API::IFileLoader { public: @@ -97,7 +100,8 @@ public: const std::string &title, const API::WorkspaceGroup_sptr &grpws_sptr, const DataObjects::Workspace2D_sptr &ws_sptr, - int64_t numberOfPeriods, bool bMonitor, + int64_t numberOfPeriods, + [[maybe_unused]] bool bMonitor, API::Algorithm *const pAlg); /// overloaded method to set the workspace property @@ -115,8 +119,6 @@ public: protected: /// Overwrites Algorithm method. void init() override; - /// checks the file is an ascii file - bool isAscii(FILE *file) const; /// Reads title from the isisraw class void readTitle(FILE *file, std::string &title); /// reads workspace parameters like number of histograms,size of vectors etc @@ -242,16 +244,9 @@ private: /// Search for the log files in the workspace, and output their names as a /// set. - std::list searchForLogFiles(const std::string &pathToRawFile); + std::list searchForLogFiles(const Poco::Path &pathToRawFile); /// Extract the log name from the path to the specific log file. std::string extractLogName(const std::string &path); - /// Checks if the file is an ASCII file - bool isAscii(const std::string &filename); - /// if alternate data stream named checksum exists for the raw file - bool adsExists(const std::string &pathToFile); - /// returns the list of log files from ADS checksum - std::set - getLogFilenamesfromADS(const std::string &pathToRawFile); }; } // namespace DataHandling diff --git a/Framework/DataHandling/inc/MantidDataHandling/MultiPeriodLoadMuonStrategy.h b/Framework/DataHandling/inc/MantidDataHandling/MultiPeriodLoadMuonStrategy.h index 6f22cc5471a54360b42fb7ac40355f532c679112..914f8f86ca1db4dcd52c0520f0ea78c602f39109 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/MultiPeriodLoadMuonStrategy.h +++ b/Framework/DataHandling/inc/MantidDataHandling/MultiPeriodLoadMuonStrategy.h @@ -27,6 +27,8 @@ public: API::Workspace_sptr loadDetectorGrouping() const override; // Load dead time table API::Workspace_sptr loadDeadTimeTable() const override; + // Get time zero table + API::Workspace_sptr getTimeZeroTable() override; private: API::WorkspaceGroup &m_workspaceGroup; diff --git a/Framework/DataHandling/inc/MantidDataHandling/PrecompiledHeader.h b/Framework/DataHandling/inc/MantidDataHandling/PrecompiledHeader.h index 2574231796d040dedc544959befa972012c016eb..06e0bb55af5af3b7dbab7cf2ab0dff2ef720de63 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/PrecompiledHeader.h +++ b/Framework/DataHandling/inc/MantidDataHandling/PrecompiledHeader.h @@ -6,11 +6,21 @@ // SPDX - License - Identifier: GPL - 3.0 + #pragma once -// Algorithm #include "MantidAPI/Algorithm.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidDataObjects/Workspace2D.h" +#include "MantidGeometry/Instrument.h" + +// STL // NeXus // clang-format off #include #include // clang-format on + +// Poco +#include +#include +#include +#include \ No newline at end of file diff --git a/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h b/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h index b8127e26aa95e10c422417e82b9edf56d600b3f6..9d6080f6dc0b8a101ad946f8ab228e281fffdb64 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h +++ b/Framework/DataHandling/inc/MantidDataHandling/ReadMaterial.h @@ -61,6 +61,8 @@ public: double scatteringXSection = EMPTY_DBL(); /// The name or path of a file containing an attenuation profile std::string attenuationProfileFileName = ""; + /// The name or path of a file containing an x ray attenuation profile + std::string xRayAttenuationProfileFileName = ""; /// A flag indicating the unit of sampleNumberDensity Kernel::MaterialBuilder::NumberDensityUnit numberDensityUnit = Kernel::MaterialBuilder::NumberDensityUnit::Atoms; @@ -104,7 +106,8 @@ private: const double zParameter, const double unitCellVolume); void setScatteringInfo(double coherentXSection, double incoherentXSection, double attenuationXSection, double scatteringXSection, - std::string attenuationProfileFileName); + std::string attenuationProfileFileName, + std::string xRayAttenuationProfileFileName); static bool isEmpty(const double toCheck); }; diff --git a/Framework/DataHandling/inc/MantidDataHandling/SaveDiffCal.h b/Framework/DataHandling/inc/MantidDataHandling/SaveDiffCal.h index 31208c7163e9e419bf0a499bc6759d1b05b6d1c1..3ea592a1fd049f927a7d952299886e7b3887b2ab 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SaveDiffCal.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SaveDiffCal.h @@ -43,6 +43,8 @@ private: void generateDetidToIndex(); bool tableHasColumn(const std::string &ColumnName) const; + // minimum of (CalibrationWorkspace_row_count, + // GroupingWorkspace_histogram_count, MaskWorkspace_histogram_count) std::size_t m_numValues{0}; API::ITableWorkspace_sptr m_calibrationWS; std::map m_detidToIndex; diff --git a/Framework/DataHandling/inc/MantidDataHandling/SinglePeriodLoadMuonStrategy.h b/Framework/DataHandling/inc/MantidDataHandling/SinglePeriodLoadMuonStrategy.h index 3c01f2909b4f9a502313e10b67bb988ba9f9fa3f..904fa5235f8756d1d3b4206c835220cfe0c0d309 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SinglePeriodLoadMuonStrategy.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SinglePeriodLoadMuonStrategy.h @@ -29,6 +29,8 @@ public: API::Workspace_sptr loadDetectorGrouping() const override; // Load dead time table API::Workspace_sptr loadDeadTimeTable() const override; + // Get time zero table + API::Workspace_sptr getTimeZeroTable() override; private: DataObjects::Workspace2D &m_workspace; diff --git a/Framework/DataHandling/src/DownloadInstrument.cpp b/Framework/DataHandling/src/DownloadInstrument.cpp index 457350b7ed8107b490fd41c9f5f64df5f36071a5..0b7e5569e32c8beb36fa6e706a6a4a3025a5a9aa 100644 --- a/Framework/DataHandling/src/DownloadInstrument.cpp +++ b/Framework/DataHandling/src/DownloadInstrument.cpp @@ -212,6 +212,14 @@ DownloadInstrument::StringToStringMap DownloadInstrument::processRepository() { Poco::Path localRepoFile(localPath, "local.json"); StringToStringMap localShas = getFileShas(localPath.toString()); + // verify repo info was downloaded correctly + if (gitHubJsonFile.getSize() == 0) { + std::stringstream msg; + msg << "Encountered empty file \"" << gitHubJson.toString() + << "\" while determining what to download"; + throw std::runtime_error(msg.str()); + } + // Parse the server JSON response Json::Reader reader; Json::Value serverContents; diff --git a/Framework/DataHandling/src/EventWorkspaceCollection.cpp b/Framework/DataHandling/src/EventWorkspaceCollection.cpp index 95467f8b94dc7f74063839e4375bb2051a1045aa..6608433bdccfd04009970cdf8d7a64b90bd7899c 100644 --- a/Framework/DataHandling/src/EventWorkspaceCollection.cpp +++ b/Framework/DataHandling/src/EventWorkspaceCollection.cpp @@ -8,6 +8,7 @@ #include "MantidAPI/Axis.h" #include "MantidAPI/Run.h" #include "MantidAPI/Sample.h" +#include "MantidDataHandling/ISISRunLogs.h" #include "MantidDataObjects/EventWorkspace.h" #include "MantidDataObjects/WorkspaceCreation.h" #include "MantidGeometry/Instrument.h" @@ -87,26 +88,16 @@ void EventWorkspaceCollection::setNPeriods( periodNumbers.end()); const bool addBoolTimeSeries = (uniquePeriods.size() == nPeriods); + auto logCreator = ISISRunLogs(temp->run()); + logCreator.addStatusLog(temp->mutableRun()); + for (size_t i = 0; i < m_WsVec.size(); ++i) { const auto periodNumber = int(i + 1); m_WsVec[i] = createEmptyEventWorkspace(); m_WsVec[i]->copyExperimentInfoFrom(temp.get()); if (addBoolTimeSeries) { - std::stringstream buffer; - buffer << "period " << periodNumber; - auto *periodBoolLog = new Kernel::TimeSeriesProperty(buffer.str()); - for (int j = 0; j < int(periodLog->size()); ++j) { - periodBoolLog->addValue(periodLog->nthTime(j), - periodNumber == periodLog->nthValue(j)); - } - Run &mutableRun = m_WsVec[i]->mutableRun(); - mutableRun.addProperty(periodBoolLog); - - Kernel::PropertyWithValue *currentPeriodProperty = - new Kernel::PropertyWithValue("current_period", periodNumber); - mutableRun.addProperty(currentPeriodProperty); + logCreator.addPeriodLogs(periodNumber, m_WsVec[i]->mutableRun()); } - copyLogs( temp, m_WsVec[i]); // Copy all logs from dummy workspace to period workspaces. diff --git a/Framework/DataHandling/src/GroupDetectors2.cpp b/Framework/DataHandling/src/GroupDetectors2.cpp index 23b4ac5d6be2f060fadc7999ffcbd56f051df3ed..4439fa6be3bc048e99dead1bce17945f9f8f8e16 100644 --- a/Framework/DataHandling/src/GroupDetectors2.cpp +++ b/Framework/DataHandling/src/GroupDetectors2.cpp @@ -113,6 +113,9 @@ void GroupDetectors2::init() { exts), "A file that consists of lists of spectra numbers to group. See the " "help for the file format"); + declareProperty( + std::make_unique>("ExcludeGroupNumbers"), + "An array of group IDs to exclude when reading from an XML file."); declareProperty( "IgnoreGroupNumber", true, "If true, use sequential spectrum numbers, otherwise use the group " @@ -569,6 +572,16 @@ void GroupDetectors2::processXMLFile( std::map> mGroupSpectraMap = loader.getGroupSpectraMap(); + const std::vector groupIDsToExclude = getProperty("ExcludeGroupNumbers"); + for (const auto &groupID : groupIDsToExclude) { + const auto detectorIter = mGroupDetectorsMap.find(groupID); + if (detectorIter != mGroupDetectorsMap.cend()) + mGroupDetectorsMap.erase(detectorIter); + const auto spectraIter = mGroupSpectraMap.find(groupID); + if (spectraIter != mGroupSpectraMap.cend()) + mGroupSpectraMap.erase(spectraIter); + } + // 3. Build m_GroupWsInds for (const auto &det : mGroupDetectorsMap) { m_GroupWsInds.emplace(det.first, std::vector()); @@ -1035,7 +1048,7 @@ GroupDetectors2::formGroups(const API::MatrixWorkspace_const_sptr &inputWS, if (originalWI < 0) continue; - spectrumGroups.emplace_back(std::vector(1, originalWI)); + spectrumGroups.emplace_back(1, originalWI); auto spectrumNumber = inputWS->getSpectrum(originalWI).getSpectrumNo(); spectrumNumbers.emplace_back(spectrumNumber); diff --git a/Framework/DataHandling/src/LoadDiffCal.cpp b/Framework/DataHandling/src/LoadDiffCal.cpp index 84b20d2ad3296dadd481b725433ae0f9bfd09db1..b28e4399d40daf2556a1b481fc85c4b27187cd6f 100644 --- a/Framework/DataHandling/src/LoadDiffCal.cpp +++ b/Framework/DataHandling/src/LoadDiffCal.cpp @@ -249,10 +249,11 @@ void LoadDiffCal::makeMaskWorkspace(const std::vector &detids, wksp->mutableRun().addProperty("Filename", m_filename); for (size_t i = 0; i < numDet; ++i) { - bool shouldUse = (use[i] > 0); + bool shouldUse = (use[i] > 0); // true if detector is calibrated auto detid = static_cast(detids[i]); // in maskworkspace 0=use, 1=dontuse wksp->setMasked(detid, !shouldUse); + // The mask value is 0 if the detector is good for use wksp->setValue(detid, (shouldUse ? 0. : 1.)); progress.report(); } @@ -539,7 +540,7 @@ void LoadDiffCal::exec() { if (groups.empty()) groups.assign(detids.size(), 1); // all go to one spectrum if (use.empty()) - use.assign(detids.size(), 1); // use everything + use.assign(detids.size(), 1); // all detectors are good, use them if (difa.empty()) difa.assign(detids.size(), 0.); // turn off difa if (tzero.empty()) diff --git a/Framework/DataHandling/src/LoadEventNexus.cpp b/Framework/DataHandling/src/LoadEventNexus.cpp index 8ede0ae01472a64907cb738e379f76e8cb8ec211..b31657335d2590ce01e5947cc59b121c94dd8ad3 100644 --- a/Framework/DataHandling/src/LoadEventNexus.cpp +++ b/Framework/DataHandling/src/LoadEventNexus.cpp @@ -307,6 +307,18 @@ void LoadEventNexus::init() { "The number of bins intially defined. Use Rebin to change " "the binning later. If there is no data loaded, or you " "select meta data only you will only get 1 bin."); + + // Flexible log loading + declareProperty( + std::make_unique>>( + "AllowList", std::vector(), Direction::Input), + "If specified, only these logs will be loaded from the file (each " + "separated by a space)."); + declareProperty( + std::make_unique>>( + "BlockList", std::vector(), Direction::Input), + "If specified, these logs will NOT be loaded from the file (each " + "separated by a space)."); } //---------------------------------------------------------------------------------------------- @@ -523,7 +535,7 @@ std::size_t numEvents(::NeXus::File &file, bool &hasTotalCounts, return numEvents; } -/** Load the instrument from the nexus file +/** Load the log from the nexus file * * @param nexusfilename :: The name of the nexus file being loaded * @param localWorkspace :: Templated workspace in which to put the instrument @@ -625,6 +637,115 @@ std::shared_ptr LoadEventNexus::runLoadNexusLogs( return out; } +/** Load the log from the nexus file + * + * @param nexusfilename :: The name of the nexus file being loaded + * @param localWorkspace :: Templated workspace in which to put the instrument + *geometry + * @param alg :: Handle of the algorithm + * @param returnpulsetimes :: flag to return shared pointer for + *BankPulseTimes, otherwise NULL. + * @param nPeriods : Number of periods (write to) + * @param periodLog : Period logs DateAndTime to int map. + * @param allow_list: list of properties that will be loaded + * @param block_list: list of properties that will be excluded from loading + * + * @return Pulse times given in the DAS logs + */ +template +std::shared_ptr LoadEventNexus::runLoadNexusLogs( + const std::string &nexusfilename, T localWorkspace, API::Algorithm &alg, + bool returnpulsetimes, int &nPeriods, + std::unique_ptr> &periodLog, + const std::vector &allow_list, + const std::vector &block_list) { + // --------------------- Load DAS Logs ----------------- + // The pulse times will be empty if not specified in the DAS logs. + // BankPulseTimes * out = NULL; + std::shared_ptr out; + API::IAlgorithm_sptr loadLogs = alg.createChildAlgorithm("LoadNexusLogs"); + + // Now execute the Child Algorithm. Catch and log any error, but don't stop. + try { + alg.getLogger().information() << "Loading logs from NeXus file..." + << "\n"; + loadLogs->setPropertyValue("Filename", nexusfilename); + loadLogs->setProperty("Workspace", + localWorkspace); + loadLogs->setProperty>("AllowList", allow_list); + loadLogs->setProperty>("BlockList", block_list); + + try { + loadLogs->setPropertyValue("NXentryName", + alg.getPropertyValue("NXentryName")); + } catch (...) { + } + + loadLogs->execute(); + + const Run &run = localWorkspace->run(); + // Get the number of periods + if (run.hasProperty("nperiods")) { + nPeriods = run.getPropertyValueAsType("nperiods"); + } + // Get the period log. Map of DateAndTime to Period int values. + if (run.hasProperty("period_log")) { + auto *temp = run.getProperty("period_log"); + // Check for corrupted period logs + std::unique_ptr> tempPeriodLog( + dynamic_cast *>(temp->clone())); + checkForCorruptedPeriods(std::move(tempPeriodLog), periodLog, nPeriods, + nexusfilename); + } + + // If successful, we can try to load the pulse times + std::vector temp; + if (localWorkspace->run().hasProperty("proton_charge")) { + auto *log = dynamic_cast *>( + localWorkspace->mutableRun().getProperty("proton_charge")); + if (log) + temp = log->timesAsVector(); + } + if (returnpulsetimes) + out = std::make_shared(temp); + + // Use the first pulse as the run_start time. + if (!temp.empty()) { + if (temp[0] < Types::Core::DateAndTime("1991-01-01T00:00:00")) + alg.getLogger().warning() << "Found entries in the proton_charge " + "sample log with invalid pulse time!\n"; + + Types::Core::DateAndTime run_start = localWorkspace->getFirstPulseTime(); + // add the start of the run as a ISO8601 date/time string. The start = + // first non-zero time. + // (this is used in LoadInstrument to find the right instrument file to + // use). + localWorkspace->mutableRun().addProperty( + "run_start", run_start.toISO8601String(), true); + } else { + alg.getLogger().warning() << "Empty proton_charge sample log. You will " + "not be able to filter by time.\n"; + } + /// Attempt to make a gonoimeter from the logs + try { + Geometry::Goniometer gm; + gm.makeUniversalGoniometer(); + localWorkspace->mutableRun().setGoniometer(gm, true); + } catch (std::runtime_error &) { + } + } catch (const InvalidLogPeriods &) { + // Rethrow so LoadEventNexus fails. + // If we don't, Mantid will crash. + throw; + } catch (...) { + alg.getLogger().error() << "Error while loading Logs from SNS Nexus. Some " + "sample logs may be missing." + << "\n"; + return out; + } + return out; +} + /** Check for corrupted period logs * If data is historical (1 periods, period is labelled 0) then change period * labels to 1 If number of periods does not match expected number of periods @@ -701,6 +822,38 @@ LoadEventNexus::runLoadNexusLogs( return ret; } +/** Load the instrument from the nexus file + * + * @param nexusfilename :: The name of the nexus file being loaded + * @param localWorkspace :: EventWorkspaceCollection in which to put the + *instrument + *geometry + * @param alg :: Handle of the algorithm + * @param returnpulsetimes :: flag to return shared pointer for + *BankPulseTimes, otherwise NULL. + * @param nPeriods : Number of periods (write to) + * @param periodLog : Period logs DateAndTime to int map. + * @param allow_list: log entry that will be loaded + * @param block_list: log entry that will be excluded + * + * @return Pulse times given in the DAS logs + */ +template <> +std::shared_ptr +LoadEventNexus::runLoadNexusLogs( + const std::string &nexusfilename, + EventWorkspaceCollection_sptr localWorkspace, API::Algorithm &alg, + bool returnpulsetimes, int &nPeriods, + std::unique_ptr> &periodLog, + const std::vector &allow_list, + const std::vector &block_list) { + auto ws = localWorkspace->getSingleHeldWorkspace(); + auto ret = runLoadNexusLogs( + nexusfilename, ws, alg, returnpulsetimes, nPeriods, periodLog, allow_list, + block_list); + return ret; +} + enum class LoadEventNexus::LoaderType { MPI, MULTIPROCESS, DEFAULT }; //----------------------------------------------------------------------------- @@ -721,6 +874,10 @@ void LoadEventNexus::loadEvents(API::Progress *const prog, // Get the time filters setTimeFilters(monitors); + // Get the log filter if provided + std::vector allow_list = getProperty("AllowList"); + std::vector block_list = getProperty("BlockList"); + // The run_start will be loaded from the pulse times. DateAndTime run_start(0, 0); bool takeTimesFromEvents = false; @@ -732,8 +889,14 @@ void LoadEventNexus::loadEvents(API::Progress *const prog, if (loadlogs) { prog->doReport("Loading DAS logs"); - m_allBanksPulseTimes = runLoadNexusLogs( - m_filename, m_ws, *this, true, nPeriods, periodLog); + if (allow_list.empty() && block_list.empty()) { + m_allBanksPulseTimes = runLoadNexusLogs( + m_filename, m_ws, *this, true, nPeriods, periodLog); + } else { + m_allBanksPulseTimes = runLoadNexusLogs( + m_filename, m_ws, *this, true, nPeriods, periodLog, allow_list, + block_list); + } try { run_start = m_ws->getFirstPulseTime(); diff --git a/Framework/DataHandling/src/LoadHelper.cpp b/Framework/DataHandling/src/LoadHelper.cpp index 35adfd050597003a62f1a14265258b6e6aa7c44c..88ae4cf954de78e4068f1563388f9df096a06ffc 100644 --- a/Framework/DataHandling/src/LoadHelper.cpp +++ b/Framework/DataHandling/src/LoadHelper.cpp @@ -267,8 +267,14 @@ void LoadHelper::recurseAndAddNexusFieldsToWsRun(NXhandle nxfileID, if (boost::algorithm::ends_with(property_name, "_time")) { // That's a time value! Convert to Mantid standard property_value = dateTimeInIsoFormat(property_value); + if (runDetails.hasProperty(property_name)) + runDetails.getProperty(property_name) + ->setValue(property_value); + else + runDetails.addProperty(property_name, property_value); + } else { + runDetails.addProperty(property_name, property_value); } - runDetails.addProperty(property_name, property_value); } else if ((type == NX_FLOAT32) || (type == NX_FLOAT64) || (type == NX_INT16) || (type == NX_INT32) || diff --git a/Framework/DataHandling/src/LoadIDFFromNexus.cpp b/Framework/DataHandling/src/LoadIDFFromNexus.cpp index e4110c861240910b55f90ff5f918f46c64ee40c7..461d17602b80b90d9d8f8d96cfbb3d6b2d1d6ba6 100644 --- a/Framework/DataHandling/src/LoadIDFFromNexus.cpp +++ b/Framework/DataHandling/src/LoadIDFFromNexus.cpp @@ -139,12 +139,7 @@ void LoadIDFFromNexus::exec() { g_log.notice() << "Using correction parameter file: " << corrParamFile.toString() << " to replace parameters.\n"; } - bool ok = loadParameterFile(corrParamFile.toString(), localWorkspace); - if (!ok) { - g_log.error() << "Unable to load parameter file " - << corrParamFile.toString() << " specified in " - << corrFilePath.toString() << ".\n"; - } + loadParameterFile(corrParamFile.toString(), localWorkspace); } else { g_log.notice() << "No correction parameter file applies to the date for " "correction file.\n"; @@ -326,11 +321,16 @@ bool LoadIDFFromNexus::loadParameterFile( g_log.notice() << "Instrument parameter file: " << fullPathName << " has been loaded.\n\n"; return true; // Success - } catch (std::runtime_error &) { - g_log.debug() << "Instrument parameter file: " << fullPathName - << " not found or un-parsable.\n"; - return false; // Failure + } catch (std::invalid_argument &e) { + g_log.information( + "LoadParameterFile: No parameter file found for this instrument"); + g_log.information(e.what()); + } catch (std::runtime_error &e) { + g_log.information( + "Unable to successfully run LoadParameterFile Child Algorithm"); + g_log.information(e.what()); } + return false; } } // namespace DataHandling diff --git a/Framework/DataHandling/src/LoadILLDiffraction.cpp b/Framework/DataHandling/src/LoadILLDiffraction.cpp index 03d2c6a3ea19b4e395f671eca7071b2e0fdc4317..47cb4a0ed97f74b039c875ae3b20fb7a40a286e1 100644 --- a/Framework/DataHandling/src/LoadILLDiffraction.cpp +++ b/Framework/DataHandling/src/LoadILLDiffraction.cpp @@ -58,7 +58,9 @@ DECLARE_NEXUS_FILELOADER_ALGORITHM(LoadILLDiffraction) int LoadILLDiffraction::confidence(NexusDescriptor &descriptor) const { // fields existent only at the ILL Diffraction - if (descriptor.pathExists("/entry0/instrument/2theta")) { + // the second one is to recognize D1B + if (descriptor.pathExists("/entry0/instrument/2theta") || + descriptor.pathExists("/entry0/instrument/Canne")) { return 80; } else { return 0; @@ -87,7 +89,7 @@ const std::string LoadILLDiffraction::summary() const { * Constructor */ LoadILLDiffraction::LoadILLDiffraction() - : IFileLoader(), m_instNames({"D20", "D2B"}) {} + : IFileLoader(), m_instNames({"D20", "D2B", "D1B"}) {} /** * Initialize the algorithm's properties. @@ -105,6 +107,8 @@ void LoadILLDiffraction::init() { "Select the type of data, with or without calibration " "already applied. If Auto then the calibrated data is " "loaded if available, otherwise the raw data is loaded."); + declareProperty("TwoThetaOffset", 0.0, + "2 theta offset for D1B data, in degrees."); declareProperty( std::make_unique>("AlignTubes", true, Direction::Input), @@ -195,8 +199,20 @@ void LoadILLDiffraction::loadDataScan() { axis.load(); // read the starting two theta - NXFloat twoTheta0 = firstEntry.openNXFloat("instrument/2theta/value"); - twoTheta0.load(); + double twoThetaValue; + if (m_instName == "D1B") { + if (getPointerToProperty("TwoThetaOffset")->isDefault()) { + g_log.notice("A 2theta offset angle is necessary for D1B data."); + twoThetaValue = 0; + } else { + twoThetaValue = getProperty("TwoThetaOffset"); + } + } else { + std::string twoThetaPath = "instrument/2theta/value"; + NXFloat twoTheta0 = firstEntry.openNXFloat(twoThetaPath); + twoTheta0.load(); + twoThetaValue = double(twoTheta0[0]); + } // figure out the dimensions m_sizeDim1 = static_cast(data.dim1()); @@ -222,8 +238,8 @@ void LoadILLDiffraction::loadDataScan() { initMovingWorkspace(scan, start_time); fillMovingInstrumentScan(data, scan); } else { - initStaticWorkspace(); - fillStaticInstrumentScan(data, scan, twoTheta0); + initStaticWorkspace(start_time); + fillStaticInstrumentScan(data, scan, twoThetaValue); } fillDataScanMetaData(scan); @@ -260,8 +276,10 @@ void LoadILLDiffraction::loadMetaData() { /** * Initializes the output workspace based on the resolved instrument, scan * points, and scan type + * + * @param start_time :: the date the run started, in ISO compliant format */ -void LoadILLDiffraction::initStaticWorkspace() { +void LoadILLDiffraction::initStaticWorkspace(const std::string &start_time) { size_t nSpectra = m_numberDetectorsActual + NUMBER_MONITORS; size_t nBins = 1; @@ -272,6 +290,9 @@ void LoadILLDiffraction::initStaticWorkspace() { } m_outWorkspace = WorkspaceFactory::Instance().create("Workspace2D", nSpectra, nBins, nBins); + + // the start time is needed in the workspace when loading the parameter file + m_outWorkspace->mutableRun().addProperty("start_time", start_time); } /** @@ -528,7 +549,7 @@ void LoadILLDiffraction::fillMovingInstrumentScan(const NXUInt &data, */ void LoadILLDiffraction::fillStaticInstrumentScan(const NXUInt &data, const NXDouble &scan, - const NXFloat &twoTheta0) { + const double &twoTheta0) { const std::vector axis = getAxis(scan); const std::vector monitor = getMonitor(scan); @@ -577,7 +598,7 @@ void LoadILLDiffraction::fillStaticInstrumentScan(const NXUInt &data, } // Move to the starting 2theta - moveTwoThetaZero(double(twoTheta0[0])); + moveTwoThetaZero(twoTheta0); } /** @@ -827,7 +848,10 @@ LoadILLDiffraction::loadEmptyInstrument(const std::string &start_time) { loadInst->setPropertyValue("InstrumentName", m_instName); auto ws = WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1); auto &run = ws->mutableRun(); - run.addProperty("run_start", start_time); + + // the start time is needed in the workspace when loading the parameter file + run.addProperty("start_time", start_time); + loadInst->setProperty("Workspace", ws); loadInst->setProperty("RewriteSpectraMap", OptionalBool(true)); loadInst->execute(); diff --git a/Framework/DataHandling/src/LoadILLPolarizedDiffraction.cpp b/Framework/DataHandling/src/LoadILLPolarizedDiffraction.cpp index d382b860cb1ef2dade9351b0f0d1b70ebc49d33a..62004982682ab74874aca00d600e837ba022dd7b 100644 --- a/Framework/DataHandling/src/LoadILLPolarizedDiffraction.cpp +++ b/Framework/DataHandling/src/LoadILLPolarizedDiffraction.cpp @@ -105,16 +105,21 @@ void LoadILLPolarizedDiffraction::init() { declareProperty(std::make_unique( "YIGFilename", "", FileProperty::OptionalLoad, ".xml"), - "File path of the YIG calibration data file to load"); + "File path of the YIG calibration data file to load."); setPropertySettings("YIGFilename", std::make_unique( "PositionCalibration", IS_EQUAL_TO, "YIGFile")); declareProperty("ConvertToScatteringAngle", false, - "Convert the bin edges to scattering angle", + "Convert the bin edges to scattering angle.", Direction::Input); declareProperty("TransposeMonochromatic", false, "Transpose the 2D workspace with monochromatic data", Direction::Input); + const std::vector TOFUnitOptions{"UncalibratedTime", + "TimeChannels"}; + declareProperty("TOFUnits", TOFUnitOptions[0], + std::make_shared(TOFUnitOptions), + "The choice of X-axis units for Time-Of-Flight data."); } std::map @@ -173,7 +178,7 @@ void LoadILLPolarizedDiffraction::loadData() { auto workspace = initStaticWorkspace(entry); // load the instrument - loadInstrument(workspace); + loadInstrument(workspace, start_time); // rotate detectors to their position during measurement moveTwoTheta(entry, workspace); @@ -296,15 +301,17 @@ LoadILLPolarizedDiffraction::initStaticWorkspace(const NXEntry &entry) { // Set x axis units if (m_acquisitionMode == TOF_MODE_ON) { - auto lblUnit = std::static_pointer_cast( - UnitFactory::Instance().create("Label")); - lblUnit->setLabel("Time", Units::Symbol::Microsecond); - workspace->getAxis(0)->unit() = lblUnit; + if (getPropertyValue("TOFUnits") == "TimeChannels") { + auto lblUnit = std::dynamic_pointer_cast( + UnitFactory::Instance().create("Label")); + lblUnit->setLabel("Time channel", Units::Symbol::EmptyLabel); + workspace->getAxis(0)->unit() = lblUnit; + } else { + workspace->getAxis(0)->unit() = UnitFactory::Instance().create("TOF"); + } } else { - auto lblUnit = std::static_pointer_cast( - UnitFactory::Instance().create("Label")); - lblUnit->setLabel("Wavelength", Units::Symbol::Angstrom); - workspace->getAxis(0)->unit() = lblUnit; + workspace->getAxis(0)->unit() = + UnitFactory::Instance().create("Wavelength"); } // Set y axis unit workspace->setYUnit("Counts"); @@ -318,9 +325,14 @@ LoadILLPolarizedDiffraction::initStaticWorkspace(const NXEntry &entry) { /** * Runs LoadInstrument as child to link the instrument to workspace * @param workspace : workspace with data from the first entry + * @param startTime :: the date the run started, in ISO compliant format */ void LoadILLPolarizedDiffraction::loadInstrument( - API::MatrixWorkspace_sptr workspace) { + API::MatrixWorkspace_sptr workspace, const std::string &startTime) { + + // the start time is needed in the workspace when loading the parameter file + workspace->mutableRun().addProperty("start_time", startTime); + IAlgorithm_sptr loadInst = createChildAlgorithm("LoadInstrument"); loadInst->setPropertyValue("Filename", m_instName + "_Definition.xml"); loadInst->setProperty("Workspace", workspace); @@ -459,10 +471,15 @@ LoadILLPolarizedDiffraction::prepareAxes(const NXEntry &entry) { NXFloat timeOfFlightInfo = entry.openNXFloat("D7/Detector/time_of_flight"); timeOfFlightInfo.load(); auto channelWidth = static_cast(timeOfFlightInfo[0]); + m_numberOfChannels = size_t(timeOfFlightInfo[1]); auto tofDelay = timeOfFlightInfo[2]; for (auto channel_no = 0; channel_no <= static_cast(m_numberOfChannels); channel_no++) { - axes.push_back(static_cast(tofDelay + channel_no * channelWidth)); + if (getPropertyValue("TOFUnits") == "UncalibratedTime") { + axes.push_back(tofDelay + channel_no * channelWidth); + } else { + axes.push_back(channel_no); + } } } else { double wavelength = 0; diff --git a/Framework/DataHandling/src/LoadILLReflectometry.cpp b/Framework/DataHandling/src/LoadILLReflectometry.cpp index 28a2bc03339339a93d9ea12a835a0c98538c75be..ab630d9b92a2047f0ceb63accb7d637e7d91d689 100644 --- a/Framework/DataHandling/src/LoadILLReflectometry.cpp +++ b/Framework/DataHandling/src/LoadILLReflectometry.cpp @@ -383,6 +383,10 @@ void LoadILLReflectometry::initWorkspace( m_localWorkspace->getAxis(0)->unit() = UnitFactory::Instance().create("TOF"); m_localWorkspace->setYUnitLabel("Counts"); + + // the start time is needed in the workspace when loading the parameter file + m_localWorkspace->mutableRun().addProperty( + "start_time", m_startTime.toISO8601String()); } /** @@ -640,6 +644,7 @@ void LoadILLReflectometry::loadNexusEntriesIntoProperties() { NXstatus stat = NXopen(filename.c_str(), NXACC_READ, &nxfileID); if (stat == NX_ERROR) throw Kernel::Exception::FileError("Unable to open File:", filename); + m_loader.addNexusFieldsToWsRun(nxfileID, m_localWorkspace->mutableRun()); NXclose(&nxfileID); } diff --git a/Framework/DataHandling/src/LoadILLSANS.cpp b/Framework/DataHandling/src/LoadILLSANS.cpp index fe75a98817b87c070edca5a98ecea9decd2c7fe0..b7515228681940deb211a55c7b24f47b756d6b1d 100644 --- a/Framework/DataHandling/src/LoadILLSANS.cpp +++ b/Framework/DataHandling/src/LoadILLSANS.cpp @@ -12,6 +12,7 @@ #include "MantidAPI/RegisterFileLoader.h" #include "MantidAPI/SpectrumInfo.h" #include "MantidAPI/WorkspaceFactory.h" +#include "MantidDataHandling/LoadHelper.h" #include "MantidGeometry/IDetector.h" #include "MantidGeometry/Instrument.h" #include "MantidGeometry/Instrument/RectangularDetector.h" @@ -27,7 +28,7 @@ #include #include #include -#include // std::accumulate +#include namespace Mantid { namespace DataHandling { @@ -104,9 +105,10 @@ void LoadILLSANS::init() { "The name to use for the output workspace"); auto mustBePositive = std::make_shared>(); mustBePositive->setLower(0); - declareProperty("Wavelength", 0.0, mustBePositive, - "The wavelength of the experiment. Used only for D16. Will " - "override the nexus' value if there is one."); + declareProperty( + "Wavelength", 0.0, mustBePositive, + "The wavelength of the experiment, in angstroms. Used only for D16. Will " + "override the nexus' value if there is one."); } //---------------------------------------------------------------------------------------------- @@ -118,7 +120,7 @@ void LoadILLSANS::exec() { NXRoot root(filename); NXEntry firstEntry = root.openFirstEntry(); const std::string instrumentPath = - m_loader.findInstrumentNexusPath(firstEntry); + m_loadHelper.findInstrumentNexusPath(firstEntry); setInstrumentName(firstEntry, instrumentPath); Progress progress(this, 0.0, 1.0, 4); progress.report("Initializing the workspace for " + m_instrumentName); @@ -145,31 +147,71 @@ void LoadILLSANS::exec() { const double angle = firstEntry.getFloat(instrumentPath + "/Gamma/value"); placeD16(-angle, distance, "detector"); + } else if (m_instrumentName == "D11B") { + initWorkSpaceD11B(firstEntry, instrumentPath); + progress.report("Loading the instrument " + m_instrumentName); + runLoadInstrument(); + + // we move the parent "detector" component, but since it is at (0,0,0), we + // need to find the distance it has to move and move it to this position + double finalDistance = + firstEntry.getFloat(instrumentPath + "/detector/det_calc") / 1000.; + V3D pos = getComponentPosition("detector_center"); + double currentDistance = pos.Z(); + + moveDetectorDistance(finalDistance - currentDistance, "detector"); + API::Run &runDetails = m_localWorkspace->mutableRun(); + runDetails.addProperty("L2", finalDistance, true); + + } else if (m_instrumentName == "D22B") { + initWorkSpaceD22B(firstEntry, instrumentPath); + progress.report("Loading the instrument " + m_instrumentName); + runLoadInstrument(); + + // first we move the central detector + double distance = + firstEntry.getFloat(instrumentPath + "/Detector 2/det2_calc"); + moveDetectorDistance(distance, "detector_back"); + API::Run &runDetails = m_localWorkspace->mutableRun(); + runDetails.addProperty("L2", distance, true); + + double offset = + firstEntry.getFloat(instrumentPath + "/Detector 2/dtr2_actual"); + moveDetectorHorizontal(-offset / 1000, "detector_back"); // mm to meter + + // then the right one + distance = firstEntry.getFloat(instrumentPath + "/Detector 1/det1_calc"); + moveDetectorDistance(distance, "detector_front"); + + // mm to meter + offset = firstEntry.getFloat(instrumentPath + "/Detector 1/dtr1_actual"); + moveDetectorHorizontal(-offset / 1000, "detector_front"); + double angle = + firstEntry.getFloat(instrumentPath + "/Detector 1/dan1_actual"); + rotateInstrument(-angle, "detector_front"); + } else { + // D11 and D22 initWorkSpace(firstEntry, instrumentPath); progress.report("Loading the instrument " + m_instrumentName); runLoadInstrument(); - double distance = m_loader.getDoubleFromNexusPath( + double distance = m_loadHelper.getDoubleFromNexusPath( firstEntry, instrumentPath + "/detector/det_calc"); - progress.report("Moving detectors"); moveDetectorDistance(distance, "detector"); + API::Run &runDetails = m_localWorkspace->mutableRun(); + runDetails.addProperty("L2", distance, true); if (m_instrumentName == "D22") { - double offset = m_loader.getDoubleFromNexusPath( + double offset = m_loadHelper.getDoubleFromNexusPath( firstEntry, instrumentPath + "/detector/dtr_actual"); moveDetectorHorizontal(-offset / 1000, "detector"); // mm to meter - /*TODO: DO NOT ROTATE UNTIL CONFIRMED BY INSTRUMENT SCIENTIST - double angle = m_loader.getDoubleFromNexusPath( - firstEntry, instrumentPath + "/detector/dan_actual"); - rotateD22(angle, "detector");*/ } } progress.report("Setting sample logs"); setFinalProperties(filename); - setPixelSize(); setProperty("OutputWorkspace", m_localWorkspace); -} // namespace DataHandling +} /** * Set member variable with the instrument name @@ -177,14 +219,19 @@ void LoadILLSANS::exec() { void LoadILLSANS::setInstrumentName(const NeXus::NXEntry &firstEntry, const std::string &instrumentNamePath) { if (instrumentNamePath.empty()) { - std::string message("Cannot set the instrument name from the Nexus file!"); - g_log.error(message); - throw std::runtime_error(message); + throw std::runtime_error( + "Cannot set the instrument name from the Nexus file!"); } - m_instrumentName = - m_loader.getStringFromNexusPath(firstEntry, instrumentNamePath + "/name"); + m_instrumentName = m_loadHelper.getStringFromNexusPath( + firstEntry, instrumentNamePath + "/name"); const auto inst = std::find(m_supportedInstruments.begin(), m_supportedInstruments.end(), m_instrumentName); + + if ((m_instrumentName == "D11" || m_instrumentName == "D22") && + firstEntry.containsGroup("data1")) { + m_instrumentName += "B"; + } + if (inst == m_supportedInstruments.end()) { throw std::runtime_error( "Instrument " + m_instrumentName + @@ -202,24 +249,24 @@ LoadILLSANS::getDetectorPositionD33(const NeXus::NXEntry &firstEntry, const std::string &instrumentNamePath) { std::string detectorPath(instrumentNamePath + "/detector"); DetectorPosition pos; - pos.distanceSampleRear = - m_loader.getDoubleFromNexusPath(firstEntry, detectorPath + "/det2_calc"); - pos.distanceSampleBottomTop = - m_loader.getDoubleFromNexusPath(firstEntry, detectorPath + "/det1_calc"); + pos.distanceSampleRear = m_loadHelper.getDoubleFromNexusPath( + firstEntry, detectorPath + "/det2_calc"); + pos.distanceSampleBottomTop = m_loadHelper.getDoubleFromNexusPath( + firstEntry, detectorPath + "/det1_calc"); pos.distanceSampleRightLeft = pos.distanceSampleBottomTop + - m_loader.getDoubleFromNexusPath(firstEntry, - detectorPath + "/det1_panel_separation"); - pos.shiftLeft = m_loader.getDoubleFromNexusPath( + m_loadHelper.getDoubleFromNexusPath( + firstEntry, detectorPath + "/det1_panel_separation"); + pos.shiftLeft = m_loadHelper.getDoubleFromNexusPath( firstEntry, detectorPath + "/OxL_actual") * 1e-3; - pos.shiftRight = m_loader.getDoubleFromNexusPath( + pos.shiftRight = m_loadHelper.getDoubleFromNexusPath( firstEntry, detectorPath + "/OxR_actual") * 1e-3; - pos.shiftUp = m_loader.getDoubleFromNexusPath(firstEntry, - detectorPath + "/OyT_actual") * + pos.shiftUp = m_loadHelper.getDoubleFromNexusPath( + firstEntry, detectorPath + "/OyT_actual") * 1e-3; - pos.shiftDown = m_loader.getDoubleFromNexusPath( + pos.shiftDown = m_loadHelper.getDoubleFromNexusPath( firstEntry, detectorPath + "/OyB_actual") * 1e-3; pos >> g_log.debug(); @@ -257,13 +304,78 @@ void LoadILLSANS::initWorkSpace(NeXus::NXEntry &firstEntry, loadMetaData(firstEntry, instrumentPath); size_t nextIndex; - nextIndex = loadDataIntoWorkspaceFromVerticalTubes(data, m_defaultBinning, 0); - nextIndex = loadDataIntoWorkspaceFromMonitors(firstEntry, nextIndex); + nextIndex = loadDataFromTubes(data, m_defaultBinning, 0); + nextIndex = loadDataFromMonitors(firstEntry, nextIndex); if (data.dim1() == 128) { m_resMode = "low"; } } +/** + * @brief LoadILLSANS::initWorkSpaceD11B Load D11B data + * @param firstEntry + * @param instrumentPath + */ +void LoadILLSANS::initWorkSpaceD11B(NeXus::NXEntry &firstEntry, + const std::string &instrumentPath) { + g_log.debug("Fetching data..."); + + NXData data1 = firstEntry.openNXData("data1"); + NXInt dataCenter = data1.openIntData(); + dataCenter.load(); + NXData data2 = firstEntry.openNXData("data2"); + NXInt dataRight = data2.openIntData(); + dataRight.load(); + NXData data3 = firstEntry.openNXData("data3"); + NXInt dataLeft = data3.openIntData(); + dataLeft.load(); + + size_t numberOfHistograms = + static_cast(dataCenter.dim0() * dataCenter.dim1() + + dataRight.dim0() * dataRight.dim1() + + dataLeft.dim0() * dataLeft.dim1()) + + N_MONITORS; + + createEmptyWorkspace(numberOfHistograms, 1); + loadMetaData(firstEntry, instrumentPath); + + size_t nextIndex; + nextIndex = loadDataFromTubes(dataCenter, m_defaultBinning, 0); + nextIndex = loadDataFromTubes(dataLeft, m_defaultBinning, nextIndex); + nextIndex = loadDataFromTubes(dataRight, m_defaultBinning, nextIndex); + nextIndex = loadDataFromMonitors(firstEntry, nextIndex); +} + +/** + * @brief LoadILLSANS::initWorkSpaceD22B Load D22B data + * @param firstEntry + * @param instrumentPath + */ +void LoadILLSANS::initWorkSpaceD22B(NeXus::NXEntry &firstEntry, + const std::string &instrumentPath) { + g_log.debug("Fetching data..."); + + NXData data2 = firstEntry.openNXData("data2"); + NXInt dataCenter = data2.openIntData(); + dataCenter.load(); + NXData data1 = firstEntry.openNXData("data1"); + NXInt dataSide = data1.openIntData(); + dataSide.load(); + + size_t numberOfHistograms = + static_cast(dataCenter.dim0() * dataCenter.dim1() + + dataSide.dim0() * dataSide.dim1()) + + N_MONITORS; + + createEmptyWorkspace(numberOfHistograms, 1); + loadMetaData(firstEntry, instrumentPath); + + size_t nextIndex; + nextIndex = loadDataFromTubes(dataCenter, m_defaultBinning, 0); + nextIndex = loadDataFromTubes(dataSide, m_defaultBinning, nextIndex); + nextIndex = loadDataFromMonitors(firstEntry, nextIndex); +} + /** * Loads data for D33 */ @@ -366,16 +478,16 @@ void LoadILLSANS::initWorkSpaceD33(NeXus::NXEntry &firstEntry, // LTOF mode std::string binPathPrefix(instrumentPath + "/tof/tof_wavelength_detector"); - binningRear = m_loader.getTimeBinningFromNexusPath(firstEntry, - binPathPrefix + "1"); - binningRight = m_loader.getTimeBinningFromNexusPath( + binningRear = m_loadHelper.getTimeBinningFromNexusPath( + firstEntry, binPathPrefix + "1"); + binningRight = m_loadHelper.getTimeBinningFromNexusPath( firstEntry, binPathPrefix + "2"); - binningLeft = m_loader.getTimeBinningFromNexusPath(firstEntry, - binPathPrefix + "3"); - binningDown = m_loader.getTimeBinningFromNexusPath(firstEntry, - binPathPrefix + "4"); - binningUp = m_loader.getTimeBinningFromNexusPath(firstEntry, - binPathPrefix + "5"); + binningLeft = m_loadHelper.getTimeBinningFromNexusPath( + firstEntry, binPathPrefix + "3"); + binningDown = m_loadHelper.getTimeBinningFromNexusPath( + firstEntry, binPathPrefix + "4"); + binningUp = m_loadHelper.getTimeBinningFromNexusPath( + firstEntry, binPathPrefix + "5"); } catch (std::runtime_error &e) { throw std::runtime_error( "Unable to load the wavelength axes for TOF data " + @@ -386,22 +498,16 @@ void LoadILLSANS::initWorkSpaceD33(NeXus::NXEntry &firstEntry, g_log.debug("Loading the data into the workspace..."); - size_t nextIndex = - loadDataIntoWorkspaceFromVerticalTubes(dataRear, binningRear, 0); - nextIndex = loadDataIntoWorkspaceFromVerticalTubes(dataRight, binningRight, - nextIndex); - nextIndex = - loadDataIntoWorkspaceFromVerticalTubes(dataLeft, binningLeft, nextIndex); - nextIndex = - loadDataIntoWorkspaceFromVerticalTubes(dataDown, binningDown, nextIndex); - nextIndex = - loadDataIntoWorkspaceFromVerticalTubes(dataUp, binningUp, nextIndex); - nextIndex = loadDataIntoWorkspaceFromMonitors(firstEntry, nextIndex); + size_t nextIndex = loadDataFromTubes(dataRear, binningRear, 0); + nextIndex = loadDataFromTubes(dataRight, binningRight, nextIndex); + nextIndex = loadDataFromTubes(dataLeft, binningLeft, nextIndex); + nextIndex = loadDataFromTubes(dataDown, binningDown, nextIndex); + nextIndex = loadDataFromTubes(dataUp, binningUp, nextIndex); + nextIndex = loadDataFromMonitors(firstEntry, nextIndex); } -size_t -LoadILLSANS::loadDataIntoWorkspaceFromMonitors(NeXus::NXEntry &firstEntry, - size_t firstIndex) { +size_t LoadILLSANS::loadDataFromMonitors(NeXus::NXEntry &firstEntry, + size_t firstIndex) { // let's find the monitors; should be monitor1 and monitor2 for (std::vector::const_iterator it = @@ -441,9 +547,9 @@ LoadILLSANS::loadDataIntoWorkspaceFromMonitors(NeXus::NXEntry &firstEntry, return firstIndex; } -size_t LoadILLSANS::loadDataIntoWorkspaceFromVerticalTubes( - NeXus::NXInt &data, const std::vector &timeBinning, - size_t firstIndex = 0) { +size_t LoadILLSANS::loadDataFromTubes(NeXus::NXInt &data, + const std::vector &timeBinning, + size_t firstIndex = 0) { // Workaround to get the number of tubes / pixels int numberOfTubes; @@ -457,6 +563,7 @@ size_t LoadILLSANS::loadDataIntoWorkspaceFromVerticalTubes( numberOfTubes = data.dim0(); histogramWidth = data.dim2(); } + const int numberOfPixelsPerTube = data.dim1(); const HistogramData::BinEdges binEdges(timeBinning); @@ -519,8 +626,10 @@ void LoadILLSANS::runLoadInstrument() { IAlgorithm_sptr loadInst = createChildAlgorithm("LoadInstrument"); if (m_resMode == "nominal") { - loadInst->setPropertyValue("InstrumentName", m_instrumentName); + loadInst->setPropertyValue("Filename", + getInstrumentFilePath(m_instrumentName)); } else if (m_resMode == "low") { + // low resolution mode we have only defined for the old D11 and D22 loadInst->setPropertyValue("Filename", getInstrumentFilePath(m_instrumentName + "lr")); } @@ -564,15 +673,15 @@ void LoadILLSANS::moveDetectorDistance(double distance, V3D pos = getComponentPosition(componentName); mover->setProperty("Workspace", m_localWorkspace); mover->setProperty("ComponentName", componentName); + mover->setProperty("X", pos.X()); mover->setProperty("Y", pos.Y()); mover->setProperty("Z", distance); mover->setProperty("RelativePosition", false); mover->executeAsChildAlg(); + g_log.debug() << "Moving component '" << componentName << "' to Z = " << distance << '\n'; - API::Run &runDetails = m_localWorkspace->mutableRun(); - runDetails.addProperty("L2", distance, true); } /** @@ -737,7 +846,7 @@ void LoadILLSANS::loadMetaData(const NeXus::NXEntry &entry, // merge runs wavelengthRes = std::round(wavelengthRes * 100) / 100.; runDetails.addProperty("wavelength", wavelength); - double ei = m_loader.calculateEnergy(wavelength); + double ei = m_loadHelper.calculateEnergy(wavelength); runDetails.addProperty("Ei", ei, true); // wavelength m_defaultBinning[0] = wavelength - wavelengthRes * wavelength * 0.01 / 2; @@ -746,6 +855,11 @@ void LoadILLSANS::loadMetaData(const NeXus::NXEntry &entry, // Add a log called timer with the value of duration const double duration = entry.getFloat("duration"); runDetails.addProperty("timer", duration); + + // the start time is needed in the workspace when loading the parameter file + std::string startDate = entry.getString("start_time"); + runDetails.addProperty( + "start_time", m_loadHelper.dateTimeInIsoFormat(startDate)); } /** @@ -759,7 +873,7 @@ void LoadILLSANS::setFinalProperties(const std::string &filename) { NXstatus nxStat = NXopen(filename.c_str(), NXACC_READ, &nxHandle); if (nxStat != NX_ERROR) { - m_loader.addNexusFieldsToWsRun(nxHandle, runDetails); + m_loadHelper.addNexusFieldsToWsRun(nxHandle, runDetails); NXclose(&nxHandle); } } @@ -818,27 +932,6 @@ void LoadILLSANS::moveSource() { mover->executeAsChildAlg(); } -/** - * Sets the width (x) and height (y) of the pixel - */ -void LoadILLSANS::setPixelSize() { - const auto instrument = m_localWorkspace->getInstrument(); - const std::string component = - (m_instrumentName == "D33") ? "back_detector" : "detector"; - auto detector = instrument->getComponentByName(component); - auto rectangle = - std::dynamic_pointer_cast(detector); - if (rectangle) { - const double dx = rectangle->xstep(); - const double dy = rectangle->ystep(); - API::Run &runDetails = m_localWorkspace->mutableRun(); - runDetails.addProperty("pixel_width", dx); - runDetails.addProperty("pixel_height", dy); - } else { - g_log.debug("No pixel size available"); - } -} - /** * Returns the wavelength axis computed in VTOF mode * @param entry : opened root nexus entry diff --git a/Framework/DataHandling/src/LoadISISNexus2.cpp b/Framework/DataHandling/src/LoadISISNexus2.cpp index 08a1687da6379ae19fdeb9e1685609cf02a34f9f..3ee2204d4474d07d5dc89ab129a21ff970a1d640 100644 --- a/Framework/DataHandling/src/LoadISISNexus2.cpp +++ b/Framework/DataHandling/src/LoadISISNexus2.cpp @@ -697,7 +697,7 @@ LoadISISNexus2::prepareSpectraBlocks(std::map &monitors, includedMonitors.emplace_back(min); } else { auto max = dataBlock.getMaxSpectrumID(); - m_spectraBlocks.emplace_back(SpectraBlock(min, max, false, "")); + m_spectraBlocks.emplace_back(min, max, false, ""); } } diff --git a/Framework/DataHandling/src/LoadMuonNexusV2.cpp b/Framework/DataHandling/src/LoadMuonNexusV2.cpp index a3c9872902fcd2c53493a456760e215ef664cb8a..1c496a14a42d266a14c170f10c91fecd12eb6255 100644 --- a/Framework/DataHandling/src/LoadMuonNexusV2.cpp +++ b/Framework/DataHandling/src/LoadMuonNexusV2.cpp @@ -8,11 +8,13 @@ #include "MantidAPI/Axis.h" #include "MantidAPI/FileProperty.h" #include "MantidAPI/RegisterFileLoader.h" +#include "MantidAPI/TableRow.h" #include "MantidAPI/WorkspaceFactory.h" #include "MantidAPI/WorkspaceGroup.h" #include "MantidDataHandling/LoadISISNexus2.h" #include "MantidDataHandling/MultiPeriodLoadMuonStrategy.h" #include "MantidDataHandling/SinglePeriodLoadMuonStrategy.h" +#include "MantidDataObjects/TableWorkspace.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidKernel/ArrayProperty.h" #include "MantidKernel/BoundedValidator.h" @@ -125,6 +127,11 @@ void LoadMuonNexusV2::init() { Direction::Output), "A vector of time zero values"); + declareProperty( + std::make_unique>( + "TimeZeroTable", "", Direction::Output, PropertyMode::Optional), + "TableWorkspace containing time zero values per spectra."); + declareProperty( "CorrectTime", true, "Boolean flag controlling whether time should be corrected by timezero.", @@ -171,6 +178,13 @@ void LoadMuonNexusV2::execLoader() { if (!getPropertyValue("DeadTimeTable").empty()) { setProperty("DeadTimeTable", deadtimeTable); } + + // Time Zero table should be returned if found + if (!getPropertyValue("TimeZerotable").empty()) { + // Create table and set property + auto timeZeroTable = m_loadMuonStrategy->getTimeZeroTable(); + setProperty("TimeZeroTable", timeZeroTable); + } } /** diff --git a/Framework/DataHandling/src/LoadMuonStrategy.cpp b/Framework/DataHandling/src/LoadMuonStrategy.cpp index f2ce76d4ae3a3c580a44c9d0c0e7d2a03ef6ee71..86f8d8ef6b6b03a58aceb3c2098dbc65806a71b4 100644 --- a/Framework/DataHandling/src/LoadMuonStrategy.cpp +++ b/Framework/DataHandling/src/LoadMuonStrategy.cpp @@ -18,6 +18,29 @@ namespace Mantid { namespace DataHandling { +/** + * Creates a timezero table for the loaded detectors + * @param numSpec :: Number of spectra (number of rows in table) + * @param timeZeros :: Vector containing time zero values for each spectra + * @return TableWorkspace of time zeros + */ +DataObjects::TableWorkspace_sptr +createTimeZeroTable(const size_t numSpec, + const std::vector &timeZeros) { + Mantid::DataObjects::TableWorkspace_sptr timeZeroTable = + std::dynamic_pointer_cast( + Mantid::API::WorkspaceFactory::Instance().createTable( + "TableWorkspace")); + timeZeroTable->addColumn("double", "time zero"); + + for (size_t specNum = 0; specNum < numSpec; ++specNum) { + Mantid::API::TableRow row = timeZeroTable->appendRow(); + row << timeZeros[specNum]; + } + + return timeZeroTable; +} + // Constructor LoadMuonStrategy::LoadMuonStrategy(Kernel::Logger &g_log, std::string filename, LoadMuonNexusV2NexusHelper &nexusLoader) diff --git a/Framework/DataHandling/src/LoadNXcanSAS.cpp b/Framework/DataHandling/src/LoadNXcanSAS.cpp index e34ed7581476b5c9b2e63ec52828188dbb07761b..47f94db7621e44241ec70be1ef771befcaedb4a1 100644 --- a/Framework/DataHandling/src/LoadNXcanSAS.cpp +++ b/Framework/DataHandling/src/LoadNXcanSAS.cpp @@ -449,10 +449,32 @@ void loadTransmissionData(H5::Group &transmission, Mantid::DataHandling::H5Util::readArray1DCoerce( transmission, sasTransmissionSpectrumTdev); //----------------------------------------- - // Load Lambda - workspace->setPoints(0, - Mantid::DataHandling::H5Util::readArray1DCoerce( - transmission, sasTransmissionSpectrumLambda)); + // Load Lambda. A bug in older versions (fixed in 6.0) allowed the + // transmission lambda points to be saved as bin edges rather than points as + // required by the NXcanSAS standard. We allow loading those files and convert + // to points on the fly + auto lambda = Mantid::DataHandling::H5Util::readArray1DCoerce( + transmission, sasTransmissionSpectrumLambda); + if (lambda.size() == workspace->blocksize()) + workspace->setPoints(0, std::move(lambda)); + else if (lambda.size() == workspace->blocksize() + 1) + workspace->setBinEdges(0, std::move(lambda)); + else { +#if defined(H5_USE_18_API) + const std::string objectName{transmission.getObjName()}; +#else + const size_t nchars = H5Iget_name(transmission.getId(), nullptr, 0); + std::string objectName; + objectName.resize(nchars); + H5Iget_name(transmission.getId(), objectName.data(), + nchars + 1); // +1 for null terminator +#endif + throw std::runtime_error( + "Unexpected array size for lambda in transmission group '" + + objectName + + "'. Expected length=" + std::to_string(workspace->blocksize()) + + ", found length=" + std::to_string(lambda.size())); + } workspace->getAxis(0)->unit() = Mantid::Kernel::UnitFactory::Instance().create("Wavelength"); diff --git a/Framework/DataHandling/src/LoadNexusLogs.cpp b/Framework/DataHandling/src/LoadNexusLogs.cpp index e1d9c755e0b8e4451d3f1a5725af42be520c2070..a400c4869ec9f35f81f9f43fe7255f3884840297 100644 --- a/Framework/DataHandling/src/LoadNexusLogs.cpp +++ b/Framework/DataHandling/src/LoadNexusLogs.cpp @@ -433,6 +433,16 @@ void LoadNexusLogs::init() { declareProperty(std::make_unique>( "NXentryName", "", Direction::Input), "Entry in the nexus file from which to read the logs"); + declareProperty( + std::make_unique>>( + "AllowList", std::vector(), Direction::Input), + "If specified, only these logs will be loaded from the file (each " + "separated by a space)."); + declareProperty( + std::make_unique>>( + "BlockList", std::vector(), Direction::Input), + "If specified, these logs will NOT be loaded from the file (each " + "separated by a space)."); } /** Executes the algorithm. Reading in the file and creating and populating @@ -447,6 +457,10 @@ void LoadNexusLogs::execLoader() { MatrixWorkspace_sptr workspace = getProperty("Workspace"); std::string entry_name = getPropertyValue("NXentryName"); + + std::vector allow_list = getProperty("AllowList"); + std::vector block_list = getProperty("BlockList"); + // Find the entry name to use (normally "entry" for SNS, "raw_data_1" for // ISIS) if entry name is empty if (entry_name.empty()) { @@ -500,6 +514,12 @@ void LoadNexusLogs::execLoader() { readStartAndEndTime(file, workspace->mutableRun()); + if (!allow_list.empty() && !block_list.empty()) { + throw std::runtime_error( + "BlockList and AllowList are mutually exclusive! " + "Please only enter values for one of these fields."); + } + const std::map> &allEntries = getFileInfo()->getAllEntries(); @@ -515,7 +535,7 @@ void LoadNexusLogs::execLoader() { // match for 2nd level entry /a/b if (std::count(entry.begin(), entry.end(), '/') == 2) { if (isLog) { - loadLogs(file, entry, group_class, workspace); + loadLogs(file, entry, group_class, workspace, allow_list, block_list); } else { loadNPeriods(file, workspace); } @@ -539,7 +559,8 @@ void LoadNexusLogs::execLoader() { continue; } // here we must search only in NxLogs and NXpositioner sets - loadLogs(file, absoluteGroupName, group_class, workspace); + loadLogs(file, absoluteGroupName, group_class, workspace, allow_list, + block_list); } }; @@ -643,6 +664,16 @@ void LoadNexusLogs::execLoader() { } } + if (!allow_list.empty()) { + for (const auto &allow : allow_list) { + if (!workspace->run().hasProperty(allow)) { + g_log.notice() << "could not load entry '" << allow + << "' that was specified in the allow list" + << "\n"; + } + } + } + // Close the file file.close(); @@ -766,11 +797,15 @@ void LoadNexusLogs::loadNPeriods( * @param absolute_entry_name :: The name of the log entry * @param entry_class :: The class type of the log entry * @param workspace :: A pointer to the workspace to store the logs + * @param allow_list :: Names of specific log entries to load + * @param block_list :: Names of specific log entries to skip when loading */ void LoadNexusLogs::loadLogs( ::NeXus::File &file, const std::string &absolute_entry_name, const std::string &entry_class, - const std::shared_ptr &workspace) const { + const std::shared_ptr &workspace, + const std::vector &allow_list, + const std::vector &block_list) const { const std::map> &allEntries = getFileInfo()->getAllEntries(); @@ -784,16 +819,49 @@ void LoadNexusLogs::loadLogs( const std::set &logsSet = itLogClass->second; auto itPrefixBegin = logsSet.lower_bound(absolute_entry_name); - for (auto it = itPrefixBegin; - it != logsSet.end() && - it->compare(0, absolute_entry_name.size(), absolute_entry_name) == 0; - ++it) { - // must be third level entry - if (std::count(it->begin(), it->end(), '/') == 3) { - if (isNxLog) { - loadNXLog(file, *it, logClass, workspace); - } else { - loadSELog(file, *it, workspace); + if (allow_list.empty()) { + for (auto it = itPrefixBegin; + it != logsSet.end() && + it->compare(0, absolute_entry_name.size(), absolute_entry_name) == 0; + ++it) { + // must be third level entry + if (std::count(it->begin(), it->end(), '/') == 3) { + if (!block_list.empty()) { + bool skip = false; + for (const auto &block : block_list) { + if ((*it).substr((*it).find_last_of("/") + 1) == block) { + skip = true; + break; + } + } + if (skip) { + continue; + } + } + + if (isNxLog) { + loadNXLog(file, *it, logClass, workspace); + } else { + loadSELog(file, *it, workspace); + } + } + } + } else { + for (const auto &allow : allow_list) { + itPrefixBegin = logsSet.find(absolute_entry_name + "/" + allow); + if (itPrefixBegin == logsSet.end()) { + // don't print warning yet since it might be found in another log + // class + continue; + } + auto it = itPrefixBegin; + // must be third level entry + if (std::count(it->begin(), it->end(), '/') == 3) { + if (isNxLog) { + loadNXLog(file, *it, logClass, workspace); + } else { + loadSELog(file, *it, workspace); + } } } } diff --git a/Framework/DataHandling/src/LoadPSIMuonBin.cpp b/Framework/DataHandling/src/LoadPSIMuonBin.cpp index af1d5e162ddabe35bbc0d5092b2f0e78aa31accf..15645279184bc1815da9a166a006bf39da6a4ad0 100644 --- a/Framework/DataHandling/src/LoadPSIMuonBin.cpp +++ b/Framework/DataHandling/src/LoadPSIMuonBin.cpp @@ -13,6 +13,7 @@ #include "MantidAPI/RegisterFileLoader.h" #include "MantidAPI/TableRow.h" #include "MantidAPI/WorkspaceFactory.h" +#include "MantidDataHandling/LoadMuonStrategy.h" #include "MantidDataObjects/TableWorkspace.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidDataObjects/WorkspaceCreation.h" @@ -126,6 +127,14 @@ void LoadPSIMuonBin::init() { declareProperty(std::make_unique>( "TimeZeroList", Kernel::Direction::Output), "A vector of time zero values"); + + declareProperty( + std::make_unique< + Mantid::API::WorkspaceProperty>( + "TimeZeroTable", "", Mantid::Kernel::Direction::Output, + Mantid::API::PropertyMode::Optional), + "TableWorkspace of time zeros for each spectra"); + declareProperty( "CorrectTime", true, "Boolean flag controlling whether time should be corrected by timezero.", @@ -225,6 +234,13 @@ void LoadPSIMuonBin::exec() { setProperty("TimeZero", absTimeZero); setProperty("TimeZeroList", correctedTimeZeroList); + // create time zero table + if (!getPropertyValue("TimeZeroTable").empty()) { + auto table = + createTimeZeroTable(m_histograms.size(), correctedTimeZeroList); + setProperty("TimeZeroTable", table); + } + auto firstGoodDataSpecIndex = static_cast( *std::max_element(m_header.firstGood, m_header.firstGood + 16)); diff --git a/Framework/DataHandling/src/LoadParameterFile.cpp b/Framework/DataHandling/src/LoadParameterFile.cpp index 7f9d7ad1691d616b4b7208fb7c2f3faae8781ee8..4d7d7989506eafd77acf1bd47dac655cf2a124ce 100644 --- a/Framework/DataHandling/src/LoadParameterFile.cpp +++ b/Framework/DataHandling/src/LoadParameterFile.cpp @@ -140,7 +140,8 @@ void LoadParameterFile::exec() { // Set all parameters that specified in all component-link elements of // pRootElem InstrumentDefinitionParser loadInstr; - loadInstr.setComponentLinks(instrument, pRootElem, &prog); + loadInstr.setComponentLinks(instrument, pRootElem, &prog, + localWorkspace->getWorkspaceStartDate()); // populate parameter map of workspace localWorkspace->populateInstrumentParameters(); diff --git a/Framework/DataHandling/src/LoadRawBin0.cpp b/Framework/DataHandling/src/LoadRawBin0.cpp index 247a1217bc830d00fc746abc7c68c8f12a51ffb6..80c751adc361603838dc6052fc156adb6191d1ea 100644 --- a/Framework/DataHandling/src/LoadRawBin0.cpp +++ b/Framework/DataHandling/src/LoadRawBin0.cpp @@ -69,7 +69,7 @@ void LoadRawBin0::exec() { // Need to check that the file is not a text file as the ISISRAW routines // don't deal with these very well, i.e // reading continues until a bad_alloc is encountered. - if (isAscii(file)) { + if (Kernel::FileDescriptor::isAscii(file)) { g_log.error() << "File \"" << m_filename << "\" is not a valid RAW file.\n"; throw std::invalid_argument("Incorrect file type encountered."); } diff --git a/Framework/DataHandling/src/LoadRawHelper.cpp b/Framework/DataHandling/src/LoadRawHelper.cpp index 6256ae624241dae50d372ffe14939a23567c7ad9..6be605651937d4aad5b62e9cf30d61fc55349fed 100644 --- a/Framework/DataHandling/src/LoadRawHelper.cpp +++ b/Framework/DataHandling/src/LoadRawHelper.cpp @@ -6,36 +6,96 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidDataHandling/LoadRawHelper.h" #include "LoadRaw/isisraw2.h" + #include "MantidAPI/Axis.h" #include "MantidAPI/FileProperty.h" #include "MantidAPI/SpectrumDetectorMapping.h" #include "MantidAPI/WorkspaceFactory.h" #include "MantidAPI/WorkspaceGroup.h" -#include "MantidDataHandling/LoadLog.h" #include "MantidDataHandling/RawFileInfo.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidGeometry/Instrument.h" -#include "MantidKernel/ArrayProperty.h" -#include "MantidKernel/ConfigService.h" #include "MantidKernel/Glob.h" #include "MantidKernel/ListValidator.h" #include "MantidKernel/OptionalBool.h" #include "MantidKernel/Strings.h" -#include "MantidKernel/TimeSeriesProperty.h" #include "MantidKernel/UnitFactory.h" - -#include -#include -#include - -#include -#include -#include #include #include +#include +#include -#include -#include //Required for gcc 4.4 +namespace { +/** + * Return the path to the alternate data stream for the given path + * @param filePath The full path to the main data path + */ +inline std::string alternateDataStream(const Poco::Path &filePath) { + return filePath.toString() + ":checksum"; +} + +/** + * This method looks for ADS with name checksum exists + * @param pathToFile The path and name of the file. + * @return True if ADS stream checksum exists + */ +#ifdef _WIN32 +inline bool hasAlternateDataStream(const Poco::Path &pathToFile) { + std::ifstream adsStream(alternateDataStream(pathToFile)); + if (!adsStream) { + return false; + } + adsStream.close(); + return true; +} +#else +inline bool hasAlternateDataStream([ + [maybe_unused]] const Poco::Path &pathToFile) { + return false; +} +#endif + +/** + * This method reads the checksum alternate data stream associated with the raw + * file and returns the filenames of the log files that exist in the same + * directory as the given file + * @param pathToRawFile The path and name of the raw file. + * @return list of logfile names. + */ +std::set +logFilesFromAlternateDataStream(const Poco::Path &pathToRawFile) { + std::set logfilesList; + std::ifstream adsStream(alternateDataStream(pathToRawFile)); + if (!adsStream) { + return logfilesList; + } + + // An example of alternate stream content: + // ad0bc56c4c556fa368565000f01e77f7 *IRIS00055132.log + // d5ace6dc7ac6c4365d48ee1f2906c6f4 *IRIS00055132.nxs + // 9c70ad392023515f775af3d3984882f3 *IRIS00055132.raw + // 66f74b6c0cc3eb497b92d4956ed8d6b5 *IRIS00055132_ICPdebug.txt + // e200aa65186b61e487175d5263b315aa *IRIS00055132_ICPevent.txt + // 91be40aa4f54d050a9eb4abea394720e *IRIS00055132_ICPstatus.txt + // 50aa2872110a9b862b01c6c83f8ce9a8 *IRIS00055132_Status.txt + Poco::Path dirOfFile(pathToRawFile.parent()); + std::string line; + while (Mantid::Kernel::Strings::extractToEOL(adsStream, line)) { + if (boost::algorithm::iends_with(line, ".txt") || + boost::algorithm::iends_with(line, ".log")) { + const size_t asteriskPos = line.find('*'); + if (asteriskPos == std::string::npos) + continue; + Poco::Path logFilePath(dirOfFile.append(line.substr(asteriskPos + 1))); + if (Poco::File(logFilePath).exists()) { + logfilesList.insert(logFilePath.toString()); + } + } + } + return logfilesList; +} + +} // namespace namespace Mantid { namespace DataHandling { @@ -99,16 +159,18 @@ void LoadRawHelper::init() { FILE *LoadRawHelper::openRawFile(const std::string &fileName) { FILE *file = fopen(fileName.c_str(), "rb"); if (file == nullptr) { - g_log.error("Unable to open file " + fileName); throw Exception::FileError("Unable to open File:", fileName); } // Need to check that the file is not a text file as the ISISRAW routines // don't deal with these very well, i.e // reading continues until a bad_alloc is encountered. - if (isAscii(file)) { - g_log.error() << "File \"" << fileName << "\" is not a valid RAW file.\n"; + if (Kernel::FileDescriptor::isAscii(file)) { fclose(file); - throw std::invalid_argument("Incorrect file type encountered."); + std::stringstream os; + os << "File \"" << fileName + << "\" is not a valid RAW file. The first 256 bytes suggest it is an " + "ascii file.\n"; + throw std::invalid_argument(os.str()); } return file; @@ -370,8 +432,7 @@ void LoadRawHelper::setWorkspaceProperty( const std::string &propertyName, const std::string &title, const WorkspaceGroup_sptr &grpws_sptr, const DataObjects::Workspace2D_sptr &ws_sptr, int64_t numberOfPeriods, - bool bMonitor, API::Algorithm *const pAlg) { - UNUSED_ARG(bMonitor); + [[maybe_unused]] bool bMonitor, API::Algorithm *const pAlg) { Property *ws = pAlg->getProperty("OutputWorkspace"); if (!ws) return; @@ -470,17 +531,7 @@ LoadRawHelper::getmonitorSpectrumList(const SpectrumDetectorMapping &mapping) { * @return WorkspaceGroup_sptr shared pointer to the workspace */ WorkspaceGroup_sptr LoadRawHelper::createGroupWorkspace() { - WorkspaceGroup_sptr workspacegrp(new WorkspaceGroup); - return workspacegrp; -} - -/** - * Check if a file is a text file - * @param file :: The file pointer - * @returns true if the file an ascii text file, false otherwise - */ -bool LoadRawHelper::isAscii(FILE *file) const { - return Kernel::FileDescriptor::isAscii(file); + return WorkspaceGroup_sptr(new WorkspaceGroup); } /** Constructs the time channel (X) vector(s) @@ -692,7 +743,7 @@ void LoadRawHelper::runLoadLog( const DataObjects::Workspace2D_sptr &localWorkspace, double progStart, double progEnd) { // search for the log file to load, and save their names in a set. - std::list logFiles = searchForLogFiles(fileName); + std::list logFiles = searchForLogFiles(Poco::Path(fileName)); g_log.debug("Loading the log files..."); if (progStart < progEnd) { @@ -1176,7 +1227,7 @@ int LoadRawHelper::confidence(Kernel::FileDescriptor &descriptor) const { * @returns A set containing paths to log files related to RAW file used. */ std::list -LoadRawHelper::searchForLogFiles(const std::string &pathToRawFile) { +LoadRawHelper::searchForLogFiles(const Poco::Path &pathToRawFile) { // If pathToRawFile is the filename of a raw datafile then search for // potential log files // in the directory of this raw datafile. Otherwise check if it is a potential @@ -1191,47 +1242,32 @@ LoadRawHelper::searchForLogFiles(const std::string &pathToRawFile) { // File property checks whether the given path exists, just check that is // actually a file - Poco::File l_path(pathToRawFile); - if (l_path.isDirectory()) { - g_log.error("In LoadLog: " + pathToRawFile + - " must be a filename not a directory."); - throw Exception::FileError("Filename is a directory:", pathToRawFile); + if (Poco::File(pathToRawFile).isDirectory()) { + throw Exception::FileError("Filename is a directory:", + pathToRawFile.toString()); } // start the process or populating potential log files into the container: // potentialLogFiles - std::string l_filenamePart = - Poco::Path(l_path.path()).getFileName(); // get filename part only - if (isAscii(pathToRawFile) && - l_filenamePart.rfind('_') != std::string::npos) { + // have we been given what looks like a log file + const auto fileExt = pathToRawFile.getExtension(); + if (boost::algorithm::iequals(fileExt, "log") || + boost::algorithm::iequals(fileExt, "txt")) { // then we will assume that the file is an ISIS log file - potentialLogFiles.insert(pathToRawFile); + potentialLogFiles.insert(pathToRawFile.toString()); } else { // then we will assume that the file is an ISIS raw file. The file validator // will have warned the user if the extension is not one of the suggested // ones. - - // strip out the raw data file identifier - std::string l_rawID; - size_t idx = l_filenamePart.rfind('.'); - - if (idx != std::string::npos) { - l_rawID = l_filenamePart.substr(0, l_filenamePart.rfind('.')); - } else { - l_rawID = l_filenamePart; - } - /// check for alternate data stream exists for raw file - /// if exists open the stream and read log files name from ADS - if (adsExists(pathToRawFile)) { - potentialLogFiles = getLogFilenamesfromADS(pathToRawFile); + if (hasAlternateDataStream(pathToRawFile)) { + /// read list of log files from alternate data stream + potentialLogFiles = logFilesFromAlternateDataStream(pathToRawFile); } else { // look for log files in the directory of the raw datafile - std::string pattern(l_rawID + "_*.txt"); - Poco::Path dir(pathToRawFile); - dir.makeParent(); - + Poco::Path pattern = pathToRawFile; + pattern.setFileName(pathToRawFile.getBaseName() + "_*.txt"); try { - Kernel::Glob::glob(Poco::Path(dir).resolve(pattern), potentialLogFiles); + Kernel::Glob::glob(pattern, potentialLogFiles); } catch (std::exception &) { } } @@ -1240,102 +1276,16 @@ LoadRawHelper::searchForLogFiles(const std::string &pathToRawFile) { potentialLogFilesList.insert(potentialLogFilesList.begin(), potentialLogFiles.begin(), potentialLogFiles.end()); - - // Remove extension from path, and append .log to path. - std::string logName = - pathToRawFile.substr(0, pathToRawFile.rfind('.')) + ".log"; - // Check if log file exists in current directory. - std::ifstream fileExists(logName.c_str()); - if (fileExists) { + // Check for .log + const Poco::File combinedLogPath( + Poco::Path(pathToRawFile).setExtension("log")); + if (combinedLogPath.exists()) { // Push three column filename to end of list. - potentialLogFilesList.insert(potentialLogFilesList.end(), logName); - } - } - return (potentialLogFilesList); -} - -/** - * This method looks for ADS with name checksum exists - * @param pathToFile The path and name of the file. - * @return True if ADS stream checksum exists - */ -bool LoadRawHelper::adsExists(const std::string &pathToFile) { -#ifdef _WIN32 - std::string adsname(pathToFile + ":checksum"); - std::ifstream adstream(adsname.c_str()); - if (!adstream) { - return false; - } - adstream.close(); - return true; -#else - UNUSED_ARG(pathToFile); - return (false); -#endif -} - -/** - * This method reads the checksum ADS associated with the raw file and returns - * the filenames of the log files - * @param pathToRawFile The path and name of the raw file. - * @return list of logfile names. - */ -std::set -LoadRawHelper::getLogFilenamesfromADS(const std::string &pathToRawFile) { - std::string adsname(pathToRawFile + ":checksum"); - std::ifstream adstream(adsname.c_str()); - if (!adstream) { - return (std::set()); - } - - std::string str; - std::string path; - std::set logfilesList; - Poco::Path logpath(pathToRawFile); - size_t pos = pathToRawFile.find_last_of('/'); - if (pos == std::string::npos) { - pos = pathToRawFile.find_last_of('\\'); - } - if (pos != std::string::npos) { - path = pathToRawFile.substr(0, pos); - } - while (Mantid::Kernel::Strings::extractToEOL(adstream, str)) { - std::string fileName; - pos = str.find('*'); - if (pos == std::string::npos) - continue; - fileName = str.substr(pos + 1, str.length() - pos); - pos = fileName.find("txt"); - if (pos == std::string::npos) - continue; - // The log files must exist to count - const auto logFilePath = std::string(path).append("/").append(fileName); - if (Poco::File(logFilePath).exists()) - logfilesList.insert(logFilePath); - } - return (logfilesList); -} - -/** - * Checks whether filename is a simple text file - * @param filename :: The filename to inspect - * @returns true if the filename has the .txt extension - */ -bool LoadRawHelper::isAscii(const std::string &filename) { - FILE *file = fopen(filename.c_str(), "rb"); - char data[256]; - size_t n = fread(data, 1, sizeof(data), file); - fclose(file); - char *pend = &data[n]; - // Call it a binary file if we find a non-ascii character in the first 256 - // bytes of the file. - for (char *p = data; p < pend; ++p) { - auto ch = static_cast(*p); - if (!(ch <= 0x7F)) { - return false; + potentialLogFilesList.insert(potentialLogFilesList.end(), + combinedLogPath.path()); } } - return true; + return potentialLogFilesList; } /** This method checks the value of LoadMonitors property and returns true or diff --git a/Framework/DataHandling/src/LoadRawSpectrum0.cpp b/Framework/DataHandling/src/LoadRawSpectrum0.cpp index 5ccd32647b269a90eca1d418609b91966ce51a13..1248e0c01467e8dfb53a90af860e63a5ac183f96 100644 --- a/Framework/DataHandling/src/LoadRawSpectrum0.cpp +++ b/Framework/DataHandling/src/LoadRawSpectrum0.cpp @@ -55,7 +55,7 @@ void LoadRawSpectrum0::exec() { // Need to check that the file is not a text file as the ISISRAW routines // don't deal with these very well, i.e // reading continues until a bad_alloc is encountered. - if (isAscii(file)) { + if (Kernel::FileDescriptor::isAscii(file)) { g_log.error() << "File \"" << m_filename << "\" is not a valid RAW file.\n"; throw std::invalid_argument("Incorrect file type encountered."); } diff --git a/Framework/DataHandling/src/MultiPeriodLoadMuonStrategy.cpp b/Framework/DataHandling/src/MultiPeriodLoadMuonStrategy.cpp index b3e0efcdaa29807c6b6fab5fd6d120f1d2c06a42..bee07338eeb8b32328cf1350782c8c43e0a36bf8 100644 --- a/Framework/DataHandling/src/MultiPeriodLoadMuonStrategy.cpp +++ b/Framework/DataHandling/src/MultiPeriodLoadMuonStrategy.cpp @@ -119,6 +119,19 @@ API::Workspace_sptr MultiPeriodLoadMuonStrategy::loadDeadTimeTable() const { return tableGroup; } +/** + * Gets time zero table from loaded time zeros + * Assumes all peridos have same time zero + * @returns :: Time zero table + */ +Workspace_sptr MultiPeriodLoadMuonStrategy::getTimeZeroTable() { + auto workspace = + std::dynamic_pointer_cast(m_workspaceGroup.getItem(0)); + const auto numSpec = workspace->getNumberHistograms(); + auto timeZeros = m_nexusLoader.loadTimeZeroListFromNexusFile(numSpec); + return createTimeZeroTable(numSpec, timeZeros); +} + /** * Finds the detectors which are loaded in the stored workspace group */ diff --git a/Framework/DataHandling/src/ReadMaterial.cpp b/Framework/DataHandling/src/ReadMaterial.cpp index 6358e842c6d7963a8e8212d3b696eed4be4c4a25..4edacadb42e621dc031f8229e8227cec8a520f1b 100644 --- a/Framework/DataHandling/src/ReadMaterial.cpp +++ b/Framework/DataHandling/src/ReadMaterial.cpp @@ -148,7 +148,8 @@ void ReadMaterial::setMaterialParameters(const MaterialParameters ¶ms) { params.unitCellVolume); setScatteringInfo(params.coherentXSection, params.incoherentXSection, params.attenuationXSection, params.scatteringXSection, - params.attenuationProfileFileName); + params.attenuationProfileFileName, + params.xRayAttenuationProfileFileName); } /** @@ -194,16 +195,17 @@ void ReadMaterial::setNumberDensity( } } -void ReadMaterial::setScatteringInfo(double coherentXSection, - double incoherentXSection, - double attenuationXSection, - double scatteringXSection, - std::string attenuationProfileFileName) { +void ReadMaterial::setScatteringInfo( + double coherentXSection, double incoherentXSection, + double attenuationXSection, double scatteringXSection, + std::string attenuationProfileFileName, + std::string xRayAttenuationProfileFileName) { builder.setCoherentXSection(coherentXSection); // in barns builder.setIncoherentXSection(incoherentXSection); // in barns builder.setAbsorptionXSection(attenuationXSection); // in barns builder.setTotalScatterXSection(scatteringXSection); // in barns builder.setAttenuationProfileFilename(attenuationProfileFileName); + builder.setXRayAttenuationProfileFilename(xRayAttenuationProfileFileName); } bool ReadMaterial::isEmpty(const double toCheck) { diff --git a/Framework/DataHandling/src/SaveDiffCal.cpp b/Framework/DataHandling/src/SaveDiffCal.cpp index 1911d8a18894b7cd8dbb9279dc9f9617ca1ea520..fb5db2a78f37671497802f18cd19d2310f5c0ab9 100644 --- a/Framework/DataHandling/src/SaveDiffCal.cpp +++ b/Framework/DataHandling/src/SaveDiffCal.cpp @@ -63,12 +63,12 @@ void SaveDiffCal::init() { declareProperty( std::make_unique>( "GroupingWorkspace", "", Direction::Input, PropertyMode::Optional), - "Optional: An GroupingWorkspace workspace giving the grouping info."); + "Optional: A GroupingWorkspace giving the grouping info."); declareProperty( std::make_unique>( "MaskWorkspace", "", Direction::Input, PropertyMode::Optional), - "Optional: An Workspace workspace giving which detectors are masked."); + "Optional: A MaskWorkspace giving which detectors are masked."); declareProperty( std::make_unique("Filename", "", FileProperty::Save, ".h5"), @@ -91,12 +91,12 @@ std::map SaveDiffCal::validateInputs() { getProperty("GroupingWorkspace"); if (bool(groupingWS) && numRows < groupingWS->getNumberHistograms()) { result["GroupingWorkspace"] = - "Must have same number of spectra as the table has rows"; + "Must have equal or less number of spectra as the table has rows"; } MaskWorkspace_const_sptr maskWS = getProperty("MaskWorkspace"); if (bool(maskWS) && numRows < maskWS->getNumberHistograms()) { result["MaskWorkspace"] = - "Must have same number of spectra as the table has rows"; + "Must have equal or less number of spectra as the table has rows"; } } } @@ -104,22 +104,45 @@ std::map SaveDiffCal::validateInputs() { return result; } +/** + * Create a dataset under a given group with a given name + * Use CalibrationWorkspace to retrieve the data + * + * @param group :: group parent to the dataset + * @param name :: column name of CalibrationWorkspace, and name of the dataset + */ void SaveDiffCal::writeDoubleFieldFromTable(H5::Group &group, const std::string &name) { auto column = m_calibrationWS->getColumn(name); // cppcheck-suppress compareBoolExpressionWithInt + // Retrieve only the first m_numValues, not necessarily the whole column auto data = column->numeric_fill<>(m_numValues); H5Util::writeArray1D(group, name, data); } +/** + * Create a dataset under a given group with a given name + * Use CalibrationWorkspace to retrieve the data. + * + * @param group :: group parent to the dataset + * @param name :: column name of CalibrationWorkspace, and name of the dataset + */ void SaveDiffCal::writeIntFieldFromTable(H5::Group &group, const std::string &name) { auto column = m_calibrationWS->getColumn(name); + // Retrieve only the first m_numValues, not necessarily the whole column auto data = column->numeric_fill(m_numValues); H5Util::writeArray1D(group, name, data); } -// TODO should flip for mask +/** + * Create a dataset under a given group with a given name + * Use GroupingWorkspace or MaskWorkspace to retrieve the data. + * + * @param group :: group parent to the dataset + * @param name :: column name of the workspace, and name of the dataset + * @param ws :: pointer to GroupingWorkspace or MaskWorkspace + */ void SaveDiffCal::writeIntFieldFromSVWS( H5::Group &group, const std::string &name, const DataObjects::SpecialWorkspace2D_const_sptr &ws) { @@ -130,14 +153,15 @@ void SaveDiffCal::writeIntFieldFromSVWS( if (bool(ws)) { for (size_t i = 0; i < m_numValues; ++i) { - auto &ids = ws->getSpectrum(i).getDetectorIDs(); - auto found = m_detidToIndex.find(*(ids.begin())); + auto &ids = ws->getSpectrum(i).getDetectorIDs(); // set of detector ID's + // check if the first detector ID in the set is in the calibration table + auto found = m_detidToIndex.find(*(ids.begin())); // (detID, row_index) if (found != m_detidToIndex.end()) { auto value = static_cast(ws->getValue(found->first)); // in maskworkspace 0=use, 1=dontuse - backwards from the file if (isMask) { if (value == 0) - value = 1; + value = 1; // thus "use" means a calibrated detector, good for use else value = 0; } @@ -178,6 +202,8 @@ void SaveDiffCal::exec() { MaskWorkspace_const_sptr maskWS = getProperty("MaskWorkspace"); std::string filename = getProperty("Filename"); + // initialize `m_numValues` as the minimum of (CalibrationWorkspace_row_count, + // GroupingWorkspace_histogram_count, MaskWorkspace_histogram_count) m_numValues = m_calibrationWS->rowCount(); if (bool(groupingWS) && groupingWS->getNumberHistograms() < m_numValues) { m_numValues = groupingWS->getNumberHistograms(); @@ -196,6 +222,8 @@ void SaveDiffCal::exec() { auto calibrationGroup = H5Util::createGroupNXS(file, "calibration", "NXentry"); + // write the d-spacing to TOF conversion parameters for the selected pixels + // as datasets under the NXentry group this->writeDoubleFieldFromTable(calibrationGroup, "difc"); this->writeDoubleFieldFromTable(calibrationGroup, "difa"); this->writeDoubleFieldFromTable(calibrationGroup, "tzero"); @@ -209,12 +237,14 @@ void SaveDiffCal::exec() { this->writeIntFieldFromSVWS(calibrationGroup, "group", groupingWS); this->writeIntFieldFromSVWS(calibrationGroup, "use", maskWS); + // check if the input calibration table has an "offset" field if (this->tableHasColumn("offset")) // optional field this->writeDoubleFieldFromTable(calibrationGroup, "offset"); else g_log.information("Not writing out values for \"offset\""); - // get the instrument information + // get the instrument information only if a GroupingWorkspace or + // MaskWorkspace is supplied by the user std::string instrumentName; std::string instrumentSource; if (bool(groupingWS)) { diff --git a/Framework/DataHandling/src/SaveNXcanSAS.cpp b/Framework/DataHandling/src/SaveNXcanSAS.cpp index 75efd53a1e0e4c87cfb69083b0fd2406b2e1f34a..337514509371166cc7c38c2dbd88b26c81e2f8c7 100644 --- a/Framework/DataHandling/src/SaveNXcanSAS.cpp +++ b/Framework/DataHandling/src/SaveNXcanSAS.cpp @@ -781,7 +781,7 @@ void addTransmission(H5::Group &group, //----------------------------------------- // Add lambda with units - const auto &lambda = workspace->x(0); + const auto lambda = workspace->points(0); std::map lambdaAttributes; auto lambdaUnit = getUnitFromMDDimension(workspace->getDimension(0)); if (lambdaUnit.empty() || lambdaUnit == "Angstrom") { diff --git a/Framework/DataHandling/src/SaveParameterFile.cpp b/Framework/DataHandling/src/SaveParameterFile.cpp index 53443e9a81bb10c3341ac90833660d4ffab5bede..c529099616efecbbb3b558eee3f727b3d7255505 100644 --- a/Framework/DataHandling/src/SaveParameterFile.cpp +++ b/Framework/DataHandling/src/SaveParameterFile.cpp @@ -112,24 +112,24 @@ void SaveParameterFile::exec() { V3D pos; std::istringstream pValueSS(pValue); pos.readPrinted(pValueSS); - toSave[cID].emplace_back(boost::make_tuple( - "x", "double", boost::lexical_cast(pos.X()))); - toSave[cID].emplace_back(boost::make_tuple( - "y", "double", boost::lexical_cast(pos.Y()))); - toSave[cID].emplace_back(boost::make_tuple( - "z", "double", boost::lexical_cast(pos.Z()))); + toSave[cID].emplace_back("x", "double", + boost::lexical_cast(pos.X())); + toSave[cID].emplace_back("y", "double", + boost::lexical_cast(pos.Y())); + toSave[cID].emplace_back("z", "double", + boost::lexical_cast(pos.Z())); } } else if (pName == "rot") { if (saveLocationParams) { V3D rot; std::istringstream pValueSS(pValue); rot.readPrinted(pValueSS); - toSave[cID].emplace_back(boost::make_tuple( - "rotx", "double", boost::lexical_cast(rot.X()))); - toSave[cID].emplace_back(boost::make_tuple( - "roty", "double", boost::lexical_cast(rot.Y()))); - toSave[cID].emplace_back(boost::make_tuple( - "rotz", "double", boost::lexical_cast(rot.Z()))); + toSave[cID].emplace_back("rotx", "double", + boost::lexical_cast(rot.X())); + toSave[cID].emplace_back("roty", "double", + boost::lexical_cast(rot.Y())); + toSave[cID].emplace_back("rotz", "double", + boost::lexical_cast(rot.Z())); } } // If it isn't a position or rotation parameter, we can just add it to the diff --git a/Framework/DataHandling/src/SetSample.cpp b/Framework/DataHandling/src/SetSample.cpp index 44d3bd57b708d360f34a5a221d7526da9a925deb..5ed86038fd61a6354c031d4b159971216fee0b8c 100644 --- a/Framework/DataHandling/src/SetSample.cpp +++ b/Framework/DataHandling/src/SetSample.cpp @@ -1123,7 +1123,7 @@ PropertyManager SetSample::materialSettingsEnsureLegacyCompatibility( if (!compatible.existsProperty("NumberDensity")) { compatible.declareProperty("NumberDensity", numberDensity); } else { - compatible.setProperty("NumberDesnity", numberDensity); + compatible.setProperty("NumberDensity", numberDensity); } } if (materialArgs.existsProperty("SampleEffectiveNumberDensity")) { diff --git a/Framework/DataHandling/src/SetSampleMaterial.cpp b/Framework/DataHandling/src/SetSampleMaterial.cpp index 7536dd5e6238fd040fe1d32e8d746509c41e8e41..96e81e8e408e78b640a5becf5d4942d80157dcdd 100644 --- a/Framework/DataHandling/src/SetSampleMaterial.cpp +++ b/Framework/DataHandling/src/SetSampleMaterial.cpp @@ -88,6 +88,12 @@ void SetSampleMaterial::init() { std::make_unique("AttenuationProfile", "", FileProperty::OptionalLoad, extensions), "The path name of the file containing the attenuation profile"); + + declareProperty( + std::make_unique("XRayAttenuationProfile", "", + FileProperty::OptionalLoad, extensions), + "The path name of the file containing the Xray attenuation profile"); + declareProperty("SampleMassDensity", EMPTY_DBL(), mustBePositive, "Measured mass density in g/cubic cm of the sample " "to be used to calculate the effective number density."); @@ -127,6 +133,7 @@ void SetSampleMaterial::init() { setPropertyGroup("AttenuationXSection", specificValuesGrp); setPropertyGroup("ScatteringXSection", specificValuesGrp); setPropertyGroup("AttenuationProfile", specificValuesGrp); + setPropertyGroup("XRayAttenuationProfile", specificValuesGrp); // Extra property settings setPropertySettings("ChemicalFormula", @@ -160,6 +167,8 @@ std::map SetSampleMaterial::validateInputs() { params.attenuationXSection = getProperty("AttenuationXSection"); params.scatteringXSection = getProperty("ScatteringXSection"); params.attenuationProfileFileName = getPropertyValue("AttenuationProfile"); + params.xRayAttenuationProfileFileName = + getPropertyValue("XRayAttenuationProfile"); const std::string numberDensityUnit = getProperty("NumberDensityUnit"); if (numberDensityUnit == "Atoms") { params.numberDensityUnit = MaterialBuilder::NumberDensityUnit::Atoms; diff --git a/Framework/DataHandling/src/SinglePeriodLoadMuonStrategy.cpp b/Framework/DataHandling/src/SinglePeriodLoadMuonStrategy.cpp index d898437c171bd9d3006c9216e5c63aac51c35aeb..fd864ece652ec1cc241a588632f1def9b9347a86 100644 --- a/Framework/DataHandling/src/SinglePeriodLoadMuonStrategy.cpp +++ b/Framework/DataHandling/src/SinglePeriodLoadMuonStrategy.cpp @@ -93,6 +93,15 @@ Workspace_sptr SinglePeriodLoadMuonStrategy::loadDeadTimeTable() const { m_detectors, m_isFileMultiPeriod, m_entryNumber); return createDeadTimeTable(m_detectors, deadTimes); } +/** + * Gets time zero table from loaded time zeros + * @returns :: Time zero table + */ +Workspace_sptr SinglePeriodLoadMuonStrategy::getTimeZeroTable() { + const auto numSpec = m_workspace.getNumberHistograms(); + auto timeZeros = m_nexusLoader.loadTimeZeroListFromNexusFile(numSpec); + return createTimeZeroTable(numSpec, timeZeros); +} /** * Performs time-zero correction on the loaded workspace. */ diff --git a/Framework/DataHandling/test/LoadEventNexusTest.h b/Framework/DataHandling/test/LoadEventNexusTest.h index f90a26cfe1dd86e3c7f6c6827b448fe9a6d23272..8838d4bae90d10280c9304f1c4cb89790f05851f 100644 --- a/Framework/DataHandling/test/LoadEventNexusTest.h +++ b/Framework/DataHandling/test/LoadEventNexusTest.h @@ -218,6 +218,27 @@ public: (150 * 150) + 2) // Two monitors } + void test_load_event_nexus_v20_ess_log_filtered() { + const std::string file = "V20_ESS_example.nxs"; + std::vector allowed = {"proton_charge", "S2HGap", "S2VGap"}; + + LoadEventNexus alg; + alg.setChild(true); + alg.setRethrows(true); + alg.initialize(); + alg.setProperty("Filename", file); + alg.setProperty("AllowList", allowed); + alg.setProperty("OutputWorkspace", "dummy_for_child"); + alg.execute(); + Workspace_sptr ws = alg.getProperty("OutputWorkspace"); + auto eventWS = std::dynamic_pointer_cast(ws); + TS_ASSERT(eventWS); + + TS_ASSERT_EQUALS(eventWS->getNumberEvents(), 1439); + TS_ASSERT_EQUALS(eventWS->detectorInfo().size(), + (150 * 150) + 2) // Two monitors + } + void test_load_event_nexus_v20_ess_integration_2018() { // Only perform this test if the version of hdf5 supports vlen strings if (NexusGeometry::Hdf5Version::checkVariableLengthStringSupport()) { @@ -963,7 +984,9 @@ public: TSM_ASSERT_EQUALS("Wrong number of periods extracted", nPeriods, 4); TSM_ASSERT_EQUALS("Groups size should be same as nperiods", outGroup->size(), nPeriods); - + // mean of proton charge for each period + std::array protonChargeMeans = {0.00110488, 0.00110392, + 0.00110343, 0.00110404}; for (size_t i = 0; i < outGroup->size(); ++i) { EventWorkspace_sptr ws = std::dynamic_pointer_cast(outGroup->getItem(i)); @@ -982,6 +1005,11 @@ public: ws->run().hasProperty(periodBoolLog)); TSM_ASSERT_EQUALS("Current period is not what was expected.", currentPeriod, i + 1); + + // Check we have correctly filtered sample logs based on the period + auto protonLog = ws->run().getTimeSeriesProperty("proton_charge"); + TS_ASSERT(protonLog->isFiltered()); + TS_ASSERT_DELTA(protonLog->mean(), protonChargeMeans[i], 1e-8); } // Make sure that the spectraNo are equal for all child workspaces. auto isFirstChildWorkspace = true; diff --git a/Framework/DataHandling/test/LoadILLDiffractionTest.h b/Framework/DataHandling/test/LoadILLDiffractionTest.h index c8fc8b6c8eca5178567c71c498ad09c537590c58..1a4bde99cc6106e313c2a3e84b91c56e62cbbbef 100644 --- a/Framework/DataHandling/test/LoadILLDiffractionTest.h +++ b/Framework/DataHandling/test/LoadILLDiffractionTest.h @@ -19,6 +19,7 @@ #include "MantidKernel/FacilityInfo.h" #include "MantidKernel/Unit.h" #include "MantidKernel/V3D.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" using namespace Mantid::API; using namespace Mantid::Kernel; @@ -36,6 +37,7 @@ public: LoadILLDiffractionTest() { ConfigService::Instance().appendDataSearchSubDir("ILL/D20/"); ConfigService::Instance().appendDataSearchSubDir("ILL/D2B/"); + ConfigService::Instance().appendDataSearchSubDir("ILL/D1B/"); } void setUp() override { @@ -82,6 +84,7 @@ public: TS_ASSERT(!outputWS->isHistogramData()) TS_ASSERT(!outputWS->isDistribution()) TS_ASSERT_EQUALS(outputWS->getAxis(0)->unit()->unitID(), "Degrees") + checkTimeFormat(outputWS); } void test_D20_no_scan() { @@ -169,6 +172,7 @@ public: TS_ASSERT_EQUALS( outputWS->run().getProperty("Detector.calibration_file")->value(), "none") + checkTimeFormat(outputWS); } void test_D20_no_scan_requesting_calibrated_throws() { @@ -186,7 +190,7 @@ public: TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("DataType", "Calibrated")) TS_ASSERT_THROWS_EQUALS(alg.execute(), std::runtime_error & e, std::string(e.what()), - "Some invalid Properties found") + "Some invalid Properties found: [ DataType ]") } void test_D20_scan() { @@ -255,6 +259,7 @@ public: "4.8\n2017-Feb-15 08:59:02.423509996 5\n"; TS_ASSERT_EQUALS(omega->value(), omegaTimeSeriesValue) + checkTimeFormat(outputWS); } void test_D20_detector_scan_offset() { @@ -278,6 +283,7 @@ public: position.getSpherical(r, theta, phi); TS_ASSERT_DELTA(theta, 5.825, 0.001); TS_ASSERT_LESS_THAN(position.X(), 0.); + checkTimeFormat(outputWS); } void test_D20_multifile() { @@ -301,6 +307,7 @@ public: TS_ASSERT(outputWS->detectorInfo().isMonitor(0)) TS_ASSERT(!outputWS->isHistogramData()) TS_ASSERT(!outputWS->isDistribution()) + checkTimeFormat(outputWS); } void test_D2B_alignment() { @@ -349,6 +356,7 @@ public: TS_ASSERT_DELTA(tube128CentreTime2.Y(), 0., 0.001) tube128CentreTime2.getSpherical(r, theta, phi); TS_ASSERT_DELTA(theta, 147.55, 0.001) + checkTimeFormat(outputWS); } void do_test_D2B_single_file(const std::string &dataType) { @@ -439,6 +447,7 @@ public: ANGULAR_DETECTOR_SPACING * double(i)), 1e-2) } + checkTimeFormat(outputWS); } TS_ASSERT(outputWS->run().hasProperty("Multi.TotalCount")) @@ -478,6 +487,41 @@ public: TS_ASSERT(run.hasProperty("ScanType")); const auto type = run.getLogData("ScanType"); TS_ASSERT_EQUALS(type->value(), "DetectorScan"); + checkTimeFormat(outputWS); + } + + void checkTimeFormat(MatrixWorkspace_const_sptr outputWS) { + TS_ASSERT(outputWS->run().hasProperty("start_time")); + TS_ASSERT(Mantid::Types::Core::DateAndTimeHelpers::stringIsISO8601( + outputWS->run().getProperty("start_time")->value())); + } + + void test_D1B() { + const int NUMBER_OF_TUBES = 1280; + const int NUMBER_OF_MONITORS = 1; + + LoadILLDiffraction alg; + alg.setChild(true); + alg.initialize(); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Filename", "473432.nxs")) + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("TwoThetaOffset", "0.0")) + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", "__")) + TS_ASSERT_THROWS_NOTHING(alg.execute()) + TS_ASSERT(alg.isExecuted()) + MatrixWorkspace_sptr outputWS = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outputWS) + const auto run = outputWS->run(); + + const auto &detInfo = outputWS->detectorInfo(); + TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), + NUMBER_OF_TUBES + NUMBER_OF_MONITORS) + + TS_ASSERT(!detInfo.isMonitor({1, 0})) + auto firstTube = detInfo.position({1, 0}); + TS_ASSERT_DELTA(firstTube.angle(V3D(0, 0, 1)) * RAD_2_DEG, 0.85, 1e-6) + + TS_ASSERT_EQUALS(outputWS->y(13)[0], 1394) + checkTimeFormat(outputWS); } private: diff --git a/Framework/DataHandling/test/LoadILLIndirect2Test.h b/Framework/DataHandling/test/LoadILLIndirect2Test.h index 62959a21b1e6753f284a010e6111bafaf46335c1..2666a76b43485e333f4899d6ac5837897bb563b5 100644 --- a/Framework/DataHandling/test/LoadILLIndirect2Test.h +++ b/Framework/DataHandling/test/LoadILLIndirect2Test.h @@ -13,6 +13,7 @@ #include "MantidDataHandling/LoadILLIndirect2.h" #include "MantidGeometry/Instrument.h" #include "MantidGeometry/Instrument/DetectorInfo.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" #include @@ -85,6 +86,7 @@ public: const auto &detInfo = output2D->detectorInfo(); constexpr double degToRad = M_PI / 180.; TS_ASSERT_DELTA(detInfo.twoTheta(65), 33.1 * degToRad, 0.01) + checkTimeFormat(output2D); } void test_first_tube_251() { @@ -108,6 +110,7 @@ public: const std::string idf = output2D->getInstrument()->getFilename(); TS_ASSERT_EQUALS(output2D->getInstrument()->getName(), "IN16BF"); TS_ASSERT(boost::ends_with(idf, "IN16BF_Definition.xml")); + checkTimeFormat(output2D); } void test_diffraction_bats() { @@ -134,6 +137,7 @@ public: TS_ASSERT_EQUALS(output2D->dataY(1050)[1156], 16) TS_ASSERT_EQUALS(output2D->dataY(871)[1157], 17) TS_ASSERT_EQUALS(output2D->dataY(746)[1157], 18) + checkTimeFormat(output2D); AnalysisDataService::Instance().clear(); } @@ -161,6 +165,7 @@ public: TS_ASSERT_EQUALS(output2D->dataY(1050)[558], 2) TS_ASSERT_EQUALS(output2D->dataY(873)[557], 2) TS_ASSERT_EQUALS(output2D->dataY(724)[561], 3) + checkTimeFormat(output2D); AnalysisDataService::Instance().clear(); } @@ -191,11 +196,18 @@ public: const Mantid::API::Run &runlogs = output->run(); TS_ASSERT(runlogs.hasProperty("Facility")); TS_ASSERT_EQUALS(runlogs.getProperty("Facility")->value(), "ILL"); + checkTimeFormat(output); // Remove workspace from the data service. AnalysisDataService::Instance().clear(); } + void checkTimeFormat(MatrixWorkspace_const_sptr outputWS) { + TS_ASSERT(outputWS->run().hasProperty("start_time")); + TS_ASSERT(Mantid::Types::Core::DateAndTimeHelpers::stringIsISO8601( + outputWS->run().getProperty("start_time")->value())); + } + private: std::string m_dataFile2013{"ILL/IN16B/034745.nxs"}; std::string m_dataFile2015{"ILL/IN16B/127500.nxs"}; diff --git a/Framework/DataHandling/test/LoadILLPolarizedDiffractionTest.h b/Framework/DataHandling/test/LoadILLPolarizedDiffractionTest.h index ef2847ddf8fe02be9583d1e3e26f1717f0b1a473..69630b3aba9eda21a04b3e1e08a33d7e63d2b8dd 100644 --- a/Framework/DataHandling/test/LoadILLPolarizedDiffractionTest.h +++ b/Framework/DataHandling/test/LoadILLPolarizedDiffractionTest.h @@ -22,6 +22,8 @@ #include "MantidKernel/FacilityInfo.h" #include "MantidKernel/Unit.h" #include "MantidKernel/V3D.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" + #include using namespace Mantid::API; @@ -129,6 +131,7 @@ public: TS_ASSERT_DELTA(workspaceEntry1->x(133)[1], 3.19, 0.01) TS_ASSERT_EQUALS(workspaceEntry1->y(133)[0], 2042) TS_ASSERT_DELTA(workspaceEntry1->e(133)[0], 45.18, 0.01) + checkTimeFormat(workspaceEntry1); } void test_D7_timeOfFlight() { @@ -154,6 +157,9 @@ public: std::dynamic_pointer_cast( outputWS->getItem(0)); TS_ASSERT(workspaceEntry1) + TS_ASSERT_EQUALS(workspaceEntry1->getAxis(0)->unit()->unitID(), "TOF") + TS_ASSERT_EQUALS(workspaceEntry1->getAxis(0)->unit()->caption(), + "Time-of-flight") TS_ASSERT_DELTA(workspaceEntry1->x(0)[0], 180.00, 0.01) TS_ASSERT_DELTA(workspaceEntry1->x(0)[1], 186.64, 0.01) @@ -214,6 +220,96 @@ public: TS_ASSERT_DELTA(workspaceEntry1->x(133)[512], 3579.68, 0.01) TS_ASSERT_EQUALS(workspaceEntry1->y(133)[511], 0) TS_ASSERT_DELTA(workspaceEntry1->e(133)[511], 0.00, 0.01) + checkTimeFormat(workspaceEntry1); + } + + void test_D7_timeOfFlight_timechannels() { + // Tests loading TOF data for D7 + LoadILLPolarizedDiffraction alg; + alg.setChild(true); + TS_ASSERT_THROWS_NOTHING(alg.initialize()) + TS_ASSERT(alg.isInitialized()) + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Filename", "395850")) + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("OutputWorkspace", "_outWS")) + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("PositionCalibration", "None")) + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("TOFUnits", "TimeChannels")) + TS_ASSERT_THROWS_NOTHING(alg.execute()) + TS_ASSERT(alg.isExecuted()) + + WorkspaceGroup_sptr outputWS = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outputWS) + TS_ASSERT(outputWS->isGroup()) + TS_ASSERT_EQUALS(outputWS->getNumberOfEntries(), 2) + do_test_general_features(outputWS, "TOF"); + + MatrixWorkspace_sptr workspaceEntry1 = + std::dynamic_pointer_cast( + outputWS->getItem(0)); + TS_ASSERT(workspaceEntry1) + TS_ASSERT_EQUALS(workspaceEntry1->getAxis(0)->unit()->unitID(), "Label") + TS_ASSERT_EQUALS(workspaceEntry1->getAxis(0)->unit()->caption(), + "Time channel") + + TS_ASSERT_EQUALS(workspaceEntry1->x(0)[0], 0) + TS_ASSERT_EQUALS(workspaceEntry1->x(0)[1], 1) + TS_ASSERT_EQUALS(workspaceEntry1->y(0)[0], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(0)[0], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(0)[511], 511) + TS_ASSERT_EQUALS(workspaceEntry1->x(0)[512], 512) + TS_ASSERT_EQUALS(workspaceEntry1->y(0)[511], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(0)[511], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(1)[0], 0) + TS_ASSERT_EQUALS(workspaceEntry1->x(1)[1], 1) + TS_ASSERT_EQUALS(workspaceEntry1->y(1)[0], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(1)[0], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(1)[511], 511) + TS_ASSERT_EQUALS(workspaceEntry1->x(1)[512], 512) + TS_ASSERT_EQUALS(workspaceEntry1->y(1)[511], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(1)[511], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(130)[0], 0) + TS_ASSERT_EQUALS(workspaceEntry1->x(130)[1], 1) + TS_ASSERT_EQUALS(workspaceEntry1->y(130)[0], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(130)[0], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(130)[365], 365) + TS_ASSERT_EQUALS(workspaceEntry1->x(130)[366], 366) + TS_ASSERT_EQUALS(workspaceEntry1->y(130)[365], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(130)[365], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(131)[0], 0) + TS_ASSERT_EQUALS(workspaceEntry1->x(131)[1], 1) + TS_ASSERT_EQUALS(workspaceEntry1->y(131)[0], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(131)[0], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(131)[365], 365) + TS_ASSERT_EQUALS(workspaceEntry1->x(131)[366], 366) + TS_ASSERT_EQUALS(workspaceEntry1->y(131)[365], 1) + TS_ASSERT_DELTA(workspaceEntry1->e(131)[365], 1.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(132)[0], 0) + TS_ASSERT_EQUALS(workspaceEntry1->x(132)[1], 1) + TS_ASSERT_EQUALS(workspaceEntry1->y(132)[0], 5468) + TS_ASSERT_DELTA(workspaceEntry1->e(132)[0], 73.94, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(132)[511], 511) + TS_ASSERT_EQUALS(workspaceEntry1->x(132)[512], 512) + TS_ASSERT_EQUALS(workspaceEntry1->y(132)[511], 5394) + TS_ASSERT_DELTA(workspaceEntry1->e(132)[511], 73.44, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(133)[0], 0) + TS_ASSERT_EQUALS(workspaceEntry1->x(133)[1], 1) + TS_ASSERT_EQUALS(workspaceEntry1->y(133)[0], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(133)[0], 0.00, 0.01) + + TS_ASSERT_EQUALS(workspaceEntry1->x(133)[511], 511) + TS_ASSERT_EQUALS(workspaceEntry1->x(133)[512], 512) + TS_ASSERT_EQUALS(workspaceEntry1->y(133)[511], 0) + TS_ASSERT_DELTA(workspaceEntry1->e(133)[511], 0.00, 0.01) } void test_D7_multifile_sum() { @@ -272,6 +368,7 @@ public: TS_ASSERT_DELTA(workspaceEntry1->x(133)[1], 3.19, 0.01) TS_ASSERT_EQUALS(workspaceEntry1->y(133)[0], 4109) TS_ASSERT_DELTA(workspaceEntry1->e(133)[0], 64.10, 0.01) + checkTimeFormat(workspaceEntry1); } void test_D7_multifile_list() { @@ -361,6 +458,7 @@ public: TS_ASSERT_DELTA(workspaceEntry12->x(133)[1], 3.19, 0.01) TS_ASSERT_EQUALS(workspaceEntry12->y(133)[0], 108504) TS_ASSERT_DELTA(workspaceEntry12->e(133)[0], 329.39, 0.01) + checkTimeFormat(workspaceEntry1); } void test_D7_default_alignment() { @@ -497,6 +595,7 @@ public: double wavelength = stod(ws->mutableRun().getLogData("monochromator.wavelength")->value()); TS_ASSERT_DELTA(wavelength, 3.09, 0.01) + checkTimeFormat(ws); } void test_D7_transpose() { @@ -566,7 +665,7 @@ public: TS_ASSERT(workspaceEntry->isHistogramData()) TS_ASSERT(!workspaceEntry->isDistribution()) TS_ASSERT_EQUALS(workspaceEntry->YUnitLabel(), "Counts") - TS_ASSERT_EQUALS(workspaceEntry->getAxis(0)->unit()->caption(), + TS_ASSERT_EQUALS(workspaceEntry->getAxis(0)->unit()->unitID(), "Wavelength") TS_ASSERT_DELTA(workspaceEntry->getAxis(1)->getValue(0), 12.66, 0.01) TS_ASSERT_DELTA(workspaceEntry->getAxis(1)->getValue(1), 13.45, 0.01) @@ -628,20 +727,23 @@ public: TS_ASSERT(workspaceEntry->isHistogramData()) TS_ASSERT(!workspaceEntry->isDistribution()) TS_ASSERT_EQUALS(workspaceEntry->YUnitLabel(), "Counts") + checkTimeFormat(workspaceEntry); if (measurementMode == "monochromatic") { TS_ASSERT_EQUALS(workspaceEntry->blocksize(), 1) - TS_ASSERT_EQUALS(workspaceEntry->getAxis(0)->unit()->caption(), + TS_ASSERT_EQUALS(workspaceEntry->getAxis(0)->unit()->unitID(), "Wavelength") } else if (measurementMode == "TOF") { - { - TS_ASSERT_EQUALS(workspaceEntry->getAxis(0)->unit()->caption(), - "Time") - TS_ASSERT_EQUALS(workspaceEntry->blocksize(), 512) - } + { TS_ASSERT_EQUALS(workspaceEntry->blocksize(), 512) } } } } + void checkTimeFormat(MatrixWorkspace_const_sptr outputWS) { + TS_ASSERT(outputWS->run().hasProperty("start_time")); + TS_ASSERT(Mantid::Types::Core::DateAndTimeHelpers::stringIsISO8601( + outputWS->run().getProperty("start_time")->value())); + } + private: const double RAD_2_DEG = 180.0 / M_PI; std::string m_oldFacility; diff --git a/Framework/DataHandling/test/LoadILLReflectometryTest.h b/Framework/DataHandling/test/LoadILLReflectometryTest.h index 984e190d71a6dd06743df69a46b64809ec37dc17..5305e3f3a8bf1c4db71507a39019ad5a0ac0ea80 100644 --- a/Framework/DataHandling/test/LoadILLReflectometryTest.h +++ b/Framework/DataHandling/test/LoadILLReflectometryTest.h @@ -19,6 +19,7 @@ #include "MantidGeometry/Instrument.h" #include "MantidGeometry/Instrument/RectangularDetector.h" #include "MantidKernel/Unit.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" using namespace Mantid::API; using Mantid::DataHandling::LoadILLReflectometry; @@ -55,6 +56,10 @@ private: std::set{0}) // sample log entry must exist TS_ASSERT(output->run().hasProperty("reduction.line_position")) + + TS_ASSERT(output->run().hasProperty("start_time")); + TS_ASSERT(Mantid::Types::Core::DateAndTimeHelpers::stringIsISO8601( + output->run().getProperty("start_time")->value())); } static double detCounts(const MatrixWorkspace_sptr &output) { diff --git a/Framework/DataHandling/test/LoadILLSANSTest.h b/Framework/DataHandling/test/LoadILLSANSTest.h index e990384d81d76a2e5dfca8cf029be5384bc9d78f..8881fac768a371e092164b5451c7155796b67e36 100644 --- a/Framework/DataHandling/test/LoadILLSANSTest.h +++ b/Framework/DataHandling/test/LoadILLSANSTest.h @@ -17,6 +17,7 @@ #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/Unit.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" using Mantid::API::AnalysisDataService; using Mantid::API::Axis; @@ -39,7 +40,9 @@ public: LoadILLSANSTest() { ConfigService::Instance().appendDataSearchSubDir("ILL/D11/"); + ConfigService::Instance().appendDataSearchSubDir("ILL/D11B/"); ConfigService::Instance().appendDataSearchSubDir("ILL/D22/"); + ConfigService::Instance().appendDataSearchSubDir("ILL/D22B/"); ConfigService::Instance().appendDataSearchSubDir("ILL/D33/"); ConfigService::Instance().appendDataSearchSubDir("ILL/D16/"); ConfigService::Instance().setFacility("ILL"); @@ -95,6 +98,53 @@ public: TS_ASSERT_DELTA(err6[0], sqrt(20), 1E-5) const auto unit = outputWS->getAxis(0)->unit()->unitID(); TS_ASSERT_EQUALS(unit, "Wavelength"); + checkTimeFormat(outputWS); + } + + void test_D11B() { + LoadILLSANS alg; + alg.setChild(true); + alg.initialize(); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Filename", "027194.nxs")) + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("OutputWorkspace", "__unused_for_child")) + TS_ASSERT_THROWS_NOTHING(alg.execute()) + TS_ASSERT(alg.isExecuted()) + MatrixWorkspace_const_sptr outputWS = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outputWS) + TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), + 192 * 256 + 2 * 32 * 256 + 2) + TS_ASSERT_EQUALS(outputWS->blocksize(), 1) + TS_ASSERT(outputWS->detectorInfo().isMonitor(192 * 256 + 2 * 32 * 256)) + TS_ASSERT(outputWS->detectorInfo().isMonitor(192 * 256 + 2 * 32 * 256 + 1)) + TS_ASSERT(outputWS->isHistogramData()) + TS_ASSERT(!outputWS->isDistribution()) + const auto &instrument = outputWS->getInstrument(); + + IComponent_const_sptr component = + instrument->getComponentByName("detector_center"); + V3D pos = component->getPos(); + TS_ASSERT_DELTA(pos.Z(), 8.9274824298, 1E-3) + + component = instrument->getComponentByName("detector_left"); + pos = component->getPos(); + TS_ASSERT_DELTA(pos.Z(), 8.82248, 1E-5) + + component = instrument->getComponentByName("detector_right"); + pos = component->getPos(); + TS_ASSERT_DELTA(pos.Z(), 8.82248, 1E-5) + + const auto &xAxis = outputWS->x(0).rawData(); + const auto &spec = outputWS->y(3630).rawData(); + const auto &err = outputWS->e(3630).rawData(); + TS_ASSERT_EQUALS(xAxis.size(), 2) + TS_ASSERT_DELTA(xAxis[0], 6.685, 1E-5) + TS_ASSERT_DELTA(xAxis[1], 7.315, 1E-5) + TS_ASSERT_EQUALS(spec[0], 246) + TS_ASSERT_DELTA(err[0], sqrt(246), 1E-5) + const auto unit = outputWS->getAxis(0)->unit()->unitID(); + TS_ASSERT_EQUALS(unit, "Wavelength"); + checkTimeFormat(outputWS); } void test_D22() { @@ -130,6 +180,60 @@ public: TS_ASSERT_DELTA(err6[0], sqrt(45), 1E-5) const auto unit = outputWS->getAxis(0)->unit()->unitID(); TS_ASSERT_EQUALS(unit, "Wavelength"); + checkTimeFormat(outputWS); + } + + void test_d22B() { + LoadILLSANS alg; + alg.setChild(true); + alg.initialize(); + TS_ASSERT_THROWS_NOTHING(alg.setPropertyValue("Filename", "000180.nxs")) + TS_ASSERT_THROWS_NOTHING( + alg.setPropertyValue("OutputWorkspace", "__unused_for_child")) + TS_ASSERT_THROWS_NOTHING(alg.execute()) + TS_ASSERT(alg.isExecuted()) + MatrixWorkspace_const_sptr outputWS = alg.getProperty("OutputWorkspace"); + TS_ASSERT(outputWS) + TS_ASSERT_EQUALS(outputWS->getNumberHistograms(), 128 * 256 + 96 * 256 + 2) + TS_ASSERT_EQUALS(outputWS->blocksize(), 1) + TS_ASSERT(outputWS->detectorInfo().isMonitor(128 * 256 + 96 * 256)) + TS_ASSERT(outputWS->detectorInfo().isMonitor(128 * 256 + 96 * 256 + 1)) + TS_ASSERT(outputWS->isHistogramData()) + TS_ASSERT(!outputWS->isDistribution()) + TS_ASSERT_EQUALS(outputWS->getAxis(0)->unit()->unitID(), "Wavelength"); + const auto &instrument = outputWS->getInstrument(); + const auto &run = outputWS->run(); + + IComponent_const_sptr comp = + instrument->getComponentByName("detector_back"); + V3D pos = comp->getPos(); + TS_ASSERT(run.hasProperty("Detector 2.det2_calc")) + double det2_calc = run.getLogAsSingleValue("Detector 2.det2_calc"); + TS_ASSERT(run.hasProperty("Detector 2.dtr2_actual")) + double dtr2_act = run.getLogAsSingleValue("Detector 2.dtr2_actual"); + TS_ASSERT_DELTA(pos.Z(), det2_calc, 1E-6) + TS_ASSERT_DELTA(pos.X(), -dtr2_act / 1000., 1E-6) + TS_ASSERT(run.hasProperty("L2")) + double l2 = run.getLogAsSingleValue("L2"); + TS_ASSERT_DELTA(l2, det2_calc, 1E-6) + + comp = instrument->getComponentByName("detector_front"); + pos = comp->getPos(); + TS_ASSERT(run.hasProperty("Detector 1.det1_calc")) + double det1_calc = run.getLogAsSingleValue("Detector 1.det1_calc"); + TS_ASSERT(run.hasProperty("Detector 1.dtr1_actual")) + double dtr1_act = run.getLogAsSingleValue("Detector 1.dtr1_actual"); + TS_ASSERT_DELTA(pos.Z(), det1_calc, 1E-6) + TS_ASSERT_DELTA(pos.X(), -dtr1_act / 1000., 1E-6) + TS_ASSERT(run.hasProperty("Detector 1.dan1_actual")) + double dan1_act = run.getLogAsSingleValue("Detector 1.dan1_actual"); + double angle, qx, qy, qz; + comp->getRotation().getAngleAxis(angle, qx, qy, qz); + TS_ASSERT_DELTA(angle, dan1_act, 1E-6) + TS_ASSERT_EQUALS(qx, 0.) + TS_ASSERT_DELTA(std::fabs(qy), 1., 1E-6) + TS_ASSERT_EQUALS(qz, 0.) + checkTimeFormat(outputWS); } void test_D16() { @@ -185,6 +289,7 @@ public: TS_ASSERT_DELTA(xAxis[1], 7.035, 1E-3) TS_ASSERT_EQUALS(spec[0], 17) TS_ASSERT_DELTA(err[0], sqrt(17), 1E-5) + checkTimeFormat(outputWS); } void test_D33() { @@ -230,6 +335,7 @@ public: TS_ASSERT_EQUALS(bottom->getPos(), V3D(0, -0.41, 1.3118)); const auto unit = outputWS->getAxis(0)->unit()->unitID(); TS_ASSERT_EQUALS(unit, "Wavelength"); + checkTimeFormat(outputWS); } void test_D33_TOF() { @@ -259,6 +365,13 @@ public: TS_ASSERT_EQUALS(tof->value(), "TOF"); const auto unit = outputWS->getAxis(0)->unit()->unitID(); TS_ASSERT_EQUALS(unit, "Wavelength"); + checkTimeFormat(outputWS); + } + + void checkTimeFormat(MatrixWorkspace_const_sptr outputWS) { + TS_ASSERT(outputWS->run().hasProperty("start_time")); + TS_ASSERT(Mantid::Types::Core::DateAndTimeHelpers::stringIsISO8601( + outputWS->run().getProperty("start_time")->value())); } }; diff --git a/Framework/DataHandling/test/LoadILLTOF2Test.h b/Framework/DataHandling/test/LoadILLTOF2Test.h index f08d2455448b23aeb9e7726a03cff19dc8e7c7e0..67b97d55707919a8e7328108f48241e9cdd19011 100644 --- a/Framework/DataHandling/test/LoadILLTOF2Test.h +++ b/Framework/DataHandling/test/LoadILLTOF2Test.h @@ -10,6 +10,8 @@ #include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/SpectrumInfo.h" #include "MantidDataHandling/LoadILLTOF2.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" + #include using namespace Mantid::API; @@ -65,6 +67,10 @@ public: AnalysisDataService::Instance().retrieveWS( outputSpace); + TS_ASSERT(output->run().hasProperty("start_time")); + TS_ASSERT(Mantid::Types::Core::DateAndTimeHelpers::stringIsISO8601( + output->run().getProperty("start_time")->value())); + TS_ASSERT_EQUALS(output->getNumberHistograms(), numberOfHistograms) const auto &spectrumInfo = output->spectrumInfo(); for (size_t wsIndex = 0; wsIndex != output->getNumberHistograms(); diff --git a/Framework/DataHandling/test/LoadMuonNexusV2Test.h b/Framework/DataHandling/test/LoadMuonNexusV2Test.h index 0cbd43451ce27ee03fc87fbedfb3b0e8334dc29d..e2f77aa426b078205db7240709337ddcf542dfd6 100644 --- a/Framework/DataHandling/test/LoadMuonNexusV2Test.h +++ b/Framework/DataHandling/test/LoadMuonNexusV2Test.h @@ -124,6 +124,32 @@ public: TS_ASSERT_DELTA(deadTimeTable->Double(62, 1), 0.0073113599, 1e-6); } + void testExecWithTimeZeroTable() { + LoadMuonNexusV2 ld; + ld.initialize(); + ld.setPropertyValue("Filename", "EMU00102347.nxs_v2"); + ld.setPropertyValue("OutputWorkspace", "outWs"); + ld.setPropertyValue("TimeZeroTable", "tzt"); + ld.setRethrows(true); + auto &ads = AnalysisDataService::Instance(); + + ld.execute(); + // Verify that the output workspaces exist + TS_ASSERT(ads.doesExist("outWs")); + TS_ASSERT(ads.doesExist("tzt")); + + Workspace2D_sptr output2D = std::dynamic_pointer_cast( + ads.retrieveWS("outWs")); + TableWorkspace_sptr tbl = ads.retrieveWS("tzt"); + + // Check number of rows and columns + TS_ASSERT_EQUALS(tbl->columnCount(), 1); + TS_ASSERT_EQUALS(tbl->rowCount(), output2D->getNumberHistograms()); + TS_ASSERT_DELTA(tbl->Double(0, 0), 0.16, 0.001); + TS_ASSERT_DELTA(tbl->Double(47, 0), 0.16, 0.001); + TS_ASSERT_DELTA(tbl->Double(95, 0), 0.16, 0.001); + } + void testExecWithGroupingTable() { LoadMuonNexusV2 ld; diff --git a/Framework/DataHandling/test/LoadNXcanSASTest.h b/Framework/DataHandling/test/LoadNXcanSASTest.h index 033734375592a3701800865d0f057eab86112f6c..753dfc1cd620cfc048c0c9f29d864c30ea606d6b 100644 --- a/Framework/DataHandling/test/LoadNXcanSASTest.h +++ b/Framework/DataHandling/test/LoadNXcanSASTest.h @@ -46,7 +46,7 @@ public: parameters.hasDx = true; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); save_file_no_issues(ws, parameters); @@ -74,7 +74,7 @@ public: parameters.hasDx = false; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); save_file_no_issues(ws, parameters); @@ -102,7 +102,7 @@ public: parameters.hasDx = false; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); const std::string outWsName = "loadNXcanSASTestOutputWorkspace"; @@ -117,13 +117,12 @@ public: transmissionCanParameters.usesTransmission = true; auto transmission = getTransmissionWorkspace(transmissionParameters); - setXValuesOn1DWorkspaceWithPointData( - transmission, transmissionParameters.xmin, transmissionParameters.xmax); + setXValuesOn1DWorkspace(transmission, transmissionParameters.xmin, + transmissionParameters.xmax); auto transmissionCan = getTransmissionWorkspace(transmissionCanParameters); - setXValuesOn1DWorkspaceWithPointData(transmissionCan, - transmissionCanParameters.xmin, - transmissionCanParameters.xmax); + setXValuesOn1DWorkspace(transmissionCan, transmissionCanParameters.xmin, + transmissionCanParameters.xmax); save_file_no_issues(ws, parameters, transmission, transmissionCan); @@ -151,6 +150,20 @@ public: removeWorkspaceFromADS(transNameCan); } + void test_that_legacy_transmissions_saved_as_histograms_are_loaded() { + NXcanSASTestParameters parameters; + parameters.filename = "NXcanSAS-histo-lambda.h5"; + const std::string outWsName{"loaded_histo_trans"}; + + Mantid::API::MatrixWorkspace_sptr wsOut; + TS_ASSERT_THROWS_NOTHING( + wsOut = load_file_no_issues(parameters, true /*load transmission*/, + outWsName)); + TS_ASSERT(!wsOut->isHistogramData()); + + removeWorkspaceFromADS(outWsName); + } + void test_that_2D_workspace_can_be_loaded() { // Arrange NXcanSASTestParameters parameters; @@ -237,7 +250,7 @@ public: parameters.isHistogram = true; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); save_file_no_issues(ws, parameters); @@ -570,8 +583,7 @@ private: parameters1D.hasDx = true; auto ws = provide1DWorkspace(parameters1D); - setXValuesOn1DWorkspaceWithPointData(ws, parameters1D.xmin, - parameters1D.xmax); + setXValuesOn1DWorkspace(ws, parameters1D.xmin, parameters1D.xmax); parameters1D.idf = getIDFfromWorkspace(ws); save_no_assert(ws, parameters1D); diff --git a/Framework/DataHandling/test/LoadNexusLogsTest.h b/Framework/DataHandling/test/LoadNexusLogsTest.h index 54292e964cf90fed76472e1d4d38241ba8d07af8..abad3be2f9c729707af251bf40205b459cc3f0f3 100644 --- a/Framework/DataHandling/test/LoadNexusLogsTest.h +++ b/Framework/DataHandling/test/LoadNexusLogsTest.h @@ -268,7 +268,7 @@ public: loader.initialize(); loader.setProperty("Workspace", testWS); loader.setPropertyValue("Filename", "larmor_array_time_series_mock.nxs"); - TS_ASSERT_THROWS_NOTHING(loader.execute()) + TS_ASSERT_THROWS_NOTHING(loader.execute()); } void test_last_time_series_log_entry_equals_end_time() { @@ -351,6 +351,112 @@ public: TS_ASSERT_DELTA(pclogFiltered2->nthValue(2), 7, 1e-5); } + void test_allow_list() { + auto testWS = createTestWorkspace(); + + std::vector allowed = {"proton_charge", "S2HGap", "S2VGap"}; + + LoadNexusLogs loader; + loader.setChild(true); + loader.initialize(); + loader.setProperty("Workspace", testWS); + loader.setPropertyValue("Filename", "LARMOR00003368.nxs"); + loader.setProperty>("AllowList", allowed); + loader.setPropertyValue("BlockList", ""); + loader.execute(); + TS_ASSERT_THROWS_NOTHING(loader.execute()); + + // selog versions + allowed.push_back("selog_S2HGap"); + allowed.push_back("selog_S2VGap"); + + // extra proton charge properties + allowed.push_back("gd_prtn_chrg"); + allowed.push_back("proton_charge_by_period"); + allowed.push_back("nperiods"); + + // The default logs that are always present: + allowed.push_back("start_time"); + allowed.push_back("end_time"); + allowed.push_back("run_title"); + + auto run = testWS->run(); + auto properties = run.getProperties(); + + TS_ASSERT_EQUALS(properties.size(), allowed.size()); + + // Lookup each name in the workspace property list + for (const auto &name : allowed) { + bool found = false; + for (const auto &prop : properties) { + if (prop->name() == name) { + found = true; + break; + } + } + TS_ASSERT_EQUALS(found, true); + if (!found) { + break; + } + } + } + + void test_block_list() { + auto testWS = createTestWorkspace(); + + std::vector blocked = {"proton_charge", "S2HGap", "S2VGap"}; + + LoadNexusLogs loader; + loader.setChild(true); + loader.initialize(); + loader.setProperty("Workspace", testWS); + loader.setPropertyValue("Filename", "LARMOR00003368.nxs"); + loader.setPropertyValue("AllowList", ""); + loader.setProperty>("BlockList", blocked); + loader.execute(); + TS_ASSERT_THROWS_NOTHING(loader.execute()); + + auto run = testWS->run(); + auto properties = run.getProperties(); + + // add 2 to account for selog_ versions of properties + TS_ASSERT_EQUALS(properties.size(), 94 - blocked.size() - 2); + + // Lookup each name in the workspace property list + for (const auto &name : blocked) { + bool found = false; + for (const auto &prop : properties) { + if (prop->name() == name) { + found = true; + break; + } + } + TS_ASSERT_EQUALS(found, false); + if (found) { + break; + } + } + } + + void test_allow_and_block_list() { + auto testWS = createTestWorkspace(); + + LoadNexusLogs loader; + loader.setChild(true); + loader.initialize(); + loader.setProperty("Workspace", testWS); + loader.setPropertyValue("Filename", "LARMOR00003368.nxs"); + loader.setPropertyValue("AllowList", ""); // Specify nothing for either + loader.setPropertyValue("BlockList", ""); // To ensure logs load as expected + loader.execute(); + TS_ASSERT_THROWS_NOTHING(loader.execute()); + + auto run = testWS->run(); + auto properties = run.getProperties(); + + TS_ASSERT_EQUALS(properties.size(), 94); + } + private: API::MatrixWorkspace_sptr createTestWorkspace() { return WorkspaceFactory::Instance().create("Workspace2D", 1, 1, 1); diff --git a/Framework/DataHandling/test/LoadNexusProcessed2Test.h b/Framework/DataHandling/test/LoadNexusProcessed2Test.h index 44f12c1c9694d042caf840df207157061db35a32..8ca69847e02b0b727b1a0855bbf822efaf2b9f60 100644 --- a/Framework/DataHandling/test/LoadNexusProcessed2Test.h +++ b/Framework/DataHandling/test/LoadNexusProcessed2Test.h @@ -132,8 +132,8 @@ public: std::vector spectrumNumbers; size_t i = wsIn->getNumberHistograms() - 1; for (size_t j = 0; j < wsIn->getNumberHistograms(); --i, ++j) { - specDefinitions.emplace_back(SpectrumDefinition(i)); - spectrumNumbers.emplace_back(SpectrumNumber(static_cast(j))); + specDefinitions.emplace_back(i); + spectrumNumbers.emplace_back(static_cast(j)); } IndexInfo info(spectrumNumbers); info.setSpectrumDefinitions(specDefinitions); @@ -189,8 +189,8 @@ public: std::vector spectrumNumbers; // We add a single detector index 0 to a single spectrum with number (1). No // other mappings provided! - specDefinitions.emplace_back(SpectrumDefinition(0)); - spectrumNumbers.emplace_back(SpectrumNumber(1)); + specDefinitions.emplace_back(0); + spectrumNumbers.emplace_back(1); IndexInfo info(spectrumNumbers); info.setSpectrumDefinitions(specDefinitions); wsIn->setIndexInfo(info); @@ -252,7 +252,7 @@ public: def.add(i); def.add(i - 1); specDefinitions.emplace_back(def); - spectrumNumbers.emplace_back(SpectrumNumber(static_cast(j))); + spectrumNumbers.emplace_back(static_cast(j)); } IndexInfo info(spectrumNumbers); info.setSpectrumDefinitions(specDefinitions); diff --git a/Framework/DataHandling/test/LoadPSIMuonBinTest.h b/Framework/DataHandling/test/LoadPSIMuonBinTest.h index 5f7ed9a01e4e0948365564e8a2de09dcde3252eb..e53b686f1eb2840d541556e70411010ff17d4bc9 100644 --- a/Framework/DataHandling/test/LoadPSIMuonBinTest.h +++ b/Framework/DataHandling/test/LoadPSIMuonBinTest.h @@ -13,11 +13,13 @@ #include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/Sample.h" #include "MantidDataHandling/LoadPSIMuonBin.h" +#include "MantidDataObjects/TableWorkspace.h" #include "MantidDataObjects/Workspace2D.h" #include "MantidKernel/FileDescriptor.h" using namespace Mantid; using namespace Mantid::DataHandling; +using namespace Mantid::DataObjects; using namespace Mantid::API; using namespace Mantid::Kernel; @@ -192,7 +194,6 @@ public: void test_time_zero_list_loaded_correctly() { LoadPSIMuonBin alg; alg.initialize(); - alg.isInitialized(); alg.setProperty("SearchForTempFile", false); alg.setProperty("Filename", getTestFilePath("deltat_tdc_dolly_1529.bin")); @@ -209,6 +210,55 @@ public: AnalysisDataService::Instance().remove("ws"); } + void test_time_zero_table_loaded_correctly() { + LoadPSIMuonBin alg; + alg.initialize(); + alg.setProperty("SearchForTempFile", false); + alg.setProperty("Filename", getTestFilePath("deltat_tdc_dolly_1529.bin")); + alg.setProperty("OutputWorkspace", "ws"); + alg.setPropertyValue("TimeZeroTable", "tzt"); + alg.execute(); + + auto &ads = AnalysisDataService::Instance(); + ITableWorkspace_sptr tbl = ads.retrieveWS("tzt"); + + TS_ASSERT_EQUALS(tbl->columnCount(), 1); + TS_ASSERT_EQUALS(tbl->getColumnNames(), + std::vector{"time zero"}); + TS_ASSERT_EQUALS(tbl->rowCount(), 4); + TS_ASSERT_DELTA(tbl->getColumn(0)->toDouble(0), 0.1582, 0.0001); + TS_ASSERT_DELTA(tbl->getColumn(0)->toDouble(1), 0.1553, 0.0001); + TS_ASSERT_DELTA(tbl->getColumn(0)->toDouble(2), 0.1592, 0.0001); + TS_ASSERT_DELTA(tbl->getColumn(0)->toDouble(3), 0.1602, 0.0001); + } + + void test_dead_time_table_loaded_correctly() { + LoadPSIMuonBin alg; + alg.initialize(); + alg.setProperty("SearchForTempFile", false); + + alg.setProperty("Filename", getTestFilePath("deltat_tdc_dolly_1529.bin")); + alg.setProperty("OutputWorkspace", "ws"); + alg.setPropertyValue("DeadTimeTable", "dtt"); + alg.execute(); + + auto &ads = AnalysisDataService::Instance(); + ITableWorkspace_sptr tbl = ads.retrieveWS("dtt"); + + TS_ASSERT_EQUALS(tbl->columnCount(), 2); + std::vector colNames{"spectrum", "dead-time"}; + TS_ASSERT_EQUALS(tbl->getColumnNames(), colNames); + TS_ASSERT_EQUALS(tbl->rowCount(), 4); + TS_ASSERT_EQUALS(tbl->getColumn(0)->toDouble(0), 1.0); + TS_ASSERT_EQUALS(tbl->getColumn(1)->toDouble(0), 0.0); + TS_ASSERT_EQUALS(tbl->getColumn(0)->toDouble(1), 2.0); + TS_ASSERT_EQUALS(tbl->getColumn(1)->toDouble(1), 0.0); + TS_ASSERT_EQUALS(tbl->getColumn(0)->toDouble(2), 3.0); + TS_ASSERT_EQUALS(tbl->getColumn(1)->toDouble(2), 0.0); + TS_ASSERT_EQUALS(tbl->getColumn(0)->toDouble(3), 4.0); + TS_ASSERT_EQUALS(tbl->getColumn(1)->toDouble(3), 0.0); + } + void test_uncorected_time_loaded_if_corrected_time_flag_is_false() { LoadPSIMuonBin alg; alg.initialize(); diff --git a/Framework/DataHandling/test/LoadParameterFileTest.h b/Framework/DataHandling/test/LoadParameterFileTest.h index aff7c89e0e4fa4868f84c6300571b6ae5209c2a3..c0f579f0251a7009330a7125a8b1ed1da3986626 100644 --- a/Framework/DataHandling/test/LoadParameterFileTest.h +++ b/Framework/DataHandling/test/LoadParameterFileTest.h @@ -11,6 +11,7 @@ #include "MantidAPI/Algorithm.h" #include "MantidAPI/AlgorithmManager.h" #include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/Run.h" #include "MantidAPI/Workspace.h" #include "MantidAPI/WorkspaceFactory.h" #include "MantidDataObjects/Workspace2D.h" @@ -231,6 +232,50 @@ public: TS_ASSERT(pLoadInstrument->isExecuted()); } + void test_load_IPF_time_validity() { + auto ws = AnalysisDataService::Instance().retrieve(wsName); + Workspace2D_sptr ws2D = std::dynamic_pointer_cast(ws); + + // set a date for the experiment + std::string start_time = "1900-01-01 00:00:00"; + ws2D->mutableRun().addProperty("run_start", start_time); + + // load in additional parameters + auto pLoaderPF = AlgorithmManager::Instance().create("LoadParameterFile"); + + TS_ASSERT_THROWS_NOTHING(pLoaderPF->initialize()); + pLoaderPF->setPropertyValue( + "Filename", + "unit_testing/IDF_for_UNIT_TESTING2_paramFile_with_dates.xml"); + pLoaderPF->setPropertyValue("Workspace", wsName); + TS_ASSERT_THROWS_NOTHING(pLoaderPF->execute()); + TS_ASSERT(pLoaderPF->isExecuted()); + MatrixWorkspace_sptr output; + + // Get back the saved workspace + TS_ASSERT_THROWS_NOTHING( + output = AnalysisDataService::Instance().retrieveWS( + wsName)); + const auto ¶mMap = output->constInstrumentParameters(); + const auto &detectorInfo = output->detectorInfo(); + const auto &det = detectorInfo.detector(detectorInfo.indexOf(1008)); + Parameter_sptr param; + param = paramMap.get(&det, "date-most-recent"); + TS_ASSERT_EQUALS(param->value(), 1); + param = paramMap.get(&det, "date-within-bound"); + TS_ASSERT_EQUALS(param->value(), 1); + param = paramMap.get(&det, "date-no-bounds-vs-end"); + TS_ASSERT_EQUALS(param->value(), 1); + param = paramMap.get(&det, "date-same-start-no-end"); + TS_ASSERT_EQUALS(param->value(), 1); + param = paramMap.get(&det, "date-no-bounds-vs-start"); + TS_ASSERT_EQUALS(param->value(), 1); + param = paramMap.get(&det, "date-same-start"); + TS_ASSERT_EQUALS(param->value(), 1); + param = paramMap.get(&det, "date-no-bounds-vs-invalid"); + TS_ASSERT_EQUALS(param->value(), 1); + } + private: std::string inputFile; std::string wsName; diff --git a/Framework/DataHandling/test/LoadRaw3Test.h b/Framework/DataHandling/test/LoadRaw3Test.h index b67c2857e45422438853a4f7242261a07a6cc17d..4eb79d96006adf7b26df8a78b98eb7ff874dbbd1 100644 --- a/Framework/DataHandling/test/LoadRaw3Test.h +++ b/Framework/DataHandling/test/LoadRaw3Test.h @@ -1020,7 +1020,7 @@ public: AnalysisDataService::Instance().remove(outputSpace); } - void test_withAlternativeDatastream() { + void testLoadFromCombinedLogFile() { LoadRaw3 loader13; if (!loader13.isInitialized()) loader13.initialize(); diff --git a/Framework/DataHandling/test/NXcanSASTestHelper.cpp b/Framework/DataHandling/test/NXcanSASTestHelper.cpp index 9680bb0d11cadd451826f97cfd6f557c99e83d20..b2b48e2b5489ff6d928c377541043762662611e9 100644 --- a/Framework/DataHandling/test/NXcanSASTestHelper.cpp +++ b/Framework/DataHandling/test/NXcanSASTestHelper.cpp @@ -17,6 +17,36 @@ #include +namespace { +// Create a histogram from a workspace and return it +Mantid::API::MatrixWorkspace_sptr +toHistogram(const Mantid::API::MatrixWorkspace_sptr &ws) { + auto toHistAlg = Mantid::API::AlgorithmManager::Instance().createUnmanaged( + "ConvertToHistogram"); + toHistAlg->initialize(); + toHistAlg->setChild(true); + toHistAlg->setProperty("InputWorkspace", ws); + toHistAlg->setProperty("OutputWorkspace", "unused"); + toHistAlg->execute(); + return toHistAlg->getProperty("OutputWorkspace"); +} + +// Create point data form a histogram +Mantid::API::MatrixWorkspace_sptr +toPointData(const Mantid::API::MatrixWorkspace_sptr &ws) { + // Convert to Point data + auto toPointAlg = Mantid::API::AlgorithmManager::Instance().createUnmanaged( + "ConvertToPointData"); + toPointAlg->initialize(); + toPointAlg->setChild(true); + toPointAlg->setProperty("InputWorkspace", ws); + toPointAlg->setProperty("OutputWorkspace", "unused"); + toPointAlg->execute(); + return toPointAlg->getProperty("OutputWorkspace"); +} + +} // namespace + namespace NXcanSASTestHelper { std::string @@ -38,16 +68,16 @@ getIDFfromWorkspace(const Mantid::API::MatrixWorkspace_sptr &workspace) { return Mantid::API::InstrumentFileFinder::getInstrumentFilename(name, date); } -void setXValuesOn1DWorkspaceWithPointData( - const Mantid::API::MatrixWorkspace_sptr &workspace, double xmin, - double xmax) { +void setXValuesOn1DWorkspace(const Mantid::API::MatrixWorkspace_sptr &workspace, + double xmin, double xmax) { auto &xValues = workspace->dataX(0); - auto size = xValues.size(); - double binWidth = (xmax - xmin) / static_cast(size - 1); - for (size_t index = 0; index < size; ++index) { - xValues[index] = xmin; - xmin += binWidth; - } + const double step = (xmax - xmin) / static_cast(xValues.size() - 1); + double value(xmin); + std::generate(std::begin(xValues), std::end(xValues), [&value, &step]() { + const auto oldValue{value}; + value += step; + return oldValue; + }); } void add_sample_log(const Mantid::API::MatrixWorkspace_sptr &workspace, @@ -96,6 +126,7 @@ provide1DWorkspace(NXcanSASTestParameters ¶meters) { ws = WorkspaceCreationHelper::create1DWorkspaceConstant( parameters.size, parameters.value, parameters.error, false); } + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); ws->setTitle(parameters.workspaceTitle); ws->getAxis(0)->unit() = @@ -110,15 +141,7 @@ provide1DWorkspace(NXcanSASTestParameters ¶meters) { // Set to point data or histogram data if (parameters.isHistogram) { - const std::string outName = "convert_to_histo_out_name"; - auto toHistAlg = Mantid::API::AlgorithmManager::Instance().createUnmanaged( - "ConvertToHistogram"); - toHistAlg->initialize(); - toHistAlg->setChild(true); - toHistAlg->setProperty("InputWorkspace", ws); - toHistAlg->setProperty("OutputWorkspace", outName); - toHistAlg->execute(); - ws = toHistAlg->getProperty("OutputWorkspace"); + ws = toHistogram(ws); } return ws; @@ -126,11 +149,15 @@ provide1DWorkspace(NXcanSASTestParameters ¶meters) { Mantid::API::MatrixWorkspace_sptr getTransmissionWorkspace(NXcanSASTestTransmissionParameters ¶meters) { - auto ws = WorkspaceCreationHelper::create1DWorkspaceConstant( - parameters.size, parameters.value, parameters.error, false); + Mantid::API::MatrixWorkspace_sptr ws = + WorkspaceCreationHelper::create1DWorkspaceConstant( + parameters.size, parameters.value, parameters.error, false); ws->setTitle(parameters.name); ws->getAxis(0)->unit() = Mantid::Kernel::UnitFactory::Instance().create("Wavelength"); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); + if (parameters.isHistogram) + ws = toHistogram(ws); return ws; } @@ -143,16 +170,7 @@ provide2DWorkspace(NXcanSASTestParameters ¶meters) { std::string axis2Binning = std::to_string(parameters.ymin) + ",1," + std::to_string(parameters.ymax); - // Convert to Histogram data - auto toHistAlg = Mantid::API::AlgorithmManager::Instance().createUnmanaged( - "ConvertToHistogram"); - std::string toHistoOutputName("toHistOutput"); - toHistAlg->initialize(); - toHistAlg->setChild(true); - toHistAlg->setProperty("InputWorkspace", ws); - toHistAlg->setProperty("OutputWorkspace", toHistoOutputName); - toHistAlg->execute(); - ws = toHistAlg->getProperty("OutputWorkspace"); + ws = toHistogram(ws); // Convert Spectrum Axis auto axisAlg = Mantid::API::AlgorithmManager::Instance().createUnmanaged( @@ -180,16 +198,7 @@ provide2DWorkspace(NXcanSASTestParameters ¶meters) { ws = rebin2DAlg->getProperty("OutputWorkspace"); if (!parameters.isHistogram) { - // Convert to Point data - auto toPointAlg = Mantid::API::AlgorithmManager::Instance().createUnmanaged( - "ConvertToPointData"); - std::string toPointOutputName("toPointOutput"); - toPointAlg->initialize(); - toPointAlg->setChild(true); - toPointAlg->setProperty("InputWorkspace", ws); - toPointAlg->setProperty("OutputWorkspace", toPointOutputName); - toPointAlg->execute(); - ws = toPointAlg->getProperty("OutputWorkspace"); + ws = toPointData(ws); } // At this point we have a mismatch between the Axis1 elements and the diff --git a/Framework/DataHandling/test/NXcanSASTestHelper.h b/Framework/DataHandling/test/NXcanSASTestHelper.h index aec86f5708d92f56494687531d4a9db492c41339..7f1af92dabae68abd439acf8d6390e44bb9bdc7f 100644 --- a/Framework/DataHandling/test/NXcanSASTestHelper.h +++ b/Framework/DataHandling/test/NXcanSASTestHelper.h @@ -13,83 +13,49 @@ namespace NXcanSASTestHelper { struct NXcanSASTestParameters { - NXcanSASTestParameters() { initParameters(); } - - void initParameters() { + NXcanSASTestParameters() { Poco::TemporaryFile tmpFile; tmpFile.keep(); filename = tmpFile.path(); - size = 10; - value = 10.23; - error = 3.45; - xerror = 2.3759 / 3.6; - hasDx = true; - xmin = 1.0; - xmax = 10.0; - runNumber = "1234"; - userFile = "my_user_file"; - workspaceTitle = "sample_worksapce"; - instrumentName = "SANS2D"; - radiationSource = "Spallation Neutron Source"; - invalidDetectors = false; - ymin = 1.0; - ymax = 12.0; - is2dData = false; - isHistogram = false; - sampleTransmissionRun = ""; - sampleDirectRun = ""; - canScatterRun = ""; - canDirectRun = ""; - hasCanRuns = false; - hasSampleRuns = false; } std::string filename; - int size; - double value; - double error; - double xerror; - bool hasDx; - double xmin; - double xmax; - double ymin; - double ymax; - std::string runNumber; - std::string userFile; - std::string workspaceTitle; - std::string instrumentName; - std::string radiationSource; + int size{10}; + double value{10.23}; + double error{3.45}; + double xerror{2.3759 / 3.6}; + bool hasDx{true}; + double xmin{1.0}; + double xmax{10.0}; + double ymin{1.0}; + double ymax{12.0}; + std::string runNumber{"1234"}; + std::string userFile{"my_user_file"}; + std::string workspaceTitle{"sample_workspace"}; + std::string instrumentName{"SANS2D"}; + std::string radiationSource{"Spallation Neutron Source"}; std::vector detectors; - bool invalidDetectors; - bool is2dData; + bool invalidDetectors{false}; + bool is2dData{false}; std::string idf; - bool isHistogram; + bool isHistogram{false}; std::string sampleTransmissionRun; std::string sampleDirectRun; std::string canScatterRun; std::string canDirectRun; - bool hasCanRuns; - bool hasSampleRuns; + bool hasCanRuns{false}; + bool hasSampleRuns{false}; }; struct NXcanSASTestTransmissionParameters { - NXcanSASTestTransmissionParameters() { initParameters(); } - void initParameters() { - size = 10; - value = 12.34; - error = 3.2345; - xmin = 1.0; - xmax = 10.0; - usesTransmission = false; - } - - int size; - double value; - double error; - double xmin; - double xmax; + int size{10}; + double value{12.34}; + double error{3.2345}; + double xmin{1.0}; + double xmax{10.0}; std::string name; - bool usesTransmission; + bool usesTransmission{false}; + bool isHistogram{false}; }; std::string @@ -98,9 +64,8 @@ concatenateStringVector(const std::vector &stringVector); std::string getIDFfromWorkspace(const Mantid::API::MatrixWorkspace_sptr &workspace); -void setXValuesOn1DWorkspaceWithPointData( - const Mantid::API::MatrixWorkspace_sptr &workspace, double xmin, - double xmax); +void setXValuesOn1DWorkspace(const Mantid::API::MatrixWorkspace_sptr &workspace, + double xmin, double xmax); void add_sample_log(const Mantid::API::MatrixWorkspace_sptr &workspace, const std::string &logName, const std::string &logValue); diff --git a/Framework/DataHandling/test/PrecompiledHeader.h b/Framework/DataHandling/test/PrecompiledHeader.h index 39ec1738948b787deed71e6a250ff3d6e27b368f..bb81e7e24b180d649f951d1e66665501c64f7eb7 100644 --- a/Framework/DataHandling/test/PrecompiledHeader.h +++ b/Framework/DataHandling/test/PrecompiledHeader.h @@ -11,8 +11,10 @@ #include "MantidAPI/Algorithm.h" #include "MantidKernel/System.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" // STL -#include -#include -#include + +// Poco +#include +#include diff --git a/Framework/DataHandling/test/SampleEnvironmentSpecTest.h b/Framework/DataHandling/test/SampleEnvironmentSpecTest.h index 6a0f4db9b9aad362f5b5005771492d36be9632f5..e9cd78d9eaf38da8125669cd8324c1b987a38d8b 100644 --- a/Framework/DataHandling/test/SampleEnvironmentSpecTest.h +++ b/Framework/DataHandling/test/SampleEnvironmentSpecTest.h @@ -47,6 +47,20 @@ public: TS_ASSERT_EQUALS(testContainer, retrieved); } + void test_Find_Single_Container_Without_ID() { + using Mantid::Geometry::Container; + SampleEnvironmentSpec spec("CRYO-001"); + auto testContainer = std::make_shared(""); + testContainer->setID("8mm"); + + TS_ASSERT_EQUALS(0, spec.ncans()); + TS_ASSERT_THROWS_NOTHING(spec.addContainer(testContainer)); + TS_ASSERT_EQUALS(1, spec.ncans()); + auto retrieved = spec.findContainer(""); + TS_ASSERT(retrieved); + TS_ASSERT_EQUALS(testContainer, retrieved); + } + void test_AddObject_Stores_Reference_To_Object() { SampleEnvironmentSpec spec("CRYO-001"); diff --git a/Framework/DataHandling/test/SaveNXcanSASTest.h b/Framework/DataHandling/test/SaveNXcanSASTest.h index 46bdbabd14ebec8da4d43704c48d5307f23d798b..1726528b328a24ed11ba2ae07f27500de3113e6e 100644 --- a/Framework/DataHandling/test/SaveNXcanSASTest.h +++ b/Framework/DataHandling/test/SaveNXcanSASTest.h @@ -92,7 +92,7 @@ public: parameters.hasSampleRuns = true; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); @@ -102,8 +102,8 @@ public: transmissionParameters.usesTransmission = true; auto transmission = getTransmissionWorkspace(transmissionParameters); - setXValuesOn1DWorkspaceWithPointData( - transmission, transmissionParameters.xmin, transmissionParameters.xmax); + setXValuesOn1DWorkspace(transmission, transmissionParameters.xmin, + transmissionParameters.xmax); // Act save_file_no_issues(ws, parameters, transmission, nullptr); @@ -128,7 +128,7 @@ public: parameters.hasCanRuns = true; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); @@ -138,9 +138,8 @@ public: transmissionCanParameters.usesTransmission = true; auto transmissionCan = getTransmissionWorkspace(transmissionCanParameters); - setXValuesOn1DWorkspaceWithPointData(transmissionCan, - transmissionCanParameters.xmin, - transmissionCanParameters.xmax); + setXValuesOn1DWorkspace(transmissionCan, transmissionCanParameters.xmin, + transmissionCanParameters.xmax); // Act save_file_no_issues(ws, parameters, nullptr, transmissionCan); @@ -166,7 +165,7 @@ public: parameters.hasSampleRuns = true; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); @@ -180,13 +179,12 @@ public: transmissionCanParameters.usesTransmission = true; auto transmission = getTransmissionWorkspace(transmissionParameters); - setXValuesOn1DWorkspaceWithPointData( - transmission, transmissionParameters.xmin, transmissionParameters.xmax); + setXValuesOn1DWorkspace(transmission, transmissionParameters.xmin, + transmissionParameters.xmax); auto transmissionCan = getTransmissionWorkspace(transmissionCanParameters); - setXValuesOn1DWorkspaceWithPointData(transmissionCan, - transmissionCanParameters.xmin, - transmissionCanParameters.xmax); + setXValuesOn1DWorkspace(transmissionCan, transmissionCanParameters.xmin, + transmissionCanParameters.xmax); // Act save_file_no_issues(ws, parameters, transmission, transmissionCan); @@ -208,7 +206,7 @@ public: parameters.invalidDetectors = false; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); @@ -233,7 +231,7 @@ public: auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); @@ -260,7 +258,7 @@ public: parameters.hasDx = false; auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); + setXValuesOn1DWorkspace(ws, parameters.xmin, parameters.xmax); parameters.idf = getIDFfromWorkspace(ws); @@ -274,7 +272,19 @@ public: removeFile(parameters.filename); } - void test_that_1D_workspace_with_transmissions_is_saved_correctly() { + void test_that_1D_workspace_with_point_transmissions_is_saved_correctly() { + const bool isHistogram{false}; + run_test_1D_workspace_with_transmissions_is_saved_correctly(isHistogram); + } + + void + test_that_1D_workspace_with_histogram_transmissions_is_saved_as_points() { + const bool isHistogram{true}; + run_test_1D_workspace_with_transmissions_is_saved_correctly(isHistogram); + } + + void run_test_1D_workspace_with_transmissions_is_saved_correctly( + const bool isHistogram) { // Arrange NXcanSASTestParameters parameters; removeFile(parameters.filename); @@ -282,31 +292,24 @@ public: parameters.detectors.emplace_back("front-detector"); parameters.detectors.emplace_back("rear-detector"); parameters.invalidDetectors = false; - + parameters.isHistogram = isHistogram; parameters.hasDx = true; - auto ws = provide1DWorkspace(parameters); - setXValuesOn1DWorkspaceWithPointData(ws, parameters.xmin, parameters.xmax); - parameters.idf = getIDFfromWorkspace(ws); // Create transmission NXcanSASTestTransmissionParameters transmissionParameters; transmissionParameters.name = sasTransmissionSpectrumNameSampleAttrValue; transmissionParameters.usesTransmission = true; + transmissionParameters.isHistogram = isHistogram; NXcanSASTestTransmissionParameters transmissionCanParameters; transmissionCanParameters.name = sasTransmissionSpectrumNameCanAttrValue; transmissionCanParameters.usesTransmission = true; + transmissionCanParameters.isHistogram = isHistogram; auto transmission = getTransmissionWorkspace(transmissionParameters); - setXValuesOn1DWorkspaceWithPointData( - transmission, transmissionParameters.xmin, transmissionParameters.xmax); - auto transmissionCan = getTransmissionWorkspace(transmissionCanParameters); - setXValuesOn1DWorkspaceWithPointData(transmissionCan, - transmissionCanParameters.xmin, - transmissionCanParameters.xmax); // Act save_file_no_issues(ws, parameters, transmission, transmissionCan); @@ -354,7 +357,7 @@ public: parameters.invalidDetectors = false; parameters.is2dData = true; - parameters.isHistogram = true; // The new bit + parameters.isHistogram = true; auto ws = provide2DWorkspace(parameters); set2DValues(ws); @@ -660,7 +663,7 @@ private: void do_assert_1D_vector_with_increasing_entries(H5::DataSet &dataSet, double min, double increment, int size) { - auto data = + const auto data = Mantid::DataHandling::H5Util::readArray1DCoerce(dataSet); TS_ASSERT_EQUALS(data.size(), static_cast(size)); for (size_t index = 0; index < data.size(); ++index) { @@ -960,6 +963,23 @@ private: static_cast(parameters.size - 1); do_assert_1D_vector_with_increasing_entries(lambdaDataSet, parameters.xmin, increment, parameters.size); + + // Size check for matching T/Tdev/lambda + do_assert_data_array_sizes_match(tDataSet, tErrorDataSet, lambdaDataSet); + } + + void do_assert_data_array_sizes_match(const H5::DataSet &tDataSet, + const H5::DataSet &tErrorDataSet, + const H5::DataSet &lambdaDataSet) { + auto arraySize = [](const H5::DataSet &dataSet) { + const auto dataSpace = dataSet.getSpace(); + return dataSpace.getSelectNpoints(); + }; + + TSM_ASSERT_EQUALS("Expected T and Tdev array lengths to match", + arraySize(tDataSet), arraySize(tErrorDataSet)); + TSM_ASSERT_EQUALS("Expected T and Lambda array lengths to match", + arraySize(lambdaDataSet), arraySize(tDataSet)); } void do_assert(NXcanSASTestParameters ¶meters, diff --git a/Framework/DataObjects/inc/MantidDataObjects/EventWorkspace.h b/Framework/DataObjects/inc/MantidDataObjects/EventWorkspace.h index 7d7a15281ecee589bb3f6ad47e968aa34a59439e..37d933f78a2b045500521d984df5f5962a934ca6 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/EventWorkspace.h +++ b/Framework/DataObjects/inc/MantidDataObjects/EventWorkspace.h @@ -60,17 +60,24 @@ public: //------------------------------------------------------------ + /// Returns true if the workspace is ragged (has differently sized spectra). + bool isRaggedWorkspace() const override; + // Returns the number of single indexable items in the workspace std::size_t size() const override; // Get the blocksize, aka the number of bins in the histogram std::size_t blocksize() const override; - - size_t getMemorySize() const override; + /// Returns the number of bins for a given histogram index. + std::size_t getNumberBins(const std::size_t &index) const override; + /// Returns the maximum number of bins in a workspace (works on ragged data). + std::size_t getMaxNumberBins() const override; // Get the number of histograms. aka the number of pixels or detectors. std::size_t getNumberHistograms() const override; + size_t getMemorySize() const override; + EventList &getSpectrum(const size_t index) override { invalidateCommonBinsFlag(); return getSpectrumWithoutInvalidation(index); diff --git a/Framework/DataObjects/inc/MantidDataObjects/MDBox.tcc b/Framework/DataObjects/inc/MantidDataObjects/MDBox.tcc index 053576a81c294bc6db0a9c12f79f20b84063dbbf..1aa7106b98651565a5c5f00e81cf9f3c97228b8f 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/MDBox.tcc +++ b/Framework/DataObjects/inc/MantidDataObjects/MDBox.tcc @@ -622,7 +622,7 @@ TMDE(void MDBox)::integrateSphere( if (out[0] < radiusSquared && out[0] > innerRadiusSquared) { const auto signal = static_cast(it.getSignal()); const auto errSquared = static_cast(it.getErrorSquared()); - vals.emplace_back(std::make_pair(signal, errSquared)); + vals.emplace_back(signal, errSquared); } } // Sort based on signal values diff --git a/Framework/DataObjects/inc/MantidDataObjects/ReflectometryTransform.h b/Framework/DataObjects/inc/MantidDataObjects/ReflectometryTransform.h index d1015c6aea51785c3c7eac7a4ba7ccd4c6f5ad8e..687ec4ed29bad520e3ff9e70352eddee930b2d37 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/ReflectometryTransform.h +++ b/Framework/DataObjects/inc/MantidDataObjects/ReflectometryTransform.h @@ -27,8 +27,8 @@ class TableWorkspace; Simple container for porting detector angular information */ struct MANTID_DATAOBJECTS_DLL DetectorAngularCache { - std::vector thetaWidths; - std::vector thetas; + std::vector twoThetaWidths; + std::vector twoThetas; std::vector detectorHeights; }; @@ -52,11 +52,6 @@ protected: const std::string m_d1ID; std::shared_ptr m_calculator; - /// Two theta angles cache - mutable std::vector m_theta; - /// Two theta widths cache - mutable std::vector m_thetaWidths; - std::shared_ptr createMDWorkspace(const Geometry::IMDDimension_sptr &, const Geometry::IMDDimension_sptr &, @@ -110,4 +105,4 @@ initAngularCaches(const Mantid::API::MatrixWorkspace *const workspace); // Helper typedef for scoped pointer of this type. using ReflectometryTransform_sptr = std::shared_ptr; } // namespace DataObjects -} // namespace Mantid \ No newline at end of file +} // namespace Mantid diff --git a/Framework/DataObjects/inc/MantidDataObjects/Workspace2D.h b/Framework/DataObjects/inc/MantidDataObjects/Workspace2D.h index d584f6ee7c220dea6dd9cdb412dbbb478fc3a515..d99ce9a721f5837da51d97a14696e52e5ad291a8 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/Workspace2D.h +++ b/Framework/DataObjects/inc/MantidDataObjects/Workspace2D.h @@ -48,12 +48,20 @@ public: return std::unique_ptr(doCloneEmpty()); } - /// Returns the histogram number - std::size_t getNumberHistograms() const override; + /// Returns true if the workspace is ragged (has differently sized spectra). + bool isRaggedWorkspace() const override; // section required for iteration std::size_t size() const override; + std::size_t blocksize() const override; + /// Returns the number of bins for a given histogram index. + std::size_t getNumberBins(const std::size_t &index) const override; + /// Returns the maximum number of bins in a workspace (works on ragged data). + std::size_t getMaxNumberBins() const override; + + /// Returns the histogram number + std::size_t getNumberHistograms() const override; Histogram1D &getSpectrum(const size_t index) override { invalidateCommonBinsFlag(); @@ -106,4 +114,4 @@ private: virtual std::size_t getHistogramNumberHelper() const; }; } // namespace DataObjects -} // Namespace Mantid \ No newline at end of file +} // Namespace Mantid diff --git a/Framework/DataObjects/inc/MantidDataObjects/WorkspaceSingleValue.h b/Framework/DataObjects/inc/MantidDataObjects/WorkspaceSingleValue.h index 1049bf071bdec620622cb7f6bff77e17835dd56a..c394057fc9c9c9c57660622d86fe71dcb4246b7f 100644 --- a/Framework/DataObjects/inc/MantidDataObjects/WorkspaceSingleValue.h +++ b/Framework/DataObjects/inc/MantidDataObjects/WorkspaceSingleValue.h @@ -38,11 +38,22 @@ public: return std::unique_ptr(doCloneEmpty()); } WorkspaceSingleValue &operator=(const WorkspaceSingleValue &other) = delete; + + /// Returns true if the workspace is ragged (has differently sized spectra). + bool isRaggedWorkspace() const override { return false; } + /// Returns the number of single indexable items in the workspace std::size_t size() const override { return 1; } /// Returns the size of each block of data returned by the dataX accessors std::size_t blocksize() const override { return 1; } + /// Returns the number of bins for a given histogram index. + std::size_t getNumberBins(const std::size_t &index) const override { + UNUSED_ARG(index); + return 1; + } + /// Returns the maximum number of bins in a workspace. + std::size_t getMaxNumberBins() const override { return 1; } /// @return the number of histograms (spectra) std::size_t getNumberHistograms() const override { return 1; } diff --git a/Framework/DataObjects/src/EventWorkspace.cpp b/Framework/DataObjects/src/EventWorkspace.cpp index c7e018571dc4aea257af4c8466cff354d90c93d4..8f0b5002a3c1b83db50363bdb2a535a218ca4851 100644 --- a/Framework/DataObjects/src/EventWorkspace.cpp +++ b/Framework/DataObjects/src/EventWorkspace.cpp @@ -25,6 +25,7 @@ #include "MantidKernel/TimeSeriesProperty.h" #include "tbb/parallel_for.h" +#include #include #include @@ -136,6 +137,21 @@ void EventWorkspace::init(const HistogramData::Histogram &histogram) { m_axes[1] = std::make_unique(this); } +/// Returns true if the workspace is ragged (has differently sized spectra). +/// @returns true if the workspace is ragged. +bool EventWorkspace::isRaggedWorkspace() const { + if (data.empty()) { + throw std::runtime_error("There are no pixels in the event workspace, " + "therefore cannot determine if it is ragged."); + } else { + const auto numberOfBins = data[0]->histogram_size(); + return std::any_of(data.cbegin(), data.cend(), + [&numberOfBins](const auto &eventList) { + return numberOfBins != eventList->histogram_size(); + }); + } +} + /// The total size of the workspace /// @returns the number of single indexable items in the workspace size_t EventWorkspace::size() const { @@ -162,6 +178,36 @@ size_t EventWorkspace::blocksize() const { } } +/** Returns the number of bins for a given histogram index. + * @param index :: The histogram index to check for the number of bins. + * @return the number of bins for a given histogram index. + */ +std::size_t EventWorkspace::getNumberBins(const std::size_t &index) const { + if (index < data.size()) + return data[index]->histogram_size(); + + throw std::invalid_argument( + "Could not find number of bins in a histogram at index " + + std::to_string(index) + ": index is too large."); +} + +/** Returns the maximum number of bins in a workspace (works on ragged data). + * @return the maximum number of bins in a workspace. + */ +std::size_t EventWorkspace::getMaxNumberBins() const { + if (data.empty()) { + return 0; + } else { + auto maxNumberOfBins = data[0]->histogram_size(); + for (const auto &iter : data) { + const auto numberOfBins = iter->histogram_size(); + if (numberOfBins > maxNumberOfBins) + maxNumberOfBins = numberOfBins; + } + return maxNumberOfBins; + } +} + /** Get the number of histograms, usually the same as the number of pixels or detectors. @returns the number of histograms / event lists diff --git a/Framework/DataObjects/src/PeakShapeSpherical.cpp b/Framework/DataObjects/src/PeakShapeSpherical.cpp index b81814575802d1535ca5299197e001352950a214..d6cf2d7915744e670e7b0f79617c923a28c8af2e 100644 --- a/Framework/DataObjects/src/PeakShapeSpherical.cpp +++ b/Framework/DataObjects/src/PeakShapeSpherical.cpp @@ -44,9 +44,6 @@ PeakShapeSpherical::PeakShapeSpherical(const double &peakRadius, : PeakShapeBase(frame, std::move(algorithmName), algorithmVersion), m_radius(peakRadius), m_backgroundInnerRadius(peakInnerRadius), m_backgroundOuterRadius(peakOuterRadius) { - if (peakRadius == m_backgroundInnerRadius) { - m_backgroundInnerRadius.reset(); - } if (peakRadius == m_backgroundOuterRadius) { m_backgroundOuterRadius.reset(); } diff --git a/Framework/DataObjects/src/ReflectometryTransform.cpp b/Framework/DataObjects/src/ReflectometryTransform.cpp index f7c9a80e978d116219f53ae75c9788d8a76d173f..4d1547d9b757c7b94bf456c2408a2c259e8b609c 100644 --- a/Framework/DataObjects/src/ReflectometryTransform.cpp +++ b/Framework/DataObjects/src/ReflectometryTransform.cpp @@ -223,8 +223,8 @@ void createVerticalAxis(MatrixWorkspace *const ws, const MantidVec &xAxisVec, */ DetectorAngularCache initAngularCaches(const MatrixWorkspace *const workspace) { const size_t nhist = workspace->getNumberHistograms(); - std::vector thetas(nhist); - std::vector thetaWidths(nhist); + std::vector twoThetas(nhist); + std::vector twoThetaWidths(nhist); std::vector detectorHeights(nhist); auto inst = workspace->getInstrument(); @@ -235,22 +235,23 @@ DetectorAngularCache initAngularCaches(const MatrixWorkspace *const workspace) { for (size_t i = 0; i < nhist; ++i) { if (!spectrumInfo.hasDetectors(i) || spectrumInfo.isMonitor(i)) { // If no detector found, skip onto the next spectrum - thetas[i] = -1.0; // Indicates a detector to skip - thetaWidths[i] = -1.0; + twoThetas[i] = -1.0; // Indicates a detector to skip + twoThetaWidths[i] = -1.0; continue; } - // We have to convert theta from radians to degrees - const double theta = spectrumInfo.signedTwoTheta(i) * rad2deg; - thetas[i] = theta; + // We have to convert twoTheta from radians to degrees + const double twoTheta = spectrumInfo.signedTwoTheta(i) * rad2deg; + twoThetas[i] = twoTheta; /** * Determine width from shape geometry. A group is assumed to contain - * detectors with the same shape & r, theta value, i.e. a ring mapped-group - * The shape is retrieved and rotated to match the rotation of the detector. - * The angular width is computed using the l2 distance from the sample + * detectors with the same shape & r, twoTheta value, i.e. a ring + * mapped-group The shape is retrieved and rotated to match the rotation of + * the detector. The angular width is computed using the l2 distance from + * the sample */ // If the spectrum is based on a group of detectors assume they all have - // same shape and same r,theta + // same shape and same r,twoTheta // DetectorGroup::getID gives ID of first detector. size_t detIndex = detectorInfo.indexOf(spectrumInfo.detector(i).getID()); double l2 = detectorInfo.l2(detIndex); @@ -263,12 +264,13 @@ DetectorAngularCache initAngularCaches(const MatrixWorkspace *const workspace) { auto minPoint(bbox.minPoint()); auto span = maxPoint - minPoint; detectorHeights[i] = span.scalar_prod(upDirVec); - thetaWidths[i] = 2.0 * std::fabs(std::atan((detectorHeights[i] / 2) / l2)) * - 180.0 / M_PI; + twoThetaWidths[i] = (std::atan(maxPoint.scalar_prod(upDirVec) / l2) - + std::atan(minPoint.scalar_prod(upDirVec) / l2)) * + rad2deg; } DetectorAngularCache cache; - cache.thetas = thetas; - cache.thetaWidths = thetaWidths; + cache.twoThetas = twoThetas; + cache.twoThetaWidths = twoThetaWidths; cache.detectorHeights = detectorHeights; return cache; } @@ -456,8 +458,8 @@ MatrixWorkspace_sptr ReflectometryTransform::executeNormPoly( // Prepare the required theta values DetectorAngularCache cache = initAngularCaches(inputWS.get()); - m_theta = cache.thetas; - m_thetaWidths = cache.thetaWidths; + auto twoThetas = cache.twoThetas; + auto twoThetaWidths = cache.twoThetaWidths; const size_t nHistos = inputWS->getNumberHistograms(); const size_t nBins = inputWS->blocksize(); @@ -476,11 +478,11 @@ MatrixWorkspace_sptr ReflectometryTransform::executeNormPoly( const auto &detector = spectrumInfo.detector(i); // Compute polygon points - const double theta = m_theta[i]; - const double thetaWidth = m_thetaWidths[i]; - const double thetaHalfWidth = 0.5 * thetaWidth; - const double thetaLower = theta - thetaHalfWidth; - const double thetaUpper = theta + thetaHalfWidth; + const double twoTheta = twoThetas[i]; + const double twoThetaWidth = twoThetaWidths[i]; + const double twoThetaHalfWidth = 0.5 * twoThetaWidth; + const double twoThetaLower = twoTheta - twoThetaHalfWidth; + const double twoThetaUpper = twoTheta + twoThetaHalfWidth; const MantidVec &X = inputWS->readX(i); const MantidVec &Y = inputWS->readY(i); @@ -491,8 +493,8 @@ MatrixWorkspace_sptr ReflectometryTransform::executeNormPoly( const double signal = Y[j]; const double error = E[j]; - auto inputQ = - m_calculator->createQuad(lamUpper, lamLower, thetaUpper, thetaLower); + auto inputQ = m_calculator->createQuad(lamUpper, lamLower, twoThetaUpper, + twoThetaLower); FractionalRebinning::rebinToFractionalOutput(inputQ, inputWS, i, j, *outWS, zBinsVec); // Find which qy bin this point lies in diff --git a/Framework/DataObjects/src/Workspace2D.cpp b/Framework/DataObjects/src/Workspace2D.cpp index b3af1ff0c703bf76610b1c8a13950c3d84b3480a..f38ce7f3bbee9b23a85e24c87e23836ecbba8385 100644 --- a/Framework/DataObjects/src/Workspace2D.cpp +++ b/Framework/DataObjects/src/Workspace2D.cpp @@ -104,6 +104,21 @@ void Workspace2D::init(const HistogramData::Histogram &histogram) { m_axes[1] = std::make_unique(this); } +/// Returns true if the workspace is ragged (has differently sized spectra). +/// @returns true if the workspace is ragged. +bool Workspace2D::isRaggedWorkspace() const { + if (data.empty()) { + throw std::runtime_error("There is no data in the Workspace2D, " + "therefore cannot determine if it is ragged."); + } else { + const auto numberOfBins = data[0]->size(); + return std::any_of(data.cbegin(), data.cend(), + [&numberOfBins](const auto &histogram) { + return numberOfBins != histogram->size(); + }); + } +} + /** Gets the number of histograms @return Integer */ @@ -134,6 +149,36 @@ size_t Workspace2D::blocksize() const { } } +/** Returns the number of bins for a given histogram index. + * @param index :: The histogram index to check for the number of bins. + * @return the number of bins for a given histogram index. + */ +std::size_t Workspace2D::getNumberBins(const std::size_t &index) const { + if (index < data.size()) + return data[index]->size(); + + throw std::invalid_argument( + "Could not find number of bins in a histogram at index " + + std::to_string(index) + ": index is too large."); +} + +/** Returns the maximum number of bins in a workspace (works on ragged data). + * @return the maximum number of bins in a workspace. + */ +std::size_t Workspace2D::getMaxNumberBins() const { + if (data.empty()) { + return 0; + } else { + auto maxNumberOfBins = data[0]->size(); + for (const auto &iter : data) { + const auto numberOfBins = iter->size(); + if (numberOfBins > maxNumberOfBins) + maxNumberOfBins = numberOfBins; + } + return maxNumberOfBins; + } +} + /** * Copy the data (Y's) from an image to this workspace. * @param image :: An image to copy the data from. diff --git a/Framework/DataObjects/test/EventWorkspaceTest.h b/Framework/DataObjects/test/EventWorkspaceTest.h index f18060e1cfd97bd0ecbb5b611db5c00dbad1e639..9296e3a694159c58f24beb8f46ae0d9a6ba36b42 100644 --- a/Framework/DataObjects/test/EventWorkspaceTest.h +++ b/Framework/DataObjects/test/EventWorkspaceTest.h @@ -147,6 +147,51 @@ public: TS_ASSERT_LESS_THAN_EQUALS(min_memory, ew->getMemorySize()); } + void + test_that_isRaggedWorkspace_returns_false_for_a_non_ragged_EventWorkspace() { + ew = createEventWorkspace(true, false); + + TS_ASSERT(!ew->isRaggedWorkspace()); + TS_ASSERT_EQUALS(ew->blocksize(), 1); + } + + void test_that_isRaggedWorkspace_returns_true_for_a_ragged_EventWorkspace() { + ew = createEventWorkspace(true, false); + ew->getSpectrum(0).setHistogram(BinEdges({0., 10., 20.})); + + TS_ASSERT(ew->isRaggedWorkspace()); + TS_ASSERT_THROWS(ew->blocksize(), const std::logic_error &); + } + + void + test_that_getNumberBins_returns_the_correct_number_of_bins_for_different_histograms_in_a_ragged_EventWorkspace() { + ew = createEventWorkspace(true, false); + ew->getSpectrum(0).setHistogram(BinEdges({0., 10., 20.})); + + TS_ASSERT(ew->isRaggedWorkspace()); + TS_ASSERT_EQUALS(ew->getNumberBins(0), 2); + TS_ASSERT_EQUALS(ew->getNumberBins(1), 1); + } + + void + test_that_getNumberBins_throws_when_provided_an_index_which_is_too_large() { + ew = createEventWorkspace(true, false); + + const auto numberOfHistograms = ew->getNumberHistograms(); + TS_ASSERT_THROWS_NOTHING(ew->getNumberBins(numberOfHistograms - 1u)); + TS_ASSERT_THROWS(ew->getNumberBins(numberOfHistograms), + const std::invalid_argument &); + } + + void + test_that_getMaxNumberBins_returns_the_correct_number_for_a_ragged_EventWorkspace() { + ew = createEventWorkspace(true, false); + ew->getSpectrum(0).setHistogram(BinEdges({0., 10., 20.})); + + TS_ASSERT(ew->isRaggedWorkspace()); + TS_ASSERT_EQUALS(ew->getMaxNumberBins(), 2); + } + void testUnequalBins() { ew = createEventWorkspace(true, false); // normal behavior diff --git a/Framework/DataObjects/test/PeakShapeSphericalFactoryTest.h b/Framework/DataObjects/test/PeakShapeSphericalFactoryTest.h index 3e3b04f1c3e7c15159b940fd85eee3876ea2ea13..c73f5d3e7b1d2582adbec2635402ba027a8ce710 100644 --- a/Framework/DataObjects/test/PeakShapeSphericalFactoryTest.h +++ b/Framework/DataObjects/test/PeakShapeSphericalFactoryTest.h @@ -68,7 +68,7 @@ public: void test_create() { const double radius = 2; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -92,7 +92,7 @@ public: const double radius = 2; const double backgroundInnerRadius = 3; const double backgroundOuterRadius = 4; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; diff --git a/Framework/DataObjects/test/PeakShapeSphericalTest.h b/Framework/DataObjects/test/PeakShapeSphericalTest.h index 06de64ef2cdff9b433a921c3e3d1e339dda85c39..6360120f2af1e6747bf1f0937816596d9b9e1439 100644 --- a/Framework/DataObjects/test/PeakShapeSphericalTest.h +++ b/Framework/DataObjects/test/PeakShapeSphericalTest.h @@ -36,7 +36,7 @@ public: void test_constructor() { const double radius = 2; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -55,7 +55,7 @@ public: const double radius = 2; const double backgroundInnerRadius = 3; const double backgroundOuterRadius = 4; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -85,9 +85,8 @@ public: PeakShapeSpherical badShape(radius, radius, radius, frame, algorithmName, algorithmVersion); - TSM_ASSERT( - "Background inner radius should be unset since is same as radius", - !badShape.backgroundInnerRadius().is_initialized()); + TSM_ASSERT("Background inner radius should be set even when same as radius", + badShape.backgroundInnerRadius().is_initialized()); TSM_ASSERT( "Background outer radius should be unset since is same as radius", !badShape.backgroundOuterRadius().is_initialized()); @@ -97,7 +96,7 @@ public: const double radius = 2; const double backgroundInnerRadius = 3; const double backgroundOuterRadius = 4; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -119,7 +118,7 @@ public: const double radius = 2; const double backgroundInnerRadius = 3; const double backgroundOuterRadius = 4; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -146,7 +145,7 @@ public: const double radius = 2; const double backgroundInnerRadius = 3; const double backgroundOuterRadius = 4; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -169,7 +168,7 @@ public: void test_toJSON() { const double radius = 2; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -191,7 +190,7 @@ public: const double radius = 2; const double backgroundInnerRadius = 3; const double backgroundOuterRadius = 4; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; const std::string algorithmName = "foo"; const int algorithmVersion = 3; @@ -240,7 +239,7 @@ public: void test_shape_name() { const double radius = 1; - const SpecialCoordinateSystem frame = HKL; + const SpecialCoordinateSystem frame = SpecialCoordinateSystem::HKL; // Construct it. PeakShapeSpherical shape(radius, frame); diff --git a/Framework/DataObjects/test/ReflectometryTransformTest.h b/Framework/DataObjects/test/ReflectometryTransformTest.h index a319ef969346cc607294931ca38e79369f13a138..202da3e875d70c4a8bb4d442f2a7d9aede9e3ef8 100644 --- a/Framework/DataObjects/test/ReflectometryTransformTest.h +++ b/Framework/DataObjects/test/ReflectometryTransformTest.h @@ -48,13 +48,13 @@ public: TS_ASSERT_DELTA(0.04, cache.detectorHeights[0], 1e-6); - auto calculatedThetaW = + auto calculatedTwoThetaW = 2.0 * std::fabs(std::atan((cache.detectorHeights[0] / 2) / l2)) * 180.0 / M_PI; TSM_ASSERT_DELTA( "Calculated theta width should agree with detector height calculation", - cache.thetaWidths[0], calculatedThetaW, 1e-6) + cache.twoThetaWidths[0], calculatedTwoThetaW, 1e-6) } void test_cache_calculation_when_x_is_up() { diff --git a/Framework/DataObjects/test/Workspace2DTest.h b/Framework/DataObjects/test/Workspace2DTest.h index 92b84e9ebadeb558a5a85b98fbbfac210b3f2aeb..b161eb3c381ba0dd47f4c397796c6925dd917be8 100644 --- a/Framework/DataObjects/test/Workspace2DTest.h +++ b/Framework/DataObjects/test/Workspace2DTest.h @@ -80,6 +80,48 @@ public: } } + void + test_that_isRaggedWorkspace_returns_false_for_a_non_ragged_Workspace2D() { + TS_ASSERT(!ws->isRaggedWorkspace()); + TS_ASSERT_EQUALS(ws->blocksize(), 5); + } + + void test_that_isRaggedWorkspace_returns_true_for_a_ragged_Workspace2D() { + Workspace2D_sptr cloned(ws->clone()); + cloned->setHistogram(0, Points(0), Counts(0)); + + TS_ASSERT(cloned->isRaggedWorkspace()); + TS_ASSERT_THROWS(cloned->blocksize(), const std::logic_error &); + } + + void + test_that_getNumberBins_returns_the_correct_number_of_bins_for_different_histograms_in_a_ragged_Workspace2D() { + Workspace2D_sptr cloned(ws->clone()); + cloned->setHistogram(0, Points(0), Counts(0)); + + TS_ASSERT(cloned->isRaggedWorkspace()); + TS_ASSERT_EQUALS(cloned->getNumberBins(0), 0); + TS_ASSERT_EQUALS(cloned->getNumberBins(1), 5); + } + + void + test_that_getNumberBins_throws_when_provided_an_index_which_is_too_large() { + const auto numberOfHistograms = ws->getNumberHistograms(); + + TS_ASSERT_THROWS_NOTHING(ws->getNumberBins(numberOfHistograms - 1)); + TS_ASSERT_THROWS(ws->getNumberBins(numberOfHistograms), + const std::invalid_argument &); + } + + void + test_that_getMaxNumberBins_returns_the_correct_number_for_a_ragged_Workspace2D() { + Workspace2D_sptr cloned(ws->clone()); + cloned->setHistogram(0, Points(0), Counts(0)); + + TS_ASSERT(cloned->isRaggedWorkspace()); + TS_ASSERT_EQUALS(cloned->getMaxNumberBins(), 5); + } + void testUnequalBins() { // try normal kind first TS_ASSERT_EQUALS(ws->blocksize(), 5); diff --git a/Framework/DataObjects/test/WorkspaceSingleValueTest.h b/Framework/DataObjects/test/WorkspaceSingleValueTest.h index a36e83684b14ec00d42a7970f3d08ca422f9e0c5..c228feeb5ce5a399dbd134637fc778476ea887da 100644 --- a/Framework/DataObjects/test/WorkspaceSingleValueTest.h +++ b/Framework/DataObjects/test/WorkspaceSingleValueTest.h @@ -100,4 +100,16 @@ public: TS_ASSERT(wsCastNonConst != nullptr); TS_ASSERT_EQUALS(wsCastConst, wsCastNonConst); } -}; \ No newline at end of file + + void test_that_isRaggedWorkspace_returns_false_for_a_WorkspaceSingleValue() { + WorkspaceSingleValue ws; + TS_ASSERT(!ws.isRaggedWorkspace()); + } + + void + test_that_getNumberBins_and_getMaxNumberBins_returns_one_for_a_WorkspaceSingleValue() { + WorkspaceSingleValue ws; + TS_ASSERT_EQUALS(ws.getNumberBins(0), 1); + TS_ASSERT_EQUALS(ws.getMaxNumberBins(), 1); + } +}; diff --git a/Framework/Doxygen/CMakeLists.txt b/Framework/Doxygen/CMakeLists.txt index 938501a48f69384ca6edd3e92630f94183a90aac..373d8fc4b2e0277f7993b6169c4483211583ace8 100644 --- a/Framework/Doxygen/CMakeLists.txt +++ b/Framework/Doxygen/CMakeLists.txt @@ -89,4 +89,10 @@ if(DOXYGEN_FOUND) ${CMAKE_CURRENT_BINARY_DIR}/Mantid.doxyfile ${CMAKE_CURRENT_BINARY_DIR}/../../doxygen/html/Mantid_Logo_Transparent.png ${CMAKE_CURRENT_BINARY_DIR}/doxy_header.html) + + # remove file that was configured for mantidplot + if (EXISTS "${CMAKE_SOURCE_DIR}/Framework/Kernel/src/ParaViewVersion.cpp") + file (REMOVE "${CMAKE_SOURCE_DIR}/Framework/Kernel/src/ParaViewVersion.cpp") + endif() + endif(DOXYGEN_FOUND) diff --git a/Framework/Doxygen/Mantid_template.doxyfile b/Framework/Doxygen/Mantid_template.doxyfile index 7a4e73f11c1362809faece616279dc6fa86d3baa..708ec7605e93e7a7ce3438b259e62573a4653cdb 100644 --- a/Framework/Doxygen/Mantid_template.doxyfile +++ b/Framework/Doxygen/Mantid_template.doxyfile @@ -110,33 +110,10 @@ INPUT = @CMAKE_CURRENT_SOURCE_DIR@/../Algorithms/inc \ @CMAKE_CURRENT_SOURCE_DIR@/../TestHelpers/src \ @CMAKE_CURRENT_SOURCE_DIR@/../WorkflowAlgorithms/inc \ @CMAKE_CURRENT_SOURCE_DIR@/../WorkflowAlgorithms/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../MantidPlot/src/analysis \ - @CMAKE_CURRENT_SOURCE_DIR@/../../MantidPlot/src/Mantid \ @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/common/inc \ @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/common/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/factory/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/factory/src \ @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/plugins/algorithm_dialogs/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/plugins/algorithm_dialogs/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/plugins/designer/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/plugins/designer/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/scientific_interfaces/DynamicPDF \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/scientific_interfaces/General \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/scientific_interfaces/ISISReflectometry \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/scientific_interfaces/ISISSANS \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/scientific_interfaces/MultiDatasetFit \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/scientific_interfaces/Muon \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/sliceviewer/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/sliceviewer/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/spectrumviewer/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/spectrumviewer/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/refdetectorview/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/refdetectorview/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/paraview_ext/PVPlugins \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/paraview_ext/VatesAPI/inc \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/paraview_ext/VatesAPI/src \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/paraview_ext/VatesSimpleGui/QtWidgets \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/paraview_ext/VatesSimpleGui/ViewWidgets + @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/plugins/algorithm_dialogs/src INPUT_ENCODING = UTF-8 @@ -157,7 +134,6 @@ EXCLUDE = @CMAKE_CURRENT_SOURCE_DIR@/../ICat/src/GSoapGenerated \ @CMAKE_CURRENT_SOURCE_DIR@/../Kernel/src/ANN_complete.cpp \ @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/common/inc/MantidQtWidgets/Common/QtPropertyBrowser \ @CMAKE_CURRENT_SOURCE_DIR@/../../qt/widgets/common/src/QtPropertyBrowser \ - @CMAKE_CURRENT_SOURCE_DIR@/../../qt/paraview_ext/PVPlugins \ @CMAKE_CURRENT_SOURCE_DIR@/../Kernel/inc/MantidKernel/span.hpp EXCLUDE_SYMLINKS = NO diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h index db0f06e6a72a01a4bc35a604d5871bba74eb7623..9ed98d22eaedb93c7dad2308b28ecc02c7b69142 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/ComponentInfo.h @@ -113,7 +113,9 @@ public: Kernel::V3D sourcePosition() const; Kernel::V3D samplePosition() const; bool hasSource() const; + bool hasEquivalentSource(const ComponentInfo &other) const; bool hasSample() const; + bool hasEquivalentSample(const ComponentInfo &other) const; bool hasDetectors(const size_t componentIndex) const; size_t source() const; size_t sample() const; diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/InstrumentDefinitionParser.h b/Framework/Geometry/inc/MantidGeometry/Instrument/InstrumentDefinitionParser.h index b05ca16158054310f8b3de5afa3c269e49c48c8a..a8ef4ede8812b6a30e1faf42f897000b7941f908 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/InstrumentDefinitionParser.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/InstrumentDefinitionParser.h @@ -69,7 +69,8 @@ public: /// specified in XML elements void setComponentLinks(std::shared_ptr &instrument, Poco::XML::Element *pRootElem, - Kernel::ProgressBase *progress = nullptr); + Kernel::ProgressBase *progress = nullptr, + std::string requestedDate = std::string()); std::string getMangledName(); @@ -192,7 +193,8 @@ private: /// Set parameter/logfile info (if any) associated with component void setLogfile(const Geometry::IComponent *comp, const Poco::XML::Element *pElem, - InstrumentParameterCache &logfileCache); + InstrumentParameterCache &logfileCache, + std::string requestedDate = std::string()); /// Parse position of facing element to V3D Kernel::V3D parseFacingElementToV3D(Poco::XML::Element *pElem); diff --git a/Framework/Geometry/inc/MantidGeometry/Objects/Track.h b/Framework/Geometry/inc/MantidGeometry/Objects/Track.h index dab094bca290d2f9c574da591123a0daef004caa..60476a4c06a0ee5046738cf0e32594596f447530 100644 --- a/Framework/Geometry/inc/MantidGeometry/Objects/Track.h +++ b/Framework/Geometry/inc/MantidGeometry/Objects/Track.h @@ -186,6 +186,7 @@ public: Track(); /// Constructor Track(const Kernel::V3D &startPt, const Kernel::V3D &unitVector); + virtual ~Track() = default; /// Adds a point of intersection to the track void addPoint(const TrackDirection direction, const Kernel::V3D &endPoint, const IObject &obj, const ComponentID compID = nullptr); @@ -235,6 +236,8 @@ public: int surfPointsCount() const { return static_cast(m_surfPoints.size()); } /// Is the link complete? int nonComplete() const; + /// Calculate attenuation across all links + virtual double calculateAttenuation(double lambda) const; private: Line m_line; ///< Line object containing origin and direction diff --git a/Framework/Geometry/src/Crystal/UnitCell.cpp b/Framework/Geometry/src/Crystal/UnitCell.cpp index 3a7feab47aa4d1665c9ceefdf1dd49f5ae4a1f50..08126bad0971c439efd46e4606fdba56de86ed42 100644 --- a/Framework/Geometry/src/Crystal/UnitCell.cpp +++ b/Framework/Geometry/src/Crystal/UnitCell.cpp @@ -7,7 +7,6 @@ #include "MantidGeometry/Crystal/UnitCell.h" #include "MantidKernel/Matrix.h" #include "MantidKernel/StringTokenizer.h" -#include "MantidKernel/System.h" #include "MantidKernel/V3D.h" #include #include @@ -27,7 +26,7 @@ UnitCell::UnitCell() : da(6), ra(6), errorda(6), G(3, 3), Gstar(3, 3), B(3, 3), ModHKL(3, 3), errorModHKL(3, 3) { da[0] = da[1] = da[2] = 1.; - da[3] = da[4] = da[5] = deg2rad * 90.0; + da[3] = da[4] = da[5] = M_PI_2; errorda[0] = errorda[1] = errorda[2] = errorda[3] = errorda[4] = errorda[5] = 0.0; MaxOrder = 0; @@ -45,7 +44,7 @@ UnitCell::UnitCell(double _a, double _b, double _c) da[1] = _b; da[2] = _c; // Angles are 90 degrees in radians ->Pi/2 - da[3] = da[4] = da[5] = 0.5 * M_PI; + da[3] = da[4] = da[5] = M_PI_2; errorda[0] = errorda[1] = errorda[2] = errorda[3] = errorda[4] = errorda[5] = 0.0; MaxOrder = 0; @@ -824,12 +823,15 @@ void UnitCell::calculateGstar() { /// Private function to calculate reciprocal lattice parameters void UnitCell::calculateReciprocalLattice() { - ra[0] = sqrt(Gstar[0][0]); // a* - ra[1] = sqrt(Gstar[1][1]); // b* - ra[2] = sqrt(Gstar[2][2]); // c* - ra[3] = acos(Gstar[1][2] / ra[1] / ra[2]); // alpha* - ra[4] = acos(Gstar[0][2] / ra[0] / ra[2]); // beta* - ra[5] = acos(Gstar[0][1] / ra[0] / ra[1]); // gamma* + ra[0] = sqrt(Gstar[0][0]); // a* + ra[1] = sqrt(Gstar[1][1]); // b* + ra[2] = sqrt(Gstar[2][2]); // c* + auto acosThreshold = [](double x) { + return std::abs(x) > 1e-15 ? acos(x) : M_PI_2; + }; + ra[3] = acosThreshold(Gstar[1][2] / ra[1] / ra[2]); // alpha* + ra[4] = acosThreshold(Gstar[0][2] / ra[0] / ra[2]); // beta* + ra[5] = acosThreshold(Gstar[0][1] / ra[0] / ra[1]); // gamma* } /// Private function to calculate #B matrix diff --git a/Framework/Geometry/src/Instrument/ComponentInfo.cpp b/Framework/Geometry/src/Instrument/ComponentInfo.cpp index fdace6688e89d88faa88f2c53f445c5fa43b61f6..678fa6eb2b9cb1af75f752c3fa1a9b208fb01018 100644 --- a/Framework/Geometry/src/Instrument/ComponentInfo.cpp +++ b/Framework/Geometry/src/Instrument/ComponentInfo.cpp @@ -221,8 +221,32 @@ Kernel::V3D ComponentInfo::samplePosition() const { bool ComponentInfo::hasSource() const { return m_componentInfo->hasSource(); } +/* + * @brief Check the sources of two componentInfo objects coincide + * + * @details check both objects either lack or have a source. If the latter, + * check their positions differ by less than 1 nm = 1e-9 m. + * + * @returns true if sources are equivalent + */ +bool ComponentInfo::hasEquivalentSource(const ComponentInfo &other) const { + return m_componentInfo->hasEquivalentSource(*(other.m_componentInfo)); +} + bool ComponentInfo::hasSample() const { return m_componentInfo->hasSample(); } +/* + * @brief Check the samples of two componentInfo objects coincide + * + * @details check both objects either lack or have a sample. If the latter, + * check their positions differ by less than 1 nm = 1e-9 m. + * + * @returns true if sources are equivalent + */ +bool ComponentInfo::hasEquivalentSample(const ComponentInfo &other) const { + return m_componentInfo->hasEquivalentSample(*(other.m_componentInfo)); +} + bool ComponentInfo::hasDetectors(const size_t componentIndex) const { if (isDetector(componentIndex)) return false; diff --git a/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp b/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp index a5c018561609616f90508489d37a82fcf8487160..87f7f3cd62268a74595ca9e9120c165556d30f02 100644 --- a/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp +++ b/Framework/Geometry/src/Instrument/InstrumentDefinitionParser.cpp @@ -23,6 +23,8 @@ #include "MantidKernel/ProgressBase.h" #include "MantidKernel/Strings.h" #include "MantidKernel/UnitFactory.h" +#include "MantidTypes/Core/DateAndTime.h" +#include "MantidTypes/Core/DateAndTimeHelpers.h" #include #include @@ -2179,13 +2181,14 @@ void InstrumentDefinitionParser::setFacing(Geometry::IComponent *comp, * @param comp :: Some component * @param pElem :: Poco::XML element that may hold \ elements * @param logfileCache :: Cache to add information about parameter to - * + * @param requestedDate :: Reference date to check the validity of the + * parameter against * @throw InstrumentDefinitionError Thrown if issues with the content of XML *instrument file */ void InstrumentDefinitionParser::setLogfile( const Geometry::IComponent *comp, const Poco::XML::Element *pElem, - InstrumentParameterCache &logfileCache) { + InstrumentParameterCache &logfileCache, std::string requestedDate) { const std::string filename = m_xmlFile->getFileFullPathStr(); // The purpose below is to have a quicker way to judge if pElem contains a @@ -2230,9 +2233,20 @@ void InstrumentDefinitionParser::setLogfile( continue; } + DateAndTime validityDate; + + if (requestedDate.empty()) { + validityDate = DateAndTime::getCurrentTime(); + } else { + validityDate.setFromISO8601(requestedDate); + } + std::string logfileID; std::string value; + DateAndTime validFrom; + DateAndTime validTo; + std::string type = "double"; // default std::string extractSingleValueAs = "mean"; // default std::string eq; @@ -2255,13 +2269,15 @@ void InstrumentDefinitionParser::setLogfile( pParamElem->getElementsByTagName("formula"); size_t numberFormula = pNLFormula->length(); - if (numberValueEle + numberLogfileEle + numberLookUp + numberFormula > 1) { + if ((numberValueEle > 0 && + numberLogfileEle + numberLookUp + numberFormula > 0) || + (numberValueEle == 0 && + numberLogfileEle + numberLookUp + numberFormula > 1)) { g_log.warning() << "XML element with name or type = " << comp->getName() - << " contains element where the value of " - "the parameter has been specified" - << " more than once. See www.mantidproject.org/IDF for " - "how the value" - << " of the parameter is set in this case."; + << " contains element where the value of the " + << "parameter has been specified more than once. See " + << "www.mantidproject.org/IDF for how the value of the " + << "parameter is set in this case."; } if (numberValueEle + numberLogfileEle + numberLookUp + numberFormula == 0) { @@ -2272,18 +2288,50 @@ void InstrumentDefinitionParser::setLogfile( continue; } - // if more than one specified for a parameter use only the first - // element + DateAndTime currentValidFrom; + DateAndTime currentValidTo; + currentValidFrom.setToMinimum(); + currentValidTo.setToMaximum(); + + // if more than one specified for a parameter, check the validity + // range if (numberValueEle >= 1) { - pValueElem = static_cast(pNLvalue->item(0)); - if (!pValueElem->hasAttribute("val")) + bool hasValue = false; + + for (unsigned long i = 0; i < numberValueEle; ++i) { + pValueElem = static_cast(pNLvalue->item(i)); + + if (!pValueElem->hasAttribute(("val"))) + continue; + + validFrom.setToMinimum(); + if (pValueElem->hasAttribute("valid-from")) + validFrom.setFromISO8601(pValueElem->getAttribute("valid-from")); + + validTo.setToMaximum(); + if (pValueElem->hasAttribute("valid-to")) + validTo.setFromISO8601(pValueElem->getAttribute("valid-to")); + + if (validFrom <= validityDate && validityDate <= validTo && + (validFrom > currentValidFrom || + (validFrom == currentValidFrom && validTo <= currentValidTo))) { + + currentValidFrom = validFrom; + currentValidTo = validTo; + } else + continue; + hasValue = true; + value = pValueElem->getAttribute("val"); + } + + if (!hasValue) { throw Kernel::Exception::InstrumentDefinitionError( "XML element with name or type = " + comp->getName() + " contains element with invalid syntax for its " - "subelement ." + - " Correct syntax is ", + "subelement . Correct syntax is ", filename); - value = pValueElem->getAttribute("val"); + } + } else if (numberLogfileEle >= 1) { // tag was used at least once. pLogfileElem = static_cast(pNLlogfile->item(0)); @@ -2480,10 +2528,13 @@ void InstrumentDefinitionParser::setLogfile( *\ elements * @param progress :: Optional progress object for reporting progress to an *algorithm + * @param requestedDate :: Optional Date against which to check the validity of + *an IPF parameter */ void InstrumentDefinitionParser::setComponentLinks( std::shared_ptr &instrument, - Poco::XML::Element *pRootElem, Kernel::ProgressBase *progress) { + Poco::XML::Element *pRootElem, Kernel::ProgressBase *progress, + std::string requestedDate) { // check if any logfile cache units set. As of this writing the only unit to // check is if "angle=radian" std::map &units = instrument->getLogfileUnit(); @@ -2576,9 +2627,10 @@ void InstrumentDefinitionParser::setComponentLinks( // Not empty Component if (sharedComp->isParametrized()) { setLogfile(sharedComp->base(), curElem, - instrument->getLogfileCache()); + instrument->getLogfileCache(), requestedDate); } else { - setLogfile(ptr.get(), curElem, instrument->getLogfileCache()); + setLogfile(ptr.get(), curElem, instrument->getLogfileCache(), + requestedDate); } } } diff --git a/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp b/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp index 5059a084c03409b4fae2d2a156a0a4d471b0a9ea..71b28148abe39f87b875bfa257215a466a80f98d 100644 --- a/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp +++ b/Framework/Geometry/src/Instrument/InstrumentVisitor.cpp @@ -175,7 +175,7 @@ InstrumentVisitor::registerComponentAssembly(const ICompAssembly &assembly) { m_parentComponentIndices->emplace_back(componentIndex); const size_t componentStop = m_assemblySortedComponentIndices->size(); - m_detectorRanges->emplace_back(std::make_pair(detectorStart, detectorStop)); + m_detectorRanges->emplace_back(detectorStart, detectorStop); m_componentRanges->emplace_back( std::make_pair(componentStart, componentStop)); @@ -213,7 +213,7 @@ InstrumentVisitor::registerGenericComponent(const IComponent &component) { // updated later in the register call of the parent. m_parentComponentIndices->emplace_back(componentIndex); // Generic components are not assemblies and do not therefore have children. - m_children->emplace_back(std::vector()); + m_children->emplace_back(); return componentIndex; } @@ -228,21 +228,19 @@ size_t InstrumentVisitor::registerInfiniteComponent( * For a generic leaf component we extend the component ids list, but * the detector indexes entries will of course be empty */ - m_detectorRanges->emplace_back( - std::make_pair(0, 0)); // Represents an empty range - // Record the ID -> index mapping + m_detectorRanges->emplace_back(0, 0); // Represents an empty range + // Record the ID -> index mapping const size_t componentIndex = commonRegistration(component); m_componentType->emplace_back(Beamline::ComponentType::Infinite); const size_t componentStart = m_assemblySortedComponentIndices->size(); - m_componentRanges->emplace_back( - std::make_pair(componentStart, componentStart + 1)); + m_componentRanges->emplace_back(componentStart, componentStart + 1); m_assemblySortedComponentIndices->emplace_back(componentIndex); // Unless this is the root component this parent is not correct and will be // updated later in the register call of the parent. m_parentComponentIndices->emplace_back(componentIndex); // Generic components are not assemblies and do not therefore have children. - m_children->emplace_back(std::vector()); + m_children->emplace_back(); return componentIndex; } diff --git a/Framework/Geometry/src/Objects/MeshObjectCommon.cpp b/Framework/Geometry/src/Objects/MeshObjectCommon.cpp index c00fc167e05c7c7c4b316844c497f465c84b5da7..64dc964bb21a219c662f6acc5f1900405be28258 100644 --- a/Framework/Geometry/src/Objects/MeshObjectCommon.cpp +++ b/Framework/Geometry/src/Objects/MeshObjectCommon.cpp @@ -220,7 +220,7 @@ const BoundingBox &getBoundingBox(const std::vector &vertices, static const double MinThickness = 0.001; double minX, maxX, minY, maxY, minZ, maxZ; minX = minY = minZ = std::numeric_limits::max(); - maxX = maxY = maxZ = std::numeric_limits::min(); + maxX = maxY = maxZ = std::numeric_limits::lowest(); // Loop over all vertices and determine minima and maxima on each axis for (const auto &vertex : vertices) { diff --git a/Framework/Geometry/src/Objects/Track.cpp b/Framework/Geometry/src/Objects/Track.cpp index 9356bcb88f6de9ca62c1669c2f0fa00ae2ca8be6..0b85278e9afa88de2dee5c4f7e240acf6f8ff6d8 100644 --- a/Framework/Geometry/src/Objects/Track.cpp +++ b/Framework/Geometry/src/Objects/Track.cpp @@ -6,6 +6,7 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidGeometry/Objects/Track.h" #include "MantidGeometry/Surfaces/Surface.h" +#include "MantidKernel/Material.h" #include "MantidKernel/Matrix.h" #include "MantidKernel/Tolerance.h" #include "MantidKernel/V3D.h" @@ -286,6 +287,16 @@ std::ostream &operator<<(std::ostream &os, TrackDirection direction) { return os; } +double Track::calculateAttenuation(double lambda) const { + double factor(1.0); + for (const auto &segment : m_links) { + const double length = segment.distInsideObject; + const auto &segObj = *(segment.object); + factor *= segObj.material().attenuation(length, lambda); + } + return factor; +} + } // NAMESPACE Geometry } // NAMESPACE Mantid diff --git a/Framework/Geometry/src/Rendering/RenderingHelpersThrowing.cpp b/Framework/Geometry/src/Rendering/RenderingHelpersThrowing.cpp index c5e07bb9a8b65c80b0ae040955938d02cf22c16f..db295385e8d3fca75ae75265421c9b699a789d65 100644 --- a/Framework/Geometry/src/Rendering/RenderingHelpersThrowing.cpp +++ b/Framework/Geometry/src/Rendering/RenderingHelpersThrowing.cpp @@ -6,6 +6,7 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidGeometry/Rendering/RenderingHelpers.h" #include +#include namespace { void throwNoOpenGLError(const std::string &function) { diff --git a/Framework/Geometry/src/Surfaces/Cylinder.cpp b/Framework/Geometry/src/Surfaces/Cylinder.cpp index c2c818f6b1d05b9c1446f04547da957f8f984312..4b0b64082988a3de824173dafae1d1ebe56b985f 100644 --- a/Framework/Geometry/src/Surfaces/Cylinder.cpp +++ b/Framework/Geometry/src/Surfaces/Cylinder.cpp @@ -395,7 +395,7 @@ void Cylinder::getBoundingBox(double &xmax, double &ymax, double &zmax, } if (!listOfPoints.empty()) { xmin = ymin = zmin = std::numeric_limits::max(); - xmax = ymax = zmax = std::numeric_limits::min(); + xmax = ymax = zmax = std::numeric_limits::lowest(); for (std::vector::const_iterator it = listOfPoints.begin(); it != listOfPoints.end(); ++it) { // std::cout<<(*it)<<'\n'; diff --git a/Framework/Geometry/src/Surfaces/Plane.cpp b/Framework/Geometry/src/Surfaces/Plane.cpp index 7755a85cd0bcb3784fdd333d80c082d987008a8f..fade39ca269e247cb197b09caacea6a772c147ff 100644 --- a/Framework/Geometry/src/Surfaces/Plane.cpp +++ b/Framework/Geometry/src/Surfaces/Plane.cpp @@ -408,7 +408,7 @@ void Plane::getBoundingBox(double &xmax, double &ymax, double &zmax, // std::cout<::max(); - xmax = ymax = zmax = -std::numeric_limits::max(); + xmax = ymax = zmax = std::numeric_limits::lowest(); for (std::vector::const_iterator it = listOfPoints.begin(); it != listOfPoints.end(); ++it) { // std::cout<<(*it)<<'\n'; diff --git a/Framework/Geometry/test/TrackTest.h b/Framework/Geometry/test/TrackTest.h index c010f5ab65beb97616eec9d8f809fd03246583cc..aef1ddfdc5fe6e8773c9712436e224abb3334cd7 100644 --- a/Framework/Geometry/test/TrackTest.h +++ b/Framework/Geometry/test/TrackTest.h @@ -10,8 +10,10 @@ #include "MantidGeometry/Objects/CSGObject.h" #include "MantidGeometry/Objects/Track.h" #include "MantidKernel/Logger.h" +#include "MantidKernel/Material.h" #include "MantidKernel/System.h" #include "MantidKernel/V3D.h" +#include "MantidTestHelpers/ComponentCreationHelper.h" #include using namespace Mantid; @@ -155,4 +157,26 @@ public: shape); // Entry at -5,-2,0 TS_ASSERT_EQUALS(A.surfPointsCount(), 2); } + + void test_calculateAttenuation() { + const double lambdaBefore(2.5), lambdaAfter(3.5); + auto shape = ComponentCreationHelper::createSphere(0.1); + shape->setMaterial(Kernel::Material( + "Vanadium", Mantid::PhysicalConstants::getNeutronAtom(23), 0.02)); + // use tracks generated by + // MCInteractionVolume::test_Solid_Sample_Gives_Expected_Tracks test + Track beforeScatter({-0.05, -0.05, -0.05}, + {-0.999343185, 0.025624184, 0.025624184}); + beforeScatter.addLink({-0.05, -0.05, -0.05}, + {-0.071481137, -0.049449202, -0.049449202}, + 0.021495255, *shape); + Track afterScatter({-0.05, -0.05, -0.05}, + {0.417472754, 0.417472755, 0.807113993}); + afterScatter.addLink({-0.05, -0.05, -0.05}, + {0.024407241, 0.024407241, 0.093853999}, 0.021495255, + *shape); + const double factor = beforeScatter.calculateAttenuation(lambdaBefore) * + afterScatter.calculateAttenuation(lambdaAfter); + TS_ASSERT_DELTA(0.0028357258, factor, 1e-8); + } }; diff --git a/Framework/Indexing/inc/MantidIndexing/Conversion.h b/Framework/Indexing/inc/MantidIndexing/Conversion.h index 8e5ff0a9188c27c11f967739cfa15536cd031f9a..647a36e2eb53577d801978bba937b571de8ef065 100644 --- a/Framework/Indexing/inc/MantidIndexing/Conversion.h +++ b/Framework/Indexing/inc/MantidIndexing/Conversion.h @@ -51,7 +51,7 @@ template < std::vector castVector(const std::vector &indices) { std::vector converted; converted.reserve(indices.size()); - for (const auto index : indices) + for (const auto &index : indices) converted.emplace_back( static_cast(static_cast(index))); return converted; diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt index 604311b857fc0c3d899e539e934ba3bf85cd306b..bccb544ae21f1789b4d72b5fea526ca5457ee95e 100644 --- a/Framework/Kernel/CMakeLists.txt +++ b/Framework/Kernel/CMakeLists.txt @@ -80,7 +80,6 @@ set(SRC_FILES src/NexusHDF5Descriptor.cpp src/NullValidator.cpp src/OptionalBool.cpp - src/ParaViewVersion.cpp src/ProgressBase.cpp src/Property.cpp src/PropertyHistory.cpp @@ -247,7 +246,6 @@ set(INC_FILES inc/MantidKernel/NexusHDF5Descriptor.h inc/MantidKernel/NullValidator.h inc/MantidKernel/OptionalBool.h - inc/MantidKernel/ParaViewVersion.h inc/MantidKernel/PhysicalConstants.h inc/MantidKernel/PocoVersion.h inc/MantidKernel/ProgressBase.h @@ -546,18 +544,9 @@ endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/inc/MantidKernel/PocoVersion.h.in ${CMAKE_CURRENT_SOURCE_DIR}/inc/MantidKernel/PocoVersion.h) -# This section deals with creating the ParaViewVersion implementation -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/ParaViewVersion.cpp.in - ${CMAKE_CURRENT_SOURCE_DIR}/src/ParaViewVersion.cpp) # This section deals with creating the GithubApiHelper implementation - -if(WIN32) - set(GITHUB_AUTHORIZATION_TOKEN "8ec7afc857540ee60af78cba1cf7779a6ed0b6b9") -elseif(APPLE) - set(GITHUB_AUTHORIZATION_TOKEN "9f1c1acd61ecb87b21ad0382f6b403c8294992c5") -else() - set(GITHUB_AUTHORIZATION_TOKEN "ccacbaf39a7ad8151fca03b4ea99a29b28f0993b") -endif() +set (GITHUB_AUTHORIZATION_TOKEN "" + CACHE STRING "token used for authenticated read access to github") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/inc/MantidKernel/GitHubApiHelper.h.in ${CMAKE_CURRENT_SOURCE_DIR}/inc/MantidKernel/GitHubApiHelper.h) @@ -574,21 +563,8 @@ endif() set(FRAMEWORK_PLUGINS_DIR ".") set(QT_PLUGINS_DIR ".") # %V will be replaced with the major version of Qt at runtime -if(MAKE_VATES) - set(PV_PLUGINS_DIR "./plugins/paraview/qt%V") - if(MSVC) - set( - PARAVIEW_PYTHON_PATHS - "${ParaView_DIR}/bin/$<$:Release>$<$:Debug>;${ParaView_DIR}/lib/$<$:Release>$<$:Debug>;${ParaView_DIR}/lib/site-packages;" - ) - else() - set(PARAVIEW_PYTHON_PATHS - "${ParaView_DIR}/lib;${ParaView_DIR}/lib/site-packages;") - endif() -else() - set(PV_PLUGINS_DIR "") - set(PARAVIEW_PYTHON_PATHS "") -endif() +set(PV_PLUGINS_DIR "") +set(PARAVIEW_PYTHON_PATHS "") set(IGNORE_PARAVIEW "0") set(UPDATE_INSTRUMENT_DEFINTITIONS "0") set(CHECK_FOR_NEW_MANTID_VERSION "0") @@ -650,20 +626,7 @@ endif() set(FRAMEWORK_PLUGINS_DIR ${MANTID_ROOT}/plugins) set(PYTHONPLUGIN_DIRS "${FRAMEWORK_PLUGINS_DIR}/python") set(PYTHONPLUGIN_MANIFEST "${PYTHONPLUGIN_DIRS}/python-plugin-manifest.txt") -if(MAKE_VATES) - if(APPLE) - set(PARAVIEW_PYTHON_PATHS "../Libraries;../Python;") - elseif(WIN32) - set(PV_LIBS - "../lib/paraview-${PARAVIEW_VERSION_MAJOR}.${PARAVIEW_VERSION_MINOR}") - set(PARAVIEW_PYTHON_PATHS "${PV_LIBS}/site-packages;") - else() # Linux - set(PV_LIBS "${CMAKE_INSTALL_PREFIX}/lib/paraview-${PARAVIEW_VERSION}") - set(PARAVIEW_PYTHON_PATHS "${PV_LIBS};${PV_LIBS}/site-packages;") - endif() -else() - set(PARAVIEW_PYTHON_PATHS "") -endif() +set(PARAVIEW_PYTHON_PATHS "") set(UPDATE_INSTRUMENT_DEFINTITIONS "${ENABLE_NETWORK_ACCESS}") set(CHECK_FOR_NEW_MANTID_VERSION "${ENABLE_NETWORK_ACCESS}") set(ENABLE_USAGE_REPORTS "${ENABLE_NETWORK_ACCESS}") diff --git a/Framework/Kernel/inc/MantidKernel/AttenuationProfile.h b/Framework/Kernel/inc/MantidKernel/AttenuationProfile.h index c210af1a6239f843e437a11cdf5206a817022afb..b9f18217b16bf282c6f7b457ff1cbb4e720115f9 100644 --- a/Framework/Kernel/inc/MantidKernel/AttenuationProfile.h +++ b/Framework/Kernel/inc/MantidKernel/AttenuationProfile.h @@ -17,10 +17,14 @@ class Material; class MANTID_KERNEL_DLL AttenuationProfile { public: + AttenuationProfile(){}; AttenuationProfile(const std::string &inputFileName, const std::string &searchPath, - Material *extrapolationMaterial = nullptr); - double getAttenuationCoefficient(const double lambda) const; + Material *extrapolationMaterial = nullptr, + double extrapolationMaxX = 100); + double getAttenuationCoefficient(const double x) const; + + void setAttenuationCoefficient(const double x, const double atten); private: Kernel::Interpolation m_Interpolator; diff --git a/Framework/Kernel/inc/MantidKernel/DataService.h b/Framework/Kernel/inc/MantidKernel/DataService.h index 1d1a5541b343455326b8f9563d31c60b3be56cf5..9bd5c8460175ef8be8177ba99e119655b0c2c8e9 100644 --- a/Framework/Kernel/inc/MantidKernel/DataService.h +++ b/Framework/Kernel/inc/MantidKernel/DataService.h @@ -419,11 +419,13 @@ public: * to unsorted * @param hiddenState Whether to include hidden objects, Defaults to * Auto which checks the current configuration to determine behavior. + * @param contain Include only object names that contain this string. * @return A vector of strings containing object names in the ADS */ - std::vector getObjectNames( - DataServiceSort sortState = DataServiceSort::Unsorted, - DataServiceHidden hiddenState = DataServiceHidden::Auto) const { + std::vector + getObjectNames(DataServiceSort sortState = DataServiceSort::Unsorted, + DataServiceHidden hiddenState = DataServiceHidden::Auto, + const std::string &contain = "") const { std::vector foundNames; @@ -442,7 +444,11 @@ public: std::lock_guard _lock(m_mutex); foundNames.reserve(datamap.size()); for (const auto &item : datamap) { - foundNames.emplace_back(item.first); + if (contain.empty()) { + foundNames.emplace_back(item.first); + } else if (item.first.find(contain) != std::string::npos) { + foundNames.emplace_back(item.first); + } } // Lock released at end of scope here } else { @@ -451,7 +457,11 @@ public: for (const auto &item : datamap) { if (!isHiddenDataServiceObject(item.first)) { // This item is not hidden add it - foundNames.emplace_back(item.first); + if (contain.empty()) { + foundNames.emplace_back(item.first); + } else if (item.first.find(contain) != std::string::npos) { + foundNames.emplace_back(item.first); + } } } // Lock released at end of scope here diff --git a/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in b/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in index aa3a30e578df7ee413113fd10e23db1f9cf5193e..bf87254cef7b1c353c1e6489634be76a657ce078 100644 --- a/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in +++ b/Framework/Kernel/inc/MantidKernel/GitHubApiHelper.h.in @@ -11,6 +11,10 @@ namespace Mantid { namespace Kernel { +namespace { + const static std::string DEFAULT_GITHUB_TOKEN("@GITHUB_AUTHORIZATION_TOKEN@"); +} + /** GitHubApiHelper : A helper class for supporting access to the github api through HTTP and HTTPS, inherits from the InternetHelper. This class automatically adds the authorization to a read only account. @@ -37,13 +41,12 @@ protected: virtual int sendRequestAndProcess(Poco::Net::HTTPClientSession &session, Poco::URI &uri, std::ostream &responseStream) override; private: - int processAnonymousRequest(const Poco::Net::HTTPResponse &response, - Poco::URI &uri, - std::ostream &responseStream); - void addAuthenticationToken() { - addHeader("Authorization", - "token @GITHUB_AUTHORIZATION_TOKEN@"); - } + int processAnonymousRequest(Poco::URI &uri, std::ostream &responseStream); + void addAuthenticationToken(); + /** + * API token for github access. This is only created during construction. + */ + std::string m_api_token; }; } // namespace Kernel diff --git a/Framework/Kernel/inc/MantidKernel/Material.h b/Framework/Kernel/inc/MantidKernel/Material.h index f24346036f86935f3c2390a1831cafff1bb632c4..766e8351a8411a0cf9bf00071b9ec89c8bb4fe07 100644 --- a/Framework/Kernel/inc/MantidKernel/Material.h +++ b/Framework/Kernel/inc/MantidKernel/Material.h @@ -85,6 +85,7 @@ public: /// Allow an explicit attenuation profile to be loaded onto the material /// that overrides the standard linear absorption coefficient void setAttenuationProfile(AttenuationProfile attenuationOverride); + void setXRayAttenuationProfile(AttenuationProfile attenuationProfile); /// Returns the name of the material const std::string &name() const; @@ -117,6 +118,8 @@ public: double attenuation(const double distance, const double lambda = PhysicalConstants::NeutronAtom::ReferenceLambda) const; + /// Compute the x-ray attenuation at a given energy over the given distance + double xRayAttenuation(const double distance, const double energy) const; /** * Returns the linear coefficient of absorption for the material in units of @@ -196,6 +199,8 @@ public: void saveNexus(::NeXus::File *file, const std::string &group) const; void loadNexus(::NeXus::File *file, const std::string &group); + bool hasValidXRayAttenuationProfile(); + private: /// Update the total atom count void countAtoms(); @@ -222,6 +227,7 @@ private: double m_totalScatterXSection; boost::optional m_attenuationOverride; + boost::optional m_xRayAttenuationProfile; }; /// Typedef for a shared pointer diff --git a/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h b/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h index 157b8165ac558d0e4287bab4920d78bfb686463e..25124275f641266293fc536c7fc28fb321a232bb 100644 --- a/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h +++ b/Framework/Kernel/inc/MantidKernel/MaterialBuilder.h @@ -45,6 +45,7 @@ public: MaterialBuilder &setIncoherentXSection(double xsec); MaterialBuilder &setAbsorptionXSection(double xsec); MaterialBuilder &setAttenuationProfileFilename(std::string filename); + MaterialBuilder &setXRayAttenuationProfileFilename(std::string filename); void setAttenuationSearchPath(std::string path); @@ -79,6 +80,7 @@ private: m_absSection; NumberDensityUnit m_numberDensityUnit; boost::optional m_attenuationProfileFileName; + boost::optional m_xRayAttenuationProfileFileName; std::string m_attenuationFileSearchPath; }; diff --git a/Framework/Kernel/inc/MantidKernel/ParaViewVersion.h b/Framework/Kernel/inc/MantidKernel/ParaViewVersion.h deleted file mode 100644 index 0343cbe8deee3ea03b776893c38c158e141f76fe..0000000000000000000000000000000000000000 --- a/Framework/Kernel/inc/MantidKernel/ParaViewVersion.h +++ /dev/null @@ -1,29 +0,0 @@ -// Mantid Repository : https://github.com/mantidproject/mantid -// -// Copyright © 2012 ISIS Rutherford Appleton Laboratory UKRI, -// NScD Oak Ridge National Laboratory, European Spallation Source, -// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS -// SPDX - License - Identifier: GPL - 3.0 + -#pragma once - -//---------------------------------------------------------------------- -// Includes -//---------------------------------------------------------------------- -#include "MantidKernel/DllConfig.h" -#include - -namespace Mantid { -namespace Kernel { -/** Class containing static methods to return the ParaView version number. - */ -class MANTID_KERNEL_DLL ParaViewVersion { -public: - static std::string targetVersion(); ///< The version number major.minor - -private: - ParaViewVersion(); ///< Private, unimplemented constructor. Not a class that - /// can be instantiated. -}; - -} // namespace Kernel -} // namespace Mantid diff --git a/Framework/Kernel/inc/MantidKernel/StdoutChannel.h b/Framework/Kernel/inc/MantidKernel/StdoutChannel.h index d9473f99d86c554976c83eccfbdeb399de1a123b..9184d9a80d25228dc5d55342d5c42d5bbc5b056e 100644 --- a/Framework/Kernel/inc/MantidKernel/StdoutChannel.h +++ b/Framework/Kernel/inc/MantidKernel/StdoutChannel.h @@ -25,4 +25,4 @@ public: /// Constructor for StdChannel StdoutChannel(); }; -} // namespace Poco \ No newline at end of file +} // namespace Poco diff --git a/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h b/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h index 0975c6d1da8d1eb3c0816ddf8c38eaba4e183929..646bda443aa11a76f7b0ac1b7a3b555ef73e558d 100644 --- a/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h +++ b/Framework/Kernel/inc/MantidKernel/TimeSeriesProperty.h @@ -284,6 +284,8 @@ public: void filterWith(const TimeSeriesProperty *filter); /// Restores the property to the unsorted state void clearFilter(); + // Returns whether the time series has been filtered + bool isFiltered() const { return m_filterApplied; } /// Updates size() void countSize() const; diff --git a/Framework/Kernel/src/AttenuationProfile.cpp b/Framework/Kernel/src/AttenuationProfile.cpp index 684dadd5e70f61fa95ea6930d7bb391488d41c67..e3480a8fa08ca199ad6c4797057f302b6b64589d 100644 --- a/Framework/Kernel/src/AttenuationProfile.cpp +++ b/Framework/Kernel/src/AttenuationProfile.cpp @@ -15,22 +15,20 @@ namespace Mantid { namespace Kernel { -namespace { -constexpr auto LARGE_LAMBDA = 100; // Lambda likely to be beyond max lambda in - // any measured spectra. In Angstroms -} - /** * Construct an attenuation profile object * @param inputFileName :: The name of the file containing the attenuation * profile data. Filename can be a full path or just file name * @param searchPath :: Path to search for the input file + * @param extrapolationMaxX :: X value at which a point will be inserted + * from the tabulated attenuation data to assist with extrapolation * @param extrapolationMaterial :: Material whose properties will be used to * extrapolate beyond the lambda range of the supplied profile */ AttenuationProfile::AttenuationProfile(const std::string &inputFileName, const std::string &searchPath, - Material *extrapolationMaterial) { + Material *extrapolationMaterial, + double extrapolationMaxX) { Poco::Path suppliedFileName(inputFileName); Poco::Path inputFilePath; std::string fileExt = suppliedFileName.getExtension(); @@ -64,31 +62,30 @@ AttenuationProfile::AttenuationProfile(const std::string &inputFileName, std::ifstream input(inputFilePath.toString(), std::ios_base::in); if (input) { std::string line; - double minLambda = std::numeric_limits::max(); - double maxLambda = std::numeric_limits::min(); + double minX = std::numeric_limits::max(); + double maxX = std::numeric_limits::lowest(); while (std::getline(input, line)) { - double lambda, alpha, error; - if (std::stringstream(line) >> lambda >> alpha >> error) { - minLambda = std::min(lambda, minLambda); - maxLambda = std::max(lambda, maxLambda); - m_Interpolator.addPoint(lambda, 1000 * alpha); + double x, alpha, error; + if (std::stringstream(line) >> x >> alpha >> error) { + minX = std::min(x, minX); + maxX = std::max(x, maxX); + m_Interpolator.addPoint(x, 1000 * alpha); } } input.close(); - // Assist the extrapolation outside the supplied lambda range to better + // Assist the extrapolation outside the supplied x range to better // handle noisy data. Add two surrounding points using the attenuation // calculated from tabulated absorption\scattering cross sections if (m_Interpolator.containData() && extrapolationMaterial) { - if ((minLambda > 0) && - (minLambda < std::numeric_limits::max())) { + if ((minX > 0) && (minX < std::numeric_limits::max())) { m_Interpolator.addPoint( 0, extrapolationMaterial->attenuationCoefficient(0)); } - if ((maxLambda < LARGE_LAMBDA) && - (maxLambda > std::numeric_limits::min())) { + if ((maxX < extrapolationMaxX) && + (maxX > std::numeric_limits::lowest())) { m_Interpolator.addPoint( - LARGE_LAMBDA, - extrapolationMaterial->attenuationCoefficient(LARGE_LAMBDA)); + extrapolationMaxX, + extrapolationMaterial->attenuationCoefficient(extrapolationMaxX)); } } } else { @@ -102,14 +99,23 @@ AttenuationProfile::AttenuationProfile(const std::string &inputFileName, } /** - * Returns the attenuation coefficient for the supplied wavelength - * @param lambda The wavelength the attenuation coefficient is required for - * (Angstroms) + * Returns the attenuation coefficient for the supplied x value + * @param x The x value (typically wavelength) that the attenuation coefficient + * is required for (Angstroms) * @returns Attenuation coefficient ie alpha in I/I0=exp(-alpha*distance) */ -double -AttenuationProfile::getAttenuationCoefficient(const double lambda) const { - return m_Interpolator.value(lambda); +double AttenuationProfile::getAttenuationCoefficient(const double x) const { + return m_Interpolator.value(x); +} +/** + * Set the attenuation coefficient at x value + * @param x The x value (typically wavelength) that the attenuation coefficient + * is required for (Angstroms) + * @param atten attenuation coefficient at x value + */ +void AttenuationProfile::setAttenuationCoefficient(const double x, + const double atten) { + m_Interpolator.addPoint(x, atten); } } // namespace Kernel } // namespace Mantid \ No newline at end of file diff --git a/Framework/Kernel/src/ConfigService.cpp b/Framework/Kernel/src/ConfigService.cpp index 95c02cb877958dc2bbc8bf6be345b5a0810e60ba..7eea6191803080db2251b66df7aa991a11857cb9 100644 --- a/Framework/Kernel/src/ConfigService.cpp +++ b/Framework/Kernel/src/ConfigService.cpp @@ -953,6 +953,9 @@ void ConfigServiceImpl::setString(const std::string &key, cacheInstrumentPaths(); } else if (key == "defaultsave.directory") { appendDataSearchDir(value); + } else if (key == "logging.channels.consoleChannel.class") { + // this key requires reloading logging for it to take effect + configureLogging(); } m_notificationCenter.postNotification(new ValueChanged(key, value, old)); diff --git a/Framework/Kernel/src/ErrorReporter.cpp b/Framework/Kernel/src/ErrorReporter.cpp index b828dee145f0dc00d3ecd781ae4b943ef168e46e..4f219f409801ff0ddc25e5ee608c83fb960d703c 100644 --- a/Framework/Kernel/src/ErrorReporter.cpp +++ b/Framework/Kernel/src/ErrorReporter.cpp @@ -13,7 +13,6 @@ #include "MantidKernel/InternetHelper.h" #include "MantidKernel/Logger.h" #include "MantidKernel/MantidVersion.h" -#include "MantidKernel/ParaViewVersion.h" #include #include @@ -97,12 +96,8 @@ std::string ErrorReporter::generateErrorMessage() const { message["osVersion"] = ConfigService::Instance().getOSVersion(); message["osReadable"] = ConfigService::Instance().getOSVersionReadable(); -#if defined(MAKE_VATES) - // paraview - message["ParaView"] = Kernel::ParaViewVersion::targetVersion(); -#else + // legacy interface requires paraview version message["ParaView"] = 0; -#endif // mantid version and sha1 message["mantidVersion"] = MantidVersion::version(); diff --git a/Framework/Kernel/src/FacilityInfo.cpp b/Framework/Kernel/src/FacilityInfo.cpp index 2590ce81c3b23789889ad21c5d70cd2cd842f168..141cb3aeadeedfa4dec8389fbb2395eaa9ca7de1 100644 --- a/Framework/Kernel/src/FacilityInfo.cpp +++ b/Framework/Kernel/src/FacilityInfo.cpp @@ -168,7 +168,9 @@ void FacilityInfo::fillInstruments(const Poco::XML::Element *elem) { try { InstrumentInfo instr(this, elem); m_instruments.emplace_back(instr); - } catch (...) { /*skip this instrument*/ + } catch (std::runtime_error &e) { /*skip this instrument*/ + g_log.warning("Failed to load instument for: " + m_name + ": " + + e.what()); } } } @@ -193,7 +195,9 @@ void FacilityInfo::fillComputeResources(const Poco::XML::Element *elem) { m_computeResInfos.emplace_back(cr); g_log.debug() << "Compute resource found: " << cr << '\n'; - } catch (...) { // next resource... + } catch (std::runtime_error &e) { // next resource... + g_log.warning("Failed to load compute resource for: " + m_name + ": " + + e.what()); } std::string name = elem->getAttribute("name"); diff --git a/Framework/Kernel/src/FileValidator.cpp b/Framework/Kernel/src/FileValidator.cpp index 4e27322968a030a27a702fdff6f7d7983c8c13e2..939ab98790da8334c8c25583af68a24e110d7ede 100644 --- a/Framework/Kernel/src/FileValidator.cpp +++ b/Framework/Kernel/src/FileValidator.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include namespace Mantid { namespace Kernel { @@ -90,6 +92,16 @@ std::string FileValidator::checkValidity(const std::string &value) const { return "File \"" + abspath + "\" not found"; } + if (m_testExist && (Poco::File(value).exists())) { + std::ifstream in; + in.open(value.c_str()); + if (!in) { + std::stringstream error; + error << "Failed to open " + value + ": " << strerror(errno); + return error.str(); + } + } + // Otherwise we are okay, file extensions are just a suggestion so no // validation on them is necessary return ""; diff --git a/Framework/Kernel/src/GitHubApiHelper.cpp b/Framework/Kernel/src/GitHubApiHelper.cpp index 216bd8902a13180eea2620ea41d8b82b7452b4d9..4b50265131ac4a662f3e18672b17c0503385c7b5 100644 --- a/Framework/Kernel/src/GitHubApiHelper.cpp +++ b/Framework/Kernel/src/GitHubApiHelper.cpp @@ -5,6 +5,7 @@ // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + #include "MantidKernel/GitHubApiHelper.h" +#include "MantidKernel/ConfigService.h" #include "MantidKernel/DateAndTime.h" #include "MantidKernel/Logger.h" #include @@ -36,6 +37,9 @@ Logger g_log("GitHubApiHelper"); const std::string RATE_LIMIT_URL("https://api.github.com/rate_limit"); +// key to retreive api token from ConfigService +const std::string CONFIG_KEY_GITHUB_TOKEN("network.github.api_token"); + std::string formatRateLimit(const int rateLimit, const int remaining, const int expires) { DateAndTime expiresDateAndTime; @@ -47,12 +51,57 @@ std::string formatRateLimit(const int rateLimit, const int remaining, << "Z"; return msg.str(); } + +/* + * Small function to encapsulate getting the token from everything else + */ +std::string getApiToken() { + // default token is empty string meaning do unauthenticated calls + std::string token(DEFAULT_GITHUB_TOKEN); + // get the token from configservice if it has been set + if (ConfigService::Instance().hasProperty(CONFIG_KEY_GITHUB_TOKEN)) { + token = ConfigService::Instance().getString(CONFIG_KEY_GITHUB_TOKEN); + } + + // unset is the user's way of intentionally turning of authentication + if (token.empty() || token == "unset") { + token = ""; + } else { + // error check that token is possibly valid - 40 char + // TODO example: 8ec7afc857540ee60af78cba1cf7779a6ed0b6b9 + if (token.size() != 40) { + g_log.notice() << "GitHub API token is not 40 characters (found " + << token.size() << ") with token =\"" << token + << "\" using unauthenticated connection\n"; + token = ""; + } + } + + // log what the token is and create final string to set in header + if (token.empty()) { + // only unauthenticated calls + g_log.information("Making unauthenticated calls to GitHub"); + return ""; + } else { + g_log.information("Attempting authenticated calls to GitHub"); + + // create full header using token + std::stringstream token_header; + token_header << "token " << token; + return token_header.str(); + } + + return token; +} } // namespace //---------------------------------------------------------------------------------------------- /** Constructor */ GitHubApiHelper::GitHubApiHelper() : InternetHelper() { + // set up the api token so it can be quickly added to the authentication + m_api_token = getApiToken(); + addAuthenticationToken(); } @@ -61,6 +110,9 @@ GitHubApiHelper::GitHubApiHelper() : InternetHelper() { */ GitHubApiHelper::GitHubApiHelper(const Kernel::ProxyInfo &proxy) : InternetHelper(proxy) { + // set up the api token so it can be quickly added to the authentication + m_api_token = getApiToken(); + addAuthenticationToken(); } @@ -69,6 +121,13 @@ void GitHubApiHelper::reset() { addAuthenticationToken(); } +void GitHubApiHelper::addAuthenticationToken() { + // only add the token if it has been set + if (!m_api_token.empty()) { + addHeader("Authorization", m_api_token); + } +} + bool GitHubApiHelper::isAuthenticated() { return (m_headers.find("Authorization") != m_headers.end()); } @@ -115,17 +174,12 @@ std::string GitHubApiHelper::getRateLimitDescription() { return formatRateLimit(limit, remaining, expires); } -int GitHubApiHelper::processAnonymousRequest( - const Poco::Net::HTTPResponse &response, Poco::URI &uri, - std::ostream &responseStream) { - if (!isAuthenticated()) { - g_log.debug("Repeating API call anonymously\n"); - removeHeader("Authorization"); - return this->sendRequest(uri.toString(), responseStream); - } else { - g_log.warning("Authentication failed and anonymous access refused\n"); - return response.getStatus(); - } +int GitHubApiHelper::processAnonymousRequest(Poco::URI &uri, + std::ostream &responseStream) { + g_log.debug("Repeating API call anonymously\n"); + removeHeader("Authorization"); + m_api_token = ""; // all future calls are anonymous + return this->sendRequest(uri.toString(), responseStream); } int GitHubApiHelper::sendRequestAndProcess(HTTPClientSession &session, @@ -153,7 +207,7 @@ int GitHubApiHelper::sendRequestAndProcess(HTTPClientSession &session, (retStatus == HTTP_NOT_FOUND)) { // If authentication fails you can get HTTP_UNAUTHORIZED or HTTP_NOT_FOUND // If the limit runs out you can get HTTP_FORBIDDEN - return this->processAnonymousRequest(*m_response, uri, responseStream); + return this->processAnonymousRequest(uri, responseStream); } else if (isRelocated(retStatus)) { return this->processRelocation(*m_response, responseStream); } else { diff --git a/Framework/Kernel/src/Interpolation.cpp b/Framework/Kernel/src/Interpolation.cpp index 7786c4c5b1b00028723fd61e385f331a4b0574a9..4a6f3a34f0702a4e8380ba4c4ead9ac63974b780 100644 --- a/Framework/Kernel/src/Interpolation.cpp +++ b/Framework/Kernel/src/Interpolation.cpp @@ -137,7 +137,7 @@ void Interpolation::addPoint(const double &xx, const double &yy) { } if (xx > m_data[N - 1].first) { - m_data.emplace_back(DataXY(xx, yy)); + m_data.emplace_back(xx, yy); return; } diff --git a/Framework/Kernel/src/MantidVersion.cpp.in b/Framework/Kernel/src/MantidVersion.cpp.in index 1df92769ad406d775872a7b7bf15241d95ced93d..c61259e0ad55db020f704703f7a9c1f5aafc00aa 100644 --- a/Framework/Kernel/src/MantidVersion.cpp.in +++ b/Framework/Kernel/src/MantidVersion.cpp.in @@ -20,7 +20,7 @@ const char* MantidVersion::version() { // The major and minor version numbers are specified in Build/CMake/VersionNumber.cmake // The patch number is the number of commits since the most recent tag of the repository - return "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@"; + return "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@@VERSION_TWEAK@"; } const char* MantidVersion::versionShort() diff --git a/Framework/Kernel/src/Material.cpp b/Framework/Kernel/src/Material.cpp index 6ffa85cad6ee8f728c1dadbf97ffaf813a8e62e5..fe3b117d56fe9900dfc6e169bd9195bf292b34ad 100644 --- a/Framework/Kernel/src/Material.cpp +++ b/Framework/Kernel/src/Material.cpp @@ -189,6 +189,11 @@ void Material::setAttenuationProfile(AttenuationProfile attenuationOverride) { m_attenuationOverride = std::move(attenuationOverride); } +void Material::setXRayAttenuationProfile( + AttenuationProfile attenuationProfile) { + m_xRayAttenuationProfile = std::move(attenuationProfile); +} + /** * Returns the name * @returns A string containing the name of the material @@ -297,6 +302,30 @@ double Material::attenuation(const double distance, const double lambda) const { return exp(-attenuationCoefficient(lambda) * distance); } +/** + * @param distance Distance (m) travelled + * @param energy KeV to compute the attenuation + * @return The dimensionless attenuation factor + */ +double Material::xRayAttenuation(const double distance, + const double energy) const { + if (m_xRayAttenuationProfile) { + return exp(-m_xRayAttenuationProfile->getAttenuationCoefficient(energy) * + distance); + } else { + throw std::runtime_error("xRayAttenuationProfile override not set"); + } +} +/* + * @returns true if m_xRayAttenuationOverride is set and false if not + */ +bool Material::hasValidXRayAttenuationProfile() { + if (m_xRayAttenuationProfile) { + return true; + } else { + return false; + } +} // NOTE: the angstrom^-2 to barns and the angstrom^-1 to cm^-1 // will cancel for mu to give units: cm^-1 double Material::linearAbsorpCoef(const double lambda) const { diff --git a/Framework/Kernel/src/MaterialBuilder.cpp b/Framework/Kernel/src/MaterialBuilder.cpp index 83c3b722f6e4338356fd5a409de0be08af382827..b78296d94c5816d39e65a11e98952d7de7eb1481 100644 --- a/Framework/Kernel/src/MaterialBuilder.cpp +++ b/Framework/Kernel/src/MaterialBuilder.cpp @@ -22,6 +22,8 @@ namespace { inline bool isEmpty(const boost::optional &value) { return !value || value == Mantid::EMPTY_DBL(); } +constexpr auto LARGE_LAMBDA = 100; // Lambda likely to be beyond max lambda in + // any measured spectra. In Angstroms } // namespace /** @@ -229,6 +231,19 @@ MaterialBuilder::setAttenuationProfileFilename(std::string filename) { return *this; } +/** + * Set a value for the attenuation profile filename + * @param filename Name of the file containing the attenuation profile + * @return A reference to the this object to allow chaining + */ +MaterialBuilder & +MaterialBuilder::setXRayAttenuationProfileFilename(std::string filename) { + if (!filename.empty()) { + m_xRayAttenuationProfileFileName = filename; + } + return *this; +} + /** * Set a value for the attenuation profile search path * @param path Path to search @@ -271,9 +286,17 @@ Material MaterialBuilder::build() const { if (m_attenuationProfileFileName) { AttenuationProfile materialAttenuation(m_attenuationProfileFileName.get(), m_attenuationFileSearchPath, - material.get()); + material.get(), LARGE_LAMBDA); material->setAttenuationProfile(materialAttenuation); } + if (m_xRayAttenuationProfileFileName) { + // don't supply a material so that extrapolation using the neutron tabulated + // attenuation data is turned off + AttenuationProfile materialAttenuation( + m_xRayAttenuationProfileFileName.get(), m_attenuationFileSearchPath, + nullptr, -1); + material->setXRayAttenuationProfile(materialAttenuation); + } return *material; } diff --git a/Framework/Kernel/src/MaterialXMLParser.cpp b/Framework/Kernel/src/MaterialXMLParser.cpp index bebaafd01d5eccdf9ab78a6f83d14b82f3483e56..0fd55f5a2b94cbf4e31788f99e13438c02b32689 100644 --- a/Framework/Kernel/src/MaterialXMLParser.cpp +++ b/Framework/Kernel/src/MaterialXMLParser.cpp @@ -55,6 +55,7 @@ const char *COHSC_ATT = "cohscatterxsec"; const char *INCOHSC_ATT = "incohscatterxsec"; const char *ABSORB_ATT = "absorptionxsec"; const char *ATTENPROF_ATT = "attenuationprofile"; +const char *ATTENPROFX_ATT = "xrayattenuationprofile"; // Base type to put in a hash struct BuilderHandle { @@ -119,6 +120,8 @@ const BuilderHandle &findHandle(const std::string &name) { insertHandle(&handles, ABSORB_ATT, &MaterialBuilder::setAbsorptionXSection); insertHandle(&handles, ATTENPROF_ATT, &MaterialBuilder::setAttenuationProfileFilename); + insertHandle(&handles, ATTENPROFX_ATT, + &MaterialBuilder::setXRayAttenuationProfileFilename); } auto iter = handles.find(name); if (iter != handles.end()) diff --git a/Framework/Kernel/src/NeutronAtom.cpp b/Framework/Kernel/src/NeutronAtom.cpp index 94531c818aaa83c7a40bd6c72f4f1e1c5d08e892..12ea4a2c225efe40e7772280170dfbb243c7ce14 100644 --- a/Framework/Kernel/src/NeutronAtom.cpp +++ b/Framework/Kernel/src/NeutronAtom.cpp @@ -93,7 +93,7 @@ NeutronAtom::NeutronAtom(const uint16_t z, const uint16_t a, * @param coh_xs :: The coherent neutron cross-section * @param inc_xs :: The incoherent neutron cross-section * @param tot_xs :: The total neutron cross-section - * @param abs_xs :: The absolute neutron cross-section + * @param abs_xs :: The absorption neutron cross-section */ NeutronAtom::NeutronAtom(const uint16_t z, const uint16_t a, const double coh_b_real, const double coh_b_img, diff --git a/Framework/Kernel/src/ParaViewVersion.cpp.in b/Framework/Kernel/src/ParaViewVersion.cpp.in deleted file mode 100644 index c6cd7150b66bd96132f780f179daa52fe90af5df..0000000000000000000000000000000000000000 --- a/Framework/Kernel/src/ParaViewVersion.cpp.in +++ /dev/null @@ -1,23 +0,0 @@ -// Mantid Repository : https://github.com/mantidproject/mantid -// -// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, -// NScD Oak Ridge National Laboratory, European Spallation Source, -// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS -// SPDX - License - Identifier: GPL - 3.0 + -//---------------------------------------------------------------------- -// Includes -//---------------------------------------------------------------------- -#include "MantidKernel/ParaViewVersion.h" - -namespace Mantid -{ -namespace Kernel -{ - -std::string ParaViewVersion::targetVersion() -{ - return "@COMPATIBLE_PARAVIEW_VERSION@"; -} - -} // namespace Kernel -} // namespace Mantid diff --git a/Framework/Kernel/src/UsageService.cpp b/Framework/Kernel/src/UsageService.cpp index 04e6447274301501be555ab312416de00fbc2821..5eabbeb5905c2047f64225888e4dfe41c32ae65e 100644 --- a/Framework/Kernel/src/UsageService.cpp +++ b/Framework/Kernel/src/UsageService.cpp @@ -12,7 +12,6 @@ #include "MantidKernel/InternetHelper.h" #include "MantidKernel/Logger.h" #include "MantidKernel/MantidVersion.h" -#include "MantidKernel/ParaViewVersion.h" #include #include @@ -274,12 +273,8 @@ std::string UsageServiceImpl::generateStartupMessage() { message["osVersion"] = ConfigService::Instance().getOSVersion(); message["osReadable"] = ConfigService::Instance().getOSVersionReadable(); -#if defined(MAKE_VATES) - // paraview - message["ParaView"] = Kernel::ParaViewVersion::targetVersion(); -#else + // legacy interface requires paraview version message["ParaView"] = 0; -#endif // mantid version and sha1 message["mantidVersion"] = MantidVersion::version(); diff --git a/Framework/Kernel/src/UserStringParser.cpp b/Framework/Kernel/src/UserStringParser.cpp index 66fb0a3e38e743b6b14b9cb2f6b2c6937793153f..c965c7352c2a9bd37946b05a301e1ba85921460a 100644 --- a/Framework/Kernel/src/UserStringParser.cpp +++ b/Framework/Kernel/src/UserStringParser.cpp @@ -52,7 +52,7 @@ void UserStringParser::parse(const std::string &userString, std::string separators("-+:"); // if input contains no separator string if (userString.find_first_of(separators) == std::string::npos) { - numbers.emplace_back(std::vector(1, toUInt(userString))); + numbers.emplace_back(1, toUInt(userString)); } else if (Contains(userString, '-')) { std::vector value = separateDelimiters(userString, "-:"); if (!value.empty()) { @@ -107,7 +107,7 @@ UserStringParser::separateColon(const std::string &input) { std::vector> separatedValues; Tokenize(input, ":", startNum, endNum, step); for (unsigned int num = startNum; num <= endNum; num += step) { - separatedValues.emplace_back(std::vector(1, num)); + separatedValues.emplace_back(1, num); } return separatedValues; diff --git a/Framework/Kernel/test/DataServiceTest.h b/Framework/Kernel/test/DataServiceTest.h index af57b7a8d48ada0e19e28844c8bc8eb91b86fcc8..651a1b3b82c118d63b33a8c624ccf7af379c4c0b 100644 --- a/Framework/Kernel/test/DataServiceTest.h +++ b/Framework/Kernel/test/DataServiceTest.h @@ -334,6 +334,15 @@ public: auto cit = std::find(names.cbegin(), names.cend(), "__Three"); TS_ASSERT_DIFFERS(cit, names.cend()); TS_ASSERT_EQUALS(objects.at(std::distance(names.cbegin(), cit)), three); + + names = svc.getObjectNames(DataServiceSort::Unsorted, + DataServiceHidden::Auto, "T"); + TS_ASSERT_EQUALS(std::find(names.cbegin(), names.cend(), "One"), + names.end()); + TS_ASSERT_DIFFERS(std::find(names.cbegin(), names.cend(), "Two"), + names.end()); + TS_ASSERT_DIFFERS(std::find(names.cbegin(), names.cend(), "TwoAgain"), + names.end()); } void test_getObjectsReturnsConfigOption() { diff --git a/Framework/Kernel/test/FileValidatorTest.h b/Framework/Kernel/test/FileValidatorTest.h index dcae0c66c8e325774a97e1d9619f45c5dea5143e..8be78e5ea4e3b91ca71adab128a42510f43cf61e 100644 --- a/Framework/Kernel/test/FileValidatorTest.h +++ b/Framework/Kernel/test/FileValidatorTest.h @@ -11,6 +11,10 @@ #include "MantidKernel/FileValidator.h" #include +#if defined(__GNUC__) || defined(__clang__) +#include "boost/filesystem.hpp" +#endif + using namespace Mantid::Kernel; class FileValidatorTest : public CxxTest::TestSuite { @@ -98,6 +102,22 @@ public: FileValidator v(vec, false); TS_ASSERT_EQUALS(v.isValid(NoFile), ""); } + void testFailsIfNoPermissions() { +#if defined(__GNUC__) || defined(__clang__) + const char *filename = "testfile.txt"; + Poco::File txt_file(filename); + txt_file.createFile(); + boost::filesystem::permissions(filename, + boost::filesystem::perms::owner_read | + boost::filesystem::remove_perms); + std::vector vec; + FileValidator v(vec); + + TS_ASSERT_EQUALS(v.isValid(txt_file.path()), + "Failed to open testfile.txt: Permission denied"); + txt_file.remove(); +#endif + } void testFailsOnEmptyFileString() { FileValidator file_val; diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h index 9ac0441018e080b3ab209b5ee782315045ff0b9f..1b02f6bd5a529523a8e5c096d53b9891fcac8026 100644 --- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h +++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/IntegratePeaksMD2.h @@ -52,6 +52,8 @@ private: /// Run the algorithm void exec() override; + std::map validateInputs() override; + template void integrate(typename DataObjects::MDEventWorkspace::sptr ws); diff --git a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/PrecompiledHeader.h b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/PrecompiledHeader.h index af1d696e079767f3cb805aa614f4413a10646a8a..7a08642e7aade86ea8fb659c5ae6c6d1421825a2 100644 --- a/Framework/MDAlgorithms/inc/MantidMDAlgorithms/PrecompiledHeader.h +++ b/Framework/MDAlgorithms/inc/MantidMDAlgorithms/PrecompiledHeader.h @@ -7,4 +7,6 @@ #pragma once #include "MantidAPI/Algorithm.h" +#include "MantidDataObjects/EventWorkspace.h" +#include "MantidDataObjects/MDEventWorkspace.h" #include "MantidKernel/Exception.h" diff --git a/Framework/MDAlgorithms/src/ConvertHFIRSCDtoMDE.cpp b/Framework/MDAlgorithms/src/ConvertHFIRSCDtoMDE.cpp index 80252d3c55f50f0251773e06d589f7888b3c7364..30020990ba18279df8d21e50c9b35678d1e5fd81 100644 --- a/Framework/MDAlgorithms/src/ConvertHFIRSCDtoMDE.cpp +++ b/Framework/MDAlgorithms/src/ConvertHFIRSCDtoMDE.cpp @@ -197,8 +197,9 @@ void ConvertHFIRSCDtoMDE::exec() { } } } else { // HB2C - s1 = (*(dynamic_cast> *>( - expInfo.getLog("s1"))))(); + auto s1Log = dynamic_cast *>( + expInfo.run().getLogData("s1")); + s1 = s1Log->valuesAsVector(); azimuthal = (*(dynamic_cast> *>( expInfo.getLog("azimuthal"))))(); @@ -274,7 +275,7 @@ void ConvertHFIRSCDtoMDE::exec() { for (size_t m = 0; m < azimuthal.size(); m++) { size_t idx = n * azimuthal.size() + m; coord_t signal = static_cast(inputWS->getSignalAt(idx)); - if (signal > 0.f) { + if (signal > 0.f && std::isfinite(signal)) { Eigen::Vector3f q_sample = goniometer * q_lab_pre[m]; inserter.insertMDEvent(signal, signal, 0, 0, q_sample.data()); } diff --git a/Framework/MDAlgorithms/src/FindPeaksMD.cpp b/Framework/MDAlgorithms/src/FindPeaksMD.cpp index 264eab4b003a9d3e7ddcc7e3b72d9bd4ff917b4f..a6023e3c30bef318afda84816b69db801dd6234e 100644 --- a/Framework/MDAlgorithms/src/FindPeaksMD.cpp +++ b/Framework/MDAlgorithms/src/FindPeaksMD.cpp @@ -762,9 +762,9 @@ void FindPeaksMD::exec() { // Do a sort by bank name and then descending bin count (intensity) std::vector> criteria; - criteria.emplace_back(std::pair("RunNumber", true)); - criteria.emplace_back(std::pair("BankName", true)); - criteria.emplace_back(std::pair("bincount", false)); + criteria.emplace_back("RunNumber", true); + criteria.emplace_back("BankName", true); + criteria.emplace_back("bincount", false); peakWS->sort(criteria); for (auto i = 0; i != peakWS->getNumberPeaks(); ++i) { diff --git a/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp b/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp index 12c86c212c11c9987cc9ddd1f9859cbfb1dacc45..043ec3271e4e6ed7c4fdb42f8cfea88856cf20ed 100644 --- a/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp +++ b/Framework/MDAlgorithms/src/IntegratePeaksMD2.cpp @@ -28,6 +28,8 @@ #include "MantidDataObjects/Workspace2D.h" #include "MantidGeometry/Instrument/DetectorInfo.h" #include "MantidHistogramData/LinearGenerator.h" +#include "MantidKernel/ArrayBoundedValidator.h" +#include "MantidKernel/ArrayProperty.h" #include "MantidKernel/ListValidator.h" #include "MantidKernel/System.h" #include "MantidKernel/Utils.h" @@ -58,23 +60,33 @@ void IntegratePeaksMD2::init() { "InputWorkspace", "", Direction::Input), "An input MDEventWorkspace."); + auto radiiValidator = std::make_shared>(); + radiiValidator->setLower(0.0); + radiiValidator->setLowerExclusive(true); declareProperty( - std::make_unique>("PeakRadius", 1.0, - Direction::Input), - "Fixed radius around each peak position in which to integrate (in the " - "same units as the workspace)."); - + std::make_unique>("PeakRadius", + std::vector({1.0}), + radiiValidator, Direction::Input), + "Fixed radius around each peak position in which to integrate, or the " + "semi-axis lengths (a,b,c) describing an ellipsoid shape used for " + "integration (in the same units as the workspace)."); + + radiiValidator->setLowerExclusive(false); declareProperty( - std::make_unique>("BackgroundInnerRadius", 0.0, - Direction::Input), - "Inner radius to use to evaluate the background of the peak.\n" + std::make_unique>("BackgroundInnerRadius", + std::vector({0.0}), + radiiValidator, Direction::Input), + "Inner radius, or three values for semi-axis lengths (a,b,c) of the " + "ellipsoid shape, used to evaluate the background of the peak.\n" "If smaller than PeakRadius, then we assume BackgroundInnerRadius = " "PeakRadius."); declareProperty( - std::make_unique>("BackgroundOuterRadius", 0.0, - Direction::Input), - "Outer radius to use to evaluate the background of the peak.\n" + std::make_unique>("BackgroundOuterRadius", + std::vector({0.0}), + radiiValidator, Direction::Input), + "Outer radius, or three values for semi-axis lengths (a,b,c) of the " + "ellipsoid shape, to use to evaluate the background of the peak.\n" "The signal density around the peak (BackgroundInnerRadius < r < " "BackgroundOuterRadius) is used to estimate the background under the " "peak.\n" @@ -162,6 +174,55 @@ void IntegratePeaksMD2::init() { "before the background subtraction."); } +std::map IntegratePeaksMD2::validateInputs() { + std::map result; + + std::vector PeakRadius = getProperty("PeakRadius"); + std::vector BackgroundInnerRadius = + getProperty("BackgroundInnerRadius"); + std::vector BackgroundOuterRadius = + getProperty("BackgroundOuterRadius"); + bool ellipsoid = getProperty("Ellipsoid"); + + if (PeakRadius.size() != 1 && PeakRadius.size() != 3) { + std::stringstream errmsg; + errmsg << "Only one or three values should be specified"; + result["PeakRadius"] = errmsg.str(); + } + + if (!ellipsoid && PeakRadius.size() != 1) { + std::stringstream errmsg; + errmsg << "One value must be specified when Ellipsoid is false"; + result["PeakRadius"] = errmsg.str(); + } + + if (BackgroundInnerRadius.size() != 1 && BackgroundInnerRadius.size() != 3) { + std::stringstream errmsg; + errmsg << "Only one or three values should be specified"; + result["BackgroundInnerRadius"] = errmsg.str(); + } + + if (!ellipsoid && BackgroundInnerRadius.size() != 1) { + std::stringstream errmsg; + errmsg << "One value must be specified when Ellipsoid is false"; + result["BackgroundInnerRadius"] = errmsg.str(); + } + + if (BackgroundOuterRadius.size() != 1 && BackgroundOuterRadius.size() != 3) { + std::stringstream errmsg; + errmsg << "Only one or three values should be specified"; + result["BackgroundOuterRadius"] = errmsg.str(); + } + + if (!ellipsoid && BackgroundOuterRadius.size() != 1) { + std::stringstream errmsg; + errmsg << "One value must be specified when Ellipsoid is false"; + result["BackgroundOuterRadius"] = errmsg.str(); + } + + return result; +} + //---------------------------------------------------------------------------------------------- /** Integrate the peaks of the workspace using parameters saved in the algorithm * class @@ -196,17 +257,35 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { ws->getSpecialCoordinateSystem(); /// Radius to use around peaks - double PeakRadius = getProperty("PeakRadius"); + std::vector PeakRadius = getProperty("PeakRadius"); /// Background (end) radius - double BackgroundOuterRadius = getProperty("BackgroundOuterRadius"); + std::vector BackgroundOuterRadius = + getProperty("BackgroundOuterRadius"); /// Start radius of the background - double BackgroundInnerRadius = getProperty("BackgroundInnerRadius"); + std::vector BackgroundInnerRadius = + getProperty("BackgroundInnerRadius"); /// One percent background correction bool useOnePercentBackgroundCorrection = getProperty("UseOnePercentBackgroundCorrection"); - if (BackgroundInnerRadius < PeakRadius) - BackgroundInnerRadius = PeakRadius; + bool manualEllip = false; + if (PeakRadius.size() > 1) { + manualEllip = true; + // make sure the background radii are 3 values (they default to 1) + if (BackgroundInnerRadius.size() == 1) + BackgroundInnerRadius.resize(3, BackgroundInnerRadius[0]); + if (BackgroundOuterRadius.size() == 1) + BackgroundOuterRadius.resize(3, BackgroundOuterRadius[0]); + } + + double minInnerRadius = PeakRadius[0]; + for (size_t r = 0; r < BackgroundInnerRadius.size(); r++) { + if (manualEllip) { + minInnerRadius = PeakRadius[r]; + } + if (BackgroundInnerRadius[r] < minInnerRadius) + BackgroundInnerRadius[r] = minInnerRadius; + } // Ellipsoid bool isEllipse = getProperty("Ellipsoid"); bool qAxisIsFixed = getProperty("FixQAxis"); @@ -220,11 +299,11 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { double adaptiveQBackgroundMultiplier = 0.0; if (adaptiveQBackground) adaptiveQBackgroundMultiplier = adaptiveQMultiplier; - std::vector PeakRadiusVector(peakWS->getNumberPeaks(), PeakRadius); + std::vector PeakRadiusVector(peakWS->getNumberPeaks(), PeakRadius[0]); std::vector BackgroundInnerRadiusVector(peakWS->getNumberPeaks(), - BackgroundInnerRadius); + BackgroundInnerRadius[0]); std::vector BackgroundOuterRadiusVector(peakWS->getNumberPeaks(), - BackgroundOuterRadius); + BackgroundOuterRadius[0]); if (cylinderBool) { numSteps = 100; size_t histogramNumber = peakWS->getNumberPeaks(); @@ -290,11 +369,11 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { out.open(outFile.c_str(), std::ofstream::out); } // volume of Background sphere with inner volume subtracted - double volumeBkg = - 4.0 / 3.0 * M_PI * - (std::pow(BackgroundOuterRadius, 3) - std::pow(BackgroundOuterRadius, 3)); + double volumeBkg = 4.0 / 3.0 * M_PI * + (std::pow(BackgroundOuterRadius[0], 3) - + std::pow(BackgroundOuterRadius[0], 3)); // volume of PeakRadius sphere - double volumeRadius = 4.0 / 3.0 * M_PI * std::pow(PeakRadius, 3); + double volumeRadius = 4.0 / 3.0 * M_PI * std::pow(PeakRadius[0], 3); // // If the following OMP pragma is included, this algorithm seg faults // sporadically when processing multiple TOPAZ runs in a script, on @@ -328,8 +407,8 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { // Do not integrate if sphere is off edge of detector double edge = detectorQ(p.getQLabFrame(), - std::max(BackgroundOuterRadius, PeakRadius)); - if (edge < std::max(BackgroundOuterRadius, PeakRadius)) { + std::max(BackgroundOuterRadius[0], PeakRadius[0])); + if (edge < std::max(BackgroundOuterRadius[0], PeakRadius[0])) { g_log.warning() << "Warning: sphere/cylinder for integration is off edge " "of detector for peak " << i << "; radius of edge = " << edge << '\n'; @@ -364,7 +443,9 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { } lenQpeak = std::sqrt(lenQpeak); } - double adaptiveRadius = adaptiveQMultiplier * lenQpeak + PeakRadius; + double adaptiveRadius = + adaptiveQMultiplier * lenQpeak + + *std::max_element(PeakRadius.begin(), PeakRadius.end()); if (adaptiveRadius <= 0.0) { g_log.error() << "Error: Radius for integration sphere of peak " << i << " is negative = " << adaptiveRadius << '\n'; @@ -378,9 +459,13 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { } PeakRadiusVector[i] = adaptiveRadius; BackgroundInnerRadiusVector[i] = - adaptiveQBackgroundMultiplier * lenQpeak + BackgroundInnerRadius; + adaptiveQBackgroundMultiplier * lenQpeak + + *std::max_element(BackgroundInnerRadius.begin(), + BackgroundInnerRadius.end()); BackgroundOuterRadiusVector[i] = - adaptiveQBackgroundMultiplier * lenQpeak + BackgroundOuterRadius; + adaptiveQBackgroundMultiplier * lenQpeak + + *std::max_element(BackgroundOuterRadius.begin(), + BackgroundOuterRadius.end()); // define the radius squared for a sphere intially CoordTransformDistance getRadiusSq(nd, center, dimensionsUsed); // set spherical shape @@ -395,7 +480,7 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { (pow(BackgroundOuterRadiusVector[i], 3) - pow(BackgroundInnerRadiusVector[i], 3)); // Integrate spherical background shell if specified - if (BackgroundOuterRadius > PeakRadius) { + if (BackgroundOuterRadius[0] > PeakRadius[0]) { // Get the total signal inside background shell ws->getBox()->integrateSphere( getRadiusSq, @@ -415,49 +500,152 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { bgSignal / (4 * M_PI * pow(PeakRadiusVector[i], 3) / 3); std::vector eigenvects; std::vector eigenvals; - findEllipsoid( - ws, getRadiusSq, pos, - static_cast(pow(PeakRadiusVector[i], 2)), qAxisIsFixed, - bgDensity, eigenvects, eigenvals); + if (PeakRadius.size() == 1) { + findEllipsoid( + ws, getRadiusSq, pos, + static_cast(pow(PeakRadiusVector[i], 2)), qAxisIsFixed, + bgDensity, eigenvects, eigenvals); + } else { + // Use the manually specified radii instead of finding them via + // findEllipsoid + std::transform(PeakRadius.begin(), PeakRadius.end(), + std::back_inserter(eigenvals), + [](double &r) { return std::pow(r, 2.0); }); + eigenvects.push_back(V3D(1.0, 0.0, 0.0)); + eigenvects.push_back(V3D(0.0, 1.0, 0.0)); + eigenvects.push_back(V3D(0.0, 0.0, 1.0)); + } // transform ellispoid onto sphere of radius = R getRadiusSq = CoordTransformDistance(nd, center, dimensionsUsed, 1, /* outD */ eigenvects, eigenvals); // Integrate ellipsoid background shell if specified - if (BackgroundOuterRadius > PeakRadius) { - // Get the total signal inside "BackgroundOuterRadius" - bgSignal = 0; - bgErrorSquared = 0; - ws->getBox()->integrateSphere( - getRadiusSq, - static_cast(pow(BackgroundOuterRadiusVector[i], 2)), - bgSignal, bgErrorSquared, - static_cast(pow(BackgroundInnerRadiusVector[i], 2)), - useOnePercentBackgroundCorrection); - // correct bg signal by Vpeak/Vshell (same as previously - // calculated for sphere) - bgSignal *= scaleFactor; - bgErrorSquared *= scaleFactor * scaleFactor; - } - // set peak shape - if (auto *shapeablePeak = dynamic_cast(&p)) { - // get radii in same proprtion as eigenvalues - auto max_stdev = - pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5); - std::vector peakRadii(3, 0.0); - std::vector backgroundInnerRadii(3, 0.0); - std::vector backgroundOuterRadii(3, 0.0); - for (size_t irad = 0; irad < peakRadii.size(); irad++) { - auto scale = pow(eigenvals[irad], 0.5) / max_stdev; - peakRadii[irad] = PeakRadiusVector[i] * scale; - backgroundInnerRadii[irad] = BackgroundInnerRadiusVector[i] * scale; - backgroundOuterRadii[irad] = BackgroundOuterRadiusVector[i] * scale; + if (PeakRadius.size() == 1) { + if (BackgroundOuterRadius[0] > PeakRadius[0]) { + // Get the total signal inside "BackgroundOuterRadius" + bgSignal = 0; + bgErrorSquared = 0; + ws->getBox()->integrateSphere( + getRadiusSq, + static_cast(pow(BackgroundOuterRadiusVector[i], 2)), + bgSignal, bgErrorSquared, + static_cast(pow(BackgroundInnerRadiusVector[i], 2)), + useOnePercentBackgroundCorrection); + // correct bg signal by Vpeak/Vshell (same as previously + // calculated for sphere) + bgSignal *= scaleFactor; + bgErrorSquared *= scaleFactor * scaleFactor; + } + // set peak shape + if (auto *shapeablePeak = dynamic_cast(&p)) { + // get radii in same proprtion as eigenvalues + auto max_stdev = + pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5); + std::vector peakRadii(3, 0.0); + std::vector backgroundInnerRadii(3, 0.0); + std::vector backgroundOuterRadii(3, 0.0); + for (size_t irad = 0; irad < peakRadii.size(); irad++) { + auto scale = pow(eigenvals[irad], 0.5) / max_stdev; + peakRadii[irad] = PeakRadiusVector[i] * scale; + backgroundInnerRadii[irad] = + BackgroundInnerRadiusVector[i] * scale; + backgroundOuterRadii[irad] = + BackgroundOuterRadiusVector[i] * scale; + } + PeakShape *ellipsoidShape = new PeakShapeEllipsoid( + eigenvects, peakRadii, backgroundInnerRadii, + backgroundOuterRadii, CoordinatesToUse, this->name(), + this->version()); + shapeablePeak->setPeakShape(ellipsoidShape); + } + } else { + // Use the manually specified radii instead of finding them via + // findEllipsoid + std::vector eigenvals_background_inner; + std::vector eigenvals_background_outer; + std::transform(BackgroundInnerRadius.begin(), + BackgroundInnerRadius.end(), + std::back_inserter(eigenvals_background_inner), + [](double &r) { return std::pow(r, 2.0); }); + std::transform(BackgroundOuterRadius.begin(), + BackgroundOuterRadius.end(), + std::back_inserter(eigenvals_background_outer), + [](double &r) { return std::pow(r, 2.0); }); + + if (BackgroundOuterRadiusVector[0] > PeakRadiusVector[0]) { + // transform ellispoid onto sphere of radius = R + auto getRadiusSqInner = + CoordTransformDistance(nd, center, dimensionsUsed, 1, /* outD */ + eigenvects, eigenvals_background_inner); + auto getRadiusSqOuter = + CoordTransformDistance(nd, center, dimensionsUsed, 1, /* outD */ + eigenvects, eigenvals_background_outer); + // Get the total signal inside "BackgroundOuterRadius" + bgSignal = 0; + bgErrorSquared = 0; + signal_t bgSignalInner = 0; + signal_t bgSignalOuter = 0; + signal_t bgErrorSquaredInner = 0; + signal_t bgErrorSquaredOuter = 0; + ws->getBox()->integrateSphere( + getRadiusSqInner, + static_cast(pow(BackgroundInnerRadiusVector[i], 2)), + bgSignalInner, bgErrorSquaredInner, 0.0, + useOnePercentBackgroundCorrection); + ws->getBox()->integrateSphere( + getRadiusSqOuter, + static_cast(pow(BackgroundOuterRadiusVector[i], 2)), + bgSignalOuter, bgErrorSquaredOuter, 0.0, + useOnePercentBackgroundCorrection); + // correct bg signal by Vpeak/Vshell (same as previously + // calculated for sphere) + bgSignal = bgSignalOuter - bgSignalInner; + bgErrorSquared = bgErrorSquaredInner + bgErrorSquaredOuter; + g_log.debug() + << "unscaled background signal from ellipsoid integration = " + << bgSignal << '\n'; + const double scaleFactor = + (PeakRadius[0] * PeakRadius[1] * PeakRadius[2]) / + (BackgroundOuterRadius[0] * BackgroundOuterRadius[1] * + BackgroundOuterRadius[2] - + BackgroundInnerRadius[0] * BackgroundInnerRadius[1] * + BackgroundInnerRadius[2]); + bgSignal *= scaleFactor; + bgErrorSquared *= scaleFactor * scaleFactor; + } + // set peak shape + if (auto *shapeablePeak = dynamic_cast(&p)) { + // get radii in same proprtion as eigenvalues + auto max_stdev = + pow(*std::max_element(eigenvals.begin(), eigenvals.end()), 0.5); + auto max_stdev_inner = + pow(*std::max_element(eigenvals_background_inner.begin(), + eigenvals_background_inner.end()), + 0.5); + auto max_stdev_outer = + pow(*std::max_element(eigenvals_background_outer.begin(), + eigenvals_background_outer.end()), + 0.5); + std::vector peakRadii(3, 0.0); + std::vector backgroundInnerRadii(3, 0.0); + std::vector backgroundOuterRadii(3, 0.0); + for (size_t irad = 0; irad < peakRadii.size(); irad++) { + peakRadii[irad] = + PeakRadiusVector[i] * pow(eigenvals[irad], 0.5) / max_stdev; + backgroundInnerRadii[irad] = + BackgroundInnerRadiusVector[i] * + pow(eigenvals_background_inner[irad], 0.5) / max_stdev_inner; + backgroundOuterRadii[irad] = + BackgroundOuterRadiusVector[i] * + pow(eigenvals_background_outer[irad], 0.5) / max_stdev_outer; + } + PeakShape *ellipsoidShape = new PeakShapeEllipsoid( + eigenvects, peakRadii, backgroundInnerRadii, + backgroundOuterRadii, CoordinatesToUse, this->name(), + this->version()); + shapeablePeak->setPeakShape(ellipsoidShape); } - PeakShape *ellipsoidShape = new PeakShapeEllipsoid( - eigenvects, peakRadii, backgroundInnerRadii, backgroundOuterRadii, - CoordinatesToUse, this->name(), this->version()); - shapeablePeak->setPeakShape(ellipsoidShape); } } // spherical integration of signal @@ -473,17 +661,17 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { signal_fit = 0; ws->getBox()->integrateCylinder( - cylinder, static_cast(PeakRadius), + cylinder, static_cast(PeakRadius[0]), static_cast(cylinderLength), signal, errorSquared, signal_fit.mutableRawData()); // Integrate around the background radius - if (BackgroundOuterRadius > PeakRadius) { + if (BackgroundOuterRadius[0] > PeakRadius[0]) { // Get the total signal inside "BackgroundOuterRadius" signal_fit = 0; ws->getBox()->integrateCylinder( - cylinder, static_cast(BackgroundOuterRadius), + cylinder, static_cast(BackgroundOuterRadius[0]), static_cast(cylinderLength), bgSignal, bgErrorSquared, signal_fit.mutableRawData()); @@ -495,9 +683,9 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { signal_t interiorErrorSquared = 0; // Integrate this 3rd radius, if needed - if (BackgroundInnerRadius != PeakRadius) { + if (BackgroundInnerRadius[0] != PeakRadius[0]) { ws->getBox()->integrateCylinder( - cylinder, static_cast(BackgroundInnerRadius), + cylinder, static_cast(BackgroundInnerRadius[0]), static_cast(cylinderLength), interiorSignal, interiorErrorSquared, signal_fit.mutableRawData()); } else { @@ -514,13 +702,13 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { // shell. bgErrorSquared -= interiorErrorSquared; // Relative volume of peak vs the BackgroundOuterRadius cylinder - const double radiusRatio = (PeakRadius / BackgroundOuterRadius); + const double radiusRatio = (PeakRadius[0] / BackgroundOuterRadius[0]); const double peakVolume = radiusRatio * radiusRatio * cylinderLength; // Relative volume of the interior of the shell vs overall // background const double interiorRatio = - (BackgroundInnerRadius / BackgroundOuterRadius); + (BackgroundInnerRadius[0] / BackgroundOuterRadius[0]); // Volume of the bg shell, relative to the volume of the // BackgroundOuterRadius cylinder const double bgVolume = @@ -650,20 +838,20 @@ void IntegratePeaksMD2::integrate(typename MDEventWorkspace::sptr ws) { double edgeMultiplier = 1.0; double peakMultiplier = 1.0; if (correctEdge) { - if (edge < BackgroundOuterRadius) { - double e1 = BackgroundOuterRadius - edge; + if (edge < BackgroundOuterRadius[0]) { + double e1 = BackgroundOuterRadius[0] - edge; // volume of cap of sphere with h = edge double f1 = - M_PI * std::pow(e1, 2) / 3 * (3 * BackgroundOuterRadius - e1); + M_PI * std::pow(e1, 2) / 3 * (3 * BackgroundOuterRadius[0] - e1); edgeMultiplier = volumeBkg / (volumeBkg - f1); } - if (edge < PeakRadius) { - double sigma = PeakRadius / 3.0; + if (edge < PeakRadius[0]) { + double sigma = PeakRadius[0] / 3.0; // assume gaussian peak - double e1 = - std::exp(-std::pow(edge, 2) / (2 * sigma * sigma)) * PeakRadius; + double e1 = std::exp(-std::pow(edge, 2) / (2 * sigma * sigma)) * + PeakRadius[0]; // volume of cap of sphere with h = edge - double f1 = M_PI * std::pow(e1, 2) / 3 * (3 * PeakRadius - e1); + double f1 = M_PI * std::pow(e1, 2) / 3 * (3 * PeakRadius[0] - e1); peakMultiplier = volumeRadius / (volumeRadius - f1); } } diff --git a/Framework/MDAlgorithms/src/MDNorm.cpp b/Framework/MDAlgorithms/src/MDNorm.cpp index cbe63646a211daa2ebfb8a4b5d9654cd7d238ed3..675fe8ab64885fa814cf0ca29c249db2ec61cb32 100644 --- a/Framework/MDAlgorithms/src/MDNorm.cpp +++ b/Framework/MDAlgorithms/src/MDNorm.cpp @@ -1008,7 +1008,8 @@ DataObjects::MDHistoWorkspace_sptr MDNorm::binInputWS( m_dEIntegrated = false; } if (!basisVector.str().empty()) { - for (auto const &proji : projection) { + for (auto proji : projection) { + proji = std::abs(proji) > 1e-10 ? proji : 0.0; basisVector << "," << proji; } value = basisVector.str(); diff --git a/Framework/MDAlgorithms/src/SmoothMD.cpp b/Framework/MDAlgorithms/src/SmoothMD.cpp index a4581bb1749f73464e64701ee0f469f06658ac69..7ebd7a20ca5104215a6a81b522e7d147072bd9e9 100644 --- a/Framework/MDAlgorithms/src/SmoothMD.cpp +++ b/Framework/MDAlgorithms/src/SmoothMD.cpp @@ -57,7 +57,9 @@ namespace { * @return function map */ SmoothFunctionMap makeFunctionMap(Mantid::MDAlgorithms::SmoothMD *instance) { - using namespace std::placeholders; + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; return { {"Hat", std::bind(&Mantid::MDAlgorithms::SmoothMD::hatSmooth, instance, _1, _2, _3)}, diff --git a/Framework/MDAlgorithms/src/ThresholdMD.cpp b/Framework/MDAlgorithms/src/ThresholdMD.cpp index 1e4ef0bc23d1f43966b442c6bb3229a4dce8b0f8..6d0b6136c7fc09ad98d831f94bc20fe9064c8d1e 100644 --- a/Framework/MDAlgorithms/src/ThresholdMD.cpp +++ b/Framework/MDAlgorithms/src/ThresholdMD.cpp @@ -105,9 +105,10 @@ void ThresholdMD::exec() { using namespace std::placeholders; boost::function comparitor = - std::bind(std::less(), _1, referenceValue); + std::bind(std::less(), std::placeholders::_1, referenceValue); if (condition == GreaterThan()) { - comparitor = std::bind(std::greater(), _1, referenceValue); + comparitor = std::bind(std::greater(), std::placeholders::_1, + referenceValue); } Progress prog(this, 0.0, 1.0, 100); diff --git a/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h b/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h index 98bf77dda72913357299b80719681682091acdf2..8318b765f1b471cf48a364418182590a68992c84 100644 --- a/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h +++ b/Framework/MDAlgorithms/test/IntegratePeaksMD2Test.h @@ -52,22 +52,28 @@ public: //------------------------------------------------------------------------------- /** Run the IntegratePeaksMD2 with the given peak radius integration param */ - static void doRun(double PeakRadius, double BackgroundRadius, + static void doRun(std::vector PeakRadius, + std::vector BackgroundRadius, std::string OutputWorkspace = "IntegratePeaksMD2Test_peaks", - double BackgroundStartRadius = 0.0, bool edge = true, - bool cyl = false, std::string fnct = "NoFit", - double adaptive = 0.0, bool ellip = false, - bool fixQAxis = false) { + std::vector BackgroundStartRadius = {}, + bool edge = true, bool cyl = false, + std::string fnct = "NoFit", double adaptive = 0.0, + bool ellip = false, bool fixQAxis = false) { IntegratePeaksMD2 alg; TS_ASSERT_THROWS_NOTHING(alg.initialize()) TS_ASSERT(alg.isInitialized()) TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("InputWorkspace", "IntegratePeaksMD2Test_MDEWS")); TS_ASSERT_THROWS_NOTHING(alg.setProperty("PeakRadius", PeakRadius)); - TS_ASSERT_THROWS_NOTHING( - alg.setProperty("BackgroundOuterRadius", BackgroundRadius)); - TS_ASSERT_THROWS_NOTHING( - alg.setProperty("BackgroundInnerRadius", BackgroundStartRadius)); + if (!BackgroundRadius.empty()) { + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("BackgroundOuterRadius", BackgroundRadius)); + } + + if (!BackgroundStartRadius.empty()) { + TS_ASSERT_THROWS_NOTHING( + alg.setProperty("BackgroundInnerRadius", BackgroundStartRadius)); + } TS_ASSERT_THROWS_NOTHING(alg.setProperty("IntegrateIfOnEdge", edge)); TS_ASSERT_THROWS_NOTHING( alg.setPropertyValue("PeaksWorkspace", "IntegratePeaksMD2Test_peaks")); @@ -219,7 +225,7 @@ public: AnalysisDataService::Instance().add("IntegratePeaksMD2Test_peaks", peakWS0); // ------------- Integrating with cylinder ------------------------ - doRun(0.1, 0.0, "IntegratePeaksMD2Test_peaks", 0.0, true, true); + doRun({0.1}, {0.0}, "IntegratePeaksMD2Test_peaks", {0.0}, true, true); TS_ASSERT_DELTA(peakWS0->getPeak(0).getIntensity(), 2.0, 1e-2); @@ -228,7 +234,7 @@ public: // Test profile Gaussian std::string fnct = "Gaussian"; - doRun(0.1, 0.0, "IntegratePeaksMD2Test_peaks", 0.0, true, true, fnct); + doRun({0.1}, {0.0}, "IntegratePeaksMD2Test_peaks", {0.0}, true, true, fnct); // More accurate integration changed values TS_ASSERT_DELTA(peakWS0->getPeak(0).getIntensity(), 2.0, 1e-2); // Error is also calculated @@ -240,7 +246,7 @@ public: // Test profile back to back exponential fnct = "BackToBackExponential"; - doRun(0.1, 0.0, "IntegratePeaksMD2Test_peaks", 0.0, true, true, fnct); + doRun({0.1}, {0.0}, "IntegratePeaksMD2Test_peaks", {0.0}, true, true, fnct); // TS_ASSERT_DELTA( peakWS0->getPeak(0).getIntensity(), 2.0, 0.2); // Error is also calculated @@ -262,8 +268,8 @@ public: // ------------- Adaptive Integration r=MQ+b where b is PeakRadius and m is // 0.01 ------------------------ peakWS0->addPeak(Peak(inst, 15050, 1.0, V3D(2., 3., 4.))); - doRun(0.1, 0.0, "IntegratePeaksMD2Test_peaks", 0.0, true, false, "NoFit", - 0.01); + doRun({0.1}, {0.0}, "IntegratePeaksMD2Test_peaks", {0.0}, true, false, + "NoFit", 0.01); TS_ASSERT_DELTA(peakWS0->getPeak(1).getIntensity(), 29.0, 1e-2); // Error is also calculated @@ -271,7 +277,7 @@ public: // ------------- Integrate with 0.1 radius but IntegrateIfOnEdge // false------------------------ - doRun(0.1, 0.0, "IntegratePeaksMD2Test_peaks", 0.0, false); + doRun({0.1}, {0.0}, "IntegratePeaksMD2Test_peaks", {0.0}, false); TS_ASSERT_DELTA(peakWS0->getPeak(0).getIntensity(), 2.0, 1e-2); @@ -290,7 +296,7 @@ public: AnalysisDataService::Instance().add("IntegratePeaksMD2Test_peaks", peakWS); // ------------- Integrate with 1.0 radius ------------------------ - doRun(1.0, 0.0); + doRun({1.0}, {0.0}); TS_ASSERT_DELTA(peakWS->getPeak(0).getIntensity(), 1000.0, 1e-2); TS_ASSERT_DELTA(peakWS->getPeak(1).getIntensity(), 1000.0, 1e-2); @@ -304,7 +310,7 @@ public: sqrt(peakWS->getPeak(2).getIntensity()), 1e-2); // ------------- Let's do it again with 2.0 radius ------------------------ - doRun(2.0, 0.0); + doRun({2.0}, {0.0}); // All peaks are fully contained TS_ASSERT_DELTA(peakWS->getPeak(0).getIntensity(), 1000.0, 1e-2); @@ -312,7 +318,7 @@ public: TS_ASSERT_DELTA(peakWS->getPeak(2).getIntensity(), 1000.0, 1e-2); // ------------- Let's do it again with 0.5 radius ------------------------ - doRun(0.5, 0.0); + doRun({0.5}, {0.0}); TS_ASSERT_DELTA(peakWS->getPeak(0).getIntensity(), 125.0, 10.0); TS_ASSERT_DELTA(peakWS->getPeak(1).getIntensity(), 1000.0, 1e-2); @@ -324,7 +330,7 @@ public: // ------------- Integrate with 1.0 radius and 2.0 // background------------------------ - doRun(1.0, 2.0); + doRun({1.0}, {2.0}); // Same 1000 since the background (~125) was subtracted, with some random // variation of the BG around // TS_ASSERT_DELTA( peakWS->getPeak(0).getIntensity(), 1000.0, 10.0); @@ -346,7 +352,7 @@ public: // ------------- Integrating without the background gives higher counts // ------------------------ - doRun(1.0, 0.0); + doRun({1.0}, {0.0}); // +125 counts due to background TS_ASSERT_DELTA(peakWS->getPeak(0).getIntensity(), 1125.0, 10.0); @@ -374,7 +380,7 @@ public: AnalysisDataService::Instance().add("IntegratePeaksMD2Test_peaks", peakWS); // Integrate and copy to a new peaks workspace - doRun(1.0, 0.0, "IntegratePeaksMD2Test_peaks_out"); + doRun({1.0}, {0.0}, "IntegratePeaksMD2Test_peaks_out"); // Old workspace is unchanged TS_ASSERT_EQUALS(peakWS->getPeak(0).getIntensity(), 0.0); @@ -412,7 +418,7 @@ public: peakWS); // First, a check with no background - doRun(1.0, 0.0, "IntegratePeaksMD2Test_peaks", 0.0); + doRun({1.0}, {0.0}, "IntegratePeaksMD2Test_peaks", {0.0}); // approx. + 500 + 333 counts due to 2 backgrounds TS_ASSERT_DELTA(peakWS->getPeak(0).getIntensity(), 1000 + 500 + 333, 30.0); TSM_ASSERT_DELTA("Simple sqrt() error", @@ -421,7 +427,7 @@ public: // Set background from 2.0 to 3.0. // So the 1/2 density background remains, we subtract the 1/3 density = // about 1500 counts - doRun(1.0, 3.0, "IntegratePeaksMD2Test_peaks", 2.0); + doRun({1.0}, {3.0}, "IntegratePeaksMD2Test_peaks", {2.0}); TS_ASSERT_DELTA(peakWS->getPeak(0).getIntensity(), 1000 + 500, 80.0); // Error is larger, since it is error of peak + error of background TSM_ASSERT_DELTA("Error has increased", @@ -429,7 +435,7 @@ public: // Now do the same without the background start radius // So we subtract both densities = a lower count - doRun(1.0, 3.0); + doRun({1.0}, {3.0}); TSM_ASSERT_LESS_THAN("Peak intensity is lower if you do not include the " "spacer shell (higher background)", peakWS->getPeak(0).getIntensity(), 1500); @@ -454,10 +460,239 @@ public: EllipsoidTestHelper(-1.0, false, true); } - void EllipsoidTestHelper(double doCounts, bool fixQAxis, bool doBkgrd) { + void test_exec_EllipsoidRadii_NoBackground_SingleCount() { + std::vector radii = {0.05, 0.03, 0.02}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + EllipsoidTestHelper(-1.0, false, false, {0.05, 0.03, 0.02}, radii); + } + + void test_exec_EllipsoidRadii_NoBackground_SingleCount_FixQAxis() { + std::vector radii = {0.05, 0.03, 0.02}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + EllipsoidTestHelper(-1.0, true, false, {0.05, 0.03, 0.02}, radii); + } + + void test_exec_EllipsoidRadii_NoBackground_SingleCount_Qy() { + std::vector radii = {0.03, 0.05, 0.02}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + EllipsoidTestHelper(-1.0, false, false, {0.03, 0.05, 0.02}, radii); + } + + void test_exec_EllipsoidRadii_NoBackground_SingleCount_Qz() { + std::vector radii = {0.03, 0.02, 0.05}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + EllipsoidTestHelper(-1.0, false, false, {0.03, 0.02, 0.05}, radii); + } + + void test_exec_EllipsoidRadii_NoBackground_NonSingleCount() { + std::vector radii = {0.05, 0.03, 0.02}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + EllipsoidTestHelper(1.0, false, false, {0.05, 0.03, 0.02}, radii); + } + + void test_exec_EllipsoidRadii_NoBackground_NonSingleCount_FixQAxis() { + std::vector radii = {0.05, 0.03, 0.02}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + EllipsoidTestHelper(1.0, true, false, {0.05, 0.03, 0.02}, radii); + } + + void test_exec_EllipsoidRadii_WithBackground_SingleCount() { + std::vector radii = {0.05, 0.03, 0.02}; + std::for_each(radii.begin(), radii.end(), + [](double &r) { r = 4.0 * sqrt(r); }); + std::vector outer; + std::transform(radii.begin(), radii.end(), std::back_inserter(outer), + [](double &r) { return r * pow(2.0, (1.0 / 3.0)); }); + EllipsoidTestHelper(-1.0, false, true, {0.05, 0.03, 0.02}, radii, radii, + outer); + } + + void test_exec_EllipsoidRadii_NoBackground_SingleCount_ScaledSphere() { + // Test if elipsoid of volume 0.5*V gives half the intensity as a sphere of + // volume V + size_t numEvents = 20000; + V3D pos(1.0, 0.0, 0.0); // peak position + double peakRad = 1.0; + + createMDEW(); + + Instrument_sptr inst = + ComponentCreationHelper::createTestInstrumentCylindrical(5); + PeaksWorkspace_sptr peakWS(new PeaksWorkspace()); + addPeak(numEvents, pos[0], pos[1], pos[2], peakRad); + peakWS->addPeak(Peak(inst, 1, 1.0, pos)); + AnalysisDataService::Instance().addOrReplace("IntegratePeaksMD2Test_peaks", + peakWS); + + doRun({1.0}, {0.0}, "IntegratePeaksMD2Test_peaks_out", {}, false); + + PeaksWorkspace_sptr peakResult = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve( + "IntegratePeaksMD2Test_peaks_out")); + TS_ASSERT(peakResult); + + double sphereInten = peakResult->getPeak(0).getIntensity(); + + // Semi axis lengths corresponding to a sphere of 0.5*V + std::vector radii(3, peakRad * pow(0.5, 1.0 / 3.0)); + + // Perform ellipsoidal integration of sphere + doRun(radii, {0.0}, "IntegratePeaksMD2Test_peaks_out", {0.0}, false, false, + "NoFit", 0.0, true, false); + + peakResult = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve( + "IntegratePeaksMD2Test_peaks_out")); + TS_ASSERT(peakResult); + + double ellipInten = peakResult->getPeak(0).getIntensity(); + + TS_ASSERT_DELTA(ellipInten, 0.5 * sphereInten, + ceil(0.002 * static_cast(sphereInten))); + } + + void test_exec_EllipsoidRadii_NoBackground_SingleCount_Vol() { + // Test an ellipsoid against theoretical vol + size_t numEvents = 2000000; + V3D pos(0.0, 0.0, 0.0); // peak position + + createMDEW(); + + Instrument_sptr inst = + ComponentCreationHelper::createTestInstrumentCylindrical(5); + PeaksWorkspace_sptr peakWS(new PeaksWorkspace()); + addUniform(numEvents, {std::make_pair(-0.5, 0.5), std::make_pair(-0.5, 0.5), + std::make_pair(-0.5, 0.5)}); + peakWS->addPeak(Peak(inst, 1, 1.0, pos)); + AnalysisDataService::Instance().addOrReplace("IntegratePeaksMD2Test_peaks", + peakWS); + + // Major axis along z + std::vector radii = {0.03, 0.04, 0.05}; + doRun(radii, {0.0}, "IntegratePeaksMD2Test_peaks_out", {0.0}, false, false, + "NoFit", 0.0, true, false); + + PeaksWorkspace_sptr peakResult = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve( + "IntegratePeaksMD2Test_peaks_out")); + TS_ASSERT(peakResult); + + double ellipInten = peakResult->getPeak(0).getIntensity(); + double ellipVol = (4.0 / 3.0) * M_PI * static_cast(numEvents) * + std::accumulate(radii.begin(), radii.end(), 1.0, + std::multiplies()); + TS_ASSERT_DELTA(ellipInten, ellipVol, + ceil(0.05 * static_cast(ellipVol))); + + // Major axis along y + radii = {0.04, 0.05, 0.03}; + doRun(radii, {0.0}, "IntegratePeaksMD2Test_peaks_out", {0.0}, false, false, + "NoFit", 0.0, true, false); + + peakResult = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve( + "IntegratePeaksMD2Test_peaks_out")); + TS_ASSERT(peakResult); + + ellipInten = peakResult->getPeak(0).getIntensity(); + TS_ASSERT_DELTA(ellipInten, ellipVol, + ceil(0.05 * static_cast(ellipVol))); + + // Major axis along x + radii = {0.05, 0.04, 0.03}; + doRun(radii, {0.0}, "IntegratePeaksMD2Test_peaks_out", {0.0}, false, false, + "NoFit", 0.0, true, false); + + peakResult = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve( + "IntegratePeaksMD2Test_peaks_out")); + TS_ASSERT(peakResult); + + ellipInten = peakResult->getPeak(0).getIntensity(); + TS_ASSERT_DELTA(ellipInten, ellipVol, + ceil(0.05 * static_cast(ellipVol))); + } + + void test_exec_EllipsoidRadii_WithBackground() { + // Test an ellipsoid against theoretical vol + size_t numEvents = 1000000; + V3D pos(0.0, 0.0, 0.0); // peak position + + createMDEW(); + + Instrument_sptr inst = + ComponentCreationHelper::createTestInstrumentCylindrical(5); + PeaksWorkspace_sptr peakWS(new PeaksWorkspace()); + addUniform(numEvents, {std::make_pair(-0.5, 0.5), std::make_pair(-0.5, 0.5), + std::make_pair(-0.5, 0.5)}); + std::vector> eigenvects; + eigenvects.push_back(std::vector{1.0, 0.0, 0.0}); + eigenvects.push_back(std::vector{0.0, 1.0, 0.0}); + eigenvects.push_back(std::vector{0.0, 0.0, 1.0}); + std::vector radii = {0.4, 0.3, 0.2}; + // addEllipsoid(numEvents, pos[0], pos[1], pos[2], eigenvects, radii, 0.0); + peakWS->addPeak(Peak(inst, 1, 1.0, pos)); + AnalysisDataService::Instance().addOrReplace("IntegratePeaksMD2Test_peaks", + peakWS); + + std::vector background = {0.4, 0.45, 0.5}; + doRun(radii, background, "IntegratePeaksMD2Test_peaks_out", {0.0}, true, + false, "NoFit", 0.0, true, false); + + PeaksWorkspace_sptr peakResult = std::dynamic_pointer_cast( + AnalysisDataService::Instance().retrieve( + "IntegratePeaksMD2Test_peaks_out")); + TS_ASSERT(peakResult); + + // the integrated intensity should end up around 0 as this is just + // approximately uniform data + double ellipInten = peakResult->getPeak(0).getIntensity(); + TS_ASSERT_DELTA(ellipInten, 0, 200); + + // Get the peak's shape + const PeakShape &shape = peakResult->getPeak(0).getPeakShape(); + PeakShapeEllipsoid const *const ellipsoidShape = + dynamic_cast(const_cast(&shape)); + + // Check the shape is what we expect + TSM_ASSERT("Wrong sort of peak", ellipsoidShape); + + TS_ASSERT_DELTA(ellipsoidShape->abcRadii(), radii, 1e-9); + TS_ASSERT_DELTA(ellipsoidShape->abcRadiiBackgroundInner(), radii, 1e-9); + TS_ASSERT_DELTA(ellipsoidShape->abcRadiiBackgroundOuter(), background, + 1e-9); + TS_ASSERT_EQUALS(ellipsoidShape->directions()[0], V3D(1, 0, 0)); + TS_ASSERT_EQUALS(ellipsoidShape->directions()[1], V3D(0, 1, 0)); + TS_ASSERT_EQUALS(ellipsoidShape->directions()[2], V3D(0, 0, 1)); + } + + void EllipsoidTestHelper( + double doCounts, bool fixQAxis, bool doBkgrd, + std::vector peakEigenvals = std::vector(), + std::vector ellipsoidRadii = std::vector(), + std::vector ellipsoidBgInnerRad = std::vector(), + std::vector ellipsoidBgOuterRad = std::vector()) { // doCounts < 0 -> all events have a count of 1 // doCounts > 0 -> counts follow multivariate normal dist + if (!ellipsoidRadii.empty()) { + TS_ASSERT(ellipsoidRadii.size() == 3); + } + + if (!ellipsoidBgInnerRad.empty()) { + TS_ASSERT(ellipsoidBgInnerRad.size() == 3); + } + + if (!ellipsoidBgOuterRad.empty()) { + TS_ASSERT(ellipsoidBgOuterRad.size() == 3); + } + createMDEW(); // peak Q @@ -466,29 +701,42 @@ public: std::vector> eigenvects; std::vector eigenvals; eigenvects.push_back(std::vector{Q[0], Q[1], Q[2]}); // para Q - eigenvals.push_back(0.05); // other orthogonal axes eigenvects.push_back(std::vector{0.0, 1.0, 0.0}); - eigenvals.push_back(0.03); eigenvects.push_back(std::vector{0.0, 0.0, 1.0}); - eigenvals.push_back(0.02); + + if (ellipsoidRadii.empty()) { + // Use default eigenvals for ellipsoid peaks + eigenvals.push_back(0.05); + eigenvals.push_back(0.03); + eigenvals.push_back(0.02); + } else { + eigenvals = peakEigenvals; + } + size_t numEvents = 20000; addEllipsoid(numEvents, Q[0], Q[1], Q[2], eigenvects, eigenvals, doCounts); // radius for integration (4 stdevs of principal axis) auto peakRadius = 4 * sqrt(eigenvals[0]); - double bgInnerRadius = 0.0; - double bgOuterRadius = 0.0; + std::vector bgInnerRadius = {}; + std::vector bgOuterRadius = {}; // background w if (doBkgrd == true) { // add random uniform - bgInnerRadius = peakRadius; - bgOuterRadius = - peakRadius * pow(2.0, 1.0 / 3.0); // twice vol of peak sphere std::vector> range; + if (ellipsoidBgInnerRad.empty() && ellipsoidBgOuterRad.empty()) { + bgInnerRadius.push_back(peakRadius); + bgOuterRadius.push_back( + peakRadius * pow(2.0, 1.0 / 3.0)); // twice vol of peak sphere + } else { + bgInnerRadius = ellipsoidBgInnerRad; + bgOuterRadius = ellipsoidBgOuterRad; + } for (size_t d = 0; d < eigenvals.size(); d++) { - range.push_back(std::pair(Q[d] - bgOuterRadius, Q[d] + bgOuterRadius)); + range.push_back( + std::pair(Q[d] - bgOuterRadius[0], Q[d] + bgOuterRadius[0])); } addUniform(static_cast(numEvents), range); } @@ -503,7 +751,14 @@ public: peakWS); // Integrate and copy to a new peaks workspace - doRun(peakRadius, bgOuterRadius, "IntegratePeaksMD2Test_peaks_out", + std::vector radiiVec; + if (ellipsoidRadii.empty()) { + radiiVec.push_back(peakRadius); + } else { + radiiVec = ellipsoidRadii; + } + + doRun(radiiVec, bgOuterRadius, "IntegratePeaksMD2Test_peaks_out", bgInnerRadius, false, /* edge correction */ false, /* cylinder*/ "NoFit", 0.0, /* adaptive*/ @@ -562,8 +817,18 @@ public: // loop over eigen vectors for (size_t ivect = 0; ivect < eigenvals.size(); ivect++) { - auto rad = peakRadius * sqrt(eigenvals[ivect] / eigenvals[0]); - double angle = axes[isort[ivect]].angle(V3D( + auto rad = sqrt(eigenvals[ivect] / eigenvals[0]); + if (ellipsoidRadii.size() < 3) { + rad *= peakRadius; + } else { + rad *= ellipsoidRadii[0]; + } + + size_t ind = isort[ivect]; + if (ellipsoidRadii.size() == 3) { + ind = ivect; + } + double angle = axes[ind].angle(V3D( eigenvects[ivect][0], eigenvects[ivect][1], eigenvects[ivect][2])); if (angle > M_PI / 2) { // axis is flipped @@ -578,9 +843,9 @@ public: // compare eigenvalue (radii propto sqrt eigenval) if (doBkgrd == true) { // use much more lenient tolerance - TS_ASSERT_DELTA(radii[isort[ivect]], rad, 0.15 * rad); + TS_ASSERT_DELTA(radii[ind], rad, 0.15 * rad); } else { - TS_ASSERT_DELTA(radii[isort[ivect]], rad, 0.05 * rad); + TS_ASSERT_DELTA(radii[ind], rad, 0.05 * rad); } TS_ASSERT_DELTA(angle, 0, M_PI * (5.0 / 180.0)); } @@ -593,7 +858,8 @@ public: const double backgroundOuterRadius = 3; const double backgroundInnerRadius = 2.5; - doRun(peakRadius, backgroundOuterRadius, "OutWS", backgroundInnerRadius); + doRun({peakRadius}, {backgroundOuterRadius}, "OutWS", + {backgroundInnerRadius}); auto outWS = AnalysisDataService::Instance().retrieveWS("OutWS"); @@ -631,7 +897,8 @@ public: const double backgroundOuterRadius = 3; const double backgroundInnerRadius = 2.5; - doRun(peakRadius, backgroundOuterRadius, "OutWS", backgroundInnerRadius); + doRun({peakRadius}, {backgroundOuterRadius}, "OutWS", + {backgroundInnerRadius}); PeaksWorkspace_sptr outWS = AnalysisDataService::Instance().retrieveWS("OutWS"); @@ -731,7 +998,7 @@ public: void test_performance_NoBackground() { for (size_t i = 0; i < 10; i++) { - IntegratePeaksMD2Test::doRun(0.02, 0.0); + IntegratePeaksMD2Test::doRun({0.02}, {0.0}); } // All peaks should be at least 1000 counts (some might be more if they // overla) @@ -745,7 +1012,7 @@ public: void test_performance_WithBackground() { for (size_t i = 0; i < 10; i++) { - IntegratePeaksMD2Test::doRun(0.02, 0.03); + IntegratePeaksMD2Test::doRun({0.02}, {0.03}); } } }; diff --git a/Framework/MDAlgorithms/test/PrecompiledHeader.h b/Framework/MDAlgorithms/test/PrecompiledHeader.h index e6fdf6551cbda585cc789772079a0760cddd669b..2af1746e37a94c7a5e99a9e2f3b05cba537cd16d 100644 --- a/Framework/MDAlgorithms/test/PrecompiledHeader.h +++ b/Framework/MDAlgorithms/test/PrecompiledHeader.h @@ -6,6 +6,11 @@ // SPDX - License - Identifier: GPL - 3.0 + #pragma once +// Mantid +#include "MantidAPI/AnalysisDataService.h" +#include "MantidTestHelpers/MDEventsTestHelper.h" +#include "MantidTestHelpers/WorkspaceCreationHelper.h" + // cxxtest #include diff --git a/Framework/Muon/inc/MantidMuon/MuonPreProcess.h b/Framework/Muon/inc/MantidMuon/MuonPreProcess.h index 70b2407de69366c355be54fb57194ae842d82aa9..9a6542f1daba4df8a33114bebc0b42d90a948003 100644 --- a/Framework/Muon/inc/MantidMuon/MuonPreProcess.h +++ b/Framework/Muon/inc/MantidMuon/MuonPreProcess.h @@ -54,6 +54,9 @@ private: MatrixWorkspace_sptr applyTimeOffset(MatrixWorkspace_sptr ws, const double &offset); + MatrixWorkspace_sptr applyTimeZeroTable(MatrixWorkspace_sptr ws, + const TableWorkspace_sptr &tz); + MatrixWorkspace_sptr applyCropping(MatrixWorkspace_sptr ws, const double &xMin, const double &xMax); @@ -68,8 +71,20 @@ private: /// Perform validation of inputs to the algorithm std::map validateInputs() override; + /// Validates the tables used in the alg (called in validateInputs) + void validateTableInputs(std::map &errors); + /// Allow WorkspaceGroup property to function correctly. bool checkGroups() override; + + /// Crop workspace with single xMin and xMax values + MatrixWorkspace_sptr cropWithSingleValues(MatrixWorkspace_sptr ws, + const double xMin, + const double xMax); + + /// Crop workspace with vector of doubles + MatrixWorkspace_sptr cropWithVectors(MatrixWorkspace_sptr ws, + const double xMin, const double xMax); }; } // namespace Muon diff --git a/Framework/Muon/inc/MantidMuon/PlotAsymmetryByLogValue.h b/Framework/Muon/inc/MantidMuon/PlotAsymmetryByLogValue.h index b4829d81f3d415ad083cb36cffa19636e2ed71d2..a41bc1f93fc2a27127ecd1cc66edd01a82501fcc 100644 --- a/Framework/Muon/inc/MantidMuon/PlotAsymmetryByLogValue.h +++ b/Framework/Muon/inc/MantidMuon/PlotAsymmetryByLogValue.h @@ -139,6 +139,8 @@ private: double m_minTime; /// Maximum time for the analysis double m_maxTime; + /// Balance parameter + double m_alpha; /// Properties needed to get the log value // LogValue name diff --git a/Framework/Muon/src/ApplyMuonDetectorGroupPairing.cpp b/Framework/Muon/src/ApplyMuonDetectorGroupPairing.cpp index 8319d702c839245ed7cf669d60dab78d8cd4788d..a8d725be39efa2bac197569c0ebc97e8ef891ad5 100644 --- a/Framework/Muon/src/ApplyMuonDetectorGroupPairing.cpp +++ b/Framework/Muon/src/ApplyMuonDetectorGroupPairing.cpp @@ -409,7 +409,7 @@ Muon::AnalysisOptions ApplyMuonDetectorGroupPairing::getUserInput() { const double alpha = static_cast(getProperty("Alpha")); grouping.pairAlphas.emplace_back(alpha); grouping.pairNames.emplace_back(this->getPropertyValue("PairName")); - grouping.pairs.emplace_back(std::make_pair(0, 1)); + grouping.pairs.emplace_back(0, 1); options.grouping = grouping; options.summedPeriods = this->getPropertyValue("SummedPeriods"); diff --git a/Framework/Muon/src/MuonAsymmetryHelper.cpp b/Framework/Muon/src/MuonAsymmetryHelper.cpp index 10c91adbabc7db727293f5a0a0a0b18df061bd2f..0e8dba04b9f8a7d0279def77ade167b192955bed 100644 --- a/Framework/Muon/src/MuonAsymmetryHelper.cpp +++ b/Framework/Muon/src/MuonAsymmetryHelper.cpp @@ -51,10 +51,12 @@ HistogramData::Histogram normaliseCounts(const HistogramData::Histogram &histogram, const double numGoodFrames) { HistogramData::Histogram result(histogram); + const auto xPoints = result.points(); + auto &yData = result.mutableY(); auto &eData = result.mutableE(); for (size_t i = 0; i < yData.size(); ++i) { - const double factor = exp(result.x()[i] / MUON_LIFETIME_MICROSECONDS); + const double factor = exp(xPoints[i] / MUON_LIFETIME_MICROSECONDS); // Correct the Y data if (yData[i] != 0.0) { yData[i] *= factor / numGoodFrames; diff --git a/Framework/Muon/src/MuonPreProcess.cpp b/Framework/Muon/src/MuonPreProcess.cpp index 3d9b97c3311e29f7661aa1293d9febce8c791322..3a770bdc13357f00bfe19515a91431c6aa49efe6 100644 --- a/Framework/Muon/src/MuonPreProcess.cpp +++ b/Framework/Muon/src/MuonPreProcess.cpp @@ -10,6 +10,7 @@ #include "MantidAPI/DataProcessorAlgorithm.h" #include "MantidAPI/FileProperty.h" #include "MantidAPI/MatrixWorkspace.h" +#include "MantidAPI/TableRow.h" #include "MantidAPI/WorkspaceFactory.h" #include "MantidAPI/WorkspaceGroup.h" #include "MantidAPI/WorkspaceGroup_fwd.h" @@ -66,6 +67,12 @@ void MuonPreProcess::init() { "become 0.0 seconds.", Direction::Input); + declareProperty( + std::make_unique>( + "TimeZeroTable", "", Direction::Input, PropertyMode::Optional), + "TableWorkspace with time zero information, used to apply time zero " + "correction"); + declareProperty( std::make_unique>( "DeadTimeTable", "", Direction::Input, PropertyMode::Optional), @@ -80,6 +87,31 @@ void MuonPreProcess::init() { setPropertyGroup("DeadTimeTable", analysisGrp); } +void MuonPreProcess::validateTableInputs( + std::map &errors) { + Workspace_sptr inputWS = this->getProperty("InputWorkspace"); + if (auto ws = std::dynamic_pointer_cast(inputWS)) { + // Dead time + TableWorkspace_sptr deadTimeTable = this->getProperty("DeadTimeTable"); + if (deadTimeTable) { + if (deadTimeTable->rowCount() > ws->getNumberHistograms()) { + errors["DeadTimeTable"] = "DeadTimeTable must have as many rows as " + "there are spectra in InputWorkspace."; + } + } + // Time zero + TableWorkspace_sptr timeZeroTable = this->getProperty("TimeZeroTable"); + if (timeZeroTable) { + if (timeZeroTable->rowCount() > ws->getNumberHistograms()) { + errors["TimeZeroTable"] = + "TimeZeroTable must have as many rows as " + "there are spectra in InputWorkspace. Use TimeOffset to apply same " + "time correcton to all data"; + } + } + } +} + std::map MuonPreProcess::validateInputs() { std::map errors; @@ -94,18 +126,10 @@ std::map MuonPreProcess::validateInputs() { } } - // Checks for dead time table - Workspace_sptr inputWS = this->getProperty("InputWorkspace"); - if (auto ws = std::dynamic_pointer_cast(inputWS)) { - TableWorkspace_sptr deadTimeTable = this->getProperty("DeadTimeTable"); - if (deadTimeTable) { - if (deadTimeTable->rowCount() > ws->getNumberHistograms()) { - errors["DeadTimeTable"] = "DeadTimeTable must have as many rows as " - "there are spectra in InputWorkspace."; - } - } - } + // Check for and validate dead time and time zero tables + validateTableInputs(errors); + Workspace_sptr inputWS = this->getProperty("InputWorkspace"); if (auto ws = std::dynamic_pointer_cast(inputWS)) { if (ws->getNumberOfEntries() == 0) { errors["InputWorkspace"] = "Input WorkspaceGroup is empty."; @@ -164,7 +188,8 @@ MuonPreProcess::correctWorkspaces(const WorkspaceGroup_sptr &wsGroup) { } /** - * Applies offset, crops and rebin the workspace according to specified params. + * Applies offset, crops and rebin the workspace according to specified + * params. * @param ws :: Workspace to correct * @return Corrected workspace */ @@ -174,8 +199,10 @@ MatrixWorkspace_sptr MuonPreProcess::correctWorkspace(MatrixWorkspace_sptr ws) { double xMax = getProperty("TimeMax"); std::vector rebinParams = getProperty("RebinArgs"); TableWorkspace_sptr deadTimes = getProperty("DeadTimeTable"); + TableWorkspace_sptr timeZeroTable = getProperty("TimeZeroTable"); ws = applyDTC(ws, deadTimes); + ws = applyTimeZeroTable(ws, timeZeroTable); ws = applyTimeOffset(ws, offset); ws = applyCropping(ws, xMin, xMax); ws = applyRebinning(ws, rebinParams); @@ -214,21 +241,80 @@ MatrixWorkspace_sptr MuonPreProcess::applyTimeOffset(MatrixWorkspace_sptr ws, } } +MatrixWorkspace_sptr +MuonPreProcess::applyTimeZeroTable(MatrixWorkspace_sptr ws, + const TableWorkspace_sptr &timeZeroTable) { + auto cloneWs = cloneWorkspace(ws); + if (!timeZeroTable) { + return cloneWs; + } + const auto numSpec = cloneWs->getNumberHistograms(); + for (auto specNum = 0u; specNum < numSpec; ++specNum) { + auto &xData = cloneWs->mutableX(specNum); + for (auto &xValue : xData) { + API::TableRow row = timeZeroTable->getRow(specNum); + xValue -= row.Double(0); + } + } + return cloneWs; +} + MatrixWorkspace_sptr MuonPreProcess::applyCropping(MatrixWorkspace_sptr ws, const double &xMin, const double &xMax) { - if (xMin != EMPTY_DBL() || xMax != EMPTY_DBL()) { - IAlgorithm_sptr crop = createChildAlgorithm("CropWorkspace"); - crop->setProperty("InputWorkspace", ws); - if (xMin != EMPTY_DBL()) - crop->setProperty("Xmin", xMin); - if (xMax != EMPTY_DBL()) - crop->setProperty("Xmax", xMax); - crop->execute(); - return crop->getProperty("OutputWorkspace"); - } else { + if (xMin == EMPTY_DBL() && xMax == EMPTY_DBL()) return ws; + + if (getPropertyValue("TimeZeroTable").empty()) + return cropWithSingleValues(ws, xMin, xMax); + else + return cropWithVectors(ws, xMin, xMax); +} + +MatrixWorkspace_sptr +MuonPreProcess::cropWithSingleValues(MatrixWorkspace_sptr ws, const double xMin, + const double xMax) { + IAlgorithm_sptr crop = createChildAlgorithm("CropWorkspace"); + crop->setProperty("InputWorkspace", ws); + if (xMin != EMPTY_DBL()) + crop->setProperty("Xmin", xMin); + if (xMax != EMPTY_DBL()) + crop->setProperty("Xmax", xMax); + crop->execute(); + return crop->getProperty("OutputWorkspace"); +} + +MatrixWorkspace_sptr MuonPreProcess::cropWithVectors(MatrixWorkspace_sptr ws, + const double xMin, + const double xMax) { + std::vector xMinVec; + std::vector xMaxVec; + + if (xMin != EMPTY_DBL()) + xMinVec.insert(xMinVec.end(), ws->getNumberHistograms(), xMin); + else { + for (auto specNum = 0u; specNum < ws->getNumberHistograms(); ++specNum) { + auto &xData = ws->mutableX(specNum); + xMinVec.emplace_back(xData[0]); // Append first value for each spectrum + } } + + if (xMax != EMPTY_DBL()) + xMaxVec.insert(xMaxVec.end(), ws->getNumberHistograms(), xMax); + else { + for (auto specNum = 0u; specNum < ws->getNumberHistograms(); ++specNum) { + auto &xData = ws->mutableX(specNum); + xMaxVec.emplace_back( + xData[xData.size() - 1]); // Append last value for each spectrum + } + } + + IAlgorithm_sptr cropRagged = createChildAlgorithm("CropWorkspaceRagged"); + cropRagged->setProperty("InputWorkspace", ws); + cropRagged->setProperty("XMin", xMinVec); + cropRagged->setProperty("XMax", xMaxVec); + cropRagged->execute(); + return cropRagged->getProperty("OutputWorkspace"); } MatrixWorkspace_sptr diff --git a/Framework/Muon/src/PlotAsymmetryByLogValue.cpp b/Framework/Muon/src/PlotAsymmetryByLogValue.cpp index 263c41d44b70149316307dbd1d9a75ee1dc5b95d..83e2aaf958d0d7e67318b4bd139583a1c724125f 100644 --- a/Framework/Muon/src/PlotAsymmetryByLogValue.cpp +++ b/Framework/Muon/src/PlotAsymmetryByLogValue.cpp @@ -160,6 +160,9 @@ void PlotAsymmetryByLogValue::init() { declareProperty(std::make_unique>( "WorkspaceNames", Direction::Input), "The range of workspaces"); + + declareProperty("Alpha", 1.0, + "The balance parameter passed to AsymmetryCalc"); } /// Validate the input properties @@ -306,6 +309,8 @@ void PlotAsymmetryByLogValue::checkProperties(size_t &firstRunNumber, m_dtcFile = getPropertyValue("DeadTimeCorrFile"); // Get runs m_fileNames = getProperty("WorkspaceNames"); + // Get balance parameter + m_alpha = getProperty("Alpha"); // If file names empty, first and last provided so need to populate vector if (m_fileNames.empty()) { @@ -343,7 +348,8 @@ void PlotAsymmetryByLogValue::checkProperties(size_t &firstRunNumber, << getPropertyValue("BackwardSpectra") << ","; ss << m_int << "," << m_minTime << "," << m_maxTime << ","; ss << m_red << "," << m_green << ","; - ss << m_logName << ", " << m_logFunc; + ss << m_logName << ", " << m_logFunc << ","; + ss << m_alpha; // Add run numbers to all properties for (const auto &run : m_rmap) { @@ -837,6 +843,7 @@ void PlotAsymmetryByLogValue::calcIntAsymmetry(const MatrixWorkspace_sptr &ws, IAlgorithm_sptr asym = createChildAlgorithm("AsymmetryCalc"); asym->setLogging(false); asym->setProperty("InputWorkspace", intWS); + asym->setProperty("Alpha", m_alpha); asym->execute(); out = asym->getProperty("OutputWorkspace"); } diff --git a/Framework/Muon/src/RemoveExpDecay.cpp b/Framework/Muon/src/RemoveExpDecay.cpp index c23d63849799f9062b65bad4aaceb76a57c37f39..8406f615e344e6b9b18f8ccd1412dd5103e92568 100644 --- a/Framework/Muon/src/RemoveExpDecay.cpp +++ b/Framework/Muon/src/RemoveExpDecay.cpp @@ -148,11 +148,12 @@ void MuonRemoveExpDecay::exec() { HistogramData::Histogram MuonRemoveExpDecay::removeDecay( const HistogramData::Histogram &histogram) const { HistogramData::Histogram result(histogram); + const auto xPoints = result.points(); auto &yData = result.mutableY(); auto &eData = result.mutableE(); for (size_t i = 0; i < yData.size(); ++i) { - const double factor = exp(result.x()[i] / MUON_LIFETIME_MICROSECONDS); + const double factor = exp(xPoints[i] / MUON_LIFETIME_MICROSECONDS); // Correct the Y data if (yData[i] != 0.0) { yData[i] *= factor; diff --git a/Framework/Muon/test/ApplyMuonDetectorGroupingTest.h b/Framework/Muon/test/ApplyMuonDetectorGroupingTest.h index cd32eb0f0165e80276c669f75e5f752420ce28a1..1c00625d811adadf2db23c8feb5db98d2788c04f 100644 --- a/Framework/Muon/test/ApplyMuonDetectorGroupingTest.h +++ b/Framework/Muon/test/ApplyMuonDetectorGroupingTest.h @@ -213,13 +213,13 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], 1.350, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[4], -0.771, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[9], -0.2158, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], 1.404, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[4], -0.766, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[9], -0.1978, 0.001); // Errors are simply normalized by a constant. - TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.00094, 0.00001); - TS_ASSERT_DELTA(wsOut->readE(0)[4], 0.00113, 0.00001); - TS_ASSERT_DELTA(wsOut->readE(0)[9], 0.00142, 0.00001); + TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.00096, 0.00001); + TS_ASSERT_DELTA(wsOut->readE(0)[4], 0.00116, 0.00001); + TS_ASSERT_DELTA(wsOut->readE(0)[9], 0.00145, 0.00001); } void @@ -244,13 +244,13 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], 1.410, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[4], -0.876, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[9], -0.053, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], 1.465, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[4], -0.873, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[9], -0.0307, 0.001); // Errors : quadrature addition + normalized by a constant. - TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.000282, 0.000001); - TS_ASSERT_DELTA(wsOut->readE(0)[4], 0.000338, 0.000001); - TS_ASSERT_DELTA(wsOut->readE(0)[9], 0.000424, 0.000001); + TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.000288, 0.000001); + TS_ASSERT_DELTA(wsOut->readE(0)[4], 0.000346, 0.000001); + TS_ASSERT_DELTA(wsOut->readE(0)[9], 0.000434, 0.000001); } void diff --git a/Framework/Muon/test/EstimateMuonAsymmetryFromCountsTest.h b/Framework/Muon/test/EstimateMuonAsymmetryFromCountsTest.h index 80bd49e6b14d47f6248032ac5feb8ab8880acb1a..78fd1aae8b0b1c0d8582a4add0b12ad7bf288ebd 100644 --- a/Framework/Muon/test/EstimateMuonAsymmetryFromCountsTest.h +++ b/Framework/Muon/test/EstimateMuonAsymmetryFromCountsTest.h @@ -125,9 +125,9 @@ public: TS_ASSERT_DELTA(outWS->x(j)[19], 0.3800, Delta); TS_ASSERT_DELTA(outWS->x(j)[49], 0.9800, Delta); // Test some Y values - TS_ASSERT_DELTA(outWS->y(j)[10], 0.0130, Delta); - TS_ASSERT_DELTA(outWS->y(j)[19], -0.1168, Delta); - TS_ASSERT_DELTA(outWS->y(j)[49], 0.0624, Delta); + TS_ASSERT_DELTA(outWS->y(j)[10], 0.0176, Delta); + TS_ASSERT_DELTA(outWS->y(j)[19], -0.1128, Delta); + TS_ASSERT_DELTA(outWS->y(j)[49], 0.0672, Delta); // Test some E values TS_ASSERT_DELTA(outWS->e(j)[10], 0.0002, Delta); TS_ASSERT_DELTA(outWS->e(j)[19], 0.0003, Delta); @@ -280,9 +280,9 @@ public: TS_ASSERT_DELTA(outWS->x(0)[19], 0.3800, Delta); TS_ASSERT_DELTA(outWS->x(0)[49], 0.9800, Delta); // Test some Y values - TS_ASSERT_DELTA(outWS->y(0)[10], -0.7974, Delta); - TS_ASSERT_DELTA(outWS->y(0)[19], -0.8233, Delta); - TS_ASSERT_DELTA(outWS->y(0)[49], -0.7875, Delta); + TS_ASSERT_DELTA(outWS->y(0)[10], -0.7965, Delta); + TS_ASSERT_DELTA(outWS->y(0)[19], -0.8226, Delta); + TS_ASSERT_DELTA(outWS->y(0)[49], -0.7866, Delta); } void test_unNorm() { @@ -305,9 +305,9 @@ public: TS_ASSERT_DELTA(outWS->x(0)[19], 0.3800, Delta); TS_ASSERT_DELTA(outWS->x(0)[49], 0.9800, Delta); // Test some Y values - TS_ASSERT_DELTA(outWS->y(0)[10], 2.0662, Delta); - TS_ASSERT_DELTA(outWS->y(0)[19], 1.8016, Delta); - TS_ASSERT_DELTA(outWS->y(0)[49], 2.1670, Delta); + TS_ASSERT_DELTA(outWS->y(0)[10], 2.0757, Delta); + TS_ASSERT_DELTA(outWS->y(0)[19], 1.8098, Delta); + TS_ASSERT_DELTA(outWS->y(0)[49], 2.1769, Delta); } }; // turn clang off, otherwise this does not compile diff --git a/Framework/Muon/test/MuonGroupingAsymmetryTest.h b/Framework/Muon/test/MuonGroupingAsymmetryTest.h index 0c05f83dbf3d10eaa826e1f291cad0a5b9ab9067..349fce9a823fdcd72325b37e920da108feae4a9d 100644 --- a/Framework/Muon/test/MuonGroupingAsymmetryTest.h +++ b/Framework/Muon/test/MuonGroupingAsymmetryTest.h @@ -255,8 +255,8 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], 2.18243, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[1], 1.68932, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], 2.25569, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[1], 1.75123, 0.001); TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.0002906, 0.00001); TS_ASSERT_DELTA(wsOut->readE(0)[1], 0.0003041, 0.00001); @@ -277,11 +277,11 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], 2.2751, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[1], 1.7005, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], 2.3505, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[1], 1.7627, 0.001); TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.0001418, 0.00001); - TS_ASSERT_DELTA(wsOut->readE(0)[1], 0.0001418, 0.00001); + TS_ASSERT_DELTA(wsOut->readE(0)[1], 0.0001518, 0.00001); } void @@ -304,8 +304,8 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], -0.29901, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[1], 0.06680, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], -0.3058997, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[1], 0.06834, 0.001); TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.0001497, 0.00001); TS_ASSERT_DELTA(wsOut->readE(0)[1], 0.0001567, 0.00001); @@ -330,8 +330,8 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], 0.29901, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[1], -0.06680, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], 0.3058997, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[1], -0.06834, 0.001); TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.0001497, 0.00001); TS_ASSERT_DELTA(wsOut->readE(0)[1], 0.0001567, 0.00001); @@ -357,8 +357,8 @@ public: TS_ASSERT_DELTA(wsOut->readX(0)[4], 0.400, 0.001); TS_ASSERT_DELTA(wsOut->readX(0)[9], 0.900, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[0], 1.39055, 0.001); - TS_ASSERT_DELTA(wsOut->readY(0)[1], 0.92922, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[0], 1.42256, 0.001); + TS_ASSERT_DELTA(wsOut->readY(0)[1], 0.95062, 0.001); TS_ASSERT_DELTA(wsOut->readE(0)[0], 0.0000577, 0.00001); TS_ASSERT_DELTA(wsOut->readE(0)[1], 0.0000604, 0.00001); diff --git a/Framework/Muon/test/MuonPreProcessTest.h b/Framework/Muon/test/MuonPreProcessTest.h index acc990e37114212825e2bb40e96e89e23cb64abe..e95b9d9e0f081e9bb25e9d64ba755e49eadab631 100644 --- a/Framework/Muon/test/MuonPreProcessTest.h +++ b/Framework/Muon/test/MuonPreProcessTest.h @@ -68,6 +68,17 @@ IAlgorithm_sptr setUpAlgorithmWithTimeOffset(const MatrixWorkspace_sptr &ws, return alg; } +// Set up algorithm with TimeZeroTable applied +IAlgorithm_sptr +setUpAlgorithmWithTimeZeroTable(const MatrixWorkspace_sptr &ws, + const ITableWorkspace_sptr &timeZeroTable) { + setUpADSWithWorkspace setup(ws); + IAlgorithm_sptr alg = + algorithmWithoutOptionalPropertiesSet(setup.inputWSName); + alg->setProperty("TimeZeroTable", timeZeroTable); + return alg; +} + // Set up algorithm with DeadTimeTable applied IAlgorithm_sptr setUpAlgorithmWithDeadTimeTable(const MatrixWorkspace_sptr &ws, @@ -217,6 +228,37 @@ public: TS_ASSERT_THROWS(alg->execute(), const std::runtime_error &); } + // -------------------------------------------------------------------------- + // Input property validation : Time Zero Table + // -------------------------------------------------------------------------- + + void test_successful_execution_with_valid_time_zero_table() { + // workspace has 5 spectra, time zero table has 5 rows + auto ws = createCountsWorkspace(5, 10, 0.0); + std::vector timeZeros = {0.5, 1.0, 1.5, 2.0, 2.5}; + ITableWorkspace_sptr timeZeroTable = createTimeZeroTable(5, timeZeros); + + auto alg = setUpAlgorithmWithTimeZeroTable(ws, timeZeroTable); + + TS_ASSERT_THROWS_NOTHING(alg->execute()); + } + + void test_cannot_execute_on_invalid_time_zero_table() { + // workspace has 2 spectra, time zero table has 5 rows + auto ws = createCountsWorkspace(2, 10, 0.0); + std::vector timeZeros = {0.5, 1.0, 1.5, 2.0, 2.5}; + ITableWorkspace_sptr timeZeroTable = createTimeZeroTable(5, timeZeros); + + auto alg = setUpAlgorithmWithTimeZeroTable(ws, timeZeroTable); + auto errors = alg->validateInputs(); + const auto expected = "TimeZeroTable must have as many rows as there are " + "spectra in InputWorkspace. Use TimeOffset to apply " + "same time correcton to all data"; + + TS_ASSERT_THROWS(alg->execute(), const std::runtime_error &); + TS_ASSERT_EQUALS(errors["TimeZeroTable"], expected); + } + // -------------------------------------------------------------------------- // Correct output : Rebin Args // -------------------------------------------------------------------------- @@ -324,6 +366,44 @@ public: TS_ASSERT_DELTA(wsOut->readY(0)[9], 9.0, 0.001); } + // -------------------------------------------------------------------------- + // Correct output : Time Zero Table + // -------------------------------------------------------------------------- + + void test_that_empty_time_zero_table_applied_correctly() { + auto ws = createCountsWorkspace(2, 2, 0.0); + std::vector timeZeros = {0, 0}; + auto timeZeroTable = createTimeZeroTable(2, timeZeros); + + auto alg = setUpAlgorithmWithTimeZeroTable(ws, timeZeroTable); + alg->execute(); + + auto wsOut = getOutputWorkspace(alg, 0); + TS_ASSERT_DELTA(wsOut->readX(0)[0], 0.0, 0.01); + TS_ASSERT_DELTA(wsOut->readX(0)[1], 0.5, 0.01); + TS_ASSERT_DELTA(wsOut->readX(0)[2], 1.0, 0.01); + TS_ASSERT_DELTA(wsOut->readX(1)[0], 0.0, 0.01); + TS_ASSERT_DELTA(wsOut->readX(1)[1], 0.5, 0.01); + TS_ASSERT_DELTA(wsOut->readX(1)[2], 1.0, 0.01); + } + + void test_not_empty_time_zero_table_applied_correctly() { + auto ws = createCountsWorkspace(2, 2, 0.0); + std::vector timeZeros = {0.25, -0.25}; // Applied as minus in alg + auto timeZeroTable = createTimeZeroTable(2, timeZeros); + + auto alg = setUpAlgorithmWithTimeZeroTable(ws, timeZeroTable); + alg->execute(); + + auto wsOut = getOutputWorkspace(alg, 0); + TS_ASSERT_DELTA(wsOut->readX(0)[0], 0.0 - 0.25, 0.01); + TS_ASSERT_DELTA(wsOut->readX(0)[1], 0.5 - 0.25, 0.01); + TS_ASSERT_DELTA(wsOut->readX(0)[2], 1.0 - 0.25, 0.01); + TS_ASSERT_DELTA(wsOut->readX(1)[0], 0.0 + 0.25, 0.01); + TS_ASSERT_DELTA(wsOut->readX(1)[1], 0.5 + 0.25, 0.01); + TS_ASSERT_DELTA(wsOut->readX(1)[2], 1.0 + 0.25, 0.01); + } + // -------------------------------------------------------------------------- // Correct output : cropping via TimeMax and TimeMin // -------------------------------------------------------------------------- diff --git a/Framework/Muon/test/RemoveExpDecayTest.h b/Framework/Muon/test/RemoveExpDecayTest.h index ab7399eee72170eca5e69be131da2742b6da2ac2..92f0fcc9d7e36bf7e376dbd0af109b6175fae1ee 100644 --- a/Framework/Muon/test/RemoveExpDecayTest.h +++ b/Framework/Muon/test/RemoveExpDecayTest.h @@ -21,7 +21,8 @@ using Mantid::Algorithms::MuonRemoveExpDecay; const std::string outputName = "MuonRemoveExpDecay_Output"; namespace { -MatrixWorkspace_sptr createWorkspace(size_t nspec, size_t maxt) { +MatrixWorkspace_sptr createWorkspace(size_t nspec, size_t maxt, + bool useBinEdges = false) { // Create a fake muon dataset double a = 0.1; // Amplitude of the oscillations @@ -33,6 +34,9 @@ MatrixWorkspace_sptr createWorkspace(size_t nspec, size_t maxt) { MantidVec Y; MantidVec E; for (size_t s = 0; s < nspec; s++) { + if (useBinEdges) { + X.emplace_back(0.0); + } for (size_t t = 0; t < maxt; t++) { double x = static_cast(t) / static_cast(maxt); double e = exp(-x / tau); @@ -91,7 +95,7 @@ public: MatrixWorkspace_sptr outWS = alg->getProperty("OutputWorkspace"); } - void test_EmptySpectrumList() { + void test_EmptySpectrumListPointsWorkspace() { auto ws = createWorkspace(2, 50); @@ -134,6 +138,49 @@ public: TS_ASSERT_DELTA(outWS->e(1)[49], 0.0078, 0.0001); } + void test_EmptySpectrumListBinEdgesWorkspace() { + + auto ws = createWorkspace(2, 50, true); + + IAlgorithm_sptr alg = AlgorithmManager::Instance().create("RemoveExpDecay"); + alg->initialize(); + alg->setChild(true); + alg->setProperty("InputWorkspace", ws); + alg->setPropertyValue("OutputWorkspace", outputName); + TS_ASSERT_THROWS_NOTHING(alg->execute()); + TS_ASSERT(alg->isExecuted()); + + MatrixWorkspace_sptr outWS = alg->getProperty("OutputWorkspace"); + + // First spectrum + // Test some X values + TS_ASSERT_DELTA(outWS->x(0)[10], 0.1800, 0.0001); + TS_ASSERT_DELTA(outWS->x(0)[19], 0.3600, 0.0001); + TS_ASSERT_DELTA(outWS->x(0)[49], 0.9600, 0.0001); + // Test some Y values + TS_ASSERT_DELTA(outWS->y(0)[10], -0.0993, 0.0001); + TS_ASSERT_DELTA(outWS->y(0)[19], -0.0113, 0.0001); + TS_ASSERT_DELTA(outWS->y(0)[49], -0.0627, 0.0001); + // Test some E values + TS_ASSERT_DELTA(outWS->e(0)[10], 0.0054, 0.0001); + TS_ASSERT_DELTA(outWS->e(0)[19], 0.0059, 0.0001); + TS_ASSERT_DELTA(outWS->e(0)[49], 0.0077, 0.0001); + + // Second spectrum + // Test some X values + TS_ASSERT_DELTA(outWS->x(1)[10], 0.1800, 0.0001); + TS_ASSERT_DELTA(outWS->x(1)[19], 0.3600, 0.0001); + TS_ASSERT_DELTA(outWS->x(1)[49], 0.9600, 0.0001); + // Test some Y values + TS_ASSERT_DELTA(outWS->y(1)[10], 0.0275, 0.0001); + TS_ASSERT_DELTA(outWS->y(1)[19], -0.1005, 0.0001); + TS_ASSERT_DELTA(outWS->y(1)[49], 0.0797, 0.0001); + // Test some E values + TS_ASSERT_DELTA(outWS->e(1)[10], 0.0054, 0.0001); + TS_ASSERT_DELTA(outWS->e(1)[19], 0.0059, 0.0001); + TS_ASSERT_DELTA(outWS->e(1)[49], 0.0078, 0.0001); + } + void test_SpectrumList() { auto ws = createWorkspace(2, 50); diff --git a/Framework/NexusGeometry/src/NexusGeometryParser.cpp b/Framework/NexusGeometry/src/NexusGeometryParser.cpp index 402cdda3f62a33445de0a89e5c433def66fc61ba..1e37aa0b0b90def36a039d65e9f37d43f9ca9635 100644 --- a/Framework/NexusGeometry/src/NexusGeometryParser.cpp +++ b/Framework/NexusGeometry/src/NexusGeometryParser.cpp @@ -7,11 +7,9 @@ #include "MantidNexusGeometry/NexusGeometryParser.h" #include "MantidGeometry/Instrument.h" #include "MantidGeometry/Objects/CSGObject.h" -#include "MantidGeometry/Objects/ShapeFactory.h" #include "MantidGeometry/Rendering/GeometryHandler.h" #include "MantidGeometry/Rendering/ShapeInfo.h" #include "MantidKernel/ChecksumHelper.h" -#include "MantidKernel/EigenConversionHelpers.h" #include "MantidNexusGeometry/AbstractLogger.h" #include "MantidNexusGeometry/H5ForwardCompatibility.h" #include "MantidNexusGeometry/Hdf5Version.h" @@ -23,8 +21,6 @@ #include #include #include -#include -#include #include #include #include @@ -37,14 +33,6 @@ using namespace H5; // Anonymous namespace namespace { -using FaceV = std::vector; - -struct Face { - Eigen::Vector3d v1; - Eigen::Vector3d v2; - Eigen::Vector3d v3; - Eigen::Vector3d v4; -}; bool isDegrees(const H5std_string &units) { using boost::regex; @@ -252,11 +240,11 @@ private: } // Provided to support invalid or null-termination character strings - std::string readOrSubsitute(const std::string &dataset, const Group &group, - std::string &substitue) { + std::string readOrSubstitute(const std::string &dataset, const Group &group, + const std::string &substitute) { auto read = get1DStringDataset(dataset, group); if (read.empty()) - read = substitue; + read = substitute; return read; } @@ -532,9 +520,9 @@ private: throw std::runtime_error("numbers of detector with shape cylinder does " "not match number of detectors"); if (cPoints.size() % 3 != 0) - throw std::runtime_error("cylinders not divisble by 3. Bad input."); + throw std::runtime_error("cylinders not divisible by 3. Bad input."); if (vPoints.size() % 3 != 0) - throw std::runtime_error("vertices not divisble by 3. Bad input."); + throw std::runtime_error("vertices not divisible by 3. Bad input."); for (size_t i = 0; i < cylinderIndexToDetId.size(); i += 2) { auto cylinderIndex = cylinderIndexToDetId[i]; @@ -628,7 +616,8 @@ private: const std::vector &windingOrder, const std::vector &vertices, const size_t numDets, const std::unordered_map &detIdToIndex, - const std::string &name, InstrumentBuilder &builder) { + const std::string &name, InstrumentBuilder &builder, + const Group &detectorGroup) { std::vector> detFaceVerts(numDets); std::vector> detFaceIndices(numDets); std::vector> detWindingOrder(numDets); @@ -638,22 +627,43 @@ private: faceIndices, detFaceVerts, detFaceIndices, detWindingOrder, detIds); + Pixels detectorPixels; + bool calculatePixelCentre = true; + if (detFaces.size() != 2 * numDets) { + // At least one pixel is 3D (comprises multiple faces) + // We need pixel offsets from the NXdetector in this case, as + // calculating centre of mass for a general polyhedron is fairly + // complex and computationally expensive + Pixels pixelOffsets = getPixelOffsets(detectorGroup); + // Calculate pixel relative positions + detectorPixels = Eigen::Affine3d::Identity() * pixelOffsets; + calculatePixelCentre = false; + } + for (size_t i = 0; i < numDets; ++i) { auto &detVerts = detFaceVerts[i]; - const auto &faceIndices = detFaceIndices[i]; + const auto &singleDetIndices = detFaceIndices[i]; const auto &detWinding = detWindingOrder[i]; - // Calculate polygon centre - Eigen::Vector3d centre = - std::accumulate(detVerts.begin() + 1, detVerts.end(), - detVerts.front()) / - detVerts.size(); - // translate shape to origin for shape coordinates. + Eigen::Vector3d centre; + if (calculatePixelCentre) { + // Our detector is 2D (described by a single face in the mesh) + // Calculate polygon centre + centre = std::accumulate(detVerts.begin() + 1, detVerts.end(), + detVerts.front()) / + detVerts.size(); + } else { + // Our detector is 3D (described by multiple faces in the mesh) + // Use pixel offset which was recorded in the NXdetector + centre = detectorPixels.col(i); + } + + // translate shape to origin for shape coordinates std::for_each(detVerts.begin(), detVerts.end(), [¢re](Eigen::Vector3d &val) { val -= centre; }); - auto shape = NexusShapeFactory::createFromOFFMesh(faceIndices, detWinding, - detVerts); + auto shape = NexusShapeFactory::createFromOFFMesh(singleDetIndices, + detWinding, detVerts); builder.addDetectorToLastBank(name + "_" + std::to_string(i), detIds[i], centre, std::move(shape)); } @@ -662,18 +672,19 @@ private: void parseMeshAndAddDetectors(InstrumentBuilder &builder, const Group &shapeGroup, const std::vector &detectorIds, - const std::string &bankName) { + const std::string &bankName, + const Group &detectorGroup) { // Load mapping between detector IDs and faces, winding order of vertices - // for faces, and face corner vertices. + // for faces, and face corner vertices const auto detFaces = readNXUInts32(shapeGroup, "detector_faces"); const auto faceIndices = readNXUInts32(shapeGroup, "faces"); const auto windingOrder = readNXUInts32(shapeGroup, "winding_order"); const auto vertices = readNXFloats(shapeGroup, "vertices"); // Sanity check entries - if (detFaces.size() != 2 * detectorIds.size()) - throw std::runtime_error("Expect to have as many detector_face entries " - "as detector_number entries"); + if (detFaces.size() < 2 * detectorIds.size()) + throw std::runtime_error("Expect to have at least as many detector_face " + "entries as detector_number entries"); if (detFaces.size() % 2 != 0) throw std::runtime_error("Unequal pairs of face indices to detector " "indices in detector_faces"); @@ -693,14 +704,16 @@ private: extractNexusMeshAndAddDetectors(detFaces, faceIndices, windingOrder, vertices, detectorIds.size(), detIdToIndex, - bankName, builder); + bankName, builder, detectorGroup); } void parseAndAddBank(const Group &shapeGroup, InstrumentBuilder &builder, const std::vector &detectorIds, - const std::string &bankName) { + const std::string &bankName, + const Group &detectorGroup) { if (utilities::hasNXAttribute(shapeGroup, NX_OFF)) { - parseMeshAndAddDetectors(builder, shapeGroup, detectorIds, bankName); + parseMeshAndAddDetectors(builder, shapeGroup, detectorIds, bankName, + detectorGroup); } else if (utilities::hasNXAttribute(shapeGroup, NX_CYLINDER)) { parseNexusCylinderDetector(shapeGroup, bankName, builder, detectorIds); } else { @@ -716,7 +729,7 @@ private: * IObject. * * Null object return if no shape can be found. - * @param detectorGroup : parent group possibily containing sub-group relating + * @param detectorGroup : parent group possibly containing sub-group relating * to shape * @param searchTubes : out parameter, true if tubes can be searched * @return shared pointer holding IObject subtype or null shared pointer @@ -727,16 +740,16 @@ private: // that have NX_class attributes of either NX_CYLINDER or NX_OFF. That way // we handle groups called any of the allowed - shape, pixel_shape, // detector_shape - auto cylinderical = utilities::findGroup(detectorGroup, NX_CYLINDER); + auto cylindrical = utilities::findGroup(detectorGroup, NX_CYLINDER); auto off = utilities::findGroup(detectorGroup, NX_OFF); searchTubes = false; - if (off && cylinderical) { + if (off && cylindrical) { throw std::runtime_error("Can either provide cylindrical OR OFF " "geometries as subgroups, not both"); } - if (cylinderical) { + if (cylindrical) { searchTubes = true; - return parseNexusCylinder(*cylinderical); + return parseNexusCylinder(*cylindrical); } else if (off) return parseNexusMesh(*off); else { @@ -751,9 +764,9 @@ private: Group instrumentGroup = utilities::findGroupOrThrow(entryGroup, NX_INSTRUMENT); Group sourceGroup = utilities::findGroupOrThrow(instrumentGroup, NX_SOURCE); - std::string sourceName = "Unspecfied"; + std::string sourceName = "Unspecified"; if (utilities::findDataset(sourceGroup, "name")) - sourceName = readOrSubsitute("name", sourceGroup, sourceName); + sourceName = readOrSubstitute("name", sourceGroup, sourceName); auto sourceTransformations = getTransformations(file, sourceGroup); auto defaultPos = Eigen::Vector3d(0.0, 0.0, 0.0); builder.addSource(sourceName, sourceTransformations * defaultPos); @@ -769,7 +782,7 @@ private: sampleTransforms * Eigen::Vector3d(0.0, 0.0, 0.0); std::string sampleName = "Unspecified"; if (utilities::findDataset(sampleGroup, "name")) - sampleName = readOrSubsitute("name", sampleGroup, sampleName); + sampleName = readOrSubstitute("name", sampleGroup, sampleName); builder.addSample(sampleName, samplePos); } @@ -827,11 +840,12 @@ public: auto detectorIds = getDetectorIds(detectorGroup); // We preferentially deal with DETECTOR_SHAPE type shapes. Pixel offsets - // not needed for this processing + // only needed if pixels are 3D for this processing auto detector_shape = utilities::findGroupByName(detectorGroup, DETECTOR_SHAPE); if (detector_shape) { - parseAndAddBank(*detector_shape, builder, detectorIds, bankName); + parseAndAddBank(*detector_shape, builder, detectorIds, bankName, + detectorGroup); continue; } @@ -856,7 +870,7 @@ public: auto index = static_cast(i); std::string name = bankName + "_" + std::to_string(index); - Eigen::Vector3d relativePos = detectorPixels.col(index); + const Eigen::Vector3d &relativePos = detectorPixels.col(index); builder.addDetectorToLastBank(name, detectorIds[index], relativePos, detShape); } diff --git a/Framework/NexusGeometry/src/NexusShapeFactory.cpp b/Framework/NexusGeometry/src/NexusShapeFactory.cpp index 6f35026ad88967c067f7b19b27f0cdfd517a5079..0a6bcde5c2f17db8d981b8b00525115fd79d611b 100644 --- a/Framework/NexusGeometry/src/NexusShapeFactory.cpp +++ b/Framework/NexusGeometry/src/NexusShapeFactory.cpp @@ -48,9 +48,10 @@ std::unique_ptr createCylinderShape( void createTrianglesFromPolygon(const std::vector &windingOrder, std::vector &triangularFaces, - int &startOfFace, int &endOfFace) { + int &startOfFace, const int &endOfFace, + int &windingOrderReached) { int polygonOrder = endOfFace - startOfFace; - auto first = windingOrder.begin() + startOfFace; + auto first = windingOrder.begin() + windingOrderReached; triangularFaces.reserve(triangularFaces.size() + 3 * polygonOrder); for (int polygonVertex = 1; polygonVertex < polygonOrder - 1; @@ -59,6 +60,7 @@ void createTrianglesFromPolygon(const std::vector &windingOrder, triangularFaces.emplace_back(*(first + polygonVertex)); triangularFaces.emplace_back(*(first + polygonVertex + 1)); } + windingOrderReached += polygonOrder; startOfFace = endOfFace; // start of the next face } @@ -73,18 +75,20 @@ createTriangularFaces(const std::vector &faceIndices, // the face normal by right-hand rule std::vector triangularFaces; - int startOfFace = 0; + auto startOfFace = static_cast(faceIndices[0]); int endOfFace = 0; + int windingOrderReached = 0; for (auto it = faceIndices.begin() + 1; it != faceIndices.end(); ++it) { - endOfFace = *it; + endOfFace = static_cast(*it); createTrianglesFromPolygon(windingOrder, triangularFaces, startOfFace, - endOfFace); + endOfFace, windingOrderReached); } // and the last face - endOfFace = static_cast(windingOrder.size()); + endOfFace = + startOfFace + static_cast(windingOrder.size()) - windingOrderReached; createTrianglesFromPolygon(windingOrder, triangularFaces, startOfFace, - endOfFace); + endOfFace, windingOrderReached); return triangularFaces; } diff --git a/Framework/NexusGeometry/test/NexusGeometryParserTest.h b/Framework/NexusGeometry/test/NexusGeometryParserTest.h index fc2c66c80ea0c747a8f4367f574273c9b69c75be..2f27433499d42fd2f19ccbc2188e2df106b2b4d5 100644 --- a/Framework/NexusGeometry/test/NexusGeometryParserTest.h +++ b/Framework/NexusGeometry/test/NexusGeometryParserTest.h @@ -48,6 +48,7 @@ std::string instrument_path(const std::string &local_name) { local_name, true, Poco::Glob::GLOB_DEFAULT); } } // namespace + class NexusGeometryParserTest : public CxxTest::TestSuite { public: // This pair of boilerplate methods prevent the suite being created statically @@ -57,13 +58,15 @@ public: } static void destroySuite(NexusGeometryParserTest *suite) { delete suite; } - std::unique_ptr makeTestInstrument() { + static std::unique_ptr + makeTestInstrument() { const auto fullpath = instrument_path("unit_testing/SMALLFAKE_example_geometry.hdf5"); return NexusGeometryParser::createInstrument( fullpath, std::make_unique()); } + void test_basic_instrument_information() { auto instrument = makeTestInstrument(); auto beamline = extractBeamline(*instrument); @@ -162,7 +165,6 @@ public: } void test_shape_cylinder_shape() { - auto instrument = makeTestInstrument(); auto beamline = extractBeamline(*instrument); auto componentInfo = std::move(beamline.first); @@ -185,7 +187,6 @@ public: } void test_mesh_shape() { - auto instrument = makeTestInstrument(); auto beamline = extractBeamline(*instrument); auto componentInfo = std::move(beamline.first); @@ -208,7 +209,6 @@ public: TS_ASSERT_DELTA(shapeBB.zMax() - shapeBB.zMin(), 2.0, 1e-9); } void test_pixel_shape_as_mesh() { - auto instrument = NexusGeometryParser::createInstrument( instrument_path("unit_testing/DETGEOM_example_1.nxs"), std::make_unique>()); @@ -229,6 +229,7 @@ public: TS_ASSERT_EQUALS(shape1Mesh->numberOfTriangles(), 2); TS_ASSERT_EQUALS(shape1Mesh->numberOfVertices(), 4); } + void test_pixel_shape_as_cylinders() { auto instrument = NexusGeometryParser::createInstrument( instrument_path("unit_testing/DETGEOM_example_2.nxs"), @@ -256,6 +257,7 @@ public: TS_ASSERT_EQUALS(shape1Cylinder->shapeInfo().height(), shape2Cylinder->shapeInfo().height()); } + void test_detector_shape_as_mesh() { auto instrument = NexusGeometryParser::createInstrument( instrument_path("unit_testing/DETGEOM_example_3.nxs"), @@ -278,6 +280,7 @@ public: TS_ASSERT_EQUALS(shape2Mesh->numberOfTriangles(), 1); TS_ASSERT_EQUALS(shape2Mesh->numberOfVertices(), 3); } + void test_detector_shape_as_cylinders() { auto instrument = NexusGeometryParser::createInstrument( instrument_path("unit_testing/DETGEOM_example_4.nxs"), @@ -316,6 +319,71 @@ public: TS_ASSERT_EQUALS(shape2Cylinder->shapeInfo().height(), 0.3); // 0.3 TS_ASSERT_EQUALS(shape3Cylinder->shapeInfo().height(), 0.2); // 0.5- 0.3 } + + void test_parse_detector_shape_with_3d_pixels() { + // GIVEN a NeXus file describing a detector with two octahedral voxels + // with: + // - detector numbers of 0 and 1 + // - pixel location defined in x_pixel_offset, y_pixel_offset, + // z_pixel_offset datasets as [1.1, 2.2, -2.0] and [1.1, 2.2, 0.0] + // w.r.t. detector origin + // - detector position defined as [2, 0, 2] w.r.t. coord system origin + // + // Multiple faces in the mesh are mapped to the same detector number, + // thus defining a 3D pixel + // Unlike 2D pixel case, pixel offset datasets must be present in the file. + // The parser will not try to calculate the centre of mass of the polyhedron + // to use as the pixel position as this is computationally expensive and + // possibly not even the "correct" pixel position for some detector types + const std::string filename = "unit_testing/VOXEL_example.nxs"; + const int expectedDetectorNumber1 = 0; + const int expectedDetectorNumber2 = 1; + const auto expectedPosition1 = Eigen::Vector3d{3.1, 2.2, 0.0}; + const auto expectedPosition2 = Eigen::Vector3d{3.1, 2.2, 2.0}; + + // WHEN the NeXus geometry is parsed + const auto instrument = NexusGeometryParser::createInstrument( + instrument_path(filename), + std::make_unique>()); + + // THEN the voxels are successfully parsed, locations match + // offsets datasets from file, and shape has expected characteristics + const auto parsedBeamline = extractBeamline(*instrument); + const auto &parsedDetInfo = *parsedBeamline.second; + TS_ASSERT_EQUALS(parsedDetInfo.size(), 2); + + const auto detectorInfo = extractDetectorInfo(*instrument); + const auto voxelPosition1 = Kernel::toVector3d( + detectorInfo->position(detectorInfo->indexOf(expectedDetectorNumber1))); + TS_ASSERT(voxelPosition1.isApprox(expectedPosition1)); + const auto voxelPosition2 = Kernel::toVector3d( + detectorInfo->position(detectorInfo->indexOf(expectedDetectorNumber2))); + TS_ASSERT(voxelPosition2.isApprox(expectedPosition2)); + + // Check shape of each of the two voxels + const auto &parsedCompInfo = *parsedBeamline.first; + const std::array pixelIndices{0, 1}; + for (const auto pixelIndex : pixelIndices) { + const auto &parsedShape = parsedCompInfo.shape(pixelIndex); + const auto *parsedShapeMesh = + dynamic_cast(&parsedShape); + // Check it looks like it might define an enclosed volume: + TS_ASSERT(parsedShapeMesh->hasValidShape()); + // The voxel is a regular octahedron, which can be treated as 2 + // square-based pyramids connected at their bases + // Volume is therefore 2 * a^2 * h/3 + // where a is base edge and h is pyramid height + // Corners of the octahedron are at unit cartesian positions: + // [1.0, 0.0, 0.0], [0.0, 1.0, 0.0] and so on, therefore + // a = sqrt(1^2 + 1^2) and h = 1 + // 2 * sqrt(1^2 + 1^2)^2 * 1/3 = 4/3 + const double expectedVolume = 1.33; + TS_ASSERT_DELTA(parsedShapeMesh->volume(), expectedVolume, 0.01); + // Each face of the octahedron is a triangle, + // therefore expect mesh to be composed of 8 triangles + TS_ASSERT_EQUALS(parsedShapeMesh->numberOfTriangles(), 8); + } + } }; class NexusGeometryParserTestPerformance : public CxxTest::TestSuite { diff --git a/Framework/PostInstall/CMakeLists.txt b/Framework/PostInstall/CMakeLists.txt index 6d37878558078a0363b9fac5be7c1aa7621590b1..7730de8225e0953738a04b56493961e2248cc51f 100644 --- a/Framework/PostInstall/CMakeLists.txt +++ b/Framework/PostInstall/CMakeLists.txt @@ -17,7 +17,7 @@ set(EXCLUDE_REGEX ".*_template") set(COMPILE_SCRIPT "message ( \"Byte-compiling Python in ${PACKAGE_ROOT}\")") set( COMPILE_SCRIPT - "${COMPILE_SCRIPT}\n execute_process ( COMMAND ${Python_EXECUTABLE} -m compileall -q -x \"${EXCLUDE_REGEX}\" \"${PACKAGE_ROOT}\" OUTPUT_QUIET ERROR_QUIET )" + "${COMPILE_SCRIPT}\n execute_process ( COMMAND ${Python_EXECUTABLE} -m compileall -q -j 0 -x \"${EXCLUDE_REGEX}\" \"${PACKAGE_ROOT}\" OUTPUT_QUIET ERROR_QUIET )" ) install(CODE ${COMPILE_SCRIPT}) diff --git a/Framework/Properties/Mantid.properties.template b/Framework/Properties/Mantid.properties.template index fcd134f488051e426eb75f05edd9132f76297f8b..7a5a41c7f2debb0a4f5b295fb7fff686d3fed593 100644 --- a/Framework/Properties/Mantid.properties.template +++ b/Framework/Properties/Mantid.properties.template @@ -232,12 +232,6 @@ projectSaving.warningSize = 10737418240 # Whether to show titles on plots plots.ShowTitle = On -# Whether to show minor ticks on plots -plots.ShowMinorTicks = Off - -# Whether to show minor gridlines on plots -plots.ShowMinorGridlines = Off - # Whether to show the legend by default plots.ShowLegend = On @@ -248,6 +242,36 @@ plots.font= plots.xAxesScale = Linear plots.yAxesScale = Linear +# The default width of the lines for the axes +plots.axesLineWidth = 1 + +# Whether to enable the grid by default +plots.enableGrid = Off + +# Whether to show minor ticks on plots +plots.ShowMinorTicks = Off + +# Whether to show minor gridlines on plots +plots.ShowMinorGridlines = Off + +# Whether to show ticks and tick labels on each side of the plot +plots.showTicksLeft = On +plots.showTicksBottom = On +plots.showTicksRight = Off +plots.showTicksTop = Off +plots.showLabelsLeft = On +plots.showLabelsBottom = On +plots.showLabelsRight = Off +plots.showLabelsTop = Off + +# The length, width, and direction of major and minor ticks +plots.ticks.major.length = 6 +plots.ticks.major.width = 1 +plots.ticks.major.direction = Out +plots.ticks.minor.length = 3 +plots.ticks.minor.width = 1 +plots.ticks.minor.direction = Out + # Default Line style on 1d plots. Options are solid, dashed, dotted, dashdot plots.line.Style = solid diff --git a/Framework/PythonInterface/CMakeLists.txt b/Framework/PythonInterface/CMakeLists.txt index 87fffd3dc9289ce2cc0b74be8933d3db5d83d75c..97cad0bd413560d3dbd8376c04ae02f52a8d6e91 100644 --- a/Framework/PythonInterface/CMakeLists.txt +++ b/Framework/PythonInterface/CMakeLists.txt @@ -104,12 +104,7 @@ include(PythonPackageTargetFunctions) # Adds `mantid` as a target in the `MantidFramework/Python` folder if(APPLE) set(_install_lib_dirs) - if(ENABLE_WORKBENCH) - list(APPEND _install_lib_dirs ${WORKBENCH_SITE_PACKAGES}) - endif() - if(ENABLE_MANTIDPLOT) - list(APPEND _install_lib_dirs ${SITE_PACKAGES}) - endif() + list(APPEND _install_lib_dirs ${WORKBENCH_SITE_PACKAGES}) else() set(_install_lib_dirs "${SITE_PACKAGES}") endif() diff --git a/Framework/PythonInterface/core/CMakeLists.txt b/Framework/PythonInterface/core/CMakeLists.txt index 9050d03d16fb461719ab91958a81f22eb4812f0d..92babb43afa5964a83157695ef3df30de1b78a7a 100644 --- a/Framework/PythonInterface/core/CMakeLists.txt +++ b/Framework/PythonInterface/core/CMakeLists.txt @@ -10,6 +10,7 @@ set(SRC_FILES src/NDArray.cpp src/ReleaseGlobalInterpreterLock.cpp src/UninstallTrace.cpp + src/PythonStdoutChannel.cpp src/WrapperHelpers.cpp src/Converters/CloneToNDArray.cpp src/Converters/DateAndTime.cpp @@ -34,6 +35,7 @@ set(INC_FILES inc/MantidPythonInterface/core/NDArray.h inc/MantidPythonInterface/core/PropertyWithValueExporter.h inc/MantidPythonInterface/core/PythonObjectInstantiator.h + inc/MantidPythonInterface/core/PythonStdoutChannel.h inc/MantidPythonInterface/core/ReleaseGlobalInterpreterLock.h inc/MantidPythonInterface/core/StlExportDefinitions.h inc/MantidPythonInterface/core/TypedValidatorExporter.h @@ -59,10 +61,13 @@ set(INC_FILES inc/MantidPythonInterface/core/Converters/WrapWithNDArray.h inc/MantidPythonInterface/core/Policies/MatrixToNumpy.h inc/MantidPythonInterface/core/Policies/RemoveConst.h - inc/MantidPythonInterface/core/Policies/ToWeakPtr.h inc/MantidPythonInterface/core/Policies/VectorToNumpy.h ) +# Add a precompiled header where they are supported +enable_precompiled_headers(inc/MantidPythonInterface/core/PrecompiledHeader.h SRC_FILES) + + # Add the target for this directory set(_target_name PythonInterfaceCore) add_library(${_target_name} ${SRC_FILES} ${INC_FILES}) @@ -75,11 +80,15 @@ set_target_properties( COMPILE_DEFINITIONS IN_MANTID_PYTHONINTERFACE_CORE ) +set(PUBLIC_TARGETS Python::NumPy ${BoostPython_LIBRARIES}) +if(USE_PYTHON_DYNAMIC_LIB) + list(APPEND PUBLIC_TARGETS Python::Python) +endif() # Dependencies target_link_libraries( ${_target_name} - PUBLIC Python::Python Python::NumPy ${BoostPython_LIBRARIES} - PRIVATE Types Kernel ${Boost_LIBRARIES} + PUBLIC ${PUBLIC_TARGETS} + PRIVATE Types Kernel ${Boost_LIBRARIES} ${POCO_LIBRARIES} ) # Add to the 'Framework/Python' group in MSVS diff --git a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/DataServiceExporter.h b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/DataServiceExporter.h index 8b6f4a87d6b1cd59fee9075645f5c79c76f2ff11..1b3391900d8958d126b5c0612f026dc7ed5941c9 100644 --- a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/DataServiceExporter.h +++ b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/DataServiceExporter.h @@ -6,6 +6,7 @@ // SPDX - License - Identifier: GPL - 3.0 + #pragma once +#include "MantidKernel/DataService.h" #include "MantidKernel/Exception.h" #include "MantidPythonInterface/core/WeakPtr.h" @@ -71,7 +72,7 @@ template struct DataServiceExporter { .def("size", &SvcType::size, arg("self"), "Returns the number of objects within the service") .def("getObjectNames", &DataServiceExporter::getObjectNamesAsList, - arg("self"), + (arg("self"), arg("contain") = ""), "Return the list of names currently known to the ADS") // Make it act like a dictionary @@ -159,11 +160,16 @@ template struct DataServiceExporter { * Return a Python list of object names from the ADS as this is * far easier to work with than a set * @param self :: A reference to the ADS object that called this method + * @param contain :: If provided, the function will return only names that + * contain this string * @returns A python list created from the set of strings */ - static boost::python::list getObjectNamesAsList(SvcType &self) { + static boost::python::list getObjectNamesAsList(SvcType &self, + const std::string &contain) { boost::python::list names; - const auto keys = self.getObjectNames(); + const auto keys = + self.getObjectNames(Mantid::Kernel::DataServiceSort::Unsorted, + Mantid::Kernel::DataServiceHidden::Auto, contain); for (auto itr = keys.begin(); itr != keys.end(); ++itr) { names.append(*itr); } diff --git a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/Policies/ToWeakPtr.h b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/Policies/ToWeakPtr.h deleted file mode 100644 index 9633c585a5cb60d132d977ff276361d370dab20f..0000000000000000000000000000000000000000 --- a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/Policies/ToWeakPtr.h +++ /dev/null @@ -1,74 +0,0 @@ -// Mantid Repository : https://github.com/mantidproject/mantid -// -// Copyright © 2015 ISIS Rutherford Appleton Laboratory UKRI, -// NScD Oak Ridge National Laboratory, European Spallation Source, -// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS -// SPDX - License - Identifier: GPL - 3.0 + -#pragma once - -#include -#include - -#include - -namespace Mantid { -namespace PythonInterface { -namespace Policies { - -namespace { -//----------------------------------------------------------------------- -// MPL helper structs -//----------------------------------------------------------------------- -/// MPL struct to figure out if a type is a std::shared_ptr -/// The general one inherits from boost::false_type -template struct IsSharedPtr : boost::false_type {}; - -/// Specialization for std::shared_ptr types to inherit from -/// boost::true_type -template -struct IsSharedPtr> : boost::true_type {}; - -//----------------------------------------------------------------------- -// Policy implementation -//----------------------------------------------------------------------- -/** - * Constructs a std::weak_ptr around the incoming std::shared_ptr - */ -template struct ToWeakPtrImpl { - // Useful types - using PointeeType = typename ArgType::element_type; - using WeakPtr = std::weak_ptr; - - inline PyObject *operator()(const ArgType &p) const { - if (!p) - Py_RETURN_NONE; - return boost::python::to_python_value()(WeakPtr(p)); - } - - inline PyTypeObject const *get_pytype() const { - return boost::python::converter::registered::converters - .to_python_target_type(); - } -}; - -//----------------------------------------------------------------------- -// Error handler -//----------------------------------------------------------------------- -template struct ToWeakPtr_Requires_Shared_Ptr_Return_Value {}; -} // namespace - -/** - * Implements the ToWeakPtr policy as required by boost.python - */ -struct ToWeakPtr { - template struct apply { - // Deduce if type is correct for policy - using type = typename boost::mpl::if_c< - IsSharedPtr::value, ToWeakPtrImpl, - ToWeakPtr_Requires_Shared_Ptr_Return_Value>::type; - }; -}; - -} // namespace Policies -} // namespace PythonInterface -} // namespace Mantid diff --git a/qt/paraview_ext/VatesAPI/test/PrecompiledHeader.h b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/PrecompiledHeader.h similarity index 57% rename from qt/paraview_ext/VatesAPI/test/PrecompiledHeader.h rename to Framework/PythonInterface/core/inc/MantidPythonInterface/core/PrecompiledHeader.h index e6fdf6551cbda585cc789772079a0760cddd669b..84f7fda9105d8329fcc089a2a0c952f2b4293d9f 100644 --- a/qt/paraview_ext/VatesAPI/test/PrecompiledHeader.h +++ b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/PrecompiledHeader.h @@ -1,15 +1,16 @@ // Mantid Repository : https://github.com/mantidproject/mantid // -// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI, // NScD Oak Ridge National Laboratory, European Spallation Source, // Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS // SPDX - License - Identifier: GPL - 3.0 + #pragma once -// cxxtest -#include - // STL -#include +#include #include -#include + +#include +#include +#include +#include diff --git a/Framework/PythonInterface/core/inc/MantidPythonInterface/core/PythonStdoutChannel.h b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/PythonStdoutChannel.h new file mode 100644 index 0000000000000000000000000000000000000000..1f8716948382357eb1ccdb1ee59cd822e5627efe --- /dev/null +++ b/Framework/PythonInterface/core/inc/MantidPythonInterface/core/PythonStdoutChannel.h @@ -0,0 +1,31 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +// +// PythonStdoutChannel.h +// +// Similar to console channel for logging. The output is on std::cout instead of +// std::clog (which is the same as std::cerr) +// Usage: use in it Mantid.properties or mantid.user.properties instead of +// ConsoleChannel class +// +// +// + +#pragma once + +#include "MantidPythonInterface/core/DllConfig.h" +#include + +namespace Poco { + +class MANTID_PYTHONINTERFACE_CORE_DLL PythonStdoutChannel + : public ConsoleChannel { +public: + /// Constructor for PythonStdoutChannel + PythonStdoutChannel(); +}; +} // namespace Poco diff --git a/Framework/PythonInterface/core/src/PythonStdoutChannel.cpp b/Framework/PythonInterface/core/src/PythonStdoutChannel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..daf0bf7c4affd03c0a4d16618b8891722c35655b --- /dev/null +++ b/Framework/PythonInterface/core/src/PythonStdoutChannel.cpp @@ -0,0 +1,40 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidPythonInterface/core/PythonStdoutChannel.h" +#include "MantidPythonInterface/core/WrapPython.h" + +#include // streamsize + +#include +#include // sink_tag +#include + +namespace { // anonymous namespace +class PyStdoutSink { +public: + typedef char char_type; + typedef boost::iostreams::sink_tag category; + + std::streamsize write(const char *s, std::streamsize n) { + // PySys_WriteStdout truncates to 1000 chars + static const std::streamsize MAXSIZE = 1000; + + std::streamsize written = std::min(n, MAXSIZE); + PySys_WriteStdout((boost::format("%%.%1%s") % written).str().c_str(), s); + + return written; + } +}; + +// wrapper of that sink to be a stream +boost::iostreams::stream PyStdout; + +} // anonymous namespace + +namespace Poco { +PythonStdoutChannel::PythonStdoutChannel() : ConsoleChannel(PyStdout) {} +} // namespace Poco diff --git a/Framework/PythonInterface/mantid/api/CMakeLists.txt b/Framework/PythonInterface/mantid/api/CMakeLists.txt index ccf49041c0bbeeee9bce2ec98264ed8e99ad9c20..e71a8195ad4de4403a9111604520146450280238 100644 --- a/Framework/PythonInterface/mantid/api/CMakeLists.txt +++ b/Framework/PythonInterface/mantid/api/CMakeLists.txt @@ -120,10 +120,14 @@ set(INC_FILES inc/MantidPythonInterface/api/WorkspacePropertyExporter.h ) +# Add a precompiled header where they are supported +list (APPEND ALL_FILES ${EXPORT_FILES} ${SRC_FILES}) +enable_precompiled_headers(inc/MantidPythonInterface/api/PrecompiledHeader.h ALL_FILES) + # Create the target for this directory add_library( - PythonAPIModule ${EXPORT_FILES} ${MODULE_DEFINITION} ${SRC_FILES} + PythonAPIModule ${ALL_FILES} ${MODULE_DEFINITION} ${INC_FILES} ${PYTHON_INSTALL_FILES} ) set_python_properties(PythonAPIModule _api) diff --git a/Framework/PythonInterface/mantid/api/_aliases.py b/Framework/PythonInterface/mantid/api/_aliases.py index 274e005b7a7bd761e1e1c600a371425d7a4cafb0..2b55a09fdcc0c89a04876394d8539f44360e7a2d 100644 --- a/Framework/PythonInterface/mantid/api/_aliases.py +++ b/Framework/PythonInterface/mantid/api/_aliases.py @@ -23,7 +23,7 @@ from mantid.kernel._aliases import lazy_instance_access # delete the python objects. # If you see a segfault late in a python process related to the GIL # it is likely an exit handler is missing. -AnalysisDataService = lazy_instance_access(AnalysisDataServiceImpl) +AnalysisDataService = lazy_instance_access(AnalysisDataServiceImpl, key_as_str=True) AlgorithmFactory = lazy_instance_access(AlgorithmFactoryImpl) AlgorithmManager = lazy_instance_access(AlgorithmManagerImpl) FileFinder = lazy_instance_access(FileFinderImpl) diff --git a/Framework/PythonInterface/mantid/api/_workspaceops.py b/Framework/PythonInterface/mantid/api/_workspaceops.py index 5982f54ae6009fbdd29e3b814b2d37b54be9119b..5f37e5819a926da2b54a1c0810ad6f3c2de66a57 100644 --- a/Framework/PythonInterface/mantid/api/_workspaceops.py +++ b/Framework/PythonInterface/mantid/api/_workspaceops.py @@ -53,12 +53,8 @@ def attach_binary_operators_to_workspace(): "And": "__and__", "Xor": "__xor__" } - # The division operator changed in Python 3 + divops = ["__truediv__", "__rtruediv__", "__itruediv__"] - if sys.version_info[0] < 3: - # For Python 2 add the older methods so that modules without __future__ - # still work - divops.extend(["__div__", "__rdiv__", "__idiv__"]) operations["Divide"] = divops # Loop through and add each one in turn diff --git a/Framework/PythonInterface/mantid/api/inc/MantidPythonInterface/api/PrecompiledHeader.h b/Framework/PythonInterface/mantid/api/inc/MantidPythonInterface/api/PrecompiledHeader.h new file mode 100644 index 0000000000000000000000000000000000000000..3b2cbe3c0a7ac2c5b77bb04fe4b356bdf6716075 --- /dev/null +++ b/Framework/PythonInterface/mantid/api/inc/MantidPythonInterface/api/PrecompiledHeader.h @@ -0,0 +1,30 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidAPI/Algorithm.h" + +// STL + +// Boost +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp index 8225c53d8f8573a49e5d76f56b325b1dc0794d9d..6f8318c6adf5b4462524c274348b375bbb17bf6d 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/IFunction.cpp @@ -46,13 +46,6 @@ PyObject *getCategories(IFunction &self) { return registered; } -/** - * Return fitting error for a parameter given its name. - */ -double getError(IFunction &self, std::string const &name) { - return self.getError(self.parameterIndex(name)); -} - // -- Set property overloads -- // setProperty(index,value,explicit) using setParameterType1 = void (IFunction::*)(size_t, const double &, bool); @@ -63,11 +56,26 @@ GNU_DIAG_OFF("conversion") BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(setParameterType1_Overloads, setParameter, 2, 3) -// setProperty(index,value,explicit) +// setProperty(name,value,explicit) using setParameterType2 = void (IFunction::*)(const std::string &, const double &, bool); BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(setParameterType2_Overloads, setParameter, 2, 3) + +// setError(index,value) +using setErrorType1 = void (IFunction::*)(size_t, double); +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(setErrorType1_Overloads, setError, 2, 2) +// setError(name,value) +using setErrorType2 = void (IFunction::*)(const std::string &, double); +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(setErrorType2_Overloads, setError, 2, 2) + +// getError(index) +using getErrorType1 = double (IFunction::*)(size_t) const; +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(getErrorType1_Overloads, getError, 1, 1) +// getError(name) +using getErrorType2 = double (IFunction::*)(const std::string &) const; +BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(getErrorType2_Overloads, getError, 1, 1) + BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(tie_Overloads, tie, 2, 3) BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(addConstraints_Overloads, addConstraints, 1, 2) @@ -147,15 +155,29 @@ void export_IFunction() { (arg("self"), arg("i"), arg("value"), arg("explicitlySet")), "Sets the value of the ith parameter")) - .def("setError", &IFunction::setError, - (args("self"), args("i"), args("err")), - "Sets the error on parameter index i") - .def("setParameter", (setParameterType2)&IFunction::setParameter, setParameterType2_Overloads( (arg("self"), arg("name"), arg("value"), arg("explicitlySet")), "Sets the value of the named parameter")) + .def("setError", (setErrorType1)&IFunction::setError, + setErrorType1_Overloads((args("self"), arg("index"), args("err")), + "Sets the error on the indexed parameter")) + + .def("setError", (setErrorType2)&IFunction::setError, + setErrorType2_Overloads((args("self"), arg("name"), args("err")), + "Sets the error on the named parameter")) + + .def("getError", (getErrorType1)&IFunction::getError, + getErrorType1_Overloads( + (arg("self"), arg("index")), + "Return fitting error of the index parameter")) + + .def("getError", (getErrorType2)&IFunction::getError, + getErrorType2_Overloads( + (arg("self"), arg("name")), + "Return fitting error of the named parameter")) + .def("__setitem__", (setParameterType2)&IFunction::setParameter, setParameterType2_Overloads( (arg("self"), arg("name"), arg("value"), arg("explicitlySet")), @@ -272,10 +294,6 @@ void export_IFunction() { .def("getParamValue", (double (IFunction::*)(std::size_t) const) & IFunction::getParameter, (arg("self"), arg("i")), "Get the value of the ith parameter") - .def("getError", &IFunction::getError, (arg("self"), arg("i")), - "Return fitting error of the ith parameter") - .def("getError", &getError, (arg("self"), arg("name")), - "Return fitting error of the named parameter") //-- Python special methods -- .def("__repr__", &IFunction::asString, arg("self"), diff --git a/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp index 50ba196911705f402686e160a81d2f29d380b04d..4e1834396bb283b7d5911c381aae2bb90c5504f0 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/IPeaksWorkspace.cpp @@ -239,15 +239,8 @@ IPeaksWorkspaceIterator makePyIterator(IPeaksWorkspace &self) { void export_IPeaksWorkspaceIterator() { class_("IPeaksWorkspaceIterator", no_init) - .def( -#if PY_VERSION_HEX >= 0x03000000 - "__next__" -#else - "next" -#endif - , - &IPeaksWorkspaceIterator::next, - return_value_policy()) + .def("__next__", &IPeaksWorkspaceIterator::next, + return_value_policy()) .def("__iter__", objects::identity_function()); } diff --git a/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp b/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp index c36db907799054cc3b1195ba634df8642014da28..3fcc06583034db1fa4f0e3fa088af0d473284916 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/MatrixWorkspace.cpp @@ -133,7 +133,7 @@ list getSpectrumNumbers(const MatrixWorkspace &self) { const auto &spectrumNums = self.indexInfo().spectrumNumbers(); list spectra; - for (const auto index : spectrumNums) { + for (const auto &index : spectrumNums) { spectra.append(static_cast(index)); } @@ -184,18 +184,6 @@ void setDxFromPyObject(MatrixWorkspace &self, const size_t wsIndex, setSpectrumFromPyObject(self, &MatrixWorkspace::dataDx, wsIndex, values); } -/** - * Adds a deprecation warning to the getNumberBins call to warn about using - * blocksize instead - * @param self A reference to the calling object - * @returns The blocksize() - */ -size_t getNumberBinsDeprecated(MatrixWorkspace &self) { - PyErr_Warn(PyExc_DeprecationWarning, - "``getNumberBins`` is deprecated, use ``blocksize`` instead."); - return self.blocksize(); -} - /** * Adds a deprecation warning to the getSampleDetails call to warn about using * getRun instead @@ -208,6 +196,18 @@ Mantid::API::Run &getSampleDetailsDeprecated(MatrixWorkspace &self) { return self.mutableRun(); } +/** + * Adds a deprecation warning to the getNumberBins call to warn about using + * blocksize instead + * @param self A reference to the calling object + * @returns The blocksize() + */ +std::size_t getNumberBinsDeprecated(MatrixWorkspace &self) { + PyErr_Warn(PyExc_DeprecationWarning, + "``getNumberBins`` is deprecated, use ``blocksize`` instead."); + return self.blocksize(); +} + /** * Adds a deprecation warning to the binIndexOf call to warn about using * yIndexOfX instead @@ -250,11 +250,12 @@ std::vector maskedBinsIndices(MatrixWorkspace &self, const int i) { * Raw Pointer wrapper of replaceAxis to allow it to work with python * @param self * @param axisIndex :: The index of the axis to replace - * @param newAxis :: A pointer to the new axis. The class will take ownership. + * @param newAxis :: A pointer to the new axis. The class will take ownership of + * its clone. */ void pythonReplaceAxis(MatrixWorkspace &self, const std::size_t &axisIndex, Axis *newAxis) { - self.replaceAxis(axisIndex, std::unique_ptr(newAxis)); + self.replaceAxis(axisIndex, std::unique_ptr(newAxis->clone(&self))); } /** @@ -343,8 +344,22 @@ void export_MatrixWorkspace() { boost::noncopyable>("MatrixWorkspace", no_init) //--------------------------------------- Meta information //----------------------------------------------------------------------- + .def("isRaggedWorkspace", &MatrixWorkspace::isRaggedWorkspace, + arg("self"), + "Returns true if the workspace is ragged (has differently sized " + "spectra).") .def("blocksize", &MatrixWorkspace::blocksize, arg("self"), "Returns size of the Y data array") + .def("getNumberBins", &MatrixWorkspace::getNumberBins, + (arg("self"), arg("index")), + "Returns the number of bins for a given histogram index.") + .def("getNumberBins", &getNumberBinsDeprecated, arg("self"), + "Returns size of the Y data array (deprecated, use " + ":class:`~mantid.api.MatrixWorkspace.blocksize` " + "instead)") + .def("getMaxNumberBins", &MatrixWorkspace::getMaxNumberBins, arg("self"), + "Returns the maximum number of bins in a workspace (works on ragged " + "data).") .def("getNumberHistograms", &MatrixWorkspace::getNumberHistograms, arg("self"), "Returns the number of spectra in the workspace") .def("getSpectrumNumbers", &getSpectrumNumbers, arg("self"), @@ -419,11 +434,6 @@ void export_MatrixWorkspace() { "Find first index in Y equal to value. Start may be specified to " "begin at a specifc index. Returns tuple with the " "histogram and bin indices.") - // Deprecated - .def("getNumberBins", &getNumberBinsDeprecated, arg("self"), - "Returns size of the Y data array (deprecated, use " - ":class:`~mantid.api.MatrixWorkspace.blocksize` " - "instead)") .def("getSampleDetails", &getSampleDetailsDeprecated, arg("self"), return_internal_reference<>(), "Return the Run object for this workspace (deprecated, use " @@ -450,7 +460,8 @@ void export_MatrixWorkspace() { "the bin-width.") .def("replaceAxis", &pythonReplaceAxis, (arg("self"), arg("axisIndex"), arg("newAxis")), - "Replaces one of the workspace's axes with the new one provided.") + "Replaces one of the workspace's axes with the new one provided. " + "The axis is cloned.") .def("applyBinEdgesFromAnotherWorkspace", &applyBinEdgesFromAnotherWorkspace, (arg("self"), arg("ws"), arg("getIndex"), arg("setIndex")), diff --git a/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp b/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp index 2611888eafff42035e9f6ab71b47992b368e7ce5..11899352b527789747cd6dedc19c3c84abbba1db 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/Run.cpp @@ -227,10 +227,15 @@ void export_Run() { "getProperties.") .def("getGoniometer", - (const Mantid::Geometry::Goniometer &(Run::*)() const) & + (const Mantid::Geometry::Goniometer &(Run::*)(const size_t) const) & Run::getGoniometer, - arg("self"), return_value_policy(), - "Return the Goniometer object associated with this run.") + (arg("self"), arg("index") = 0), + return_value_policy(), + "Return Goniometer object associated with this run by index, " + "default first goniometer.") + + .def("getNumGoniometers", &Run::getNumGoniometers, arg("self"), + "Return the number of goniometer objects associated with this run.") .def("addProperty", &addProperty, (arg("self"), arg("name"), arg("value"), arg("replace")), diff --git a/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp b/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp index 7fa61b02f0e49c219509f0d769ea6bed6854ec9f..e8859ee7e5716bb7360f5df8ffee5c90acd976a9 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/Sample.cpp @@ -79,6 +79,8 @@ void export_Sample() { .def("getShape", &Sample::getShape, arg("self"), "Returns a shape of a Sample object.", return_value_policy()) + .def("hasEnvironment", &Sample::hasEnvironment, arg("self"), + "Returns True if the sample has an environment defined") // -------------------------Operators // ------------------------------------- .def("__len__", &Sample::size, arg("self"), diff --git a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoPythonIterator.cpp b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoPythonIterator.cpp index ab13eb7dd092b63c8f11826d044a4d1652fed303..1a616e7ff869c9a3f3a95d5579993ae14252f696 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoPythonIterator.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/SpectrumInfoPythonIterator.cpp @@ -20,15 +20,8 @@ void export_SpectrumInfoPythonIterator() { // Export to Python class_("SpectrumInfoPythonIterator", no_init) .def("__iter__", objects::identity_function()) - .def( -#if PY_VERSION_HEX >= 0x03000000 - "__next__" -#else - "next" -#endif - , - &SpectrumInfoPythonIterator::next, - return_value_policy()); + .def("__next__", &SpectrumInfoPythonIterator::next, + return_value_policy()); /* Return value policy for next is to copy the const reference. Copy by value is essential for python 2.0 compatibility because items (SpectrumInfoItem) will diff --git a/Framework/PythonInterface/mantid/api/src/Exports/WorkspaceGroup.cpp b/Framework/PythonInterface/mantid/api/src/Exports/WorkspaceGroup.cpp index ef4493b2126b15df977b88f31ba898162831d23a..b92a7a16149c38b6ae793ce44400ab421da7de54 100644 --- a/Framework/PythonInterface/mantid/api/src/Exports/WorkspaceGroup.cpp +++ b/Framework/PythonInterface/mantid/api/src/Exports/WorkspaceGroup.cpp @@ -6,10 +6,10 @@ // SPDX - License - Identifier: GPL - 3.0 + #include "MantidAPI/WorkspaceGroup.h" #include "MantidAPI/AnalysisDataService.h" +#include "MantidAPI/WorkspaceGroup_fwd.h" #include "MantidPythonInterface/api/RegisterWorkspacePtrToPython.h" #include "MantidPythonInterface/core/DataServiceExporter.h" #include "MantidPythonInterface/core/GetPointer.h" -#include "MantidPythonInterface/core/Policies/ToWeakPtr.h" #include #include @@ -21,6 +21,20 @@ using namespace Mantid::API; using namespace Mantid::PythonInterface; using namespace boost::python; +namespace { + +PyObject *convertWsToObj(Workspace_sptr ws) { + if (Mantid ::API::AnalysisDataService::Instance().doesExist(ws->getName())) { + // Decay to weak ptr so the ADS manages lifetime + using PtrT = std::weak_ptr; + PtrT weak_ptr = ws; + return boost::python::to_python_value()(weak_ptr); + } else { + return boost::python::to_python_value()(ws); + } +} +} // namespace + GET_POINTER_SPECIALIZATION(WorkspaceGroup) /** @@ -57,13 +71,13 @@ void addWorkspace(WorkspaceGroup &self, const boost::python::object &pyobj) { Workspace_sptr>::extractCppValue(pyobj)); } -Workspace_sptr getItem(WorkspaceGroup &self, const int &index) { +PyObject *getItem(WorkspaceGroup &self, const int &index) { if (index < 0) { if (static_cast(-index) > self.size()) self.throwIndexOutOfRangeError(index); - return self.getItem(self.size() + index); + return convertWsToObj(self.getItem(self.size() + index)); } - return self.getItem(index); + return convertWsToObj(self.getItem(index)); } void export_WorkspaceGroup() { @@ -90,7 +104,6 @@ void export_WorkspaceGroup() { .def("remove", &WorkspaceGroup::remove, (arg("self"), arg("workspace_name")), "Remove a name from the group") .def("getItem", getItem, (arg("self"), arg("index")), - return_value_policy(), "Returns the item at the given index") .def("isMultiPeriod", &WorkspaceGroup::isMultiperiod, arg("self"), "Returns true if the workspace group is multi-period") @@ -102,8 +115,7 @@ void export_WorkspaceGroup() { WorkspaceGroup::contains, (arg("self"), arg("workspace name")), "Does this group contain the named workspace?") - .def("__getitem__", getItem, (arg("self"), arg("index")), - return_value_policy()) + .def("__getitem__", getItem, (arg("self"), arg("index"))) .def("__iter__", range>( &group_begin, &group_end)); diff --git a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt index dbbb45e11435e7ed83573a750054a6ebfb56c561..7a1a5a6b45fb41c2075fbd0990967e2ef2e2816b 100644 --- a/Framework/PythonInterface/mantid/geometry/CMakeLists.txt +++ b/Framework/PythonInterface/mantid/geometry/CMakeLists.txt @@ -58,9 +58,13 @@ set(INC_FILES inc/MantidPythonInterface/geometry/ComponentInfoPythonIterator.h inc/MantidPythonInterface/geometry/DetectorInfoPythonIterator.h ) +# Add a precompiled header where they are supported +list (APPEND ALL_FILES ${EXPORT_FILES} ${SRC_FILES}) +enable_precompiled_headers(inc/MantidPythonInterface/geometry/PrecompiledHeader.h ALL_FILES) + # Create the target for this directory add_library( - PythonGeometryModule ${EXPORT_FILES} ${MODULE_DEFINITION} ${SRC_FILES} + PythonGeometryModule ${ALL_FILES} ${MODULE_DEFINITION} ${INC_FILES} ${PYTHON_INSTALL_FILES} ) set_python_properties(PythonGeometryModule _geometry) diff --git a/Framework/PythonInterface/mantid/geometry/inc/MantidPythonInterface/geometry/PrecompiledHeader.h b/Framework/PythonInterface/mantid/geometry/inc/MantidPythonInterface/geometry/PrecompiledHeader.h new file mode 100644 index 0000000000000000000000000000000000000000..a970baa18011a14f7d828f0b8664e2fc7f6134e0 --- /dev/null +++ b/Framework/PythonInterface/mantid/geometry/inc/MantidPythonInterface/geometry/PrecompiledHeader.h @@ -0,0 +1,21 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2021 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +// STL + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp index b57912af6f0d76060730b36fb00d4120d73533e1..87af8b02577c69423ca40e435528c3dca4c240cf 100644 --- a/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp +++ b/Framework/PythonInterface/mantid/geometry/src/Exports/ComponentInfo.cpp @@ -99,9 +99,19 @@ void export_ComponentInfo() { .def("hasSource", &ComponentInfo::hasSource, arg("self"), "Returns True if a source is present.") + .def("hasEquivalentSource", &ComponentInfo::hasEquivalentSource, + arg("self"), arg("other"), + "Returns True is both beamlines either lack a Source or " + "have a Source at the same position.") + .def("hasSample", &ComponentInfo::hasSample, arg("self"), "Returns True if a sample is present.") + .def("hasEquivalentSample", &ComponentInfo::hasEquivalentSample, + arg("self"), arg("other"), + "Returns True is both beamlines either lack a Sample or " + "have a Sample at the same position.") + .def("source", &ComponentInfo::source, arg("self"), "Returns the source component index.") diff --git a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt index 96583ed97913e0354d923e560007576439c28f2a..1a945d99a7d423a77e0c64fff468c95cc4b6966d 100644 --- a/Framework/PythonInterface/mantid/kernel/CMakeLists.txt +++ b/Framework/PythonInterface/mantid/kernel/CMakeLists.txt @@ -61,6 +61,7 @@ set(EXPORT_FILES src/Exports/StringContainsValidator.cpp src/Exports/PropertyFactory.cpp src/Exports/RebinParamsValidator.cpp + src/Exports/PhysicalConstants.cpp ) set(MODULE_DEFINITION ${CMAKE_CURRENT_BINARY_DIR}/kernel.cpp) @@ -110,7 +111,7 @@ configure_file( ) # Package version -if(WIN32 OR (APPLE AND ENABLE_MANTIDPLOT)) +if(WIN32) # NeXus library is in the same place relative to the Python library get_filename_component(NEXUSLIB_FILE ${NEXUSLIB} NAME) set(NEXUSLIB ../../${NEXUSLIB_FILE}) @@ -124,9 +125,13 @@ configure_file( ${CMAKE_CURRENT_BINARY_DIR}/${MPISETUP_PY}.install.py ) +# Add a precompiled header where they are supported +list (APPEND ALL_FILES ${EXPORT_FILES} ${SRC_FILES}) +enable_precompiled_headers(inc/MantidPythonInterface/kernel/Registry/PrecompiledHeader.h ALL_FILES) + # Create the target for this directory add_library( - PythonKernelModule ${EXPORT_FILES} ${MODULE_DEFINITION} ${SRC_FILES} + PythonKernelModule ${ALL_FILES} ${MODULE_DEFINITION} ${INC_FILES} ${PYTHON_INSTALL_FILES} ) set_python_properties(PythonKernelModule _kernel) diff --git a/Framework/PythonInterface/mantid/kernel/_aliases.py b/Framework/PythonInterface/mantid/kernel/_aliases.py index a12a32b7c330b79fde96b08bb467f60b51dbbc2a..6385c6deae2138c84f3cda9e85b433972bd0810f 100644 --- a/Framework/PythonInterface/mantid/kernel/_aliases.py +++ b/Framework/PythonInterface/mantid/kernel/_aliases.py @@ -13,7 +13,7 @@ from mantid.kernel import (ConfigServiceImpl, Logger, PropertyManagerDataServiceImpl, UnitFactoryImpl, UsageServiceImpl) -def lazy_instance_access(cls): +def lazy_instance_access(cls, key_as_str=False): """ Takes a singleton class and wraps it in an LazySingletonHolder that constructs the instance on first access. @@ -26,6 +26,9 @@ def lazy_instance_access(cls): :param cls: The singleton class type + :param key_as_str: When ``True``, the key supplied to ``__getitem__``, ``__setitem__``, + ``__delitem__``, and ``__contains__`` will be converted to a str. ``None`` wil return ``False`` + for ``__contains__`` :return: A new LazySingletonHolder wrapping cls """ @@ -44,16 +47,39 @@ def lazy_instance_access(cls): return cls.__getattribute__(cls.Instance(), "__len__")() def __getitem__(self, item): + if key_as_str: + if item is None: + raise KeyError(item) + else: + cls.__getattribute__(cls.Instance(), "__getitem__")(str(item)) return cls.__getattribute__(cls.Instance(), "__getitem__")(item) def __setitem__(self, item, value): + if key_as_str: + if item is None: + raise KeyError(item) + else: + return cls.__getattribute__(cls.Instance(), "__setitem__")(str(item), value) return cls.__getattribute__(cls.Instance(), "__setitem__")(item, value) def __delitem__(self, item): + if key_as_str: + if item is None: + raise KeyError(item) + else: + return cls.__getattribute__(cls.Instance(), "__delitem__")(str(item)) return cls.__getattribute__(cls.Instance(), "__delitem__")(item) def __contains__(self, item): - return cls.__getattribute__(cls.Instance(), "__contains__")(item) + if key_as_str: + # check for things that evaluate to False + if (item is None) or (not bool(item)): + return False + else: + # convert the ``item`` to a string + return cls.__getattribute__(cls.Instance(), "__contains__")(str(item)) + else: + return cls.__getattribute__(cls.Instance(), "__contains__")(item) return LazySingletonHolder() diff --git a/Framework/PythonInterface/mantid/kernel/funcinspect.py b/Framework/PythonInterface/mantid/kernel/funcinspect.py index 6f06b35533e10b0f00407b2ec94165b250b0346f..86c7aeb1bf0031c18b6b4e6a8454d921e88b2253 100644 --- a/Framework/PythonInterface/mantid/kernel/funcinspect.py +++ b/Framework/PythonInterface/mantid/kernel/funcinspect.py @@ -274,14 +274,14 @@ def process_frame(frame): # put this in a loop and stack the results in an array. count = 0 max_returns = 0 # Must count the max_returns ourselves in this case - while count < len(ins_stack[call_function_locs[i][0]:call_function_locs[i][1]]): - (offset_, op_, name_, argument_, argvalue_) = ins[call_function_locs[i][0]+count] + while count < len(ins_stack[call_function_locs[last_i][0]:call_function_locs[last_i][1]]): + (offset_, op_, name_, argument_, argvalue_) = ins_stack[call_function_locs[last_i][0]+count] if name_ == 'UNPACK_SEQUENCE': # Many Return Values, One equal sign hold = [] if argvalue_ > max_returns: max_returns = argvalue_ for index in range(argvalue_): - (_offset_, _op_, _name_, _argument_, _argvalue_) = ins[call_function_locs[i][0] + count+1+index] + (_offset_, _op_, _name_, _argument_, _argvalue_) = ins_stack[call_function_locs[last_i][0] + count+1+index] hold.append(_argvalue_) count = count + argvalue_ output_var_names.append(hold) diff --git a/Framework/PythonInterface/mantid/kernel/inc/MantidPythonInterface/kernel/Registry/PrecompiledHeader.h b/Framework/PythonInterface/mantid/kernel/inc/MantidPythonInterface/kernel/Registry/PrecompiledHeader.h new file mode 100644 index 0000000000000000000000000000000000000000..19ebe4695dd7dd3e7e6afabfadf71f9bb1305a56 --- /dev/null +++ b/Framework/PythonInterface/mantid/kernel/inc/MantidPythonInterface/kernel/Registry/PrecompiledHeader.h @@ -0,0 +1,25 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + +#pragma once + +#include "MantidKernel/DllConfig.h" + +// STL +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/Framework/PythonInterface/mantid/kernel/src/Exports/PhysicalConstants.cpp b/Framework/PythonInterface/mantid/kernel/src/Exports/PhysicalConstants.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97bb0ab760716b76f0ea13661fe1c48afe52f823 --- /dev/null +++ b/Framework/PythonInterface/mantid/kernel/src/Exports/PhysicalConstants.cpp @@ -0,0 +1,43 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source, +// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +// SPDX - License - Identifier: GPL - 3.0 + + +#include "MantidKernel/PhysicalConstants.h" +#include + +namespace { +class PhysicalConstants {}; +} // namespace + +void export_PhysicalConstants() { + using namespace boost::python; + + class_("PhysicalConstants") + .def_readonly("N_A", &Mantid::PhysicalConstants::N_A) + .def_readonly("h", &Mantid::PhysicalConstants::h) + .def_readonly("h_bar", &Mantid::PhysicalConstants::h_bar) + .def_readonly("g", &Mantid::PhysicalConstants::g) + .def_readonly("NeutronMass", &Mantid::PhysicalConstants::NeutronMass) + .def_readonly("NeutronMassAMU", + &Mantid::PhysicalConstants::NeutronMassAMU) + .def_readonly("AtomicMassUnit", + &Mantid::PhysicalConstants::AtomicMassUnit) + .def_readonly("meV", &Mantid::PhysicalConstants::meV) + .def_readonly("meVtoWavenumber", + &Mantid::PhysicalConstants::meVtoWavenumber) + .def_readonly("meVtoFrequency", + &Mantid::PhysicalConstants::meVtoFrequency) + .def_readonly("meVtoKelvin", &Mantid::PhysicalConstants::meVtoKelvin) + .def_readonly("E_mev_toNeutronWavenumberSq", + &Mantid::PhysicalConstants::E_mev_toNeutronWavenumberSq) + .def_readonly("MuonLifetime", &Mantid::PhysicalConstants::MuonLifetime) + .def_readonly("StandardAtmosphere", + &Mantid::PhysicalConstants::StandardAtmosphere) + .def_readonly("BoltzmannConstant", + &Mantid::PhysicalConstants::BoltzmannConstant) + .def_readonly("MuonGyromagneticRatio", + &Mantid::PhysicalConstants::MuonGyromagneticRatio); +} \ No newline at end of file diff --git a/Framework/PythonInterface/mantid/kernel/src/kernel.cpp.in b/Framework/PythonInterface/mantid/kernel/src/kernel.cpp.in index 8237e4850be22aa3bf5809f00b5d89815212d11f..7ab970b671f2556d470da23fe364f2a8ea041392 100644 --- a/Framework/PythonInterface/mantid/kernel/src/kernel.cpp.in +++ b/Framework/PythonInterface/mantid/kernel/src/kernel.cpp.in @@ -14,11 +14,14 @@ #include #include #include +#include +#include #include "MantidKernel/MantidVersion.h" #include "MantidPythonInterface/core/NDArray.h" #include "MantidPythonInterface/kernel/Registry/TypeRegistry.h" #include "MantidPythonInterface/kernel/kernel.h" +#include "MantidPythonInterface/core/PythonStdoutChannel.h" // See // http://docs.scipy.org/doc/numpy/reference/c-api.array.html#PY_ARRAY_UNIQUE_SYMBOL @@ -37,37 +40,6 @@ namespace converter = boost::python::converter; // Forward declare @EXPORT_DECLARE@ -#if PY_VERSION_HEX < 0x03000000 - /** - * Adds a from_python converter to converter unicode strings to std::string - * by encoding them in utf-8 if possible. Adapted from - * https://www.boost.org/doc/libs/1_68_0/libs/python/doc/html/faq/how_can_i_automatically_convert_.html - */ -struct from_unicode_py2 { - from_unicode_py2() { - converter::registry::push_back(&convertible, &construct, - type_id()); - } - static void *convertible(PyObject *obj_ptr) { - return (PyUnicode_Check(obj_ptr)) ? obj_ptr : 0; - } - static void construct(PyObject *obj_ptr, - converter::rvalue_from_python_stage1_data *data) { - PyObject *pyutf8 = PyUnicode_AsUTF8String(obj_ptr); - if (pyutf8 == 0) - boost::python::throw_error_already_set(); - const char *value = PyString_AsString(pyutf8); - if (value == 0) - boost::python::throw_error_already_set(); - using rvalue_from_python_std_string = converter::rvalue_from_python_storage; - void *storage = reinterpret_cast(data)->storage.bytes; - new (storage) std::string(value); - data->convertible = storage; - Py_DECREF(pyutf8); // Must be deleted after the std::string has taken a copy - } -}; -#endif - BOOST_PYTHON_MODULE(_kernel) { // Doc string options - User defined, python arguments, C++ call signatures boost::python::docstring_options docstrings(true, true, false); @@ -75,6 +47,10 @@ BOOST_PYTHON_MODULE(_kernel) { Mantid::PythonInterface::importNumpy(); // Import numpy for this DLL _import_array(); + // register the python logger with poco + Poco::LoggingFactory::defaultFactory().registerChannelClass( + "PythonStdoutChannel", + new Poco::Instantiator); def("version_str", &Mantid::Kernel::MantidVersion::version, "Returns the Mantid version string in the form \"major.minor.patch\""); @@ -91,11 +67,6 @@ BOOST_PYTHON_MODULE(_kernel) { def("paper_citation", &Mantid::Kernel::MantidVersion::paperCitation, "Returns The citation for the Mantid paper"); -#if PY_VERSION_HEX < 0x03000000 - // accept unicode strings wherever we see std::string (as long as they can be - // encoded in utf8) - from_unicode_py2(); -#endif Mantid::PythonInterface::Registry::TypeRegistry::registerBuiltins(); @EXPORT_FUNCTIONS@ diff --git a/Framework/PythonInterface/mantid/plots/axesfunctions.py b/Framework/PythonInterface/mantid/plots/axesfunctions.py index d0f6acd8956956acc89539e320b81f58d6c5c16a..95af8b90049d2840ecd84d024e2e51c17ac827a1 100644 --- a/Framework/PythonInterface/mantid/plots/axesfunctions.py +++ b/Framework/PythonInterface/mantid/plots/axesfunctions.py @@ -113,9 +113,19 @@ def _get_data_for_plot(axes, workspace, kwargs, with_dy=False, with_dx=False): workspace_index, distribution, kwargs = get_wksp_index_dist_and_label( workspace, axis, **kwargs) if axis == MantidAxType.BIN: - # Overwrite any user specified xlabel - axes.set_xlabel("Spectrum") x, y, dy, dx = get_bins(workspace, workspace_index, with_dy) + vertical_axis = workspace.getAxis(1) + if isinstance(vertical_axis, mantid.api.NumericAxis): + axes.set_xlabel(vertical_axis.getUnit().unitID()) + values = vertical_axis.extractValues() + if isinstance(vertical_axis, mantid.api.BinEdgeAxis): + # for bin edge axis we have one more edge than content + values = (values[0:-1] + values[1:])/2. + x = values + if isinstance(vertical_axis, mantid.api.SpectraAxis): + spectrum_numbers = workspace.getSpectrumNumbers() + x = [spectrum_numbers[i] for i in x] + elif axis == MantidAxType.SPECTRUM: x, y, dy, dx = get_spectrum(workspace, workspace_index, normalize_by_bin_width, with_dy, with_dx) @@ -147,6 +157,10 @@ def _plot_impl(axes, workspace, args, kwargs): kwargs['drawstyle'] = 'steps-post' else: normalize_by_bin_width, kwargs = get_normalize_by_bin_width(workspace, axes, **kwargs) + # the get... function returns kwargs without 'normalize_by_bin_width', but it is needed in _get_data_for_plot to + # avoid reverting to the default norm setting, in the event that this function is being called as part of a plot + # restoration + kwargs['normalize_by_bin_width'] = normalize_by_bin_width x, y, _, _, indices, axis, kwargs = _get_data_for_plot(axes, workspace, kwargs) if kwargs.pop('update_axes_labels', True): _setLabels1D(axes, diff --git a/Framework/PythonInterface/mantid/plots/datafunctions.py b/Framework/PythonInterface/mantid/plots/datafunctions.py index 75615210441837c62bb84280dc213a059af29f2a..479d7672456e5e47f5892ca860ab3fdc1087c509 100644 --- a/Framework/PythonInterface/mantid/plots/datafunctions.py +++ b/Framework/PythonInterface/mantid/plots/datafunctions.py @@ -391,25 +391,27 @@ def get_bin_indices(workspace): return indices -def get_bins(workspace, wkspIndex, withDy=False): +def get_bins(workspace, bin_index, withDy=False): """ - Extract all the bins for a spectrum + Extract a requested bin from each spectrum :param workspace: a Workspace2D or an EventWorkspace - :param wkspIndex: workspace index + :param bin_index: the index of a bin :param withDy: if True, it will return the error in the "counts", otherwise None """ - bins = get_bin_indices(workspace) - y_values = [] + indices = get_bin_indices(workspace) + x_values, y_values = [], [] dy = [] if withDy else None - for bin_index in bins: - y_values.append(workspace.readY(int(bin_index))[wkspIndex]) - if withDy: - dy.append(workspace.readE(int(bin_index))[wkspIndex]) - + for row_index in indices: + y_data = workspace.readY(int(row_index)) + if bin_index < len(y_data): + x_values.append(row_index) + y_values.append(y_data[bin_index]) + if withDy: + dy.append(workspace.readE(int(row_index))[bin_index]) dx = None - return bins, y_values, dy, dx + return x_values, y_values, dy, dx def get_md_data2d_bin_bounds(workspace, normalization, indices=None, transpose=False): @@ -548,8 +550,7 @@ def _workspace_indices(y_bins, workspace): def _workspace_indices_maxpooling(y_bins, workspace): - from mantid.simpleapi import Integration - summed_spectra_workspace = Integration(workspace) + summed_spectra_workspace = _integrate_workspace(workspace) summed_spectra = summed_spectra_workspace.extractY() workspace_indices = [] for y_range in pairwise(y_bins): @@ -563,6 +564,19 @@ def _workspace_indices_maxpooling(y_bins, workspace): return workspace_indices +def _integrate_workspace(workspace): + from mantid.api import AlgorithmManager + integration = AlgorithmManager.createUnmanaged("Integration") + integration.initialize() + integration.setAlwaysStoreInADS(False) + integration.setLogging(False) + integration.setChild(True) + integration.setProperty("InputWorkspace", workspace) + integration.setProperty("OutputWorkspace", "__dummy") + integration.execute() + return integration.getProperty("OutputWorkspace").value + + def interpolate_y_data(workspace, x, y, normalize_by_bin_width, spectrum_info=None, maxpooling=False): workspace_indices = _workspace_indices_maxpooling(y, workspace) \ if maxpooling else _workspace_indices(y, workspace) diff --git a/Framework/PythonInterface/mantid/plots/legend.py b/Framework/PythonInterface/mantid/plots/legend.py index 30bc5b6e0c971a74fe28e416f22e26092492d247..95f58099f3b84d6d4014fcebd06c9b2658351a90 100644 --- a/Framework/PythonInterface/mantid/plots/legend.py +++ b/Framework/PythonInterface/mantid/plots/legend.py @@ -43,16 +43,10 @@ class LegendProperties(dict): props['visible'] = legend.get_visible() title = legend.get_title() - if sys.version_info[0] >= 3: - if isinstance(title.get_text(), str): - props['title'] = title.get_text() - else: - props['title'] = None + if isinstance(title.get_text(), str): + props['title'] = title.get_text() else: - if isinstance(title.get_text(), unicode): - props['title'] = title.get_text() - else: - props['title'] = None + props['title'] = None # For Matplotlib <3.2 we have to remove the 'None' string from the title # which is generated by set_text() in text.py diff --git a/Framework/PythonInterface/mantid/plots/mantidaxes.py b/Framework/PythonInterface/mantid/plots/mantidaxes.py index 5202346e5ccd33379a3da8ac73dc26a604bf9cbc..da103618ffdbf57d7077875c0a161d7e4d347b54 100644 --- a/Framework/PythonInterface/mantid/plots/mantidaxes.py +++ b/Framework/PythonInterface/mantid/plots/mantidaxes.py @@ -44,17 +44,25 @@ WATERFALL_XOFFSET_DEFAULT, WATERFALL_YOFFSET_DEFAULT = 10, 20 def plot_decorator(func): def wrapper(self, *args, **kwargs): func_value = func(self, *args, **kwargs) + func_name = func.__name__ # Saves saving it on array objects if datafunctions.validate_args(*args, **kwargs): # Fill out kwargs with the values of args kwargs["workspaces"] = args[0].name() - kwargs["function"] = func.__name__ + kwargs["function"] = func_name if 'wkspIndex' not in kwargs and 'specNum' not in kwargs: kwargs['specNum'] = MantidAxes.get_spec_number_or_bin(args[0], kwargs) if "cmap" in kwargs and isinstance(kwargs["cmap"], Colormap): kwargs["cmap"] = kwargs["cmap"].name self.creation_args.append(kwargs) + elif func_name == "axhline" or func_name == "axvline": + self.creation_args.append({ + 'function': func_name, + 'args': args, + 'kwargs': kwargs + }) + return func_value return wrapper @@ -203,6 +211,15 @@ class MantidAxes(Axes): return ads.retrieve(ws_name), ws_artists.spec_num raise ValueError("Artist: '{}' not tracked by axes.".format(artist)) + def get_artists_workspace_and_workspace_index(self, artist): + """Retrieve the workspace and spec num of the given artist""" + for ws_name, ws_artists_list in self.tracked_workspaces.items(): + for ws_artists in ws_artists_list: + for ws_artist in ws_artists._artists: + if artist == ws_artist: + return ads.retrieve(ws_name), ws_artists.workspace_index + raise ValueError("Artist: '{}' not tracked by axes.".format(artist)) + def get_artists_sample_log_plot_details(self, artist): """Retrieve the sample log plot details of the given artist""" for ws_name, ws_artists_list in self.tracked_workspaces.items(): @@ -442,9 +459,17 @@ class MantidAxes(Axes): kwargs['distribution'] = not self.get_artist_normalization_state(artist) workspace, spec_num = self.get_artists_workspace_and_spec_num(artist) + # deal with MDHisto workspace + if workspace.isMDHistoWorkspace(): + # the MDHisto does not have the distribution concept. + # This is available only for Workspace2D + if 'distribution' in kwargs.keys(): + del kwargs['distribution'] # check if it is a sample log plot - if spec_num is None: + elif spec_num is None: sample_log_plot_details = self.get_artists_sample_log_plot_details(artist) + # we plot MDHisto workspaces, Workspace2D spectra, and Sample Logs + # if you get here, the LogName is valid and not None kwargs['LogName'] = sample_log_plot_details[0] if sample_log_plot_details[1] is not None: kwargs['Filtered'] = sample_log_plot_details[1] @@ -454,7 +479,7 @@ class MantidAxes(Axes): errorbars = False # neither does distribution if 'distribution' in kwargs.keys(): - del kwargs['distribution'] + del kwargs['distribution'] else: if kwargs.get('axis', None) == MantidAxType.BIN: workspace_index = spec_num @@ -632,7 +657,7 @@ class MantidAxes(Axes): with autoscale_on_update(self, autoscale_on): artist = self.track_workspace_artist(workspace, - axesfunctions.plot(self, normalize_by_bin_width = is_normalized, + axesfunctions.plot(self, normalize_by_bin_width=is_normalized, *args, **kwargs), _data_update, spec_num, is_normalized, MantidAxes.is_axis_of_type(MantidAxType.SPECTRUM, kwargs), @@ -760,6 +785,14 @@ class MantidAxes(Axes): else: return Axes.errorbar(self, *args, **kwargs) + @plot_decorator + def axhline(self, *args, **kwargs): + return Axes.axhline(self, *args, **kwargs) + + @plot_decorator + def axvline(self, *args, **kwargs): + return Axes.axvline(self, *args, **kwargs) + @plot_decorator def pcolor(self, *args, **kwargs): """ @@ -897,20 +930,23 @@ class MantidAxes(Axes): col.remove() if hasattr(artist_orig, 'colorbar_cid'): artist_orig.callbacksSM.disconnect(artist_orig.colorbar_cid) - if artist_orig.norm.vmin == 0: # avoid errors with log 0 - artist_orig.norm.vmin += 1e-6 - artists_new = colorfunc(self, workspace, norm=artist_orig.norm, **kwargs) - - artists_new.set_cmap(artist_orig.cmap) - if hasattr(artist_orig, 'interpolation'): - artists_new.set_interpolation(artist_orig.get_interpolation()) - - artists_new.autoscale() - artists_new.set_norm( - type(artist_orig.norm)(vmin=artists_new.norm.vmin, vmax=artists_new.norm.vmax)) - + # If the colormap has been overridden then it needs to be passed in at + # creation time + if 'colors' not in kwargs: + kwargs['cmap'] = artists_orig[-1].cmap + artists_new = colorfunc(self, workspace, **kwargs) + # Copy properties from old to new if not isinstance(artists_new, Iterable): artists_new = [artists_new] + # assume 1:1 match between old/new artist lists + # and update relevant properties + for src, dest in zip(artists_orig, artists_new): + if hasattr(dest, 'update_from'): + dest.update_from(src) + if hasattr(dest, 'set_interpolation'): + dest.set_interpolation(src.get_interpolation()) + dest.autoscale() + dest.set_norm(src.norm) try: axesfunctions.update_colorplot_datalimits(self, artists_new) @@ -1248,32 +1284,54 @@ class MantidAxes3D(Axes3D): return Axes.set_title(self, *args, **kwargs) def set_xlim3d(self, *args): - min, max = super().set_xlim3d(*args) - self._set_overflowing_data_to_nan(min, max, 0) + super().set_xlim3d(*args) + self._set_overflowing_data_to_nan(0) def set_ylim3d(self, *args): - min, max = super().set_ylim3d(*args) - - self._set_overflowing_data_to_nan(min, max, 1) + super().set_ylim3d(*args) + self._set_overflowing_data_to_nan(1) def set_zlim3d(self, *args): - min, max = super().set_zlim3d(*args) + super().set_zlim3d(*args) + self._set_overflowing_data_to_nan(2) - self._set_overflowing_data_to_nan(min, max, 2) + def autoscale(self, *args, **kwargs): + super().autoscale(*args, **kwargs) + self._set_overflowing_data_to_nan() - def _set_overflowing_data_to_nan(self, min, max, axis_index): + def _set_overflowing_data_to_nan(self, axis_index=None): """ - Sets any data for the given axis that is less than min or greater than max to nan so only the parts of the plot - that are within the axes are visible. - :param min: the lower axis limit. - :param max: the upper axis limit. + Sets any data for the given axis that is less than min[axis_index] or greater than max[axis_index] + to nan so only the parts of the plot that are within the axes are visible. :param axis_index: the index of the axis being edited, 0 for x, 1 for y, 2 for z. """ - if hasattr(self, 'original_data'): - axis_data = self.original_data[axis_index].copy() - axis_data[np.less(axis_data, min, where=~np.isnan(axis_data))] = np.nan - axis_data[np.greater(axis_data, max, where=~np.isnan(axis_data))] = np.nan - self.collections[0]._vec[axis_index] = axis_data + + min_vals, max_vals = zip(self.get_xlim3d(),self.get_ylim3d(),self.get_zlim3d()) + if hasattr(self, 'original_data_surface'): + if axis_index is None: + axis_index_list = [0,1,2] + else: + axis_index_list = [axis_index] + + for axis_index in axis_index_list: + axis_data = self.original_data_surface[axis_index].copy() + axis_data[np.less(axis_data, min_vals[axis_index], where=~np.isnan(axis_data))] = np.nan + axis_data[np.greater(axis_data, max_vals[axis_index], where=~np.isnan(axis_data))] = np.nan + self.collections[0]._vec[axis_index] = axis_data + + if hasattr(self, 'original_data_wireframe'): + + all_data = copy.deepcopy(self.original_data_wireframe) + + for spectrum in range(len(all_data)): + spectrum_data = all_data[spectrum] + for point in range(len(spectrum_data)): + for axis in range(3): + if (np.less(spectrum_data[point][axis],min_vals[axis]) + or np.greater(spectrum_data[point][axis],max_vals[axis])): + all_data[spectrum][point] = np.repeat(np.nan,3) + + self.collections[0].set_segments(all_data) def plot(self, *args, **kwargs): """ @@ -1346,9 +1404,14 @@ class MantidAxes3D(Axes3D): """ if datafunctions.validate_args(*args): logger.debug('using plotfunctions3D') - return axesfunctions3D.plot_wireframe(self, *args, **kwargs) + line_c = axesfunctions3D.plot_wireframe(self, *args, **kwargs) else: - return Axes3D.plot_wireframe(self, *args, **kwargs) + line_c = Axes3D.plot_wireframe(self, *args, **kwargs) + + # Create a copy of the original data points because data are set to nan when the axis limits are changed. + self.original_data_wireframe = copy.deepcopy(line_c._segments3d) + + return line_c def plot_surface(self, *args, **kwargs): """ @@ -1371,18 +1434,18 @@ class MantidAxes3D(Axes3D): """ if datafunctions.validate_args(*args): logger.debug('using plotfunctions3D') - polyc = axesfunctions3D.plot_surface(self, *args, **kwargs) + poly_c = axesfunctions3D.plot_surface(self, *args, **kwargs) else: - polyc = Axes3D.plot_surface(self, *args, **kwargs) + poly_c = Axes3D.plot_surface(self, *args, **kwargs) # This is a bit of a hack, should be able to remove # when matplotlib supports plotting masked arrays - polyc._A = safe_masked_invalid(polyc._A) + poly_c._A = safe_masked_invalid(poly_c._A) # Create a copy of the original data points because data are set to nan when the axis limits are changed. - self.original_data = copy.deepcopy(polyc._vec) + self.original_data_surface = copy.deepcopy(poly_c._vec) - return polyc + return poly_c def contour(self, *args, **kwargs): """ diff --git a/Framework/PythonInterface/mantid/plots/mantidimage.py b/Framework/PythonInterface/mantid/plots/mantidimage.py new file mode 100644 index 0000000000000000000000000000000000000000..9297df6ec3a49d70c1b94c4c431f3a142dce8a58 --- /dev/null +++ b/Framework/PythonInterface/mantid/plots/mantidimage.py @@ -0,0 +1,95 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source, +# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +# SPDX - License - Identifier: GPL - 3.0 + +import matplotlib.image as mimage +import numpy as np +from qtpy.QtCore import Qt + +from enum import Enum + +# Threshold defining whether an image is light or dark ( x < Threshold = Dark) +THRESHOLD = 100 + + +class ImageIntensity(Enum): + LIGHT = 1 + DARK = 2 + + +class MantidImage(mimage.AxesImage): + def __init__(self, ax, + cmap=None, + norm=None, + interpolation=None, + origin=None, + extent=None, + filternorm=1, + filterrad=4.0, + resample=False, + **kwargs): + super().__init__(ax, + cmap=cmap, + norm=norm, + interpolation=interpolation, + origin=origin, + extent=extent, + filternorm=filternorm, + filterrad=filterrad, + resample=resample, + **kwargs) + + # Increase the default pen thickness + self.update_pen_thickness(1.5) + + def draw(self, renderer, *args, **kwargs): + self.update_pen_color() + super().draw(renderer, *args, **kwargs) + + def update_pen_color(self, color=None): + """Update the pen color used to draw tool in the matplotlib toolbar, e.g + the zoombox. If no color is specified the color is automatically determined + by considering how dark, or light the image is and setting a pen appropriately. + If the canvas is not a MantidFigureCanvas, the method will be skipped. + :param color: A qt color instance + """ + from workbench.plotting.mantidfigurecanvas import MantidFigureCanvas + if not isinstance(self.axes.get_figure().canvas, MantidFigureCanvas): + return + if color is None: + image_intensity = self._calculate_greyscale_intensity() + if image_intensity == ImageIntensity.DARK: + color = Qt.white + else: + color = Qt.black + self.axes.get_figure().canvas.pen_color = color + + def update_pen_thickness(self, value): + """Update the pen thickness used to draw tool in the matplotlib toolbar, e.g + the zoombox. + If the canvas is not a MantidFigureCanvas, the method will be skipped. + :param value: Thickness of the pen line + """ + from workbench.plotting.mantidfigurecanvas import MantidFigureCanvas + if isinstance(self.axes.get_figure().canvas, MantidFigureCanvas): + self.axes.get_figure().canvas.pen_thickness = value + + def _calculate_greyscale_intensity(self) -> ImageIntensity: + """ + Calculate the intensity of the image in greyscale. + The intensity is given in the range [0, 255] where: + -0 is black - i.e. a dark image and + -255 is white, a light image. + """ + rgb = self.to_rgba(self._A, alpha=None, bytes=True, norm=True) + r, g, b = rgb[:, :, 0], rgb[:, :, 1], rgb[:, :, 2] + # CCIR 601 conversion from rgb to luma/greyscale + # see https://en.wikipedia.org/wiki/Luma_(video) + grey = 0.2989 * r + 0.5870 * g + 0.1140 * b + mean = np.mean(grey) + if mean > THRESHOLD: + return ImageIntensity.LIGHT + else: + return ImageIntensity.DARK diff --git a/Framework/PythonInterface/mantid/plots/modest_image/modest_image.py b/Framework/PythonInterface/mantid/plots/modest_image/modest_image.py index fbf3ac338e16c0385d78998a15dad98cbcf6a206..d98487b4905a90287c5ab74b7da10cf76851e194 100644 --- a/Framework/PythonInterface/mantid/plots/modest_image/modest_image.py +++ b/Framework/PythonInterface/mantid/plots/modest_image/modest_image.py @@ -11,18 +11,19 @@ set_extent. """ import matplotlib rcParams = matplotlib.rcParams - import matplotlib.image as mi import matplotlib.colors as mcolors import matplotlib.cbook as cbook from matplotlib.transforms import IdentityTransform, Affine2D +from mantid.plots.mantidimage import MantidImage + import numpy as np IDENTITY_TRANSFORM = IdentityTransform() -class ModestImage(mi.AxesImage): +class ModestImage(MantidImage): """ Computationally modest image class. diff --git a/Framework/PythonInterface/mantid/plots/plotfunctions.py b/Framework/PythonInterface/mantid/plots/plotfunctions.py index 23fc714a639e5cbfedb3b4d0c667416b0405a3a4..713c1c3703c680217079a1c72cfabc47e5644037 100644 --- a/Framework/PythonInterface/mantid/plots/plotfunctions.py +++ b/Framework/PythonInterface/mantid/plots/plotfunctions.py @@ -338,18 +338,65 @@ def get_plot_fig(overplot=None, ax_properties=None, window_title=None, axes_num= fig.canvas.set_window_title(window_title) if not overplot: + for ax in fig.axes: + ax.tick_params( + which='both', + left="on" == ConfigService.getString("plots.showTicksLeft").lower(), + bottom="on" == ConfigService.getString("plots.showTicksBottom").lower(), + right="on" == ConfigService.getString("plots.showTicksRight").lower(), + top="on" == ConfigService.getString("plots.showTicksTop").lower(), + labelleft="on" == ConfigService.getString("plots.showLabelsLeft").lower(), + labelbottom="on" == ConfigService.getString("plots.showLabelsBottom").lower(), + labelright="on" == ConfigService.getString("plots.showLabelsRight").lower(), + labeltop="on" == ConfigService.getString("plots.showLabelsTop").lower(), + ) + ax.xaxis.set_tick_params( + which='major', + width=int(ConfigService.getString("plots.ticks.major.width")), + length=int(ConfigService.getString("plots.ticks.major.length")), + direction=ConfigService.getString("plots.ticks.major.direction").lower() + ) + ax.yaxis.set_tick_params( + which='major', + width=int(ConfigService.getString("plots.ticks.major.width")), + length=int(ConfigService.getString("plots.ticks.major.length")), + direction=ConfigService.getString("plots.ticks.major.direction").lower() + ) + if ConfigService.getString("plots.ShowMinorTicks").lower() == "on": for ax in fig.axes: ax.minorticks_on() + ax.xaxis.set_tick_params( + which='minor', + width=int(ConfigService.getString("plots.ticks.minor.width")), + length=int(ConfigService.getString("plots.ticks.minor.length")), + direction=ConfigService.getString("plots.ticks.minor.direction").lower() + ) + ax.yaxis.set_tick_params( + which='minor', + width=int(ConfigService.getString("plots.ticks.minor.width")), + length=int(ConfigService.getString("plots.ticks.minor.length")), + direction=ConfigService.getString("plots.ticks.minor.direction").lower() + ) + for ax in fig.axes: ax.show_minor_gridlines = ConfigService.getString("plots.ShowMinorGridlines").lower() == "on" + for spine in ['top', 'bottom', 'left', 'right']: + ax.spines[spine].set_linewidth(float(ConfigService.getString("plots.axesLineWidth"))) + + if ConfigService.getString("plots.enableGrid").lower() == "on": + try: + fig.canvas.manager.toolbar.toggle_grid(enable=True) + except AttributeError: + # The canvas has no manager, or the manager has no toolbar + pass return fig, fig.axes # ----------------------------------------------------------------------------- -# Pricate Methods +# Private Methods # ----------------------------------------------------------------------------- def _validate_plot_inputs(workspaces, spectrum_nums, wksp_indices, tiled=False, overplot=False): """Raises a ValueError if any arguments have the incorrect types""" diff --git a/Framework/PythonInterface/mantid/plots/resampling_image/samplingimage.py b/Framework/PythonInterface/mantid/plots/resampling_image/samplingimage.py index 820e37adc8c69d741cc360fcda3d2b1e997a9689..35d7eb0c914873c4d8fd676e90b2e1021252f642 100644 --- a/Framework/PythonInterface/mantid/plots/resampling_image/samplingimage.py +++ b/Framework/PythonInterface/mantid/plots/resampling_image/samplingimage.py @@ -4,16 +4,17 @@ # NScD Oak Ridge National Laboratory, European Spallation Source, # Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS # SPDX - License - Identifier: GPL - 3.0 + -import matplotlib.image as mimage import matplotlib.colors import numpy as np from mantid.plots.datafunctions import get_matrix_2d_ragged, get_normalize_by_bin_width +from mantid.plots.mantidimage import MantidImage +from mantid.api import MatrixWorkspace MAX_HISTOGRAMS = 5000 -class SamplingImage(mimage.AxesImage): +class SamplingImage(MantidImage): def __init__(self, ax, workspace, @@ -145,13 +146,11 @@ class SamplingImage(mimage.AxesImage): def _update_maxpooling_option(self): """ Updates the maxpooling option, used when the image is downsampled - If the workspace is large, we skip this maxpooling step and set the option as False + If the workspace is large, or ragged, we skip this maxpooling step and set the option as False """ axis = self.ws.getAxis(1) - if self.ws.getNumberHistograms() <= MAX_HISTOGRAMS and axis.isSpectra(): - self._maxpooling = True - else: - self._maxpooling = False + self._maxpooling = (self.ws.getNumberHistograms() <= MAX_HISTOGRAMS and axis.isSpectra() and + not self.ws.isRaggedWorkspace()) def imshow_sampling(axes, @@ -192,6 +191,27 @@ def imshow_sampling(axes, workspace.getDimension(0).getMaximum(), workspace.getDimension(1).getMinimum(), workspace.getDimension(1).getMaximum()) + if isinstance(workspace, MatrixWorkspace) and not workspace.isCommonBins(): + # for MatrixWorkspace the x extent obtained from dimension 0 corresponds to the first spectrum + # this is not correct in case of ragged workspaces, where we need to obtain the global xmin and xmax + # moreover the axis might be in ascending or descending order, so x[0] is not necessarily the minimum + xmax, xmin = None, None # don't initialise with values from first spectrum as could be a monitor + si = workspace.spectrumInfo() + for i in range(workspace.getNumberHistograms()): + if si.hasDetectors(i) and not si.isMonitor(i): + x_axis = workspace.readX(i) + x_i_first = x_axis[0] + x_i_last = x_axis[-1] + x_i_min = min([x_i_first, x_i_last]) + x_i_max = max([x_i_first, x_i_last]) + # effectively ignore spectra with nan or inf values + if np.isfinite(x_i_min): + xmin = min([x_i_min, xmin]) if xmin else x_i_min + if np.isfinite(x_i_max): + xmax = max([x_i_max, xmax]) if xmax else x_i_max + x0 = xmin if xmin else x0 + x1 = xmax if xmax else x1 + if workspace.getDimension(1).getNBins() == workspace.getAxis(1).length(): width = workspace.getDimension(1).getBinWidth() y0 -= width / 2 diff --git a/Framework/PythonInterface/mantid/py36compat/__init__.py b/Framework/PythonInterface/mantid/py36compat/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a89431a2bd26938e251a5d5d6f491a04e04b8d69 --- /dev/null +++ b/Framework/PythonInterface/mantid/py36compat/__init__.py @@ -0,0 +1,26 @@ +# Mantid Repository : https://github.com/mantidproject/mantid +# +# Copyright © 2020 ISIS Rutherford Appleton Laboratory UKRI, +# NScD Oak Ridge National Laboratory, European Spallation Source +# Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS +# SPDX - License - Identifier: GPL - 3.0 + + + +""" +mantid.py36compat + +Provides a compatibility layer to backport features found in later +Python versions (3.7 onwards) to Ubuntu 18.04 / RHEL 7 +which are both on Python 3.6. + +""" +import sys + +__requires_compat = False if sys.version_info[0:2] > (3, 6) else True + +if __requires_compat: + from ._dataclasses.dataclasses import dataclass +else: + from dataclasses import dataclass + +__all__ = "dataclass" diff --git a/Framework/PythonInterface/mantid/py36compat/_dataclasses/LICENSE.txt b/Framework/PythonInterface/mantid/py36compat/_dataclasses/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/Framework/PythonInterface/mantid/py36compat/_dataclasses/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Framework/PythonInterface/mantid/py36compat/_dataclasses/__init__.py b/Framework/PythonInterface/mantid/py36compat/_dataclasses/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2f2dca231a769663ac3c746153f4ebb4e32c1505 --- /dev/null +++ b/Framework/PythonInterface/mantid/py36compat/_dataclasses/__init__.py @@ -0,0 +1 @@ +from .dataclasses import * \ No newline at end of file diff --git a/Framework/PythonInterface/mantid/py36compat/_dataclasses/dataclasses.py b/Framework/PythonInterface/mantid/py36compat/_dataclasses/dataclasses.py new file mode 100644 index 0000000000000000000000000000000000000000..fd165404b70ea900680d8a125194bac847c10d1c --- /dev/null +++ b/Framework/PythonInterface/mantid/py36compat/_dataclasses/dataclasses.py @@ -0,0 +1,1186 @@ +# Included from https://github.com/ericvsmith/dataclasses + +import re +import sys +import copy +import types +import inspect +import keyword + +__all__ = ['dataclass', + 'field', + 'Field', + 'FrozenInstanceError', + 'InitVar', + 'MISSING', + + # Helper functions. + 'fields', + 'asdict', + 'astuple', + 'make_dataclass', + 'replace', + 'is_dataclass', + ] + +# Conditions for adding methods. The boxes indicate what action the +# dataclass decorator takes. For all of these tables, when I talk +# about init=, repr=, eq=, order=, unsafe_hash=, or frozen=, I'm +# referring to the arguments to the @dataclass decorator. When +# checking if a dunder method already exists, I mean check for an +# entry in the class's __dict__. I never check to see if an attribute +# is defined in a base class. + +# Key: +# +=========+=========================================+ +# + Value | Meaning | +# +=========+=========================================+ +# | | No action: no method is added. | +# +---------+-----------------------------------------+ +# | add | Generated method is added. | +# +---------+-----------------------------------------+ +# | raise | TypeError is raised. | +# +---------+-----------------------------------------+ +# | None | Attribute is set to None. | +# +=========+=========================================+ + +# __init__ +# +# +--- init= parameter +# | +# v | | | +# | no | yes | <--- class has __init__ in __dict__? +# +=======+=======+=======+ +# | False | | | +# +-------+-------+-------+ +# | True | add | | <- the default +# +=======+=======+=======+ + +# __repr__ +# +# +--- repr= parameter +# | +# v | | | +# | no | yes | <--- class has __repr__ in __dict__? +# +=======+=======+=======+ +# | False | | | +# +-------+-------+-------+ +# | True | add | | <- the default +# +=======+=======+=======+ + + +# __setattr__ +# __delattr__ +# +# +--- frozen= parameter +# | +# v | | | +# | no | yes | <--- class has __setattr__ or __delattr__ in __dict__? +# +=======+=======+=======+ +# | False | | | <- the default +# +-------+-------+-------+ +# | True | add | raise | +# +=======+=======+=======+ +# Raise because not adding these methods would break the "frozen-ness" +# of the class. + +# __eq__ +# +# +--- eq= parameter +# | +# v | | | +# | no | yes | <--- class has __eq__ in __dict__? +# +=======+=======+=======+ +# | False | | | +# +-------+-------+-------+ +# | True | add | | <- the default +# +=======+=======+=======+ + +# __lt__ +# __le__ +# __gt__ +# __ge__ +# +# +--- order= parameter +# | +# v | | | +# | no | yes | <--- class has any comparison method in __dict__? +# +=======+=======+=======+ +# | False | | | <- the default +# +-------+-------+-------+ +# | True | add | raise | +# +=======+=======+=======+ +# Raise because to allow this case would interfere with using +# functools.total_ordering. + +# __hash__ + +# +------------------- unsafe_hash= parameter +# | +----------- eq= parameter +# | | +--- frozen= parameter +# | | | +# v v v | | | +# | no | yes | <--- class has explicitly defined __hash__ +# +=======+=======+=======+========+========+ +# | False | False | False | | | No __eq__, use the base class __hash__ +# +-------+-------+-------+--------+--------+ +# | False | False | True | | | No __eq__, use the base class __hash__ +# +-------+-------+-------+--------+--------+ +# | False | True | False | None | | <-- the default, not hashable +# +-------+-------+-------+--------+--------+ +# | False | True | True | add | | Frozen, so hashable, allows override +# +-------+-------+-------+--------+--------+ +# | True | False | False | add | raise | Has no __eq__, but hashable +# +-------+-------+-------+--------+--------+ +# | True | False | True | add | raise | Has no __eq__, but hashable +# +-------+-------+-------+--------+--------+ +# | True | True | False | add | raise | Not frozen, but hashable +# +-------+-------+-------+--------+--------+ +# | True | True | True | add | raise | Frozen, so hashable +# +=======+=======+=======+========+========+ +# For boxes that are blank, __hash__ is untouched and therefore +# inherited from the base class. If the base is object, then +# id-based hashing is used. +# +# Note that a class may already have __hash__=None if it specified an +# __eq__ method in the class body (not one that was created by +# @dataclass). +# +# See _hash_action (below) for a coded version of this table. + + +# Raised when an attempt is made to modify a frozen class. +class FrozenInstanceError(AttributeError): pass + +# A sentinel object for default values to signal that a default +# factory will be used. This is given a nice repr() which will appear +# in the function signature of dataclasses' constructors. +class _HAS_DEFAULT_FACTORY_CLASS: + def __repr__(self): + return '' +_HAS_DEFAULT_FACTORY = _HAS_DEFAULT_FACTORY_CLASS() + +# A sentinel object to detect if a parameter is supplied or not. Use +# a class to give it a better repr. +class _MISSING_TYPE: + pass +MISSING = _MISSING_TYPE() + +# Since most per-field metadata will be unused, create an empty +# read-only proxy that can be shared among all fields. +_EMPTY_METADATA = types.MappingProxyType({}) + +# Markers for the various kinds of fields and pseudo-fields. +class _FIELD_BASE: + def __init__(self, name): + self.name = name + def __repr__(self): + return self.name +_FIELD = _FIELD_BASE('_FIELD') +_FIELD_CLASSVAR = _FIELD_BASE('_FIELD_CLASSVAR') +_FIELD_INITVAR = _FIELD_BASE('_FIELD_INITVAR') + +# The name of an attribute on the class where we store the Field +# objects. Also used to check if a class is a Data Class. +_FIELDS = '__dataclass_fields__' + +# The name of an attribute on the class that stores the parameters to +# @dataclass. +_PARAMS = '__dataclass_params__' + +# The name of the function, that if it exists, is called at the end of +# __init__. +_POST_INIT_NAME = '__post_init__' + +# String regex that string annotations for ClassVar or InitVar must match. +# Allows "identifier.identifier[" or "identifier[". +# https://bugs.python.org/issue33453 for details. +_MODULE_IDENTIFIER_RE = re.compile(r'^(?:\s*(\w+)\s*\.)?\s*(\w+)') + +class _InitVarMeta(type): + def __getitem__(self, params): + return self + +class InitVar(metaclass=_InitVarMeta): + pass + + +# Instances of Field are only ever created from within this module, +# and only from the field() function, although Field instances are +# exposed externally as (conceptually) read-only objects. +# +# name and type are filled in after the fact, not in __init__. +# They're not known at the time this class is instantiated, but it's +# convenient if they're available later. +# +# When cls._FIELDS is filled in with a list of Field objects, the name +# and type fields will have been populated. +class Field: + __slots__ = ('name', + 'type', + 'default', + 'default_factory', + 'repr', + 'hash', + 'init', + 'compare', + 'metadata', + '_field_type', # Private: not to be used by user code. + ) + + def __init__(self, default, default_factory, init, repr, hash, compare, + metadata): + self.name = None + self.type = None + self.default = default + self.default_factory = default_factory + self.init = init + self.repr = repr + self.hash = hash + self.compare = compare + self.metadata = (_EMPTY_METADATA + if metadata is None or len(metadata) == 0 else + types.MappingProxyType(metadata)) + self._field_type = None + + def __repr__(self): + return ('Field(' + f'name={self.name!r},' + f'type={self.type!r},' + f'default={self.default!r},' + f'default_factory={self.default_factory!r},' + f'init={self.init!r},' + f'repr={self.repr!r},' + f'hash={self.hash!r},' + f'compare={self.compare!r},' + f'metadata={self.metadata!r},' + f'_field_type={self._field_type}' + ')') + + # This is used to support the PEP 487 __set_name__ protocol in the + # case where we're using a field that contains a descriptor as a + # defaul value. For details on __set_name__, see + # https://www.python.org/dev/peps/pep-0487/#implementation-details. + # + # Note that in _process_class, this Field object is overwritten + # with the default value, so the end result is a descriptor that + # had __set_name__ called on it at the right time. + def __set_name__(self, owner, name): + func = getattr(type(self.default), '__set_name__', None) + if func: + # There is a __set_name__ method on the descriptor, call + # it. + func(self.default, owner, name) + + +class _DataclassParams: + __slots__ = ('init', + 'repr', + 'eq', + 'order', + 'unsafe_hash', + 'frozen', + ) + + def __init__(self, init, repr, eq, order, unsafe_hash, frozen): + self.init = init + self.repr = repr + self.eq = eq + self.order = order + self.unsafe_hash = unsafe_hash + self.frozen = frozen + + def __repr__(self): + return ('_DataclassParams(' + f'init={self.init!r},' + f'repr={self.repr!r},' + f'eq={self.eq!r},' + f'order={self.order!r},' + f'unsafe_hash={self.unsafe_hash!r},' + f'frozen={self.frozen!r}' + ')') + + +# This function is used instead of exposing Field creation directly, +# so that a type checker can be told (via overloads) that this is a +# function whose type depends on its parameters. +def field(*, default=MISSING, default_factory=MISSING, init=True, repr=True, + hash=None, compare=True, metadata=None): + """Return an object to identify dataclass fields. + + default is the default value of the field. default_factory is a + 0-argument function called to initialize a field's value. If init + is True, the field will be a parameter to the class's __init__() + function. If repr is True, the field will be included in the + object's repr(). If hash is True, the field will be included in + the object's hash(). If compare is True, the field will be used + in comparison functions. metadata, if specified, must be a + mapping which is stored but not otherwise examined by dataclass. + + It is an error to specify both default and default_factory. + """ + + if default is not MISSING and default_factory is not MISSING: + raise ValueError('cannot specify both default and default_factory') + return Field(default, default_factory, init, repr, hash, compare, + metadata) + + +def _tuple_str(obj_name, fields): + # Return a string representing each field of obj_name as a tuple + # member. So, if fields is ['x', 'y'] and obj_name is "self", + # return "(self.x,self.y)". + + # Special case for the 0-tuple. + if not fields: + return '()' + # Note the trailing comma, needed if this turns out to be a 1-tuple. + return f'({",".join([f"{obj_name}.{f.name}" for f in fields])},)' + + +def _create_fn(name, args, body, *, globals=None, locals=None, + return_type=MISSING): + # Note that we mutate locals when exec() is called. Caller + # beware! The only callers are internal to this module, so no + # worries about external callers. + if locals is None: + locals = {} + return_annotation = '' + if return_type is not MISSING: + locals['_return_type'] = return_type + return_annotation = '->_return_type' + args = ','.join(args) + body = '\n'.join(f' {b}' for b in body) + + # Compute the text of the entire function. + txt = f'def {name}({args}){return_annotation}:\n{body}' + + exec(txt, globals, locals) + return locals[name] + + +def _field_assign(frozen, name, value, self_name): + # If we're a frozen class, then assign to our fields in __init__ + # via object.__setattr__. Otherwise, just use a simple + # assignment. + # + # self_name is what "self" is called in this function: don't + # hard-code "self", since that might be a field name. + if frozen: + return f'object.__setattr__({self_name},{name!r},{value})' + return f'{self_name}.{name}={value}' + + +def _field_init(f, frozen, globals, self_name): + # Return the text of the line in the body of __init__ that will + # initialize this field. + + default_name = f'_dflt_{f.name}' + if f.default_factory is not MISSING: + if f.init: + # This field has a default factory. If a parameter is + # given, use it. If not, call the factory. + globals[default_name] = f.default_factory + value = (f'{default_name}() ' + f'if {f.name} is _HAS_DEFAULT_FACTORY ' + f'else {f.name}') + else: + # This is a field that's not in the __init__ params, but + # has a default factory function. It needs to be + # initialized here by calling the factory function, + # because there's no other way to initialize it. + + # For a field initialized with a default=defaultvalue, the + # class dict just has the default value + # (cls.fieldname=defaultvalue). But that won't work for a + # default factory, the factory must be called in __init__ + # and we must assign that to self.fieldname. We can't + # fall back to the class dict's value, both because it's + # not set, and because it might be different per-class + # (which, after all, is why we have a factory function!). + + globals[default_name] = f.default_factory + value = f'{default_name}()' + else: + # No default factory. + if f.init: + if f.default is MISSING: + # There's no default, just do an assignment. + value = f.name + elif f.default is not MISSING: + globals[default_name] = f.default + value = f.name + else: + # This field does not need initialization. Signify that + # to the caller by returning None. + return None + + # Only test this now, so that we can create variables for the + # default. However, return None to signify that we're not going + # to actually do the assignment statement for InitVars. + if f._field_type is _FIELD_INITVAR: + return None + + # Now, actually generate the field assignment. + return _field_assign(frozen, f.name, value, self_name) + + +def _init_param(f): + # Return the __init__ parameter string for this field. For + # example, the equivalent of 'x:int=3' (except instead of 'int', + # reference a variable set to int, and instead of '3', reference a + # variable set to 3). + if f.default is MISSING and f.default_factory is MISSING: + # There's no default, and no default_factory, just output the + # variable name and type. + default = '' + elif f.default is not MISSING: + # There's a default, this will be the name that's used to look + # it up. + default = f'=_dflt_{f.name}' + elif f.default_factory is not MISSING: + # There's a factory function. Set a marker. + default = '=_HAS_DEFAULT_FACTORY' + return f'{f.name}:_type_{f.name}{default}' + + +def _init_fn(fields, frozen, has_post_init, self_name): + # fields contains both real fields and InitVar pseudo-fields. + + # Make sure we don't have fields without defaults following fields + # with defaults. This actually would be caught when exec-ing the + # function source code, but catching it here gives a better error + # message, and future-proofs us in case we build up the function + # using ast. + seen_default = False + for f in fields: + # Only consider fields in the __init__ call. + if f.init: + if not (f.default is MISSING and f.default_factory is MISSING): + seen_default = True + elif seen_default: + raise TypeError(f'non-default argument {f.name!r} ' + 'follows default argument') + + globals = {'MISSING': MISSING, + '_HAS_DEFAULT_FACTORY': _HAS_DEFAULT_FACTORY} + + body_lines = [] + for f in fields: + line = _field_init(f, frozen, globals, self_name) + # line is None means that this field doesn't require + # initialization (it's a pseudo-field). Just skip it. + if line: + body_lines.append(line) + + # Does this class have a post-init function? + if has_post_init: + params_str = ','.join(f.name for f in fields + if f._field_type is _FIELD_INITVAR) + body_lines.append(f'{self_name}.{_POST_INIT_NAME}({params_str})') + + # If no body lines, use 'pass'. + if not body_lines: + body_lines = ['pass'] + + locals = {f'_type_{f.name}': f.type for f in fields} + return _create_fn('__init__', + [self_name] + [_init_param(f) for f in fields if f.init], + body_lines, + locals=locals, + globals=globals, + return_type=None) + + +def _repr_fn(fields): + return _create_fn('__repr__', + ('self',), + ['return self.__class__.__qualname__ + f"(' + + ', '.join([f"{f.name}={{self.{f.name}!r}}" + for f in fields]) + + ')"']) + + +def _frozen_get_del_attr(cls, fields): + # XXX: globals is modified on the first call to _create_fn, then + # the modified version is used in the second call. Is this okay? + globals = {'cls': cls, + 'FrozenInstanceError': FrozenInstanceError} + if fields: + fields_str = '(' + ','.join(repr(f.name) for f in fields) + ',)' + else: + # Special case for the zero-length tuple. + fields_str = '()' + return (_create_fn('__setattr__', + ('self', 'name', 'value'), + (f'if type(self) is cls or name in {fields_str}:', + ' raise FrozenInstanceError(f"cannot assign to field {name!r}")', + f'super(cls, self).__setattr__(name, value)'), + globals=globals), + _create_fn('__delattr__', + ('self', 'name'), + (f'if type(self) is cls or name in {fields_str}:', + ' raise FrozenInstanceError(f"cannot delete field {name!r}")', + f'super(cls, self).__delattr__(name)'), + globals=globals), + ) + + +def _cmp_fn(name, op, self_tuple, other_tuple): + # Create a comparison function. If the fields in the object are + # named 'x' and 'y', then self_tuple is the string + # '(self.x,self.y)' and other_tuple is the string + # '(other.x,other.y)'. + + return _create_fn(name, + ('self', 'other'), + [ 'if other.__class__ is self.__class__:', + f' return {self_tuple}{op}{other_tuple}', + 'return NotImplemented']) + + +def _hash_fn(fields): + self_tuple = _tuple_str('self', fields) + return _create_fn('__hash__', + ('self',), + [f'return hash({self_tuple})']) + + +def _is_classvar(a_type, typing): + # This test uses a typing internal class, but it's the best way to + # test if this is a ClassVar. + return type(a_type) is typing._ClassVar + + +def _is_initvar(a_type, dataclasses): + # The module we're checking against is the module we're + # currently in (dataclasses.py). + return a_type is dataclasses.InitVar + + +def _is_type(annotation, cls, a_module, a_type, is_type_predicate): + # Given a type annotation string, does it refer to a_type in + # a_module? For example, when checking that annotation denotes a + # ClassVar, then a_module is typing, and a_type is + # typing.ClassVar. + + # It's possible to look up a_module given a_type, but it involves + # looking in sys.modules (again!), and seems like a waste since + # the caller already knows a_module. + + # - annotation is a string type annotation + # - cls is the class that this annotation was found in + # - a_module is the module we want to match + # - a_type is the type in that module we want to match + # - is_type_predicate is a function called with (obj, a_module) + # that determines if obj is of the desired type. + + # Since this test does not do a local namespace lookup (and + # instead only a module (global) lookup), there are some things it + # gets wrong. + + # With string annotations, cv0 will be detected as a ClassVar: + # CV = ClassVar + # @dataclass + # class C0: + # cv0: CV + + # But in this example cv1 will not be detected as a ClassVar: + # @dataclass + # class C1: + # CV = ClassVar + # cv1: CV + + # In C1, the code in this function (_is_type) will look up "CV" in + # the module and not find it, so it will not consider cv1 as a + # ClassVar. This is a fairly obscure corner case, and the best + # way to fix it would be to eval() the string "CV" with the + # correct global and local namespaces. However that would involve + # a eval() penalty for every single field of every dataclass + # that's defined. It was judged not worth it.