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 __init__(self, problem, callback=(lambda s: None), memory=5): self._setup(problem, callback) self.update_state() self.update_adjoint_state() Q = self.parameter.function_space() self._memory = memory q, dJ = self.search_direction, self.gradient M = firedrake.TrialFunction(Q) * firedrake.TestFunction(Q) * dx f = firedrake.Function(Q) problem = firedrake.LinearVariationalProblem( M, dJ, f, form_compiler_parameters=self._fc_params) self._search_direction_solver = firedrake.LinearVariationalSolver( problem, solver_parameters=self._solver_params) self._search_direction_solver.solve() q.assign(-f) self._f = f self._rho = [] self._ps = [self.parameter.copy(deepcopy=True)] self._fs = [q.copy(deepcopy=True)] self._fs[-1] *= -1 self._callback(self)
def _initialise_problem(self): """ Set up the Firedrake machinery for solving the problem. """ # Define trial and test functions on the space self._u = fd.TrialFunction(self.V) self._v = fd.TestFunction(self.V) # Define sesquilinear form and antilinear functional self._a = self._define_form(self._A, self._n) self._set_L() self._set_pre() # Define problem and solver (following code courtesy of Lawrence # Mitchell, via Slack) problem = fd.LinearVariationalProblem(self._a, self._L, self.u_h, aP=self._a_pre, constant_jacobian=False) self._solver = fd.LinearVariationalSolver( problem, solver_parameters=self._solver_parameters) self._initialised = True
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 update_solver(self): if self._nontrivial: self.solver = [] for i in range(self.n_stages): prob = firedrake.LinearVariationalProblem( self.a_rk, self.l_rk, self.tendency[i]) solver = firedrake.LinearVariationalSolver( prob, options_prefix=self.name + '_k{:}'.format(i), solver_parameters=self.solver_parameters) self.solver.append(solver)
def __init__(self, problem, callback=(lambda s: None)): self._setup(problem, callback) self.update_state() self.update_adjoint_state() q, dJ = self.search_direction, self.gradient Q = q.function_space() M = firedrake.TrialFunction(Q) * firedrake.TestFunction(Q) * dx problem = firedrake.LinearVariationalProblem( M, -dJ, q, form_compiler_parameters=self._fc_params) self._search_direction_solver = firedrake.LinearVariationalSolver( problem, solver_parameters=self._solver_params) self.update_search_direction() self._callback(self)
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 __init__(self, problem, u_degree=1, p_degree=1, lambda_degree=1, method="cgls"): self.problem = problem self.method = method mesh = problem.mesh bcs_p = problem.bcs_p bcs_u = problem.bcs_u if method == "cgls": pressure_family = "CG" velocity_family = "CG" dirichlet_method = "topological" elif method == "dgls": pressure_family = "DG" velocity_family = "DG" dirichlet_method = "geometric" elif method == "sdhm": pressure_family = "DG" velocity_family = "DG" trace_family = "HDiv Trace" dirichlet_method = "topological" else: raise ValueError( f"Invalid FEM for solving Darcy Flow. Method provided: {method}" ) self._U = fire.VectorFunctionSpace(mesh, velocity_family, u_degree) self._V = fire.FunctionSpace(mesh, pressure_family, p_degree) if method == "cgls" or method == "dgls": self._W = self._U * self._V if method == "sdhm": self._T = fire.FunctionSpace(mesh, trace_family, lambda_degree) self._W = self._U * self._V * self._T self.solution = fire.Function(self._W) if method == "cgls": self.u, self.p = self.solution.split() self._a, self._L = self.cgls_form(problem, mesh, bcs_p) if method == "dgls": self.u, self.p = self.solution.split() self._a, self._L = self.dgls_form(problem, mesh, bcs_p) if method == "sdhm": self.u, self.p, self.tracer = self.solution.split() self._a, self._L = self.sdhm_form(problem, mesh, bcs_p, bcs_u) self.u.rename("u", "label") self.p.rename("p", "label") if method == "sdhm": self.solver_parameters = { "snes_type": "ksponly", "mat_type": "matfree", "pmat_type": "matfree", "ksp_type": "preonly", "pc_type": "python", # Use the static condensation PC for hybridized problems # and use a direct solve on the reduced system for lambda_h "pc_python_type": "firedrake.SCPC", "pc_sc_eliminate_fields": "0, 1", "condensed_field": { "ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps", }, } F = self._a - self._L nvp = fire.NonlinearVariationalProblem(F, self.solution) self.solver = fire.NonlinearVariationalSolver( nvp, solver_parameters=self.solver_parameters) else: self.bcs = [] rho = self.problem.rho for uboundary, iboundary, component in bcs_u: if component is not None: self.bcs.append( DirichletExpressionBC( self._W.sub(0).sub(component), rho * uboundary, iboundary, method=dirichlet_method, )) else: self.bcs.append( DirichletExpressionBC(self._W.sub(0), rho * uboundary, iboundary, method=dirichlet_method)) # self.solver_parameters = { # # This setup is suitable for 3D # 'ksp_type': 'lgmres', # 'pc_type': 'lu', # 'mat_type': 'aij', # 'ksp_rtol': 1e-8, # 'ksp_max_it': 2000, # 'ksp_monitor': None # } self.solver_parameters = { "mat_type": "aij", "ksp_type": "preonly", "pc_type": "lu", "pc_factor_mat_solver_type": "mumps", } lvp = fire.LinearVariationalProblem(self._a, self._L, self.solution, bcs=self.bcs) self.solver = fire.LinearVariationalSolver( lvp, solver_parameters=self.solver_parameters)
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()
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)
if post_process: # Scalar post-process # Define elemental matrices / vectors for the local problems pp, psi = fd.TrialFunctions(Wpp) ww, phi = fd.TestFunctions(Wpp) # linear system a_pp = fd.inner ( fd.grad ( ww ) , Dm*fd.grad ( pp ) ) * fd.dx + fd.inner (ww , psi ) * fd.dx + \ fd.inner ( phi , pp ) * fd.dx L_pp = -fd.inner(fd.grad(ww), qh) * fd.dx + fd.inner(phi, ch) * fd.dx ch_pp = fd.Function(Wpp) uh_pp = fd.Function(PP, name="c-star") # scalar post-process ppproblem = fd.LinearVariationalProblem(a_pp, L_pp, ch_pp) ppsolver = fd.LinearVariationalSolver(ppproblem) A = fd.Tensor(a_pp) b = fd.Tensor(L_pp) # %% # 4) Solve problem # solve # set boundary conditions bc = fd.DirichletBC(W.sub(2), c0, "on_boundary") # this really need? problem = fd.NonlinearVariationalProblem(a - L, w, bcs=bc) rtol = 1e-4 hybrid_solver_params = { "snes_type": "ksponly",
# full weak form F = F_t + F_a + F_d # %% # 4) Solve problem # ----------------- wave_speed = fd.conditional(fd.lt(np.abs(vnorm), tol), h_E / tol, h_E / vnorm) # CFL dt = 0.1 * fd.interpolate(wave_speed, DG1).dat.data.min() Dt.assign(dt) outfile = fd.File("plots/adr_dg.pvd") limiter = fd.VertexBasedLimiter(DG1) # Kuzmin slope limiter c_ = fd.Function(DG1, name="c") problem = fd.LinearVariationalProblem(fd.lhs(F), fd.rhs(F), c_, bcs=bc) solver = fd.LinearVariationalSolver(problem) # initialize timestep t = 0.0 it = 0 p = 0 while t < sim_time: # check dt dt = np.min([sim_time - t, dt]) if (t + dt > ptimes[p]): dt = ptimes[p] - t Dt.assign(dt) # move next time step
def newton_search(E, u, bc, tolerance, scale, max_iterations=50, armijo=1e-4, contraction_factor=0.5, form_compiler_parameters={}, solver_parameters={'ksp_type': 'preonly', 'pc_type': 'lu'}): r"""Find the minimizer of a convex functional Parameters ---------- E : firedrake.Form The functional to be minimized u0 : firedrake.Function Initial guess for the minimizer tolerance : float Stopping criterion for the optimization procedure scale : firedrake.Form A positive scale functional by which to measure the objective max_iterations : int, optional Optimization procedure will stop at this many iterations regardless of convergence armijo : float, optional The constant in the Armijo condition (see Nocedal and Wright) contraction_factor : float, optional The amount by which to backtrack in the line search if the Armijo condition is not satisfied form_compiler_parameters : dict, optional Extra options to pass to the firedrake form compiler solver_parameters : dict, optional Extra options to pass to the linear solver Returns ------- firedrake.Function The approximate minimizer of `E` to within tolerance """ F = firedrake.derivative(E, u) H = firedrake.derivative(F, u) v = firedrake.Function(u.function_space()) dE_dv = firedrake.action(F, v) def assemble(*args, **kwargs): return firedrake.assemble( *args, **kwargs, form_compiler_parameters=form_compiler_parameters) problem = firedrake.LinearVariationalProblem(H, -F, v, bc, form_compiler_parameters=form_compiler_parameters, constant_jacobian=False) solver = firedrake.LinearVariationalSolver(problem, solver_parameters=solver_parameters) n = 0 while True: # Compute a search direction solver.solve() # Compute the directional derivative, check if we're done slope = assemble(dE_dv) assert slope < 0 if (abs(slope) < assemble(scale) * tolerance) or (n >= max_iterations): return u # Backtracking search E0 = assemble(E) α = firedrake.Constant(1) Eα = firedrake.replace(E, {u: u + α * v}) while assemble(Eα) > E0 + armijo * α.values()[0] * slope: α.assign(α * contraction_factor) u.assign(u + α * v) n += 1
def initialize(self, pc): if pc.getType() != "python": raise ValueError("Expecting PC type python") prefix = pc.getOptionsPrefix() + "diagfft_" # we assume P has things stuffed inside of it _, P = pc.getOperators() context = P.getPythonContext() appctx = context.appctx self.appctx = appctx # all at once solution passed through the appctx self.w_all = appctx.get("w_all", None) # FunctionSpace checks test, trial = context.a.arguments() if test.function_space() != trial.function_space(): raise ValueError("Pressure space test and trial space differ") W = test.function_space() # basic model function space get_blockV = appctx.get("get_blockV", None) self.blockV = get_blockV() M = int(W.dim() / self.blockV.dim()) assert (self.blockV.dim() * M == W.dim()) self.M = M self.NM = W.dim() # Input/Output wrapper Functions self.xf = fd.Function(W) # input self.yf = fd.Function(W) # output # Gamma coefficients Nt = M exponents = np.arange(Nt) / Nt alphav = appctx.get("alpha", None) self.Gam = alphav**exponents # Di coefficients thetav = appctx.get("theta", None) Dt = appctx.get("dt", None) C1col = np.zeros(Nt) C2col = np.zeros(Nt) C1col[:2] = np.array([1, -1]) / Dt C2col[:2] = np.array([thetav, 1 - thetav]) self.D1 = np.sqrt(Nt) * fft(self.Gam * C1col) self.D2 = np.sqrt(Nt) * fft(self.Gam * C2col) # Block system setup # First need to build the vector function space version of # blockV mesh = self.blockV.mesh() Ve = self.blockV.ufl_element() if isinstance(Ve, fd.MixedElement): MixedCpts = [] self.ncpts = Ve.num_sub_elements() for cpt in range(Ve.num_sub_elements()): SubV = Ve.sub_elements()[cpt] if isinstance(SubV, fd.FiniteElement): MixedCpts.append(fd.VectorElement(SubV, dim=2)) elif isinstance(SubV, fd.VectorElement): shape = (2, SubV.num_sub_elements()) MixedCpts.append(fd.TensorElement(SubV, shape)) elif isinstance(SubV, fd.TensorElement): shape = (2, ) + SubV._shape MixedCpts.append(fd.TensorElement(SubV, shape)) else: raise NotImplementedError dim = len(MixedCpts) self.CblockV = np.prod( [fd.FunctionSpace(mesh, MixedCpts[i]) for i in range(dim)]) else: self.ncpts = 1 if isinstance(Ve, fd.FiniteElement): self.CblockV = fd.FunctionSpace(mesh, fd.VectorElement(Ve, dim=2)) elif isinstance(Ve, fd.VectorElement): shape = (2, Ve.num_sub_elements()) self.CblockV = fd.FunctionSpace(mesh, fd.TensorElement(Ve, shape)) elif isinstance(Ve, fd.TensorElement): shape = (2, ) + Ve._shape self.CblockV = fd.FunctionSpace(mesh, fd.TensorElement(Ve, shape)) else: raise NotImplementedError # Now need to build the block solver vs = fd.TestFunctions(self.CblockV) self.u0 = fd.Function(self.CblockV) # we will create a linearisation us = fd.split(self.u0) # extract the real and imaginary parts vsr = [] vsi = [] usr = [] usi = [] if isinstance(Ve, fd.MixedElement): N = Ve.num_sub_elements() for i in range(N): SubV = Ve.sub_elements()[i] if len(SubV.value_shape()) == 0: vsr.append(vs[i][0]) vsi.append(vs[i][1]) usr.append(us[i][0]) usi.append(us[i][1]) elif len(SubV.value_shape()) == 1: vsr.append(vs[i][0, :]) vsi.append(vs[i][1, :]) usr.append(us[i][0, :]) usi.append(us[i][1, :]) elif len(SubV.value_shape()) == 2: vsr.append(vs[i][0, :, :]) vsi.append(vs[i][1, :, :]) usr.append(us[i][0, :, :]) usi.append(us[i][1, :, :]) else: raise NotImplementedError else: if isinstance(Ve, fd.FiniteElement): vsr.append(vs[0]) vsi.append(vs[1]) usr.append(us[0]) usi.append(us[1]) elif isinstance(Ve, fd.VectorElement): vsr.append(vs[0, :]) vsi.append(vs[1, :]) usr.append(self.u0[0, :]) usi.append(self.u0[1, :]) elif isinstance(Ve, fd.TensorElement): vsr.append(vs[0, :]) vsi.append(vs[1, :]) usr.append(self.u0[0, :]) usi.append(self.u0[1, :]) else: raise NotImplementedError # input and output functions self.Jprob_in = fd.Function(self.CblockV) self.Jprob_out = fd.Function(self.CblockV) # A place to store all the inputs to the block problems self.xfi = fd.Function(W) self.xfr = fd.Function(W) # Building the nonlinear operator self.Jsolvers = [] self.Js = [] form_mass = appctx.get("form_mass", None) form_function = appctx.get("form_function", None) # setting up the Riesz map # input for the Riesz map self.xtemp = fd.Function(self.CblockV) v = fd.TestFunction(self.CblockV) u = fd.TrialFunction(self.CblockV) a = fd.assemble(fd.inner(u, v) * fd.dx) self.Proj = fd.LinearSolver(a, options_prefix=prefix + "mass_") # building the block problem solvers for i in range(M): D1i = fd.Constant(np.imag(self.D1[i])) D1r = fd.Constant(np.real(self.D1[i])) D2i = fd.Constant(np.imag(self.D2[i])) D2r = fd.Constant(np.real(self.D2[i])) # pass sigma into PC: sigma = self.D1[i]**2 / self.D2[i] sigma_inv = self.D2[i]**2 / self.D1[i] appctx_h = appctx.copy() appctx_h["sr"] = fd.Constant(np.real(sigma)) appctx_h["si"] = fd.Constant(np.imag(sigma)) appctx_h["sinvr"] = fd.Constant(np.real(sigma_inv)) appctx_h["sinvi"] = fd.Constant(np.imag(sigma_inv)) appctx_h["D2r"] = D2r appctx_h["D2i"] = D2i appctx_h["D1r"] = D1r appctx_h["D1i"] = D1i A = (D1r * form_mass(*usr, *vsr) - D1i * form_mass(*usi, *vsr) + D2r * form_function(*usr, *vsr) - D2i * form_function(*usi, *vsr) + D1r * form_mass(*usi, *vsi) + D1i * form_mass(*usr, *vsi) + D2r * form_function(*usi, *vsi) + D2i * form_function(*usr, *vsi)) # The linear operator J = fd.derivative(A, self.u0) # The rhs v = fd.TestFunction(self.CblockV) L = fd.inner(v, self.Jprob_in) * fd.dx block_prefix = prefix + str(i) + '_' jprob = fd.LinearVariationalProblem(J, L, self.Jprob_out) Jsolver = fd.LinearVariationalSolver(jprob, appctx=appctx_h, options_prefix=block_prefix) self.Jsolvers.append(Jsolver)
def solve(self, dt, D0, u, A, D_inflow=None, **kwargs): r"""Propogate the damage forward by one timestep This function uses a Runge-Kutta scheme to upwind damage (limiting damage diffusion) while sourcing and sinking damage assocaited with crevasse opening/crevasse healing Parameters ---------- dt : float Timestep D0 : firedrake.Function initial damage feild should be discontinuous u : firedrake.Function Ice velocity A : firedrake.Function fluidity parameter D_inflow : firedrake.Function Damage of the upstream ice that advects into the domain Returns ------- D : firedrake.Function Ice damage at `t + dt` """ D_inflow = D_inflow if D_inflow is not None else D0 Q = D0.function_space() dD, φ = firedrake.TrialFunction(Q), firedrake.TestFunction(Q) d = φ * dD * dx D = D0.copy(deepcopy=True) n = firedrake.FacetNormal(Q.mesh()) un = 0.5 * (inner(u, n) + abs(inner(u, n))) L1 = dt * (D * div(φ * u) * dx - φ * max_value(inner(u, n), 0) * D * ds - φ * min_value(inner(u, n), 0) * D_inflow * ds - (φ('+') - φ('-')) * (un('+') * D('+') - un('-') * D('-')) * dS) D1 = firedrake.Function(Q) D2 = firedrake.Function(Q) L2 = firedrake.replace(L1, {D: D1}) L3 = firedrake.replace(L1, {D: D2}) dq = firedrake.Function(Q) # Three-stage strong structure-preserving Runge Kutta (SSPRK3) method params = { 'ksp_type': 'preonly', 'pc_type': 'bjacobi', 'sub_pc_type': 'ilu' } prob1 = firedrake.LinearVariationalProblem(d, L1, dq) solv1 = firedrake.LinearVariationalSolver(prob1, solver_parameters=params) prob2 = firedrake.LinearVariationalProblem(d, L2, dq) solv2 = firedrake.LinearVariationalSolver(prob2, solver_parameters=params) prob3 = firedrake.LinearVariationalProblem(d, L3, dq) solv3 = firedrake.LinearVariationalSolver(prob3, solver_parameters=params) solv1.solve() D1.assign(D + dq) solv2.solve() D2.assign(0.75 * D + 0.25 * (D1 + dq)) solv3.solve() D.assign((1.0 / 3.0) * D + (2.0 / 3.0) * (D2 + dq)) # Increase/decrease damage depending on stress and strain rates ε = sym(grad(u)) ε_1 = eigenvalues(ε)[0] σ = M(ε, A) σ_e = sqrt(inner(σ, σ) - det(σ)) ε_h = firedrake.Constant(self.healing_strain_rate) σ_d = firedrake.Constant(self.damage_stress) γ_h = firedrake.Constant(self.healing_rate) γ_d = firedrake.Constant(self.damage_rate) healing = γ_h * min_value(ε_1 - ε_h, 0) fracture = γ_d * conditional(σ_e - σ_d > 0, ε_1, 0.) * (1 - D) # Clamp damage field to [0, 1] D.project(min_value(max_value(D + dt * (healing + fracture), 0), 1)) return D