Skip to content

Commit

Permalink
Add some example models (#1)
Browse files Browse the repository at this point in the history
Add some initial models to get started. Beam and goniometer are more complete,
scan, detector and crystal less so.

Adds a dependency on gemmi for crystal symmetry and cell operations.

Add an example test creating a crystal to test the install.
  • Loading branch information
jbeilstenedmands authored and ndevenish committed Jan 20, 2025
1 parent ccbc741 commit 2654929
Show file tree
Hide file tree
Showing 18 changed files with 1,126 additions and 10 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
on: [push, pull_request]
jobs:
build_test:
name: Build/Test
strategy:
matrix:
os: [ubuntu, macOS]
defaults:
run:
shell: bash -l {0}
runs-on: ${{ matrix.os }}-latest
steps:
- uses: actions/checkout@v4
- uses: conda-incubator/setup-miniconda@v3
with:
channels: conda-forge
- name: Install dependencies
run: |
conda env update --file requirements.txt --name test
conda list
- name: Build
run: mkdir build && cd build && cmake .. && make
- name: Run tests
run: cd build && ctest -L dx2tests --output-on-failure
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# #######################################################################
# External Dependencies
find_package(HDF5 REQUIRED)
find_package(gemmi CONFIG REQUIRED)

# #######################################################################
# Automatic Dependencies
Expand Down Expand Up @@ -107,4 +108,4 @@ install(
)
export(EXPORT DX2Targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/DX2Targets.cmake"
)
)
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

## Quick Start

- Ensure you have an environment with CMake and compilers available
- Ensure you have an environment with CMake and compilers available (see requirements.txt)
- Dependencies:
- HDF5 (Required)
- gemmi (Required)
- [Eigen](https://eigen.tuxfamily.org/) (Optional, downloaded if missing)
- [nlohmann/json](https://github.com/nlohmann/json) (Optional, downloaded if missing)
- [{fmt}](https://github.com/fmtlib/fmt) (Optional, downloaded if missing)
Expand All @@ -15,11 +16,11 @@ mkdir build
cd build
cmake ..
```
You can then build with `make`, and run the tests with `make test`.
You can then build with `make`, and run the test with `./tests/test_make_models`.

## Writing And Running Tests

See https://google.github.io/googletest/primer.html. The `tests/` subdirectory
See https://google.github.io/googletest/primer.html. The `tests` subdirectory
has an example test.

You can run test executables directly, run `ctest`, or run `make test` (or
Expand Down
2 changes: 2 additions & 0 deletions dx2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ target_link_libraries(
Eigen3::Eigen
nlohmann_json::nlohmann_json
hdf5::hdf5
gemmi::gemmi_cpp
)

target_include_directories(
dx2 PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
target_link_libraries(dx2 PUBLIC $<TARGET_NAME_IF_EXISTS:hdf5::hdf5>)
304 changes: 304 additions & 0 deletions include/dx2/beam.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
#ifndef DX2_MODEL_BEAM_H
#define DX2_MODEL_BEAM_H
#include <Eigen/Dense>
#include <nlohmann/json.hpp>

using Eigen::Vector3d;
using json = nlohmann::json;

/*
This file defines a set of Beam classes.
BeamBase defines most of the class attributes except the wavelength.
The two subclasses are MonochromaticBeam and PolychromaticBeam,
which define a single wavelength or wavelength range respectively.
MonochromaticBeam is subclassed further into MonoXrayBeam and
MonoElectronBeam, these simply set the correct probe name
when serializing/deserializing to/from json.
*/

class BeamBase {
// A base class for beam objects
public:
BeamBase() = default;
BeamBase(Vector3d direction, double divergence, double sigma_divergence,
Vector3d polarization_normal, double polarization_fraction,
double flux, double transmission, double sample_to_source_distance);

protected:
void init_from_json(json beam_data);
void add_to_json(json beam_data) const;
Vector3d sample_to_source_direction_{0.0, 0.0,
1.0}; // called direction_ in dxtbx
double divergence_{0.0}; // "beam divergence - be more specific with name?"
double sigma_divergence_{0.0}; // standard deviation of the beam divergence
Vector3d polarization_normal_{0.0, 1.0, 0.0};
double polarization_fraction_{0.999};
double flux_{0.0};
double transmission_{1.0};
double sample_to_source_distance_{0.0}; // FIXME is this really needed?
};

class MonochromaticBeam : public BeamBase {
// A monochromatic beam (i.e. single wavelength value)
public:
MonochromaticBeam() = default;
MonochromaticBeam(double wavelength);
MonochromaticBeam(Vector3d s0);
MonochromaticBeam(double wavelength, Vector3d direction, double divergence,
double sigma_divergence, Vector3d polarization_normal,
double polarization_fraction, double flux,
double transmission, double sample_to_source_distance);
MonochromaticBeam(json beam_data);
json to_json(std::string probe) const;
double get_wavelength() const;
void set_wavelength(double wavelength);
Vector3d get_s0() const;
void set_s0(Vector3d s0);

protected:
double wavelength_{0.0};
};

class MonoXrayBeam : public MonochromaticBeam {
// Same as the parent class, except explicitly set a probe type when calling
// to_json
using MonochromaticBeam::MonochromaticBeam;

public:
json to_json() const;
};

class MonoElectronBeam : public MonochromaticBeam {
// Same as the parent class, except explicitly set a probe type when calling
// to_json
using MonochromaticBeam::MonochromaticBeam;

public:
json to_json() const;
};

class PolychromaticBeam : public BeamBase {
// A polychromatic beam (i.e. a wavelength range)
public:
PolychromaticBeam() = default;
PolychromaticBeam(std::array<double, 2> wavelength_range);
PolychromaticBeam(std::array<double, 2> wavelength_range, Vector3d direction);
PolychromaticBeam(std::array<double, 2> wavelength_range, Vector3d direction,
double divergence, double sigma_divergence,
Vector3d polarization_normal, double polarization_fraction,
double flux, double transmission,
double sample_to_source_distance);
PolychromaticBeam(json beam_data);
json to_json(std::string probe) const;
std::array<double, 2> get_wavelength_range() const;
void set_wavelength_range(std::array<double, 2> wavelength_range);

protected:
std::array<double, 2> wavelength_range_{{0.0, 0.0}};
};

// BeamBase definitions

// full constructor
BeamBase::BeamBase(Vector3d direction, double divergence,
double sigma_divergence, Vector3d polarization_normal,
double polarization_fraction, double flux,
double transmission, double sample_to_source_distance)
: sample_to_source_direction_{direction}, divergence_{divergence},
sigma_divergence_{sigma_divergence},
polarization_normal_{polarization_normal},
polarization_fraction_{polarization_fraction}, flux_{flux},
transmission_{transmission},
sample_to_source_distance_{sample_to_source_distance} {}

void BeamBase::init_from_json(json beam_data) {
// Load values from a json object.
// Allow these to not be present so we can load a simple json dict without all
// these items.
if (beam_data.find("direction") != beam_data.end()) {
Vector3d direction{{beam_data["direction"][0], beam_data["direction"][1],
beam_data["direction"][2]}};
sample_to_source_direction_ = direction;
}
if (beam_data.find("divergence") != beam_data.end()) {
divergence_ = beam_data["divergence"];
}
if (beam_data.find("sigma_divergence") != beam_data.end()) {
sigma_divergence_ = beam_data["sigma_divergence"];
}
if (beam_data.find("polarization_normal") != beam_data.end()) {
Vector3d pn{{beam_data["polarization_normal"][0],
beam_data["polarization_normal"][1],
beam_data["polarization_normal"][2]}};
polarization_normal_ = pn;
}
if (beam_data.find("polarization_fraction") != beam_data.end()) {
polarization_fraction_ = beam_data["polarization_fraction"];
}
if (beam_data.find("flux") != beam_data.end()) {
flux_ = beam_data["flux"];
}
if (beam_data.find("transmission") != beam_data.end()) {
transmission_ = beam_data["transmission"];
}
if (beam_data.find("sample_to_source_distance") != beam_data.end()) {
sample_to_source_distance_ = beam_data["sample_to_source_distance"];
}
}

void BeamBase::add_to_json(json beam_data) const {
// Add the members to the json object to prepare for serialization.
beam_data["direction"] = sample_to_source_direction_;
beam_data["divergence"] = divergence_;
beam_data["sigma_divergence"] = sigma_divergence_;
beam_data["polarization_normal"] = polarization_normal_;
beam_data["polarization_fraction"] = polarization_fraction_;
beam_data["flux"] = flux_;
beam_data["transmission"] = transmission_;
beam_data["sample_to_source_distance"] = sample_to_source_distance_;
}

// MonochromaticBeam definitions

// full constructor
MonochromaticBeam::MonochromaticBeam(double wavelength, Vector3d direction,
double divergence, double sigma_divergence,
Vector3d polarization_normal,
double polarization_fraction, double flux,
double transmission,
double sample_to_source_distance)
: BeamBase{direction,
divergence,
sigma_divergence,
polarization_normal,
polarization_fraction,
flux,
transmission,
sample_to_source_distance},
wavelength_{wavelength} {}

MonochromaticBeam::MonochromaticBeam(double wavelength)
: wavelength_{wavelength} {}

MonochromaticBeam::MonochromaticBeam(Vector3d s0) {
double len = s0.norm();
wavelength_ = 1.0 / len;
sample_to_source_direction_ = -1.0 * s0 / len;
}

// constructor from json data
MonochromaticBeam::MonochromaticBeam(json beam_data) {
// minimal required keys
std::vector<std::string> required_keys = {"wavelength"};
for (const auto &key : required_keys) {
if (beam_data.find(key) == beam_data.end()) {
throw std::invalid_argument("Key " + key +
" is missing from the input beam JSON");
}
}
wavelength_ = beam_data["wavelength"];
init_from_json(beam_data);
}

// serialize to json format
json MonochromaticBeam::to_json(std::string probe = "x-ray") const {
// create a json object that conforms to a dials model serialization.
json beam_data = {{"__id__", "monochromatic"}, {"probe", probe}};
beam_data["wavelength"] = wavelength_;
add_to_json(beam_data);
return beam_data;
}

double MonochromaticBeam::get_wavelength() const { return wavelength_; }
void MonochromaticBeam::set_wavelength(double wavelength) {
wavelength_ = wavelength;
}

Vector3d MonochromaticBeam::get_s0() const {
return -sample_to_source_direction_ / wavelength_;
}
void MonochromaticBeam::set_s0(Vector3d s0) {
double len = s0.norm();
wavelength_ = 1.0 / len;
sample_to_source_direction_ = -1.0 * s0 / len;
}

// PolychromaticBeam definitions

// full constructor
PolychromaticBeam::PolychromaticBeam(std::array<double, 2> wavelength_range,
Vector3d direction, double divergence,
double sigma_divergence,
Vector3d polarization_normal,
double polarization_fraction, double flux,
double transmission,
double sample_to_source_distance)
: BeamBase{direction,
divergence,
sigma_divergence,
polarization_normal,
polarization_fraction,
flux,
transmission,
sample_to_source_distance},
wavelength_range_{wavelength_range} {}

PolychromaticBeam::PolychromaticBeam(std::array<double, 2> wavelength_range)
: wavelength_range_{wavelength_range} {}

PolychromaticBeam::PolychromaticBeam(std::array<double, 2> wavelength_range,
Vector3d direction)
: wavelength_range_{wavelength_range} {
sample_to_source_direction_ = direction / direction.norm();
}

// constructor from json data
PolychromaticBeam::PolychromaticBeam(json beam_data) {
// minimal required keys
std::vector<std::string> required_keys = {"wavelength_range"};
for (const auto &key : required_keys) {
if (beam_data.find(key) == beam_data.end()) {
throw std::invalid_argument("Key " + key +
" is missing from the input beam JSON");
}
}
wavelength_range_ = beam_data["wavelength_range"];
init_from_json(beam_data);
}

// serialize to json format
json PolychromaticBeam::to_json(std::string probe = "x-ray") const {
// create a json object that conforms to a dials model serialization.
json beam_data = {{"__id__", "polychromatic"}, {"probe", probe}};
beam_data["wavelength_range"] = wavelength_range_;
add_to_json(beam_data);
return beam_data;
}

std::array<double, 2> PolychromaticBeam::get_wavelength_range() const {
return wavelength_range_;
}
void PolychromaticBeam::set_wavelength_range(
std::array<double, 2> wavelength_range) {
wavelength_range_ = wavelength_range;
}

// MonoXrayBeam definitions

json MonoXrayBeam::to_json() const {
// call the parent function with the correct probe name (which is the same as
// the default in this case)
return MonochromaticBeam::to_json("x-ray");
}

// MonoElectronBeam definitions

json MonoElectronBeam::to_json() const {
// call the parent function with the correct probe name
return MonochromaticBeam::to_json("electron");
}

#endif // DX2_MODEL_BEAM_H
Loading

0 comments on commit 2654929

Please sign in to comment.