def scalar_product(self, a, b): """Computes the scalar product between control type functions a and b. Parameters ---------- a : list[dolfin.function.function.Function] The first argument. b : list[dolfin.function.function.Function] The second argument. Returns ------- float The value of the scalar product. """ result = 0.0 for i in range(self.control_dim): x = fenics.as_backend_type(a[i].vector()).vec() y = fenics.as_backend_type(b[i].vector()).vec() temp, _ = self.scalar_products_matrices[i].getVecs() self.scalar_products_matrices[i].mult(x, temp) result += temp.dot(y) return result
def _assemble_petsc_system(A_form, b_form, bcs=None): """Assembles a system symmetrically and converts objects to PETSc format. Parameters ---------- A_form : ufl.form.Form The UFL form for the left-hand side of the linear equation. b_form : ufl.form.Form The UFL form for the right-hand side of the linear equation. bcs : None or dolfin.fem.dirichletbc.DirichletBC or list[dolfin.fem.dirichletbc.DirichletBC] A list of Dirichlet boundary conditions. Returns ------- petsc4py.PETSc.Mat The petsc matrix for the left-hand side of the linear equation. petsc4py.PETSc.Vec The petsc vector for the right-hand side of the linear equation. Notes ----- This function always uses the ident_zeros method of the matrix in order to add a one to the diagonal in case the corresponding row only consists of zeros. This allows for well-posed problems on the boundary etc. """ A, b = fenics.assemble_system(A_form, b_form, bcs, keep_diagonal=True) A.ident_zeros() A = fenics.as_backend_type(A).mat() b = fenics.as_backend_type(b).vec() return A, b
def solve(self): """Solves the Riesz projection problem to obtain the shape gradient of the cost functional. Returns ------- gradient : dolfin.function.function.Function The function representing the shape gradient of the (reduced) cost functional. """ self.state_problem.solve() self.adjoint_problem.solve() if not self.has_solution: self.form_handler.regularization.update_geometric_quantities() self.form_handler.assembler.assemble( self.form_handler.fe_shape_derivative_vector) b = fenics.as_backend_type( self.form_handler.fe_shape_derivative_vector).vec() _solve_linear_problem(self.ksp, self.form_handler.scalar_product_matrix, b, self.gradient.vector().vec(), self.ksp_options) self.gradient.vector().apply('') self.has_solution = True self.gradient_norm_squared = self.form_handler.scalar_product( self.gradient, self.gradient) return self.gradient
def solve(self): """Solves the Riesz projection problem to obtain the gradient of the (reduced) cost functional. Returns ------- gradients : list[dolfin.function.function.Function] The list of gradient of the cost functional. """ self.state_problem.solve() self.adjoint_problem.solve() if not self.has_solution: for i in range(self.form_handler.control_dim): b = fenics.as_backend_type( fenics.assemble( self.form_handler.gradient_forms_rhs[i])).vec() _solve_linear_problem(ksp=self.ksps[i], b=b, x=self.gradients[i].vector().vec()) self.gradients[i].vector().apply('') self.has_solution = True self.gradient_norm_squared = self.form_handler.scalar_product( self.gradients, self.gradients) return self.gradients
def imExIF(self, ts, t, u, udot, f): """Implicit/Explicit IFunction for PETSc TS This is designed for use as the LHS in an implicit/explicit PETSc time-stepper. The DE to be solved is always A.u' = b(u). u corresponds to self.sol. A is a constant (i.e., u-independent) matrix calculated by assembling the u'-dependent term of the weak form, and b a u-dependent vector calculated by assembling the remaining terms. This equation may be solved in any of three forms: Fully implicit: A.u' - b(u) = 0 implicit/explicit: A.u' = b(u) Fully explicit: u' = A^(-1).b(u) Corresponding to these are seven functions that can be provided to PETSc.TS: imExIF, imExIJ, imExRHSF, and imExRHSJ calculate the LHS function and Jacobian and the RHS function and Jacobian for the implicit/explicit case. Arguments: t: the time. u: a petsc4py.PETSc.Vec containing the state vector (not used). udot: a petsc4py.PETSc.Vec containing the time derivative u' f: a petsc4py.PETSc.Vec in which A.u' will be left. """ try: self.ksdg.setup_problem(t) except TypeError: self.ksdg.setup_problem() pA = fe.as_backend_type(self.ksdg.A).mat() pA.mult(udot, f) f.assemble()
def apply_time_boundary_conditions(domain, V, u0, A, b): """Apply the time slice boundary conditions by hand. Args: domain: Space-time domain. V: Function space. u0: Initial data. A: The stiffness matrix. b: The right-hand side. Outputs: A: The new stiffness matrix with the boundary conditions. b: The new right-hand side with the boundary conditions. """ # Export matrices to PETSc A = as_backend_type(A).mat() b = as_backend_type(b).vec() # Apply terminal boundary condition on v by zeroing the corresponding # matrix rows. The dof indices are saved for later. def on_terminal_slice(x): return domain.get_terminal_slice().inside(x, True) \ and (not domain.get_spatial_boundary().inside(x, True)) (rows_to_zero, _) = get_dof_by_criterion(V, on_terminal_slice) A.zeroRows(rows_to_zero, diag=0) # Apply initial boundary condition on u def on_initial_slice(x): return domain.get_initial_slice().inside(x, True) \ and (not domain.get_spatial_boundary().inside(x, True)) (dof_no, dof_coor) = get_dof_by_criterion(V, on_initial_slice) # Update the matrices A.setOption(PETSc.Mat.Option.NEW_NONZERO_LOCATION_ERR, 0) for (i, k) in enumerate(dof_no): j = rows_to_zero[i] A[j, k] = 1.0 b[j] = u0(dof_coor[i]) A.assemble() b.assemble() # put petsc4py matrix back to fenics A = PETScMatrix(A) b = PETScVector(b) return (A, b)
def imExRHSF(self, ts, t, u, g, *args, **kwargs): """Implicit/Explicit RHSFunction for PETSc TS This is designed for use as the RHS in an implicit/explicit PETSc time-stepper. The DE to be solved is always A.u' = b(u). u corresponds to self.ksdg.sol. A is a constant (i.e., u-independent) matrix calculated by assembling the u'-dependent term of the weak form, and b a u-dependent vector calculated by assembling the remaining terms. This equation may be solved in any of three forms: Fully implicit: A.u' - b(u) = 0 implicit/explicit: A.u' = b(u) Fully explicit: u' = A^(-1).b(u) Corresponding to these are seven functions that can be provided to PETSc.TS: imExIF, imExIJ, imExRHSF, and imExRHSJ calculate the LHS function and Jacobian and the RHS function and Jacobian for the implicit/explicit case. Arguments: t: the time. u: a petsc4py.PETSc.Vec containing the state vector. g: a petsc4py.PETSc.Vec in which b will be left. """ # # PETSc apparently calls RHSF when it shoud call RHSJ. We # detect this by the extra argument and reroute the call. # if (args): return self.imExRHSJ(t, u, g, *args, **kwargs) psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') try: self.ksdg.setup_problem(t) except TypeError: self.ksdg.setup_problem() self.ksdg.b = fe.assemble(self.ksdg.all_terms) if hasattr(self.ksdg, 'bcs'): for bc in self.ksdg.bcs: bc.apply(self.ksdg.b) pb = fe.as_backend_type(self.ksdg.b).vec() pb.copy(g) g.assemble()
def imExRHSJ(self, ts, t, u, J, B): """Implicit/Explicit RHSFunction for PETSc TS This is designed for use as the RHS in an implicit/explicit PETSc time-stepper. The DE to be solved is always A.u' = b(u). u corresponds to self.sol. A is a constant (i.e., u-independent) matrix calculated by assembling the u'-dependent term of the weak form, and b a u-dependent vector calculated by assembling the remaining terms. This equation may be solved in any of three forms: Fully implicit: A.u' - b(u) = 0 implicit/explicit: A.u' = b(u) Fully explicit: u' = A^(-1).b(u) Corresponding to these are seven functions that can be provided to PETSc.TS: imExIF, imExIJ, imExRHSF, and imExRHSJ calculate the LHS function and Jacobian and the RHS function and Jacobian for the implicit/explicit case. Arguments: t: the time. u: a petsc4py.PETSc.Vec containing the state vector J, B: matrices in which the Jacobian of b(u) is left. """ try: self.ksdg.setup_problem(t) except TypeError: self.ksdg.setup_problem() psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') Jsol = fe.assemble(self.ksdg.J_terms) pJsol = fe.as_backend_type(Jsol).mat() pA.copy(J) J.scale(shift) J.axpy(1.0, pJsol) pJsol.copy(J) J.assemble() if J != B: J.copy(B) B.assemble() return True
def compute_curvature(self): """Computes the mean curvature vector of the geometry. Returns ------- None """ if self.mu_curvature > 0.0: A = fenics.assemble(self.a_curvature, keep_diagonal=True) A.ident_zeros() A = fenics.as_backend_type(A).mat() b = fenics.assemble(self.L_curvature) b = fenics.as_backend_type(b).vec() _solve_linear_problem(A=A, b=b, x=self.kappa_curvature.vector().vec()) else: pass
def explicitRHSF(self, ts, t, u, g): """Explicit RHSFunction for PETSc TS This is designed for use as the RHS in an explicit PETSc time-stepper. The DE to be solved is always A.u' = b(u). u corresponds to self.ksdg.sol. A is a constant (i.e., u-independent) matrix calculated by assembling the u'-dependent term of the weak form, and b a u-dependent vector calculated by assembling the remaining terms. This equation may be solved in any of three forms: Fully implicit: A.u' - b(u) = 0 implicit/explicit: A.u' = b(u) Fully explicit: u' = A^(-1).b(u) Corresponding to these are seven functions that can be provided to PETSc.TS: explicitRHSF calculates the RHS function for the explicit case. Arguments: t: the time. u: a petsc4py.PETSc.Vec containing the state vector. g: a petsc4py.PETSc.Vec in which A^(-1).b(u) will be left. """ psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') try: self.ksdg.setup_problem(t) except TypeError: self.ksdg.setup_problem() self.ksdg.b = fe.assemble(self.ksdg.all_terms) if hasattr(self.ksdg, 'bcs'): for bc in self.ksdg.bcs: bc.apply(self.ksdg.b) ret = self.ksdg.solver.solve(self.ksdg.dsol.vector(), self.ksdg.b) pdu = fe.as_backend_type(self.ksdg.dsol.vector()).vec() pdu.copy(g) g.assemble()
def historyMonitor(self, ts, k, t, u): """For use as TS monitor. Stores results in history""" # logTS("historyMonitor", flush=True) h = ts.getTimeStep() if not hasattr(self, 'history'): self.history = [] # # make a local copy of the dof vector # psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') lu = self.ksdg.sol.vector().gather_on_zero() self.history.append(dict(step=k, h=h, t=t, u=lu.array.copy()))
def scalar_product(self, a, b): """Computes the scalar product between two deformation functions. Parameters ---------- a : dolfin.function.function.Function The first argument. b : dolfin.function.function.Function The second argument. Returns ------- float The value of the scalar product. """ x = fenics.as_backend_type(a.vector()).vec() y = fenics.as_backend_type(b.vector()).vec() temp, _ = self.scalar_product_matrix.getVecs() self.scalar_product_matrix.mult(x, temp) result = temp.dot(y) return result
def saveMonitor(ts, k, t, u): # # make a local copy of the dof vector # # logTS('saveMonitor entered') psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') lu = self.ksdg.sol.vector().gather_on_zero() if ts.comm.rank == 0: lu = lu[self.remap] # # reopen and close every time, so file valid after abort # tsf = KSDGTimeSeries(tsname, 'r+') tsf.store(lu, t, k=k) tsf.close()
def update_scalar_product(self): """Updates the linear elasticity equations to the current geometry. Updates the left-hand-side of the linear elasticity equations (needed when the geometry changes). Returns ------- None """ self.__compute_mu_elas() self.assembler.assemble(self.fe_scalar_product_matrix) self.fe_scalar_product_matrix.ident_zeros() self.scalar_product_matrix = fenics.as_backend_type( self.fe_scalar_product_matrix).mat()
def checkpointMonitor(self, ts, k, t, u, prefix): """For use as TS monitor. Checkpoints results""" h = ts.getTimeStep() # # make a local copy of the dof vector # if not hasattr(self, 'remap'): self.lmesh = gather_mesh(self.ksdg.mesh) logTS('checkpointMonitor: self.lmesh', self.lmesh) self.remap = dofremap(self.ksdg) logTS('checkpointMonitor: self.remap', self.remap) psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') lu = self.ksdg.sol.vector().gather_on_zero() cpname = prefix + '_' + str(k) + '.h5' if self.comm.rank == 0: lu = lu[self.remap] cpf = KSDGTimeSeries(cpname, 'w') cpf.store(lu, t, k=k) cpf.close()
def convert_fenics_csr_matrix_to_scipy_csr_matrix(A_fenics): ai, aj, av = fenics.as_backend_type(A_fenics).mat().getValuesCSR() A_scipy = sps.csr_matrix((av, aj, ai)) return A_scipy
def hessian_application(self, h, out): r"""Computes the application of the Hessian to some element This is needed in the truncated Newton method where we solve the system .. math:: J''(u) [\Delta u] = - J'(u) via iterative methods (conjugate gradient or conjugate residual method) Parameters ---------- h : list[dolfin.function.function.Function] A function to which we want to apply the Hessian to. out : list[dolfin.function.function.Function] A list of functions into which the result is saved. Returns ------- None """ for i in range(self.control_dim): self.test_directions[i].vector()[:] = h[i].vector()[:] self.states_prime = self.form_handler.states_prime self.adjoints_prime = self.form_handler.adjoints_prime self.bcs_list_ad = self.form_handler.bcs_list_ad if not self.form_handler.state_is_picard or self.form_handler.state_dim == 1: for i in range(self.state_dim): A, b = _assemble_petsc_system( self.form_handler.sensitivity_eqs_lhs[i], self.form_handler.sensitivity_eqs_rhs[i], self.bcs_list_ad[i]) _solve_linear_problem(self.state_ksps[i], A, b, self.states_prime[i].vector().vec(), self.form_handler.state_ksp_options[i]) self.states_prime[i].vector().apply('') for i in range(self.state_dim): A, b = _assemble_petsc_system( self.form_handler.adjoint_sensitivity_eqs_lhs[-1 - i], self.form_handler.w_1[-1 - i], self.bcs_list_ad[-1 - i]) _solve_linear_problem( self.adjoint_ksps[-1 - i], A, b, self.adjoints_prime[-1 - i].vector().vec(), self.form_handler.adjoint_ksp_options[-1 - i]) self.adjoints_prime[-1 - i].vector().apply('') else: for i in range(self.maxiter + 1): res = 0.0 for j in range(self.form_handler.state_dim): res_j = fenics.assemble( self.form_handler.sensitivity_eqs_picard[j]) [ bc.apply(res_j) for bc in self.form_handler.bcs_list_ad[j] ] res += pow(res_j.norm('l2'), 2) if res == 0: break res = np.sqrt(res) if i == 0: res_0 = res if self.picard_verbose: print('Picard Sensitivity 1 Iteration ' + str(i) + ': ||res|| (abs): ' + format(res, '.3e') + ' ||res|| (rel): ' + format(res / res_0, '.3e')) if res / res_0 < self.rtol or res < self.atol: break if i == self.maxiter: raise NotConvergedError( 'Picard iteration for the computation of the state sensitivity', 'Maximum number of iterations were exceeded.') for j in range(self.form_handler.state_dim): A, b = _assemble_petsc_system( self.form_handler.sensitivity_eqs_lhs[j], self.form_handler.sensitivity_eqs_rhs[j], self.bcs_list_ad[j]) _solve_linear_problem( self.state_ksps[j], A, b, self.states_prime[j].vector().vec(), self.form_handler.state_ksp_options[j]) self.states_prime[j].vector().apply('') if self.picard_verbose: print('') for i in range(self.maxiter + 1): res = 0.0 for j in range(self.form_handler.state_dim): res_j = fenics.assemble( self.form_handler.adjoint_sensitivity_eqs_picard[j]) [ bc.apply(res_j) for bc in self.form_handler.bcs_list_ad[j] ] res += pow(res_j.norm('l2'), 2) if res == 0: break res = np.sqrt(res) if i == 0: res_0 = res if self.picard_verbose: print('Picard Sensitivity 2 Iteration ' + str(i) + ': ||res|| (abs): ' + format(res, '.3e') + ' ||res|| (rel): ' + format(res / res_0, '.3e')) if res / res_0 < self.rtol or res < self.atol: break if i == self.maxiter: raise NotConvergedError( 'Picard iteration for the computation of the adjoint sensitivity', 'Maximum number of iterations were exceeded.') for j in range(self.form_handler.state_dim): A, b = _assemble_petsc_system( self.form_handler.adjoint_sensitivity_eqs_lhs[-1 - j], self.form_handler.w_1[-1 - j], self.bcs_list_ad[-1 - j]) _solve_linear_problem( self.adjoint_ksps[-1 - j], A, b, self.adjoints_prime[-1 - j].vector().vec(), self.form_handler.adjoint_ksp_options[-1 - j]) self.adjoints_prime[-1 - j].vector().apply('') if self.picard_verbose: print('') for i in range(self.control_dim): b = fenics.as_backend_type( fenics.assemble(self.form_handler.hessian_rhs[i])).vec() _solve_linear_problem(self.ksps[i], b=b, x=out[i].vector().vec(), ksp_options=self.riesz_ksp_options[i]) out[i].vector().apply('') self.no_sensitivity_solves += 2
def implicitIJ(self, ts, t, u, udot, shift, J, B): """Fully implicit IJacobian for PETSc TS This is designed for use as the LHS in a fully implicit PETSc time-stepper. The DE to be solved is always A.u' = b(u). u corresponds to self.ksdg.sol. A is a constant (i.e., u-independent) matrix calculated by assembling the u'-dependent term of the weak form, and b a u-dependent vector calculated by assembling the remaining terms. This equation may be solved in any of three forms: Fully implicit: A.u' - b(u) = 0 implicit/explicit: A.u' = b(u) Fully explicit: u' = A^(-1).b(u) Corresponding to these are seven functions that can be provided to PETSc.TS: implicitIF and implicitIJ calculate the LHS A.u' - b and its Jacobian for the fully implicit form. Arguments: t: the time. u: a petsc4py.PETSc.Vec containing the state vector udot: a petsc4py.PETSc.Vec containing the u' (not used) shift: a real number -- see PETSc Ts documentation. J, B: matrices in which the Jacobian shift*A - Jacobian(b(u)) are left. """ logTS('implicitIJ entered') try: try: self.ksdg.setup_problem(t) except TypeError: self.ksdg.setup_problem() pA = fe.as_backend_type(self.ksdg.A).mat() Adiag = pA.getDiagonal() logTS('Adiag.array', Adiag.array) psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) self.ksdg.sol.vector().apply('insert') # if isinstance(self.ksdg.JU_terms, list): # JUs = [fe.assemble(JU_term) # for JU_term in self.ksdg.JU_terms] # else: # JUs = [fe.assemble(self.ksdg.JU_terms)] # pJUs = [fe.as_backend_type(JU).mat() # for JU in JUs] # if isinstance(self.ksdg.Jrho_terms, list): # # Jrhos = [fe.assemble(Jrho_term, # # form_compiler_parameters = {"quadrature_degree": 3}) # # for Jrho_term in self.ksdg.Jrho_terms] # Jrhos = [fe.assemble(Jrho_term) # for Jrho_term in self.ksdg.Jrho_terms] # else: # Jrhos = [fe.assemble(self.ksdg.Jrho_terms)] # pJrhos = [fe.as_backend_type(Jrho).mat() # for Jrho in Jrhos] Jsol = fe.assemble(self.ksdg.J_terms) # # Don't know if this is really what I should be doing: # for bc in self.ksdg.bcs: bc.apply(Jsol) pJsol = fe.as_backend_type(Jsol).mat() Jdiag = pJsol.getDiagonal() logTS('Jdiag.array', Jdiag.array) logTS('Adiag.array/Jdiag.array', Adiag.array / Jdiag.array) logTS('shift', shift) pA.copy(J) J.scale(shift) J.axpy(-1.0, pJsol) J.assemble() logTS('implicitIJ, J assembled') if J != B: J.copy(B) B.assemble() except FloatingPointError as e: logTS('implicitIF got FloatingPointError', str(type(e), str(e))) einfo = sys.exc_info() sys.excepthook(*einfo) except Exception as e: logTS('implicitIJ got exception', str(type(e)), str(e)) tbstr = traceback.format_exc() logTS('Exception:', tbstr) logTS('str(ts)', str(self)) einfo = sys.exc_info() raise einfo[1].with_traceback(einfo[2]) return True
def __init__(self, lagrangian, bcs_list, states, controls, adjoints, config, riesz_scalar_products, control_constraints, ksp_options, adjoint_ksp_options, require_control_constraints): """Initializes the ControlFormHandler class. Parameters ---------- lagrangian : cashocs._forms.Lagrangian The lagrangian corresponding to the optimization problem. bcs_list : list[list[dolfin.fem.dirichletbc.DirichletBC]] The list of DirichletBCs for the state equation. states : list[dolfin.function.function.Function] The function that acts as the state variable. controls : list[dolfin.function.function.Function] The function that acts as the control variable. adjoints : list[dolfin.function.function.Function] The function that acts as the adjoint variable. config : configparser.ConfigParser The configparser object of the config file. riesz_scalar_products : list[ufl.form.Form] The UFL forms of the scalar products for the control variables. control_constraints : list[list[dolfin.function.function.Function]] The control constraints of the problem. 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. require_control_constraints : list[bool] A list of boolean flags that indicates, whether the i-th control has actual control constraints present. """ FormHandler.__init__(self, lagrangian, bcs_list, states, adjoints, config, ksp_options, adjoint_ksp_options) # Initialize the attributes from the arguments self.controls = controls self.riesz_scalar_products = riesz_scalar_products self.control_constraints = control_constraints self.require_control_constraints = require_control_constraints self.control_dim = len(self.controls) self.control_spaces = [x.function_space() for x in self.controls] # Define the necessary functions self.states_prime = [fenics.Function(V) for V in self.state_spaces] self.adjoints_prime = [fenics.Function(V) for V in self.adjoint_spaces] self.test_directions = [ fenics.Function(V) for V in self.control_spaces ] self.trial_functions_control = [ fenics.TrialFunction(V) for V in self.control_spaces ] self.test_functions_control = [ fenics.TestFunction(V) for V in self.control_spaces ] self.temp = [fenics.Function(V) for V in self.control_spaces] # Compute the necessary equations self.__compute_gradient_equations() if self.opt_algo == 'newton' or \ (self.opt_algo == 'pdas' and self.inner_pdas == 'newton'): self.__compute_newton_forms() # Initialize the scalar products fenics_scalar_product_matrices = [ fenics.assemble(self.riesz_scalar_products[i], keep_diagonal=True) for i in range(self.control_dim) ] self.scalar_products_matrices = [ fenics.as_backend_type(fenics_scalar_product_matrices[i]).mat() for i in range(self.control_dim) ] copy_scalar_product_matrices = [ fenics_scalar_product_matrices[i].copy() for i in range(self.control_dim) ] [ copy_scalar_product_matrices[i].ident_zeros() for i in range(self.control_dim) ] self.riesz_projection_matrices = [ fenics.as_backend_type(copy_scalar_product_matrices[i]).mat() for i in range(self.control_dim) ] # Test for symmetry of the scalar products for i in range(self.control_dim): if not self.riesz_projection_matrices[i].isSymmetric(): if not self.riesz_projection_matrices[i].isSymmetric(1e-15): if not (self.riesz_projection_matrices[i] - self.riesz_projection_matrices[i].copy().transpose( )).norm() / self.riesz_projection_matrices[i].norm( ) < 1e-15: raise InputError( 'cashocs._forms.ControlFormHandler', 'riesz_scalar_products', 'Supplied scalar product form is not symmetric.')
def __init__(self, ksdg, t0=0.0, dt=0.001, tmax=20, maxsteps=100, rtol=1e-5, atol=1e-5, restart=True, tstype=PETSc.TS.Type.ROSW, finaltime=PETSc.TS.ExactFinalTime.STEPOVER, rollback_factor=None, hmin=None, comm=PETSc.COMM_WORLD): """ Create a timestepper Required positional argument: ksdg: the KSDGSolver in which the problem has been set up. Keyword arguments: t0=0.0: the initial time. dt=0.001: the initial time step. maxdt = 0.5: the maximum time step tmax=20: the final time. maxsteps=100: maximum number of steps to take. rtol=1e-5: relative error tolerance. atol=1e-5: absolute error tolerance. restart=True: whether to set the initial condition to rho0, U0 tstype=PETSc.TS.Type.ROSW: implicit solver to use. finaltime=PETSc.TS.ExactFinalTime.STEPOVER: how to handle final time step. fpe_factor=0.1: scale time step by this factor on floating point error. hmin=1e-20: minimum step size comm = PETSc.COMM_WORLD You can set other options by calling TS member functions directly. If you want to pay attention to PETSc command-line options, you should call ts.setFromOptions(). Call ts.solve() to run the timestepper, call ts.cleanup() to destroy its data when you're finished. """ # print("KSDGTS __init__ entered", flush=True) super().__init__() self.create() # self.create(comm=comm) # self._comm = comm self.mpi_comm = self.comm if (not isinstance(self.comm, type(MPI.COMM_SELF))): self.mpi_comm = self.comm.tompi4py() self.ksdg = ksdg popts = PETSc.Options() if rollback_factor is None: try: self.rollback_factor = popts.getReal( 'ts_adapt_scale_solve_failed') except KeyError: self.rollback_factor = self.default_rollback_factor else: self.rollback_factor = rollback_factor if hmin: self.hmin = hmin else: self.hmin = self.default_hmin self.setProblemType(self.ProblemType.NONLINEAR) self.setMaxSNESFailures(1) self.setTolerances(atol=atol, rtol=rtol) self.setType(tstype) logTS('KSDGTS __init__, calling setup_problem') try: self.ksdg.setup_problem(t0) except TypeError: self.ksdg.setup_problem() logTS('KSDGTS __init__, setup_problem returned') self.history = [] J = fe.as_backend_type(self.ksdg.A).mat().duplicate() J.setOption(PETSc.Mat.Option.NEW_NONZERO_ALLOCATION_ERR, False) J.setUp() if restart: self.ksdg.restart() u = fe.as_backend_type(self.ksdg.sol.vector()).vec().duplicate() u.setUp() f = u.duplicate() f.setUp() self.params = dict(t0=t0, dt=dt, tmax=tmax, maxsteps=maxsteps, rtol=rtol, atol=atol, tstype=tstype, finaltime=finaltime, restart=restart, J=J, u=u, f=f) fe.as_backend_type(self.ksdg.sol.vector()).vec().copy(u) # logTS('init u.array', u.array) self.setSolution(u) self.setTime(t0) self.setTimeStep(dt) self.setMaxSteps(max_steps=maxsteps) self.setMaxTime(max_time=tmax) self.setExactFinalTime(finaltime) self.setMaxTime(tmax) self.setMaxSteps(maxsteps) fe.parameters['krylov_solver']['nonzero_initial_guess'] = True
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
def ConvertFenicsBackendToScipyCSRSparse(A): A_mat = df.as_backend_type(A).mat() return csr_matrix(A_mat.getValuesCSR()[::-1], shape = A_mat.size)
def fenics_matrix_to_csr_matrix(fenics_matrix): return csr_matrix( fenics.as_backend_type(fenics_matrix).mat().getValuesCSR()[::-1], shape=(fenics_matrix.size(0), fenics_matrix.size(1)), )
def implicitIF(self, ts, t, u, udot, f): """Fully implicit IFunction for PETSc TS This is designed for use as the LHS in a fully implicit PETSc time-stepper. The DE to be solved is always A.u' = b(u). u corresponds to self.ksdg.sol. A is a constant (i.e., u-independent) matrix calculated by assembling the u'-dependent term of the weak form, and b a u-dependent vector calculated by assembling the remaining terms. This equation may be solved in any of three forms: Fully implicit: A.u' - b(u) = 0 implicit/explicit: A.u' = b(u) Fully explicit: u' = A^(-1).b(u) Corresponding to these are seven functions that can be provided to PETSc.TS: implicitIF and implicitIJ calculate the LHS A.u' - b and its Jacobian for the fully implicit form. Arguments: t: the time u: a petsc4py.PETSc.Vec containing the state vector udot: a petsc4py.PETSc.Vec containing the time derivative u' f: a petsc4py.PETSc.Vec in which A.u' - b will be left. """ try: # if self.rollback: # raise ValueError('rolling back') logTS('implicitIF entered') try: self.ksdg.setup_problem(t) except TypeError: self.ksdg.setup_problem() # logTS('self.ksdg.A.array()', self.ksdg.A.array()) pA = fe.as_backend_type(self.ksdg.A).mat() # logTS('u.array', u.array) psol = fe.as_backend_type(self.ksdg.sol.vector()).vec() u.copy(psol) # logTS('psol.array', psol.array) self.ksdg.sol.vector().apply('insert') self.ksdg.b = fe.assemble(self.ksdg.all_terms) # logTS('self.ksdg.b[:]', self.ksdg.b[:]) if hasattr(self.ksdg, 'bcs'): for bc in self.ksdg.bcs: bc.apply(self.ksdg.b) self.ksdg.b.apply('insert') pb = fe.as_backend_type(self.ksdg.b).vec() # logTS('pb.array', pb.array) pb.copy(f) f.scale(-1.0) pA.multAdd(udot, f, f) f.assemble() logTS('implicitIF, f assembled') # if not np.min(u.array) > 0: # logTS('np.min(u.array)', np.min(u.array)) # raise(ValueError('nonpositive function values')) # logTS('udot.array', udot.array) if np.isnan(np.min(udot.array)): logTS('np.min(udot.array)', np.min(udot.array)) # raise(ValueError('nans in udot')) # logTS('f.array', f.array) if np.isnan(np.min(f.array)): logTS('np.min(f.array)', np.min(f.array)) # raise(ValueError('nans in computed f')) except (ValueError, FloatingPointError) as e: logTS('implicitIF got FloatingPointError', str(type(e)), str(e)) einfo = sys.exc_info() tbstr = traceback.format_exc() logTS('traceback', tbstr) # self.rollback = True # signal trouble upstairs f.set(0.0) f.assemble() except Exception as e: logTS('implicitIF got exception', str(type(e)), str(e)) tbstr = traceback.format_exc() logTS('Exception:', tbstr) logTS('str(ts)', str(self)) einfo = sys.exc_info() raise einfo[1].with_traceback(einfo[2])
def setup_problem(self, t, debug=False): self.set_time(t) # # assemble the matrix, if necessary (once for all time points) # if not hasattr(self, 'A'): logVARIABLE('making matrix A') self.drho_integral = sum([ tdrho * wrho * self.dx for tdrho, wrho in zip(self.tdrhos, self.wrhos) ]) self.dU_integral = sum( [tdU * wU * self.dx for tdU, wU in zip(self.tdUs, self.wUs)]) logVARIABLE('assembling A') self.A = fe.PETScMatrix() logVARIABLE('self.A', self.A) fe.assemble(self.drho_integral + self.dU_integral, tensor=self.A) logVARIABLE('A assembled. Applying BCs') pA = fe.as_backend_type(self.A).mat() Adiag = pA.getDiagonal() logVARIABLE('Adiag.array', Adiag.array) # self.A = fe.assemble(self.drho_integral + self.dU_integral + # self.dP_integral) for bc in self.bcs: bc.apply(self.A) Adiag = pA.getDiagonal() logVARIABLE('Adiag.array', Adiag.array) self.dsol = Function(self.VS) dsolsplit = self.dsol.split() self.drhos, self.dUs = (dsolsplit[:2**self.dim], dsolsplit[2**self.dim:]) # # assemble RHS (for each time point, but compile only once) # # # These are the values of rho and U themselves (not their # symmetrized versions) on all subdomains of the original # domain. # if not hasattr(self, 'rhosds'): self.rhosds = matmul(self.eomat, self.irhos) # self.Usds is a list of nligands lists. Sublist i is of # length 2**dim and lists the value of ligand i on each of the # 2**dim subdomains. # if not hasattr(self, 'Usds'): self.Usds = [ matmul(self.eomat, self.iUs[i * 2**self.dim:(i + 1) * 2**self.dim]) for i in range(self.nligands) ] if not hasattr(self, 'rho_terms'): logVARIABLE('making rho_terms') self.sigma = self.iparams['sigma'] self.s2 = self.sigma * self.sigma / 2 self.rhomin = self.iparams['rhomin'] self.rhopen = self.iparams['rhopen'] self.grhopen = self.iparams['grhopen'] # # Compute fluxes on subdomains. # Vsds is a list of length 2**dim, the value of V on each # subdomain. # self.Vsds = [] for Usd, rhosd in zip(zip(*self.Usds), self.rhosds): self.Vsds.append(self.V(Usd, ufl.max_value(rhosd, self.rhomin))) self.vsds = [ -ufl.grad(Vsd) - (self.s2 * ufl.grad(rhosd) / ufl.max_value(rhosd, self.rhomin)) for Vsd, rhosd in zip(self.Vsds, self.rhosds) ] self.fluxsds = [ vsd * rhosd for vsd, rhosd in zip(self.vsds, self.rhosds) ] self.vnsds = [ ufl.max_value(ufl.dot(vsd, self.n), 0) for vsd in self.vsds ] self.facet_fluxsds = [ (vnsd('+') * ufl.max_value(rhosd('+'), 0.0) - vnsd('-') * ufl.max_value(rhosd('-'), 0.0)) for vnsd, rhosd in zip(self.vnsds, self.rhosds) ] # # Now combine the subdomain fluxes to get the fluxes for # the symmetrized functions # self.fluxs = matmul((2.0**-self.dim) * self.eomat, self.fluxsds) self.facet_fluxs = matmul((2.0**-self.dim) * self.eomat, self.facet_fluxsds) self.rho_flux_jump = sum([ -facet_flux * ufl.jump(wrho) * self.dS for facet_flux, wrho in zip(self.facet_fluxs, self.wrhos) ]) self.rho_grad_move = sum([ ufl.dot(flux, ufl.grad(wrho)) * self.dx for flux, wrho in zip(self.fluxs, self.wrhos) ]) self.rho_penalty = sum([ -(self.degree**2 / self.havg) * ufl.dot(ufl.jump(rho, self.n), ufl.jump(self.rhopen * wrho, self.n)) * self.dS for rho, wrho in zip(self.irhos, self.wrhos) ]) self.grho_penalty = sum([ self.degree**2 * (ufl.jump(ufl.grad(rho), self.n) * ufl.jump(ufl.grad(-self.grhopen * wrho), self.n)) * self.dS for rho, wrho in zip(self.irhos, self.wrhos) ]) self.rho_terms = (self.rho_flux_jump + self.rho_grad_move + self.rho_penalty + self.grho_penalty) logVARIABLE('rho_terms made') if not hasattr(self, 'U_terms'): logVARIABLE('making U_terms') self.Umin = self.iparams['Umin'] self.Upen = self.iparams['Upen'] self.gUpen = self.iparams['gUpen'] self.U_decay = 0.0 self.U_secretion = 0.0 self.jump_gUw = 0.0 self.U_diffusion = 0.0 self.U_penalty = 0.0 self.gU_penalty = 0.0 for j, lig in enumerate(self.iligands.ligands()): sl = slice(j * 2**self.dim, (j + 1) * 2**self.dim) self.U_decay += sum([ -lig.gamma * iUi * wUi * self.dx for iUi, wUi in zip(self.iUs[sl], self.wUs[sl]) ]) self.U_secretion += sum([ lig.s * rho * wU * self.dx for rho, wU in zip(self.irhos, self.wUs[sl]) ]) self.jump_gUw += sum([ ufl.jump(lig.D * wU * ufl.grad(U), self.n) * self.dS for wU, U in zip(self.wUs[sl], self.iUs[sl]) ]) self.U_diffusion += sum([ -lig.D * ufl.dot(ufl.grad(U), ufl.grad(wU)) * self.dx for U, wU in zip(self.iUs[sl], self.wUs[sl]) ]) self.U_penalty += sum([ (-self.degree**2 / self.havg) * ufl.dot(ufl.jump(U, self.n), ufl.jump(self.Upen * wU, self.n)) * self.dS for U, wU in zip(self.iUs[sl], self.wUs[sl]) ]) self.gU_penalty += sum([ -self.degree**2 * ufl.jump(ufl.grad(U), self.n) * ufl.jump(ufl.grad(self.gUpen * wU), self.n) * self.dS for U, wU in zip(self.iUs[sl], self.wUs[sl]) ]) self.U_terms = ( # decay and secretion self.U_decay + self.U_secretion + # diffusion self.jump_gUw + self.U_diffusion + # penalties (to enforce continuity) self.U_penalty + self.gU_penalty) logVARIABLE('U_terms made') if not hasattr(self, 'all_terms'): logVARIABLE('making all_terms') self.all_terms = self.rho_terms + self.U_terms if not hasattr(self, 'J_terms'): logVARIABLE('making J_terms') self.J_terms = fe.derivative(self.all_terms, self.sol)