def __init__(self, variable, expression, name, symmetric=None): self.variable = variable if symmetric is None: self.expression = expression elif symmetric: if ufl.shape(expression) == (2, 2): self.expression = as_vector([ symmetric_tensor_to_vector(expression)[i] for i in range(4) ]) else: self.expression = symmetric_tensor_to_vector(expression) else: if ufl.shape(expression) == (2, 2): self.expression = as_vector([ nonsymmetric_tensor_to_vector(expression)[i] for i in range(5) ]) else: self.expression = nonsymmetric_tensor_to_vector(expression) shape = ufl.shape(self.expression) if len(shape) == 1: self.shape = shape[0] elif shape == (): self.shape = 0 else: self.shape = shape self.name = name
def nonsymmetric_tensor_to_vector(T, T22=0): """ Return nonsymmetric tensor components in vector form notation following MFront conventions T22 can be specified when T is only (2,2) """ if ufl.shape(T) == (2, 2): return as_vector([T[0, 0], T[1, 1], T22, T[0, 1], T[1, 0]]) elif ufl.shape(T) == (3, 3): return as_vector([ T[0, 0], T[1, 1], T[2, 2], T[0, 1], T[1, 0], T[0, 2], T[2, 0], T[1, 2], T[2, 1] ]) elif len(ufl.shape(T)) == 1: return T else: raise NotImplementedError
def vector_to_tensor(T): """ Return vector following MFront conventions as a tensor """ if ufl.shape(T) == (4, ): return as_tensor([[T[0], T[3] / sqrt(2)], T[3] / sqrt(2), T[1]]) elif ufl.shape(T) == (6, ): return as_tensor([[T[0], T[3] / sqrt(2), T[4] / sqrt(2)], [T[3] / sqrt(2), T[1], T[5] / sqrt(2)], [T[4] / sqrt(2), T[5] / sqrt(2), T[2]]]) elif ufl.shape(T) == (5, ): return as_tensor([[T[0], T[3]], T[4], T[1]]) elif ufl.shape(T) == (9, ): return as_tensor([[T[0], T[3], T[5]], [T[4], T[1], T[7]], [T[6], T[8], T[2]]]) else: raise NotImplementedError
def assemble(e, dx): """Assemble UFL form given by UFL expression e and UFL integration measure dx. The expression can be a tensor quantity of rank 0, 1 or 2. Parameters ---------- e: UFL Expr dx: UFL Measure Returns ------- Assembled form f = e * dx as scalar or numpy array (depends on rank of e). """ import numpy as np from mpi4py import MPI rank = ufl.rank(e) shape = ufl.shape(e) if rank == 0: f_ = MPI.COMM_WORLD.allreduce(dolfinx.fem.assemble_scalar(e * dx), op=MPI.SUM) elif rank == 1: f_ = np.zeros(shape) for row in range(shape[0]): f_[row] = MPI.COMM_WORLD.allreduce(dolfinx.fem.assemble_scalar(e[row] * dx), op=MPI.SUM) elif rank == 2: f_ = np.zeros(shape) for row in range(shape[0]): for col in range(shape[1]): f_[row, col] = MPI.COMM_WORLD.allreduce(dolfinx.fem.assemble_scalar(e[row, col] * dx), op=MPI.SUM) return f_
def cartesianCurl(f, F): """ The curl operator corresponding to ``cartesianGrad(f,F)``. For ``f`` of rank 1, it returns a vector in 3D or a scalar in 2D. For ``f`` scalar in 2D, it returns a vector. """ n = rank(f) gradf = cartesianGrad(f, F) if (n == 1): m = shape(f)[0] eps = PermutationSymbol(m) if (m == 3): (i, j, k) = indices(3) return as_tensor(eps[i, j, k] * gradf[k, j], (i, )) elif (m == 2): (j, k) = indices(2) return eps[j, k] * gradf[k, j] else: print("ERROR: Unsupported dimension of argument to curl.") exit() elif (n == 0): return as_vector((-gradf[1], gradf[0])) else: print("ERROR: Unsupported rank of argument to curl.") exit()
def project_on(self, space, degree, as_tensor=False): """ Returns the projection on a standard CG/DG space Parameters ---------- space: str FunctionSpace type ("CG", "DG",...) degree: int FunctionSpace degree as_tensor: bool Returned as a tensor if True, in vector notation otherwise """ fun = self.function if as_tensor: fun = vector_to_tensor(self.function) shape = ufl.shape(fun) V = TensorFunctionSpace(self.mesh, space, degree, shape=shape) elif self.shape == 1: V = FunctionSpace(self.mesh, space, degree) else: V = VectorFunctionSpace(self.mesh, space, degree, dim=self.shape) v = Function(V, name=self.name) v.assign( project(fun, V, form_compiler_parameters={ "quadrature_degree": self.quadrature_degree })) return v
def compute_tangent_form(self): """Computes derivative of residual""" # derivatives of variable u self.tangent_form = derivative(self.residual, self.u, self.du) # derivatives of fluxes for f in self.fluxes.values(): for (t, dg) in zip(f.tangent_blocks.values(), f.variables): tdg = t * dg.variation(self.du) if len(shape(t)) > 0 and shape(t)[0] == 1: tdg = tdg[0] self.tangent_form += derivative(self.residual, f.function, tdg) # derivatives of internal state variables for s in self.state_variables["internal"].values(): for (t, dg) in zip(s.tangent_blocks.values(), s.variables): tdg = t * dg.variation(self.du) if len(shape(t)) > 0 and shape(t)[0] == 1: tdg = tdg[0] self.tangent_form += derivative(self.residual, s.function, tdg)
def axi_grad(r, v): """ Axisymmetric gradient in cylindrical coordinate (er, etheta, ez) for: * a scalar v(r, z) * a 2d-vectorial (vr(r,z), vz(r, z)) * a 3d-vectorial (vr(r,z), 0, vz(r, z)) """ if ufl.shape(v) == (3, ): return as_matrix([[v[0].dx(0), -v[1] / r, v[0].dx(1)], [v[1].dx(0), v[0] / r, v[1].dx(1)], [v[2].dx(0), 0, v[2].dx(1)]]) elif ufl.shape(v) == (2, ): return as_matrix([[v[0].dx(0), v[0].dx(1), 0], [v[1].dx(0), v[1].dx(1), 0], [0, 0, v[0] / r]]) elif ufl.shape(v) == (): return as_vector([v.dx(0), 0, v.dx(1)]) else: raise NotImplementedError
def x_i(self, x, i): N = shape(x)[0] n = N // 2 l = [] for j in range(i * n, (i + 1) * n): l += [ x[j], ] if (n == 1): return l[0] return as_vector(l)
def lumpedProject(f, V): v = TestFunction(V) s = ufl.shape(v) if (len(s) > 0): lhsTrial = as_vector(s[0] * (Constant(1.0), )) else: lhsTrial = Constant(1.0) lhs = assemble(inner(lhsTrial, v) * dx) rhs = assemble(inner(f, v) * dx) u = Function(V) as_backend_type(u.vector())\ .vec().pointwiseDivide(as_backend_type(rhs).vec(), as_backend_type(lhs).vec()) return u
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 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
def sigma(u, p, mu): """ The fluid Cauchy stress, in terms of velocity ``u``, pressure ``p``, and dynamic viscosity ``mu``. """ return sigmaVisc(u, mu) - p * Identity(ufl.shape(u)[0])