Mantid
Loading...
Searching...
No Matches
Goniometer.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
10#include "MantidKernel/Quat.h"
12#include <boost/algorithm/string.hpp>
13#include <cstdlib>
14#include <sstream>
15#include <stdexcept>
16#include <string>
17#include <utility>
18
19#include <vector>
20
21using namespace Mantid::Kernel;
23
24namespace Mantid::Geometry {
26using Kernel::Quat;
27using Kernel::V3D;
28
30
31void GoniometerAxis::saveNexus(Nexus::File *file, const std::string &group) const {
32 file->makeGroup(group, "NXmotor", true);
33 file->writeData("name", name);
34 file->writeData("angle", angle);
35 file->openData("angle");
36 std::string unit = (angleunit == angDegrees) ? "deg" : "rad";
37 file->putAttr("unit", unit);
38 std::string sen = (sense == CW) ? "CW" : "CCW";
39 file->putAttr("sense", sen);
40 file->closeData();
41 rotationaxis.saveNexus(file, "rotationaxis");
42 file->closeGroup();
43}
44
45void GoniometerAxis::loadNexus(Nexus::File *file, const std::string &group) {
46 file->openGroup(group, "NXmotor");
47 file->readData("name", name);
48 file->readData("angle", angle);
49 file->openData("angle");
50 std::string s;
51 file->getAttr("sense", s);
52 sense = (s == "CW") ? CW : CCW;
53 file->getAttr("unit", s);
54 angleunit = (s == "rad") ? angRadians : angDegrees;
55 file->closeData();
56 rotationaxis.loadNexus(file, "rotationaxis");
57 file->closeGroup();
58}
59
62Goniometer::Goniometer() : m_R(3, 3, true), m_initFromR(false) {}
63
68 bool isRot = rot.isRotation(); // this can also throw if matrix is invalid rotation, but that is fine
69 if (!isRot) {
70 // constructor should fail if the matrix is invalid
71 throw std::invalid_argument("rot has not been evaluated to be a valid rotation matrix");
72 }
73 m_R = rot;
74 m_initFromR = true;
75}
76
79 : m_R(other.m_R), m_motors(other.m_motors), m_initFromR(other.m_initFromR) {}
80
83 if (this != &other) {
84 m_R = other.m_R;
85 m_motors = other.m_motors;
86 m_initFromR = other.m_initFromR;
87 }
88 return *this;
89}
90
91// Move constructor
93 : m_R(std::move(other.m_R)), m_motors(std::move(other.m_motors)), m_initFromR(other.m_initFromR) {}
94
95// Move assignment operator
97 if (this != &other) {
98 m_R = std::move(other.m_R);
99 m_motors = std::move(other.m_motors);
100 m_initFromR = other.m_initFromR;
101 }
102 return *this;
103}
104
107const Kernel::DblMatrix &Goniometer::getR() const { return m_R; }
108
113 m_R = std::move(rot);
114 m_initFromR = true;
115}
116
118bool Goniometer::isDefined() const { return m_initFromR || (!m_motors.empty()); }
119
120bool Goniometer::operator==(const Goniometer &other) const { return this->m_R == other.m_R; }
121
122bool Goniometer::operator!=(const Goniometer &other) const { return this->m_R != other.m_R; }
123
128std::string Goniometer::axesInfo() {
129 if (m_initFromR) {
130 return std::string("Goniometer was initialized from a rotation matrix. No "
131 "information about axis is available.\n");
132 } else {
133 std::stringstream info;
134 std::vector<GoniometerAxis>::iterator it;
135
136 if (m_motors.empty()) {
137 info << "No axis is found\n";
138 } else {
139 info << "Name \t Direction \t Sense \t Angle \n";
140 std::string strCW("CW"), strCCW("CCW");
141 for (it = m_motors.begin(); it < m_motors.end(); ++it) {
142 std::string sense = ((*it).sense == CCW) ? strCCW : strCW;
143 double angle = ((*it).angleunit == angDegrees) ? ((*it).angle) : ((*it).angle * rad2deg);
144 info << (*it).name << "\t" << (*it).rotationaxis << "\t" << sense << "\t" << angle << '\n';
145 }
146 }
147 return info.str();
148 }
149}
150
160void Goniometer::pushAxis(const std::string &name, double axisx, double axisy, double axisz, double angle, int sense,
161 int angUnit) {
162 if (m_initFromR) {
163 throw std::runtime_error("Initialized from a rotation matrix, so no axes can be pushed.");
164 } else {
165 if (!std::isfinite(axisx) || !std::isfinite(axisy) || !std::isfinite(axisz) || !std::isfinite(angle)) {
166 g_log.warning() << "NaN encountered while trying to push axis to "
167 "goniometer, Operation aborted"
168 << "\naxis name" << name << "\naxisx" << axisx << "\naxisy" << axisx << "\naxisz" << axisz
169 << "\nangle" << angle;
170 return;
171 }
172 // check if such axis is already defined
173 const auto it =
174 std::find_if(m_motors.cbegin(), m_motors.cend(), [&name](const auto &axis) { return axis.name == name; });
175 if (it != m_motors.cend()) {
176 throw std::invalid_argument("Motor name already defined");
177 }
178 GoniometerAxis a(name, V3D(axisx, axisy, axisz), angle, sense, angUnit);
179 m_motors.emplace_back(a);
180 }
181 recalculateR();
182}
183
188void Goniometer::setRotationAngle(const std::string &name, double value) {
189 bool changed = false;
190 std::vector<GoniometerAxis>::iterator it;
191 for (it = m_motors.begin(); it < m_motors.end(); ++it) {
192 if (name == it->name) {
193 it->angle = value;
194 changed = true;
195 }
196 }
197 if (!changed) {
198 throw std::invalid_argument("Motor name " + name + " not found");
199 }
200 recalculateR();
201}
202
207void Goniometer::setRotationAngle(size_t axisnumber, double value) {
208 if (axisnumber >= m_motors.size())
209 throw std::out_of_range("Goniometer::setRotationAngle(): axis number specified is too large.");
210 (m_motors.at(axisnumber)).angle = value; // it will throw out of range exception
211 // if axisnumber is not in range
212 recalculateR();
213}
214
225 bool inner) {
226 V3D Q(position);
227 if (Kernel::ConfigService::Instance().getString("Q.convention") == "Crystallography")
228 Q *= -1;
229
230 Matrix<double> starting_goniometer = getR();
231
232 if (!inner)
233 Q = starting_goniometer * Q;
234
235 double wv = 2.0 * M_PI / wavelength;
236 double norm_q2 = Q.norm2();
237 double theta = acos(1 - norm_q2 / (2 * wv * wv)); // [0, pi]
238 double phi = asin(-Q[1] / (wv * sin(theta))); // [-pi/2, pi/2]
239 V3D Q_lab(-wv * sin(theta) * cos(phi), -wv * sin(theta) * sin(phi), wv * (1 - cos(theta)));
240
241 if (flip_x)
242 Q_lab[0] *= -1;
243
244 if (inner) {
245 starting_goniometer.Invert();
246 Q_lab = starting_goniometer * Q_lab;
247 }
248
249 // Solve to find rotation matrix, assuming only rotation around y-axis
250 // A * X = B
251 Matrix<double> A({Q[0], Q[2], Q[2], -Q[0]}, 2, 2);
252 A.Invert();
253 std::vector<double> B{Q_lab[0], Q_lab[2]};
254 std::vector<double> X = A * B;
255 double rot = atan2(X[1], X[0]);
256
257 Matrix<double> goniometer(3, 3, true);
258 goniometer[0][0] = cos(rot);
259 goniometer[0][2] = sin(rot);
260 goniometer[2][0] = -sin(rot);
261 goniometer[2][2] = cos(rot);
262 if (inner) {
263 starting_goniometer.Invert();
264 setR(starting_goniometer * goniometer);
265 } else {
266 setR(goniometer * starting_goniometer);
267 }
268}
269
272const GoniometerAxis &Goniometer::getAxis(size_t axisnumber) const {
273 return m_motors.at(axisnumber); // it will throw out of range exception if
274 // axisnumber is not in range
275}
276
279const GoniometerAxis &Goniometer::getAxis(const std::string &axisname) const {
280 const auto it =
281 std::find_if(m_motors.cbegin(), m_motors.cend(), [&axisname](const auto &axis) { return axis.name == axisname; });
282 if (it != m_motors.cend()) {
283 return *it;
284 }
285 throw std::invalid_argument("Motor name " + axisname + " not found");
286}
287
289size_t Goniometer::getNumberAxes() const { return m_motors.size(); }
290
299 m_motors.clear();
300 this->pushAxis("omega", 0., 1., 0., 0., Mantid::Geometry::CCW, Mantid::Geometry::angDegrees);
301 this->pushAxis("chi", 0., 0., 1., 0., Mantid::Geometry::CCW, Mantid::Geometry::angDegrees);
302 this->pushAxis("phi", 0., 1., 0., 0., Mantid::Geometry::CCW, Mantid::Geometry::angDegrees);
303}
304
309std::vector<double> Goniometer::getEulerAngles(const std::string &convention) {
310 return Quat(getR()).getEulerAngles(convention);
311}
312
318 if (m_motors.size() != 3) {
319 g_log.warning() << "Goniometerdoes not have 3 axes. Cannot determine "
320 "convention.\n";
321 return "";
322 }
323
324 std::string convention;
325 for (const auto &motor : m_motors) {
326 switch (std::abs(motor.rotationaxis.masterDir())) {
327 case 1:
328 convention += "X";
329 break;
330 case 2:
331 convention += "Y";
332 break;
333 case 3:
334 convention += "Z";
335 break;
336 default:
337 g_log.warning() << "Goniometer has an axis with a non-standard "
338 "rotation axis.\n";
339 return "";
340 }
341 }
342 return convention;
343}
344
347 if (m_initFromR) {
348 g_log.information() << "Goniometer was initialized from a rotation matrix. No "
349 << "recalculation from motors will be done.\n";
350 return;
351 }
352
353 std::vector<GoniometerAxis>::iterator it;
354 std::vector<double> elements;
355 Quat QGlobal, QCurrent;
356
357 for (it = m_motors.begin(); it < m_motors.end(); ++it) {
358 double ang = (*it).angle * (*it).sense;
359 if ((*it).angleunit == angRadians)
360 ang *= rad2deg;
361 QCurrent = Quat(ang, (*it).rotationaxis);
362 QGlobal *= QCurrent;
363 }
364 elements = QGlobal.getRotation();
365 m_R = DblMatrix(elements);
366}
367
368//--------------------------------------------------------------------------------------------
373void Goniometer::saveNexus(Nexus::File *file, const std::string &group) const {
374 file->makeGroup(group, "NXpositioner", true);
375 file->putAttr("version", 1);
376 // Because the order of the axes is very important, they have to be written
377 // and read out in the same order
378 file->writeData("num_axes", int(m_motors.size()));
379 for (size_t i = 0; i < m_motors.size(); i++)
380 m_motors[i].saveNexus(file, "axis" + Strings::toString(i));
381 DblMatrix rMatrix = getR();
382 // Flatten DblMatrix for saving
383 std::vector<double> matrixData = rMatrix.getVector();
384 file->writeData("rotation_matrix", matrixData);
385 file->closeGroup();
386}
387
388//--------------------------------------------------------------------------------------------
393void Goniometer::loadNexus(Nexus::File *file, const std::string &group) {
394 file->openGroup(group, "NXpositioner");
395 int num_axes;
396 file->readData("num_axes", num_axes);
397 m_motors.clear();
398 m_motors.reserve(num_axes);
399 // If a rotation matrix is available and no rotation axes are provided load should behave like initFromR
400 if (file->hasData("rotation_matrix") && (num_axes < 1)) {
401 std::vector<double> matrixData;
402 file->readData("rotation_matrix", matrixData);
403 DblMatrix rotMat(matrixData);
404 setR(std::move(rotMat));
405 m_initFromR = true; // set as initFromR to prevent overwrite on R recalc
406 } else {
407 for (int i = 0; i < num_axes; i++) {
408 GoniometerAxis newAxis;
409 newAxis.loadNexus(file, "axis" + Strings::toString(i));
410 m_motors.emplace_back(newAxis);
411 }
412 }
413 file->closeGroup();
414 // Refresh cached values
415 recalculateR();
416}
417
418} // namespace Mantid::Geometry
std::string name
Definition Run.cpp:60
double value
The value of the point.
Definition FitMW.cpp:51
double position
Definition GetAllEi.cpp:154
Class to represent a particular goniometer setting, which is described by the rotation matrix.
Definition Goniometer.h:55
Goniometer()
Default constructor The rotation matrix is initialized to identity.
void setRotationAngle(const std::string &name, double value)
Set rotation angle for an axis using motor name.
std::vector< double > getEulerAngles(const std::string &convention="YZX")
Return Euler angles acording to a convention.
void recalculateR()
Private function to recalculate R when setRotationAngle is called.
void makeUniversalGoniometer()
Make a default universal goniometer with phi,chi,omega angles according to SNS convention.
bool operator!=(const Goniometer &other) const
bool isDefined() const
the method reports if the goniometer was defined with some parameters
bool operator==(const Goniometer &other) const
void saveNexus(Nexus::File *file, const std::string &group) const
Save the object to an open NeXus file.
std::string getConventionFromMotorAxes() const
return the goniometer convention used determine from the motor axes
void pushAxis(const std::string &name, double axisx, double axisy, double axisz, double angle=0., int sense=CCW, int angUnit=angDegrees)
Add an additional axis to the goniometer, closer to the sample.
void setR(Kernel::DblMatrix rot)
Set the new rotation matrix.
bool m_initFromR
Flag to specify if the goniometer is initialized from a rotation matrix.
Definition Goniometer.h:118
std::vector< GoniometerAxis > m_motors
Motors vector contains GoniometerAxis objects, the last one is the closest to the sample.
Definition Goniometer.h:116
const Kernel::DblMatrix & getR() const
Return global rotation matrix.
void loadNexus(Nexus::File *file, const std::string &group)
Load the object from an open NeXus file.
Kernel::DblMatrix m_R
Global rotation matrix of the goniometer.
Definition Goniometer.h:113
std::string axesInfo()
Return information about axes.
Goniometer & operator=(const Goniometer &other)
Copy assigment constructor.
void calcFromQSampleAndWavelength(const Mantid::Kernel::V3D &Q, double wavelength, bool flip_x=false, bool inner=false)
Calculate goniometer for rotation around y-asix for constant wavelength from Q Sample.
const GoniometerAxis & getAxis(size_t axisnumber) const
Get GoniometerAxis obfject using motor number.
The Logger class is in charge of the publishing messages from the framework through various channels.
Definition Logger.h:51
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
T Invert()
LU inversion routine.
Definition Matrix.cpp:924
bool isRotation() const
Check if a matrix represents a proper rotation @ return :: true/false.
Definition Matrix.cpp:1422
std::vector< T > getVector() const
Definition Matrix.cpp:77
Class for quaternions.
Definition Quat.h:39
std::vector< double > getEulerAngles(const std::string &convention) const
Calculate the Euler angles that are equivalent to this Quaternion.
Definition Quat.cpp:729
std::vector< double > getRotation(bool check_normalisation=false, bool throw_on_errors=false) const
returns the rotation matrix defined by this quaternion as an 9-point
Definition Quat.cpp:453
Class for 3D vectors.
Definition V3D.h:34
void loadNexus(Nexus::File *file, const std::string &name)
Load the object from an open NeXus file.
Definition V3D.cpp:415
void saveNexus(Nexus::File *file, const std::string &name) const
Save the object to an open NeXus file.
Definition V3D.cpp:404
constexpr double rad2deg
Radians to degrees conversion factor.
Definition AngleUnits.h:23
@ CCW
Clockwise rotation.
Definition Goniometer.h:34
Mantid::Kernel::Logger g_log("Goniometer")
std::string toString(const T &value)
Convert a number to a string.
Definition Strings.cpp:734
Mantid::Kernel::Matrix< double > DblMatrix
Definition Matrix.h:206
Counter clockwise rotation.
Definition Goniometer.h:38
void loadNexus(Nexus::File *file, const std::string &group)
double angle
GoniometerAxis direction.
Definition Goniometer.h:41
int angleunit
Rotation sense (1 for CCW, -1 for CW)
Definition Goniometer.h:43
Kernel::V3D rotationaxis
GoniometerAxis name.
Definition Goniometer.h:40
void saveNexus(Nexus::File *file, const std::string &group) const