def S(self, u_, p_, ivar=None, tang=False): C_ = variable(self.kin.C(u_)) stress = constantvalue.zero((3, 3)) # volumetric (kinematic) growth if self.mat_growth: theta_ = ivar["theta"] # material has to be evaluated with C_e only, however total S has # to be computed by differentiating w.r.t. C (S = 2*dPsi/dC) self.mat = materiallaw(self.C_e(C_, theta_), self.I) else: self.mat = materiallaw(C_, self.I) m = 0 for matlaw in self.matmodels: stress += self.add_stress_mat(matlaw, self.matparams[m], ivar, C_) m += 1 # add remodeled material if self.mat_growth and self.mat_remodel: self.stress_base = stress self.stress_remod = constantvalue.zero((3, 3)) m = 0 for matlaw in self.matmodels_remod: self.stress_remod += self.add_stress_mat( matlaw, self.matparams_remod[m], ivar, C_) m += 1 # update the stress expression: S = (1-phi(theta)) * S_base + phi(theta) * S_remod stress = ( 1. - self.phi_remod(theta_) ) * self.stress_base + self.phi_remod(theta_) * self.stress_remod # if we have p (hydr. pressure) as variable in a 2-field functional if self.incompr_2field: if self.mat_growth: # TeX: S_{\mathrm{vol}} = -2 \frac{\partial[p(J^{\mathrm{e}}-1)]}{\partial \boldsymbol{C}} stress += -2. * diff( p_ * (sqrt(det(self.C_e(C_, theta_))) - 1.), C_) else: # TeX: S_{\mathrm{vol}} = -2 \frac{\partial[p(J-1)]}{\partial \boldsymbol{C}} = -Jp\boldsymbol{C}^{-1} stress += -2. * diff(p_ * (sqrt(det(C_)) - 1.), C_) if tang: return 2. * diff(stress, C_) else: return stress
def rP(self, u, alpha, v, beta): w_1 = self.w(1) a = self.a sigma = self.sigma eps = self.eps return inner(sqrt(a(alpha))*sigma(v) + diff(a(alpha), alpha)/sqrt(a(alpha))*sigma(u)*beta, sqrt(a(alpha))*eps(v) + diff(a(alpha), alpha)/sqrt(a(alpha))*eps(u)*beta) + \ 2*w_1*self.ell ** 2 * dot(grad(beta), grad(beta))
def rP(self, u, alpha, v, beta): w_1 = self.w(1) a = self.a sigma = self.sigma eps = u.dx(0) return (sqrt(a(alpha))*sigma(v) + diff(a(alpha), alpha)/sqrt(a(alpha))*sigma(u)*beta)* \ (sqrt(a(alpha))*v.dx(0) + diff(a(alpha), alpha)/sqrt(a(alpha))*eps*beta) + \ 2*w_1*self.ell ** 2 * beta.dx(0)*beta.dx(0)
def rN(self, u, alpha, beta): a = self.a w = self.w sigma = self.sigma eps = u.dx(0) da = diff(a(alpha), alpha) dda = diff(diff(a(alpha), alpha), alpha) ddw = diff(diff(w(alpha), alpha), alpha) return -(1. / 2. * (dda - 2 * da**2. / a(alpha)) * inner(sigma(u), eps)) * beta**2.
def rN(self, u, alpha, beta): a = self.a w = self.w sigma = self.sigma eps = self.eps da = diff(a(alpha), alpha) dda = diff(diff(a(alpha), alpha), alpha) ddw = diff(diff(w(alpha), alpha), alpha) return -(1. / 2. * (dda - 2. * da**2. / a(alpha)) * inner(sigma(u), eps(u)) + 1. / 2. * ddw) * beta**2.
def rN(self, u, alpha, beta): a = self.a w = self.w sigma = self.sigma eps = self.eps da = diff(a(alpha), alpha) dda = diff(diff(a(alpha), alpha), alpha) ddw = diff(diff(w(alpha), alpha), alpha) eps0 = self.eps0 sigma0 = self.sigma0 Wt = 1. / 2. * inner(sigma(u) - sigma0(), eps(u) - eps0()) return -((dda - 2. * da**2.) / a(alpha) * Wt) * beta**2.
def eigenstate_legacy(A): """Eigenvalues and eigenprojectors of the 3x3 (real-valued) tensor A. Provides the spectral decomposition A = sum_{a=0}^{2} λ_a * E_a with eigenvalues λ_a and their associated eigenprojectors E_a = n_a^R x n_a^L ordered by magnitude. The eigenprojectors of eigenvalues with multiplicity n are returned as 1/n-fold projector. Note: Tensor A must not have complex eigenvalues! """ if ufl.shape(A) != (3, 3): raise RuntimeError( f"Tensor A of shape {ufl.shape(A)} != (3, 3) is not supported!") # eps = 1.0e-10 # A = ufl.variable(A) # # --- determine eigenvalues λ0, λ1, λ2 # # additively decompose: A = tr(A) / 3 * I + dev(A) = q * I + B q = ufl.tr(A) / 3 B = A - q * ufl.Identity(3) # observe: det(λI - A) = 0 with shift λ = q + ω --> det(ωI - B) = 0 = ω**3 - j * ω - b j = ufl.tr( B * B ) / 2 # == -I2(B) for trace-free B, j < 0 indicates A has complex eigenvalues b = ufl.tr(B * B * B) / 3 # == I3(B) for trace-free B # solve: 0 = ω**3 - j * ω - b by substitution ω = p * cos(phi) # 0 = p**3 * cos**3(phi) - j * p * cos(phi) - b | * 4 / p**3 # 0 = 4 * cos**3(phi) - 3 * cos(phi) - 4 * b / p**3 | --> p := sqrt(j * 4 / 3) # 0 = cos(3 * phi) - 4 * b / p**3 # 0 = cos(3 * phi) - r with -1 <= r <= +1 # phi_k = [acos(r) + (k + 1) * 2 * pi] / 3 for k = 0, 1, 2 p = 2 / ufl.sqrt(3) * ufl.sqrt(j + eps**2) # eps: MMM r = 4 * b / p**3 r = ufl.Max(ufl.Min(r, +1 - eps), -1 + eps) # eps: LMM, MMH phi = ufl.acos(r) / 3 # sorted eigenvalues: λ0 <= λ1 <= λ2 λ0 = q + p * ufl.cos(phi + 2 / 3 * ufl.pi) # low λ1 = q + p * ufl.cos(phi + 4 / 3 * ufl.pi) # middle λ2 = q + p * ufl.cos(phi) # high # # --- determine eigenprojectors E0, E1, E2 # E0 = ufl.diff(λ0, A).T E1 = ufl.diff(λ1, A).T E2 = ufl.diff(λ2, A).T # return [λ0, λ1, λ2], [E0, E1, E2]
def holzapfelogden_dev(self, params, f0, s0, C): # anisotropic invariants - keep in mind that for growth, self.C is the elastic part of C (hence != to function input variable C) I4 = dot(dot(self.C,f0), f0) I6 = dot(dot(self.C,s0), s0) I8 = dot(dot(self.C,s0), f0) # to guarantee initial configuration is stress-free (in case of initially non-orthogonal fibers f0 and s0) I8 -= dot(f0,s0) a_0, b_0 = params['a_0'], params['b_0'] a_f, b_f = params['a_f'], params['b_f'] a_s, b_s = params['a_s'], params['b_s'] a_fs, b_fs = params['a_fs'], params['b_fs'] try: fiber_comp = params['fiber_comp'] except: fiber_comp = False # conditional parameters: fibers are only active in tension if fiber_comp is False if not fiber_comp: a_f_c = conditional(ge(I4,1.), a_f, 0.) a_s_c = conditional(ge(I6,1.), a_s, 0.) else: a_f_c = a_f a_s_c = a_s # Holzapfel-Ogden (Holzapfel and Ogden 2009) material w/o split applied to invariants I4, I6, I8 (Sansour 2008) psi_dev = a_0/(2.*b_0)*(exp(b_0*(self.Ic_bar-3.)) - 1.) + \ a_f_c/(2.*b_f)*(exp(b_f*(I4-1.)**2.) - 1.) + a_s_c/(2.*b_s)*(exp(b_s*(I6-1.)**2.) - 1.) + \ a_fs/(2.*b_fs)*(exp(b_fs*I8**2.) - 1.) S = 2.*diff(psi_dev,C) return S
def hyperelasticity(domain, q, p, nf=0): # Based on https://github.com/firedrakeproject/firedrake-bench/blob/experiments/forms/firedrake_forms.py V = ufl.FunctionSpace(domain, ufl.VectorElement('P', domain.ufl_cell(), q)) P = ufl.FunctionSpace(domain, ufl.VectorElement('P', domain.ufl_cell(), p)) v = ufl.TestFunction(V) du = ufl.TrialFunction(V) # Incremental displacement u = ufl.Coefficient(V) # Displacement from previous iteration B = ufl.Coefficient(V) # Body force per unit mass # Kinematics I = ufl.Identity(domain.ufl_cell().topological_dimension()) F = I + ufl.grad(u) # Deformation gradient C = F.T*F # Right Cauchy-Green tensor E = (C - I)/2 # Euler-Lagrange strain tensor E = ufl.variable(E) # Material constants mu = ufl.Constant(domain) # Lame's constants lmbda = ufl.Constant(domain) # Strain energy function (material model) psi = lmbda/2*(ufl.tr(E)**2) + mu*ufl.tr(E*E) S = ufl.diff(psi, E) # Second Piola-Kirchhoff stress tensor PK = F*S # First Piola-Kirchoff stress tensor # Variational problem it = ufl.inner(PK, ufl.grad(v)) - ufl.inner(B, v) f = [ufl.Coefficient(P) for _ in range(nf)] return ufl.derivative(reduce(ufl.inner, list(map(ufl.div, f)) + [it])*ufl.dx, u, du)
def guccione_dev(self, params, f0, s0, C): n0 = cross(f0, s0) # anisotropic invariants - keep in mind that for growth, self.E is the elastic part of E E_ff = dot(dot(self.E, f0), f0) # fiber GL strain E_ss = dot(dot(self.E, s0), s0) # cross-fiber GL strain E_nn = dot(dot(self.E, n0), n0) # radial GL strain E_fs = dot(dot(self.E, f0), s0) E_fn = dot(dot(self.E, f0), n0) E_sn = dot(dot(self.E, s0), n0) c_0 = params['c_0'] b_f = params['b_f'] b_t = params['b_t'] b_fs = params['b_fs'] Q = b_f * E_ff**2. + b_t * (E_ss**2. + E_nn**2. + 2. * E_sn**2. ) + b_fs * (2. * E_fs**2. + 2. * E_fn**2.) psi_dev = 0.5 * c_0 * (exp(Q) - 1.) S = 2. * diff(psi_dev, C) return S
def stress(self, strain, alpha): # Differentiate the elastic energy w.r.t. the strain tensor eps_ = ufl.variable(strain) # Derivative of energy w.r.t. the strain tensor to obtain the stress # tensor sigma = ufl.diff(self.elastic_energy_density_strain(eps_, alpha), eps_) return sigma
def s(e): """ Stress as function of strain from strain energy function """ e = ufl.variable(e) W = lamé_μ * e * e + lamé_λ / 2 * e**2 # Saint-Venant Kirchhoff s = ufl.diff(W, e) return s
def S(E): """ Stress as function of strain from strain energy function """ E = ufl.variable(E) W = lamé_μ * ufl.inner(E, E) + lamé_λ / 2 * ufl.tr(E)**2 # Saint-Venant Kirchhoff S = ufl.diff(W, E) return S
def sussmanbathe_vol(self, params, C): kappa = params['kappa'] psi_vol = (kappa / 2.) * (sqrt(self.IIIc) - 1.)**2. S = 2. * diff(psi_vol, C) return S
def stvenantkirchhoff(self, params, C): Emod, nu = params['Emod'], params['nu'] psi = Emod*nu/( 2.*(1.+nu)*(1.-2.*nu) ) * self.trE**2. + Emod/(2.*(1.+nu)) * self.trE2 S = 2.*diff(psi,C) return S
def ogden_vol(self, params, C): kappa = params['kappa'] psi_vol = (kappa / 4.) * (self.IIIc - 2. * ln(sqrt(self.IIIc)) - 1.) S = 2. * diff(psi_vol, C) return S
def res_dtheta_growth(self, u_, p_, ivar, theta_old_, thres, dt, rquant): theta_ = ivar["theta"] grfnc = growthfunction(theta_, self.I) try: omega = self.gandrparams['thres_tol'] except: omega = 0 try: reduc = self.gandrparams['trigger_reduction'] except: reduc = 1 assert (reduc <= 1 and reduc > 0) # threshold should not be lower than specified (only relevant for multiscale analysis, where threshold is set element-wise) threshold = conditional(gt(thres, self.gandrparams['growth_thres']), (1. + omega) * thres, self.gandrparams['growth_thres']) # trace of elastic Mandel stress if self.growth_trig == 'volstress': trigger = reduc * tr(self.M_e(u_, p_, self.kin.C(u_), ivar)) # elastic fiber stretch elif self.growth_trig == 'fibstretch': trigger = reduc * self.fibstretch_e(self.kin.C(u_), theta_, self.kin.fib_funcs[0]) else: raise NameError("Unknown growth_trig!") # growth function ktheta = grfnc.grfnc1(trigger, threshold, self.gandrparams) # growth residual r_growth = theta_ - theta_old_ - ktheta * (trigger - threshold) * dt # tangent K_growth = diff(r_growth, theta_) # increment del_theta = -r_growth / K_growth if rquant == 'res_del': return r_growth, del_theta elif rquant == 'ktheta': return ktheta elif rquant == 'tang': return K_growth else: raise NameError("Unknown return quantity!")
def neohooke_dev(self, params, C): mu = params['mu'] # classic NeoHookean material (isochoric version) psi_dev = (mu / 2.) * (self.Ic_bar - 3.) S = 2. * diff(psi_dev, C) return S
def mooneyrivlin_dev(self, params, C): c1, c2 = params['c1'], params['c2'] # Mooney Rivlin material (isochoric version) psi_dev = c1 * (self.Ic_bar - 3.) + c2 * (self.IIc_bar - 3.) S = 2. * diff(psi_dev, C) return S
def codeDG(self): code = self._code() u = self.trialFunction ubar = Coefficient(u.ufl_function_space()) penalty = self.penalty if penalty is None: penalty = 1 if isinstance(penalty, Expr): if penalty.ufl_shape == (): penalty = as_vector([penalty]) try: penalty = expand_indices( expand_derivatives(expand_compounds(penalty))) except: pass assert penalty.ufl_shape == u.ufl_shape dmPenalty = as_vector([ replace( expand_derivatives(diff(replace(penalty, {u: ubar}), ubar))[i, i], {ubar: u}) for i in range(u.ufl_shape[0]) ]) else: dmPenalty = None code.append(AccessModifier("public")) x = SpatialCoordinate(self.space.cell()) predefined = {} self.predefineCoefficients(predefined, x) spatial = Variable('const auto', 'y') predefined.update({ x: UnformattedExpression( 'auto', 'entity().geometry().global( Dune::Fem::coordinate( x ) )') }) generateMethod(code, penalty, 'RRangeType', 'penalty', args=['const Point &x', 'const DRangeType &u'], targs=['class Point', 'class DRangeType'], static=False, const=True, predefined=predefined) generateMethod(code, dmPenalty, 'RRangeType', 'linPenalty', args=['const Point &x', 'const DRangeType &u'], targs=['class Point', 'class DRangeType'], static=False, const=True, predefined=predefined) return code
def phi_remod(self, theta_, tang=False): if self.growth_dir == 'isotropic': # tri-axial phi = 1.-1./(theta_*theta_*theta_) elif self.growth_dir == 'fiber': # uni-axial phi = 1.-1./theta_ elif self.growth_dir == 'crossfiber': # bi-axial phi = 1.-1./(theta_*theta_) elif self.growth_dir == 'radial': # uni-axial phi = 1.-1./theta_ else: raise NameError("Unknown growth direction.") if tang: return diff(phi,theta_) else: return phi
def _find_linear_terms(rhs_exprs, u): """Help function that takes a list of rhs expressions and return a list of bools determining what component, rhs_exprs[i], is linear wrt u[i]. """ uu = [Constant(1.0) for _ in rhs_exprs] if len(rhs_exprs) > 1: repl = {u: ufl.as_vector(uu)} else: repl = {u: uu[0]} linear_terms = [] for i, ui in enumerate(uu): comp_i_s = expand_indices(ufl.replace(rhs_exprs[i], repl)) linear_terms.append(ui in extract_coefficients(comp_i_s) and ui not in extract_coefficients( expand_derivatives(ufl.diff(comp_i_s, ui)))) return linear_terms
def F_g(self, theta_, tang=False): # split of deformation gradient into elastic and growth part: F = F_e*F_g gr = growth(theta_,self.I) if self.growth_dir == 'isotropic': defgrd_gr = gr.isotropic() elif self.growth_dir == 'fiber': defgrd_gr = gr.fiber(self.kin.fib_funcs[0]) elif self.growth_dir == 'crossfiber': defgrd_gr = gr.crossfiber(self.kin.fib_funcs[0]) elif self.growth_dir == 'radial': defgrd_gr = gr.radial(self.kin.fib_funcs[0],self.kin.fib_funcs[1]) else: raise NameError("Unknown growth direction.") if tang: return diff(defgrd_gr,theta_) else: return defgrd_gr
def dtheta_dp(self, u_, p_, ivar, theta_old_, thres, dt): theta_ = ivar["theta"] dFg_dtheta = self.F_g(theta_,tang=True) ktheta = self.res_dtheta_growth(u_, p_, ivar, theta_old_, thres, dt, 'ktheta') K_growth = self.res_dtheta_growth(u_, p_, ivar, theta_old_, thres, dt, 'tang') if self.growth_trig == 'volstress': tangdp = (ktheta*dt/K_growth) * ( diff(tr(self.M_e(u_,p_,self.kin.C(u_),ivar)),p_) ) elif self.growth_trig == 'fibstretch': tangdp = as_ufl(0) else: raise NameError("Unkown growth_trig!") return tangdp
# The first line creates an object of type ``InitialConditions``. The # following two lines make ``u`` and ``u0`` interpolants of ``u_init`` # (since ``u`` and ``u0`` are finite element functions, they may not be # able to represent a given function exactly, but the function can be # approximated by interpolating it in a finite element space). # # .. index:: automatic differentiation # # The chemical potential :math:`df/dc` is computed using automated # differentiation:: # Compute the chemical potential df/dc c = variable(c) f = 100 * c**2 * (1 - c)**2 dfdc = diff(f, c) # The first line declares that ``c`` is a variable that some function can # be differentiated with respect to. The next line is the function # :math:`f` defined in the problem statement, and the third line performs # the differentiation of ``f`` with respect to the variable ``c``. # # It is convenient to introduce an expression for :math:`\mu_{n+\theta}`:: # mu_(n+theta) mu_mid = (1.0 - theta) * mu0 + theta * mu # which is then used in the definition of the variational forms:: # Weak statement of the equations L0 = inner(c, q) * dx - inner(c0, q) * dx + dt * inner(grad(mu_mid),
def _rush_larsen_scheme_generator(rhs_form, solution, time, order, generalized): """Generates a list of forms and solutions for a given Butcher tableau *Arguments* rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation solution (_Function_) The prognostic variable time (_Constant_) A Constant holding the time at the start of the time step order (int) The order of the scheme generalized (bool) If True generate a generalized Rush Larsen scheme, linearizing all components. """ DX = _check_form(rhs_form) if DX != ufl.dP: raise TypeError("Expected a form with a Pointintegral.") # Create time step dt = Constant(0.1) # Get test function # arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() # Get time dependent expressions time_dep_expressions = _time_dependent_expressions(rhs_form, time) # Extract rhs expressions from form rhs_integrand = rhs_form.integrals()[0].integrand() rhs_exprs, v = extract_tested_expressions(rhs_integrand) vector_rhs = len(v.ufl_shape) > 0 and v.ufl_shape[0] > 1 system_size = v.ufl_shape[0] if vector_rhs else 1 # Fix for indexing of v for scalar expressions v = v if vector_rhs else [v] # Extract linear terms if not using generalized Rush Larsen if not generalized: linear_terms = _find_linear_terms(rhs_exprs, solution) else: linear_terms = [True for _ in range(system_size)] # Wrap the rhs expressions into a ufl vector type rhs_exprs = ufl.as_vector([rhs_exprs[i] for i in range(system_size)]) rhs_jac = ufl.diff(rhs_exprs, solution) # Takes time! if vector_rhs: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[ind, ind])) for ind in range(system_size)] else: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[0]))] solution = [solution] ufl_stage_forms = [] dolfin_stage_forms = [] dt_stage_offsets = [] # Stage solutions (3 per order rhs, linearized, and final step) # If 2nd order the final step for 1 step is a stage if order == 1: stage_solutions = [] rl_ufl_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 1.0, 0.0, v, DX, time_dep_expressions) elif order == 2: # Stage solution for order 2 if vector_rhs: stage_solutions = [Function(solution.function_space(), name="y_1/2")] else: stage_solutions = [Function(solution[0].function_space(), name="y_1/2")] stage_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 0.5, 0.0, v, DX, time_dep_expressions) rl_ufl_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, stage_solutions[0], dt, time, 1.0, 0.5, v, DX, time_dep_expressions) ufl_stage_forms.append([stage_form]) dolfin_stage_forms.append([Form(stage_form)]) # Get last stage form last_stage = Form(rl_ufl_form) human_form = "%srush larsen %s" % ("generalized " if generalized else "", str(order)) return rhs_form, linear_terms, ufl_stage_forms, dolfin_stage_forms, last_stage, \ stage_solutions, dt, dt_stage_offsets, human_form, None
svals = np.zeros_like(stretchVals) plt.plot(timeVals, stretchVals) plt.savefig("stretchesVisco.png") plt.close() # stabilization parameters h = FacetArea(mesh) h_avg = avg(h) # new variable name to take derivatives FF = Identity(3) + grad(u) CC = FF.T * FF Fv = variable(FF) S = diff(freeEnergy(Fv.T * Fv, CCv), Fv) # first PK stress dl_interp(CC, C) dl_interp(CC, Cn) my_identity = grad(SpatialCoordinate(mesh)) dl_interp(my_identity, CCv) dl_interp(my_identity, Cvn) dl_interp(my_identity, C_quart) dl_interp(my_identity, C_thr_quart) dl_interp(my_identity, C_half) a_uv = (derivative(freeEnergy(CC, CCv), u, v) * dx + qvals / h_avg * dot(jump(u), jump(v)) * dS) jac = derivative(a_uv, u, du)
def _rush_larsen_scheme_generator_adm(rhs_form, solution, time, order, generalized, perturbation): """Generates a list of forms and solutions for the adjoint linearisation of the Rush-Larsen scheme *Arguments* rhs_form (ufl.Form) A UFL form representing the rhs for a time differentiated equation solution (_Function_) The prognostic variable time (_Constant_) A Constant holding the time at the start of the time step order (int) The order of the scheme generalized (bool) If True generate a generalized Rush Larsen scheme, linearizing all components. perturbation (Function) The vector on which we compute the adjoint action. """ DX = _check_form(rhs_form) if DX != ufl.dP: raise TypeError("Expected a form with a Pointintegral.") # Create time step dt = Constant(0.1) # Get test function # arguments = rhs_form.arguments() # coefficients = rhs_form.coefficients() # Get time dependent expressions time_dep_expressions = _time_dependent_expressions(rhs_form, time) # Extract rhs expressions from form rhs_integrand = rhs_form.integrals()[0].integrand() rhs_exprs, v = extract_tested_expressions(rhs_integrand) vector_rhs = len(v.ufl_shape) > 0 and v.ufl_shape[0] > 1 system_size = v.ufl_shape[0] if vector_rhs else 1 # Fix for indexing of v for scalar expressions v = v if vector_rhs else [v] # Extract linear terms if not using generalized Rush Larsen if not generalized: linear_terms = _find_linear_terms(rhs_exprs, solution) else: linear_terms = [True for _ in range(system_size)] # Wrap the rhs expressions into a ufl vector type rhs_exprs = ufl.as_vector([rhs_exprs[i] for i in range(system_size)]) rhs_jac = ufl.diff(rhs_exprs, solution) # Takes time! if vector_rhs: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[ind, ind])) for ind in range(system_size)] soln = solution else: diff_rhs_exprs = [expand_indices(expand_derivatives(rhs_jac[0]))] solution = [solution] soln = solution[0] ufl_stage_forms = [] dolfin_stage_forms = [] dt_stage_offsets = [] trial = TrialFunction(soln.function_space()) # Stage solutions (3 per order rhs, linearized, and final step) # If 2nd order the final step for 1 step is a stage if order == 1: stage_solutions = [] # Fetch the original step rl_ufl_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 1.0, 0.0, v, DX, time_dep_expressions) # If this is commented out, we don't get NaNs. Yhy is # solution a list of length zero anyway? rl_ufl_form = safe_action(safe_adjoint(derivative(rl_ufl_form, soln, trial)), perturbation) elif order == 2: # Stage solution for order 2 fn_space = soln.function_space() stage_solutions = [Function(fn_space, name="y_1/2"), Function(fn_space, name="y_1"), Function(fn_space, name="y_bar_1/2")] y_half_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, None, dt, time, 0.5, 0.0, v, DX, time_dep_expressions) y_one_form = _rush_larsen_step(rhs_exprs, diff_rhs_exprs, linear_terms, system_size, solution, stage_solutions[0], dt, time, 1.0, 0.5, v, DX, time_dep_expressions) y_bar_half_form = safe_action(safe_adjoint(derivative(y_one_form, stage_solutions[0], trial)), perturbation) rl_ufl_form = safe_action(safe_adjoint(derivative(y_one_form, soln, trial)), perturbation) + \ safe_action(safe_adjoint(derivative(y_half_form, soln, trial)), stage_solutions[2]) ufl_stage_forms.append([y_half_form]) ufl_stage_forms.append([y_one_form]) ufl_stage_forms.append([y_bar_half_form]) dolfin_stage_forms.append([Form(y_half_form)]) dolfin_stage_forms.append([Form(y_one_form)]) dolfin_stage_forms.append([Form(y_bar_half_form)]) # Get last stage form last_stage = Form(rl_ufl_form) human_form = "%srush larsen %s" % ("generalized " if generalized else "", str(order)) return rhs_form, linear_terms, ufl_stage_forms, dolfin_stage_forms, last_stage, \ stage_solutions, dt, dt_stage_offsets, human_form, perturbation
def __init__(self, F_form: typing.List, u: typing.List, lmbda: dolfinx.fem.Function, A_form=None, B_form=None, prefix=None): """SLEPc problem and solver wrapper. Wrapper for a generalised eigenvalue problem obtained from UFL residual forms. Parameters ---------- F_form Residual forms u Current solution vectors lmbda Eigenvalue function. Residual forms must be linear in lmbda for linear eigenvalue problems. A_form, optional Override automatically derived A B_form, optional Override automatically derived B Note ---- In general, eigenvalue problems have form T(lmbda) * x = 0, where T(lmbda) is a matrix-valued function. Linear eigenvalue problems have T(lmbda) = A + lmbda * B, and if B is not identity matrix then this problem is called generalized (linear) eigenvalue problem. """ self.F_form = F_form self.u = u self.lmbda = lmbda self.comm = u[0].function_space.mesh.comm self.ur = [] self.ui = [] for func in u: self.ur.append( dolfinx.fem.Function(func.function_space, name=func.name)) self.ui.append( dolfinx.fem.Function(func.function_space, name=func.name)) # Prepare tangent form M0 which has terms involving lambda self.M0 = [[None for i in range(len(self.u))] for j in range(len(self.u))] for i in range(len(self.u)): for j in range(len(self.u)): self.M0[i][j] = ufl.algorithms.expand_derivatives( ufl.derivative(F_form[i], self.u[j], ufl.TrialFunction( self.u[j].function_space))) if B_form is None: B0 = [[None for i in range(len(self.u))] for j in range(len(self.u))] for i in range(len(self.u)): for j in range(len(self.u)): # Differentiate wrt. lambda and replace all remaining lambda with Zero B0[i][j] = ufl.algorithms.expand_derivatives( ufl.diff(self.M0[i][j], lmbda)) B0[i][j] = ufl.replace(B0[i][j], {lmbda: ufl.zero()}) if B0[i][j].empty(): B0[i][j] = None self.B_form = B0 else: self.B_form = B_form if A_form is None: A0 = [[None for i in range(len(self.u))] for j in range(len(self.u))] for i in range(len(self.u)): for j in range(len(self.u)): A0[i][j] = ufl.replace(self.M0[i][j], {lmbda: ufl.zero()}) if A0[i][j].empty(): A0[i][j] = None continue self.A_form = A0 else: self.A_form = A_form self.eps = SLEPc.EPS().create(self.comm) self.eps.setOptionsPrefix(prefix) self.eps.setFromOptions() self.A_form = dolfinx.fem.form(self.A_form) self.B_form = dolfinx.fem.form(self.B_form) self.A = dolfinx.fem.petsc.create_matrix_block(self.A_form) self.B = None if not self.empty_B(): self.B = dolfinx.fem.petsc.create_matrix_block(self.B_form)
def stress(self, eps, alpha): eps_ = variable(eps) sigma = diff(self.elastic_energy_density(eps_, alpha), eps_) return sigma
def test_diff_then_integrate(): # Define 1D geometry n = 21 mesh = UnitIntervalMesh(MPI.comm_world, n) # Shift and scale mesh x0, x1 = 1.5, 3.14 mesh.coordinates()[:] *= (x1 - x0) mesh.coordinates()[:] += x0 x = SpatialCoordinate(mesh)[0] xs = 0.1 + 0.8 * x / x1 # scaled to be within [0.1,0.9] # Define list of expressions to test, and configure # accuracies these expressions are known to pass with. # The reason some functions are less accurately integrated is # likely that the default choice of quadrature rule is not perfect F_list = [] def reg(exprs, acc=10): for expr in exprs: F_list.append((expr, acc)) # FIXME: 0*dx and 1*dx fails in the ufl-ffc-jit framework somewhere # reg([Constant(0.0, cell=cell)]) # reg([Constant(1.0, cell=cell)]) monomial_list = [x**q for q in range(2, 6)] reg(monomial_list) reg([2.3 * p + 4.5 * q for p in monomial_list for q in monomial_list]) reg([x**x]) reg([x**(x**2)], 8) reg([x**(x**3)], 6) reg([x**(x**4)], 2) # Special functions: reg([atan(xs)], 8) reg([sin(x), cos(x), exp(x)], 5) reg([ln(xs), pow(x, 2.7), pow(2.7, x)], 3) reg([asin(xs), acos(xs)], 1) reg([tan(xs)], 7) try: import scipy except ImportError: scipy = None if hasattr(math, 'erf') or scipy is not None: reg([erf(xs)]) else: print( "Warning: skipping test of erf, old python version and no scipy.") # if 0: # print("Warning: skipping tests of bessel functions, doesn't build on all platforms.") # elif scipy is None: # print("Warning: skipping tests of bessel functions, missing scipy.") # else: # for nu in (0, 1, 2): # # Many of these are possibly more accurately integrated, # # but 4 covers all and is sufficient for this test # reg([bessel_J(nu, xs), bessel_Y(nu, xs), bessel_I(nu, xs), bessel_K(nu, xs)], 4) # To handle tensor algebra, make an x dependent input tensor # xx and square all expressions def reg2(exprs, acc=10): for expr in exprs: F_list.append((inner(expr, expr), acc)) xx = as_matrix([[2 * x**2, 3 * x**3], [11 * x**5, 7 * x**4]]) x3v = as_vector([3 * x**2, 5 * x**3, 7 * x**4]) cc = as_matrix([[2, 3], [4, 5]]) reg2([xx]) reg2([x3v]) reg2([cross(3 * x3v, as_vector([-x3v[1], x3v[0], x3v[2]]))]) reg2([xx.T]) reg2([tr(xx)]) reg2([det(xx)]) reg2([dot(xx, 0.1 * xx)]) reg2([outer(xx, xx.T)]) reg2([dev(xx)]) reg2([sym(xx)]) reg2([skew(xx)]) reg2([elem_mult(7 * xx, cc)]) reg2([elem_div(7 * xx, xx + cc)]) reg2([elem_pow(1e-3 * xx, 1e-3 * cc)]) reg2([elem_pow(1e-3 * cc, 1e-3 * xx)]) reg2([elem_op(lambda z: sin(z) + 2, 0.03 * xx)], 2) # pretty inaccurate... # FIXME: Add tests for all UFL operators: # These cause discontinuities and may be harder to test in the # above fashion: # 'inv', 'cofac', # 'eq', 'ne', 'le', 'ge', 'lt', 'gt', 'And', 'Or', 'Not', # 'conditional', 'sign', # 'jump', 'avg', # 'LiftingFunction', 'LiftingOperator', # FIXME: Test other derivatives: (but algorithms for operator # derivatives are the same!): # 'variable', 'diff', # 'Dx', 'grad', 'div', 'curl', 'rot', 'Dn', 'exterior_derivative', # Run through all operators defined above and compare integrals debug = 0 for F, acc in F_list: # Apply UFL differentiation f = diff(F, SpatialCoordinate(mesh))[..., 0] if debug: print(F) print(x) print(f) # Apply integration with DOLFIN # (also passes through form compilation and jit) M = f * dx f_integral = assemble_scalar(M) # noqa f_integral = MPI.sum(mesh.mpi_comm(), f_integral) # Compute integral of f manually from anti-derivative F # (passes through PyDOLFIN interface and uses UFL evaluation) F_diff = F((x1, )) - F((x0, )) # Compare results. Using custom relative delta instead # of decimal digits here because some numbers are >> 1. delta = min(abs(f_integral), abs(F_diff)) * 10**-acc assert f_integral - F_diff <= delta
def eigenstate(A): """Eigenvalues and eigenprojectors of the 3x3 (real-valued) tensor A. Provides the spectral decomposition A = sum_{a=0}^{2} λ_a * E_a with (ordered) eigenvalues λ_a and their associated eigenprojectors E_a = n_a^R x n_a^L. Note: Tensor A must not have complex eigenvalues! """ if ufl.shape(A) != (3, 3): raise RuntimeError( f"Tensor A of shape {ufl.shape(A)} != (3, 3) is not supported!") # eps = 3.0e-16 # slightly above 2**-(53 - 1), see https://en.wikipedia.org/wiki/IEEE_754 # A = ufl.variable(A) # # --- determine eigenvalues λ0, λ1, λ2 # I1, I2, I3 = invariants_principal(A) dq = 2 * I1**3 - 9 * I1 * I2 + 27 * I3 # Δx = [ A[0, 1] * A[1, 2] * A[2, 0] - A[0, 2] * A[1, 0] * A[2, 1], A[0, 1]**2 * A[1, 2] - A[0, 1] * A[0, 2] * A[1, 1] + A[0, 1] * A[0, 2] * A[2, 2] - A[0, 2]**2 * A[2, 1], A[0, 0] * A[0, 1] * A[2, 1] - A[0, 1]**2 * A[2, 0] - A[0, 1] * A[2, 1] * A[2, 2] + A[0, 2] * A[2, 1]**2, A[0, 0] * A[0, 2] * A[1, 2] + A[0, 1] * A[1, 2]**2 - A[0, 2]**2 * A[1, 0] - A[0, 2] * A[1, 1] * A[1, 2], A[0, 0] * A[0, 1] * A[1, 2] - A[0, 1] * A[0, 2] * A[1, 0] - A[0, 1] * A[1, 2] * A[2, 2] + A[0, 2] * A[1, 2] * A[2, 1], # noqa: E501 A[0, 0] * A[0, 2] * A[2, 1] - A[0, 1] * A[0, 2] * A[2, 0] + A[0, 1] * A[1, 2] * A[2, 1] - A[0, 2] * A[1, 1] * A[2, 1], # noqa: E501 A[0, 1] * A[1, 0] * A[1, 2] - A[0, 2] * A[1, 0] * A[1, 1] + A[0, 2] * A[1, 0] * A[2, 2] - A[0, 2] * A[1, 2] * A[2, 0], # noqa: E501 A[0, 0]**2 * A[1, 2] - A[0, 0] * A[0, 2] * A[1, 0] - A[0, 0] * A[1, 1] * A[1, 2] - A[0, 0] * A[1, 2] * A[2, 2] + A[0, 1] * A[1, 0] * A[1, 2] + A[0, 2] * A[1, 0] * A[2, 2] + A[1, 1] * A[1, 2] * A[2, 2] - A[1, 2]**2 * A[2, 1], # noqa: E501 A[0, 0]**2 * A[1, 2] - A[0, 0] * A[0, 2] * A[1, 0] - A[0, 0] * A[1, 1] * A[1, 2] - A[0, 0] * A[1, 2] * A[2, 2] + A[0, 2] * A[1, 0] * A[1, 1] + A[0, 2] * A[1, 2] * A[2, 0] + A[1, 1] * A[1, 2] * A[2, 2] - A[1, 2]**2 * A[2, 1], # noqa: E501 A[0, 0] * A[0, 1] * A[1, 1] - A[0, 0] * A[0, 1] * A[2, 2] - A[0, 1]**2 * A[1, 0] + A[0, 1] * A[0, 2] * A[2, 0] - A[0, 1] * A[1, 1] * A[2, 2] + A[0, 1] * A[2, 2]**2 + A[0, 2] * A[1, 1] * A[2, 1] - A[0, 2] * A[2, 1] * A[2, 2], # noqa: E501 A[0, 0] * A[0, 1] * A[1, 1] - A[0, 0] * A[0, 1] * A[2, 2] + A[0, 0] * A[0, 2] * A[2, 1] - A[0, 1]**2 * A[1, 0] - A[0, 1] * A[1, 1] * A[2, 2] + A[0, 1] * A[1, 2] * A[2, 1] + A[0, 1] * A[2, 2]**2 - A[0, 2] * A[2, 1] * A[2, 2], # noqa: E501 A[0, 0] * A[0, 1] * A[1, 2] - A[0, 0] * A[0, 2] * A[1, 1] + A[0, 0] * A[0, 2] * A[2, 2] - A[0, 1] * A[1, 1] * A[1, 2] - A[0, 2]**2 * A[2, 0] + A[0, 2] * A[1, 1]**2 - A[0, 2] * A[1, 1] * A[2, 2] + A[0, 2] * A[1, 2] * A[2, 1], # noqa: E501 A[0, 0] * A[0, 2] * A[1, 1] - A[0, 0] * A[0, 2] * A[2, 2] - A[0, 1] * A[0, 2] * A[1, 0] + A[0, 1] * A[1, 1] * A[1, 2] - A[0, 1] * A[1, 2] * A[2, 2] + A[0, 2]**2 * A[2, 0] - A[0, 2] * A[1, 1]**2 + A[0, 2] * A[1, 1] * A[2, 2], # noqa: E501 A[0, 0]**2 * A[1, 1] - A[0, 0]**2 * A[2, 2] - A[0, 0] * A[0, 1] * A[1, 0] + A[0, 0] * A[0, 2] * A[2, 0] - A[0, 0] * A[1, 1]**2 + A[0, 0] * A[2, 2]**2 + A[0, 1] * A[1, 0] * A[1, 1] - A[0, 2] * A[2, 0] * A[2, 2] + A[1, 1]**2 * A[2, 2] - A[1, 1] * A[1, 2] * A[2, 1] - A[1, 1] * A[2, 2]**2 + A[1, 2] * A[2, 1] * A[2, 2] ] # noqa: E501 Δy = [ A[0, 2] * A[1, 0] * A[2, 1] - A[0, 1] * A[1, 2] * A[2, 0], A[1, 0]**2 * A[2, 1] - A[1, 0] * A[1, 1] * A[2, 0] + A[1, 0] * A[2, 0] * A[2, 2] - A[1, 2] * A[2, 0]**2, A[0, 0] * A[1, 0] * A[1, 2] - A[0, 2] * A[1, 0]**2 - A[1, 0] * A[1, 2] * A[2, 2] + A[1, 2]**2 * A[2, 0], A[0, 0] * A[2, 0] * A[2, 1] - A[0, 1] * A[2, 0]**2 + A[1, 0] * A[2, 1]**2 - A[1, 1] * A[2, 0] * A[2, 1], A[0, 0] * A[1, 0] * A[2, 1] - A[0, 1] * A[1, 0] * A[2, 0] - A[1, 0] * A[2, 1] * A[2, 2] + A[1, 2] * A[2, 0] * A[2, 1], # noqa: E501 A[0, 0] * A[1, 2] * A[2, 0] - A[0, 2] * A[1, 0] * A[2, 0] + A[1, 0] * A[1, 2] * A[2, 1] - A[1, 1] * A[1, 2] * A[2, 0], # noqa: E501 A[0, 1] * A[1, 0] * A[2, 1] - A[0, 1] * A[1, 1] * A[2, 0] + A[0, 1] * A[2, 0] * A[2, 2] - A[0, 2] * A[2, 0] * A[2, 1], # noqa: E501 A[0, 0]**2 * A[2, 1] - A[0, 0] * A[0, 1] * A[2, 0] - A[0, 0] * A[1, 1] * A[2, 1] - A[0, 0] * A[2, 1] * A[2, 2] + A[0, 1] * A[1, 0] * A[2, 1] + A[0, 1] * A[2, 0] * A[2, 2] + A[1, 1] * A[2, 1] * A[2, 2] - A[1, 2] * A[2, 1]**2, # noqa: E501 A[0, 0]**2 * A[2, 1] - A[0, 0] * A[0, 1] * A[2, 0] - A[0, 0] * A[1, 1] * A[2, 1] - A[0, 0] * A[2, 1] * A[2, 2] + A[0, 1] * A[1, 1] * A[2, 0] + A[0, 2] * A[2, 0] * A[2, 1] + A[1, 1] * A[2, 1] * A[2, 2] - A[1, 2] * A[2, 1]**2, # noqa: E501 A[0, 0] * A[1, 0] * A[1, 1] - A[0, 0] * A[1, 0] * A[2, 2] - A[0, 1] * A[1, 0]**2 + A[0, 2] * A[1, 0] * A[2, 0] - A[1, 0] * A[1, 1] * A[2, 2] + A[1, 0] * A[2, 2]**2 + A[1, 1] * A[1, 2] * A[2, 0] - A[1, 2] * A[2, 0] * A[2, 2], # noqa: E501 A[0, 0] * A[1, 0] * A[1, 1] - A[0, 0] * A[1, 0] * A[2, 2] + A[0, 0] * A[1, 2] * A[2, 0] - A[0, 1] * A[1, 0]**2 - A[1, 0] * A[1, 1] * A[2, 2] + A[1, 0] * A[1, 2] * A[2, 1] + A[1, 0] * A[2, 2]**2 - A[1, 2] * A[2, 0] * A[2, 2], # noqa: E501 A[0, 0] * A[1, 0] * A[2, 1] - A[0, 0] * A[1, 1] * A[2, 0] + A[0, 0] * A[2, 0] * A[2, 2] - A[0, 2] * A[2, 0]**2 - A[1, 0] * A[1, 1] * A[2, 1] + A[1, 1]**2 * A[2, 0] - A[1, 1] * A[2, 0] * A[2, 2] + A[1, 2] * A[2, 0] * A[2, 1], # noqa: E501 A[0, 0] * A[1, 1] * A[2, 0] - A[0, 0] * A[2, 0] * A[2, 2] - A[0, 1] * A[1, 0] * A[2, 0] + A[0, 2] * A[2, 0]**2 + A[1, 0] * A[1, 1] * A[2, 1] - A[1, 0] * A[2, 1] * A[2, 2] - A[1, 1]**2 * A[2, 0] + A[1, 1] * A[2, 0] * A[2, 2], # noqa: E501 A[0, 0]**2 * A[1, 1] - A[0, 0]**2 * A[2, 2] - A[0, 0] * A[0, 1] * A[1, 0] + A[0, 0] * A[0, 2] * A[2, 0] - A[0, 0] * A[1, 1]**2 + A[0, 0] * A[2, 2]**2 + A[0, 1] * A[1, 0] * A[1, 1] - A[0, 2] * A[2, 0] * A[2, 2] + A[1, 1]**2 * A[2, 2] - A[1, 1] * A[1, 2] * A[2, 1] - A[1, 1] * A[2, 2]**2 + A[1, 2] * A[2, 1] * A[2, 2] ] # noqa: E501 Δd = [9, 6, 6, 6, 8, 8, 8, 2, 2, 2, 2, 2, 2, 1] Δ = 0 for i in range(len(Δd)): Δ += Δx[i] * Δd[i] * Δy[i] Δxp = [ A[1, 0], A[2, 0], A[2, 1], -A[0, 0] + A[1, 1], -A[0, 0] + A[2, 2], -A[1, 1] + A[2, 2] ] Δyp = [ A[0, 1], A[0, 2], A[1, 2], -A[0, 0] + A[1, 1], -A[0, 0] + A[2, 2], -A[1, 1] + A[2, 2] ] Δdp = [6, 6, 6, 1, 1, 1] dp = 0 for i in range(len(Δdp)): dp += 1 / 2 * Δxp[i] * Δdp[i] * Δyp[i] # Avoid dp = 0 and disc = 0, both are known with absolute error of ~eps**2 # Required to avoid sqrt(0) derivatives and negative square roots dp += eps**2 Δ += eps**2 phi3 = ufl.atan_2(ufl.sqrt(27) * ufl.sqrt(Δ), dq) # sorted eigenvalues: λ0 <= λ1 <= λ2 λ = [(I1 + 2 * ufl.sqrt(dp) * ufl.cos((phi3 + 2 * ufl.pi * k) / 3)) / 3 for k in range(1, 4)] # # --- determine eigenprojectors E0, E1, E2 # E = [ufl.diff(λk, A).T for λk in λ] return λ, E