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 freeEnergy(C, Cv): J = sqrt(det(C)) I1 = tr(C) Ce = C * inv(Cv) Ie1 = tr(Ce) Je = J / sqrt(det(Cv)) psiEq = (3**(1 - alph1) / (2.0 * alph1) * mu1 * (I1**alph1 - 3**alph1) + 3**(1 - alph2) / (2.0 * alph2) * mu2 * (I1**alph2 - 3**alph2) - (mu1 + mu2) * ln(J) + mu_pr / 2 * (J - 1)**2) psiNeq = (3**(1 - a1) / (2.0 * a1) * m1 * (Ie1**a1 - 3**a1) + 3**(1 - a2) / (2.0 * a2) * m2 * (Ie1**a2 - 3**a2) - (m1 + m2) * ln(Je)) return psiEq + psiNeq
def invariants_principal(A): """Principal invariants of (real-valued) tensor A. https://doi.org/10.1007/978-3-7091-0174-2_3 """ i1 = ufl.tr(A) i2 = (ufl.tr(A)**2 - ufl.tr(A * A)) / 2 i3 = ufl.det(A) return i1, i2, i3
def problem(): mesh = dolfin.UnitCubeMesh(MPI.comm_world, 10, 10, 10) cell = mesh.ufl_cell() vec_element = dolfin.VectorElement("Lagrange", cell, 1) # scl_element = dolfin.FiniteElement("Lagrange", cell, 1) Q = dolfin.FunctionSpace(mesh, vec_element) # Qs = dolfin.FunctionSpace(mesh, scl_element) # Coefficients v = dolfin.function.argument.TestFunction(Q) # Test function du = dolfin.function.argument.TrialFunction(Q) # Incremental displacement u = dolfin.Function(Q) # Displacement from previous iteration B = dolfin.Constant((0.0, -0.5, 0.0), cell) # Body force per unit volume T = dolfin.Constant((0.1, 0.0, 0.0), cell) # Traction force on the boundary # B, T = dolfin.Function(Q), dolfin.Function(Q) # Kinematics d = u.geometric_dimension() F = ufl.Identity(d) + grad(u) # Deformation gradient C = F.T * F # Right Cauchy-Green tensor # Invariants of deformation tensors Ic = tr(C) J = det(F) # Elasticity parameters E, nu = 10.0, 0.3 mu = dolfin.Constant(E / (2 * (1 + nu)), cell) lmbda = dolfin.Constant(E * nu / ((1 + nu) * (1 - 2 * nu)), cell) # mu, lmbda = dolfin.Function(Qs), dolfin.Function(Qs) # Stored strain energy density (compressible neo-Hookean model) psi = (mu / 2) * (Ic - 3) - mu * ln(J) + (lmbda / 2) * (ln(J))**2 # Total potential energy Pi = psi * dx - dot(B, u) * dx - dot(T, u) * ds # Compute first variation of Pi (directional derivative about u in the direction of v) F = ufl.derivative(Pi, u, v) # Compute Jacobian of F J = ufl.derivative(F, u, du) return J, F
def __init__(self, C, I): self.C = C self.I = I # Cauchy-Green invariants self.Ic = tr(C) self.IIc = 0.5 * (tr(C)**2. - tr(C * C)) self.IIIc = det(C) # isochoric Cauchy-Green invariants self.Ic_bar = self.IIIc**(-1. / 3.) * self.Ic self.IIc_bar = self.IIIc**(-2. / 3.) * self.IIc # Green-Lagrange strain and invariants (for convenience, used e.g. by St.-Venant-Kirchhoff material) self.E = 0.5 * (C - I) self.trE = tr(self.E) self.trE2 = tr(self.E * self.E)
def hyperelasticity_action_forms(mesh, vec_el): cell = mesh.ufl_cell() Q = dolfin.FunctionSpace(mesh, vec_el) # Coefficients v = dolfin.function.argument.TestFunction(Q) # Test function du = dolfin.function.argument.TrialFunction(Q) # Incremental displacement u = dolfin.Function(Q) # Displacement from previous iteration u.vector().set(0.5) B = dolfin.Constant((0.0, -0.5, 0.0), cell) # Body force per unit volume T = dolfin.Constant((0.1, 0.0, 0.0), cell) # Traction force on the boundary # Kinematics d = u.geometric_dimension() F = ufl.Identity(d) + grad(u) # Deformation gradient C = F.T * F # Right Cauchy-Green tensor # Invariants of deformation tensors Ic = tr(C) J = det(F) # Elasticity parameters E, nu = 10.0, 0.3 mu = dolfin.Constant(E / (2 * (1 + nu)), cell) lmbda = dolfin.Constant(E * nu / ((1 + nu) * (1 - 2 * nu)), cell) # Stored strain energy density (compressible neo-Hookean model) psi = (mu / 2) * (Ic - 3) - mu * ln(J) + (lmbda / 2) * (ln(J)) ** 2 # Total potential energy Pi = psi * dx - dot(B, u) * dx - dot(T, u) * ds # Compute first variation of Pi (directional derivative about u in the direction of v) F = ufl.derivative(Pi, u, v) # Compute Jacobian of F J = ufl.derivative(F, u, du) w = dolfin.Function(Q) w.vector().set(1.2) L = ufl.action(J, w) return None, L, None
def eig(A): """Eigenvalues of 3x3 tensor""" eps = 1.0e-12 q = ufl.tr(A) / 3.0 p1 = 0.5 * (A[0, 1]**2 + A[1, 0]**2 + A[0, 2]**2 + A[2, 0]**2 + A[1, 2]**2 + A[2, 1]**2) p2 = (A[0, 0] - q)**2 + (A[1, 1] - q)**2 + (A[2, 2] - q)**2 + 2 * p1 p = ufl.sqrt(p2 / 6) B = (A - q * ufl.Identity(3)) r = ufl.det(B) / (2 * p**3) r = ufl.Max(ufl.Min(r, 1.0 - eps), -1.0 + eps) phi = ufl.acos(r) / 3.0 eig0 = ufl.conditional(p2 < eps, q, q + 2 * p * ufl.cos(phi)) eig2 = ufl.conditional(p2 < eps, q, q + 2 * p * ufl.cos(phi + (2 * numpy.pi / 3))) eig1 = ufl.conditional(p2 < eps, q, 3 * q - eig0 - eig2) # since trace(A) = eig1 + eig2 + eig3 return eig0, eig1, eig2
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 test_div_grad_then_integrate_over_cells_and_boundary(): # Define 2D geometry n = 10 mesh = RectangleMesh(Point(0.0, 0.0), Point(2.0, 3.0), 2 * n, 3 * n) x, y = SpatialCoordinate(mesh) xs = 0.1 + 0.8 * x / 2 # scaled to be within [0.1,0.9] # ys = 0.1 + 0.8 * y / 3 # scaled to be within [0.1,0.9] n = FacetNormal(mesh) # 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([xs**xs]) reg( [xs**(xs**2)], 8 ) # Note: Accuracies here are from 1D case, not checked against 2D results. reg([xs**(xs**3)], 6) reg([xs**(xs**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) # 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]]) xxs = as_matrix([[2 * xs**2, 3 * xs**3], [11 * xs**5, 7 * xs**4]]) x3v = as_vector([3 * x**2, 5 * x**3, 7 * x**4]) cc = as_matrix([[2, 3], [4, 5]]) reg2( [xx] ) # TODO: Make unit test for UFL from this, results in listtensor with free indices 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 * xxs, 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 if debug: F_list = F_list[1:] for F, acc in F_list: if debug: print('\n', "F:", str(F)) # Integrate over domain and its boundary int_dx = assemble(div(grad(F)) * dx(mesh)) # noqa int_ds = assemble(dot(grad(F), n) * ds(mesh)) # noqa if debug: print(int_dx, int_ds) # Compare results. Using custom relative delta instead of # decimal digits here because some numbers are >> 1. delta = min(abs(int_dx), abs(int_ds)) * 10**-acc assert int_dx - int_ds <= delta
def inv(A): """Matrix invariants""" return ufl.tr(A), 1. / 2 * A[_i, _j] * A[_i, _j], ufl.det(A)
def dJdC(self, u_): C_ = variable(self.C(u_)) J = sqrt(det(C_)) return diff(J,C_)
def J(self, u_): return det(self.F(u_))
def dJedC(self, u_, theta_): C_ = variable(self.kin.C(u_)) Je = sqrt(det(self.C_e(C_, theta_))) return diff(Je,C_)
def J_e(self, u_, theta_): return det(self.kin.F(u_)*inv(self.F_g(theta_)))
def assemble_test(cell_batch_size: int): mesh = dolfin.UnitCubeMesh(MPI.comm_world, 40, 40, 40) def isochoric(F): C = F.T*F I_1 = tr(C) I4_f = dot(e_f, C*e_f) I4_s = dot(e_s, C*e_s) I8_fs = dot(e_f, C*e_s) def cutoff(x): return 1.0/(1.0 + ufl.exp(-(x - 1.0)*30.0)) def scaled_exp(a0, a1, argument): return a0/(2.0*a1)*(ufl.exp(b*argument) - 1) E_1 = scaled_exp(a, b, I_1 - 3.) E_f = cutoff(I4_f)*scaled_exp(a_f, b_f, (I4_f - 1.)**2) E_s = cutoff(I4_s)*scaled_exp(a_s, b_s, (I4_s - 1.)**2) E_3 = scaled_exp(a_fs, b_fs, I8_fs**2) E = E_1 + E_f + E_s + E_3 return E cell = mesh.ufl_cell() lamda = dolfin.Constant(0.48, cell) a = dolfin.Constant(1.0, cell) b = dolfin.Constant(1.0, cell) a_s = dolfin.Constant(1.0, cell) b_s = dolfin.Constant(1.0, cell) a_f = dolfin.Constant(1.0, cell) b_f = dolfin.Constant(1.0, cell) a_fs = dolfin.Constant(1.0, cell) b_fs = dolfin.Constant(1.0, cell) # For more fun, make these general vector fields rather than # constants: e_s = dolfin.Constant([0.0, 1.0, 0.0], cell) e_f = dolfin.Constant([1.0, 0.0, 0.0], cell) V = dolfin.FunctionSpace(mesh, ufl.VectorElement("CG", cell, 1)) u = dolfin.Function(V) du = dolfin.function.argument.TrialFunction(V) v = dolfin.function.argument.TestFunction(V) # Misc elasticity related tensors and other quantities F = grad(u) + ufl.Identity(3) F = ufl.variable(F) J = det(F) Fbar = J**(-1.0/3.0)*F # Define energy E_volumetric = lamda*0.5*ln(J)**2 psi = isochoric(Fbar) + E_volumetric # Find first Piola-Kircchoff tensor P = ufl.diff(psi, F) # Define the variational formulation F = inner(P, grad(v))*dx # Take the derivative J = ufl.derivative(F, u, du) a, L = J, F if cell_batch_size > 1: cxx_flags = "-O2 -ftree-vectorize -funroll-loops -march=native -mtune=native" else: cxx_flags = "-O2" assembler = dolfin.fem.assembling.Assembler([[a]], [L], [], form_compiler_parameters={"cell_batch_size": cell_batch_size, "enable_cross_cell_gcc_ext": True, "cpp_optimize_flags": cxx_flags}) t = -time.time() A, b = assembler.assemble( mat_type=dolfin.cpp.fem.Assembler.BlockType.monolithic) t += time.time() return A, b, t
def test_neohooke(): mesh = dolfinx.mesh.create_unit_cube(MPI.COMM_WORLD, 7, 7, 7) V = dolfinx.fem.VectorFunctionSpace(mesh, ("P", 1)) P = dolfinx.fem.FunctionSpace(mesh, ("P", 1)) L = dolfinx.fem.FunctionSpace(mesh, ("DG", 0)) u = dolfinx.fem.Function(V, name="u") v = ufl.TestFunction(V) p = dolfinx.fem.Function(P, name="p") q = ufl.TestFunction(P) lmbda0 = dolfinx.fem.Function(L) d = mesh.topology.dim Id = ufl.Identity(d) F = Id + ufl.grad(u) C = F.T * F J = ufl.det(F) E_, nu_ = 10.0, 0.3 mu, lmbda = E_ / (2 * (1 + nu_)), E_ * nu_ / ((1 + nu_) * (1 - 2 * nu_)) psi = (mu / 2) * (ufl.tr(C) - 3) - mu * ufl.ln(J) + lmbda / 2 * ufl.ln(J)**2 + (p - 1.0)**2 pi = psi * ufl.dx F0 = ufl.derivative(pi, u, v) F1 = ufl.derivative(pi, p, q) # Number of eigenvalues to find nev = 8 opts = PETSc.Options("neohooke") opts["eps_smallest_magnitude"] = True opts["eps_nev"] = nev opts["eps_ncv"] = 50 * nev opts["eps_conv_abs"] = True # opts["eps_non_hermitian"] = True opts["eps_tol"] = 1.0e-14 opts["eps_max_it"] = 1000 opts["eps_error_relative"] = "ascii::ascii_info_detail" opts["eps_monitor"] = "ascii" slepcp = dolfiny.slepcblockproblem.SLEPcBlockProblem([F0, F1], [u, p], lmbda0, prefix="neohooke") slepcp.solve() # mat = dolfiny.la.petsc_to_scipy(slepcp.eps.getOperators()[0]) # eigvals, eigvecs = linalg.eigsh(mat, which="SM", k=nev) with dolfinx.io.XDMFFile(MPI.COMM_WORLD, "eigvec.xdmf", "w") as ofile: ofile.write_mesh(mesh) for i in range(nev): eigval, ur, ui = slepcp.getEigenpair(i) # Expect first 6 eignevalues 0, i.e. rigid body modes if i < 6: assert np.isclose(eigval, 0.0) for func in ur: name = func.name func.name = f"{name}_eigvec_{i}_real" ofile.write_function(func) func.name = name
# Functions u = Coefficient(element) # Displacement from previous iteration # B = Coefficient(element) # Body force per unit volume # T = Coefficient(element) # Traction force on the boundary # Now, we can define the kinematic quantities involved in the model:: # Kinematics d = len(u) I = Identity(d) # Identity tensor F = variable(I + grad(u)) # Deformation gradient C = F.T*F # Right Cauchy-Green tensor # Invariants of deformation tensors Ic = tr(C) J = det(F) # Before defining the energy density and thus the total potential # energy, it only remains to specify constants for the elasticity # parameters:: # Elasticity parameters E = 10.0 nu = 0.3 mu = E/(2*(1 + nu)) lmbda = E*nu/((1 + nu)*(1 - 2*nu)) # Both the first variation of the potential energy, and the Jacobian of # the variation, can be automatically computed by a call to # ``derivative``::
def holzapfel_ogden(mesh, q, p, nf=0): # Based on https://gist.github.com/meg-simula/3ab3fd63264c8cf1912b # # Original credit note: # "Original implementation by Gabriel Balaban, # modified by Marie E. Rognes" from ufl import (Constant, VectorConstant, Identity, Coefficient, TestFunction, conditional, det, diff, dot, exp, grad, inner, tr, variable) # Define some random parameters a = Constant(mesh) b = Constant(mesh) a_s = Constant(mesh) b_s = Constant(mesh) a_f = Constant(mesh) b_f = Constant(mesh) a_fs = Constant(mesh) b_fs = Constant(mesh) # For more fun, make these general vector fields rather than # constants: e_s = VectorConstant(mesh) e_f = VectorConstant(mesh) # Define the isochoric energy contribution def isochoric(C): I_1 = tr(C) I4_f = dot(e_f, C*e_f) I4_s = dot(e_s, C*e_s) I8_fs = dot(e_s, C*e_f) def heaviside(x): return conditional(x < 1, 0, 1) def scaled_exp(a, b, x): return a/(2*b)*(exp(b*x) - 1) E_1 = scaled_exp(a, b, I_1 - 3) E_f = heaviside(I4_f)*scaled_exp(a_f, b_f, (I4_f - 1)**2) E_s = heaviside(I4_s)*scaled_exp(a_s, b_s, (I4_s - 1)**2) E_3 = scaled_exp(a_fs, b_fs, I8_fs**2) E = E_1 + E_f + E_s + E_3 return E # Define mesh and function space V = ufl.FunctionSpace(mesh, ufl.VectorElement("CG", mesh.ufl_cell(), q)) u = Coefficient(V) v = TestFunction(V) # Misc elasticity related tensors and other quantities I = Identity(mesh.ufl_cell().topological_dimension()) F = grad(u) + I F = variable(F) J = det(F) Cbar = J**(-2/3)*F.T*F # Define energy Psi = isochoric(Cbar) # Find first Piola-Kirchhoff tensor P = diff(Psi, F) # Define the variational formulation it = inner(P, grad(v)) P = ufl.FunctionSpace(mesh, ufl.VectorElement('P', mesh.ufl_cell(), p)) f = [ufl.Coefficient(P) for _ in range(nf)] return ufl.derivative(reduce(ufl.inner, list(map(ufl.div, f)) + [it])*ufl.dx, u)