From 61141309dff34e17047fd172f44d7c0faf84a6a3 Mon Sep 17 00:00:00 2001
From: Roman Tolchenov <roman.tolchenov@stfc.ac.uk>
Date: Tue, 3 Apr 2018 15:10:36 +0100
Subject: [PATCH] Move detectors after summing. Re #21742

---
 .../ReflectometryReductionOne2.h              |   5 +
 .../src/ReflectometryReductionOne2.cpp        |  44 +++++++
 .../test/ReflectometryReductionOne2Test.h     | 113 ++++++++++++++++++
 3 files changed, 162 insertions(+)

diff --git a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h
index 49a8f680da0..e5627b9f462 100644
--- a/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h
+++ b/Framework/Algorithms/inc/MantidAlgorithms/ReflectometryReductionOne2.h
@@ -141,6 +141,10 @@ private:
   void verifySpectrumMaps(API::MatrixWorkspace_const_sptr ws1,
                           API::MatrixWorkspace_const_sptr ws2,
                           const bool severe);
+  // Correct the detector angle
+  Mantid::API::MatrixWorkspace_sptr
+  correctDetectorAngle(Mantid::API::MatrixWorkspace_sptr inputWS);
+
 
   // Find and cache constants
   void findDetectorGroups();
@@ -162,6 +166,7 @@ private:
   bool m_normaliseMonitors;     // normalise by monitors and direct beam
   bool m_normaliseTransmission; // transmission or algorithmic correction
   bool m_sum;                   // whether to do summation
+  bool m_correctAngleInLambda;  // whether to correct the angle of detectors
   double m_theta0;              // horizon angle
   // groups of spectrum indices of the detectors of interest
   std::vector<std::vector<size_t>> m_detectorGroups;
diff --git a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
index 28612fa314a..30e4667a32a 100644
--- a/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
+++ b/Framework/Algorithms/src/ReflectometryReductionOne2.cpp
@@ -7,6 +7,7 @@
 #include "MantidGeometry/Objects/BoundingBox.h"
 #include "MantidHistogramData/LinearGenerator.h"
 #include "MantidIndexing/IndexInfo.h"
+#include "MantidKernel/ListValidator.h"
 #include "MantidKernel/MandatoryValidator.h"
 #include "MantidKernel/StringTokenizer.h"
 #include "MantidKernel/Unit.h"
@@ -361,6 +362,16 @@ void ReflectometryReductionOne2::init() {
                       "ThetaIn", Mantid::EMPTY_DBL(), Direction::Input),
                   "Angle in degrees");
 
+  // Angle correction type
+  const std::vector<std::string> correctionType{"VerticalShift",
+                                                "RotateAroundSample"};
+  auto correctionTypeValidator = boost::make_shared<StringListValidator>(correctionType);
+  declareProperty(
+      "DetectorCorrectionType", correctionType[0], correctionTypeValidator,
+      "Whether detectors should be shifted vertically or rotated around the "
+      "sample position if angle correction is applied.",
+      Direction::Input);
+
   // Processing instructions
   declareProperty(Kernel::make_unique<PropertyWithValue<std::string>>(
                       "ProcessingInstructions", "",
@@ -460,6 +471,8 @@ void ReflectometryReductionOne2::exec() {
     m_normaliseMonitors = false;
     m_sum = false;
   }
+  // Angle correction must be done if ThetaIn is set
+  m_correctAngleInLambda = !(*getProperty("ThetaIn")).isDefault() && !summingInQ();
 
   // Create the output workspace in wavelength
   MatrixWorkspace_sptr IvsLam = makeIvsLam();
@@ -624,6 +637,10 @@ MatrixWorkspace_sptr ReflectometryReductionOne2::makeIvsLam() {
       result = transOrAlgCorrection(result, true);
       outputDebugWorkspace(result, wsName, "_norm_trans", debug, step);
     }
+    if (m_correctAngleInLambda) {
+      g_log.debug("Correcting the angles\n");
+      result = correctDetectorAngle(result);
+    }
   }
 
   return result;
@@ -1334,5 +1351,32 @@ void ReflectometryReductionOne2::verifySpectrumMaps(
     }
   }
 }
+
+/** Apply angle correction to the detectors.
+
+  @param inputWS [in] :: A workspace to correct.
+  @return :: The corrected workspace.
+ */
+Mantid::API::MatrixWorkspace_sptr
+ReflectometryReductionOne2::correctDetectorAngle(
+    Mantid::API::MatrixWorkspace_sptr inputWS) {
+  const auto detectorIDs = inputWS->getSpectrum(0).getDetectorIDs();
+  auto result = inputWS;
+  double const thetaIn = getProperty("ThetaIn");
+  double const twoTheta = 2.0 * thetaIn;
+  const std::string correctionType = getProperty("DetectorCorrectionType");
+  for (auto detectorID : detectorIDs) {
+    auto correctAngle = this->createChildAlgorithm("SpecularReflectionPositionCorrect");
+    correctAngle->initialize();
+    correctAngle->setProperty("InputWorkspace", result);
+    correctAngle->setProperty("TwoTheta", twoTheta);
+    correctAngle->setProperty("DetectorCorrectionType", correctionType);
+    correctAngle->setProperty("DetectorID", detectorID);
+    correctAngle->execute();
+    result = correctAngle->getProperty("OutputWorkspace");
+  }
+  return result;
+}
+
 } // namespace Algorithms
 } // namespace Mantid
diff --git a/Framework/Algorithms/test/ReflectometryReductionOne2Test.h b/Framework/Algorithms/test/ReflectometryReductionOne2Test.h
index 5ea8486ee85..5e637af2401 100644
--- a/Framework/Algorithms/test/ReflectometryReductionOne2Test.h
+++ b/Framework/Algorithms/test/ReflectometryReductionOne2Test.h
@@ -7,6 +7,8 @@
 #include "MantidAPI/Axis.h"
 #include "MantidAPI/FrameworkManager.h"
 #include "MantidAPI/MatrixWorkspace.h"
+#include "MantidGeometry/Instrument.h"
+#include "MantidGeometry/Instrument/ReferenceFrame.h"
 #include "MantidHistogramData/HistogramY.h"
 #include "MantidTestHelpers/WorkspaceCreationHelper.h"
 
@@ -581,6 +583,82 @@ public:
     TS_ASSERT_DELTA(outQ->y(0)[7], 2.607359, 1e-6);
   }
 
+  void test_angle_correction() {
+    ReflectometryReductionOne2 alg;
+    setupAlgorithm(alg, 1.5, 15.0, "1+2");
+    alg.setProperty("ThetaIn", 22.0);
+    MatrixWorkspace_sptr outLam = runAlgorithmLam(alg);
+
+    TS_ASSERT_DELTA(outLam->y(0)[0], 4.0, 1e-14);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 4.0, 1e-14);
+
+    auto detector = outLam->getDetector(0);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, -1, detector), 44.0, 1e-14);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, 2), 44.0, 1e-14);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, 3), 44.0, 1e-14);
+
+    auto shift2 = calculateShift(m_multiDetectorWS, outLam, 2);
+    TS_ASSERT_DELTA(shift2.X(), 0, 1e-14);
+    TS_ASSERT_DELTA(shift2.Y(), -0.0715561259, 1e-10);
+    TS_ASSERT_DELTA(shift2.Z(), 0, 1e-14);
+
+    auto shift3 = calculateShift(m_multiDetectorWS, outLam, 3);
+    TS_ASSERT_DELTA(shift3.X(), 0, 1e-14);
+    TS_ASSERT_DELTA(shift3.Y(), -0.1715561259, 1e-10);
+    TS_ASSERT_DELTA(shift3.Z(), 0, 1e-14);
+  }
+
+  void test_angle_correction_multi() {
+    ReflectometryReductionOne2 alg;
+    setupAlgorithm(alg, 1.5, 15.0, "1:2");
+    alg.setProperty("ThetaIn", 22.0);
+    MatrixWorkspace_sptr outLam = runAlgorithmLam(alg, 14, 2);
+
+    TS_ASSERT_DELTA(outLam->y(0)[0], 2.0, 1e-14);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 2.0, 1e-14);
+
+    auto detector = outLam->getDetector(0);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, -1, detector), 44.0, 1e-14);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, 2), 44.0, 1e-14);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, 3), 45.0, 1e-14);
+
+    auto shift2 = calculateShift(m_multiDetectorWS, outLam, 2);
+    TS_ASSERT_DELTA(shift2.X(), 0, 1e-14);
+    TS_ASSERT_DELTA(shift2.Y(), -0.0715561259, 1e-10);
+    TS_ASSERT_DELTA(shift2.Z(), 0, 1e-14);
+
+    auto shift3 = calculateShift(m_multiDetectorWS, outLam, 3);
+    TS_ASSERT_DELTA(shift3.X(), 0, 1e-14);
+    TS_ASSERT_DELTA(shift3.Y(), 0, 1e-10);
+    TS_ASSERT_DELTA(shift3.Z(), 0, 1e-14);
+  }
+
+  void test_angle_correction_rotation() {
+    ReflectometryReductionOne2 alg;
+    setupAlgorithm(alg, 1.5, 15.0, "1+2");
+    alg.setProperty("ThetaIn", 22.0);
+    alg.setProperty("DetectorCorrectionType", "RotateAroundSample");
+    MatrixWorkspace_sptr outLam = runAlgorithmLam(alg);
+
+    TS_ASSERT_DELTA(outLam->y(0)[0], 4.0, 1e-14);
+    TS_ASSERT_DELTA(outLam->y(0)[7], 4.0, 1e-14);
+
+    auto detector = outLam->getDetector(0);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, -1, detector), 44.0, 1e-13);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, 2), 44.0, 1e-14);
+    TS_ASSERT_DELTA(calculateTwoTheta(outLam, 3), 44.0, 1e-14);
+
+    auto shift2 = calculateShift(m_multiDetectorWS, outLam, 2);
+    TS_ASSERT_DELTA(shift2.X(), 0.0358923903, 1e-10);
+    TS_ASSERT_DELTA(shift2.Y(), -0.0368952475, 1e-10);
+    TS_ASSERT_DELTA(shift2.Z(), 0, 1e-14);
+
+    auto shift3 = calculateShift(m_multiDetectorWS, outLam, 3);
+    TS_ASSERT_DELTA(shift3.X(), 0.0865005079, 1e-10);
+    TS_ASSERT_DELTA(shift3.Y(), -0.0880235564, 1e-10);
+    TS_ASSERT_DELTA(shift3.Z(), 0, 1e-14);
+  }
+
 private:
   // Do standard algorithm setup
   void setupAlgorithm(ReflectometryReductionOne2 &alg,
@@ -661,6 +739,41 @@ private:
 
     return outQ;
   }
+
+  // Calculate the two theta angle of a detector
+  double calculateTwoTheta(MatrixWorkspace_sptr ws, Mantid::detid_t detid,
+                           Mantid::Geometry::IDetector_const_sptr detector =
+                               Mantid::Geometry::IDetector_const_sptr()) {
+    auto instrument = ws->getInstrument();
+    auto sample = instrument->getComponentByName("some-surface-holder");
+    if (!detector) {
+      detector = instrument->getDetector(detid);
+    }
+    auto detSample = detector->getPos() - sample->getPos();
+
+    auto refFrame = instrument->getReferenceFrame();
+
+    const double upoffset = refFrame->vecPointingUp().scalar_prod(detSample);
+    const double beamoffset =
+        refFrame->vecPointingAlongBeam().scalar_prod(detSample);
+
+    return std::atan2(upoffset, beamoffset) * 180 / M_PI;
+  }
+
+  // Calculate the shift in detector position
+  Mantid::Kernel::V3D calculateShift(MatrixWorkspace_sptr inputWS,
+                                     MatrixWorkspace_sptr outputWS,
+                                     Mantid::detid_t detid) {
+    auto inputInstrument = inputWS->getInstrument();
+    auto inputDetector = inputInstrument->getDetector(detid);
+    auto inPos = inputDetector->getPos();
+
+    auto outputInstrument = outputWS->getInstrument();
+    auto outputDetector = outputInstrument->getDetector(detid);
+    auto outPos = outputDetector->getPos();
+
+    return outPos - inPos;
+  }
 };
 
 #endif /* ALGORITHMS_TEST_REFLECTOMETRYREDUCTIONONE2TEST_H_ */
-- 
GitLab