def get_equation_module(for_distribution=False): """ Returns extension module that deals with the equation of motion. Will try to return from cache before recompiling. By default, dolfin will chose a cache directory using a digest of our code and some version numbers. This procedure enables dolfin to detect changes to our code and recompile on the fly. However, when we distribute FinMag we don't need or want on the fly recompilation and we'd rather have the resulting files placed in a directory known ahead of time. For this, call this function once with `for_distribution` set to True and ship FinMag including the directory build/equation. During normal use, our known cache directory is always checked before dolfin's temporary ones. Its existence bypasses on the fly recompilation. """ # __file__ will not be available during module init if this module is # compiled with cython. So the following line shouldn't be moved to the # module level. It is perfectly safe inside this function though. MODULE_DIR = path.dirname(path.abspath(__file__)) SOURCE_DIR = path.join(MODULE_DIR, "native") # Define our own cache base directory instead of the default one. This # helps in distributing only the compiled code without sources. CACHE_DIR = path.join(MODULE_DIR, "build") signature = "equation" if for_distribution else "" # dolfin will chose # Try to get the module from the known distribution location before # asking instant about its cache. This way a distributed copy of FinMag # should never attempt recompilation (which would fail without sources). equation_module = instant.import_module("equation", CACHE_DIR) if equation_module is not None: log.debug("Got equation extension module from distribution location.") else: with open(path.join(SOURCE_DIR, "equation.h"), "r") as header: code = header.read() equation_module = df.compile_extension_module( code=code, sources=["equation.cpp", "terms.cpp", "derivatives.cpp"], source_directory=SOURCE_DIR, # where the sources given above are include_dirs=[SOURCE_DIR, find_petsc(), find_slepc()], # where to look for header files # dolfin's compile_extension_module will pass on `module_name` to # instant's build_module as `signature`. That's the name of the # directory it will be cached in. So don't worry if instant's doc # says that passing a module name will disable caching. module_name=signature, cache_dir=CACHE_DIR, ) return equation_module
def test_sum(): sig = "((instant unittest test16.py))" # Trying to import module module = import_module(sig, cache_dir="test_cache") if module is None: print("Defining code") c_code = """ class Sum { public: virtual double sum(double a, double b){ return a+b; } }; double use_Sum(Sum& sum, double a, double b) { return sum.sum(a,b); } """ print("Compiling code") module = build_module(code=c_code, signature=sig, cache_dir="test_cache") # Testing module Sum = module.Sum use_Sum = module.use_Sum sum = Sum() a = 3.7 b = 4.8 c = use_Sum(sum, a, b) print("The sum of %g and %g is %g"% (a, b, c)) class Sub(Sum): def __init__(self): Sum.__init__(self) def sum(self, a, b): print("sub") return a-b; sub = Sub() a = 3.7 b = 4.8 c = use_Sum(sub, a, b) print("The sub of %g and %g is %g"% (a, b, c))
def compile_extension_module(code, module_name="", additional_declarations="", additional_system_headers=None, **instant_kwargs): """ Just In Time compile DOLFIN C++ code into a Python module. *Arguments* code C++ code which implements any function or C++ class. Any function or class available in the C++ DOLFIN namespace can be used and/or subclassed. All typemaps from the original Python interface are available, making it possible to interface with for example NumPy for Array<double/int> arguments. Source which is not wrapped in a dolfin namespace will be automatically wrapped. module_name Force a name of the module. If not set a name based on the hex representation of the code will be used. additional_declarations Additional SWIG declarations can be passed using this argument. additional_system_headers : System headers needed to compile the generated can be included using this argument. The headers are passed using a list of 'str' *Returns* The JIT compiled extension module *Examples of usage* The following toy example shows how one can use compiled extension modules to access low level PETSc routines: .. code-block:: python from numpy import arange code = ''' namespace dolfin { void PETSc_exp(std::shared_ptr<dolfin::PETScVector> vec) { Vec x = vec->vec(); assert(x); VecExp(x); } } ''' ext_module = compile_extension_module(code, additional_system_headers=["petscvec.h"]) comm = mpi_comm_world() vec = PETScVector(comm, 10) vec[:] = arange(10) print vec[-1] ext_module.PETSc_exp(vec) print vec[-1] """ # Check the provided arguments expect_arg(str, code, "first") expect_arg(str, module_name, "module_name") expect_arg(str, additional_declarations, "additional_declarations") additional_system_headers = \ expect_list_of(str, additional_system_headers, "additional_system_headers") # Check that the code does not use 'using namespace dolfin' if re.search("using\s+namespace\s+dolfin",code): cpp.dolfin_error("compilemodule.py", "ensure correct argument to compile_extension_module", "Do not use 'using namespace dolfin'. "\ "Include the code in namespace dolfin {...} instead") # Check if the code does not use namespace dolfin {...} if not re.search("namespace\s+dolfin\s*\{[\s\S]+\}", code): # Wrap and indet code in namespace dolfin codelines = ["namespace dolfin","{"] codelines += [" " + line for line in code.split("\n")] codelines += ["}"] code = "\n".join(codelines) # Create unique module name for this application run if module_name is "": module_name = "dolfin_compile_code_%s" % \ hashlib.md5(repr(code) + dolfin.__version__ + \ str(_interface_version)+\ additional_declarations +\ str(additional_system_headers)).hexdigest() # Extract dolfin dependencies and class names used_types, declared_types = parse_and_extract_type_info(code) # Add any bases of the declared types to used_types for declared_type, bases in declared_types.items(): used_types.update(bases) # Filter out dolfin types and add derived and bases for each type used_dolfin_types = [] for dolfin_type in dolfin_type_def: for used_type in used_types: if dolfin_type in used_type: # Add bases and derived types used_dolfin_types.extend(\ dolfin_type_def[dolfin_type]["bases"]) # Add dolfin type used_dolfin_types.append(dolfin_type) break # Generate dependency info dependencies = {} for dolfin_type in used_dolfin_types: if dolfin_type_def[dolfin_type]["submodule"] not in dependencies: dependencies[dolfin_type_def[dolfin_type]["submodule"]] = [] dependencies[dolfin_type_def[dolfin_type]["submodule"]].append(\ dolfin_type_def[dolfin_type]["header"]) # Need special treatment for template definitions in function/pre.i if "function" in dependencies: for dolfin_type in ["FunctionSpace", "Function"]: dependencies["function"].append(dolfin_type_def[dolfin_type]["header"]) # Add uint type if "common" in dependencies: dependencies["common"].append("dolfin/common/types.h") else: dependencies["common"] = ["dolfin/common/types.h"] # Sort the dependencies dependencies = sort_submodule_dependencies(dependencies, submodule_info) import_lines, headers_includes, file_dependencies = \ build_swig_import_info(dependencies, submodule_info, "dolfin.cpp.") # Extract header info dolfin_system_headers = [header for header in file_dependencies \ if not "pre.i" in header] # Check the handed import files interface_import_files = [] # Check cache compiled_module = instant.import_module(module_name) if compiled_module: # Check that the swig version of the compiled module is the same as # dolfin was compiled with check_swig_version(compiled_module) return compiled_module sys.stdout.flush() dolfin.info("Calling DOLFIN just-in-time (JIT) compiler, this may take some time.") # Configure instant and add additional system headers # Add dolfin system headers instant_kwargs["system_headers"] = ["cmath", "iostream","complex", "stdexcept","numpy/arrayobject.h", "memory", "dolfin/common/types.h", "dolfin/math/basic.h"] + \ instant_kwargs.get("system_headers", []) instant_kwargs["system_headers"] += dolfin_system_headers # Add user specified system headers instant_kwargs["system_headers"] += additional_system_headers # Add cmake packages instant_kwargs["cmake_packages"] = ["DOLFIN"] + \ instant_kwargs.get("cmake_packages", []) instant_kwargs["signature"] = module_name declaration_strs = {"additional_declarations":""} declaration_strs["dolfin_import_statement"] = \ "\n".join(import_lines) # Add any provided additional declarations if additional_declarations is not None: declaration_strs["additional_declarations"] += additional_declarations # Add any shared_ptr declarations declaration_strs["shared_ptr_declarations"] = \ extract_shared_ptr_declaration(declared_types, used_dolfin_types, \ shared_ptr_classes) # Compile extension module with instant compiled_module = instant.build_module(\ code = code, additional_declarations = _additional_declarations % declaration_strs, **instant_kwargs) sys.stdout.flush() # Check that the swig version of the compiled module is the same as # dolfin was compiled with check_swig_version(compiled_module) return compiled_module
def jit_form(form, parameters=None): "Just-in-time compile the given form." from ffc.backends.ufc import build_ufc_module # Check that we get a Form if not isinstance(form, Form): error("Unable to convert object to a UFL form: %s" % repr(form)) # Check parameters parameters = _check_parameters(form, parameters) # Set log level set_level(parameters["log_level"]) set_prefix(parameters["log_prefix"]) # Wrap input jit_object = JITObject(form, parameters) # Set prefix for generated code module_name = "ffc_form_" + jit_object.signature() # Use Instant cache if possible cache_dir = parameters["cache_dir"] or None module = instant.import_module(module_name, cache_dir=cache_dir) if module: debug("Reusing form from cache.") else: # Take lock to serialise file removal. # Need to add "_0" to lock as instant.import_module acquire # lock with name: module_name with instant.file_lock(instant.get_default_cache_dir(), module_name + "_0") as lock: # Retry Instant cache. The module may have been created while we waited # for the lock, even if it didn't exist before. module = instant.import_module(module_name, cache_dir=cache_dir) if module: debug("Reusing form from cache.") else: # Write a message log(INFO + 5, "Calling FFC just-in-time (JIT) compiler, this may take some time.") # Generate code compile_form(form, prefix=module_name, parameters=parameters) # Build module using Instant (through UFC) debug("Compiling and linking Python extension module, this may take some time.") hfile = module_name + ".h" cppfile = module_name + ".cpp" if parameters["cpp_optimize"]: cppargs = parameters["cpp_optimize_flags"].split() else: cppargs = ["-O0"] module = build_ufc_module( hfile, source_directory = os.curdir, signature = module_name, sources = [cppfile] if parameters["split"] else [], cppargs = cppargs, cache_dir = cache_dir) # Remove code if os.path.isfile(hfile): os.unlink(hfile) if parameters["split"] : if os.path.isfile(cppfile): os.unlink(cppfile) # Construct instance of compiled form check_swig_version(module) prefix = module_name compiled_form = _instantiate_form(module, prefix) return compiled_form, module, prefix
def test_timing(): #_t = None def tic(): global _t _t = -time.time() def toc(msg=""): t = time.time() + _t print("t = %f (%s)" % (t, msg)) return t c_code = """ double sum(double a, double b) { return a+b; } """ class Sig: def __init__(self, sig): self.sig = sig def signature(self): time.sleep(1.0) return self.sig def __hash__(self): time.sleep(0.5) return hash(self.sig) def __cmp__(self, other): if isinstance(other, Sig): return cmp(self.sig, other.sig) return -1 sig = Sig("((test18.py signature))") cache_dir = "test_cache" # Time a few builds tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t1 = toc("first build") tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t2 = toc("second build") tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t3 = toc("third build") # Time importing tic() module = import_module(sig, cache_dir) assert module is not None t4 = toc("first import") tic() module = import_module(sig, cache_dir) assert module is not None t5 = toc("second import") assert t1 > 1 assert t2 < 1 and t2 > 0.4 assert t3 < 1 and t3 > 0.4 assert t4 < 1 and t4 > 0.4 assert t5 < 1 and t5 > 0.4
def jit(ode, field_states=None, field_parameters=None, monitored=None, code_params=None, cppargs=None): """ Generate a goss::ODEParameterized from a gotran ode and JIT compile it Arguments: ---------- ode : gotran.ODE The gotran ode, either as an ODE or as an ODERepresentation field_states : list A list of state names, which should be treated as field states field_parameters : list A list of parameter names, which should be treated as field parameters monitored : list A list of names of intermediates of the ODE. Code for monitoring the intermediates will be generated. code_params : dict Parameters controling the code generation cppargs : str Default C++ argument passed to the C++ compiler """ # Code generators cgen = GossCodeGenerator(ode, field_states, field_parameters, monitored, code_params) cgen.params.class_code = True pgen = PythonCodeGenerator(cgen.params) # Create unique module name for this application run module_name = "goss_compiled_module_{0}_{1}".format(\ ode.name, hashlib.sha1((ode.signature() + \ repr(code_params) + \ repr(field_states) + \ repr(field_parameters) + \ repr(monitored) + \ #instant.get_swig_version() + \ instant.__version__ + \ gotran.__version__ + \ str(cppargs)).encode()).hexdigest()) # Check cache compiled_module = instant.import_module(module_name) if compiled_module: return getattr(compiled_module, cgen.name)() push_log_level(INFO) # Init state code python_code = pgen.init_states_code(ode) cpp_code = cgen.class_code() info("Calling GOSS just-in-time (JIT) compiler, this may take some "\ "time...") sys.stdout.flush() # Configure instant and add additional system headers instant_kwargs = configure_instant() instant_kwargs["cppargs"] = cppargs or instant_kwargs["cppargs"] instant_kwargs["cmake_packages"] = ["GOSS"] declaration_form = dict(\ ModelName = cgen.name, python_code = python_code, ) # Compile extension module with instant compiled_module = instant.build_module(\ code = cpp_code, additional_declarations = _additional_declarations.format(\ **declaration_form), signature = module_name, **instant_kwargs) info(" done") pop_log_level() sys.stdout.flush() # Return an instantiated class return getattr(compiled_module, cgen.name)()
def test_build(): #_t = None def tic(): global _t _t = -time.time() def toc(msg=""): t = time.time() + _t print("t = %f (%s)" % (t, msg)) return t c_code = """ double sum(double a, double b) { return a+b; } """ class Sig: def __init__(self, sig): self.sig = sig def signature(self): time.sleep(1.0) return self.sig def __hash__(self): time.sleep(0.5) return hash(self.sig) def __cmp__(self, other): if isinstance(other, Sig): return cmp(self.sig, other.sig) return -1 modulename = "test19_ext" cache_dir = "test19_cache" shutil.rmtree(cache_dir, ignore_errors=True) shutil.rmtree(modulename, ignore_errors=True) # Build and rebuild with explicit modulename tic() module = build_module(code=c_code, modulename=modulename, cache_dir=cache_dir) assert module is not None t1 = toc("(1) With modulename") tic() module = build_module(code=c_code, modulename=modulename, cache_dir=cache_dir) assert module is not None t2 = toc("(2) With modulename") assert t1 > t2 # Try importing module in a separate python process python_interp = sys.executable cmd = python_interp + ' -c "import %s"' % modulename print(cmd) stat = os.system(cmd) assert stat == 0 # a # Build and rebuild with a valid filename as signature sig = "test19_signature_module" tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t1 = toc("(1) With signature") tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t2 = toc("(2) With signature") assert t1 > t2 tic() module = import_module(sig, cache_dir) assert module is not None t3 = toc("(3) import_module") assert t1 > t3 # Try importing module in a separate python process python_interp = sys.executable cmd = python_interp + ' -c "import instant; assert instant.import_module(\'%s\', \'%s\') is not None"' % ( sig, cache_dir) print(cmd) stat = os.system(cmd) assert stat == 0 # b # Build and rebuild with generic signature string sig = "((test19_signature_module))" tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t1 = toc("(1) With signature") tic() module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) assert module is not None t2 = toc("(2) With signature") assert t1 > t2 tic() module = import_module(sig, cache_dir) assert module is not None t3 = toc("(3) import_module") assert t1 > t3 # Try importing module in a separate python process python_interp = sys.executable cmd = python_interp + ' -c "import instant; assert instant.import_module(\'%s\', \'%s\') is not None"' % ( sig, cache_dir) print(cmd) stat = os.system(cmd) assert stat == 0 # c print( "Skipping unit test, see https://bugs.launchpad.net/instant/+bug/518389" ) # Build and rebuild with generic signature object #sig = Sig("((test19_signature_module))") #tic() #module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) #assert module is not None #t1 = toc("(1) With signature") #tic() #module = build_module(code=c_code, signature=sig, cache_dir=cache_dir) #assert module is not None #t2 = toc("(2) With signature") #assert t1 > t2 #tic() #module = import_module(sig, cache_dir) #assert module is not None #t3 = toc("(3) import_module") #assert t1 > t3 # Build and rebuild without modulename or signature tic() module = build_module(code=c_code, cache_dir=cache_dir) assert module is not None t1 = toc("(1) Without modulename or signature") tic() module = build_module(code=c_code, cache_dir=cache_dir) assert module is not None t2 = toc("(2) Without modulename or signature") assert t1 > t2