# -*- encoding=utf-8 -*-
#*************************************************************************
#  Copyright (C) 2019 by Robert Caulk                                    *
#  rob.caulk@gmail.com                                                   *
#                                                                        *
#  This program is free software; it is licensed under the terms of the  *
#  GNU General Public License v2 or later. See file LICENSE for details. *
#*************************************************************************/
#
# Script demonstrating the three point bending test used to generate
# acoustic emissions during tensile fracture in the following paper:
# Caulk, Robert A. Modeling acoustic emissions in heterogeneous rocks 
# during tensile fracture with the Discrete Element Method.
# Open Geomechanics, Volume 2 (2020) , article no. 2, 19 p. doi : 
# 10.5802/ogeo.5. 
# https://opengeomechanics.centre-mersenne.org/item/OGEO_2020__2__A2_0/


from yade import ymport, utils, pack, export
from yade import plot
from pylab import *
import os
import math
import numpy as np
import numpy.linalg as la
import time
timeStr = time.strftime('%m-%d-%Y')

readParamsFromTable(noTableOk=True,
	conf = 0,
	weibullCutOffMax=10,
	weibullCutOffMin=0.1,
	xSectionShape = 4,
	xSectionScale = 0,

)

from yade.params.table import *

mn,mx=Vector3(-0.04, -.140,-0.020),Vector3(0.04, .140, 0.020)

young = 30e9
poisson = 0.3
targetPorosity = 0.55
density = 5000
relDuctility=30
finalFricDegree = 19 
intRadius= 1.329
sigmaT = 10e6
iterper=1000
cohesion=40e6
dispVel = -0.02 # m/s
highFric = 45

identifier='shape-'+str(xSectionShape)+'-'+timeStr
outputDir='out_'+identifier
if not os.path.exists(outputDir):
	os.mkdir(outputDir)
if not os.path.exists('txt'):
	os.mkdir('txt')
	
output = './'+outputDir+'/'+identifier

wallMat = O.materials.append(FrictMat(young=80e9,poisson=.45,frictionAngle=radians(highFric),density=7000,label='frictionlessWalls'))

JCFmat = O.materials.append(JCFpmMat(young=young, cohesion=cohesion, density=density, frictionAngle=radians(finalFricDegree), tensileStrength=sigmaT, poisson=poisson, label='JCFmat'))

#### preprocessing to get dimensions
numSpheres=10000

pred = pack.inAlignedBox(mn, mx) #- pack.inCylinder((0, -.01, 0), (0, .01, 0), 0.04)
sp = pack.randomDensePack(pred, radius=0.0015, spheresInCell=numSpheres, rRelFuzz=0.25, returnSpherePack=True)
sp.toSimulation()
export.textExt('240x80mmBeam_1.5mmrad.spheres','x_y_z_r')

dim=utils.aabbExtrema()
xinf=dim[0][0]
xsup=dim[1][0]
X=xsup-xinf
yinf=dim[0][1]
ysup=dim[1][1]
Y=ysup-yinf
zinf=dim[0][2]
zsup=dim[1][2]
Z=zsup-zinf

r=X/15.

O.reset() 

wallMat = O.materials.append(FrictMat(young=80e9,poisson=.45,frictionAngle=radians(highFric),density=7000,label='frictionlessWalls'))

# Rigid blocks
block1 = O.bodies.append(geom.facetCylinder(center=(xinf-0.971*r,yinf+2*r,0),radius=r,height=Z,orientation=Quaternion((0, 0, 0), 90),segmentsNumber=20,wire=False,material=wallMat)) 

block2 = O.bodies.append(geom.facetCylinder(center=(xinf-0.915*r,ysup-2*r,0),radius=r,height=Z,orientation=Quaternion((0, 0, 0), 90),segmentsNumber=20,wire=False,material=wallMat)) 

# Loading piston
piston=O.bodies.append(geom.facetCylinder(center=(xsup+0.943*r,0,0),radius=r,height=Z,dynamic=False,orientation=Quaternion((1,0, 0),0),segmentsNumber=20,wire=False,material=wallMat))

p0=O.bodies[piston[0]].state.pos[1]

JCFmat = O.materials.append(JCFpmMat(young=young, cohesion=cohesion, density=density, frictionAngle=radians(finalFricDegree), tensileStrength=sigmaT, poisson=poisson, label='JCFmat'))

# Specimen
beam = O.bodies.append(ymport.textExt('240x80mmBeam_1.5mmrad.spheres', 'x_y_z_r',color=(0,0.2,0.7), material='JCFmat'))

AEfile = 'AEcounts_'+identifier+'.txt'

f = open('txt/'+AEfile, 'w')
f.write('time iteration AEcount P deflection pistonDisp\n')
f.close

# remove rigid block DOFs
for i in block1:
	O.bodies[i].state.blockedDOFs='xyzXYZ'	

for i in block2:
	O.bodies[i].state.blockedDOFs='xyzXYZ'

# set engine list
O.engines=[
        ForceResetter(),
        InsertionSortCollider([Bo1_Box_Aabb(),Bo1_Sphere_Aabb(aabbEnlargeFactor=intRadius,label='Saabb'),Bo1_Facet_Aabb()]),
 InteractionLoop(
  [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=intRadius,label='SSgeom'),Ig2_Facet_Sphere_ScGeom()],
  [Ip2_FrictMat_FrictMat_FrictPhys(),Ip2_JCFpmMat_JCFpmMat_JCFpmPhys(cohesiveTresholdIteration=10, label='jcf', xSectionWeibullShapeParameter=xSectionShape,weibullCutOffMin=weibullCutOffMin, weibullCutOffMax=weibullCutOffMax)],
  [Law2_ScGeom_JCFpmPhys_JointedCohesiveFrictionalPM(recordCracks=True, recordMoments=True,Key=identifier,label='interactionLaw'),Law2_ScGeom_FrictPhys_CundallStrack()]
 ),

        GlobalStiffnessTimeStepper(active=1,timeStepUpdateInterval=100,timestepSafetyCoefficient=0.4, defaultDt=0.1*utils.PWaveTimeStep()),
	TranslationEngine(ids=piston,translationAxis=(1,0,0), velocity=dispVel, label='pistonEngine'),
	TranslationEngine(ids=block1,translationAxis=(1,0,0), velocity=0, label='block1Engine'),
	TranslationEngine(ids=block2,translationAxis=(1,0,0), velocity=0, label='block2Engine'),
        VTKRecorder(dead=0,iterPeriod=iterper*2,initRun=True,fileName=(output+'-'),recorders=['jcfpm','cracks','facets','moments','intr'],Key=identifier,label='vtk'),
	NewtonIntegrator(damping=0.2)
]

# options for AE model
interactionLaw.momentRadiusFactor=3
interactionLaw.clusterMoments=True
interactionLaw.neverErase=True
interactionLaw.useIncrementalForm=True
interactionLaw.useStrainEnergy = True

O.dt = 0.004 * utils.PWaveTimeStep()

# collect data for active plotting and post processing
from yade import plot
O.engines=O.engines[0:8]+[PyRunner(dead=0,iterPeriod=int(iterper/4),command='stressStrainHist()',label='dataCollector')]+O.engines[8:9]

O.engines=O.engines[0:9]+[PyRunner(dead=0,iterPeriod=iterper,command='stopifDamaged()',label='damageCheck')]+O.engines[9:10]

# engage blocks to ensure force balance and remove dynamics of system
O.engines=O.engines[0:10]+[PyRunner(dead=0, iterPeriod=1,command='ensureBlockEngagement()',label='blockEngagement')]+O.engines[10:11]

def ensureBlockEngagement():
	Pblock1, Pblock2 = checkBlockEngagement()
	P = getPistonForce()
	maxVel = 0.01
	if Pblock1 != P/2.:
		multiplier = (P/2. - Pblock1)/(P/2.)
		block1Engine.velocity = multiplier*maxVel
	if Pblock2 != P/2.:
		multiplier = (P/2. - Pblock2)/(P/2.)
		block2Engine.velocity = multiplier*maxVel

def getPistonForce():
	P=0
	for i in piston:
		P += la.norm(O.forces.f(i))
	return P

def getPistonDisp():
	pistonDisp = O.bodies[piston[0]].state.displ().norm()
	block1Disp = O.bodies[block1[0]].state.displ().norm()
	block2Disp = O.bodies[block2[0]].state.displ().norm()
	return pistonDisp + (block1Disp + block2Disp)/2.

def checkBlockEngagement():
	Pblock1 = Pblock2 = 0
	for i in block1:
		Pblock1+= la.norm(O.forces.f(i))
	for i in block2:
		Pblock2+= la.norm(O.forces.f(i))
	return Pblock1, Pblock2


def stressStrainHist():
	plot.addData(
		i=O.iter,
		time = O.time,
		disp = -O.time*dispVel,
		P = getPistonForce(),
		pistonVel = pistonEngine.velocity,
		PistonDisp = getPistonDisp(),
		Pblock1 = checkBlockEngagement()[0],
		Pblock2 = checkBlockEngagement()[1])
	plot.saveDataTxt('txt/data'+identifier+'.txt',vars= ('i', 'disp','P', 'pistonVel','PistonDisp'))
	momentFile = 'moments_'+identifier+'.txt'

	AEcount=0
	if os.path.isfile(momentFile):
		AEcount = sum(1 for line in open(momentFile))

	f = open('txt/'+AEfile, 'a')
	P, pistonDisp = plot.data['P'],plot.data['PistonDisp']
	f.write('%g %g %g %g %g\n' % (O.time, O.iter, AEcount, P[-1], pistonDisp[-1]))
	f.close

def stopifDamaged():
	P=plot.data['P']
	if O.iter > 10000:
		if  max(P)>2000 and P[-1] < 0.5*max(P):
			print('failure reached')
			sigma_t = 3*max(P)*(2*(ysup-2*r))/(2*Z*X**2)
			print("Tensile strength=",sigma_t/1e6)
			O.pause()

plot.plots={'PistonDisp':('P'), 'i':('Pblock1','Pblock2')}
plot.plot(subPlots=True)

O.step()
### initializes the interaction detection factor
SSgeom.interactionDetectionFactor=-1.
Saabb.aabbEnlargeFactor=-1.

O.run()

waitIfBatch()
