diff --git a/CMakeLists.txt b/CMakeLists.txt
index 716afdc684a18e00d5b2afab6532ab4a93122488..f1e31b27a393edaad08d6662c8b60f89de466794 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -54,6 +54,12 @@ else()
 endif()
 find_package(fmt REQUIRED)
 
+if(DEFINED IRT_AUXFILE)
+  add_compile_definitions(IRT_AUXFILE)
+  find_package(IRT REQUIRED)
+  include_directories( ${CMAKE_INSTALL_PREFIX}/include ${CMAKE_INSTALL_PREFIX}/include/IRT)
+endif()
+
 #-----------------------------------------------------------------------------------
 set(a_lib_name ${PROJECT_NAME})
 
@@ -67,6 +73,9 @@ dd4hep_add_plugin(${a_lib_name}
 target_link_libraries(${a_lib_name}
   PUBLIC ${DD4hep_required_libraries} fmt::fmt
   )
+if(DEFINED IRT_AUXFILE)
+  target_link_libraries(${a_lib_name} PUBLIC IRT)
+endif()
 
 #-----------------------------------------------------------------------------------
 # Parse jinja templates: once by default, and once for all yml files
diff --git a/compact/drich.xml b/compact/drich.xml
index c98d41301f7d33363cff28faf98d432a7b341b09..2cb26423d7747558e3a2c2c2a9969dcd6101b55f 100644
--- a/compact/drich.xml
+++ b/compact/drich.xml
@@ -32,10 +32,13 @@
                            0 = off
 - `DRICH_debug_mirror`:    1 = draw full mirror shape for single sector; 0 = off
 - `DRICH_debug_sensors`:   1 = draw full sensor sphere for a single sector; 0 = off
+- `DRICH_create_irt_file`: 1 = create an auxiliary IRT config ROOT file; 0 = off
+                               - the file name is set by detector attribute `irt_filename` below
 </comment>
 <constant name="DRICH_debug_optics"    value="0"/>
 <constant name="DRICH_debug_mirror"    value="0"/>
 <constant name="DRICH_debug_sensors"   value="0"/>
+<constant name="DRICH_create_irt_file" value="0"/>
 </define>
 
 
@@ -55,6 +58,7 @@
   material="Aluminum"
   vis_vessel="DRICH_vessel_vis"
   vis_gas="DRICH_gas_vis"
+  irt_filename="irt-drich.root"
   >
 
 
diff --git a/scripts/create_IRT_auxfile.py b/scripts/create_IRT_auxfile.py
new file mode 100755
index 0000000000000000000000000000000000000000..1f0b79a3dd03b466dd0d1839678b245b4d967656
--- /dev/null
+++ b/scripts/create_IRT_auxfile.py
@@ -0,0 +1,54 @@
+# produce auxiliary ROOT file for the IRT algorithm
+
+import shutil, os, sys, argparse
+import xml.etree.ElementTree as et
+
+parser = argparse.ArgumentParser()
+parser.add_argument(
+        '-o', '--output-name', dest='outFile', default='irt-drich.root',
+        help='output auxiliary ROOT file name', type=str
+        )
+argv = parser.parse_args()
+
+import DDG4
+
+
+def run():
+
+    # compact files
+    if not 'DETECTOR_PATH' in os.environ:
+        print('ERROR: env var DETECTOR_PATH not set',file=sys.stderr)
+        exit(1)
+    mainFile = os.environ['DETECTOR_PATH'] + '/ecce.xml'
+    richFile = os.environ['DETECTOR_PATH'] + '/compact/drich.xml'
+
+    # backup original richFile, then parse
+    shutil.copy(richFile,richFile+'.bak')
+    richTree = et.parse(richFile)
+
+    # enable `DRICH_create_irt_file` mode
+    for constant in richTree.iter(tag='constant'):
+        if(constant.attrib['name']=='DRICH_create_irt_file'):
+            constant.set('value','1')
+
+    # set auxiliary file name
+    for detector in richTree.iter(tag='detector'):
+        detector.set('irt_filename',argv.outFile)
+
+    # overwrite original richFile
+    richTree.write(richFile)
+
+    # produce IRT config file
+    try:
+        kernel = DDG4.Kernel()
+        kernel.loadGeometry(f'file:{mainFile}')
+        kernel.terminate()
+        print(f'\n -> produced {argv.outFile}\n')
+    except:
+        pass
+
+    # revert to the original richFile
+    os.replace(richFile+'.bak',richFile)
+
+if __name__ == "__main__":
+    run()
diff --git a/src/DRICH_geo.cpp b/src/DRICH_geo.cpp
index 1a2bd4f2a616b71511ba4b2141c58e763fc9d672..0dfaf46fabe4520deadcf98a1d6999a9c744bb52 100644
--- a/src/DRICH_geo.cpp
+++ b/src/DRICH_geo.cpp
@@ -17,6 +17,16 @@
 
 #include <XML/Helper.h>
 
+#ifdef IRT_AUXFILE
+#include <CherenkovDetectorCollection.h>
+#include <CherenkovPhotonDetector.h>
+#include <CherenkovRadiator.h>
+#include <OpticalBoundary.h>
+#include <ParametricSurface.h>
+
+#include <TFile.h>
+#endif
+
 using namespace dd4hep;
 using namespace dd4hep::rec;
 
@@ -101,6 +111,11 @@ static Ref_t createDetector(Detector& desc, xml::Handle_t handle, SensitiveDetec
   long debugOpticsMode = desc.constantAsLong("DRICH_debug_optics");
   bool debugMirror     = desc.constantAsLong("DRICH_debug_mirror") == 1;
   bool debugSensors    = desc.constantAsLong("DRICH_debug_sensors") == 1;
+#ifdef IRT_AUXFILE
+  // - IRT auxiliary file
+  auto irtAuxFileName = detElem.attr<std::string>(_Unicode(irt_filename));
+  bool createIrtFile  = desc.constantAsLong("DRICH_create_irt_file") == 1;
+#endif
 
   // if debugging optics, override some settings
   bool debugOptics = debugOpticsMode > 0;
@@ -132,6 +147,58 @@ static Ref_t createDetector(Detector& desc, xml::Handle_t handle, SensitiveDetec
   printout(DEBUG, "DRICH_geo", "sectorMask, sectorOffset, moduleMask, moduleOffset = 0x%x %d 0x%x %d",
            sectorBits.mask(), sectorBits.offset(), moduleBits.mask(), moduleBits.offset());
 
+#ifdef IRT_AUXFILE
+  // IRT geometry auxiliary file ===========================================================
+  /* - optionally generate an auxiliary ROOT file, storing geometry objects for IRT
+   * - use compact file variable `DRICH_create_irt_file` to control this
+   */
+  TFile*                       irtAuxFile = nullptr;
+  CherenkovDetectorCollection* irtGeometry;
+  CherenkovDetector*           irtDetector;
+  if (createIrtFile) {
+    irtAuxFile = new TFile(irtAuxFileName.c_str(), "RECREATE");
+    printout(ALWAYS, "IRTLOG", "Producing auxiliary ROOT file for IRT: %s", irtAuxFileName.c_str());
+    irtGeometry = new CherenkovDetectorCollection();
+    irtDetector = irtGeometry->AddNewDetector(detName.c_str());
+  }
+
+  // container volume (envelope?)
+  /* FIXME: have no connection to GEANT G4LogicalVolume pointers; however all is needed
+   * is to make them unique so that std::map work internally; resort to using integers,
+   * who cares; material pointer can seemingly be '0', and effective refractive index
+   * for all radiators will be assigned at the end by hand; FIXME: should assign it on
+   * per-photon basis, at birth, like standalone GEANT code does;
+   */
+  FlatSurface* irtBoundary;
+  TVector3     normX(1, 0, 0); // normal vectors
+  TVector3     normY(0, -1, 0);
+  if (createIrtFile) {
+    irtBoundary = new FlatSurface((1 / mm) * TVector3(0, 0, vesselZmin), normX, normY);
+    for (int isec = 0; isec < nSectors; isec++) {
+      auto rad = irtGeometry->SetContainerVolume(
+          irtDetector,             // Cherenkov detector
+          "GasVolume",             // name
+          isec,                    // path
+          (G4LogicalVolume*)(0x0), // G4LogicalVolume (inaccessible? use an integer instead)
+          nullptr,                 // G4RadiatorMaterial (inaccessible?)
+          irtBoundary              // surface
+      );
+      rad->SetAlternativeMaterialName(gasvolMat.ptr()->GetName());
+    }
+  }
+
+  // photon detector // FIXME: args (G4Solid,G4Material) inaccessible?
+  CherenkovPhotonDetector* irtPhotonDetector;
+  if (createIrtFile) {
+    irtDetector->SetReadoutCellMask(cellMask); // readout mask
+    irtPhotonDetector = new CherenkovPhotonDetector(nullptr, nullptr);
+    irtGeometry->AddPhotonDetector(irtDetector,      // Cherenkov detector
+                                   nullptr,          // G4LogicalVolume (inaccessible?)
+                                   irtPhotonDetector // photon detector
+    );
+  }
+#endif
+
   // BUILD VESSEL ====================================================================
   /* - `vessel`: aluminum enclosure, the mother volume of the dRICh
    * - `gasvol`: gas volume, which fills `vessel`; all other volumes defined below
@@ -265,6 +332,47 @@ static Ref_t createDetector(Detector& desc, xml::Handle_t handle, SensitiveDetec
     // filterSkin.isValid();
   };
 
+#ifdef IRT_AUXFILE
+  // IRT aerogel + filter
+  /* AddFlatRadiator will create a pair of flat refractive surfaces internally;
+   * FIXME: should make a small gas gap at the upstream end of the gas volume;
+   */
+  FlatSurface* aerogelFlatSurface;
+  FlatSurface* filterFlatSurface;
+  if (createIrtFile) {
+    double irtAerogelZpos = vesselPos.z() + aerogelPV.position().z(); // center of aerogel, w.r.t. IP
+    double irtFilterZpos  = vesselPos.z() + filterPV.position().z();  // center of filter, w.r.t. IP
+    aerogelFlatSurface    = new FlatSurface((1 / mm) * TVector3(0, 0, irtAerogelZpos), normX, normY);
+    filterFlatSurface     = new FlatSurface((1 / mm) * TVector3(0, 0, irtFilterZpos), normX, normY);
+    for (int isec = 0; isec < nSectors; isec++) {
+      auto aerogelFlatRadiator = irtGeometry->AddFlatRadiator(
+          irtDetector,             // Cherenkov detector
+          "Aerogel",               // name
+          isec,                    // path
+          (G4LogicalVolume*)(0x1), // G4LogicalVolume (inaccessible? use an integer instead)
+          nullptr,                 // G4RadiatorMaterial
+          aerogelFlatSurface,      // surface
+          aerogelThickness / mm    // surface thickness
+      );
+      auto filterFlatRadiator = irtGeometry->AddFlatRadiator(
+          irtDetector,             // Cherenkov detector
+          "Filter",                // name
+          isec,                    // path
+          (G4LogicalVolume*)(0x2), // G4LogicalVolume (inaccessible? use an integer instead)
+          nullptr,                 // G4RadiatorMaterial
+          filterFlatSurface,       // surface
+          filterThickness / mm     // surface thickness
+      );
+      aerogelFlatRadiator->SetAlternativeMaterialName(aerogelMat.ptr()->GetName());
+      filterFlatRadiator->SetAlternativeMaterialName(filterMat.ptr()->GetName());
+    }
+    printout(ALWAYS, "IRTLOG", "irtAerogelZpos = %f cm", irtAerogelZpos);
+    printout(ALWAYS, "IRTLOG", "irtFilterZpos  = %f cm", irtFilterZpos);
+    printout(ALWAYS, "IRTLOG", "aerogel thickness = %f cm", aerogelThickness);
+    printout(ALWAYS, "IRTLOG", "filter thickness  = %f cm", filterThickness);
+  }
+#endif
+
   // SECTOR LOOP //////////////////////////////////////////////////////////////////////
 
   // initialize sensor centroids (used for mirror parameterization below); this is
@@ -397,6 +505,45 @@ static Ref_t createDetector(Detector& desc, xml::Handle_t handle, SensitiveDetec
             sensorSkin.isValid();
           };
 
+#ifdef IRT_AUXFILE
+          // IRT sensors
+          FlatSurface* sensorFlatSurface;
+          if (createIrtFile) {
+            // get sensor position, w.r.t. IP
+            // - sensorGlobalPos X and Y are equivalent to sensorPV.position()
+            double sensorLocalPos[3] = {0.0, 0.0, 0.0};
+            double sensorBufferPos[3];
+            double sensorGlobalPos[3];
+            sensorPV.ptr()->LocalToMaster(sensorLocalPos, sensorBufferPos);
+            vesselPV.ptr()->LocalToMaster(sensorBufferPos, sensorGlobalPos);
+
+            // get sensor flat surface normX and normY
+            // - ignore vessel transformation, since it is a pure translation
+            double sensorLocalNormX[3] = {1.0, 0.0, 0.0};
+            double sensorLocalNormY[3] = {0.0, 1.0, 0.0};
+            double sensorGlobalNormX[3], sensorGlobalNormY[3];
+            sensorPV.ptr()->LocalToMasterVect(sensorLocalNormX, sensorGlobalNormX);
+            sensorPV.ptr()->LocalToMasterVect(sensorLocalNormY, sensorGlobalNormY);
+
+            // create the IRT sensor geometry
+            sensorFlatSurface = new FlatSurface((1 / mm) * TVector3(sensorGlobalPos), TVector3(sensorGlobalNormX),
+                                                TVector3(sensorGlobalNormY));
+            irtDetector->CreatePhotonDetectorInstance(isec,              // sector
+                                                      irtPhotonDetector, // CherenkovPhotonDetector
+                                                      imodsec,           // copy number
+                                                      sensorFlatSurface  // surface
+            );
+            /* // (sensor printout is verbose, uncomment to enable)
+            if(imod==0) {
+              printout(ALWAYS, "IRTLOG", "");
+              printout(ALWAYS, "IRTLOG", "  SECTOR %d SENSORS:", isec);
+            }
+            printout(ALWAYS, "IRTLOG", "    sensor (imodsec,x,y,z) = 0x%08x  %5.2f  %5.2f  %5.2f cm",
+                imodsec, sensorGlobalPos[0], sensorGlobalPos[1], sensorGlobalPos[2]);
+            */
+          }
+#endif
+
           // increment sensor module number
           imod++;
 
@@ -512,8 +659,59 @@ static Ref_t createDetector(Detector& desc, xml::Handle_t handle, SensitiveDetec
     SkinSurface mirrorSkin(desc, mirrorDE, Form("mirror_optical_surface%d", isec), mirrorSurf, mirrorVol);
     mirrorSkin.isValid();
 
+#ifdef IRT_AUXFILE
+    // get mirror center coordinates, w.r.t. IP
+    /* - we have sector 0 coordinates `(zM,xM,rM)`, but here we try to access the numbers more generally,
+     *   so we get the mirror centers after sectorRotation
+     * - FIXME: boolean solids make this a bit tricky, both here and from `GeoSvc`, is there an easier way?
+     */
+    SphericalSurface* mirrorSphericalSurface;
+    OpticalBoundary*  mirrorOpticalBoundary;
+    if (createIrtFile) {
+      auto mirrorFinalPlacement = mirrorSectorPlacement * mirrorPlacement;
+      auto mirrorFinalCenter    = vesselPos + mirrorFinalPlacement.Translation().Vect(); // w.r.t. IP
+      mirrorSphericalSurface    = new SphericalSurface(
+          (1 / mm) * TVector3(mirrorFinalCenter.x(), mirrorFinalCenter.y(), mirrorFinalCenter.z()), mirrorRadius / mm);
+      mirrorOpticalBoundary = new OpticalBoundary(irtDetector->GetContainerVolume(), // CherenkovRadiator radiator
+                                                  mirrorSphericalSurface,            // surface
+                                                  false                              // bool refractive
+      );
+      irtDetector->AddOpticalBoundary(isec, mirrorOpticalBoundary);
+      printout(ALWAYS, "IRTLOG", "");
+      printout(ALWAYS, "IRTLOG", "  SECTOR %d MIRROR:", isec);
+      printout(ALWAYS, "IRTLOG", "    mirror x = %f cm", mirrorFinalCenter.x());
+      printout(ALWAYS, "IRTLOG", "    mirror y = %f cm", mirrorFinalCenter.y());
+      printout(ALWAYS, "IRTLOG", "    mirror z = %f cm", mirrorFinalCenter.z());
+      printout(ALWAYS, "IRTLOG", "    mirror R = %f cm", mirrorRadius);
+    }
+
+    // IRT: complete the radiator volume description; this is the rear side of the container gas volume
+    if (createIrtFile)
+      irtDetector->GetRadiator("GasVolume")->m_Borders[isec].second = mirrorSphericalSurface;
+#endif
+
   }; // END SECTOR LOOP //////////////////////////
 
+#ifdef IRT_AUXFILE
+  // write IRT auxiliary file
+  if (createIrtFile) {
+    // set refractive indices
+    // FIXME: are these (weighted) averages? can we automate this?
+    std::map<std::string, double> rIndices;
+    rIndices.insert({"GasVolume", 1.0008});
+    rIndices.insert({"Aerogel", 1.0190});
+    rIndices.insert({"Filter", 1.5017});
+    for (auto const& [rName, rIndex] : rIndices) {
+      auto rad = irtDetector->GetRadiator(rName.c_str());
+      if (rad)
+        rad->SetReferenceRefractiveIndex(rIndex);
+    }
+    // write
+    irtGeometry->Write();
+    irtAuxFile->Close();
+  }
+#endif
+
   return det;
 }