diff --git a/Framework/Algorithms/inc/MantidAlgorithms/SANSCollimationLengthEstimator.h b/Framework/Algorithms/inc/MantidAlgorithms/SANSCollimationLengthEstimator.h index d662b7a2d25d6a05531eb96a76db2b34c28654e9..18d9fb5b5cfad47c692e9a9a3c6badc8dcb30601 100644 --- a/Framework/Algorithms/inc/MantidAlgorithms/SANSCollimationLengthEstimator.h +++ b/Framework/Algorithms/inc/MantidAlgorithms/SANSCollimationLengthEstimator.h @@ -2,6 +2,7 @@ #define MANTID_ALGORITHMS_SANSCOLLISIONLENGTHESTIMATOR_H #include "MantidKernel/System.h" +#include "MantidKernel/Property.h" #include "MantidAPI/MatrixWorkspace_fwd.h" /**Helper class which provides the Collimation Length for SANS instruments @@ -39,6 +40,7 @@ private: double getCollimationLengthWithGuides( Mantid::API::MatrixWorkspace_sptr inOutWS, const double L1, const double collimationLengthCorrection) const; + double getGuideValue(Mantid::Kernel::Property *prop) const; }; } } diff --git a/Framework/Algorithms/src/Q1D2.cpp b/Framework/Algorithms/src/Q1D2.cpp index 495d921058017d3549c8ac020d8ff4d9e06df281..f7ee159e7f5e3dfd8fa21e54596033db5c4ee508 100644 --- a/Framework/Algorithms/src/Q1D2.cpp +++ b/Framework/Algorithms/src/Q1D2.cpp @@ -119,6 +119,9 @@ void Q1D2::exec() { // define the (large number of) data objects that are going to be used in all // iterations of the loop below + // Flag to decide if Q Resolution is to be used + auto useQResolution = qResolution ? true : false; + // this will become the output workspace from this algorithm MatrixWorkspace_sptr outputWS = setUpOutputWorkspace(getProperty("OutputBinning")); @@ -130,15 +133,19 @@ void Q1D2::exec() { MantidVec normSum(YOut.size(), 0.0); // the error on the normalisation MantidVec normError2(YOut.size(), 0.0); - // the averaged Q resolution - MantidVec &qResolutionOut = outputWS->dataDx(0); + + // the averaged Q resolution. We need the a named dummy variable although it + // won't be + // used since we only want to create a reference to DX if it is really + // required. Referencing + // DX sets a flag which might not be desirable. + MantidVec dummy; + MantidVec &qResolutionOut = + useQResolution ? outputWS->dataDx(0) : outputWS->dataY(0); const int numSpec = static_cast<int>(m_dataWS->getNumberHistograms()); Progress progress(this, 0.05, 1.0, numSpec + 1); - // Flag to decide if Q Resolution is to be used - auto useQResolution = qResolution ? true : false; - PARALLEL_FOR3(m_dataWS, outputWS, pixelAdj) for (int i = 0; i < numSpec; ++i) { PARALLEL_START_INTERUPT_REGION @@ -278,7 +285,9 @@ void Q1D2::exec() { WorkspaceFactory::Instance().create(outputWS); ws_sumOfCounts->dataX(0) = outputWS->dataX(0); ws_sumOfCounts->dataY(0) = outputWS->dataY(0); - ws_sumOfCounts->dataDx(0) = outputWS->dataDx(0); + if (useQResolution) { + ws_sumOfCounts->dataDx(0) = outputWS->dataDx(0); + } for (size_t i = 0; i < outputWS->dataE(0).size(); i++) { ws_sumOfCounts->dataE(0)[i] = sqrt(outputWS->dataE(0)[i]); } @@ -286,7 +295,9 @@ void Q1D2::exec() { MatrixWorkspace_sptr ws_sumOfNormFactors = WorkspaceFactory::Instance().create(outputWS); ws_sumOfNormFactors->dataX(0) = outputWS->dataX(0); - ws_sumOfNormFactors->dataDx(0) = outputWS->dataDx(0); + if (useQResolution) { + ws_sumOfNormFactors->dataDx(0) = outputWS->dataDx(0); + } for (size_t i = 0; i < ws_sumOfNormFactors->dataY(0).size(); i++) { ws_sumOfNormFactors->dataY(0)[i] = normSum[i]; ws_sumOfNormFactors->dataE(0)[i] = sqrt(normError2[i]); diff --git a/Framework/Algorithms/src/SANSCollimationLengthEstimator.cpp b/Framework/Algorithms/src/SANSCollimationLengthEstimator.cpp index 624e311c48b091897f00330e3da26a6f1daa01a5..aaaabfe5a3f2ce9b2c5c61e3e1afb47d53bb2881 100644 --- a/Framework/Algorithms/src/SANSCollimationLengthEstimator.cpp +++ b/Framework/Algorithms/src/SANSCollimationLengthEstimator.cpp @@ -2,10 +2,27 @@ #include "MantidAPI/MatrixWorkspace.h" #include "MantidKernel/Logger.h" #include "MantidKernel/TimeSeriesProperty.h" +#include "MantidKernel/Property.h" #include "MantidKernel/V3D.h" +#include "boost/lexical_cast.hpp" namespace { Mantid::Kernel::Logger g_log("SANSCollimationLengthEstimator"); + +/** + * Provide an string and check if it can be converted to a double + * @param val: a value as a string + * @returns true if it is convertible else false + */ +bool checkForDouble(std::string val) { + auto isDouble = false; + try { + boost::lexical_cast<double>(val); + isDouble = true; + } catch (boost::bad_lexical_cast const &) { + } + return isDouble; +} } namespace Mantid { @@ -29,6 +46,7 @@ double SANSCollimationLengthEstimator::provideCollimationLength( // to 4 const double defaultLColim = 4.0; auto collimationLengthID = "collimation-length-correction"; + if (!workspace->getInstrument()->hasParameter(collimationLengthID)) { g_log.error("Error in SANSCollimtionLengthEstimator: The instrument " "parameter file does not contain a collimation length " @@ -53,15 +71,21 @@ double SANSCollimationLengthEstimator::provideCollimationLength( workspace->getInstrument()->getStringParameter( "special-default-collimation-length-method"); if (specialCollimationMethod[0] == "guide") { - return getCollimationLengthWithGuides(workspace, L1, - collimationLengthCorrection[0]); + try { + return getCollimationLengthWithGuides(workspace, L1, + collimationLengthCorrection[0]); + } catch (std::invalid_argument &ex) { + g_log.notice() << ex.what(); + g_log.notice() + << "SANSCollimationLengthEstimator: Not using any guides"; + return L1 - collimationLengthCorrection[0]; + } } else { throw std::invalid_argument("Error in SANSCollimationLengthEstimator: " "Unknown special collimation method."); } - } else { - return L1 - collimationLengthCorrection[0]; } + return L1 - collimationLengthCorrection[0]; } /** @@ -96,7 +120,7 @@ double SANSCollimationLengthEstimator::getCollimationLengthWithGuides( if (!inOutWS->getInstrument()->hasParameter( "guide-collimation-length-increment")) { throw std::invalid_argument( - "TOFSANSResolutionByPixel: Could not find a guid increment."); + "TOFSANSResolutionByPixel: Could not find a guide increment."); } auto numberOfGuides = static_cast<unsigned int>( @@ -106,14 +130,14 @@ double SANSCollimationLengthEstimator::getCollimationLengthWithGuides( // Make sure that all guides are there. They are labelled as Guide1, Guide2, // Guide3, ... - // The entry is a numeric TimeSeriesProperty + // The entry is a numeric TimeSeriesProperty or a numeric entry, if something + // else then default std::vector<double> guideValues; for (unsigned int i = 1; i <= numberOfGuides; i++) { auto guideName = "Guide" + boost::lexical_cast<std::string>(i); if (inOutWS->run().hasProperty(guideName)) { - auto guideProperty = - inOutWS->run().getTimeSeriesProperty<double>(guideName); - guideValues.push_back(guideProperty->firstValue()); + auto guideValue = getGuideValue(inOutWS->run().getProperty(guideName)); + guideValues.push_back(guideValue); } else { throw std::invalid_argument("TOFSANSResolutionByPixel: Mismatch between " "specified number of Guides and actual " @@ -147,5 +171,30 @@ double SANSCollimationLengthEstimator::getCollimationLengthWithGuides( } return lCollim; } + +/** + * Extracts the value of the guide + * @param prop: a property + * @returns the guide value + */ +double SANSCollimationLengthEstimator::getGuideValue( + Mantid::Kernel::Property *prop) const { + if (auto timeSeriesProperty = + dynamic_cast<TimeSeriesProperty<double> *>(prop)) { + return timeSeriesProperty->firstValue(); + } else if (auto doubleProperty = + dynamic_cast<PropertyWithValue<double> *>(prop)) { + auto val = doubleProperty->value(); + if (checkForDouble(val)) { + g_log.warning("SANSCollimationLengthEstimator: The Guide was not " + "recoginized as a TimeSeriesProperty, but rather as a " + "Numeric."); + return boost::lexical_cast<double, std::string>(val); + } + } + throw std::invalid_argument("TOFSANSResolutionByPixel: Unknown type for " + "Guides. Currently only Numeric and TimeSeries " + "are supported."); +} } } \ No newline at end of file diff --git a/Framework/Algorithms/test/Q1D2Test.h b/Framework/Algorithms/test/Q1D2Test.h index cd3817f71357612cc8c61f887d781cfb5674b2c5..c964161268241380674c4f38dea60e3f01c6b5e5 100644 --- a/Framework/Algorithms/test/Q1D2Test.h +++ b/Framework/Algorithms/test/Q1D2Test.h @@ -201,6 +201,8 @@ public: TS_ASSERT_DELTA(result->readE(0)[2], 404981, 10) TS_ASSERT_DELTA(result->readE(0)[10], 489710.39, 100) TS_ASSERT(boost::math::isnan(result->readE(0)[7])) + + TSM_ASSERT("Should not have a DX value", !result->hasDx(0)) } void testInvalidPixelAdj() { @@ -397,6 +399,7 @@ public: value2); // Act + TSM_ASSERT("Resolution workspace should not be NULL", qResolution) Mantid::Algorithms::Q1D2 Q1D; TS_ASSERT_THROWS_NOTHING(Q1D.initialize()); TS_ASSERT(Q1D.isInitialized()) @@ -426,6 +429,7 @@ public: Mantid::API::AnalysisDataService::Instance().retrieve( outputWS + "_sumOfCounts"))) + TSM_ASSERT("Should have the x error flag set", result->hasDx(0)); // That output will be SUM_i(Yin_i*QRES_in_i)/(SUM_i(Y_in_i)) for each q // value // In our test workspace we set QRes_in_1 to 1, this means that all DX diff --git a/Framework/Algorithms/test/SANSCollimationLengthEstimatorTest.h b/Framework/Algorithms/test/SANSCollimationLengthEstimatorTest.h index 1c778585cff318f7ff07ef813cb88e22f913bc13..495c78b0519f52d29a8c52136531c9d08c57a548 100644 --- a/Framework/Algorithms/test/SANSCollimationLengthEstimatorTest.h +++ b/Framework/Algorithms/test/SANSCollimationLengthEstimatorTest.h @@ -236,7 +236,7 @@ public: std::invalid_argument); } - void test_that_missing_guide_cutoff_throws_an_error() { + void test_that_missing_guide_cutoff_produces_a_default_value() { // Arrange double collimationLengthCorrection = 20; double collimationLengthIncrement = 12; @@ -252,13 +252,14 @@ public: samplePosition); auto collimationLengthEstimator = SANSCollimationLengthEstimator(); // Act + Assert - TSM_ASSERT_THROWS( - "Should throw an exception since we are missing the guide cutoffs.", + TSM_ASSERT_EQUALS( + "Should produce a fallback value of 25-20=5 since the guide cutoffs " + "are missing", collimationLengthEstimator.provideCollimationLength(testWorkspace), - std::invalid_argument); + 5.0); } - void test_that_missing_number_of_guides_throws_an_error() { + void test_that_missing_number_of_guides_produces_a_default_value() { // Arrange double collimationLengthCorrection = 20; double collimationLengthIncrement = 12; @@ -274,14 +275,15 @@ public: samplePosition); auto collimationLengthEstimator = SANSCollimationLengthEstimator(); // Act + Assert - TSM_ASSERT_THROWS( - "Should throw an exception since we are missing the number of guides " - "spec.", + TSM_ASSERT_EQUALS( + "Should produce a fallback value of 25-20=5 since the number of guides " + "spec is missing", collimationLengthEstimator.provideCollimationLength(testWorkspace), - std::invalid_argument); + 5.0); } - void test_that_missing_collimation_length_increment_throws_an_error() { + void + test_that_missing_collimation_length_increment_produces_a_default_value() { // Arrange double collimationLengthCorrection = 20; double collimationLengthIncrement = -1; @@ -297,11 +299,11 @@ public: samplePosition); auto collimationLengthEstimator = SANSCollimationLengthEstimator(); // Act + Assert - TSM_ASSERT_THROWS( - "Should throw an exception since we are missing the collimation length " - "increment.", + TSM_ASSERT_EQUALS( + "Should produce a fallback value of 25-20=5 since the collimation " + "length increment is missing.", collimationLengthEstimator.provideCollimationLength(testWorkspace), - std::invalid_argument); + 5.0); } void @@ -321,11 +323,11 @@ public: samplePosition); auto collimationLengthEstimator = SANSCollimationLengthEstimator(); // Act + Assert - TSM_ASSERT_THROWS( - "Should throw an exception since there is mismatch between the " - "specified number of guides and the guides in the logs", + TSM_ASSERT_EQUALS( + "Should produce a fallback value of 25-20=5 since there is a mismatch " + "between the number of guides in the log and in the spec", collimationLengthEstimator.provideCollimationLength(testWorkspace), - std::invalid_argument); + 5.0); } void test_that_5_log_guides_are_all_picked_up_and_contribute() { diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h index fec5247513bd2620f1dff9fd8ae13370afaaf479..fb4d8c849507594507225e8d696385b96365b02c 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSConstants.h @@ -18,11 +18,18 @@ public: QString getPythonSuccessKeyword(); QString getPythonEmptyKeyword(); QString getPythonTrueKeyword(); + QString getPythonFalseKeyword(); + QString getQResolutionH1ToolTipText(); + QString getQResolutionH2ToolTipText(); + QString getQResolutionA1ToolTipText(); + QString getQResolutionA2ToolTipText(); + // Input related double getMaxDoubleValue(); int getMaxIntValue(); int getDecimals(); + }; } diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h index 0dc60514e8c56492f6a263d1a0f58d40ac7aeb7c..91245c83f0e6b1b595c428da017db8928915d5f2 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.h @@ -75,9 +75,9 @@ public: NoSample, ///< No sample workspace has yet been loaded Loading, ///< Workspaces are loading Ready, ///< A sample workspace is loaded and the reduce buttons should be - ///active - OneD, ///< Signifies a 1D reduction - TwoD ///< For 2D reductions + /// active + OneD, ///< Signifies a 1D reduction + TwoD ///< For 2D reductions }; /// Default Constructor SANSRunWindow(QWidget *parent = 0); @@ -109,6 +109,9 @@ private: ONE_D_ANALYSIS }; + /// Enum for the two states of the Q Resolution aperture selection + enum QResoluationAperture { CIRCULAR, RECTANGULAR }; + /// Initialize the layout virtual void initLayout(); /// Init Python environment @@ -310,6 +313,8 @@ private slots: void onLeftRightCheckboxChanged(); /// React to change in Up/Down checkbox void onUpDownCheckboxChanged(); + /// Handle a change of the aperture geometry for QResolution + void handleQResolutionApertureChange(int aperture); private: /// used to specify the range of validation to do @@ -459,6 +464,35 @@ private: void applyLOQSettings(bool isNowLOQ); /// Set the beam finder details void setBeamFinderDetails(); + /// Gets the QResolution settings and shows them in the GUI + void retrieveQResolutionSettings(); + /// Gets the QResolution settings for the aperture, decides for the the + /// correct setting and displays it + void retrieveQResolutionAperture(); + /// General getter for aperture settings of Q Resolution + QString retrieveQResolutionGeometry(QString command); + /// Write the QResolution GUI changes to a python script + void writeQResolutionSettingsToPythonScript(QString &pythonCode); + /// Write single line for Q Resolution + void writeQResolutionSettingsToPythonScriptSingleEntry( + QString value, QString code_entry, const QString lineEnding, + QString &py_code) const; + /// Sets the cirucular aperture, ie labels and populates the values with what + /// is available from the user file + void setupQResolutionCircularAperture(); + /// Sets the rectuanular aperture + void setupQResolutionRectangularAperture(QString h1, QString w1, QString h2, + QString w2); + /// Set the rectangular aperture + void setupQResolutionRectangularAperture(); + /// Set the aperture type + void setQResolutionApertureType(QResoluationAperture apertureType, + QString a1H1Label, QString a2H2Label, + QString a1H1, QString a2H2, + QString toolTipA1H1, QString toolTipA2H2, + bool w1W2Disabled); + /// Initialize the QResolution settings + void initQResolutionSettings(); UserSubWindow *slicingWindow; }; diff --git a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui index 984a9246bc607f29b47f881614a2fd3cec300f03..f257113b7f3f4e3be75364623c5321259958a719 100644 --- a/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui +++ b/MantidQt/CustomInterfaces/inc/MantidQtCustomInterfaces/SANSRunWindow.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>950</width> - <height>545</height> + <height>605</height> </rect> </property> <property name="sizePolicy"> @@ -868,74 +868,120 @@ Zero Error</string> <string>Reduction Settings</string> </attribute> <layout class="QGridLayout" name="gridLayout_13"> - <item row="0" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <widget class="QLabel" name="label_23"> - <property name="text"> - <string>Account for gravity</string> - </property> - <property name="alignment"> - <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="gravity_check"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <spacer name="horizontalSpacer_23"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QLabel" name="label_15"> - <property name="text"> - <string>Extra Length (m)</string> - </property> - </widget> - </item> + <item row="1" column="1"> + <layout class="QVBoxLayout" name="verticalLayout_8"> <item> - <widget class="QLineEdit" name="gravity_extra_length_line_edit"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maximumSize"> - <size> - <width>70</width> - <height>16777215</height> - </size> + <widget class="QGroupBox" name="groupBox_18"> + <property name="title"> + <string>Transmission Settings</string> </property> + <layout class="QVBoxLayout" name="verticalLayout_15"> + <item> + <layout class="QGridLayout" name="gridLayout_32"> + <item row="1" column="4"> + <widget class="QLineEdit" name="trans_roi_files_line_edit"> + <property name="minimumSize"> + <size> + <width>200</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="trans_radius_line_edit"/> + </item> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_25"> + <item> + <widget class="QCheckBox" name="trans_M3_check_box"> + <property name="text"> + <string>M3</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="trans_M4_check_box"> + <property name="text"> + <string>M4</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="0" column="2"> + <widget class="QCheckBox" name="trans_radius_check_box"> + <property name="text"> + <string>Radius</string> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QCheckBox" name="trans_roi_files_checkbox"> + <property name="text"> + <string>ROI files</string> + </property> + </widget> + </item> + <item row="0" column="1" rowspan="2"> + <widget class="QFrame" name="frame"> + <property name="frameShape"> + <enum>QFrame::VLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="lineWidth"> + <number>1</number> + </property> + </widget> + </item> + <item row="0" column="3" rowspan="2"> + <widget class="QFrame" name="frame_2"> + <property name="frameShape"> + <enum>QFrame::VLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="lineWidth"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_28"> + <item> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Shift:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="trans_M3M4_line_edit"/> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_26"> + <item> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>Masking files:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="trans_masking_line_edit"/> + </item> + </layout> + </item> + </layout> </widget> </item> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> </layout> </item> <item row="1" column="0" rowspan="2"> @@ -1620,121 +1666,218 @@ intermediate ranges are there for comparison</string> </item> </layout> </item> - <item row="1" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_8"> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QGroupBox" name="groupBox_18"> - <property name="title"> - <string>Transmission Settings</string> + <widget class="QLabel" name="label_23"> + <property name="text"> + <string>Account for gravity</string> </property> - <layout class="QVBoxLayout" name="verticalLayout_15"> - <item> - <layout class="QGridLayout" name="gridLayout_32"> - <item row="1" column="4"> - <widget class="QLineEdit" name="trans_roi_files_line_edit"> - <property name="minimumSize"> - <size> - <width>200</width> - <height>0</height> - </size> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QLineEdit" name="trans_radius_line_edit"/> - </item> - <item row="0" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_25"> - <item> - <widget class="QCheckBox" name="trans_M3_check_box"> - <property name="text"> - <string>M3</string> - </property> - </widget> - </item> - <item> - <widget class="QCheckBox" name="trans_M4_check_box"> - <property name="text"> - <string>M4</string> - </property> - </widget> - </item> - </layout> - </item> - <item row="0" column="2"> - <widget class="QCheckBox" name="trans_radius_check_box"> - <property name="text"> - <string>Radius</string> - </property> - </widget> - </item> - <item row="0" column="4"> - <widget class="QCheckBox" name="trans_roi_files_checkbox"> - <property name="text"> - <string>ROI files</string> - </property> - </widget> - </item> - <item row="0" column="1" rowspan="2"> - <widget class="QFrame" name="frame"> - <property name="frameShape"> - <enum>QFrame::VLine</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - <property name="lineWidth"> - <number>1</number> - </property> - </widget> - </item> - <item row="0" column="3" rowspan="2"> - <widget class="QFrame" name="frame_2"> - <property name="frameShape"> - <enum>QFrame::VLine</enum> - </property> - <property name="frameShadow"> - <enum>QFrame::Raised</enum> - </property> - <property name="lineWidth"> - <number>1</number> - </property> - </widget> - </item> - <item row="1" column="0"> - <layout class="QHBoxLayout" name="horizontalLayout_28"> - <item> - <widget class="QLabel" name="label_17"> - <property name="text"> - <string>Shift:</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="trans_M3M4_line_edit"/> - </item> - </layout> - </item> - </layout> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="gravity_check"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_23"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="label_15"> + <property name="text"> + <string>Extra Length (m)</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="gravity_extra_length_line_edit"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="maximumSize"> + <size> + <width>70</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + <item row="3" column="0"> + <widget class="QGroupBox" name="groupBox_6"> + <property name="title"> + <string>Efficiency correction</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <item row="0" column="1"> + <widget class="QLabel" name="label_27"> + <property name="text"> + <string>Rear</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="direct_file"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>275</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="label_28"> + <property name="text"> + <string>Front</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLineEdit" name="front_direct_file"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="4" column="0" rowspan="2"> + <widget class="QGroupBox" name="groupBox_17"> + <property name="title"> + <string>Apply flood correction file</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_13"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_21"> + <item> + <widget class="QCheckBox" name="enableRearFlood_ck"> + <property name="text"> + <string/> + </property> + </widget> </item> <item> - <layout class="QHBoxLayout" name="horizontalLayout_26"> - <item> - <widget class="QLabel" name="label_16"> - <property name="text"> - <string>Masking files:</string> - </property> - </widget> - </item> - <item> - <widget class="QLineEdit" name="trans_masking_line_edit"/> - </item> - </layout> + <widget class="MantidQt::MantidWidgets::MWRunFiles" name="floodRearFile" native="true"> + <property name="findRunFiles" stdset="0"> + <bool>false</bool> + </property> + <property name="label" stdset="0"> + <string>Rear Flood File</string> + </property> + <property name="multipleFiles" stdset="0"> + <bool>false</bool> + </property> + <property name="optional" stdset="0"> + <bool>false</bool> + </property> + <property name="multiEntry" stdset="0"> + <bool>false</bool> + </property> + </widget> </item> </layout> - </widget> - </item> - </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_22"> + <item> + <widget class="QCheckBox" name="enableFrontFlood_ck"> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item> + <widget class="MantidQt::MantidWidgets::MWRunFiles" name="floodFrontFile" native="true"> + <property name="findRunFiles" stdset="0"> + <bool>false</bool> + </property> + <property name="label" stdset="0"> + <string>Front Flood File</string> + </property> + <property name="multipleFiles" stdset="0"> + <bool>false</bool> + </property> + <property name="optional" stdset="0"> + <bool>false</bool> + </property> + <property name="multiEntry" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item row="7" column="1"> + <spacer name="verticalSpacer_8"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> </item> <item row="2" column="1" rowspan="3"> <layout class="QVBoxLayout" name="verticalLayout_11"> @@ -1981,136 +2124,6 @@ intermediate ranges are there for comparison</string> </item> </layout> </item> - <item row="3" column="0"> - <widget class="QGroupBox" name="groupBox_6"> - <property name="title"> - <string>Efficiency correction</string> - </property> - <layout class="QGridLayout" name="gridLayout_2"> - <item row="0" column="1"> - <widget class="QLabel" name="label_27"> - <property name="text"> - <string>Rear</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLineEdit" name="direct_file"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="minimumSize"> - <size> - <width>275</width> - <height>0</height> - </size> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - </widget> - </item> - <item row="1" column="1"> - <widget class="QLabel" name="label_28"> - <property name="text"> - <string>Front</string> - </property> - </widget> - </item> - <item row="1" column="2"> - <widget class="QLineEdit" name="front_direct_file"> - <property name="enabled"> - <bool>false</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Maximum"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - </widget> - </item> - </layout> - </widget> - </item> - <item row="4" column="0" rowspan="2"> - <widget class="QGroupBox" name="groupBox_17"> - <property name="title"> - <string>Apply flood correction file</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout_13"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_21"> - <item> - <widget class="QCheckBox" name="enableRearFlood_ck"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="MantidQt::MantidWidgets::MWRunFiles" name="floodRearFile" native="true"> - <property name="findRunFiles" stdset="0"> - <bool>false</bool> - </property> - <property name="label" stdset="0"> - <string>Rear Flood File</string> - </property> - <property name="multipleFiles" stdset="0"> - <bool>false</bool> - </property> - <property name="optional" stdset="0"> - <bool>false</bool> - </property> - <property name="multiEntry" stdset="0"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </item> - <item> - <layout class="QHBoxLayout" name="horizontalLayout_22"> - <item> - <widget class="QCheckBox" name="enableFrontFlood_ck"> - <property name="text"> - <string/> - </property> - </widget> - </item> - <item> - <widget class="MantidQt::MantidWidgets::MWRunFiles" name="floodFrontFile" native="true"> - <property name="findRunFiles" stdset="0"> - <bool>false</bool> - </property> - <property name="label" stdset="0"> - <string>Front Flood File</string> - </property> - <property name="multipleFiles" stdset="0"> - <bool>false</bool> - </property> - <property name="optional" stdset="0"> - <bool>false</bool> - </property> - <property name="multiEntry" stdset="0"> - <bool>false</bool> - </property> - </widget> - </item> - </layout> - </item> - </layout> - </widget> - </item> <item row="5" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_6"> <item> @@ -2318,6 +2331,200 @@ intermediate ranges are there for comparison</string> </item> </layout> </item> + <item row="7" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_27"> + <item> + <widget class="QGroupBox" name="q_resolution_group_box"> + <property name="title"> + <string>QResolution</string> + </property> + <property name="checkable"> + <bool>true</bool> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <layout class="QGridLayout" name="gridLayout_33"> + <item row="2" column="9"> + <widget class="QLabel" name="q_resolution_delta_r_label"> + <property name="text"> + <string>Delta R [mm]:</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLineEdit" name="q_resolution_a1_h1_input"> + <property name="maximumSize"> + <size> + <width>35</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item row="2" column="7"> + <widget class="QLabel" name="q_resolution_collimation_length_label"> + <property name="text"> + <string>LCollim [m]:</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLineEdit" name="q_resolution_a2_h2_input"> + <property name="maximumSize"> + <size> + <width>35</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item row="0" column="8" colspan="3"> + <widget class="QLineEdit" name="q_resolution_moderator_input"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="readOnly"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="10"> + <widget class="QLineEdit" name="q_resolution_delta_r_input"> + <property name="maximumSize"> + <size> + <width>35</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item row="2" column="8"> + <widget class="QLineEdit" name="q_resolution_collimation_length_input"> + <property name="maximumSize"> + <size> + <width>35</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item row="0" column="4"> + <widget class="QLabel" name="q_resolution_w1_label"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>W1[mm]</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLabel" name="q_resolution_a1_h1_label"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>A1[mm]:</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QComboBox" name="q_resolution_combo_box"> + <property name="maximumSize"> + <size> + <width>50</width> + <height>16777215</height> + </size> + </property> + <item> + <property name="text"> + <string>Circ.</string> + </property> + </item> + <item> + <property name="text"> + <string>Rect.</string> + </property> + </item> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="q_resolution_a2_h2_label"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>A2[mm]:</string> + </property> + </widget> + </item> + <item row="2" column="5"> + <widget class="QLineEdit" name="q_resolution_w2_input"> + <property name="maximumSize"> + <size> + <width>35</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + <item row="2" column="4"> + <widget class="QLabel" name="q_resolution_w2_label"> + <property name="maximumSize"> + <size> + <width>40</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>W2[mm]</string> + </property> + </widget> + </item> + <item row="0" column="7"> + <widget class="QLabel" name="q_resolution_moderator_label"> + <property name="text"> + <string>Moderator:</string> + </property> + </widget> + </item> + <item row="0" column="5"> + <widget class="QLineEdit" name="q_resolution_w1_input"> + <property name="maximumSize"> + <size> + <width>35</width> + <height>16777215</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </item> </layout> </widget> <widget class="QWidget" name="tab_4"> diff --git a/MantidQt/CustomInterfaces/src/SANSConstants.cpp b/MantidQt/CustomInterfaces/src/SANSConstants.cpp index 93ffc5689b8ed4ae782a91792d896044413f0c8c..f4ebb7860159f9ba54412ff4b20e5d6389e62444 100644 --- a/MantidQt/CustomInterfaces/src/SANSConstants.cpp +++ b/MantidQt/CustomInterfaces/src/SANSConstants.cpp @@ -23,8 +23,8 @@ QString SANSConstants::getPythonSuccessKeyword() { * @returns the python keyword for an empty object */ QString SANSConstants::getPythonEmptyKeyword() { - const static QString pythonSuccessKeyword = "None"; - return pythonSuccessKeyword; + const static QString pythonEmptyKeyword = "None"; + return pythonEmptyKeyword; } /** @@ -32,10 +32,56 @@ QString SANSConstants::getPythonEmptyKeyword() { * @returns the python true keyword */ QString SANSConstants::getPythonTrueKeyword() { - const static QString pythonSuccessKeyword = "True"; - return pythonSuccessKeyword; + const static QString pythonTrueKeyword = "True"; + return pythonTrueKeyword; +} + +/** + * Defines the python keyword for false , ie False + * @returns the python false keyword + */ +QString SANSConstants::getPythonFalseKeyword() { + const static QString pythonFalseKeyword = "False"; + return pythonFalseKeyword; } +/** + * Gets the tooltip for h11 for QResolution + * @returns tooltip text for h1 + */ +QString SANSConstants::getQResolutionH1ToolTipText() { + const QString qResolutionH1ToolTipText = "The height of the first aperture in mm."; + return qResolutionH1ToolTipText; +} + +/** + * Gets the tooltip for h2 for QResolution + * @returns tooltip text for h2 + */ +QString SANSConstants::getQResolutionH2ToolTipText() { + const QString qResolutionH2ToolTipText = "The height of the seoncd aperture in mm."; + return qResolutionH2ToolTipText; +} + +/** + * Gets the tooltip for a1 for QResolution + * @returns tooltip text for a1 + */ +QString SANSConstants::getQResolutionA1ToolTipText() { + const QString qResolutionA1ToolTipText = "The diameter for the first aperture"; + return qResolutionA1ToolTipText; +} + +/** + * Gets the tooltip for a2 for QResolution + * @returns tooltip text for a2 + */ +QString SANSConstants::getQResolutionA2ToolTipText() { + const QString qResolutionA2ToolTipText = "The diameter for the second aperture"; + return qResolutionA2ToolTipText; +} + + /** * Gets the max double value * @returns the max double diff --git a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp index fdb9b58f4dec0cb0c1b326bdd302e3c0b6d53e45..8a70e38da5fe71fe03ab4723ede9784d9007d9db 100644 --- a/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp +++ b/MantidQt/CustomInterfaces/src/SANSRunWindow.cpp @@ -244,8 +244,10 @@ void SANSRunWindow::initLayout() { m_uiForm.centre_logging->attachLoggingChannel(); connect(m_uiForm.clear_centre_log, SIGNAL(clicked()), m_uiForm.centre_logging, SLOT(clear())); - connect(m_uiForm.up_down_checkbox, SIGNAL(stateChanged(int)), this, SLOT(onUpDownCheckboxChanged())); - connect(m_uiForm.left_right_checkbox, SIGNAL(stateChanged(int)), this, SLOT(onLeftRightCheckboxChanged())); + connect(m_uiForm.up_down_checkbox, SIGNAL(stateChanged(int)), this, + SLOT(onUpDownCheckboxChanged())); + connect(m_uiForm.left_right_checkbox, SIGNAL(stateChanged(int)), this, + SLOT(onLeftRightCheckboxChanged())); // Create the widget hash maps initWidgetMaps(); @@ -319,6 +321,9 @@ void SANSRunWindow::initLayout() { // Setup the Transmission Settings initTransmissionSettings(); + // Setup the QResolution Settings + initQResolutionSettings(); + // Set the validators setValidators(); @@ -424,7 +429,7 @@ void SANSRunWindow::setupSaveBox() { SLOT(enableOrDisableDefaultSave())); } } -/** Raises a saveWorkspaces dialog which allows people to save any workspace +/** Raises a saveWorkspaces dialog which allows people to save any workspace * workspaces the user chooses */ void SANSRunWindow::saveWorkspacesDialog() { @@ -1040,6 +1045,9 @@ bool SANSRunWindow::loadUserFile() { // Masking table updateMaskTable(); + // Setup the QResolution + retrieveQResolutionSettings(); + if (runReduceScriptFunction("print i.ReductionSingleton().mask.phi_mirror") .trimmed() == "True") { m_uiForm.mirror_phi->setChecked(true); @@ -2302,6 +2310,9 @@ QString SANSRunWindow::readUserFileGUIChanges(const States type) { // Set the Transmision settings writeTransmissionSettingsToPythonScript(exec_reduce); + // Set the QResolution settings + writeQResolutionSettingsToPythonScript(exec_reduce); + // set the user defined center (Geometry Tab) // this information is used just after loading the data in order to move to // the center @@ -2404,6 +2415,12 @@ void SANSRunWindow::handleReduceButtonClick(const QString &typeStr) { // Need to check which mode we're in if (runMode == SingleMode) { py_code += readSampleObjectGUIChanges(); + + // Provide a final check here to ensure that the settings are consistent. If + // they are not consistent, the function + // throws and the user has to fix these inconsistencies + py_code += "\ni.are_settings_consistent()"; + py_code += reduceSingleRun(); // output the name of the output workspace, this is returned up by the // runPythonCode() call below @@ -2729,7 +2746,8 @@ void SANSRunWindow::handleRunFindCentre() { py_code += "i.SetDetectorFloodFile('')\n"; // We need to load the FinDirectionEnum class - py_code += "from centre_finder import FindDirectionEnum as FindDirectionEnum \n"; + py_code += + "from centre_finder import FindDirectionEnum as FindDirectionEnum \n"; // Find centre function py_code += "i.FindBeamCentre(rlow=" + m_uiForm.beam_rmin->text() + ",rupp=" + m_uiForm.beam_rmax->text() + ",MaxIter=" + @@ -2756,7 +2774,7 @@ void SANSRunWindow::handleRunFindCentre() { setProcessingState(Ready); return; } - py_code += ", tolerance=" + QString::number(tolerance); + py_code += ", tolerance=" + QString::number(tolerance); // Set which part of the beam centre finder should be used auto updownIsRequired = m_uiForm.up_down_checkbox->isChecked(); @@ -4379,8 +4397,9 @@ bool SANSRunWindow::areSettingsValid() { m_uiForm.q_dq_opt, "Qx"); // TRANS SAMPLE - checkWaveLengthAndQValues(isValid, message, m_uiForm.trans_min, - m_uiForm.trans_max, m_uiForm.trans_opt, "Trans"); + if (m_uiForm.transFit_ck->isChecked()) + checkWaveLengthAndQValues(isValid, message, m_uiForm.trans_min, + m_uiForm.trans_max, m_uiForm.trans_opt, "Trans"); // TRANS CAN if (m_uiForm.trans_selector_opt->currentText().toUpper().contains( @@ -4457,8 +4476,7 @@ void SANSRunWindow::updateBeamCenterCoordinates() { // from the ticket #5942 both detectors have center coordinates double dbl_param = runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[0]") - .toDouble(); + "print i.ReductionSingleton().get_beam_center('rear')[0]").toDouble(); // get the scale factor1 for the beam centre to scale it correctly double dbl_paramsf = runReduceScriptFunction( @@ -4470,9 +4488,9 @@ void SANSRunWindow::updateBeamCenterCoordinates() { runReduceScriptFunction( "print i.ReductionSingleton().get_beam_center_scale_factor2()") .toDouble(); - dbl_param = runReduceScriptFunction( - "print i.ReductionSingleton().get_beam_center('rear')[1]") - .toDouble(); + dbl_param = + runReduceScriptFunction( + "print i.ReductionSingleton().get_beam_center('rear')[1]").toDouble(); m_uiForm.rear_beam_y->setText(QString::number(dbl_param * dbl_paramsf)); // front dbl_param = runReduceScriptFunction( @@ -4493,9 +4511,9 @@ void SANSRunWindow::setBeamFinderDetails() { auto instrumentName = m_uiForm.inst_opt->currentText(); // Set the labels according to the instrument - auto requiresAngle = runReduceScriptFunction( - "print i.is_current_workspace_an_angle_workspace()") - .simplified(); + auto requiresAngle = + runReduceScriptFunction( + "print i.is_current_workspace_an_angle_workspace()").simplified(); QString labelPosition; if (requiresAngle == m_constants.getPythonTrueKeyword()) { labelPosition = "Current ( " + QString(QChar(0x03B2)) + " , y ) ["; @@ -4507,5 +4525,327 @@ void SANSRunWindow::setBeamFinderDetails() { m_uiForm.beam_centre_finder_groupbox->setTitle(labelPosition); } +/** + * Retrieves the Q resolution settings and apply them to the GUI + */ +void SANSRunWindow::retrieveQResolutionSettings() { + // Set if the QResolution should be used at all + QString getUseage = "i.get_q_resultution_use()\n"; + QString resultUsage(runPythonCode(getUseage, false)); + resultUsage = resultUsage.simplified(); + if (resultUsage == m_constants.getPythonTrueKeyword()) { + m_uiForm.q_resolution_group_box->setChecked(true); + } else if (resultUsage == m_constants.getPythonFalseKeyword()) { + m_uiForm.q_resolution_group_box->setChecked(false); + } else { + g_log.warning(resultUsage.toStdString()); + g_log.warning("Not a valid setting for the useage of QResolution"); + m_uiForm.q_resolution_group_box->setChecked(false); + } + + // Set the Collimation length + auto resultCollimationLength = + retrieveQResolutionGeometry("i.get_q_resolution_collimation_length()\n"); + m_uiForm.q_resolution_collimation_length_input->setText( + resultCollimationLength); + + // Set the Delta R value + auto resultDeltaR = + retrieveQResolutionGeometry("i.get_q_resolution_delta_r()\n"); + m_uiForm.q_resolution_delta_r_input->setText(resultDeltaR); + + // Set the moderator file + QString getModeratorFile = "i.get_q_resolution_moderator()\n"; + QString resultModeratorFile = runPythonCode(getModeratorFile, false); + if (resultModeratorFile == m_constants.getPythonEmptyKeyword()) { + resultModeratorFile = ""; + } + m_uiForm.q_resolution_moderator_input->setText(resultModeratorFile); + + // Set the geometry, ie if rectangular or circular aperture + retrieveQResolutionAperture(); +} + +/** + * Retrieve the QResolution setting for the aperture. Select the aperture + * type depending on the available values, ie if there are H1, W1, H2, W2 + * specified, + * then we are dealing with are rectangular aperture, else with a circular + */ +void SANSRunWindow::retrieveQResolutionAperture() { + // Get the H1, W1, H2, W2 + auto h1 = retrieveQResolutionGeometry("i.get_q_resolution_h1()\n"); + auto w1 = retrieveQResolutionGeometry("i.get_q_resolution_w1()\n"); + auto h2 = retrieveQResolutionGeometry("i.get_q_resolution_h2()\n"); + auto w2 = retrieveQResolutionGeometry("i.get_q_resolution_w2()\n"); + + // If at least one of them is empty, then use circular, otherwise use + // rectangular + auto useCircular = + h1.isEmpty() || w1.isEmpty() || h2.isEmpty() || w2.isEmpty(); + if (useCircular) { + setupQResolutionCircularAperture(); + } else { + setupQResolutionRectangularAperture(h1, w1, h2, w2); + } +} + +/** + * Gets the geometry settings and checks if they are empty or not + * @param command: the python command to execute + * @returns either a length (string) in mm or an empty string + */ +QString SANSRunWindow::retrieveQResolutionGeometry(QString command) { + QString result(runPythonCode(command, false)); + result = result.simplified(); + if (result == m_constants.getPythonEmptyKeyword()) { + result = ""; + } + return result; +} + +/** + * Setup the GUI for use with a circular aperture + */ +void SANSRunWindow::setupQResolutionCircularAperture() { + // Get the apertures of the diameter + auto a1 = retrieveQResolutionGeometry("i.get_q_resolution_a1()\n"); + auto a2 = retrieveQResolutionGeometry("i.get_q_resolution_a2()\n"); + + setQResolutionApertureType(QResoluationAperture::CIRCULAR, "A1 [mm]", + "A2 [mm]", a1, a2, + m_constants.getQResolutionA1ToolTipText(), + m_constants.getQResolutionA2ToolTipText(), true); +} + +/** + * Setup the GUI for use with a rectangular aperture + * @param h1: the height of the first aperture + * @param w1: the width of the first aperture + * @param h2: the height of the second aperture + * @param w2: the width of the second aperture + */ +void SANSRunWindow::setupQResolutionRectangularAperture(QString h1, QString w1, + QString h2, + QString w2) { + // Set the QResolution Aperture + setQResolutionApertureType(QResoluationAperture::RECTANGULAR, "H1 [mm]", + "H2 [mm]", h1, h2, + m_constants.getQResolutionH1ToolTipText(), + m_constants.getQResolutionH2ToolTipText(), false); + + // Set the W1 and W2 values + m_uiForm.q_resolution_w1_input->setText(w1); + m_uiForm.q_resolution_w2_input->setText(w2); + + // Set the ToolTip for a1 + m_uiForm.q_resolution_a1_h1_input->setToolTip( + m_constants.getQResolutionH1ToolTipText()); + m_uiForm.q_resolution_a1_h1_label->setToolTip( + m_constants.getQResolutionH1ToolTipText()); + + // Set the ToolTip for a2 + m_uiForm.q_resolution_a2_h2_input->setToolTip( + m_constants.getQResolutionH2ToolTipText()); + m_uiForm.q_resolution_a2_h2_label->setToolTip( + m_constants.getQResolutionH2ToolTipText()); +} + +/** + * Setup the GUI for use iwth a rectangular aperture + */ +void SANSRunWindow::setupQResolutionRectangularAperture() { + auto h1 = retrieveQResolutionGeometry("i.get_q_resolution_h1()\n"); + auto w1 = retrieveQResolutionGeometry("i.get_q_resolution_w1()\n"); + auto h2 = retrieveQResolutionGeometry("i.get_q_resolution_h2()\n"); + auto w2 = retrieveQResolutionGeometry("i.get_q_resolution_w2()\n"); + + setupQResolutionRectangularAperture(h1, w1, h2, w2); +} + +/** + * Set the QResolution aperture GUI + * @param apertureType: the type of the aperture + * @param a1H1Label: the label for the a1/h1 input + * @param a2H2Label: the label for the a2/h2 input + * @param a1H1: the a1H1 value + * @param a2H2: the a2H2 value + * @param toolTipA1H1: the tooltip text for the first aperture parameter + * @param toolTipA2H2: the tooltip text for the second aperture parameter + * @param w1W2Disabled: if the w1W2Inputs should be disabled + */ +void SANSRunWindow::setQResolutionApertureType( + QResoluationAperture apertureType, QString a1H1Label, QString a2H2Label, + QString a1H1, QString a2H2, QString toolTipA1H1, QString toolTipA2H2, + bool w1W2Disabled) { + // Set the labels + m_uiForm.q_resolution_a1_h1_label->setText(a1H1Label); + m_uiForm.q_resolution_a2_h2_label->setText(a2H2Label); + + // Set the values + m_uiForm.q_resolution_a1_h1_input->setText(a1H1); + m_uiForm.q_resolution_a2_h2_input->setText(a2H2); + + // Ensure that the W1 and W2 boxes are not accesible + m_uiForm.q_resolution_w1_label->setDisabled(w1W2Disabled); + m_uiForm.q_resolution_w2_label->setDisabled(w1W2Disabled); + m_uiForm.q_resolution_w1_input->setDisabled(w1W2Disabled); + m_uiForm.q_resolution_w2_input->setDisabled(w1W2Disabled); + + // Set the QCheckBox to the correct value + m_uiForm.q_resolution_combo_box->setCurrentIndex(apertureType); + + // Set the ToolTip for a1/a2 + m_uiForm.q_resolution_a1_h1_input->setToolTip(toolTipA1H1); + m_uiForm.q_resolution_a1_h1_label->setToolTip(toolTipA1H1); + + // Set the ToolTip for a2 + m_uiForm.q_resolution_a2_h2_input->setToolTip(toolTipA2H2); + m_uiForm.q_resolution_a2_h2_label->setToolTip(toolTipA2H2); +} + +/** + * Write the GUI changes for the QResolution settings to the python code string + * @param pythonCode: A reference to the python code + */ +void SANSRunWindow::writeQResolutionSettingsToPythonScript( + QString &pythonCode) { + // Clear the current settings + pythonCode += "i.reset_q_resolution_settings()\n"; + const QString lineEnding1 = ")\n"; + const QString lineEnding2 = "')\n"; + // Set usage of QResolution + auto usageGUI = m_uiForm.q_resolution_group_box->isChecked(); + QString useage = usageGUI ? m_constants.getPythonTrueKeyword() + : m_constants.getPythonFalseKeyword(); + pythonCode += "i.set_q_resolution_use(use=" + useage + ")\n"; + + // Set collimation length + auto collimationLength = + m_uiForm.q_resolution_collimation_length_input->text().simplified(); + writeQResolutionSettingsToPythonScriptSingleEntry( + collimationLength, + "i.set_q_resolution_collimation_length(collimation_length=", lineEnding1, + pythonCode); + // Set the moderator file + auto moderatorFile = + m_uiForm.q_resolution_moderator_input->text().simplified(); + writeQResolutionSettingsToPythonScriptSingleEntry( + moderatorFile, "i.set_q_resolution_moderator(file_name='", lineEnding2, + pythonCode); + // Set the delta r value + auto deltaR = m_uiForm.q_resolution_delta_r_input->text().simplified(); + writeQResolutionSettingsToPythonScriptSingleEntry( + deltaR, "i.set_q_resolution_delta_r(delta_r=", lineEnding1, pythonCode); + // Set the aperture properties depending on the aperture type + auto a1H1 = m_uiForm.q_resolution_a1_h1_input->text().simplified(); + auto a2H2 = m_uiForm.q_resolution_a2_h2_input->text().simplified(); + if (m_uiForm.q_resolution_combo_box->currentIndex() == + QResoluationAperture::CIRCULAR) { + writeQResolutionSettingsToPythonScriptSingleEntry( + a1H1, "i.set_q_resolution_a1(a1=", lineEnding1, pythonCode); + writeQResolutionSettingsToPythonScriptSingleEntry( + a2H2, "i.set_q_resolution_a2(a2=", lineEnding1, pythonCode); + } else if (m_uiForm.q_resolution_combo_box->currentIndex() == + QResoluationAperture::RECTANGULAR) { + writeQResolutionSettingsToPythonScriptSingleEntry( + a1H1, "i.set_q_resolution_h1(h1=", lineEnding1, pythonCode); + writeQResolutionSettingsToPythonScriptSingleEntry( + a2H2, "i.set_q_resolution_h2(h2=", lineEnding1, pythonCode); + // Set the W1 and W2 parameters + auto w1 = m_uiForm.q_resolution_w1_input->text().simplified(); + writeQResolutionSettingsToPythonScriptSingleEntry( + w1, "i.set_q_resolution_w1(w1=", lineEnding1, pythonCode); + auto w2 = m_uiForm.q_resolution_w2_input->text().simplified(); + writeQResolutionSettingsToPythonScriptSingleEntry( + w2, "i.set_q_resolution_w2(w2=", lineEnding1, pythonCode); + } else { + g_log.error("SANSRunWindow: Tried to select a QResolution aperture which " + "does not seem to exist"); + } +} + +/** + * Write a single line of python code for Q Resolution + * @param value: The value to set + * @param code_entry: tye python method to run + * @param lineEnding: the line ending + * @param py_code: the code segment to which we want to append + */ +void SANSRunWindow::writeQResolutionSettingsToPythonScriptSingleEntry( + QString value, QString code_entry, const QString lineEnding, + QString &py_code) const { + if (!value.isEmpty()) { + py_code += code_entry + value + lineEnding; + } +} + +/** + * Handle a chagne of the QResolution aperture selection + * @param aperture: the current index + */ +void SANSRunWindow::handleQResolutionApertureChange(int aperture) { + if (aperture == QResoluationAperture::CIRCULAR) { + setupQResolutionCircularAperture(); + } else if (aperture == QResoluationAperture::RECTANGULAR) { + setupQResolutionRectangularAperture(); + } else { + g_log.error("SANSRunWindow: Tried to select a QResolution aperture which " + "does not seem to exist"); + } +} + +/** + * Initialize the QResolution settings + */ +void SANSRunWindow::initQResolutionSettings() { + // Connect the change of the change of the aperture + QObject::connect(m_uiForm.q_resolution_combo_box, + SIGNAL(currentIndexChanged(int)), this, + SLOT(handleQResolutionApertureChange(int))); + + // Set the Tooltips for Moderator + const QString moderator("The full path to the moderator file."); + m_uiForm.q_resolution_moderator_input->setToolTip(moderator); + m_uiForm.q_resolution_moderator_label->setToolTip(moderator); + + // Set the ToolTip for the Collimation length + const QString collimationLength("The collimation length in m."); + m_uiForm.q_resolution_collimation_length_input->setToolTip(collimationLength); + m_uiForm.q_resolution_collimation_length_label->setToolTip(collimationLength); + + // Set the ToolTip for Delta R + const QString deltaR("The delta r in mm."); + m_uiForm.q_resolution_delta_r_input->setToolTip(deltaR); + m_uiForm.q_resolution_delta_r_label->setToolTip(deltaR); + + // Set the ToolTip for w1 + const QString w1("The width of the first aperture in mm."); + m_uiForm.q_resolution_w1_input->setToolTip(w1); + m_uiForm.q_resolution_w1_label->setToolTip(w1); + + // Set the ToolTip for w2 + const QString w2("The width of the second aperture in mm."); + m_uiForm.q_resolution_w2_input->setToolTip(w2); + m_uiForm.q_resolution_w2_label->setToolTip(w2); + + // Set the dropdown menu + const QString aperture("Select if a circular or rectangular aperture \n" + "should be used"); + m_uiForm.q_resolution_combo_box->setToolTip(aperture); + + // Set the ToolTip for a1 + m_uiForm.q_resolution_a1_h1_input->setToolTip( + m_constants.getQResolutionA1ToolTipText()); + m_uiForm.q_resolution_a1_h1_label->setToolTip( + m_constants.getQResolutionA1ToolTipText()); + + // Set the ToolTip for a2 + m_uiForm.q_resolution_a2_h2_input->setToolTip( + m_constants.getQResolutionA2ToolTipText()); + m_uiForm.q_resolution_a2_h2_label->setToolTip( + m_constants.getQResolutionA2ToolTipText()); +} + } // namespace CustomInterfaces } // namespace MantidQt diff --git a/Testing/SystemTests/tests/analysis/SANSQResolutionTest.py b/Testing/SystemTests/tests/analysis/SANSQResolutionTest.py new file mode 100644 index 0000000000000000000000000000000000000000..27d110e3c6155b4dff3aacfd9d9713cd54da0f1c --- /dev/null +++ b/Testing/SystemTests/tests/analysis/SANSQResolutionTest.py @@ -0,0 +1,57 @@ +#pylint: disable=no-init +import stresstesting +from mantid.simpleapi import * +from ISISCommandInterface import * + +class SANSQResolutionWithoutGravity(stresstesting.MantidStressTest): + def runTest(self): + SANS2D() + MaskFile('MASKSANS2D_094i_RKH.txt') + SetDetectorOffsets('REAR', -16.0, 58.0, 0.0, 0.0, 0.0, 0.0) + SetDetectorOffsets('FRONT', -44.0, -20.0, 47.0, 0.0, 1.0, 1.0) + Gravity(False) + Set1D() + AssignSample('2500.nxs') + + # Provide settings for QResolution + set_q_resolution_use(True) + moderator_file_name = "ModeratorStdDev_TS2_SANS_30Jul2015.txt" + set_q_resolution_a1(a1 = 2) + set_q_resolution_a2(a2 = 3) + set_q_resolution_delta_r(delta_r = 2) + set_q_resolution_collimation_length(collimation_length=10) + set_q_resolution_moderator(file_name = moderator_file_name) + + WavRangeReduction(4.6, 12.85, False) + + def validate(self): + self.disableChecking.append('Instrument') + return True + +class SANSQResolutionWithGravity(stresstesting.MantidStressTest): + def runTest(self): + SANS2D() + MaskFile('MASKSANS2D_094i_RKH.txt') + SetDetectorOffsets('REAR', -16.0, 58.0, 0.0, 0.0, 0.0, 0.0) + SetDetectorOffsets('FRONT', -44.0, -20.0, 47.0, 0.0, 1.0, 1.0) + Gravity(flag = True, extra_length = 10.0) + Set1D() + AssignSample('2500.nxs') + + # Provide settings for QResolution + set_q_resolution_use(True) + moderator_file_name = "ModeratorStdDev_TS2_SANS_30Jul2015.txt" + set_q_resolution_h1(h1 = 2) + set_q_resolution_w1(w1 = 3) + set_q_resolution_h2(h2 = 4) + set_q_resolution_w2(w2 = 5) + + set_q_resolution_delta_r(delta_r = 2) + set_q_resolution_collimation_length(collimation_length=5) + set_q_resolution_moderator(file_name = moderator_file_name) + + WavRangeReduction(4.6, 12.85, False) + + def validate(self): + self.disableChecking.append('Instrument') + return True diff --git a/instrument/SANS2D_Parameters.xml b/instrument/SANS2D_Parameters.xml index 0b77a59c67ad7961949bafc5d086aef6d5e0ebe0..1af3f09a1e0b62c90485a8839fe287757ef8f299 100644 --- a/instrument/SANS2D_Parameters.xml +++ b/instrument/SANS2D_Parameters.xml @@ -48,27 +48,27 @@ </parameter> <parameter name="collimation-length-correction"> - <!-- This is a correction length in meter for the L1 value to obtain the default collimation length--> +<!-- This is a correction length in meter for the L1 value to obtain the default collimation length--> <value val="17.2765"/> </parameter> -<parameter name="special-default-collimation-length-method"> - <!-- This method determines how the default collimation is to be calculated--> +<parameter name="special-default-collimation-length-method" type="string"> +<!-- This method determines how the default collimation is to be calculated--> <value val="guide"/> </parameter> <parameter name="guide-collimation-length-increment"> - <!-- This is a correction length increment in meter--> +<!-- This is a correction length increment in meter--> <value val="2.0"/> </parameter> <parameter name="guide-cutoff"> - <!-- This is the cutoff value for the guides--> +<!-- This is the cutoff value for the guides--> <value val="130.0"/> </parameter> <parameter name="number-of-guides"> - <!-- This number of guides--> +<!-- This number of guides--> <value val="5"/> </parameter> diff --git a/instrument/SANS2D_Parameters_Tubes.xml b/instrument/SANS2D_Parameters_Tubes.xml index ee0931ce6f5a3d0631345d52f9be2ac08468569f..1ccf3194d107ea9340c6cd4c48fecb7193d0b018 100644 --- a/instrument/SANS2D_Parameters_Tubes.xml +++ b/instrument/SANS2D_Parameters_Tubes.xml @@ -48,27 +48,27 @@ </parameter> <parameter name="collimation-length-correction"> - <!-- This is a correction length in meter for the L1 value to obtain the default collimation length--> +<!-- This is a correction length in meter for the L1 value to obtain the default collimation length--> <value val="17.2765"/> </parameter> -<parameter name="special-default-collimation-length-method"> - <!-- This method determines how the default collimation is to be calculated--> +<parameter name="special-default-collimation-length-method" type="string"> +<!-- This method determines how the default collimation is to be calculated--> <value val="guide"/> </parameter> <parameter name="guide-collimation-length-increment"> - <!-- This is a correction length increment in meter--> +<!-- This is a correction length increment in meter--> <value val="2.0"/> </parameter> <parameter name="guide-cutoff"> - <!-- This is the cutoff value for the guides--> +<!-- This is the cutoff value for the guides--> <value val="130.0"/> </parameter> <parameter name="number-of-guides"> - <!-- This number of guides--> +<!-- This number of guides--> <value val="5"/> </parameter> diff --git a/scripts/SANS/ISISCommandInterface.py b/scripts/SANS/ISISCommandInterface.py index 41ab1de5f8bb008e1a871655be6858323e7b6f33..f418e0afdc93d0927be7a9d7ea58496b83465d93 100644 --- a/scripts/SANS/ISISCommandInterface.py +++ b/scripts/SANS/ISISCommandInterface.py @@ -25,7 +25,8 @@ try: import mantidplot except: mantidplot = None - #this should happen when this is called from outside Mantidplot and only then, the result is that attempting to plot will raise an exception + #this should happen when this is called from outside Mantidplot and only then, + #the result is that attempting to plot will raise an exception pass try: @@ -500,10 +501,26 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su Cf_can = CropWorkspace(InputWorkspace=Cf_can, OutputWorkspace=Cf_can, XMin=minQ, XMax=maxQ) Cr_can = CropWorkspace(InputWorkspace=Cr_can, OutputWorkspace=Cr_can, XMin=minQ, XMax=maxQ) - mergedQ = (Cf+shift*Nf+Cr)/(Nf/scale + Nr) + # We want: (Cf+shift*Nf+Cr)/(Nf/scale + Nr) + shifted_norm_front = Scale(InputWorkspace = Nf, Operation = "Multiply", Factor = shift) + scaled_norm_front = Scale(InputWorkspace = Nf, Operation = "Multiply", Factor = (1./scale)) + dividend = Cf + shifted_norm_front + Cr + divisor = scaled_norm_front + Nr + mergedQ = dividend/divisor + + DeleteWorkspace(dividend) + DeleteWorkspace(divisor) + DeleteWorkspace(scaled_norm_front) + DeleteWorkspace(shifted_norm_front) + if consider_can: mergedQ -= (Cf_can+Cr_can)/(Nf_can/scale + Nr_can) + # We need to correct the errors. Note that the Can contribution is ignored here. + su.correct_q_resolution_for_merged(count_ws_front = Cf, + count_ws_rear = Cr, + output_ws = mergedQ, + scale = scale) RenameWorkspace(InputWorkspace=mergedQ,OutputWorkspace= retWSname_merged) # save the properties Transmission and TransmissionCan inside the merged workspace @@ -533,8 +550,10 @@ def WavRangeReduction(wav_start=None, wav_end=None, full_trans_wav=None, name_su #applying scale and shift on the front detector reduced data if reduce_front_flag: frontWS = mtd[retWSname_front] - frontWS = (frontWS+shift)*scale + buffer = Scale(InputWorkspace = frontWS, Operation = "Add", Factor = shift) + frontWS = Scale(InputWorkspace = buffer, Operation = "Multiply", Factor = scale) RenameWorkspace(InputWorkspace=frontWS,OutputWorkspace= retWSname_front) + DeleteWorkspace(buffer) # finished calculating cross section so can restore these value ReductionSingleton().to_Q.outputParts = toRestoreOutputParts @@ -1442,6 +1461,255 @@ def AddRuns(runs, instrument ='sans2d', saveAsEvent=False, binning = "Monitors", isOverlay = isOverlay, time_shifts = time_shifts) + + +##################### Accesor functions for QResolution +def get_q_resolution_moderator(): + ''' + Gets the moderator file path + @returns the moderator file path or nothing + ''' + val = ReductionSingleton().to_Q.get_q_resolution_moderator() + if val == None: + val = '' + print str(val) + return val + +def set_q_resolution_moderator(file_name): + ''' + Sets the moderator file path + @param file_name: the full file path + ''' + try: + ReductionSingleton().to_Q.set_q_resolution_moderator(file_name) + except RuntimeError, details: + sanslog.error("The specified moderator file could not be found. Please specify a file" + "which exists in the search directories. See details: %s" %str(details)) + +#-- Use q resoltion +def get_q_resultution_use(): + ''' + Gets if the q resolution option is being used + @returns true if the resolution option is being used, else false + ''' + val = ReductionSingleton().to_Q.get_use_q_resolution() + print str(val) + return val + +def set_q_resolution_use(use): + ''' + Sets if the q resolution option is being used + @param use: use flag + ''' + if use == True: + ReductionSingleton().to_Q.set_use_q_resolution(True) + elif use == False: + ReductionSingleton().to_Q.set_use_q_resolution(False) + else: + sanslog.warning('Warning: Could could not set useage of QResolution') + +#-- Collimation length +def get_q_resolution_collimation_length(): + ''' + Get the collimation length + @returns the collimation length in mm + ''' + element = ReductionSingleton().to_Q.get_q_resolution_collimation_length() + msg = "CollimationLength" + if su.is_convertible_to_float(element) or not element: + pass + else: + sanslog.warning('Warning: Could not convert %s to float.' % msg) + print str(element) + return element + +def set_q_resolution_collimation_length(collimation_length): + ''' + Sets the collimation length + @param collimation_length: the collimation length + ''' + if collimation_length == None: + return + msg = "Collimation Length" + if su.is_convertible_to_float(collimation_length): + c_l = float(collimation_length) + ReductionSingleton().to_Q.set_q_resolution_collimation_length(c_l) + else: + sanslog.warning('Warning: Could not convert %s to float.' % msg) + + +#-- Delta R +def get_q_resolution_delta_r(): + ''' + Get the delta r value + @returns the delta r in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_delta_r, "DeltaR") + print str(val) + return val + +def set_q_resolution_delta_r(delta_r): + ''' + Sets the delta r value + @param delta_r: the delta r value + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_delta_r, delta_r, "DeltaR") + +#-- A1 +def get_q_resolution_a1(): + ''' + Get the A1 diameter + @returns the diameter for the first aperature in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_a1, "A1") + print str(val) + return val + +def set_q_resolution_a1(a1): + ''' + Sets the a1 value + @param a1: the a1 value in mm + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_a1, a1, "A1") + +#-- A2 +def get_q_resolution_a2(): + ''' + Get the A2 diameter + @returns the diameter for the second aperature in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_a2, "A2") + print str(val) + return val + +def set_q_resolution_a2(a2): + ''' + Sets the a2 value + @param a2: the a2 value in mm + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_a2, a2, "A2") + +#-- H1 +def get_q_resolution_h1(): + ''' + Get the first height for rectangular apertures + @returns the first height in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_h1, "H1") + print str(val) + return val + +def set_q_resolution_h1(h1): + ''' + Set the first height for rectangular apertures + @param h1: the first height in mm + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_h1, h1, "H1") + +#-- H2 +def get_q_resolution_h2(): + ''' + Get the second height for rectangular apertures + @returns the second height in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_h2, "H2") + print str(val) + return val + +def set_q_resolution_h2(h2): + ''' + Set the second height for rectangular apertures + @param h2: the second height in mm + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_h2, h2, "H2") + +#-- W1 +def get_q_resolution_w1(): + ''' + Get the first width for rectangular apertures + @returns the first width in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_w1, "W1") + print str(val) + return val + +def set_q_resolution_w1(w1): + ''' + Set the first width for rectangular apertures + @param w1: the first width in mm + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_w1, w1, "W1") + +#-- W2 +def get_q_resolution_w2(): + ''' + Get the second width for rectangular apertures + @returns the second width in mm + ''' + val = get_q_resolution_float(ReductionSingleton().to_Q.get_q_resolution_w2, "W2") + print str(val) + return val + +def set_q_resolution_w2(w2): + ''' + Set the second width for rectangular apertures + @param w1: the second width in mm + ''' + set_q_resolution_float(ReductionSingleton().to_Q.set_q_resolution_w2, w2, "W2") + + +#-- Reset +def reset_q_resolution_settings(): + ''' + Resets the q settings + ''' + ReductionSingleton().to_Q.reset_q_settings() + +#-- Set float value +def set_q_resolution_float(func, arg, msg): + ''' + Set a q resolution value + @param func: the speficied function to run + @param arg: the argument + @param mgs: error message + ''' + if arg == None: + return + + if su.is_convertible_to_float(arg): + d_r = su.millimeter_2_meter(float(arg)) + func(d_r) + else: + sanslog.warning('Warning: Could not convert %s to float.' % msg) + +def get_q_resolution_float(func, msg): + ''' + Gets a q resolution value and checks if it has been set. + @param func: the speficied function to run + @param mgs: error message + @return the correct value + ''' + element = func() + + if su.is_convertible_to_float(element): + element = su.meter_2_millimeter(element) + elif not element: + pass + else: + sanslog.warning('Warning: Could not convert %s to float.' % msg) + return element + +def are_settings_consistent(): + ''' + Runs the consistency check over all reductionssteps and reports cosistency + issues to the user. The user needs to sort out these issues. + ''' + try: + ReductionSingleton().perform_consistency_check() + except RuntimeError, details: + sanslog.error("There was an inconsistency issue with your settings. See details: %s" % str(details)) + raise RuntimeError("Please fix the following inconsistencies: %s" % str(details)) + def is_current_workspace_an_angle_workspace(): ''' Queries if the current workspace, stored in the reducer is a workspace diff --git a/scripts/SANS/SANSUtility.py b/scripts/SANS/SANSUtility.py index c594e7548e3d319888d9136a2b8c14c6831b8221..00e66a500675e9cd6a5035926f19b6f6ef40d0d0 100644 --- a/scripts/SANS/SANSUtility.py +++ b/scripts/SANS/SANSUtility.py @@ -976,12 +976,19 @@ def is_convertible_to_float(input_value): ''' Check if the input can be converted to float @param input_value :: a general input + @returns true if input can be converted to float else false ''' - try: - dummy_converted = float(input_value) - except ValueError: - return False - return True + is_convertible = True + if not input_value: + is_convertible = False + else: + try: + dummy_converted = float(input_value) + is_convertible = True + except ValueError: + is_convertible = False + return is_convertible + def is_valid_xml_file_list(input_value): ''' @@ -1133,6 +1140,104 @@ def is_1D_workspace(workspace): else: return False +def meter_2_millimeter(num): + ''' + Converts from m to mm + @param float in m + @returns float in mm + ''' + return num*1000. + +def millimeter_2_meter(num): + ''' + Converts from mm to m + @param float in mm + @returns float in m + ''' + return num/1000. + +def correct_q_resolution_for_can(original_workspace, can_workspace, subtracted_workspace): + ''' + We need to transfer the DX error values from the original workspaces to the subtracted + workspace. Richard wants us to ignore potential DX values for the CAN workspace (they + would be very small any way). The Q Resolution functionality only exists currently + for 1D, ie when only one spectrum is present. + @param original_workspace: the original workspace + @param can_workspace: the can workspace + @param subtracted_workspace: the subtracted workspace + ''' + dummy1 = can_workspace + if original_workspace.getNumberHistograms() == 1: + subtracted_workspace.setDx(0, original_workspace.dataDx(0)) + +def correct_q_resolution_for_merged(count_ws_front, count_ws_rear, + output_ws, scale): + ''' + We need to transfer the DX error values from the original workspaces to the merged worksapce. + We have: + C(Q) = Sum_all_lambda_for_particular_Q(Counts(lambda)) + weightedQRes(Q) = Sum_all_lambda_for_particular_Q(Counts(lambda)* qRes(lambda)) + ResQ(Q) = weightedQRes(Q)/C(Q) + Richard suggested: + ResQMerged(Q) = (weightedQRes_FRONT(Q)*scale + weightedQRes_REAR(Q))/ + (C_FRONT(Q)*scale + C_REAR(Q)) + Note that we drop the shift here. + The Q Resolution functionality only exists currently + for 1D, ie when only one spectrum is present. + @param count_ws_front: the front counts + @param count_ws_rear: the rear counts + @param output_ws: the output workspace + ''' + def divide_q_resolution_by_counts(q_res, counts): + # We are dividing DX by Y. Note that len(DX) = len(Y) + 1 + # Unfortunately, we need some knowlege about the Q1D algorithm here. + # The two last entries of DX are duplicate in Q1D and this is how we + # treat it here. + q_res_buffer = np.divide(q_res[0:-1],counts) + q_res_buffer = np.append(q_res_buffer, q_res_buffer[-1]) + return q_res_buffer + + def multiply_q_resolution_by_counts(q_res, counts): + # We are dividing DX by Y. Note that len(DX) = len(Y) + 1 + # Unfortunately, we need some knowlege about the Q1D algorithm here. + # The two last entries of DX are duplicate in Q1D and this is how we + # treat it here. + q_res_buffer = np.multiply(q_res[0:-1],counts) + q_res_buffer = np.append(q_res_buffer, q_res_buffer[-1]) + return q_res_buffer + + if count_ws_rear.getNumberHistograms() != 1: + return + + # We require both count workspaces to contain the DX value + if not count_ws_rear.hasDx(0) or not count_ws_front.hasDx(0): + return + + q_resolution_front = count_ws_front.readDx(0) + q_resolution_rear = count_ws_rear.readDx(0) + counts_front = count_ws_front.readY(0) + counts_rear = count_ws_rear.readY(0) + + # We need to make sure that the workspaces match in length + if ((len(q_resolution_front) != len(q_resolution_rear)) or + (len(counts_front) != len(counts_rear))): + return + + # Get everything for the FRONT detector + q_res_front_norm_free = multiply_q_resolution_by_counts(q_resolution_front, counts_front) + q_res_front_norm_free = q_res_front_norm_free*scale + counts_front = counts_front*scale + + # Get everything for the REAR detector + q_res_rear_norm_free = multiply_q_resolution_by_counts(q_resolution_rear, counts_rear) + + # Now add and divide + new_q_res= np.add(q_res_front_norm_free, q_res_rear_norm_free) + new_counts = np.add(counts_front,counts_rear) + q_resolution = divide_q_resolution_by_counts(new_q_res,new_counts) + + # Set the dx error + output_ws.setDx(0, q_resolution) ############################################################################### ######################### Start of Deprecated Code ############################ diff --git a/scripts/SANS/isis_instrument.py b/scripts/SANS/isis_instrument.py index 7cbaff03481cbfafcb4b570dd99e3c7286527f70..c3f6a6862cdd81fed75c58f6cbec11aed52a10ec 100644 --- a/scripts/SANS/isis_instrument.py +++ b/scripts/SANS/isis_instrument.py @@ -763,6 +763,12 @@ class ISISInstrument(BaseInstrument): self.set_up_for_run(run_num) if self._newCalibrationWS: + # We are about to transfer the Instrument Parameter File from the + # calibration to the original workspace. We want to add new parameters + # which the calibration file has not yet picked up. + # IMPORTANT NOTE: This takes the parameter settings from the original workspace + # if they are old too, then we don't pick up newly added parameters + self._add_parmeters_absent_in_calibration(ws_name, self._newCalibrationWS) self.changeCalibration(ws_name) # centralize the bank to the centre @@ -792,6 +798,64 @@ class ISISInstrument(BaseInstrument): ''' return self.beam_centre_pos1_after_move, self.beam_centre_pos2_after_move + def _add_parmeters_absent_in_calibration(self, ws_name, calib_name): + ''' + We load the instrument specific Instrument Parameter File (IPF) and check if + there are any settings which the calibration workspace does not have. The calibration workspace + has its own parameter map stored in the nexus file. This means that if we add new + entries to the IPF, then they are not being picked up. We do not want to change the existing + values, just add new entries. + @param ws_name: the name of the main workspace with the data + @param calibration_workspace: the name of the calibration workspace + ''' + if calib_name == None or ws_name == None: + return + workspace = mtd[ws_name] + calibration_workspace = mtd[calib_name] + + # 1.Iterate over all parameters in the original workspace + # 2. Compare with the calibration workspace + # 3. If it does not exist, then add it + original_parmeters = workspace.getInstrument().getParameterNames() + for param in original_parmeters: + if not calibration_workspace.getInstrument().hasParameter(param): + self._add_new_parameter_to_calibration(param, workspace, calibration_workspace) + + def _add_new_parameter_to_calibration(self, param_name, workspace, calibration_workspace): + ''' + Adds the missing value from the Instrument Parameter File (IPF) of the workspace to + the IPF of the calibration workspace. We check for the + 1. Type + 2. Value + 3. Name + 4. ComponentName (which is the instrument) + @param param_name: the name of the parameter to add + @param workspace: the donor of the parameter + @param calibration_workspace: the receiver of the parameter + ''' + ws_instrument = workspace.getInstrument() + component_name = ws_instrument.getName() + ipf_type = ws_instrument.getParameterType(param_name) + # For now we only expect string, int and double + type_ids = ["string", "int", "double"] + value = None + type_to_save = "Number" + if ipf_type == type_ids[0]: + value = ws_instrument.getStringParameter(param_name) + type_to_save = "String" + elif ipf_type == type_ids[1]: + value = ws_instrument.getIntParameter(param_name) + elif ipf_type == type_ids[2]: + value = ws_instrument.getNumberParameter(param_name) + else: + raise RuntimeError("ISISInstrument: An Instrument Parameter File value of unknown type" + "is trying to be copied. Cannot handle this currently.") + SetInstrumentParameter(Workspace = calibration_workspace, + ComponentName = component_name, + ParameterName = param_name, + ParameterType = type_to_save, + Value = str(value[0])) + class LOQ(ISISInstrument): """ diff --git a/scripts/SANS/isis_reducer.py b/scripts/SANS/isis_reducer.py index ad08212b01d0dff48e45ffae70d7deed7c818c28..ea848550a1331b41d2723989448780a9df7ca012 100644 --- a/scripts/SANS/isis_reducer.py +++ b/scripts/SANS/isis_reducer.py @@ -669,6 +669,30 @@ class ISISReducer(Reducer): except: #if the workspace can't be deleted this function does nothing pass + def get_reduction_steps(self): + ''' + Provides a way to access the reduction steps + @returns the reduction steps + ''' + return self._reduction_steps + + def perform_consistency_check(self): + ''' + Runs the consistency check over all reduction steps + ''' + was_empty = False + if not self._reduction_steps: + self._to_steps() + was_empty = True + + try: + to_check = self._reduction_steps + for element in to_check: + element.run_consistency_check() + except RuntimeError, details: + if was_empty: + self._reduction_steps = None + raise RuntimeError(str(details)) def update_beam_center(self): """ diff --git a/scripts/SANS/isis_reduction_steps.py b/scripts/SANS/isis_reduction_steps.py index fb7bd90f899a96141a3d26895628cecd71e70462..9244b651695459c796a47c17489a354cb79286ce 100644 --- a/scripts/SANS/isis_reduction_steps.py +++ b/scripts/SANS/isis_reduction_steps.py @@ -13,7 +13,7 @@ import math import copy import re import traceback - +import math from mantid.kernel import Logger sanslog = Logger("SANS") @@ -25,11 +25,18 @@ from SANSUtility import (GetInstrumentDetails, MaskByBinRange, mask_detectors_with_masking_ws, check_child_ws_for_name_and_type_for_added_eventdata, extract_spectra, extract_child_ws_for_added_eventdata, load_monitors_for_multiperiod_event_data, MaskWithCylinder, get_masked_det_ids, get_masked_det_ids_from_mask_file, INCIDENT_MONITOR_TAG, - can_load_as_event_workspace) + can_load_as_event_workspace, is_convertible_to_float,correct_q_resolution_for_can) import isis_instrument import isis_reducer from reducer_singleton import ReductionStep + +DEBUG = False + +# A global name for the Q Resolution workspace which lives longer than a reducer core +QRESOLUTION_WORKSPACE_NAME = "Q_Resolution_ISIS_SANS" +QRESOLUTION_MODERATOR_WORKSPACE_NAME = "Q_Resolution_MODERATOR_ISIS_SANS" + def _issueWarning(msg): """ Prints a message to the log marked as warning @@ -493,6 +500,8 @@ class CanSubtraction(ReductionStep): #we now have the can workspace, use it Minus(LHSWorkspace=tmp_smp,RHSWorkspace= tmp_can,OutputWorkspace= workspace) + # Correct the Q resolution entries in the output workspace + correct_q_resolution_for_can(mtd[tmp_smp], mtd[tmp_can], mtd[workspace]) #clean up the workspaces ready users to see them if required if reducer.to_Q.output_type == '1D': @@ -522,6 +531,17 @@ class CanSubtraction(ReductionStep): periods_in_file = property(get_periods_in_file, None, None, None) + def _pass_dx_values_to_can_subtracted_if_required(self, original_ws, subtracted_ws): + ''' + We pass the DX values from the original workspace to the subtracted workspace. + This means we currently do nothing with potential DX values in the can workspace. + Also that if there are DX values, then they are in all spectra + ''' + if not original_ws.hasDx(0): + return + for index in range(0, original_ws.getNumHistograms()): + subtraced_ws.setDx(index, original_ws.dataDX(index)) + class Mask_ISIS(ReductionStep): """ Marks some spectra so that they are not included in the analysis @@ -749,7 +769,8 @@ class Mask_ISIS(ReductionStep): self.arm_x=0.0 self.arm_y=0.0 else: - _issueWarning('Unrecognized line masking command "' + details + '" syntax is MASK/LINE width angle or MASK/LINE width angle x y') + _issueWarning('Unrecognized line masking command "' + details + + '" syntax is MASK/LINE width angle or MASK/LINE width angle x y') else: _issueWarning('Unrecognized masking option "' + details + '"') elif len(parts) == 3: @@ -768,7 +789,9 @@ class Mask_ISIS(ReductionStep): elif detname.upper() == 'REAR': self.time_mask_r += ';' + bin_range else: - _issueWarning('Detector \'' + detname + '\' not found in currently selected instrument ' + self.instrument.name() + '. Skipping line.') + _issueWarning('Detector \'' + detname + + '\' not found in currently selected instrument ' + + self.instrument.name() + '. Skipping line.') else: _issueWarning('Unrecognized masking line "' + details + '"') else: @@ -780,7 +803,9 @@ class Mask_ISIS(ReductionStep): elif detect.upper() == 'REAR': self.spec_mask_r += ',' + mask_string else: - _issueWarning('Detector \'' + detect + '\' not found in currently selected instrument ' + self.instrument.name() + '. Skipping line.') + _issueWarning('Detector \'' + detect + + '\' not found in currently selected instrument ' + + self.instrument.name() + '. Skipping line.') def _ConvertToSpecList(self, maskstring, detector): ''' @@ -863,11 +888,16 @@ class Mask_ISIS(ReductionStep): Purpose of this method is to populate self._lim_phi_xml ''' # convert all angles to be between 0 and 360 - while phimax > 360 : phimax -= 360 - while phimax < 0 : phimax += 360 - while phimin > 360 : phimin -= 360 - while phimin < 0 : phimin += 360 - while phimax<phimin : phimax += 360 + while phimax > 360 : + phimax -= 360 + while phimax < 0 : + phimax += 360 + while phimin > 360 : + phimin -= 360 + while phimin < 0 : + phimin += 360 + while phimax<phimin : + phimax += 360 #Convert to radians phimin = math.pi*phimin/180.0 @@ -879,7 +909,8 @@ class Mask_ISIS(ReductionStep): + self._infinite_plane(id+'_plane2',centre, [-math.cos(-phimax + math.pi/2.0),-math.sin(-phimax + math.pi/2.0),0]) if use_mirror: - self._lim_phi_xml += self._infinite_plane(id+'_plane3',centre, [math.cos(-phimax + math.pi/2.0),math.sin(-phimax + math.pi/2.0),0]) \ + self._lim_phi_xml += self._infinite_plane(id+'_plane3',centre, + [math.cos(-phimax + math.pi/2.0),math.sin(-phimax + math.pi/2.0),0]) \ + self._infinite_plane(id+'_plane4',centre, [-math.cos(-phimin + math.pi/2.0),-math.sin(-phimin + math.pi/2.0),0]) \ + '<algebra val="#(('+id+'_plane1 '+id+'_plane2):('+id+'_plane3 '+id+'_plane4))" />' else: @@ -1842,6 +1873,18 @@ class ConvertToQISIS(ReductionStep): self.w_cut = 0.0 # Whether to output parts when running either Q1D2 or Qxy self.outputParts = False + # Flag if a QResolution workspace should be used + self.use_q_resolution = False + # QResolution settings + self._q_resolution_moderator_file_name = None + self._q_resolution_delta_r = None + self._q_resolution_a1 = None + self._q_resolution_a2 = None + self._q_resolution_h1 = None + self._q_resolution_w1 = None + self._q_resolution_h2 = None + self._q_resolution_w2 = None + self._q_resolution_collimation_length = None def set_output_type(self, descript): """ @@ -1893,7 +1936,8 @@ class ConvertToQISIS(ReductionStep): if (not self._grav_extra_length_set) or override: self._grav_extra_length = extra_length else: - msg = "User file can't override previous extra length setting for gravity correction; extra length remains " + str(self._grav_extra_length) + msg = ("User file can't override previous extra length setting for" + + " gravity correction; extra length remains " + str(self._grav_extra_length)) print msg sanslog.warning(msg) @@ -1916,6 +1960,26 @@ class ConvertToQISIS(ReductionStep): else: raise RuntimeError('Normalization workspaces must be created by CalculateNorm() and passed to this step') + # Create the QResolution workspace, but only if it A) is requested by the user and does not exist + # B) is requested by the user, exists, but does not + # have the correct binning --> This is currently not implemented, + # but should be addressed in an optimization step + qResolution = self._get_q_resolution_workspace(det_bank_workspace = workspace) + + # Debug output + if DEBUG: + sanslog.warning("###############################################") + sanslog.warning("File : %s" % str(self._q_resolution_moderator_file_name)) + sanslog.warning("A1 : %s" % str(self._q_resolution_a1)) + sanslog.warning("A2 : %s" % str(self._q_resolution_a2)) + sanslog.warning("H1 : %s" % str(self._q_resolution_h1)) + sanslog.warning("H2 : %s" % str(self._q_resolution_h1)) + sanslog.warning("W1 : %s" % str(self._q_resolution_w1)) + sanslog.warning("W2 : %s" % str(self._q_resolution_w2)) + sanslog.warning("LCol: %s" % str(self._q_resolution_collimation_length)) + sanslog.warning("DR : %s" % str(self._q_resolution_delta_r)) + sanslog.warning("Exists: %s" % str(qResolution != None)) + try: if self._Q_alg == 'Q1D': Q1D(DetBankWorkspace=workspace, @@ -1928,7 +1992,8 @@ class ConvertToQISIS(ReductionStep): WaveCut=self.w_cut, OutputParts=self.outputParts, WavePixelAdj = wavepixeladj, - ExtraLength=self._grav_extra_length) + ExtraLength=self._grav_extra_length, + QResolution=qResolution) elif self._Q_alg == 'Qxy': Qxy(InputWorkspace=workspace, OutputWorkspace= workspace, @@ -1953,6 +2018,230 @@ class ConvertToQISIS(ReductionStep): reducer.deleteWorkspaces([wave_adj, pixel_adj, wavepixeladj]) + def _get_q_resolution_workspace(self, det_bank_workspace): + ''' + Calculates the QResolution workspace if this is required + @param det_bank_workspace: the main worspace which is being reduced + @returns the QResolution workspace or None + ''' + # Check if the a calculation is asked for by the user + if self.use_q_resolution == False: + return None + + # Make sure that all parameters that are needed are available + self._set_up_q_resolution_parameters() + + # Run a consistency check + try: + self.run_consistency_check() + except RuntimeError, details: + sanslog.warning("ConverToQISIS: There was something wrong with the Q Resolution" + " settings. Running the reduction without the Q Resolution" + " Setting. See details %s" % str(details)) + return None + + # Check if Q Resolution exists in mtd + exists = mtd.doesExist(QRESOLUTION_WORKSPACE_NAME) + + # Future improvement here: If the binning has not changed and the instrument is + # the same then we can reuse the existing QResolution workspace if it exists + if exists: + #return self._get_existing_q_resolution(det_bank_workspace) + return self._create_q_resolution(det_bank_workspace = det_bank_workspace) + else: + return self._create_q_resolution(det_bank_workspace = det_bank_workspace) + + def _create_q_resolution(self, det_bank_workspace): + ''' + Creates the Q Resolution workspace + @returns the q resolution workspace + ''' + sigma_moderator = self._get_sigma_moderator_workspace() + + # We need the radius, not the diameter in the TOFSANSResolutionByPixel algorithm + sample_radius = 0.5*self.get_q_resolution_a2() + source_radius = 0.5*self.get_q_resolution_a1() + + # The radii and the deltaR are expected to be in mm + TOFSANSResolutionByPixel(InputWorkspace = det_bank_workspace, + OutputWorkspace = QRESOLUTION_WORKSPACE_NAME, + DeltaR = self.get_q_resolution_delta_r()*1000., + SampleApertureRadius = sample_radius*1000., + SourceApertureRadius = source_radius*1000., + SigmaModerator = sigma_moderator, + CollimationLength = self.get_q_resolution_collimation_length(), + AccountForGravity=self._use_gravity, + ExtraLength=self._grav_extra_length) + + if not mtd.doesExist(QRESOLUTION_WORKSPACE_NAME): + raise RuntimeError("ConvertTpQIsis: Could not create the q resolution workspace") + + DeleteWorkspace(sigma_moderator) + return mtd[QRESOLUTION_WORKSPACE_NAME] + + def _get_sigma_moderator_workspace(self): + ''' + Gets the sigma moderator workspace. + @returns the sigma moderator workspace + ''' + moderator_ws = Load(Filename = self.get_q_resolution_moderator()) + moderator_histogram_ws = ConvertToHistogram(InputWorkspace = moderator_ws) + moderator_wavelength_ws = ConvertUnits(InputWorkspace=moderator_histogram_ws,Target="Wavelength") + + DeleteWorkspace(moderator_ws) + DeleteWorkspace(moderator_histogram_ws) + return moderator_wavelength_ws + + def _get_existing_q_resolution(self, det_bank_workspace): + ''' + If the existing Q Resolution workspace has the correct binning, + then we use it, else we have to create it + @det_bank_workspace: the main workspace + ''' + if self._has_matching_binning(det_bank_workspace): + return mtd[QRESOLUTION_WORKSPACE_NAME] + else: + return self._create_q_resolution(det_bank_workspace = det_bank_workspace) + + def _has_matching_binning(self, det_bank_workspace): + ''' + Check if the binning of the q resolution workspace + and the main workspace do not match + @det_bank_workspace: the main workspace + ''' + # Here we need to check if the binning has changed, ie if the + # existing + dummy_ws = det_bank_workspace + raise RuntimeError("The QResolution optimization has not been implemented yet") + + def set_q_resolution_moderator(self, file_name): + ''' + Sets the moderator file name for Q Resolution + @param file_name: the name of the moderator file + ''' + try: + q_res_file_path, dummy_suggested_name = getFileAndName(file_name) + except: + raise RuntimeError("Invalid input for mask file. (%s)" % str(file_name)) + q_res_file_path = q_res_file_path.replace("\\", "/") + self._q_resolution_moderator_file_name = q_res_file_path + + def get_q_resolution_moderator(self): + return self._q_resolution_moderator_file_name + + def set_q_resolution_a1(self, a1): + self._q_resolution_a1 = a1 + + def get_q_resolution_a1(self): + return self._q_resolution_a1 + + def set_q_resolution_a2(self, a2): + self._q_resolution_a2 = a2 + + def get_q_resolution_a2(self): + return self._q_resolution_a2 + + def set_q_resolution_delta_r(self, delta_r): + self._q_resolution_delta_r = delta_r + + def get_q_resolution_delta_r(self): + return self._q_resolution_delta_r + + def set_q_resolution_h1(self, h1): + self._q_resolution_h1 = h1 + + def get_q_resolution_h1(self): + return self._q_resolution_h1 + + def set_q_resolution_h2(self, h2): + self._q_resolution_h2 = h2 + + def get_q_resolution_h2(self): + return self._q_resolution_h2 + + def set_q_resolution_w1(self, w1): + self._q_resolution_w1 = w1 + + def get_q_resolution_w1(self): + return self._q_resolution_w1 + + def set_q_resolution_w2(self, w2): + self._q_resolution_w2 = w2 + + def get_q_resolution_w2(self): + return self._q_resolution_w2 + + def set_q_resolution_collimation_length(self, collimation_length): + self._q_resolution_collimation_length = collimation_length + + def get_q_resolution_collimation_length(self): + return self._q_resolution_collimation_length + + def set_use_q_resolution(self, enabled): + self.use_q_resolution = enabled + + def get_use_q_resolution(self): + return self.use_q_resolution + + def run_consistency_check(self): + ''' + Provides the consistency check for the ConvertTOQISIS + ''' + # Make sure that everythign for the QResolution calculation is setup correctly + if self.use_q_resolution: + self._check_q_settings_complete() + + def _check_q_settings_complete(self): + ''' + Check that the q resolution settings are complete. + We need a moderator file path. And the other settings have to be self consistent + ''' + try: + dummy_file_path, dummy_suggested_name = getFileAndName(self._q_resolution_moderator_file_name) + except: + raise RuntimeError("The specified moderator file is not valid. Please make sure that that it exists in your search directory.") + + # If A1 is set, then A2 should be set and vice versa + if ((self.get_q_resolution_a1() is None and self.get_q_resolution_a2() is not None) or + (self.get_q_resolution_a2() is None and self.get_q_resolution_a1() is not None)): + raise RuntimeError("Both, A1 and A2, need to be specified.") + + def _set_up_q_resolution_parameters(self): + ''' + Prepare the parameters which need preparing + ''' + # If we have values for H1 and W1 then set A1 to the correct value + if self._q_resolution_h1 and self._q_resolution_w1 and self._q_resolution_h2 and self._q_resolution_w2: + self._q_resolution_a1 = self._set_up_diameter(self._q_resolution_h1, self._q_resolution_w1) + self._q_resolution_a2 = self._set_up_diameter(self._q_resolution_h2, self._q_resolution_w2) + + + def _set_up_diameter(self, h, w): + ''' + Prepare the diameter parameter. If there are corresponding H and W values, then + use them instead. Richard provided the formula: A = 2*sqrt((H^2 + W^2)/6) + @param h: the height + @param w: the width + @returns the new diameter + ''' + return 2*math.sqrt((h*h + w*w)/6) + + def reset_q_settings(self): + ''' + Reset of the q resolution settings + ''' + self.use_q_resolution = False + self._q_resolution_moderator_file_name = None + self._q_resolution_delta_r = None + self._q_resolution_a1 = None + self._q_resolution_a2 = None + self._q_resolution_h1 = None + self._q_resolution_w1 = None + self._q_resolution_h2 = None + self._q_resolution_w2 = None + self._q_resolution_collimation_length = None + + class UnitsConvert(ReductionStep): """ Executes ConvertUnits and then Rebin on the same workspace. If no re-bin limits are @@ -2134,7 +2423,8 @@ class UserFile(ReductionStep): 'TRANS/': self._read_trans_line, 'MON/' : self._read_mon_line, 'TUBECALIBFILE': self._read_calibfile_line, - 'MASKFILE': self._read_maskfile_line} + 'MASKFILE': self._read_maskfile_line, + 'QRESOL/': self._read_q_resolution_line} def __deepcopy__(self, memo): """Called when a deep copy is requested @@ -2147,7 +2437,8 @@ class UserFile(ReductionStep): 'TRANS/': fresh._read_trans_line, 'MON/' : fresh._read_mon_line, 'TUBECALIBFILE': self._read_calibfile_line, - 'MASKFILE': self._read_maskfile_line + 'MASKFILE': self._read_maskfile_line, + 'QRESOL/': self._read_q_resolution_line } return fresh @@ -2182,6 +2473,9 @@ class UserFile(ReductionStep): # Check if one of the efficency files hasn't been set and assume the other is to be used reducer.instrument.copy_correction_files() + # Run a consistency check + reducer.perform_consistency_check() + self.executed = True return self.executed @@ -2786,6 +3080,77 @@ class UserFile(ReductionStep): reducer.transmission_calculator.calculated_can = arguments[1] + def _read_q_resolution_line(self, arguments, reducer): + ''' + Parses the input for QResolution + @param arguments: the arguments of a QResolution line + @param reducer: a reducer object + ''' + arguments = arguments.upper() + if arguments.find('=') == -1: + return self._read_q_resolution_line_on_off(arguments, reducer) + + # Split and remove the white spaces + arguments = arguments.split('=') + arguments = [element.strip() for element in arguments] + + # Check if it is the moderator file name, if so add it and return + if arguments[0].startswith('MODERATOR'): + try: + reducer.to_Q.set_q_resolution_moderator(file_name = arguments[1]) + except: + sanslog.error("The specified moderator file could not be found. Please specify a file which exists in the search directories.") + return + + # All arguments need to be convertible to a float + if not is_convertible_to_float(arguments[1]): + return 'Value not a float in line: ' + + # Now check for the actual key + if arguments[0].startswith('DELTAR'): + reducer.to_Q.set_q_resolution_delta_r(delta_r= float(arguments[1])/1000.) + elif arguments[0].startswith('A1'): + # Input is in mm but we need m later on + reducer.to_Q.set_q_resolution_a1(a1 = float(arguments[1])/1000.) + elif arguments[0].startswith('A2'): + # Input is in mm but we need m later on + reducer.to_Q.set_q_resolution_a2(a2 = float(arguments[1])/1000.) + elif arguments[0].startswith('LCOLLIM'): + # Input is in m and we need it to be in m later on + reducer.to_Q.set_q_resolution_collimation_length(collimation_length = float(arguments[1])) + elif arguments[0].startswith('H1'): + # Input is in mm but we need m later on + reducer.to_Q.set_q_resolution_h1(h1 = float(arguments[1])/1000.) + elif arguments[0].startswith('W1'): + # Input is in mm but we need m later on + reducer.to_Q.set_q_resolution_w1(w1 = float(arguments[1])/1000.) + elif arguments[0].startswith('H2'): + # Input is in mm but we need m later on + reducer.to_Q.set_q_resolution_h2(h2 = float(arguments[1])/1000.) + elif arguments[0].startswith('W2'): + # Input is in mm but we need m later on + reducer.to_Q.set_q_resolution_w2(w2 = float(arguments[1])/1000.) + else: + return 'Unrecognised line: ' + + def _read_q_resolution_line_on_off(self, arguments, reducer): + ''' + Handles the ON/OFF setting for QResolution + @param arguements: the line arguments + @param reducer: a reducer object + ''' + # Remove white space + on_off = "".join(arguments.split()) + + # We expect only ON or OFF + if on_off == "ON": + reducer.to_Q.set_use_q_resolution(enabled = True) + elif on_off == "OFF": + reducer.to_Q.set_use_q_resolution(enabled = False) + else: + return 'Unrecognised line: ' + + def _check_instrument(self, inst_name, reducer): if reducer.instrument is None: raise RuntimeError('Use SANS2D() or LOQ() to set the instrument before Maskfile()') diff --git a/scripts/reducer_singleton.py b/scripts/reducer_singleton.py index 5b2a860adff42c677007b20699a4fedc27a6897f..94ba12194cd1260f6aaf9d33578ee8d02e455ef7 100644 --- a/scripts/reducer_singleton.py +++ b/scripts/reducer_singleton.py @@ -37,6 +37,13 @@ class ReductionStep(object): """ raise NotImplementedError + def run_consistency_check(self): + ''' + Run a consistency check of the settings of the ReuctionStep + ''' + return + + class Reducer(object): """ Base reducer class. Instrument-specific reduction processes should be @@ -156,9 +163,6 @@ class Reducer(object): f.close() return self.log_text - - - class ReductionSingleton(object): """ Singleton reduction class """ diff --git a/scripts/test/SANSCommandInterfaceTest.py b/scripts/test/SANSCommandInterfaceTest.py index fe0c9ea2ac70e5e413a98a1a80d4d5d8e549d3e9..859194caf9f0bce30df577a4c9e69afada66db03 100644 --- a/scripts/test/SANSCommandInterfaceTest.py +++ b/scripts/test/SANSCommandInterfaceTest.py @@ -7,7 +7,7 @@ import isis_reduction_steps as reduction_steps from mantid.simpleapi import * from mantid.kernel import DateAndTime import random - +import math class SANSCommandInterfaceGetAndSetTransmissionSettings(unittest.TestCase): def test_that_gets_transmission_monitor(self): # Arrange @@ -256,5 +256,136 @@ class TestFitRescaleAndShift(unittest.TestCase): DeleteWorkspace(ws2) + +class SANSCommandInterfaceGetAndSetQResolutionSettings(unittest.TestCase): + ''' + Test the input and output mechanims for the QResolution settings + ''' + def test_full_setup_for_circular_apertures(self): + # Arrange + command_iface.Clean() + command_iface.SANS2D() + a1 = 2 # in mm + a2 = 3 # in mm + delta_r = 4 # in mm + collimation_length = 10 # in m + norm = reduction_steps.CalculateNormISIS() + ReductionSingleton().to_Q = reduction_steps.ConvertToQISIS(norm) + + # Act + command_iface.set_q_resolution_a1(a1 = a1) + command_iface.set_q_resolution_a2(a2 = a2) + command_iface.set_q_resolution_delta_r(delta_r = delta_r) + command_iface.set_q_resolution_collimation_length(collimation_length = collimation_length) + command_iface.set_q_resolution_use(use = True) + ReductionSingleton().to_Q._set_up_q_resolution_parameters() + + # Assert + a1_stored = ReductionSingleton().to_Q.get_q_resolution_a1() # in m + a1_expected = a1/1000. + self.assertEqual(a1_stored, a1_expected) + + a2_stored = ReductionSingleton().to_Q.get_q_resolution_a2() # in m + a2_expected = a2/1000. + self.assertEqual(a2_stored, a2_expected) + + collimation_length_stored = ReductionSingleton().to_Q.get_q_resolution_collimation_length() # in m + collimation_length_expected = collimation_length + self.assertEqual(collimation_length_stored, collimation_length_expected) + + delta_r_stored = ReductionSingleton().to_Q.get_q_resolution_delta_r() # in m + delta_r_expected = delta_r/1000. + self.assertEqual(delta_r_stored, delta_r_expected) + + def test_full_setup_for_rectangular_apertures(self): + # Arrange + command_iface.Clean() + command_iface.SANS2D() + a1 = 2 # in mm + a2 = 3 # in mm + delta_r = 4 # in mm + collimation_length = 10 # in m + h1 = 9 # in mm + w1 = 8 # in mm + h2 = 7 # in mm + w2 = 5 # in mm + norm = reduction_steps.CalculateNormISIS() + ReductionSingleton().to_Q = reduction_steps.ConvertToQISIS(norm) + + # Act + command_iface.set_q_resolution_a1(a1 = a1) + command_iface.set_q_resolution_a2(a2 = a2) + command_iface.set_q_resolution_delta_r(delta_r = delta_r) + command_iface.set_q_resolution_h1(h1 = h1) + command_iface.set_q_resolution_w1(w1 = w1) + command_iface.set_q_resolution_h2(h2 = h2) + command_iface.set_q_resolution_w2(w2 = w2) + command_iface.set_q_resolution_collimation_length(collimation_length = collimation_length) + command_iface.set_q_resolution_use(use = True) + ReductionSingleton().to_Q._set_up_q_resolution_parameters() + + # Assert + a1_stored = ReductionSingleton().to_Q.get_q_resolution_a1() # in m + a1_expected = 2*math.sqrt((h1/1000.*h1/1000. + w1/1000.*w1/1000.)/6) + self.assertEqual(a1_stored, a1_expected) + + a2_stored = ReductionSingleton().to_Q.get_q_resolution_a2() # in m + a2_expected = 2*math.sqrt((h2/1000.*h2/1000. + w2/1000.*w2/1000.)/6) + self.assertEqual(a2_stored, a2_expected) + + collimation_length_stored = ReductionSingleton().to_Q.get_q_resolution_collimation_length() # in m + collimation_length_expected = collimation_length + self.assertEqual(collimation_length_stored, collimation_length_expected) + + delta_r_stored = ReductionSingleton().to_Q.get_q_resolution_delta_r() # in m + delta_r_expected = delta_r/1000. + self.assertEqual(delta_r_stored, delta_r_expected) + + + def test_full_setup_for_rectangular_apertures_which_are_only_partially_specified(self): + # Arrange + command_iface.Clean() + command_iface.SANS2D() + a1 = 2 # in mm + a2 = 3 # in mm + delta_r = 4 # in mm + collimation_length = 10 # in m + h1 = 9 # in mm + w1 = 8 # in mm + h2 = 7 # in mm + # We take out w2, hence we don't have a full rectangular spec + norm = reduction_steps.CalculateNormISIS() + ReductionSingleton().to_Q = reduction_steps.ConvertToQISIS(norm) + + # Act + command_iface.set_q_resolution_a1(a1 = a1) + command_iface.set_q_resolution_a2(a2 = a2) + command_iface.set_q_resolution_delta_r(delta_r = delta_r) + command_iface.set_q_resolution_h1(h1 = h1) + command_iface.set_q_resolution_w1(w1 = w1) + command_iface.set_q_resolution_h2(h2 = h2) + + command_iface.set_q_resolution_collimation_length(collimation_length = collimation_length) + command_iface.set_q_resolution_use(use = True) + ReductionSingleton().to_Q._set_up_q_resolution_parameters() + + # Assert + a1_stored = ReductionSingleton().to_Q.get_q_resolution_a1() # in m + a1_expected = a1/1000. + self.assertEqual(a1_stored, a1_expected) + + a2_stored = ReductionSingleton().to_Q.get_q_resolution_a2() # in m + a2_expected = a2/1000. + self.assertEqual(a2_stored, a2_expected) + + collimation_length_stored = ReductionSingleton().to_Q.get_q_resolution_collimation_length() # in m + collimation_length_expected = collimation_length + self.assertEqual(collimation_length_stored, collimation_length_expected) + + delta_r_stored = ReductionSingleton().to_Q.get_q_resolution_delta_r() # in m + delta_r_expected = delta_r/1000. + self.assertEqual(delta_r_stored, delta_r_expected) + + if __name__ == "__main__": unittest.main() diff --git a/scripts/test/SANSIsisInstrumentTest.py b/scripts/test/SANSIsisInstrumentTest.py index e3570bbce18d2d06137ffb477c62452152f92507..62c7f9cb1fd96537476f9453faa490e70318a8ad 100644 --- a/scripts/test/SANSIsisInstrumentTest.py +++ b/scripts/test/SANSIsisInstrumentTest.py @@ -1,5 +1,6 @@ import unittest import mantid +from mantid.simpleapi import * import isis_instrument as instruments @@ -33,5 +34,53 @@ class SANSIsisInstrumentTest(unittest.TestCase): self.assertEqual(None, start_Tof) self.assertEqual(None, end_Tof) +class TestParameterMapModifications(unittest.TestCase): + def create_sample_workspace(self, ws_name, types, values, names): + ws = CreateSampleWorkspace(OutputWorkspace = ws_name) + length = len(types) + if any(len(element) != length for element in [types, values, names]): + return + for i in range(0, length): + SetInstrumentParameter(ws, ParameterName=names[i], + ParameterType= types[i], + Value = values[i]) + + def test_that_calibration_workspace_has_absent_entries_copied_over(self): + # Arrange + ws_name1 = "ws1" + types1 = ["Number", "String", "Number"] + values1 = ["100.4", "test", "200"] + names1 = ["val1", "val2", "val3"] + self.create_sample_workspace(ws_name1, types1, values1, names1) + + ws_name2 = "ws2" + types2 = ["Number", "String", "Number"] + values2 = ["2.4", "test3", "3"] + names2 = ["val1", "val2", "val4"] + self.create_sample_workspace(ws_name2, types2, values2, names2) + + inst = instruments.SANS2D() + + # Act + inst._add_parmeters_absent_in_calibration(ws_name1, ws_name2) + + # Assert + ws2 = mtd[ws_name2] + self.assertTrue(ws2.getInstrument().hasParameter("val1")) + self.assertTrue(ws2.getInstrument().hasParameter("val2")) + self.assertTrue(ws2.getInstrument().hasParameter("val3")) + self.assertTrue(ws2.getInstrument().hasParameter("val4")) + + self.assertTrue(len(ws2.getInstrument().getNumberParameter("val1"))== 1) + self.assertTrue(ws2.getInstrument().getNumberParameter("val1")[0] == 2.4) + + self.assertTrue(len(ws2.getInstrument().getIntParameter("val3"))== 1) + self.assertTrue(ws2.getInstrument().getIntParameter("val3")[0] == 200) + + # Clean up + DeleteWorkspace(ws_name1) + DeleteWorkspace(ws_name2) + + if __name__ == "__main__": unittest.main() diff --git a/scripts/test/SANSReductionStepsUserFileTest.py b/scripts/test/SANSReductionStepsUserFileTest.py index 32fcf7892be0b91dfa1b55c6bf5194f1d760e4f2..d06952672db11d8ca3569444b84667427d60258c 100644 --- a/scripts/test/SANSReductionStepsUserFileTest.py +++ b/scripts/test/SANSReductionStepsUserFileTest.py @@ -40,5 +40,101 @@ class SANSReductionStepsUserFileTest(unittest.TestCase): self.assertEqual(None, start_TOF_ROI, 'The start time should not have been set') self.assertEqual(None, end_TOF_ROI, 'The end time should not have been set') + + +class MockConvertTOQISISQResolution(object): + def __init__(self): + super(MockConvertTOQISISQResolution, self).__init__() + + self.delta_r = None + self.a1 = None + self.a2 = None + self.w1 = None + self.h1 = None + self.w2 = None + self.h2 = None + self.collim = None + self.on_off = None + + def set_q_resolution_delta_r(self, delta_r): + self.delta_r = delta_r + + def set_q_resolution_a1(self, a1): + self.a1 = a1 + + def set_q_resolution_a2(self, a2): + self.a2 = a2 + + def set_q_resolution_collimation_length(self, collimation_length): + self.collim = collimation_length + + def set_q_resolution_h1(self, h1): + self.h1 = h1 + + def set_q_resolution_h2(self, h2): + self.h2 = h2 + + def set_q_resolution_w1(self, w1): + self.w1 = w1 + + def set_q_resolution_w2(self, w2): + self.w2 = w2 + + def set_use_q_resolution(self, enabled): + self.on_off = enabled + +class MockReducerQResolution(object): + def __init__(self): + super(MockReducerQResolution, self).__init__() + self.to_Q = MockConvertTOQISISQResolution() + +class TestQResolutionInUserFile(unittest.TestCase): + def test_that_good_input_is_accepted(self): + # Arrange + reducer = MockReducerQResolution() + user_file = reduction_steps.UserFile() + + a1_val = 1 + a2_val = 2 + h1_val = 3 + w1_val = 4 + h2_val = 5 + w2_val = 6 + lcollim_val = 7 + delta_r_val = 8 + on_off_val = True + values = ["QRESOL/A1=" + str(a1_val), "QRESOL/A2 =" + str(a2_val), "QRESOL/H1 =" + str(h1_val), + "QRESOL/W1 =" + str(w1_val), "QRESOL/H2=" + str(h2_val), "QRESOL/W2= " + str(w2_val), + "QRESOL/LCOLLIM= " + str(lcollim_val),"QRESOL/LCOLLIM= " + str(lcollim_val), + "QRESOL/DELTAR= " + str(delta_r_val),"QRESOL/ ON"] + + # Act + for value in values: + user_file.read_line(value, reducer) + + # Assert + self.assertEqual(reducer.to_Q.a1,a1_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.a2,a2_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.h1,h1_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.h2,h2_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.w1,w1_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.w2,w2_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.delta_r,delta_r_val/1000., "Should be the input in meters") + self.assertEqual(reducer.to_Q.collim,lcollim_val, "Should be the input in meters") + self.assertEqual(reducer.to_Q.on_off,on_off_val, "Should be set to True") + + def test_that_non_float_type_for_a1_causes_error_warning(self): + # Arrange + reducer = MockReducerQResolution() + user_file = reduction_steps.UserFile() + a1_val = "sdf" + value = "A1=" + str(a1_val) + # Act + error = user_file._read_q_resolution_line(value, reducer) + # Assert + self.assertTrue(error != None) + + + if __name__ == "__main__": unittest.main() diff --git a/scripts/test/SANSUtilityTest.py b/scripts/test/SANSUtilityTest.py index 1fbad1df28e69622f4fea9f1f1a2123b1eca825b..4a301439f6ce04f5f8a0a457822b580fd6e09013 100644 --- a/scripts/test/SANSUtilityTest.py +++ b/scripts/test/SANSUtilityTest.py @@ -70,6 +70,33 @@ def provide_histo_workspace_with_one_spectrum(ws_name, x_start, x_end, bin_width BinWidth = bin_width) +def provide_workspace_with_x_errors(workspace_name,use_xerror = True, nspec = 1, + x_in = [1,2,3,4,5,6,7,8,9,10], + y_in = [2,2,2,2,2,2,2,2,2], + e_in = [1,1,1,1,1,1,1,1,1], + x_error = [1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9, 10.1]): + x = [] + y = [] + e = [] + for item in range(0, nspec): + x = x + x_in + y = y + y_in + e = e + e_in + + CreateWorkspace(DataX = x, + DataY =y, + DataE = e, + NSpec = nspec, + UnitX = "MomentumTransfer", + OutputWorkspace = workspace_name) + if use_xerror: + ws = mtd[workspace_name] + for hists in range(0, nspec): + x_error_array = np.asarray(x_error) + ws.setDx(hists, x_error_array) + + + # This test does not pass and was not used before 1/4/2015. SansUtilitytests was disabled. class SANSUtilityTest(unittest.TestCase): @@ -986,5 +1013,161 @@ class TestErrorPropagationFitAndRescale(unittest.TestCase): DeleteWorkspace(rear) +class TestGetCorrectQResolution(unittest.TestCase): + def test_error_is_passed_from_original_to_subtracted_workspace(self): + # Arrange + orig_name = "orig" + can_name = "can" + result_name = "result" + provide_workspace_with_x_errors(orig_name, True) + provide_workspace_with_x_errors(can_name, True) + provide_workspace_with_x_errors(result_name, False) + orig = mtd[orig_name] + can = mtd[can_name] + result = mtd[result_name] + # Act + su.correct_q_resolution_for_can(orig, can, result) + # Assert + dx_orig = orig.dataDx(0) + dx_result = result.dataDx(0) + self.assertTrue(result.hasDx(0)) + for index in range(0, len(dx_orig)): + self.assertEqual(dx_orig[index], dx_result[index]) + # Clean up + DeleteWorkspace(orig) + DeleteWorkspace(can) + DeleteWorkspace(result) + + def test_error_is_ignored_for_more_than_one_spectrum(self): + # Arrange + orig_name = "orig" + can_name = "can" + result_name = "result" + provide_workspace_with_x_errors(orig_name, True, 2) + provide_workspace_with_x_errors(can_name, True, 2) + provide_workspace_with_x_errors(result_name, False, 2) + orig = mtd[orig_name] + can = mtd[can_name] + result = mtd[result_name] + # Act + su.correct_q_resolution_for_can(orig, can, result) + # Assert + self.assertFalse(result.hasDx(0)) + # Clean up + DeleteWorkspace(orig) + DeleteWorkspace(can) + DeleteWorkspace(result) + + +class TestGetQResolutionForMergedWorkspaces(unittest.TestCase): + def test_error_is_ignored_for_more_than_one_spectrum(self): + # Arrange + front_name = "front" + rear_name = "rear" + result_name = "result" + provide_workspace_with_x_errors(front_name, True, 2) + provide_workspace_with_x_errors(rear_name, True, 2) + provide_workspace_with_x_errors(result_name, False, 2) + front = mtd[front_name] + rear = mtd[rear_name] + result = mtd[result_name] + scale = 2. + # Act + su.correct_q_resolution_for_merged(front, rear,result, scale) + # Assert + self.assertFalse(result.hasDx(0)) + # Clean up + DeleteWorkspace(front) + DeleteWorkspace(rear) + DeleteWorkspace(result) + + def test_error_is_ignored_when_only_one_input_has_dx(self): + # Arrange + front_name = "front" + rear_name = "rear" + result_name = "result" + provide_workspace_with_x_errors(front_name, True, 1) + provide_workspace_with_x_errors(rear_name, False, 1) + provide_workspace_with_x_errors(result_name, False, 1) + front = mtd[front_name] + rear = mtd[rear_name] + result = mtd[result_name] + scale = 2. + # Act + su.correct_q_resolution_for_merged(front, rear,result, scale) + # Assert + self.assertFalse(result.hasDx(0)) + # Clean up + DeleteWorkspace(front) + DeleteWorkspace(rear) + DeleteWorkspace(result) + + def test_that_non_matching_workspaces_are_detected(self): + # Arrange + front_name = "front" + rear_name = "rear" + result_name = "result" + x1 = [1,2,3] + e1 = [1,1] + y1 = [2,2] + dx1 = [1.,2.,3.] + x2 = [1,2,3,4] + e2 = [1,1, 1] + y2 = [2,2, 2] + dx2 = [1.,2.,3.,4.] + provide_workspace_with_x_errors(front_name, True, 1, x1, y1, e1, dx1) + provide_workspace_with_x_errors(rear_name, True, 1, x2, y2, e2, dx2) + provide_workspace_with_x_errors(result_name, False, 1) + front = mtd[front_name] + rear = mtd[rear_name] + result = mtd[result_name] + scale = 2. + # Act + su.correct_q_resolution_for_merged(front, rear,result, scale) + # Assert + self.assertFalse(result.hasDx(0)) + # Clean up + DeleteWorkspace(front) + DeleteWorkspace(rear) + DeleteWorkspace(result) + + def test_correct_x_error_is_produced(self): + # Arrange + x = [1,2,3] + e = [1,1] + y_front = [2,2] + dx_front = [1.,2.,3.] + y_rear = [1.5,1.5] + dx_rear = [3.,2.,1.] + front_name = "front" + rear_name = "rear" + result_name = "result" + provide_workspace_with_x_errors(front_name, True, 1, x, y_front, e, dx_front) + provide_workspace_with_x_errors(rear_name, True, 1, x, y_rear, e, dx_rear) + provide_workspace_with_x_errors(result_name, False, 1, x, y_front, e) + front = mtd[front_name] + rear = mtd[rear_name] + result = mtd[result_name] + scale = 2. + # Act + su.correct_q_resolution_for_merged(front, rear,result, scale) + # Assert + self.assertTrue(result.hasDx(0)) + + dx_expected_0 = (dx_front[0]*y_front[0]*scale + dx_rear[0]*y_rear[0])/(y_front[0]*scale + y_rear[0]) + dx_expected_1 = (dx_front[1]*y_front[1]*scale + dx_rear[1]*y_rear[1])/(y_front[1]*scale + y_rear[1]) + dx_expected_2 = dx_expected_1 + dx_result = result.readDx(0) + self.assertTrue(len(dx_result) ==3) + self.assertEqual(dx_result[0], dx_expected_0) + self.assertEqual(dx_result[1], dx_expected_1) + self.assertEqual(dx_result[2], dx_expected_2) + + # Clean up + DeleteWorkspace(front) + DeleteWorkspace(rear) + DeleteWorkspace(result) + + if __name__ == "__main__": unittest.main()