def facet_normal(self, o): mesh = coarsen(o.ufl_domain()) return firedrake.FacetNormal(mesh)
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 facet_normal_2(mesh): r"""Compute the horizontal component of the unit outward normal vector to a mesh""" ν = firedrake.FacetNormal(mesh) return firedrake.as_vector((ν[0], ν[1]))
j = np.floor(y / (Ly / (ny * n))).astype(int) return kl[i, j] Kinv.dat.data[:, 0, 0] = 1 / \ fix_perm_map(ccenter.dat.data[:, 0], ccenter.dat.data[:, 1]) Kinv.dat.data[:, 1, 1] = 1 / \ fix_perm_map(ccenter.dat.data[:, 0], ccenter.dat.data[:, 1]) # ------- # 3.4) Variational Form # the bilinear and linear forms of the variational problem are defined as: :: a = fd.dot(v, Kinv * u) * fd.dx - fd.div(v) * p * fd.dx + q * fd.div(u) * fd.dx f = fd.Constant(0.0) n = fd.FacetNormal(mesh) L = q * f * fd.dx - fd.Constant(pbar) * fd.inner(v, n) * fd.ds(outlet) # ---- # 3.5) set boundary conditions # The strongly enforced boundary conditions on the BDM space on the top and # bottom of the domain are declared as: :: bc0 = fd.DirichletBC(W.sub(0), fd.Constant(qbar), inlet) bc1 = fd.DirichletBC(W.sub(0), fd.Constant(q0bar), noflow) # ---- # 4) Define and solve the problem # # Now we're ready to solve the variational problem. We define `w` to be a # function to hold the solution on the mixed space. w = fd.Function(W)
def cgls_form(self, problem, mesh, bcs_p): rho = problem.rho mu = problem.mu k = problem.k f = problem.f q, p = fire.TrialFunctions(self._W) w, v = fire.TestFunctions(self._W) n = fire.FacetNormal(mesh) # Stabilizing parameters h = fire.CellDiameter(mesh) has_mesh_characteristic_length = True delta_0 = fire.Constant(1) delta_1 = fire.Constant(-1 / 2) delta_2 = fire.Constant(1 / 2) delta_3 = fire.Constant(1 / 2) # Some good stabilizing methods that I use: # 1) CLGS (Correa and Loula method, it's a Galerkin Least-Squares residual formulation): # * delta_0 = 1 # * delta_1 = -1/2 # * delta_2 = 1/2 # * delta_3 = 1/2 # 2) CLGS (Div): # * delta_0 = 1 # * delta_1 = -1/2 # * delta_2 = 1/2 # * delta_3 = 0 # 3) Original Hughes's adjoint (variational multiscale) method (HVM): # * delta_0 = -1 # * delta_1 = 1/2 # * delta_2 = 0 # * delta_3 = 0 # 4) HVM (Div): # * delta_0 = -1 # * delta_1 = 1/2 # * delta_2 = 1/2 # * delta_3 = 0 # 5) Enhanced HVM (eHVM, this one is proposed by me. It was never published before): # * delta_0 = -1 # * delta_1 = 1/2 # * delta_2 = 1/2 # * delta_3 = 1/2 # I'm currently investigating these modifications in my thesis. They work good for DG methods. if has_mesh_characteristic_length: delta_2 = delta_2 * h * h delta_3 = delta_3 * h * h kappa = rho * k / mu inv_kappa = 1.0 / kappa # Classical mixed terms a = (dot(inv_kappa * q, w) - div(w) * p - delta_0 * v * div(q)) * dx L = -delta_0 * f * v * dx # Add the contributions of the pressure boundary conditions to L for pboundary, iboundary in bcs_p: L -= pboundary * dot(w, n) * ds(iboundary) # Stabilizing terms a += (delta_1 * inner(kappa * (inv_kappa * q + grad(p)), delta_0 * inv_kappa * w + grad(v)) * dx) a += delta_2 * inv_kappa * div(q) * div(w) * dx a += delta_3 * inner(kappa * curl(inv_kappa * q), curl( inv_kappa * w)) * dx L += delta_2 * inv_kappa * f * div(w) * dx return a, L
def sdhm_form(self, problem, mesh, bcs_p, bcs_u): rho = problem.rho mu = problem.mu k = problem.k f = problem.f q, p, lambda_h = fire.split(self.solution) w, v, mu_h = fire.TestFunctions(self._W) n = fire.FacetNormal(mesh) h = fire.CellDiameter(mesh) # Stabilizing parameters has_mesh_characteristic_length = True beta_0 = fire.Constant(1e-15) delta_0 = fire.Constant(1) delta_1 = fire.Constant(-1 / 2) delta_2 = fire.Constant(1 / 2) delta_3 = fire.Constant(1 / 2) # h_avg = (h('+') + h('-')) / 2. beta = beta_0 / h beta_avg = beta_0 / h("+") if has_mesh_characteristic_length: delta_2 = delta_2 * h * h delta_3 = delta_3 * h * h kappa = rho * k / mu inv_kappa = 1.0 / kappa # Classical mixed terms a = (dot(inv_kappa * q, w) - div(w) * p - delta_0 * v * div(q)) * dx L = -delta_0 * f * v * dx # Hybridization terms a += lambda_h("+") * dot(w, n)("+") * dS + mu_h("+") * dot(q, n)("+") * dS a += beta_avg * kappa("+") * (lambda_h("+") - p("+")) * (mu_h("+") - v("+")) * dS # Add the contributions of the pressure boundary conditions to L primal_bc_markers = list(mesh.exterior_facets.unique_markers) for pboundary, iboundary in bcs_p: primal_bc_markers.remove(iboundary) a += (pboundary * dot(w, n) + mu_h * dot(q, n)) * ds(iboundary) a += beta * kappa * (lambda_h - pboundary) * mu_h * ds(iboundary) unprescribed_primal_bc = primal_bc_markers for bc_marker in unprescribed_primal_bc: a += (lambda_h * dot(w, n) + mu_h * dot(q, n)) * ds(bc_marker) a += beta * kappa * lambda_h * mu_h * ds(bc_marker) # Add the (weak) contributions of the velocity boundary conditions to L for uboundary, iboundary, component in bcs_u: if component is not None: dim = mesh.geometric_dimension() bc_array = [] for _ in range(dim): bc_array.append(0.0) bc_array[component] = uboundary bc_as_vector = fire.Constant(bc_array) L += mu_h * dot(bc_as_vector, n) * ds(iboundary) else: L += mu_h * dot(uboundary, n) * ds(iboundary) # Stabilizing terms a += (delta_1 * inner(kappa * (inv_kappa * q + grad(p)), delta_0 * inv_kappa * w + grad(v)) * dx) a += delta_2 * inv_kappa * div(q) * div(w) * dx a += delta_3 * inner(kappa * curl(inv_kappa * q), curl( inv_kappa * w)) * dx L += delta_2 * inv_kappa * f * div(w) * dx return a, L
def facet_normal(self, o): mesh = o.ufl_domain() hierarchy, level = utils.get_level(mesh) new_mesh = hierarchy[level - 1] return firedrake.FacetNormal(new_mesh.ufl_domain())
def nearby_preconditioning_piecewise_experiment_set( A_pre_type,n_pre_type,dim,num_pieces,seed,num_repeats, k_list,h_list,p_list,noise_master_level_list,noise_modifier_list, save_location): """Test nearby preconditioning for a range of parameter values. Performs nearby preconditioning tests for a range of values of k, the mesh size h, and the size of the random noise (which can be specified in terms of k and h). The random noise is piecewise constant on a grid unrelated to the finite-element mesh. Parameters: A_pre_type - string - options are 'constant', giving A_pre = [[1.0,0.0],[0.0,1.0]]. n_pre_type - string - options are 'constant', giving n_pre = 1.0; 'jump_down' giving n_pre = 2/3 on a central square of side length 1/3, and 1 otherwise; and 'jump_up' giving n_pre = 1.5 on a central square of side length 1/3, and 1 otherwise. dim - 2 or 3, the dimension of the problem. num_pieces - see helmholtz.coefficients.PieceWiseConstantCoeffGenerator. seed - see StochasticHelmholtzProblem. num_repeats - see nearby_preconditioning_test. k_list - list of positive floats - the values of k for which we will run experiments. h_list - list of 2-tuples; in each tuple (call it t) t[0] should be a positive float and t[1] should be a float. These specify the values of the mesh size h for which we will run experiments. h = t[0] * k**t[1]. p_list - list of positive ints, the polynomial degrees to run experiments for. Degree >= 5 will be very slow because of the implementation in Firedrake. noise_master_level_list - list of 2-tuples, where each entry of the tuple is a positive float. This defines the values of base_noise_A and base_noise_n to be used in the experiments. Call a given tuple t. Then base_noise_A = t[0] and base_noise_n = t[1]. noise_modifier_list - list of 4-tuples; the entries of each tuple should be floats. Call a given tuple t. This modifies the base noise so that the L^\infty norms of A and n are less than or equal to (respectively) base_noise_A * h**t[0] * k**t[1] and base_noise_n * h**t[2] * k**t[3]. save_location - see utils.write_repeats_to_csv. """ if not(isinstance(A_pre_type,str)): raise TypeError("Input A_pre_type should be a string") elif A_pre_type is not "constant": raise HelmholtzNotImplementedError( "Currently only implemented A_pre_type = 'constant'.") if not(isinstance(n_pre_type,str)): raise TypeError("Input n_pre_type should be a string") if not(isinstance(k_list,list)): raise TypeError("Input k_list should be a list.") elif any(not(isinstance(k,float)) for k in k_list): raise TypeError("Input k_list should be a list of floats.") elif any(k <= 0 for k in k_list): raise TypeError( "Input k_list should be a list of positive floats.") if not(isinstance(h_list,list)): raise TypeError("Input h_list should be a list.") elif any(not(isinstance(h_tuple,tuple)) for h_tuple in h_list): raise TypeError("Input h_list should be a list of tuples.") elif any(len(h_tuple) is not 2 for h_tuple in h_list): raise TypeError("Input h_list should be a list of 2-tuples.") elif any(not(isinstance(h_tuple[0],float)) for h_tuple in h_list)\ or any(h_tuple[0] <= 0 for h_tuple in h_list): raise TypeError( "The first item of every tuple in h_list\ should be a positive float.") elif any(not(isinstance(h_tuple[1],float)) for h_tuple in h_list): raise TypeError( "The second item of every tuple in h_list should be a float.") if not(isinstance(noise_master_level_list,list)): raise TypeError( "Input noise_master_level_list should be a list.") elif any(not(isinstance(noise_tuple,tuple)) for noise_tuple in noise_master_level_list): raise TypeError( "Input noise_master_level_list should be a list of tuples.") elif any(len(noise_tuple) is not 2 for noise_tuple in noise_master_level_list): raise TypeError( "Input noise_master_level_list should be a list of 2-tuples.") elif any(any(not(isinstance(noise_tuple[i],float)) for i in range(len(noise_tuple))) for noise_tuple in noise_master_level_list): raise TypeError( "Input noise_master_level_list\ should be a list of 2-tuples of floats.") if not(isinstance(noise_modifier_list,list)): raise TypeError("Input noise_modifier_list should be a list.") elif any(not(isinstance(mod_tuple,tuple)) for mod_tuple in noise_modifier_list): raise TypeError( "Input noise_modifier_list should be a list of tuples.") elif any(len(mod_tuple) is not 4 for mod_tuple in noise_modifier_list): raise TypeError( "Input noise_modifier_list should be a list of 4-tuples.") elif any(any(not(isinstance(mod_tuple[i],float)) for i in range(len(mod_tuple))) for mod_tuple in noise_modifier_list): raise TypeError( "Input noise_modifier_list\ should be a list of 4-tuples of floats.") for k in k_list: for h_tuple in h_list: for p in p_list: h = h_tuple[0] * k**h_tuple[1] mesh_points = hh_utils.h_to_num_cells(h,dim) mesh = fd.UnitSquareMesh(mesh_points,mesh_points) V = fd.FunctionSpace(mesh, "CG", p) f = 0.0 d = fd.as_vector([1.0/fd.sqrt(2.0),1.0/fd.sqrt(2.0)]) x = fd.SpatialCoordinate(mesh) nu = fd.FacetNormal(mesh) g=1j*k*fd.exp(1j*k*fd.dot(x,d))*(fd.dot(d,nu)-1) if A_pre_type is "constant": A_pre = fd.as_matrix([[1.0,0.0],[0.0,1.0]]) if n_pre_type is "constant": n_pre = 1.0 elif n_pre_type is "jump_down": n_pre = (2.0/3.0)\ + hh_utils.nd_indicator( x,1.0/3.0, np.array([[1.0/3.0,2.0/3.0], [1.0/3.0,2.0/3.0], [1.0/3.0,2.0/3.0]] ) ) elif n_pre_type is "jump_up": n_pre = 1.5\ + hh_utils.nd_indicator( x,-1.0/2.0, np.array([[1.0/3.0,2.0/3.0], [1.0/3.0,2.0/3.0], [1.0/3.0,2.0/3.0]] ) ) for noise_master in noise_master_level_list: A_noise_master = noise_master[0] n_noise_master = noise_master[1] for modifier in noise_modifier_list: if fd.COMM_WORLD.rank == 0: print(k,h_tuple,noise_master,modifier) A_modifier = h ** modifier[0] * k**modifier[1] n_modifier = h ** modifier[2] * k**modifier[3] A_noise_level = A_noise_master * A_modifier n_noise_level = n_noise_master * n_modifier A_stoch = coeff.PiecewiseConstantCoeffGenerator( mesh,num_pieces,A_noise_level,A_pre,[2,2]) n_stoch = coeff.PiecewiseConstantCoeffGenerator( mesh,num_pieces,n_noise_level,n_pre,[1]) np.random.seed(seed) GMRES_its = nearby_preconditioning_experiment( V,k,A_pre,A_stoch,n_pre,n_stoch,f,g,num_repeats) if fd.COMM_WORLD.rank == 0: hh_utils.write_GMRES_its( GMRES_its,save_location, {'k' : k, 'h_tuple' : h_tuple, 'p' : p, 'num_pieces' : num_pieces, 'A_pre_type' : A_pre_type, 'n_pre_type' : n_pre_type, 'noise_master' : noise_master, 'modifier' : modifier, 'num_repeats' : num_repeats } )
def _wall_flux(z, g, boundary_ids): n = firedrake.FacetNormal(z.ufl_domain()) h, q = firedrake.split(z) # Mirror value of the fluid momentum q_ex = q - 2 * inner(q, n) * n return _boundary_flux(z, h, q_ex, g, boundary_ids)
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
def heat_flux(w_sol, t, OUTLET): u_sol, _ = fd.split(w_sol) scale_factor = 5.0 n = fd.FacetNormal(w_sol.ufl_domain()) return fd.assemble( fd.Constant(-scale_factor) * inner(t * u_sol, n) * ds(OUTLET))