Coverage for pyxfem/solidmechanics/elasticity.py: 46%
98 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-24 07:17 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-24 07:17 +0000
1''' Tools for Elasticity '''
2from enum import Enum
3from abc import ABC, abstractmethod
4import warnings
5import numpy as np
6import scipy as sp
7from pyxfem.postpro.interpolator import ConstantPerTriInterpolator, LinearPerTriInterpolator, get_trifinder
10class MaterialHypothesis(Enum):
11 """ Enum class to define the avalaible Material hypothesis"""
12 PLANESTRESS = 1
13 PLANESTRAIN = 2
14 DIM3 = 3
16PLANESTRESS = MaterialHypothesis.PLANESTRESS
17PLANESTRAIN = MaterialHypothesis.PLANESTRAIN
18DIM3 = MaterialHypothesis.DIM3
19HYP2DIM = {PLANESTRESS:2, PLANESTRAIN:2, DIM3:3}
21def YoungPoisson2LambdaMu(E, nu): # pylint: disable=C0103
22 """ Return Lame coefficients (Lambda, mu) from Young Modulus (E) and poisson ration nu """
23 lamb, mu = (E*nu/(1.+nu)/(1.-2*nu), E/2./(1+nu))
24 return lamb, mu
26class LinearIsotropeElasticMaterial():
27 ''' Linear isotrope elastic material '''
28 def __init__(self, E:float = 1., nu:float= 0.3, # pylint: disable=C0103
29 hyp2d:MaterialHypothesis = PLANESTRAIN,
30 elastic_limit:float = 1.):
31 self.E = E # pylint: disable=C0103
32 self.nu = nu
33 self.lamb, self.mu = YoungPoisson2LambdaMu(E, nu)
34 self.hypothesis = hyp2d
35 self.elastic_limit = elastic_limit
36 if hyp2d == PLANESTRAIN :
37 kappa = 3-4*nu # kolossov constant en deformation plane (echantillon massif)
38 self.C2222CSR = planeStrainC2222CSR(lam = self.lamb, mu = self.mu) # pylint: disable=C0103
39 elif hyp2d == PLANESTRESS :
40 kappa = (3-nu)/(1+nu)
41 warnings.warn("warning, material tangent operator not definined yet for PLANESTRESS")
42 self.C2222CSR = None
43 else :
44 kappa = None
45 self.kappa = kappa
46 self.C2222CSR = planeStrainC2222CSR(lam = self.lamb, mu = self.mu)
48class Tensor2222CSR():
49 ''' grouping CSR matrix repesentation of order 4 tensor with base space of dimension 2
51 For now we have, for A a second order tensor (repesented as a 2d matrix in rowmajor storage)
52 - ISym: .5 (AT + A) = ISym: A -> ISym.dot(A.flatten()).reshape((2, 2))
53 - IxI I*trace(A) = IxI: A -> IxI.dot(A.flatten()).reshape((2, 2))
54 '''
55 _data = np.array([1., .5, .5, .5, .5, 1.])
56 _row = np.array([0, 1, 2, 1, 2, 3])
57 _col = np.array([0, 1, 1, 2, 2, 3])
58 ISym = sp.sparse.csr_array((_data, (_row, _col)))
59 _data = np.array([1., 1., 1., 1.])
60 _col = np.array([0, 0, 3, 3])
61 _row = np.array([0, 3, 0, 3])
62 IxI = sp.sparse.csr_array((_data, (_row, _col)))
64ISym = Tensor2222CSR.ISym
65IxI = Tensor2222CSR.IxI
67def planeStrainC2222CSR(*, lam:float|None = None, mu:float|None = None, # pylint: disable=C0103
68 mat:LinearIsotropeElasticMaterial|None = None ):
69 ''' Compute the C operator (tensor 4) for isotropic material
70 such as sigma = C:eps, for plane strain isotrope assumption
71 input : lambda and mu Lamé's coefficient or a linearIsotropeElasticMaterial instance.
72 '''
73 if lam is None or mu is None :
74 if mat is not None:
75 lam = mat.lamb
76 mu = mat.mu
77 else :
78 raise ValueError
79 return lam*IxI + 2*mu*ISym
81def stress_from_strain(strain, lamb, mu):
82 '''compute stress from plane deformation Strain. return sigma2d in voigt format and sigmazz'''
83 ltr = lamb*(strain[0] + strain[1])
84 sigmaxx = ltr + 2.*mu*strain[0]
85 sigmayy = ltr + 2.*mu*strain[1]
86 sigmaxy = 2.*mu*strain[2]
87 sigmazz = ltr
88 print(sigmaxx.shape, sigmayy.shape, sigmaxy.shape)
89 sigma2d = np.array([sigmaxx, sigmayy, sigmaxy])
90 return sigma2d, sigmazz
92def VonMises(stress2d, sigmazz): # pylint: disable=C0103
93 ''' return the von mises stress '''
94 sxx = stress2d[0]
95 syy = stress2d[1]
96 sxy = stress2d[2]
97 szz =sigmazz
98 p = (sxx+syy+szz)/3
99 dxx = sxx-p
100 dyy = syy-p
101 dzz = szz-p
102 dxy = sxy
103 return np.sqrt(dyy**2+dxx**2+dzz**2+2*dxy**2)
105def to_voigt2d(s11, s22, s12):
106 ''' stack the 3 stress component in voigt format '''
107 return np.stack([s11, s22, s12], axis =-1)
109def to_tensor2d(s11, s22, s12):
110 ''' stack the 3 stress component in 2d tenso2r format '''
111 return np.stack([s11, s12, s12, s22], axis =-1).reshape( s11.shape + (2,2))
113class ElasticField(ABC):
114 ''' base class for elastic field : thing thah can evaluate displacement and stress
115 (and strain ..) at each point xy
116 '''
117 @abstractmethod
118 def displacement(self, xy):
119 ''' return the displacement vector at points xy '''
120 raise NotImplementedError
121 @abstractmethod
122 def stress(self, xy):
123 ''' return the stress tensor at points xy '''
124 raise NotImplementedError
125 @abstractmethod
126 def strain(self, xy):
127 ''' return the strain tensor at points xy '''
128 raise NotImplementedError
130class P1FemElasticfield(ElasticField):
131 ''' implementation of ElasticField that use the classical
132 P1 Fem interpolation (over triangles) '''
133 def __init__(self, xy, t2v, vdisp, estress, trifinder = None):
134 if trifinder is None:
135 trifinder = get_trifinder(xy=xy, t2v =t2v)
136 self.stress_interp = ConstantPerTriInterpolator(xy, t2v, estress, trifinder = trifinder)
137 self.disp_interp = LinearPerTriInterpolator(xy, t2v, values = vdisp,
138 trifinder = trifinder)
139 def displacement(self, xy):
140 return self.disp_interp(xy)
141 def stress(self, xy):
142 return self.stress_interp(xy)
143 def strain(self, xy):
144 raise NotImplementedError