diff --git a/include/dx2/beam.h b/include/dx2/beam.h index c855176..e088a1b 100644 --- a/include/dx2/beam.h +++ b/include/dx2/beam.h @@ -1,8 +1,8 @@ #ifndef DX2_MODEL_BEAM_H #define DX2_MODEL_BEAM_H #include +#include #include - using Eigen::Vector3d; using json = nlohmann::json; @@ -14,22 +14,25 @@ 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. */ +enum Probe { xray = 1, electron = 2, neutron = 3 }; + class BeamBase { // A base class for beam objects public: + virtual ~BeamBase() = default; 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); + double flux, double transmission, Probe probe, + double sample_to_source_distance); + Probe get_probe() const; + std::string get_probe_name() const; protected: void init_from_json(json beam_data); - void add_to_json(json beam_data) const; + 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?" @@ -38,6 +41,7 @@ class BeamBase { double polarization_fraction_{0.999}; double flux_{0.0}; double transmission_{1.0}; + Probe probe_{xray}; double sample_to_source_distance_{0.0}; // FIXME is this really needed? }; @@ -50,9 +54,10 @@ class MonochromaticBeam : public BeamBase { 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); + double transmission, Probe probe, + double sample_to_source_distance); MonochromaticBeam(json beam_data); - json to_json(std::string probe) const; + json to_json() const; double get_wavelength() const; void set_wavelength(double wavelength); Vector3d get_s0() const; @@ -62,24 +67,6 @@ class MonochromaticBeam : public BeamBase { 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: @@ -89,10 +76,10 @@ class PolychromaticBeam : public BeamBase { PolychromaticBeam(std::array wavelength_range, Vector3d direction, double divergence, double sigma_divergence, Vector3d polarization_normal, double polarization_fraction, - double flux, double transmission, + double flux, double transmission, Probe probe, double sample_to_source_distance); PolychromaticBeam(json beam_data); - json to_json(std::string probe) const; + json to_json() const; std::array get_wavelength_range() const; void set_wavelength_range(std::array wavelength_range); @@ -106,12 +93,13 @@ class PolychromaticBeam : public BeamBase { BeamBase::BeamBase(Vector3d direction, double divergence, double sigma_divergence, Vector3d polarization_normal, double polarization_fraction, double flux, - double transmission, double sample_to_source_distance) + double transmission, Probe probe, + 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}, + transmission_{transmission}, probe_{probe}, sample_to_source_distance_{sample_to_source_distance} {} void BeamBase::init_from_json(json beam_data) { @@ -144,12 +132,22 @@ void BeamBase::init_from_json(json beam_data) { if (beam_data.find("transmission") != beam_data.end()) { transmission_ = beam_data["transmission"]; } + if (beam_data.find("probe") != beam_data.end()) { + std::string probe = beam_data["probe"]; + if (probe == "x-ray") { + probe_ = Probe::xray; + } else if (probe == "neutron") { + probe_ = Probe::neutron; + } else if (probe == "electron") { + probe_ = Probe::electron; + } + } 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 { +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_; @@ -158,9 +156,27 @@ void BeamBase::add_to_json(json beam_data) const { beam_data["polarization_fraction"] = polarization_fraction_; beam_data["flux"] = flux_; beam_data["transmission"] = transmission_; + beam_data["probe"] = "x-ray"; // FIXME get_probe_name() not working? beam_data["sample_to_source_distance"] = sample_to_source_distance_; } +Probe BeamBase::get_probe() const { return probe_; } + +std::string BeamBase::get_probe_name() const { + // Return a name that matches NeXus definitions from + // https://manual.nexusformat.org/classes/base_classes/NXsource.html + switch (probe_) { + case xray: + return std::string("x-ray"); + case electron: + return std::string("electron"); + case neutron: + return std::string("neutron"); + default: + throw std::runtime_error("Unknown probe type"); + } +} + // MonochromaticBeam definitions // full constructor @@ -168,7 +184,7 @@ MonochromaticBeam::MonochromaticBeam(double wavelength, Vector3d direction, double divergence, double sigma_divergence, Vector3d polarization_normal, double polarization_fraction, double flux, - double transmission, + double transmission, Probe probe, double sample_to_source_distance) : BeamBase{direction, divergence, @@ -177,6 +193,7 @@ MonochromaticBeam::MonochromaticBeam(double wavelength, Vector3d direction, polarization_fraction, flux, transmission, + probe, sample_to_source_distance}, wavelength_{wavelength} {} @@ -204,9 +221,9 @@ MonochromaticBeam::MonochromaticBeam(json beam_data) { } // serialize to json format -json MonochromaticBeam::to_json(std::string probe = "x-ray") const { +json MonochromaticBeam::to_json() const { // create a json object that conforms to a dials model serialization. - json beam_data = {{"__id__", "monochromatic"}, {"probe", probe}}; + json beam_data = {{"__id__", "monochromatic"}}; beam_data["wavelength"] = wavelength_; add_to_json(beam_data); return beam_data; @@ -234,7 +251,7 @@ PolychromaticBeam::PolychromaticBeam(std::array wavelength_range, double sigma_divergence, Vector3d polarization_normal, double polarization_fraction, double flux, - double transmission, + double transmission, Probe probe, double sample_to_source_distance) : BeamBase{direction, divergence, @@ -243,6 +260,7 @@ PolychromaticBeam::PolychromaticBeam(std::array wavelength_range, polarization_fraction, flux, transmission, + probe, sample_to_source_distance}, wavelength_range_{wavelength_range} {} @@ -270,9 +288,9 @@ PolychromaticBeam::PolychromaticBeam(json beam_data) { } // serialize to json format -json PolychromaticBeam::to_json(std::string probe = "x-ray") const { +json PolychromaticBeam::to_json() const { // create a json object that conforms to a dials model serialization. - json beam_data = {{"__id__", "polychromatic"}, {"probe", probe}}; + json beam_data = {{"__id__", "polychromatic"}}; beam_data["wavelength_range"] = wavelength_range_; add_to_json(beam_data); return beam_data; @@ -286,19 +304,33 @@ void PolychromaticBeam::set_wavelength_range( wavelength_range_ = wavelength_range; } -// MonoXrayBeam definitions +// Functions to handle run-time polymorphism for json +// serialization/deserialization. -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"); +std::shared_ptr make_beam(json beam_data) { + if (beam_data.find("wavelength") != beam_data.end()) { + return std::make_shared(beam_data); + } else if (beam_data.find("wavelength_range") != beam_data.end()) { + return std::make_shared(beam_data); + } else { + throw std::runtime_error("Unrecognised beam type in json data"); + } } -// MonoElectronBeam definitions - -json MonoElectronBeam::to_json() const { - // call the parent function with the correct probe name - return MonochromaticBeam::to_json("electron"); +json make_beam_json(std::shared_ptr beamptr) { + // first try to cast to mono + MonochromaticBeam *monobeam = + dynamic_cast(beamptr.get()); + if (monobeam != nullptr) { + return (*monobeam).to_json(); + } + PolychromaticBeam *polybeam = + dynamic_cast(beamptr.get()); + if (polybeam != nullptr) { + return (*polybeam).to_json(); + } + throw std::runtime_error( + "Unable to cast base beam pointer for json creation"); } #endif // DX2_MODEL_BEAM_H diff --git a/include/dx2/experiment.h b/include/dx2/experiment.h index ceb70db..b9a76da 100644 --- a/include/dx2/experiment.h +++ b/include/dx2/experiment.h @@ -6,35 +6,44 @@ #include #include #include +#include #include - using Eigen::Vector3d; using json = nlohmann::json; -template class Experiment { +class Experiment { public: Experiment() = default; Experiment(json experiment_data); + Experiment(std::shared_ptr beam, Scan scan, Goniometer gonio, + Detector detector); json to_json() const; Goniometer goniometer() const; - BeamType beam() const; + std::shared_ptr beam() const; Scan scan() const; Detector detector() const; Crystal crystal() const; void set_crystal(Crystal crystal); protected: - BeamType _beam{}; + std::shared_ptr _beam{}; Scan _scan{}; Goniometer _goniometer{}; Detector _detector{}; Crystal _crystal{}; }; -template -Experiment::Experiment(json experiment_data) { +Experiment::Experiment(std::shared_ptr beam, Scan scan, + Goniometer gonio, Detector detector) { + this->_beam = beam; + this->_scan = scan; + this->_goniometer = gonio; + this->_detector = detector; +} + +Experiment::Experiment(json experiment_data) { json beam_data = experiment_data["beam"][0]; - BeamType beam = BeamType(beam_data); + std::shared_ptr beam = make_beam(beam_data); json scan_data = experiment_data["scan"][0]; Scan scan(scan_data); json gonio_data = experiment_data["goniometer"][0]; @@ -54,7 +63,7 @@ Experiment::Experiment(json experiment_data) { } } -template json Experiment::to_json() const { +json Experiment::to_json() const { // save this experiment as an example experiment list json elist_out; // a list of potentially multiple experiments elist_out["__id__"] = "ExperimentList"; @@ -71,7 +80,7 @@ template json Experiment::to_json() const { // add the the actual models elist_out["scan"] = std::array{_scan.to_json()}; elist_out["goniometer"] = std::array{_goniometer.to_json()}; - elist_out["beam"] = std::array{_beam.to_json()}; + elist_out["beam"] = std::array{make_beam_json(_beam)}; elist_out["detector"] = std::array{_detector.to_json()}; if (_crystal.get_U_matrix().determinant()) { @@ -85,29 +94,123 @@ template json Experiment::to_json() const { return elist_out; } -template Scan Experiment::scan() const { +std::shared_ptr Experiment::beam() const { return _beam; } + +Scan Experiment::scan() const { return _scan; } + +Goniometer Experiment::goniometer() const { return _goniometer; } + +Detector Experiment::detector() const { return _detector; } + +Crystal Experiment::crystal() const { return _crystal; } + +void Experiment::set_crystal(Crystal crystal) { _crystal = crystal; } + +template class TypedExperiment { +public: + TypedExperiment(BeamType &beam, Scan scan, Goniometer gonio, + Detector detector); + BeamType &get_beam() const; + Scan scan() const; + Detector detector() const; + Goniometer goniometer() const; + Crystal crystal() const; + void set_crystal(Crystal crystal); + json to_json() const; + +private: + BeamType &_beam; + Scan _scan; + Goniometer _goniometer; + Detector _detector; + Crystal _crystal; +}; + +template +TypedExperiment::TypedExperiment(BeamType &beam, Scan scan, + Goniometer gonio, Detector detector) + : _beam(beam), _scan(scan), _goniometer(gonio), _detector(detector) {} + +template +BeamType &TypedExperiment::get_beam() const { + return _beam; +} + +template Scan TypedExperiment::scan() const { return _scan; } -template Goniometer Experiment::goniometer() const { +template +Goniometer TypedExperiment::goniometer() const { return _goniometer; } -template Detector Experiment::detector() const { +template +Detector TypedExperiment::detector() const { return _detector; } -template Crystal Experiment::crystal() const { +template +Crystal TypedExperiment::crystal() const { return _crystal; } -template -void Experiment::set_crystal(Crystal crystal) { +template +void TypedExperiment::set_crystal(Crystal crystal) { _crystal = crystal; } -template BeamType Experiment::beam() const { - return _beam; +template json TypedExperiment::to_json() const { + // save this experiment as an example experiment list + json elist_out; // a list of potentially multiple experiments + elist_out["__id__"] = "ExperimentList"; + json expt_out; // our single experiment + // no imageset (for now?). + expt_out["__id__"] = "Experiment"; + expt_out["identifier"] = "test"; + expt_out["beam"] = + 0; // the indices of the models that will correspond to our experiment + expt_out["detector"] = 0; + expt_out["goniometer"] = 0; + expt_out["scan"] = 0; + + // add the the actual models + elist_out["scan"] = std::array{_scan.to_json()}; + elist_out["goniometer"] = std::array{_goniometer.to_json()}; + elist_out["beam"] = std::array{_beam.to_json()}; + elist_out["detector"] = std::array{_detector.to_json()}; + + if (_crystal.get_U_matrix().determinant()) { + expt_out["crystal"] = 0; + elist_out["crystal"] = std::array{_crystal.to_json()}; + } else { + elist_out["crystal"] = std::array{}; + } + + elist_out["experiment"] = std::array{expt_out}; + return elist_out; +} + +// factory function to make a typed experiment +template +TypedExperiment make_typed_experiment( + json experiment_data) { // can add other types e.g. detector, scan + json beam_data = experiment_data["beam"][0]; + std::shared_ptr beamptr = + make_beam(beam_data); // may raise std::runtime_error + + // Make sure we can cast to the requested types. + BeamType *converted = dynamic_cast(beamptr.get()); + if (converted == nullptr) { + throw std::runtime_error("Unable to make requested beam type"); + } + json scan_data = experiment_data["scan"][0]; + Scan scan(scan_data); + json gonio_data = experiment_data["goniometer"][0]; + Goniometer gonio(gonio_data); + json detector_data = experiment_data["detector"][0]; + Detector detector(detector_data); + return TypedExperiment(*converted, scan, gonio, detector); } #endif // DX2_MODEL_EXPERIMENT_H \ No newline at end of file