def power(self, o, fp, gp): f, g = o.ufl_operands if not is_true_ufl_scalar(f): error("Expecting scalar expression f in f**g.") if not is_true_ufl_scalar(g): error("Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) # do/df = g * f**(g-1) = g / f * o # do/dg = ln(f) * f**g = ln(f) * o # do/df * df + do/dg * dg = o * (g / f * df + ln(f) * dg) if isinstance(gp, Zero): # This probably produces better results for the common # case of f**constant op = fp * g * f**(g-1) else: # Note: This produces expressions like (1/w)*w**5 instead of w**4 # op = o * (fp * g / f + gp * ln(f)) # This reuses o op = f**(g-1) * (g*fp + f*ln(f)*gp) # This gives better accuracy in dolfin integration test # Example: d/dx[x**(x**3)]: # f = x # g = x**3 # df = 1 # dg = 3*x**2 # op1 = o * (fp * g / f + gp * ln(f)) # = x**(x**3) * (x**3/x + 3*x**2*ln(x)) # op2 = f**(g-1) * (g*fp + f*ln(f)*gp) # = x**(x**3-1) * (x**3 + x*3*x**2*ln(x)) return op
def power(self, o, fp, gp): f, g = o.ufl_operands if not is_true_ufl_scalar(f): error("Expecting scalar expression f in f**g.") if not is_true_ufl_scalar(g): error("Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) # do/df = g * f**(g-1) = g / f * o # do/dg = ln(f) * f**g = ln(f) * o # do/df * df + do/dg * dg = o * (g / f * df + ln(f) * dg) if isinstance(gp, Zero): # This probably produces better results for the common # case of f**constant op = fp * g * f**(g - 1) else: # Note: This produces expressions like (1/w)*w**5 instead of w**4 # op = o * (fp * g / f + gp * ln(f)) # This reuses o op = f**(g - 1) * ( g * fp + f * ln(f) * gp ) # This gives better accuracy in dolfin integration test # Example: d/dx[x**(x**3)]: # f = x # g = x**3 # df = 1 # dg = 3*x**2 # op1 = o * (fp * g / f + gp * ln(f)) # = x**(x**3) * (x**3/x + 3*x**2*ln(x)) # op2 = f**(g-1) * (g*fp + f*ln(f)*gp) # = x**(x**3-1) * (x**3 + x*3*x**2*ln(x)) return op
def __init__(self, arg1, arg2): Operator.__init__(self) ufl_assert(is_true_ufl_scalar(arg1), "Expecting scalar argument 1.") ufl_assert(is_true_ufl_scalar(arg2), "Expecting scalar argument 2.") self._name = "atan_2" self._arg1 = arg1 self._arg2 = arg2
def __init__(self, arg1, arg2): Operator.__init__(self, (arg1, arg2)) if isinstance(arg1, (ComplexValue, complex)) or isinstance(arg2, (ComplexValue, complex)): raise TypeError("Atan2 does not support complex numbers.") if not is_true_ufl_scalar(arg1): error("Expecting scalar argument 1.") if not is_true_ufl_scalar(arg2): error("Expecting scalar argument 2.")
def __init__(self, arg1, arg2): Operator.__init__(self, (arg1, arg2)) if isinstance(arg1, (ComplexValue, complex)) or isinstance( arg2, (ComplexValue, complex)): raise TypeError("Atan2 does not support complex numbers.") if not is_true_ufl_scalar(arg1): error("Expecting scalar argument 1.") if not is_true_ufl_scalar(arg2): error("Expecting scalar argument 2.")
def __init__(self, name, classname, nu, argument): Operator.__init__(self) ufl_assert(is_true_ufl_scalar(nu), "Expecting scalar nu.") ufl_assert(is_true_ufl_scalar(argument), "Expecting scalar argument.") fnu = float(nu) inu = int(nu) if fnu == inu: nu = as_ufl(inu) else: nu = as_ufl(fnu) self._classname = classname self._name = name self._nu = nu self._argument = argument
def __new__(cls, a, b): a = as_ufl(a) b = as_ufl(b) # Assertions # TODO: Enabled workaround for nonscalar division in __div__, # so maybe we can keep this assertion. Some algorithms may need updating. if not is_ufl_scalar(a): error("Expecting scalar nominator in Division.") if not is_true_ufl_scalar(b): error("Division by non-scalar is undefined.") if isinstance(b, Zero): error("Division by zero!") # Simplification a/b -> a if isinstance(a, Zero) or b == 1: return a # Simplification "literal a / literal b" -> "literal value of a/b" # Avoiding integer division by casting to float if isinstance(a, ScalarValue) and isinstance(b, ScalarValue): return as_ufl(float(a._value) / float(b._value)) # Simplification "a / a" -> "1" if not a.free_indices() and not a.shape() and a == b: return as_ufl(1) # construct and initialize a new Division object self = AlgebraOperator.__new__(cls) self._init(a, b) return self
def __init__(self, name, classname, nu, argument): if not is_true_ufl_scalar(nu): error("Expecting scalar nu.") if not is_true_ufl_scalar(argument): error("Expecting scalar argument.") # Use integer representation if suitable fnu = float(nu) inu = int(nu) if fnu == inu: nu = as_ufl(inu) else: nu = as_ufl(fnu) Operator.__init__(self, (nu, argument)) self._classname = classname self._name = name
def sensitivity_rhs(a, u, L, v): """UFL form operator: Compute the right hand side for a sensitivity calculation system. The derivation behind this computation is as follows. Assume a, L to be bilinear and linear forms corresponding to the assembled linear system Ax = b. Where x is the vector of the discrete function corresponding to u. Let v be some scalar variable this equation depends on. Then we can write 0 = d/dv[Ax-b] = dA/dv x + A dx/dv - db/dv, A dx/dv = db/dv - dA/dv x, and solve this system for dx/dv, using the same bilinear form a and matrix A from the original system. Assume the forms are written v = variable(v_expression) L = IL(v)*dx a = Ia(v)*dx where IL and Ia are integrand expressions. Define a Coefficient u representing the solution to the equations. Then we can compute db/dv and dA/dv from the forms da = diff(a, v) dL = diff(L, v) and the action of da on u by dau = action(da, u) In total, we can build the right hand side of the system to compute du/dv with the single line dL = diff(L, v) - action(diff(a, v), u) or, using this function dL = sensitivity_rhs(a, u, L, v) """ msg = "Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable)." ufl_assert(isinstance(a, Form), msg) ufl_assert(isinstance(u, Coefficient), msg) ufl_assert(isinstance(L, Form), msg) ufl_assert(isinstance(v, Variable), msg) ufl_assert(is_true_ufl_scalar(v), "Expecting scalar variable.") from ufl.operators import diff return diff(L, v) - action(diff(a, v), u)
def power(self, o, a, b): f, fp = a g, gp = b # Debugging prints, should never happen: if not is_true_ufl_scalar(f): print ":" * 80 print "f =", str(f) print "g =", str(g) print ":" * 80 ufl_assert(is_true_ufl_scalar(f), "Expecting scalar expression f in f**g.") ufl_assert(is_true_ufl_scalar(g), "Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) # #do_df = g * f**(g-1) #do_dg = ln(f) * f**g #op = do_df*fp + do_dg*gp # #do_df = o * g / f # f**g * g / f #do_dg = ln(f) * o #op = do_df*fp + do_dg*gp # Got two possible alternatives here: if True: # This version looks better. # Rewriting o as f*f**(g-1) we can do: f_g_m1 = f**(g - 1) op = f_g_m1 * (fp * g + f * ln(f) * gp) # In this case we can rewrite o using new subexpression o = f * f_g_m1 else: # Pulling o out gives: op = o * (fp * g / f + ln(f) * gp) # This produces expressions like (1/w)*w**5 instead of w**4 # If we do this, we reuse o o = self.reuse_if_possible(o, f, g) return (o, op)
def power(self, o, a, b): f, fp = a g, gp = b # Debugging prints, should never happen: if not is_true_ufl_scalar(f): print ":"*80 print "f =", str(f) print "g =", str(g) print ":"*80 ufl_assert(is_true_ufl_scalar(f), "Expecting scalar expression f in f**g.") ufl_assert(is_true_ufl_scalar(g), "Expecting scalar expression g in f**g.") # Derivation of the general case: o = f(x)**g(x) # #do_df = g * f**(g-1) #do_dg = ln(f) * f**g #op = do_df*fp + do_dg*gp # #do_df = o * g / f # f**g * g / f #do_dg = ln(f) * o #op = do_df*fp + do_dg*gp # Got two possible alternatives here: if True: # This version looks better. # Rewriting o as f*f**(g-1) we can do: f_g_m1 = f**(g-1) op = f_g_m1*(fp*g + f*ln(f)*gp) # In this case we can rewrite o using new subexpression o = f*f_g_m1 else: # Pulling o out gives: op = o*(fp*g/f + ln(f)*gp) # This produces expressions like (1/w)*w**5 instead of w**4 # If we do this, we reuse o o = self.reuse_if_possible(o, f, g) return (o, op)
def __new__(cls, a, b): a = as_ufl(a) b = as_ufl(b) if not is_true_ufl_scalar(a): error("Cannot take the power of a non-scalar expression.") if not is_true_ufl_scalar(b): error("Cannot raise an expression to a non-scalar power.") if isinstance(a, ScalarValue) and isinstance(b, ScalarValue): return as_ufl(a._value ** b._value) if a == 0 and isinstance(b, ScalarValue): bf = float(b) if bf < 0: error("Division by zero, annot raise 0 to a negative power.") else: return zero() if b == 1: return a if b == 0: return IntValue(1) # construct and initialize a new Power object self = AlgebraOperator.__new__(cls) self._init(a, b) return self
def division(self, o, a, b): f, fp = a g, gp = b o = self.reuse_if_possible(o, f, g) ufl_assert(is_ufl_scalar(f), "Not expecting nonscalar nominator") ufl_assert(is_true_ufl_scalar(g), "Not expecting nonscalar denominator") #do_df = 1/g #do_dg = -h/g #op = do_df*fp + do_df*gp #op = (fp - o*gp) / g # Get o and gp as scalars, multiply, then wrap as a tensor again so, oi = as_scalar(o) sgp, gi = as_scalar(gp) o_gp = so*sgp if oi or gi: o_gp = as_tensor(o_gp, oi + gi) op = (fp - o_gp) / g return (o, op)
def division(self, o, fp, gp): f, g = o.ufl_operands if not is_ufl_scalar(f): error("Not expecting nonscalar nominator") if not is_true_ufl_scalar(g): error("Not expecting nonscalar denominator") # do_df = 1/g # do_dg = -h/g # op = do_df*fp + do_df*gp # op = (fp - o*gp) / g # Get o and gp as scalars, multiply, then wrap as a tensor # again so, oi = as_scalar(o) sgp, gi = as_scalar(gp) o_gp = so * sgp if oi or gi: o_gp = as_tensor(o_gp, oi + gi) op = (fp - o_gp) / g return op
def division(self, o, a, b): f, fp = a g, gp = b o = self.reuse_if_possible(o, f, g) ufl_assert(is_ufl_scalar(f), "Not expecting nonscalar nominator") ufl_assert(is_true_ufl_scalar(g), "Not expecting nonscalar denominator") #do_df = 1/g #do_dg = -h/g #op = do_df*fp + do_df*gp #op = (fp - o*gp) / g # Get o and gp as scalars, multiply, then wrap as a tensor again so, oi = as_scalar(o) sgp, gi = as_scalar(gp) o_gp = so * sgp if oi or gi: o_gp = as_tensor(o_gp, oi + gi) op = (fp - o_gp) / g return (o, op)
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 __init__(self, name, argument): Operator.__init__(self, (argument,)) if not is_true_ufl_scalar(argument): error("Expecting scalar argument.") self._name = name
def __init__(self, name, argument): Operator.__init__(self, (argument, )) if not is_true_ufl_scalar(argument): error("Expecting scalar argument.") self._name = name
def __rmul__(self, integrand): # Let other types implement multiplication with Measure # if they want to (to support the dolfin-adjoint TimeMeasure) if not isinstance(integrand, Expr): return NotImplemented # Allow only scalar integrands if not is_true_ufl_scalar(integrand): error("Trying to integrate expression of rank %d with free indices %r." \ % (integrand.rank(), integrand.free_indices())) # Is the measure in a state where multiplication is not allowed? if self._domain_description == Measure.DOMAIN_ID_UNDEFINED: error("Missing domain id. You need to select a subdomain, " +\ "e.g. M = f*dx(0) for subdomain 0.") #else: # TODO: Do it this way instead, and move all logic below into preprocess: # # Return a one-integral form: # from ufl.form import Form # return Form( [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data())] ) # # or if we move domain data into Form instead: # integrals = [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata())] # domain_data = { self.domain_type(): self.domain_data() } # return Form(integrals, domain_data) # TODO: How to represent all kinds of domain descriptions is still a bit unclear # Create form if we have a sufficient domain description elif (# We have a complete measure with domain description isinstance(self._domain_description, DomainDescription) # Is the measure in a basic state 'foo*dx'? or self._domain_description == Measure.DOMAIN_ID_UNIQUE # Is the measure over everywhere? or self._domain_description == Measure.DOMAIN_ID_EVERYWHERE # Is the measure in a state not allowed prior to preprocessing? or self._domain_description == Measure.DOMAIN_ID_OTHERWISE ): # Create and return a one-integral form from ufl.form import Form return Form( [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data())] ) # Did we get several ids? elif isinstance(self._domain_description, tuple): # FIXME: Leave this analysis to preprocessing return sum(integrand*self.reconstruct(domain_id=d) for d in self._domain_description) # Did we get a name? elif isinstance(self._domain_description, str): # FIXME: Leave this analysis to preprocessing # Get all domains and regions from integrand to analyse domains = extract_domains(integrand) # Get domain or region with this name from integrand, error if multiple found name = self._domain_description candidates = set() for TD in domains: if TD.name() == name: candidates.add(TD) ufl_assert(len(candidates) > 0, "Found no domain with name '%s' in integrand." % name) ufl_assert(len(candidates) == 1, "Multiple distinct domains with same name encountered in integrand.") D, = candidates # Reconstruct measure with the found named domain or region measure = self.reconstruct(domain_id=D) return integrand*measure # Did we get a number? elif isinstance(self._domain_description, int): # FIXME: Leave this analysis to preprocessing # Get all top level domains from integrand to analyse domains = extract_top_domains(integrand) # Get domain from integrand, error if multiple found if len(domains) == 0: # This is the partially defined integral from dolfin expression mess cell = integrand.cell() D = None if cell is None else as_domain(cell) elif len(domains) == 1: D, = domains else: error("Ambiguous reference to integer subdomain with multiple top domains in integrand.") if D is None: # We have a number but not a domain? Leave it to preprocess... # This is the case with badly formed forms which can occur from dolfin # Create and return a one-integral form from ufl.form import Form return Form( [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data())] ) else: # Reconstruct measure with the found numbered subdomain measure = self.reconstruct(domain_id=D[self._domain_description]) return integrand*measure # Provide error to user else: error("Invalid domain id %s." % str(self._domain_description))
def __init__(self, arg1, arg2): Operator.__init__(self, (arg1, arg2)) if not is_true_ufl_scalar(arg1): error("Expecting scalar argument 1.") if not is_true_ufl_scalar(arg2): error("Expecting scalar argument 2.")
def __rmul__(self, integrand): # Let other types implement multiplication with Measure # if they want to (to support the dolfin-adjoint TimeMeasure) if not isinstance(integrand, Expr): return NotImplemented # Allow only scalar integrands if not is_true_ufl_scalar(integrand): error("Trying to integrate expression of rank %d with free indices %r." \ % (integrand.rank(), integrand.free_indices())) # Is the measure in a state where multiplication is not allowed? if self._domain_description == Measure.DOMAIN_ID_UNDEFINED: error("Missing domain id. You need to select a subdomain, " +\ "e.g. M = f*dx(0) for subdomain 0.") #else: # TODO: Do it this way instead, and move all logic below into preprocess: # # Return a one-integral form: # from ufl.form import Form # return Form( [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data())] ) # # or if we move domain data into Form instead: # integrals = [Integral(integrand, self.domain_type(), self.domain_id(), self.metadata())] # domain_data = { self.domain_type(): self.domain_data() } # return Form(integrals, domain_data) # TODO: How to represent all kinds of domain descriptions is still a bit unclear # Create form if we have a sufficient domain description elif ( # We have a complete measure with domain description isinstance(self._domain_description, DomainDescription) # Is the measure in a basic state 'foo*dx'? or self._domain_description == Measure.DOMAIN_ID_UNIQUE # Is the measure over everywhere? or self._domain_description == Measure.DOMAIN_ID_EVERYWHERE # Is the measure in a state not allowed prior to preprocessing? or self._domain_description == Measure.DOMAIN_ID_OTHERWISE): # Create and return a one-integral form from ufl.form import Form return Form([ Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data()) ]) # Did we get several ids? elif isinstance(self._domain_description, tuple): # FIXME: Leave this analysis to preprocessing return sum(integrand * self.reconstruct(domain_id=d) for d in self._domain_description) # Did we get a name? elif isinstance(self._domain_description, str): # FIXME: Leave this analysis to preprocessing # Get all domains and regions from integrand to analyse domains = extract_domains(integrand) # Get domain or region with this name from integrand, error if multiple found name = self._domain_description candidates = set() for TD in domains: if TD.name() == name: candidates.add(TD) ufl_assert( len(candidates) > 0, "Found no domain with name '%s' in integrand." % name) ufl_assert( len(candidates) == 1, "Multiple distinct domains with same name encountered in integrand." ) D, = candidates # Reconstruct measure with the found named domain or region measure = self.reconstruct(domain_id=D) return integrand * measure # Did we get a number? elif isinstance(self._domain_description, int): # FIXME: Leave this analysis to preprocessing # Get all top level domains from integrand to analyse domains = extract_top_domains(integrand) # Get domain from integrand, error if multiple found if len(domains) == 0: # This is the partially defined integral from dolfin expression mess cell = integrand.cell() D = None if cell is None else as_domain(cell) elif len(domains) == 1: D, = domains else: error( "Ambiguous reference to integer subdomain with multiple top domains in integrand." ) if D is None: # We have a number but not a domain? Leave it to preprocess... # This is the case with badly formed forms which can occur from dolfin # Create and return a one-integral form from ufl.form import Form return Form([ Integral(integrand, self.domain_type(), self.domain_id(), self.metadata(), self.domain_data()) ]) else: # Reconstruct measure with the found numbered subdomain measure = self.reconstruct( domain_id=D[self._domain_description]) return integrand * measure # Provide error to user else: error("Invalid domain id %s." % str(self._domain_description))
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 __init__(self, name, argument): Operator.__init__(self) ufl_assert(is_true_ufl_scalar(argument), "Expecting scalar argument.") self._name = name self._argument = argument
def sensitivity_rhs(a, u, L, v): """UFL form operator: Compute the right hand side for a sensitivity calculation system. The derivation behind this computation is as follows. Assume *a*, *L* to be bilinear and linear forms corresponding to the assembled linear system .. math:: Ax = b. Where *x* is the vector of the discrete function corresponding to *u*. Let *v* be some scalar variable this equation depends on. Then we can write .. math:: 0 = \\frac{d}{dv}(Ax-b) = \\frac{dA}{dv} x + A \\frac{dx}{dv} - \\frac{db}{dv}, A \\frac{dx}{dv} = \\frac{db}{dv} - \\frac{dA}{dv} x, and solve this system for :math:`\\frac{dx}{dv}`, using the same bilinear form *a* and matrix *A* from the original system. Assume the forms are written :: v = variable(v_expression) L = IL(v)*dx a = Ia(v)*dx where ``IL`` and ``Ia`` are integrand expressions. Define a ``Coefficient u`` representing the solution to the equations. Then we can compute :math:`\\frac{db}{dv}` and :math:`\\frac{dA}{dv}` from the forms :: da = diff(a, v) dL = diff(L, v) and the action of ``da`` on ``u`` by :: dau = action(da, u) In total, we can build the right hand side of the system to compute :math:`\\frac{du}{dv}` with the single line :: dL = diff(L, v) - action(diff(a, v), u) or, using this function, :: dL = sensitivity_rhs(a, u, L, v) """ if not (isinstance(a, Form) and isinstance(u, Coefficient) and isinstance(L, Form) and isinstance(v, Variable)): error("Expecting (a, u, L, v), (bilinear form, function, linear form and scalar variable).") if not is_true_ufl_scalar(v): error("Expecting scalar variable.") from ufl.operators import diff return diff(L, v) - action(diff(a, v), u)
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 = [] warnings = [] if not isinstance(form, Form): msg = "Validation failed, not a Form:\n%s" % repr(form) error(msg) #errors.append(msg) #return errors # 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.domain() for e in iter_expressions(form) for t in traverse_terminals(e)) - set((None,)) if not domains: errors.append("Missing domain definition in form.") top_domains = set(dom.top_domain() for dom in domains if dom is not None) if not top_domains: errors.append("Missing domain definition in form.") elif len(top_domains) > 1: warnings.append("Multiple top domain definitions in form: %s" % str(top_domains)) # Check that cell is the same everywhere cells = set(dom.cell() for dom in top_domains) - set((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_terminals(e): if isinstance(f, Coefficient): c = f.count() if c in coefficients: g = coefficients[c] if not f is g: errors.append("Found different Coefficients with " + \ "same count: %s and %s." % (repr(f), repr(g))) else: coefficients[c] = f elif isinstance(f, Argument): c = f.count() if c in arguments: g = arguments[c] if not f is g: if c == -2: msg = "TestFunctions" elif c == -1: msg = "TrialFunctions" else: msg = "Arguments with same count" msg = "Found different %s: %s and %s." % (msg, repr(f), repr(g)) errors.append(msg) else: arguments[c] = 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:\n%s\n%s" % \ (str(expression), repr(expression))) # Check that restrictions are permissible for integral in form.integrals(): # Only allow restricitions on interior facet integrals and surface measures if integral.measure().domain_type() in (Measure.INTERIOR_FACET, Measure.SURFACE): 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)