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.
Esempio n. 7
0
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]
Esempio n. 8
0
    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
Esempio n. 9
0
File: run.py Progetto: miklos1/urumi
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)
Esempio n. 10
0
    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
Esempio n. 11
0
 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
Esempio n. 13
0
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
Esempio n. 14
0
    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
Esempio n. 15
0
 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
Esempio n. 16
0
    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!")
Esempio n. 18
0
    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
Esempio n. 19
0
    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
Esempio n. 20
0
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
Esempio n. 21
0
    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
Esempio n. 23
0
    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
Esempio n. 24
0
    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
Esempio n. 25
0
# 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),
Esempio n. 26
0
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
Esempio n. 27
0
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)
Esempio n. 28
0
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
Esempio n. 29
0
    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
Esempio n. 31
0
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
Esempio n. 32
0
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