← Back to team overview

ffc team mailing list archive

Consolidating dolfin-swig and dolfin

 

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

Follow ups