From 16f43a3977f689e8293ee7e667a005153b623149 Mon Sep 17 00:00:00 2001 From: Ross Whitfield <whitfieldre@ornl.gov> Date: Tue, 24 Apr 2018 15:59:08 -0400 Subject: [PATCH] Allow PeaksWorkspace createPeakQSample to calculate goniometer for CW Moved code from FindPeaksMD into new method on Goniometer. Then added option onto peaks workspace. --- Framework/DataObjects/src/PeaksWorkspace.cpp | 22 +++++++- .../MantidGeometry/Instrument/Goniometer.h | 4 ++ .../Geometry/src/Instrument/Goniometer.cpp | 32 ++++++++++++ Framework/Geometry/test/GoniometerTest.h | 50 +++++++++++++++++++ Framework/MDAlgorithms/src/FindPeaksMD.cpp | 28 ++--------- docs/source/algorithms/FindPeaksMD-v1.rst | 2 + docs/source/concepts/PeaksWorkspace.rst | 21 ++++++++ 7 files changed, 134 insertions(+), 25 deletions(-) diff --git a/Framework/DataObjects/src/PeaksWorkspace.cpp b/Framework/DataObjects/src/PeaksWorkspace.cpp index 7cbe6aea457..8f8ecd91dc2 100644 --- a/Framework/DataObjects/src/PeaksWorkspace.cpp +++ b/Framework/DataObjects/src/PeaksWorkspace.cpp @@ -16,6 +16,7 @@ #include "MantidKernel/PhysicalConstants.h" #include "MantidKernel/Quat.h" #include "MantidKernel/Unit.h" +#include "MantidKernel/UnitConversion.h" #include "MantidKernel/V3D.h" #include <algorithm> @@ -278,7 +279,26 @@ PeaksWorkspace::createPeak(const Kernel::V3D &position, */ Peak *PeaksWorkspace::createPeakQSample(const V3D &position) const { // Create a peak from QSampleFrame - const auto goniometer = run().getGoniometer(); + + Geometry::Goniometer goniometer; + + LogManager_const_sptr props = getLogs(); + if (props->hasProperty("wavelength") || props->hasProperty("energy")) { + // Assume constant wavelenth + // Calculate Q lab from Q sample and wavelength + double wavelength; + if (props->hasProperty("energy")) { + wavelength = Kernel::UnitConversion::run( + "Energy", "Wavelength", + props->getPropertyValueAsType<double>("energy"), 0, 0, 0, + Kernel::DeltaEMode::Elastic, 0); + } else { + wavelength = props->getPropertyValueAsType<double>("wavelength"); + } + goniometer.calcFromQSampleAndWavelength(position, wavelength); + } else { + goniometer = run().getGoniometer(); + } // create a peak using the qLab frame auto peak = new Peak(getInstrument(), position, goniometer.getR()); // Take the run number from this diff --git a/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h b/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h index 3a1b47ce811..7ffbb2626a3 100644 --- a/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h +++ b/Framework/Geometry/inc/MantidGeometry/Instrument/Goniometer.h @@ -91,6 +91,10 @@ public: // Set rotation angle for an axis in the units the angle is set (default -- // degrees) void setRotationAngle(size_t axisnumber, double value); + // Calculate goniometer for rotation around y-axis for constant wavelength + // from Q Sample + void calcFromQSampleAndWavelength(const Mantid::Kernel::V3D &Q, + double wavelength); // Get axis object const GoniometerAxis &getAxis(size_t axisnumber) const; // Get axis object diff --git a/Framework/Geometry/src/Instrument/Goniometer.cpp b/Framework/Geometry/src/Instrument/Goniometer.cpp index 3bae3f88603..c672be30d65 100644 --- a/Framework/Geometry/src/Instrument/Goniometer.cpp +++ b/Framework/Geometry/src/Instrument/Goniometer.cpp @@ -178,6 +178,38 @@ void Goniometer::setRotationAngle(size_t axisnumber, double value) { recalculateR(); } +/**Calculate goniometer for rotation around y-asix for constant wavelength from + * Q Sample + * @param Q :: Q Sample position in reciprocal space + * @param wavelength :: wavelength +*/ +void Goniometer::calcFromQSampleAndWavelength(const Mantid::Kernel::V3D &Q, + double wavelength) { + double wv = 2.0 * M_PI / wavelength; + double norm_q2 = Q.norm2(); + double theta = acos(1 - norm_q2 / (2 * wv * wv)); // [0, pi] + double phi = asin(-Q[1] / wv * sin(theta)); // [-pi/2, pi/2] + V3D Q_lab(-wv * sin(theta) * cos(phi), -wv * sin(theta) * sin(phi), + wv * (1 - cos(theta))); + + // Solve to find rotation matrix, assuming only rotation around y-axis + // A * X = B + Matrix<double> A({Q[0], Q[2], Q[2], -Q[0]}, 2, 2); + A.Invert(); + std::vector<double> B{Q_lab[0], Q_lab[2]}; + std::vector<double> X = A * B; + double rot = atan2(X[1], X[0]); + g_log.information() << "Found goniometer rotation to be " << rot * 180 / M_PI + << " degrees for Q sample = " << Q << "\n"; + + Matrix<double> goniometer(3, 3, true); + goniometer[0][0] = cos(rot); + goniometer[0][2] = sin(rot); + goniometer[2][0] = -sin(rot); + goniometer[2][2] = cos(rot); + setR(goniometer); +} + /// Get GoniometerAxis obfject using motor number /// @param axisnumber :: axis number (from 0) const GoniometerAxis &Goniometer::getAxis(size_t axisnumber) const { diff --git a/Framework/Geometry/test/GoniometerTest.h b/Framework/Geometry/test/GoniometerTest.h index 7cfad5d1b3f..afcdf3a9a3a 100644 --- a/Framework/Geometry/test/GoniometerTest.h +++ b/Framework/Geometry/test/GoniometerTest.h @@ -179,6 +179,56 @@ public: } } + void test_calcFromQSampleAndWavelength() { + Goniometer G; + double wavelength = 2 * M_PI; // k=1 + DblMatrix R; + V3D Q; + + // 0 degree rotation + Q = V3D(-1, 0, 1); + G.calcFromQSampleAndWavelength(Q, wavelength); + R = G.getR(); + TS_ASSERT_DELTA(R[0][0], 1.0, 0.001); + TS_ASSERT_DELTA(R[0][1], 0.0, 0.001); + TS_ASSERT_DELTA(R[0][2], 0.0, 0.001); + TS_ASSERT_DELTA(R[1][0], 0.0, 0.001); + TS_ASSERT_DELTA(R[1][1], 1.0, 0.001); + TS_ASSERT_DELTA(R[1][2], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][0], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][1], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][2], 1.0, 0.001); + + // -90 degree rotation + Q = V3D(1, 0, 1); + G.calcFromQSampleAndWavelength(Q, wavelength); + R = G.getR(); + TS_ASSERT_DELTA(R[0][0], 0.0, 0.001); + TS_ASSERT_DELTA(R[0][1], 0.0, 0.001); + TS_ASSERT_DELTA(R[0][2], -1.0, 0.001); + TS_ASSERT_DELTA(R[1][0], 0.0, 0.001); + TS_ASSERT_DELTA(R[1][1], 1.0, 0.001); + TS_ASSERT_DELTA(R[1][2], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][0], 1.0, 0.001); + TS_ASSERT_DELTA(R[2][1], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][2], 0.0, 0.001); + + // 30 degree rotation + wavelength = 1.54; + Q = V3D(-0.63523489, -0.12302677, -0.29517982); + G.calcFromQSampleAndWavelength(Q, wavelength); + R = G.getR(); + TS_ASSERT_DELTA(R[0][0], 0.866, 0.001); + TS_ASSERT_DELTA(R[0][1], 0.0, 0.001); + TS_ASSERT_DELTA(R[0][2], 0.5, 0.01); + TS_ASSERT_DELTA(R[1][0], 0.0, 0.001); + TS_ASSERT_DELTA(R[1][1], 1.0, 0.001); + TS_ASSERT_DELTA(R[1][2], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][0], -0.5, 0.01); + TS_ASSERT_DELTA(R[2][1], 0.0, 0.001); + TS_ASSERT_DELTA(R[2][2], 0.866, 0.001); + } + /** Save and load to NXS file */ void test_nexus() { NexusTestHelper th(true); diff --git a/Framework/MDAlgorithms/src/FindPeaksMD.cpp b/Framework/MDAlgorithms/src/FindPeaksMD.cpp index 9856bc510dc..7c19297f430 100644 --- a/Framework/MDAlgorithms/src/FindPeaksMD.cpp +++ b/Framework/MDAlgorithms/src/FindPeaksMD.cpp @@ -4,6 +4,7 @@ #include "MantidDataObjects/MDHistoWorkspace.h" #include "MantidDataObjects/PeaksWorkspace.h" #include "MantidGeometry/Crystal/EdgePixel.h" +#include "MantidGeometry/Instrument/Goniometer.h" #include "MantidGeometry/Objects/InstrumentRayTracer.h" #include "MantidKernel/BoundedValidator.h" #include "MantidKernel/EnabledWhenProperty.h" @@ -295,30 +296,9 @@ FindPeaksMD::createPeak(const Mantid::Kernel::V3D &Q, const double binCount, if (calcGoniometer) { // Calculate Q lab from Q sample and wavelength double wavelength = getProperty("Wavelength"); - double wv = 2.0 * M_PI / wavelength; - double norm_q2 = Q.norm2(); - double theta = acos(1 - norm_q2 / (2 * wv * wv)); - double phi = asin(-Q[1] / wv * sin(theta)); - V3D Q_lab(-wv * sin(theta) * cos(phi), -wv * sin(theta) * sin(phi), - wv * (1 - cos(theta))); - - // Solve to find rotation matrix, assuming only rotation around y-axis - // A * X = B - Matrix<double> A({Q[0], Q[2], Q[2], -Q[0]}, 2, 2); - A.Invert(); - std::vector<double> B{Q_lab[0], Q_lab[2]}; - std::vector<double> X = A * B; - double rot = atan2(X[1], X[0]); - g_log.information() << "Found goniometer rotation to be " - << rot * 180 / M_PI - << " degrees for peak at Q sample = " << Q << "\n"; - - Matrix<double> goniometer(3, 3, true); - goniometer[0][0] = cos(rot); - goniometer[0][2] = sin(rot); - goniometer[2][0] = -sin(rot); - goniometer[2][2] = cos(rot); - p = boost::make_shared<Peak>(inst, Q, goniometer); + Geometry::Goniometer goniometer; + goniometer.calcFromQSampleAndWavelength(Q, wavelength); + p = boost::make_shared<Peak>(inst, Q, goniometer.getR()); } else { p = boost::make_shared<Peak>(inst, Q, m_goniometer); diff --git a/docs/source/algorithms/FindPeaksMD-v1.rst b/docs/source/algorithms/FindPeaksMD-v1.rst index 924f356c771..48f18985df8 100644 --- a/docs/source/algorithms/FindPeaksMD-v1.rst +++ b/docs/source/algorithms/FindPeaksMD-v1.rst @@ -85,6 +85,8 @@ First calculate the :math:`\textbf{Q}_{lab}` using .. math:: \phi = \sin^{-1}(-\textbf{Q}_{sample}^y \sin(\theta)/k) +where :math:`\theta` is from 0 to :math:`\pi` and :math:`\phi` is from :math:`-\pi/2` to :math:`\pi/2`. This means that it will assume your detector position is on the left of the beam even it it's not. + Now you have :math:`\theta`, :math:`\phi` and k you can get :math:`\textbf{Q}_{lab}` using (1). We need to now solve :math:`G \textbf{Q}_{sample} = diff --git a/docs/source/concepts/PeaksWorkspace.rst b/docs/source/concepts/PeaksWorkspace.rst index d6c43dbd29d..1195279873f 100644 --- a/docs/source/concepts/PeaksWorkspace.rst +++ b/docs/source/concepts/PeaksWorkspace.rst @@ -49,6 +49,27 @@ Each Peak object contains a PeakShape. Only the integration algorithms which act Subtypes of PeakShape will then provide additional information. For example PeakShapeSpherical provides the radius as well as background inner, and background outer radius. +Calculate Goniometer For Constant Wavelength +-------------------------------------------- + +If you set the `wavelength` (in Ã…) or `energy` (in meV) property on a +PeaksWorkspace when the createPeak method is used the goniometer +rotation with be calculated. This allows you to use one instrument +definition for multiple goniometer rotations, for example adding peaks +in Slice Viewer from multiple combined MD workspaces. It only works +for a constant wavelength source and only for Q sample workspaces. It +also assumes the goniometer rotation is around the y-axis only. For +details on the calculation see "Calculate Goniometer For Constant +Wavelength" at :ref:`FindPeaksMD <algm-FindPeaksMD>`. + +.. code-block:: python + + pws = mtd['name_of_peaks_workspace'] + pws.run().addProperty('wavelength', 1.54, True) + # or + pws.run().addProperty('energy', 34.48, True) + + Using PeaksWorkspaces in Python --------------------------------- -- GitLab