← Back to team overview

yade-users team mailing list archive

[Question #697461]: Triaxial Dense no Peak

 

New question #697461 on Yade:
https://answers.launchpad.net/yade/+question/697461

Hello everyone,

I am a PhD Student at INSA de lyon and I am making a rectangular triaxial simulation of a real experiment that exists here. 

I have managed to create a simulation following the many examples in this forum and the triax-tutorial/session1 file.  But I am having problems simulating a dense soil behavior (hardening - peak - softening). I tried the following methods to create a dense specimen: 

1 - Insertion through cloud with 0 friction angle and then apply radius expansion,
2 - Insertion through cloud with a high friction angle (45) and then apply radius expansion. Next gradual reduction of the friction angle (from 45 to 0.1) with piston activation to reduce the porosity
3 - Insertion through cloud with a high friction angle (45) and then apply  radius expansion. Next gradual reduction of the friction angle (from 45 to 0.1) with radius expansion to reduce the porosity

In all three cases I cannot get a peak stress, it seems like the soil is not  loose but also not dense enough. In the 2 and 3 cases I manage to reduce the porosity from 0.45 to 0.4 .

In this Google drive folder you will be able to find the script and also a Image of the behavior of my simulations.
https://drive.google.com/drive/folders/1Fdw4PusTu-Li1z45kz1JrEfQeYGyga69?usp=sharing

PS:  in the image the porosity is not updated after the start of the real triaxial test thus leading to a constant porosity. 
PS 2 : I am a little too obssessed with automating things, so I hope my script does not confuse you.

Thank you very much for your time,

GONCALVES DE O C Joao
joao.goncalves-de-oliveira-chueire@xxxxxxxxxxxx

SCRIPT ::

# encoding: utf-8
# 
# Made by GONCALVES DE OLIVEIRA CHUEIRE Joao Augusto as part of PhD at INSA de Lyon
#
# The initial packing composed of a cloud of specified granulometry (radii and percentages)
# The triaxial consists of 3 stages:
#
# 1. Calibration : after grains insertion their size is increased untill sigStar value
#	is reached to create the sample asked.	
# 2. Isotropic consolidation : piston advancing until sigCons is reached in all directions;
# 3. Triaxial test : Constant-strain compression along the z-axis, while maintaining constant  
#	stress (sigCons) laterally. Run untill a certain vertical strain level is reached; 
# 
# Controlling of strain and stresses is performed via TriaxialStressController,
# of which parameters determine type of control and also stability condition .
# The changes between each phase is made with the checkState function and the 
# variable stt_dict.
#

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Variables		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# Geometry
w=0.10				# specimen targeted widith in 0 direction
l=0.35				# specimen targeted length in 1 direction
h=0.45				# specimen targeted height in 2 direction

# Material Parameters
	#grain
gDsty 	= 2600		# grain density
gYoung 	= 1e9		# grain young modulus
gPRat 	= 0.3		# grain poisson ratio
gFrAng 	= 30		# grain friciton angle
	#wall
wDsty 	= 0		# wall density
wYoung 	= 50e9		# wall young modulus
wPRat 	= 0.3		# wall poisson ratio
wFrAng 	= 0.0		# wall friciton angle

# Simulation Parameters - [convention : positive is traction]
	#insertion parameters
insT 	='NEW'		# insertion type : 'NEW' or 'LOAD' 
tPo	= 0.40		# targeted porosity
rfz	= 0.33		# radius delta factor
nPart	= 3000		# nb of particles
	#triaxial parameters
sigStar	=-90e3		# calibration stress
sigCons	=-100e3		# consolidation stress
sigComp	=-100e3		# compressison stress
thk 	= 0.001		# piston thickness
lRate	=-0.05		# compression strain rate
dplSpd	= 0.1		# piston maximal displacement speed
eMax	= 0.15		# strain value to stop execution
	#general
Damp 	= 0.2		# damp coef

# Save path - Matlab & VTK
svPath='generalvariables/'
vtkPath='vtk/'

#acceptable errors to pass to next phase
uFR = 0.03		# unbalanced force on Radius Increase phase
uFS = 0.01		# unbalanced force on Calibration phase
uFC = 0.001		# unbalanced force on Consolidation phase
stS = 5000		# stress on Calibration phase
stC = 500		# stress on Consolidation phase
mnI = 20e3		# minimal number of timesteps to nextphase

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Start files		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# Auto generated variables
startT = O.time		# time calculation
stt_dict={'val': 0} # state variable used on stateCheck function

# Imports 
from datetime import date
import errno
from yade import pack, qt, plot,os,timing

#Create folder
try:
	os.mkdir(svPath)
except OSError as exc:
	if exc.errno != errno.EEXIST:
		raise
	pass
try:
	os.mkdir(vtkPath)
except OSError as exc:
	if exc.errno != errno.EEXIST:
		raise
	pass

#Create StressForce files
fnm=svPath+'servoforce.txt'
f=open(fnm,'w')
f.write('Yade triaxial simulation - Piston files \n')
f.write('Executed at '+date.today().strftime("%d/%m/%Y")+' by GONCALVES Joao\n')
f.write('Step\tSx\tSy\tSz\tUz\tUy1\tUy2\tUx1\tUx2 \n' )
f.close()

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Materials and geometries		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

# first choose a high gFrAng0
if gFrAng>45:
	gFrAng0=float(gFrAng)
else:
	gFrAng0=45.

#Create materials for spheres and plates
SphereMat = O.materials.append(FrictMat(young = gYoung, poisson = gPRat,
	frictionAngle = radians(gFrAng0), density = gDsty))
WallMat = O.materials.append(FrictMat(young = wYoung, poisson = wPRat,
	frictionAngle = radians(wFrAng), density = wDsty))

if insT=='NEW':
	# Create box - box are created before spheres, thus the triax_ID is not redefined
	mn,mx = Vector3(-w/2,0.,0.),Vector3(w/2,l,h)
	wallIds=O.bodies.append(aabbWalls([mn,mx],thickness=thk,oversizeFactor=2,material=WallMat))
	#Changing following creating type 
	sp=pack.SpherePack()
	sp.makeCloud(minCorner=mn,maxCorner=mx,rMean=-1,rRelFuzz=rfz,
		num=nPart,periodic=False)
	sp.toSimulation(material = SphereMat)
elif insT=='LOAD':
	#Try to load file
	loadNm=insT.upper()+'ins.yade.bz2'
	try:
		sp.load(loadNm)
	except:
		print('#**#**   File not found in the root of the execution script   **#**#')
		exit()
else:
	print('#**#**   New/Load Preference not set   **#**#')
	exit()

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Engines		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Triaxial Controller
triax1= TriaxialStressController(
	# specify target values and whether they are strains or stresses
	goal1=sigStar,
	goal2=sigStar,
	goal3=sigStar,
	## switch stress/strain control using a bitmask. What is a bitmask, huh?!
	## Say x=1 if stess is controlled on x, else x=0. Same for for y and z, which are 1 or 0.
	## Then an integer uniquely defining the combination of all these tests is: mask = x*1 + y*2 + z*4
	## to put it differently, the mask is the integer whose binary representation is zyx, i.e.
	## "001" (1) means "x", "011" (3) means "x and y", "111" (7) means "x and y and z", etc.
	stressMask=7,
	thickness = thk,
	max_vel=dplSpd,	# velocity
	wall_back_activated = False,	#turn off movment on the wall direction 2(z) movment +
	internalCompaction = True,		#If true the confining pressure is generated by growing particles
	maxMultiplier=1.+1.5e5/gYoung,	# spheres growing factor (fast growth)
	finalMaxMultiplier=1.+4e3/gYoung,
	computeStressStrainInterval = 10,
	)

# Contact friction
newton=NewtonIntegrator(damping=Damp)
O.dt = 0.25*PWaveTimeStep()

#Stuff?
O.usesTimeStepper=True
O.timingEnabled=True
#O.trackEnergy=True # enable energy tracking in the code

# Start engine
O.engines=[
	ForceResetter(),
	InsertionSortCollider([Bo1_Sphere_Aabb(),Bo1_Box_Aabb()]),
	InteractionLoop(
		[Ig2_Sphere_Sphere_ScGeom(),Ig2_Box_Sphere_ScGeom()],
		[Ip2_FrictMat_FrictMat_FrictPhys()],
		[Law2_ScGeom_FrictPhys_CundallStrack()],
	),
	#GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.4),
	triax1,
	newton,
	PyRunner(iterPeriod=1000,command='addPlotData()'),
	PyRunner(iterPeriod=1000,command='cmdUpdate()'),
	PyRunner(iterPeriod=2000,command='checkState()'),
	PyRunner(iterPeriod=2000,command='saveFiles()'),
	#VTKRecorder(iterPeriod=5000,fileName=vtkPath+'vt',recorders=['spheres','bstresses','coordNumber','mass','velocity'])
	#TriaxialStateRecorder(iterPeriod=2000,file=svPath+'WallStresses')
]

# Add plots
plot.plots={'i':('ub',),'i ':('s1','s2','s3'),' i':('e1','e2','e3'),' i  ':('por'),
	# energy plot' i ':(O.energy.keys,None,'totE'),
}
# Plot
plot.plot()

if insT=='LOAD':
	# If load jump directly to the consolidation
	prepareCalibration()
	prepareConsolidation()

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Main Functions		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def checkState():
	global stt_dict,uFR,uFS,uFC,stS,stC,mnI,emax,gFrAng0,Porosity
	# Function that controls the triaxial engine behavior in three phases
	if stt_dict['val']==0:
		# Diam change : increase the diameter of the particles to create a sample
		# where are particles are in contact. Using a high gFrAngle0
		unb=unbalancedForce()
		strXY =(triax1.stress(triax1.wall_right_id)[0]+triax1.stress(triax1.wall_top_id)[1])/2
		strZ=triax1.stress(triax1.wall_front_id)[2]
		if unb<uFR and abs(sigStar-strXY)<stS and abs(sigStar-strZ)<stS and O.iter>mnI:
			prepareCalibration()
	if stt_dict['val']==1:
		# Calibration : gradually lower the value of gFrAng0 until the target porosity is reached
		unb=unbalancedForce()
		if unb<uFS:
			if Porosity<=tPo:
				prepareConsolidation()
			else:
				if gFrAng0==0.20:
					prepareConsolidation()
					print('Targeted porosity could not be reached, continuing with p=' + str(Porosity))
					return
				elif gFrAng0>5.:
					gFrAng0=5.
				elif gFrAng0==5.:
					gFrAng0=1.
				elif gFrAng0==1.:
					gFrAng0=0.20
				O.materials[0].frictionAngle=radians(gFrAng0)
				print('Ang='+str(gFrAng0)+' Porosity :'+ str(Porosity))
				setContactFriction(radians(gFrAng0))
				stt_dict.update({'consoSt':O.iter})
	elif stt_dict['val']==2:
		# Consolidation : insotropic control untill all pistons are applying the same
		# stress to the grains.
		unb=unbalancedForce()
		strXY =(triax1.stress(triax1.wall_right_id)[0]+triax1.stress(triax1.wall_top_id)[1])/2
		strZ=triax1.stress(triax1.wall_front_id)[2]
		if unb<uFC and abs(sigCons-strXY)<stC and abs(sigCons-strZ)<stC and (O.iter-stt_dict['consoSt'])>mnI:
			prepareCompression()
	else:
		# Compression : piston direction 2 control changed to strian. Run untill the 
		# maximal strain defined above is applyed. 
		e2=-triax1.strain[2]
		if e2>eMax:
			O.save(svPath+insT.upper()+'compEnd.yade.bz2')
			stt_dict.update({'compEnd':O.iter})
			print('#**#**   End Compression at '+ str(stt_dict['compEnd'])+'   **#**#')
			# Prepare to end simulation
			#Save plot data
			plot.saveDataTxt(svPath+'PlotData.txt.bz2')
			#Save last files
			saveFiles()
			#create file for Matlab
			endFile()
			O.pause()		

def prepareCalibration():
	global stt_dict
	# Count nb of spheres
	nbSph=len(O.bodies)-6
	# Save variables for later usage
	stt_dict.update({'val': 1,'consoSt':O.iter,'nbS':nbSph})
	# add gravity
	newton.gravity = (0.0,0.0,-9.81)
	# Prind in cmd line
	print('#**#**   End Diametre change at '+ str(O.iter)+'   **#**#')
	
def prepareConsolidation():
	global stt_dict
	# Prepare for consolidation
	# Save state
	O.save(svPath+insT.upper()+'ins.yade.bz2')
	# Save variables for later usage
	stt_dict.update({'val': 2,'consoSt':O.iter,'Pz':O.bodies[5].state.pos[2],
		'Py1':O.bodies[3].state.pos[1],'Py2': O.bodies[2].state.pos[1],
		'Px1':O.bodies[1].state.pos[0],'Px2':O.bodies[0].state.pos[0]})
	# Change piston states
	triax1.goal1 = sigCons
	triax1.goal2 = sigCons
	triax1.goal3 = sigCons
	triax1.depth0 = triax1.depth
	triax1.height0 = triax1.height
	triax1.width0 = triax1.width
	triax1.internalCompaction = False
	#update material values
	O.materials[0].frictionAngle=radians(gFrAng)
	setContactFriction(radians(gFrAng))
	# restart plot data
	#if insT!='LOAD':
		#plot.saveDataTxt(svPath+'InsertionPlotData.txt.bz2')
		#plot.resetData()
	# Prind in cmd line
	print('#**#**   End Calibration at '+ str(stt_dict['consoSt'])+'   **#**#')
	print('w: '+str(triax1.depth)+'\t l: '+str(triax1.height)+'\t h: '+ str(triax1.width))

def prepareCompression():
	global stt_dict
	# Save variables
	O.save(svPath+insT.upper()+'consoEnd.yade.bz2')
	stt_dict.update({'val': 3,'consoEnd':O.iter})
	# Change piston states
	triax1.goal1 = sigComp
	triax1.goal2 = sigComp
	triax1.goal3 = lRate
	triax1.stressMask=3 	# integer for binary 011 
	# Prind in cmd line
	print('#**#**   End Consolidation at '+ str(stt_dict['consoEnd'])+'   **#**#')

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Execution Data		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def cmdUpdate():
	sxy=(-triax1.stress(triax1.wall_right_id)[0]-triax1.stress(triax1.wall_top_id)[1])/2
	print('I: %d\t ubf: %.4f\tsxy: %.0f\tsz: %.0f\tez: %.3f'%(O.iter,unbalancedForce(),
		sxy,-triax1.stress(triax1.wall_front_id)[2],-triax1.strain[2]))

# Plots preparation
def addPlotData():
	global Porosity
	#mStress = -(triax1.stress(triax1.wall_right_id)[0]+triax1.stress(triax1.wall_top_id)[1]+
	# triax1.stress(triax1.wall_front_id)[2])/3.0
	#Vol = ((O.bodies[1].state.pos[0]-O.bodies[0].state.pos[0])*
	#	(O.bodies[3].state.pos[1]-O.bodies[2].state.pos[1])*
	#	(O.bodies[5].state.pos[2]-O.bodies[4].state.pos[2]))
	# Volume spheres
	Vsph = sum(4.0/3.0*pi*b.shape.radius**3 for b in O.bodies if isinstance(b.shape,Sphere))
	Porosity = 1-Vsph/(h*w*l)
	plot.addData(e1=-triax1.strain[0], e2=-triax1.strain[1], e3=-triax1.strain[2],
		ev=-triax1.strain[0]-triax1.strain[1]-triax1.strain[2],
		s1=-triax1.stress(triax1.wall_right_id)[0],
		s2=-triax1.stress(triax1.wall_top_id)[1],
		s3=-triax1.stress(triax1.wall_front_id)[2],
		ub=unbalancedForce(),
		i=O.iter,
		por=Porosity
		#totE=O.energy.total(),**O.energy
		#dstress=(-triax1.stress(triax1.wall_top_id)[1])-(-triax1.stress(triax1.wall_right_id)[0]),
		#disx=triax1.width,
		#disy=triax1.height,
		#disz=triax1.depth,
		#porosity=Porosity,
		#ke=utils.kineticEnergy(),
		)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#############################		Save Functions		#############################
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

def saveGrains():
	# Save grains details into a txt file for matlab to read
	global stt_dict
	# per grain stress calculation following https://yade-dem.org/doc/user.html#micro-stress-and-micro-strain
	TW=TesselationWrapper()
	TW.setState()
	TW.computeVolumes()
	stress=bodyStressTensors()
	# start writing 
	fnm=svPath+'grains'+str(O.iter)+'.txt'
	f=open(fnm,'w')
	f.write('Yade triaxial simulation - Grains files for step'+str(O.iter)+'\n')
	f.write('Executed at '+date.today().strftime("%d/%m/%Y")+' by GONCALVES Joao\n')
	f.write('Number of grains : '+str(stt_dict['nbS'])+'\n')
	f.write('ID\tX\tY\tZ\tRadius\tUx\tUy\tUz\tSx\tSy\tSz\tSxy\tSxz\tSyz \n' )
	for k in O.bodies:
		if isinstance(k.shape, Sphere):  
			stp = stress[k.id]*4.*pi/3.*k.shape.radius**3/TW.volume(k.id)
			[ID,r,pos,vel]=[k.id,k.shape.radius,k.state.pos,k.state.vel]
			f.write(str(ID)+'\t'+str(pos[0])+'\t'+str(pos[1])+'\t'+str(pos[2])+
				'\t'+str(r)+'\t'+str(vel[0])+'\t'+str(vel[1])+'\t'+str(vel[2])+
				'\t'+str(stp[0,0])+'\t'+str(stp[1,1])+'\t'+str(stp[2,2])+
				'\t'+str((stp[0,1]+stp[1,0])/2)+'\t'+str((stp[0,2]+stp[2,0])/2)+
				'\t'+str((stp[1,2]+stp[2,1])/2)+'\n'
				)
	f.close()

def saveContacts():
	# Save contacts details into a txt file for matlab to read
	global stt_dict
	fnm=svPath+'contForce'+str(O.iter)+'.txt'
	f=open(fnm,'w')
	f.write('Yade triaxial simulation - Contact files step'+str(O.iter)+'\n')
	f.write('Executed at '+date.today().strftime("%d/%m/%Y")+' by GONCALVES Joao\n')
	f.write('Number of grains : '+str(stt_dict['nbS'])+'\n')
	f.write('ID1\tID2\tFx\tFy\tFz \n' )
	for k in O.interactions:
		[ID1,ID2,force]=[k.id1,k.id2,k.phys.normalForce+k.phys.shearForce]
		f.write(str(ID1)+'\t'+str(ID2)+'\t'+str(force[0])+'\t'+str(force[1])+'\t'+str(force[2])+'\n')
	f.close()

def savePiston():
	# Save pistons details into a txt file for matlab to read
	fnm=svPath+'servoforce.txt'
	f=open(fnm,'a')
	f.write(str(O.iter)+'\t'+str(triax1.stress(triax1.wall_right_id)[0])+'\t'+str(triax1.stress(triax1.wall_top_id)[1])+'\t'+
		str(triax1.stress(triax1.wall_front_id)[2])+'\t'+str(O.bodies[5].state.pos[2])+'\t'+
		str(O.bodies[3].state.pos[1])+'\t'+str(O.bodies[2].state.pos[1])+'\t'+
		str(O.bodies[1].state.pos[0])+'\t'+str(O.bodies[0].state.pos[0])+'\n'
		)
	f.close()

def saveFiles():
	# Command to lauch all three save files in one pyrunner.
	# Only launch after the calibration part
	if stt_dict['val']>1:
		saveGrains()
		saveContacts()
		savePiston()

def endFile():
	# Print file with important information for matlab
	global stt_dict
	print('Time taken:',(O.time - startT))
	#YADEtoMatlab file
	f=open('YADEtoMatlab.txt','w')
	f.write('The following values give the Matlab app information about the simulaiton\n')
	f.write('Executed at '+date.today().strftime("%d/%m/%Y")+' by GONCALVES Joao\n')
	f.write('Number of grains : '+str(stt_dict['nbS'])+'\n')
	f.write('  \n')
	f.write('ExpWidth = '+str(triax1.width0)+'\n' )
	f.write('ExpLength = '+str(triax1.height0)+'\n' )
	f.write('ExpHeight = '+str(triax1.depth0)+'\n' )
	f.write('Timestep = '+str(O.dt)+'\n' )
	f.write('StartCons = '+str(stt_dict['consoSt'])+'\n' )
	f.write('EndCons = '+str(stt_dict['consoEnd'])+'\n' )
	f.write('EndComp = '+str(stt_dict['compEnd'])+'\n' )
	f.write('vtkFolder = '+svPath[:-1]+'\n' )
	f.write('genFolder = '+svPath[:-1]+'\n' )
	f.write('PositionPz = '+str(stt_dict['Pz'])+'\n' )
	f.write('PositionPy1 = '+str(stt_dict['Py1'])+'\n' )
	f.write('PositionPy2 = '+str(stt_dict['Py2'])+'\n' )
	f.write('PositionPx1 = '+str(stt_dict['Px1'])+'\n' )
	f.write('PositionPx2 = '+str(stt_dict['Px2'])+'\n' )
	f.write('  \n')
	f.close()

#############################	 Auto Run 	#############################
#O.run(); O.wait()


#For info :
# %F.Pt  => F stand for flag (nb of characters), P stand for precision (nb of val after .) and t for the
#type of data (d signed integer decimal, floating point decimal, e floating point exponential format)
#f.write('%-16g %-16g %-16g %-16g %-16g %-16g %-16g %-16d %-16g\n'%(pos[0],pos[1],pos[2],rad,displ[0],displ[1],displ[2],b.id,b.material.density))
 

-- 
You received this question notification because your team yade-users is
an answer contact for Yade.