#ifndef __JDETECTOR__JDETECTORTOOLKIT__ #define __JDETECTOR__JDETECTORTOOLKIT__ #include #include #include #include #include #include #include #include #include #include #include "JDetector/JDetector.hh" #include "JDetector/JMonteCarloDetector.hh" #include "JDetector/JTimeRange.hh" #include "JDetector/JLocation.hh" #include "JDetector/JStringCounter.hh" #include "JDetector/JPMTIdentifier.hh" #include "JGeometry3D/JVector3D.hh" #include "JGeometry3D/JVersor3D.hh" #include "JGeometry3D/JCylinder3D.hh" #include "JGeometry3D/JRotation3D.hh" #include "JPhysics/JConstants.hh" #include "JMath/JConstants.hh" #include "JMath/JMathToolkit.hh" #include "JTools/JRange.hh" #include "JIO/JFileStreamIO.hh" #include "Jeep/JeepToolkit.hh" #include "JLang/JException.hh" #include "JLang/gzstream.h" #include "JLang/JManip.hh" #include "JLang/Jpp.hh" /** * \author mdejong */ namespace JDETECTOR {} namespace JPP { using namespace JDETECTOR; } /** * Auxiliary classes and methods for detector calibration and simulation. */ namespace JDETECTOR { using JLANG::JException; using JLANG::JFileOpenException; using JLANG::JProtocolException; using JLANG::JValueOutOfRange; using JGEOMETRY3D::JRotation3D; /** * File name extensions. */ static const char* const GENDET_DETECTOR_FILE_FORMAT = "det"; //!< file format used by gendet static const char* const BINARY_DETECTOR_FILE_FORMAT[] = { "dat", "datx" }; //!< JIO binary file format static const char* const KM3NET_DETECTOR_FILE_FORMAT = "detx"; //!< %KM3NeT standard ASCII format static const char* const ZIPPED_DETECTOR_FILE_FORMAT = "gz"; //!< zipped %KM3NeT standard ASCII format static const char* const GDML_DETECTOR_FILE_FORMAT = "gdml"; //!< KM3Sim input format static const char* const GDML_SCHEMA = getenv("GDML_SCHEMA_DIR"); //!< directory necessary for correct GDML header output static const char* const CAN_MARGIN_M = getenv("CAN_MARGIN_M"); //!< extension of the detector size to comply with the can definition static const char* const G4GDML_DEFAULT_SCHEMALOCATION = "http://service-spi.web.cern.ch/service-spi/app/releases/GDML/schema/gdml.xsd"; /** * Get maximal distance between modules in detector. * The option can be used to include base modules, if any. * * \param detector detector * \param option option * \return maximal distance [m] */ inline double getMaximalDistance(const JDetector& detector, const bool option = false) { using namespace JPP; double dmax = 0.0; for (JDetector::const_iterator i1 = detector.begin(); i1 != detector.end(); ++i1) { for (JDetector::const_iterator i2 = detector.begin(); i2 != i1; ++i2) { if (option || (i1->getFloor() != 0 && i2->getFloor() != 0)) { const double ds = getDistance(i1->getPosition(), i2->getPosition()); if (ds > dmax) { dmax = ds; } } } } return dmax; } /** * Get maximal time between optical modules in detector following causality. * * \param detector detector * \return maximal time [ns] */ inline double getMaximalTime(const JDetector& detector) { using namespace JPP; return getMaximalDistance(detector) * getIndexOfRefraction() * getInverseSpeedOfLight(); } /** * Get maximal time between optical modules in detector following causality. * The road width corresponds to the maximal distance traveled by the light. * * \param detector detector * \param roadWidth_m road width [m] * \return maximal time [ns] */ inline double getMaximalTime(const JDetector& detector, const double roadWidth_m) { using namespace JPP; const double Dmax_m = getMaximalDistance(detector); return (sqrt((Dmax_m + roadWidth_m*getSinThetaC()) * (Dmax_m - roadWidth_m*getSinThetaC())) + roadWidth_m * getSinThetaC() * getTanThetaC()) * getInverseSpeedOfLight(); } /** * Get de-calibrated time range. * * The de-calibrated time range is corrected for minimal and maximal time offset of PMTs in given module. * * \param timeRange time range [ns] * \param module module * \return time range [ns] */ inline JTimeRange getTimeRange(const JTimeRange& timeRange, const JModule& module) { if (!module.empty()) { JTimeRange time_range(JTimeRange::DEFAULT_RANGE()); for (JModule::const_iterator pmt = module.begin(); pmt != module.end(); ++pmt) { const JCalibration& calibration = pmt->getCalibration(); time_range.include(putTime(timeRange.getLowerLimit(), calibration)); time_range.include(putTime(timeRange.getUpperLimit(), calibration)); } return time_range; } else { return timeRange; } } /** * Get number of PMTs. * * \param module module * \return number of PMTs */ inline int getNumberOfPMTs(const JModule& module) { return module.size(); } /** * Get number of PMTs. * * \param detector detector * \return number of PMTs */ inline int getNumberOfPMTs(const JDetector& detector) { int number_of_pmts = 0; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { number_of_pmts += getNumberOfPMTs(*module); } return number_of_pmts; } /** * Get list of strings identifiers. * * \param detector detector * \return list of string identifiers */ inline std::set getStringIDs(const JDetector& detector) { std::set buffer; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { buffer.insert(module->getString()); } return buffer; } /** * Get list of modules identifiers. * * \param detector detector * \param option option to include base modules * \return list of module identifiers */ inline std::set getModuleIDs(const JDetector& detector, const bool option = false) { std::set buffer; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { if (option || !module->empty()) { buffer.insert(module->getID()); } } return buffer; } /** * Get number of floors. * * \param detector detector * \return number of floors */ inline int getNumberOfFloors(const JDetector& detector) { std::set buffer; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { buffer.insert(module->getFloor()); } return buffer.size(); } /** * Type definition for range of floors. */ typedef JTOOLS::JRange floor_range; /** * Get range of floors. * * \param detector detector * \return range of floors */ inline floor_range getRangeOfFloors(const JDetector& detector) { floor_range buffer = floor_range::DEFAULT_RANGE(); for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { buffer.include(module->getFloor()); } return buffer; } /** * Get number of modules. * * \param detector detector * \return number of modules */ inline int getNumberOfModules(const JDetector& detector) { std::set buffer; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { buffer.insert(module->getID()); } return buffer.size(); } /** * Get rotation over X axis in Geant4 coordinate system * * \param dir direction * \return X-rotation [deg] */ inline double GetXrotationG4(const JVersor3D& dir) { using namespace JPP; const double phi = atan2(dir.getDY(), dir.getDZ())*(180.0/PI); if (phi < 0.0){ return phi + 360.0; } else{ return phi; } } /** * Get rotation over Y axis in Geant4 coordinate system * * \param dir direction * \return Y-rotation [deg] */ inline double GetYrotationG4(const JVersor3D& dir) { using namespace JPP; return asin(-dir.getDX())*(180.0/PI); } inline void read_gdml(std::istream&, JDetector&) { THROW(JException, "Operation not defined."); } /** * Writes KM3Sim GDML input file from detector * * \param out output stream * \param detector detector */ inline void write_gdml(std::ostream& out, const JDetector& detector) { using namespace std; using namespace JPP; const double DEFAULT_CAN_MARGIN_M = 350.0; // default can margin [m] const double DEFAULT_WORLD_MARGIN_M = 50.0; // default world margin [m] const JCylinder3D cylinder(detector.begin(), detector.end()); double can_margin; if (CAN_MARGIN_M) { can_margin = atof(CAN_MARGIN_M); } else { cerr << "CAN_MARGIN_M not defined! Setting standard CAN_MARGIN_M " << DEFAULT_CAN_MARGIN_M << " m." << endl; can_margin = DEFAULT_CAN_MARGIN_M; //this is given in meters like all the dimensions in the GDML file (look at the unit field of every position and solid definition) } const double WorldCylinderHeight = 2*(cylinder.getZmax() - cylinder.getZmin() + can_margin + DEFAULT_WORLD_MARGIN_M); const double WorldRadius = cylinder.getLength() + cylinder.getRadius() + can_margin + DEFAULT_WORLD_MARGIN_M; const double Crust_Z_size = WorldCylinderHeight/2; const double Crust_Z_position = -WorldCylinderHeight/4; out << "\n\n\n\n"; } else { out << GDML_SCHEMA << "gdml.xsd\">\n\n\n"; } out << "\n"; out << "\n"; out << "\n\n"; int PMTs_NO = 0; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { if(module->empty()) continue; const JVector3D center = module->getCenter(); out << "getString() << "_Dom" << module->getID() << "\" unit=\"m\" x=\"" << module->getX() << "\" y=\"" << module->getY() << "\" z=\"" << module->getZ() << "\"/>\n"; for (JModule::const_iterator pmt = module->begin(); pmt != module->end(); ++pmt) { const JVector3D vec = static_cast(*pmt).sub(center); out << "getID() << "_" << module->getID() << "\" unit=\"m\" x=\"" << vec.getX() << "\" y=\"" << vec.getY() << "\" z=\"" << vec.getZ() << "\"/>\n"; out << "getID() << "_" << module->getID() << "\" unit=\"deg\" x=\"" << GetXrotationG4(*pmt) << "\" y=\"" << GetYrotationG4(*pmt) << "\" z=\"0.000000\"/>\n"; out << "getID() << "\"/>\n"; PMTs_NO++; } } out << "\n"; out << "\n\n\n"; out << "\n\n"; out << "\n"; out << "\n"; out << "\n"; out << "\n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; out << "\n\n\n"; out << "\n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; out << "\n"; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { if(module->empty()) continue; out << " getID() << "\">\n"; out << " \n"; out << " \n"; for (JModule::const_iterator pmt = module->begin(); pmt != module->end(); ++pmt) { out << " \n"; out << " \n"; out << " getID() << "_" << module->getID() << "\"/>\n"; out << " getID() << "_" << module->getID() << "\"/>\n"; out << " \n"; } out << " \n"; } out << "\n"; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { if(module->empty()) continue; out << " getID() << "\">\n"; out << " \n"; out << " \n"; out << " \n"; out << " getID() << "\"/>\n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; } out << "\n"; out << "\n"; out << " \n"; out << " \n"; out << "\n"; out << "\n"; out << "\n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; out << " \n"; for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) { if(module->empty()) continue; out << " \n"; out << " getID() << "\"/>\n"; out << " getString() << "_Dom" << module->getID() << "\"/>\n"; out << " \n"; out << " \n"; } out << "\n"; out << "\n"; out << "\n"; out << "\n"; out << "\n"; out << "\n"; } /** * Load detector from input file. * * Supported file formats: * - ASCII file, extension JDETECTOR::GENDET_DETECTOR_FILE_FORMAT, gendet format; * - binary file, extension JDETECTOR::BINARY_DETECTOR_FILE_FORMAT, Jpp internal format; * - ASCII file, extension JDETECTOR::KM3NET_DETECTOR_FILE_FORMAT, %KM3NeT standard format; * - gzipped file, extension JDETECTOR::ZIPPED_DETECTOR_FILE_FORMAT, %KM3NeT standard format; * * \param file_name file name * \param detector detector */ inline void load(const std::string& file_name, JDetector& detector) { using namespace std; using namespace JPP; if (getFilenameExtension(file_name) == GENDET_DETECTOR_FILE_FORMAT) { JMonteCarloDetector buffer(true); ifstream in(file_name.c_str()); if (!in) { THROW(JFileOpenException, "File not opened for reading: " << file_name); } in >> buffer; in.close(); detector.swap(buffer); } else if (getFilenameExtension(file_name) == BINARY_DETECTOR_FILE_FORMAT[0] || getFilenameExtension(file_name) == BINARY_DETECTOR_FILE_FORMAT[1]) { JFileStreamReader in(file_name.c_str()); if (!in) { THROW(JFileOpenException, "File not opened for reading: " << file_name); } try { detector.read(in); } catch(const exception& error) { // recovery procedure for old format of comments JDetector::setStartOfComment(JDetector::OLD_START_OF_COMMENT); in.clear(); in.rewind(); detector.read(in); JDetector::setStartOfComment(JDetector::NEW_START_OF_COMMENT); } in.close(); } else if (getFilenameExtension(file_name) == KM3NET_DETECTOR_FILE_FORMAT) { ifstream in(file_name.c_str()); if (!in) { THROW(JFileOpenException, "File not opened for reading: " << file_name); } in >> detector; in.close(); } else if (getFilenameExtension(file_name) == ZIPPED_DETECTOR_FILE_FORMAT) { igzstream in(file_name.c_str()); if (!in) { THROW(JFileOpenException, "File not opened for reading: " << file_name); } in >> detector; in.close(); } else { THROW(JProtocolException, "Protocol not defined: " << file_name); } if (!detector.hasVersion()) { THROW(JValueOutOfRange, "Invalid version <" << detector.getVersion() << ">"); } } /** * Store detector to output file. * * Supported file formats: * - binary file, extension JDETECTOR::BINARY_DETECTOR_FILE_FORMAT, Jpp internal format; * - ASCII file, extension JDETECTOR::KM3NET_DETECTOR_FILE_FORMAT, %KM3NeT standard format; * - gzipped file, extension JDETECTOR::ZIPPED_DETECTOR_FILE_FORMAT, %KM3NeT standard format; * - gdml file, extension JDETECTOR::GDML_DETECTOR_FILE_FORMAT, KM3Sim input format; * * \param file_name file name * \param detector detector */ inline void store(const std::string& file_name, const JDetector& detector) { using namespace std; using namespace JPP; if (getFilenameExtension(file_name) == BINARY_DETECTOR_FILE_FORMAT[1]) { JFileStreamWriter out(file_name.c_str()); if (!out) { THROW(JFileOpenException, "File not opened for writing: " << file_name); } detector.write(out); out.close(); } else if (getFilenameExtension(file_name) == KM3NET_DETECTOR_FILE_FORMAT) { std::ofstream out(file_name.c_str()); if (!out) { THROW(JFileOpenException, "File not opened for writing: " << file_name); } out << detector; out.close(); } else if (getFilenameExtension(file_name) == ZIPPED_DETECTOR_FILE_FORMAT) { ogzstream out(file_name.c_str()); if (!out) { THROW(JFileOpenException, "File not opened for writing: " << file_name); } out << detector; out.close(); } else if (getFilenameExtension(file_name) == GDML_DETECTOR_FILE_FORMAT) { std::ofstream out(file_name.c_str()); if (!out) { THROW(JFileOpenException, "File not opened for writing: " << file_name); } write_gdml(out, detector); out.close(); } else { THROW(JProtocolException, "Protocol not defined: " << file_name); } } /** * Auxiliary class to get rotation matrix between two optical modules. */ struct JRotation : public JRotation3D { static const size_t NUMBER_OF_DIMENSIONS = 3; //!< Number of dimensions /** * Get rotation matrix to go from first to second module. * * \param first first module * \param second second module * \return rotation matrix */ const JRotation3D& operator()(const JModule& first, const JModule& second) { this->setIdentity(); if (first.size() == second.size()) { const size_t N = first.size(); if (N >= NUMBER_OF_DIMENSIONS) { in .resize(N); out.resize(N); for (size_t i = 0; i != N; ++i) { in [i] = first .getPMT(i).getDirection(); out[i] = second.getPMT(i).getDirection(); } for (size_t i = 0; i != NUMBER_OF_DIMENSIONS; ++i) { if (!orthonormalise(i)) { THROW(JException, "Failure to orthonormalise direction " << i); } } this->a00 = out[0].getX() * in[0].getX() + out[1].getX() * in[1].getX() + out[2].getX() * in[2].getX(); this->a01 = out[0].getX() * in[0].getY() + out[1].getX() * in[1].getY() + out[2].getX() * in[2].getY(); this->a02 = out[0].getX() * in[0].getZ() + out[1].getX() * in[1].getZ() + out[2].getX() * in[2].getZ(); this->a10 = out[0].getY() * in[0].getX() + out[1].getY() * in[1].getX() + out[2].getY() * in[2].getX(); this->a11 = out[0].getY() * in[0].getY() + out[1].getY() * in[1].getY() + out[2].getY() * in[2].getY(); this->a12 = out[0].getY() * in[0].getZ() + out[1].getY() * in[1].getZ() + out[2].getY() * in[2].getZ(); this->a20 = out[0].getZ() * in[0].getX() + out[1].getZ() * in[1].getX() + out[2].getZ() * in[2].getX(); this->a21 = out[0].getZ() * in[0].getY() + out[1].getZ() * in[1].getY() + out[2].getZ() * in[2].getY(); this->a22 = out[0].getZ() * in[0].getZ() + out[1].getZ() * in[1].getZ() + out[2].getZ() * in[2].getZ(); } else { THROW(JException, "Module " << first.getID() << " size " << N << " < " << NUMBER_OF_DIMENSIONS); } } else { THROW(JException, "Module " << first.getID() << " size " << first.size() << " != " << second.size()); } return *this; } private: /** * Put normalised primary direction at specified index and orthoganilise following directions.\n * This procedure follows Gram-Schmidt process. * * \param index index * \param precision precision * \return true if primary direction exists; else false */ bool orthonormalise(const size_t index, const double precision = std::numeric_limits::epsilon()) { using namespace std; size_t pos = index; for (size_t i = index + 1; i != in.size(); ++i) { if (in[i].getLengthSquared() > in[pos].getLengthSquared()) { pos = i; } } const double u = in[pos].getLength(); if (u > precision) { in [pos] /= u; out[pos] /= u; if (pos != index) { swap(in [pos], in [index]); swap(out[pos], out[index]); } for (size_t i = index + 1; i != in.size(); ++i) { const double dot = in[index].getDot(in[i]); in [i] -= dot * in [index]; out[i] -= dot * out[index]; } return true; } else { return false; } } std::vector in; std::vector out; }; /** * Function object to get rotation matrix to go from first to second module. */ static JRotation getRotation; /** * Get position to go from first to second module. * * \param first first module * \param second second module * \return position */ inline JPosition3D getPosition(const JModule& first, const JModule& second) { return second.getPosition() - first.getPosition(); } /** * Get calibration to go from first to second calibration. * * \param first first calibration * \param second second calibration * \return calibration */ inline JCalibration getCalibration(const JCalibration& first, const JCalibration& second) { return JCalibration(second.getT0() - first.getT0()); } } #endif