def supply_shape_derivative(self, shape_derivative): """Overrides the shape derivative of the reduced cost functional. This allows users to implement their own shape derivative and use cashocs as a solver library only. Parameters ---------- shape_derivative : ufl.form.Form The shape_derivative of the reduced (!) cost functional w.r.t. controls. Returns ------- None """ try: if not shape_derivative.__module__ == 'ufl.form' and type(shape_derivative).__name__ == 'Form': raise InputError('cashocs._shape_optimization.shape_optimization_problem.ShapeOptimizationProblem.supply_shape_derivative', 'shape_derivative', 'shape_derivative have to be a ufl form') except: raise InputError('cashocs._shape_optimization.shape_optimization_problem.ShapeOptimizationProblem.supply_shape_derivative', 'shape_derivative', 'shape_derivative has to be a ufl form') if len(shape_derivative.arguments()) == 2: raise InputError('cashocs._shape_optimization.shape_optimization_problem.ShapeOptimizationProblem.supply_shape_derivative', 'shape_derivative', 'Do not use TrialFunction for the shape_derivative.') elif len(shape_derivative.arguments()) == 0: raise InputError('cashocs._shape_optimization.shape_optimization_problem.ShapeOptimizationProblem.supply_shape_derivative', 'shape_derivative', 'The specified shape_derivative must include a TestFunction object.') if not shape_derivative.arguments()[0].ufl_function_space().ufl_element() == self.form_handler.deformation_space.ufl_element(): raise InputError('cashocs._shape_optimization.shape_optimization_problem.ShapeOptimizationProblem.supply_shape_derivative', 'shape_derivative', 'The TestFunction has to be chosen from the same space as the corresponding adjoint.') if not shape_derivative.arguments()[0].ufl_function_space() == self.form_handler.deformation_space: shape_derivative = replace(shape_derivative, {shape_derivative.arguments()[0] : self.form_handler.test_vector_field}) if self.form_handler.degree_estimation: estimated_degree = np.maximum(estimate_total_polynomial_degree(self.form_handler.riesz_scalar_product), estimate_total_polynomial_degree(shape_derivative)) self.form_handler.assembler = fenics.SystemAssembler(self.form_handler.riesz_scalar_product, shape_derivative, self.form_handler.bcs_shape, form_compiler_parameters={'quadrature_degree' : estimated_degree}) else: try: self.form_handler.assembler = fenics.SystemAssembler(self.form_handler.riesz_scalar_product, shape_derivative, self.form_handler.bcs_shape) except (AssertionError, ValueError): estimated_degree = np.maximum(estimate_total_polynomial_degree(self.form_handler.riesz_scalar_product), estimate_total_polynomial_degree(shape_derivative)) self.form_handler.assembler = fenics.SystemAssembler(self.form_handler.riesz_scalar_product, shape_derivative, self.form_handler.bcs_shape, form_compiler_parameters={'quadrature_degree' : estimated_degree}) self.has_custom_derivative = True
def _forward_solve(self, lhs, rhs, func, bcs, **kwargs): solver = self.block_helper.forward_solver if solver is None: solver = backend.KrylovSolver(self.method, self.preconditioner) if self.assemble_system: A, _ = backend.assemble_system(lhs, rhs, bcs) if self.pc_operator is not None: P = self._replace_form(self.pc_operator) P, _ = backend.assemble_system(P, rhs, bcs) solver.set_operators(A, P) else: solver.set_operator(A) else: A = compat.assemble_adjoint_value(lhs) [bc.apply(A) for bc in bcs] if self.pc_operator is not None: P = self._replace_form(self.pc_operator) P = compat.assemble_adjoint_value(P) [bc.apply(P) for bc in bcs] solver.set_operators(A, P) else: solver.set_operator(A) self.block_helper.forward_solver = solver if self.assemble_system: system_assembler = backend.SystemAssembler(lhs, rhs, bcs) b = backend.Function(self.function_space).vector() system_assembler.assemble(b) else: b = compat.assemble_adjoint_value(rhs) [bc.apply(b) for bc in bcs] solver.parameters.update(self.krylov_solver_parameters) solver.solve(func.vector(), b) return func
def _forward_solve(self, lhs, rhs, func, bcs, **kwargs): solver = self.block_helper.forward_solver if solver is None: if self.assemble_system: A, _ = backend.assemble_system(lhs, rhs, bcs, **self.assemble_kwargs) else: A = compat.assemble_adjoint_value(lhs, **self.assemble_kwargs) [bc.apply(A) for bc in bcs] solver = backend.LUSolver(A, self.method) self.block_helper.forward_solver = solver if self.assemble_system: system_assembler = backend.SystemAssembler(lhs, rhs, bcs) b = backend.Function(self.function_space).vector() system_assembler.assemble(b) else: b = compat.assemble_adjoint_value(rhs) [bc.apply(b) for bc in bcs] if self.ident_zeros_tol is not None: A.ident_zeros(self.ident_zeros_tol) solver.parameters.update(self.lu_solver_parameters) solver.solve(func.vector(), b) return func
def __init__(self, lagrangian, bcs_list, states, adjoints, boundaries, config, ksp_options, adjoint_ksp_options, shape_scalar_product=None, deformation_space=None): """Initializes the ShapeFormHandler object. Parameters ---------- lagrangian : cashocs._forms.Lagrangian The Lagrangian corresponding to the shape optimization problem bcs_list : list[list[dolfin.fem.dirichletbc.DirichletBC]] list of boundary conditions for the state variables states : list[dolfin.function.function.Function] list of state variables adjoints : list[dolfin.function.function.Function] list of adjoint variables boundaries : dolfin.cpp.mesh.MeshFunctionSizet a MeshFunction for the boundary markers config : configparser.ConfigParser the configparser object storing the problems config ksp_options : list[list[list[str]]] The list of command line options for the KSP for the state systems. adjoint_ksp_options : list[list[list[str]]] The list of command line options for the KSP for the adjoint systems. shape_scalar_product : ufl.form.Form The weak form of the scalar product used to determine the shape gradient. """ FormHandler.__init__(self, lagrangian, bcs_list, states, adjoints, config, ksp_options, adjoint_ksp_options) self.boundaries = boundaries self.shape_scalar_product = shape_scalar_product self.degree_estimation = self.config.getboolean('ShapeGradient', 'degree_estimation', fallback=False) self.use_pull_back = self.config.getboolean('ShapeGradient', 'use_pull_back', fallback=True) if deformation_space is None: self.deformation_space = fenics.VectorFunctionSpace( self.mesh, 'CG', 1) else: self.deformation_space = deformation_space self.test_vector_field = fenics.TestFunction(self.deformation_space) self.regularization = Regularization(self) # Calculate the necessary UFL forms self.inhomogeneous_mu = False self.__compute_shape_derivative() self.__compute_shape_gradient_forms() self.__setup_mu_computation() if self.degree_estimation: self.estimated_degree = np.maximum( estimate_total_polynomial_degree(self.riesz_scalar_product), estimate_total_polynomial_degree(self.shape_derivative)) self.assembler = fenics.SystemAssembler(self.riesz_scalar_product, self.shape_derivative, self.bcs_shape, form_compiler_parameters={ 'quadrature_degree': self.estimated_degree }) else: try: self.assembler = fenics.SystemAssembler( self.riesz_scalar_product, self.shape_derivative, self.bcs_shape) except (AssertionError, ValueError): self.estimated_degree = np.maximum( estimate_total_polynomial_degree( self.riesz_scalar_product), estimate_total_polynomial_degree(self.shape_derivative)) self.assembler = fenics.SystemAssembler( self.riesz_scalar_product, self.shape_derivative, self.bcs_shape, form_compiler_parameters={ 'quadrature_degree': self.estimated_degree }) self.assembler.keep_diagonal = True self.fe_scalar_product_matrix = fenics.PETScMatrix() self.fe_shape_derivative_vector = fenics.PETScVector() self.update_scalar_product() # test for symmetry if not self.scalar_product_matrix.isSymmetric(): if not self.scalar_product_matrix.isSymmetric(1e-15): if not (self.scalar_product_matrix - self.scalar_product_matrix.copy().transpose() ).norm() / self.scalar_product_matrix.norm() < 1e-15: raise InputError( 'cashocs._forms.ShapeFormHandler', 'shape_scalar_product', 'Supplied scalar product form is not symmetric.') if self.opt_algo == 'newton' \ or (self.opt_algo == 'pdas' and self.inner_pdas == 'newton'): raise NotImplementedError( 'Second order methods are not implemented for shape optimization yet' )
def damped_newton_solve(F, u, bcs, rtol=1e-10, atol=1e-10, max_iter=50, convergence_type='combined', norm_type='l2', damped=True, verbose=True, ksp=None, ksp_options=None): r"""A damped Newton method for solving nonlinear equations. The damped Newton method is based on the natural monotonicity test from `Deuflhard, Newton methods for nonlinear problems <https://doi.org/10.1007/978-3-642-23899-4>`_. It also allows fine tuning via a direct interface, and absolute, relative, and combined stopping criteria. Can also be used to specify the solver for the inner (linear) subproblems via petsc ksps. The method terminates after ``max_iter`` iterations, or if a termination criterion is satisfied. These criteria are given by - a relative one in case ``convergence_type = 'rel'``, i.e., .. math:: \lvert\lvert F_{k} \rvert\rvert \leq \texttt{rtol} \lvert\lvert F_0 \rvert\rvert. - an absolute one in case ``convergence_type = 'abs'``, i.e., .. math:: \lvert\lvert F_{k} \rvert\rvert \leq \texttt{atol}. - a combination of both in case ``convergence_type = 'combined'``, i.e., .. math:: \lvert\lvert F_{k} \rvert\rvert \leq \texttt{atol} + \texttt{rtol} \lvert\lvert F_0 \rvert\rvert. The norm chosen for the termination criterion is specified via ``norm_type``. Parameters ---------- F : ufl.form.Form The variational form of the nonlinear problem to be solved by Newton's method. u : dolfin.function.function.Function The sought solution / initial guess. It is not assumed that the initial guess satisfies the Dirichlet boundary conditions, they are applied automatically. The method overwrites / updates this Function. bcs : list[dolfin.fem.dirichletbc.DirichletBC] A list of DirichletBCs for the nonlinear variational problem. rtol : float, optional Relative tolerance of the solver if convergence_type is either ``'combined'`` or ``'rel'`` (default is ``rtol = 1e-10``). atol : float, optional Absolute tolerance of the solver if convergence_type is either ``'combined'`` or ``'abs'`` (default is ``atol = 1e-10``). max_iter : int, optional Maximum number of iterations carried out by the method (default is ``max_iter = 50``). convergence_type : {'combined', 'rel', 'abs'} Determines the type of stopping criterion that is used. norm_type : {'l2', 'linf'} Determines which norm is used in the stopping criterion. damped : bool, optional If ``True``, then a damping strategy is used. If ``False``, the classical Newton-Raphson iteration (without damping) is used (default is ``True``). verbose : bool, optional If ``True``, prints status of the iteration to the console (default is ``True``). ksp : petsc4py.PETSc.KSP, optional The PETSc ksp object used to solve the inner (linear) problem if this is ``None`` it uses the direct solver MUMPS (default is ``None``). ksp_options : list[list[str]] The list of options for the linear solver. Returns ------- dolfin.function.function.Function The solution of the nonlinear variational problem, if converged. This overrides the input function u. Examples -------- Consider the problem .. math:: \begin{alignedat}{2} - \Delta u + u^3 &= 1 \quad &&\text{ in } \Omega=(0,1)^2 \\ u &= 0 \quad &&\text{ on } \Gamma. \end{alignedat} This is solved with the code :: from fenics import * import cashocs mesh, _, boundaries, dx, _, _ = cashocs.regular_mesh(25) V = FunctionSpace(mesh, 'CG', 1) u = Function(V) v = TestFunction(V) F = inner(grad(u), grad(v))*dx + pow(u,3)*v*dx - Constant(1)*v*dx bcs = cashocs.create_bcs_list(V, Constant(0.0), boundaries, [1,2,3,4]) cashocs.damped_newton_solve(F, u, bcs) """ if not convergence_type in ['rel', 'abs', 'combined']: raise InputError('cashocs.nonlinear_solvers.damped_newton_solve', 'convergence_type', 'Input convergence_type has to be one of \'rel\', \'abs\', or \'combined\'.') if not norm_type in ['l2', 'linf']: raise InputError('cashocs.nonlinear_solvers.damped_newton_solve', 'norm_type', 'Input norm_type has to be one of \'l2\' or \'linf\'.') # create the PETSc ksp if ksp is None: if ksp_options is None: ksp_options = [ ['ksp_type', 'preonly'], ['pc_type', 'lu'], ['pc_factor_mat_solver_type', 'mumps'], ['mat_mumps_icntl_24', 1] ] ksp = PETSc.KSP().create() _setup_petsc_options([ksp], [ksp_options]) ksp.setFromOptions() # Calculate the Jacobian. dF = fenics.derivative(F, u) # Setup increment and function for monotonicity test V = u.function_space() du = fenics.Function(V) ddu = fenics.Function(V) u_save = fenics.Function(V) iterations = 0 [bc.apply(u.vector()) for bc in bcs] # copy the boundary conditions and homogenize them for the increment bcs_hom = [fenics.DirichletBC(bc) for bc in bcs] [bc.homogenize() for bc in bcs_hom] assembler = fenics.SystemAssembler(dF, -F, bcs_hom) assembler.keep_diagonal = True A_fenics = fenics.PETScMatrix() residual = fenics.PETScVector() # Compute the initial residual assembler.assemble(A_fenics, residual) A_fenics.ident_zeros() A = fenics.as_backend_type(A_fenics).mat() b = fenics.as_backend_type(residual).vec() res_0 = residual.norm(norm_type) if res_0 == 0.0: if verbose: print('Residual vanishes, input is already a solution.') return u res = res_0 if verbose: print('Newton Iteration ' + format(iterations, '2d') + ' - residual (abs): ' + format(res, '.3e') + ' (tol = ' + format(atol, '.3e') + ') residual (rel): ' + format(res/res_0, '.3e') + ' (tol = ' + format(rtol, '.3e') + ')') if convergence_type == 'abs': tol = atol elif convergence_type == 'rel': tol = rtol*res_0 else: tol = rtol*res_0 + atol # While loop until termination while res > tol and iterations < max_iter: iterations += 1 lmbd = 1.0 breakdown = False u_save.vector()[:] = u.vector()[:] # Solve the inner problem _solve_linear_problem(ksp, A, b, du.vector().vec(), ksp_options) du.vector().apply('') # perform backtracking in case damping is used if damped: while True: u.vector()[:] += lmbd*du.vector()[:] assembler.assemble(residual) b = fenics.as_backend_type(residual).vec() _solve_linear_problem(ksp=ksp, b=b, x=ddu.vector().vec(), ksp_options=ksp_options) ddu.vector().apply('') if ddu.vector().norm(norm_type)/du.vector().norm(norm_type) <= 1: break else: u.vector()[:] = u_save.vector()[:] lmbd /= 2 if lmbd < 1e-6: breakdown = True break else: u.vector()[:] += du.vector()[:] if breakdown: raise NotConvergedError('Newton solver (state system)', 'Stepsize for increment too low.') if iterations == max_iter: raise NotConvergedError('Newton solver (state system)', 'Maximum number of iterations were exceeded.') # compute the new residual assembler.assemble(A_fenics, residual) A_fenics.ident_zeros() A = fenics.as_backend_type(A_fenics).mat() b = fenics.as_backend_type(residual).vec() [bc.apply(residual) for bc in bcs_hom] res = residual.norm(norm_type) if verbose: print('Newton Iteration ' + format(iterations, '2d') + ' - residual (abs): ' + format(res, '.3e') + ' (tol = ' + format(atol, '.3e') + ') residual (rel): ' + format(res/res_0, '.3e') + ' (tol = ' + format(rtol, '.3e') + ')') if res < tol: if verbose: print('\nNewton Solver converged after ' + str(iterations) + ' iterations.\n') break return u