/** * Generate a random point within the beam profile using the supplied random * number source * @param rng A reference to a random number generator * @return An IBeamProfile::Ray describing the start and direction */ IBeamProfile::Ray RectangularBeamProfile::generatePoint( Kernel::PseudoRandomNumberGenerator &rng) const { V3D pt; pt[m_upIdx] = m_min[m_upIdx] + rng.nextValue() * m_height; pt[m_horIdx] = m_min[m_horIdx] + rng.nextValue() * m_width; pt[m_beamIdx] = m_min[m_beamIdx]; return {pt, m_beamDir}; }
/** * Return a random point in a cuboid shape. * @param shapeInfo cuboid's shape info * @param rng a random number generate * @return a random point inside the cuboid */ Kernel::V3D inCuboid(const detail::ShapeInfo &shapeInfo, Kernel::PseudoRandomNumberGenerator &rng) { const auto geometry = shapeInfo.cuboidGeometry(); const double r1{rng.nextValue()}; const double r2{rng.nextValue()}; const double r3{rng.nextValue()}; const Kernel::V3D basis1{geometry.leftFrontTop - geometry.leftFrontBottom}; const Kernel::V3D basis2{geometry.leftBackBottom - geometry.leftFrontBottom}; const Kernel::V3D basis3{geometry.rightFrontBottom - geometry.leftFrontBottom}; return geometry.leftFrontBottom + (basis1 * r1 + basis2 * r2 + basis3 * r3); }
/** * Return a random point in cylinder. * @param shapeInfo cylinder's shape info * @param rng a random number generator * @return a point */ Kernel::V3D inCylinder(const detail::ShapeInfo &shapeInfo, Kernel::PseudoRandomNumberGenerator &rng) { const auto geometry = shapeInfo.cylinderGeometry(); const double r1{rng.nextValue()}; const double r2{rng.nextValue()}; const double r3{rng.nextValue()}; const double polar{2. * M_PI * r1}; // The sqrt is needed for a uniform distribution of points. const double r{geometry.radius * std::sqrt(r2)}; const double z{geometry.height * r3}; const Kernel::V3D alongAxis{geometry.axis * z}; auto localPoint = localPointInCylinder(geometry.axis, alongAxis, polar, r); return localPoint + geometry.centreOfBottomBase; }
/** * Return a random point in sphere. * @param shapeInfo sphere's shape info * @param rng a random number generator * @return a point */ Kernel::V3D inSphere(const detail::ShapeInfo &shapeInfo, Kernel::PseudoRandomNumberGenerator &rng) { const auto geometry = shapeInfo.sphereGeometry(); const double r1{rng.nextValue()}; const double r2{rng.nextValue()}; const double r3{rng.nextValue()}; const double azimuthal{2. * M_PI * r1}; // The acos is needed for a uniform distribution of points. const double polar{std::acos(2. * r2 - 1.)}; const double r{r3 * geometry.radius}; const double x{r * std::cos(azimuthal) * std::sin(polar)}; const double y{r * std::sin(azimuthal) * std::sin(polar)}; const double z{r * std::cos(polar)}; return geometry.centre + Kernel::V3D{x, y, z}; }
/** * Return a random point in a hollow cylinder * @param shapeInfo hollow cylinder's shape info * @param rng a random number generator * @return a point */ Kernel::V3D inHollowCylinder(const detail::ShapeInfo &shapeInfo, Kernel::PseudoRandomNumberGenerator &rng) { const auto geometry = shapeInfo.hollowCylinderGeometry(); const double r1{rng.nextValue()}; const double r2{rng.nextValue()}; const double r3{rng.nextValue()}; const double polar{2. * M_PI * r1}; // We need a random number between the inner radius and outer radius, but also // need the square root for a uniform distribution of points const double c1 = geometry.innerRadius * geometry.innerRadius; const double c2 = geometry.radius * geometry.radius; const double r{std::sqrt(c1 + (c2 - c1) * r2)}; const double z{geometry.height * r3}; const Kernel::V3D alongAxis{geometry.axis * z}; auto localPoint = localPointInCylinder(geometry.axis, alongAxis, polar, r); return localPoint + geometry.centreOfBottomBase; }
/** * Generate a random point within one of the environment's components. The * method first selects a random component and then selects a random point * within that component using Object::generatePointObject * @param rng A reference to a PseudoRandomNumberGenerator where * nextValue should return a flat random number between 0.0 & 1.0 * @param activeRegion Restrict the generated point to be defined by this box * @param maxAttempts The maximum number of attempts at generating a point * @return The generated point */ Kernel::V3D SampleEnvironment::generatePoint(Kernel::PseudoRandomNumberGenerator &rng, const Geometry::BoundingBox &activeRegion, const size_t maxAttempts) const { auto componentIndex = rng.nextInt(1, static_cast<int>(nelements())) - 1; return m_components[componentIndex]->generatePointInObject(rng, activeRegion, maxAttempts); }
/** * Calculate the attenuation correction factor the volume given a start and * end point. * @param rng A reference to a PseudoRandomNumberGenerator producing * random number between [0,1] * @param startPos Origin of the initial track * @param endPos Final position of neutron after scattering (assumed to be * outside of the "volume") * @param lambdaBefore Wavelength, in \f$\\A^-1\f$, before scattering * @param lambdaAfter Wavelength, in \f$\\A^-1\f$, after scattering * @return The fraction of the beam that has been attenuated. A negative number * indicates the track was not valid. */ double MCInteractionVolume::calculateAbsorption( Kernel::PseudoRandomNumberGenerator &rng, const Kernel::V3D &startPos, const Kernel::V3D &endPos, double lambdaBefore, double lambdaAfter) const { // Generate scatter point. If there is an environment present then // first select whether the scattering occurs on the sample or the // environment. The attenuation for the path leading to the scatter point // is calculated in reverse, i.e. defining the track from the scatter pt // backwards for simplicity with how the Track object works. This avoids // having to understand exactly which object the scattering occurred in. V3D scatterPos; if (m_env && (rng.nextValue() > 0.5)) { scatterPos = m_env->generatePoint(rng, m_activeRegion, MAX_SCATTER_ATTEMPTS); } else { scatterPos = m_sample.generatePointInObject(rng, m_activeRegion, MAX_SCATTER_ATTEMPTS); } auto toStart = startPos - scatterPos; toStart.normalize(); Track beforeScatter(scatterPos, toStart); int nlinks = m_sample.interceptSurface(beforeScatter); if (m_env) { nlinks += m_env->interceptSurfaces(beforeScatter); } // This should not happen but numerical precision means that it can // occasionally occur with tracks that are very close to the surface if (nlinks == 0) { return -1.0; } // Function to calculate total attenuation for a track auto calculateAttenuation = [](const Track &path, double lambda) { double factor(1.0); for (const auto &segment : path) { const double length = segment.distInsideObject; const auto &segObj = *(segment.object); const auto &segMat = segObj.material(); factor *= attenuation(segMat.numberDensity(), segMat.totalScatterXSection(lambda) + segMat.absorbXSection(lambda), length); } return factor; }; // Now track to final destination V3D scatteredDirec = endPos - scatterPos; scatteredDirec.normalize(); Track afterScatter(scatterPos, scatteredDirec); m_sample.interceptSurface(afterScatter); if (m_env) { m_env->interceptSurfaces(afterScatter); } return calculateAttenuation(beforeScatter, lambdaBefore) * calculateAttenuation(afterScatter, lambdaAfter); }
/** * Return a random point in a generic shape limited by a bounding box. * @param object an object in which the point is generated * @param rng a random number generator * @param box a box restricting the point's volume * @param maxAttempts number of attempts to find a suitable point * @return a point or none if maxAttempts was exceeded */ boost::optional<Kernel::V3D> bounded(const IObject &object, Kernel::PseudoRandomNumberGenerator &rng, const BoundingBox &box, size_t maxAttempts) { boost::optional<Kernel::V3D> point{boost::none}; if (box.isNull()) { throw std::invalid_argument( "Invalid bounding box. Cannot generate random point."); } for (size_t attempts{0}; attempts < maxAttempts; ++attempts) { const double r1 = rng.nextValue(); const double r2 = rng.nextValue(); const double r3 = rng.nextValue(); auto pt = box.generatePointInside(r1, r2, r3); if (object.isValid(pt)) { point = pt; break; } }; return point; }