Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-30 10:30:04

0001 
0002 #ifndef _JEVEVENTPROCESSOR_PY_H_
0003 #define _JEVEVENTPROCESSOR_PY_H_
0004 
0005 // This defines the JEventProcessorPY classes used to implement
0006 // a JEventProcessor class in Python. The design uses a pair of
0007 // classes with a HasA relationship.
0008 //
0009 // The JEventProcessorPYTrampoline class is a C++ class that
0010 // inherits from JEventProcessor and is what JANA uses for callbacks.
0011 //
0012 // The JEventProcessorPY class is a python class that inherits
0013 // from pyobject in pybind11. It also serves as a base class
0014 // for Python JEventProcessor classes.
0015 //
0016 // Two classes are needed because the design of JANA requires
0017 // ownership of the JEventProcessor object be given to JApplication.
0018 // At the same time, pybind11 insists on ownership of all pyobjects.
0019 //
0020 // n.b. There may actually be a way to do this with one class by
0021 // manipulating the ref counter in the python object
0022 
0023 #include <JANA/JVersion.h>
0024 
0025 #include <mutex>
0026 #include <iostream>
0027 using std::cout;
0028 using std::endl;
0029 
0030 #include <pybind11/pybind11.h>
0031 namespace py = pybind11;
0032 
0033 
0034 #if JANA2_HAVE_ROOT
0035 #include <TObject.h>
0036 #include <TClass.h>
0037 #include <TDataMember.h>
0038 #include <TMethodCall.h>
0039 #include <TList.h>
0040 #endif // JANA2_HAVE_ROOT
0041 
0042 #include <JANA/JEventProcessor.h>
0043 #include <JANA/JEvent.h>
0044 #include <JANA/Utils/JStringification.h>
0045 
0046 std::mutex pymutex; // This is bad practice to put this in a header, but it is needed in both the plugin and the module
0047 extern bool PY_INITIALIZED;  // declared in janapy.h
0048 
0049 //#pragma GCC visibility push(hidden)
0050 
0051 class JEventProcessorPY {
0052 
0053     public:
0054 
0055     //----------------------------------------
0056     // JEventProcessorPY
0057     JEventProcessorPY(py::object &py_obj):pyobj(py_obj),jstringification(new JStringification){
0058 
0059         cout << "JEventProcessorPY constructor called with py:object : " << this  << endl;
0060 
0061         // Get the name of the Python class inheriting from JEventProcessorPY
0062         // so it can be displayed as the JEventProcessor name (see JEventProcessorPYTrampoline)
0063         auto name_obj = py_obj.get_type().attr("__name__");
0064         class_name = py::cast<std::string>(name_obj);
0065 
0066         try { pymInit    = pyobj.attr("Init"   );  has_pymInit    = true; }catch(...){}
0067         try { pymProcess = pyobj.attr("Process");  has_pymProcess = true; }catch(...){}
0068         try { pymFinish  = pyobj.attr("Finish" );  has_pymFinish  = true; }catch(...){}
0069 
0070     }
0071 
0072     //----------------------------------------
0073     // ~JEventProcessorPY
0074     ~JEventProcessorPY() {
0075         cout << "JEventProcessorPY destructor called : " << this  << endl;
0076     }
0077 
0078     //----------------------------------------
0079     // SetJApplication
0080     //
0081     /// This sets the internal mApplication member. It is called by the
0082     /// JEventProcessorPYTrampoline class constructor, which itself is
0083     /// only called when the Python jana.AddProcessor(proc) method is
0084     /// called.
0085     void SetJApplication(JApplication *japp) {
0086         mApplication = japp;
0087     }
0088 
0089     //----------------------------------------
0090     // Init
0091     void Init(void){
0092 
0093         if( has_pymInit && PY_INITIALIZED ) {
0094             lock_guard<mutex> lck(pymutex);
0095             pymInit();
0096         }
0097 
0098     }
0099 
0100     //----------------------------------------
0101     // Process
0102     void Process(const std::shared_ptr<const JEvent>& aEvent){
0103 
0104         // Prefetch any factory in the prefetch list.
0105         // This does not actually fetch them, but does activate the factories
0106         // to ensure the objects are already created before locking the mutex.
0107         // We try fetching as both JObject and TObject for the rare case when
0108         // the objects may be TObjects but not JObjects. We cannot currently
0109         // handle the case where the factory object is neither a JObject nor
0110         // a TObject.
0111         for( auto p : prefetch_factories ){
0112 
0113             auto fac = aEvent->GetFactory( p.first, p.second);
0114             if( fac == nullptr ){
0115                 LOG_ERROR(default_cout_logger) << "Unable to find factory specified for prefetching: factory=" << p.first << " tag=" << p.second << LOG_END;
0116             }else {
0117                 auto v = fac->GetAs<JObject>();
0118 #if JANA2_HAVE_ROOT
0119                 if( v.empty() )fac->GetAs<TObject>();
0120 #endif // JANA2_HAVE_ROOT
0121 
0122                 _DBG_<<"Prefetching from factory: " << p.first <<":" << p.second << "  - " << v.size() << " objects" <<std::endl;
0123             }
0124         }
0125 
0126         if( has_pymProcess  && PY_INITIALIZED ) {
0127 
0128             // According to the Python documentation we should be wrapping the call to pmProcess() below
0129             // in the following that activates the GIL lock. In practice, this seemed to allow each thread
0130             // to call pymProcess(), once, but then the program stalled. Hence, we use our own mutex.
0131             // PyGILState_STATE gstate = PyGILState_Ensure();
0132             // PyGILState_Release(gstate);
0133             lock_guard<mutex> lck(pymutex);
0134 
0135             // This magic line creates a shared_ptr on the stack with a custom deleter.
0136             // The custom deleter is used to reset the mEvent data member back to
0137             // nullptr so that it does not hold on the the JEvent after we return
0138             // from this method. We use this trick to ensure it happens even if an
0139             // exception is thrown from pymProcess().
0140             std::shared_ptr<int> mEvent_free(nullptr, [=](int */*ptr*/){ mEvent = nullptr;});
0141             mEvent = aEvent; // remember this JEvent so it can be used in calls to Get()
0142 
0143             pymProcess();
0144         }
0145     }
0146 
0147     //----------------------------------------
0148     // Finish
0149     void Finish(void){
0150 
0151         if( has_pymFinish  && PY_INITIALIZED ) {
0152             lock_guard<mutex> lck(pymutex);
0153             pymFinish();
0154         }
0155 
0156     }
0157 
0158     //----------------------------------------
0159     // Prefetch
0160     void Prefetch(py::object &fac_name, py::object tag = py::none()){
0161 
0162         /// This is called from python to register factories to be activated
0163         /// before the Process method of the python JEventProcessor class
0164         /// is called. Since the python code must be executed in serial, this
0165         /// allows object generation by factories to be done in parallel before
0166         /// that to try and minimize the time in serial operations.
0167         ///
0168         /// When calling from python, the first argument may be any of a
0169         /// string, list, or dictionary. The second argument is for the optional
0170         /// factory tag which is only considered if the first argument is a
0171         /// string.
0172         ///
0173         /// If the first argument is a string, it is taken as the data type to
0174         /// prefetch.
0175         ///
0176         /// If the first argument is a list, it is taken to be the names of multiple
0177         /// data types to prefetch. For this case, the default (i.e. empty) factory
0178         /// tag is used for all of them.
0179         ///
0180         /// If the first argument is a dictionary, then the keys are taken as
0181         /// the data types and corresponding values taken as the factory tags.
0182 
0183         if( py::isinstance<py::dict>(fac_name) ){
0184             // Python dictionary was passed
0185             for(auto p : fac_name.cast<py::dict>()){
0186                 auto &fac_obj = p.first;
0187                 auto &tag_obj = p.second;
0188                 std::string fac_name_str = py::str(fac_obj);
0189                 std::string tag_str = tag_obj.is(py::none()) ? "":py::str(tag_obj);
0190                 prefetch_factories[fac_name_str] = tag_str;
0191             }
0192         }else if( py::isinstance<py::list>(fac_name) ){
0193             // Python list was passed
0194             for(auto &fac_obj : fac_name.cast<py::list>()){
0195                 std::string fac_name_str = py::str(fac_obj);
0196                 prefetch_factories[fac_name_str] = "";
0197             }
0198         }else if( py::isinstance<py::str>(fac_name) ){
0199             // Python string was passed
0200             std::string fac_name_str = py::str(fac_name);
0201             std::string tag_str = tag.is(py::none()) ? "":py::str(tag);
0202             prefetch_factories[fac_name_str] = tag_str;
0203         }else{
0204             LOG_ERROR(default_cout_logger) << "Unknown type passed to Prefetch: " << std::string(py::str(fac_name)) << LOG_END;
0205         }
0206     }
0207 
0208     //----------------------------------------
0209     // Get
0210     py::object Get(py::object &fac_name, py::object tag = py::none()) {
0211         std::string fac_name_str = py::str(fac_name);
0212         std::string tag_str = tag.is(py::none()) ? "":py::str(tag);
0213 
0214         std::vector<std::string> json_vec;
0215         jstringification->GetObjectSummariesAsJSON(json_vec, mEvent, fac_name_str, tag_str);
0216 
0217         py::list list;
0218         for(auto obj_json : json_vec){
0219 
0220             try {
0221                 auto json_loads = pymodule_json->attr("loads" );
0222                 auto dict = json_loads( py::str(obj_json) );
0223                 list.append( dict );
0224             }catch(...){
0225                 LOG_ERROR(default_cout_logger) << "Python json loads function not available!" << LOG_END;
0226             }
0227         }
0228 
0229         return list;
0230     }
0231 
0232     // Data members
0233     py::module_ *pymodule = nullptr;       // This gets set in janapy_AddProcessor
0234     py::module_ *pymodule_json = nullptr;  // This gets set in janapy_AddProcessor
0235     std::string class_name = "JEventProcssorPY";
0236     py::object &pyobj; // _self_
0237     py::object pymInit;
0238     py::object pymProcess;
0239     py::object pymFinish;
0240     bool has_pymInit    = false;
0241     bool has_pymProcess = false;
0242     bool has_pymFinish  = false;
0243 
0244     JApplication *mApplication = nullptr;
0245     std::shared_ptr<const JEvent> mEvent;
0246     std::map<std::string, std::string> prefetch_factories;
0247     std::shared_ptr<const JStringification> jstringification;
0248 };
0249 
0250 class JEventProcessorPYTrampoline: public JEventProcessor {
0251 
0252 public:
0253     JEventProcessorPYTrampoline(JApplication *japp, JEventProcessorPY *jevent_proc):JEventProcessor(japp),jevent_proc_py(jevent_proc){
0254         SetTypeName(jevent_proc->class_name);
0255         jevent_proc_py->SetJApplication(japp); // copy JApplication pointer to our JEventProcessorPY
0256     }
0257 
0258     void Init(void){ jevent_proc_py->Init(); }
0259     void Process(const std::shared_ptr<const JEvent>& aEvent){ jevent_proc_py->Process(aEvent); }
0260     void Finish(void){ jevent_proc_py->Finish(); }
0261 
0262 private:
0263     JEventProcessorPY *jevent_proc_py = nullptr;
0264 };
0265 #endif  // _JEVEVENTPROCESSOR_PY_H_