2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
5#include "Identifier/Identifier.h"
6#include "AthenaKernel/RNGWrapper.h"
8#include "StoreGate/WriteHandle.h"
11#include "CLHEP/Random/RandFlat.h"
13#include "ModuleIdentifierMatchUtil.h"
14#include "ConnectedModulesUtil.h"
15#include "EmulatedDefectsBuilder.h"
17#include "nlohmann/json.hpp"
19#include "TVirtualRWMutex.h"
20#include "ROOT/RNTupleWriter.hxx"
21#include "ROOT/RNTupleReader.hxx"
22#include "ROOT/RNTupleModel.hxx"
35 template <typename T_ID>
36 concept IdentifierHelperWithOtherSideConecpt = requires(T_ID id_helper) { id_helper.get_other_side(); };
38 template <class T_ModuleHelper>
39 unsigned int getSensorColumn(const T_ModuleHelper &helper, unsigned int cell_idx) {
40 return cell_idx % helper.nSensorColumns();
42 template <class T_ModuleHelper>
43 unsigned int getSensorRow(const T_ModuleHelper &helper, unsigned int cell_idx) {
44 return cell_idx / helper.nSensorColumns();
47 unsigned int makeCheckerboard(unsigned int cell_idx, unsigned int n_rows, unsigned int n_columns,
51 unsigned int row_i=cell_idx / n_columns;
52 unsigned int col_i = cell_idx % n_columns;
53 unsigned int row_odd = row_i < n_rows/2 || !odd_row_toggle ? 1 : 0;
54 unsigned int div_row = n_rows/7;
55 unsigned int div_cols = n_columns/7;
56 row_i = std::min((((row_i / div_row ) & (0xffff-1)) + row_odd)*div_row + (row_i%div_row), n_rows-1);
57 unsigned int col_odd = col_i < n_columns/2 || !odd_col_toggle ? 1 : 0;
58 col_i = std::min((((col_i / div_cols ) & (0xffff-1)) + col_odd)*div_cols + (col_i%div_cols),n_columns-1);
59 cell_idx= row_i * n_columns + (col_i % n_columns);
63 /** helper to consistently lock and unlock when leaving the scope.
64 * similar to std::lock_guard, but locking when not needed for the scope
69 MyLockGuard(std::mutex &a_mutex, bool disable)
70 : m_mutex( !disable ? &a_mutex : nullptr)
72 if (m_mutex) {m_mutex->lock(); }
75 if (m_mutex) {m_mutex->unlock(); }
82 template <class T_ModuleHelper>
83 void histogramDefects(const T_ModuleHelper &helper,
84 const typename std::pair<typename T_ModuleHelper::KEY_TYPE,typename T_ModuleHelper::KEY_TYPE> &key,
86 std::array<unsigned int,4> ranges_row_col = helper.offlineRange(key);
87 for (unsigned int row_i=ranges_row_col[0]; row_i<ranges_row_col[1]; ++row_i) {
88 for (unsigned int col_i=ranges_row_col[2]; col_i<ranges_row_col[3]; ++col_i) {
89 h2->Fill(col_i, row_i);
94 // create an array containing all possible combinations of M-tuples of numbers of 1 to N
95 // The M numbers are unique per M-tuple, and the order has no significance.
96 template <typename T, std::size_t N_MAX, std::size_t M>
97 constexpr std::array<T, N_MAX> packCombinations(unsigned int N) {
98 std::array<T, N_MAX> packed_permutations{};
100 std::array<unsigned int,M> last_val;
101 for (unsigned int i=0; i<N; ++i) {
105 for (unsigned int i=0; i<N; ++i) {
106 packed_permutations[idx]|= static_cast<T>(1u<<last_val[i]);
111 if (++last_val[i]<=M-(N-i)) {
113 last_val[i]=last_val[i-1]+1;
121 return packed_permutations;
124 // count the number of elements up the first which is zero if it is not the first element.
125 template <typename T, std::size_t N_MAX>
126 constexpr unsigned int findLastNonEmpty(const std::array<T, N_MAX> &arr) {
128 while (++i<arr.size() && arr[i]!=0);
132 // create an arry which contains the number of combinations for the array of combinations.
133 template <typename T, typename T2, std::size_t N_MAX, std::size_t N>
134 constexpr std::array<unsigned char,N> makeCombinationCounts(const std::array<std::array<T2, N_MAX>, N> &arr) {
135 std::array<unsigned char,N> ret;
137 for (const std::array<T2, N_MAX> &sub_arr : arr) {
138 ret[idx++]=static_cast<T>(findLastNonEmpty(sub_arr));
143 // create the possible combinations of defect corners for 1 to 4 defect corners
144 static constexpr std::array< std::array<unsigned char,6>, 4> corner_combinations {
145 packCombinations<unsigned char,6,4>(1),
146 packCombinations<unsigned char,6,4>(2),
147 packCombinations<unsigned char,6,4>(3),
148 packCombinations<unsigned char,6,4>(4),
151 // count the number of possible combinations;
152 static constexpr std::array<unsigned char,4> n_corner_combinations (makeCombinationCounts<unsigned char>(corner_combinations) );
154 template <typename T>
159 bool hasExtensions(const std::string &name, const std::string_view &ext) {
160 return name.size()>=ext.size() && name.substr(name.size()-ext.size(),ext.size())==ext;
163 template <typename T_EmulatedDefects>
164 void toJson(const T_EmulatedDefects &defects, const std::string &name) {
166 for (unsigned int id_hash = 0; id_hash < defects.size(); ++id_hash) {
167 if (defects.isModuleDefect(id_hash) || !defects[id_hash].empty()) {
168 nlohmann::json module_data;
169 if (defects.isModuleDefect(id_hash)) {
170 module_data["isDefect"]=true;
173 module_data["defects"]=defects[id_hash];
176 std::stringstream id_hash_name;
177 id_hash_name << id_hash;
178 data[id_hash_name.str()]=module_data;
181 std::ofstream out(name.c_str());
185 template <typename T_EmulatedDefects>
186 void toRoot(const T_EmulatedDefects &defects, const std::string &name) {
187 ROOT::TWriteLockGuard lock (ROOT::gCoreMutex);
189 auto model=ROOT::RNTupleModel::Create();
190 auto nt_defects=model->MakeField< std::vector<typename T_EmulatedDefects::KEY_TYPE> >("defects");
191 auto nt_is_defect=model->MakeField<bool>("isDefect");
192 ROOT::RNTupleWriteOptions options;
193 options.SetCompression(ROOT::RCompressionSetting::EAlgorithm::kZSTD, ROOT::RCompressionSetting::ELevel::kDefaultZSTD);
194 auto writer = ROOT::RNTupleWriter::Recreate(std::move(model), "defects", name.c_str(), options);
195 for (unsigned int id_hash = 0; id_hash < defects.size(); ++id_hash) {
197 if (defects.isModuleDefect(id_hash)) {
201 *nt_is_defect =false;
202 *nt_defects = defects[id_hash];
208 template <typename KEY_TYPE>
211 bool open(const std::string &name) {
212 std::ifstream input_file(name);
216 input_file >> m_jsDefects;
220 return m_jsDefects.items();
222 template <typename T>
223 static std::size_t moduleHash(const T &element) {
225 return strtol(element.key().c_str(),&ptr, 10);
227 template <typename T>
228 static nlohmann::json value(const T &element) {
229 return element.value();
231 static bool isDefect(const nlohmann::json &value) {
232 return !value.contains("isDefect") ? false : value["isDefect"].get<bool>();
234 static std::vector<KEY_TYPE> moduleDefects(const nlohmann::json &value) {
235 return !value.contains("defects") ? std::vector<KEY_TYPE>() : value["defects"].get< std::vector<KEY_TYPE> >();
238 nlohmann::json m_jsDefects;
241 template <typename KEY_TYPE>
244 bool open(const std::string &name) {
245 auto model=ROOT::RNTupleModel::Create();
246 m_defects=model->MakeField< std::vector<KEY_TYPE> >("defects");
247 m_isDefect=model->MakeField<bool>("isDefect");
248 m_reader = ROOT::RNTupleReader::Open(std::move(model), "defects", name.c_str());
249 return m_reader.get() != nullptr;
251 ROOT::RNTupleReader &items() {
254 static auto moduleHash( std::size_t entry_i) {
257 auto value(std::size_t entry_i) {
258 m_reader->LoadEntry(entry_i);
261 bool isDefect([[maybe_unused]] std::size_t entry_i) {
264 const std::vector<KEY_TYPE> &moduleDefects([[maybe_unused]] std::size_t entry_i) {
268 std::unique_ptr< ROOT::RNTupleReader > m_reader;
269 std::shared_ptr< bool > m_isDefect;
270 std::shared_ptr<std::vector<KEY_TYPE> > m_defects;
273 template <typename T_ModuleHelper, typename T_ReaderAdapter, typename T_EmulatedDefects>
274 void fromFile(const InDetDD::SiDetectorElementCollection &det_ele_coll, T_EmulatedDefects &defects, const std::string &name) {
275 T_ReaderAdapter reader;
276 if (!reader.open(name)) {
277 std::stringstream amsg;
278 amsg << "Failed to read \"" << name << "\"";
279 throw std::runtime_error(amsg.str());
282 for (auto entry_i : reader.items()) {
283 auto module_i = reader.moduleHash(entry_i);
284 assert( module_i < det_ele_coll.size());
285 auto value = reader.value(entry_i);
286 if (reader.isDefect(value)) {
287 defects.setModuleDefect(module_i);
289 else if (!defects.isModuleDefect(module_i)) {
290 std::vector<typename T_EmulatedDefects::KEY_TYPE> &module_defects = defects.at(module_i);
291 if (module_defects.empty()) {
292 module_defects = reader.moduleDefects(value); // move ?
295 InDetDD::SiDetectorElementCollection::const_value_type det_ele = det_ele_coll[module_i];
296 T_ModuleHelper helper(det_ele->design());
297 auto input_defects = reader.moduleDefects(value);
298 for (typename std::vector<typename T_EmulatedDefects::KEY_TYPE>::const_reverse_iterator iter = input_defects.rbegin();
299 iter != input_defects.rend();
302 if (T_ModuleHelper::isRangeKey(*iter)) {
304 if (iter == input_defects.rend()) {
307 EmulatedDefectsBuilder::insertKeyRange(helper,
309 std::make_pair(key, *iter),
310 T_ModuleHelper::getDefectTypeComponent(key));
311 if (T_ModuleHelper::getDefectTypeComponent(key) != T_ModuleHelper::getDefectTypeComponent(*iter)) {
312 auto [end_key_iter, end_iter] = InDet::EmulatedDefects<T_ModuleHelper>::lower_bound(module_defects, *iter);
313 if (end_key_iter != end_iter && T_ModuleHelper::makeBaseKey(*iter) == T_ModuleHelper::makeBaseKey(*end_key_iter)) {
314 *end_key_iter |= T_ModuleHelper::getDefectTypeComponent(*iter);
319 EmulatedDefectsBuilder::insertKey(helper,
322 T_ModuleHelper::getDefectTypeComponent(key));
331 template <typename T_ModuleHelper, typename T_EmulatedDefects>
332 void fromJson(const InDetDD::SiDetectorElementCollection &det_ele_coll, T_EmulatedDefects &defects, const std::string &name) {
333 fromFile<T_ModuleHelper, JsonAdapter<typename T_ModuleHelper::KEY_TYPE> >(det_ele_coll, defects, name);
336 template <typename T_ModuleHelper, typename T_EmulatedDefects>
337 void fromRoot(const InDetDD::SiDetectorElementCollection &det_ele_coll, T_EmulatedDefects &defects, const std::string &name) {
338 ROOT::TReadLockGuard lock (ROOT::gCoreMutex);
339 fromFile<T_ModuleHelper, RootAdapter<typename T_ModuleHelper::KEY_TYPE> >(det_ele_coll, defects, name);
347 template<class T_Derived>
348 StatusCode DefectsEmulatorCondAlgImpl<T_Derived>::initialize(){
349 ATH_CHECK(m_detEleCollKey.initialize());
350 ATH_CHECK(m_writeKey.initialize());
351 ATH_CHECK(detStore()->retrieve(m_idHelper,derived().IDName()));
352 if constexpr( T_ModuleHelper::ROW_BITS==0 || T_ModuleHelper::COL_BITS==0) {
353 if (!m_cornerDefectParamsPerPattern.empty() || !m_cornerDefectNCornerFractionsPerPattern.empty()) {
354 ATH_MSG_ERROR("Corner defects are not supported in this instance but are configured.");
355 return StatusCode::FAILURE;
359 return initializeBase(T_ModuleHelper::nMasks(), m_idHelper->wafer_hash_max());
362 template<class T_Derived>
363 StatusCode DefectsEmulatorCondAlgImpl<T_Derived>::execute(const EventContext& ctx) const {
364 SG::WriteCondHandle<T_EmulatedDefects> defectsOut(m_writeKey, ctx);
365 if (defectsOut.isValid()) {
366 return StatusCode::SUCCESS;
368 SG::ReadCondHandle<InDetDD::SiDetectorElementCollection> detEleColl(m_detEleCollKey,ctx);
369 ATH_CHECK(detEleColl.isValid());
370 defectsOut.addDependency(detEleColl);
372 std::unique_ptr<T_EmulatedDefects> defects = std::make_unique<T_EmulatedDefects>(*(detEleColl.cptr()));
374 for (const std::string &input_file : m_inputFiles.value()) {
375 if (hasExtensions(input_file,".json")) {
376 fromJson<T_ModuleHelper>(*(detEleColl.cptr()),*defects, input_file);
378 else if (hasExtensions(input_file,".root")) {
379 fromRoot<T_ModuleHelper>(*(detEleColl.cptr()), *defects, input_file);
383 // last counts (+1) are counts for entire modules being defect
384 std::vector<std::array<unsigned int,kNCounts> > counts(T_ModuleHelper::nMasks()+1, std::array<unsigned int,kNCounts>{});
386 std::size_t n_error=0u;
387 unsigned int n_defects_total=0;
388 unsigned int module_without_matching_pattern=0u;
390 auto connected_modules = derived().getModuleConnectionMap(*(detEleColl.cptr()));
392 std::array<CLHEP::HepRandomEngine *, kMaskDefects + T_ModuleHelper::nMasks() > rndmEngine;
393 if (this->m_rngPerDefectType) {
394 assert (m_rngName.size() == rndmEngine.size());
396 for (const std::string &a_rngName : m_rngName) {
397 ATHRNG::RNGWrapper* rngWrapper = m_rndmSvc->getEngine(this, a_rngName );
398 rngWrapper->setSeed( a_rngName, ctx );
399 assert( idx < rndmEngine.size());
400 rndmEngine[idx++]=rngWrapper->getEngine(ctx);
404 assert( !m_rngName.empty());
405 ATHRNG::RNGWrapper* rngWrapper = m_rndmSvc->getEngine(this, m_rngName[0]);
406 rngWrapper->setSeed( m_rngName[0], ctx );
407 CLHEP::HepRandomEngine *a_rndmEngine = rngWrapper->getEngine(ctx);
408 for (CLHEP::HepRandomEngine *&elm : rndmEngine) {
413 ModuleIdentifierMatchUtil::ModuleData_t module_data;
414 std::vector<unsigned int> module_pattern_idx;
415 module_pattern_idx.reserve( m_modulePattern.size() );
416 std::vector<double> cumulative_prob_dist;
417 cumulative_prob_dist.reserve( m_modulePattern.size() );
418 std::vector<unsigned int> n_mask_defects;
419 n_mask_defects.reserve( T_ModuleHelper::nMasks());
421 std::size_t det_ele_sz = detEleColl->size();
422 for (unsigned int module_i=0u; module_i < det_ele_sz; ++module_i) {
423 typename T_DetectorElementCollection::const_value_type det_ele = (*detEleColl.cptr())[module_i];
424 if (defects->isModuleDefect(module_i)) continue;
425 if (derived().isModuleDefect(ctx, module_i)) {
426 defects->setModuleDefect(module_i);
427 if (m_histogrammingEnabled && !m_moduleHist.empty()) {
428 // Always fill externally provided defects as if the module would
429 // match the first pattern, since there is no pattern associated to
430 // the external defects.
431 std::lock_guard<std::mutex> lock(m_histMutex);
432 histogramDefectModule(0, 0, module_i, det_ele->center());
437 T_ModuleHelper helper(det_ele->design());
443 ++(counts.back()[ kNElements ]); // module defects
445 // find pattern matching this module
446 const T_ModuleDesign &moduleDesign = dynamic_cast<const T_ModuleDesign &>(det_ele->design());
447 ModuleIdentifierMatchUtil::setModuleData(*m_idHelper,
451 ModuleIdentifierMatchUtil::moduleMatches(m_modulePattern.value(), module_data, module_pattern_idx);
452 if (module_pattern_idx.empty()) {
453 ++module_without_matching_pattern;
457 // it is possible that multiple patterns match a module
458 // For module defects a pattern is selected from all matching patterns randomly. This matters, because
459 // patterns control whether a defect is propagated to all modules connected to the same physical sensor
461 makeCumulativeProbabilityDist(module_pattern_idx,kModuleDefectProb, cumulative_prob_dist);
462 float prob=(cumulative_prob_dist.empty() || cumulative_prob_dist.back()<=0.) ? 1. : CLHEP::RandFlat::shoot(rndmEngine[kModuleDefects],1.);
464 unsigned int match_i=module_pattern_idx.size();
466 for (; match_i-->0; ) {
467 assert( match_i < cumulative_prob_dist.size());
468 if (prob > cumulative_prob_dist[match_i]) break;
472 if (match_i < module_pattern_idx.size()) {
473 unsigned int hist_pattern_i = (m_fillHistogramsPerPattern ? module_pattern_idx[match_i] : 0 );
474 // mark entire module as defect;
475 (counts.back()[ kNDefects ])
476 += 1-defects->isModuleDefect(module_i); // do not double count
477 defects->setModuleDefect(module_i);
478 // if configured accordingly also mark the other "modules" as defect which
479 // are part of the same physical module
480 ATH_MSG_VERBOSE( "Add module defect "
481 << " hash=" << m_idHelper->wafer_hash(det_ele->identify())
482 << " barel_ec=" << m_idHelper->barrel_ec(det_ele->identify())
483 << " layer_disk=" << m_idHelper->layer_disk(det_ele->identify())
484 << " phi_module=" << m_idHelper->phi_module(det_ele->identify())
485 << " eta_module=" << m_idHelper->eta_module(det_ele->identify())
486 << " side=" << ModuleIdentifierMatchUtil::detail::getZeroOrSide(*m_idHelper,det_ele->identify())
487 << " columns_strips=" << module_data[4]);
489 // if histogramming is enabled lock also for the connected modules
490 // to avoid frequent lock/unlocks.
491 // Anyway the algorithm should run only once per job
492 MyLockGuard lock(m_histMutex, m_histogrammingEnabled);
493 if (m_histogrammingEnabled) {
494 histogramDefectModule(module_pattern_idx[match_i], hist_pattern_i, module_i, det_ele->center());
497 if constexpr(IdentifierHelperWithOtherSideConecpt<decltype(*m_idHelper)>) {
498 // propagate module defects to modules connected to the same physical sensor
499 // if configured accordingly.
500 assert( module_pattern_idx[match_i] < m_modulePattern.size());
501 ConnectedModulesUtil::visitMatchingConnectedModules(
503 m_modulePattern[module_pattern_idx[match_i]],
504 *(detEleColl.cptr()),
507 [defects_ptr=defects.get(),
509 module_pattern_i=module_pattern_idx[match_i],
511 this](unsigned int child_id_hash,const InDetDD::SiDetectorElement &connected_det_ele) {
513 (counts.back()[ kNDefects ])
514 += 1-defects_ptr->isModuleDefect(child_id_hash); // do not double count
515 defects_ptr->setModuleDefect(child_id_hash);
517 if (m_histogrammingEnabled) {
518 histogramDefectModule(module_pattern_i, hist_pattern_i, child_id_hash, connected_det_ele.center());
521 ATH_MSG_VERBOSE( "Propagate module defect to other split modules "
522 << " hash=" << m_idHelper->wafer_hash(connected_det_ele.identify())
523 << " barel_ec=" << m_idHelper->barrel_ec(connected_det_ele.identify())
524 << " layer_disk=" << m_idHelper->layer_disk(connected_det_ele.identify())
525 << " phi_module=" << m_idHelper->phi_module(connected_det_ele.identify())
526 << " eta_module=" << m_idHelper->eta_module(connected_det_ele.identify())
527 << " side=" << ModuleIdentifierMatchUtil::detail::getZeroOrSide(*m_idHelper, connected_det_ele.identify()) );
532 unsigned int hist_pattern_i = (m_fillHistogramsPerPattern ? module_pattern_idx.front() : 0 );
534 // throw number of defects of all defect types excluding module defects which are already handled
535 // e.g. chip-defects, core-column defects, single pixel or strip defects
536 unsigned int cells = helper.nCells();
537 std::vector<typename T_ModuleHelper::KEY_TYPE> &module_defects=(*defects).at(module_i);
538 module_defects.reserve( throwNumberOfDefects(std::span(&rndmEngine.data()[kMaskDefects], rndmEngine.size()-kMaskDefects),
540 T_ModuleHelper::nMasks(),
544 // if histogramming is enabled lock for the entire loop, since it would
545 // be rather inefficient to lock for each defect pixel or strip separately and
546 // this algorithm should run only a single time per job anyway.
547 MyLockGuard lock(m_histMutex, m_histogrammingEnabled);
548 auto [matrix_histogram_index, matrix_index]=(m_histogrammingEnabled
549 ? findHist(hist_pattern_i, helper.nSensorRows(), helper.nSensorColumns())
550 : std::make_pair(0u,0u));
552 // create defects starting from the mask (or group) defect covering the largest area (assuming
553 // that masks are in ascending order) e.g. defect chips, core-column defects, individual pixel
555 auto masks = helper.masks();
556 for (unsigned int mask_i = masks.size(); mask_i-->0; ) {
558 assert( mask_i < n_mask_defects.size());
559 counts[mask_i][kNElements] += helper.nElements(mask_i);
560 unsigned int n_defects = n_mask_defects[mask_i];
562 // module with mask (or group) defects i.e. core-column defects, defect chips, ...
563 assert( mask_i < counts.size());
564 counts[mask_i][kMaxDefectsPerModule] = std::max(counts[mask_i][kMaxDefectsPerModule],n_defects);
565 unsigned int current_defects = module_defects.size();
566 assert( !m_histogrammingEnabled || ( hist_pattern_i < m_hist.size() && matrix_histogram_index < m_hist[hist_pattern_i].size()) );
567 TH2 *h2 = (m_histogrammingEnabled ? m_hist.at(hist_pattern_i).at(matrix_histogram_index) : nullptr);
569 for (unsigned int defect_i=0; defect_i < n_defects; ++defect_i) {
570 // retry if a random position has been chosen already, but at most m_maxAttempts times
571 unsigned int attempt_i=0;
572 for (attempt_i=0; attempt_i<m_maxAttempts.value(); ++attempt_i) {
573 // chose random pixel or strip which defines the defect location
574 assert(kMaskDefects+mask_i < rndmEngine.size() );
575 unsigned int cell_idx=CLHEP::RandFlat::shoot(rndmEngine[kMaskDefects+mask_i],cells);
577 if (m_checkerBoardToggle) {
579 // restrict defects on checker board
580 cell_idx=makeCheckerboard(cell_idx,
581 helper.nSensorRows(),
582 helper.nSensorColumns(),
583 m_oddRowToggle.value(),
584 m_oddColToggle.value() );
587 std::pair<typename T_ModuleHelper::KEY_TYPE, typename T_ModuleHelper::KEY_TYPE> key_range;
588 if (mask_i==0 && m_perPatternAndMaskFractions[match_i].at(mask_i).size()>1) {
589 // for cell defects throw a group size,
590 // the fractions are actual fractions for group sizes of 1,2,... where the first element
591 // is the fraction for a group size of 1.
592 float group_size_prob=CLHEP::RandFlat::shoot(rndmEngine[kMaskDefects+mask_i],1.);
593 unsigned int group_size=m_perPatternAndMaskFractions.at(match_i).at(mask_i).size();
594 for (; group_size-->0 && group_size_prob <= m_perPatternAndMaskFractions[match_i][mask_i][group_size];);
596 // squeeze the random row index into the number of rows reduced by the group size.
597 unsigned int sensor_row = std::min(static_cast<unsigned int>(getSensorRow(helper, cell_idx) %
598 (helper.nSensorRows() - group_size -1)),
599 static_cast<unsigned int>(helper.nSensorRows() - group_size - 1));
600 unsigned int sensor_col = getSensorColumn(helper, cell_idx);
601 key_range = std::make_pair( helper.hardwareCoordinates(sensor_row,
603 helper.hardwareCoordinates(sensor_row+group_size,
607 key_range= T_ModuleHelper::makeRangeForMask(helper.hardwareCoordinates(getSensorRow(helper, cell_idx),
608 getSensorColumn(helper, cell_idx)),
612 if (EmulatedDefectsBuilder::insertKeyRange(helper, module_defects, key_range, T_ModuleHelper::makeDefectTypeKey(mask_i))) {
614 histogramDefects(helper,key_range,h2);
619 assert( mask_i+1 < counts.size());
620 ++counts[mask_i][ kNRetries ];
622 assert( mask_i+1 < counts.size());
623 counts[mask_i][kNMaxRtriesExceeded] += attempt_i >= m_maxAttempts.value();
625 unsigned int new_defects = module_defects.size() - current_defects;
626 assert( mask_i+1 < counts.size());
627 counts[mask_i][ kNDefects ] += new_defects;
629 ++(counts[mask_i][kNModulesWithDefects]);
634 // Create corner defects only for modules with columns and rows.
635 if constexpr( T_ModuleHelper::ROW_BITS>0 && T_ModuleHelper::COL_BITS>0) {
636 if (!m_cornerDefectParamsPerPattern.empty() && !m_cornerDefectParamsPerPattern[module_pattern_idx.front()].empty()) {
637 if (module_pattern_idx.size()!=1) {
638 ATH_MSG_WARNING("Multiple module pattern match module " << module_i
639 << ", but for corner defects only the parameters are used which are associated to the first pattern" );
641 // create defects at certain corners outside of a circle which crosses the sensor edges at certain positions
642 // chosen in a random range. The edge crossing at the particular corner defines a sagitta of a certain value
643 // chosen in a random range
645 // throw the number of corners with corner defects.
646 float defect_prob=CLHEP::RandFlat::shoot(rndmEngine[kCornerDefects],1.);
647 unsigned int n_corners=m_perPatternCornerDefectNCornerCummulativeProb[module_pattern_idx.front() ].size();
649 n_corners>0 && defect_prob<m_perPatternCornerDefectNCornerCummulativeProb[module_pattern_idx.front() ][n_corners-1];
651 unsigned int n_current_defects =module_defects.size();
652 if (n_corners<m_perPatternCornerDefectNCornerCummulativeProb[module_pattern_idx.front() ].size()) {
653 assert( !m_histogrammingEnabled || ( hist_pattern_i < m_hist.size() && matrix_histogram_index < m_hist[hist_pattern_i].size()) );
654 TH2 *h2 = (m_histogrammingEnabled ? m_hist.at(hist_pattern_i).at(matrix_histogram_index) : nullptr);
656 assert( n_corners < n_corner_combinations.size() );
657 // chose the corners which have defects randomly
658 unsigned int the_combination =CLHEP::RandFlat::shoot(rndmEngine[kCornerDefects],n_corner_combinations[n_corners] );
659 unsigned char corner_mask = corner_combinations[n_corners][the_combination];
660 // columns, rows and their pitch are already in direction of the hardware columns and rows
661 unsigned int hwa_columns = helper.columns();
662 unsigned int hwa_rows = helper.rows();
663 float hwa_columnPitch = helper.columnPitch();
664 float hwa_rowPitch = helper.rowPitch();
666 for (unsigned int corner_i=0; corner_i<4; ++corner_i) {
667 if (corner_mask & (1u << corner_i)) {
668 // chose random positions at which the circle crosses the edges at the particular corner
669 // and chose a random sagitta defined by the corssings.
670 assert( m_cornerDefectParamsPerPattern[module_pattern_idx.front()].size()==kNCornerDefectParams);
671 float rx=m_cornerDefectParamsPerPattern[module_pattern_idx.front()][kCornerDefectWidthColumnDirectionOffset]
672 +CLHEP::RandFlat::shoot(rndmEngine[kCornerDefects],m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectWidthColumnDirection]);
673 float ry=m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkWidthRowDirectionOffset]
674 +CLHEP::RandFlat::shoot(rndmEngine[kCornerDefects],m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkWidthRowDirection]);
678 // make sure that the sagitta is small enough that the circle can actually cross the bounds.
679 // conditions: the circle radius Rc has to be larger than rx and ry. If the circle radius
680 // is equal to rx or ry, the circle will just touch the border.
681 // This leads to quadratic equations x^2 + 2*hp1/2 * x + q solved by x1/2,1/2 = -hp1/2 +- sqrt(hp1/2^2-q)
682 float r2=rx*rx+ry*ry;
683 float hp1=0.5*sqrt(r2)*rx/ry;
684 float hp2=0.5*sqrt(r2)*ry/rx;
686 float sagitta_max = std::min( (-hp1 + sqrt( hp1*hp1 -q )),(-hp2 + sqrt( hp2*hp2 -q )) );
687 float sagitta_range = m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagitta];
688 float sagitta_offset = m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagittaOffset];
689 sagitta_offset=std::min(sagitta_offset,sagitta_max);
690 sagitta_range=std::min( sagitta_range, sagitta_max - sagitta_offset);
691 if (sagitta_range != m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagitta]
692 || sagitta_offset != m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagittaOffset]) {
693 ATH_MSG_VERBOSE("limited sagitta range from "
694 << m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagittaOffset]
696 << (m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagittaOffset]
697 + m_cornerDefectParamsPerPattern[module_pattern_idx.front() ][kCornerDefectkSagitta])
698 << " to " << sagitta_offset << ".." << (sagitta_offset+sagitta_range));
700 sagitta=sagitta_offset+CLHEP::RandFlat::shoot(rndmEngine[kCornerDefects],sagitta_range);
703 // compute column range with defects
704 double scale_rx=(corner_i==1 || corner_i==2) ? -1.f : 1.f;
705 double scale_ry=(corner_i<=1) ? -1.f : 1.f;
706 std::array<unsigned int,2> row_range{0,hwa_rows};
707 unsigned int row_var_idx=(corner_i<=1) ? 0 : 1;
708 unsigned int col_start=(corner_i==1 || corner_i==2) ? hwa_columns : 0;
710 Amg::Vector2D corner_pos{col_start*hwa_columnPitch,row_range[row_var_idx^1]*hwa_rowPitch};
712 unsigned int col_end = std::min(static_cast<unsigned int>(std::max(0,static_cast<int>(col_start)
713 + static_cast<int>(rx*scale_rx / hwa_columnPitch +.5))),
715 if (col_end < col_start) {
716 std::swap(col_start, col_end);
719 // compute circle parameters
720 Amg::Vector2D c0 = corner_pos + Amg::Vector2D{0.f,ry*scale_ry};
721 Amg::Vector2D c1 = corner_pos + Amg::Vector2D{rx*scale_rx,0.f};
722 double rho2=sqr(rx) + sqr(ry);
723 double radius = rho2/(8*sagitta)+0.5*sagitta;
724 Amg::Vector2D Rc = c0+(c1-c0)*.5 - (scale_ry*scale_rx) * (radius-sagitta)
726 *Amg::Vector2D{-ry*scale_ry,-rx*scale_rx};
728 double ymin=std::min(c0[1],c1[1]);
729 double ymax=std::max(c0[1],c1[1]);
730 for (unsigned int col_i=col_start; col_i< col_end; ++col_i) {
731 //compute row range with defects per column i.e. the row at which the circle crosses the column
732 // and the corresponding edge row (top or bottom)
733 double q = sqr(col_i*hwa_columnPitch-Rc[0]) + sqr(Rc[1]) - sqr(radius);
738 double cx = (-ph+sQ );
739 if (cx<ymin || cx>=ymax) {
740 double cx2=(-ph-sQ );
741 // pick the physical solution and make sure that
742 // that the solution remains in the allowed range despite
744 if (cx2<ymin || cx2 >=ymax) {
745 double d1=std::min(std::abs(cx-ymin),std::abs(cx-ymax));
746 double d2=std::min(std::abs(cx2-ymin),std::abs(cx2-ymax));
750 cx = std::clamp( cx, ymin, ymax - hwa_rowPitch*.25);
756 if (cx>=ymin && cx<ymax) {
757 row_range[row_var_idx]=static_cast<unsigned int>( std::max(0.,cx / hwa_rowPitch + .5));
759 if (row_range[0] < row_range[1] && row_range[0] <hwa_rows) {
760 // create corner defects for one column
761 std::pair<typename T_ModuleHelper::KEY_TYPE, typename T_ModuleHelper::KEY_TYPE> key_range{
762 helper.swapOfflineRowsColumns() ? helper.hardwareCoordinates(col_i, std::min(row_range[0],helper.nSensorColumns()-1u))
763 : helper.hardwareCoordinates(std::min(row_range[0],helper.nSensorRows()-1u),col_i),
764 helper.swapOfflineRowsColumns() ? helper.hardwareCoordinates(col_i, std::min(row_range[1]-1, helper.nSensorColumns()-1u))
765 : helper.hardwareCoordinates(std::min(row_range[1]-1,helper.nSensorRows()-1u),col_i)
768 if (EmulatedDefectsBuilder::insertKeyRange(helper, module_defects, key_range, T_ModuleHelper::makeDefectTypeKey(masks.size()>1 ? 1 : 0))) {
770 histogramDefects(helper,key_range,h2);
779 // add corner defects to random pixel defects for histogramming
780 n_mask_defects[0] += module_defects.size() - n_current_defects;
784 if (m_histogrammingEnabled) {
785 fillPerModuleHistograms(module_pattern_idx.front(),hist_pattern_i,matrix_histogram_index, matrix_index, module_i,
786 T_ModuleHelper::nMasks(), n_mask_defects, det_ele->center());
788 n_defects_total+=module_defects.size();
791 m_modulesWithoutDefectParameters += module_without_matching_pattern;
792 if (!m_outputFile.value().empty()) {
793 if (hasExtensions(m_outputFile.value(),".json")) {
794 toJson(*defects, m_outputFile.value());
796 else if (hasExtensions(m_outputFile.value(),".root")) {
797 toRoot(*defects, m_outputFile.value());
800 ATH_CHECK( defectsOut.record (std::move(defects)) );
802 if (msgLvl(MSG::INFO)) {
803 printSummaryOfDefectGeneration(T_ModuleHelper::nMasks(), n_error, n_defects_total,counts);
806 return StatusCode::SUCCESS;