Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,7 @@ UseTab: Never
# Do not format protobuf files
Language: Proto
DisableFormat: true
---
# Do not format JSON configuration files
Language: Json
DisableFormat: true
4 changes: 3 additions & 1 deletion Common/SimConfig/include/SimConfig/SimConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ struct SimConfigData {
bool mForwardKine = false; // true if tracks and event headers are to be published on a FairMQ channel (for reading by other consumers)
bool mWriteToDisc = true; // whether we write simulation products (kine, hits) to disc
VertexMode mVertexMode = VertexMode::kDiamondParam; // by default we should use die InteractionDiamond parameter
std::string mExtGeomFile = ""; // optional path to a JSON file describing external (CAD) geometry modules to inject

ClassDefNV(SimConfigData, 4);
ClassDefNV(SimConfigData, 5);
};

// A singleton class which can be used
Expand Down Expand Up @@ -178,6 +179,7 @@ class SimConfig
bool forwardKine() const { return mConfigData.mForwardKine; }
bool writeToDisc() const { return mConfigData.mWriteToDisc; }
VertexMode getVertexMode() const { return mConfigData.mVertexMode; }
std::string getExtGeomFilename() const { return mConfigData.mExtGeomFile; }

// returns the pair of collision context filename as well as event prefix encoded
// in the mFromCollisionContext string. Returns empty string if information is not available or set.
Expand Down
4 changes: 3 additions & 1 deletion Common/SimConfig/src/SimConfig.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@ void SimConfig::initOptions(boost::program_options::options_description& options
"asservice", bpo::value<bool>()->default_value(false), "run in service/server mode")(
"noGeant", bpo::bool_switch(), "prohibits any Geant transport/physics (by using tight cuts)")(
"forwardKine", bpo::bool_switch(), "forward kinematics on a FairMQ channel")(
"noDiscOutput", bpo::bool_switch(), "switch off writing sim results to disc (useful in combination with forwardKine)");
"noDiscOutput", bpo::bool_switch(), "switch off writing sim results to disc (useful in combination with forwardKine)")(
"extGeomFile", bpo::value<std::string>()->default_value(""), "Path to a JSON file describing external (CAD) geometry modules to inject (see Detectors/Passive ExternalModule). Modules are added when their 'name' is part of the active module list.");
options.add_options()("fromCollContext", bpo::value<std::string>()->default_value(""), "Use a pregenerated collision context to infer number of events to simulate, how to embedd them, the vertex position etc. Takes precedence of other options such as \"--nEvents\". The format is COLLISIONCONTEXTFILE.root[:SIGNALNAME] where SIGNALNAME is the event part in the context which is relevant.");
}

Expand Down Expand Up @@ -354,6 +355,7 @@ bool SimConfig::resetFromParsedMap(boost::program_options::variables_map const&
if (vm.count("noemptyevents")) {
mConfigData.mFilterNoHitEvents = true;
}
mConfigData.mExtGeomFile = vm["extGeomFile"].as<std::string>();
mConfigData.mFromCollisionContext = vm["fromCollContext"].as<std::string>();
auto collcontext_simprefix = getCollContextFilenameAndEventPrefix();
adjustFromCollContext(collcontext_simprefix.first, collcontext_simprefix.second);
Expand Down
1 change: 1 addition & 0 deletions Detectors/Base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ o2_add_library(DetectorsBase
src/GlobalParams.cxx
src/O2Tessellated.cxx
src/TGeoGeometryUtils.cxx
src/CADGeometryUtils.cxx
PUBLIC_LINK_LIBRARIES FairRoot::Base
O2::CommonUtils
O2::DetectorsCommonDataFormats
Expand Down
50 changes: 50 additions & 0 deletions Detectors/Base/include/DetectorsBase/CADGeometryUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

/// \file CADGeometryUtils.h
/// \brief Helpers to inject CAD-derived (TGeo) geometry into O2 simulation
///
/// These utilities are shared between purely passive external modules
/// (o2::passive::ExternalModule) and sensitive external detectors
/// (o2::ext::ExternalDetector). They deal with the geometry produced by
/// scripts/geometry/O2_CADtoTGeo.py, which is emitted as a ROOT macro.

#ifndef ALICEO2_BASE_CADGEOMETRYUTILS_H
#define ALICEO2_BASE_CADGEOMETRYUTILS_H

#include <string>

class TGeoVolume;

namespace o2::base
{

/// JIT-compile a CAD-derived ROOT geometry macro (as produced by O2_CADtoTGeo.py)
/// and execute it to obtain the top TGeoVolume of the described module.
///
/// The macro body is wrapped into a unique namespace (derived from \a instanceTag)
/// so that several such macros — which all export identically named symbols
/// (build(), get_builder_hook_unchecked(), ...) — can coexist in the same Cling
/// session without colliding. Returns nullptr on failure.
///
/// \param macroFile path to the geometry macro (shell variables are expanded)
/// \param instanceTag a short tag used to build a unique, human-readable namespace
TGeoVolume* buildCADVolumeFromMacro(const std::string& macroFile, const std::string& instanceTag);

/// Re-register the TGeo media used in the volume tree rooted at \a top into the O2
/// MaterialManager under ownership of \a modulename, rewiring the volumes to the
/// newly created media. This brings the CAD-imported media under O2's media/cut
/// handling (so that e.g. tracking cuts apply consistently).
void remapCADMedia(TGeoVolume* top, const char* modulename);

} // namespace o2::base

#endif
11 changes: 8 additions & 3 deletions Detectors/Base/include/DetectorsBase/Detector.h
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,14 @@ class DetImpl : public o2::base::Detector
{
using Hit_t = typename std::remove_pointer<decltype(static_cast<Det*>(this)->Det::getHits(0))>::type;
using Collector_t = tbb::concurrent_unordered_map<int, std::vector<std::vector<std::unique_ptr<Hit_t>>>>;
static Collector_t hitcollector; // note: we can't put this as member because
// decltype type deduction doesn't seem to work for class members; so we use a static member
// and will use some pointer member to communicate this data to other functions
// note: we can't put this as a member because decltype type deduction doesn't seem to work for
// class members; so we use a static and communicate it to other functions via a pointer member.
// The collector must be kept *per detector instance* (keyed by 'this'): for most detectors there
// is a single instance per C++ type, but several external detectors share the same type
// (o2::ext::ExternalDetector) and would otherwise clobber/double-free each other's buffers.
// tbb::concurrent_unordered_map is node-based, so the reference stays valid across insertions.
static tbb::concurrent_unordered_map<void const*, Collector_t> hitcollectors;
auto& hitcollector = hitcollectors[this];
mHitCollectorBufferPtr = (char*)&hitcollector;

int probe = 0;
Expand Down
202 changes: 202 additions & 0 deletions Detectors/Base/src/CADGeometryUtils.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

#include "DetectorsBase/CADGeometryUtils.h"
#include "DetectorsBase/MaterialManager.h"
#include <CommonUtils/FileSystemUtils.h>
#include <TGeoVolume.h>
#include <TGeoNode.h>
#include <TGeoMaterial.h>
#include <TGeoMedium.h>
#include <TInterpreter.h>
#include <TROOT.h>
#include <TString.h>
#include <TGlobal.h>
#include <fairlogger/Logger.h>
#include <atomic>
#include <cctype>
#include <filesystem>
#include <fstream>
#include <functional>
#include <string>
#include <unordered_map>
#include <unordered_set>

namespace o2::base
{

TGeoVolume* buildCADVolumeFromMacro(const std::string& macroFile, const std::string& instanceTag)
{
if (macroFile.empty()) {
return nullptr;
}
auto expandedHookFileName = o2::utils::expandShellVarsInFileName(macroFile);
if (!std::filesystem::exists(expandedHookFileName)) {
LOG(error) << "External geometry macro " << expandedHookFileName << " does not exist";
return nullptr;
}

// We JIT the macro into a *unique* namespace per call. This is essential when several
// external geometries are present at the same time: every macro produced by
// O2_CADtoTGeo.py exports identically named symbols (build(), get_builder_hook_unchecked(),
// LoadFacets(), ...). Loading them all into the single global Cling scope would collide
// (the first definition wins and subsequent macros are silently ignored). By wrapping each
// macro body in its own namespace we keep the symbols separate. The preprocessor #include
// lines must stay at global scope, so we hoist them out of the namespace.
std::ifstream macroStream(expandedHookFileName, std::ios::in);
if (!macroStream.is_open()) {
LOG(error) << "Cannot open external geometry macro " << expandedHookFileName;
return nullptr;
}
std::string preamble; // #include (and other top-level preprocessor) lines -> global scope
std::string body; // everything else -> wrapped into a unique namespace
std::string line;
while (std::getline(macroStream, line)) {
auto firstNonSpace = line.find_first_not_of(" \t");
if (firstNonSpace != std::string::npos && line[firstNonSpace] == '#') {
preamble += line + "\n";
} else {
body += line + "\n";
}
}

// build a unique, valid C++ identifier for the namespace
static std::atomic<int> instanceCounter{0};
std::string ns = std::string("o2_cadgeom_") + instanceTag + "_" + std::to_string(instanceCounter++);
for (auto& c : ns) {
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
c = '_';
}
}

const std::string wrapped = preamble + "\nnamespace " + ns + " {\n" + body + "\n}\n";
if (!gInterpreter->Declare(wrapped.c_str())) {
LOG(error) << "Failed to JIT external geometry macro " << expandedHookFileName;
return nullptr;
}

// retrieve the builder hook from the unique namespace
const std::string globalName = "__" + ns + "_hook__";
gROOT->ProcessLine(Form("std::function<TGeoVolume*()> %s = %s::get_builder_hook_unchecked();",
globalName.c_str(), ns.c_str()));
auto global = gROOT->GetGlobal(globalName.c_str());
if (!global) {
LOG(error) << "Could not retrieve geometry builder hook from macro " << expandedHookFileName;
return nullptr;
}
auto hook = *reinterpret_cast<std::function<TGeoVolume*()>*>(global->GetAddress());
LOG(info) << "CAD geometry hook initialized from file " << expandedHookFileName << " (namespace " << ns << ")";

auto top = hook();
if (!top) {
LOG(error) << "CAD geometry macro " << expandedHookFileName << " did not return a top volume";
}
return top;
}

void remapCADMedia(TGeoVolume* top, const char* modulename)
{
std::unordered_map<TGeoMedium*, TGeoMedium*> medium_ptr_mapping;
std::unordered_set<TGeoVolume*> volumes_already_treated;
int counter = 1;

// The transformer function
auto transform_media = [&](TGeoVolume* vol_) {
if (volumes_already_treated.find(vol_) != volumes_already_treated.end()) {
// this volume was already transformed
return;
}
volumes_already_treated.insert(vol_);

if (dynamic_cast<TGeoVolumeAssembly*>(vol_)) {
// do nothing for assemblies (they don't have a medium)
return;
}

auto medium = vol_->GetMedium();
if (!medium) {
return;
}

auto iter = medium_ptr_mapping.find(medium);
if (iter != medium_ptr_mapping.end()) {
// This medium has already been transformed, so
// we just update the volume
vol_->SetMedium(iter->second);
return;
} else {
LOG(info) << "Transforming media with name " << medium->GetName() << " for volume " << vol_->GetName();

// we found a medium, not yet treated
auto curr_mat = medium->GetMaterial();
auto& matmgr = o2::base::MaterialManager::Instance();

matmgr.Material(modulename, counter, curr_mat->GetName(), curr_mat->GetA(), curr_mat->GetZ(), curr_mat->GetDensity(), curr_mat->GetRadLen(), curr_mat->GetIntLen());
// TGeo medium params are stored in a flat array with the following convention
// fParams[0] = isvol;
// fParams[1] = ifield;
// fParams[2] = fieldm;
// fParams[3] = tmaxfd;
// fParams[4] = stemax;
// fParams[5] = deemax;
// fParams[6] = epsil;
// fParams[7] = stmin;
const auto isvol = medium->GetParam(0);
const auto isxfld = medium->GetParam(1);
const auto sxmgmx = medium->GetParam(2);
const auto tmaxfd = medium->GetParam(3);
const auto stemax = medium->GetParam(4);
const auto deemax = medium->GetParam(5);
const auto epsil = medium->GetParam(6);
const auto stmin = medium->GetParam(7);

matmgr.Medium(modulename, counter, medium->GetName(), counter, isvol, isxfld, sxmgmx, tmaxfd, stemax, deemax, epsil, stmin);

// there will be new Material and Medium objects; fetch them
auto new_med = matmgr.getTGeoMedium(modulename, counter);

// insert into cache
medium_ptr_mapping[medium] = new_med;
vol_->SetMedium(new_med);
counter++;
}
}; // end transformer lambda

// a generic volume walker
std::function<void(TGeoVolume*)> visit_volume;
visit_volume = [&](TGeoVolume* vol) -> void {
if (!vol) {
return;
}

// call the transformer
transform_media(vol);

// Recurse into daughters
const int nd = vol->GetNdaughters();
for (int i = 0; i < nd; ++i) {
TGeoNode* node = vol->GetNode(i);
if (!node) {
continue;
}
TGeoVolume* child = node->GetVolume();
if (!child) {
continue;
}

visit_volume(child);
}
};

visit_volume(top);
}

} // namespace o2::base
1 change: 1 addition & 0 deletions Detectors/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_subdirectory(TOF)
add_subdirectory(ZDC)

add_subdirectory(ITSMFT)
add_subdirectory(External) # sensitive external (CAD-derived) detectors; uses ITSMFT hit type
add_subdirectory(TRD)

add_subdirectory(MUON)
Expand Down
22 changes: 22 additions & 0 deletions Detectors/External/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2019-2020 CERN and copyright holders of ALICE O2.
# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
# All rights not expressly granted are reserved.
#
# This software is distributed under the terms of the GNU General Public
# License v3 (GPL Version 3), copied verbatim in the file "COPYING".
#
# In applying this license CERN does not waive the privileges and immunities
# granted to it by virtue of its status as an Intergovernmental Organization
# or submit itself to any jurisdiction.

o2_add_library(ExternalDetectors
SOURCES src/ExternalDetector.cxx
PUBLIC_LINK_LIBRARIES O2::DetectorsBase
O2::SimulationDataFormat
O2::CommonUtils
RapidJSON::RapidJSON)

o2_target_root_dictionary(ExternalDetectors
HEADERS include/ExternalDetectors/Hit.h
include/ExternalDetectors/ExternalDetector.h
LINKDEF src/ExternalDetectorsLinkDef.h)
Loading
Loading