def gauss_newton_energy_norm(self, q): r"""Compute the energy norm of a field w.r.t. the Gauss-Newton operator The energy norm of a field :math:`q` w.r.t. the Gauss-Newton operator :math:`H` can be computed using one fewer linear solve than if we were to calculate the action of :math:`H\cdot q` on :math:`q`. This saves computation when using the conjugate gradient method to solve for the search direction. """ u, p = self.state, self.parameter dE = derivative(self._E, u) dR = derivative(self._R, p) dF_du, dF_dp = self._dF_du, derivative(self._F, p) v = firedrake.Function(u.function_space()) firedrake.solve(dF_du == action(dF_dp, q), v, self._bc, solver_parameters=self._solver_params, form_compiler_parameters=self._fc_params) return self._assemble( firedrake.energy_norm(derivative(dE, u), v) + firedrake.energy_norm(derivative(dR, p), q))
def _newton_solve(z, E, scale, tolerance=1e-6, armijo=1e-4, max_iterations=50): F = derivative(E, z) H = derivative(F, z) Q = z.function_space() bc = firedrake.DirichletBC(Q, 0, 'on_boundary') p = firedrake.Function(Q) for iteration in range(max_iterations): firedrake.solve(H == -F, p, bc, solver_parameters={'ksp_type': 'preonly', 'pc_type': 'lu'}) dE_dp = assemble(action(F, p)) α = 1.0 E0 = assemble(E) Ez = assemble(replace(E, {z: z + firedrake.Constant(α) * p})) while (Ez > E0 + armijo * α * dE_dp) or np.isnan(Ez): α /= 2 Ez = assemble(replace(E, {z: z + firedrake.Constant(α) * p})) z.assign(z + firedrake.Constant(α) * p) if abs(dE_dp) < tolerance * assemble(scale): return z raise ValueError("Newton solver failed to converge after {0} iterations" .format(max_iterations))
def __init__(self, problem, tolerance, solver_parameters=None, **kwargs): r"""Solve a MinimizationProblem using Newton's method with backtracking line search Parameters ---------- problem : MinimizationProblem The particular problem instance to solve tolerance : float dimensionless tolerance for when to stop iterating, measured with with respect to the problem's scale functional solver_parameters : dict (optional) Linear solve parameters for computing the search direction armijo : float (optional) Parameter in the Armijo condition for line search; defaults to 1e-4, see Nocedal and Wright contraction : float (optional) shrinking factor for backtracking line search; defaults to .5 max_iterations : int (optional) maximum number of outer-level Newton iterations; defaults to 50 """ self.problem = problem self.tolerance = tolerance if solver_parameters is None: solver_parameters = default_solver_parameters self.armijo = kwargs.pop("armijo", 1e-4) self.contraction = kwargs.pop("contraction", 0.5) self.max_iterations = kwargs.pop("max_iterations", 50) u = self.problem.u V = u.function_space() v = firedrake.Function(V) self.v = v E = self.problem.E self.F = firedrake.derivative(E, u) self.J = firedrake.derivative(self.F, u) self.dE_dv = firedrake.action(self.F, v) bcs = None if self.problem.bcs: bcs = firedrake.homogenize(self.problem.bcs) problem = firedrake.LinearVariationalProblem( self.J, -self.F, v, bcs, constant_jacobian=False, form_compiler_parameters=self.problem.form_compiler_parameters, ) self.search_direction_solver = firedrake.LinearVariationalSolver( problem, solver_parameters=solver_parameters ) self.search_direction_solver.solve() self.t = firedrake.Constant(0.0) self.iteration = 0
def evaluate_adj_component(self, inputs, adj_inputs, block_variable, idx, prepared=None): if not self.linear and self.func == block_variable.output: # We are not able to calculate derivatives wrt initial guess. return None F_form = prepared["form"] adj_sol = prepared["adj_sol"] adj_sol_bdy = prepared["adj_sol_bdy"] c = block_variable.output c_rep = block_variable.saved_output if isinstance(c, firedrake.Function): trial_function = firedrake.TrialFunction(c.function_space()) elif isinstance(c, firedrake.Constant): mesh = self.compat.extract_mesh_from_form(F_form) trial_function = firedrake.TrialFunction( c._ad_function_space(mesh)) elif isinstance(c, firedrake.DirichletBC): tmp_bc = self.compat.create_bc( c, value=self.compat.extract_subfunction(adj_sol_bdy, c.function_space())) return [tmp_bc] elif isinstance(c, self.compat.MeshType): # Using CoordianteDerivative requires us to do action before # differentiating, might change in the future. F_form_tmp = firedrake.action(F_form, adj_sol) X = firedrake.SpatialCoordinate(c_rep) dFdm = firedrake.derivative( -F_form_tmp, X, firedrake.TestFunction(c._ad_function_space())) dFdm = self.compat.assemble_adjoint_value(dFdm, **self.assemble_kwargs) return dFdm # dFdm_cache works with original variables, not block saved outputs. if c in self._dFdm_cache: dFdm = self._dFdm_cache[c] else: dFdm = -firedrake.derivative(self.lhs, c, trial_function) dFdm = firedrake.adjoint(dFdm) self._dFdm_cache[c] = dFdm # Replace the form coefficients with checkpointed values. replace_map = self._replace_map(dFdm) replace_map[self.func] = self.get_outputs()[0].saved_output dFdm = replace(dFdm, replace_map) dFdm = dFdm * adj_sol dFdm = self.compat.assemble_adjoint_value(dFdm, **self.assemble_kwargs) return dFdm
def update_search_direction(self): r"""Solve the Gauss-Newton system for the new search direction using the preconditioned conjugate gradient method""" p, q, dJ = self.parameter, self.search_direction, self.gradient dR = derivative(self.regularization, self.parameter) Q = q.function_space() M = firedrake.TrialFunction(Q) * firedrake.TestFunction(Q) * dx + \ derivative(dR, p) # Compute the preconditioned residual z = firedrake.Function(Q) firedrake.solve(M == -dJ, z, solver_parameters=self._solver_params, form_compiler_parameters=self._fc_params) # This variable is a search direction for a search direction, which # is definitely not confusing at all. s = z.copy(deepcopy=True) q *= 0.0 old_cost = np.inf while True: z_mnorm = self._assemble(firedrake.energy_norm(M, z)) s_hnorm = self.gauss_newton_energy_norm(s) α = z_mnorm / s_hnorm δz = firedrake.Function(Q) g = self.gauss_newton_mult(s) firedrake.solve(M == g, δz, solver_parameters=self._solver_params, form_compiler_parameters=self._fc_params) q += α * s z -= α * δz β = self._assemble(firedrake.energy_norm(M, z)) / z_mnorm s *= β s += z energy_norm = self.gauss_newton_energy_norm(q) cost = 0.5 * energy_norm + self._assemble(action(dJ, q)) if (abs(old_cost - cost) / (0.5 * energy_norm) < self._search_tolerance): return old_cost = cost
def solve( self, parameters={ "snes_type": "newtonls", "snes_monitor": True, "ksp_type": "preonly", "pc_type": "lu", "mat_type": "aij", "pc_factor_mat_solver_type": "mumps" }): problem = fe.NonlinearVariationalProblem( F=self.variational_form_residual, u=self.solution, bcs=self.dirichlet_boundary_conditions, J=fe.derivative(self.variational_form_residual, self.solution)) solver = fe.NonlinearVariationalSolver(problem=problem, solver_parameters=parameters) solver.solve() self.snes_iteration_count += solver.snes.getIterationNumber() return self.solution
def form(self, pc, test, trial): """Implements the interface for AuxiliaryOperatorPC.""" appctx = self.get_appctx(pc) F = appctx["F"] butcher_tableau = appctx["butcher_tableau"] t = appctx["t"] dt = appctx["dt"] u0 = appctx["u0"] bcs = appctx["bcs"] bc_type = appctx["bc_type"] splitting = appctx["splitting"] nullspace = appctx["nullspace"] # Make a modified Butcher tableau, probably with some kind # of sparser structure (e.g. LD part of LDU factorization) Atilde = self.getAtilde(butcher_tableau.A) butcher_new = copy.deepcopy(butcher_tableau) butcher_new.A = Atilde # Get the UFL for the system with the modified Butcher tableau Fnew, w, bcnew, bignsp, _ = getForm(F, butcher_new, t, dt, u0, bcs, bc_type, splitting, nullspace) # Now we get the Jacobian for the modified system, # which becomes the auxiliary operator! test_old = Fnew.arguments()[0] a = replace(derivative(Fnew, w, du=trial), {test_old: test}) return a, bcnew
def setup(self, **kwargs): for name, field in kwargs.items(): if name in self._fields.keys(): self._fields[name].assign(field) else: self._fields[name] = utilities.copy(field) # Create homogeneous BCs for the Dirichlet part of the boundary u = self._fields.get('velocity', self._fields.get('u')) V = u.function_space() bcs = firedrake.DirichletBC(V, u, self._dirichlet_ids) if not self._dirichlet_ids: bcs = None # Find the numeric IDs for the ice front boundary_ids = u.ufl_domain().exterior_facets.unique_markers ice_front_ids_comp = set(self._dirichlet_ids + self._side_wall_ids) ice_front_ids = list(set(boundary_ids) - ice_front_ids_comp) # Create the action and scale functionals _kwargs = { 'side_wall_ids': self._side_wall_ids, 'ice_front_ids': ice_front_ids } action = self._model.action(**self._fields, **_kwargs) F = firedrake.derivative(action, u) degree = self._model.quadrature_degree(**self._fields) params = {'form_compiler_parameters': {'quadrature_degree': degree}} problem = firedrake.NonlinearVariationalProblem(F, u, bcs, **params) self._solver = firedrake.NonlinearVariationalSolver( problem, solver_parameters=self._solver_parameters)
def initial_values(sim): print("Solving steady heat driven cavity to obtain initial values") Ra = 2.518084e6 Pr = 6.99 sim.grashof_number = sim.grashof_number.assign(Ra / Pr) sim.prandtl_number = sim.prandtl_number.assign(Pr) dim = sim.mesh.geometric_dimension() T_c = sim.cold_wall_temperature.__float__() w = fe.interpolate( fe.Expression((0., ) + (0., ) * dim + (T_c, ), element=sim.element), sim.function_space) F = heat_driven_cavity_variational_form_residual( sim=sim, solution=w) * fe.dx(degree=sim.quadrature_degree) T_h = sim.hot_wall_temperature.__float__() problem = fe.NonlinearVariationalProblem( F=F, u=w, bcs=dirichlet_boundary_conditions(sim), J=fe.derivative(F, w)) solver = fe.NonlinearVariationalSolver(problem=problem, solver_parameters={ "snes_type": "newtonls", "snes_monitor": True, "ksp_type": "preonly", "pc_type": "lu", "mat_type": "aij", "pc_factor_mat_solver_type": "mumps" }) def solve(): solver.solve() return w w, _ = \ sapphire.continuation.solve_with_bounded_regularization_sequence( solve = solve, solution = w, backup_solution = fe.Function(w), regularization_parameter = sim.grashof_number, initial_regularization_sequence = ( 0., sim.grashof_number.__float__())) return w
def derivative_form(self, w): if args.discretisation == "pkp0": fd.warning(fd.RED % "Using residual without grad-div") F = solver.F_nograddiv else: F = solver.F F = fd.replace(F, {F.arguments()[0]: solver.z_adj}) L = F + self.value_form() X = fd.SpatialCoordinate(solver.z.ufl_domain()) return fd.derivative(L, X, w)
def initialize(self, init_solution): self.solution_old.assign(init_solution) u, p = firedrake.split(self.solution) u_old, p_old = self.solution_old.split() u_star_theta = (1-self.theta)*u_old + self.theta*self.u_star u_theta = (1-self.theta)*u_old + self.theta*u p_theta = (1-self.theta_p)*p_old + self.theta_p*p u_lag, p_lag = self.solution_lag.split() u_lag_theta = (1-self.theta)*u_old + self.theta*u_lag p_lag_theta = (1-self.theta_p)*p_old + self.theta_p*p_lag # setup predictor solve, this solves for u_start only using a fixed p_lag_theta for pressure self.fields_star = self.fields.copy() self.fields_star['pressure'] = p_lag_theta self.Fstar = self.equations[0].mass_term(self.u_star_test, self.u_star-u_old) self.Fstar -= self.dt_const*self.equations[0].residual(self.u_star_test, u_star_theta, u_lag_theta, self.fields_star, bcs=self.bcs) self.predictor_problem = firedrake.NonlinearVariationalProblem(self.Fstar, self.u_star) self.predictor_solver = firedrake.NonlinearVariationalSolver(self.predictor_problem, solver_parameters=self.predictor_solver_parameters, options_prefix='predictor_momentum') # the correction solve, solving the coupled system: # u1 = u* - dt*G ( p_theta - p_lag_theta) # div(u1) = 0 self.F = self.equations[0].mass_term(self.u_test, u-self.u_star) pg_term = [term for term in self.equations[0]._terms if isinstance(term, PressureGradientTerm)][0] pg_fields = self.fields.copy() # note that p_theta-p_lag_theta = theta_p*(p1-p_lag) pg_fields['pressure'] = self.theta_p * (p - p_lag) self.F -= self.dt_const*pg_term.residual(self.u_test, u_theta, u_lag_theta, pg_fields, bcs=self.bcs) div_term = [term for term in self.equations[1]._terms if isinstance(term, DivergenceTerm)][0] div_fields = self.fields.copy() div_fields['velocity'] = u self.F -= self.dt_const*div_term.residual(self.p_test, p_theta, p_lag_theta, div_fields, bcs=self.bcs) W = self.solution.function_space() if self.pressure_nullspace is None: mixed_nullspace = None else: mixed_nullspace = firedrake.MixedVectorSpaceBasis(W, [W.sub(0), self.pressure_nullspace]) self.problem = firedrake.NonlinearVariationalProblem(self.F, self.solution) self.solver = firedrake.NonlinearVariationalSolver(self.problem, solver_parameters=self.solver_parameters, appctx={'a': firedrake.derivative(self.F, self.solution), 'schur_nullspace': self.pressure_nullspace, 'dt': self.dt_const, 'dx': self.equations[1].dx, 'ds': self.equations[1].ds, 'bcs': self.bcs, 'n': div_term.n}, nullspace=mixed_nullspace, transpose_nullspace=mixed_nullspace, options_prefix=self.name) self._initialized = True
def initialize_solvers(self): # Kinematics # Right Cauchy-Green tensor if self.nonlin: d = self.X.geometric_dimension() I = fd.Identity(d) # Identity tensor F = I + fd.grad(self.X) # Deformation gradient C = F.T * F E = (C - I) / 2. # Green-Lagrangian strain # E = 1./2.*( fd.grad(self.X).T + fd.grad(self.X) + fd.grad(self.X).T * fd.grad(self.X) ) # alternative equivalent definition else: E = 1. / 2. * (fd.grad(self.X).T + fd.grad(self.X) ) # linear strain self.W = (self.lam / 2.) * (fd.tr(E))**2 + self.mu * fd.tr(E * E) # f = fd.Constant((0, 0, -self.g)) # body force / rho # T = self.surface_force() # Total potential energy Pi = self.W * fd.dx # Compute first variation of Pi (directional derivative about X in the direction of v) F_expr = fd.derivative(Pi, self.X, self.v) self.DBC = fd.DirichletBC(self.V, fd.as_vector([0., 0., 0.]), self.bottom_id) # delX = fd.nabla_grad(self.X) # delv_B = fd.nabla_grad(self.v) # T_x_dv = self.lam * fd.div(self.X) * fd.div(self.v) \ # + self.mu * ( fd.inner( delX, delv_B + fd.transpose(delv_B) ) ) self.a_U = fd.dot(self.trial, self.v) * fd.dx # self.L_U = ( fd.dot( self.U, self.v ) - self.dt/2./self.rho * T_x_dv ) * fd.dx self.L_U = fd.dot( self.U, self.v) * fd.dx - self.dt / 2. / self.rho * F_expr #\ # + self.dt/2./self.rho*fd.dot(T,self.v)*fd.ds(1) # surface force at x==0 plane # + self.dt/2.*fd.dot(f,self.v)*fd.dx # body force self.a_X = fd.dot(self.trial, self.v) * fd.dx # self.L_interface = fd.dot(self.phi_vect, self.v) * fd.ds(self.interface_id) self.L_X = fd.dot((self.X + self.dt * self.U), self.v) * fd.dx #\ # - self.dt/self.rho * self.L_interface self.LVP_U = fd.LinearVariationalProblem(self.a_U, self.L_U, self.U, bcs=[self.DBC]) self.LVS_U = fd.LinearVariationalSolver(self.LVP_U) self.LVP_X = fd.LinearVariationalProblem(self.a_X, self.L_X, self.X, bcs=[self.DBC]) self.LVS_X = fd.LinearVariationalSolver(self.LVP_X)
def eval_dJdw(self): u = self.u v = self.v J = self.J F = self.F X = self.X w = self.w V = self.V params = self.params solve(self.F == 0, u, bcs=self.bc, solver_parameters=params) bil_form = adjoint(derivative(F, u)) rhs = -derivative(J, u) u_adj = Function(V) solve(assemble(bil_form), u_adj, assemble(rhs), bcs=self.bc, solver_parameters=params) L = J + replace(self.F, {v: u_adj}) self.L = L self.bil_form = bil_form return assemble(derivative(L, X, w))
def solve_firedrake(q, kappa0, kappa1): x = firedrake.SpatialCoordinate(mesh) f = x[0] u = firedrake.Function(V) bcs = [firedrake.DirichletBC(V, firedrake.Constant(0.0), "on_boundary")] inner, grad, dx = ufl.inner, ufl.grad, ufl.dx JJ = 0.5 * inner(kappa0 * grad(u), grad(u)) * dx - q * kappa1 * f * u * dx v = firedrake.TestFunction(V) F = firedrake.derivative(JJ, u, v) firedrake.solve(F == 0, u, bcs=bcs) return u
def gauss_newton_mult(self, q): """Multiply a field by the Gauss-Newton operator""" u, p = self.state, self.parameter dE = derivative(self._E, u) dR = derivative(self._R, p) dF_du, dF_dp = self._dF_du, derivative(self._F, p) w = firedrake.Function(u.function_space()) firedrake.solve(dF_du == action(dF_dp, q), w, self._bc, solver_parameters=self._solver_params, form_compiler_parameters=self._fc_params) v = firedrake.Function(u.function_space()) firedrake.solve(adjoint(dF_du) == derivative(dE, u, w), v, self._bc, solver_parameters=self._solver_params, form_compiler_parameters=self._fc_params) return action(adjoint(dF_dp), v) + derivative(dR, p, q)
def solve(self, q, f, dirichlet_ids=[], **kwargs): u = firedrake.Function(f.function_space()) L = self.action(q, u, f) F = firedrake.derivative(L, u) V = u.function_space() bc = firedrake.DirichletBC(V, firedrake.Constant(0), dirichlet_ids) firedrake.solve(F == 0, u, bc, solver_parameters={ 'ksp_type': 'preonly', 'pc_type': 'lu' }) return u
def _setup(self, **kwargs): q = kwargs["q"].copy(deepcopy=True) f = kwargs["f"].copy(deepcopy=True) u = kwargs["u"].copy(deepcopy=True) L = self.model.action(u=u, q=q, f=f) F = firedrake.derivative(L, u) V = u.function_space() bc = firedrake.DirichletBC(V, u, self.dirichlet_ids) params = { "solver_parameters": { "ksp_type": "preonly", "pc_type": "lu" } } problem = firedrake.NonlinearVariationalProblem(F, u, bc) self._solver = firedrake.NonlinearVariationalSolver(problem, **params) self._fields = {"q": q, "f": f, "u": u}
def _setup(self, problem, callback=(lambda s: None)): self._problem = problem self._callback = callback self._p = problem.parameter.copy(deepcopy=True) self._u = problem.state.copy(deepcopy=True) self._model_args = dict(**problem.model_args, dirichlet_ids=problem.dirichlet_ids) u_name, p_name = problem.state_name, problem.parameter_name args = dict(**self._model_args, **{u_name: self._u, p_name: self._p}) # Make the form compiler use a reasonable number of quadrature points degree = problem.model.quadrature_degree(**args) self._fc_params = {'quadrature_degree': degree} # Create the error, regularization, and barrier functionals self._E = problem.objective(self._u) self._R = problem.regularization(self._p) self._J = self._E + self._R # Create the weak form of the forward model, the adjoint state, and # the derivative of the objective functional self._F = derivative(problem.model.action(**args), self._u) self._dF_du = derivative(self._F, self._u) # Create a search direction dR = derivative(self._R, self._p) self._solver_params = {'ksp_type': 'preonly', 'pc_type': 'lu'} Q = self._p.function_space() self._q = firedrake.Function(Q) # Create the adjoint state variable V = self.state.function_space() self._λ = firedrake.Function(V) dF_dp = derivative(self._F, self._p) # Create Dirichlet BCs where they apply for the adjoint solve rank = self._λ.ufl_element().num_sub_elements() if rank == 0: zero = firedrake.Constant(0) else: zero = firedrake.as_vector((0, ) * rank) self._bc = firedrake.DirichletBC(V, zero, problem.dirichlet_ids) # Create the derivative of the objective functional self._dE = derivative(self._E, self._u) dR = derivative(self._R, self._p) self._dJ = (action(adjoint(dF_dp), self._λ) + dR)
def firedrakeSmooth(q0, alpha=2e3): """[summary] Parameters ---------- q0 : firedrake function firedrake function to be smooth alpha : float, optional parameter that controls the amount of smoothing, which is approximately the smoothing lengthscale in m, by default 2e3 Returns ------- q firedrake interp function smoothed result """ q = q0.copy(deepcopy=True) J = 0.5 * ((q - q0)**2 + alpha**2 * inner(grad(q), grad(q))) * dx F = firedrake.derivative(J, q) firedrake.solve(F == 0, q) return q
def wrapper(self, *args, **kwargs): from firedrake import derivative, adjoint, TrialFunction init(self, *args, **kwargs) self._ad_F = self.F self._ad_u = self.u self._ad_bcs = self.bcs self._ad_J = self.J try: # Some forms (e.g. SLATE tensors) are not currently # differentiable. dFdu = derivative(self.F, self.u, TrialFunction(self.u.function_space())) self._ad_adj_F = adjoint(dFdu) except TypeError: self._ad_adj_F = None self._ad_kwargs = { 'Jp': self.Jp, 'form_compiler_parameters': self.form_compiler_parameters, 'is_linear': self.is_linear } self._ad_count_map = {}
def hyperelasticity(mesh, degree): V = VectorFunctionSpace(mesh, 'Q', degree) v = TestFunction(V) du = TrialFunction(V) # Incremental displacement u = Function(V) # Displacement from previous iteration B = Function(V) # Body force per unit mass # Kinematics I = Identity(mesh.topological_dimension()) F = I + grad(u) # Deformation gradient C = F.T * F # Right Cauchy-Green tensor E = (C - I) / 2 # Euler-Lagrange strain tensor E = variable(E) # Material constants mu = Constant(1.0) # Lame's constants lmbda = Constant(0.001) # Strain energy function (material model) psi = lmbda / 2 * (tr(E)**2) + mu * tr(E * E) S = diff(psi, E) # Second Piola-Kirchhoff stress tensor PK = F * S # First Piola-Kirchoff stress tensor # Variational problem return derivative((inner(PK, grad(v)) - inner(B, v)) * dx, u, du)
def setup(self, **kwargs): for name, field in kwargs.items(): if name in self._fields.keys(): self._fields[name].assign(field) else: if isinstance(field, firedrake.Constant): self._fields[name] = firedrake.Constant(field) elif isinstance(field, firedrake.Function): self._fields[name] = field.copy(deepcopy=True) else: raise TypeError( "Input %s field has type %s, must be Constant or Function!" % (name, type(field)) ) # Create homogeneous BCs for the Dirichlet part of the boundary u = self._fields["velocity"] V = u.function_space() bcs = firedrake.DirichletBC(V, u, self._dirichlet_ids) if not self._dirichlet_ids: bcs = None # Find the numeric IDs for the ice front boundary_ids = u.ufl_domain().exterior_facets.unique_markers ice_front_ids_comp = set(self._dirichlet_ids + self._side_wall_ids) ice_front_ids = list(set(boundary_ids) - ice_front_ids_comp) # Create the action and scale functionals _kwargs = {"side_wall_ids": self._side_wall_ids, "ice_front_ids": ice_front_ids} action = self._model.action(**self._fields, **_kwargs) F = firedrake.derivative(action, u) problem = firedrake.NonlinearVariationalProblem(F, u, bcs) self._solver = firedrake.NonlinearVariationalSolver( problem, solver_parameters=self._solver_parameters )
def diagnostic_solve(self, u0, h, s, A, **kwargs): r"""Solve for the ice velocity from the thickness and surface elevation Parameters ---------- u0 : firedrake.Function Ice velocity h : firedrake.Function Ice thickness s : firedrake.Function Ice surface elevation A : firedrake.Function or firedrake.Constant Rate factor Returns ------- u : firedrake.Function Ice velocity Other parameters ---------------- **kwargs All other keyword arguments will be passed on to the 'mass', 'gravity' and 'penalty' functions that was set when this model object was initialized """ u = u0.copy(deepcopy=True) action = self.action(u=u, h=h, s=s, A=A, **kwargs) F = firedrake.derivative(action, u) firedrake.solve(F == 0, u, form_compiler_parameters={'quadrature_degree': 4}) return u
def solve(self) -> fe.Function: """Set up the problem and solver, and solve. This is a JIT (just in time), ensuring that the problem and solver setup are up-to-date before calling the solver. All compiled objects are cached, so the JIT problem and solver setup does not have any significant performance overhead. """ problem = fe.NonlinearVariationalProblem( F=self.weak_form_residual(), u=self.solution, bcs=self.dirichlet_boundary_conditions(), J=fe.derivative(self.weak_form_residual(), self.solution)) solver = fe.NonlinearVariationalSolver( problem=problem, nullspace=self.nullspace(), solver_parameters=self.solver_parameters) solver.solve() self.snes_iteration_count += solver.snes.getIterationNumber() return self.solution
def _setup(self, problem, callback=(lambda s: None)): self._problem = problem self._callback = callback self._p = problem.parameter.copy(deepcopy=True) self._u = problem.state.copy(deepcopy=True) self._solver = self.problem.solver_type(self.problem.model, **self.problem.solver_kwargs) u_name, p_name = problem.state_name, problem.parameter_name solve_kwargs = dict(**problem.diagnostic_solve_kwargs, **{ u_name: self._u, p_name: self._p }) # Make the form compiler use a reasonable number of quadrature points degree = problem.model.quadrature_degree(**solve_kwargs) self._fc_params = {'quadrature_degree': degree} # Create the error, regularization, and barrier functionals self._E = problem.objective(self._u) self._R = problem.regularization(self._p) self._J = self._E + self._R # Create the weak form of the forward model, the adjoint state, and # the derivative of the objective functional A = problem.model.action(**solve_kwargs) self._F = derivative(A, self._u) self._dF_du = derivative(self._F, self._u) # Create a search direction dR = derivative(self._R, self._p) # TODO: Make this customizable self._solver_params = default_solver_parameters Q = self._p.function_space() self._q = firedrake.Function(Q) # Create the adjoint state variable V = self.state.function_space() self._λ = firedrake.Function(V) dF_dp = derivative(self._F, self._p) # Create Dirichlet BCs where they apply for the adjoint solve rank = self._λ.ufl_element().num_sub_elements() if rank == 0: zero = Constant(0) else: zero = firedrake.as_vector((0, ) * rank) self._bc = firedrake.DirichletBC(V, zero, problem.dirichlet_ids) # Create the derivative of the objective functional self._dE = derivative(self._E, self._u) dR = derivative(self._R, self._p) self._dJ = (action(adjoint(dF_dp), self._λ) + dR) # Create problem and solver objects for the adjoint state L = adjoint(self._dF_du) adjoint_problem = firedrake.LinearVariationalProblem( L, -self._dE, self._λ, self._bc, form_compiler_parameters=self._fc_params, constant_jacobian=False) self._adjoint_solver = firedrake.LinearVariationalSolver( adjoint_problem, solver_parameters=self._solver_params)
def heat_exchanger_optimization(mu=0.03, n_iters=1000): output_dir = "2D/" path = os.path.abspath(__file__) dir_path = os.path.dirname(path) mesh = fd.Mesh(f"{dir_path}/2D_mesh.msh") # Perturb the mesh coordinates. Necessary to calculate shape derivatives S = fd.VectorFunctionSpace(mesh, "CG", 1) s = fd.Function(S, name="deform") mesh.coordinates.assign(mesh.coordinates + s) # Initial level set function x, y = fd.SpatialCoordinate(mesh) PHI = fd.FunctionSpace(mesh, "CG", 1) phi_expr = sin(y * pi / 0.2) * cos(x * pi / 0.2) - fd.Constant(0.8) # Avoid recording the operation interpolate into the tape. # Otherwise, the shape derivatives will not be correct with fda.stop_annotating(): phi = fd.interpolate(phi_expr, PHI) phi.rename("LevelSet") fd.File(output_dir + "phi_initial.pvd").write(phi) # Physics mu = fd.Constant(mu) # viscosity alphamin = 1e-12 alphamax = 2.5 / (2e-4) parameters = { "mat_type": "aij", "ksp_type": "preonly", "ksp_converged_reason": None, "pc_type": "lu", "pc_factor_mat_solver_type": "mumps", } stokes_parameters = parameters temperature_parameters = parameters u_inflow = 2e-3 tin1 = fd.Constant(10.0) tin2 = fd.Constant(100.0) P2 = fd.VectorElement("CG", mesh.ufl_cell(), 2) P1 = fd.FiniteElement("CG", mesh.ufl_cell(), 1) TH = P2 * P1 W = fd.FunctionSpace(mesh, TH) U = fd.TrialFunction(W) u, p = fd.split(U) V = fd.TestFunction(W) v, q = fd.split(V) epsilon = fd.Constant(10000.0) def hs(phi, epsilon): return fd.Constant(alphamax) * fd.Constant(1.0) / ( fd.Constant(1.0) + exp(-epsilon * phi)) + fd.Constant(alphamin) def stokes(phi, BLOCK_INLET_MOUTH, BLOCK_OUTLET_MOUTH): a_fluid = mu * inner(grad(u), grad(v)) - div(v) * p - q * div(u) darcy_term = inner(u, v) return (a_fluid * dx + hs(phi, epsilon) * darcy_term * dx(0) + alphamax * darcy_term * (dx(BLOCK_INLET_MOUTH) + dx(BLOCK_OUTLET_MOUTH))) # Dirichlet boundary conditions inflow1 = fd.as_vector([ u_inflow * sin( ((y - (line_sep - (dist_center + inlet_width))) * pi) / inlet_width), 0.0, ]) inflow2 = fd.as_vector([ u_inflow * sin(((y - (line_sep + dist_center)) * pi) / inlet_width), 0.0, ]) noslip = fd.Constant((0.0, 0.0)) # Stokes 1 bcs1_1 = fd.DirichletBC(W.sub(0), noslip, WALLS) bcs1_2 = fd.DirichletBC(W.sub(0), inflow1, INLET1) bcs1_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET1) bcs1_4 = fd.DirichletBC(W.sub(0), noslip, INLET2) bcs1_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET2) bcs1 = [bcs1_1, bcs1_2, bcs1_3, bcs1_4, bcs1_5] # Stokes 2 bcs2_1 = fd.DirichletBC(W.sub(0), noslip, WALLS) bcs2_2 = fd.DirichletBC(W.sub(0), inflow2, INLET2) bcs2_3 = fd.DirichletBC(W.sub(1), fd.Constant(0.0), OUTLET2) bcs2_4 = fd.DirichletBC(W.sub(0), noslip, INLET1) bcs2_5 = fd.DirichletBC(W.sub(0), noslip, OUTLET1) bcs2 = [bcs2_1, bcs2_2, bcs2_3, bcs2_4, bcs2_5] # Forward problems U1, U2 = fd.Function(W), fd.Function(W) L = inner(fd.Constant((0.0, 0.0, 0.0)), V) * dx problem = fd.LinearVariationalProblem(stokes(-phi, INMOUTH2, OUTMOUTH2), L, U1, bcs=bcs1) solver_stokes1 = fd.LinearVariationalSolver( problem, solver_parameters=stokes_parameters, options_prefix="stokes_1") solver_stokes1.solve() problem = fd.LinearVariationalProblem(stokes(phi, INMOUTH1, OUTMOUTH1), L, U2, bcs=bcs2) solver_stokes2 = fd.LinearVariationalSolver( problem, solver_parameters=stokes_parameters, options_prefix="stokes_2") solver_stokes2.solve() # Convection difussion equation ks = fd.Constant(1e0) cp_value = 5.0e5 cp = fd.Constant(cp_value) T = fd.FunctionSpace(mesh, "DG", 1) t = fd.Function(T, name="Temperature") w = fd.TestFunction(T) # Mesh-related functions n = fd.FacetNormal(mesh) h = fd.CellDiameter(mesh) u1, p1 = fd.split(U1) u2, p2 = fd.split(U2) def upwind(u): return (dot(u, n) + abs(dot(u, n))) / 2.0 u1n = upwind(u1) u2n = upwind(u2) # Penalty term alpha = fd.Constant(500.0) # Bilinear form a_int = dot(grad(w), ks * grad(t) - cp * (u1 + u2) * t) * dx a_fac = (fd.Constant(-1.0) * ks * dot(avg(grad(w)), jump(t, n)) * dS + fd.Constant(-1.0) * ks * dot(jump(w, n), avg(grad(t))) * dS + ks("+") * (alpha("+") / avg(h)) * dot(jump(w, n), jump(t, n)) * dS) a_vel = (dot( jump(w), cp * (u1n("+") + u2n("+")) * t("+") - cp * (u1n("-") + u2n("-")) * t("-"), ) * dS + dot(w, cp * (u1n + u2n) * t) * ds) a_bnd = (dot(w, cp * dot(u1 + u2, n) * t) * (ds(INLET1) + ds(INLET2)) + w * t * (ds(INLET1) + ds(INLET2)) - w * tin1 * ds(INLET1) - w * tin2 * ds(INLET2) + alpha / h * ks * w * t * (ds(INLET1) + ds(INLET2)) - ks * dot(grad(w), t * n) * (ds(INLET1) + ds(INLET2)) - ks * dot(grad(t), w * n) * (ds(INLET1) + ds(INLET2))) aT = a_int + a_fac + a_vel + a_bnd LT_bnd = (alpha / h * ks * tin1 * w * ds(INLET1) + alpha / h * ks * tin2 * w * ds(INLET2) - tin1 * ks * dot(grad(w), n) * ds(INLET1) - tin2 * ks * dot(grad(w), n) * ds(INLET2)) problem = fd.LinearVariationalProblem(derivative(aT, t), LT_bnd, t) solver_temp = fd.LinearVariationalSolver( problem, solver_parameters=temperature_parameters, options_prefix="temperature", ) solver_temp.solve() # fd.solve(eT == 0, t, solver_parameters=temperature_parameters) # Cost function: Flux at the cold outlet scale_factor = 4e-4 Jform = fd.assemble( fd.Constant(-scale_factor * cp_value) * inner(t * u1, n) * ds(OUTLET1)) # Constraints: Pressure drop on each fluid power_drop = 1e-2 Power1 = fd.assemble(p1 / power_drop * ds(INLET1)) Power2 = fd.assemble(p2 / power_drop * ds(INLET2)) phi_pvd = fd.File("phi_evolution.pvd") def deriv_cb(phi): with stop_annotating(): phi_pvd.write(phi[0]) c = fda.Control(s) # Reduced Functionals Jhat = LevelSetFunctional(Jform, c, phi, derivative_cb_pre=deriv_cb) P1hat = LevelSetFunctional(Power1, c, phi) P1control = fda.Control(Power1) P2hat = LevelSetFunctional(Power2, c, phi) P2control = fda.Control(Power2) Jhat_v = Jhat(phi) print("Initial cost function value {:.5f}".format(Jhat_v), flush=True) print("Power drop 1 {:.5f}".format(Power1), flush=True) print("Power drop 2 {:.5f}".format(Power2), flush=True) beta_param = 0.08 # Regularize the shape derivatives only in the domain marked with 0 reg_solver = RegularizationSolver(S, mesh, beta=beta_param, gamma=1e5, dx=dx, design_domain=0) tol = 1e-5 dt = 0.05 params = { "alphaC": 1.0, "debug": 5, "alphaJ": 1.0, "dt": dt, "K": 1e-3, "maxit": n_iters, "maxtrials": 5, "itnormalisation": 10, "tol_merit": 5e-3, # new merit can be within 0.5% of the previous merit # "normalize_tol" : -1, "tol": tol, } solver_parameters = { "reinit_solver": { "h_factor": 2.0, } } # Optimization problem problem = InfDimProblem( Jhat, reg_solver, ineqconstraints=[ Constraint(P1hat, 1.0, P1control), Constraint(P2hat, 1.0, P2control), ], solver_parameters=solver_parameters, ) results = nlspace_solve(problem, params) return results
def compute_time_accuracy_via_mms( gridsize, element_degree, timestep_sizes, endtime): mesh = fe.UnitIntervalMesh(gridsize) element = fe.FiniteElement("P", mesh.ufl_cell(), element_degree) V = fe.FunctionSpace(mesh, element) u_h = fe.Function(V) v = fe.TestFunction(V) un = fe.Function(V) u_m = manufactured_solution(mesh) bc = fe.DirichletBC(V, u_m, "on_boundary") _F = F(u_h, v, un) problem = fe.NonlinearVariationalProblem( _F - v*R(u_m)*dx, u_h, bc, fe.derivative(_F, u_h)) solver = fe.NonlinearVariationalSolver(problem) t.assign(0.) initial_values = fe.interpolate(u_m, V) L2_norm_errors = [] print("Delta_t, L2_norm_error") for timestep_size in timestep_sizes: Delta_t.assign(timestep_size) un.assign(initial_values) time = 0. t.assign(time) while time < (endtime - TIME_EPSILON): time += timestep_size t.assign(time) solver.solve() un.assign(u_h) L2_norm_errors.append( math.sqrt(fe.assemble(fe.inner(u_h - u_m, u_h - u_m)*dx))) print(str(timestep_size) + ", " + str(L2_norm_errors[-1])) r = timestep_sizes[-2]/timestep_sizes[-1] e = L2_norm_errors log = math.log order = log(e[-2]/e[-1])/log(r) return order
def initial_values(sim): print("Solving steady heat driven cavity to obtain initial values") Ra = 2.518084e6 Pr = 6.99 sim.reference_temperature_range__degC.assign(10.) sim.grashof_number = sim.grashof_number.assign(Ra / Pr) sim.prandtl_number = sim.prandtl_number.assign(Pr) w = fe.Function(sim.function_space) p, u, T = w.split() p.assign(0.) ihat, jhat = sim.unit_vectors() u.assign(0. * ihat + 0. * jhat) T.assign(sim.cold_wall_temperature) F = heat_driven_cavity_variational_form_residual( sim=sim, solution=w) * fe.dx(degree=sim.quadrature_degree) problem = fe.NonlinearVariationalProblem( F=F, u=w, bcs=dirichlet_boundary_conditions(sim), J=fe.derivative(F, w)) solver = fe.NonlinearVariationalSolver(problem=problem, solver_parameters={ "snes_type": "newtonls", "snes_monitor": None, "ksp_type": "preonly", "pc_type": "lu", "mat_type": "aij", "pc_factor_mat_solver_type": "mumps" }) def solve(): solver.solve() return w w, _ = \ sapphire.continuation.solve_with_bounded_regularization_sequence( solve = solve, solution = w, backup_solution = fe.Function(w), regularization_parameter = sim.grashof_number, initial_regularization_sequence = ( 0., sim.grashof_number.__float__())) return w
n = 50 mesh = fd.UnitSquareMesh(n, n) V = fd.VectorFunctionSpace(mesh, "CG", 2) x = ufl.SpatialCoordinate(mesh) expr = ufl.as_vector([ufl.sin(2 * ufl.pi * x[0]), ufl.cos(2 * ufl.pi * x[1])]) u = fd.interpolate(expr, V) u_dot = fd.Function(V) v = fd.TestFunction(V) nu = fd.Constant(0.0001) # for burgers if equation == "heat": nu = fd.Constant(0.1) # for heat M = fd.derivative(fd.inner(u, v) * fd.dx, u) R = -(fd.inner(fd.grad(u) * u, v) + nu * fd.inner(fd.grad(u), fd.grad(v))) * fd.dx if equation == "heat": R = -nu * fd.inner(fd.grad(u), fd.grad(v)) * fd.dx F = fd.action(M, u_dot) - R bc = fd.DirichletBC(V, (0.0, 0.0), "on_boundary") t = 0.0 end = 0.1 tspan = (t, end) state_out = fd.File("result/state.pvd") def ts_monitor(ts, steps, time, X):
def __init__(self, solver): r"""State machine for solving the Gauss-Newton subproblem via the preconditioned conjugate gradient method""" self._assemble = solver._assemble u = solver.state p = solver.parameter E = solver._E dE = derivative(E, u) R = solver._R dR = derivative(R, p) F = solver._F dF_du = derivative(F, u) dF_dp = derivative(F, p) # TODO: Make this an arbitrary RHS -- the solver can set it to the # gradient if we want dJ = solver.gradient bc = solver._bc V = u.function_space() Q = p.function_space() # Create the preconditioned residual and solver z = firedrake.Function(Q) s = firedrake.Function(Q) φ, ψ = firedrake.TestFunction(Q), firedrake.TrialFunction(Q) M = φ * ψ * dx + derivative(dR, p) residual_problem = firedrake.LinearVariationalProblem( M, -dJ, z, form_compiler_parameters=solver._fc_params, constant_jacobian=False) residual_solver = firedrake.LinearVariationalSolver( residual_problem, solver_parameters=solver._solver_params) self._preconditioner = M self._residual = z self._search_direction = s self._residual_solver = residual_solver # Create a variable to store the current solution of the Gauss-Newton # problem and the solutions of the auxiliary tangent sub-problems q = firedrake.Function(Q) v = firedrake.Function(V) w = firedrake.Function(V) # Create linear problem and solver objects for the auxiliary tangent # sub-problems tangent_linear_problem = firedrake.LinearVariationalProblem( dF_du, action(dF_dp, s), w, bc, form_compiler_parameters=solver._fc_params, constant_jacobian=False) tangent_linear_solver = firedrake.LinearVariationalSolver( tangent_linear_problem, solver_parameters=solver._solver_params) adjoint_tangent_linear_problem = firedrake.LinearVariationalProblem( adjoint(dF_du), derivative(dE, u, w), v, bc, form_compiler_parameters=solver._fc_params, constant_jacobian=False) adjoint_tangent_linear_solver = firedrake.LinearVariationalSolver( adjoint_tangent_linear_problem, solver_parameters=solver._solver_params) self._rhs = dJ self._solution = q self._tangent_linear_solution = w self._tangent_linear_solver = tangent_linear_solver self._adjoint_tangent_linear_solution = v self._adjoint_tangent_linear_solver = adjoint_tangent_linear_solver self._product = action(adjoint(dF_dp), v) + derivative(dR, p, s) # Create the update to the residual and the associated solver δz = firedrake.Function(Q) Gs = self._product delta_residual_problem = firedrake.LinearVariationalProblem( M, Gs, δz, form_compiler_parameters=solver._fc_params, constant_jacobian=False) delta_residual_solver = firedrake.LinearVariationalSolver( delta_residual_problem, solver_parameters=solver._solver_params) self._delta_residual = δz self._delta_residual_solver = delta_residual_solver self._residual_energy = 0. self._search_direction_energy = 0. self.reinit()