Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable calorimeter hit merging by functions #1668

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
73dd28e
Add merge matrix to configuration + initialization
ruse-traveler Nov 5, 2024
edf5174
Add hit merger to BHCal plugin
ruse-traveler Nov 5, 2024
9d145b5
Fill in merge-map-building using provided mappings
ruse-traveler Nov 5, 2024
54aa6b0
Add test case in the BHCal (merging towers-into-tiles)
ruse-traveler Nov 5, 2024
4e9f901
Fix compile time errors
ruse-traveler Nov 6, 2024
0155a7b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2024
903fe3e
Add debugging statements
ruse-traveler Nov 6, 2024
b650a62
Merge branch 'add-merge-matrix-to-hit-merger' of github.com:eic/EICre…
ruse-traveler Nov 6, 2024
19079b5
Fix seg fault by tying mappings to fields
ruse-traveler Nov 6, 2024
2930d2c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Nov 6, 2024
6586f07
Implement expression workaround
ruse-traveler Nov 7, 2024
94b4395
Merge branch 'add-merge-matrix-to-hit-merger' of github.com:eic/EICre…
ruse-traveler Nov 7, 2024
b346c13
Add helper methods to consistently make phi mapping + adjacency matrix
ruse-traveler Nov 13, 2024
7bf1b8c
Merge branch 'main' of github.com:eic/EICrecon into add-merge-matrix-…
ruse-traveler Nov 14, 2024
a6d8617
Finish wiring in merged hits
ruse-traveler Nov 14, 2024
eba0c58
Clean up WIP comments
ruse-traveler Nov 14, 2024
174e38f
Remove spurious line break
ruse-traveler Nov 14, 2024
ec9c5ec
Merge branch 'main' into add-merge-matrix-to-hit-merger
ruse-traveler Dec 19, 2024
3b6e445
Move mapping/matrix makers upstream
ruse-traveler Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 98 additions & 20 deletions src/algorithms/calorimetry/CalorimeterHitsMerger.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (C) 2022 Chao Peng, Jihee Kim, Sylvester Joosten, Whitney Armstrong, Wouter Deconinck, David Lawrence
// Copyright (C) 2022 Chao Peng, Jihee Kim, Sylvester Joosten, Whitney Armstrong, Wouter Deconinck, David Lawrence, Derek Anderson

/*
* An algorithm to group readout hits from a calorimeter
Expand All @@ -12,16 +12,17 @@

#include <DD4hep/Alignments.h>
#include <DD4hep/DetElement.h>
#include <DD4hep/IDDescriptor.h>
#include <DD4hep/Objects.h>
#include <DD4hep/Readout.h>
#include <DD4hep/VolumeManager.h>
#include <DDSegmentation/BitFieldCoder.h>
#include <edm4eic/CalorimeterHit.h>
#include <Evaluator/DD4hepUnits.h>
#include <Math/GenVector/Cartesian3D.h>
#include <Math/GenVector/DisplacementVector3D.h>
#include <fmt/core.h>
#include <algorithm>
#include <algorithms/service.h>
#include <cmath>
#include <cstddef>
#include <gsl/pointers>
Expand All @@ -31,6 +32,7 @@
#include <vector>

#include "algorithms/calorimetry/CalorimeterHitsMergerConfig.h"
#include "services/evaluator/EvaluatorSvc.h"

namespace eicrecon {

Expand All @@ -41,24 +43,57 @@ void CalorimeterHitsMerger::init() {
return;
}

// initialize descriptor + decoders
try {
auto id_desc = m_detector->readout(m_cfg.readout).idSpec();
id_mask = 0;
std::vector<std::pair<std::string, int>> ref_fields;
for (size_t i = 0; i < m_cfg.fields.size(); ++i) {
id_mask |= id_desc.field(m_cfg.fields[i])->mask();
// use the provided id number to find ref cell, or use 0
int ref = i < m_cfg.refs.size() ? m_cfg.refs[i] : 0;
ref_fields.emplace_back(m_cfg.fields[i], ref);
}
ref_mask = id_desc.encode(ref_fields);
id_desc = m_detector->readout(m_cfg.readout).idSpec();
id_decoder = id_desc.decoder();
for (const auto& field : m_cfg.fields) {
const short index = id_decoder->index(field);
}
} catch (...) {
auto mess = fmt::format("Failed to load ID decoder for {}", m_cfg.readout);
warning(mess);
auto mess = fmt::format("Failed to load ID decoder for {}", m_cfg.readout);
warning(mess);
// throw std::runtime_error(mess);
}
id_mask = ~id_mask;
debug("ID mask in {:s}: {:#064b}", m_cfg.readout, id_mask);

// if no field-by-field mappings provided, initialize relevant bitmasks
// otherwise intialize relevant functionals
if (m_cfg.mappings.empty()) {
id_mask = 0;
std::vector<RefField> ref_fields;
for (size_t i = 0; i < m_cfg.fields.size(); ++i) {
id_mask |= id_desc.field(m_cfg.fields[i])->mask();
// use the provided id number to find ref cell, or use 0
int ref = i < m_cfg.refs.size() ? m_cfg.refs[i] : 0;
ref_fields.emplace_back(m_cfg.fields[i], ref);
}
ref_mask = id_desc.encode(ref_fields);
id_mask = ~id_mask;
debug("ID mask in {:s}: {:#064b}", m_cfg.readout, id_mask);
Comment on lines +62 to +72
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put mask operations as closures to the same ref_maps, so that the application of transofrmation is uniform?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite follow... So by closure do you mean if a user provides both a bitmask and a mapping, they both get applied?

Copy link
Member

@veprbl veprbl Nov 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allow me to clarify. I meant that this code can be replaced with something like

for (size_t ix = 0; std::string &field : m_cfg.fields) {
  uint64_t ref = m_cfg.refs.at(ix);
  ref_maps[field] = [ref](const edm4eic::CalorimeterHit&) -> int {
    return ref;
  };
  ix++;
}

Then we can remove all the different logic for handling ref_mask and id_mask.

Copy link
Member

@veprbl veprbl Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this it's more like:

for (size_t ix = 0; std::string &field : m_cfg.fields) {
  uint64_t ref_mask = m_cfg.refs.at(ix);
  auto field_element = id_desc.field(m_cfg.fields[i]);
  ref_maps[field] = [ref_mask,field_element](const edm4eic::CalorimeterHit& hit) -> int {
    return (field_element->value(hit.cellID()) & ~ref_mask) | ref_mask;
  };
  ix++;
}

} else {

// lambda to translate IDDescriptor fields into function parameters
std::function hit_to_map = [this](const edm4eic::CalorimeterHit& hit) {
std::unordered_map<std::string, double> params;
for (const auto& name_field : id_desc.fields()) {
params.emplace(name_field.first, name_field.second->value(hit.getCellID()));
trace("{} = {}", name_field.first, name_field.second->value(hit.getCellID()));
}
return params;
};

// intialize functions
// - NOTE this assumes provided fields are 1-to-1!
auto& svc = algorithms::ServiceSvc::instance();
for (std::size_t iMap = 0; const auto& mapping : m_cfg.mappings) {
if (iMap < m_cfg.fields.size()) {
Comment on lines +88 to +89
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also breaks my brain a bit. A check for matching length and a 1-to-1 loop would be nicer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! This was trying to work in the above design choice that providing a map makes the algorithm ignores any provided bitmasks.

ref_maps[m_cfg.fields.at(iMap)] = svc.service<EvaluatorSvc>("EvaluatorSvc")->compile(mapping, hit_to_map);
trace("Mapping for field {} = {}", m_cfg.fields.at(iMap), mapping);
}
++iMap;
} // end loop over mappings
}

}

void CalorimeterHitsMerger::process(
Expand All @@ -69,14 +104,17 @@ void CalorimeterHitsMerger::process(
auto [out_hits] = output;

// find the hits that belong to the same group (for merging)
std::unordered_map<uint64_t, std::vector<std::size_t>> merge_map;
std::size_t ix = 0;
for (const auto &h : *in_hits) {
MergeMap merge_map;
if (m_cfg.mappings.empty()) {
for (std::size_t ix = 0; const auto &h : *in_hits) {
uint64_t id = h.getCellID() & id_mask;
merge_map[id].push_back(ix);

ix++;
}
} else {
build_map_via_funcs(in_hits,merge_map);
}
debug("Merge map built: merging {} hits into {} merged hits", in_hits->size(), merge_map.size());

// sort hits by energy from large to small
for (auto &it : merge_map) {
Expand Down Expand Up @@ -140,4 +178,44 @@ void CalorimeterHitsMerger::process(
debug("Size before = {}, after = {}", in_hits->size(), out_hits->size());
}

void CalorimeterHitsMerger::build_map_via_funcs(
const edm4eic::CalorimeterHitCollection* in_hits,
MergeMap& merge_map
) const {

std::vector<RefField> ref_fields;
for (std::size_t iHit = 0; const auto& hit : *in_hits) {

// make sure vector is clear
ref_fields.clear();
for (std::size_t iField = 0; const auto& name_field : id_desc.fields()) {

// check if field has associated mapping
const bool foundMapping = (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks a bit complicated, and I doubt that it actually works for mixing of "mappings" transformations and "refs" bitmasks. Could it be simplified assuming 1-to-1 loop?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, as-is it's set up so that if a user provides a mapping, it defaults to using that and will ignore any provided bitmasks. But it can definitely be just turned into a 1-to-1 loop where it checks for the mapping or the bitmask.

std::find(m_cfg.fields.begin(), m_cfg.fields.end(), name_field.first) != m_cfg.fields.end()
);

// if mapping provided for field, apply it
// otherwise just copy index
if (foundMapping) {
ref_fields.push_back(
{name_field.first, ref_maps[name_field.first](hit)}
);
} else {
ref_fields.push_back(
{name_field.first, id_decoder->get(hit.getCellID(), name_field.first)}
);
}
++iField;
}
const uint64_t ref_id = id_desc.encode(ref_fields);

// add hit to appropriate group
merge_map[ref_id].push_back(iHit);
++iHit;

} // end hit loop

} // end 'build_map_via_funcs(edm4eic::CalorimeterHitsCollection*, MergeMap&)'

} // namespace eicrecon
21 changes: 20 additions & 1 deletion src/algorithms/calorimetry/CalorimeterHitsMerger.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (C) 2022 Chao Peng, Jihee Kim, Sylvester Joosten, Whitney Armstrong, Wouter Deconinck, David Lawrence
// Copyright (C) 2022 Chao Peng, Jihee Kim, Sylvester Joosten, Whitney Armstrong, Wouter Deconinck, David Lawrence, Derek Anderson

/*
* An algorithm to group readout hits from a calorimeter
Expand All @@ -10,21 +10,32 @@

#pragma once

#include <DD4hep/BitFieldCoder.h>
#include <DD4hep/Detector.h>
#include <DD4hep/IDDescriptor.h>
#include <DDRec/CellIDPositionConverter.h>
#include <algorithms/algorithm.h>
#include <algorithms/geo.h>
#include <edm4eic/CalorimeterHitCollection.h>
#include <stdint.h>
#include <functional>
#include <gsl/pointers>
#include <map>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>

#include "CalorimeterHitsMergerConfig.h"
#include "algorithms/interfaces/WithPodConfig.h"

namespace eicrecon {

// aliases for convenience
using MergeMap = std::unordered_map<uint64_t, std::vector<std::size_t>>;
using RefField = std::pair<std::string, int>;
using MapFunc = std::function<int(const edm4eic::CalorimeterHit&)>;

using CalorimeterHitsMergerAlgorithm = algorithms::Algorithm<
algorithms::Input<
edm4eic::CalorimeterHitCollection
Expand All @@ -51,10 +62,18 @@ namespace eicrecon {
private:
uint64_t id_mask{0}, ref_mask{0};

private:
mutable std::map<std::string, MapFunc> ref_maps;
dd4hep::IDDescriptor id_desc;
dd4hep::BitFieldCoder* id_decoder;

private:
const dd4hep::Detector* m_detector{algorithms::GeoSvc::instance().detector()};
const dd4hep::rec::CellIDPositionConverter* m_converter{algorithms::GeoSvc::instance().cellIDPositionConverter()};

private:
void build_map_via_funcs(const edm4eic::CalorimeterHitCollection* in_hits, MergeMap& merge_map) const;

};

} // namespace eicrecon
1 change: 1 addition & 0 deletions src/algorithms/calorimetry/CalorimeterHitsMergerConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace eicrecon {
std::string readout{""};
std::vector<std::string> fields{};
std::vector<int> refs{};
std::vector<std::string> mappings{};

};

Expand Down
92 changes: 80 additions & 12 deletions src/detectors/BHCAL/BHCAL.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,66 @@
#include "factories/calorimetry/CalorimeterClusterRecoCoG_factory.h"
#include "factories/calorimetry/CalorimeterHitDigi_factory.h"
#include "factories/calorimetry/CalorimeterHitReco_factory.h"
#include "factories/calorimetry/CalorimeterHitsMerger_factory.h"
#include "factories/calorimetry/CalorimeterIslandCluster_factory.h"
#include "factories/calorimetry/CalorimeterTruthClustering_factory.h"
#include "factories/calorimetry/TrackClusterMergeSplitter_factory.h"

namespace eicrecon {

// --------------------------------------------------------------------------
// Helper method to generate appropriate phi map
// --------------------------------------------------------------------------
std::string MakePhiMap(const std::size_t nMerge = 1) {
ruse-traveler marked this conversation as resolved.
Show resolved Hide resolved

// convert num to merge to string
const std::string sNum = std::to_string(nMerge);

// construct mapping
std::string map = "";
if (nMerge > 1) {
map = "phi-(" + sNum + "*((phi/" + sNum + ")-floor(phi/" + sNum + ")))";
} else {
map = "phi";
}
return map;

} // end 'MakePhiMap(std::size_t)'

// --------------------------------------------------------------------------
// Helper method to generate appropriate adjacency matrix
// --------------------------------------------------------------------------
std::string MakeAdjacencyMatrix(const std::size_t nMerge = 1) {

// set up checks at wraparound.
// magic constants:
// 320 - number of tiles per row
const std::string sMerge = std::to_string(nMerge);
const std::string wrapDef = "(abs(phi_1 - phi_2) == (320 - 1))";
const std::string wrapMerge = "(abs(320 - abs(phi_1 - phi_2)) <= " + sMerge + ")";

// combine strings into horizontal adjacency conditions
const std::string phiAdjacent = "(abs(phi_1 - phi_2) == " + sMerge + ")";
const std::string wrapAdjacent = nMerge > 1 ? wrapMerge : wrapDef;
ruse-traveler marked this conversation as resolved.
Show resolved Hide resolved

// put everything together
std::string matrix("");
matrix += "(";
// check for vertically adjacent tiles
matrix += " ( (abs(eta_1 - eta_2) == 1) && (abs(phi_1 - phi_2) == 0) ) ||";
// check for horizontally adjacent tiles
matrix += " ( (abs(eta_1 - eta_2) == 0) && " + phiAdjacent + " ) ||";
// check for horizontally adjacent tiles at wraparound
matrix += " ( (abs(eta_1 - eta_2) == 0) && " + wrapAdjacent + " )";
matrix += ") == 1";
return matrix;

} // end 'MakeAdjacencyMatrix(std::size_t)'

}



extern "C" {

void InitPlugin(JApplication *app) {
Expand All @@ -31,17 +87,15 @@ extern "C" {
decltype(CalorimeterHitDigiConfig::pedSigmaADC) HcalBarrel_pedSigmaADC = 2;
decltype(CalorimeterHitDigiConfig::resolutionTDC) HcalBarrel_resolutionTDC = 1 * dd4hep::picosecond;

// Set default adjacency matrix. Magic constants:
// 320 - number of tiles per row
decltype(CalorimeterIslandClusterConfig::adjacencyMatrix) HcalBarrel_adjacencyMatrix =
"("
// check for vertically adjacent tiles
" ( (abs(eta_1 - eta_2) == 1) && (abs(phi_1 - phi_2) == 0) ) ||"
// check for horizontally adjacent tiles
" ( (abs(eta_1 - eta_2) == 0) && (abs(phi_1 - phi_2) == 1) ) ||"
// check for horizontally adjacent tiles at wraparound
" ( (abs(eta_1 - eta_2) == 0) && (abs(phi_1 - phi_2) == (320 - 1)) )"
") == 1";
// ---------------------------------------------------------------------
// if needed, merge adjacent tiles into a tower and adjust maps/matrices
// ---------------------------------------------------------------------
const std::size_t nPhiToMerge = 1;
const std::string phiMap = MakePhiMap(nPhiToMerge);
const std::string adjMatrix = MakeAdjacencyMatrix(nPhiToMerge);

// set matrix
decltype(CalorimeterIslandClusterConfig::adjacencyMatrix) HcalBarrel_adjacencyMatrix = adjMatrix;

app->Add(new JOmniFactoryGeneratorT<CalorimeterHitDigi_factory>(
"HcalBarrelRawHits",
Expand All @@ -66,6 +120,7 @@ extern "C" {
},
app // TODO: Remove me once fixed
));

app->Add(new JOmniFactoryGeneratorT<CalorimeterHitReco_factory>(
"HcalBarrelRecHits", {"HcalBarrelRawHits"}, {"HcalBarrelRecHits"},
{
Expand All @@ -83,12 +138,24 @@ extern "C" {
},
app // TODO: Remove me once fixed
));

app->Add(new JOmniFactoryGeneratorT<CalorimeterHitsMerger_factory>(
"HcalBarrelMergedHits", {"HcalBarrelRecHits"}, {"HcalBarrelMergedHits"},
{
.readout = "HcalBarrelHits",
.fields = {"phi"},
.mappings = {phiMap}
},
app // TODO: Remove me once fixed
));

app->Add(new JOmniFactoryGeneratorT<CalorimeterTruthClustering_factory>(
"HcalBarrelTruthProtoClusters", {"HcalBarrelRecHits", "HcalBarrelHits"}, {"HcalBarrelTruthProtoClusters"},
app // TODO: Remove me once fixed
));

app->Add(new JOmniFactoryGeneratorT<CalorimeterIslandCluster_factory>(
"HcalBarrelIslandProtoClusters", {"HcalBarrelRecHits"}, {"HcalBarrelIslandProtoClusters"},
"HcalBarrelIslandProtoClusters", {"HcalBarrelMergedHits"}, {"HcalBarrelIslandProtoClusters"},
{
.adjacencyMatrix = HcalBarrel_adjacencyMatrix,
.readout = "HcalBarrelHits",
Expand Down Expand Up @@ -179,5 +246,6 @@ extern "C" {
app // TODO: Remove me once fixed
)
);

}
}
1 change: 1 addition & 0 deletions src/factories/calorimetry/CalorimeterHitsMerger_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class CalorimeterHitsMerger_factory : public JOmniFactory<CalorimeterHitsMerger_
ParameterRef<std::string> m_readout {this, "readout", config().readout};
ParameterRef<std::vector<std::string>> m_fields {this, "fields", config().fields};
ParameterRef<std::vector<int>> m_refs {this, "refs", config().refs};
ParameterRef<std::vector<std::string>> m_mappings {this, "mappings", config().mappings};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mappings sounds a bit generic to me, and it doesn't self-describe that there should be as many mappings as fields. Should we call it something like field transformations or rewrites?

Suggested change
ParameterRef<std::vector<std::string>> m_mappings {this, "mappings", config().mappings};
ParameterRef<std::vector<std::string>> m_field_transformations {this, "fieldTransformations", config().field_transformations};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! I also like that name!


Service<AlgorithmsInit_service> m_algorithmsInit {this};

Expand Down
1 change: 1 addition & 0 deletions src/services/io/podio/JEventProcessorPODIO.cc
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ JEventProcessorPODIO::JEventProcessorPODIO() {
"LFHCALSplitMergeClusterAssociations",
"HcalBarrelRawHits",
"HcalBarrelRecHits",
"HcalBarrelMergedHits",
"HcalBarrelClusters",
"HcalBarrelClusterAssociations",
"HcalBarrelSplitMergeClusters",
Expand Down
Loading