diff --git a/Framework/DataHandling/inc/MantidDataHandling/SetSample.h b/Framework/DataHandling/inc/MantidDataHandling/SetSample.h index cafbf459fffb8ffec00a93504c4229b9f4135918..6769bd955b1d072b382907e536ddbc38b0c71856 100644 --- a/Framework/DataHandling/inc/MantidDataHandling/SetSample.h +++ b/Framework/DataHandling/inc/MantidDataHandling/SetSample.h @@ -7,6 +7,7 @@ namespace Mantid { namespace Geometry { +class ReferenceFrame; class SampleEnvironment; } namespace DataHandling { @@ -49,13 +50,18 @@ private: const Geometry::SampleEnvironment * setSampleEnvironment(API::MatrixWorkspace_sptr &workspace, - const Kernel::PropertyManager &args); + const Kernel::PropertyManager_const_sptr &args); void setSampleShape(API::MatrixWorkspace_sptr &workspace, - const Kernel::PropertyManager_sptr &args, + const Kernel::PropertyManager_const_sptr &args, const Geometry::SampleEnvironment *sampleEnv); - std::string tryCreateXMLFromArgsOnly(const Kernel::PropertyManager_sptr args); - std::string createFlatPlateXML(const Kernel::PropertyManager &args) const; + std::string + tryCreateXMLFromArgsOnly(const Kernel::PropertyManager &args, + const Geometry::ReferenceFrame &refFrame); + std::string + createFlatPlateXML(const Kernel::PropertyManager &args, + const Geometry::ReferenceFrame &refFrame) const; std::string createCylinderLikeXML(const Kernel::PropertyManager &args, + const Geometry::ReferenceFrame &refFrame, bool hollow) const; void runSetSampleShape(API::MatrixWorkspace_sptr &workspace, diff --git a/Framework/DataHandling/src/SetSample.cpp b/Framework/DataHandling/src/SetSample.cpp index 3fc2e9c80c9b9c4312c887bae87be1ed96514e20..38e268798773c3250b6504c01d4ab888e363ef86 100644 --- a/Framework/DataHandling/src/SetSample.cpp +++ b/Framework/DataHandling/src/SetSample.cpp @@ -3,12 +3,15 @@ #include "MantidAPI/MatrixWorkspace.h" #include "MantidAPI/Sample.h" #include "MantidGeometry/Instrument.h" +#include "MantidGeometry/Instrument/Goniometer.h" +#include "MantidGeometry/Instrument/ReferenceFrame.h" #include "MantidGeometry/Instrument/SampleEnvironmentFactory.h" #include "MantidKernel/ConfigService.h" #include "MantidKernel/FacilityInfo.h" #include "MantidKernel/InstrumentInfo.h" #include "MantidKernel/Logger.h" #include "MantidKernel/Material.h" +#include "MantidKernel/Matrix.h" #include "MantidKernel/PropertyManager.h" #include "MantidKernel/PropertyManagerProperty.h" @@ -19,32 +22,63 @@ namespace Mantid { namespace DataHandling { +using Geometry::Goniometer; +using Geometry::ReferenceFrame; using Geometry::SampleEnvironment; using Kernel::Logger; +using Kernel::PropertyWithValue; +using Kernel::Quat; using Kernel::V3D; namespace { +/// Private namespace storing property name strings +namespace PropertyNames { +/// Input workspace property name +const std::string INPUT_WORKSPACE("InputWorkspace"); +/// Geometry property name +const std::string GEOMETRY("Geometry"); +/// Material property name +const std::string MATERIAL("Material"); +/// Environment property name +const std::string ENVIRONMENT("Environment"); +} +/// Private namespace storing sample environment args +namespace SEArgs { +/// Static Name string +const std::string NAME("Name"); +/// Static Container string +const std::string CONTAINER("Container"); +} +/// Provate namespace storing geometry args +namespace GeometryArgs { +/// Static Shape string +const std::string SHAPE("Shape"); +} -/** - * Retrieve "Axis" property value. The most commmon use case is calling - * this algorithm from Python where Axis is input as a C long. The - * definition of long varies across platforms and long v = args.getProperty() - * is currently unable to cope so we go via the long route to retrieve - * the value. - * @param args Dictionary-type containing the argument - */ -int64_t getAxisIndex(const Kernel::PropertyManager &args) { - using Kernel::Property; - using Kernel::PropertyWithValue; - int64_t axisIdx(1); - if (args.existsProperty("Axis")) { - Kernel::Property *axisProp = args.getProperty("Axis"); - axisIdx = static_cast<PropertyWithValue<int64_t> *>(axisProp)->operator()(); - if (axisIdx < 0 || axisIdx > 2) - throw std::invalid_argument( - "Geometry.Axis value must be either 0,1,2 (X,Y,Z)"); - } - return axisIdx; +/// Private namespace storing sample environment args +namespace ShapeArgs { +/// Static FlatPlate string +const std::string FLAT_PLATE("FlatPlate"); +/// Static Cylinder string +const std::string CYLINDER("Cylinder"); +/// Static HollowCylinder string +const std::string HOLLOW_CYLINDER("HollowCylinder"); +/// Static CSG string +const std::string CSG("CSG"); +/// Static Width string +const std::string WIDTH("Width"); +/// Static Height string +const std::string HEIGHT("Height"); +/// Static Thick string +const std::string THICK("Thick"); +/// Static Center string +const std::string CENTER("Center"); +/// Static Radius string +const std::string RADIUS("Radius"); +/// Static InnerRadius string +const std::string INNER_RADIUS("InnerRadius"); +/// Static OuterRadius string +const std::string OUTER_RADIUS("OuterRadius"); } /** @@ -55,7 +89,7 @@ int64_t getAxisIndex(const Kernel::PropertyManager &args) { * @param axis The index of the height-axis of the cylinder */ V3D cylBaseCentre(const std::vector<double> &cylCentre, double height, - int64_t axisIdx) { + unsigned axisIdx) { const V3D halfHeight = [&]() { switch (axisIdx) { case 0: @@ -76,7 +110,7 @@ V3D cylBaseCentre(const std::vector<double> &cylCentre, double height, * @param axisIdx Index 0,1,2 for the axis of a cylinder * @return A string containing the axis tag for this index */ -std::string axisXML(int64_t axisIdx) { +std::string axisXML(unsigned axisIdx) { switch (axisIdx) { case 0: return "<axis x=\"1\" y=\"0\" z=\"0\" />"; @@ -109,28 +143,58 @@ const std::string SetSample::summary() const { /// Validate the inputs against each other @see Algorithm::validateInputs std::map<std::string, std::string> SetSample::validateInputs() { - using Kernel::PropertyManager_sptr; + using Kernel::PropertyManager; + using Kernel::PropertyManager_const_sptr; std::map<std::string, std::string> errors; + auto existsAndNotEmptyString = + [](const PropertyManager &pm, const std::string &name) { + if (pm.existsProperty(name)) { + const auto value = pm.getPropertyValue(name); + return !value.empty(); + } + return false; + }; + + auto existsAndNegative = + [](const PropertyManager &pm, const std::string &name) { + if (pm.existsProperty(name)) { + const double value = pm.getProperty(name); + if (value < 0.0) { + return true; + } + } + return false; + }; + // Validate Environment - PropertyManager_sptr environArgs = getProperty("Environment"); + const PropertyManager_const_sptr environArgs = + getProperty(PropertyNames::ENVIRONMENT); if (environArgs) { - if (!environArgs->existsProperty("Name")) { - errors["Environment"] = "Environment flags must contain a 'Name' entry."; - } else { - std::string name = environArgs->getPropertyValue("Name"); - if (name.empty()) { - errors["Environment"] = "Environment 'Name' flag is an empty string!"; - } + if (!existsAndNotEmptyString(*environArgs, SEArgs::NAME)) { + errors[PropertyNames::ENVIRONMENT] = + "Environment flags require a non-empty 'Name' entry."; } - if (!environArgs->existsProperty("Container")) { - errors["Environment"] = - "Environment flags must contain a 'Container' entry."; - } else { - std::string name = environArgs->getPropertyValue("Container"); - if (name.empty()) { - errors["Environment"] = "Environment 'Can' flag is an empty string!"; + if (!existsAndNotEmptyString(*environArgs, SEArgs::CONTAINER)) { + errors[PropertyNames::ENVIRONMENT] = + "Environment flags require a non-empty 'Container' entry."; + } + } + + // Validate as much of the shape information as possible + const PropertyManager_const_sptr geomArgs = + getProperty(PropertyNames::GEOMETRY); + if (geomArgs) { + if (existsAndNotEmptyString(*geomArgs, GeometryArgs::SHAPE)) { + const std::array<const std::string *, 6> positiveValues = { + {&ShapeArgs::HEIGHT, &ShapeArgs::WIDTH, &ShapeArgs::THICK, + &ShapeArgs::RADIUS, &ShapeArgs::INNER_RADIUS, + &ShapeArgs::OUTER_RADIUS}}; + for (const auto &arg : positiveValues) { + if (existsAndNegative(*geomArgs, *arg)) { + errors[PropertyNames::GEOMETRY] = *arg + " argument < 0.0"; + } } } } @@ -147,18 +211,18 @@ void SetSample::init() { using Kernel::PropertyManagerProperty; // Inputs - declareProperty(Kernel::make_unique<WorkspaceProperty<>>("InputWorkspace", "", - Direction::InOut), + declareProperty(Kernel::make_unique<WorkspaceProperty<>>( + PropertyNames::INPUT_WORKSPACE, "", Direction::InOut), "A workspace whose sample properties will be updated"); declareProperty(Kernel::make_unique<PropertyManagerProperty>( - "Geometry", Direction::Input), + PropertyNames::GEOMETRY, Direction::Input), "A dictionary of geometry parameters for the sample."); declareProperty(Kernel::make_unique<PropertyManagerProperty>( - "Material", Direction::Input), + PropertyNames::MATERIAL, Direction::Input), "A dictionary of material parameters for the sample. See " "SetSampleMaterial for all accepted parameters"); declareProperty( - Kernel::make_unique<PropertyManagerProperty>("Environment", + Kernel::make_unique<PropertyManagerProperty>(PropertyNames::ENVIRONMENT, Direction::Input), "A dictionary of parameters to configure the sample environment"); } @@ -170,17 +234,17 @@ void SetSample::exec() { using API::MatrixWorkspace_sptr; using Kernel::PropertyManager_sptr; - MatrixWorkspace_sptr workspace = getProperty("InputWorkspace"); - PropertyManager_sptr environArgs = getProperty("Environment"); - PropertyManager_sptr geometryArgs = getProperty("Geometry"); - PropertyManager_sptr materialArgs = getProperty("Material"); + MatrixWorkspace_sptr workspace = getProperty(PropertyNames::INPUT_WORKSPACE); + PropertyManager_sptr environArgs = getProperty(PropertyNames::ENVIRONMENT); + PropertyManager_sptr geometryArgs = getProperty(PropertyNames::GEOMETRY); + PropertyManager_sptr materialArgs = getProperty(PropertyNames::MATERIAL); // The order here is important. Se the environment first. If this // defines a sample geometry then we can process the Geometry flags // combined with this const SampleEnvironment *sampleEnviron(nullptr); if (environArgs) { - sampleEnviron = setSampleEnvironment(workspace, *environArgs); + sampleEnviron = setSampleEnvironment(workspace, environArgs); } if (geometryArgs || sampleEnviron) { @@ -199,15 +263,15 @@ void SetSample::exec() { * @param args The dictionary of flags for the environment * @return A pointer to the new sample environment */ -const Geometry::SampleEnvironment * -SetSample::setSampleEnvironment(API::MatrixWorkspace_sptr &workspace, - const Kernel::PropertyManager &args) { +const Geometry::SampleEnvironment *SetSample::setSampleEnvironment( + API::MatrixWorkspace_sptr &workspace, + const Kernel::PropertyManager_const_sptr &args) { using Geometry::SampleEnvironmentSpecFileFinder; using Geometry::SampleEnvironmentFactory; using Kernel::ConfigService; - const std::string envName = args.getPropertyValue("Name"); - const std::string canName = args.getPropertyValue("Container"); + const std::string envName = args->getPropertyValue(SEArgs::NAME); + const std::string canName = args->getPropertyValue(SEArgs::CONTAINER); // The specifications need to be qualified by the facility and instrument. // Check instrument for name and then lookup facility if facility // is unknown then set to default facility & instrument. @@ -248,7 +312,7 @@ SetSample::setSampleEnvironment(API::MatrixWorkspace_sptr &workspace, * @return A string containing the XML definition of the shape */ void SetSample::setSampleShape(API::MatrixWorkspace_sptr &workspace, - const Kernel::PropertyManager_sptr &args, + const Kernel::PropertyManager_const_sptr &args, const Geometry::SampleEnvironment *sampleEnv) { using Geometry::Container; /* The sample geometry can be specified in two ways: @@ -258,10 +322,13 @@ void SetSample::setSampleShape(API::MatrixWorkspace_sptr &workspace, */ // Try known shapes or CSG first if supplied - const auto xml = tryCreateXMLFromArgsOnly(args); - if (!xml.empty()) { - runSetSampleShape(workspace, xml); - return; + if (args) { + const auto refFrame = workspace->getInstrument()->getReferenceFrame(); + const auto xml = tryCreateXMLFromArgsOnly(*args, *refFrame); + if (!xml.empty()) { + runSetSampleShape(workspace, xml); + return; + } } // Any arguments in the args dict are assumed to be values that should // override the default set by the sampleEnv samplegeometry if it exists @@ -300,23 +367,26 @@ void SetSample::setSampleShape(API::MatrixWorkspace_sptr &workspace, /** * Create the required XML for a given shape type plus its arguments * @param args A dict of flags defining the shape + * @param refFrame Defines the reference frame for the shape * @return A string containing the XML if possible or an empty string */ std::string -SetSample::tryCreateXMLFromArgsOnly(const Kernel::PropertyManager_sptr args) { +SetSample::tryCreateXMLFromArgsOnly(const Kernel::PropertyManager &args, + const Geometry::ReferenceFrame &refFrame) { std::string result; - if (!args || !args->existsProperty("Shape")) { + if (!args.existsProperty(GeometryArgs::SHAPE)) { return result; } - const auto shape = args->getPropertyValue("Shape"); - if (shape == "CSG") { - result = args->getPropertyValue("Value"); - } else if (shape == "FlatPlate") { - result = createFlatPlateXML(*args); - } else if (boost::algorithm::ends_with(shape, "Cylinder")) { + const auto shape = args.getPropertyValue(GeometryArgs::SHAPE); + if (shape == ShapeArgs::CSG) { + result = args.getPropertyValue("Value"); + } else if (shape == ShapeArgs::FLAT_PLATE) { + result = createFlatPlateXML(args, refFrame); + } else if (boost::algorithm::ends_with(shape, ShapeArgs::CYLINDER)) { result = createCylinderLikeXML( - *args, boost::algorithm::starts_with(shape, "Hollow")); + args, refFrame, + boost::algorithm::equals(shape, ShapeArgs::HOLLOW_CYLINDER)); } else { throw std::invalid_argument( "Unknown 'Shape' argument provided in " @@ -332,61 +402,85 @@ SetSample::tryCreateXMLFromArgsOnly(const Kernel::PropertyManager_sptr args) { /** * Create the XML required to define a flat plate from the given args * @param args A user-supplied dict of args + * @param refFrame Defines the reference frame for the shape * @return The XML definition string */ std::string -SetSample::createFlatPlateXML(const Kernel::PropertyManager &args) const { - // X - double widthInCM = args.getProperty("Width"); - // Y - double heightInCM = args.getProperty("Height"); - // Z - double thickInCM = args.getProperty("Thick"); - std::vector<double> center = args.getProperty("Center"); - // convert to metres - std::transform(center.begin(), center.end(), center.begin(), - [](double val) { return val *= 0.01; }); - - // Half lengths in metres (*0.01*0.5) +SetSample::createFlatPlateXML(const Kernel::PropertyManager &args, + const Geometry::ReferenceFrame &refFrame) const { + // Helper to take 3 coordinates and turn them to a V3D respecting the + // current reference frame + auto makeV3D = [&refFrame](double x, double y, double z) { + V3D v; + v[refFrame.pointingHorizontal()] = x; + v[refFrame.pointingUp()] = y; + v[refFrame.pointingAlongBeam()] = z; + return v; + }; + const double widthInCM = args.getProperty(ShapeArgs::WIDTH); + const double heightInCM = args.getProperty(ShapeArgs::HEIGHT); + const double thickInCM = args.getProperty(ShapeArgs::THICK); + // Convert to half-"width" in metres const double szX = (widthInCM * 5e-3); const double szY = (heightInCM * 5e-3); const double szZ = (thickInCM * 5e-3); + // Contruct cuboid corners. Define points about origin, rotate and then + // translate to final center position + auto lfb = makeV3D(szX, -szY, -szZ); + auto lft = makeV3D(szX, szY, -szZ); + auto lbb = makeV3D(szX, -szY, szZ); + auto rfb = makeV3D(-szX, -szY, -szZ); + // optional rotation about the center of object + if (args.existsProperty("Angle")) { + Goniometer gr; + const auto upAxis = makeV3D(0, 1, 0); + gr.pushAxis("up", upAxis.X(), upAxis.Y(), upAxis.Z(), + args.getProperty("Angle"), Geometry::CCW, Geometry::angDegrees); + auto &rotation = gr.getR(); + lfb.rotate(rotation); + lft.rotate(rotation); + lbb.rotate(rotation); + rfb.rotate(rotation); + } + std::vector<double> center = args.getProperty(ShapeArgs::CENTER); + const V3D centrePos(center[0] * 0.01, center[1] * 0.01, center[2] * 0.01); + // translate to true center after rotation + lfb += centrePos; + lft += centrePos; + lbb += centrePos; + rfb += centrePos; std::ostringstream xmlShapeStream; xmlShapeStream << " <cuboid id=\"sample-shape\"> " - << "<left-front-bottom-point x=\"" << szX + center[0] - << "\" y=\"" << -szY + center[1] << "\" z=\"" - << -szZ + center[2] << "\" /> " - << "<left-front-top-point x=\"" << szX + center[0] - << "\" y=\"" << szY + center[1] << "\" z=\"" - << -szZ + center[2] << "\" /> " - << "<left-back-bottom-point x=\"" << szX + center[0] - << "\" y=\"" << -szY + center[1] << "\" z=\"" - << szZ + center[2] << "\" /> " - << "<right-front-bottom-point x=\"" << -szX + center[0] - << "\" y=\"" << -szY + center[1] << "\" z=\"" - << -szZ + center[2] << "\" /> " + << "<left-front-bottom-point x=\"" << lfb.X() << "\" y=\"" + << lfb.Y() << "\" z=\"" << lfb.Z() << "\" /> " + << "<left-front-top-point x=\"" << lft.X() << "\" y=\"" + << lft.Y() << "\" z=\"" << lft.Z() << "\" /> " + << "<left-back-bottom-point x=\"" << lbb.X() << "\" y=\"" + << lbb.Y() << "\" z=\"" << lbb.Z() << "\" /> " + << "<right-front-bottom-point x=\"" << rfb.X() << "\" y =\"" + << rfb.Y() << "\" z=\"" << rfb.Z() << "\" /> " << "</cuboid>"; - return xmlShapeStream.str(); } /** * Create the XML required to define a cylinder from the given args * @param args A user-supplied dict of args + * @param refFrame Defines the reference frame for the shape * @param hollow True if an annulus is to be created * @return The XML definition string */ std::string SetSample::createCylinderLikeXML(const Kernel::PropertyManager &args, + const Geometry::ReferenceFrame &refFrame, bool hollow) const { const std::string tag = hollow ? "hollow-cylinder" : "cylinder"; - double height = args.getProperty("Height"); - double innerRadius = hollow ? args.getProperty("InnerRadius") : 0.0; - double outerRadius = - hollow ? args.getProperty("OuterRadius") : args.getProperty("Radius"); - std::vector<double> centre = args.getProperty("Center"); - int64_t axisIdx = getAxisIndex(args); + double height = args.getProperty(ShapeArgs::HEIGHT); + double innerRadius = hollow ? args.getProperty(ShapeArgs::INNER_RADIUS) : 0.0; + double outerRadius = hollow ? args.getProperty(ShapeArgs::OUTER_RADIUS) + : args.getProperty("Radius"); + std::vector<double> centre = args.getProperty(ShapeArgs::CENTER); // convert to metres height *= 0.01; innerRadius *= 0.01; @@ -395,6 +489,7 @@ SetSample::createCylinderLikeXML(const Kernel::PropertyManager &args, [](double val) { return val *= 0.01; }); // XML needs center position of bottom base but user specifies center of // cylinder + const unsigned axisIdx = static_cast<unsigned>(refFrame.pointingUp()); const V3D baseCentre = cylBaseCentre(centre, height, axisIdx); std::ostringstream xmlShapeStream; @@ -421,7 +516,7 @@ SetSample::createCylinderLikeXML(const Kernel::PropertyManager &args, void SetSample::runSetSampleShape(API::MatrixWorkspace_sptr &workspace, const std::string &xml) { auto alg = createChildAlgorithm("CreateSampleShape"); - alg->setProperty("InputWorkspace", workspace); + alg->setProperty(PropertyNames::INPUT_WORKSPACE, workspace); alg->setProperty("ShapeXML", xml); alg->executeAsChildAlg(); } @@ -437,7 +532,7 @@ void SetSample::runChildAlgorithm(const std::string &name, API::MatrixWorkspace_sptr &workspace, const Kernel::PropertyManager &args) { auto alg = createChildAlgorithm(name); - alg->setProperty("InputWorkspace", workspace); + alg->setProperty(PropertyNames::INPUT_WORKSPACE, workspace); alg->updatePropertyValues(args); alg->executeAsChildAlg(); } diff --git a/Framework/DataHandling/test/SetSampleTest.h b/Framework/DataHandling/test/SetSampleTest.h index e90effc98c62d4b76210beb0e04c5ce144ba6161..d15ae0e33896641ab5a1aa6ddc6471cd0ce3b7ea 100644 --- a/Framework/DataHandling/test/SetSampleTest.h +++ b/Framework/DataHandling/test/SetSampleTest.h @@ -4,6 +4,7 @@ #include <cxxtest/TestSuite.h> #include "MantidDataHandling/SetSample.h" +#include "MantidGeometry/Instrument/ReferenceFrame.h" #include "MantidGeometry/Instrument/SampleEnvironment.h" #include "MantidGeometry/Objects/Object.h" #include "MantidGeometry/Objects/Rules.h" @@ -91,8 +92,7 @@ public: sampleShape->setID("mysample"); inputWS->mutableSample().setShape(*sampleShape); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Material", createMaterialProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); @@ -115,8 +115,7 @@ public: sampleShape->setMaterial(alum); inputWS->mutableSample().setShape(*sampleShape); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Geometry", createGenericGeometryProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); @@ -143,8 +142,7 @@ public: auto &config = ConfigService::Instance(); const auto defaultDirs = config.getString("instrumentDefinition.directory"); config.setString("instrumentDefinition.directory", m_testRoot); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Environment", createEnvironmentProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); @@ -175,8 +173,7 @@ public: auto &config = ConfigService::Instance(); const auto defaultDirs = config.getString("instrumentDefinition.directory"); config.setString("instrumentDefinition.directory", m_testRoot); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Environment", createEnvironmentProps()); alg->setProperty("Geometry", createOverrideGeometryProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); @@ -201,9 +198,9 @@ public: void test_Setting_Geometry_As_FlatPlate() { using Mantid::Kernel::V3D; auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); + setTestReferenceFrame(inputWS); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Geometry", createFlatPlateGeometryProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); @@ -214,16 +211,46 @@ public: auto tag = sampleShape.getShapeXML().find("cuboid"); TS_ASSERT(tag != std::string::npos); - TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0, 0.01))); + // Center + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0.01, 0, 0))); + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0.0105, 0.025, 0.02))); + // Origin TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0, 0.0))); } + void test_Setting_Geometry_As_FlatPlate_With_Rotation() { + using Mantid::Kernel::V3D; + auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); + setTestReferenceFrame(inputWS); + + auto alg = createAlgorithm(inputWS); + const double angle(45.0); + alg->setProperty("Geometry", createFlatPlateGeometryProps(angle)); + TS_ASSERT_THROWS_NOTHING(alg->execute()); + TS_ASSERT(alg->isExecuted()); + + // New shape + const auto &sampleShape = inputWS->sample().getShape(); + TS_ASSERT(sampleShape.hasValidShape()); + auto tag = sampleShape.getShapeXML().find("cuboid"); + TS_ASSERT(tag != std::string::npos); + + // Center should be preserved inside the shape + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0.01, 0, 0))); + // V3D(0.0005, 0.025, 0.02) rotated by 45 degrees CCW and translated + // to center + TS_ASSERT_EQUALS(true, + sampleShape.isValid(V3D(-0.00732412, 0.01803122, 0.02))); + // End of horizontal axis should now not be inside the object + TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.025, 0))); + } + void test_Setting_Geometry_As_Cylinder() { using Mantid::Kernel::V3D; auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); + setTestReferenceFrame(inputWS); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Geometry", createCylinderGeometryProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); @@ -234,18 +261,18 @@ public: auto tag = sampleShape.getShapeXML().find("cylinder"); TS_ASSERT(tag != std::string::npos); - TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0.009, 0.015))); - TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, -0.009, 0.015))); - TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.011, 0.015))); - TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, -0.011, 0.015))); + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0.049, 0.019))); + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0.049, 0.001))); + TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.06, 0.021))); + TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.06, -0.001))); } void test_Setting_Geometry_As_HollowCylinder() { using Mantid::Kernel::V3D; auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); + setTestReferenceFrame(inputWS); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); alg->setProperty("Geometry", createHollowCylinderGeometryProps()); TS_ASSERT_THROWS_NOTHING(alg->execute()); TS_ASSERT(alg->isExecuted()); @@ -253,10 +280,10 @@ public: // New shape const auto &sampleShape = inputWS->sample().getShape(); TS_ASSERT(sampleShape.hasValidShape()); - TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0.009, 0.045))); - TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, -0.009, 0.045))); - TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.011, 0.045))); - TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, -0.011, 0.045))); + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0.035, 0.019))); + TS_ASSERT_EQUALS(true, sampleShape.isValid(V3D(0, 0.035, 0.001))); + TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.041, 0.021))); + TS_ASSERT_EQUALS(false, sampleShape.isValid(V3D(0, 0.041, -0.001))); } //---------------------------------------------------------------------------- @@ -268,8 +295,7 @@ public: using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); auto args = boost::make_shared<PropertyManager>(); args->declareProperty( @@ -283,8 +309,7 @@ public: using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); auto args = boost::make_shared<PropertyManager>(); args->declareProperty( @@ -298,8 +323,7 @@ public: using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; auto inputWS = WorkspaceCreationHelper::create2DWorkspaceBinned(1, 1); - auto alg = createAlgorithm(); - alg->setProperty("InputWorkspace", inputWS); + auto alg = createAlgorithm(inputWS); auto args = boost::make_shared<PropertyManager>(); args->declareProperty( @@ -313,18 +337,106 @@ public: TS_ASSERT_THROWS(alg->execute(), std::runtime_error); } + void test_Negative_FlatPlate_Dimensions_Give_Validation_Errors() { + using Mantid::API::IAlgorithm; + using Mantid::Kernel::PropertyManager; + using DoubleProperty = Mantid::Kernel::PropertyWithValue<double>; + using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; + + auto alg = createAlgorithm(); + auto args = boost::make_shared<PropertyManager>(); + args->declareProperty( + Mantid::Kernel::make_unique<StringProperty>("Shape", "FlatPlate"), ""); + std::array<const std::string, 3> dimensions = { + {"Width", "Height", "Thick"}}; + const std::string geometryProp("Geometry"); + for (const auto &dim : dimensions) { + args->declareProperty( + Mantid::Kernel::make_unique<DoubleProperty>(dim, -1.0), ""); + alg->setProperty(geometryProp, args); + TS_ASSERT(validateErrorProduced(*alg, geometryProp)); + args->removeProperty(dim); + } + } + + void test_Negative_Cylinder_Dimensions_Give_Validation_Errors() { + using Mantid::Kernel::PropertyManager; + using DoubleProperty = Mantid::Kernel::PropertyWithValue<double>; + using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; + + auto alg = createAlgorithm(); + auto args = boost::make_shared<PropertyManager>(); + args->declareProperty( + Mantid::Kernel::make_unique<StringProperty>("Shape", "Cylinder"), ""); + std::array<const std::string, 2> dimensions = {{"Radius", "Height"}}; + const std::string geometryProp("Geometry"); + for (const auto &dim : dimensions) { + args->declareProperty( + Mantid::Kernel::make_unique<DoubleProperty>(dim, -1.0), ""); + alg->setProperty(geometryProp, args); + TS_ASSERT(validateErrorProduced(*alg, geometryProp)); + args->removeProperty(dim); + } + } + + void test_Negative_HollowCylinder_Dimensions_Give_Validation_Errors() { + using Mantid::API::IAlgorithm; + using Mantid::Kernel::PropertyManager; + using DoubleProperty = Mantid::Kernel::PropertyWithValue<double>; + using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; + + auto alg = createAlgorithm(); + auto args = boost::make_shared<PropertyManager>(); + args->declareProperty( + Mantid::Kernel::make_unique<StringProperty>("Shape", "FlatPlate"), ""); + std::array<const std::string, 3> dimensions = { + {"InnerRadius", "OuterRadius", "Height"}}; + const std::string geometryProp("Geometry"); + for (const auto &dim : dimensions) { + args->declareProperty( + Mantid::Kernel::make_unique<DoubleProperty>(dim, -1.0), ""); + alg->setProperty(geometryProp, args); + TS_ASSERT(validateErrorProduced(*alg, geometryProp)); + args->removeProperty(dim); + } + } + //---------------------------------------------------------------------------- // Non-test methods //---------------------------------------------------------------------------- private: - Mantid::API::IAlgorithm_uptr createAlgorithm() { + Mantid::API::IAlgorithm_uptr + createAlgorithm(const Mantid::API::MatrixWorkspace_sptr &inputWS = + Mantid::API::MatrixWorkspace_sptr()) { auto alg = Mantid::Kernel::make_unique<SetSample>(); alg->setChild(true); alg->setRethrows(true); alg->initialize(); + if (inputWS) { + alg->setProperty("InputWorkspace", inputWS); + } return std::move(alg); } + bool validateErrorProduced(Mantid::API::IAlgorithm &alg, + const std::string &name) { + const auto errors = alg.validateInputs(); + if (errors.find(name) != errors.end()) + return true; + else + return false; + } + + void setTestReferenceFrame(Mantid::API::MatrixWorkspace_sptr workspace) { + using Mantid::Geometry::Instrument; + using Mantid::Geometry::ReferenceFrame; + // Use Z=up,Y=across,X=beam so we test it listens to the reference frame + auto inst = boost::make_shared<Instrument>(); + inst->setReferenceFrame(boost::make_shared<ReferenceFrame>( + Mantid::Geometry::Z, Mantid::Geometry::X, Mantid::Geometry::Right, "")); + workspace->setInstrument(inst); + } + Mantid::Kernel::PropertyManager_sptr createMaterialProps() { using Mantid::Kernel::PropertyManager; using StringProperty = Mantid::Kernel::PropertyWithValue<std::string>; @@ -373,7 +485,8 @@ private: return props; } - Mantid::Kernel::PropertyManager_sptr createFlatPlateGeometryProps() { + Mantid::Kernel::PropertyManager_sptr + createFlatPlateGeometryProps(double angle = 0.0) { using namespace Mantid::Kernel; using DoubleArrayProperty = ArrayProperty<double>; using DoubleProperty = PropertyWithValue<double>; @@ -388,10 +501,13 @@ private: Mantid::Kernel::make_unique<DoubleProperty>("Height", 4), ""); props->declareProperty( Mantid::Kernel::make_unique<DoubleProperty>("Thick", 0.1), ""); - std::vector<double> center{0, 0, 1}; + std::vector<double> center{1, 0, 0}; props->declareProperty( Mantid::Kernel::make_unique<DoubleArrayProperty>("Center", center), ""); - + if (angle != 0.0) { + props->declareProperty( + Mantid::Kernel::make_unique<DoubleProperty>("Angle", angle), ""); + } return props; } @@ -399,7 +515,6 @@ private: using namespace Mantid::Kernel; using DoubleArrayProperty = ArrayProperty<double>; using DoubleProperty = PropertyWithValue<double>; - using IntProperty = PropertyWithValue<int64_t>; using StringProperty = PropertyWithValue<std::string>; auto props = boost::make_shared<PropertyManager>(); @@ -412,8 +527,6 @@ private: std::vector<double> center{0, 0, 1}; props->declareProperty( Mantid::Kernel::make_unique<DoubleArrayProperty>("Center", center), ""); - props->declareProperty(Mantid::Kernel::make_unique<IntProperty>("Axis", 1), - ""); return props; } @@ -422,7 +535,6 @@ private: using namespace Mantid::Kernel; using DoubleArrayProperty = ArrayProperty<double>; using DoubleProperty = PropertyWithValue<double>; - using IntProperty = PropertyWithValue<int64_t>; using StringProperty = PropertyWithValue<std::string>; auto props = boost::make_shared<PropertyManager>(); @@ -438,8 +550,6 @@ private: std::vector<double> center{0, 0, 1}; props->declareProperty( Mantid::Kernel::make_unique<DoubleArrayProperty>("Center", center), ""); - props->declareProperty(Mantid::Kernel::make_unique<IntProperty>("Axis", 1), - ""); return props; } @@ -458,6 +568,7 @@ private: throw std::runtime_error("Expected SurfPoint as top rule"); } } + std::string m_testRoot; // Use the TEST_LIVE entry in Facilities const std::string m_facilityName = "TEST_LIVE"; diff --git a/docs/source/algorithms/SetSample-v1.rst b/docs/source/algorithms/SetSample-v1.rst index bb33da877dfd6a1b23466f36517d7045965cd8dd..10ff85bfa9fd1623a96357189ccfe2fc1ef3c58d 100644 --- a/docs/source/algorithms/SetSample-v1.rst +++ b/docs/source/algorithms/SetSample-v1.rst @@ -24,7 +24,7 @@ relate to the respective argument. Environment ########### -Specifies the sample enviorment kit to be used. There are two required keywords: +Specifies the sample environment kit to be used. There are two required keywords: - ``Name``: The name of the predefined kit - ``Container``: The id of the container within the predefined kit @@ -71,14 +71,20 @@ For defining the full shape a key called ``Shape`` specifying the desired shape expected along with additional keys specifying the values (all values are assumed to be in centimeters): -- ``FlatPlate``: Width, Height, Thick, Center -- ``Cylinder``: Height, Radius, Center, Axis (X=0, Y=1, Z=2) -- ``HollowCylinder``: Height, InnerRadius, OuterRadius, Center, Axis(X=0, Y=1, Z=2) +- ``FlatPlate``: Width, Height, Thick, Center, Angle +- ``Cylinder``: Height, Radius, Center +- ``HollowCylinder``: Height, InnerRadius, OuterRadius, Center - ``CSG``: Value is a string containing any generic shape as detailed in :ref:`HowToDefineGeometricShape` The ``Center`` key is expected to be a list of three values indicating the :python:`[X,Y,Z]` -position of the center. +position of the center. The reference frame of the defined instrument is used to +set the coordinate system for the shape. + +The ``Angle`` argument for a flat plate shape is expected to be in degrees and is defined as +the angle between the positive beam axis and the normal to the face perpendicular to the +beam axis when it is not rotated, increasing in an anti-clockwise sense. The rotation is +performed about the vertical axis of the instrument's reference frame. Material ######## @@ -132,11 +138,11 @@ The following example uses a test file called ``CRYO-01.xml`` in the # A fake host workspace, replace this with your real one. ws = CreateSampleWorkspace() - # Use geometry from environment but set differnet height for sample + # Use geometry from environment but set different height for sample SetSample(ws, Environment={'Name': 'CRYO-01', 'Container': '8mm'}, Geometry={'Shape': 'HollowCylinder', 'Height': 4.0, 'InnerRadius': 0.8, 'OuterRadius': 1.0, - 'Center': [0.,0.,0.], 'Axis':1}, + 'Center': [0.,0.,0.]}, Material={'ChemicalFormula': '(Li7)2-C-H4-N-Cl6'}) .. categories:: diff --git a/docs/source/release/v3.9.0/framework.rst b/docs/source/release/v3.9.0/framework.rst index bdf9eb17cb1c5af4e4d021986f9094e120e2a997..8b047254ba8f6969736216667015e806806980f3 100644 --- a/docs/source/release/v3.9.0/framework.rst +++ b/docs/source/release/v3.9.0/framework.rst @@ -27,6 +27,7 @@ Improved - :ref:`MonteCarloAbsorption <algm-MonteCarloAbsorption>`: * an `Interpolation` option has been added. Availabile options are: `Linear` & `CSpline`. * the method of selecting the scattering point has ben updated to give better agreement with numerical algorithms such as :ref:`CylinderAbsorption <algm-CylinderAbsorption>`. +- :ref:`SetSample <algm-SetSample>` now accepts an Angle argument for defining a rotated flat plate sample. Renamed #######