Skip to content

Commit

Permalink
feat: Cuboidal detector builder from blueprint (#2887)
Browse files Browse the repository at this point in the history
Adding the Unit Test for the Cuboidal Container Builder. The Unit Test creates a simple Telescope-style geometry with two arms and four layers from a Blueprint. The Builder chain is tested to reproduce the detector with expected parameters. 

Changes in BlueprintHelper are made to accommodate the Cuboidal geometry. The old fillGaps method is separated into fillGapsCylindrical, fillGapsCuboidal and the interface method that calls the specific implementations. Modifications are made in the endPoint finding function to accommodate not only Z, but also X and Y directions. It should be noted that without adjustToParent flag being set to true, gaps are not guaranteed to be filled properly.  

A small fix related to the selection of the Portal volume attachment direction is added to the CuboidalDetectorHelper. Now it is possible to connect containers and create >1D connected structures. It should be noted that the portal merging is not yet consistent between the volume and container connections.
  • Loading branch information
ssdetlab authored Jan 22, 2024
1 parent 390dd30 commit ccd2d3e
Show file tree
Hide file tree
Showing 7 changed files with 620 additions and 133 deletions.
1 change: 1 addition & 0 deletions CI/codespell_ignore.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ ans
dthe
dthe
vart
pixelx
14 changes: 12 additions & 2 deletions Core/include/Acts/Detector/detail/BlueprintHelper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,20 @@ void sort(Blueprint::Node& node, bool recursive = true);
///
/// @param node the node for with the gaps should be filled
/// @param adjustToParent nodes, if nodes should be adjusted to parent
///
/// @note currently only cylindrical volumes are supported
void fillGaps(Blueprint::Node& node, bool adjustToParent = true);

/// @brief Fill the gaps in the Cylindrical container node
///
/// @param node the node for with the gaps should be filled
/// @param adjustToParent nodes, if nodes should be adjusted to parent
void fillGapsCylindrical(Blueprint::Node& node, bool adjustToParent = true);

/// @brief Fill the gaps in the Cuboidal container node
///
/// @param node the node for with the gaps should be filled
/// @param adjustToParent nodes, if nodes should be adjusted to parent
void fillGapsCuboidal(Blueprint::Node& node, bool adjustToParent = true);

} // namespace BlueprintHelper
} // namespace detail
} // namespace Experimental
Expand Down
327 changes: 219 additions & 108 deletions Core/src/Detector/detail/BlueprintHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,27 @@

namespace {

std::array<Acts::Vector3, 2u> cylEndpointsZ(
const Acts::Experimental::Blueprint::Node& node) {
Acts::Vector3 axisZ = node.transform.rotation().col(2);
auto halfZ = node.boundaryValues[2];
std::array<Acts::Vector3, 2u> endPointsXYZ(
const Acts::Experimental::Blueprint::Node& node, Acts::BinningValue bVal) {
unsigned int bIdx = 0;
switch (bVal) {
case Acts::binX:
bIdx = 0;
break;
case Acts::binY:
bIdx = 1;
break;
case Acts::binZ:
bIdx = 2;
break;
default:
break;
}
Acts::Vector3 axis = node.transform.rotation().col(bIdx);
auto halfL = node.boundaryValues[bIdx];
Acts::Vector3 center = node.transform.translation();
Acts::Vector3 p0 = center - halfZ * axisZ;
Acts::Vector3 p1 = center + halfZ * axisZ;
Acts::Vector3 p0 = center - halfL * axis;
Acts::Vector3 p1 = center + halfL * axis;
return {p0, p1};
}

Expand Down Expand Up @@ -69,118 +83,215 @@ void Acts::Experimental::detail::BlueprintHelper::fillGaps(
if (node.isLeaf()) {
return;
}
if (node.boundsType == VolumeBounds::eCylinder && node.binning.size() == 1) {
fillGapsCylindrical(node, adjustToParent);
} else if (node.boundsType == VolumeBounds::eCuboid &&
node.binning.size() == 1) {
// Doesn't look like NOT adjusting to parent
// makes sense. The gaps are not going
// to be filled in non-binned directions
fillGapsCuboidal(node, adjustToParent);
} else {
throw std::runtime_error(
"BlueprintHelper: gap filling is not implemented for "
"this boundary type");
}
}

if (node.boundsType == VolumeBounds::eCylinder) {
// Nodes must be sorted
sort(node, false);

// Container values
auto cInnerR = node.boundaryValues[0];
auto cOuterR = node.boundaryValues[1];
auto cHalfZ = node.boundaryValues[2];

std::vector<std::unique_ptr<Blueprint::Node>> gaps;
// Only 1D binning implemented for the moment
if (node.binning.size() == 1) {
auto bVal = node.binning.front();
if (bVal == binZ) {
// adjust inner/outer radius
if (adjustToParent) {
std::for_each(node.children.begin(), node.children.end(),
[&](auto& child) {
child->boundaryValues[0] = cInnerR;
child->boundaryValues[1] = cOuterR;
});
}
auto [negC, posC] = cylEndpointsZ(node);
// Assume sorted along the local z axis
unsigned int igap = 0;
for (auto& child : node.children) {
auto [neg, pos] = cylEndpointsZ(*child);
ActsScalar gapSpan = (neg - negC).norm();
if (gapSpan > s_onSurfaceTolerance) {
// Fill a gap node
auto gapName = node.name + "_gap_" + std::to_string(igap);
auto gapTransform = Transform3::Identity();
gapTransform.rotate(node.transform.rotation());
gapTransform.translate(0.5 * (neg + negC));
auto gap = std::make_unique<Blueprint::Node>(
gapName, gapTransform, VolumeBounds::eCylinder,
std::vector<ActsScalar>{cInnerR, cOuterR, 0.5 * gapSpan});
gaps.push_back(std::move(gap));
++igap;
}
// Set to new current negative value
negC = pos;
}
// Check if a last one needs to be filled
ActsScalar gapSpan = (negC - posC).norm();
if (gapSpan > s_onSurfaceTolerance) {
// Fill a gap node
auto gapName = node.name + "_gap_" + std::to_string(igap);
auto gapTransform = Transform3::Identity();
gapTransform.rotate(node.transform.rotation());
gapTransform.translate(0.5 * (negC + posC));
auto gap = std::make_unique<Blueprint::Node>(
gapName, gapTransform, VolumeBounds::eCylinder,
std::vector<ActsScalar>{cInnerR, cOuterR, 0.5 * gapSpan});
gaps.push_back(std::move(gap));
}
void Acts::Experimental::detail::BlueprintHelper::fillGapsCylindrical(
Blueprint::Node& node, bool adjustToParent) {
// Nodes must be sorted
sort(node, false);

} else if (bVal == binR) {
// We have binning in R present
if (adjustToParent) {
std::for_each(node.children.begin(), node.children.end(),
[&](auto& child) {
child->transform = node.transform;
child->boundaryValues[2] = cHalfZ;
});
}
// Fill the gaps in R
unsigned int igap = 0;
ActsScalar lastR = cInnerR;
for (auto& child : node.children) {
ActsScalar iR = child->boundaryValues[0];
if (std::abs(iR - lastR) > s_onSurfaceTolerance) {
auto gap = std::make_unique<Blueprint::Node>(
node.name + "_gap_" + std::to_string(igap), node.transform,
VolumeBounds::eCylinder,
std::vector<ActsScalar>{lastR, iR, cHalfZ});
gaps.push_back(std::move(gap));
++igap;
}
// Set to new outer radius
lastR = child->boundaryValues[1];
}
// Check if a last one needs to be filled
if (std::abs(lastR - cOuterR) > s_onSurfaceTolerance) {
auto gap = std::make_unique<Blueprint::Node>(
node.name + "_gap_" + std::to_string(igap), node.transform,
VolumeBounds::eCylinder,
std::vector<ActsScalar>{lastR, cOuterR, cHalfZ});
gaps.push_back(std::move(gap));
}
} else {
throw std::runtime_error(
"BlueprintHelper: gap filling not implemented for "
"cylinder and this binning type.");
// Container values
auto cInnerR = node.boundaryValues[0];
auto cOuterR = node.boundaryValues[1];
auto cHalfZ = node.boundaryValues[2];

std::vector<std::unique_ptr<Blueprint::Node>> gaps;
// Only 1D binning implemented for the moment
auto bVal = node.binning.front();
if (bVal == binZ) {
// adjust inner/outer radius
if (adjustToParent) {
std::for_each(node.children.begin(), node.children.end(),
[&](auto& child) {
child->boundaryValues[0] = cInnerR;
child->boundaryValues[1] = cOuterR;
});
}
auto [negC, posC] = endPointsXYZ(node, bVal);
// Assume sorted along the local z axis
unsigned int igap = 0;
for (auto& child : node.children) {
auto [neg, pos] = endPointsXYZ(*child, bVal);
ActsScalar gapSpan = (neg - negC).norm();
if (gapSpan > s_onSurfaceTolerance) {
// Fill a gap node
auto gapName = node.name + "_gap_" + std::to_string(igap);
auto gapTransform = Transform3::Identity();
gapTransform.rotate(node.transform.rotation());
gapTransform.translate(0.5 * (neg + negC));
auto gap = std::make_unique<Blueprint::Node>(
gapName, gapTransform, VolumeBounds::eCylinder,
std::vector<ActsScalar>{cInnerR, cOuterR, 0.5 * gapSpan});
gaps.push_back(std::move(gap));
++igap;
}
// Set to new current negative value
negC = pos;
}
// Insert
for (auto& gap : gaps) {
node.add(std::move(gap));
// Check if a last one needs to be filled
ActsScalar gapSpan = (negC - posC).norm();
if (gapSpan > s_onSurfaceTolerance) {
// Fill a gap node
auto gapName = node.name + "_gap_" + std::to_string(igap);
auto gapTransform = Transform3::Identity();
gapTransform.rotate(node.transform.rotation());
gapTransform.translate(0.5 * (negC + posC));
auto gap = std::make_unique<Blueprint::Node>(
gapName, gapTransform, VolumeBounds::eCylinder,
std::vector<ActsScalar>{cInnerR, cOuterR, 0.5 * gapSpan});
gaps.push_back(std::move(gap));
}

// Sort again after inserting
sort(node, false);
// Fill the gaps recursively
} else if (bVal == binR) {
// We have binning in R present
if (adjustToParent) {
std::for_each(node.children.begin(), node.children.end(),
[&](auto& child) {
child->transform = node.transform;
child->boundaryValues[2] = cHalfZ;
});
}
// Fill the gaps in R
unsigned int igap = 0;
ActsScalar lastR = cInnerR;
for (auto& child : node.children) {
fillGaps(*child, adjustToParent);
ActsScalar iR = child->boundaryValues[0];
if (std::abs(iR - lastR) > s_onSurfaceTolerance) {
auto gap = std::make_unique<Blueprint::Node>(
node.name + "_gap_" + std::to_string(igap), node.transform,
VolumeBounds::eCylinder,
std::vector<ActsScalar>{lastR, iR, cHalfZ});
gaps.push_back(std::move(gap));
++igap;
}
// Set to new outer radius
lastR = child->boundaryValues[1];
}
// Check if a last one needs to be filled
if (std::abs(lastR - cOuterR) > s_onSurfaceTolerance) {
auto gap = std::make_unique<Blueprint::Node>(
node.name + "_gap_" + std::to_string(igap), node.transform,
VolumeBounds::eCylinder,
std::vector<ActsScalar>{lastR, cOuterR, cHalfZ});
gaps.push_back(std::move(gap));
}

} else {
throw std::runtime_error(
"BlueprintHelper: gap filling not implemented for "
"this boundary type");
"cylinder and this binning type.");
}

// Insert
for (auto& gap : gaps) {
node.add(std::move(gap));
}

// Sort again after inserting
sort(node, false);
// Fill the gaps recursively
for (auto& child : node.children) {
fillGaps(*child, adjustToParent);
}
}

void Acts::Experimental::detail::BlueprintHelper::fillGapsCuboidal(
Blueprint::Node& node, bool adjustToParent) {
// Nodes must be sorted
sort(node, false);

// Cuboidal detector binnings
std::array<Acts::BinningValue, 3u> allowedBinVals = {binX, binY, binZ};

std::vector<std::unique_ptr<Blueprint::Node>> gaps;
auto binVal = node.binning.front();

// adjust non-binned directions
if (adjustToParent) {
std::for_each(node.children.begin(), node.children.end(), [&](auto& child) {
for (auto bv : allowedBinVals) {
if (bv != binVal) {
// Both boundary values and translation
// have to be adjusted
child->boundaryValues[bv] = node.boundaryValues[bv];
child->transform.translation()[bv] = node.transform.translation()[bv];
}
}
});
}
auto [negC, posC] = endPointsXYZ(node, binVal);

// Assume sorted along the local binned axis
unsigned int igap = 0;
for (auto& child : node.children) {
auto [neg, pos] = endPointsXYZ(*child, binVal);
if (neg[binVal] < negC[binVal]) {
throw std::runtime_error("BlueprintHelper: Overlap detected for child '" +
child->name + "' of node '" + node.name + "'");
}
ActsScalar gapSpan = (neg - negC).norm();
if (gapSpan > s_onSurfaceTolerance) {
// Fill a gap node
auto gapName = node.name + "_gap_" + std::to_string(igap);
auto gapTransform = Transform3::Identity();
gapTransform.rotate(node.transform.rotation());
gapTransform.translate(0.5 * (neg + negC));
std::vector<ActsScalar> gapBounds{0, 0, 0};
gapBounds[binVal] = 0.5 * gapSpan;
for (auto bv : allowedBinVals) {
if (bv != binVal) {
gapBounds[bv] = node.boundaryValues[bv];
}
}
auto gap = std::make_unique<Blueprint::Node>(
gapName, gapTransform, VolumeBounds::eCuboid, gapBounds);
gaps.push_back(std::move(gap));
++igap;
}
// Set to new current negative value
negC = pos;
}
// Check if a last one needs to be filled
ActsScalar gapSpan = (negC - posC).norm();
if (gapSpan > s_onSurfaceTolerance) {
// Fill a gap node
auto gapName = node.name + "_gap_" + std::to_string(igap);
auto gapTransform = Transform3::Identity();
gapTransform.rotate(node.transform.rotation());
gapTransform.translate(0.5 * (negC + posC));
std::vector<ActsScalar> gapBounds{0, 0, 0};
gapBounds[binVal] = 0.5 * gapSpan;
for (auto bv : allowedBinVals) {
if (bv != binVal) {
gapBounds[bv] = node.boundaryValues[bv];
}
}
auto gap = std::make_unique<Blueprint::Node>(
gapName, gapTransform, VolumeBounds::eCuboid, gapBounds);
gaps.push_back(std::move(gap));
}

// Insert
for (auto& gap : gaps) {
node.add(std::move(gap));
}

// Sort again after inserting
sort(node, false);
// Fill the gaps recursively
for (auto& child : node.children) {
fillGaps(*child, adjustToParent);
}
}
11 changes: 8 additions & 3 deletions Core/src/Detector/detail/CuboidalDetectorHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,15 @@ Acts::Experimental::detail::CuboidalDetectorHelper::connect(
auto portalSurface =
Surface::makeShared<PlaneSurface>(portalTransform, portalBounds);
auto portal = std::make_shared<Portal>(portalSurface);

// Assign the portal direction
// in a consistent way
Acts::Direction dir =
(index % 2 == 0) ? Direction::Forward : Direction::Backward;

// Make the stitch boundaries
pReplacements.push_back(
PortalReplacement(portal, index, Direction::Backward,
stitchBoundaries, (mergedInX ? binX : binY)));
pReplacements.push_back(PortalReplacement(
portal, index, dir, stitchBoundaries, (mergedInX ? binX : binY)));
}
}

Expand Down
Loading

0 comments on commit ccd2d3e

Please sign in to comment.