...
 
Commits (104)
......@@ -175,7 +175,7 @@ function(dune_add_formcompiler_system_test)
_add_test(NAME ${tname}
COMMAND ${CMAKE_BINARY_DIR}/run-in-dune-env ${SYSTEMTEST_SCRIPT}
--exec ${tname}
--exec "$<TARGET_FILE_DIR:${tname}>/$<TARGET_FILE_NAME:${tname}>"
--ini "${CMAKE_CURRENT_BINARY_DIR}/${inifile}"
--source ${CMAKE_CURRENT_SOURCE_DIR}
--mpi-exec "${MPIEXEC}"
......
......@@ -3,6 +3,7 @@
pushd python/loopy
git apply ../../patches/loopy/Current.patch
git apply ../../patches/loopy/0001-Disable-a-logging-statement-that-breaks.patch
git apply ../../patches/loopy/0001-fix-parameter.patch
popd
pushd python/ufl
......
From 88db18150dc45225065fcef9794af9ba5fbbbfd8 Mon Sep 17 00:00:00 2001
From: Sebastian Hegmann <shegmann@nina.iwr.uni-heidelberg.de>
Date: Thu, 1 Aug 2019 11:35:14 +0200
Subject: [PATCH] fix parameter
---
loopy/symbolic.py | 34 +++++++++++++++++-----------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/loopy/symbolic.py b/loopy/symbolic.py
index 0cc8f4ba..bd9879bd 100644
--- a/loopy/symbolic.py
+++ b/loopy/symbolic.py
@@ -127,37 +127,37 @@ class PartialEvaluationMapper(
class WalkMapper(WalkMapperBase):
- def map_literal(self, expr, *args):
- self.visit(expr)
+ def map_literal(self, expr, *args, **kwargs):
+ self.visit(expr, *args, **kwargs)
- def map_array_literal(self, expr, *args):
- if not self.visit(expr):
+ def map_array_literal(self, expr, *args, **kwargs):
+ if not self.visit(expr, *args, **kwargs):
return
for ch in expr.children:
- self.rec(ch, *args)
+ self.rec(ch, *args, **kwargs)
- def map_group_hw_index(self, expr, *args):
- self.visit(expr)
+ def map_group_hw_index(self, expr, *args, **kwargs):
+ self.visit(expr, *args, **kwargs)
- def map_local_hw_index(self, expr, *args):
- self.visit(expr)
+ def map_local_hw_index(self, expr, *args, **kwargs):
+ self.visit(expr, *args, **kwargs)
- def map_reduction(self, expr, *args):
- if not self.visit(expr):
+ def map_reduction(self, expr, *args, **kwargs):
+ if not self.visit(expr, *args, **kwargs):
return
- self.rec(expr.expr, *args)
+ self.rec(expr.expr, *args, **kwargs)
- def map_type_cast(self, expr, *args):
- if not self.visit(expr):
+ def map_type_cast(self, expr, *args, **kwargs):
+ if not self.visit(expr, *args, **kwargs):
return
- self.rec(expr.child, *args)
+ self.rec(expr.child, *args, **kwargs)
map_tagged_variable = WalkMapperBase.map_variable
- def map_loopy_function_identifier(self, expr, *args):
- self.visit(expr)
+ def map_loopy_function_identifier(self, expr, *args, **kwargs):
+ self.visit(expr, *args, **kwargs)
map_linear_subscript = WalkMapperBase.map_subscript
--
2.14.3
Subproject commit f411383630b272a3a5d3e28b82acaaa530a64723
Subproject commit ae1da38a47dd1f0cc256ffc8949d2b8bd51f1f1b
from dune.codegen.blockstructured.basis import name_localbasis
from dune.codegen.pdelab.driver import isDG
from dune.codegen.loopy.target import type_floatingpoint
from dune.codegen.pdelab.geometry import world_dimension
from dune.codegen.pdelab.spaces import type_leaf_gfs, name_leaf_lfs, name_lfs_bound, initialize_function_spaces
from dune.codegen.generation import basis_mixin, class_member, include_file, kernel_cached, temporary_variable, \
instruction, domain, generator_factory
from dune.codegen.pdelab.basis import GenericBasisMixin, declare_cache_temporary, name_localbasis_cache
from dune.codegen.tools import get_pymbolic_basename
from loopy.match import Writes
from dune.codegen.pdelab.spaces import type_leaf_gfs, name_leaf_lfs, initialize_function_spaces
from dune.codegen.generation import basis_mixin, class_member, include_file, domain, generator_factory
from dune.codegen.pdelab.basis import GenericBasisMixin
import pymbolic.primitives as prim
@basis_mixin("extruded")
......@@ -24,42 +21,8 @@ class ExtrudedBasisMixin(GenericBasisMixin):
def lfs_inames(self, element, restriction, number, context=""):
return (lfs_iname(element, restriction, number, context=context),)
# only need this to define the local basis explicitly
@kernel_cached
def evaluate_basis(self, element, name, restriction):
lfs = name_leaf_lfs(element, restriction)
temporary_variable(name,
shape=(name_lfs_bound(lfs), 1),
decl_method=declare_cache_temporary(element, restriction, 'Function'),
)
cache = name_localbasis_cache(element)
qp = self.to_cell(self.quadrature_position())
localbasis = name_localbasis(element)
instruction(inames=self.quadrature_inames(),
code='{} = {}.evaluateFunction({}, {});'.format(name, cache, str(qp), localbasis,),
assignees=frozenset({name}),
read_variables=frozenset({get_pymbolic_basename(qp)}),
depends_on=frozenset({Writes(get_pymbolic_basename(qp))})
)
# only need this to define the local basis explicitly
@kernel_cached
def evaluate_reference_gradient(self, element, name, restriction):
lfs = name_leaf_lfs(element, restriction)
temporary_variable(name,
shape=(name_lfs_bound(lfs), 1, world_dimension()),
decl_method=declare_cache_temporary(element, restriction, 'Jacobian'),
)
cache = name_localbasis_cache(element)
qp = self.to_cell(self.quadrature_position())
localbasis = name_localbasis(element)
instruction(inames=self.quadrature_inames(),
code='{} = {}.evaluateJacobian({}, {});'.format(name, cache, str(qp), localbasis,),
assignees=frozenset({name}),
read_variables=frozenset({get_pymbolic_basename(qp)}),
depends_on=frozenset({Writes(get_pymbolic_basename(qp))})
)
def flatten_basis_index(self, index, element):
return prim.Variable(index[0])
@generator_factory(item_tags=("iname",), cache_key_generator=lambda e, r, c: (e, c), context_tags=("kernel",))
......
from dune.codegen.blockstructured.geometry import name_element_corners
from dune.codegen.extruded.quadrature import level_iname
from dune.codegen.options import get_form_option
from dune.codegen.ufl.modified_terminals import Restriction
from loopy.match import Writes, Id, Or, Tagged
from loopy.match import Writes, Id, Or
from dune.codegen.pdelab.quadrature import quadrature_preamble
......@@ -9,26 +11,167 @@ from dune.codegen.pdelab.restriction import restricted_name
from dune.codegen.generation import geometry_mixin, temporary_variable, instruction, domain, \
get_counted_variable, kernel_cached, preamble, get_global_context_value, cached
from dune.codegen.pdelab.geometry import GenericPDELabGeometryMixin, enforce_boundary_restriction, world_dimension, \
name_in_cell_geometry, local_dimension, declare_normal, name_intersection_geometry_wrapper
name_in_cell_geometry, local_dimension, declare_normal, name_intersection_geometry_wrapper, SymbolicGeometryMixin
import pymbolic.primitives as prim
import numpy as np
from dune.codegen.tools import get_pymbolic_basename
from dune.codegen.tools import get_pymbolic_basename, maybe_wrap_subscript
@kernel_cached
def copy_base(src, dst, bound, additional_inames):
iname = get_counted_variable("base_copy")
domain(iname, bound)
if isinstance(src, prim.Subscript):
expr = prim.Subscript(src.aggregate, src.index_tuple + (prim.Variable(iname),))
else:
expr = prim.Subscript(prim.Variable(src), (prim.Variable(iname),))
instruction(expression=expr,
assignee=prim.Subscript(prim.Variable(dst), (prim.Variable(iname),)),
within_inames=frozenset(additional_inames),
depends_on=frozenset({Writes(dst)}))
if isinstance(src, str):
src = prim.Variable(src)
if isinstance(dst, str):
dst = prim.Variable(dst)
src = maybe_wrap_subscript(src, prim.Variable(iname))
dst = maybe_wrap_subscript(dst, prim.Variable(iname))
instruction(expression=src, assignee=dst, within_inames=frozenset(additional_inames),
within_inames_is_final=True,
depends_on=frozenset({Writes(get_pymbolic_basename(src))}))
@geometry_mixin("extruded_symbolic")
class ExtrudedSymbolicGeometryMixin(SymbolicGeometryMixin):
@kernel_cached
def geometry_dofs(self):
vertices_name = name_element_corners()
macro_vertices = prim.Variable(vertices_name)
if self.measure == 'bottom_facet':
level = 0
elif self.measure == 'top_facet':
level = get_levels() - 1
else:
level = prim.Variable(level_iname())
lower_dofs = [[prim.Subscript(macro_vertices, (i, j)) for j in range(world_dimension() - 1)] +
[get_height() / get_levels() * level] for i in range(2)]
upper_dofs = [[prim.Subscript(macro_vertices, (i, j)) for j in range(world_dimension() - 1)] +
[get_height() / get_levels() * (level + 1)] for i in range(2)]
dofs = lower_dofs + upper_dofs
return dofs
def to_cell(self, local):
return local
@geometry_mixin("extruded_symbolic_horizontal_face")
class ExtrudedSymbolicHorizontalGeometryMixin(ExtrudedSymbolicGeometryMixin):
@kernel_cached
def _add_transformation(self):
from dune.codegen.extruded.transformations import add_horizontal_skeleton_predicate
from dune.codegen.generation.loopy import transform
transform(add_horizontal_skeleton_predicate, self.quadrature_inames())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._add_transformation()
def cell_facet_jacobian(self, o):
CFJ = np.eye(world_dimension(), world_dimension() - 1)
i, j = self.indices
self.indices = None
return CFJ[i, j]
def reference_normal(self, o):
n = [0.] * (world_dimension() - 1) + ([-1.] if self.measure != 'top_facet' else [1.])
i, = self.indices
self.indices = None
return n[i]
@kernel_cached
def _to_cell(self, local, restriction):
basename = get_pymbolic_basename(local)
name = "{}_in_{}side".format(basename, "in" if restriction is Restriction.POSITIVE else "out")
dim = world_dimension()
temporary_variable(name, shape=(dim,), shape_impl=('fv',))
# TODO: copy local instead of qp_base
qp_base = self.quadrature_position_base()
copy_base(qp_base, name, local_dimension(), self.quadrature_inames())
instruction(expression=0 if restriction == Restriction.POSITIVE else 1,
assignee=prim.Subscript(prim.Variable(name), (world_dimension() - 1)),
within_inames=frozenset(self.quadrature_inames()))
return prim.Variable(name)
def to_cell(self, local):
restriction = enforce_boundary_restriction(self)
return self._to_cell(local, restriction)
@geometry_mixin("extruded_symbolic_horizontal_boundary")
class ExtrudedSymbolicHorizontalBoundaryGeometryMixin(ExtrudedSymbolicHorizontalGeometryMixin):
def _add_transformation(self):
pass
@geometry_mixin("extruded_symbolic_vertical_face")
class ExtrudedSymbolicVerticalGeometryMixin(ExtrudedSymbolicGeometryMixin):
def cell_facet_jacobian(self, o):
CFJ = np.array([[0], [1]])
i, j = self.indices
self.indices = None
return CFJ[i, j]
def reference_normal(self, o):
face_id = get_global_context_value('face_id')
if face_id is None:
n = np.array([-1, 0])
elif face_id == 0:
n = np.array([-1, 0])
elif face_id == 1:
n = np.array([1, 0])
else:
raise NotImplementedError()
i, = self.indices
self.indices = None
return n[i]
def to_cell(self, local):
restriction = enforce_boundary_restriction(self)
basename = get_pymbolic_basename(local)
name = "{}_in_{}side".format(basename, "in" if restriction is Restriction.POSITIVE else "out")
dim = world_dimension()
temporary_variable(name, shape=(dim,), shape_impl=('fv',))
name_base = name + "_base"
temporary_variable(name_base, shape=(dim - 1,), shape_impl=("fv",))
geo = name_in_cell_geometry(restriction)
qp_base = self.quadrature_position_base()
quadrature_preamble(self,
"{} = {}.global({});".format(name_base,
geo,
str(qp_base),
),
assignees=frozenset({name_base}),
read_variables=frozenset({get_pymbolic_basename(qp_base)}),
depends_on=frozenset({Writes(get_pymbolic_basename(qp_base))}),
)
copy_base(name_base, name, local_dimension(), self.quadrature_inames())
qp_interval = self.quadrature_position_interval(0)
instruction(expression=qp_interval,
assignee=prim.Subscript(prim.Variable(name), (dim - 1)),
depends_on=frozenset({Writes(basename)}),
within_inames=frozenset(self.quadrature_inames()))
return prim.Variable(name)
@geometry_mixin("extruded")
......
......@@ -30,6 +30,15 @@ def extruded_generate_kernels_per_integral(integrals):
with global_context(integral_type="cell"):
yield generate_volume_kernel_calls()
elif pdelab_kernel == "boundary":
possible_face_ids = [0, 1] # TODO: currently limited to extrusion of 1D grid
for face_id in possible_face_ids:
with global_context(kernel=kernel + "_face_id{}".format(face_id), face_id=face_id):
yield generate_kernel(integrals)
with global_context(integral_type="exterior_facet"):
yield generate_boundary_kernel_switch(possible_face_ids)
else:
yield generate_kernel(integrals)
......@@ -88,3 +97,25 @@ def generate_volume_kernel_calls():
"}"]
return ClassMember(signature + block)
def generate_boundary_kernel_switch(possible_face_ids):
# Extract the signature
signature = assembly_routine_signature()
args = ", ".join(tuple(a for c, a in assembly_routine_args()))
kernel = kernel_name()
# Construct the switch statement
block = ["{",
" size_t variant = ig.indexInInside();",
" switch(variant)",
" {"]
for face_id in possible_face_ids:
block.append(" case {}: {}({}); break;".format(face_id, kernel + "_face_id{}".format(face_id), args))
block.append(' default: DUNE_THROW(Dune::Exception, "Variation not implemented.");')
block.append(" }")
block.append("}")
return ClassMember(signature + block)
......@@ -36,7 +36,7 @@ def add_horizontal_skeleton_predicate(kernel, quadrature_inames):
def _add_level_predicate(kernel, ids, predicate):
new_insns = []
for insn in kernel.instructions:
if insn.id in ids:
if insn.id in ids & kernel.iname_to_insns()[level_iname()]:
new_insns.append(insn.copy(predicates=frozenset({predicate}) | insn.predicates))
else:
new_insns.append(insn)
......
......@@ -10,6 +10,7 @@ from dune.codegen.error import CodegenLoopyError
import loopy as lp
import numpy as np
from dune.codegen.loopy.symbolic import Assignment
iname = generator_factory(item_tags=("iname",), context_tags="kernel")
function_mangler = generator_factory(item_tags=("mangler",), context_tags="kernel")
......@@ -17,8 +18,8 @@ silenced_warning = generator_factory(item_tags=("silenced_warning",), no_deco=Tr
kernel_cached = generator_factory(item_tags=("default_cached",), context_tags="kernel")
class DuneGlobalArg(lp.GlobalArg):
allowed_extra_kwargs = lp.GlobalArg.allowed_extra_kwargs + ["managed"]
class DuneGlobalArg(lp.ArrayArg):
allowed_extra_kwargs = lp.ArrayArg.allowed_extra_kwargs + ["managed"]
@generator_factory(item_tags=("argument", "globalarg"),
......@@ -29,7 +30,12 @@ def globalarg(name, shape=lp.auto, managed=True, **kw):
shape = (shape,)
from dune.codegen.loopy.target import dtype_floatingpoint
dtype = kw.pop("dtype", dtype_floatingpoint())
return DuneGlobalArg(name, dtype=dtype, shape=shape, managed=managed, **kw)
return DuneGlobalArg(name,
dtype=dtype,
shape=shape,
managed=managed,
address_space=lp.AddressSpace.GLOBAL,
**kw)
@generator_factory(item_tags=("argument", "constantarg"),
......@@ -70,6 +76,9 @@ def domain(iname, shape):
iname = ",".join(iname)
if isinstance(shape, str):
valuearg(shape)
elif isinstance(shape, tuple):
assert len(shape) == 2
return "{{ [{0}] : {1}<={0}<{2} }}".format(iname, shape[0], shape[1])
return "{{ [{0}] : 0<={0}<{1} }}".format(iname, shape)
......@@ -112,7 +121,7 @@ def expr_instruction_impl(**kw):
if 'assignees' in kw:
from pymbolic.primitives import Variable
kw['assignees'] = frozenset(Variable(i) for i in kw['assignees'])
return lp.ExpressionInstruction(**kw)
return Assignment(**kw)
@generator_factory(item_tags=("instruction", "callinstruction"),
......
......@@ -3,7 +3,6 @@
Use this module to insert pymbolic nodes and the likes.
"""
from dune.codegen.error import CodegenError
from dune.codegen.sumfact.symbolic import SumfactKernel, VectorizedSumfactKernel
from pymbolic.mapper.substitutor import make_subst_func
import loopy as lp
......@@ -29,8 +28,8 @@ class FusedMultiplyAdd(prim.Expression):
def __getinitargs__(self):
return (self.mul_op1, self.mul_op2, self.add_op)
def stringifier(self):
return lp.symbolic.StringifyMapper
def make_stringifier(self, originating_stringifier=None):
return lp.symbolic.StringifyMapper()
mapper_method = intern("map_fused_multiply_add")
......@@ -139,3 +138,23 @@ def substitute(expr, replacemap):
monkey patches etc.
"""
return lp.symbolic.SubstitutionMapper(make_subst_func(replacemap))(expr)
def get_dependencies(expr):
class DependencyMapperFlattenSubscript(lp.symbolic.DependencyMapper):
def map_subscript(self, expr):
rec = self.combine([self.rec(expr.aggregate), self.rec(expr.index)])
iname_rec = self.combine([self.rec(i) for i in expr.index_tuple if isinstance(i, prim.Variable)])
return rec - iname_rec
dep_mapper = DependencyMapperFlattenSubscript()
return frozenset(dep.name for dep in dep_mapper(expr))
# This class assumes that no assignments writes to its indices
class Assignment(lp.Assignment):
def assignee_subscript_deps(self):
if isinstance(self.assignee, prim.Subscript):
from loopy.symbolic import get_dependencies
return (get_dependencies(self.assignee.aggregate),)
else:
return frozenset()
import string
import loopy as lp
import pymbolic as pmbl
from dune.codegen.tools import get_pymbolic_basename
from loopy.symbolic import WalkMapper, IdentityMapper
from pytools import UniqueNameGenerator
class CountTerms(WalkMapper):
"""
Iterates over all expressions and counts the individual terms.
These are stored in a map, with a corresponding counter variable.
Static terms are skipped because they are not to be replaced later.
"""
def __init__(self):
self.cache = {}
self.ids = {}
return WalkMapper.__init__(self)
def addToDict(self, expr, insnId):
if expr not in self.cache.keys():
self.cache[expr] = 1
else:
self.cache[expr] = self.cache[expr] + 1
self.ids[expr] = self.ids.get(expr, []) + [insnId]
def getDict(self):
return self.cache
def getIds(self):
return self.ids
def visit(self, expr, insnId):
self.addToDict(expr, insnId)
return True
def map_subscript(self, expr, insnId):
pass
def map_variable(self, expr, insnId):
pass
def map_constant(self, expr, insnId):
pass
def map_loopy_function_identifier(self, expr, insnId):
pass
def map_call(self, expr, insnId):
# This whole special case if very unfortunate, but it will eventually go away
# in the transition to kernel callables anyway.
from dune.codegen.pdelab.argument import PDELabAccumulationFunction, CoefficientAccess
from dune.codegen.loopy.vcl import VCLLoad, VCLStore
if isinstance(expr.function, PDELabAccumulationFunction):
self.rec(expr.parameters[-1], insnId)
elif isinstance(expr.function, (VCLLoad, VCLStore, CoefficientAccess)):
pass
else:
WalkMapper.map_call(self, expr, insnId)
class ReplaceMapper(IdentityMapper):
"""
Creates new variables for the instructions for all terms that occur more
than once. The new variables are returned, for replacing the original
ones.
"""
def __init__(self, terms, letterExpr):
self.terms = terms
self.letterExpr = letterExpr
def rec(self, expr):
if expr in self.terms.keys() and \
self.terms[expr] > 1:
return pmbl.primitives.Variable(self.letterExpr[expr])
return IdentityMapper.rec(self, expr)
class SubtreeChecker(WalkMapper):
def __init__(self, sub):
self.sub = sub
self.found = False
def __call__(self, expr):
self.rec(expr)
return self.found
def visit(self, expr):
if expr == self.sub:
self.found = True
return True
def is_subtree(sub, expr):
if sub is expr:
return False
return SubtreeChecker(sub)(expr)
def in_list(i, new_id):
if new_id.keys():
if i in new_id.keys():
return new_id[i]
return []
def cse(kernel):
"""
The function finds all terms in a kernel and replaces them with simple
placeholders. First the mapper function counts all the terms and saves them
in a map. After that the new instruction will be created and a copy of the
kernel is returned.
"""
if not kernel:
return kernel
insn = kernel.instructions
cinsns = [i for i in insn if isinstance(i, lp.CInstruction)]
mapper = CountTerms()
for i in insn:
if isinstance(i, (lp.Assignment, lp.CallInstruction)):
mapper(i.expression, i.id)
alphabet = string.ascii_lowercase
addInsn = []
new_id = {}
letterExpr = {}
count = 0
# returns the terms in the map that occur more than one time
cses = dict((e, n) for e, n in mapper.getDict().items() if n > 1)
# filter all subexpressions if they appear only in a subtree
fcses = dict((cse, count) for cse, count in cses.items()
if not any(tuple(is_subtree(cse, supexpr) and n == count for supexpr, n in cses.items())))
def writer_map_with_aliasing(kernel):
alias_map = kernel.get_temporary_to_base_storage_map()
from collections import defaultdict
invert_map = defaultdict(list)
for k, v in alias_map.items():
invert_map[v].append(k)
new_writer_map = defaultdict(set)
writer_map= kernel.writer_map()
for k, v in writer_map.items():
if k in alias_map:
aliases = invert_map[alias_map[k]]
for alias in aliases:
new_writer_map[alias] |= v
else:
new_writer_map[k] = v
return new_writer_map
writer_map = writer_map_with_aliasing(kernel)
pdelab_variables = set("r r_n r_s jac jac_s_s jac_s_n jac_n_n jac_n_s".split())
name_generator = UniqueNameGenerator(kernel.all_variable_names() | pdelab_variables)
for j, expr in enumerate(fcses.keys()):
# Create temporary variable
tempVarLetter = name_generator(alphabet[count % len(alphabet)])
tmpVar = kernel.temporary_variables
tmpVar[tempVarLetter] = lp.TemporaryVariable(tempVarLetter)
count += 1
curLetter = "precompute_{}".format(tempVarLetter)
for insn in mapper.getIds().get(expr, []):
new_id.setdefault(insn, [])
new_id[insn].append(curLetter)
from functools import reduce
inames = reduce(lambda x, y: x & y, [kernel.all_insn_inames()[i] for i in mapper.getIds().get(expr, [])])
predicates = reduce(lambda x, y: x & y,
[kernel.id_to_insn[i].predicates for i in mapper.getIds().get(expr, [])])
dep_map = lp.symbolic.DependencyMapper()
deps = set()
deps |= dep_map(expr)
for predicate in predicates:
deps |= dep_map(predicate)
deps = reduce(lambda x, y: x | y, [writer_map.get(get_pymbolic_basename(d), set()) for d in deps], set())
addInsn.append(lp.Assignment(pmbl.primitives.Variable(tempVarLetter),
expr,
within_inames=inames,
id=curLetter,
depends_on=frozenset(deps),
predicates=predicates))
letterExpr[expr] = tempVarLetter
if addInsn and kernel.instructions:
replace = ReplaceMapper(fcses, letterExpr)
insns = [i.copy(expression=replace(i.expression),
depends_on=i.depends_on.union(
frozenset(in_list(i.id, new_id))),
) for i in kernel.instructions
if isinstance(i, (lp.Assignment, lp.CallInstruction))]
kernel = kernel.copy(instructions=cinsns + insns + addInsn)
return kernel
return kernel.copy()
......@@ -206,7 +206,6 @@ def _vectorize_quadrature_loop(knl, inames, suffix):
tuple(prim.Subscript(prim.Variable(get_vector_view_name(quantity)),
(vector_indices.get(horizontal) + i, prim.Variable(vec_iname)))
for i in range(horizontal))),
depends_on=frozenset({'continue_stmt{}'.format(suffix)}),
within_inames=common_inames.union(frozenset({outer_iname, vec_iname})),
within_inames_is_final=True,
id="{}_rotate{}".format(quantity, suffix),
......@@ -268,12 +267,13 @@ def _vectorize_quadrature_loop(knl, inames, suffix):
(vector_indices.get(horizontal) + last_index, prim.Variable(vec_iname)),
),
substitute(insn.expression, replacemap),
depends_on=frozenset({"continue_stmt{}".format(suffix), lp.match.Tagged("sumfact_stage1")}),
depends_on=frozenset({lp.match.Tagged("sumfact_stage1")}),
depends_on_is_final=True,
within_inames=common_inames.union(frozenset({outer_iname, vec_iname})),
within_inames_is_final=True,
id=insn.id,
tags=frozenset({"vec_write{}".format(suffix), "sumfact_stage2"})
tags=frozenset({"vec_write{}".format(suffix), "sumfact_stage2"}),
no_sync_with=frozenset({(lp.match.Tagged("sumfact_stage2"), "any")}),
)
)
......
......@@ -84,14 +84,16 @@ def initialize_options():
# Validate global options
scheme_global = _load_scheme()
validator_global = CodegenOptionsValidator(scheme_global, require_all=True)
if not validator_global.validate(_global_options.__dict__):
opt_dict = _global_options.__dict__
if not validator_global.validate({k: v for k, v in opt_dict.items() if not k.startswith("_")}):
raise RuntimeError("Global options validation failed: {}".format(validator_global.errors))
# Validate form options
scheme_form = _load_scheme(form=True)
validator_form = CodegenOptionsValidator(scheme_form, require_all=True)
for form in [i.strip() for i in _global_options.operators.split(",")]:
if not validator_form.validate(_form_options[form].__dict__):
opt_dict = _form_options[form].__dict__
if not validator_form.validate({k: v for k, v in opt_dict.items() if not k.startswith("_")}):
raise RuntimeError("Form options validation failed: {}".format(validator_form.errors))
......@@ -308,12 +310,15 @@ def form_option_context(conditional=True, **opts):
# Backup old values and set to new ones
backup = {}
for k, v in opts.items():
backup[k] = get_form_option(k, form=form)
try:
backup[k] = get_form_option(k, form=form)
except AttributeError:
pass
set_form_option(k, v, form=form)
yield
# Restore old values
if conditional:
for k in opts.keys():
set_form_option(k, backup[k], form=form)
for k, v in backup.items():
set_form_option(k, v, form=form)
......@@ -6,6 +6,10 @@ adjoint:
type: boolean
default: False
helpstr: "Generate adjoint operator"
apply_cse:
type: boolean
default: False
helpstr: "Whether to apply a CSE algorithm"
basis_mixins:
type: string
default: "generic"
......@@ -220,4 +224,4 @@ vectorization_vertical:
type: string
default:
nullable: True
helpstr: "an explicit value for vertical vectorization read by the 'explicit' strategy"
\ No newline at end of file
helpstr: "an explicit value for vertical vectorization read by the 'explicit' strategy"
This diff is collapsed.
......@@ -16,7 +16,7 @@ from dune.codegen.pdelab.driver import (FEM_name_mangling,
isSimplical,
name_initree,
preprocess_leaf_data,
)
get_form)
from dune.codegen.pdelab.driver.driverblock import (name_driver_block,
type_driver_block,)
from dune.codegen.loopy.target import type_floatingpoint
......@@ -168,7 +168,14 @@ def typedef_fem(element, name):
# The blockstructured code branch has its own handling of finite element selection
if get_form_option("blockstructured"):
include_file("dune/codegen/blockstructured/blockstructuredqkfem.hh", filetag="driver")
degree = degree * get_form_option("number_of_blocks")
form = get_form()
meta_dicts = [i.metadata() for i in form.integrals()]
refinement = meta_dicts[0].get("refinement", None)
if refinement:
assert all([c == refinement.cells_per_dimension[0] for c in refinement.cells_per_dimension])
degree = degree * refinement.cells_per_dimension[0]
else:
degree = degree * get_form_option("number_of_blocks")
return "using {} = Dune::PDELab::BlockstructuredQkLocalFiniteElementMap<{}, {}, {}, {}>;" \
.format(name, gv, df, r, degree)
......
from dune.codegen.loopy.symbolic import substitute
from dune.codegen.ufl.modified_terminals import Restriction
from dune.codegen.pdelab.restriction import restricted_name
from dune.codegen.generation import (class_member,
......@@ -11,18 +12,16 @@ from dune.codegen.generation import (class_member,
preamble,
temporary_variable,
valuearg,
instruction
)
from dune.codegen.options import get_form_option
from dune.codegen.loopy.target import dtype_floatingpoint, type_floatingpoint
from dune.codegen.pdelab.quadrature import (quadrature_preamble,
)
from dune.codegen.tools import get_pymbolic_basename
from ufl.algorithms import MultiFunction
from pymbolic.primitives import Variable
from dune.codegen.tools import get_pymbolic_basename, maybe_wrap_subscript
from loopy.match import Writes
import numpy as np
import pymbolic.primitives as prim
from pytools import memoize
......@@ -55,6 +54,65 @@ class GeometryMixinBase(object):
raise NotImplementedError("Geometry Mixins should implement a to_cell mapping")
@geometry_mixin("symbolic")
class SymbolicGeometryMixin(GeometryMixinBase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
_basis = (1 - prim.Variable('x0'), prim.Variable('x0'))
_grad = ((-1,), (1,))
def substitute_dimension(dim):
return tuple(substitute(p, {'x0': prim.Variable('x{}'.format(dim))}) for p in _basis)
self.coordinate_grad = _grad
self.coordinate_basis = _basis
for i in range(1, world_dimension()):
self.coordinate_grad = tuple(tuple(gi * p_1d for gi in g) + (b * g_1d[0],)
for p_1d, g_1d in zip(substitute_dimension(i), _grad)
for g, b in zip(self.coordinate_grad, self.coordinate_basis))
self.coordinate_basis = tuple(p * p_1d for p_1d in substitute_dimension(i) for p in self.coordinate_basis)
def geometry_dofs(self):
raise NotImplementedError
@kernel_cached
def _init(self, name, basis, qp):
basis = [substitute(b, dict([(d, maybe_wrap_subscript(qp, i))
for i, d in enumerate(['x{}'.format(i) for i in range(world_dimension())])]))
for b in basis]
vertices = self.geometry_dofs()
insn_kwargs = dict(within_inames=frozenset(self.quadrature_inames()),
within_inames_is_final=True, # TODO: why is this needed?
depends_on=frozenset({Writes(get_pymbolic_basename(qp))}))
if self.reference_grad:
for i in range(world_dimension()):
for j in range(world_dimension()):
instruction(assignee=prim.Subscript(prim.Variable(name), (i, j)),
expression=sum(vertices[basis_index][i] * b[j] for basis_index, b in enumerate(basis)),
**insn_kwargs)
else:
for i in range(world_dimension()):
instruction(assignee=prim.Subscript(prim.Variable(name), (i,)),
expression=sum(vertices[basis_index][i] * b for basis_index, b in enumerate(basis)),
**insn_kwargs)
def spatial_coordinate(self, o):
basis = self.coordinate_grad if self.reference_grad else self.coordinate_basis
measure = self.measure
name = ("DX_grad" if self.reference_grad else "DX") + "_" + measure
shape = (world_dimension(), world_dimension(),) if self.reference_grad else (world_dimension(),)
temporary_variable(name, shape=shape, managed=True)
qp = self.to_cell(self.quadrature_position())
self._init(name, basis, qp)
return prim.Variable(name)
@geometry_mixin("generic")
class GenericPDELabGeometryMixin(GeometryMixinBase):
def spatial_coordinate(self, o):
......
This diff is collapsed.
......@@ -97,7 +97,10 @@ def define_matrix_inverse(name, name_inv, shape, visitor):
instruction(expression=exprs[i][j],
assignee=assignee[i][j],
within_inames=frozenset(visitor.quadrature_inames()),
depends_on=frozenset({Writes(name), Writes(det_inv)}))
depends_on=frozenset({Writes(name), Writes(det_inv)}),
tags=frozenset({"inversion_{}".format(name)}),
no_sync_with=frozenset({(lp.match.Tagged("inversion_{}".format(name)), "any")}),
)
def name_determinant(matrix, shape, visitor):
......
import dune.codegen.structured_refinement.accumulation
import dune.codegen.structured_refinement.argument
import dune.codegen.structured_refinement.basis
import dune.codegen.structured_refinement.geometry
import dune.codegen.structured_refinement.kernel_generation
import dune.codegen.structured_refinement.quadrature
import dune.codegen.structured_refinement.implementations.blockstructured_geometry
import dune.codegen.structured_refinement.implementations.extruded_geometry
\ No newline at end of file
from dune.codegen.structured_refinement.tools import name_accumulation_alias
from dune.codegen.generation import accumulation_mixin, instruction, get_global_context_value
from dune.codegen.loopy.symbolic import get_dependencies
from dune.codegen.loopy.target import dtype_floatingpoint
from dune.codegen.options import get_form_option
from dune.codegen.pdelab.geometry import world_dimension, name_intersection_geometry_wrapper
from dune.codegen.pdelab.localoperator import determine_accumulation_space, GenericAccumulationMixin, \
get_accumulation_info, PDELabAccumulationInfo
from dune.codegen.pdelab.argument import name_accumulation_variable
from dune.codegen.pdelab.localoperator import boundary_predicates
from dune.codegen.generation.loopy import function_mangler, temporary_variable
import loopy as lp
import pymbolic.primitives as prim
from dune.codegen.tools import get_pymbolic_basename
from dune.codegen.ufl.modified_terminals import Restriction
from loopy.match import Writes, Tagged
from meshstructure import HyperCubeRefinement
@accumulation_mixin("structured_refinement")
class StructuredRefinementAccumulationMixin(GenericAccumulationMixin):
def get_accumulation_info(self, expr):
restriction = self.restriction
if self.measure in ['exterior_facet', 'bottom_facet', 'top_facet']:
restriction = self.current_info[0].restriction
return get_accumulation_info(expr, restriction, self.indices, self)
def _list_infos(self, expr, number):
from dune.codegen.ufl.modified_terminals import extract_modified_arguments
ma = extract_modified_arguments(expr, argnumber=number)
if len(ma) == 0:
if number == 1:
yield None
return
element = ma[0].argexpr.ufl_element()
if self.measure == "bottom_facet":
restrictions = (Restriction.POSITIVE,)
elif self.measure == "top_facet":
restrictions = (Restriction.NEGATIVE,)
elif self.measure == "cell":
restrictions = (Restriction.NONE,)
elif self.measure == "exterior_facet":
restrictions = (Restriction.POSITIVE,)
elif self.measure == "interior_facet":
restrictions = (Restriction.POSITIVE, Restriction.NEGATIVE)
else:
raise NotImplementedError
for res in restrictions:
for ei in range(element.value_size()):
yield PDELabAccumulationInfo(element_index=ei, restriction=res)
def list_accumulation_infos(self, expr):
testgen = self._list_infos(expr, 0)
trialgen = self._list_infos(expr, 1)
import itertools
return itertools.product(testgen, trialgen)
def generate_accumulation_instruction(self, expr):
if get_global_context_value("form_type") == "jacobian":
raise NotImplemented()
else:
return generate_accumulation_instruction_vectorized(expr, self)
@function_mangler
def residual_weight_mangler(knl, func, arg_dtypes):
if isinstance(func, str) and func.endswith('.weight'):
return lp.CallMangleInfo(func, (lp.types.NumpyType(dtype_floatingpoint()),), ())
# TODO: is this necessary? maybe using the correct indices is sufficient
def blockstructured_boundary_predicated(visitor, measure, subdomain_id):
predicates = []
if subdomain_id not in ['everywhere', 'otherwise']:
if isinstance(visitor.refinement, HyperCubeRefinement):
subelem_inames = visitor.cell_inames # TODO: maybe these inames should be accessible elsewhere?
def iname_equals(iname, i):
return prim.Comparison(prim.Variable(iname), "==", i)
k = get_form_option("number_of_blocks")
face_id = get_global_context_value("face_id")
# TODO: this assumes a particular order of inames
if face_id == 0:
predicates.append(iname_equals(subelem_inames[0], 0))
elif face_id == 1:
predicates.append(iname_equals(subelem_inames[0], k - 1))
elif face_id == 2:
predicates.append(iname_equals(subelem_inames[1], 0))
elif face_id == 3:
predicates.append(iname_equals(subelem_inames[1], k - 1))
elif face_id == 4:
predicates.append(iname_equals(subelem_inames[2], 0))
elif face_id == 5:
predicates.append(iname_equals(subelem_inames[2], k - 1))
else:
raise NotImplementedError()
return frozenset(predicates)
def generate_accumulation_instruction_vectorized(expr, visitor):
# Collect the lfs and lfs indices for the accumulate call
test_lfs = determine_accumulation_space(visitor.test_info, 0)
# In the jacobian case, also determine the space for the ansatz space
ansatz_lfs = determine_accumulation_space(visitor.trial_info, 1)
# Collect the lfs and lfs indices for the accumulate call
accumvar = name_accumulation_variable(test_lfs.get_restriction() + ansatz_lfs.get_restriction())
accumvar_alias = name_accumulation_alias(accumvar, test_lfs)
predicates = boundary_predicates(visitor.measure, visitor.subdomain_id)
predicates = predicates.union(blockstructured_boundary_predicated(visitor, visitor.measure, visitor.subdomain_id))
quad_inames = visitor.quadrature_inames()
lfs_iname, = visitor.test_info.inames
index = visitor.lfs_inames_mapped(lfs_iname, test_lfs.get_restriction()[0])
expr_with_weight = prim.Product((expr, prim.Call(prim.Variable(accumvar + '.weight'), ())))
assignee = prim.Subscript(prim.Variable(accumvar_alias), (index,))
accum_expr = prim.Sum((expr_with_weight, assignee))
deps = get_dependencies(expr_with_weight)
for predicate in predicates:
deps |= get_dependencies(predicate)
deps |= {get_pymbolic_basename(index)}
deps = frozenset({Writes(d) for d in deps})
instruction(assignee=assignee,
expression=accum_expr,
forced_iname_deps=frozenset(quad_inames + (lfs_iname,)),
forced_iname_deps_is_final=True,
predicates=predicates,
tags=frozenset({'accum'}),
depends_on=deps,
no_sync_with=frozenset({(Tagged('accum'), 'any')})
)
from dune.codegen.generation import kernel_cached, valuearg
from dune.codegen.structured_refinement.tools import name_container_alias
from loopy.types import NumpyType
import pymbolic.primitives as prim
# TODO remove the need for element
@kernel_cached
def pymbolic_coefficient(visitor, container, lfs, element, index, restriction):
# TODO introduce a proper type for local function spaces!
if isinstance(lfs, str):
valuearg(lfs, dtype=NumpyType("str"))
# If the LFS is not yet a pymbolic expression, make it one
if not isinstance(lfs, prim.Expression):
lfs = prim.Variable(lfs)
# use higher order FEM index instead of Q1 index
coeff_alias = name_container_alias(container, lfs, element)
return prim.Subscript(prim.Variable(coeff_alias), (visitor.lfs_inames_mapped(index, restriction),))
import pymbolic.primitives as prim
from dune.codegen.generation import (basis_mixin,
instruction,
temporary_variable,
globalarg,
class_member,
initializer_list,
include_file,
domain, get_global_context_value)
from dune.codegen.loopy.target import type_floatingpoint
from dune.codegen.pdelab.basis import (GenericBasisMixin,
type_localbasis,
FEM_name_mangling)
from dune.codegen.pdelab.driver import (isPk,
isQk,
)
from dune.codegen.pdelab.geometry import world_dimension
from dune.codegen.pdelab.restriction import restricted_name
from dune.codegen.pdelab.spaces import type_leaf_gfs, initialize_function_spaces
import numpy as np
from dune.codegen.ufl.modified_terminals import Restriction
from meshstructure import HyperCubeRefinement, MeshExtrusion, Point
from dune.codegen.generation.loopy import kernel_cached
@basis_mixin("structured_refinement")
class StructuredRefinementBasisMixin(GenericBasisMixin):
@kernel_cached
def _dof_indices(self, restriction):
source_set = self.source_set
if isinstance(self.refinement, HyperCubeRefinement):
target_set = self.refinement.entities[world_dimension()][0]
dof_per_codim = 1 # TODO: remove hard assumption on Q1
elif isinstance(self.refinement, MeshExtrusion):
target_set = source_set
dof_per_codim = 2**world_dimension() # TODO: remove hard assumption on DG Q1
else:
raise NotImplemented
cell_multiindex = source_set.indices
if get_global_context_value('pdelab_kernel') == 'volume' and restriction == Restriction.NEGATIVE:
# TODO: need to find out in which direction neighbour cell lies, currently assume below
cell_multiindex = cell_multiindex[:-1] + (cell_multiindex[-1] - 1,)
# TODO: change order s.t. fastest changing index comes first in case of blockstructured refinement
if isinstance(self.refinement, HyperCubeRefinement):
cell_multiindex = tuple(reversed(cell_multiindex))
dof_indices = self.refinement.index_relation(Point(cell_multiindex, source_set), target_set)
linear_dof_indices = tuple(target_set.linear_index_map(p.multiindex) for p in dof_indices)
indices = ()
for index in linear_dof_indices:
# TODO: this assumes only one codim with dofs attached
indices = indices + tuple([index * dof_per_codim + i for i in range(dof_per_codim)])
return indices
# use correct restriction on boundary facets
def initialize_function_spaces(self, expr):
restriction = self.restriction
if self.measure in ['exterior_facet', 'bottom_facet', 'top_facet']:
restriction = self.current_info[0].restriction
return initialize_function_spaces(expr, restriction, self.indices)
def lfs_inames(self, element, restriction, number, context=""):
assert not ((context == '') and (number is None))
if number is not None:
if context != '':
context = "{}_{}".format(number, context)
else:
context = str(number)
# TODO: this is only needed for the number of dof per micro element, this should be easier
linear_dof_indices = self._dof_indices(restriction)
iname = "dof_idx" + context + ("_s" if restriction else "")
domain(iname, len(linear_dof_indices))
return (iname,)
def lfs_inames_mapped(self, iname, restriction):
linear_dof_indices = self._dof_indices(restriction)
if isinstance(iname, tuple):
iname, = iname
dof_map = restricted_name("dof_map", restriction)
temporary_variable(dof_map, dtype=np.int32, shape=(len(linear_dof_indices),), managed=False)
for i, p in enumerate(linear_dof_indices):
instruction(assignee=prim.Subscript(prim.Variable(dof_map), (i,)),
expression=p,
forced_iname_deps=frozenset(self.cell_inames),
tags=frozenset({"dof_map_init"})
)
return prim.Subscript(prim.Variable(dof_map), (prim.Variable(iname),))
# define FE basis explicitly in localoperator
@class_member(classtag="operator")
def typedef_localbasis(element, name):
df = "typename {}::Traits::GridView::ctype".format(type_leaf_gfs(element))
r = type_floatingpoint()
dim = world_dimension()
if isPk(element):
include_file("dune/localfunctions/lagrange/lagrangesimplex.hh", filetag="operatorfile")
basis_type = "Impl::LagrangeSimplexLocalBasis<{}, {}, {}, {}>".format(df, r, dim, element._degree)
elif isQk(element):
include_file("dune/localfunctions/lagrange/lagrangecube.hh", filetag="operatorfile")
basis_type = "Impl::LagrangeCubeLocalBasis<{}, {}, {}, {}>".format(df, r, dim, element._degree)
else:
raise NotImplementedError("Element type not known in code generation")
return "using {} = Dune::{};".format(name, basis_type)
@class_member(classtag="operator")
def define_localbasis(leaf_element, name):
localBasis_type = type_localbasis(leaf_element)
initializer_list(name, (), classtag="operator")
return "const {} {};".format(localBasis_type, name)
def name_localbasis(leaf_element):
name = "{}_microElementBasis".format(FEM_name_mangling(leaf_element))
globalarg(name)
define_localbasis(leaf_element, name)
return name
import pymbolic.primitives as prim
from loopy.match import Writes
from dune.codegen.generation import (geometry_mixin,
temporary_variable,
instruction,
get_global_context_value,
domain,
kernel_cached,
transform)
from dune.codegen.loopy.symbolic import substitute
from dune.codegen.options import get_option
from dune.codegen.pdelab.geometry import (world_dimension,
name_cell_geometry,
boundary_restriction, GeometryMixinBase)
from dune.codegen.tools import get_pymbolic_basename, maybe_wrap_subscript
from dune.codegen.ufl.modified_terminals import Restriction
@geometry_mixin("structured_refinement")
class StructuredRefinementGeometryMixin(GeometryMixinBase):
@kernel_cached
def _add_transformation(self):
from dune.codegen.structured_refinement.transformations import add_corner_dependencies
transform(add_corner_dependencies, self.quadrature_inames())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._add_transformation()
# TODO: currently only VectorElement Q1 allowed
_basis = (1 - prim.Variable('x0'), prim.Variable('x0'))
_grad = ((-1,), (1,))
def substitute_dimension(dim):
return tuple(substitute(p, {'x0': prim.Variable('x{}'.format(dim))}) for p in _basis)
self.coordinate_grad = _grad
self.coordinate_basis = _basis
for i in range(1, world_dimension()):
self.coordinate_grad = tuple(tuple(gi * p_1d for gi in g) + (b * g_1d[0],)
for p_1d, g_1d in zip(substitute_dimension(i), _grad)
for g, b in zip(self.coordinate_grad, self.coordinate_basis))
self.coordinate_basis = tuple(p * p_1d for p_1d in substitute_dimension(i) for p in self.coordinate_basis)
def geometry_dofs(self):
raise NotImplementedError
@kernel_cached
def _init_transformation(self, name, basis, qp):
basis = [substitute(b, dict([(d, maybe_wrap_subscript(qp, i))
for i, d in enumerate(['x{}'.format(i) for i in range(world_dimension())])]))
for b in basis]
vertices = self.geometry_dofs()
insn_kwargs = dict(within_inames=frozenset(self.quadrature_inames()),
within_inames_is_final=True, # TODO: why is this needed?
depends_on=frozenset({Writes(get_pymbolic_basename(qp))}))
if self.reference_grad:
for i in range(world_dimension()):
for j in range(world_dimension()):
instruction(assignee=prim.Subscript(prim.Variable(name), (i, j)),
expression=sum(vertices[basis_index][i] * b[j] for basis_index, b in enumerate(basis)),
**insn_kwargs)
else:
for i in range(world_dimension()):
instruction(assignee=prim.Subscript(prim.Variable(name), (i,)),
expression=sum(vertices[basis_index][i] * b for basis_index, b in enumerate(basis)),
**insn_kwargs)
def spatial_coordinate(self, o):
basis = self.coordinate_grad if self.reference_grad else self.coordinate_basis
measure = self.measure
name = ("DX_grad" if self.reference_grad else "DX") + "_" + measure
shape = (world_dimension(), world_dimension(),) if self.reference_grad else (world_dimension(),)
temporary_variable(name, shape=shape, managed=True)
qp = self.to_cell(self.quadrature_position())
self._init_transformation(name, basis, qp)
return prim.Variable(name)
def define_element_corners(name):
from dune.codegen.pdelab.driver import get_form
if get_option("use_extruded_mesh"):
base_cell = {'quadrilateral': 2,
'hexahedron': 4}
n_corners = base_cell[get_form().ufl_cell().cellname()]
temporary_variable(name, shape_impl=('fv', 'fv'), shape=(n_corners, world_dimension() - 1))
else:
n_corners = get_form().ufl_cell().num_vertices()
temporary_variable(name, shape_impl=('fv', 'fv'), shape=(n_corners, world_dimension()))
iname = "i_corner"
domain(iname, n_corners)
it = get_global_context_value("integral_type")
kernel = get_global_context_value("pdelab_kernel")
if kernel:
if kernel == 'volume':
restriction = Restriction.NONE
elif kernel == 'skeleton':
restriction = Restriction.POSITIVE
else:
restriction = boundary_restriction(it)
else:
if it == 'cell':
restriction = Restriction.NONE
elif it == 'interior_facet':
restriction = Restriction.POSITIVE
else:
restriction = boundary_restriction(it)
instruction(code="{0}[{1}] = {2}.corner({3});".format(name, iname, name_cell_geometry(restriction), iname),
assignees=frozenset({name}),
within_inames=frozenset({iname}), within_inames_is_final=True,
tags=frozenset({'element_corners'}))
def name_element_corners():
name = "corners"
define_element_corners(name)