class MultigridProblemMixin(object): """Provides functionality of a problem to have multigrid as its space solver Contains every aspect of the Problem that has to be solved, like the stencil from which on may derive :math:`A_h` for each level. """ valid_boundary_conditions = ['periodic', 'dirichlet'] 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 evaluate_wrt_space(self, **kwargs): """ Parameters ---------- values : :py:class:`numpy.ndarray` """ assert_named_argument('values', kwargs, types=np.ndarray, descriptor="Values", checking_obj=self) return self.get_rhs_space_operators('default')\ .dot(kwargs['values'].flatten())\ .reshape(kwargs['values'].shape) @property def mg_core(self): return self._mg_core @mg_core.setter def mg_core(self, value): self._mg_core = value @property def rhs_function_wrt_space(self): return self._rhs_function_wrt_space @rhs_function_wrt_space.setter 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 @property def boundaries(self): """Getter for the boundarietypes """ return self._boundaries @property def boundary_functions(self): """Getter for the boundary functions """ return self._boundary_functions @property def geometry(self): """Getter for the geometry """ return self._geometry def get_rhs_space_operators(self, delta_time): if self._rhs_space_operators.get(delta_time) is None: raise RuntimeError("MG System Matrix not found for delta time %s." % delta_time) return self._rhs_space_operators[delta_time] def set_rhs_space_operator(self, delta_time, operator='default'): self._rhs_space_operators[delta_time] = operator 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 construct_space_tensor(self, number_of_points_list, stencil=None): """Constructs the Spacetensor which is important for the evaluation in the case of Dirichlet boundary conditions Parameters ---------- number_of_points_list : :py:class:`int` or :py:class:`numpy.ndarray` Number of points which will be distributed equiv-spaced on the grid """ if isinstance(number_of_points_list, (int, float, complex)): assert_is_instance(stencil, Stencil, descriptor="Stencil", checking_obj=self) npoints = int(number_of_points_list) # LOG.debug("Your number %s was modified to %s" % (number_of_points_list, npoints)) assert_condition(npoints > max(stencil.arr.shape), ValueError, message="Not enough points for the stencil", checking_obj=self) npoints = np.asarray([npoints] * len(self.spacial_dim)) elif isinstance(number_of_points_list, np.ndarray): assert_condition(len(number_of_points_list.shape) == 1 and number_of_points_list.size == len(self.spacial_dim), ValueError, message="The number_of_points list is wrong", checking_obj=self) npoints = np.floor(number_of_points_list) else: raise ValueError("Wrong number of points list") # first we assign the memory using numpy # spt(npoints,dim) self._act_npoints = npoints lspc = [] for i in range(len(self.spacial_dim)): lspc.append(np.linspace(self._geometry[i, 0], self._geometry[i, 1], npoints[i])) if len(self.spacial_dim) > 1: space_tensor = np.asarray(np.meshgrid(*lspc)) else: space_tensor = np.linspace(self._geometry[0, 0], self._geometry[0, 1], npoints) return space_tensor def fill_rhs(self, level): """Fills the rhs of an level """ if level.space_tensor is None: level.space_tensor = self.construct_space_tensor(list(level.mid.shape)) level.rhs[:] = self.rhs_function_wrt_space(level.mid, level.space_tensor) 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 print_lines_for_log(self): _lines = OrderedDict() _lines['Boundaries'] = OrderedDict( { 'Left': OrderedDict( { 'Type': self.boundaries[0], 'Fnc': self.boundary_functions[0][0].__str__() } ), 'Right': OrderedDict( { 'Type': self.boundaries[0], 'Fnc': self.boundary_functions[0][1].__str__() } ) } ) return _lines
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)