def is_globally_constant(expr): """Check if an expression is globally constant, which includes spatially independent constant coefficients that are not known before assembly time.""" # TODO: This does not consider gradients of coefficients, so false # negatives are possible. # from ufl.argument import Argument # from ufl.coefficient import Coefficient from ufl.geometry import GeometricQuantity from ufl.core.terminal import FormArgument for e in traverse_unique_terminals(expr): # Return False if any single terminal is not constant if e._ufl_is_literal_: # Accept literals first, they are the most common # terminals continue elif isinstance(e, FormArgument): # Accept only Real valued Arguments and Coefficients if e.ufl_element().family() == "Real": continue else: return False elif isinstance(e, GeometricQuantity): # Reject all geometric quantities, they all vary over # cells return False # All terminals passed constant check return True
def _generate_space(expression: Operator): # Extract mesh from expression (from dolfin/fem/projection.py, _extract_function_space function) meshes = set( [ufl_domain.ufl_cargo() for ufl_domain in extract_domains(expression)]) for t in traverse_unique_terminals( expression): # from ufl/domain.py, extract_domains if hasattr(t, "_mesh"): meshes.add(t._mesh) assert len(meshes) == 1 mesh = meshes.pop() # The EIM algorithm will evaluate the expression at vertices. However, since the Operator expression may # contain e.g. a gradient of a solution defined in a C^0 space, we resort to DG1 spaces. shape = expression.ufl_shape assert len(shape) in (0, 1, 2) if len(shape) == 0: space = FunctionSpace(mesh, "Discontinuous Lagrange", 1) elif len(shape) == 1: space = VectorFunctionSpace(mesh, "Discontinuous Lagrange", 1, dim=shape[0]) elif len(shape) == 2: space = TensorFunctionSpace(mesh, "Discontinuous Lagrange", 1, shape=shape) else: raise ValueError( "Invalid expression in ParametrizedExpressionFactory.__init__().") return space
def _extract_arguments(form): # This is a copy of extract_type in ufl.algorithms.analysis # without wrapping the result in a set return [ o for e in iter_expressions(form) for o in traverse_unique_terminals(e) if isinstance(o, Argument) ]
def __init__(self, expr, locations, mesh=None): # Extract mesh from one of the arguments if mesh is None: for arg in traverse_unique_terminals(expr): print arg if isinstance(arg, Function): mesh = arg.function_space().mesh() break assert mesh is not None expr = icompile(expr) size = expr.ufl_element().value_size() limit = mesh.num_entities(mesh.topology().dim()) bbox_tree = mesh.bounding_box_tree() evals = [] for x in locations: cell = bbox_tree.compute_first_entity_collision(Point(*x)) if -1 < cell < limit: foo = lambda x=x, expr=expr: (expr)(x) else: foo = lambda size=size: (np.finfo(float).max) * np.ones(size) evals.append(foo) self.probes = evals # Return the value in the shape of vector/matrix self.nprobes = len(locations)
def _basic_expression_name(expression): str_repr = "" coefficients_replacement = dict() # Preprocess indices first, as their numeric value might change from run to run, but they # are always sorted the same way indices = set() min_index = None for t in traverse_unique_terminals(expression): if isinstance(t, MultiIndex): for i in t.indices(): if isinstance(i, MuteIndex): if min_index is None or i.count() < min_index: min_index = i.count() indices.add(i) for i in indices: coefficients_replacement[repr(i)] = "MuteIndexRBniCS(" + str(i.count() - min_index) + ")" # Process the expression visited = set() for n in wrapping.expression_iterator(expression): if n in visited: continue if hasattr(n, "_cppcode"): coefficients_replacement[repr(n)] = str(n._cppcode) str_repr += repr(n._cppcode) visited.add(n) elif wrapping.is_problem_solution_type(n): if wrapping.is_problem_solution(n): (preprocessed_n, component, truth_solution) = wrapping.solution_identify_component(n) problem = get_problem_from_solution(truth_solution) coefficients_replacement[repr(preprocessed_n)] = "solution of " + str(problem.name()) + " (exact problem decorator: " + str(hasattr(problem, "__is_exact__")) + ", component: " + str(component) + ")" elif wrapping.is_problem_solution_dot(n): (preprocessed_n, component, truth_solution_dot) = wrapping.solution_dot_identify_component(n) problem = get_problem_from_solution_dot(truth_solution_dot) coefficients_replacement[repr(preprocessed_n)] = "solution_dot of " + str(problem.name()) + " (exact problem decorator: " + str(hasattr(problem, "__is_exact__")) + ", component: " + str(component) + ")" else: (preprocessed_n, component, problem) = wrapping.get_auxiliary_problem_for_non_parametrized_function(n) coefficients_replacement[repr(preprocessed_n)] = "non parametrized function associated to auxiliary problem " + str(problem.name()) if len(component) == 1 and component[0] is not None: coefficients_replacement[repr(preprocessed_n)] += ", component " + str(component[0]) elif len(component) > 1: coefficients_replacement[repr(preprocessed_n)] += ", component " + str(component) str_repr += coefficients_replacement[repr(preprocessed_n)] # Make sure to skip any parent solution related to this one visited.add(n) visited.add(preprocessed_n) for parent_n in wrapping.solution_iterator(preprocessed_n): visited.add(parent_n) elif isinstance(n, Constant): vals = n.values() coefficients_replacement[repr(n)] = str(vals) str_repr += repr(str(vals)) visited.add(n) else: str_repr += repr(n) visited.add(n) for key, value in coefficients_replacement.items(): str_repr = str_repr.replace(key, value) hash_code = hashlib.sha1(str_repr.encode("utf-8")).hexdigest() return hash_code
def nonlinear_operator(self, o): # Cutoff traversal by not having *ops in argument list of this # handler. Traverse only the terminals under here the fastest # way we know of: for t in traverse_unique_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: raise ArityMismatch("Applying nonlinear operator {0} to expression depending on form argument {1}.".format(o._ufl_class_.__name__, t)) return self._et
def is_extension_integrand(expr, tdim): '''Some of the arguments need extension''' if tdim == 2: return any((topological_dim(arg)+1) == tdim for arg in traverse_unique_terminals(expr)) # Line extends to line top_crit = any(topological_dim(arg) == tdim for arg in traverse_unique_terminals(expr)) # This is not quite enough because a regular fenics integral might be # like this if the meshes of the terminals are the same. So we check # for difference mesh_ids = set(t.ufl_domain().ufl_cargo().id() for t in traverse_unique_terminals(expr) if topological_dim(t) == tdim) return top_crit and len(mesh_ids) > 1
def compute_terminal_hashdata(expressions, renumbering): if not isinstance(expressions, list): expressions = [expressions] assert renumbering is not None # Extract a unique numbering of free indices, as well as form # arguments, and just take repr of the rest of the terminals while # we're iterating over them terminal_hashdata = {} labels = {} index_numbering = {} for expression in expressions: for expr in traverse_unique_terminals(expression): if isinstance(expr, MultiIndex): # Indices need a canonical numbering for a stable # signature, thus this algorithm data = compute_multiindex_hashdata(expr, index_numbering) elif isinstance(expr, ConstantValue): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Coefficient): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Constant): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Argument): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, GeometricQuantity): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Label): # Numbering labels as we visit them # TODO: Include in # renumbering data = labels.get(expr) if data is None: data = "L%d" % len(labels) labels[expr] = data elif isinstance(expr, ExprList): # Not really a terminal but can have 0 operands... data = "[]" elif isinstance(expr, ExprMapping): # Not really a terminal but can have 0 operands... data = "{}" else: error("Unknown terminal type %s" % type(expr)) terminal_hashdata[expr] = data return terminal_hashdata
def nonlinear_operator(self, o): # Cutoff traversal by not having *ops in argument list of this # handler. Traverse only the terminals under here the fastest # way we know of: for t in traverse_unique_terminals(o): if t._ufl_typecode_ == Argument._ufl_typecode_: raise ArityMismatch( "Applying nonlinear operator {0} to expression depending on form argument {1}." .format(o._ufl_class_.__name__, t)) return self._et
def compute_terminal_hashdata(expressions, renumbering): if not isinstance(expressions, list): expressions = [expressions] assert renumbering is not None # Extract a unique numbering of free indices, as well as form # arguments, and just take repr of the rest of the terminals while # we're iterating over them terminal_hashdata = {} labels = {} index_numbering = {} for expression in expressions: for expr in traverse_unique_terminals(expression): if isinstance(expr, MultiIndex): # Indices need a canonical numbering for a stable # signature, thus this algorithm data = compute_multiindex_hashdata(expr, index_numbering) elif isinstance(expr, ConstantValue): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Coefficient): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Argument): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, GeometricQuantity): data = expr._ufl_signature_data_(renumbering) elif isinstance(expr, Label): # Numbering labels as we visit them # TODO: Include in # renumbering data = labels.get(expr) if data is None: data = "L%d" % len(labels) labels[expr] = data elif isinstance(expr, ExprList): # Not really a terminal but can have 0 operands... data = "[]" elif isinstance(expr, ExprMapping): # Not really a terminal but can have 0 operands... data = "{}" else: error("Unknown terminal type %s" % type(expr)) terminal_hashdata[expr] = data return terminal_hashdata
def extract_type(a, ufl_type): """Build a set of all objects of class ufl_type found in a. The argument a can be a Form, Integral or Expr.""" if issubclass(ufl_type, Terminal): # Optimization return set(o for e in iter_expressions(a) for o in traverse_unique_terminals(e) if isinstance(o, ufl_type)) else: return set(o for e in iter_expressions(a) for o in unique_pre_traversal(e) if isinstance(o, ufl_type))
def space_for(expr, shape=None): '''Function space where expr should be represented''' # Don't want to call eval here as it beats the goal of being lazy foos = filter(lambda f: isinstance(f, Function), traverse_unique_terminals(expr)) _, = set(f.function_space().mesh().id() for f in foos) elm = common_sub_element([f.function_space() for f in foos]) shape = expr.ufl_shape if shape is None else shape mesh = foos[0].function_space().mesh() return make_space(elm, shape, mesh)
def _check_timederiv(o: TimeDerivative, self: Memoizer) -> Result: op, = o.ufl_operands if self(op): # op already has a TimeDerivative applied to it raise ValueError("Can only handle first-order systems") terminals = tuple( set([ x for x in traverse_unique_terminals(op) if not isinstance(x, MultiIndex) ])) if len(terminals) != 1 or not isinstance(terminals[0], Coefficient): raise ValueError("Time derivative must apply to a single coefficient") return terminals
def _check_facet_geometry(integral_data): for itg_data in integral_data: for itg in itg_data.integrals: it = itg_data.integral_type # Facet geometry is only valid in facet integrals. # Allowing custom integrals to pass as well, although # that's not really strict enough. if not ("facet" in it or "custom" in it or "interface" in it): # Not a facet integral for expr in traverse_unique_terminals(itg.integrand()): cls = expr._ufl_class_ if issubclass(cls, GeometricFacetQuantity): error("Integral of type %s cannot contain a %s." % (it, cls.__name__))
def __init__(self, func, other): super().__init__() self.other = None self.expr = None if isinstance(other, float) or isinstance(other, int): other = AdjFloat(other) if isinstance(other, OverloadedType): self.add_dependency(other, no_duplicates=True) elif not (isinstance(other, float) or isinstance(other, int)): # Assume that this is a point-wise evaluated UFL expression (firedrake only) for op in traverse_unique_terminals(other): if isinstance(op, OverloadedType): self.add_dependency(op, no_duplicates=True) self.expr = other
def series_rule(expr): '''Eval expression where the terminals are time series''' # Make first sure that the series are compatible in the sense # of having same f and time interval times = timeseries.common_interval(list(traverse_unique_terminals(expr))) assert len(times) series = map(Interpreter.eval, expr.ufl_operands) # We apply the op to functions in the series and construct a new one args = izip(*[s if isinstance(s, timeseries.TempSeries) else repeat(Interpreter.eval(s)) for s in series]) functions = [Interpreter.eval(apply(type(expr), arg)) for arg in args] return timeseries.TempSeries(zip(functions, times))
def find_geometric_dimension(expr): "Find the geometric dimension of an expression." gdims = set() for t in traverse_unique_terminals(expr): if hasattr(t, "ufl_domain"): domain = t.ufl_domain() if domain is not None: gdims.add(domain.geometric_dimension()) if hasattr(t, "ufl_element"): element = t.ufl_element() if element is not None: cell = element.cell() if cell is not None: gdims.add(cell.geometric_dimension()) if len(gdims) != 1: error("Cannot determine geometric dimension from expression.") gdim, = gdims return gdim
def is_injection_integral(integral): ''' Cell integral over domain that is that over domain that has child ''' if not integral.integral_type() == 'cell': return False fmesh = integral.ufl_domain().ufl_cargo() if not is_injection_integrand(integral.integrand()): return False for arg in filter(is_injection, traverse_unique_terminals(integral.integrand())): if arg.injection_['mesh'].id() == fmesh.id(): return True return False
def is_restriction_integral(integral): ''' A restriction integral here is: 1) the measure is defined over a subdomain mesh 2) in the parent map of the mesh we find a mesh of at least on argument of the integrand ''' # Domain of the measure restriction_domain = integral.ufl_domain().ufl_cargo() if not isinstance(restriction_domain, SubDomainMesh): return False mapping = restriction_domain.parent_entity_map for t in traverse_unique_terminals(integral.integrand()): # Of argument domain = t.ufl_domain() if domain is not None: mesh = domain.ufl_cargo() if mesh.id() in mapping: return True return False
def eval(expr): # Terminals/base cases (also TempSeries) if isinstance(expr, Interpreter.terminal_type): return expr if isinstance(expr, Interpreter.value_type): return expr.value() # Okay: now we have expr with arguments. If this expression involves # times series then all the non number arguments should be compatible # time series terminals = filter(lambda t: isinstance(t, Function), traverse_unique_terminals(expr)) # Don't mix function and terminals series = filter(lambda t: isinstance(t, timeseries.TempSeries), terminals) assert len(series) == len(terminals) or len(series) == 0, map(len, (series, terminals)) # For series, we apply op to functions and make new series if series: assert not isinstance(expr, Interpreter.index_type) return series_rule(expr) expr_type = type(expr) # Require reshaping and all args are functions if expr_type in Interpreter.reshape_type: return numpy_reshaped(expr, op=Interpreter.reshape_type[expr_type]) # Indexing [] is special as the second argument gives slicing if isinstance(expr, Interpreter.index_type): return indexed_rule(expr) # No reshaping neeed op = Interpreter.no_reshape_type[expr_type] # Throw if we don't support this args = map(Interpreter.eval, expr.ufl_operands) # Manipulate coefs of arguments to get coefs of the expression coefs = map(coefs_of, args) V_coefs = op(*coefs) # Make that function V = space_of(args) return make_function(V, V_coefs)
def series_rule(expr): '''Eval expression where the terminals are time series''' foos = filter(lambda f: isinstance(f, Function), traverse_unique_terminals(expr)) # Make first sure that the series are compatible in the sense of having same time # interval times = timeseries.common_interval(foos) assert len(times) # Compatibility of spaces common_sub_element([f.function_space() for f in foos]) # The idea now is to propagate the expression by which I mean that # we grow the expr using nodes in the series def unpack(expr): '''expr -> iterable of expr''' return (apply(type(expr), sum(args, ())) for args in expand(expr.ufl_operands)) def expand(operands): iterators = [] for o in operands: if isinstance(o, timeseries.TempSeries): iterators.append(((f, ) for f in o)) # Nonseries terminal elif not o.ufl_operands: iterators.append(((f, ) for f in repeat(o))) # An expression else: iterators.append(((f, ) for f in unpack(o))) return izip(*iterators) nodes = unpack(expr) # A series of new nodes -> series of functions return Interpreter.eval(timeseries.TempSeries(zip(nodes, times)))
def assemble(self, form, arity): '''Assemble a biliner(2), linear(1) form''' reduced_integrals = self.select_integrals(form) #! Selector # Signal to xii.assemble if not reduced_integrals: return None components = [] for integral in form.integrals(): # Delegate to friend if integral not in reduced_integrals: components.append( xii.assembler.xii_assembly.assemble(Form([integral]))) continue reduced_mesh = integral.ufl_domain().ufl_cargo() integrand = integral.integrand() # Split arguments in those that need to be and those that are # already restricted. terminals = set(traverse_unique_terminals(integrand)) # FIXME: is it enough info (in general) to decide terminals_to_restrict = sorted( self.restriction_filter(terminals, reduced_mesh), key=lambda t: self.is_compatible(t, reduced_mesh)) # You said this is a trace ingral! assert terminals_to_restrict # Let's pick a guy for restriction terminal = terminals_to_restrict.pop() # We have some assumption on the candidate assert self.is_compatible(terminal, reduced_mesh) data = self.reduction_matrix_data(terminal) integrand = ufl2uflcopy(integrand) # With sane inputs we can get the reduced element and setup the # intermediate function space where the reduction of terminal # lives V = terminal.function_space() TV = self.reduced_space(V, reduced_mesh) #! Space construc # Setup the matrix to from space of the trace_terminal to the # intermediate space. FIXME: normal and trace_mesh #! mat construct df.info('\tGetting reduction op') rop_timer = df.Timer('rop') T = self.reduction_matrix(V, TV, reduced_mesh, data) df.info('\tDone (reduction op) %g' % rop_timer.stop()) # T if is_test_function(terminal): replacement = df.TestFunction(TV) # Passing the args to get the comparison a make substitution integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) if arity == 2: # Make attempt on the substituted form A = xii.assembler.xii_assembly.assemble(trace_form) components.append(block_transpose(T) * A) else: b = xii.assembler.xii_assembly.assemble(trace_form) Tb = df.Function(V).vector() # Alloc and apply T.transpmult(b, Tb) components.append(Tb) if is_trial_function(terminal): assert arity == 2 replacement = df.TrialFunction(TV) # Passing the args to get the comparison integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) A = xii.assembler.xii_assembly.assemble(trace_form) components.append(A * T) # Okay, then this guy might be a function if isinstance(terminal, df.Function): replacement = df.Function(TV) # Replacement is not just a placeholder T.mult(terminal.vector(), replacement.vector()) # Substitute integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) components.append( xii.assembler.xii_assembly.assemble(trace_form)) # The whole form is then the sum of integrals return reduce(operator.add, components)
def is_trace_integrand(expr, tdim): '''Some of the arguments need restriction''' return any((topological_dim(arg)-1) == tdim for arg in traverse_unique_terminals(expr))
def validate_form( form ): # TODO: Can we make this return a list of errors instead of raising exception? """Performs all implemented validations on a form. Raises exception if something fails.""" errors = [] if not isinstance(form, Form): msg = "Validation failed, not a Form:\n%s" % ufl_err_str(form) error(msg) # errors.append(msg) # return errors # FIXME: There's a bunch of other checks we should do here. # FIXME: Add back check for multilinearity # Check that form is multilinear # if not is_multilinear(form): # errors.append("Form is not multilinear in arguments.") # FIXME DOMAIN: Add check for consistency between domains somehow domains = set(t.ufl_domain() for e in iter_expressions(form) for t in traverse_unique_terminals(e)) - {None} if not domains: errors.append("Missing domain definition in form.") # Check that cell is the same everywhere cells = set(dom.ufl_cell() for dom in domains) - {None} if not cells: errors.append("Missing cell definition in form.") elif len(cells) > 1: errors.append("Multiple cell definitions in form: %s" % str(cells)) # Check that no Coefficient or Argument instance have the same # count unless they are the same coefficients = {} arguments = {} for e in iter_expressions(form): for f in traverse_unique_terminals(e): if isinstance(f, Coefficient): c = f.count() if c in coefficients: g = coefficients[c] if f is not g: errors.append("Found different Coefficients with " + "same count: %s and %s." % (repr(f), repr(g))) else: coefficients[c] = f elif isinstance(f, Argument): n = f.number() p = f.part() if (n, p) in arguments: g = arguments[(n, p)] if f is not g: if n == 0: msg = "TestFunctions" elif n == 1: msg = "TrialFunctions" else: msg = "Arguments with same number and part" msg = "Found different %s: %s and %s." % (msg, repr(f), repr(g)) errors.append(msg) else: arguments[(n, p)] = f # Check that all integrands are scalar for expression in iter_expressions(form): if not is_true_ufl_scalar(expression): errors.append("Found non-scalar integrand expression: %s\n" % ufl_err_str(expression)) # Check that restrictions are permissible for integral in form.integrals(): # Only allow restrictions on interior facet integrals and # surface measures if integral.integral_type().startswith("interior_facet"): check_restrictions(integral.integrand(), True) else: check_restrictions(integral.integrand(), False) # Raise exception with all error messages # TODO: Return errors list instead, need to collect messages from # all validations above first. if errors: final_msg = 'Found errors in validation of form:\n%s' % '\n\n'.join( errors) error(final_msg)
def extract_domains(expr): "Return all domains expression is defined on." domainlist = [] for t in traverse_unique_terminals(expr): domainlist.extend(t.ufl_domains()) return sorted(join_domains(domainlist))
def eval(expr): # Guys with their own logic for collapsing into functions # Okay we combine 2 design patters, LazyNodes do it themselves # series rely on the interpreter if isinstance(expr, operators.LazyNode): return expr.evaluate() # For series we eval each node and make a series of functions # NOTE: intersept here because TempSeries is a terminal type if isinstance(expr, timeseries.TempSeries): return timeseries.TempSeries( zip(map(Interpreter.eval, expr), expr.times)) # Terminals/base cases (also TempSeries) -> identity if isinstance(expr, Interpreter.terminal_type): return expr # To number if isinstance(expr, Interpreter.value_type): return expr.value() # To number if isinstance(expr, Constant): return float(expr) # To number if isinstance(expr, ufl.constantvalue.Zero): return 0 # Recast spatial coordinate as CG1 functions if isinstance(expr, ufl.geometry.SpatialCoordinate): mesh = expr.ufl_domain().ufl_cargo() r = Expression(('x[0]', 'x[1]', 'x[2]')[:mesh.geometry().dim()], degree=1) return interpolate(r, VectorFunctionSpace(mesh, 'CG', 1)) # Okay: now we have expr with arguments. If this expression involves # times series then all the non number arguments should be compatible # time series terminals = filter(lambda t: isinstance(t, Function), traverse_unique_terminals(expr)) # Don't mix function and terminals series = filter(lambda t: isinstance(t, timeseries.TempSeries), terminals) assert len(series) == len(terminals) or len(series) == 0, map( type, terminals) # For series, we apply op to functions and make new series if series: return series_rule(expr) expr_type = type(expr) # Require reshaping and all args are functions if expr_type in Interpreter.reshape_type: return numpy_reshaped(expr, op=Interpreter.reshape_type[expr_type]) # Clement if expr_type in Interpreter.diff_type: # NOTE: Clement is its own thing-it does not use this interpreter # for subexpression evaluation return clement_interpolate(expr) # Define tensor by componenents if isinstance(expr, Interpreter.compose_type): return component_tensor_rule(expr) # A indexed by FixedIndex or Index if isinstance(expr, Interpreter.index_type): return indexed_rule(expr) # No reshaping neeed op = Interpreter.no_reshape_type[ expr_type] # Throw if we don't support this args = map(Interpreter.eval, expr.ufl_operands) # Manipulate coefs of arguments to get coefs of the expression coefs = map(coefs_of, args) V_coefs = op(*coefs) # Make that function V = space_of(args) return make_function(V, V_coefs)
def is_cellwise_constant(expr): "Return whether expression is constant over a single cell." # TODO: Implement more accurately considering e.g. derivatives? return all(t.is_cellwise_constant() for t in traverse_unique_terminals(expr))
def is_point_trace_integral(integral): '''A point trace integral is one where some argument is a point trace.''' return any(hasattr(t, 'dirac_') for t in traverse_unique_terminals(integral.integrand()))
def validate_form(form): # TODO: Can we make this return a list of errors instead of raising exception? """Performs all implemented validations on a form. Raises exception if something fails.""" errors = [] if not isinstance(form, Form): msg = "Validation failed, not a Form:\n%s" % ufl_err_str(form) error(msg) # errors.append(msg) # return errors # FIXME: There's a bunch of other checks we should do here. # FIXME: Add back check for multilinearity # Check that form is multilinear # if not is_multilinear(form): # errors.append("Form is not multilinear in arguments.") # FIXME DOMAIN: Add check for consistency between domains somehow domains = set(t.ufl_domain() for e in iter_expressions(form) for t in traverse_unique_terminals(e)) - {None} if not domains: errors.append("Missing domain definition in form.") # Check that cell is the same everywhere cells = set(dom.ufl_cell() for dom in domains) - {None} if not cells: errors.append("Missing cell definition in form.") elif len(cells) > 1: errors.append("Multiple cell definitions in form: %s" % str(cells)) # Check that no Coefficient or Argument instance have the same # count unless they are the same coefficients = {} arguments = {} for e in iter_expressions(form): for f in traverse_unique_terminals(e): if isinstance(f, Coefficient): c = f.count() if c in coefficients: g = coefficients[c] if f is not g: errors.append("Found different Coefficients with " + "same count: %s and %s." % (repr(f), repr(g))) else: coefficients[c] = f elif isinstance(f, Argument): n = f.number() p = f.part() if (n, p) in arguments: g = arguments[(n, p)] if f is not g: if n == 0: msg = "TestFunctions" elif n == 1: msg = "TrialFunctions" else: msg = "Arguments with same number and part" msg = "Found different %s: %s and %s." % (msg, repr(f), repr(g)) errors.append(msg) else: arguments[(n, p)] = f # Check that all integrands are scalar for expression in iter_expressions(form): if not is_true_ufl_scalar(expression): errors.append("Found non-scalar integrand expression: %s\n" % ufl_err_str(expression)) # Check that restrictions are permissible for integral in form.integrals(): # Only allow restrictions on interior facet integrals and # surface measures if integral.integral_type().startswith("interior_facet"): check_restrictions(integral.integrand(), True) else: check_restrictions(integral.integrand(), False) # Raise exception with all error messages # TODO: Return errors list instead, need to collect messages from # all validations above first. if errors: final_msg = 'Found errors in validation of form:\n%s' % '\n\n'.join(errors) error(final_msg)
def is_restriction_integrand(expr): '''Is it?''' return any((is_restriction(arg) for arg in traverse_unique_terminals(expr)))
def is_average_integrand(expr, tdim): '''Some of the arguments need restriction''' return any((topological_dim(arg) == tdim + 2) and isinstance(arg, Argument) for arg in traverse_unique_terminals(expr))
def assemble(self, form, arity): '''Assemble a biliner(2), linear(1) form''' reduced_integrals = self.select_integrals(form) #! Selector # Signal to xii.assemble if not reduced_integrals: return None components = [] for integral in form.integrals(): # Delegate to friend if integral not in reduced_integrals: components.append(xii.assembler.xii_assembly.assemble(Form([integral]))) continue reduced_mesh = integral.ufl_domain().ufl_cargo() integrand = integral.integrand() # Split arguments in those that need to be and those that are # already restricted. terminals = set(traverse_unique_terminals(integrand)) # FIXME: is it enough info (in general) to decide terminals_to_restrict = self.restriction_filter(terminals, reduced_mesh) # You said this is a trace ingral! assert terminals_to_restrict # Let's pick a guy for restriction terminal = terminals_to_restrict.pop() # We have some assumption on the candidate assert self.is_compatible(terminal, reduced_mesh) data = self.reduction_matrix_data(terminal) integrand = ufl2uflcopy(integrand) # With sane inputs we can get the reduced element and setup the # intermediate function space where the reduction of terminal # lives V = terminal.function_space() TV = self.reduced_space(V, reduced_mesh) #! Space construc # Setup the matrix to from space of the trace_terminal to the # intermediate space. FIXME: normal and trace_mesh #! mat construct T = self.reduction_matrix(V, TV, reduced_mesh, data) # T if is_test_function(terminal): replacement = df.TestFunction(TV) # Passing the args to get the comparison a make substitution integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) if arity == 2: # Make attempt on the substituted form A = xii.assembler.xii_assembly.assemble(trace_form) components.append(block_transpose(T)*A) else: b = xii.assembler.xii_assembly.assemble(trace_form) Tb = df.Function(V).vector() # Alloc and apply T.transpmult(b, Tb) components.append(Tb) if is_trial_function(terminal): assert arity == 2 replacement = df.TrialFunction(TV) # Passing the args to get the comparison integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) A = xii.assembler.xii_assembly.assemble(trace_form) components.append(A*T) # Okay, then this guy might be a function if isinstance(terminal, df.Function): replacement = df.Function(TV) # Replacement is not just a placeholder T.mult(terminal.vector(), replacement.vector()) # Substitute integrand = replace(integrand, terminal, replacement, attributes=self.attributes) trace_form = Form([integral.reconstruct(integrand=integrand)]) components.append(xii.assembler.xii_assembly.assemble(trace_form)) # The whole form is then the sum of integrals return reduce(operator.add, components)
def is_trace_integrand(expr, tdim): '''Some of the arguments need restriction''' return any((topological_dim(arg) - 1) == tdim for arg in traverse_unique_terminals(expr))