← Back to team overview

ffc team mailing list archive

Re: Consolidating dolfin-swig and dolfin

 

I'll take a look.

There will be a fix to this problem in the not so distant future. I'm
working with Ola Skavhaug and Kent Andre Mardal on a new common
interface specification for the code generation. This will be named
UFC - Unified Form-assembly Code and be the default (and perhaps only)
output format from FFC. The idea is to have a common output format for
FFC and SyFi which is a similar tool developed by Kent.

There will also be a common input language: UFL - Unified Form
Language for both compilers.

The UFC will be chosen to work well with SWIG out of the box, so we
would not need any special solutions for SWIG.

I hope we'll have a first draft ready for the UFC within a month or
two.

/Anders


On Thu, Sep 07, 2006 at 03:23:07AM +0200, Johan Jansson wrote:
> FFC has a special output format for PyDOLFIN: "dolfin-swig", which is
> very similar to the standard "dolfin" format, but generates a bit
> simpler code since SWIG does not support some concepts in C++ (nested
> classes primarily, and namespaces somewhat). This is bad for
> maintainability, since a change in one format has to be made in the
> other, possibly with some minor modification.
> 
> I've done some work in consolidating the two formats and I think I've
> achieved an acceptable result. The new "dolfin" format is semantically
> equivalent to the old format, so there is no visible change.
> 
> The two main changes are: moving nested class declarations out of the
> enclosing class and generating a map (which is output as a SWIG file)
> between DOLFIN class names and PyDOLFIN class names (the scope
> resolution operator "::" doesn't exist in Python, so it has to be
> removed or replaced).
> 
> The consequence is that SWIG can at least parse the nested classes,
> though it (probably) ignores the fact that they are nested. This is a
> hack (and an additional workaround is necessary to compensate for the
> scoping), but it will have to do until there is better support in
> SWIG. The SWIG developers have stated that nested classes won't be
> implemented in the foreseeable future (i.e. until someone volunteers
> responsibility of the problem), so we can't just wait.
> 
> I've attached the diff of the changes (against changeset
> 3a54d08c5669). Tell me if you have any objections, otherwise I'll go
> ahead and make the corresponding changes to PyDOLFIN as well.
> 
>   Johan

> diff -r 3a54d08c5669 src/ffc/format/dolfin.py
> --- a/src/ffc/format/dolfin.py	Wed Jun 14 18:04:10 2006 +0200
> +++ b/src/ffc/format/dolfin.py	Thu Sep 07 02:52:38 2006 +0200
> @@ -37,6 +37,15 @@ format = { "sum": lambda l: " + ".join(l
>             "tmp declaration": lambda j, k: "const real tmp%d_%d" % (j, k),
>             "tmp access": lambda j, k: "tmp%d_%d" % (j, k) }
>  
> +def swig(swigmap):
> +    output = ""
> +
> +    for p in swigmap.items():
> +        output += "%%rename(%s) %s;\n" % (p[1], p[0])
> +
> +    return output
> +
> +
>  def init(options):
>      "Initialize code generation for DOLFIN format."
>  
> @@ -54,6 +63,9 @@ def write(forms, options):
>  
>      # Get name of form
>      name = forms[0].name
> +
> +    # Initialize SWIG map
> +    swigmap = {}
>  
>      # Write file header
>      output = ""
> @@ -80,8 +92,11 @@ be able to use it with DOLFIN.""", -1)
>          else:
>              xmlfile = "%s.xml" % forms[j].name
>  
> -        # Write form
> -        output += __form(form, type, options, xmlfile)
> +        # Write form prototype
> +        output += __form(form, type, options, xmlfile, swigmap, True)
> +
> +        # Write form implementation
> +        output += __form(form, type, options, xmlfile, swigmap, False)
>  
>      # Write file footer
>      output += __file_footer()
> @@ -92,6 +107,15 @@ be able to use it with DOLFIN.""", -1)
>      file.write(output)
>      file.close()
>      debug("Output written to " + filename)
> +
> +    print swigmap
> +
> +    # Write SWIG file
> +    swigfilename = name + ".i"
> +    swigfile = open(swigfilename, "w")
> +    swigfile.write(swig(swigmap))
> +    swigfile.close()
> +    debug("Swig output written to " + filename)
>  
>      # Write XML files if compiling for BLAS
>      if options["blas"]:
> @@ -196,26 +220,36 @@ def __file_footer_element():
>  
>  #endif\n"""
>  
> -def __elements(form):
> +def __elements(form, subclass, swigmap, prototype = False):
>      "Generate finite elements for DOLFIN."
>  
>      output = ""
>      
>      # Write test element (if any)
>      if form.test:
> -        output += __element(form.test, "TestElement")
> +        output += __element(form.test, subclass,
> +                            "TestElement", prototype)
> +        swigmap["dolfin::" + form.name + "::" + subclass + "::" + "TestElement"] = \
> +            form.name + subclass + "TestElement"
>  
>      # Write trial element (if any)
>      if form.trial:
> -        output += __element(form.trial, "TrialElement")
> +        output += __element(form.trial, subclass,
> +                            "TrialElement", prototype)
> +        swigmap["dolfin::" + form.name + "::" + subclass + "::" + "TrialElement"] = \
> +            form.name + subclass + "TrialElement"
>  
>      # Write function elements (if any)
>      for j in range(len(form.elements)):
> -        output += __element(form.elements[j], "FunctionElement_%d" % j)
> +        output += __element(form.elements[j], subclass,
> +                            "FunctionElement_%d" % j, prototype)
> +        swigmap["dolfin::" + form.name + "::" + subclass + "::" + \
> +                "FunctionElement_%d" % j] = \
> +                form.name + subclass + "FunctionElement_%d" % j
>      
>      return output
>  
> -def __element(element, name):
> +def __element(element, subclass, name, prototype = False):
>      "Generate finite element for DOLFIN."
>  
>      # Generate code for initialization of tensor dimensions
> @@ -267,7 +301,8 @@ def __element(element, name):
>      subelements = ""
>      if isinstance(element, MixedElement):
>          for i in range(len(element.elements)):
> -            subelements += __element(element.elements[i], "SubElement_%d" % i)
> +            subelements += __element(element.elements[i], "",
> +                                     "SubElement_%d" % i)
>  
>      # Generate code for FiniteElementSpec
>      if element.type_str == "mixed":
> @@ -280,9 +315,23 @@ def __element(element, name):
>                 (element.type_str, element.shape_str, element.degree())
>  
>      # Generate output
> -    output = """\
> -
> -class %s : public dolfin::FiniteElement
> +    if subclass != "":
> +        subclass += "::"
> +
> +    if prototype == True:
> +        # Write element prototype
> +
> +        output = """\
> +  class %s;
> +
> +""" % (name)
> +        return output
> +    else:
> +        # Write element implementation
> +
> +        output = """\
> +
> +class %s%s : public dolfin::FiniteElement
>  {
>  public:
>  
> @@ -360,7 +409,7 @@ private:
>    FiniteElement** subelements;
>  
>  };
> -""" % (name, name,
> +""" % (subclass, name, name,
>         diminit,
>         elementinit,
>         name,
> @@ -377,9 +426,9 @@ private:
>         spec,
>         subelements)
>  
> -    return indent(output, 2)
> -
> -def __form(form, type, options, xmlfile):
> +        return output
> +
> +def __form(form, type, options, xmlfile, swigmap, prototype = False):
>      "Generate form for DOLFIN."
>      
>      #ptr = "".join(['*' for i in range(form.rank)])
> @@ -395,8 +444,10 @@ def __form(form, type, options, xmlfile)
>      if constinit:
>          constinit = ", " + constinit
>      
> -    # Write class header
> -    output = """\
> +    if prototype == True:
> +        # Write form prototype
> +
> +        output = """\
>  /// This class contains the form to be evaluated, including
>  /// contributions from the interior and boundary of the domain.
>  
> @@ -405,98 +456,125 @@ public:
>  public:
>  """ % (subclass, baseclass)
>  
> -    # Write elements
> -    output += __elements(form)
> -    
> -    # Write constructor
> -    output += """\
> -
> -  %s(%s) : dolfin::%s(%d)%s
> -  {
> -""" % (subclass, arguments, baseclass, form.nfunctions, constinit)
> -
> -    # Initialize test and trial elements
> -    if form.test:
> -        output += """\
> -    // Create finite element for test space
> -    _test = new TestElement();
> -"""
> -    if form.trial:
> -        output += """\
> -
> -    // Create finite element for trial space
> -    _trial = new TrialElement();
> -"""
> +        # Write element prototypes
> +        output += __elements(form, subclass, swigmap, True)
> +
> +        # Write constructor
> +        output += """\
> +
> +  %s(%s);
> +""" % (subclass, arguments)
> +
> +        # Interior contribution
> +        output += """\
> +
> +  void eval(real block[], const AffineMap& map) const;
> +"""
> +
> +        # Boundary contribution (if any)
> +        output += """\
> +
> +  void eval(real block[], const AffineMap& map, unsigned int facet) const;
> +"""
> +
> +        # Declare class members (if any)
> +        if form.nconstants > 0:
> +            output += """\
> +
> +private:
> +
> +"""
> +
> +        # Create declaration list for for constants (if any)
> +        if form.nconstants > 0:
> +            for j in range(form.nconstants):
> +                output += """\
> +  const real& c%d;""" % j
> +            output += "\n"
> +
> +        # Class footer
> +        output += """
> +};
> +
> +"""
> +
> +        # Write elements
> +        output += __elements(form, subclass, swigmap, False)
> +
> +    else:
> +        # Write form implementation
>          
> -    # Add functions (if any)
> -    if form.nfunctions > 0:
> -        output += """\
> -
> -    // Add functions\n"""
> -        for j in range(form.nfunctions):
> -            output += "    add(w%d, new FunctionElement_%d());\n" % (j, j)
> -
> -    # Initialize BLAS array (if any)
> -    if options["blas"]:
> -        output += """\
> -
> -    // Initialize form data for BLAS
> -    blas.init(\"%s\");\n""" % xmlfile
> -
> -    output += "  }\n"
> -
> -    # Interior contribution (if any)
> -    if form.AKi.terms:
> -        eval = __eval_interior(form, options)
> -        output += """\
> -
> -  void eval(real block[], const AffineMap& map) const
> -  {
> -%s  }
> -""" % eval
> -    else:
> -        output += """\
> -
> -  // No contribution from the interior
> -  void eval(real block[], const AffineMap& map) const {}
> -"""
> -
> -    # Boundary contribution (if any)
> -    if form.AKb[0].terms:
> -        eval = __eval_boundary(form, options)
> -        output += """\
> -
> -  void eval(real block[], const AffineMap& map, unsigned int facet) const
> -  {
> -%s  }
> -""" % eval
> -    else:
> -        output += """\
> -
> -  // No contribution from the boundary
> -  void eval(real block[], const AffineMap& map, unsigned int facet) const {}   
> -"""
> -
> -    # Declare class members (if any)
> -    if form.nconstants > 0:
> -        output += """\
> -
> -private:
> -
> -"""
> -
> -    # Create declaration list for for constants (if any)
> -    if form.nconstants > 0:
> -        for j in range(form.nconstants):
> -            output += """\
> -  const real& c%d;""" % j
> -        output += "\n"
> -
> -    # Class footer
> -    output += """
> -};
> -
> -"""
> +        # Write constructor
> +        output = """\
> +
> +%s::%s(%s) : dolfin::%s(%d)%s
> +{
> +""" % (subclass, subclass, arguments, baseclass, form.nfunctions, constinit)
> +
> +        # Initialize test and trial elements
> +        if form.test:
> +            output += """\
> +  // Create finite element for test space
> +  _test = new TestElement();
> +"""
> +        if form.trial:
> +            output += """\
> +
> +  // Create finite element for trial space
> +  _trial = new TrialElement();
> +"""
> +        
> +        # Add functions (if any)
> +        if form.nfunctions > 0:
> +            output += """\
> +
> +  // Add functions\n"""
> +            for j in range(form.nfunctions):
> +                output += "  add(w%d, new FunctionElement_%d());\n" % (j, j)
> +
> +        # Initialize BLAS array (if any)
> +        if options["blas"]:
> +            output += """\
> +
> +  // Initialize form data for BLAS
> +  blas.init(\"%s\");\n""" % xmlfile
> +
> +        output += "}\n"
> +
> +        # Interior contribution (if any)
> +        if form.AKi.terms:
> +            eval = __eval_interior(form, options)
> +            output += """\
> +
> +void %s::eval(real block[], const AffineMap& map) const
> +{
> +%s}
> +""" % (subclass, eval)
> +        else:
> +            output += """\
> +
> +// No contribution from the interior
> +void %s::eval(real block[], const AffineMap& map) const {}
> +""" % subclass
> +
> +        # Boundary contribution (if any)
> +        if form.AKb[0].terms:
> +            eval = __eval_boundary(form, options)
> +            output += """\
> +
> +void %s::eval(real block[], const AffineMap& map, unsigned int facet) const
> +{
> +%s}
> +""" % (subclass, eval)
> +        else:
> +            output += """\
> +
> +// No contribution from the boundary
> +void %s::eval(real block[], const AffineMap& map, unsigned int facet) const {}   
> +""" % subclass
> +
> +    swigmap["dolfin::" + form.name + "::" + subclass] = \
> +        form.name + subclass
>  
>      return output
>  
> @@ -514,22 +592,22 @@ def __eval_interior_default(form, option
>      if not options["debug-no-geometry-tensor"]:
>          if len(form.cKi) > 0:
>              output += """\
> -    // Compute coefficients
> -%s
> -""" % "".join(["    const real %s = %s;\n" % (cKi.name, cKi.value) for cKi in form.cKi if cKi.used])
> -        output += """\
> -    // Compute geometry tensors
> -%s"""  % "".join(["    const real %s = %s;\n" % (gK.name, gK.value) for gK in form.AKi.gK if gK.used])
> -    else:
> -        output += """\
> -    // Compute geometry tensors
> -%s""" % "".join(["    const real %s = 0.0;\n" % gK.name for gK in form.AKi.gK if gK.used])
> +  // Compute coefficients
> +%s
> +""" % "".join(["  const real %s = %s;\n" % (cKi.name, cKi.value) for cKi in form.cKi if cKi.used])
> +        output += """\
> +  // Compute geometry tensors
> +%s"""  % "".join(["  const real %s = %s;\n" % (gK.name, gK.value) for gK in form.AKi.gK if gK.used])
> +    else:
> +        output += """\
> +  // Compute geometry tensors
> +%s""" % "".join(["  const real %s = 0.0;\n" % gK.name for gK in form.AKi.gK if gK.used])
>  
>      if not options["debug-no-element-tensor"]:
>          output += """\
>  
> -    // Compute element tensor
> -%s""" % "".join(["    %s = %s;\n" % (aK.name, aK.value) for aK in form.AKi.aK])
> +  // Compute element tensor
> +%s""" % "".join(["  %s = %s;\n" % (aK.name, aK.value) for aK in form.AKi.aK])
>  
>      return output
>  
> @@ -541,24 +619,24 @@ def __eval_interior_blas(form, options):
>      if not options["debug-no-geometry-tensor"]:
>          if len(form.cKi) > 0:
>              output += """\
> -    // Compute coefficients
> -%s
> -""" % "".join(["    const real %s = %s;\n" % (cKi.name, cKi.value) for cKi in form.cKi if cKi.used])
> -        output += """\
> -    // Reset geometry tensors
> -    for (unsigned int i = 0; i < blas.ni; i++)
> -      blas.Gi[i] = 0.0;
> -
> -    // Compute entries of G multiplied by nonzero entries of A
> -%s
> -""" % "".join(["    blas.Gi[%d] = %s;\n" % (j, form.AKi.gK[j].value)
> +  // Compute coefficients
> +%s
> +""" % "".join(["  const real %s = %s;\n" % (cKi.name, cKi.value) for cKi in form.cKi if cKi.used])
> +        output += """\
> +  // Reset geometry tensors
> +  for (unsigned int i = 0; i < blas.ni; i++)
> +    blas.Gi[i] = 0.0;
> +
> +  // Compute entries of G multiplied by nonzero entries of A
> +%s
> +""" % "".join(["  blas.Gi[%d] = %s;\n" % (j, form.AKi.gK[j].value)
>                 for j in range(len(form.AKi.gK)) if form.AKi.gK[j].used])
>  
>      # Compute element tensor
>      if not options["debug-no-element-tensor"]:
>          output += """\
> -    // Compute element tensor using level 2 BLAS
> -    cblas_dgemv(CblasRowMajor, CblasNoTrans, blas.mi, blas.ni, 1.0, blas.Ai, blas.ni, blas.Gi, 1, 0.0, block, 1);
> +  // Compute element tensor using level 2 BLAS
> +  cblas_dgemv(CblasRowMajor, CblasNoTrans, blas.mi, blas.ni, 1.0, blas.Ai, blas.ni, blas.Gi, 1, 0.0, block, 1);
>  """
>  
>      return output
> @@ -577,31 +655,31 @@ def __eval_boundary_default(form, option
>      if not options["debug-no-geometry-tensor"]:
>          if len(form.cKb) > 0:
>              output += """\
> -    // Compute coefficients
> -%s
> -""" % "".join(["    const real %s = %s;\n" % (cKb.name, cKb.value) for cKb in form.cKb if cKb.used])
> -        output += """\
> -    // Compute geometry tensors
> -%s""" % "".join(["    const real %s = %s;\n" % (gK.name, gK.value) for gK in form.AKb[-1].gK if gK.used])
> -    else:
> -        output += """\
> -    // Compute geometry tensors
> -%s""" % "".join(["    const real %s = 0.0;\n" % gK.name for gK in form.AKb[-1].gK if gK.used])
> +  // Compute coefficients
> +%s
> +""" % "".join(["  const real %s = %s;\n" % (cKb.name, cKb.value) for cKb in form.cKb if cKb.used])
> +        output += """\
> +  // Compute geometry tensors
> +%s""" % "".join(["  const real %s = %s;\n" % (gK.name, gK.value) for gK in form.AKb[-1].gK if gK.used])
> +    else:
> +        output += """\
> +  // Compute geometry tensors
> +%s""" % "".join(["  const real %s = 0.0;\n" % gK.name for gK in form.AKb[-1].gK if gK.used])
>  
>      if not options["debug-no-element-tensor"]:
>          output += """\
>  
> -    // Compute element tensor
> -    switch ( facet )
> -    { """
> +  // Compute element tensor
> +  switch ( facet )
> +  { """
>          for akb in form.AKb:
>            output += """ 
> -    case %s:"""  % akb.facet   
> +  case %s:"""  % akb.facet   
>            output += """ 
> -%s      break; \n""" % "".join(["      %s = %s;\n" % (aK.name, aK.value) for aK in akb.aK])
> -
> -        output += """\
> -    } \n"""
> +%s      break; \n""" % "".join(["    %s = %s;\n" % (aK.name, aK.value) for aK in akb.aK])
> +
> +        output += """\
> +  } \n"""
>      return output
>  
>  def __eval_boundary_blas(form, options):
> @@ -612,24 +690,24 @@ def __eval_boundary_blas(form, options):
>      if not options["debug-no-geometry-tensor"]:
>          if len(form.cKb) > 0:
>              output += """\
> -    // Compute coefficients
> -%s
> -""" % "".join(["    const real %s = %s;\n" % (cKb.name, cKb.value) for cKb in form.cKb if cKb.used])        
> -        output += """\
> -    // Reset geometry tensors
> -    for (unsigned int i = 0; i < blas.nb; i++)
> -      blas.Gb[i] = 0.0;
> -
> -    // Compute entries of G multiplied by nonzero entries of A
> -%s
> -""" % "".join(["    blas.Gb[%d] = %s;\n" % (j, form.AKb.gK[j].value)
> +  // Compute coefficients
> +%s
> +""" % "".join(["  const real %s = %s;\n" % (cKb.name, cKb.value) for cKb in form.cKb if cKb.used])        
> +        output += """\
> +  // Reset geometry tensors
> +  for (unsigned int i = 0; i < blas.nb; i++)
> +    blas.Gb[i] = 0.0;
> +
> +  // Compute entries of G multiplied by nonzero entries of A
> +%s
> +""" % "".join(["  blas.Gb[%d] = %s;\n" % (j, form.AKb.gK[j].value)
>                  for j in range(len(form.AKb.gK)) if form.AKb.gK[j].used])
>  
>      # Compute element tensor
>      if not options["debug-no-element-tensor"]:
>          output += """\
> -    // Compute element tensor using level 2 BLAS
> -    cblas_dgemv(CblasRowMajor, CblasNoTrans, blas.mb, blas.nb, 1.0, blas.Ab, blas.nb, blas.Gb, 1, 0.0, block, 1);
> +  // Compute element tensor using level 2 BLAS
> +  cblas_dgemv(CblasRowMajor, CblasNoTrans, blas.mb, blas.nb, 1.0, blas.Ab, blas.nb, blas.Gb, 1, 0.0, block, 1);
>  """
>  
>      return output

> _______________________________________________
> FFC-dev mailing list
> FFC-dev@xxxxxxxxxx
> http://www.fenics.org/mailman/listinfo/ffc-dev



Follow ups

References