PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/ufl/algorithms/apply_function_pullbacks.py

https://bitbucket.org/chaffra/ufl
Python | 266 lines | 157 code | 40 blank | 69 comment | 43 complexity | 58e08f7fd53ca3a3a19549d4ca592925 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. """Algorithm for replacing gradients in an expression with reference gradients and coordinate mappings."""
  3. # Copyright (C) 2008-2016 Martin Sandve Alnæs
  4. #
  5. # This file is part of UFL.
  6. #
  7. # UFL is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU Lesser General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # UFL is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with UFL. If not, see <http://www.gnu.org/licenses/>.
  19. #
  20. # Modified by Lizao Li <lzlarryli@gmail.com>, 2016
  21. from six.moves import xrange as range
  22. from ufl.log import error
  23. from ufl.core.multiindex import indices
  24. from ufl.corealg.multifunction import MultiFunction, memoized_handler
  25. from ufl.algorithms.map_integrands import map_integrand_dags
  26. from ufl.classes import (ReferenceValue,
  27. Jacobian, JacobianInverse, JacobianDeterminant,
  28. Index)
  29. from ufl.tensors import as_tensor, as_vector
  30. from ufl.utils.sequences import product
  31. def sub_elements_with_mappings(element):
  32. "Return an ordered list of the largest subelements that have a defined mapping."
  33. if element.mapping() != "undefined":
  34. return [element]
  35. elements = []
  36. for subelm in element.sub_elements():
  37. if subelm.mapping() != "undefined":
  38. elements.append(subelm)
  39. else:
  40. elements.extend(sub_elements_with_mappings(subelm))
  41. return elements
  42. def create_nested_lists(shape):
  43. if len(shape) == 0:
  44. return [None]
  45. elif len(shape) == 1:
  46. return [None]*shape[0]
  47. else:
  48. return [create_nested_lists(shape[1:]) for i in range(shape[0])]
  49. def reshape_to_nested_list(components, shape):
  50. if len(shape) == 0:
  51. assert len(components) == 1
  52. return [components[0]]
  53. elif len(shape) == 1:
  54. assert len(components) == shape[0]
  55. return components
  56. else:
  57. n = product(shape[1:])
  58. return [reshape_to_nested_list(components[n*i:n*(i+1)], shape[1:]) for i in range(shape[0])]
  59. def apply_single_function_pullbacks(g):
  60. element = g.ufl_element()
  61. mapping = element.mapping()
  62. r = ReferenceValue(g)
  63. gsh = g.ufl_shape
  64. rsh = r.ufl_shape
  65. # Shortcut the "identity" case which includes Expression and
  66. # Constant from dolfin that may be ill-formed without a domain
  67. # (until we get that fixed)
  68. if mapping == "identity":
  69. assert rsh == gsh
  70. return r
  71. gsize = product(gsh)
  72. rsize = product(rsh)
  73. # Create some geometric objects for reuse
  74. domain = g.ufl_domain()
  75. J = Jacobian(domain)
  76. detJ = JacobianDeterminant(domain)
  77. Jinv = JacobianInverse(domain)
  78. # Create contravariant transform for reuse (note that detJ is the
  79. # _signed_ (pseudo-)determinant)
  80. transform_hdiv = (1.0/detJ) * J
  81. # Shortcut simple cases for a more efficient representation,
  82. # including directly Piola-mapped elements and mixed elements of
  83. # any combination of affinely mapped elements without symmetries
  84. if mapping == "symmetries":
  85. fcm = element.flattened_sub_element_mapping()
  86. assert gsize >= rsize
  87. assert len(fcm) == gsize
  88. assert sorted(set(fcm)) == sorted(range(rsize))
  89. g_components = [r[fcm[i]] for i in range(gsize)]
  90. g_components = reshape_to_nested_list(g_components, gsh)
  91. f = as_tensor(g_components)
  92. assert f.ufl_shape == g.ufl_shape
  93. return f
  94. elif mapping == "contravariant Piola":
  95. assert transform_hdiv.ufl_shape == (gsize, rsize)
  96. i, j = indices(2)
  97. f = as_vector(transform_hdiv[i, j]*r[j], i)
  98. # f = as_tensor(transform_hdiv[i, j]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here?
  99. assert f.ufl_shape == g.ufl_shape
  100. return f
  101. elif mapping == "covariant Piola":
  102. assert Jinv.ufl_shape == (rsize, gsize)
  103. i, j = indices(2)
  104. f = as_vector(Jinv[j, i]*r[j], i)
  105. # f = as_tensor(Jinv[j, i]*r[k,j], (k,i)) # FIXME: Handle Vector(Piola) here?
  106. assert f.ufl_shape == g.ufl_shape
  107. return f
  108. elif mapping == "double covariant Piola":
  109. i, j, m, n = indices(4)
  110. f = as_tensor(Jinv[m, i]*r[m, n]*Jinv[n, j], (i, j))
  111. assert f.ufl_shape == g.ufl_shape
  112. return f
  113. elif mapping == "double contravariant Piola":
  114. i, j, m, n = indices(4)
  115. f = as_tensor((1.0/detJ)*(1.0/detJ)*J[i, m]*r[m, n]*J[j, n], (i, j))
  116. assert f.ufl_shape == g.ufl_shape
  117. return f
  118. # By placing components in a list and using as_vector at the end,
  119. # we're assuming below that both global function g and its
  120. # reference value r have vector shape, which is the case for most
  121. # elements with the exceptions:
  122. # - TensorElements
  123. # - All cases with scalar subelements and without symmetries
  124. # are covered by the shortcut above
  125. # (ONLY IF REFERENCE VALUE SHAPE PRESERVES TENSOR RANK)
  126. # - All cases with scalar subelements and without symmetries are
  127. # covered by the shortcut above
  128. # - VectorElements of vector-valued basic elements (FIXME)
  129. # - TensorElements with symmetries (FIXME)
  130. assert len(gsh) == 1
  131. assert len(rsh) == 1
  132. g_components = [None]*gsize
  133. gpos = 0
  134. rpos = 0
  135. for subelm in sub_elements_with_mappings(element):
  136. gm = product(subelm.value_shape())
  137. rm = product(subelm.reference_value_shape())
  138. mp = subelm.mapping()
  139. if mp == "identity":
  140. assert gm == rm
  141. for i in range(gm):
  142. g_components[gpos + i] = r[rpos + i]
  143. elif mp == "symmetries":
  144. """
  145. tensor_element.value_shape() == (2,2)
  146. tensor_element.reference_value_shape() == (3,)
  147. tensor_element.symmetry() == { (1,0): (0,1) }
  148. tensor_element.component_mapping() == { (0,0): 0, (0,1): 1, (1,0): 1, (1,1): 2 }
  149. tensor_element.flattened_component_mapping() == { 0: 0, 1: 1, 2: 1, 3: 2 }
  150. """
  151. fcm = subelm.flattened_sub_element_mapping()
  152. assert gm >= rm
  153. assert len(fcm) == gm
  154. assert sorted(set(fcm)) == sorted(range(rm))
  155. for i in range(gm):
  156. g_components[gpos + i] = r[rpos + fcm[i]]
  157. elif mp == "contravariant Piola":
  158. assert transform_hdiv.ufl_shape == (gm, rm)
  159. # Get reference value vector corresponding to this subelement:
  160. rv = as_vector([r[rpos+k] for k in range(rm)])
  161. # Apply transform with IndexSum over j for each row
  162. j = Index()
  163. for i in range(gm):
  164. g_components[gpos + i] = transform_hdiv[i, j]*rv[j]
  165. elif mp == "covariant Piola":
  166. assert Jinv.ufl_shape == (rm, gm)
  167. # Get reference value vector corresponding to this subelement:
  168. rv = as_vector([r[rpos+k] for k in range(rm)])
  169. # Apply transform with IndexSum over j for each row
  170. j = Index()
  171. for i in range(gm):
  172. g_components[gpos + i] = Jinv[j, i]*rv[j]
  173. elif mp == "double covariant Piola":
  174. # components are flatten, map accordingly
  175. rv = as_vector([r[rpos+k] for k in range(rm)])
  176. dim = subelm.value_shape()[0]
  177. for i in range(dim):
  178. for j in range(dim):
  179. gv = 0
  180. # int times Index is not allowed. so sum by hand
  181. for m in range(dim):
  182. for n in range(dim):
  183. gv += Jinv[m, i]*rv[m*dim+n]*Jinv[n, j]
  184. g_components[gpos + i * dim + j] = gv
  185. elif mp == "double contravariant Piola":
  186. # components are flatten, map accordingly
  187. rv = as_vector([r[rpos+k] for k in range(rm)])
  188. dim = subelm.value_shape()[0]
  189. for i in range(dim):
  190. for j in range(dim):
  191. gv = 0
  192. # int times Index is not allowed. so sum by hand
  193. for m in range(dim):
  194. for n in range(dim):
  195. gv += (1.0/detJ)*(1.0/detJ)*J[i, m]*rv[m*dim+n]*J[j, n]
  196. g_components[gpos + i * dim + j] = gv
  197. else:
  198. error("Unknown subelement mapping type %s for element %s." % (mp, str(subelm)))
  199. gpos += gm
  200. rpos += rm
  201. # Wrap up components in a vector, must return same shape as input
  202. # function g
  203. assert len(gsh) == 1
  204. f = as_vector(g_components)
  205. assert f.ufl_shape == g.ufl_shape
  206. return f
  207. class FunctionPullbackApplier(MultiFunction):
  208. def __init__(self):
  209. MultiFunction.__init__(self)
  210. expr = MultiFunction.reuse_if_untouched
  211. def terminal(self, t):
  212. return t
  213. @memoized_handler
  214. def form_argument(self, o):
  215. # Represent 0-derivatives of form arguments on reference
  216. # element
  217. return apply_single_function_pullbacks(o)
  218. def apply_function_pullbacks(expr):
  219. """Change representation of coefficients and arguments in expression
  220. by applying Piola mappings where applicable and representing all
  221. form arguments in reference value.
  222. @param expr:
  223. An Expr.
  224. """
  225. return map_integrand_dags(FunctionPullbackApplier(), expr)