File indexing completed on 2025-12-17 09:22:07
0001
0002
0003
0004
0005
0006 #include "PHG4MicromegasHitReco.h"
0007
0008 #include <fun4all/Fun4AllReturnCodes.h>
0009 #include <fun4all/SubsysReco.h> // for SubsysReco
0010
0011 #include <g4detectors/PHG4CylinderGeom.h>
0012 #include <g4detectors/PHG4CylinderGeomContainer.h>
0013
0014 #include <g4main/PHG4Hit.h>
0015 #include <g4main/PHG4HitContainer.h>
0016 #include <g4main/PHG4Hitv1.h>
0017
0018 #include <micromegas/CylinderGeomMicromegas.h>
0019 #include <micromegas/MicromegasDefs.h>
0020
0021 #include <phool/PHCompositeNode.h>
0022 #include <phool/PHIODataNode.h>
0023 #include <phool/PHNode.h>
0024 #include <phool/PHNodeIterator.h>
0025 #include <phool/PHObject.h> // for PHObject
0026 #include <phool/PHRandomSeed.h>
0027 #include <phool/getClass.h>
0028 #include <phool/phool.h>
0029
0030 #include <phparameter/PHParameterInterface.h> // for PHParameterInterface
0031
0032 #include <trackbase/ActsGeometry.h>
0033 #include <trackbase/TrkrDefs.h>
0034 #include <trackbase/TrkrHitSet.h>
0035 #include <trackbase/TrkrHitSetContainerv1.h>
0036 #include <trackbase/TrkrHitTruthAssocv1.h>
0037 #include <trackbase/TrkrHitv2.h>
0038
0039 #include <TVector2.h>
0040 #include <TVector3.h>
0041
0042 #include <gsl/gsl_randist.h>
0043 #include <gsl/gsl_rng.h> // for gsl_rng_alloc
0044
0045 #include <cassert>
0046 #include <cmath> // for atan2, sqrt, M_PI
0047 #include <cstdlib> // for exit
0048 #include <iostream> // for operator<<, basic...
0049 #include <map> // for _Rb_tree_const_it...
0050 #include <numeric>
0051
0052 namespace
0053 {
0054
0055 template <class T>
0056 constexpr T square(const T& x)
0057 {
0058 return x * x;
0059 }
0060
0061
0062 template <class T>
0063 inline T gaus(const T& x, const T& sigma)
0064 {
0065 return std::exp(-square(x / sigma) / 2) / (sigma * std::sqrt(2 * M_PI));
0066 }
0067
0068
0069 template <class T>
0070 inline T bind_angle(const T& angle)
0071 {
0072 if (angle >= M_PI)
0073 {
0074 return angle - 2 * M_PI;
0075 }
0076 if (angle < -M_PI)
0077 {
0078 return angle + 2 * M_PI;
0079 }
0080
0081 return angle;
0082 }
0083
0084
0085 template <class T>
0086 inline T get_rectangular_fraction(const T& xloc, const T& sigma, const T& pitch)
0087 {
0088 return (std::erf((xloc + pitch / 2) / (M_SQRT2 * sigma)) - std::erf((xloc - pitch / 2) / (M_SQRT2 * sigma))) / 2;
0089 }
0090
0091
0092
0093
0094
0095 template <class T>
0096 inline T get_zigzag_fraction(const T& xloc, const T& sigma, const T& pitch)
0097 {
0098 return
0099
0100 (pitch - xloc) * (std::erf(xloc / (M_SQRT2 * sigma)) - std::erf((xloc - pitch) / (M_SQRT2 * sigma))) / (pitch * 2) + (gaus(xloc - pitch, sigma) - gaus(xloc, sigma)) * square(sigma) / pitch
0101
0102
0103 + (pitch + xloc) * (std::erf((xloc + pitch) / (M_SQRT2 * sigma)) - std::erf(xloc / (M_SQRT2 * sigma))) / (pitch * 2) + (gaus(xloc + pitch, sigma) - gaus(xloc, sigma)) * square(sigma) / pitch;
0104 }
0105
0106
0107 [[maybe_unused]] inline std::ostream& operator<<(std::ostream& out, const TVector3& position)
0108 {
0109 out << "(" << position.x() << ", " << position.y() << ", " << position.z() << ")";
0110 return out;
0111 }
0112
0113 }
0114
0115
0116 PHG4MicromegasHitReco::PHG4MicromegasHitReco(const std::string& name)
0117 : SubsysReco(name)
0118 , PHParameterInterface(name)
0119 {
0120
0121 const uint seed = PHRandomSeed();
0122 m_rng.reset(gsl_rng_alloc(gsl_rng_mt19937));
0123 gsl_rng_set(m_rng.get(), seed);
0124
0125 InitializeParameters();
0126 }
0127
0128
0129 int PHG4MicromegasHitReco::InitRun(PHCompositeNode* topNode)
0130 {
0131 UpdateParametersWithMacro();
0132
0133
0134 m_tmin = get_double_param("micromegas_tmin");
0135 m_tmax = get_double_param("micromegas_tmax");
0136 m_electrons_per_gev = get_double_param("micromegas_electrons_per_gev");
0137 m_gain = get_double_param("micromegas_gain");
0138 m_cloud_sigma = get_double_param("micromegas_cloud_sigma");
0139 m_diffusion_trans = get_double_param("micromegas_diffusion_trans");
0140 m_added_smear_sigma_z = get_double_param("micromegas_added_smear_sigma_z");
0141 m_added_smear_sigma_rphi = get_double_param("micromegas_added_smear_sigma_rphi");
0142
0143
0144 std::cout
0145 << "PHG4MicromegasHitReco::InitRun\n"
0146 << " m_tmin: " << m_tmin << "ns, m_tmax: " << m_tmax << "ns\n"
0147 << " m_electrons_per_gev: " << m_electrons_per_gev << "\n"
0148 << " m_gain: " << m_gain << "\n"
0149 << " m_cloud_sigma: " << m_cloud_sigma << "cm\n"
0150 << " m_diffusion_trans: " << m_diffusion_trans << "cm/sqrt(cm)\n"
0151 << " m_added_smear_sigma_z: " << m_added_smear_sigma_z << "cm\n"
0152 << " m_added_smear_sigma_rphi: " << m_added_smear_sigma_rphi << "cm\n"
0153 << std::endl;
0154
0155
0156 PHNodeIterator iter(topNode);
0157 auto* dstNode = dynamic_cast<PHCompositeNode*>(iter.findFirst("PHCompositeNode", "DST"));
0158 if (!dstNode)
0159 {
0160 std::cout << "PHG4MicromegasHitReco::InitRun - DST Node missing, doing nothing." << std::endl;
0161 exit(1);
0162 }
0163
0164
0165 auto* hitsetcontainer = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
0166 if (!hitsetcontainer)
0167 {
0168 std::cout << "PHG4MicromegasHitReco::InitRun - creating TRKR_HITSET." << std::endl;
0169
0170
0171 PHNodeIterator dstiter(dstNode);
0172 auto* trkrnode = dynamic_cast<PHCompositeNode*>(dstiter.findFirst("PHCompositeNode", "TRKR"));
0173 if (!trkrnode)
0174 {
0175 trkrnode = new PHCompositeNode("TRKR");
0176 dstNode->addNode(trkrnode);
0177 }
0178
0179
0180 hitsetcontainer = new TrkrHitSetContainerv1;
0181 auto* newNode = new PHIODataNode<PHObject>(hitsetcontainer, "TRKR_HITSET", "PHObject");
0182 trkrnode->addNode(newNode);
0183 }
0184
0185
0186 auto* hittruthassoc = findNode::getClass<TrkrHitTruthAssoc>(topNode, "TRKR_HITTRUTHASSOC");
0187 if (!hittruthassoc)
0188 {
0189 std::cout << "PHG4MicromegasHitReco::InitRun - creating TRKR_HITTRUTHASSOC." << std::endl;
0190
0191
0192 PHNodeIterator dstiter(dstNode);
0193 auto* trkrnode = dynamic_cast<PHCompositeNode*>(dstiter.findFirst("PHCompositeNode", "TRKR"));
0194 if (!trkrnode)
0195 {
0196 trkrnode = new PHCompositeNode("TRKR");
0197 dstNode->addNode(trkrnode);
0198 }
0199
0200 hittruthassoc = new TrkrHitTruthAssocv1;
0201 auto* newNode = new PHIODataNode<PHObject>(hittruthassoc, "TRKR_HITTRUTHASSOC", "PHObject");
0202 trkrnode->addNode(newNode);
0203 }
0204
0205 return Fun4AllReturnCodes::EVENT_OK;
0206 }
0207
0208
0209 int PHG4MicromegasHitReco::process_event(PHCompositeNode* topNode)
0210 {
0211
0212
0213 auto* g4hitcontainer = findNode::getClass<PHG4HitContainer>(topNode, "G4HIT_MICROMEGAS");
0214 assert(g4hitcontainer);
0215
0216
0217 m_acts_geometry = findNode::getClass<ActsGeometry>(topNode, "ActsGeometry");
0218 assert(m_acts_geometry);
0219
0220
0221 const auto geonodename = full_geonodename();
0222 auto* geonode = findNode::getClass<PHG4CylinderGeomContainer>(topNode, geonodename);
0223 assert(geonode);
0224
0225
0226 auto* trkrhitsetcontainer = findNode::getClass<TrkrHitSetContainer>(topNode, "TRKR_HITSET");
0227 assert(trkrhitsetcontainer);
0228
0229
0230 auto* hittruthassoc = findNode::getClass<TrkrHitTruthAssoc>(topNode, "TRKR_HITTRUTHASSOC");
0231 assert(hittruthassoc);
0232
0233
0234 auto layer_range = g4hitcontainer->getLayers();
0235 for (auto layer_it = layer_range.first; layer_it != layer_range.second; ++layer_it)
0236 {
0237
0238 const auto layer = *layer_it;
0239
0240
0241 auto* layergeom = dynamic_cast<CylinderGeomMicromegas*>(geonode->GetLayerGeom(layer));
0242 assert(layergeom);
0243
0244
0245
0246
0247
0248
0249
0250
0251 const auto mesh_local_z = layergeom->get_drift_direction() == MicromegasDefs::DriftDirection::OUTWARD ? layergeom->get_thickness() / 2 : -layergeom->get_thickness() / 2;
0252
0253
0254
0255
0256
0257
0258
0259
0260
0261
0262
0263 const PHG4HitContainer::ConstRange g4hit_range = g4hitcontainer->getHits(layer);
0264
0265
0266 for (auto g4hit_it = g4hit_range.first; g4hit_it != g4hit_range.second; ++g4hit_it)
0267 {
0268
0269 PHG4Hit* g4hit = g4hit_it->second;
0270
0271
0272 if (g4hit->get_t(0) > m_tmax)
0273 {
0274 continue;
0275 }
0276 if (g4hit->get_t(1) < m_tmin)
0277 {
0278 continue;
0279 }
0280
0281
0282 TVector3 world_in(g4hit->get_x(0), g4hit->get_y(0), g4hit->get_z(0));
0283 TVector3 world_out(g4hit->get_x(1), g4hit->get_y(1), g4hit->get_z(1));
0284
0285
0286 const int tileid = g4hit->get_property_int(PHG4Hit::prop_index_i);
0287
0288
0289 const auto local_in = layergeom->get_local_from_world_coords(tileid, m_acts_geometry, world_in);
0290 const auto local_out = layergeom->get_local_from_world_coords(tileid, m_acts_geometry, world_out);
0291
0292
0293 const auto nprimary = get_primary_electrons(g4hit);
0294 if (!nprimary)
0295 {
0296 continue;
0297 }
0298
0299
0300 const TrkrDefs::hitsetkey hitsetkey = MicromegasDefs::genHitSetKey(layer, layergeom->get_segmentation_type(), tileid);
0301 const auto hitset_it = trkrhitsetcontainer->findOrAddHitSet(hitsetkey);
0302
0303
0304 using charge_map_t = std::map<int, double>;
0305 charge_map_t total_charges;
0306
0307
0308 for (uint ie = 0; ie < nprimary; ++ie)
0309 {
0310
0311
0312
0313 const auto t = gsl_ran_flat(m_rng.get(), 0.0, 1.0);
0314 auto local = local_in * t + local_out * (1.0 - t);
0315
0316 if (m_diffusion_trans > 0)
0317 {
0318
0319
0320 const double z = local.z();
0321 const double drift_distance = std::abs(z - mesh_local_z);
0322 const double diffusion = gsl_ran_gaussian(m_rng.get(), m_diffusion_trans * std::sqrt(drift_distance));
0323 const double diffusion_angle = gsl_ran_flat(m_rng.get(), -M_PI, M_PI);
0324
0325
0326 local += TVector3(diffusion * std::cos(diffusion_angle), diffusion * std::sin(diffusion_angle), 0);
0327 }
0328
0329 const auto& added_smear_sigma = layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_PHI ? m_added_smear_sigma_rphi : m_added_smear_sigma_z;
0330
0331 if (added_smear_sigma > 0)
0332 {
0333
0334 const double added_smear_trans = gsl_ran_gaussian(m_rng.get(), added_smear_sigma);
0335 const double added_smear_angle = gsl_ran_flat(m_rng.get(), -M_PI, M_PI);
0336 local += TVector3(added_smear_trans * std::cos(added_smear_angle), added_smear_trans * std::sin(added_smear_angle), 0);
0337 }
0338
0339
0340 const auto fractions = distribute_charge(layergeom, tileid, {local.x(), local.y()}, m_cloud_sigma);
0341
0342
0343 if (Verbosity() > 10)
0344 {
0345 const auto sum = std::accumulate(fractions.begin(), fractions.end(), double(0),
0346 [](double value, const charge_pair_t& pair)
0347 { return value + pair.second; });
0348 std::cout << "PHG4MicromegasHitReco::process_event - sum: " << sum << std::endl;
0349 }
0350
0351
0352 const auto gain = get_single_electron_amplification();
0353
0354
0355 for (const auto& pair : fractions)
0356 {
0357 const int strip = pair.first;
0358 if (strip < 0 || strip >= (int) layergeom->get_strip_count(tileid, m_acts_geometry))
0359 {
0360 continue;
0361 }
0362
0363 const auto it = total_charges.lower_bound(strip);
0364 if (it != total_charges.end() && it->first == strip)
0365 {
0366 it->second += pair.second * gain;
0367 }
0368 else
0369 {
0370 total_charges.insert(it, std::make_pair(strip, pair.second * gain));
0371 }
0372 }
0373 }
0374
0375
0376
0377 for (const auto pair : total_charges)
0378 {
0379
0380 const int strip = pair.first;
0381
0382
0383 TrkrDefs::hitkey hitkey = MicromegasDefs::genHitKey(strip);
0384 auto* hit = hitset_it->second->getHit(hitkey);
0385 if (!hit)
0386 {
0387
0388 hit = new TrkrHitv2;
0389 hitset_it->second->addHitSpecificKey(hitkey, hit);
0390 }
0391
0392
0393 hit->addEnergy(pair.second);
0394
0395
0396 hittruthassoc->addAssoc(hitsetkey, hitkey, g4hit_it->first);
0397 }
0398 }
0399 }
0400
0401 return Fun4AllReturnCodes::EVENT_OK;
0402 }
0403
0404
0405 void PHG4MicromegasHitReco::SetDefaultParameters()
0406 {
0407
0408
0409
0410
0411
0412
0413 set_default_double_param("micromegas_tmin", -20);
0414 set_default_double_param("micromegas_tmax", 800);
0415
0416
0417
0418
0419
0420 static constexpr double Ar_dEdx = 2.44;
0421 static constexpr double iC4H10_dEdx = 5.93;
0422 static constexpr double mix_dEdx = 0.9 * Ar_dEdx + 0.1 * iC4H10_dEdx;
0423
0424
0425 static constexpr double Ar_ntot = 94;
0426 static constexpr double iC4H10_ntot = 195;
0427 static constexpr double mix_ntot = 0.9 * Ar_ntot + 0.1 * iC4H10_ntot;
0428
0429
0430 static constexpr double mix_electrons_per_gev = 1e6 * mix_ntot / mix_dEdx;
0431 set_default_double_param("micromegas_electrons_per_gev", mix_electrons_per_gev);
0432
0433
0434 set_default_double_param("micromegas_gain", 2000);
0435
0436
0437 set_default_double_param("micromegas_cloud_sigma", 0.04);
0438
0439
0440 set_default_double_param("micromegas_diffusion_trans", 0.03);
0441
0442
0443 set_default_double_param("micromegas_added_smear_sigma_z", 0);
0444 set_default_double_param("micromegas_added_smear_sigma_rphi", 0);
0445 }
0446
0447
0448 uint PHG4MicromegasHitReco::get_primary_electrons(PHG4Hit* g4hit) const
0449 {
0450 return gsl_ran_poisson(m_rng.get(), g4hit->get_eion() * m_electrons_per_gev);
0451 }
0452
0453
0454 uint PHG4MicromegasHitReco::get_single_electron_amplification() const
0455 {
0456
0457
0458
0459
0460
0461 return gsl_ran_exponential(m_rng.get(), m_gain);
0462 }
0463
0464
0465 PHG4MicromegasHitReco::charge_list_t PHG4MicromegasHitReco::distribute_charge(
0466 CylinderGeomMicromegas* layergeom,
0467 uint tileid,
0468 const TVector2& local_coords,
0469 double sigma) const
0470 {
0471
0472 auto stripnum = layergeom->find_strip_from_local_coords(tileid, m_acts_geometry, local_coords);
0473
0474
0475 if (stripnum < 0)
0476 {
0477 return charge_list_t();
0478 }
0479
0480
0481 const auto pitch = layergeom->get_pitch();
0482
0483
0484 const auto strip_count = layergeom->get_strip_count(tileid, m_acts_geometry);
0485 const int nstrips = 5. * sigma / pitch + 1;
0486 const auto stripnum_min = std::clamp<int>(stripnum - nstrips, 0, strip_count);
0487 const auto stripnum_max = std::clamp<int>(stripnum + nstrips, 0, strip_count);
0488
0489
0490 charge_list_t charge_list;
0491
0492
0493 for (int strip = stripnum_min; strip <= stripnum_max; ++strip)
0494 {
0495
0496 const auto strip_location = layergeom->get_local_coordinates(tileid, m_acts_geometry, strip);
0497
0498
0499
0500
0501
0502
0503 const auto xloc = layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_PHI ? (strip_location.X() - local_coords.X()) : (strip_location.Y() - local_coords.Y());
0504
0505
0506
0507
0508
0509
0510 const bool zigzag_strips = (layergeom->get_segmentation_type() == MicromegasDefs::SegmentationType::SEGMENTATION_Z);
0511
0512
0513 const auto fraction = zigzag_strips ? get_zigzag_fraction(xloc, sigma, pitch) : get_rectangular_fraction(xloc, sigma, pitch);
0514
0515
0516 charge_list.emplace_back(strip, fraction);
0517 }
0518
0519 return charge_list;
0520 }