Skip to content

Conversation

@sungbinoh
Copy link
Contributor

@sungbinoh sungbinoh commented Aug 27, 2025

Description

Adding phi and |E| for each hit for caf-level calorimetry
Related PR in sbnanaobj: PR151

  • Have you added a label? (bug/enhancement/physics etc.)
  • Have you assigned at least 1 reviewer?
  • Is this PR related to an open issue / project?
  • Does this PR affect CAF data format? If so, please assign a CAF maintainer as additional reviewer.
  • Does this PR require merging another PR in a different repository (such as sbnanobj/sbnobj etc.)? If so, please link it in the description.
  • Are you submitting this PR on behalf of someone else who made the code changes? If so, please mention them in the description.

MC BNB + cosmic flat.caf

  • phi
Screenshot 2025-08-27 at 10 10 47 AM
  • efield
Screenshot 2025-08-27 at 10 10 55 AM

Data BNB + light flat.caf

  • phi
Screenshot 2025-08-27 at 10 03 49 AM
  • efield
Screenshot 2025-08-27 at 10 03 59 AM

Data Offbeam + light flat.caf

  • phi
Screenshot 2025-08-27 at 10 06 32 AM
  • efield
Screenshot 2025-08-27 at 10 06 47 AM

Copy link
Member

@PetrilloAtWork PetrilloAtWork left a comment

Choose a reason for hiding this comment

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

Points to discuss:

  1. I oppose having TrackCaloSkimmer depend on CAFMaker. CAF making has no role in TrackCaloSkimmer. Having a shared type definition (caf::ParticleIDE) is good, but it should live either in Calibration library, or in its own place (and be in sbn namespace rather than caf).
  2. The definition of the electric field correction may imply a bug in the code. The result of an investigation on the matter should be written down as comment in the code.
  3. I ask to reconsider the unit of the angle phi. Having it in degrees means that every computation will need to convert it to radians. Personally I would consider to store its cosine instead, but if an angle is required, I believe it should be stored as radians.

I have left some additional points here and there, mostly C++ style and recommendations.

truehits[c].p.y = (truehits[c].p.y*old_elec + ide->y*ide->numElectrons) / new_elec;
truehits[c].p.z = (truehits[c].p.z*old_elec + ide->z*ide->numElectrons) / new_elec;

truehits[c].time = (truehits[c].time*old_elec + tick*ide->numElectrons) / new_elec;
Copy link
Member

Choose a reason for hiding this comment

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

This way of processing leaves me puzzled. I think it originates in 12-year old code that was written that way while interfacing with GEANT4 steps (so, at the same time when the true hits were being created) so that the information would be always valid. But here, in a single loop, it's just a waste times the 6 lines it's used in (7 now).
Not saying it's your duty to fix it, but it would be nice to.
Basically the alternative is either to use a sum in the look, and then divide once at the end, with a dedicated loop, or to use averaging objects (like lar::util::StatCollector and geo::vect::MiddlePointAccumulator).

Copy link
Contributor

Choose a reason for hiding this comment

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

This is written purposefully. The number of electrons can get quite large, so you get numerical stability issues if you do a weighted sum the naive way. (sum p_i * w_i / sum w_i). I'm not sure if you mean something else, but in any case I think this is ok as is.

Copy link
Member

@PetrilloAtWork PetrilloAtWork Sep 3, 2025

Choose a reason for hiding this comment

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

I do mean that.
So, you take truehits[c].time*old_elec + tick*ide->numElectrons, which I will call time_sum, you divide by new_elec, and then at the next loop you multiply again for new_elec (which in the meanwhile has become old_elec) to get time_sum again, and add a number.
How is that ensuring stability compared to storing old_time_sum all the way?

Copy link
Member

Choose a reason for hiding this comment

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

(I actually do have my answer, but it assumes the internal precision of the FPU stack/registers is larger than the output CPU registry, plus some compiler optimisation)

// Gets relative E field Distortions
geo::Vector_t EFieldOffsets = sce->GetEfieldOffsets(loc);
// Add 1 in X direction as this is the direction of the drift field
EFieldOffsets = EFieldOffsets + geo::Vector_t{1, 0, 0};
Copy link
Member

Choose a reason for hiding this comment

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

It is not clear from SpaceCharge::GetEfieldOffset() whether it returns a "relative" offset (that is, assuming that the field vector is pointing on positive axis) or an absolute one (that is, in detector reference). In the latter case, the expression to the full field would need to include the absolute direction of the nominal field at the location loc.
Please check this, and document the assumption adding a comment above this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you, added more words in the comments!

float totE;
};

struct ParticleIDE {
Copy link
Member

Choose a reason for hiding this comment

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

This class has not much to to with a particle. It has more to do with the TPC readout. Please consider a better name ("ReadoutIDE"? I am bad in finding names).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree! Updated the ParticleIDE to ReadoutIDE, I think that it is a good naming!

const std::vector<geo::BoxBoundedGeo> &active_volumes,
const std::vector<std::vector<geo::BoxBoundedGeo>> &tpc_volumes,
const std::map<int, std::vector<std::pair<geo::WireID, const sim::IDE*>>> id_to_ide_map,
const std::map<int, std::vector<caf::ParticleIDE>> id_to_ide_map,
Copy link
Member

Choose a reason for hiding this comment

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

Object parameters should be passed by reference unless there are explicit reasons to do otherwise ([CF-112]:

Suggested change
const std::map<int, std::vector<caf::ParticleIDE>> id_to_ide_map,
const std::map<int, std::vector<caf::ParticleIDE>>& id_to_ide_map,

Comment on lines 554 to 555
std::vector<caf::ParticleIDE> empty;
const std::vector<caf::ParticleIDE> &particle_ides = id_to_ide_map.count(particle.TrackId()) ? id_to_ide_map.at(particle.TrackId()) : empty;
Copy link
Member

Choose a reason for hiding this comment

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

The current implementation performs the same query twice, and that would be better avoided. A way to avoid it, wrapped in a lambda function:

Suggested change
std::vector<caf::ParticleIDE> empty;
const std::vector<caf::ParticleIDE> &particle_ides = id_to_ide_map.count(particle.TrackId()) ? id_to_ide_map.at(particle.TrackId()) : empty;
auto trackReadoutIDEs = [&id_to_ide_map](int trackID) -> std::vector<caf::ParticleIDE> const&
{
static std::vector<caf::ParticleIDE> const empty;
auto const itIDEs = id_to_ide_map.find(trackID);
return (itIDEs == id_to_ide_map.end())? empty: itIDEs->second;
};
std::vector<caf::ParticleIDE> const& particle_ides = trackReadoutIDEs(particle.TrackId());

This is a suggestion.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's ok as is -- this would be a small optimization at the cost of readability.

@sungbinoh
Copy link
Contributor Author

Points to discuss:

  1. I oppose having TrackCaloSkimmer depend on CAFMaker. CAF making has no role in TrackCaloSkimmer. Having a shared type definition (caf::ParticleIDE) is good, but it should live either in Calibration library, or in its own place (and be in sbn namespace rather than caf).
  2. The definition of the electric field correction may imply a bug in the code. The result of an investigation on the matter should be written down as comment in the code.
  3. I ask to reconsider the unit of the angle phi. Having it in degrees means that every computation will need to convert it to radians. Personally I would consider to store its cosine instead, but if an angle is required, I believe it should be stored as radians.

I have left some additional points here and there, mostly C++ style and recommendations.

Hi @PetrilloAtWork , Thank you! I have made several updates following the suggestions.
For points to discuss,

  1. I have moved the ReadoutIDE's definition into the TrackCaloSkimmer.h and in the sbn namespace. It is referred in CAFMAker's side.
  2. Have added additional texts to resolve the confusion
  3. I agree with your point but keeping it as degree to have the exactly same value provided by the GnocchiCalorimetry_module.cc. Converting it to radian should be simple.

Please also have a look to our responses to your additional points.
Thank you!!

Copy link
Member

@PetrilloAtWork PetrilloAtWork left a comment

Choose a reason for hiding this comment

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

Thank you for the answers. There still a clarification that I would like on the previous comments, but more important is that I noticed something I missed in the first review about the service management.
In short:

  • link list should only include service interfaces, not implementation (like the Standard ones)
  • loading of the service should be limited to once per produce() call.

Inline comments explain more.

larcorealg::Geometry
larcore::Geometry_Geometry_service
larcorealg::GeoAlgo
lardata::DetectorInfoServices_DetectorPropertiesServiceStandard_service
Copy link
Member

Choose a reason for hiding this comment

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

Missed this... This library should not be necessary — if it is, there is a problem.
Try with:

Suggested change
lardata::DetectorInfoServices_DetectorPropertiesServiceStandard_service
lardataalg::headers

(also, make sure it's appropriately indented) for the library, and add

               lardata::DetectorPropertiesService

below (e.g. around line 69) for the module.
Rationale: DetectorPropertiesServiceStandard is an implementation of a service interface. Which implementation to use is decided run-time by reading the FHiCL services configuration, so it does not make sense to link a specific implementation at build time. What we really need is the "library" pulling in the interface. I hope my suggestion is the LArSoft's right name for the one we need here.

Copy link
Member

Choose a reason for hiding this comment

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

I have checked FillReco.h... we do have a problem: lardataalg/DetectorInfo/DetectorPropertiesStandard.h is included directly. It should not be. Ever.
Please replace that line with the appropriate header, which I think is:

#include "lardataalg/DetectorInfo/DetectorPropertiesData.h"

(bonus points if you try to remove also the inclusion of ParticleInventoryService.h on the previous line, since it looks like it's not used — and if it is needed, leave it there)
With my thanks for fixing this pre-existing bug.

if (sce->EnableSimEfieldSCE()) {
// Gets fractional E field distortions w.r.t. the nominal field on x-axis
geo::Vector_t EFieldOffsets = sce->GetEfieldOffsets(loc);
// Add 1 in X direction as this is the direction of the drift field, not caring if it is +x or -x direction, since we only want |E|
Copy link
Member

Choose a reason for hiding this comment

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

Just to make double sure: does the offset as returned by sce always assume the nominal electric field to have positive x direction?
That assumption is an essential ingredient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It depends on how the SCE map is saved. For SBN SCE studies, it is assumed the nominal electric field has positive x direction.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it depends on that... unfortunately I don't see that assumption documented.
However, I see many places where that assumption is made, and if you haven't noticed anything strange in testing, that will have to do.

@PetrilloAtWork
Copy link
Member

Points to discuss:
[...]
3. I ask to reconsider the unit of the angle phi. Having it in degrees means that every computation will need to convert it to radians. Personally I would consider to store its cosine instead, but if an angle is required, I believe it should be stored as radians.
[...]

  1. I agree with your point but keeping it as degree to have the exactly same value provided by the GnocchiCalorimetry_module.cc. Converting it to radian should be simple.

It is not about how it was written in the original code (in the same way in which it does not matter whether an energy was stored in MeV or GeV or erg), but rather how people expect it, and how it is expected to be used. CAF promise is that angles would be in radians; there needs to be a real need for that promise to be broken. I am not even sure there is any exception now (maybe some times bound to timestamps stored in nanoseconds).

@sungbinoh
Copy link
Contributor Author

Points to discuss:
[...]
3. I ask to reconsider the unit of the angle phi. Having it in degrees means that every computation will need to convert it to radians. Personally I would consider to store its cosine instead, but if an angle is required, I believe it should be stored as radians.
[...]

  1. I agree with your point but keeping it as degree to have the exactly same value provided by the GnocchiCalorimetry_module.cc. Converting it to radian should be simple.

It is not about how it was written in the original code (in the same way in which it does not matter whether an energy was stored in MeV or GeV or erg), but rather how people expect it, and how it is expected to be used. CAF promise is that angles would be in radians; there needs to be a real need for that promise to be broken. I am not even sure there is any exception now (maybe some times bound to timestamps stored in nanoseconds).

That makes sense. (sorry, I've not known that there has been CAF promise). Updated phi to rad from degree.

@gputnam
Copy link
Contributor

gputnam commented Sep 3, 2025

@PetrilloAtWork I believe all of your comments have now been addressed. Please take another look.

Copy link
Member

@PetrilloAtWork PetrilloAtWork left a comment

Choose a reason for hiding this comment

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

There are some indentation fixes pending.
Worse, GitHub ate a request from yesterday's review.
The rest should be ok.

//
// Use this to get the (SCE corrected) efield and the angle to the drift direction
unsigned traj_point_index = thms.at(i_hit)->Index();
if (track.HasValidPoint(traj_point_index)) {
Copy link
Member

Choose a reason for hiding this comment

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

Please fix this indentation.

Suggested change
if (track.HasValidPoint(traj_point_index)) {
if (track.HasValidPoint(traj_point_index)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

sbncode_CAFMaker
sbnobj::Common_Calibration_dict
larevt::SpaceCharge
caf_RecoUtils
Copy link
Member

Choose a reason for hiding this comment

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

Please fix the indentation:

Suggested change
caf_RecoUtils
caf_RecoUtils

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed!

Comment on lines 69 to 70
// Useful functions
#include "sbncode/CAFMaker/RecoUtils/RecoUtils.h"
Copy link
Member

Choose a reason for hiding this comment

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

I suggest a comment detailing why they are useful 😉

Suggested change
// Useful functions
#include "sbncode/CAFMaker/RecoUtils/RecoUtils.h"
#include "sbncode/CAFMaker/RecoUtils/RecoUtils.h" // sbn::ReadoutIDE, sbn::PrepTrueHits()...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

Comment on lines 802 to 803
double GetEfield(const detinfo::DetectorPropertiesData& dprop, const geo::Point_t loc) {
auto const* sce = lar::providerFrom<spacecharge::SpaceChargeService>();
Copy link
Member

Choose a reason for hiding this comment

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

I am sorry, this is frustrating me and I am sure it will frustrate you too... somehow a comment on this function was not received by GitHub.
So the gist of it is that invoking a service from art at every point is not good, and this should happen only once in CAFMaker::produce().
So my request is to pass the space charge service provider as an argument from upstream:

Suggested change
double GetEfield(const detinfo::DetectorPropertiesData& dprop, const geo::Point_t loc) {
auto const* sce = lar::providerFrom<spacecharge::SpaceChargeService>();
double GetEfield(const detinfo::DetectorPropertiesData& dprop, spacecharge::SpaceCharge const& sce, const geo::Point_t& loc) {

Notes:

  • I am suggesting to pass a reference instead of a pointer to be consistent with dprop service data — that will require changing access below from sce-> to sce..
  • I am also suggesting to pass loc by reference rather than by value.
  • In CAFMaker::produce() there is already the variable sce defined (as pointer), which should be passed through FillTrackPlaneCalo() to here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! *sce is passed from CAFMaker::produce() into FillTrackCalo() as reference, and into FillTrackPlaneCalo() from FillTrackCalo().

Comment on lines 14 to 15
#include "larevt/SpaceChargeServices/SpaceChargeService.h"
#include "larcore/CoreUtils/ServiceUtil.h"
Copy link
Member

Choose a reason for hiding this comment

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

These will not be needed after the change below.

Suggested change
#include "larevt/SpaceChargeServices/SpaceChargeService.h"
#include "larcore/CoreUtils/ServiceUtil.h"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done! And moved #include "larevt/SpaceCharge/SpaceCharge.h" into FillReco.h from FillReco.cxx.

@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@SBNSoftware SBNSoftware deleted a comment from FNALbuild Sep 4, 2025
@FNALbuild
Copy link

✔️ CI build for LArSoft Succeeded on slf7 for c14:prof -- details available through the CI dashboard

@FNALbuild
Copy link

✔️ CI build for LArSoft Succeeded on slf7 for e26:prof -- details available through the CI dashboard

@FNALbuild
Copy link

❌ CI build for SBND Failed at phase build SBND on slf7 for c14:prof -- details available through the CI dashboard

🚨 For more details about the failed phase, check the build SBND phase logs

parent CI build details are available through the CI dashboard

@FNALbuild
Copy link

❌ CI build for ICARUS Failed at phase build ICARUS on slf7 for c14:prof -- details available through the CI dashboard

🚨 For more details about the failed phase, check the build ICARUS phase logs

parent CI build details are available through the CI dashboard

@FNALbuild
Copy link

⚠️ CI build for SBND Warning at phase ci_tests SBND on slf7 for e26:prof - ignored warnings for build -- details available through the CI dashboard

🚨 For more details about the warning phase, check the ci_tests SBND phase logs

parent CI build details are available through the CI dashboard

@FNALbuild
Copy link

❌ CI build for ICARUS Failed at phase ci_tests ICARUS on slf7 for e26:prof - ignored warnings for build -- details available through the CI dashboard

🚨 For more details about the failed phase, check the ci_tests ICARUS phase logs

parent CI build details are available through the CI dashboard

@kjplows kjplows merged commit 8c5781a into SBNSoftware:release/SBN2025A Sep 5, 2025
3 of 6 checks passed
@github-project-automation github-project-automation bot moved this from Testing to Done in SBN software development Sep 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done
Status: Todo

Development

Successfully merging this pull request may close these issues.

5 participants