Program Listing for File goniometer.cxx

↰ Return to documentation for file (dx2/goniometer.cxx)

#include "dx2/goniometer.hpp"
#include <math.h>

Matrix3d axis_and_angle_as_matrix(Vector3d axis, double angle, bool deg) {
  double q0 = 0.0;
  double q1 = 0.0;
  double q2 = 0.0;
  double q3 = 0.0;
  if (deg) {
    angle *= M_PI / 180.0;
  }
  if (!(std::fmod(angle, 2.0 * M_PI))) {
    q0 = 1.0;
  } else {
    double h = 0.5 * angle;
    q0 = std::cos(h);
    double s = std::sin(h);
    Vector3d n = axis / axis.norm();
    q1 = n[0] * s;
    q2 = n[1] * s;
    q3 = n[2] * s;
  }
  Matrix3d m{{2 * (q0 * q0 + q1 * q1) - 1, 2 * (q1 * q2 - q0 * q3),
              2 * (q1 * q3 + q0 * q2)},
             {2 * (q1 * q2 + q0 * q3), 2 * (q0 * q0 + q2 * q2) - 1,
              2 * (q2 * q3 - q0 * q1)},
             {2 * (q1 * q3 - q0 * q2), 2 * (q2 * q3 + q0 * q1),
              2 * (q0 * q0 + q3 * q3) - 1}};
  return m;
}

void Goniometer::init() {
  // Sets the matrices from the axes and angles
  setting_rotation_ = calculate_setting_rotation();
  sample_rotation_ = calculate_sample_rotation();
  rotation_axis_ = axes_[scan_axis_];
}

Matrix3d Goniometer::calculate_setting_rotation() {
  Matrix3d setting_rotation{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
  for (std::size_t i = scan_axis_ + 1; i < axes_.size(); i++) {
    Matrix3d R = axis_and_angle_as_matrix(axes_[i], angles_[i], true);
    setting_rotation = R * setting_rotation;
  }
  return setting_rotation;
}

Matrix3d Goniometer::calculate_sample_rotation() {
  Matrix3d sample_rotation{{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
  for (std::size_t i = 0; i < scan_axis_; i++) {
    Matrix3d R = axis_and_angle_as_matrix(axes_[i], angles_[i], true);
    sample_rotation = R * sample_rotation;
  }
  return sample_rotation;
}

Goniometer::Goniometer(std::vector<Vector3d> axes, std::vector<double> angles,
                       std::vector<std::string> names, std::size_t scan_axis)
    : axes_{axes.begin(), axes.end()}, angles_{angles.begin(), angles.end()},
      names_{names.begin(), names.end()}, scan_axis_{scan_axis} {
  if (scan_axis_ >= axes_.size()) {
    throw std::invalid_argument(
        "Goniometer scan axis number is out of range of axis length");
  }
  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) {
  // 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()) {
      // 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;
    axis[0] = (*it)[0];
    axis[1] = (*it)[1];
    axis[2] = (*it)[2];
    axes.push_back(axis);
  }
  axes_ = axes;
  for (json::iterator it = goniometer_data["angles"].begin();
       it != goniometer_data["angles"].end(); it++) {
    angles.push_back(*it);
  }
  angles_ = angles;
  for (json::iterator it = goniometer_data["names"].begin();
       it != goniometer_data["names"].end(); it++) {
    names.push_back(*it);
  }
  names_ = names;
  scan_axis_ = goniometer_data["scan_axis"];
  init();
}

json Goniometer::to_json() const {
  json goniometer_data;
  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;
}