def __init__(self, rst_stencil, level_in, level_out, *args, pre_assign=None, **kwargs): super(RestrictionByStencilForLevels, self).__init__(*args, **kwargs) if pre_assign is None: # no pre assignment function self.pre_assign = lambda a, b: b else: assert_is_callable(pre_assign, "Pre assignment function is not callable") self.pre_assign = pre_assign assert_is_instance(rst_stencil, Stencil, "Not a Stencil") assert_is_instance(level_in, IMultigridLevel, "Not a IMultigridLevel") assert_is_instance(level_out, IMultigridLevel, "Not a IMultigridLevel") self.l_in = level_in self.l_out = level_out self.rst_stencil = rst_stencil # check if the number of points per level matches self.dip = [] for i in range(rst_stencil.dim): self.dip.append((level_in.mid.shape[i]-1)/(level_out.mid.shape[i]-1) - 1) # print("in.shape[", i, "]: ", level_in.mid.shape[i]) # print("out.shape[", i, "]: ", level_out.mid.shape[i]) # print("dip[", i, "]: ", self.dip[-1]) if (self.dip[-1] % 1) != 0: raise ValueError("The Level do not match in direction " + str(i)) # now just construct a slice tuple and the evaluable view from the finer grid self.evaluable_view = level_in.evaluable_restriction_view(rst_stencil) self.slices = [] for i in range(rst_stencil.dim): self.slices.append(slice(None, None, self.dip[i]+1))
def find_root(fun, x0, method="hybr"): """Wrapper around SciPy's generic root finding algorithm to support complex numbers. SciPy's generic root finding algorithm (``scipy.optimize.root``) is not able to deal with functions returning and/or accepting arrays with complex numbers. This wrapped call will first convert all arrays of complex numbers into arrays of floats while splitting each complex number up into two floats. Parameters ---------- fun : :py:class:`callable` Complex function to find the root of x0 : :py:class:`numpy.ndarray` Initial guess. method : :py:class:`str` Root finding method to be used. See ``scipy.optimize.root`` for details. Returns ------- Same solution object as ``scipy.optimize.root`` but with ``x`` being converted back including complex numbers. Examples -------- from pypint.plugins.implicit_solvers.find_root import find_root import numpy fun = lambda x: (-1.0 + 1.0j) * x sol = find_root(fun, numpy.array([0.0])) """ assert_is_instance(x0, np.ndarray, descriptor="Initial Guess") assert_is_callable(fun, descriptor="Function to find root of") assert_is_instance(method, str, descriptor="Root finding method") _value_map = {} _transformed_size = 0 _transform_necessary = False for i in range(0, x0.size): if isinstance(x0[i], complex): _value_map[i] = [_transformed_size, _transformed_size + 1] _transformed_size += 2 _transform_necessary = True else: _value_map[i] = [_transformed_size] _transformed_size += 1 if _transform_necessary: _wrapped_func = \ lambda x_next: _transform_to_real(fun(_transform_to_complex(x_next, _value_map)), _value_map, _transformed_size) sol = root(fun=_wrapped_func, x0=_transform_to_real(x0, _value_map, _transformed_size), method=method) else: sol = root(fun=fun, x0=x0, method=method) if sol.success and _transform_necessary: sol.x = _transform_to_complex(sol.x, _value_map) return sol
def implicit_solve(self, next_x, func, method="unused", **kwargs): """A solver for the implicit equations. """ assert_is_instance(next_x, np.ndarray, descriptor="Initial Guess", checking_obj=self) assert_is_callable(func, descriptor="Function of RHS for Implicit Solver", checking_obj=self) sol = scop.newton_krylov(func, next_x.reshape(-1)) assert_is_instance(sol, np.ndarray, descriptor="Solution", checking_obj=self) return sol.reshape(self.dim_for_time_solver)
def __init__(self, level_in, level_out, stencil_list, *args, pre_assign=None, **kwargs): """init """ super(InterpolationByStencilForLevelsClassical, self).__init__(*args, **kwargs) if pre_assign is None: # no pre assignment function self.pre_assign = lambda a, b: b else: assert_is_callable(pre_assign, "Pre assignment function is not callable") self.pre_assign = pre_assign # check if all parameters are fitting assert_is_instance(stencil_list, list) for st in stencil_list: assert_is_instance(st[0], Stencil, "that is not a stencil") self.stencil_list = stencil_list assert_is_instance(level_in, IMultigridLevel, "Not a IMultigridLevel") assert_is_instance(level_out, IMultigridLevel, "Not a IMultigridLevel") self.level_in = level_in self.level_out = level_out # increase in points for each direction self.iip = [] self.fits = False for i in range(level_in.mid.ndim): self.iip.append((level_out.mid.shape[i]-1)/(level_in.mid.shape[i]) - 1) n = level_out.mid.shape[i] m = level_in.mid.shape[i] self.fits = False while m <= n: if m == n: self.fits = True break else: m = m*2+1 if not self.fits: raise ValueError("The Levels do not match in direction " + str(i)) # compute evaluable views with the positions and slices self.slices_out = [] for st, pos in stencil_list: sl_out = [] for i in range(st.dim): sl_out.append(slice(pos[i], None, self.iip[i]+1)) # print("Initial Position:", pos) # print("Slice", sl_out) self.slices_out.append(tuple(sl_out.copy()))
def __init__(self, residual_tolerance_dict, max_iteration_dict, *args, **kwargs): self.rtd = residual_tolerance_dict self.mid = max_iteration_dict if "res_comp_method" in kwargs.keys(): self.compute_residual = kwargs["res_comp_method"] assert_is_callable(self.compute_residual, "Residual computation method " "should be callable ") else: self.compute_residual = self._standard_residual_computation super(ResidualErrorControl, self).__init__(args, kwargs)
def __init__(self, stencil_list, level_in, level_out, *args, pre_assign=None, **kwargs): """init """ super(InterpolationByStencilForLevels, self).__init__(*args, **kwargs) if pre_assign is None: # no pre assignment function self.pre_assign = lambda a, b: b else: assert_is_callable(pre_assign, "Pre assignment function is not callable") self.pre_assign = pre_assign # check if all parameters are fitting assert_is_instance(stencil_list, list) for st in stencil_list: assert_is_instance(st[0], Stencil, "that is not a stencil") self.stencil_list = stencil_list assert_is_instance(level_in, IMultigridLevel, "Not a IMultigridLevel") assert_is_instance(level_out, IMultigridLevel, "Not a IMultigridLevel") self.level_in = level_in self.level_out = level_out # increase in points for each direction self.iip = [] for i in range(level_in.mid.ndim): self.iip.append((level_out.mid.shape[i]-1)/(level_in.mid.shape[i]-1) - 1) # print("in.shape[", i, "]: ", level_in.mid.shape[i]) # print("out.shape[", i, "]: ", level_out.mid.shape[i]) # print("iip[", i, "]: ", self.iip[-1]) if (self.iip[-1] % 1) != 0: raise ValueError("The Levels do not match in direction " + str(i)) # compute evaluable views with the positions and slices self.evaluable_views = [] self.slices_out = [] self.slices_in = [] for st, pos in stencil_list: sl_out = [] sl_in = [] for i in range(st.dim): sl_out.append(slice(pos[i], None, self.iip[i]+1)) if pos[i] == 0: sl_in.append(slice(None, None)) else: sl_in.append(slice(0, -1)) # print("Stencilcenter : ", st.center) # print("Stencilborder :", st.b) self.evaluable_views.append(level_in.evaluable_interpolation_view(st)) # print("The view: \n", self.evaluable_views[-1]) self.slices_out.append(tuple(sl_out.copy())) self.slices_in.append(tuple(sl_in.copy()))
def implicit_solve(self, next_x, func, method="hybr", **kwargs): """A solver for implicit equations. Finds the implicitly defined :math:`x_{i+1}` for the given right hand side function :math:`f(x_{i+1})`, such that :math:`x_{i+1}=f(x_{i+1})`. Parameters ---------- next_x : :py:class:`numpy.ndarray` A starting guess for the implicitly defined value. rhs_call : :py:class:`callable` The right hand side function depending on the implicitly defined new value. method : :py:class:`str` *(optional, default=``hybr``)* Method fo the root finding algorithm. See `scipy.optimize.root <http://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.root.html#scipy.optimize.root>` for details. Returns ------- next_x : :py:class:`numpy.ndarray` The calculated new value. Raises ------ ValueError : * if ``next_x`` is not a :py:class:`numpy.ndarray` of shape :py:attr:`.IProblem.dim` * if ``fun`` is not :py:class:`callable` * if computed solution is not a `:py:class:`numpy.ndarray` UserWarning : If the implicit solver did not converged, i.e. the solution object's ``success`` is not :py:class:`True`. """ assert_is_instance(next_x, np.ndarray, descriptor="Initial Guess", checking_obj=self) assert_is_callable(func, descriptor="Function of RHS for Implicit Solver", checking_obj=self) sol = find_root(fun=func, x0=next_x.reshape(-1), method=method) if not sol.success: warnings.warn("Implicit solver did not converged.") LOG.debug("sol.x: %s" % sol.x) LOG.error("Implicit solver failed: %s" % sol.message) else: assert_is_instance(sol.x, np.ndarray, descriptor="Solution", checking_obj=self) return sol.x.reshape(self.dim_for_time_solver)
def init(self, problem, **kwargs): """Initializes the solver with a given problem and options. Parameters ---------- problem : :py:class:`.IProblem` The problem this solver should solve. integrator : :py:class:`.IntegratorBase` Integrator to be used by this solver. threshold : :py:class:`.ThresholdCheck` *(optional)* see :py:attr:`.threshold` """ self._problem = problem if 'integrator' in kwargs: assert_is_callable(kwargs['integrator'], message="Integrator must be instantiable.", checking_obj=self) self._integrator = kwargs['integrator']() if "threshold" in kwargs and isinstance(kwargs["threshold"], ThresholdCheck): self.threshold = kwargs["threshold"]
def exact(self, time): """Evaluates given exact solution function at given time and with given time-dependent data. Parameters ---------- time : :py:class:`float` Time point :math:`t` Returns ------- exact_solution : :py:class:`numpy.ndarray` Raises ------ ValueError : * if ``time`` is not a :py:class:`float` * if ``phi_of_time`` is not a :py:class:`numpy.ndarray` * if not exact function is given """ assert_is_instance(time, float, descriptor="Time Point", checking_obj=self) assert_is_callable(self._exact_function, descriptor="Exact Function", checking_obj=self) return self._exact_function(time)
def eval_f(self, u=None, function=None, space_tensor=None): """Evaluates the right hand side with the actual space tensor, and the current :math:`u`. """ assert_condition(self._act_space_tensor is not None, ValueError, message="A current space tensor is needed", checking_obj=self) if function is None: if u is None: return self.rhs_function_wrt_space(self._act_space_tensor) else: assert_is_instance(u, np.ndarray, "u is not an numpy array") assert_condition(u.shape == self._act_space_tensor[1].shape, "u has the wrong shape", self) return self.rhs_function_wrt_space(u, self._act_space_tensor) else: if u is None: assert_is_callable(function, "Function is not callable", self) return function(self._act_space_tensor) else: assert_is_instance(u, np.ndarray, "u is not an numpy array") assert_condition(u.shape == self._act_space_tensor[1].shape, "u has the wrong shape", self) return function(u, self._act_space_tensor)
def rhs_function_wrt_time(self, function): assert_is_callable(function, descriptor="Function of the RHS w.r.t Time", checking_obj=self) self._rhs_function_wrt_time = function
def exact_function(self, exact_function): assert_is_callable(exact_function, descriptor="Exact Function", checking_obj=self) self._exact_function = exact_function
def __init__(self, mg_prob, stencil_form, *args, **kwargs): for keys in kwargs.keys(): print(keys) # assert_condition(problem_is_multigrid_problem(mg_prob), ValueError, message="Not a multigrid Problem") assert_is_callable(stencil_form, "StencilForm has to be a function") self.mg_problem = mg_prob self.levels = [] self.smoothers = [] self.stencils = [] self.rst_ops = [] self.ipl_ops = [] self.dim = kwargs["dim"] self.n_pre = kwargs.get("n_pre", 3) self.n_post = kwargs.get("n_post", 3) self.num_levels = kwargs.get("num_levels", 3) # append course level shape = kwargs["shape_coarse"] if kwargs.get("dim") == 1: self.levels.append(MultigridLevel1D(shape, self.mg_problem, max_borders=kwargs["max_borders"], role="CL")) elif kwargs.get("dim") == 2: self.levels.append(MultigridLevel2D(shape, self.mg_problem, max_borders=kwargs["max_borders"], role="CL")) #append course stencil self.stencils.append(Stencil(*stencil_form(self.levels[-1]))) self.smoothers.append(DirectSolverSmoother(self.stencils[-1], self.levels[-1])) for i in range(kwargs["num_levels"]-1): if i == kwargs["num_levels"]-2: role = "FL" else: role = "ML" if kwargs.get("dim") == 1: shape = shape*2+1 self.levels.append(MultigridLevel1D(shape, self.mg_problem, max_borders=kwargs["max_borders"], role=role)) elif kwargs.get("dim") == 2: shape = (shape[0]*2+1, shape[1]*2+1) self.levels.append(MultigridLevel2D(shape, self.mg_problem, max_borders=kwargs["max_borders"], role=role)) self.stencils.append(Stencil(*stencil_form(self.levels[-1]))) # append smoother if kwargs["smoothing_type"] is "jacobi": omega = kwargs["smooth_opts"]["omega"] # l_plus = np.asarray([0, -2.0/omega, 0]) # l_minus = np.asarray([1.0, -2.0*(1.0 - 1.0/omega), 1.0]) l_plus = self.stencils[-1].l_plus_jacobi(omega) l_minus = self.stencils[-1].l_minus_jacobi(omega) self.smoothers.append(SplitSmoother(l_plus, l_minus, self.levels[-1])) elif kwargs["smoothing_type"] is "ilu": self.smoothers.append(ILUSmoother(self.stencils[-1], self.levels[-1], **kwargs["smooth_opts"])) else: raise ValueError("Wrong smoothing type") # append interpolation self.ipl_ops.append(kwargs["ipl_class"](self.levels[-2], self.levels[-1], *kwargs.get("ipl_opts"), pre_assign=iadd)) self.rst_ops.append(kwargs["rst_class"](self.levels[-1], self.levels[-2], *kwargs.get("rst_opts")))
def __init__(self, *args, **kwargs): """ Parameters ---------- rhs_function_wrt_space : :py:class:`callable` function returning the space-dependent values for the right hand side as used by the space solver boundaries : :py:class:`None` or :py:class:`list` of :py:class:`str` *(optional)* defaults to ``periodic`` for each dimension boundary_functions : :py:class:`None` or :py:class:`list` of :py:class:`callable` *(optional)* functions defined on the boundaries of the geometry geometry : :py:class:`None` or :py:class:`numpy.ndarray` *(optional)* specifying the dimension and extend of the geometry """ assert_is_instance(self, IProblem, message="This Mixin is only valid for IProblems.", checking_obj=self) assert_named_argument('rhs_function_wrt_space', kwargs, descriptor="RHS for space solver", checking_obj=self) assert_is_callable(kwargs['rhs_function_wrt_space'], descriptor="RHS for space solver", checking_obj=self) self._rhs_function_wrt_space = kwargs['rhs_function_wrt_space'] # check if boundary conditions are specified if kwargs.get('boundaries') is None: self._boundaries = ['periodic'] * len(self.spacial_dim) elif isinstance(kwargs['boundaries'], str) \ and kwargs['boundaries'] in MultigridProblemMixin.valid_boundary_conditions: self._boundaries = [kwargs['boundaries']] * len(self.spacial_dim) elif isinstance(kwargs['boundaries'], list): check = 0 for bc in kwargs['boundaries']: if bc in MultigridProblemMixin.valid_boundary_conditions: check += 1 if check == len(self.spacial_dim) * 2: self._boundaries = kwargs['boundaries'] else: LOG.warning('Boundary specifications are not valid, will use periodic boundaries for each dimension.') self._boundaries = ['periodic'] * len(self.spacial_dim) else: LOG.warning('Boundary specifications are not valid, will use periodic boundaries for each dimension') self._boundaries = ['periodic'] * len(self.spacial_dim) # assign according to the boundary conditions the right functions if kwargs.get('boundary_functions') is None: self._boundary_functions = [None] * len(self.spacial_dim) else: assert_is_instance(kwargs['boundary_functions'], list, descriptor="Boundary Functions", checking_obj=self) check = 0 assert_condition(len(kwargs['boundary_functions']) == len(self.spacial_dim), ValueError, message="Not enough boundary functions given.", checking_obj=self) for ftpls in kwargs['boundary_functions']: if ftpls is 'dirichlet': assert_is_instance(ftpls, list, message="Dirichlet function list not available", checking_obj=self) assert_condition(len(ftpls) == 2, ValueError, message="Wrong number of functions", checking_obj=self) assert_is_callable(ftpls[0], "Not a function", self) assert_is_callable(ftpls[1], "Not a function", self) check += 1 self._boundary_functions = kwargs['boundary_functions'] # construct or save the geometry if kwargs.get('geometry') is None: self._geometry = np.asarray([[0, 1]] * len(self.spacial_dim)) else: assert_is_instance(kwargs['geometry'], np.ndarray, descriptor="Geometry", checking_obj=self) assert_condition(len(kwargs["geometry"].shape) == 2, ValueError, message="Numpy array has the wrong dimensions", checking_obj=self) assert_condition(kwargs['geometry'].shape[0] == len(self.spacial_dim) and kwargs['geometry'].shape[1] == 2, ValueError, message="Numpy array has a wrong shape", checking_obj=self) self._geometry = kwargs['geometry'] self._rhs_space_operators = {} self._implicit_solve_method = kwargs.get('implicit_solve_method', 'direct') self._mg_core = None # the Space tensor which is actually used self._act_space_tensor = None self._act_grid_distances = None # the points actually used self._act_npoints = None
def mg_solve(self, next_x, method='direct', **kwargs): """Runs the multigrid solver This is where all the magic happens on each call of the space solver from the iterative time solver, i.e. on every iteration for each time-level in each sweep on each step. Parameters ---------- method : :py:class:`str` defaults to ``direct`` ``mg`` for full multigrid cycles; additional keyword arguments passed to the multigrid solver can be given; additional arguments required: ``mg_level`` ``direct`` for using the a predefined multigrid smoother as a direct solver via :py:class:`.DirectSolverSmoother`; additional arguments required: ``mg_level`` ``stencil`` Raises ------ ValueError if given ``method`` is not one of ``mg`` or ``direct`` Returns ------- solution """ if method == 'mg': assert_named_argument('stencil_fnc', kwargs, descriptor="Stencil Generation Function", checking_obj=self) assert_is_callable(kwargs['stencil_fnc'], descriptor="Stencil Generation Function", checking_obj=self) # LOG.debug("Using Multigrid as implicit space solver.") mg_core_options = {} mg_core_options.update(MG_SMOOTHER_PRESETS["Jacobi"]) mg_core_options.update(MG_LEVEL_PRESETS["Standard-1D"]) mg_core_options.update(MG_RESTRICTION_PRESETS["Standard-1D"]) mg_core_options.update(MG_INTERPOLATION_PRESETS["Standard-1D"]) mg_core_options["shape_coarse"] = 4 mg_core_options["n_pre"] = 1 mg_core_options["n_post"] = 1 self.mg_core = MultiGridCore(self, lambda h: (kwargs['stencil_fnc'](h), np.array([1])), **mg_core_options) self.mg_core.levels[-1].mid[:] = next_x.reshape(self.mg_core.levels[-1].mid.shape) self.mg_core.levels[-1].rhs = kwargs['rhs'].reshape(self.mg_core.levels[-1].rhs.shape) self.mg_core.pad(-1) self.mg_core.modify_rhs(-1) self.mg_core.v_cycle() self.mg_core.v_cycle() # LOG.debug("input: %s --> %s" % (next_x.shape, self._mg_core.levels[-1].mid.shape)) return self.mg_core.levels[-1].mid.reshape(next_x.shape) elif method == 'direct': if kwargs.get('solver') is None: # assert_named_argument('mg_level', kwargs, types=IMultigridLevel, descriptor="Multigrid Level", # checking_obj=self) assert_named_argument('stencil', kwargs, types=Stencil, descriptor="MG Stencil", checking_obj=self) solver_function = DirectSolverSmoother(kwargs['stencil'], kwargs['mg_level']).relax else: solver_function = kwargs['solver'] # LOG.debug("next_x.shape: {:s}".format(next_x.shape)) return solver_function(next_x) else: raise ValueError("Unknown method: '%s'" % method)
def rhs_function_wrt_space(self, function): assert_is_callable(function, descriptor="Function of RHS w.r.t. Space", checking_obj=self) self._rhs_function_wrt_space = function