def errornorm(u, uh, norm_type="L2", degree_rise=None, mesh=None): """Compute the error :math:`e = u - u_h` in the specified norm. :arg u: a :class:`.Function` or UFL expression containing an "exact" solution :arg uh: a :class:`.Function` containing the approximate solution :arg norm_type: the type of norm to compute, see :func:`.norm` for details of supported norm types. :arg degree_rise: ignored. :arg mesh: an optional mesh on which to compute the error norm (currently ignored). """ urank = len(u.ufl_shape) uhrank = len(uh.ufl_shape) if urank != uhrank: raise RuntimeError("Mismatching rank between u and uh") if not isinstance(uh, function.Function): raise ValueError("uh should be a Function, is a %r", type(uh)) if isinstance(u, function.Function): degree_u = u.function_space().ufl_element().degree() degree_uh = uh.function_space().ufl_element().degree() if degree_uh > degree_u: warning("Degree of exact solution less than approximation degree") return norm(u - uh, norm_type=norm_type, mesh=mesh)
def _form_string_kernel(body, measure, args, **kwargs): kargs = [] if body.find("][") >= 0: warning("""Your kernel body contains a double indirection.\n""" """You should update it to single indirections.\n""" """\n""" """Mail [email protected] for advice.\n""") for var, (func, intent) in args.items(): if isinstance(func, constant.Constant): if intent is not READ: raise RuntimeError("Only READ access is allowed to Constant") # Constants modelled as Globals, so no need for double # indirection ndof = func.dat.cdim kargs.append(ast.Decl(ScalarType_c, ast.Symbol(var, (ndof, )), qualifiers=["const"])) else: # Do we have a component of a mixed function? if isinstance(func, Indexed): c, i = func.ufl_operands idx = i._indices[0]._value ndof = c.function_space()[idx].finat_element.space_dimension() else: if len(func.function_space()) > 1: raise NotImplementedError("Must index mixed function in par_loop.") ndof = func.function_space().finat_element.space_dimension() if measure.integral_type() == 'interior_facet': ndof *= 2 kargs.append(ast.Decl(ScalarType_c, ast.Symbol(var, (ndof, )))) body = body.replace(var+".dofs", str(ndof)) return pyop2.Kernel(ast.FunDecl("void", "par_loop_kernel", kargs, ast.FlatBlock(body), pred=["static"]), "par_loop_kernel", **kwargs)
def _form_string_kernel(body, measure, args, **kwargs): kargs = [] if body.find("][") >= 0: warning("""Your kernel body contains a double indirection.\n""" """You should update it to single indirections.\n""" """\n""" """Mail [email protected] for advice.\n""") for var, (func, intent) in args.items(): if isinstance(func, constant.Constant): if intent is not READ: raise RuntimeError("Only READ access is allowed to Constant") # Constants modelled as Globals, so no need for double # indirection ndof = func.dat.cdim kargs.append(ast.Decl("double", ast.Symbol(var, (ndof, )), qualifiers=["const"])) else: # Do we have a component of a mixed function? if isinstance(func, Indexed): c, i = func.ufl_operands idx = i._indices[0]._value ndof = c.function_space()[idx].finat_element.space_dimension() else: if len(func.function_space()) > 1: raise NotImplementedError("Must index mixed function in par_loop.") ndof = func.function_space().finat_element.space_dimension() if measure.integral_type() == 'interior_facet': ndof *= 2 kargs.append(ast.Decl("double", ast.Symbol(var, (ndof, )))) body = body.replace(var+".dofs", str(ndof)) return pyop2.Kernel(ast.FunDecl("void", "par_loop_kernel", kargs, ast.FlatBlock(body), pred=["static"]), "par_loop_kernel", **kwargs)
def TensorFunctionSpaceHierarchy(mesh_hierarchy, *args, **kwargs): from firedrake.logging import warning, RED warning( RED % "TensorFunctionSpaceHierarchy is obsolete. Just build a FunctionSpace on the relevant mesh" ) return tuple( firedrake.TensorFunctionSpace(mesh, *args, **kwargs) for mesh in mesh_hierarchy)
def get_patches(self, V): mesh = V._mesh mesh_dm = mesh.topology_dm if mesh.layers: warning("applying ASMVankaPC on an extruded mesh") # Obtain the topological entities to use to construct the stars depth = PETSc.Options().getInt(self.prefix + "construct_dim", default=-1) height = PETSc.Options().getInt(self.prefix + "construct_codim", default=-1) if (depth == -1 and height == -1) or (depth != -1 and height != -1): raise ValueError(f"Must set exactly one of {self.prefix}construct_dim or {self.prefix}construct_codim") # Accessing .indices causes the allocation of a global array, # so we need to cache these for efficiency V_local_ises_indices = [] for (i, W) in enumerate(V): V_local_ises_indices.append(V.dof_dset.local_ises[i].indices) # Build index sets for the patches ises = [] if depth != -1: (start, end) = mesh_dm.getDepthStratum(depth) else: (start, end) = mesh_dm.getHeightStratum(height) for seed in range(start, end): # Only build patches over owned DoFs if mesh_dm.getLabelValue("pyop2_ghost", seed) != -1: continue # Create point list from mesh DM star, _ = mesh_dm.getTransitiveClosure(seed, useCone=False) pt_array = set() for pt in star.tolist(): closure, _ = mesh_dm.getTransitiveClosure(seed, useCone=True) pt_array.update(closure.tolist()) # Get DoF indices for patch indices = [] for (i, W) in enumerate(V): section = W.dm.getDefaultSection() for p in pt_array: dof = section.getDof(p) if dof <= 0: continue off = section.getOffset(p) # Local indices within W W_indices = numpy.arange(off*W.value_size, W.value_size * (off + dof), dtype=IntType) indices.extend(V_local_ises_indices[i][W_indices]) iset = PETSc.IS().createGeneral(indices, comm=COMM_SELF) ises.append(iset) return ises
def errornorm(u, uh, norm_type="L2", degree_rise=3, mesh=None): """Compute the error :math:`e = u - u_h` in the specified norm. :arg u: a :class:`.Function` containing an "exact" solution :arg uh: a :class:`.Function` containing the approximate solution :arg norm_type: the type of norm to compute, see :func:`.norm` for details of supported norm types. :arg degree_rise: increase in polynomial degree to use as the approximation space for computing the error. :arg mesh: an optional mesh on which to compute the error norm (currently ignored). This function works by :func:`.project`\ing ``u`` and ``uh`` into a space of degree ``degree_rise`` higher than the degree of ``uh`` and computing the error there. """ urank = len(u.ufl_shape) uhrank = len(uh.ufl_shape) rank = urank if urank != uhrank: raise RuntimeError("Mismatching rank between u and uh") degree = uh.function_space().ufl_element().degree() if isinstance(degree, tuple): degree = max(degree) + degree_rise else: degree += degree_rise # The exact solution might be an expression, in which case this test is irrelevant. if isinstance(u, function.Function): degree_u = u.function_space().ufl_element().degree() if degree > degree_u: warning("Degree of exact solution less than approximation degree") mesh = uh.function_space().mesh() if rank == 0: V = functionspace.FunctionSpace(mesh, 'DG', degree) elif rank == 1: V = functionspace.VectorFunctionSpace(mesh, 'DG', degree, dim=u.ufl_shape[0]) else: raise RuntimeError("Don't know how to compute error norm for tensor valued functions") u_ = projection.project(u, V) uh_ = projection.project(uh, V) uh_ -= u_ return norm(uh_, norm_type=norm_type, mesh=mesh)
def _extract_kwargs(**kwargs): parameters = kwargs.get('solver_parameters', None) if 'parameters' in kwargs: warning(RED % "The 'parameters' keyword is deprecated, use 'solver_parameters' instead.") parameters = kwargs['parameters'] if 'solver_parameters' in kwargs: warning(RED % "'parameters' and 'solver_parameters' passed, using the latter") parameters = kwargs['solver_parameters'] # Make sure we don't stomp on a dict the user has passed in. parameters = parameters.copy() if parameters is not None else {} nullspace = kwargs.get('nullspace', None) tnullspace = kwargs.get('transpose_nullspace', None) options_prefix = kwargs.get('options_prefix', None) return parameters, nullspace, tnullspace, options_prefix
def FunctionHierarchy(fs_hierarchy, functions=None): """ outdated and returns warning & list of functions corresponding to each level of a functionspace hierarchy :arg fs_hierarchy: the :class:`~.FunctionSpaceHierarchy` to build on. :arg functions: optional :class:`~.Function` for each level. """ from firedrake.logging import warning, RED warning(RED % "FunctionHierarchy is obsolete. Falls back by returning list of functions") if functions is not None: assert len(functions) == len(fs_hierarchy) for f, V in zip(functions, fs_hierarchy): assert f.function_space() == V return tuple(functions) else: return tuple([firedrake.Function(f) for f in fs_hierarchy])
def inserted_options(self): """Context manager inside which the petsc options database contains the parameters from this object.""" from firedrake.logging import warning try: for k, v in self.parameters.items(): key = self.options_prefix + k if type(v) is bool: if "monitor" in k or "view" in k: warning("""Firedrake will stop translating True/False options soon.\n""" """This is to allow controlling boolean options with a default value of True.\n""" """To obtain the old behaviour you should translate:\n""" """ {%r: True} => {"option": None}\n""" """and\n""" """ {%r: False} => {}\n""" % (k, k)) if v: self.options_object[key] = None else: self.options_object[key] = v yield finally: for k in self.to_delete: del self.options_object[self.options_prefix + k]
def inserted_options(self): """Context manager inside which the petsc options database contains the parameters from this object.""" from firedrake.logging import warning try: for k, v in self.parameters.items(): key = self.options_prefix + k if type(v) is bool: if "monitor" in k or "view" in k: warning( """Firedrake will stop translating True/False options soon.\n""" """This is to allow controlling boolean options with a default value of True.\n""" """To obtain the old behaviour you should translate:\n""" """ {%r: True} => {"option": None}\n""" """and\n""" """ {%r: False} => {}\n""" % (k, k)) if v: self.options_object[key] = None else: self.options_object[key] = v yield finally: for k in self.to_delete: del self.options_object[self.options_prefix + k]
def stepper(self, t_start, t_end, w, t_visualization): """ Timesteps the shallow water equations from t_start to t_end using a 3rd order SSP Runge-Kutta scheme :param t_start: start time :type t_start: float :param t_end: end time :type t_end: float :param w: Current state vector function :param t_visualization: time interval to write the free surface water depth to a .pvd file :type t_visualization: float """ self.w = w # setup the solver - this is a timed stage for profiling reasons! with timed_stage("Setup of forms and solver"): self.__solver_setup() # used as the original state vector in each RK3 step self.w_old = Function(self.V).assign(self.w) # initial slope modification self.__update_slope_modification() hout = Function(self.v_h) hout.rename("free surface depth") # free surface depth hout_file = File("h.pvd") # bed depth bout = Function(self.v_h).project(self.b_) bout.rename("topography") bout_file = File("b.pvd") self.Project = Projector( conditional(self.h <= (self.plot_tol * self.E), self.b_, self.h + self.b_), hout) self.Project.project() hout_file.write(hout) bout_file.write(bout) self.t = t_start # start counter of how many time dumps self.c = 1 # warning boolean marker self.wmark = 0 while self.t < t_end: # find new timestep with timed_stage("Finding adaptive time-step"): self.dt = self.AT.FindTimestep(self.w) self.Dt.assign(self.dt) # check that prescribed timestep doesn't fall below minimum timestep if self.dt < self.MinTimestep: if self.wmark is False: warning(RED % "Minimum timestep has been reached. " + "Simulation might become unstable.") self.wmark = 1 self.dt = self.MinTimestep self.Dt.assign(self.dt) # check if remaining time to next time dump is less than timestep # correct if neeeded if self.dt + self.t > self.c * t_visualization: self.dt = (self.c * t_visualization) - self.t self.Dt.assign(self.dt) # check if remaining time to end time is less than timestep # correct if needed if (self.t <= (self.c + 1) * t_visualization) and (self.dt + self.t > t_end): self.dt = t_end - self.t self.Dt.assign(self.dt) with timed_stage("Runge-Kutta time-stepping scheme"): self.solver.solve() self.w.assign(self.w_) # slope limiter with timed_stage("Slope limiting"): self.__update_slope_limiter() # slope modification with timed_stage("Slope modification"): self.__update_slope_modification() self.solver.solve() self.w.assign((3.0 / 4.0) * self.w_old + (1.0 / 4.0) * self.w_) # slope limiter with timed_stage("Slope limiting"): self.__update_slope_limiter() # slope modification with timed_stage("Slope modification"): self.__update_slope_modification() self.solver.solve() self.w.assign((1.0 / 3.0) * self.w_old + (2.0 / 3.0) * self.w_) # slope limiter with timed_stage("Slope limiting"): self.__update_slope_limiter() # slope modification with timed_stage("Slope modification"): self.__update_slope_modification() self.w_old.assign(self.w) # timstep complete - dump realisation if needed self.t += self.dt with timed_stage("Visualization"): if self.t == self.c * t_visualization: self.Project.project() hout_file.write(hout) bout_file.write(bout) self.c += 1 self.dt = self.initial_dt self.Dt.assign(self.dt) # return timestep self.dt = self.initial_dt self.Dt.assign(self.dt) return self.w
def stepper(self, t_start, t_end, w, t_visualization): """ Timesteps the shallow water equations from t_start to t_end using a 3rd order SSP Runge-Kutta scheme :param t_start: start time :type t_start: float :param t_end: end time :type t_end: float :param w: Current state vector function :param t_visualization: time interval to write the free surface water depth to a .pvd file :type t_visualization: float """ self.w = w # setup the solver - this is a timed stage for profiling reasons! with timed_stage("Setup of forms and solver"): self.__solver_setup() # used as the original state vector in each RK3 step self.w_old = Function(self.V).assign(self.w) # initial slope modification self.__update_slope_modification() hout = Function(self.v_h) hout.rename("free surface depth") # free surface depth hout_file = File("h.pvd") # bed depth bout = Function(self.v_h).project(self.b_) bout.rename("topography") bout_file = File("b.pvd") self.Project = Projector(conditional(self.h <= (self.plot_tol * self.E), self.b_, self.h + self.b_), hout) self.Project.project() hout_file.write(hout) bout_file.write(bout) self.t = t_start # start counter of how many time dumps self.c = 1 # warning boolean marker self.wmark = 0 while self.t < t_end: # find new timestep with timed_stage("Finding adaptive time-step"): self.dt = self.AT.FindTimestep(self.w) self.Dt.assign(self.dt) # check that prescribed timestep doesn't fall below minimum timestep if self.dt < self.MinTimestep: if self.wmark is False: warning(RED % "Minimum timestep has been reached. " + "Simulation might become unstable.") self.wmark = 1 self.dt = self.MinTimestep self.Dt.assign(self.dt) # check if remaining time to next time dump is less than timestep # correct if neeeded if self.dt + self.t > self.c * t_visualization: self.dt = (self.c * t_visualization) - self.t self.Dt.assign(self.dt) # check if remaining time to end time is less than timestep # correct if needed if (self.t <= (self.c + 1) * t_visualization) and (self.dt + self.t > t_end): self.dt = t_end - self.t self.Dt.assign(self.dt) with timed_stage("Runge-Kutta time-stepping scheme"): self.solver.solve() self.w.assign(self.w_) # slope limiter with timed_stage("Slope limiting"): self.__update_slope_limiter() # slope modification with timed_stage("Slope modification"): self.__update_slope_modification() self.solver.solve() self.w.assign((3.0 / 4.0) * self.w_old + (1.0 / 4.0) * self.w_) # slope limiter with timed_stage("Slope limiting"): self.__update_slope_limiter() # slope modification with timed_stage("Slope modification"): self.__update_slope_modification() self.solver.solve() self.w.assign((1.0 / 3.0) * self.w_old + (2.0 / 3.0) * self.w_) # slope limiter with timed_stage("Slope limiting"): self.__update_slope_limiter() # slope modification with timed_stage("Slope modification"): self.__update_slope_modification() self.w_old.assign(self.w) # timstep complete - dump realisation if needed self.t += self.dt with timed_stage("Visualization"): if self.t == self.c * t_visualization: self.Project.project() hout_file.write(hout) bout_file.write(bout) self.c += 1 self.dt = self.initial_dt self.Dt.assign(self.dt) # return timestep self.dt = self.initial_dt self.Dt.assign(self.dt) return self.w
from ufl.indexed import Indexed from ufl.domain import join_domains from pyop2 import READ, WRITE, RW, INC, MIN, MAX import pyop2 import loopy import coffee.base as ast from firedrake.logging import warning from firedrake import constant from firedrake.utils import ScalarType_c try: from cachetools import LRUCache kernel_cache = LRUCache(maxsize=128) except ImportError: warning("cachetools not available, firedrake.par_loop calls will be slowed down") kernel_cache = None __all__ = ['par_loop', 'direct', 'READ', 'WRITE', 'RW', 'INC', 'MIN', 'MAX'] class _DirectLoop(object): r"""A singleton object which can be used in a :func:`par_loop` in place of the measure in order to indicate that the loop is a direct loop over degrees of freedom.""" def integral_type(self): return "direct" def __repr__(self):
import sys import ufl import ctypes from ctypes import POINTER, c_int, c_double, c_void_p from pyop2 import op2 from pyop2.datatypes import ScalarType, IntType, as_ctypes from firedrake import functionspaceimpl from firedrake.logging import warning from firedrake import utils from firedrake import vector try: import cachetools except ImportError: warning( "cachetools not available, expression assembly will be slowed down") cachetools = None __all__ = ['Function', 'PointNotInDomainError'] class _CFunction(ctypes.Structure): """C struct collecting data from a :class:`Function`""" _fields_ = [ ("n_cols", c_int), ("n_layers", c_int), ("coords", POINTER(c_double)), ("coords_map", POINTER(as_ctypes(IntType))), # FIXME: what if f does not have type double? ("f", POINTER(c_double)), ("f_map", POINTER(as_ctypes(IntType))),
def assemble(f, tensor=None, bcs=None, form_compiler_parameters=None, inverse=False, mat_type=None, sub_mat_type=None, appctx={}, options_prefix=None, **kwargs): r"""Evaluate f. :arg f: a :class:`~ufl.classes.Form`, :class:`~ufl.classes.Expr` or a :class:`~slate.TensorBase` expression. :arg tensor: an existing tensor object to place the result in (optional). :arg bcs: a list of boundary conditions to apply (optional). :arg form_compiler_parameters: (optional) dict of parameters to pass to the form compiler. Ignored if not assembling a :class:`~ufl.classes.Form`. Any parameters provided here will be overridden by parameters set on the :class:`~ufl.classes.Measure` in the form. For example, if a ``quadrature_degree`` of 4 is specified in this argument, but a degree of 3 is requested in the measure, the latter will be used. :arg inverse: (optional) if f is a 2-form, then assemble the inverse of the local matrices. :arg mat_type: (optional) string indicating how a 2-form (matrix) should be assembled -- either as a monolithic matrix ('aij' or 'baij'), a block matrix ('nest'), or left as a :class:`.ImplicitMatrix` giving matrix-free actions ('matfree'). If not supplied, the default value in ``parameters["default_matrix_type"]`` is used. BAIJ differs from AIJ in that only the block sparsity rather than the dof sparsity is constructed. This can result in some memory savings, but does not work with all PETSc preconditioners. BAIJ matrices only make sense for non-mixed matrices. :arg sub_mat_type: (optional) string indicating the matrix type to use *inside* a nested block matrix. Only makes sense if ``mat_type`` is ``nest``. May be one of 'aij' or 'baij'. If not supplied, defaults to ``parameters["default_sub_matrix_type"]``. :arg appctx: Additional information to hang on the assembled matrix if an implicit matrix is requested (mat_type "matfree"). :arg options_prefix: PETSc options prefix to apply to matrices. If f is a :class:`~ufl.classes.Form` then this evaluates the corresponding integral(s) and returns a :class:`float` for 0-forms, a :class:`.Function` for 1-forms and a :class:`.Matrix` or :class:`.ImplicitMatrix` for 2-forms. If f is an expression other than a form, it will be evaluated pointwise on the :class:`.Function`\s in the expression. This will only succeed if all the Functions are on the same :class:`.FunctionSpace`. If f is a Slate tensor expression, then it will be compiled using Slate's linear algebra compiler. If ``tensor`` is supplied, the assembled result will be placed there, otherwise a new object of the appropriate type will be returned. If ``bcs`` is supplied and ``f`` is a 2-form, the rows and columns of the resulting :class:`.Matrix` corresponding to boundary nodes will be set to 0 and the diagonal entries to 1. If ``f`` is a 1-form, the vector entries at boundary nodes are set to the boundary condition values. """ if "nest" in kwargs: nest = kwargs.pop("nest") from firedrake.logging import warning, RED warning(RED % "The 'nest' argument is deprecated, please set 'mat_type' instead") if nest is not None: mat_type = "nest" if nest else "aij" collect_loops = kwargs.pop("collect_loops", False) allocate_only = kwargs.pop("allocate_only", False) if len(kwargs) > 0: raise TypeError("Unknown keyword arguments '%s'" % ', '.join(kwargs.keys())) if isinstance(f, (ufl.form.Form, slate.TensorBase)): loops = _assemble(f, tensor=tensor, bcs=solving._extract_bcs(bcs), form_compiler_parameters=form_compiler_parameters, inverse=inverse, mat_type=mat_type, sub_mat_type=sub_mat_type, appctx=appctx, assemble_now=not collect_loops, allocate_only=allocate_only, options_prefix=options_prefix) loops = tuple(loops) if collect_loops and not allocate_only: # Will this be useful? return loops else: for l in loops: m = l() return m elif isinstance(f, ufl.core.expr.Expr): return assemble_expressions.assemble_expression(f) else: raise TypeError("Unable to assemble: %r" % f)
def VectorFunctionSpaceHierarchy(mesh_hierarchy, *args, **kwargs): from firedrake.logging import warning, RED warning(RED % "VectorFunctionSpaceHierarchy is obsolete. Just build a FunctionSpace on the relevant mesh") return tuple(firedrake.VectorFunctionSpace(mesh, *args, **kwargs) for mesh in mesh_hierarchy)
def flatten_parameters(parameters, sep="_"): """Flatten a nested parameters dict, joining keys with sep. :arg parameters: a dict to flatten. :arg sep: separator of keys. Used to flatten parameter dictionaries with nested structure to a flat dict suitable to pass to PETSc. For example: .. code-block:: python flatten_parameters({"a": {"b": {"c": 4}, "d": 2}, "e": 1}, sep="_") => {"a_b_c": 4, "a_d": 2, "e": 1} If a "prefix" key already ends with the provided separator, then it is not used to concatenate the keys. Hence: .. code-block:: python flatten_parameters({"a_": {"b": {"c": 4}, "d": 2}, "e": 1}, sep="_") => {"a_b_c": 4, "a_d": 2, "e": 1} # rather than => {"a__b_c": 4, "a__d": 2, "e": 1} """ from firedrake.logging import warning new = type(parameters)() if not len(parameters): return new def flatten(parameters, *prefixes): """Iterate over nested dicts, yielding (*keys, value) pairs.""" sentinel = object() try: option = sentinel for option, value in parameters.items(): # Recurse into values to flatten any dicts. for pair in flatten(value, option, *prefixes): yield pair # Make sure zero-length dicts come back. if option is sentinel: yield (prefixes, parameters) except AttributeError: # Non dict values are just returned. yield (prefixes, parameters) def munge(keys): """Ensure that each intermediate key in keys ends in sep. Also, reverse the list.""" for key in reversed(keys[1:]): if len(key) and not key.endswith(sep): yield key + sep else: yield key else: yield keys[0] for keys, value in flatten(parameters): option = "".join(map(str, munge(keys))) if option in new: warning("Ignoring duplicate option: %s (existing value %s, new value %s)", option, new[option], value) new[option] = value return new
def MixedFunctionSpaceHierarchy(mesh_hierarchy, *args, **kwargs): from firedrake.logging import warning, RED warning(RED % "TensorFunctionSpaceHierarchy is obsolete. Just build a FunctionSpace on the relevant mesh") kwargs.pop("mesh", None) return tuple(firedrake.MixedFunctionSpace(*args, mesh=mesh, **kwargs) for mesh in mesh_hierarchy)
def flatten_parameters(parameters, sep="_"): """Flatten a nested parameters dict, joining keys with sep. :arg parameters: a dict to flatten. :arg sep: separator of keys. Used to flatten parameter dictionaries with nested structure to a flat dict suitable to pass to PETSc. For example: .. code-block:: python flatten_parameters({"a": {"b": {"c": 4}, "d": 2}, "e": 1}, sep="_") => {"a_b_c": 4, "a_d": 2, "e": 1} If a "prefix" key already ends with the provided separator, then it is not used to concatenate the keys. Hence: .. code-block:: python flatten_parameters({"a_": {"b": {"c": 4}, "d": 2}, "e": 1}, sep="_") => {"a_b_c": 4, "a_d": 2, "e": 1} # rather than => {"a__b_c": 4, "a__d": 2, "e": 1} """ new = type(parameters)() if not len(parameters): return new def flatten(parameters, *prefixes): """Iterate over nested dicts, yielding (*keys, value) pairs.""" sentinel = object() try: option = sentinel for option, value in parameters.items(): # Recurse into values to flatten any dicts. for pair in flatten(value, option, *prefixes): yield pair # Make sure zero-length dicts come back. if option is sentinel: yield (prefixes, parameters) except AttributeError: # Non dict values are just returned. yield (prefixes, parameters) def munge(keys): """Ensure that each intermediate key in keys ends in sep. Also, reverse the list.""" for key in reversed(keys[1:]): if len(key) and not key.endswith(sep): yield key + sep else: yield key else: yield keys[0] for keys, value in flatten(parameters): option = "".join(map(str, munge(keys))) if option in new: warning( "Ignoring duplicate option: %s (existing value %s, new value %s)", option, new[option], value) new[option] = value return new
def set_defaults(solver_parameters, arguments, *, ksp_defaults={}, snes_defaults={}): """Set defaults for solver parameters. :arg solver_parameters: dict of user solver parameters to override/extend defaults :arg arguments: arguments for the bilinear form (need to know if we have a Real block). :arg ksp_defaults: Default KSP parameters. :arg snes_defaults: Default SNES parameters.""" if solver_parameters: # User configured something, try and set sensible direct solve # defaults for missing bits. parameters = solver_parameters.copy() for k, v in snes_defaults.items(): parameters.setdefault(k, v) keys = frozenset(parameters.keys()) ksp_defaults = ksp_defaults.copy() skip = set() if "pc_type" in keys: # Might reasonably expect to get petsc defaults skip.update({"pc_factor_mat_solver_type", "ksp_type"}) if parameters.get("mat_type") in {"matfree", "nest"}: # Non-LU defaults. ksp_defaults["ksp_type"] = "gmres" ksp_defaults["pc_type"] = "jacobi" for k, v in ksp_defaults.items(): if k not in skip: parameters.setdefault(k, v) return parameters # OK, we're in full defaults mode now. parameters = dict(chain.from_iterable( d.items() for d in (ksp_defaults, snes_defaults))) if any(V.ufl_element().family() == "Real" for a in arguments for V in a.function_space()): test, trial = arguments if test.function_space() != trial.function_space(): # Don't know what to do here. How did it happen? raise ValueError("Can't generate defaults for non-square problems with real blocks") fields = [] reals = [] for i, V_ in enumerate(test.function_space()): if V_.ufl_element().family() == "Real": reals.append(i) else: fields.append(i) if len(fields) == 0: # Just reals, GMRES opts = {"ksp_type": "gmres", "pc_type": "none"} parameters.update(opts) else: warning("Real block detected, generating Schur complement elimination PC") # Full Schur complement eliminating onto Real blocks. opts = {"mat_type": "matfree", "ksp_type": "fgmres", "pc_type": "fieldsplit", "pc_fieldsplit_type": "schur", "pc_fieldsplit_schur_fact_type": "full", "pc_fieldsplit_0_fields": ",".join(map(str, fields)), "pc_fieldsplit_1_fields": ",".join(map(str, reals)), "fieldsplit_0": { "ksp_type": "preonly", "pc_type": "python", "pc_python_type": "firedrake.AssembledPC", "assembled": DEFAULT_KSP_PARAMETERS, }, "fieldsplit_1": { "ksp_type": "gmres", "pc_type": "none", }} parameters.update(opts) return parameters else: # We can assemble an AIJ matrix, factor it with a sparse # direct method. return parameters
def evict(self): """Run the cache eviction algorithm. This works out the permitted cache size and deletes objects until it is achieved. Cache values are assumed to have a ``value`` attribute and eviction occurs in increasing ``value`` order. Currently ``value`` is an index of the assembly operation, so older operations are evicted first. The cache will be evicted down to 90% of permitted size. The permitted size is either the explicit ``parameters["assembly_cache"]["max_bytes"]`` or it is the amount of memory per core scaled by ``parameters["assembly_cache"]["max_factor"]`` (by default the scale factor is 0.6). In MPI parallel, the nbytes of each cache entry is set to the maximum over all processes, while the available memory is set to the minimum. This produces a conservative caching policy which is guaranteed to result in the same evictions on each processor. """ if not parameters["assembly_cache"]["eviction"]: return max_cache_size = min(parameters["assembly_cache"]["max_bytes"] or float("inf"), (memory or float("inf")) * parameters["assembly_cache"]["max_factor"] ) if max_cache_size == float("inf"): if not self.evictwarned: warning("No maximum assembly cache size. Install psutil >= 2.0.0 or risk leaking memory!") self.evictwarned = True return cache_size = self.nbytes if cache_size < max_cache_size: return debug("Cache eviction triggered. %s bytes in cache, %s bytes allowed" % (cache_size, max_cache_size)) # Evict down to 90% full. bytes_to_evict = cache_size - 0.9 * max_cache_size sorted_cache = sorted(self.cache.items(), key=lambda x: x[1][1].value) nbytes = lambda x: x[1][1].nbytes candidates = [] while bytes_to_evict > 0: next = sorted_cache.pop(0) candidates.append(next) bytes_to_evict -= nbytes(next) for c in reversed(candidates): if bytes_to_evict + nbytes(c) < 0: # We may have been overzealous. bytes_to_evict += nbytes(c) else: del self.cache[c[0]]
def __lshift__(self, arg): from firedrake.logging import warning, RED warning(RED % "The << syntax is deprecated, use File.write") self.write(arg)
import ufl import ctypes from ctypes import POINTER, c_int, c_double, c_void_p import coffee.base as ast from pyop2 import op2 from firedrake import functionspaceimpl from firedrake.logging import warning from firedrake import utils from firedrake import vector try: import cachetools except ImportError: warning("cachetools not available, expression assembly will be slowed down") cachetools = None __all__ = ['Function', 'PointNotInDomainError'] valuetype = np.float64 class _CFunction(ctypes.Structure): """C struct collecting data from a :class:`Function`""" _fields_ = [("n_cols", c_int), ("n_layers", c_int), ("coords", POINTER(c_double)), ("coords_map", POINTER(c_int)),
def assemble(f, tensor=None, bcs=None, form_compiler_parameters=None, inverse=False, mat_type=None, sub_mat_type=None, appctx={}, options_prefix=None, **kwargs): """Evaluate f. :arg f: a :class:`~ufl.classes.Form`, :class:`~ufl.classes.Expr` or a :class:`~slate.TensorBase` expression. :arg tensor: an existing tensor object to place the result in (optional). :arg bcs: a list of boundary conditions to apply (optional). :arg form_compiler_parameters: (optional) dict of parameters to pass to the form compiler. Ignored if not assembling a :class:`~ufl.classes.Form`. Any parameters provided here will be overridden by parameters set on the :class:`~ufl.classes.Measure` in the form. For example, if a ``quadrature_degree`` of 4 is specified in this argument, but a degree of 3 is requested in the measure, the latter will be used. :arg inverse: (optional) if f is a 2-form, then assemble the inverse of the local matrices. :arg mat_type: (optional) string indicating how a 2-form (matrix) should be assembled -- either as a monolithic matrix ('aij' or 'baij'), a block matrix ('nest'), or left as a :class:`.ImplicitMatrix` giving matrix-free actions ('matfree'). If not supplied, the default value in ``parameters["default_matrix_type"]`` is used. BAIJ differs from AIJ in that only the block sparsity rather than the dof sparsity is constructed. This can result in some memory savings, but does not work with all PETSc preconditioners. BAIJ matrices only make sense for non-mixed matrices. :arg sub_mat_type: (optional) string indicating the matrix type to use *inside* a nested block matrix. Only makes sense if ``mat_type`` is ``nest``. May be one of 'aij' or 'baij'. If not supplied, defaults to ``parameters["default_sub_matrix_type"]``. :arg appctx: Additional information to hang on the assembled matrix if an implicit matrix is requested (mat_type "matfree"). :arg options_prefix: PETSc options prefix to apply to matrices. If f is a :class:`~ufl.classes.Form` then this evaluates the corresponding integral(s) and returns a :class:`float` for 0-forms, a :class:`.Function` for 1-forms and a :class:`.Matrix` or :class:`.ImplicitMatrix` for 2-forms. If f is an expression other than a form, it will be evaluated pointwise on the :class:`.Function`\s in the expression. This will only succeed if all the Functions are on the same :class:`.FunctionSpace`. If f is a Slate tensor expression, then it will be compiled using Slate's linear algebra compiler. If ``tensor`` is supplied, the assembled result will be placed there, otherwise a new object of the appropriate type will be returned. If ``bcs`` is supplied and ``f`` is a 2-form, the rows and columns of the resulting :class:`.Matrix` corresponding to boundary nodes will be set to 0 and the diagonal entries to 1. If ``f`` is a 1-form, the vector entries at boundary nodes are set to the boundary condition values. """ if "nest" in kwargs: nest = kwargs.pop("nest") from firedrake.logging import warning, RED warning( RED % "The 'nest' argument is deprecated, please set 'mat_type' instead") if nest is not None: mat_type = "nest" if nest else "aij" collect_loops = kwargs.pop("collect_loops", False) allocate_only = kwargs.pop("allocate_only", False) if len(kwargs) > 0: raise TypeError("Unknown keyword arguments '%s'" % ', '.join(kwargs.keys())) if isinstance(f, (ufl.form.Form, slate.TensorBase)): return _assemble(f, tensor=tensor, bcs=solving._extract_bcs(bcs), form_compiler_parameters=form_compiler_parameters, inverse=inverse, mat_type=mat_type, sub_mat_type=sub_mat_type, appctx=appctx, collect_loops=collect_loops, allocate_only=allocate_only, options_prefix=options_prefix) elif isinstance(f, ufl.core.expr.Expr): return assemble_expressions.assemble_expression(f) else: raise TypeError("Unable to assemble: %r" % f)
def __init__(self, code=None, element=None, cell=None, degree=None, **kwargs): r""" :param code: a string C statement, or list of statements. :param element: a :class:`~ufl.finiteelement.finiteelement.FiniteElement`, optional (currently ignored) :param cell: a :class:`~ufl.classes.Cell`, optional (currently ignored) :param degree: the degree of quadrature to use for evaluation (currently ignored) :param kwargs: user-defined values that are accessible in the Expression code. These values maybe updated by accessing the property of the same name. This can be used, for example, to pass in the current timestep to an Expression without necessitating recompilation. For example: .. code-block:: python f = Function(V) e = Expression('sin(x[0]*t)', t=t) while t < T: f.interpolate(e) ... t += dt e.t = t The currently ignored parameters are retained for API compatibility with Dolfin. """ # Init also called in mesh constructor, but expression can be built without mesh utils._init() self.code = None self._shape = () if code is not None: warning( "C string Expressions will be removed soon! See: https://www.firedrakeproject.org/interpolation.html#c-string-expressions" ) arr = np.array(code) self._shape = arr.shape # Flatten to something indexable for use. self.code = arr.flatten() for val in self.code: if str(val).strip() == "": raise ValueError("Cannot provide empty expression") self.cell = cell self.degree = degree # These attributes are required by ufl.Coefficient to render the repr # of an Expression. Since we don't call the ufl.Coefficient constructor # (since we don't yet know the element) we need to set them ourselves self._element = element self._repr = None self._count = 0 self._user_args = [] # Changing counter used to record when user changes values self._state = 0 # Save the kwargs so that when we rebuild an expression we can # reconstruct the user arguments. self._kwargs = {} if len(kwargs) == 0: # No need for magic, since there are no user arguments. return # We have to build a new class to add these properties to # since properties work on classes not instances and we don't # want every Expression to have all the properties of all # Expressions. cls = type(self.__class__.__name__, (self.__class__, ), {}) for slot, val in sorted(kwargs.items(), key=itemgetter(0)): # Save the argument for later reconstruction self._kwargs[slot] = val # Scalar arguments have to be treated specially val = np.array(val, dtype=np.float64) shape = val.shape rank = len(shape) if rank == 0: shape = 1 val = op2.Global(shape, val, dtype=ScalarType, name=slot) # Record the Globals in a known order (for later passing # to a par_loop). Remember their "name" too, so we can # construct a kwarg dict when applying python expressions. self._user_args.append((slot, val)) # And save them as an attribute setattr(self, '_%s' % slot, val) # We have to do this because of the worthlessness of # Python's support for closing over variables. def make_getx(slot): def getx(self): glob = getattr(self, '_%s' % slot) return glob.data_ro return getx def make_setx(slot): def setx(self, value): glob = getattr(self, '_%s' % slot) glob.data = value self._kwargs[slot] = value # Bump state self._state += 1 return setx # Add public properties for the user-defined variables prop = property(make_getx(slot), make_setx(slot)) setattr(cls, slot, prop) # Set the class on this instance to the newly built class with # properties attached. self.__class__ = cls