# -*- coding: utf-8 -*-
# Copyright (C) 2005-2016 Anders Logg
#
# This file is part of FFC.
#
# FFC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FFC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FFC. If not, see <http://www.gnu.org/licenses/>.
#
# Modified by Garth N. Wells, 2006-2009
# Modified by Marie E. Rognes, 2007-2010
# Modified by Kristian B. Oelgaard, 2010
# Modified by Lizao Li, 2015
# Python modules
import numpy
# FFC modules
from ffc.log import error
# UFL utils
from ufl.utils.sequences import product
[docs]class MixedElement:
"Create a FFC mixed element from a list of FFC/FIAT elements."
def __init__(self, elements):
self._elements = elements
self._entity_dofs = _combine_entity_dofs(self._elements)
[docs] def elements(self):
return self._elements
[docs] def space_dimension(self):
return sum(e.space_dimension() for e in self._elements)
[docs] def value_shape(self):
# Values of Tensor elements are flattened in MixedElements
num_comps = lambda x: numpy.prod(x) if x else 1
return (sum(num_comps(e.value_shape()) or 1 for e in self._elements),)
[docs] def entity_dofs(self):
return self._entity_dofs
[docs] def mapping(self):
return [m for e in self._elements for m in e.mapping()]
[docs] def dual_basis(self):
return [L for e in self._elements for L in e.dual_basis()]
[docs] def num_components(self):
return sum(_num_components(e) for e in self._elements)
[docs] def tabulate(self, order, points):
"""
Tabulate values on mixed element by appropriately reordering
the tabulated values for the nested elements.
The table of values is organized as follows:
D^a v_i[j](x_k) = table[a][i][j][k]
where a is a multi-index (tuple) representing the derivative.
For example, a = (1, 1) represents d/dx d/dy.
"""
# Special case: only one element
# NOTE: KBO: Why do we need this special case? (FFC Bug #798578)
# When calling tabulate() on a MixedElement one should be able to
# rely on getting data back which is ordered like a mixed element
# irrespective of the number of elements?
# if len(self._elements) == 1:
# return self._elements[0].tabulate(order, points)
# Zeros for insertion into mixed table
table_shape = (self.space_dimension(), self.num_components(), len(points))
# Iterate over elements and fill in non-zero values
irange = (0, 0)
crange = (0, 0)
mixed_table = {}
for element in self._elements:
# Tabulate element
table = element.tabulate(order, points)
# Compute range for insertion into mixed table
irange = (irange[1], irange[1] + element.space_dimension())
crange = (crange[1], crange[1] + _num_components(element))
# Insert table into mixed table
for dtuple in table.keys():
# Insert zeros if necessary (should only happen first time)
if dtuple not in mixed_table:
# NOTE: It is super important to create a new numpy.zeros
# instance to avoid manipulating a numpy reference in case
# it is created outside the loop.
mixed_table[dtuple] = numpy.zeros(table_shape)
# Insert non-zero values
if (crange[1] - crange[0]) > 1:
mixed_table[dtuple][irange[0]:irange[1], crange[0]:crange[1]] = numpy.reshape(table[dtuple], (irange[1] - irange[0], crange[1] - crange[0], len(points)))
else:
mixed_table[dtuple][irange[0]:irange[1], crange[0]] = table[dtuple]
return mixed_table
#--- Utility functions ---
def _combine_entity_dofs(elements):
"""
Combine the entity_dofs from a list of elements into a combined
entity_dof containing the information for all the elements.
"""
# Return {} if no elements
if not elements:
return {}
# Initialize entity_dofs dictionary
entity_dofs = dict((key, {}) for key in elements[0].entity_dofs())
for dim in elements[0].entity_dofs():
for entity in elements[0].entity_dofs()[dim]:
entity_dofs[dim][entity] = []
offset = 0
# Insert dofs from each element into the mixed entity_dof.
for e in elements:
dofs = e.entity_dofs()
for dim in dofs:
for entity in dofs[dim]:
# Must take offset into account
shifted_dofs = [v + offset for v in dofs[dim][entity]]
# Insert dofs from this element into the entity_dofs
entity_dofs[dim][entity] += shifted_dofs
# Adjust offset
offset += e.space_dimension()
return entity_dofs
def _num_components(element):
"Compute number of components for element."
return product(element.value_shape())