def compute_form_with_arity(form, arity, arguments=None): """Compute parts of form of given arity.""" # Extract all arguments in form if arguments is None: arguments = form.arguments() parts = [arg.part() for arg in arguments] if set(parts) - {None}: error("compute_form_with_arity cannot handle parts.") if len(arguments) < arity: warning("Form has no parts with arity %d." % arity) return 0*form # Assuming that the form is not a sum of terms # that depend on different arguments, e.g. (u+v)*dx # would result in just v*dx. But that doesn't make # any sense anyway. sub_arguments = set(arguments[:arity]) pe = PartExtracter(sub_arguments) def _transform(e): e, provides = pe.visit(e) if provides == sub_arguments: return e return Zero() return map_integrands(_transform, form)
def _reduce_facet_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if domain.ufl_cell().topological_dimension() < 3: error( "Facet edge lengths only make sense for topological dimension >= 3." ) elif not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning( "Only know how to compute facet edge lengths of P1 or Q1 cell." ) return o else: # P1 tetrahedron or Q1 hexahedron edges = FacetEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen2 = [edges[e, j] * edges[e, j] for e in range(num_edges)] return sqrt(reduce(reduction_op, elen2))
def max_facet_edge_length(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning( "Only know how to compute the max_facet_edge_length of an affine cell." ) return o cellname = domain.ufl_cell().cellname() if cellname == "triangle": return self.facet_area(FacetArea(domain)) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = FacetEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [ sqrt((J[i, j] * trev[edge, j]) * (J[i, k] * trev[edge, k])) for edge in range(num_edges) ] return max_value(elen[0], max_value(elen[1], elen[2])) else: error("Unhandled cell type %s." % cellname)
def canonicalize_metadata(metadata): """Assuming metadata to be a dict with string keys and builtin python types as values. Transform dict to a tuple of (key, value) item tuples ordered by key, with dict, list and tuple values converted the same way recursively. Lists and tuples are converted to tuples. Other values are converted using str(). This is such that the end result can be hashed and sorted using regular <, because python 3 doesn't allow e.g. (3 < "auto") which occurs regularly in metadata. """ if metadata is None: return () if isinstance(metadata, dict): keys = sorted(metadata.keys()) assert all(isinstance(key, str) for key in keys) values = [metadata[key] for key in keys] elif isinstance(metadata, (tuple, list)): values = metadata newvalues = [] for value in values: if isinstance(value, (dict, list, tuple)): value = canonicalize_metadata(value) elif isinstance(value, (int, float, str)) or value is None: value = str(value) else: warning("Applying str() to a metadata value of type {0}, don't know if this is safe.".format(type(value).__name__)) value = str(value) newvalues.append(value) if isinstance(metadata, dict): return tuple(zip(keys, newvalues)) else: return tuple(newvalues)
def is_undefined(self): """Return whether this cell is undefined, in which case no dimensions are available.""" warning( "cell.is_undefined() is deprecated, undefined cells are no longer allowed." ) return False
def evaluate(self, x, mapping, component, index_values): a = self._argument.evaluate(x, mapping, component, index_values) try: res = getattr(math, self._name)(a) except ValueError: warning('Value error in evaluation of function %s with argument %s.' % (self._name, a)) raise return res
def assign_precedences(precedence_list): "Given a precedence list, assign ints to class._precedence." pm, missing = build_precedence_mapping(precedence_list) for c, p in sorted(pm.items(), key=lambda x: x[0].__name__): c._precedence = p if missing: msg = "Missing precedence levels for classes:\n" +\ "\n".join(' %s' % c for c in sorted(missing)) warning(msg)
def evaluate(self, x, mapping, component, index_values): a = self._arg1.evaluate(x, mapping, component, index_values) b = self._arg2.evaluate(x, mapping, component, index_values) try: res = math.atan2(a,b) except ValueError: warning('Value error in evaluation of function %s with arguments %s, %s.' % (self._name, a,b)) raise return res
def flatten( e ): # TODO: Fix or remove! Maybe this works better now with IndexSum marking implicit summations. """Convert an UFL expression to a new UFL expression, with sums and products flattened from binary tree nodes to n-ary tree nodes.""" warning( "flatten doesn't work correctly for some indexed products, like (u[i]*v[i])*(q[i]*r[i])" ) return apply_transformer(e, TreeFlattener())
def circumradius(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning( "Only know how to compute the circumradius of an affine cell.") return o cellname = domain.ufl_cell().cellname() cellvolume = self.cell_volume(CellVolume(domain)) if cellname == "interval": r = 0.5 * cellvolume elif cellname == "triangle": J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = 3 i, j, k = indices(3) elen = [ sqrt((J[i, j] * trev[edge, j]) * (J[i, k] * trev[edge, k])) for edge in range(num_edges) ] r = (elen[0] * elen[1] * elen[2]) / (4.0 * cellvolume) elif cellname == "tetrahedron": J = self.jacobian(Jacobian(domain)) trev = CellEdgeVectors(domain) num_edges = 6 i, j, k = indices(3) elen = [ sqrt((J[i, j] * trev[edge, j]) * (J[i, k] * trev[edge, k])) for edge in range(num_edges) ] # elen[3] = length of edge 3 # la, lb, lc = lengths of the sides of an intermediate triangle la = elen[3] * elen[2] lb = elen[4] * elen[1] lc = elen[5] * elen[0] # p = perimeter p = (la + lb + lc) # s = semiperimeter s = p / 2 # area of intermediate triangle with Herons formula triangle_area = sqrt(s * (s - la) * (s - lb) * (s - lc)) r = triangle_area / (6.0 * cellvolume) else: error("Unhandled cell type %s." % cellname) return r
def evaluate(self, x, mapping, component, index_values): a, b = self.ufl_operands a = a.evaluate(x, mapping, component, index_values) b = b.evaluate(x, mapping, component, index_values) try: res = max(a, b) except ValueError: warning('Value error in evaluation of max() of %s and %s.' % self.ufl_operands) raise return res
def execute_ufl_code(uflcode, filename): # Execute code namespace = {} namespace.update(vars(ufl)) try: exec(uflcode, namespace) except Exception as e: warning(infostring) raise e return namespace
def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) try: if isinstance(a, numbers.Real): res = getattr(math, self._name)(a) else: res = getattr(cmath, self._name)(a) except ValueError: warning('Value error in evaluation of function %s with argument %s.' % (self._name, a)) raise return res
def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: res = math.atan2(a, b) except ValueError: warning( 'Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) raise return res
def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) b = self.ufl_operands[1].evaluate(x, mapping, component, index_values) try: res = math.atan2(a, b) except TypeError: error('Atan2 does not support complex numbers.') except ValueError: warning('Value error in evaluation of function atan_2 with arguments %s, %s.' % (a, b)) raise return res
def evaluate(self, x, mapping, component, index_values): a = self.ufl_operands[0].evaluate(x, mapping, component, index_values) try: if isinstance(a, numbers.Real): res = getattr(math, self._name)(a) else: res = getattr(cmath, self._name)(a) except ValueError: warning( 'Value error in evaluation of function %s with argument %s.' % (self._name, a)) raise return res
def cell_volume(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning("Only know how to compute the cell volume of an affine cell.") return o r = self.jacobian_determinant(JacobianDeterminant(domain)) r0 = ReferenceCellVolume(domain) return abs(r * r0)
def __call__(self, *args, **kwargs): """UFL form operator: Evaluate form by replacing arguments and coefficients. Replaces form.arguments() with given positional arguments in same number and ordering. Number of positional arguments must be 0 or equal to the number of Arguments in the form. The optional keyword argument coefficients can be set to a dict to replace Coefficients with expressions of matching shapes. Example: V = FiniteElement("CG", triangle, 1) v = TestFunction(V) u = TrialFunction(V) f = Coefficient(V) g = Coefficient(V) a = g*inner(grad(u), grad(v))*dx M = a(f, f, coefficients={ g: 1 }) Is equivalent to M == grad(f)**2*dx. """ repdict = {} if args: arguments = self.arguments() if len(arguments) != len(args): error("Need %d arguments to form(), got %d." % (len(arguments), len(args))) repdict.update(zip(arguments, args)) coefficients = kwargs.pop("coefficients") if kwargs: error("Unknown kwargs %s." % str(list(kwargs))) if coefficients is not None: coeffs = self.coefficients() for f in coefficients: if f in coeffs: repdict[f] = coefficients[f] else: warning("Coefficient %s is not in form." % ufl_err_str(f)) if repdict: from ufl.formoperators import replace return replace(self, repdict) else: return self
def coefficient(self, o): # Define dw/dw := d/ds [w + s v] = v debug("In CoefficientAD.coefficient:") debug("o = %s" % o) debug("self._w = %s" % self._w) debug("self._v = %s" % self._v) # Find o among w for (w, v) in izip(self._w, self._v): if o == w: return (w, v) # If o is not among coefficient derivatives, return do/dw=0 oprimesum = Zero(o.shape()) oprimes = self._cd._data.get(o) if oprimes is None: if self._cd._data: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes,) ufl_assert(len(oprimes) == len(self._v), "Got a tuple of arguments, "+\ "expecting a matching tuple of coefficient derivatives.") # Compute do/dw_j = do/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in izip(oprimes, self._v): so, oi = as_scalar(oprime) rv = len(v.shape()) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: oprimesum += as_tensor(prod, oi1) else: oprimesum += prod # Example: # (f : g) -> (dfdu : v) : g + ditto # shape(f) == shape(g) == shape(dfdu : v) # shape(dfdu) == shape(f) + shape(v) return (o, oprimesum)
def coefficient(self, o): # Define dw/dw := d/ds [w + s v] = v debug("In CoefficientAD.coefficient:") debug("o = %s" % o) debug("self._w = %s" % self._w) debug("self._v = %s" % self._v) # Find o among w for (w, v) in izip(self._w, self._v): if o == w: return (w, v) # If o is not among coefficient derivatives, return do/dw=0 oprimesum = Zero(o.shape()) oprimes = self._cd._data.get(o) if oprimes is None: if self._cd._data: # TODO: Make it possible to silence this message in particular? # It may be good to have for debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes, ) ufl_assert(len(oprimes) == len(self._v), "Got a tuple of arguments, "+\ "expecting a matching tuple of coefficient derivatives.") # Compute do/dw_j = do/dw_h : v. # Since we may actually have a tuple of oprimes and vs in a # 'mixed' space, sum over them all to get the complete inner # product. Using indices to define a non-compound inner product. for (oprime, v) in izip(oprimes, self._v): so, oi = as_scalar(oprime) rv = len(v.shape()) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so * v[oi2] if oi1: oprimesum += as_tensor(prod, oi1) else: oprimesum += prod # Example: # (f : g) -> (dfdu : v) : g + ditto # shape(f) == shape(g) == shape(dfdu : v) # shape(dfdu) == shape(f) + shape(v) return (o, oprimesum)
def execute_ufl_code(uflcode, filename): # Execute code namespace = {} try: pycode = "from ufl import *\n" + uflcode exec pycode in namespace except: # Dump python code for debugging if this fails basename = os.path.splitext(os.path.basename(filename))[0] basename = "%s_debug" % basename pyname = "%s.py" % basename pycode = "#!/usr/bin/env python\nfrom ufl import *\nset_level(DEBUG)\n" + uflcode with file(pyname, "w") as f: f.write(pycode) warning(infostring % pyname) m = __import__(basename) error("An error occured, aborting load_forms.") return namespace
def jump(v, n=None): "UFL operator: Take the jump of v across a facet." v = as_ufl(v) cell = v.cell() if cell is None: warning("TODO: Not all expressions have a cell. Is it right to return zero from jump then?") # TODO: Is this right? If v has no cell, it doesn't depend on # anything spatially variable or any form arguments, and thus # the jump is zero. In other words, I'm assuming that # "v.cell() is None" is equivalent with "v is a constant". return Zero(v.shape(), v.free_indices(), v.index_dimensions()) else: if n is None: return v('+') - v('-') r = v.rank() if r == 0: return v('+')*n('+') + v('-')*n('-') else: return dot(v('+'), n('+')) + dot(v('-'), n('-'))
def facet_area(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() tdim = domain.topological_dimension() if not domain.is_piecewise_linear_simplex_domain(): # Don't lower for non-affine cells, instead leave it to # form compiler warning("Only know how to compute the facet area of an affine cell.") return o # Area of "facet" of interval (i.e. "area" of a vertex) is defined as 1.0 if tdim == 1: return FloatValue(1.0) r = self.facet_jacobian_determinant(FacetJacobianDeterminant(domain)) r0 = ReferenceFacetVolume(domain) return abs(r * r0)
def expand_derivatives(form, **kwargs): """Expand all derivatives of expr. In the returned expression g which is mathematically equivalent to expr, there are no VariableDerivative or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. """ # For a deprecation period (I see that dolfin-adjoint passes some # args here) if kwargs: warning("Deprecation: expand_derivatives no longer takes any keyword arguments") # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) # Apply differentiation form = apply_derivatives(form) return form
def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get *self* from *mapping* and return the component asked for." f = mapping.get(self) # No mapping, trying to evaluate self as a constant if f is None: try: try: f = float(self) except TypeError: f = complex(self) if derivatives: f = 0.0 return f except Exception: pass # If it has an ufl_evaluate function, call it if hasattr(self, 'ufl_evaluate'): return self.ufl_evaluate(x, component, derivatives) # Take component if any warning( "Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self)) f = self if component: f = f[component] return f # Found a callable in the mapping if callable(f): if derivatives: f = f(x, derivatives) else: f = f(x) else: if derivatives: return 0.0 # Take component if any (expecting nested tuple) for c in component: f = f[c] return f
def jump(v, n=None): "UFL operator: Take the jump of *v* across a facet." v = as_ufl(v) is_constant = len(extract_domains(v)) > 0 if is_constant: if n is None: return v('+') - v('-') r = len(v.ufl_shape) if r == 0: return v('+') * n('+') + v('-') * n('-') else: return dot(v('+'), n('+')) + dot(v('-'), n('-')) else: warning("Returning zero from jump of expression without a domain. This may be erroneous if a dolfin.Expression is involved.") # FIXME: Is this right? If v has no domain, it doesn't depend # on anything spatially variable or any form arguments, and # thus the jump is zero. In other words, I'm assuming that "v # has no geometric domains" is equivalent with "v is a spatial # constant". Update: This is NOT true for # jump(Expression("x[0]")) from dolfin. return Zero(v.ufl_shape, v.ufl_free_indices, v.ufl_index_dimensions)
def jump(v, n=None): "UFL operator: Take the jump of v across a facet." v = as_ufl(v) cell = v.cell() if cell is None: warning( "TODO: Not all expressions have a cell. Is it right to return zero from jump then?" ) # TODO: Is this right? If v has no cell, it doesn't depend on # anything spatially variable or any form arguments, and thus # the jump is zero. In other words, I'm assuming that # "v.cell() is None" is equivalent with "v is a constant". return Zero(v.shape(), v.free_indices(), v.index_dimensions()) else: if n is None: return v('+') - v('-') r = v.rank() if r == 0: return v('+') * n('+') + v('-') * n('-') else: return dot(v('+'), n('+')) + dot(v('-'), n('-'))
def _reduce_facet_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if domain.ufl_cell().topological_dimension() < 3: error("Facet edge lengths only make sense for topological dimension >= 3.") elif not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute facet edge lengths of P1 or Q1 cell.") return o else: # P1 tetrahedron or Q1 hexahedron edges = FacetEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen2 = [edges[e, j]*edges[e, j] for e in range(num_edges)] return sqrt(reduce(reduction_op, elen2))
def evaluate(self, x, mapping, component, index_values, derivatives=()): "Get *self* from *mapping* and return the component asked for." f = mapping.get(self) # No mapping, trying to evaluate self as a constant if f is None: try: try: f = float(self) except TypeError: f = complex(self) if derivatives: f = 0.0 return f except Exception: pass # If it has an ufl_evaluate function, call it if hasattr(self, 'ufl_evaluate'): return self.ufl_evaluate(x, component, derivatives) # Take component if any warning("Couldn't map '%s' to a float, returning ufl object without evaluation." % str(self)) f = self if component: f = f[component] return f # Found a callable in the mapping if callable(f): if derivatives: f = f(x, derivatives) else: f = f(x) else: if derivatives: return 0.0 # Take component if any (expecting nested tuple) for c in component: f = f[c] return f
def expand_derivatives(form, **kwargs): """Expand all derivatives of expr. In the returned expression g which is mathematically equivalent to expr, there are no VariableDerivative or CoefficientDerivative objects left, and Grad objects have been propagated to Terminal nodes. """ # For a deprecation period (I see that dolfin-adjoint passes some # args here) if kwargs: warning( "Deprecation: expand_derivatives no longer takes any keyword arguments" ) # Lower abstractions for tensor-algebra types into index notation form = apply_algebra_lowering(form) # Apply differentiation form = apply_derivatives(form) return form
def cell_diameter(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.ufl_coordinate_element().degree() in {1, (1, 1)}: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute cell diameter of P1 or Q1 cell.") return o elif domain.is_piecewise_linear_simplex_domain(): # Simplices return self.max_cell_edge_length(MaxCellEdgeLength(domain)) else: # Q1 cells, maximal distance between any two vertices verts = CellVertices(domain) verts = [verts[v, ...] for v in range(verts.ufl_shape[0])] j = Index() elen2 = (real((v0 - v1)[j] * conj((v0 - v1)[j])) for v0, v1 in combinations(verts, 2)) return real(sqrt(reduce(max_value, elen2)))
def _reduce_cell_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute cell edge lengths of P1 or Q1 cell.") return o elif domain.ufl_cell().cellname() == "interval": # Interval optimization, square root not needed return self.cell_volume(CellVolume(domain)) else: # Other P1 or Q1 cells edges = CellEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen2 = [real(edges[e, j] * conj(edges[e, j])) for e in range(num_edges)] return real(sqrt(reduce(reduction_op, elen2)))
def is_multilinear(form): "Check if form is multilinear in arguments." # An attempt at implementing is_multilinear using extract_argument_dependencies. # TODO: This has some false negatives for "multiple configurations". (Does it still? Needs testing!) # TODO: FFC probably needs a variant of this which checks for some sorts of linearity # in Coefficients as well, this should be a fairly simple extension of the current algorithm. try: for e in iter_expressions(form): deps = extract_argument_dependencies(e) nargs = [len(d) for d in deps] if len(nargs) == 0: debug("This form is a functional.") if len(nargs) == 1: debug("This form is linear in %d arguments." % nargs[0]) if len(nargs) > 1: warning("This form has more than one argument "\ "'configuration', it has terms that are linear in %s "\ "arguments respectively." % str(nargs)) except NotMultiLinearException, msg: warning("Form is not multilinear, the offending term is: %s" % msg) return False
def cell_diameter(self, o): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute cell diameter of P1 or Q1 cell.") return o elif domain.is_piecewise_linear_simplex_domain(): # Simplices return self.max_cell_edge_length(MaxCellEdgeLength(domain)) else: # Q1 cells, maximal distance between any two vertices verts = CellVertices(domain) verts = [verts[v, ...] for v in range(verts.ufl_shape[0])] j = Index() elen2 = ((v0-v1)[j]*(v0-v1)[j] for v0, v1 in combinations(verts, 2)) return sqrt(reduce(max_value, elen2))
def _reduce_cell_edge_length(self, o, reduction_op): if self._preserve_types[o._ufl_typecode_]: return o domain = o.ufl_domain() if not domain.ufl_coordinate_element().degree() == 1: # Don't lower bendy cells, instead leave it to form compiler warning("Only know how to compute cell edge lengths of P1 or Q1 cell.") return o elif domain.ufl_cell().cellname() == "interval": # Interval optimization, square root not needed return self.cell_volume(CellVolume(domain)) else: # Other P1 or Q1 cells edges = CellEdgeVectors(domain) num_edges = edges.ufl_shape[0] j = Index() elen2 = [edges[e, j]*edges[e, j] for e in range(num_edges)] return sqrt(reduce(reduction_op, elen2))
def canonicalize_metadata(metadata): """Assuming metadata to be a dict with string keys and builtin python types as values. Transform dict to a tuple of (key, value) item tuples ordered by key, with dict, list and tuple values converted the same way recursively. Lists and tuples are converted to tuples. Other values are converted using str(). This is such that the end result can be hashed and sorted using regular <, because python 3 doesn't allow e.g. (3 < "auto") which occurs regularly in metadata. """ if metadata is None: return () if isinstance(metadata, dict): keys = sorted(metadata.keys()) assert all(isinstance(key, str) for key in keys) values = [metadata[key] for key in keys] elif isinstance(metadata, (tuple, list)): values = metadata newvalues = [] for value in values: if isinstance(value, (dict, list, tuple)): value = canonicalize_metadata(value) elif isinstance(value, (int, float, str)) or value is None: value = str(value) else: warning( "Applying str() to a metadata value of type {0}, don't know if this is safe." .format(type(value).__name__)) value = str(value) newvalues.append(value) if isinstance(metadata, dict): return tuple(zip(keys, newvalues)) else: return tuple(newvalues)
def interpret_ufl_namespace(namespace): "Takes a namespace dict from an executed ufl file and converts it to a FileData object." # Object to hold all returned data ufd = FileData() # Extract object names for Form, Coefficient and FiniteElementBase objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. for name, value in sorted_by_key(namespace): # Store objects by reserved name OR instance id reserved_names = ("unknown", ) # Currently only one reserved name if name in reserved_names: # Store objects with reserved names ufd.reserved_objects[name] = value # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value elif isinstance( value, (FiniteElementBase, Coefficient, Argument, Form, Expr)): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name ufd.object_by_name[name] = value # Get list of exported forms forms = namespace.get("forms") if forms is None: # Get forms from object_by_name, which has already mapped # tuple->Form where needed def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") forms = [a_form, L_form, M_form] # Add forms F and J if not "a" and "L" are used if a_form is None or L_form is None: F_form = get_form("F") J_form = get_form("J") forms += [F_form, J_form] # Remove Nones forms = [f for f in forms if isinstance(f, Form)] ufd.forms = forms # Validate types if not isinstance(ufd.forms, (list, tuple)): error("Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) if not all(isinstance(a, Form) for a in ufd.forms): error("Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") if elements is None: elements = [ufd.object_by_name.get(name) for name in ("element", )] elements = [e for e in elements if e is not None] ufd.elements = elements # Validate types if not isinstance(ufd.elements, (list, tuple)): error("Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): error( "Expecting 'elements' to be a list of FiniteElementBase instances." ) # Get list of exported coefficients # TODO: Temporarily letting 'coefficients' override 'functions', # but allow 'functions' for compatibility functions = namespace.get("functions", []) if functions: warning( "Deprecation warning: Rename 'functions' to 'coefficients' to export coefficients." ) ufd.coefficients = namespace.get("coefficients", functions) # Validate types if not isinstance(ufd.coefficients, (list, tuple)): error("Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): error( "Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, (list, tuple)): error("Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) if not all(isinstance(e, Expr) for e in ufd.expressions): error("Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd
def interpret_ufl_namespace(namespace): "Takes a namespace dict from an executed ufl file and converts it to a FileData object." # Object to hold all returned data ufd = FileData() # Extract object names for Form, Coefficient and FiniteElementBase objects # The use of id(obj) as key in object_names is necessary # because we need to distinguish between instances, # and not just between objects with different values. for name, value in sorted_by_key(namespace): # Store objects by reserved name OR instance id reserved_names = ("unknown",) # Currently only one reserved name if name in reserved_names: # Store objects with reserved names ufd.reserved_objects[name] = value # FIXME: Remove after FFC is updated to use reserved_objects: ufd.object_names[name] = value ufd.object_by_name[name] = value elif isinstance(value, (FiniteElementBase, Coefficient, Argument, Form, Expr)): # Store instance <-> name mappings for important objects # without a reserved name ufd.object_names[id(value)] = name ufd.object_by_name[name] = value # Get list of exported forms forms = namespace.get("forms") if forms is None: # Get forms from object_by_name, which has already mapped # tuple->Form where needed def get_form(name): form = ufd.object_by_name.get(name) return form if isinstance(form, Form) else None a_form = get_form("a") L_form = get_form("L") M_form = get_form("M") forms = [a_form, L_form, M_form] # Add forms F and J if not "a" and "L" are used if a_form is None or L_form is None: F_form = get_form("F") J_form = get_form("J") forms += [F_form, J_form] # Remove Nones forms = [f for f in forms if isinstance(f, Form)] ufd.forms = forms # Validate types if not isinstance(ufd.forms, (list, tuple)): error("Expecting 'forms' to be a list or tuple, not '%s'." % type(ufd.forms)) if not all(isinstance(a, Form) for a in ufd.forms): error("Expecting 'forms' to be a list of Form instances.") # Get list of exported elements elements = namespace.get("elements") if elements is None: elements = [ufd.object_by_name.get(name) for name in ("element",)] elements = [e for e in elements if e is not None] ufd.elements = elements # Validate types if not isinstance(ufd.elements, (list, tuple)): error("Expecting 'elements' to be a list or tuple, not '%s'." % type(ufd.elements)) if not all(isinstance(e, FiniteElementBase) for e in ufd.elements): error("Expecting 'elements' to be a list of FiniteElementBase instances.") # Get list of exported coefficients # TODO: Temporarily letting 'coefficients' override 'functions', # but allow 'functions' for compatibility functions = namespace.get("functions", []) if functions: warning("Deprecation warning: Rename 'functions' to 'coefficients' to export coefficients.") ufd.coefficients = namespace.get("coefficients", functions) # Validate types if not isinstance(ufd.coefficients, (list, tuple)): error("Expecting 'coefficients' to be a list or tuple, not '%s'." % type(ufd.coefficients)) if not all(isinstance(e, Coefficient) for e in ufd.coefficients): error("Expecting 'coefficients' to be a list of Coefficient instances.") # Get list of exported expressions ufd.expressions = namespace.get("expressions", []) # Validate types if not isinstance(ufd.expressions, (list, tuple)): error("Expecting 'expressions' to be a list or tuple, not '%s'." % type(ufd.expressions)) if not all(isinstance(e, Expr) for e in ufd.expressions): error("Expecting 'expressions' to be a list of Expr instances.") # Return file data return ufd
def grad(self, g): # If we hit this type, it has already been propagated to a # coefficient (or grad of a coefficient), # FIXME: Assert # this! so we need to take the gradient of the variation or # return zero. Complications occur when dealing with # derivatives w.r.t. single components... # Figure out how many gradients are around the inner terminal ngrads = 0 o = g while isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): error("Expecting gradient of a FormArgument, not %s" % ufl_err_str(o)) def apply_grads(f): for i in range(ngrads): f = Grad(f) return f # Find o among all w without any indexing, which makes this # easy for (w, v) in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) # If o is not among coefficient derivatives, return do/dw=0 gprimesum = Zero(g.ufl_shape) def analyse_variation_argument(v): # Analyse variation argument if isinstance(v, FormArgument): # Case: d/dt [w[...] + t v] vval, vcomp = v, () elif isinstance(v, Indexed): # Case: d/dt [w + t v[...]] # Case: d/dt [w[...] + t v[...]] vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: error("Expecting argument or component of argument.") if not all(isinstance(k, FixedIndex) for k in vcomp): error("Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, and get the # right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp + kk] # Place scalar component(s) Dvkk into the right tensor # positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj * Dvkk, jj + kk) return gprimeterm # Accumulate contributions from variations in different # components for (w, v) in zip(self._w, self._v): # Analyse differentiation variable coefficient if isinstance(w, FormArgument): if not w == o: continue wshape = w.ufl_shape if isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) elif isinstance(v, ListTensor): # Case: d/dt [w + t <...,v,...>] for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) gprimesum = gprimesum + compute_gprimeterm( ngrads, vval, vcomp, wshape, wcomp) else: if wshape != (): error("Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm( ngrads, vval, vcomp, wshape, wcomp) elif isinstance( w, Indexed ): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands if not wval == o: continue assert isinstance(wval, FormArgument) if not all(isinstance(k, FixedIndex) for k in wcomp): error( "Expecting only fixed indices in differentiation variable." ) wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm( ngrads, vval, vcomp, wshape, wcomp) else: error("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = # self._cd.get(o) if 0: oprimes = self._cd.get(o) if oprimes is None: if self._cd: # TODO: Make it possible to silence this message # in particular? It may be good to have for # debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes, ) if len(oprimes) != len(self._v): error("Got a tuple of arguments, expecting a" " matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs # in a 'mixed' space, sum over them all to get the # complete inner product. Using indices to define a # non-compound inner product. for (oprime, v) in zip(oprimes, self._v): error("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so * v[oi2] if oi1: gprimesum += as_tensor(prod, oi1) else: gprimesum += prod return gprimesum
def expr(self, v, *ops): "For most operators we take the max degree of its operands." warning("Missing degree estimation handler for type %s" % v._ufl_class_.__name__) return self._add_degrees(v, *ops)
def flatten(e): # TODO: Fix or remove! Maybe this works better now with IndexSum marking implicit summations. """Convert an UFL expression to a new UFL expression, with sums and products flattened from binary tree nodes to n-ary tree nodes.""" warning("flatten doesn't work correctly for some indexed products, like (u[i]*v[i])*(q[i]*r[i])") return apply_transformer(e, TreeFlattener())
def expr(self, v, *ops): "For most operators we take the max degree of its operands." warning("Missing degree estimation handler for type %s" % v._uflclass.__name__) return self._add_degrees(v, *ops)
def domain(self): warning("Cell.domain() is deprecated, use cell.cellname() instead.") return self.cellname()
def is_undefined(self): """Return whether this cell is undefined, in which case no dimensions are available.""" warning("cell.is_undefined() is deprecated, undefined cells are no longer allowed.") return False
def canonical_element_description(family, cell, order, form_degree): """Given basic element information, return corresponding element information on canonical form. Input: family, cell, (polynomial) order, form_degree Output: family (canonical), short_name (for printing), order, value shape, reference value shape, sobolev_space. This is used by the FiniteElement constructor to ved input data against the element list and aliases defined in ufl. """ # Get domain dimensions if cell is not None: tdim = cell.topological_dimension() gdim = cell.geometric_dimension() if isinstance(cell, Cell): cellname = cell.cellname() else: cellname = None else: tdim = None gdim = None cellname = None # Catch general FEEC notation "P" and "S" if form_degree is not None and family in ("P", "S"): family, order = feec_element(family, tdim, order, form_degree) if form_degree is not None and family in ("P L2", "S L2"): family, order = feec_element_l2(family, tdim, order, form_degree) # Check whether this family is an alias for something else while family in aliases: if tdim is None: error("Need dimension to handle element aliases.") (family, order) = aliases[family](family, tdim, order, form_degree) # Check that the element family exists if family not in ufl_elements: error('Unknown finite element "%s".' % family) # Check that element data is valid (and also get common family # name) (family, short_name, value_rank, sobolev_space, mapping, krange, cellnames) = ufl_elements[family] # Accept CG/DG on all kind of cells, but use Q/DQ on "product" cells if cellname in set(cubes) - set(simplices) or isinstance(cell, TensorProductCell): if family == "Lagrange": family = "Q" elif family == "Discontinuous Lagrange": if order >= 1: warning("Discontinuous Lagrange element requested on %s, creating DQ element." % cell.cellname()) family = "DQ" elif family == "Discontinuous Lagrange L2": if order >= 1: warning("Discontinuous Lagrange L2 element requested on %s, creating DQ L2 element." % cell.cellname()) family = "DQ L2" # Validate cellname if a valid cell is specified if not (cellname is None or cellname in cellnames): error('Cellname "%s" invalid for "%s" finite element.' % (cellname, family)) # Validate order if specified if order is not None: if krange is None: error('Order "%s" invalid for "%s" finite element, ' 'should be None.' % (order, family)) kmin, kmax = krange if not (kmin is None or (asarray(order) >= kmin).all()): error('Order "%s" invalid for "%s" finite element.' % (order, family)) if not (kmax is None or (asarray(order) <= kmax).all()): error('Order "%s" invalid for "%s" finite element.' % (istr(order), family)) # Override sobolev_space for piecewise constants (TODO: necessary?) if order == 0: sobolev_space = L2 if value_rank == 2: # Tensor valued fundamental elements in HEin have this shape if gdim is None or tdim is None: error("Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim, tdim) value_shape = (gdim, gdim) elif value_rank == 1: # Vector valued fundamental elements in HDiv and HCurl have a shape if gdim is None or tdim is None: error("Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim,) value_shape = (gdim,) elif value_rank == 0: # All other elements are scalar values reference_value_shape = () value_shape = () else: error("Invalid value rank %d." % value_rank) return family, short_name, order, value_shape, reference_value_shape, sobolev_space, mapping
def grad(self, g): # If we hit this type, it has already been propagated to a # coefficient (or grad of a coefficient), # FIXME: Assert # this! so we need to take the gradient of the variation or # return zero. Complications occur when dealing with # derivatives w.r.t. single components... # Figure out how many gradients are around the inner terminal ngrads = 0 o = g while isinstance(o, Grad): o, = o.ufl_operands ngrads += 1 if not isinstance(o, FormArgument): error("Expecting gradient of a FormArgument, not %s" % ufl_err_str(o)) def apply_grads(f): for i in range(ngrads): f = Grad(f) return f # Find o among all w without any indexing, which makes this # easy for (w, v) in zip(self._w, self._v): if o == w and isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) # If o is not among coefficient derivatives, return do/dw=0 gprimesum = Zero(g.ufl_shape) def analyse_variation_argument(v): # Analyse variation argument if isinstance(v, FormArgument): # Case: d/dt [w[...] + t v] vval, vcomp = v, () elif isinstance(v, Indexed): # Case: d/dt [w + t v[...]] # Case: d/dt [w[...] + t v[...]] vval, vcomp = v.ufl_operands vcomp = tuple(vcomp) else: error("Expecting argument or component of argument.") if not all(isinstance(k, FixedIndex) for k in vcomp): error("Expecting only fixed indices in variation.") return vval, vcomp def compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp): # Apply gradients directly to argument vval, and get the # right indexed scalar component(s) kk = indices(ngrads) Dvkk = apply_grads(vval)[vcomp+kk] # Place scalar component(s) Dvkk into the right tensor # positions if wshape: Ejj, jj = unit_indexed_tensor(wshape, wcomp) else: Ejj, jj = 1, () gprimeterm = as_tensor(Ejj*Dvkk, jj+kk) return gprimeterm # Accumulate contributions from variations in different # components for (w, v) in zip(self._w, self._v): # Analyse differentiation variable coefficient if isinstance(w, FormArgument): if not w == o: continue wshape = w.ufl_shape if isinstance(v, FormArgument): # Case: d/dt [w + t v] return apply_grads(v) elif isinstance(v, ListTensor): # Case: d/dt [w + t <...,v,...>] for wcomp, vsub in unwrap_list_tensor(v): if not isinstance(vsub, Zero): vval, vcomp = analyse_variation_argument(vsub) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: if wshape != (): error("Expecting scalar coefficient in this branch.") # Case: d/dt [w + t v[...]] wval, wcomp = w, () vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) elif isinstance(w, Indexed): # This path is tested in unit tests, but not actually used? # Case: d/dt [w[...] + t v[...]] # Case: d/dt [w[...] + t v] wval, wcomp = w.ufl_operands if not wval == o: continue assert isinstance(wval, FormArgument) if not all(isinstance(k, FixedIndex) for k in wcomp): error("Expecting only fixed indices in differentiation variable.") wshape = wval.ufl_shape vval, vcomp = analyse_variation_argument(v) gprimesum = gprimesum + compute_gprimeterm(ngrads, vval, vcomp, wshape, wcomp) else: error("Expecting coefficient or component of coefficient.") # FIXME: Handle other coefficient derivatives: oprimes = # self._cd.get(o) if 0: oprimes = self._cd.get(o) if oprimes is None: if self._cd: # TODO: Make it possible to silence this message # in particular? It may be good to have for # debugging... warning("Assuming d{%s}/d{%s} = 0." % (o, self._w)) else: # Make sure we have a tuple to match the self._v tuple if not isinstance(oprimes, tuple): oprimes = (oprimes,) if len(oprimes) != len(self._v): error("Got a tuple of arguments, expecting a" " matching tuple of coefficient derivatives.") # Compute dg/dw_j = dg/dw_h : v. # Since we may actually have a tuple of oprimes and vs # in a 'mixed' space, sum over them all to get the # complete inner product. Using indices to define a # non-compound inner product. for (oprime, v) in zip(oprimes, self._v): error("FIXME: Figure out how to do this with ngrads") so, oi = as_scalar(oprime) rv = len(v.ufl_shape) oi1 = oi[:-rv] oi2 = oi[-rv:] prod = so*v[oi2] if oi1: gprimesum += as_tensor(prod, oi1) else: gprimesum += prod return gprimesum
def canonical_element_description(family, cell, order, form_degree): """Given basic element information, return corresponding element information on canonical form. Input: family, cell, (polynomial) order, form_degree Output: family (canonical), short_name (for printing), order, value shape, reference value shape, sobolev_space. This is used by the FiniteElement constructor to ved input data against the element list and aliases defined in ufl. """ # Get domain dimensions if cell is not None: tdim = cell.topological_dimension() gdim = cell.geometric_dimension() if isinstance(cell, Cell): cellname = cell.cellname() else: cellname = None else: tdim = None gdim = None cellname = None # Catch general FEEC notation "P" and "S" if form_degree is not None and family in ("P", "S"): family, order = feec_element(family, tdim, order, form_degree) # Check whether this family is an alias for something else while family in aliases: if tdim is None: error("Need dimension to handle element aliases.") (family, order) = aliases[family](family, tdim, order, form_degree) # Check that the element family exists if family not in ufl_elements: error('Unknown finite element "%s".' % family) # Check that element data is valid (and also get common family # name) (family, short_name, value_rank, sobolev_space, mapping, krange, cellnames) = ufl_elements[family] # Accept CG/DG on all kind of cells, but use Q/DQ on "product" cells if cellname in set(cubes) - set(simplices) or isinstance(cell, TensorProductCell): if family == "Lagrange": family = "Q" elif family == "Discontinuous Lagrange": if order >= 1: warning("Discontinuous Lagrange element requested on %s, creating DQ element." % cell.cellname()) family = "DQ" # Validate cellname if a valid cell is specified if not (cellname is None or cellname in cellnames): error('Cellname "%s" invalid for "%s" finite element.' % (cellname, family)) # Validate order if specified if order is not None: if krange is None: error('Order "%s" invalid for "%s" finite element, ' 'should be None.' % (order, family)) kmin, kmax = krange if not (kmin is None or (asarray(order) >= kmin).all()): error('Order "%s" invalid for "%s" finite element.' % (order, family)) if not (kmax is None or (asarray(order) <= kmax).all()): error('Order "%s" invalid for "%s" finite element.' % (istr(order), family)) # Override sobolev_space for piecewise constants (TODO: necessary?) if order == 0: sobolev_space = L2 if value_rank == 2: # Tensor valued fundamental elements in HEin have this shape if gdim is None or tdim is None: error("Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim, tdim) value_shape = (gdim, gdim) elif value_rank == 1: # Vector valued fundamental elements in HDiv and HCurl have a shape if gdim is None or tdim is None: error("Cannot infer shape of element without topological and geometric dimensions.") reference_value_shape = (tdim,) value_shape = (gdim,) elif value_rank == 0: # All other elements are scalar values reference_value_shape = () value_shape = () else: error("Invalid value rank %d." % value_rank) return family, short_name, order, value_shape, reference_value_shape, sobolev_space, mapping