← Back to team overview

dolfin team mailing list archive

Re: Compile time of forms and linear algebra operators

 

Very nice!

I've applied the patches and they seem to work fine.

I agree with your suggestions below, but will fix later.

-- 
Anders


On Thu, Jul 03, 2008 at 09:34:55PM +0200, Johan Hake wrote:
> On Monday 30 June 2008 22:28:42 Anders Logg wrote:
> > On Mon, Jun 30, 2008 at 10:26:19PM +0200, Martin Sandve Alnæs wrote:
> > > 2008/6/30 Anders Logg <logg@xxxxxxxxx>:
> > > > On Mon, Jun 30, 2008 at 09:47:31PM +0200, Martin Sandve Alnæs wrote:
> > > >> The best way to achieve good UFC compatibility is to limit
> > > >> assumptions about the form compilers to a minimum, and
> > > >> (a) ufl form
> > > >> (b) which compiler?
> > > >> (c) a generic set of compiler options (any kind of object)
> > > >> sounds to me like a good generalization.
> > > >
> > > > Sounds good. We can just have a parameter
> > > >
> > > >  form_compiler_options
> > > >
> > > > which is passed on to the form compiler in assemble().
> > > >
> > > > Should the choice of form compiler be an argument to assemble() or
> > > > should it be a global option set by dolfin_set()?
> > >
> > > I think that can safely stay a global option, at least for now.
> > >
> > > In particular, mixing form compilers in one application will
> > > be messy because of dofmaps, and I don't think fixing that
> > > is where we want to spend our time.
> >
> > ok.
> >
> > Hake, do you want to supply a patch for this (a slight modification of
> > your previous patch). I'm on vacation.
> 
> Ok, a patch is provided. 
> 
> As the suggested interface to jit was jit(form,options), I also needed to 
> update ffc.jit. I hope this was what you have intetended. 
> 
> In addition to the update in jit functionality I also cleaned up the extracton 
> of coefficients.
> 
> All python demos are running. I do not know if there are any demos on the 
> latets assemble_system that Kent added. If you have a python demo on that 
> could you please verify that it runs Kent?
> 
> A copy of the dolfin commit message which explain what I did.
> ********************************************************************
> A clean up in assemble.py
>   - Putted code for extracting coeffisients in own function
>   - Added the parameter "form compiler", which can be set by, e.g.,
>     dolfin_set("form compiler","ffc")
>   - Dolfin assume that the form compiler implements the jit function, 
>     which takes a form as first argument and an optional option parameter
>     as second argument.
>   - The options parameter is optional. If provided it must be a dict that is
>     handed directly to the registered form compilers jit function.
>   - Added a private function _jit(form, options) which uses the jit compiler
>     that is provided by the module, registered in dolfin_get("form compiler").
>   - This commit requires that also ffc is updated to newest version, as 
>     the jit interface needed to be updated.
> ********************************************************************
> 
> The change in FFC was done so it was as noninvasive for the rest of the code 
> as possible. This made it a bit hackish. I suggest that the parameter system 
> for ffc should be looked upon.
> 
> It would be nice if ffc kept its options in one dict that could be updated
> by users either from jit() or from the command line. As it is now, there are 
> several parameters that is provided from function to function.
> 
> I also suggest that the names of these options should be intuitive, so e.g., 
> changing "precision=" to "precision" and "quadrature_points=" to "quadrature 
> points".
> 
> Johan

> # HG changeset patch
> # User "Johan Hake <hake@xxxxxxxxx>"
> # Date 1215111425 -7200
> # Node ID c60b15e025c0a10ede4e457514b2e6602f69ad65
> # Parent  d65f637ae991396daf448dc455cd24402d68ffdc
> A clean up in assemble.py
>   - Putted code for extracting coeffisients in own function
>   - Added the parameter "form compiler", which can be set by, e.g.,
>     dolfin_set("form compiler","ffc")
>   - Dolfin assume that the form compiler implements the jit function, which takes
>     a form as first argument and an optional option parameter as second argument.
>   - The options parameter is optional. If provided it must be a dict that is
>     handed directly to the registered form compilers jit function.
>   - Added a private function _jit(form, options) which uses the jit compiler
>     that is provided by the module, registered in dolfin_get("form compiler").
>   - This commit requires that also ffc is updated to newest version, as the jit
>     interface needed to be updated.
> 
> diff -r d65f637ae991 -r c60b15e025c0 site-packages/dolfin/assemble.py
> --- a/site-packages/dolfin/assemble.py	Thu Jul 03 20:45:38 2008 +0200
> +++ b/site-packages/dolfin/assemble.py	Thu Jul 03 20:57:05 2008 +0200
> @@ -36,56 +36,30 @@ _dof_map_cache = {}
>  # Cache for tensors
>  _tensor_cache = {}
>  
> +# Temporary storage of compiled coeffisient functions avoiding
> +# Python deleting our function objects before assembly!
> +_compiled_coefficients = []
> +
> +# Add a parameter for form compiler
> +dolfin_add("form compiler","ffc")
> +
>  # JIT assembler
>  def assemble(form, mesh, coefficients=None, dof_maps=None,
>      cell_domains=None, exterior_facet_domains=None, interior_facet_domains=None, reset_tensor=None,
> -    tensor=None, backend=None, return_dofmaps=False):
> +    tensor=None, backend=None, return_dofmaps=False, form_compiler_option = None):
>      "Assemble form over mesh and return tensor"
> -
> -    # Create empty list of coefficients, filled below
> -    _coefficients = ArrayFunctionPtr()
> -        
> -    # Extract coefficients
> -    if not coefficients is None: 
> -        # Compile all strings as dolfin::Function
> -        string_expressions = []
> -        for c in coefficients:
> -            # Note: To allow tuples of floats or ints below, this logic becomes more involved...
> -            if isinstance(c, (tuple, str)):
> -                string_expressions.append(c)
> -        if string_expressions:
> -            compiled_functions = compile_functions(string_expressions, mesh)
> -            compiled_functions.reverse()
> -        
> -        # Avoid Python deleting our function objects before assembly!
> -        remember_coefficients = []
> -
> -        # Build list of coefficients
> -        for c in coefficients:
> -            # Note: We could generalize this to support more objects 
> -            # like sympy expressions, tuples for constant vectors, etc...
> -            if isinstance(c, (float, int)):
> -                c = cpp_Function(mesh, float(c))
> -            elif isinstance(c, (tuple, str)):
> -                c = compiled_functions.pop()
> -            _coefficients.push_back(c)
> -            remember_coefficients.append(c)
>  
>      # Check if we need to compile the form (JIT)
>      if not hasattr(form, "create_cell_integral"):
>          # FFC form, call JIT compile
> -        optimize = dolfin_get("optimize form") or dolfin_get("optimize")
> -        (compiled_form, module, form_data) = jit(form, optimize=optimize)
> -        
> -        # Extract coefficients from form if no coefficients are provided
> -        if coefficients is None:
> -            for c in form_data.coefficients:
> -                _coefficients.push_back(c.f)
> -        
> +        (compiled_form, module, form_data) = _jit(form, form_compiler_option)
>      else:    
>          # UFC form, no need to compile
>          compiled_form = form
> -        
> +        form_data     = None
> +
> +    # Extract coefficients
> +    _coefficients = _extract_coefficients(coefficients, form_data)
>  
>      # FIXME: do we need these lines now? None works fine with Assembler
>      # Create dummy arguments for domains (not yet supported in Python)
> @@ -110,7 +84,10 @@ def assemble(form, mesh, coefficients=No
>      # Assemble tensor from compiled form
>      cpp_assemble(tensor, compiled_form, mesh, _coefficients, dof_maps,
>                   cell_domains, exterior_facet_domains, interior_facet_domains, reset_tensor)
> -    
> +
> +    # Clear any temporary stored coeffisient functions
> +    _clear_compiled_coefficients()
> +
>      # Convert to float for scalars
>      if rank == 0:
>          tensor = tensor.getval()
> @@ -121,91 +98,34 @@ def assemble(form, mesh, coefficients=No
>      else:
>          return tensor
>  
> +
>  # JIT system assembler
>  def assemble_system(A_form, b_form, bc, mesh, A_coefficients=None, b_coefficients=None, A_dof_maps=None, b_dof_maps=None,
>      cell_domains=None, exterior_facet_domains=None, interior_facet_domains=None, reset_tensor=None,
> -    A_tensor=None, b_tensor=None, backend=None, return_dofmaps=False):
> +    A_tensor=None, b_tensor=None, backend=None, return_dofmaps=False, form_compiler_option = None):
>      "Assemble form over mesh and return tensor"
>  
> -    # Create empty list of coefficients, filled below
> -    _A_coefficients = ArrayFunctionPtr()
> -    _b_coefficients = ArrayFunctionPtr()
> +    # Check if we need to compile the A_form (JIT)
> +    if not hasattr(A_form, "create_cell_integral"):
> +        # FFC form, call JIT compile
> +        (A_compiled_form, module, A_form_data) = _jit(A_form, form_compiler_option)
> +    else:    
> +        # UFC form, no need to compile
> +        compiled_form = form
> +        A_form_data     = None
> +
> +    # Check if we need to compile the b_form (JIT)
> +    if not hasattr(b_form, "create_cell_integral"):
> +        # FFC form, call JIT compile
> +        (b_compiled_form, module, b_form_data) = _jit(b_form, form_compiler_option)
> +    else:    
> +        # UFC form, no need to compile
> +        compiled_form = form
> +        b_form_data     = None
>  
>      # Extract coefficients
> -    if not A_coefficients is None: 
> -        # Compile all strings as dolfin::Function
> -        string_expressions = []
> -        for c in A_coefficients:
> -            # Note: To allow tuples of floats or ints below, this logic becomes more involved...
> -            if isinstance(c, (tuple, str)):
> -                string_expressions.append(c)
> -        if string_expressions:
> -            compiled_functions = compile_functions(string_expressions, mesh)
> -            compiled_functions.reverse()
> -        
> -        # Build list of coefficients
> -        for c in A_coefficients:
> -            # Note: We could generalize this to support more objects 
> -            # like sympy expressions, tuples for constant vectors, etc...
> -            if isinstance(c, (float, int)):
> -                c = cpp_Function(mesh, float(c))
> -            elif isinstance(c, (tuple, str)):
> -                c = compiled_functions.pop()
> -            _A_coefficients.push_back(c)
> -
> -    # Extract coefficients
> -    if not b_coefficients is None: 
> -        # Compile all strings as dolfin::Function
> -        string_expressions = []
> -        for c in b_coefficients:
> -            # Note: To allow tuples of floats or ints below, this logic becomes more involved...
> -            if isinstance(c, (tuple, str)):
> -                string_expressions.append(c)
> -        if string_expressions:
> -            compiled_functions = compile_functions(string_expressions, mesh)
> -            compiled_functions.reverse()
> -        
> -        # Build list of coefficients
> -        for c in b_coefficients:
> -            # Note: We could generalize this to support more objects 
> -            # like sympy expressions, tuples for constant vectors, etc...
> -            if isinstance(c, (float, int)):
> -                c = cpp_Function(mesh, float(c))
> -            elif isinstance(c, (tuple, str)):
> -                c = compiled_functions.pop()
> -            _b_coefficients.push_back(c)
> -
> -
> -    # Check if we need to compile the form (JIT)
> -    if not hasattr(A_form, "create_cell_integral"):
> -        # FFC form, call JIT compile
> -        optimize = dolfin_get("optimize form") or dolfin_get("optimize")
> -        (A_compiled_form, module, form_data) = jit(A_form, optimize=optimize)
> -        
> -        # Extract coefficients from form if no coefficients are provided
> -        if A_coefficients is None:
> -            for c in form_data.coefficients:
> -                _A_coefficients.push_back(c.f)
> -        
> -    else:    
> -        # UFC form, no need to compile
> -        A_compiled_form = form
> -
> -    # Check if we need to compile the form (JIT)
> -    if not hasattr(b_form, "create_cell_integral"):
> -        # FFC form, call JIT compile
> -        optimize = dolfin_get("optimize form") or dolfin_get("optimize")
> -        (b_compiled_form, module, form_data) = jit(b_form, optimize=optimize)
> -        
> -        # Extract coefficients from form if no coefficients are provided
> -        if b_coefficients is None:
> -            for c in form_data.coefficients:
> -                _b_coefficients.push_back(c.f)
> -        
> -    else:    
> -        # UFC form, no need to compile
> -        b_compiled_form = form
> -        
> +    _A_coefficients = _extract_coefficients(A_coefficients, A_form_data)
> +    _b_coefficients = _extract_coefficients(b_coefficients, b_form_data)
>  
>      # FIXME: do we need these lines now? None works fine with Assembler
>      # Create dummy arguments for domains (not yet supported in Python)
> @@ -235,6 +155,8 @@ def assemble_system(A_form, b_form, bc, 
>                          b_tensor, b_compiled_form, _b_coefficients, b_dof_maps,  
>                          mesh, bc, cell_domains, exterior_facet_domains, interior_facet_domains, reset_tensor)
>      
> +    # Clear any temporary stored coeffisient functions
> +    _clear_compiled_coefficients()
>      
>      # Return value
>      if return_dofmaps:
> @@ -263,6 +185,68 @@ def _create_dof_map_set(form, compiled_f
>          _dof_map_cache[form] = dof_maps
>  
>      return dof_maps
> +
> +def _jit(form, options = None):
> +    """ Just in time compile any provided form
> +
> +    It uses the jit function from the form compiler registered by
> +    dolfin_set("form compiler")"""
> +
> +    # Extract the form compiler
> +    compiler_str = dolfin_get("form compiler")
> +    try:
> +        exec("import %s"%compiler_str)
> +        jit = eval("%s.jit"%compiler_str)
> +    except ImportError:
> +        raise RuntimeError, "Could not import %s form compiler"%compiler_str
> +    except AttributeError:
> +        raise RuntimeError, "Form compiler must implement the jit function"
> +    
> +    if options is None:
> +        options = {}
> +    options["cpp optimize"] = dolfin_get("optimize form") or dolfin_get("optimize")
> +    return jit(form, options)
> +
> +def _extract_coefficients(coefficients, form_data):
> +    "Extract provided coefficients"
> +    
> +    # Create empty list of coefficients, filled below
> +    _coefficients = ArrayFunctionPtr()
> +        
> +    # Extract coefficients
> +    if coefficients is None:
> +        # If no coefficients are porvided try to extract them from form_data
> +        if hasattr(form_data,"coefficients"):
> +            for c in form_data.coefficients:
> +                _coefficients.push_back(c.f)
> +    else:
> +        # Compile all strings as dolfin::Function
> +        string_expressions = []
> +        for c in coefficients:
> +            # Note: To allow tuples of floats or ints below, this logic becomes more involved...
> +            if isinstance(c, (tuple, str)):
> +                string_expressions.append(c)
> +        if string_expressions:
> +            compiled_functions = compile_functions(string_expressions, mesh)
> +            compiled_functions.reverse()
> +        
> +        # Build list of coefficients
> +        for c in coefficients:
> +            # Note: We could generalize this to support more objects 
> +            # like sympy expressions, tuples for constant vectors, etc...
> +            if isinstance(c, (float, int)):
> +                c = cpp_Function(mesh, float(c))
> +            elif isinstance(c, (tuple, str)):
> +                c = compiled_functions.pop()
> +            _coefficients.push_back(c)
> +            _compiled_coefficients.append(c)
> +
> +    return _coefficients
> +
> +def _clear_compiled_coefficients():
> +    "Clear stored compiled coeffisient functions" 
> +    while _compiled_coefficients:
> +        _compiled_coefficients.pop()
>  
>  def _create_tensor(form, rank, backend, tensor, reset_tensor):
>      "Create tensor for form"
> @@ -318,7 +302,7 @@ class Function(ffc_Function, cpp_Functio
>              else:
>                  form = TestFunction(element)*dx
>              # Compile form and create dof map
> -            (compiled_form, module, form_data) = jit(form)
> +            (compiled_form, module, form_data) = _jit(form)
>              self.dof_maps = DofMapSet(compiled_form, mesh)
>              # Initialize FFC and DOLFIN Function
>              ffc_Function.__init__(self, element)
> @@ -423,7 +407,7 @@ class LinearPDE:
>  
>          # FIXME: Maybe there is a better solution?
>          # Compile form, needed to create discrete function
> -        (compiled_form, module, form_data) = jit(self.a)
> +        (compiled_form, module, form_data) = _jit(self.a)
>  
>          # Apply boundary conditions
>          for bc in self.bcs:
> @@ -478,7 +462,7 @@ class DirichletBC(cpp_DirichletBC):
>          "Apply boundary condition to linear system"
>          
>          # Compile form
> -        (compiled_form, module, form_data) = jit(form)        
> +        (compiled_form, module, form_data) = _jit(form)        
>  
>          # Create dof maps
>          dof_maps = DofMapSet(compiled_form, self.mesh())
> @@ -490,7 +474,7 @@ class DirichletBC(cpp_DirichletBC):
>          "Apply boundary condition to linear system"
>          
>          # Compile form
> -        (compiled_form, module, form_data) = jit(form)        
> +        (compiled_form, module, form_data) = _jit(form)        
>  
>          # Create dof maps
>          dof_maps = DofMapSet(compiled_form, self.mesh())
> @@ -509,7 +493,7 @@ class PeriodicBC(cpp_PeriodicBC):
>          "Apply boundary condition to linear system"
>          
>          # Compile form
> -        (compiled_form, module, form_data) = jit(form)        
> +        (compiled_form, module, form_data) = _jit(form)        
>  
>          # Create dof maps
>          dof_maps = DofMapSet(compiled_form, self.mesh())

> # HG changeset patch
> # User "Johan Hake <hake@xxxxxxxxx>"
> # Date 1215111845 -7200
> # Node ID 77e33e2dbc3ccd7abfe644d4e7a00a8bf7fd2155
> # Parent  ca574155bd2d52db55beea7d04b0ca19894d3264
> Change the interface to jit. It now takes a form as first argument and
> and optional options argument at second. It now complies to the interface
> used in pyDOLFIN's assemble function.
> 
> diff -r ca574155bd2d -r 77e33e2dbc3c src/ffc/jit/jit.py
> --- a/src/ffc/jit/jit.py	Tue Jun 24 07:31:48 2008 +0200
> +++ b/src/ffc/jit/jit.py	Thu Jul 03 21:04:05 2008 +0200
> @@ -33,11 +33,36 @@ FFC_OPTIONS_JIT = FFC_OPTIONS.copy()
>  #FFC_OPTIONS_JIT["no-evaluate_basis"] = True
>  FFC_OPTIONS_JIT["no-evaluate_basis_derivatives"] = True
>  
> -def jit(input_form, representation=FFC_REPRESENTATION, language=FFC_LANGUAGE, options=FFC_OPTIONS_JIT, optimize=False):
> -    "Just-in-time compile the given form or element"
> +def jit(input_form, options = None):
> +    """ Just-in-time compile the given form or element
> +    
> +    Parameters:
> +    input_form : The form
> +    options    : An option dict. 
> +    """
>  
> +    # Collect options
> +    _options = FFC_OPTIONS_JIT.copy()
> +    if options is None:
> +        # Default options
> +        cpp_optimize   = False
> +        representation = FFC_REPRESENTATION
> +        language       = FFC_LANGUAGE
> +    elif isinstance(options,dict):
> +        cpp_optimize   = options.pop("cpp optimize",False)
> +        representation = options.pop("representation",FFC_REPRESENTATION)
> +        language       = options.pop("language",FFC_LANGUAGE)
> +        for key, value in options.iteritems():
> +            if _options.has_key(key):
> +                _options[key] = value
> +            else:
> +                # FIXME: Warn that options is not set?
> +                pass
> +    else:
> +        raise RuntimeError, "options must be a dict"
> +        
>      # Set C++ compiler options
> -    if optimize:
> +    if cpp_optimize: 
>          cpp_args = "-O2"
>      else:
>          cpp_args = "-O0"
> @@ -57,7 +82,7 @@ def jit(input_form, representation=FFC_R
>      # Compute md5 checksum of form signature
>      signature = " ".join([str(form),
>                            ", ".join([element.signature() for element in form_data.elements]),
> -                          representation, language, str(options), cpp_args])
> +                          representation, language, str(_options), cpp_args])
>      md5sum = "form_" + md5.new(signature).hexdigest()
>  
>      # Get name of form
> @@ -102,7 +127,7 @@ def jit(input_form, representation=FFC_R
>      if compiled_form is None:
>          
>          # Build form module
> -        build_module(form, representation, language, options, md5sum, form_dir, module_dir, prefix, cpp_args)
> +        build_module(form, representation, language, _options, md5sum, form_dir, module_dir, prefix, cpp_args)
>  
>          # Import form module
>          sys.path.append(form_dir)

Attachment: signature.asc
Description: Digital signature


References