~ubuntu-branches/ubuntu/natty/ffc/natty

« back to all changes in this revision

Viewing changes to ffc/compiler/finiteelement.py

  • Committer: Bazaar Package Importer
  • Author(s): Johannes Ring
  • Date: 2010-02-03 20:22:35 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20100203202235-fe8d0kajuvgy2sqn
Tags: 0.9.0-1
* New upstream release.
* debian/control: Bump Standards-Version (no changes needed).
* Update debian/copyright and debian/copyright_hints.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"Code generation for finite element"
2
 
 
3
 
__author__ = "Anders Logg (logg@simula.no)"
4
 
__date__ = "2007-01-23 -- 2009-08-26"
5
 
__copyright__ = "Copyright (C) 2007-2009 Anders Logg"
6
 
__license__  = "GNU GPL version 3 or any later version"
7
 
 
8
 
# Modified by Kristian Oelgaard 2009
9
 
# Modified by Marie Rognes 2008
10
 
# Modified by Garth N. Wells 2009
11
 
 
12
 
# FIAT modules
13
 
from FIAT.shapes import LINE
14
 
 
15
 
# FFC common modules
16
 
from ffc.common.log import debug, error
17
 
 
18
 
# FFC fem modules
19
 
from ffc.fem.finiteelement import *
20
 
from ffc.fem.vectorelement import *
21
 
from ffc.fem.dofmap import *
22
 
from ffc.fem.quadratureelement import *
23
 
 
24
 
# FFC code generation common modules
25
 
from evaluatebasis import evaluate_basis
26
 
from evaluatebasisderivatives import evaluate_basis_derivatives
27
 
from codeutils import inner_product
28
 
 
29
 
def generate_finite_elements(form_data, format):
30
 
    "Generate code for finite elements, including recursively nested sub elements."
31
 
 
32
 
    debug("Generating code for finite elements...")
33
 
    code = []
34
 
 
35
 
    # Iterate over form elements
36
 
    for (i, element) in enumerate(form_data.ffc_elements):
37
 
 
38
 
        # Extract sub elements
39
 
        sub_elements = _extract_sub_elements(element, (i,))
40
 
 
41
 
        # Generate code for each element
42
 
        for (label, sub_element) in sub_elements:
43
 
            code += [(label, generate_finite_element(sub_element, format))]
44
 
                
45
 
    debug("done")
46
 
    return code
47
 
 
48
 
def generate_finite_element(element, format):
49
 
    """Generate dictionary of code for the given finite element
50
 
    according to the given format."""
51
 
 
52
 
    code = {}
53
 
 
54
 
    # Generate code for signature
55
 
    code["signature"] = element.signature()
56
 
 
57
 
    # Generate code for cell_shape
58
 
    code["cell_shape"] = format["cell shape"](element.cell_shape())
59
 
    
60
 
    # Generate code for space_dimension
61
 
    code["space_dimension"] = "%d" % element.space_dimension()
62
 
 
63
 
    # Generate code for value_rank
64
 
    # FIXME: This is just a temporary hack to 'support' tensor elements
65
 
    code["value_rank"] = "%d" % element.value_rank()
66
 
    code["value_rank"] = "%d" % element._rank
67
 
 
68
 
    # Generate code for value_dimension
69
 
    code["value_dimension"] = ["%d" % element.value_dimension(i) for i in range(max(element.value_rank(), 1))]
70
 
 
71
 
    # Disable code generation for unsupported functions of QuadratureElement,
72
 
    # (or MixedElements including QuadratureElements)
73
 
    if not True in [isinstance(e, QuadratureElement) for e in element.extract_elements()]:
74
 
        # Generate code for evaluate_basis
75
 
        code["evaluate_basis"] = evaluate_basis(element, format)
76
 
 
77
 
        # Generate code for evaluate_basis_derivatives
78
 
        code["evaluate_basis_derivatives"] = evaluate_basis_derivatives(element, format)
79
 
 
80
 
        # Generate vectorised version of evaluate functions
81
 
        code["evaluate_basis_all"] =\
82
 
          format["exception"]("The vectorised version of evaluate_basis() is not yet implemented.")
83
 
        code["evaluate_basis_derivatives_all"] =\
84
 
          format["exception"]("The vectorised version of evaluate_basis_derivatives() is not yet implemented.")
85
 
 
86
 
        # Generate code for interpolate_vertex_values
87
 
        code["interpolate_vertex_values"] = __generate_interpolate_vertex_values(element, format)
88
 
    else:
89
 
        code["evaluate_basis"] =\
90
 
          format["exception"]("evaluate_basis() is not supported for QuadratureElement")
91
 
        code["evaluate_basis_derivatives"] =\
92
 
          format["exception"]("evaluate_basis_derivatives() is not supported for QuadratureElement")
93
 
 
94
 
        # Generate vectorised version of evaluate functions
95
 
        code["evaluate_basis_all"] =\
96
 
          format["exception"]("evaluate_basis_all() is not supported for QuadratureElement.")
97
 
        code["evaluate_basis_derivatives_all"] =\
98
 
          format["exception"]("evaluate_basis_derivatives_all() is not supported for QuadratureElement.")
99
 
 
100
 
        code["interpolate_vertex_values"] =\
101
 
          format["exception"]("interpolate_vertex_values() is not supported for QuadratureElement")
102
 
 
103
 
    # Generate code for evaluate_dof
104
 
    code["evaluate_dof"] = __generate_evaluate_dof(element, format)
105
 
 
106
 
    # Generate code for num_sub_elements
107
 
    code["num_sub_elements"] = "%d" % element.num_sub_elements()
108
 
 
109
 
    return code
110
 
 
111
 
def _extract_sub_elements(element, parent):
112
 
    """Recursively extract sub elements as a list of tuples where
113
 
    each tuple consists of a tuple labeling the sub element and
114
 
    the sub element itself."""
115
 
    
116
 
    if element.num_sub_elements() == 1:
117
 
        return [(parent, element)]
118
 
    sub_elements = []
119
 
    for i in range(element.num_sub_elements()):
120
 
        sub_elements += _extract_sub_elements(element.sub_element(i), parent + (i,))
121
 
    return sub_elements + [(parent, element)]
122
 
 
123
 
# FIXME: This is C++ dependent, move relevant parts to ufc_format.py
124
 
def __generate_evaluate_dof(element, format):
125
 
    "Generate code for evaluate_dof"
126
 
 
127
 
    # Generate code as a list of lines
128
 
    code = []
129
 
 
130
 
    # Get code formats
131
 
    block = format["block"]
132
 
    separator = format["separator"]
133
 
    floating_point = format["floating point"]
134
 
    comment = format["comment"]
135
 
 
136
 
    # Generate dof map and get _copy_ of dof representations. 
137
 
    dof_map = DofMap(element)
138
 
    dofs = [DofRepresentation(dof) for dof in dof_map.dual_basis()]
139
 
    num_dofs = len(dofs)
140
 
 
141
 
    # For ease in the code generalization, pad the points, directions
142
 
    # and weights with zeros according to the maximal number of
143
 
    # points. (Hence, the copy of the dofs above.)
144
 
    max_num_points = dof_map.get_max_num_of_points()
145
 
    num_points_per_dof = [dof.pad_points_and_weights(max_num_points)
146
 
                          for dof in dofs]
147
 
 
148
 
    # Compute the value dimension of the functions
149
 
    num_values = 1
150
 
    for i in range(element.value_rank()):
151
 
        num_values *= element.value_dimension(i)
152
 
    # Check that the value dimension is the same for all dofs and that
153
 
    # it matches the dimension of the function
154
 
    value_dim = pick_first([dof.value_dim() for dof in dofs])
155
 
    if value_dim != num_values:
156
 
        error("Directional component does not match vector dimension")
157
 
 
158
 
    # Initialize the points, weights and directions for the dofs:
159
 
    code += [comment("The reference points, direction and weights:")]
160
 
    s = block(separator.join(
161
 
        [block(separator.join([block(separator.join([floating_point(c)
162
 
                                                     for c in point]))
163
 
                               for point in dof.points]))
164
 
         for dof in dofs]))
165
 
    code  += ["%sX[%d][%d][%d] = %s;" % (format["table declaration"],
166
 
                                         num_dofs, max_num_points,
167
 
                                         element.cell_dimension(), s)]
168
 
    s = block(separator.join(
169
 
        [block(separator.join([floating_point(w) for w in dof.weights]))
170
 
         for dof in dofs]))
171
 
    code += ["%sW[%d][%d] = %s;" % (format["table declaration"],
172
 
                                    num_dofs, max_num_points, s)]
173
 
 
174
 
    s = block(separator.join(
175
 
        [block(separator.join([block(separator.join([floating_point(dk)
176
 
                                                    for dk in d]))
177
 
                               for d in dof.directions]))
178
 
         for dof in dofs]))
179
 
    code += ["%sD[%d][%d][%d] = %s;" % (format["table declaration"],
180
 
                                        num_dofs, max_num_points,
181
 
                                        num_values, s)]
182
 
    code += [""]
183
 
 
184
 
    # Compute the declarations needed for function mapping and the
185
 
    # code for mapping each set of function values:
186
 
    (map_declarations, map_values_code) = \
187
 
                       __map_function_values(num_values, element, format)
188
 
    code += [map_declarations]
189
 
 
190
 
    # Loop over the number of points (if more than one) and evaluate
191
 
    # the functional
192
 
    code += ["%sresult = 0.0;" % format["float declaration"]]
193
 
    code += [comment("Iterate over the points:") ]
194
 
    tab = 0
195
 
    endloop = ""
196
 
 
197
 
    # If there is more than one point, we need to add a table of the
198
 
    # number of points per dof, add the loop, add indentation and add
199
 
    # an end brackets
200
 
    index = "0"
201
 
    if max_num_points > 1:
202
 
        num_points_per_dof_code = block(separator.join([str(n) for n in num_points_per_dof]))
203
 
        code += ["%sns[%d] = %s;" % (format["static const uint declaration"],
204
 
                                      num_dofs, num_points_per_dof_code)]
205
 
        code += ["%s%s" % (format["loop"]("j", "0", "ns[i]"), " {")]  
206
 
        (tab, endloop, index) = (2, "\n} // End for", "j")
207
 
 
208
 
    # Map the points from the reference onto the physical element
209
 
    code += [indent(format["snippet map_onto_physical"](element.cell_dimension())
210
 
                    % {"j": index}, tab)]
211
 
    
212
 
    # Evaluate the function at the physical points
213
 
    code += [indent(comment("Evaluate function at physical points"), tab)]
214
 
    code += [indent("double values[%d];" % num_values, tab)]
215
 
    code += [indent("f.evaluate(values, y, c);\n", tab)]
216
 
    
217
 
    # Map the function values according to the given mapping(s)
218
 
    code += [indent(comment("Map function values using appropriate mapping"),
219
 
                    tab)]
220
 
    code += [indent(map_values_code, tab)]
221
 
    code += [""]
222
 
 
223
 
    # Note that we do not map the weights (yet).
224
 
    code += [indent(comment("Note that we do not map the weights (yet)."),tab)]
225
 
    code += [""]
226
 
 
227
 
    # Take the directional components of the function values and
228
 
    # multiply by the weights:
229
 
    code += [indent(format["snippet calculate dof"] % {"dim": value_dim,
230
 
                                                       "index": index}, tab)]
231
 
    # End possible loop 
232
 
    code += [endloop]
233
 
    
234
 
    # Return the calculated value
235
 
    code += [format["return"]("result")]
236
 
    return code
237
 
 
238
 
# FIXME: This is C++ dependent, move relevant parts to ufc_format.py
239
 
def __map_function_values(num_values, element, format):
240
 
 
241
 
    block = format["block"]
242
 
    separator = format["separator"]
243
 
    comment = format["comment"]
244
 
    precode = []
245
 
    code = []
246
 
 
247
 
    # Compute the mapping associated with each dof:
248
 
    # Looping the extracted element should have the same effect as using the
249
 
    # old element.space_mapping() function.
250
 
    # mappings = [mapping_to_int[element.space_mapping(i)]
251
 
    #             for i in range(element.space_dimension())]
252
 
    mappings = []
253
 
    for e in element.extract_elements():
254
 
        for d in range(e.space_dimension()):
255
 
            mappings.append(mapping_to_int[e.mapping()])
256
 
 
257
 
    whichmappings = set(mappings)
258
 
 
259
 
    # If there is more than one mapping involved, we will need to
260
 
    # keep track of them at runtime:
261
 
    if len(whichmappings) > 1:
262
 
        precode += ["%smappings[%d] = %s;" %
263
 
                    (format["static const uint declaration"], len(mappings),
264
 
                     block(separator.join(([str(m) for m in mappings]))))]
265
 
        
266
 
    # Check whether we will need a piola
267
 
    contrapiola_present = mapping_to_int[CONTRAVARIANT_PIOLA] in whichmappings
268
 
    copiola_present = mapping_to_int[COVARIANT_PIOLA] in whichmappings
269
 
    piola_present = contrapiola_present or copiola_present
270
 
 
271
 
    # Add code for the jacobian if we need it for the
272
 
    # mappings. Otherwise, just add code for the vertex coordinates
273
 
    if contrapiola_present:
274
 
        # If contravariant piola: Will need J, det J and J^{-1}
275
 
        precode += [format["snippet jacobian"](element.cell_dimension())
276
 
                 % {"restriction":""}]
277
 
        precode += ["\ndouble copyofvalues[%d];" % num_values]
278
 
    elif copiola_present:
279
 
        # If covariant piola: Will need J only
280
 
        precode += [format["snippet only jacobian"](element.cell_dimension())
281
 
                 % {"restriction":""}]
282
 
        precode += ["\ndouble copyofvalues[%d];" % num_values]
283
 
    else:
284
 
        precode += [format["get cell vertices"]]
285
 
    
286
 
    # We have to add offsets to the code if there are mixed
287
 
    # piola-mapped elements with an offset. (Ex: DG0 + RT)
288
 
    offset = ""
289
 
    if element.num_sub_elements() > 1 and piola_present:
290
 
        value_offsets = []
291
 
        adjustment = 0
292
 
        for i in range(element.num_sub_elements()):
293
 
            subelement = element.sub_element(i)
294
 
            value_offsets += [adjustment]*subelement.space_dimension()
295
 
            adjustment += subelement.value_dimension(0)
296
 
 
297
 
        # if mapping[i] != AFFINE == 0 and value_offset[i] !=
298
 
        # 0 for all i, then need_offsets = True:
299
 
        need_offsets = bool(max([mappings[i] and value_offsets[i]
300
 
                                 for i in range(len(mappings))]))
301
 
        if need_offsets:
302
 
            precode += ["const int offsets[%d] = %s;" %
303
 
                        (len(value_offsets),
304
 
                         block(separator.join([str(o) for o in value_offsets])))]
305
 
            offset = "offsets[i] + "
306
 
 
307
 
 
308
 
    # Then it just remains to actually add the different mappings to the code:
309
 
    n = element.cell_dimension()
310
 
    mappings_code = {mapping_to_int[AFFINE]: __affine_map(),
311
 
                     mapping_to_int[CONTRAVARIANT_PIOLA]:
312
 
                     __contravariant_piola(n, offset),
313
 
                     mapping_to_int[COVARIANT_PIOLA]: __covariant_piola(n, offset)}
314
 
 
315
 
    ifs = ["mappings[i] == %d" % mapping for mapping in whichmappings]
316
 
    cases = [mappings_code[mapping] for mapping in whichmappings]
317
 
    code  += [__generate_if_block(ifs, cases,
318
 
                                  comment("Other mappings not applicable."))]
319
 
    return ("\n".join(precode), "\n".join(code))
320
 
 
321
 
# FIXME: This is C++ dependent, move to ufc_format.py
322
 
def __affine_map():
323
 
    return "// Affine map: Do nothing"
324
 
 
325
 
# FIXME: This is C++ dependent, move to ufc_format.py
326
 
def __contravariant_piola(dim, offset=""):
327
 
    code = []
328
 
    code += ["// Copy old values:"]
329
 
    for i in range(dim):
330
 
        code += ["copyofvalues[%s%d] = values[%s%d];" % (offset, i, offset, i)]
331
 
    code += ["// Do the inverse of div piola "]
332
 
    for i in range(dim):
333
 
        terms = ["Jinv_%d%d*copyofvalues[%s%d]" % (i, j, offset, j)
334
 
                 for j in range(dim)]
335
 
        code += ["values[%s%d] = detJ*(%s);" % (offset, i, "+".join(terms))]
336
 
    return "\n".join(code)
337
 
 
338
 
# FIXME: This is C++ dependent, move to ufc_format.py
339
 
def __covariant_piola(dim, offset=""):
340
 
    code = []
341
 
    code += ["// Copy old values:"]
342
 
    for i in range(dim):
343
 
        code += ["copyofvalues[%s%d] = values[%s%d];" % (offset, i, offset, i)]
344
 
    code += ["// Do the inverse of curl piola "]
345
 
    for i in range(dim):
346
 
        terms = ["J_%d%d*copyofvalues[%s%d]" % (j, i, offset, j)
347
 
                 for j in range(dim)]
348
 
        code += ["values[%s%d] = %s;" % (offset, i, "+".join(terms))]
349
 
    return  "\n".join(code)
350
 
 
351
 
# FIXME: This is C++ dependent, move to ufc_format.py
352
 
def __generate_if_block(ifs, cases, default = ""):
353
 
    "Generate if block from given ifs and cases"
354
 
    if len(ifs) != len(cases):
355
 
        error("Mismatch of dimensions ifs and cases")
356
 
 
357
 
    # Special case: no cases
358
 
    if len(ifs) == 0:
359
 
        return default
360
 
 
361
 
    # Special case: one case
362
 
    if len(ifs) == 1:
363
 
        return cases[0]
364
 
 
365
 
    # Create ifs
366
 
    code = "if (%s) { \n" % ifs[0]
367
 
    code += "%s\n" % indent(cases[0], 2)
368
 
    for i in range(len(ifs)-1):
369
 
        code += "} else if (%s) {\n %s \n" % (ifs[i+1], indent(cases[i+1], 2))
370
 
    code += "} else { \n %s \n}" % indent(default,2)
371
 
    return code
372
 
 
373
 
def __generate_interpolate_vertex_values(element, format):
374
 
    "Generate code for interpolate_vertex_values"
375
 
 
376
 
    # Check that we have a scalar- or vector-valued element
377
 
    if element.value_rank() > 1:
378
 
        return format["exception"]("interpolate_vertex_values not implemented for this type of element")
379
 
 
380
 
    # Generate code as a list of declarations
381
 
    code = []
382
 
 
383
 
    # Set vertices (note that we need to use the FIAT reference cells)
384
 
    if element.cell_shape() == LINE:
385
 
        vertices = [(0,), (1,)]
386
 
    elif element.cell_shape() == TRIANGLE:
387
 
        vertices = [(0, 0), (1, 0), (0, 1)]
388
 
    elif element.cell_shape() == TETRAHEDRON:
389
 
        vertices =  [(0, 0, 0), (1, 0, 0), (0, 1, 0), (0, 0, 1)]
390
 
 
391
 
    # Extract nested sub elements
392
 
    sub_elements = element.extract_elements()
393
 
 
394
 
    # Iterate over sub elements
395
 
    offset_dof_values = 0
396
 
    offset_vertex_values = 0
397
 
    need_jacobian = False
398
 
 
399
 
    # Compute data size
400
 
    size = 0
401
 
    for sub_element in sub_elements:
402
 
        size += sub_element.value_dimension(0)
403
 
 
404
 
    for sub_element in sub_elements:
405
 
 
406
 
        # Tabulate basis functions at vertices
407
 
        table = sub_element.tabulate(0, vertices)
408
 
 
409
 
        # Check which transform we should use to map the basis functions
410
 
        mapping = sub_element.mapping()
411
 
 
412
 
        # Generate different code depending on mapping
413
 
        if mapping == AFFINE:
414
 
 
415
 
            code += [format["comment"]("Evaluate at vertices and use affine mapping")]
416
 
 
417
 
            # Handle scalars and vectors
418
 
            if sub_element.value_rank() == 0:
419
 
                for v in range(len(vertices)):
420
 
                    coefficients = table[0][sub_element.cell_dimension()*(0,)][:, v]
421
 
                    dof_values = [format["dof values"](offset_dof_values + n) for n in range(len(coefficients))]
422
 
                    name = format["vertex values"](size*v + offset_vertex_values)
423
 
                    value = inner_product(coefficients, dof_values, format)
424
 
                    code += [(name, value)]
425
 
            else:
426
 
                for dim in range(sub_element.value_dimension(0)):
427
 
                    for v in range(len(vertices)):
428
 
                        coefficients = table[dim][0][sub_element.cell_dimension()*(0,)][:, v]
429
 
                        dof_values = [format["dof values"](offset_dof_values + n) for n in range(len(coefficients))]
430
 
                        name = format["vertex values"](size*v + offset_vertex_values + dim)
431
 
                        value = inner_product(coefficients, dof_values, format)
432
 
                        code += [(name, value)]
433
 
 
434
 
        elif (mapping == CONTRAVARIANT_PIOLA or mapping == COVARIANT_PIOLA):
435
 
 
436
 
            code += [format["comment"]("Evaluate at vertices and use Piola mapping")]
437
 
 
438
 
            # Remember to add code later for Jacobian
439
 
            need_jacobian = True
440
 
 
441
 
            # Check that dimension matches for Piola transform
442
 
            if not sub_element.value_dimension(0) == sub_element.cell_dimension():
443
 
                error("Vector dimension of basis function does not match for Piola transform.")
444
 
 
445
 
            # Get entities for the dofs
446
 
            dof_entities = DofMap(sub_element).dof_entities()
447
 
 
448
 
            for dim in range(sub_element.value_dimension(0)):
449
 
                for v in range(len(vertices)):
450
 
                    terms = []
451
 
                    for n in range(sub_element.space_dimension()):
452
 
                        # Get basis function values at vertices
453
 
                        coefficients = [table[j][0][sub_element.cell_dimension()*(0,)][n, v] for j in range(sub_element.value_dimension(0))]
454
 
 
455
 
                        if mapping == COVARIANT_PIOLA:
456
 
                            # Get row of inverse transpose Jacobian
457
 
                            jacobian_row = [format["transform"]("JINV", j, dim, None) for j in range(sub_element.cell_dimension())]
458
 
                        else:
459
 
                            # mapping == CONTRAVARIANT_PIOLA:
460
 
                            # Get row of Jacobian
461
 
                            jacobian_row = [format["transform"]("J", j, dim, None) for j in range(sub_element.cell_dimension())]
462
 
                            
463
 
                        # Multiply vector-valued basis function with Jacobian
464
 
                        basis_function = inner_product(coefficients, jacobian_row, format)
465
 
                        # Add paranthesis if necessary
466
 
                        if "+" in basis_function or "-" in basis_function: # Cheating, should use dictionary
467
 
                            basis_function = format["grouping"](basis_function)
468
 
                            # Multiply with dof value
469
 
                        factors = [format["dof values"](offset_dof_values + n), basis_function]
470
 
                        # Add term
471
 
                        if not basis_function == format["floating point"](0):
472
 
                            terms += [format["multiply"](factors)]
473
 
                    if len(terms) > 1:
474
 
                        sum = format["grouping"](format["add"](terms))
475
 
                    else:
476
 
                        sum = format["add"](terms)
477
 
                    name = format["vertex values"](size*v + offset_vertex_values + dim)
478
 
                    if mapping == CONTRAVARIANT_PIOLA:
479
 
                        value = format["multiply"]([format["inverse"](format["determinant"](None)), sum])
480
 
                    else: 
481
 
                        value = format["multiply"]([sum])
482
 
                    code += [(name, value)]
483
 
 
484
 
        else:
485
 
            error("Unknown mapping: " + str(mapping))
486
 
 
487
 
        offset_dof_values    += sub_element.space_dimension()
488
 
        offset_vertex_values += sub_element.value_dimension(0)
489
 
 
490
 
    # Insert code for computing quantities needed for Piola mapping
491
 
    if need_jacobian:
492
 
        code.insert(0, format["snippet jacobian"](element.cell_dimension()) % {"restriction": ""})        
493
 
    
494
 
    return code