def _updateRHS(self, expressions, subs, **constants): """ """ ev = symb.Evaluator() names = [] for name in sorted(expressions): if name in self.__COEFFICIENTS: ev.addExpression(expressions[name]) names.append(name) if len(names) == 0: return self.trace3("Starting expression evaluation.") T0 = time() ev.subs(**subs) res = ev.evaluate() if len(names) == 1: res = [res] self.trace3("RHS expressions evaluated in %f seconds." % (time() - T0)) if self._debug > self.DEBUG2: for i in range(len(names)): self.trace3("util.Lsup(%s)=%s" % (names[i], util.Lsup(res[i]))) args = dict(zip(names, res)) # reset coefficients may be set at previous calls: for n in self.__COEFFICIENTS: if not n in args and not n in constants: args[n] = Data() args = dict(list(args.items()) + list(constants.items())) if not 'r' in args: args['r'] = Data() self._lpde.setValue(**args)
def _updateMatrix(self, expressions, subs): """ """ ev = symb.Evaluator() names = [] for name in sorted(expressions): if not name in self.__COEFFICIENTS: ev.addExpression(expressions[name]) names.append(name) if len(names) == 0: return self.trace3("Starting expression evaluation.") T0 = time() ev.subs(**subs) res = ev.evaluate() if len(names) == 1: res = [res] self.trace3("Matrix expressions evaluated in %f seconds." % (time() - T0)) if self._debug > self.DEBUG2: for i in range(len(names)): self.trace3("util.Lsup(%s)=%s" % (names[i], util.Lsup(res[i]))) self._lpde.setValue(**dict(zip(names, res)))
def getSensitivity(self, f, g=None, **subs): """ Calculates the sensitivity of the solution of an input factor ``f`` in direction ``g``. :param f: the input factor to be investigated. ``f`` may be of rank 0 or 1. :type f: `Symbol` :param g: the direction(s) of change. If not present, it is *g=eye(n)* where ``n`` is the number of components of ``f``. :type g: ``list`` or single of ``float``, ``numpy.array`` or `Data`. :param subs: Substitutions for all symbols used in the coefficients including unknown *u* and the input factor ``f`` to be investigated :return: the sensitivity :rtype: `Data` with shape *u.getShape()+(len(g),)* if *len(g)>1* or *u.getShape()* if *len(g)==1* """ s_f = f.getShape() if len(s_f) == 0: len_f = 1 elif len(s_f) == 1: len_f = s_f[0] else: raise ValueError("rank of input factor must be zero or one.") if not g is None: if len(s_f) == 0: if not isinstance(g, list): g = [g] else: if isinstance(g, list): if len(g) == 0: raise ValueError("no direction given.") if len(getShape(g[0])) == 0: g = [ g ] # if g[0] is a scalar we assume that the list g is to be interprested a data object else: g = [g] # at this point g is a list of directions: len_g = len(g) for g_i in g: if not getShape(g_i) == s_f: raise ValueError( "shape of direction (=%s) must match rank of input factor (=%s)" % (getShape(g_i), s_f)) else: len_g = len_f #*** at this point g is a list of direction or None and len_g is the # number of directions to be investigated. # now we make sure that the operator in the lpde is set (it is the same # as for the Newton-Raphson scheme) # if the solution etc are cached this could be omitted: constants = {} expressions = {} for n, e in sorted(self._set_coeffs.items(), key=lambda x: x[0]): if n not in self.__COEFFICIENTS: if symb.isSymbol(e): expressions[n] = e else: constants[n] = e self._lpde.setValue(**constants) self._updateMatrix(self, expressions, subs) #===================================================================== self._lpde.getSolverOptions().setAbsoluteTolerance(0.) self._lpde.getSolverOptions().setTolerance(self._rtol) self._lpde.getSolverOptions().setVerbosity(self._debug > self.DEBUG1) #===================================================================== # # evaluate the derivatives of X, etc with respect to f: # ev = symb.Evaluator() names = [] if hasattr(self, "_r"): if symb.isSymbol(self._r): names.append('r') ev.addExpression(self._r.diff(f)) for n in sorted(self._set_coeffs.keys()): if n in self.__COEFFICIENTS and symb.isSymbol(self._set_coeffs[n]): if n == "X" or n == "X_reduced": T0 = time() B, A = symb.getTotalDifferential(self._set_coeffs[n], f, 1) if n == 'X_reduced': self.trace3( "Computing A_reduced, B_reduced took %f seconds." % (time() - T0)) names.append('A_reduced') ev.addExpression(A) names.append('B_reduced') ev.addExpression(B) else: self.trace3("Computing A, B took %f seconds." % (time() - T0)) names.append('A') ev.addExpression(A) names.append('B') ev.addExpression(B) elif n == "Y" or n == "Y_reduced": T0 = time() D, C = symb.getTotalDifferential(self._set_coeffs[n], f, 1) if n == 'Y_reduced': self.trace3( "Computing C_reduced, D_reduced took %f seconds." % (time() - T0)) names.append('C_reduced') ev.addExpression(C) names.append('D_reduced') ev.addExpression(D) else: self.trace3("Computing C, D took %f seconds." % (time() - T0)) names.append('C') ev.addExpression(C) names.append('D') ev.addExpression(D) elif n in ("y", "y_reduced", "y_contact", "y_contact_reduced", "y_dirac"): names.append('d' + name[1:]) ev.addExpression(self._set_coeffs[name].diff(f)) relevant_symbols['d' + name[1:]] = self._set_coeffs[name].diff(f) res = ev.evaluate() if len(names) == 1: res = [res] self.trace3("RHS expressions evaluated in %f seconds." % (time() - T0)) if self._debug > self.DEBUG2: for i in range(len(names)): self.trace3("util.Lsup(%s)=%s" % (names[i], util.Lsup(res[i]))) coeffs_f = dict(zip(names, res)) # # now we are ready to calculate the right hand side coefficients into # args by multiplication with g and grad(g). if len_g > 1: if self.getNumSolutions() == 1: u_g = Data(0., (len_g, ), self._lpde.getFunctionSpaceForSolution()) else: u_g = Data(0., (self.getNumSolutions(), len_g), self._lpde.getFunctionSpaceForSolution()) for i in range(len_g): # reset coefficients may be set at previous calls: args = {} for n in self.__COEFFICIENTS: args[n] = Data() args['r'] = Data() if g is None: # g_l=delta_{il} and len_f=len_g for n, v in coeffs_f: name = None if len_f > 1: val = v[:, i] else: val = v if n.startswith("d"): name = 'y' + n[1:] elif n.startswith("D"): name = 'Y' + n[1:] elif n.startswith("r"): name = 'r' + n[1:] if name: args[name] = val else: g_i = g[i] for n, v in coeffs_f: name = None if n.startswith("d"): name = 'y' + n[1:] val = self.__mm(v, g_i) elif n.startswith("r"): name = 'r' val = self.__mm(v, g_i) elif n.startswith("D"): name = 'Y' + n[1:] val = self.__mm(v, g_i) elif n.startswith("B") and isinstance(g_i, Data): name = 'Y' + n[1:] val = self.__mm(v, grad(g_i)) elif n.startswith("C"): name = 'X' + n[1:] val = matrix_multiply(v, g_i) elif n.startswith("A") and isinstance(g_i, Data): name = 'X' + n[1:] val = self.__mm(v, grad(g_i)) if name: if name in args: args[name] += val else: args[name] = val self._lpde.setValue(**args) u_g_i = self._lpde.getSolution() if len_g > 1: if self.getNumSolutions() == 1: u_g[i] = -u_g_i else: u_g[:, i] = -u_g_i else: u_g = -u_g_i return u_g
def getSolution(self, **subs): """ Returns the solution of the PDE. :param subs: Substitutions for all symbols used in the coefficients including the initial value for the unknown *u*. :return: the solution :rtype: `Data` """ # get the initial value for the iteration process # collect components of unknown in u_syms u_syms = [] simple_u = False for i in numpy.ndindex(self._unknown.getShape()): u_syms.append( symb.Symbol(self._unknown[i]).atoms(sympy.Symbol).pop().name) if len(set(u_syms)) == 1: simple_u = True e = symb.Evaluator(self._unknown) for sym in u_syms: if not sym in subs: raise KeyError("Initial value for '%s' missing." % sym) if not isinstance(subs[sym], Data): subs[sym] = Data(subs[sym], self._lpde.getFunctionSpaceForSolution()) e.subs(**{sym: subs[sym]}) ui = e.evaluate() # modify ui so it meets the constraints: q = self._lpde.getCoefficient("q") if not q.isEmpty(): if hasattr(self, "_r"): r = self._r if symb.isSymbol(r): r = symb.Evaluator(r).evaluate(**subs) elif not isinstance(r, Data): r = Data(r, self._lpde.getFunctionSpaceForSolution()) elif r.isEmpty(): r = 0 else: r = 0 ui = q * r + (1 - q) * ui # separate symbolic expressions from other coefficients constants = {} expressions = {} for n, e in sorted(self._set_coeffs.items(), key=lambda x: x[0]): if symb.isSymbol(e): expressions[n] = e else: constants[n] = e # set constant PDE values now self._lpde.setValue(**constants) self._lpde.getSolverOptions().setAbsoluteTolerance(0.) self._lpde.getSolverOptions().setVerbosity(self._debug > self.DEBUG1) #===================================================================== # perform Newton iterations until error is small enough or # maximum number of iterations reached n = 0 omega = 1. # relaxation factor use_simplified_Newton = False defect_norm = None delta_norm = None converged = False #subs[u_sym]=ui if simple_u: subs[u_syms[0]] = ui else: for i in range(len(u_syms)): subs[u_syms[i]] = ui[i] while not converged: if n > self._iteration_steps_max: raise iteration_steps_maxReached( "maximum number of iteration steps reached, giving up.") self.trace1(5 * "=" + " iteration step %d " % n + 5 * "=") # calculate the correction delta_u if n == 0: self._updateLinearPDE(expressions, subs, **constants) defect_norm = self._getDefectNorm( self._lpde.getRightHandSide()) LINTOL = 0.1 else: if not use_simplified_Newton: self._updateMatrix(expressions, subs) if q_u is None: LINTOL = 0.1 * min(qtol / defect_norm) else: LINTOL = 0.1 * max(q_u**2, min(qtol / defect_norm)) LINTOL = max(1e-4, min(LINTOL, 0.1)) #LINTOL=1.e-5 self._lpde.getSolverOptions().setTolerance(LINTOL) self.trace1("PDE is solved with rel. tolerance = %e" % LINTOL) delta_u = self._lpde.getSolution() #check for reduced defect: omega = min(2 * omega, 1.) # raise omega defect_reduced = False ui_old = ui while not defect_reduced: ui = ui_old - delta_u * omega if simple_u: subs[u_syms[0]] = ui else: for i in range(len(u_syms)): subs[u_syms[i]] = ui[i] self._updateRHS(expressions, subs, **constants) new_defect_norm = self._getDefectNorm( self._lpde.getRightHandSide()) defect_reduced = False for i in range(len(new_defect_norm)): if new_defect_norm[i] < defect_norm[i]: defect_reduced = True #print new_defect_norm #q_defect=max(self._getSafeRatio(new_defect_norm, defect_norm)) # if defect_norm==0 and new_defect_norm!=0 # this will be util.DBLE_MAX #self.trace1("Defect reduction = %e with relaxation factor %e."%(q_defect, omega)) if not defect_reduced: omega *= 0.5 if omega < self._omega_min: raise DivergenceDetected( "Underrelaxtion failed to reduce defect, giving up." ) self.trace1("Defect reduction with relaxation factor %e." % (omega, )) delta_norm, delta_norm_old = self._getSolutionNorm( delta_u) * omega, delta_norm defect_norm, defect_norm_old = new_defect_norm, defect_norm u_norm = self._getSolutionNorm(ui, atol=self._atol) # set the tolerance on equation level: qtol = self._getSafeRatio(defect_norm_old * u_norm * self._rtol, delta_norm) # if defect_norm_old==0 and defect_norm_old!=0 this will be util.DBLE_MAX # -> the ordering of the equations is not appropriate. # if defect_norm_old==0 and defect_norm_old==0 this is zero so # convergence can happen for defect_norm==0 only. if not max(qtol) < util.DBLE_MAX: raise InadmissiblePDEOrdering( "Review ordering of PDE equations.") # check stopping criteria if not delta_norm_old is None: q_u = max(self._getSafeRatio(delta_norm, delta_norm_old)) # if delta_norm_old==0 and delta_norm!=0 # this will be util.DBLE_MAX if q_u <= self._quadratic_convergence_limit and not omega < 1.: quadratic_convergence = True self.trace1( "Quadratic convergence detected (rate %e <= %e)" % (q_u, self._quadratic_convergence_limit)) converged = all( [defect_norm[i] <= qtol[i] for i in range(len(qtol))]) else: self.trace1( "No quadratic convergence detected (rate %e > %e, omega=%e)" % (q_u, self._quadratic_convergence_limit, omega)) quadratic_convergence = False converged = False else: q_u = None converged = False quadratic_convergence = False if self._debug > self.DEBUG0: for i in range(len(u_norm)): self.trace1( "Component %s: u: %e, du: %e, defect: %e, qtol: %e" % (i, u_norm[i], delta_norm[i], defect_norm[i], qtol[i])) if converged: self.trace1("Iteration has converged.") # Can we switch to simplified Newton? if quadratic_convergence: q_defect = max(self._getSafeRatio(defect_norm, defect_norm_old)) if q_defect < self._simplified_newton_limit: use_simplified_Newton = True self.trace1( "Simplified Newton-Raphson is applied (rate %e < %e)." % (q_defect, self._simplified_newton_limit)) n += 1 self.trace1(5 * "=" + " Newton-Raphson iteration completed after %d steps " % n + 5 * "=") return ui