def init(integrands, source, *args, **kwargs): coefficients = kwargs.pop('coefficients', dict()) coefficientNames = integrands._coefficientNames if len(args) == 1 and isinstance(args[0], dict): coefficients.update(args[0]) args = [] else: args = list(a for a in args if not isinstance(a,DirichletBC) and not a is None) dirichletBCs = list(a for a in args if isinstance(a,DirichletBC)) if len(args) > len(coefficientNames) + len(dirichletBCs): raise ValueError('Too many coefficients passed.') args += [None] * (len(coefficientNames) - len(args)) for name, value in kwargs.items(): try: i = coefficientNames[name] except KeyError: raise ValueError('No such coefficent: ' + name + '.') if args[i] is not None: raise ValueError('Coefficient already given as positional argument: ' + name + '.') args[i] = value for key, value in coefficients.items(): if isinstance(key, Coefficient): try: i = integrands._renumbering[key] except AttributeError: raise ValueError('Cannot map UFL coefficients, because model was not generated from UFL form.') except KeyError: raise ValueError('No such coefficient: ' + str(key) + '.') elif isString(key): try: i = coefficientNames[key] except KeyError: raise ValueError('No such coefficent: ' + key + '.') else: raise ValueError('Expecting keys of coefficient map to be strings or intances of ufl.Coefficient.') if args[i] is not None: raise ValueError('Coefficient already given as positional or keyword argument: ' + str(key) + '.') args[i] = value if hasattr(integrands, '_renumbering'): for c, i in integrands._renumbering.items(): if isinstance(c, GridFunction): if args[i] is None: args[i] = c.gf if any(arg is None for arg in args): missing = [name for name, i in coefficientNames.items() if args[i] is None] raise ValueError('Missing coefficients: ' + ', '.join(missing) + '.') integrands.base.__init__(integrands, *args, **kwargs) for c in source.constantList: if hasattr(c,"name") and hasattr(c,"value"): assert hasattr(integrands,c.name) getattr(type(integrands), c.name).fset(integrands, c.value)
def append(self, *lines): for line in lines: if isinstance(line, (list, tuple)): self.append(*list(line)) elif isString(line): self.lines += line.split('\n') else: raise Exception( 'Only strings (or lists of them) can be appended to an UnformattedBlock' )
def addStorage(obj, storage): if not storage: storage = str("numpy") if isString(storage): import dune.create as create assert storage, "wrong storage (" + storage + ") passed to space" storage = create.discretefunction(storage)(obj) else: storage = storage(obj) setattr(obj, "storage", storage) return storage
def initModel(model, *args, **kwargs): coefficients = kwargs.pop('coefficients', dict()) if len(args) == 1 and isinstance(args[0], dict): coefficients.update(args[0]) args = [] else: args = list(args) try: coefficientNames = model._coefficientNames except: coefficientNames = [] if len(args) > len(coefficientNames): raise ValueError('Too many coefficients passed.') args += [None] * (len(coefficientNames) - len(args)) for name, value in kwargs: i = coefficientNames.get(name) if i is None: raise ValueError('No such coefficent: ' + name + '.') if args[i] is not None: raise ValueError('Coefficient already given as positional argument: ' + name + '.') args[i] = value for key, value in coefficients.items(): if isinstance(key, Coefficient): try: i = model._renumbering[key] except AttributeError: raise ValueError('Cannot map UFL coefficients, because model was not generated from UFL form.') except KeyError: raise ValueError('No such coefficient: ' + str(key) + '.') elif isString(key): i = coefficientNames.get(name) if i is None: raise ValueError('No such coefficent: ' + name + '.') else: raise ValueError('Expecting keys of coefficient map to be ufl.Coefficient instances.') if args[i] is not None: raise ValueError('Coefficient already given as positional or keyword argument: ' + str(key) + '.') args[i] = value if hasattr(model, '_renumbering'): for c in (k for k in model._renumbering if isinstance(k, GridFunction)): i = model._renumbering[c] if args[i] is None: args[i] = c.gf if any(arg is None for arg in args): missing = [name for name, i in coefficientNames.items() if args[i] is None] raise ValueError('Missing coefficients: ' + ', '.join(missing) + '.') model._init(*args)
def function(gv,callback,includeFiles=None,*args,name=None,order=None,dimRange=None): if name is None: name = "tmp"+str(gv._gfCounter) gv.__class__._gfCounter += 1 if isString(callback): if includeFiles is None: raise ValueError("""if `callback` is the name of a C++ function then at least one include file containing that function must be provided""") # unique header guard is added further down source = '#include <config.h>\n\n' source += '#define USING_DUNE_PYTHON 1\n\n' includes = [] if isString(includeFiles): if not os.path.dirname(includeFiles): with open(includeFiles, "r") as include: source += include.read() source += "\n" else: source += "#include <"+includeFiles+">\n" includes += [includeFiles] elif hasattr(includeFiles,"readable"): # for IOString with includeFiles as include: source += include.read() source += "\n" elif isinstance(includeFiles, list): for includefile in includeFiles: if not os.path.dirname(includefile): with open(includefile, "r") as include: source += include.read() source += "\n" else: source += "#include <"+includefile+">\n" includes += [includefile] includes += gv.cppIncludes argTypes = [] for arg in args: t,i = cppType(arg) argTypes.append(t) includes += i signature = callback + "( " + ", ".join(argTypes) + " )" moduleName = "gf_" + hashIt(signature) + "_" + hashIt(source) # add unique header guard with moduleName source = '#ifndef Guard_'+moduleName+'\n' + \ '#define Guard_'+moduleName+'\n\n' + \ source includes = sorted(set(includes)) source += "".join(["#include <" + i + ">\n" for i in includes]) source += "\n" source += '#include <dune/python/grid/function.hh>\n' source += '#include <dune/python/pybind11/pybind11.h>\n' source += '\n' source += "PYBIND11_MODULE( " + moduleName + ", module )\n" source += "{\n" source += " module.def( \"gf\", [module] ( "+gv.cppTypeName + " &gv"+"".join([", "+argTypes[i] + " arg" + str(i) for i in range(len(argTypes))]) + " ) {\n" source += " auto callback="+callback+"<"+gv.cppTypeName+">( "+",".join(["arg"+str(i) for i in range(len(argTypes))]) +"); \n" source += " return Dune::Python::registerGridFunction<"+gv.cppTypeName+",decltype(callback)>(module,pybind11::cast(gv),\"tmp\",callback);\n" source += " }," source += " "+",".join(["pybind11::keep_alive<0,"+str(i+1)+">()" for i in range(len(argTypes)+1)]) source += ");\n" source += "}\n" source += "#endif\n" gf = builder.load(moduleName, source, signature).gf(gv,*args) else: if len(inspect.signature(callback).parameters) == 1: # global function, turn into a local function callback_ = callback callback = lambda e,x: callback_(e.geometry.toGlobal(x)) else: callback_ = None if dimRange is None: # if no `dimRange` attribute is set on the callback, # try to evaluate the function to determin the dimension of # the return value. This can fail if the function is singular in # the computational domain in which case an exception is raised e = gv.elements.__iter__().__next__() try: y = callback(e,e.referenceElement.position(0,0)) except ArithmeticError: try: y = callback(e,e.referenceElement.position(0,2)) except ArithmeticError: raise TypeError("can not determin dimension of range of "+ "given grid function due to arithmetic exceptions being "+ "raised. Add a `dimRange` parameter to the grid function to "+ "solve this issue - set `dimRange`=0 for a scalar function.") try: dimRange = len(y) except TypeError: dimRange = 0 if dimRange > 0: scalar = "false" else: scalar = "true" FieldVector(dimRange*[0]) # register FieldVector for the return value if not dimRange in gv.__class__._functions.keys(): # unique header key is added further down source = '#include <config.h>\n\n' source += '#define USING_DUNE_PYTHON 1\n\n' includes = gv.cppIncludes signature = gv.cppTypeName+"::gf<"+str(dimRange)+">" moduleName = "gf_" + hashIt(signature) + "_" + hashIt(source) # add unique header guard with moduleName source = '#ifndef Guard_'+moduleName+'\n' + \ '#define Guard_'+moduleName+'\n\n' + \ source includes = sorted(set(includes)) source += "".join(["#include <" + i + ">\n" for i in includes]) source += "\n" source += '#include <dune/python/grid/function.hh>\n' source += '#include <dune/python/pybind11/pybind11.h>\n' source += '\n' source += "PYBIND11_MODULE( " + moduleName + ", module )\n" source += "{\n" source += " typedef pybind11::function Evaluate;\n"; source += " Dune::Python::registerGridFunction< "+gv.cppTypeName+", Evaluate, "+str(dimRange)+" >( module, \"gf\", "+scalar+" );\n" source += "}\n" source += "#endif\n" gfModule = builder.load(moduleName, source, signature) gfFunc = getattr(gfModule,"gf"+str(dimRange)) if callback_ is not None: gfFunc.localCall = gfFunc.__call__ feval = lambda self,e,x=None: callback_(e) if x is None else self.localCall(e,x) subclass = type(gfFunc.__name__, (gfFunc,), {"__call__": feval}) gv.__class__._functions[dimRange] = subclass else: gv.__class__._functions[dimRange] = gfFunc gf = gv.__class__._functions[dimRange](gv,callback) def gfPlot(gf, *args, **kwargs): gf.grid.plot(gf,*args,**kwargs) gf.plot = gfPlot.__get__(gf) gf.name = name gf.order = order return gf
def load(self, moduleName, source, pythonName, extraCMake=None): # use with-statement to log info if compiling takes some time class PrintCompiling: def handle_signal(self, signum, frame): logger.info("Compiling "+pythonName) def __enter__(self): signal.signal(signal.SIGALRM, self.handle_signal) signal.alarm(1) def __exit__(self, type, value, traceback): signal.alarm(0) class EmptyPrintCompiling: def __enter__(self): pass def __exit__(self, type, value, traceback): pass ## TODO replace if rank with something better ## and remove barrier further down # check if we need to initialize dune-py either because # this is the first call to load or because an external module with metadata has been registered if not self.initialized or not self.externalPythonModules == getExternalPythonModules(): self.initialize() if comm.rank == 0: module = sys.modules.get("dune.generated." + moduleName) if module is None: logger.debug("Module {} not loaded".format(moduleName)) # make sure nothing (compilation, generating and building) is taking place with Lock(os.path.join(self.dune_py_dir, '..', 'lock-module.lock'), flags=LOCK_EX): # module must be generated so lock the source file with Lock(os.path.join(self.dune_py_dir, 'lock-'+moduleName+'.lock'), flags=LOCK_EX): sourceFileName = os.path.join(self.generated_dir, moduleName + ".cc") line = "dune_add_pybind11_module(NAME " + moduleName + " EXCLUDE_FROM_ALL)" # first check if this line is already present in the CMakeLists file # (possible if a previous script was stopped by user before module was compiled) with open(os.path.join(self.generated_dir, "CMakeLists.txt"), 'r') as out: found = line in out.read() if not os.path.isfile(sourceFileName) or not found: logger.info("Generating " + pythonName) # don't print 'Compiling' twice PrintCompiling = EmptyPrintCompiling code = str(source) with open(os.path.join(sourceFileName), 'w') as out: out.write(code) assert os.path.isfile(sourceFileName), "Error in writing module .cc file" if not found: origPos = -1 with open(os.path.join(self.generated_dir, "CMakeLists.txt"), 'a') as out: # store original file size origPos = out.tell() out.write(line+"\n") if extraCMake is not None: for x in extraCMake: out.write(x.replace("TARGET",moduleName)+"\n") # update build system logger.debug( "Configuring module {} ({}) with CMake".format( pythonName, moduleName ) ) try: # self.compile() cmake = subprocess.Popen( "cmake .".split(), cwd=self.dune_py_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = cmake.communicate() #logger.debug("CMake configuration output: "+buffer_to_str(stdout)) if cmake.returncode > 0: raise CompileError(buffer_to_str(stderr)) except: # all exceptions will cause a problem here os.remove(os.path.join(sourceFileName)) # remove line from CMakeLists to avoid problems with open(os.path.join(self.generated_dir, "CMakeLists.txt"), 'a') as out: out.truncate(origPos) raise elif isString(source) and not source == open(os.path.join(sourceFileName), 'r').read(): logger.info("Compiling " + pythonName + " (updated)") # don't print 'Compiling' twice PrintCompiling = EmptyPrintCompiling code = str(source) with open(os.path.join(sourceFileName), 'w') as out: out.write(code) else: # we only can directly load the module # sanity check line = "dune_add_pybind11_module(NAME " + moduleName + " EXCLUDE_FROM_ALL)" # the CMakeLists file should already include this line with open(os.path.join(self.generated_dir, "CMakeLists.txt"), 'r') as out: found = line in out.read() assert found, "CMakeLists.txt file does not contain an entry to build"+moduleName # end of exclusive dune-py lock # we always compile even if the module is always compiled since it can happen # that dune-py was updated in the mean time. # This step is quite fast but there is room for optimization. # for compilation a shared lock is enough with Lock(os.path.join(self.dune_py_dir, '..', 'lock-module.lock'), flags=LOCK_SH): # lock generated module with Lock(os.path.join(self.dune_py_dir, 'lock-'+moduleName+'.lock'), flags=LOCK_EX): with PrintCompiling(): self.compile(target=moduleName) ## TODO remove barrier here comm.barrier() logger.debug("Loading " + moduleName) module = importlib.import_module("dune.generated." + moduleName) if self.force: logger.info("Reloading " + pythonName) module = reload_module(module) return module
def load(className, includeFiles, *args, options=None, bufferProtocol=False, dynamicAttr=False, holder="default", baseClasses=None ): if options is None: options=[] if baseClasses is None: baseClasses=[] if not bufferProtocol: # kwargs.get("bufferProtocol", False): clsParams = [] else: clsParams = ['pybind11::buffer_protocol()'] if dynamicAttr: clsParams += ['pybind11::dynamic_attr()'] source = '#include <config.h>\n\n' source += '#define USING_DUNE_PYTHON 1\n\n' includes = [] if isString(includeFiles) or hasattr(includeFiles,"readable"): includeFiles = [includeFiles] for includefile in includeFiles: if isString(includefile): if not os.path.dirname(includefile): with open(includefile, "r") as include: source += include.read() source += "\n" else: source += "#include <"+includefile+">\n" includes += [includefile] elif hasattr(includefile,"readable"): # for IOString with includefile as include: source += include.read() source += "\n" argTypes = [] for arg in args: t,i = cppType(arg) argTypes.append(t) includes += i signature = className + "( " + ", ".join(argTypes) + " )" moduleName = "class_" + hashIt(signature) + "_" + hashIt(source) includes = sorted(set(includes)) includes += ["python/dune/generated/"+moduleName+".cc"] source += "".join(["#include <" + i + ">\n" for i in includes]) source += "\n" source += '#include <dune/python/common/typeregistry.hh>\n' source += '#include <dune/python/pybind11/pybind11.h>\n' source += '\n' source += "PYBIND11_MODULE( " + moduleName + ", module )\n" source += "{\n" for i, bc in enumerate(baseClasses): if not holder == "default": baseHolder = ", " + holder + "<" + bc + ">" else: baseHolder = '' source += 'Dune::Python::insertClass' +\ '< ' + bc + baseHolder + '>' +\ '( module, "cls' + str(i) + '"' +\ ', Dune::Python::GenerateTypeName("' + bc + '")' +\ ', Dune::Python::IncludeFiles{}' +\ ");\n" options.append(bc) if not holder == "default": options += [holder + "<" + className + ">"] source += "auto cls = Dune::Python::insertClass< "+className+\ ', '.join(('',)+tuple(options)) + ' >('+\ "module, \"cls\","+','.join(('',)+tuple(clsParams))+\ "Dune::Python::GenerateTypeName(\""+className+"\"),"+\ "Dune::Python::IncludeFiles{"+",".join(["\""+f+"\"" for f in includes])+"}).first;\n" ctorArgs = ", ".join([argTypes[i] + " arg" + str(i) for i in range(len(argTypes))]) source += "cls.def( pybind11::init( [] ( "+ctorArgs+" ) {\n" source += "return new "+className+"( "+",".join(["arg"+str(i) for i in range(len(argTypes))]) +"); \n" source += "})"+" ".join([", pybind11::keep_alive< 1, {} >()".format(i+2) for i in range(len(argTypes))]) + "\n" source += ");\n" source += "}" source = "#ifndef def_"+moduleName+\ "\n#define def_"+moduleName+"\n"+\ source+\ "\n#endif\n" return builder.load(moduleName, source, signature).cls(*args)
def load(functionName, includes, *args): '''Just in time compile an algorithm. Generates binding for a single (template) function. The name of the function and the C++ types of the arguments passed to this function are used to generate a static type used in the bindings. The file(s) containing the code for the function are passed in either as single string or as a list of strings. Note that these files will be copied into the generated module. The file name can include a path name. So in the simples case `includes="header.hh" will include the file from the current working directory. To include a file from the directory containing the calling script use `includes=dune.generator.path(__file__)+"header.hh"`. Args: functionName: name of the C++ function to provide bindings for includes: single or list of files to add to the generated module *args: list of arguments that will be passed to the generated module Returns: Callalble object ''' # header guard is added further down source = '#include <config.h>\n\n' source += '#define USING_DUNE_PYTHON 1\n\n' if isString(includes): with open(includes, "r") as include: source += include.read() source += "\n" includes = [] elif hasattr(includes,"readable"): # for IOString with includes as include: source += include.read() source += "\n" includes = [] elif isinstance(includes, list): for includefile in includes: with open(includefile, "r") as include: source += include.read() source += "\n" includes = [] argTypes = [] for arg in args: t,i = cppType(arg) argTypes.append(t) includes += i signature = functionName + "( " + ", ".join(argTypes) + " )" moduleName = "algorithm_" + hashIt(signature) + "_" + hashIt(source) # add unique header guard with moduleName source = '#ifndef Guard_'+moduleName+'\n' + \ '#define Guard_'+moduleName+'\n\n' + \ source includes = sorted(set(includes)) source += "".join(["#include <" + i + ">\n" for i in includes]) source += "\n" source += '#include <dune/python/common/typeregistry.hh>\n' source += '#include <dune/python/pybind11/pybind11.h>\n' source += '\n' source += "PYBIND11_MODULE( " + moduleName + ", module )\n" source += "{\n" source += " module.def( \"run\", [] ( " + ", ".join([argTypes[i] + " arg" + str(i) for i in range(len(argTypes))]) + " ) {\n" source += " return " + functionName + "( " + ", ".join(["arg" + str(i) for i in range(len(argTypes))]) + " );\n" source += " } );\n" source += "}\n" source += "#endif\n" return builder.load(moduleName, source, signature).run
def load(self, moduleName, source, pythonName): ## TODO replace if rank if something better ## and remove barrier further down if comm.rank == 0: module = sys.modules.get("dune.generated." + moduleName) if module is None: # make sure nothing (compilation, generating and building) is # taking place with Lock(os.path.join(self.dune_py_dir, 'lock-module.lock'), flags=LOCK_EX): # module must be generated so lock the source file with Lock(os.path.join(self.dune_py_dir, 'lock-' + moduleName + '.lock'), flags=LOCK_EX): sourceFileName = os.path.join(self.generated_dir, moduleName + ".cc") line = "dune_add_pybind11_module(NAME " + moduleName + " EXCLUDE_FROM_ALL)" # first check if this line is already present in the CMakeLists file # (possible if a previous script was stopped by user before module was compiled) with open( os.path.join(self.generated_dir, "CMakeLists.txt"), 'r') as out: found = line in out.read() if not os.path.isfile(sourceFileName) or not found: logger.info("Compiling " + pythonName) code = str(source) with open(os.path.join(sourceFileName), 'w') as out: out.write(code) if not found: with open( os.path.join(self.generated_dir, "CMakeLists.txt"), 'a') as out: out.write(line + "\n") # update build system logger.debug("Rebuilding module") try: self.compile() except KeyboardInterrupt: os.remove(os.path.join(sourceFileName)) raise elif isString(source) and not source == open( os.path.join(sourceFileName), 'r').read(): logger.info("Compiling " + pythonName + " (updated)") code = str(source) with open(os.path.join(sourceFileName), 'w') as out: out.write(code) else: logger.debug("Loading " + pythonName) line = "dune_add_pybind11_module(NAME " + moduleName + " EXCLUDE_FROM_ALL)" # the CMakeLists file should already include this line with open( os.path.join(self.generated_dir, "CMakeLists.txt"), 'r') as out: found = line in out.read() assert found, "CMakeLists file does not contain an entry to build" + moduleName # end of exclusive dune-py lock # for compilation a shared lock is enough with Lock(os.path.join(self.dune_py_dir, 'lock-module.lock'), flags=LOCK_SH): # lock generated module with Lock(os.path.join(self.dune_py_dir, 'lock-' + moduleName + '.lock'), flags=LOCK_EX): logger.debug("Now compiling " + moduleName) self.compile(moduleName) ## end if module is not None ## TODO remove barrier here comm.barrier() module = importlib.import_module("dune.generated." + moduleName) if self.force: logger.info("Reloading " + pythonName) module = reload_module(module) return module