diff options
author | 2022-01-19 09:01:47 -0800 | |
---|---|---|
committer | 2022-01-19 09:01:47 -0800 | |
commit | 4e9d98c528dfebf05aa9d07edf451eff29f37f65 (patch) | |
tree | 2a3ed3586aa7895455f40ec4f40d7b1bb2156ef6 /Python | |
parent | 73858579e90701b5f1fc1ef9009a8067294aa975 (diff) | |
download | WarpX-4e9d98c528dfebf05aa9d07edf451eff29f37f65.tar.gz WarpX-4e9d98c528dfebf05aa9d07edf451eff29f37f65.tar.zst WarpX-4e9d98c528dfebf05aa9d07edf451eff29f37f65.zip |
Refactor python callback handling (#2703)
* added support to uninstall an external Poisson solver and return to using the default MLMG solver; also updated some callbacks.py calls to Python3
* refactor callback handling - use a map to handle all the different callbacks
* warpx_callback_py_map does not need to link to C
* Apply suggestions from code review
Co-authored-by: Axel Huebl <axel.huebl@plasma.ninja>
* further suggested changes from code review
* added function ExecutePythonCallback to reduce code duplication
* moved ExecutePythonCallback to WarpX_py
* added function IsPythonCallbackInstalled
Co-authored-by: Axel Huebl <axel.huebl@plasma.ninja>
Diffstat (limited to 'Python')
-rw-r--r-- | Python/pywarpx/callbacks.py | 65 |
1 files changed, 38 insertions, 27 deletions
diff --git a/Python/pywarpx/callbacks.py b/Python/pywarpx/callbacks.py index 35cc51894..9ae36266e 100644 --- a/Python/pywarpx/callbacks.py +++ b/Python/pywarpx/callbacks.py @@ -86,32 +86,36 @@ class CallbackFunctions(object): self.lcallonce = lcallonce def __call__(self,*args,**kw): - "Call all of the functions in the list" + """Call all of the functions in the list""" tt = self.callfuncsinlist(*args,**kw) self.time = self.time + tt if self.lcallonce: self.funcs = [] def clearlist(self): + """Unregister/clear out all registered C callbacks""" self.funcs = [] + libwarpx.libwarpx_so.warpx_clear_callback_py( + ctypes.c_char_p(self.name.encode('utf-8')) + ) - def __nonzero__(self): - "Returns True if functions are installed, otherwise False" + def __bool__(self): + """Returns True if functions are installed, otherwise False""" return self.hasfuncsinstalled() def __len__(self): - "Returns number of functions installed" + """Returns number of functions installed""" return len(self.funcs) def hasfuncsinstalled(self): - "Checks if there are any functions installed" + """Checks if there are any functions installed""" return len(self.funcs) > 0 def _getmethodobject(self,func): - "For call backs that are methods, returns the method's instance" + """For call backs that are methods, returns the method's instance""" return func[0] def callbackfunclist(self): - "Generator returning callable functions from the list" + """Generator returning callable functions from the list""" funclistcopy = copy.copy(self.funcs) for f in funclistcopy: if isinstance(f,list): @@ -147,15 +151,16 @@ class CallbackFunctions(object): yield result def installfuncinlist(self,f): - "Check if the specified function is installed" + """Check if the specified function is installed""" if len(self.funcs) == 0: # If this is the first function installed, set the callback in the C++ # to call this class instance. # Note that the _c_func must be saved. _CALLBACK_FUNC_0 = ctypes.CFUNCTYPE(None) self._c_func = _CALLBACK_FUNC_0(self) - callback_setter = getattr(libwarpx.libwarpx_so, f'warpx_set_callback_py_{self.name}') - callback_setter(self._c_func) + libwarpx.libwarpx_so.warpx_set_callback_py( + ctypes.c_char_p(self.name.encode('utf-8')), self._c_func + ) if isinstance(f,types.MethodType): # --- If the function is a method of a class instance, then save a full # --- reference to that instance and the method name. @@ -177,7 +182,7 @@ class CallbackFunctions(object): self.funcs.append(f) def uninstallfuncinlist(self,f): - "Uninstall the specified function" + """Uninstall the specified function""" # --- An element by element search is needed # --- f can be a function or method object, or a name (string). # --- Note that method objects can not be removed by name. @@ -185,27 +190,34 @@ class CallbackFunctions(object): for func in funclistcopy: if f == func: self.funcs.remove(f) - return + break elif isinstance(func,list) and isinstance(f,types.MethodType): object = self._getmethodobject(func) - if f.im_self is object and f.__name__ == func[1]: + if f.__self__ is object and f.__name__ == func[1]: self.funcs.remove(func) - return + break elif isinstance(func,str): if f.__name__ == func: self.funcs.remove(func) - return + break elif isinstance(f,str): if isinstance(func,str): funcname = func elif isinstance(func,list): funcname = None else: funcname = func.__name__ if f == funcname: self.funcs.remove(func) - return - raise Exception('Warning: no such function had been installed') + break + + # check that a function was removed + if len(self.funcs) == len(funclistcopy): + raise Exception(f'Warning: no function, {f}, had been installed') + + # if there are no functions left, remove the C callback + if not self.hasfuncsinstalled(): + self.clearlist() def isinstalledfuncinlist(self,f): - "Checks if the specified function is installed" + """Checks if the specified function is installed""" # --- An element by element search is needed funclistcopy = copy.copy(self.funcs) for func in funclistcopy: @@ -213,7 +225,7 @@ class CallbackFunctions(object): return 1 elif isinstance(func,list) and isinstance(f,types.MethodType): object = self._getmethodobject(func) - if f.im_self is object and f.__name__ == func[1]: + if f.__self__ is object and f.__name__ == func[1]: return 1 elif isinstance(func,str): if f.__name__ == func: @@ -221,7 +233,7 @@ class CallbackFunctions(object): return 0 def callfuncsinlist(self,*args,**kw): - "Call the functions in the list" + """Call the functions in the list""" bb = time.time() for f in self.callbackfunclist(): #barrier() @@ -320,16 +332,15 @@ def callfrompoissonsolver(f): installpoissonsolver(f) return f def installpoissonsolver(f): - """Adds a function to solve Poisson's equation. Note that the C++ object - warpx_py_poissonsolver is declared as a nullptr but once the call to set it - to _c_poissonsolver below is executed it is no longer a nullptr, and therefore - if (warpx_py_poissonsolver) evaluates to True. For this reason a poissonsolver - cannot be uninstalled with the uninstallfuncinlist functionality at present.""" + """Installs an external function to solve Poisson's equation""" if _poissonsolver.hasfuncsinstalled(): - raise RuntimeError('Only one field solver can be installed.') + raise RuntimeError("Only one external Poisson solver can be installed.") _poissonsolver.installfuncinlist(f) +def uninstallpoissonsolver(f): + """Removes the external function to solve Poisson's equation""" + _poissonsolver.uninstallfuncinlist(f) def isinstalledpoissonsolver(f): - """Checks if the function is called for a field solve""" + """Checks if the function is called to solve Poisson's equation""" return _poissonsolver.isinstalledfuncinlist(f) # ---------------------------------------------------------------------------- |