Permalink
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
176 lines (137 sloc) 6.32 KB
import numpy as np
import scipy.sparse.linalg as sp
import itertools
# turn of warning for zero division (occurs due to vectorization)
np.seterr(divide='ignore', invalid='ignore')
# ----------------------------------- GRID ------------------------------------
Nx = 31 # number of voxels in x-direction
Ny = 31 # number of voxels in y-direction
Nz = 1 # number of voxels in z-direction
shape = [Nx,Ny,Nz] # number of voxels as list: [Nx,Ny,Nz]
ndof = 3**2*Nx*Ny*Nz # number of degrees-of-freedom
# ---------------------- PROJECTION, TENSORS, OPERATIONS ----------------------
# tensor operations/products: np.einsum enables index notation, avoiding loops
# e.g. ddot42 performs $C_ij = A_ijkl B_lk$ for the entire grid
trans2 = lambda A2 : np.einsum('ijxyz ->jixyz ',A2 )
ddot22 = lambda A2,B2: np.einsum('ijxyz ,jixyz ->xyz ',A2,B2)
ddot42 = lambda A4,B2: np.einsum('ijklxyz,lkxyz ->ijxyz ',A4,B2)
ddot44 = lambda A4,B4: np.einsum('ijklxyz,lkmnxyz->ijmnxyz',A4,B4)
dot11 = lambda A1,B1: np.einsum('ixyz ,ixyz ->xyz ',A1,B1)
dot22 = lambda A2,B2: np.einsum('ijxyz ,jkxyz ->ikxyz ',A2,B2)
dot24 = lambda A2,B4: np.einsum('ijxyz ,jkmnxyz->ikmnxyz',A2,B4)
dot42 = lambda A4,B2: np.einsum('ijklxyz,lmxyz ->ijkmxyz',A4,B2)
dyad22 = lambda A2,B2: np.einsum('ijxyz ,klxyz ->ijklxyz',A2,B2)
# identity tensor [single tensor]
i = np.eye(3)
# identity tensors [grid of tensors]
I = np.einsum('ij,xyz' , i ,np.ones([Nx,Ny,Nz]))
I4 = np.einsum('ijkl,xyz->ijklxyz',np.einsum('il,jk',i,i),np.ones([Nx,Ny,Nz]))
I4rt = np.einsum('ijkl,xyz->ijklxyz',np.einsum('ik,jl',i,i),np.ones([Nx,Ny,Nz]))
II = dyad22(I,I)
I4s = (I4+I4rt)/2.
I4d = (I4s-II/3.)
# projection operator (zero for zero frequency, associated with the mean)
# NB: vectorized version of "../linear-elasticity.py"
# - allocate / define support function
Ghat4 = np.zeros([3,3,3,3,Nx,Ny,Nz]) # projection operator
x = np.zeros([3 ,Nx,Ny,Nz],dtype='int64') # position vectors
q = np.zeros([3 ,Nx,Ny,Nz],dtype='int64') # frequency vectors
delta = lambda i,j: np.float(i==j) # Dirac delta function
# - set "x" as position vector of all grid-points [grid of vector-components]
x[0],x[1],x[2] = np.mgrid[:Nx,:Ny,:Nz]
# - convert positions "x" to frequencies "q" [grid of vector-components]
for i in range(3):
freq = np.arange(-(shape[i]-1)/2,+(shape[i]+1)/2,dtype='int64')
q[i] = freq[x[i]]
# - compute "Q = ||q||", and "norm = 1/Q" being zero for the mean (Q==0)
# NB: avoid zero division
q = q.astype(np.float64)
Q = dot11(q,q)
Z = Q==0
Q[Z] = 1.
norm = 1./Q
norm[Z] = 0.
# - set projection operator [grid of tensors]
for i, j, l, m in itertools.product(range(3), repeat=4):
Ghat4[i,j,l,m] = -(norm**2.)*(q[i]*q[j]*q[l]*q[m])+\
.5*norm*( delta(j,l)*q[i]*q[m]+delta(j,m)*q[i]*q[l] +\
delta(i,l)*q[j]*q[m]+delta(i,m)*q[j]*q[l] )
# (inverse) Fourier transform (for each tensor component in each direction)
fft = lambda x: np.fft.fftshift(np.fft.fftn (np.fft.ifftshift(x),[Nx,Ny,Nz]))
ifft = lambda x: np.fft.fftshift(np.fft.ifftn(np.fft.ifftshift(x),[Nx,Ny,Nz]))
# functions for the projection 'G', and the product 'G : K : eps'
G = lambda A2 : np.real( ifft( ddot42(Ghat4,fft(A2)) ) ).reshape(-1)
K_deps = lambda depsm: ddot42(K4,depsm.reshape(3,3,Nx,Ny,Nz))
G_K_deps = lambda depsm: G(K_deps(depsm))
# ------------------- PROBLEM DEFINITION / CONSTITIVE MODEL -------------------
# constitutive response to a certain loading
# NB: completely uncoupled from the FFT-solver, but implemented as a regular
# grid of quadrature points, to have an efficient code;
# each point is completely independent, just evaluated at the same time
# NB: all points for both models, but selectively ignored per materials
# this avoids loops or a problem specific constitutive implementation
# linear elasticity
# -----------------
def elastic(eps):
# parameters
K = 2. # bulk modulus
mu = 1. # shear modulus
# elastic stiffness tensor, and stress response
C4 = K*II+2.*mu*I4d
sig = ddot42(C4,eps)
return sig,C4
# non-linear elasticity
# ---------------------
def nonlin_elastic(eps):
K = 2. # bulk modulus
sig0 = 0.5 # reference stress
eps0 = 0.1 # reference strain
n = 10. # hardening exponent
epsm = ddot22(eps,I)/3.
epsd = eps-epsm*I
epseq = np.sqrt(2./3.*ddot22(epsd,epsd))
sig = 3.*K*epsm*I+2./3.*sig0/(eps0**n)*(epseq**(n-1.))*epsd
sig = 3.*K*epsm*I*(epseq==0.).astype(np.float)+sig*(epseq!=0.).astype(np.float)
K4_d = 2./3.*sig0/(eps0**n)*(dyad22(epsd,epsd)*2./3.*(n-1.)*epseq**(n-3.)+epseq**(n-1.)*I4d)
K4 = K*II+K4_d*(epseq!=0.).astype(np.float)
return sig,K4
# laminate of two materials
# -------------------------
def constitutive(eps):
phase = np.zeros([Nx,Ny,Nz]); phase[:26,:,:] = 1.
sig_P1,K4_P1 = nonlin_elastic(eps)
sig_P2,K4_P2 = elastic(eps)
sig = phase*sig_P1+(1.-phase)*sig_P2
K4 = phase*K4_P1 +(1.-phase)*K4_P2
return sig,K4
# ----------------------------- NEWTON ITERATIONS -----------------------------
# initialize: strain and stress/tangent
eps = np.zeros([3,3,Nx,Ny,Nz])
sig,K4 = constitutive(eps)
# set macroscopic loading
DE = np.zeros([3,3,Nx,Ny,Nz])
DE[0,1] += 0.05
DE[1,0] += 0.05
# initial residual: distribute "DE" over grid using "K4"
b = -G_K_deps(DE)
eps += DE
# compute DOF-normalization, set Newton iteration counter
En = np.linalg.norm(eps)
iiter = 0
# iterate as long as the iterative update does not vanish
while True:
# solve linear system using the Conjugate Gradient iterative solver
depsm,_ = sp.cg(tol=1.e-14,
A = sp.LinearOperator(shape=(ndof,ndof),matvec=G_K_deps,dtype='float'),
b = b,
)
# add solution of linear system to DOFs
eps += depsm.reshape(3,3,Nx,Ny,Nz)
# new residual
sig,K4 = constitutive(eps)
b = -G(sig)
# check for convergence
print('{0:10.2e}'.format(np.linalg.norm(depsm)/En))
if np.linalg.norm(depsm)/En<1.e-6 and iiter>0: break
# update Newton iteration counter
iiter += 1