Salome HOME
ea267bc5c71a08f468d3873ec11484779ee0d6c5
[tools/adao_interface.git] / AdaoExchangeLayer4Quintet.cxx
1 // Copyright (C) 2019 EDF R&D
2 //
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License.
7 //
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11 // Lesser General Public License for more details.
12 //
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
16 //
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
18 //
19 // Author: Anthony Geay, anthony.geay@edf.fr, EDF R&D
20
21 #include "AdaoExchangeLayer4Quintet.hxx"
22 #include "AdaoExchangeLayerException.hxx"
23 #include "AdaoModelKeyVal.hxx"
24 #include "PyObjectRAII.hxx"
25 #include "Python.h"
26
27 #include "py2cpp/py2cpp.hxx"
28
29 #include <semaphore.h>
30
31 #include <iostream>
32 #include <fstream>
33 #include <sstream>
34 #include <clocale>
35 #include <cstdlib>
36 #include <thread>
37 #include <future>
38
39 struct DataExchangedBetweenThreads // data written by subthread and read by calling thread
40 {
41 public:
42   DataExchangedBetweenThreads();
43   ~DataExchangedBetweenThreads();
44 public:
45   sem_t _sem;
46   sem_t _sem_result_is_here;
47   volatile bool _finished = false;
48   volatile PyObject *_data = nullptr;
49 };
50
51 /////////////////////////////////////////////
52
53 struct AdaoCallbackSt
54 {
55   PyObject_HEAD
56   DataExchangedBetweenThreads *_data;
57 };
58
59 static PyObject *adaocallback_call(AdaoCallbackSt *self, PyObject *args, PyObject *kw)
60 {
61   if(!PyTuple_Check(args))
62     throw AdaoExchangeLayerException("Input args is not a tuple as expected !");
63   if(PyTuple_Size(args)!=1)
64     throw AdaoExchangeLayerException("Input args is not a tuple of size 1 as expected !");
65   PyObjectRAII zeobj(PyObjectRAII::FromBorrowed(PyTuple_GetItem(args,0)));
66   if(zeobj.isNull())
67     throw AdaoExchangeLayerException("Retrieve of elt #0 of input tuple has failed !");
68   volatile PyObject *ret(nullptr);
69   PyThreadState *tstate(PyEval_SaveThread());// GIL is acquired (see ExecuteAsync). Before entering into non python section. Release lock
70   {
71     self->_data->_finished = false;
72     self->_data->_data = zeobj;
73     sem_post(&self->_data->_sem);
74     sem_wait(&self->_data->_sem_result_is_here);
75     ret = self->_data->_data;
76   }
77   PyEval_RestoreThread(tstate);//End of parallel section. Reaquire the GIL and restore the thread state
78   return (PyObject *)ret;
79 }
80
81 static int adaocallback___init__(PyObject *self, PyObject *args, PyObject *kwargs) { return 0; }
82
83 static PyObject *adaocallback___new__(PyTypeObject *type, PyObject *args, PyObject *kwargs)
84 {
85   return (PyObject *)( type->tp_alloc(type, 0) );
86 }
87
88 static void adaocallback_dealloc(PyObject *self)
89 {
90   Py_TYPE(self)->tp_free(self);
91 }
92
93 PyTypeObject AdaoCallbackType = {
94   PyVarObject_HEAD_INIT(&PyType_Type, 0)
95   "adaocallbacktype",
96   sizeof(AdaoCallbackSt),
97   0,
98   adaocallback_dealloc,       /*tp_dealloc*/
99   0,                          /*tp_print*/
100   0,                          /*tp_getattr*/
101   0,                          /*tp_setattr*/
102   0,                          /*tp_compare*/
103   0,                          /*tp_repr*/
104   0,                          /*tp_as_number*/
105   0,                          /*tp_as_sequence*/
106   0,                          /*tp_as_mapping*/
107   0,                          /*tp_hash*/
108   (ternaryfunc)adaocallback_call,  /*tp_call*/
109   0,                          /*tp_str*/
110   0,                          /*tp_getattro*/
111   0,                          /*tp_setattro*/
112   0,                          /*tp_as_buffer*/
113   Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE,  /*tp_flags*/
114   0,                          /*tp_doc*/
115   0,                          /*tp_traverse*/
116   0,                          /*tp_clear*/
117   0,                          /*tp_richcompare*/
118   0,                          /*tp_weaklistoffset*/
119   0,                          /*tp_iter*/
120   0,                          /*tp_iternext*/
121   0,                          /*tp_methods*/
122   0,                          /*tp_members*/
123   0,                          /*tp_getset*/
124   0,                          /*tp_base*/
125   0,                          /*tp_dict*/
126   0,                          /*tp_descr_get*/
127   0,                          /*tp_descr_set*/
128   0,                          /*tp_dictoffset*/
129   adaocallback___init__,      /*tp_init*/
130   PyType_GenericAlloc,        /*tp_alloc*/
131   adaocallback___new__,       /*tp_new*/
132   PyObject_GC_Del,            /*tp_free*/
133 };
134
135 /////////////////////////////////////////////
136
137 DataExchangedBetweenThreads::DataExchangedBetweenThreads()
138 {
139   if(sem_init(&_sem,0,0)!=0)// put value to 0 to lock by default
140     throw AdaoExchangeLayerException("Internal constructor : Error on initialization of semaphore !");
141   if(sem_init(&_sem_result_is_here,0,0)!=0)// put value to 0 to lock by default
142     throw AdaoExchangeLayerException("Internal constructor : Error on initialization of semaphore !");
143 }
144
145 DataExchangedBetweenThreads::~DataExchangedBetweenThreads()
146 {
147   sem_destroy(&_sem);
148   sem_destroy(&_sem_result_is_here);
149 }
150
151 class AdaoCallbackKeeper
152 {
153 public:
154   void assign(AdaoCallbackSt *pt, DataExchangedBetweenThreads *data)
155   {
156     release();
157     _pt = pt;
158     _pt->_data = data;
159   }
160   PyObject *getPyObject() const { return reinterpret_cast<PyObject*>(_pt); }
161   ~AdaoCallbackKeeper() { release(); }
162 private:
163   void release() { if(_pt) { Py_XDECREF(_pt); } }
164 private:
165   AdaoCallbackSt *_pt = nullptr;
166 };
167
168 class AdaoExchangeLayer4Quintet::Internal
169 {
170 public:
171   Internal():_context(PyObjectRAII::FromNew(PyDict_New()))
172   { 
173     PyObject *mainmod(PyImport_AddModule("__main__"));
174     PyObject *globals(PyModule_GetDict(mainmod));
175     PyObject *bltins(PyEval_GetBuiltins());
176     PyDict_SetItemString(_context,"__builtins__",bltins);
177   }
178 public:
179   PyObjectRAII _context;
180   PyObjectRAII _generate_case_func;
181   PyObjectRAII _decorator_func;
182   PyObjectRAII _adao_case;
183   PyObjectRAII _execute_func;
184   AdaoCallbackKeeper _py_call_back;
185   std::future< void > _fut;
186   PyThreadState *_tstate = nullptr;
187   DataExchangedBetweenThreads _data_btw_threads;
188 };
189
190 wchar_t **ConvertToWChar(int argc, const char *argv[])
191 {
192   wchar_t **ret(new wchar_t*[argc]);
193   for(int i=0;i<argc;++i)
194     {
195       std::size_t len(strlen(argv[i])+1);
196       wchar_t *elt(new wchar_t[len]);
197       ret[i]=elt;
198       std::mbstowcs(elt, argv[i], len);
199     }
200   return ret;
201 }
202
203 void FreeWChar(int argc, wchar_t **tab)
204 {
205   for(int i=0;i<argc;++i)
206     delete [] tab[i];
207   delete [] tab;
208 }
209
210 AdaoExchangeLayer4Quintet::AdaoExchangeLayer4Quintet()
211 {
212 }
213
214 AdaoExchangeLayer4Quintet::~AdaoExchangeLayer4Quintet()
215 {
216   delete _internal;
217 }
218
219 void AdaoExchangeLayer4Quintet::init()
220 {
221   initPythonIfNeeded();
222 }
223
224 PyObject *AdaoExchangeLayer4Quintet::getPythonContext() const
225 {
226   if(!_internal)
227     throw AdaoExchangeLayerException("getPythonContext : not initialized !");
228   return _internal->_context;
229 }
230
231 void AdaoExchangeLayer4Quintet::initPythonIfNeeded()
232 {
233   if (!Py_IsInitialized())
234     {
235       const char *TAB[]={"AdaoExchangeLayer4Quintet"};
236       wchar_t **TABW(ConvertToWChar(1,TAB));
237       // Python is not initialized
238       Py_SetProgramName(const_cast<wchar_t *>(TABW[0]));
239       Py_Initialize(); // Initialize the interpreter
240       PySys_SetArgv(1,TABW);
241       FreeWChar(1,TABW);
242       PyEval_InitThreads();
243     }
244   delete _internal;
245   _internal = new Internal;
246 }
247
248 class Visitor1 : public AdaoModel::PythonLeafVisitor
249 {
250 public:
251   Visitor1(PyObjectRAII func, PyObject *context):_func(func),_context(context)
252   {
253   }
254   
255   void visit(AdaoModel::MainModel *godFather, AdaoModel::PyObjKeyVal *obj) override
256   {
257     if(obj->getKey()=="Matrix" || obj->getKey()=="DiagonalSparseMatrix")
258       {
259         std::ostringstream oss; oss << "__" << _cnt++;
260         std::string varname(oss.str());
261         obj->setVal(Py_None);
262         PyDict_SetItemString(_context,varname.c_str(),Py_None);
263         obj->setVarName(varname);
264         return ;
265       }
266     if(obj->getKey()=="OneFunction")
267       {
268         std::ostringstream oss; oss << "__" << _cnt++;
269         std::string varname(oss.str());
270         obj->setVal(_func);
271         PyDict_SetItemString(_context,varname.c_str(),_func);
272         obj->setVarName(varname);
273         return ;
274       }
275   }
276 private:
277   unsigned int _cnt = 0;
278   PyObjectRAII _func;
279   PyObject *_context = nullptr;
280 };
281
282 void AdaoExchangeLayer4Quintet::loadTemplate(AdaoModel::MainModel *model)
283 {
284   const char DECORATOR_FUNC[]="def DecoratorAdao(cppFunc):\n"
285       "    def evaluator( xserie ):\n"
286       "        import numpy as np\n"
287       "        yserie = [np.array(elt) for elt in cppFunc(xserie)]\n"
288       "        return yserie\n"
289       "    return evaluator\n";
290   this->_internal->_py_call_back.assign(PyObject_GC_New(AdaoCallbackSt,&AdaoCallbackType),
291       &this->_internal->_data_btw_threads);
292   PyObject *callbackPyObj(this->_internal->_py_call_back.getPyObject());
293   //
294   {
295       PyObjectRAII res(PyObjectRAII::FromNew(PyRun_String(DECORATOR_FUNC,Py_file_input,this->_internal->_context,this->_internal->_context)));
296       PyObjectRAII decoratorGenerator( PyObjectRAII::FromBorrowed(PyDict_GetItemString(this->_internal->_context,"DecoratorAdao")) );
297       if(decoratorGenerator.isNull())
298         throw AdaoExchangeLayerException("Fail to locate DecoratorAdao function !");
299       PyObjectRAII args(PyObjectRAII::FromNew(PyTuple_New(1)));
300       { PyTuple_SetItem(args,0,callbackPyObj); Py_XINCREF(callbackPyObj); }
301       this->_internal->_decorator_func = PyObjectRAII::FromNew(PyObject_CallObject(decoratorGenerator,args));
302       if(this->_internal->_decorator_func.isNull())
303         throw AdaoExchangeLayerException("Fail to generate result of DecoratorAdao function !");
304   }
305   //
306   Visitor1 visitor(this->_internal->_decorator_func,this->_internal->_context);
307   model->visitPythonLeaves(&visitor);
308   //
309   {
310     std::string sciptPyOfModelMaker(model->pyStr());
311     {
312       std::ofstream ofs("/tmp/H87074/jj");
313       ofs << sciptPyOfModelMaker;
314     }
315     PyObjectRAII res(PyObjectRAII::FromNew(PyRun_String(sciptPyOfModelMaker.c_str(),Py_file_input,this->_internal->_context,this->_internal->_context)));
316     PyErr_Print();
317     _internal->_adao_case = PyObjectRAII::FromNew(PyDict_GetItemString(this->_internal->_context,"case"));
318   }
319   if(_internal->_adao_case.isNull())
320     throw AdaoExchangeLayerException("Fail to generate ADAO case object !");
321   //
322   _internal->_execute_func=PyObjectRAII::FromNew(PyObject_GetAttrString(_internal->_adao_case,"execute"));
323   if(_internal->_execute_func.isNull())
324     throw AdaoExchangeLayerException("Fail to locate execute function of ADAO case object !");
325 }
326
327 void ExecuteAsync(PyObject *pyExecuteFunction, DataExchangedBetweenThreads *data)
328 {
329   AutoGIL gil; // launched in a separed thread -> protect python calls
330   PyObjectRAII args(PyObjectRAII::FromNew(PyTuple_New(0)));
331   PyObjectRAII nullRes(PyObjectRAII::FromNew(PyObject_CallObject(pyExecuteFunction,args)));// go to adaocallback_call
332   PyErr_Print();
333   data->_finished = true;
334   data->_data = nullptr;
335   sem_post(&data->_sem);
336 }
337
338 void AdaoExchangeLayer4Quintet::execute()
339 {
340   _internal->_tstate=PyEval_SaveThread(); // release the lock acquired in AdaoExchangeLayer4Quintet::initPythonIfNeeded by PyEval_InitThreads()
341   _internal->_fut = std::async(std::launch::async,ExecuteAsync,_internal->_execute_func,&_internal->_data_btw_threads);
342 }
343
344 bool AdaoExchangeLayer4Quintet::next(PyObject *& inputRequested)
345 {
346   sem_wait(&_internal->_data_btw_threads._sem);
347   if(_internal->_data_btw_threads._finished)
348     {
349       inputRequested = nullptr;
350       return false;
351     }
352   else
353     {
354       inputRequested = (PyObject *)_internal->_data_btw_threads._data;
355       return true;
356     }
357 }
358
359 void AdaoExchangeLayer4Quintet::setResult(PyObject *outputAssociated)
360 {
361   _internal->_data_btw_threads._data = outputAssociated;
362   _internal->_data_btw_threads._finished = false;
363   sem_post(&_internal->_data_btw_threads._sem_result_is_here);
364 }
365
366 PyObject *AdaoExchangeLayer4Quintet::getResult()
367 {
368   _internal->_fut.wait();
369   PyEval_RestoreThread(_internal->_tstate);
370   AutoGIL gil;
371   // now retrieve case.get("Analysis")[-1]
372   PyObjectRAII get_func_of_adao_case(PyObjectRAII::FromNew(PyObject_GetAttrString(_internal->_adao_case,"get")));
373   if(get_func_of_adao_case.isNull())
374     throw AdaoExchangeLayerException("Fail to locate \"get\" method from ADAO case !");
375   PyObjectRAII all_intermediate_results;
376   {// retrieve return data from case.get("Analysis")
377     PyObjectRAII args(PyObjectRAII::FromNew(PyTuple_New(1)));
378     PyTuple_SetItem(args,0,PyUnicode_FromString("Analysis"));
379     all_intermediate_results=PyObjectRAII::FromNew(PyObject_CallObject(get_func_of_adao_case,args));
380     if(all_intermediate_results.isNull())
381       throw AdaoExchangeLayerException("Fail to retrieve result of case.get(\"Analysis\") !");
382   }
383   PyObjectRAII optimum;
384   {
385     PyObjectRAII param(PyObjectRAII::FromNew(PyLong_FromLong(-1)));
386     optimum=PyObjectRAII::FromNew(PyObject_GetItem(all_intermediate_results,param));
387     if(optimum.isNull())
388       throw AdaoExchangeLayerException("Fail to retrieve result of last element of case.get(\"Analysis\") !");
389   }
390   /*PyObjectRAII code(PyObjectRAII::FromNew(Py_CompileString("case.get(\"Analysis\")[-1]","retrieve result",Py_file_input)));
391   if(code.isNull())
392     throw AdaoExchangeLayerException("Fail to compile code to retrieve result after ADAO computation !");
393     PyObjectRAII res(PyObjectRAII::FromNew(PyEval_EvalCode(code,_internal->_context,_internal->_context)));*/
394   return optimum.retn();
395 }