def set_linear_dirichlet_constant(mesh, dt, var_name='Temperature', bc_temperature=1300., temperature=300., source_value=0., axis=1, right=True, tol=1e-4, conductivity=1., density=1., specific_heat=1.): ''' Heat equation problem where an edge/surface is set at a given temperature and everything else at another. Parameters ---------- axis : int Controls boundary to which Dirichlet bc is applied. right : bool Dirichlet bc is applied to the planar and perpendicular face found in the max (True) or min (False) coordinate. ''' # function space V = FunctionSpace(mesh, 'Lagrange', 1) # boundary conditions bcs = [ set_dirichlet_bc_lim(V, value=bc_temperature, axis=axis, right=right, tol=tol) ] # initial condition u_initial = Constant(temperature) u_n = Function(V, name=var_name) u_n.interpolate(u_initial) # problem formulation f = Constant(source_value) u_n, a, L = get_formulation_constant_props(V, u_initial, f, dt, conductivity, density, specific_heat, var_name=var_name) u = Function(V, name=var_name) return u_n, a, L, u, bcs
def get_formulation(V, u_initial, f, dt, var_name='Temperature'): # interpolate initial condition u_n = Function(V, name=var_name) u_n.interpolate(u_initial) # function spaces u_h = TrialFunction(V) v = TestFunction(V) # formulation a = u_h * v * dx + dt * dot(grad(u_h), grad(v)) * dx L = (u_n + dt * f) * v * dx return u_n, a, L
def set_poisson_eq_2d(n=8): ''' Basic example where only the mesh refinement is controlled. ''' # mesh mesh = UnitSquareMesh(n, n) # function space V = FunctionSpace(mesh, 'P', 1) # bcs u_D = Expression('1 + x[0]*x[0] + 2*x[1]*x[1]', degree=2) def boundary(x, on_boundary): return on_boundary bc = DirichletBC(V, u_D, boundary) # variational problem u = TrialFunction(V) v = TestFunction(V) f = Constant(-6.0) a = dot(grad(u), grad(v)) * dx L = f * v * dx # define unknown u = Function(V) return a, L, u, bc
def read_checkpoint(self, V, name: str, counter: int = -1) -> Function: """Read finite element Function from checkpointing format Parameters ---------- V Function space of saved function. name Name of function as saved into XDMF file. counter : optional Position of function in the file within functions of the same name. Counter is used to read function saved as time series. To get last saved function use counter=-1, or counter=-2 for one before last, etc. Note ---- Parameter `V: Function space` must be the same as saved function space except for ordering of mesh entities. Returns ------- dolfin.function.function.Function The finite element Function read from checkpoint file """ V_cpp = getattr(V, "_cpp_object", V) u_cpp = self._cpp_object.read_checkpoint(V_cpp, name, counter) return Function(V, u_cpp.vector())
def set_linear_equal_opposite(mesh, dt, var_name='Temperature', axis=0, bc_temperature=300., source_value=0., conductivity=1., density=1., specific_heat=1., V=None): ''' Heat equation problem where two opposite edges/faces are set at the same temperature and the points in between follow a quadratic variation. Notes ----- * assumes opposite faces of interest are planar and perpendicular to the axis. ''' # function space if V is None: V = FunctionSpace(mesh, 'Lagrange', 1) # boundary conditions x_min, x_max = get_mesh_axis_lims(mesh, axis) bcs = [ set_dirichlet_bc(V, x, bc_temperature, axis) for x in [x_min, x_max] ] # initial condition c = (x_max - x_min) / 2 param = bc_temperature / (c**2) u_initial = Expression('param * (x[axis] - c) * (x[axis] - c)', degree=2, param=param, c=c, name='T', axis=axis) # problem formulation f = Constant(source_value) u_n, a, L = get_formulation_constant_props(V, u_initial, f, dt, conductivity, density, specific_heat, var_name=var_name) u = Function(V, name=var_name) return u_n, a, L, u, bcs
def load_last_checkpoint(xdmf_filename, V, var_name): # TODO: file should be passed in order to open only once? # get times times = get_times_from_xdfm_checkpoints(xdmf_filename) # load last checkpoint with XDMFFile(MPI.comm_self, xdmf_filename) as file: u = Function(V, name=var_name) file.read_checkpoint(u, var_name, -1) return u, times[-1], len(times) - 1
def get_formulation_constant_props(V, u_initial, f, dt, conductivity, density, specific_heat, var_name='Temperature'): # medium properties k = Constant(conductivity) rho = Constant(density) c_p = Constant(specific_heat) # interpolate initial condition u_n = Function(V, name=var_name) u_n.interpolate(u_initial) # function spaces u_h = TrialFunction(V) v = TestFunction(V) # formulation param1 = rho * c_p param2 = k / param1 a = u_h * v * dx + param2 * dt * dot(grad(u_h), grad(v)) * dx L = (u_n + param1 * dt * f) * v * dx return u_n, a, L
def initialize_data(self): """ Extract required objects for defining error control forms. This will be stored and reused. """ # Developer's note: The UFL-FFC-DOLFIN--PyDOLFIN toolchain for # error control is quite fine-tuned. In particular, the order # of coefficients in forms is (and almost must be) used for # their assignment. This means that the order in which these # coefficients are defined matters and should be considered # fixed. # Primal trial element space self._V = self.u.function_space() # Primal test space == Dual trial space Vhat = self.weak_residual.arguments()[0].function_space() # Discontinuous version of primal trial element space self._dV = tear(self._V) # Extract cell and geometric dimension mesh = self._V.mesh() dim = mesh.topology().dim() # Function representing improved dual E = increase_order(Vhat) self._Ez_h = Function(E) # Function representing cell bubble function B = FunctionSpace(mesh, "B", dim + 1) self._b_T = Function(B) self._b_T.vector()[:] = 1.0 # Function representing strong cell residual self._R_T = Function(self._dV) # Function representing cell cone function C = FunctionSpace(mesh, "DG", dim) self._b_e = Function(C) # Function representing strong facet residual self._R_dT = Function(self._dV) # Function for discrete dual on primal test space self._z_h = Function(Vhat) # Piecewise constants for assembling indicators self._DG0 = FunctionSpace(mesh, "DG", 0)
def initialize_data(self): """ Extract required objects for defining error control forms. This will be stored and reused. """ # Developer's note: The UFL-FFC-DOLFIN--PyDOLFIN toolchain for # error control is quite fine-tuned. In particular, the order # of coefficients in forms is (and almost must be) used for # their assignment. This means that the order in which these # coefficients are defined matters and should be considered # fixed. # Primal trial element space self._V = self.u.function_space() # Primal test space == Dual trial space Vhat = self.weak_residual.arguments()[0].function_space() # Discontinuous version of primal trial element space self._dV = tear(self._V) # Extract cell and geometric dimension mesh = self._V.mesh() dim = mesh.topology().dim() # Function representing improved dual E = increase_order(Vhat) self._Ez_h = Function(E) # Function representing cell bubble function B = FunctionSpace(mesh, "B", dim + 1) self._b_T = Function(B) self._b_T.vector()[:] = 1.0 # Function representing strong cell residual self._R_T = Function(self._dV) # Function representing cell cone function C = FunctionSpace(mesh, "DG", dim) self._b_e = Function(C) # Function representing strong facet residual self._R_dT = Function(self._dV) # Function for discrete dual on primal test space self._z_h = Function(Vhat) # Piecewise constants for assembling indicators self._DG0 = FunctionSpace(mesh, "DG", 0)
def interpolate(v, V): """Return interpolation of a given function into a given finite element space. *Arguments* v a :py:class:`Function <dolfin.function.function.Function>` or a :py:class:`MultiMeshFunction <dolfin.function.function.MultiMeshFunction>` or an :py:class:`Expression <dolfin.functions.expression.Expression>` V a :py:class:`FunctionSpace (standard, mixed, etc.) <dolfin.functions.functionspace.FunctionSpace>` or a :py:class:`MultiMeshFunctionSpace <dolfin.function.MultiMeshFunctionSpace>`. *Example of usage* .. code-block:: python v = Expression("sin(pi*x[0])") V = FunctionSpace(mesh, "Lagrange", 1) Iv = interpolate(v, V) """ # Check arguments # if not isinstance(V, cpp.functionFunctionSpace): # cpp.dolfin_error("interpolation.py", # "compute interpolation", # "Illegal function space for interpolation, not a FunctionSpace (%s)" % str(v)) if isinstance(V, MultiMeshFunctionSpace): Pv = MultiMeshFunction(V) else: Pv = Function(V) # Compute interpolation if hasattr(v, "_cpp_object"): Pv.interpolate(v._cpp_object) else: Pv.interpolate(v) return Pv
def convert_xdmf_checkpoints_to_vtk(xdmf_filename, V, var_name, vtk_filename=None): # get times times = get_times_from_xdfm_checkpoints(xdmf_filename) # create vtk file if vtk_filename is None: vtk_filename = xdmf_filename.split('.')[0] + '.pvd' vtkfile = File(vtk_filename) # load variable and save as vtk with XDMFFile(xdmf_filename) as file: for i, t in enumerate(times): u = Function(V, name=var_name) file.read_checkpoint(u, var_name, i) vtkfile << (u, t)
def read_function(self, V, name: str): """Read finite element Function from file Parameters ---------- V Function space of saved function. name Name of function as saved into HDF file. Returns ------- dolfin.function.function.Function Function read from file Note ---- Parameter `V: Function space` must be the same as saved function space except for ordering of mesh entities. """ V_cpp = getattr(V, "_cpp_object", V) u_cpp = self._cpp_object.read(V_cpp, name) return Function(V, u_cpp.vector())
def r2_errornorm(u, uh, norm_type="l2", degree_rise=3, mesh=None ): """ This function is a modification of FEniCS's built-in errornorm function that adopts the :math:`r^2dr` measure as opposed to the standard Cartesian :math:`dx` measure. For documentation and usage, see the original module <https://bitbucket.org/fenics-project/dolfin/src/master/python/dolfin/fem/norms.py>_. """ # Get mesh if isinstance(u, cpp.function.Function) and mesh is None: mesh = u.function_space().mesh() if isinstance(uh, cpp.function.Function) and mesh is None: mesh = uh.function_space().mesh() # if isinstance(uh, MultiMeshFunction) and mesh is None: # mesh = uh.function_space().multimesh() if hasattr(uh, "_cpp_object") and mesh is None: mesh = uh._cpp_object.function_space().mesh() if hasattr(u, "_cpp_object") and mesh is None: mesh = u._cpp_object.function_space().mesh() if mesh is None: raise RuntimeError("Cannot compute error norm. Missing mesh.") # Get rank if not u.ufl_shape == uh.ufl_shape: raise RuntimeError("Cannot compute error norm. Value shapes do not match.") shape = u.ufl_shape rank = len(shape) # Check that uh is associated with a finite element if uh.ufl_element().degree() is None: raise RuntimeError("Cannot compute error norm. Function uh must have a finite element.") # Degree for interpolation space. Raise degree with respect to uh. degree = uh.ufl_element().degree() + degree_rise # Check degree of 'exact' solution u degree_u = u.ufl_element().degree() if degree_u is not None and degree_u < degree: cpp.warning("Degree of exact solution may be inadequate for accurate result in errornorm.") # Create function space if rank == 0: V = FunctionSpace(mesh, "Discontinuous Lagrange", degree) elif rank == 1: V = VectorFunctionSpace(mesh, "Discontinuous Lagrange", degree, dim=shape[0]) elif rank > 1: V = TensorFunctionSpace(mesh, "Discontinuous Lagrange", degree, shape=shape) # Interpolate functions into finite element space pi_u = interpolate(u, V) pi_uh = interpolate(uh, V) # Compute the difference e = Function(V) e.assign(pi_u) e.vector().axpy(-1.0, pi_uh.vector()) # Compute norm return r2_norm(e, func_degree=degree, norm_type=norm_type, mesh=mesh )
def _rush_larsen_scheme_generator_adm(rhs_form, solution, time, order, generalized, perturbation): """Generates a list of forms and solutions for the adjoint linearisation of the Rush-Larsen scheme *Arguments* rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation solution (_Function_) The prognostic variable time (_Constant_) A Constant holding the time at the start of the time step order (int) The order of the scheme generalized (bool) If True generate a generalized Rush Larsen scheme, linearizing all components. perturbation (Function) The vector on which we compute the adjoint action. """ DX = _check_form(rhs_form) if DX != ufl.dP: raise TypeError("Expected a form with a Pointintegral.") # Create time step dt = Constant(0.1) # Get test function # arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() # Get time dependent expressions time_dep_expressions = _time_dependent_expressions(rhs_form, time) # Extract rhs expressions from form rhs_integrand = rhs_form.integrals()[0].integrand() rhs_exprs, v = extract_tested_expressions(rhs_integrand) vector_rhs = len(v.ufl_shape) > 0 and v.ufl_shape[0] > 1 system_size = v.ufl_shape[0] if vector_rhs else 1 # Fix for indexing of v for scalar expressions v = v if vector_rhs else [v] # Extract linear terms if not using generalized Rush Larsen if not generalized: linear_terms = _find_linear_terms(rhs_exprs, solution) else: linear_terms = [True for _ in range(system_size)] # Wrap the rhs expressions into a ufl vector type rhs_exprs = ufl.as_vector([rhs_exprs[i] for i in range(system_size)]) rhs_jac = ufl.diff(rhs_exprs, solution) # Takes time! if vector_rhs: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[ind, ind])) for ind in range(system_size)] soln = solution else: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[0]))] solution = [solution] soln = solution[0] ufl_stage_forms = [] dolfin_stage_forms = [] dt_stage_offsets = [] trial = TrialFunction(soln.function_space()) # Stage solutions (3 per order rhs, linearized, and final step) # If 2nd order the final step for 1 step is a stage if order == 1: stage_solutions = [] # Fetch the original step rl_ufl_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 1.0, 0.0, v, DX, time_dep_expressions) # If this is commented out, we don't get NaNs. Yhy is # solution a list of length zero anyway? rl_ufl_form = safe_action(safe_adjoint(derivative(rl_ufl_form, soln, trial)), perturbation) elif order == 2: # Stage solution for order 2 fn_space = soln.function_space() stage_solutions = [Function(fn_space, name="y_1/2"), Function(fn_space, name="y_1"), Function(fn_space, name="y_bar_1/2")] y_half_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 0.5, 0.0, v, DX, time_dep_expressions) y_one_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, stage_solutions[0], dt, time, 1.0, 0.5, v, DX, time_dep_expressions) y_bar_half_form = safe_action(safe_adjoint(derivative(y_one_form, stage_solutions[0], trial)), perturbation) rl_ufl_form = safe_action(safe_adjoint(derivative(y_one_form, soln, trial)), perturbation) + \ safe_action(safe_adjoint(derivative(y_half_form, soln, trial)), stage_solutions[2]) ufl_stage_forms.append([y_half_form]) ufl_stage_forms.append([y_one_form]) ufl_stage_forms.append([y_bar_half_form]) dolfin_stage_forms.append([Form(y_half_form)]) dolfin_stage_forms.append([Form(y_one_form)]) dolfin_stage_forms.append([Form(y_bar_half_form)]) # Get last stage form last_stage = Form(rl_ufl_form) human_form = "%srush larsen %s" % ("generalized " if generalized else "", str(order)) return rhs_form, linear_terms, ufl_stage_forms, dolfin_stage_forms, last_stage, \ stage_solutions, dt, dt_stage_offsets, human_form, perturbation
def _butcher_scheme_generator_adm(a, b, c, time, solution, rhs_form, adj): """Generates a list of forms and solutions for a given Butcher tableau *Arguments* a (2 dimensional numpy array) The a matrix of the Butcher tableau. b (1-2 dimensional numpy array) The b vector of the Butcher tableau. If b is 2 dimensional the scheme includes an error estimator and can be used in adaptive solvers. c (1 dimensional numpy array) The c vector the Butcher tableau. time (_Constant_) A Constant holding the time at the start of the time step solution (_Function_) The prognostic variable rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation adj (_Function_) The derivative of the functional with respect to y_n+1 """ a = _check_abc(a, b, c) size = a.shape[0] DX = _check_form(rhs_form) # Get test function arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() v = arguments[0] # Create time step dt = Constant(0.1) # rhs forms dolfin_stage_forms = [] ufl_stage_forms = [] # Stage solutions k = [Function(solution.function_space(), name="k_%d" % i) for i in range(size)] kbar = [Function(solution.function_space(), name="kbar_%d" % i) for i in range(size)] # Create the stage forms y_ = solution time_ = time time_dep_expressions = _time_dependent_expressions(rhs_form, time) zero_ = ufl.zero(*y_.ufl_shape) forward_forms = [] stage_solutions = [] jacobian_indices = [] # The recomputation of the forward run: for i, ki in enumerate(k): # Check whether the stage is explicit explicit = a[i, i] == 0 # Evaluation arguments for the ith stage evalargs = y_ + dt * sum([float(a[i, j]) * k[j] for j in range(i + 1)], zero_) time = time_ + dt * c[i] replace_dict = _replace_dict_time_dependent_expression( time_dep_expressions, time_, dt, c[i]) replace_dict[y_] = evalargs replace_dict[time_] = time stage_form = ufl.replace(rhs_form, replace_dict) forward_forms.append(stage_form) if explicit: stage_forms = [stage_form] jacobian_indices.append(-1) else: # Create a F=0 form and differentiate it stage_form_implicit = stage_form - ufl.inner(ki, v) * DX stage_forms = [stage_form_implicit, derivative( stage_form_implicit, ki)] jacobian_indices.append(0) ufl_stage_forms.append(stage_forms) dolfin_stage_forms.append([Form(form) for form in stage_forms]) stage_solutions.append(ki) for i, kbari in reversed(list(enumerate(kbar))): # Check whether the stage is explicit explicit = a[i, i] == 0 # And now the adjoint linearisation: stage_form_adm = ufl.inner(dt * b[i] * adj, v) * DX + sum( [dt * float(a[j, i]) * safe_action(safe_adjoint(derivative( forward_forms[j], y_)), kbar[j]) for j in range(i, size)]) if explicit: stage_forms_adm = [stage_form_adm] jacobian_indices.append(-1) else: # Create a F=0 form and differentiate it stage_form_adm -= ufl.inner(kbar[i], v) * DX stage_forms_adm = [stage_form_adm, derivative(stage_form_adm, kbari)] jacobian_indices.append(1) ufl_stage_forms.append(stage_forms_adm) dolfin_stage_forms.append([Form(form) for form in stage_forms_adm]) stage_solutions.append(kbari) # Only one last stage if len(b.shape) == 1: last_stage = Form(ufl.inner(adj, v) * DX + sum( [safe_action(safe_adjoint(derivative(forward_forms[i], y_)), kbar[i]) for i in range(size)])) else: raise Exception("Not sure what to do here") human_form = "unimplemented" return ufl_stage_forms, dolfin_stage_forms, jacobian_indices, last_stage, \ stage_solutions, dt, human_form, adj
def _rush_larsen_scheme_generator(rhs_form, solution, time, order, generalized): """Generates a list of forms and solutions for a given Butcher tableau *Arguments* rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation solution (_Function_) The prognostic variable time (_Constant_) A Constant holding the time at the start of the time step order (int) The order of the scheme generalized (bool) If True generate a generalized Rush Larsen scheme, linearizing all components. """ DX = _check_form(rhs_form) if DX != ufl.dP: raise TypeError("Expected a form with a Pointintegral.") # Create time step dt = Constant(0.1) # Get test function # arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() # Get time dependent expressions time_dep_expressions = _time_dependent_expressions(rhs_form, time) # Extract rhs expressions from form rhs_integrand = rhs_form.integrals()[0].integrand() rhs_exprs, v = extract_tested_expressions(rhs_integrand) vector_rhs = len(v.ufl_shape) > 0 and v.ufl_shape[0] > 1 system_size = v.ufl_shape[0] if vector_rhs else 1 # Fix for indexing of v for scalar expressions v = v if vector_rhs else [v] # Extract linear terms if not using generalized Rush Larsen if not generalized: linear_terms = _find_linear_terms(rhs_exprs, solution) else: linear_terms = [True for _ in range(system_size)] # Wrap the rhs expressions into a ufl vector type rhs_exprs = ufl.as_vector([rhs_exprs[i] for i in range(system_size)]) rhs_jac = ufl.diff(rhs_exprs, solution) # Takes time! if vector_rhs: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[ind, ind])) for ind in range(system_size)] else: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[0]))] solution = [solution] ufl_stage_forms = [] dolfin_stage_forms = [] dt_stage_offsets = [] # Stage solutions (3 per order rhs, linearized, and final step) # If 2nd order the final step for 1 step is a stage if order == 1: stage_solutions = [] rl_ufl_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 1.0, 0.0, v, DX, time_dep_expressions) elif order == 2: # Stage solution for order 2 if vector_rhs: stage_solutions = [Function(solution.function_space(), name="y_1/2")] else: stage_solutions = [Function(solution[0].function_space(), name="y_1/2")] stage_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 0.5, 0.0, v, DX, time_dep_expressions) rl_ufl_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, stage_solutions[0], dt, time, 1.0, 0.5, v, DX, time_dep_expressions) ufl_stage_forms.append([stage_form]) dolfin_stage_forms.append([Form(stage_form)]) # Get last stage form last_stage = Form(rl_ufl_form) human_form = "%srush larsen %s" % ("generalized " if generalized else "", str(order)) return rhs_form, linear_terms, ufl_stage_forms, dolfin_stage_forms, last_stage, \ stage_solutions, dt, dt_stage_offsets, human_form, None
def _butcher_scheme_generator_tlm(a, b, c, time, solution, rhs_form, perturbation): """Generates a list of forms and solutions for a given Butcher tableau *Arguments* a (2 dimensional numpy array) The a matrix of the Butcher tableau. b (1-2 dimensional numpy array) The b vector of the Butcher tableau. If b is 2 dimensional the scheme includes an error estimator and can be used in adaptive solvers. c (1 dimensional numpy array) The c vector the Butcher tableau. time (_Constant_) A Constant holding the time at the start of the time step solution (_Function_) The prognostic variable rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation perturbation (_Function_) The perturbation in the initial condition of the solution """ a = _check_abc(a, b, c) size = a.shape[0] DX = _check_form(rhs_form) # Get test function arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() v = arguments[0] # Create time step dt = Constant(0.1) # rhs forms dolfin_stage_forms = [] ufl_stage_forms = [] # Stage solutions k = [Function(solution.function_space(), name="k_%d" % i) for i in range(size)] kdot = [Function(solution.function_space(), name="kdot_%d" % i) for i in range(size)] # Create the stage forms y_ = solution time_ = time time_dep_expressions = _time_dependent_expressions(rhs_form, time) zero_ = ufl.zero(*y_.ufl_shape) forward_forms = [] stage_solutions = [] jacobian_indices = [] for i, ki in enumerate(k): # Check whether the stage is explicit explicit = a[i, i] == 0 # Evaluation arguments for the ith stage evalargs = y_ + dt * sum([float(a[i, j]) * k[j] for j in range(i + 1)], zero_) time = time_ + dt * c[i] replace_dict = _replace_dict_time_dependent_expression(time_dep_expressions, time_, dt, c[i]) replace_dict[y_] = evalargs replace_dict[time_] = time stage_form = ufl.replace(rhs_form, replace_dict) forward_forms.append(stage_form) # The recomputation of the forward run: if explicit: stage_forms = [stage_form] jacobian_indices.append(-1) else: # Create a F=0 form and differentiate it stage_form_implicit = stage_form - ufl.inner(ki, v) * DX stage_forms = [stage_form_implicit, derivative(stage_form_implicit, ki)] jacobian_indices.append(0) ufl_stage_forms.append(stage_forms) dolfin_stage_forms.append([Form(form) for form in stage_forms]) stage_solutions.append(ki) # And now the tangent linearisation: stage_form_tlm = safe_action(derivative(stage_form, y_), perturbation) + \ sum([dt * float(a[i, j]) * safe_action(derivative( forward_forms[j], y_), kdot[j]) for j in range(i + 1)]) if explicit: stage_forms_tlm = [stage_form_tlm] jacobian_indices.append(-1) else: # Create a F=0 form and differentiate it stage_form_tlm -= ufl.inner(kdot[i], v) * DX stage_forms_tlm = [stage_form_tlm, derivative(stage_form_tlm, kdot[i])] jacobian_indices.append(1) ufl_stage_forms.append(stage_forms_tlm) dolfin_stage_forms.append([Form(form) for form in stage_forms_tlm]) stage_solutions.append(kdot[i]) # Only one last stage if len(b.shape) == 1: last_stage = Form(ufl.inner(perturbation + sum( [dt * float(bi) * kdoti for bi, kdoti in zip(b, kdot)], zero_), v) * DX) else: raise Exception("Not sure what to do here") human_form = [] for i in range(size): kterm = " + ".join("%sh*k_%s" % ("" if a[i, j] == 1.0 else "%s*" % a[i, j], j) for j in range(size) if a[i, j] != 0) if c[i] in [0.0, 1.0]: cih = " + h" if c[i] == 1.0 else "" else: cih = " + %s*h" % c[i] kdotterm = " + ".join("%(a)sh*action(derivative(f(t_n%(cih)s, y_n + " "%(kterm)s), kdot_%(i)s" % {"a": ("" if a[i, j] == 1.0 else "%s*" % a[i, j], j), "i": i, "cih": cih, "kterm": kterm} for j in range(size) if a[i, j] != 0) if len(kterm) == 0: human_form.append("k_%(i)s = f(t_n%(cih)s, y_n)" % {"i": i, "cih": cih}) human_form.append("kdot_%(i)s = action(derivative(" "f(t_n%(cih)s, y_n), y_n), ydot_n)" % {"i": i, "cih": cih}) else: human_form.append("k_%(i)s = f(t_n%(cih)s, y_n + %(kterm)s)" % {"i": i, "cih": cih, "kterm": kterm}) human_form.append("kdot_%(i)s = action(derivative(f(t_n%(cih)s, " "y_n + %(kterm)s), y_n) + %(kdotterm)s" % {"i": i, "cih": cih, "kterm": kterm, "kdotterm": kdotterm}) parentheses = "(%s)" if np.sum(b > 0) > 1 else "%s" human_form.append("ydot_{n+1} = ydot_n + h*" + parentheses % (" + ".join( "%skdot_%s" % ("" if b[i] == 1.0 else "%s*" % b[i], i) for i in range(size) if b[i] > 0))) human_form = "\n".join(human_form) return ufl_stage_forms, dolfin_stage_forms, jacobian_indices, last_stage, \ stage_solutions, dt, human_form, perturbation
def _butcher_scheme_generator(a, b, c, time, solution, rhs_form): """Generates a list of forms and solutions for a given Butcher tableau *Arguments* a (2 dimensional numpy array) The a matrix of the Butcher tableau. b (1-2 dimensional numpy array) The b vector of the Butcher tableau. If b is 2 dimensional the scheme includes an error estimator and can be used in adaptive solvers. c (1 dimensional numpy array) The c vector the Butcher tableau. time (_Constant_) A Constant holding the time at the start of the time step solution (_Function_) The prognostic variable rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation """ a = _check_abc(a, b, c) size = a.shape[0] DX = _check_form(rhs_form) # Get test function arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() v = arguments[0] # Create time step dt = Constant(0.1) # rhs forms dolfin_stage_forms = [] ufl_stage_forms = [] # Stage solutions k = [Function(solution.function_space(), name="k_%d" % i) for i in range(size)] jacobian_indices = [] # Create the stage forms y_ = solution time_ = time time_dep_expressions = _time_dependent_expressions(rhs_form, time) zero_ = ufl.zero(*y_.ufl_shape) for i, ki in enumerate(k): # Check whether the stage is explicit explicit = a[i, i] == 0 # Evaluation arguments for the ith stage evalargs = y_ + dt * sum([float(a[i, j]) * k[j] for j in range(i + 1)], zero_) time = time_ + dt * c[i] replace_dict = _replace_dict_time_dependent_expression(time_dep_expressions, time_, dt, c[i]) replace_dict[y_] = evalargs replace_dict[time_] = time stage_form = ufl.replace(rhs_form, replace_dict) if explicit: stage_forms = [stage_form] jacobian_indices.append(-1) else: # Create a F=0 form and differentiate it stage_form -= ufl.inner(ki, v) * DX stage_forms = [stage_form, derivative(stage_form, ki)] jacobian_indices.append(0) ufl_stage_forms.append(stage_forms) dolfin_stage_forms.append([Form(form) for form in stage_forms]) # Only one last stage if len(b.shape) == 1: last_stage = Form(ufl.inner(y_ + sum([dt * float(bi) * ki for bi, ki in zip(b, k)], zero_), v) * DX) else: # FIXME: Add support for adaptivity in RKSolver and # MultiStageScheme last_stage = [Form(ufl.inner(y_ + sum([dt * float(bi) * ki for bi, ki in zip(b[0, :], k)], zero_), v) * DX), Form(ufl.inner(y_ + sum([dt * float(bi) * ki for bi, ki in zip(b[1, :], k)], zero_), v) * DX)] # Create the Function holding the solution at end of time step # k.append(solution.copy()) # Generate human form of MultiStageScheme human_form = [] for i in range(size): kterm = " + ".join("%sh*k_%s" % ("" if a[i, j] == 1.0 else "%s*" % a[i, j], j) for j in range(size) if a[i, j] != 0) if c[i] in [0.0, 1.0]: cih = " + h" if c[i] == 1.0 else "" else: cih = " + %s*h" % c[i] if len(kterm) == 0: human_form.append("k_%(i)s = f(t_n%(cih)s, y_n)" % {"i": i, "cih": cih}) else: human_form.append("k_%(i)s = f(t_n%(cih)s, y_n + %(kterm)s)" % {"i": i, "cih": cih, "kterm": kterm}) parentheses = "(%s)" if np.sum(b > 0) > 1 else "%s" human_form.append("y_{n+1} = y_n + h*" + parentheses % (" + ".join( "%sk_%s" % ("" if b[i] == 1.0 else "%s*" % b[i], i) for i in range(size) if b[i] > 0))) human_form = "\n".join(human_form) return ufl_stage_forms, dolfin_stage_forms, jacobian_indices, last_stage, \ k, dt, human_form, None
class DOLFINErrorControlGenerator(ErrorControlGenerator): """ This class provides a realization of ffc.errorcontrol.errorcontrolgenerators.ErrorControlGenerator for use with UFL forms defined over DOLFIN objects """ def __init__(self, F, M, u): """ *Arguments* F (tuple or Form) tuple of (bilinear, linear) forms or linear form M (Form) functional or linear form u (Coefficient) The coefficient considered as the unknown. """ ErrorControlGenerator.__init__(self, __import__("dolfin"), F, M, u) def initialize_data(self): """ Extract required objects for defining error control forms. This will be stored and reused. """ # Developer's note: The UFL-FFC-DOLFIN--PyDOLFIN toolchain for # error control is quite fine-tuned. In particular, the order # of coefficients in forms is (and almost must be) used for # their assignment. This means that the order in which these # coefficients are defined matters and should be considered # fixed. # Primal trial element space self._V = self.u.function_space() # Primal test space == Dual trial space Vhat = self.weak_residual.arguments()[0].function_space() # Discontinuous version of primal trial element space self._dV = tear(self._V) # Extract cell and geometric dimension mesh = self._V.mesh() dim = mesh.topology().dim() # Function representing improved dual E = increase_order(Vhat) self._Ez_h = Function(E) # Function representing cell bubble function B = FunctionSpace(mesh, "B", dim + 1) self._b_T = Function(B) self._b_T.vector()[:] = 1.0 # Function representing strong cell residual self._R_T = Function(self._dV) # Function representing cell cone function C = FunctionSpace(mesh, "DG", dim) self._b_e = Function(C) # Function representing strong facet residual self._R_dT = Function(self._dV) # Function for discrete dual on primal test space self._z_h = Function(Vhat) # Piecewise constants for assembling indicators self._DG0 = FunctionSpace(mesh, "DG", 0)
def project(v, V=None, bcs=None, mesh=None, function=None, solver_type="lu", preconditioner_type="default", form_compiler_parameters=None): """Return projection of given expression *v* onto the finite element space *V*. *Arguments* v a :py:class:`Function <dolfin.functions.function.Function>` or an :py:class:`Expression <dolfin.functions.expression.Expression>` bcs Optional argument :py:class:`list of DirichletBC <dolfin.fem.bcs.DirichletBC>` V Optional argument :py:class:`FunctionSpace <dolfin.functions.functionspace.FunctionSpace>` mesh Optional argument :py:class:`mesh <dolfin.cpp.Mesh>`. solver_type see :py:func:`solve <dolfin.fem.solving.solve>` for options. preconditioner_type see :py:func:`solve <dolfin.fem.solving.solve>` for options. form_compiler_parameters see :py:class:`Parameters <dolfin.cpp.Parameters>` for more information. *Example of usage* .. code-block:: python v = Expression("sin(pi*x[0])") V = FunctionSpace(mesh, "Lagrange", 1) Pv = project(v, V) This is useful for post-processing functions or expressions which are not readily handled by visualization tools (such as for example discontinuous functions). """ # Try figuring out a function space if not specified if V is None: # Create function space based on Expression element if trying # to project an Expression if isinstance(v, dolfin.function.expression.Expression): if mesh is not None and isinstance(mesh, cpp.mesh.Mesh): V = FunctionSpace(mesh, v.ufl_element()) # else: # cpp.dolfin_error("projection.py", # "perform projection", # "Expected a mesh when projecting an Expression") else: # Otherwise try extracting function space from expression V = _extract_function_space(v, mesh) # Projection into a MultiMeshFunctionSpace if isinstance(V, MultiMeshFunctionSpace): # Create the measuresum of uncut and cut-cells dX = ufl.dx() + ufl.dC() # Define variational problem for projection w = TestFunction(V) Pv = TrialFunction(V) a = ufl.inner(w, Pv) * dX L = ufl.inner(w, v) * dX # Assemble linear system A = assemble_multimesh(a, form_compiler_parameters=form_compiler_parameters) b = assemble_multimesh(L, form_compiler_parameters=form_compiler_parameters) # Solve linear system for projection if function is None: function = MultiMeshFunction(V) cpp.la.solve(A, function.vector(), b, solver_type, preconditioner_type) return function # Ensure we have a mesh and attach to measure if mesh is None: mesh = V.mesh() dx = ufl.dx(mesh) # Define variational problem for projection w = TestFunction(V) Pv = TrialFunction(V) a = ufl.inner(w, Pv) * dx L = ufl.inner(w, v) * dx # Assemble linear system A, b = assemble_system(a, L, bcs=bcs, form_compiler_parameters=form_compiler_parameters) # Solve linear system for projection if function is None: function = Function(V) cpp.la.solve(A, function.vector(), b, solver_type, preconditioner_type) return function
def project(v, V=None, func_degree=None, bcs=None, mesh=None, function=None, solver_type="lu", preconditioner_type="default", form_compiler_parameters=None): """ This function is a modification of FEniCS's built-in project function that adopts the :math:`r^2dr` measure as opposed to the standard Cartesian :math:`dx` measure. For documentation and usage, see the `original module <https://bitbucket.org/fenics-project/dolfin/src/master/python/dolfin/fem/projection.py>`_. .. note:: Note the extra argument func_degree: this is used to interpolate the :math:`r^2` Expression to the same degree as used in the definition of the Trial and Test function spaces. """ # Try figuring out a function space if not specified if V is None: # Create function space based on Expression element if trying # to project an Expression if isinstance(v, Expression): # FIXME: Add handling of cpp.MultiMesh if mesh is not None and isinstance(mesh, cpp.mesh.Mesh): V = FunctionSpace(mesh, v.ufl_element()) # else: # cpp.dolfin_error("projection.py", # "perform projection", # "Expected a mesh when projecting an Expression") else: # Otherwise try extracting function space from expression V = _extract_function_space(v, mesh) # Ensure we have a mesh and attach to measure if mesh is None: mesh = V.mesh() dx = ufl.dx(mesh) # Define variational problem for projection # DS: HERE IS WHERE I MODIFY r2 = Expression('pow(x[0],2)', degree=func_degree) w = TestFunction(V) Pv = TrialFunction(V) a = ufl.inner(w, Pv) * r2 * dx L = ufl.inner(w, v) * r2 * dx # Assemble linear system A, b = assemble_system(a, L, bcs=bcs, form_compiler_parameters=form_compiler_parameters) # Solve linear system for projection if function is None: function = Function(V) cpp.la.solve(A, function.vector(), b, solver_type, preconditioner_type) return function
def part(self, i, deepcopy=False): f = Function(self._cpp_object.part(i, deepcopy)) f.rename(self._cpp_object.name(), self._cpp_object.label()) return f
class DOLFINErrorControlGenerator(ErrorControlGenerator): """ This class provides a realization of ffc.errorcontrol.errorcontrolgenerators.ErrorControlGenerator for use with UFL forms defined over DOLFIN objects """ def __init__(self, F, M, u): """ *Arguments* F (tuple or Form) tuple of (bilinear, linear) forms or linear form M (Form) functional or linear form u (Coefficient) The coefficient considered as the unknown. """ ErrorControlGenerator.__init__(self, __import__("dolfin"), F, M, u) def initialize_data(self): """ Extract required objects for defining error control forms. This will be stored and reused. """ # Developer's note: The UFL-FFC-DOLFIN--PyDOLFIN toolchain for # error control is quite fine-tuned. In particular, the order # of coefficients in forms is (and almost must be) used for # their assignment. This means that the order in which these # coefficients are defined matters and should be considered # fixed. # Primal trial element space self._V = self.u.function_space() # Primal test space == Dual trial space Vhat = self.weak_residual.arguments()[0].function_space() # Discontinuous version of primal trial element space self._dV = tear(self._V) # Extract cell and geometric dimension mesh = self._V.mesh() dim = mesh.topology().dim() # Function representing improved dual E = increase_order(Vhat) self._Ez_h = Function(E) # Function representing cell bubble function B = FunctionSpace(mesh, "B", dim + 1) self._b_T = Function(B) self._b_T.vector()[:] = 1.0 # Function representing strong cell residual self._R_T = Function(self._dV) # Function representing cell cone function C = FunctionSpace(mesh, "DG", dim) self._b_e = Function(C) # Function representing strong facet residual self._R_dT = Function(self._dV) # Function for discrete dual on primal test space self._z_h = Function(Vhat) # Piecewise constants for assembling indicators self._DG0 = FunctionSpace(mesh, "DG", 0)
def errornorm(u, uh, norm_type="l2", degree_rise=3, mesh=None): """ Compute and return the error :math:`e = u - u_h` in the given norm. *Arguments* u, uh :py:class:`Functions <dolfin.functions.function.Function>` norm_type Type of norm. The :math:`L^2` -norm is default. For other norms, see :py:func:`norm <dolfin.fem.norms.norm>`. degree_rise The number of degrees above that of u_h used in the interpolation; i.e. the degree of piecewise polynomials used to approximate :math:`u` and :math:`u_h` will be the degree of :math:`u_h` + degree_raise. mesh Optional :py:class:`Mesh <dolfin.cpp.Mesh>` on which to compute the error norm. In simple cases, one may just define .. code-block:: python e = u - uh and evalute for example the square of the error in the :math:`L^2` -norm by .. code-block:: python assemble(e**2*dx(mesh)) However, this is not stable w.r.t. round-off errors considering that the form compiler may expand(#) the expression above to:: e**2*dx = u**2*dx - 2*u*uh*dx + uh**2*dx and this might get further expanded into thousands of terms for higher order elements. Thus, the error will be evaluated by adding a large number of terms which should sum up to something close to zero (if the error is small). This module computes the error by first interpolating both :math:`u` and :math:`u_h` to a common space (of high accuracy), then subtracting the two fields (which is easy since they are expressed in the same basis) and then evaluating the integral. (#) If using the tensor representation optimizations. The quadrature represenation does not suffer from this problem. """ # Check argument # if not isinstance(u, cpp.function.GenericFunction): # cpp.dolfin_error("norms.py", # "compute error norm", # "Expecting a Function or Expression for u") # if not isinstance(uh, cpp.function.Function): # cpp.dolfin_error("norms.py", # "compute error norm", # "Expecting a Function for uh") # Get mesh if isinstance(u, cpp.function.Function) and mesh is None: mesh = u.function_space().mesh() if isinstance(uh, cpp.function.Function) and mesh is None: mesh = uh.function_space().mesh() if hasattr(uh, "_cpp_object") and mesh is None: mesh = uh._cpp_object.function_space().mesh() if hasattr(u, "_cpp_object") and mesh is None: mesh = u._cpp_object.function_space().mesh() if mesh is None: cpp.dolfin_error("norms.py", "compute error norm", "Missing mesh") # Get rank if not u.ufl_shape == uh.ufl_shape: cpp.dolfin_error("norms.py", "compute error norm", "Value shapes don't match") shape = u.ufl_shape rank = len(shape) # Check that uh is associated with a finite element if uh.ufl_element().degree() is None: cpp.dolfin_error("norms.py", "compute error norm", "Function uh must have a finite element") # Degree for interpolation space. Raise degree with respect to uh. degree = uh.ufl_element().degree() + degree_rise # Check degree of 'exact' solution u degree_u = u.ufl_element().degree() if degree_u is not None and degree_u < degree: cpp.warning("Degree of exact solution may be inadequate for accurate result in errornorm.") # Create function space if rank == 0: V = FunctionSpace(mesh, "Discontinuous Lagrange", degree) elif rank == 1: V = VectorFunctionSpace(mesh, "Discontinuous Lagrange", degree, dim=shape[0]) elif rank > 1: V = TensorFunctionSpace(mesh, "Discontinuous Lagrange", degree, shape=shape) # Interpolate functions into finite element space pi_u = interpolate(u, V) pi_uh = interpolate(uh, V) # Compute the difference e = Function(V) e.assign(pi_u) e.vector().axpy(-1.0, pi_uh.vector()) # Compute norm return norm(e, norm_type=norm_type, mesh=mesh)
def part(self, i, deepcopy=False): f = Function(self._cpp_object.part(i, deepcopy)) f.rename(self._cpp_object.name(), self._cpp_object.label()) return f