Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 55 additions & 11 deletions dx2/goniometer.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,52 @@ Goniometer::Goniometer(std::vector<Vector3d> axes, std::vector<double> angles,
init();
}

Goniometer::Goniometer(Matrix3d sample_rotation, Vector3d rotation_axis,
Matrix3d setting_rotation)
: sample_rotation_{sample_rotation}, rotation_axis_{rotation_axis},
setting_rotation_{setting_rotation} {}

Matrix3d Goniometer::get_setting_rotation() const { return setting_rotation_; }

Matrix3d Goniometer::get_sample_rotation() const { return sample_rotation_; }

Vector3d Goniometer::get_rotation_axis() const { return rotation_axis_; }

Goniometer::Goniometer(json goniometer_data) {
std::vector<std::string> required_keys = {"axes", "angles", "names",
"scan_axis"};
for (const auto &key : required_keys) {
// The goniometer data can either be single or multi axis form.
std::vector<std::string> multi_axis_keys = {"axes", "angles", "names",
"scan_axis"};
std::vector<std::string> single_axis_keys = {
"rotation_axis", "fixed_rotation", "setting_rotation"};

for (const auto &key : multi_axis_keys) {
if (goniometer_data.find(key) == goniometer_data.end()) {
throw std::invalid_argument("Key " + key +
" is missing from the input goniometer JSON");
// Could be a single axis gonio - they only provide rotation, fixed and
// setting
for (const auto &akey : single_axis_keys) {
if (goniometer_data.find(akey) == goniometer_data.end()) {
throw std::invalid_argument("Key " + key +
" is missing from the input goniometer "
"JSON - treating as single axis but" +
" key " + akey + " also missing.");
}
}
// We can create from the rotation axis data.
rotation_axis_ = Vector3d(goniometer_data["rotation_axis"][0],
goniometer_data["rotation_axis"][1],
goniometer_data["rotation_axis"][2]);
json setting = goniometer_data["setting_rotation"];
setting_rotation_ << setting[0], setting[1], setting[2], setting[3],
setting[4], setting[5], setting[6], setting[7], setting[8]; // F
json sample = goniometer_data["fixed_rotation"];
sample_rotation_ << sample[0], sample[1], sample[2], sample[3], sample[4],
sample[5], sample[6], sample[7], sample[8]; // S
return;
}
}
std::vector<Vector3d> axes;
std::vector<std::string> names;
std::vector<double> angles;
for (json::iterator it = goniometer_data["axes"].begin();
it != goniometer_data["axes"].end(); it++) {
Vector3d axis;
Expand All @@ -95,13 +125,11 @@ Goniometer::Goniometer(json goniometer_data) {
axes.push_back(axis);
}
axes_ = axes;
std::vector<double> angles;
for (json::iterator it = goniometer_data["angles"].begin();
it != goniometer_data["angles"].end(); it++) {
angles.push_back(*it);
}
angles_ = angles;
std::vector<std::string> names;
for (json::iterator it = goniometer_data["names"].begin();
it != goniometer_data["names"].end(); it++) {
names.push_back(*it);
Expand All @@ -113,9 +141,25 @@ Goniometer::Goniometer(json goniometer_data) {

json Goniometer::to_json() const {
json goniometer_data;
goniometer_data["axes"] = axes_;
goniometer_data["angles"] = angles_;
goniometer_data["names"] = names_;
goniometer_data["scan_axis"] = scan_axis_;
if (axes_.size() > 0) {
// Multi-axis format
goniometer_data["axes"] = axes_;
goniometer_data["angles"] = angles_;
goniometer_data["names"] = names_;
goniometer_data["scan_axis"] = scan_axis_;
} else {
// Single-axis format
goniometer_data["rotation_axis"] = rotation_axis_;
goniometer_data["fixed_rotation"] = std::vector<double>{
sample_rotation_(0, 0), sample_rotation_(0, 1), sample_rotation_(0, 2),
sample_rotation_(1, 0), sample_rotation_(1, 1), sample_rotation_(1, 2),
sample_rotation_(2, 0), sample_rotation_(2, 1), sample_rotation_(2, 2)};
goniometer_data["setting_rotation"] =
std::vector<double>{setting_rotation_(0, 0), setting_rotation_(0, 1),
setting_rotation_(0, 2), setting_rotation_(1, 0),
setting_rotation_(1, 1), setting_rotation_(1, 2),
setting_rotation_(2, 0), setting_rotation_(2, 1),
setting_rotation_(2, 2)};
}
return goniometer_data;
}
18 changes: 13 additions & 5 deletions include/dx2/goniometer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,33 @@ Matrix3d axis_and_angle_as_matrix(Vector3d axis, double angle,

class Goniometer {
// A class to represent a multi-axis goniometer.
// A single-axis goniometer just doesn't have axes and angles defined.
public:
Goniometer() = default;
Goniometer(std::vector<Vector3d> axes, std::vector<double> angles,
std::vector<std::string> names, std::size_t scan_axis);
Goniometer(json goniometer_data);
Goniometer(Matrix3d sample_rotation, Vector3d axis,
Matrix3d setting_rotation);
Matrix3d get_setting_rotation() const;
Matrix3d get_sample_rotation() const;
Vector3d get_rotation_axis() const;
json to_json() const;

protected:
void init(); // Sets the matrices from the axes and angles
void init(); // Sets the matrices from the axes and angles for multi-axis
// goniometers.
// These two functions calculate F and S for multi-axis goniometers.
Matrix3d calculate_setting_rotation();
Matrix3d calculate_sample_rotation();
// The core 2 matrices and rotation axis that define a goniometer.
Matrix3d sample_rotation_{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; // F
Vector3d rotation_axis_{{1.0, 0.0, 0.0}}; // R'
Matrix3d setting_rotation_{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; // S
std::vector<Vector3d> axes_{{1.0, 0.0, 0.0}};
std::vector<double> angles_{{0.0}};
std::vector<std::string> names_{"omega"};
std::size_t scan_axis_{0};
// The next three quantities do not get non-empty defaults - only needed
// to hold additional information for multi-axis goniometers.
std::vector<Vector3d> axes_{};
std::vector<double> angles_{};
std::vector<std::string> names_{};
std::size_t scan_axis_ = 0;
};
4 changes: 4 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ target_link_libraries(test_reflection_table GTest::gtest_main dx2)
add_executable(test_detector_attenuations test_detector_attenuations.cxx)
target_link_libraries(test_detector_attenuations GTest::gtest_main dx2)

add_executable(test_goniometer test_goniometer.cxx)
target_link_libraries(test_goniometer GTest::gtest_main dx2 nlohmann_json::nlohmann_json)

include(GoogleTest)

gtest_discover_tests(test_crystal PROPERTIES LABELS dx2tests)
gtest_discover_tests(test_read_h5_array WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests" PROPERTIES LABELS dx2tests)
gtest_discover_tests(test_write_h5_array WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests" PROPERTIES LABELS dx2tests)
gtest_discover_tests(test_reflection_table WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests" PROPERTIES LABELS dx2tests)
gtest_discover_tests(test_detector_attenuations WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests" PROPERTIES LABELS dx2tests)
gtest_discover_tests(test_goniometer WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tests" PROPERTIES LABELS dx2tests)
76 changes: 76 additions & 0 deletions tests/test_goniometer.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@

#include <dx2/goniometer.hpp>
#include <gtest/gtest.h>
#include <nlohmann/json.hpp>

TEST(ModelTests, SingleAxisGoniometerTest) {
// Test loading single axis goniometer from json.
// Note the fixed rotation is not strictly valid, but fine for testing
// serialization.
std::string json_str = R"({
"goniometer": [
{
"rotation_axis": [1.0, 0.0, 0.0],
"fixed_rotation": [0.99, 0.01, 0.0, -0.01, 0.99, 0.0, 0.0, 0.0, 1.0],
"setting_rotation": [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]
}
]
})";
json j = json::parse(json_str);
auto goniometer_data = j["goniometer"][0];
Goniometer gonio(goniometer_data);
Matrix3d setting = gonio.get_setting_rotation();
Matrix3d sample = gonio.get_sample_rotation();
Matrix3d expected_setting{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
Matrix3d expected_sample{
{0.99, 0.01, 0.0}, {-0.01, 0.99, 0.0}, {0.00, 0.00, 1.0}};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
EXPECT_NEAR(setting(i, j), expected_setting(i, j), 1E-6);
EXPECT_NEAR(sample(i, j), expected_sample(i, j), 1E-6);
}
}
json output = gonio.to_json();
std::vector<double> expected_fixed = {0.99, 0.01, 0.0, -0.01, 0.99,
0.0, 0.0, 0.0, 1.0};
for (int i = 0; i < 9; ++i) {
ASSERT_EQ(output["fixed_rotation"][i], expected_fixed[i]);
}
}

TEST(ModelTests, MultiAxisGoniometerTest) {
// Test loading multi axis goniometer from json.
std::string json_str = R"({
"goniometer": [
{
"axes": [
[1.0, -0.0025, 0.0056],
[-0.006, -0.0264, -0.9996],
[1.0, 0.0, 0.0]
],
"angles": [0.0, 5.0, 0.0],
"names": ["phi", "chi", "omega"],
"scan_axis": 2
}
]
})";
json j = json::parse(json_str);
auto goniometer_data = j["goniometer"][0];
Goniometer gonio(goniometer_data);
Matrix3d setting = gonio.get_setting_rotation();
Matrix3d sample = gonio.get_sample_rotation();
Matrix3d expected_setting{{1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}};
Matrix3d expected_sample{{0.996195, 0.0871244, -0.00227816},
{-0.0871232, 0.996197, 0.000623378},
{0.00232381, -0.000422525, 0.999997}};
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
EXPECT_NEAR(setting(i, j), expected_setting(i, j), 1E-6);
EXPECT_NEAR(sample(i, j), expected_sample(i, j), 1E-6);
}
}
json output = gonio.to_json();
ASSERT_EQ(output["angles"][0], 0.0);
ASSERT_EQ(output["angles"][1], 5.0);
ASSERT_EQ(output["angles"][0], 0.0);
}
Loading