def solve_problem_variational_form(self): u = fa.Function(self.V) du = fa.TrialFunction(self.V) v = fa.TestFunction(self.V) E = self._energy_density(u) * fa.dx dE = fa.derivative(E, u, v) jacE = fa.derivative(dE, u, du) fa.solve(dE == 0, u, self.bcs, J=jacE) return u
def vjp_solve_eval_impl( g: np.array, fenics_solution: fenics.Function, fenics_residual: ufl.Form, fenics_inputs: List[FenicsVariable], bcs: List[fenics.DirichletBC], ) -> Tuple[np.array]: """Computes the gradients of the output with respect to the inputs.""" # Convert tangent covector (adjoint) to a FEniCS variable adj_value = numpy_to_fenics(g, fenics_solution) adj_value = adj_value.vector() F = fenics_residual u = fenics_solution V = u.function_space() dFdu = fenics.derivative(F, u) adFdu = ufl.adjoint( dFdu, reordered_arguments=ufl.algorithms.extract_arguments(dFdu) ) u_adj = fenics.Function(V) adj_F = ufl.action(adFdu, u_adj) adj_F = ufl.replace(adj_F, {u_adj: fenics.TrialFunction(V)}) adj_F_assembled = fenics.assemble(adj_F) if len(bcs) != 0: for bc in bcs: bc.homogenize() hbcs = bcs for bc in hbcs: bc.apply(adj_F_assembled) bc.apply(adj_value) fenics.solve(adj_F_assembled, u_adj.vector(), adj_value) fenics_grads = [] for fenics_input in fenics_inputs: if isinstance(fenics_input, fenics.Function): V = fenics_input.function_space() dFdm = fenics.derivative(F, fenics_input, fenics.TrialFunction(V)) adFdm = fenics.adjoint(dFdm) result = fenics.assemble(-adFdm * u_adj) if isinstance(fenics_input, fenics.Constant): fenics_grad = fenics.Constant(result.sum()) else: # fenics.Function fenics_grad = fenics.Function(V, result) fenics_grads.append(fenics_grad) # Convert FEniCS gradients to jax array representation jax_grads = ( None if fg is None else np.asarray(fenics_to_numpy(fg)) for fg in fenics_grads ) jax_grad_tuple = tuple(jax_grads) return jax_grad_tuple
def __compute_shape_derivative(self): """Computes the shape derivative. Returns ------- None Notes ----- This only works properly if differential operators only act on state and adjoint variables, else the results are incorrect. A corresponding warning whenever this could be the case is issued. """ # Shape derivative of Lagrangian w/o regularization and pull-backs self.shape_derivative = fenics.derivative( self.lagrangian.lagrangian_form, fenics.SpatialCoordinate(self.mesh), self.test_vector_field) # Add pull-backs if self.use_pull_back: self.state_adjoint_ids = [coeff.id() for coeff in self.states] + [ coeff.id() for coeff in self.adjoints ] self.material_derivative_coeffs = [] for coeff in self.lagrangian.lagrangian_form.coefficients(): if coeff.id() in self.state_adjoint_ids: pass else: if not (coeff.ufl_element().family() == 'Real'): self.material_derivative_coeffs.append(coeff) if len(self.material_derivative_coeffs) > 0: warning( 'Shape derivative might be wrong, if differential operators act on variables other than states and adjoints. \n' 'You can check for correctness of the shape derivative with cashocs.verification.shape_gradient_test\n' ) for coeff in self.material_derivative_coeffs: # temp_space = fenics.FunctionSpace(self.mesh, coeff.ufl_element()) # placeholder = fenics.Function(temp_space) # temp_form = fenics.derivative(self.lagrangian.lagrangian_form, coeff, placeholder) # material_derivative = replace(temp_form, {placeholder : fenics.dot(fenics.grad(coeff), self.test_vector_field)}) material_derivative = fenics.derivative( self.lagrangian.lagrangian_form, coeff, fenics.dot(fenics.grad(coeff), self.test_vector_field)) material_derivative = expand_derivatives(material_derivative) self.shape_derivative += material_derivative # Add regularization self.shape_derivative += self.regularization.compute_shape_derivative()
def __compute_state_equations(self): """Calculates the weak form of the state equation for the use with fenics. Returns ------- None """ if self.state_is_linear: self.state_eq_forms = [ replace( self.state_forms[i], { self.states[i]: self.trial_functions_state[i], self.adjoints[i]: self.test_functions_state[i] }) for i in range(self.state_dim) ] else: self.state_eq_forms = [ fenics.derivative(self.state_forms[i], self.adjoints[i], self.test_functions_state[i]) for i in range(self.state_dim) ] if self.state_is_picard: self.state_picard_forms = [ fenics.derivative(self.state_forms[i], self.adjoints[i], self.test_functions_state[i]) for i in range(self.state_dim) ] if self.state_is_linear: self.state_eq_forms_lhs = [] self.state_eq_forms_rhs = [] for i in range(self.state_dim): try: a, L = fenics.system(self.state_eq_forms[i]) except UFLException: raise CashocsException( 'The state system could not be transferred to a linear system.\n' 'Perhaps you specified that the system is linear, allthough it is not.\n' 'In your config, in the StateEquation section, try using is_linear = False.' ) self.state_eq_forms_lhs.append(a) if L.empty(): zero_form = fenics.inner( fenics.Constant( np.zeros(self.test_functions_state[i].ufl_shape)), self.test_functions_state[i]) * self.dx self.state_eq_forms_rhs.append(zero_form) else: self.state_eq_forms_rhs.append(L)
def _solve(self, z, x=None): # problem variables du = TrialFunction(self.V) # incremental displacement v = TestFunction(self.V) # test function u = Function(self.V) # displacement from previous iteration # kinematics ii = Identity(3) # identity tensor dimension 3 f = ii + grad(u) # deformation gradient c = f.T * f # right Cauchy-Green tensor # invariants of deformation tensors ic = tr(c) j = det(f) # elasticity parameters if type(z) in [list, np.ndarray]: param = self.param_remapper(z[0]) if self.param_remapper is not None else z[0] else: param = self.param_remapper(z) if self.param_remapper is not None else z e_var = variable(Constant(param)) # Young's modulus nu = Constant(.3) # Shear modulus (Lamè's second parameter) mu, lmbda = e_var / (2 * (1 + nu)), e_var * nu / ((1 + nu) * (1 - 2 * nu)) # strain energy density, total potential energy psi = (mu / 2) * (ic - 3) - mu * ln(j) + (lmbda / 2) * (ln(j)) ** 2 pi = psi * dx - self.time * dot(self.f, u) * self.ds(3) ff = derivative(pi, u, v) # compute first variation of pi jj = derivative(ff, u, du) # compute jacobian of f # solving if x is not None: numeric_evals = np.zeros(shape=(x.shape[1], len(self.times))) evals = np.zeros(shape=(x.shape[1], len(self.eval_times))) else: numeric_evals = None evals = None for it, t in enumerate(self.times): self.time.t = t self.solver(ff == 0, u, self.bcs, J=jj, bcs=self.bcs, solver_parameters=self.solver_parameters) if x is not None: numeric_evals[:, it] = np.log(np.array([-u(x_)[2] for x_ in x.T]).T) # time-interpolation if x is not None: for i in range(evals.shape[0]): evals[i, :] = np.interp(self.eval_times, self.times, numeric_evals[i, :]) return (evals, u) if x is not None else u
def effective_field(m, volume=None): w_Zeeman = - mu0 * Ms * fe.dot(m, H) w_exchange = A * fe.inner(fe.grad(m), fe.grad(m)) w_DMI = D * fe.inner(m, fe.curl(m)) w_ani = - K * fe.inner(m, ea)**2 w = w_Zeeman + w_exchange + w_DMI + w_ani return -1/(mu0*Ms) * fe.derivative(w*fe.dx, m)
def jvp_assemble_eval( fenics_function: Callable, fenics_templates: Iterable[FenicsVariable], primals: Tuple[np.array], tangents: Tuple[np.array], ) -> Tuple[np.array]: """Computes the jacobian-vector product for fenics.assemble """ numpy_output_primal, output_primal_form, fenics_primals = assemble_eval( fenics_function, fenics_templates, *primals) # Now tangent evaluation! fenics_tangents = convert_all_to_fenics(fenics_primals, *tangents) output_tangent_form = 0.0 for fp, ft in zip(fenics_primals, fenics_tangents): output_tangent_form += fenics.derivative(output_primal_form, fp, ft) if not isinstance(output_tangent_form, float): output_tangent_form = ufl.algorithms.expand_derivatives( output_tangent_form) output_tangent = fenics.assemble(output_tangent_form) jax_output_tangent = output_tangent return numpy_output_primal, jax_output_tangent
def vjp_assemble_impl( g: np.array, fenics_output_form: ufl.Form, fenics_inputs: List[FenicsVariable], ) -> Tuple[np.array]: """Computes the gradients of the output with respect to the inputs.""" # Compute derivative form for the output with respect to each input fenics_grads_forms = [] for fenics_input in fenics_inputs: # Need to construct direction (test function) first if isinstance(fenics_input, fenics.Function): V = fenics_input.function_space() elif isinstance(fenics_input, fenics.Constant): mesh = fenics_output_form.ufl_domain().ufl_cargo() V = fenics.FunctionSpace(mesh, "Real", 0) else: raise NotImplementedError dv = fenics.TestFunction(V) fenics_grad_form = fenics.derivative(fenics_output_form, fenics_input, dv) fenics_grads_forms.append(fenics_grad_form) # Assemble the derivative forms fenics_grads = [fenics.assemble(form) for form in fenics_grads_forms] # Convert FEniCS gradients to jax array representation jax_grads = (None if fg is None else np.asarray(g * fenics_to_numpy(fg)) for fg in fenics_grads) jax_grad_tuple = tuple(jax_grads) return jax_grad_tuple
def Estimate(Ic, J, u, v, du, BoundaryConditions, InitialState, u_1): # Use Neo-Hookean constitutive model Nu_H = 0.49 # (-) Mu_NH = 1.15 # (kPa) Lambda = 2*Mu_NH*Nu_H/(1-2*Nu_H) # (kPa) Psi = CompressibleNeoHookean(Mu_NH, Lambda, Ic, J) Pi = Psi * fe.dx # First directional derivative of the potential energy Fpi = fe.derivative(Pi,u,v) # Jacobian of Fpi Jac = fe.derivative(Fpi,u,du) # Define option for the compiler (optional) ffc_options = {"optimize": True, \ "eliminate_zeros": True, \ "precompute_basis_const": True, \ "precompute_ip_const": True, \ "quadrature_degree": 2, \ "representation" : "uflacs" } # Define the problem Problem = fe.NonlinearVariationalProblem(Fpi, u, BoundaryConditions, Jac, form_compiler_parameters=ffc_options) # Define the solver Solver = fe.NonlinearVariationalSolver(Problem) # Set solver parameters (optional) Prm = Solver.parameters Prm['nonlinear_solver'] = 'newton' Prm['newton_solver']['linear_solver'] = 'cg' # Conjugate gradient Prm['newton_solver']['preconditioner'] = 'icc' # Incomplete Choleski Prm['newton_solver']['krylov_solver']['nonzero_initial_guess'] = True # Set initial displacement u_1.s = InitialState # Compute solution and save displacement Solver.solve() return u
def jvp_solve_eval( fenics_function: Callable, fenics_templates: Iterable[FenicsVariable], primals: Tuple[np.array], tangents: Tuple[np.array], ) -> Tuple[np.array]: """Computes the tangent linear model """ ( numpy_output_primal, fenics_solution_primal, residual_form, fenics_primals, bcs, ) = solve_eval(fenics_function, fenics_templates, *primals) # Now tangent evaluation! F = residual_form u = fenics_solution_primal V = u.function_space() if len(bcs) != 0: for bc in bcs: bc.homogenize() hbcs = bcs fenics_tangents = convert_all_to_fenics(fenics_primals, *tangents) fenics_output_tangents = [] for fp, ft in zip(fenics_primals, fenics_tangents): dFdu = fenics.derivative(F, u) dFdm = fenics.derivative(F, fp, ft) u_tlm = fenics.Function(V) tlm_F = ufl.action(dFdu, u_tlm) + dFdm tlm_F = ufl.replace(tlm_F, {u_tlm: fenics.TrialFunction(V)}) fenics.solve(ufl.lhs(tlm_F) == ufl.rhs(tlm_F), u_tlm, bcs=hbcs) fenics_output_tangents.append(u_tlm) jax_output_tangents = (fenics_to_numpy(ft) for ft in fenics_output_tangents) jax_output_tangent = sum(jax_output_tangents) return numpy_output_primal, jax_output_tangent
def setup_solver(self): """ Sometimes it is necessary to set up the solver again after breaking important references, e.g. after re-meshing. """ self._governing_form = self.governing_form() self._boundary_conditions = self.boundary_conditions() self._problem = fenics.NonlinearVariationalProblem( F=self._governing_form, u=self.solution, bcs=self._boundary_conditions, J=fenics.derivative(form=self._governing_form, u=self.solution)) save_parameters = False if hasattr(self, "solver"): save_parameters = True if save_parameters: solver_parameters = self.solver.parameters.copy() self.solver = fenics.NonlinearVariationalSolver(problem=self._problem) if save_parameters: self.solver.parameters = solver_parameters.copy() self._adaptive_goal = self.adaptive_goal() if self._adaptive_goal is not None: save_parameters = False if self.adaptive_solver is not None: save_parameters = True if save_parameters: adaptive_solver_parameters = self.adaptive_solver.parameters.copy( ) self.adaptive_solver = fenics.AdaptiveNonlinearVariationalSolver( problem=self._problem, goal=self._adaptive_goal) if save_parameters: self.adaptive_solver.parameters = adaptive_solver_parameters.copy( ) self.solver_needs_setup = False
def setup_solver(self): F = self.d_LHS - self.d_RHS J = fe.derivative(F, self._sp.w) # Initialize solver problem = fe.NonlinearVariationalProblem(F, self._sp.w, bcs=self.bcs, J=J) self.solver = fe.NonlinearVariationalSolver(problem) self.solver.parameters['newton_solver']['relative_tolerance'] = 1e-6
def solve_problem_weak_form(self): u = fa.Function(self.V) du = fa.TrialFunction(self.V) v = fa.TestFunction(self.V) F = fa.inner(fa.grad(u), fa.grad(v)) * fa.dx - self.source * v * fa.dx J = fa.derivative(F, u, du) # The problem in this case is indeed linear, but using a nonlinear # solver doesn't hurt problem = fa.NonlinearVariationalProblem(F, u, self.bcs, J) solver = fa.NonlinearVariationalSolver(problem) solver.solve() return u
def __compute_gradient_equations(self): """Calculates the variational form of the gradient equation, for the Riesz projection. Returns ------- None """ self.gradient_forms_rhs = [ fenics.derivative(self.lagrangian.lagrangian_form, self.controls[i], self.test_functions_control[i]) for i in range(self.control_dim) ]
def solve_fenics(kappa0, kappa1): f = fenics.Expression( "10*exp(-(pow(x[0] - 0.5, 2) + pow(x[1] - 0.5, 2)) / 0.02)", degree=2) u = fenics.Function(V) bcs = [fenics.DirichletBC(V, fenics.Constant(0.0), "on_boundary")] inner, grad, dx = ufl.inner, ufl.grad, ufl.dx JJ = 0.5 * inner(kappa0 * grad(u), grad(u)) * dx - kappa1 * f * u * dx v = fenics.TestFunction(V) F = fenics.derivative(JJ, u, v) fenics.solve(F == 0, u, bcs=bcs) return u, F, bcs
def solve_steady_state_heiser_weissinger(kappa): w = fe.Function(V_up) (u, p) = fe.split(w) p = fe.variable(p) (eta, q) = fe.TestFunctions(V_up) dw = fe.TrialFunction(V_up) kappa = fe.Constant(kappa) bcs_u, bcs_p, bcs_v = load_2d_muscle_bc(V_up.sub(0), V_up.sub(1), None, boundaries) F = deformation_grad(u) I_1, I_2, J = invariants(F) F_iso = isochronic_deformation_grad(F, J) I_1_iso, I_2_iso = invariants(F)[0:2] W = material_mooney_rivlin(I_1_iso, I_2_iso, c_10, c_01) #+ incompr_relaxation(p, kappa) g = incompr_constr(J) # Lagrange function (without constraint) L = -W # Modified Lagrange function (with constraints) L_mod = L - p * g P = first_piola_stress(L, F) G = incompr_stress(g, F) # = J*fe.inv(F.T) Lp = const_eq(L_mod, p) a_static = weak_div_term(P + p * G, eta) + inner(B, eta) * dx + inner(Lp, q) * dx J_static = fe.derivative(a_static, w, dw) ffc_options = {"optimize": True} problem = fe.NonlinearVariationalProblem( a_static, w, bcs_u + bcs_p, J=J_static, form_compiler_parameters=ffc_options) solver = fe.NonlinearVariationalSolver(problem) solver.solve() return w
def setup_solver(self): F = self.d_LHS - self.d_RHS J = fe.derivative(F, self._domain.w) # Initialize solver problem = fe.NonlinearVariationalProblem(F, self._domain.w, bcs=self._dbcs, J=J) self.solver = fe.NonlinearVariationalSolver(problem) for key, val in self._solver_parameters.items(): if isinstance(val, dict): for sub_key, sub_val in val.items(): self.solver.parameters[key][sub_key] = sub_val else: self.solver.parameters[key] = val
def _create_variational_problem(m0, u0, W, dt): """We set up the variational problem.""" p, q = fc.TestFunctions(W) w = fc.Function(W) # Function to solve for m, u = fc.split(w) # Relabel i.e. initialise m_prev, u_prev as m0, u0. m_prev, u_prev = m0, u0 m_mid = 0.5 * (m + m_prev) u_mid = 0.5 * (u + u_prev) F = ( (q * u + q.dx(0) * u.dx(0) - q * m) * fc.dx + # q part (p * (m - m_prev) + dt * (p * m_mid * u_mid.dx(0) - p.dx(0) * m_mid * u_mid)) * fc.dx # p part ) J = fc.derivative(F, w) problem = fc.NonlinearVariationalProblem(F, w, J=J) solver = fc.NonlinearVariationalSolver(problem) solver.parameters["newton_solver"]["maximum_iterations"] = 100 # Default is 50 solver.parameters["newton_solver"]["error_on_nonconvergence"] = False return solver, w, m_prev, u_prev
def FromPhysics(cls, physics, dtype=torch.double, device=torch.device("cpu")): V = physics.V Vc = physics.Vc if V.mesh().num_cells() > 290: raise Exception('ROM exceeds intended maximum size') M = np.zeros((V.dim(), V.dim(), Vc.dim())) m = df.Function(Vc) for i in range(Vc.dim()): m.vector()[:] = 0 m.vector()[i] = 1 a2 = df.derivative(physics.a, physics.alpha, m) M[:, :, i] = df.assemble(a2).array().copy() M = torch.tensor(M, dtype=dtype, device=device) return cls(physics, M, dtype=dtype, device=device)
fe.plot(h_n) plt.title('Initial boundary condition of enthalpy') plt.xlabel('x') plt.ylabel('Enthalpy') plt.show() # Here is where we solve the newton's linearized problem. The variable JF stores the jacobian of the function F with respect to the enthalpy h. h_t = ( h - h_n ) / Delta_t #{d\T}/{d\h} Simple gateaux derivative of the c*h^3 function diffth = (3 * (h)**2 ) #{d\h}/{d\t} but as piecewise as we know the intermediate values F = (diffth * (fe.dot(fe.grad(v), fe.grad(h))) + (rho / K) * (h_t * v)) * fe.dx #The nonlinear function to be solved is assembled JF = fe.derivative( F, h, fe.TrialFunction(V) ) #Taking the derivative of the non linear function to solve by newton's method #Invoking the Newton's method problem = fe.NonlinearVariationalProblem(F, h, bc, JF) solver = fe.NonlinearVariationalSolver(problem) solver.solve() fe.plot(h) plt.xlabel('x') plt.ylabel('Enthalpy') plt.title('Solution of Enthalpy distribution initially against x ') plt.show() fe.plot(phis(h)) plt.xlabel('x') plt.ylabel('$\phi$') plt.title('Solid volume fraction before melting') plt.show()
P = fe.diff(W_iso, F) S = 2 * fe.diff(W_iso, C) F_static = inner(S, DE(v)) * dx - rho * inner(b, v) * dx - inner( t_bar, v) * ds + (p / kappa + J - 1) * q * dx #F_static = inner(P, grad(v))*dx - rho*inner(b, v)*dx - inner(t_bar, v)*ds + (p/kappa + J - 1)*q*dx file_u = fe.File("../results/displacement.pvd") file_p = fe.File("../results/pressure.pvd") bcul = fe.DirichletBC(V.sub(0), u_left, left) bcur = fe.DirichletBC(V.sub(0), u_right, right) #bcpl = fe.DirichletBC(V.sub(1), p_left, left) bcs = [bcul, bcur] # bcpl] J_static = fe.derivative(F_static, w, dw) #fe.solve(F_static==0, w, bcs) ffc_options = {"optimize": True} problem = fe.NonlinearVariationalProblem(F_static, w, bcs, J=J_static, form_compiler_parameters=ffc_options) solver = fe.NonlinearVariationalSolver(problem) # set parameters prm = solver.parameters if True: iterative_solver = True
def staggered_solve(self): self.U = fe.VectorFunctionSpace(self.mesh, 'CG', 1) self.W = fe.FunctionSpace(self.mesh, 'CG', 1) self.WW = fe.FunctionSpace(self.mesh, 'DG', 0) self.EE = fe.TensorFunctionSpace(self.mesh, 'DG', 0) self.MM = fe.VectorFunctionSpace(self.mesh, 'CG', 1) self.eta = fe.TestFunction(self.U) self.zeta = fe.TestFunction(self.W) q = fe.TestFunction(self.WW) del_x = fe.TrialFunction(self.U) del_d = fe.TrialFunction(self.W) p = fe.TrialFunction(self.WW) self.x_new = fe.Function(self.U, name="u") self.d_new = fe.Function(self.W, name="d") self.d_pre = fe.Function(self.W) self.x_pre = fe.Function(self.U) x_old = fe.Function(self.U) d_old = fe.Function(self.W) self.H_old = fe.Function(self.WW) self.map_plot = fe.Function(self.MM, name="m") e = fe.Function(self.EE, name="e") self.create_custom_xdmf_files() self.file_results = fe.XDMFFile('data/xdmf/{}/u.xdmf'.format( self.case_name)) self.file_results.parameters["functions_share_mesh"] = True vtkfile_e = fe.File('data/pvd/simulation/{}/e.pvd'.format( self.case_name)) vtkfile_u = fe.File('data/pvd/simulation/{}/u.pvd'.format( self.case_name)) vtkfile_d = fe.File('data/pvd/simulation/{}/d.pvd'.format( self.case_name)) for i, (disp, rp) in enumerate( zip(self.displacements, self.relaxation_parameters)): print('\n') print( '=================================================================================' ) print('>> Step {}, disp boundary condition = {} [mm]'.format( i, disp)) print( '=================================================================================' ) self.i = i self.update_weak_form_due_to_Model_C_bug() if self.update_weak_form: self.set_bcs_staggered() print("Update weak form...") self.build_weak_form_staggered() print("Taking derivatives of weak form...") J_u = fe.derivative(self.G_u, self.x_new, del_x) J_d = fe.derivative(self.G_d, self.d_new, del_d) print("Define nonlinear problems...") p_u = fe.NonlinearVariationalProblem(self.G_u, self.x_new, self.BC_u, J_u) p_d = fe.NonlinearVariationalProblem(self.G_d, self.d_new, self.BC_d, J_d) print("Define solvers...") solver_u = fe.NonlinearVariationalSolver(p_u) solver_d = fe.NonlinearVariationalSolver(p_d) self.update_weak_form = False print("Update history weak form") a = p * q * fe.dx L = history(self.H_old, self.update_history(), self.psi_cr) * q * fe.dx if self.map_flag: self.interpolate_map() # delta_x = self.x - self.x_hat # self.map_plot.assign(fe.project(delta_x, self.MM)) self.presLoad.t = disp newton_prm = solver_u.parameters['newton_solver'] newton_prm['maximum_iterations'] = 100 # newton_prm['absolute_tolerance'] = 1e-8 newton_prm['relaxation_parameter'] = rp newton_prm = solver_d.parameters['newton_solver'] newton_prm['maximum_iterations'] = 100 # newton_prm['absolute_tolerance'] = 1e-8 newton_prm['relaxation_parameter'] = rp vtkfile_e_staggered = fe.File( 'data/pvd/simulation/{}/step{}/e.pvd'.format( self.case_name, i)) vtkfile_u_staggered = fe.File( 'data/pvd/simulation/{}/step{}/u.pvd'.format( self.case_name, i)) vtkfile_d_staggered = fe.File( 'data/pvd/simulation/{}/step{}/d.pvd'.format( self.case_name, i)) iteration = 0 err = 1. while err > self.staggered_tol: iteration += 1 solver_d.solve() solver_u.solve() if self.solution_scheme == 'explicit': break # # Remarks(Tianju): self.x_new.vector() does not behave as expected: producing nan values # The following lines of codes cause issues # We use an error measure similar in https://doi.org/10.1007/s10704-019-00372-y # np_x_new = np.asarray(self.x_new.vector()) # np_d_new = np.asarray(self.d_new.vector()) # np_x_old = np.asarray(x_old.vector()) # np_d_old = np.asarray(d_old.vector()) # err_x = np.linalg.norm(np_x_new - np_x_old) / np.sqrt(len(np_x_new)) # err_d = np.linalg.norm(np_d_new - np_d_old) / np.sqrt(len(np_d_new)) # err = max(err_x, err_d) # # Remarks(Tianju): dolfin (2019.1.0) errornorm function has severe bugs not behave as expected # The bug seems to be fixed in later versions # The following sometimes produces nonzero results in dolfin (2019.1.0) # print(fe.errornorm(self.d_new, self.d_new, norm_type='l2')) err_x = fe.errornorm(self.x_new, x_old, norm_type='l2') err_d = fe.errornorm(self.d_new, d_old, norm_type='l2') err = max(err_x, err_d) x_old.assign(self.x_new) d_old.assign(self.d_new) e.assign( fe.project(strain(self.mfem_grad(self.x_new)), self.EE)) print( '---------------------------------------------------------------------------------' ) print( '>> iteration. {}, err_u = {:.5}, err_d = {:.5}, error = {:.5}' .format(iteration, err_x, err_d, err)) print( '---------------------------------------------------------------------------------' ) # vtkfile_e_staggered << e # vtkfile_u_staggered << self.x_new # vtkfile_d_staggered << self.d_new if err < self.staggered_tol or iteration >= self.staggered_maxiter: print( '=================================================================================' ) print('\n') break print("L2 projection to update the history function...") fe.solve(a == L, self.H_old, []) # self.d_pre.assign(self.d_new) # self.H_old.assign(fe.project(history(self.H_old, self.update_history(), self.psi_cr), self.WW)) if self.map_flag and not self.finish_flag: self.update_map() if self.compute_and_save_intermediate_results: print("Save files...") self.file_results.write(e, i) self.file_results.write(self.x_new, i) self.file_results.write(self.d_new, i) self.file_results.write(self.map_plot, i) vtkfile_e << e vtkfile_u << self.x_new vtkfile_d << self.d_new # Assume boundary is not affected by the map. # There's no need to use the mfem_grad wrapper so that fe.grad is used for speed-up print("Define forces...") sigma = cauchy_stress_plus(strain(fe.grad(self.x_new)), self.psi) sigma_minus = cauchy_stress_minus(strain(fe.grad(self.x_new)), self.psi_minus) sigma_plus = cauchy_stress_plus(strain(fe.grad(self.x_new)), self.psi_plus) sigma_degraded = g_d(self.d_new) * sigma_plus + sigma_minus print("Compute forces...") if self.case_name == 'pure_shear': f_full = float(fe.assemble(sigma[0, 1] * self.ds(1))) f_degraded = float( fe.assemble(sigma_degraded[0, 1] * self.ds(1))) else: f_full = float(fe.assemble(sigma[1, 1] * self.ds(1))) f_degraded = float( fe.assemble(sigma_degraded[1, 1] * self.ds(1))) print("Force full is {}".format(f_full)) print("Force degraded is {}".format(f_degraded)) self.delta_u_recorded.append(disp) self.force_full.append(f_full) self.force_degraded.append(f_degraded) # if force_upper < 0.5 and i > 10: # break if self.display_intermediate_results and i % 10 == 0: self.show_force_displacement() self.save_data_in_loop() if self.display_intermediate_results: plt.ioff() plt.show()
mu = Ey / (2. * (1 + nu)) lambda_ = Ey * nu / ((1 + nu) * (1 - 2 * nu)) return lambda_ / 2. * (fe.tr(E))**2. + mu * fe.tr(E * E) # Collect the strain density functions integrals_E = [] for i in materials: psi = strainDensityFunction(E, materials[i][0], materials[i][1]) integrals_E.append(psi * dxp(i)) # Total potential energy Pi = sum(integrals_E) - sum(integrals_V) - sum(integrals_S) # Compute 1st variation of Pi (directional derivative about u in dir. of v) F = fe.derivative(Pi, u, v) # Compute Jacobian of F J = fe.derivative(F, u, du) ############################## SOLVER PARAMS ################################## # Define the solver params problem = fe.NonlinearVariationalProblem(F, u, bcs, J) solver = fe.NonlinearVariationalSolver(problem) prm = solver.parameters prm['nonlinear_solver'] = 'newton' prm['newton_solver']['linear_solver'] = 'petsc' prm['newton_solver']['error_on_nonconvergence'] = True prm['newton_solver']['absolute_tolerance'] = 1E-9 prm['newton_solver']['relative_tolerance'] = 1E-8
res = -tau * fe.inner(fe.dot(u, fe.grad(v)), fe.grad(u) * u) * fe.dx res += -tau * fe.inner(fe.dot(u, fe.grad(v)), fe.grad(p)) * fe.dx #res += - tau * fe.inner(fe.dot(u, fe.grad(v)), -1 * fv1 * nu_trial * fe.div(fe.grad(u))) * fe.dx #TODO - update residual term for new turbulence model res += -tau * fe.inner(fe.dot(u, fe.grad(v)), -1 * nu * fe.div(fe.grad(u))) * fe.dx res += -tau * fe.inner(fe.dot(u, fe.grad(q)), fe.div(u)) * fe.dx res += -tau * fe.inner(fe.dot(u, fe.grad(v)), -1 * b) * fe.dx if STAB: weakForm += res stab = -tau * fe.inner(fe.grad(q), fe.grad(p)) * fe.dx weakForm = weakForm + stab dW = fe.TrialFunction(W) dFdW = fe.derivative(weakForm, W0, dW) if MODEL: bcSet = [ bc_1, bc_2, bc_3, bc_inflow, bc_p, bc_v_x0, bc_v_x1, bc_v_y1, bc_v_in, bc_v_top ] else: bcSet = [bc_1, bc_2, bc_3, bc_inflow, bc_p] problem = fe.NonlinearVariationalProblem(weakForm, W0, bcSet, J=dFdW) solver = fe.NonlinearVariationalSolver(problem) prm = solver.parameters t = 0.0
def SolveProblem(LoadCase, ConstitutiveModel, BCsType, FinalRelativeStretch, RelativeStepSize, Dimensions, NumberElements, Mesh, V, u, du, v, Ic, J, F, Psi, Plot = False, Paraview = False): if LoadCase == 'Compression': # Load case [u_0, u_1, InitialState, Direction, Normal, NumberSteps, DeltaStretch] = LoadCaseDefinition(LoadCase, FinalRelativeStretch, RelativeStepSize, Dimensions, BCsType) elif LoadCase == 'Tension': # Load case [u_0, u_1, InitialState, Direction, Normal, NumberSteps, DeltaStretch] = LoadCaseDefinition(LoadCase, FinalRelativeStretch, RelativeStepSize, Dimensions, BCsType) elif LoadCase == 'SimpleShear': # Load case [u_0, u_1, InitialState, Direction, Normal, NumberSteps, DeltaStretch] = LoadCaseDefinition(LoadCase, FinalRelativeStretch*2, RelativeStepSize*2, Dimensions, BCsType) # Boundary conditions [BoundaryConditions, ds] = BCsDefinition(Dimensions, Mesh, V, u_0, u_1, LoadCase, BCsType) # Estimation of the displacement field using Neo-Hookean model (necessary for Ogden) u = Estimate(Ic, J, u, v, du, BoundaryConditions, InitialState, u_1) # Reformulate the problem with the correct constitutive model Pi = Psi * fe.dx # First directional derivative of the potential energy Fpi = fe.derivative(Pi,u,v) # Jacobian of Fpi Jac = fe.derivative(Fpi,u,du) # Define option for the compiler (optional) ffc_options = {"optimize": True, \ "eliminate_zeros": True, \ "precompute_basis_const": True, \ "precompute_ip_const": True } # Define the problem Problem = fe.NonlinearVariationalProblem(Fpi, u, BoundaryConditions, Jac, form_compiler_parameters=ffc_options) # Define the solver Solver = fe.NonlinearVariationalSolver(Problem) # Set solver parameters (optional) Prm = Solver.parameters Prm['nonlinear_solver'] = 'newton' Prm['newton_solver']['linear_solver'] = 'cg' # Conjugate gradient Prm['newton_solver']['preconditioner'] = 'icc' # Incomplete Choleski Prm['newton_solver']['krylov_solver']['nonzero_initial_guess'] = True # Data frame to store values cols = ['Stretches','P'] df = pd.DataFrame(columns=cols, index=range(int(NumberSteps)+1), dtype='float64') if Paraview == True: # Results File Output_Path = os.path.join('OptimizationResults', BCsType, ConstitutiveModel) ResultsFile = xdmffile = fe.XDMFFile(os.path.join(Output_Path, str(NumberElements) + 'Elements_' + LoadCase + '.xdmf')) ResultsFile.parameters["flush_output"] = True ResultsFile.parameters["functions_share_mesh"] = True if Plot == True: plt.rc('figure', figsize=[12,7]) fig = plt.figure() ax = fig.add_subplot(1, 1, 1) # Set the stretch state to initial state StretchState = InitialState # Loop to solve for each step for Step in range(int(NumberSteps+1)): # Update current state u_1.s = StretchState # Compute solution and save displacement Solver.solve() # First Piola Kirchoff (nominal) stress P = fe.diff(Psi, F) # Nominal stress vectors normal to upper surface p = fe.dot(P,Normal) # Reaction force on the upper surface f = fe.assemble(fe.inner(p,Direction)*ds(2)) # Mean nominal stress on the upper surface Pm = f/fe.assemble(1*ds(2)) # Save values to table df.loc[Step].Stretches = StretchState df.loc[Step].P = Pm # Plot if Plot == True: ax.cla() ax.plot(df.Stretches, df.P, color = 'r', linestyle = '--', label = 'P', marker = 'o', markersize = 8, fillstyle='none') ax.set_xlabel('Stretch ratio (-)') ax.set_ylabel('Stresses (kPa)') ax.xaxis.set_major_locator(plt.MultipleLocator(0.02)) ax.legend(loc='upper left', frameon=True, framealpha=1) display(fig) clear_output(wait=True) if Paraview == True: # Project the displacement onto the vector function space u_project = fe.project(u, V, solver_type='cg') u_project.rename('Displacement (mm)', '') ResultsFile.write(u_project,Step) # Compute nominal stress vector p_project = fe.project(p, V) p_project.rename("Nominal stress vector (kPa)","") ResultsFile.write(p_project,Step) # Update the stretch state StretchState += DeltaStretch return df
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 compute_static_deformation(self): assert self.mesh is not None # now we define subdomains on the mesh bottom = fe.CompiledSubDomain('near(x[2], 0) && on_boundary') top = fe.CompiledSubDomain('near(x[2], 1) && on_boundary') # middle = fe.CompiledSubDomain('x[2] > 0.3 && x[2] < 0.7') # Initialize mesh function for interior domains self.domains = fe.MeshFunction('size_t', self.mesh, 3) self.domains.set_all(0) # middle.mark(self.domains, 1) # Initialize mesh function for boundary domains self.boundaries = fe.MeshFunction('size_t', self.mesh, 2) self.boundaries.set_all(0) bottom.mark(self.boundaries, 1) top.mark(self.boundaries, 2) # Define new measures associated with the interior domains and # exterior boundaries self.dx = fe.Measure('dx', domain=self.mesh, subdomain_data=self.domains) self.ds = fe.Measure('ds', domain=self.mesh, subdomain_data=self.boundaries) # define function spaces V = fe.VectorFunctionSpace(self.mesh, "Lagrange", 1) # now we define subdomains on the mesh bottom = fe.CompiledSubDomain('near(x[2], 0) && on_boundary') top = fe.CompiledSubDomain('near(x[2], 1) && on_boundary') # middle = fe.CompiledSubDomain('x[2] > 0.3 && x[2] < 0.7') d = self.mesh.geometry().dim() # Initialize mesh function for interior domains self.domains = fe.MeshFunction('size_t', self.mesh, d) self.domains.set_all(0) # middle.mark(self.domains, 1) # Initialize mesh function for boundary domains self.boundaries = fe.MeshFunction('size_t', self.mesh, d - 1) self.boundaries.set_all(0) bottom.mark(self.boundaries, 1) top.mark(self.boundaries, 2) # Define new measures associated with the interior domains and # exterior boundaries self.dx = fe.Measure('dx', domain=self.mesh, subdomain_data=self.domains) self.ds = fe.Measure('ds', domain=self.mesh, subdomain_data=self.boundaries) c_zero = fe.Constant((0, 0, 0)) # define boundary conditions bc_bottom = fe.DirichletBC(V, c_zero, bottom) bc_top = fe.DirichletBC(V, c_zero, top) bcs = [bc_bottom] # , bc_top] # define functions du = TrialFunction(V) v = TestFunction(V) u = Function(V) B = fe.Constant((0., 2.0, 0.)) T = fe.Constant((0.0, 0.0, 0.0)) d = u.geometric_dimension() I = fe.Identity(d) F = I + grad(u) C = F.T * F I_1 = tr(C) J = det(F) E, mu = 10., 0.3 mu, lmbda = fe.Constant(E / (2 * (1 + mu))), fe.Constant( E * mu / ((1 + mu) * (1 - 2 * mu))) # stored energy (comp. neo-hookean model) psi = (mu / 2.) * (I_1 - 3) - mu * fe.ln(J) + (lmbda / 2.) * (fe.ln(J))**2 dx = self.dx ds = self.ds Pi = psi * fe.dx - dot(B, u) * fe.dx - dot(T, u) * fe.ds F = fe.derivative(Pi, u, v) J = fe.derivative(F, u, du) fe.solve(F == 0, u, bcs, J=J) # save results self.u = u # write to disk output = fe.File("/tmp/static.pvd") output << u
def setup_derivative(self): """ Set the derivative of the governing form, needed for the nonlinear solver. """ self.derivative_of_governing_form = fenics.derivative(self.governing_form, self.state.solution, fenics.TrialFunction(self.function_space))
def __compute_newton_forms(self): """Calculates the needed forms for the truncated Newton method. Returns ------- None """ # Use replace -> derivative to speed up the computations self.sensitivity_eqs_temp = [ replace(self.state_forms[i], {self.adjoints[i]: self.test_functions_state[i]}) for i in range(self.state_dim) ] self.sensitivity_eqs_lhs = [ fenics.derivative(self.sensitivity_eqs_temp[i], self.states[i], self.trial_functions_state[i]) for i in range(self.state_dim) ] if self.state_is_picard: self.sensitivity_eqs_picard = [ fenics.derivative(self.sensitivity_eqs_temp[i], self.states[i], self.states_prime[i]) for i in range(self.state_dim) ] # Need to distinguish cases due to empty sum in case state_dim = 1 if self.state_dim > 1: self.sensitivity_eqs_rhs = [ -summation([ fenics.derivative(self.sensitivity_eqs_temp[i], self.states[j], self.states_prime[j]) for j in range(self.state_dim) if j != i ]) - summation([ fenics.derivative(self.sensitivity_eqs_temp[i], self.controls[j], self.test_directions[j]) for j in range(self.control_dim) ]) for i in range(self.state_dim) ] else: self.sensitivity_eqs_rhs = [ -summation([ fenics.derivative(self.sensitivity_eqs_temp[i], self.controls[j], self.test_directions[j]) for j in range(self.control_dim) ]) for i in range(self.state_dim) ] # Add the right-hand-side for the picard iteration if self.state_is_picard: for i in range(self.state_dim): self.sensitivity_eqs_picard[i] -= self.sensitivity_eqs_rhs[i] # Compute forms for the truncated Newton method self.L_y = [ fenics.derivative(self.lagrangian.lagrangian_form, self.states[i], self.test_functions_state[i]) for i in range(self.state_dim) ] self.L_u = [ fenics.derivative(self.lagrangian.lagrangian_form, self.controls[i], self.test_functions_control[i]) for i in range(self.control_dim) ] self.L_yy = [[ fenics.derivative(self.L_y[i], self.states[j], self.states_prime[j]) for j in range(self.state_dim) ] for i in range(self.state_dim)] self.L_yu = [[ fenics.derivative(self.L_u[i], self.states[j], self.states_prime[j]) for j in range(self.state_dim) ] for i in range(self.control_dim)] self.L_uy = [[ fenics.derivative(self.L_y[i], self.controls[j], self.test_directions[j]) for j in range(self.control_dim) ] for i in range(self.state_dim)] self.L_uu = [[ fenics.derivative(self.L_u[i], self.controls[j], self.test_directions[j]) for j in range(self.control_dim) ] for i in range(self.control_dim)] self.w_1 = [ summation([self.L_yy[i][j] for j in range(self.state_dim)]) + summation([self.L_uy[i][j] for j in range(self.control_dim)]) for i in range(self.state_dim) ] self.w_2 = [ summation([self.L_yu[i][j] for j in range(self.state_dim)]) + summation([self.L_uu[i][j] for j in range(self.control_dim)]) for i in range(self.control_dim) ] # Use replace -> derivative for faster computations self.adjoint_sensitivity_eqs_diag_temp = [ replace(self.state_forms[i], {self.adjoints[i]: self.trial_functions_adjoint[i]}) for i in range(self.state_dim) ] mapping_dict = { self.adjoints[j]: self.adjoints_prime[j] for j in range(self.state_dim) } self.adjoint_sensitivity_eqs_all_temp = [ replace(self.state_forms[i], mapping_dict) for i in range(self.state_dim) ] self.adjoint_sensitivity_eqs_lhs = [ fenics.derivative(self.adjoint_sensitivity_eqs_diag_temp[i], self.states[i], self.test_functions_adjoint[i]) for i in range(self.state_dim) ] if self.state_is_picard: self.adjoint_sensitivity_eqs_picard = [ fenics.derivative(self.adjoint_sensitivity_eqs_all_temp[i], self.states[i], self.test_functions_adjoint[i]) for i in range(self.state_dim) ] # Need cases distinction due to empty sum for state_dim == 1 if self.state_dim > 1: for i in range(self.state_dim): self.w_1[i] -= summation([ fenics.derivative(self.adjoint_sensitivity_eqs_all_temp[j], self.states[i], self.test_functions_adjoint[i]) for j in range(self.state_dim) if j != i ]) else: pass # Add right-hand-side for picard iteration if self.state_is_picard: for i in range(self.state_dim): self.adjoint_sensitivity_eqs_picard[i] -= self.w_1[i] self.adjoint_sensitivity_eqs_rhs = [ summation([ fenics.derivative(self.adjoint_sensitivity_eqs_all_temp[j], self.controls[i], self.test_functions_control[i]) for j in range(self.state_dim) ]) for i in range(self.control_dim) ] self.w_3 = [ -self.adjoint_sensitivity_eqs_rhs[i] for i in range(self.control_dim) ] self.hessian_rhs = [ self.w_2[i] + self.w_3[i] for i in range(self.control_dim) ]
def __compute_adjoint_equations(self): """Calculates the weak form of the adjoint equation for use with fenics. Returns ------- None """ # Use replace -> derivative to speed up computations self.lagrangian_temp_forms = [ replace(self.lagrangian.lagrangian_form, {self.adjoints[i]: self.trial_functions_adjoint[i]}) for i in range(self.state_dim) ] if self.state_is_picard: self.adjoint_picard_forms = [ fenics.derivative(self.lagrangian.lagrangian_form, self.states[i], self.test_functions_adjoint[i]) for i in range(self.state_dim) ] self.adjoint_eq_forms = [ fenics.derivative(self.lagrangian_temp_forms[i], self.states[i], self.test_functions_adjoint[i]) for i in range(self.state_dim) ] self.adjoint_eq_lhs = [] self.adjoint_eq_rhs = [] for i in range(self.state_dim): a, L = fenics.system(self.adjoint_eq_forms[i]) self.adjoint_eq_lhs.append(a) if L.empty(): zero_form = fenics.inner( fenics.Constant( np.zeros(self.test_functions_adjoint[i].ufl_shape)), self.test_functions_adjoint[i]) * self.dx self.adjoint_eq_rhs.append(zero_form) else: self.adjoint_eq_rhs.append(L) # Compute the adjoint boundary conditions if self.state_adjoint_equal_spaces: self.bcs_list_ad = [[ fenics.DirichletBC(bc) for bc in self.bcs_list[i] ] for i in range(self.state_dim)] [[bc.homogenize() for bc in self.bcs_list_ad[i]] for i in range(self.state_dim)] else: def get_subdx(V, idx, ls): if V.id() == idx: return ls if V.num_sub_spaces() > 1: for i in range(V.num_sub_spaces()): ans = get_subdx(V.sub(i), idx, ls + [i]) if ans is not None: return ans else: return None self.bcs_list_ad = [[1 for bc in range(len(self.bcs_list[i]))] for i in range(self.state_dim)] for i in range(self.state_dim): for j, bc in enumerate(self.bcs_list[i]): idx = bc.function_space().id() subdx = get_subdx(self.state_spaces[i], idx, ls=[]) W = self.adjoint_spaces[i] for num in subdx: W = W.sub(num) shape = W.ufl_element().value_shape() try: if shape == (): self.bcs_list_ad[i][j] = fenics.DirichletBC( W, fenics.Constant(0), bc.domain_args[0], bc.domain_args[1]) else: self.bcs_list_ad[i][j] = fenics.DirichletBC( W, fenics.Constant([0] * W.ufl_element().value_size()), bc.domain_args[0], bc.domain_args[1]) except AttributeError: if shape == (): self.bcs_list_ad[i][j] = fenics.DirichletBC( W, fenics.Constant(0), bc.sub_domain) else: self.bcs_list_ad[i][j] = fenics.DirichletBC( W, fenics.Constant([0] * W.ufl_element().value_size()), bc.sub_domain)