# -*- encoding=utf-8 -*-

# Modefied Oedometric Test for particle deformation under const. volume
# The reference paper [Haustein2017]

from __future__ import print_function
from yade import utils, plot, timing
from yade import pack

o = Omega()

# Physical parameters
fr = 0.3
rho = 2000
tc = 0.002
en = 0.7
et = 0.7
Diameter = 16.5e-3  # R

#*************************************

o.dt = 1.0e-5  # Timestep

#*************************************

# Add material
mat1 = O.materials.append(ViscElMat(frictionAngle=fr, tc=tc, en=en, et=et))

# Spheres for compression

sp = pack.SpherePack()
sp.makeCloud((-4.0 * Diameter, -4.0 * Diameter, -2.5 * Diameter), (3.0 * Diameter, 3.0 * Diameter, 15.0 * Diameter), rMean=Diameter / 2.0, num=300)
sp.toSimulation()

######################################################################
O.bodies.append(geom.facetBox((0, 0, 0), (4.0 * Diameter, 4.0 * Diameter, 4.0 * Diameter), wallMask=63 - 32, material=mat1))

# Add engines
o.engines = [
        ForceResetter(),
        InsertionSortCollider([Bo1_Sphere_Aabb(aabbEnlargeFactor=1.05), Bo1_Wall_Aabb(),
                               Bo1_Facet_Aabb()]),
        InteractionLoop(
                [Ig2_Sphere_Sphere_ScGeom(interactionDetectionFactor=1.05),
                 Ig2_Facet_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()], [Ip2_ViscElMat_ViscElMat_ViscElPhys()], [Law2_ScGeom_ViscElPhys_Basic()]
        ),
        NewtonIntegrator(damping=0.1, gravity=[0, 0, -9.81]),
        #VTKRecorder(fileName='vtk-',recorders=['all'],iterPeriod=20000),
        PyRunner(command='checkForce()', realPeriod=1, label="fCheck"),
        DeformControl(label="DefControl")
]


def checkForce():
	# at the very start, unbalanced force can be low as there is only few
	# contacts, but it does not mean the packing is stable
	if O.iter < 20000:
		return
	# the rest will be run only if unbalanced is < 1.0 (stabilized packing)
	timing.reset()
	if unbalancedForce() > 0.2:
		return

	highSphere = 0.0
	for b in O.bodies:
		if highSphere < b.state.pos[2] and isinstance(b.shape, Sphere):
			highSphere = b.state.pos[2]
		else:
			pass

	# add plate at upper box side
	O.bodies.append(wall(highSphere + 0.5 * Diameter, axis=2, sense=-1, material=mat1))
	# without this line, the plate variable would only exist inside this
	# function
	global plate
	plate = O.bodies[-1]  # the last particles is the plate
	# Wall objects are "fixed" by default, i.e. not subject to forces
	# prescribing a velocity will therefore make it move at constant velocity
	# (downwards)
	plate.state.vel = (0, 0, -.01)
	# start plotting the data now, it was not interesting before
	O.engines = O.engines + [PyRunner(command='addPlotData()', iterPeriod=1000)]
	# next time, do not call this function anymore, but the next one
	# (unloadPlate) instead
	fCheck.command = 'unloadPlate()'


def unloadPlate():
	# if the force on plate exceeds maximum load, start unloading
	# if abs(O.forces.f(plate.id)[2]) > 5e-2:
	# if plate.state.pos[2] < (-4*Diameter+40.0e-3):
	if abs(O.forces.f(plate.id)[2]) > 5e2:
		plate.state.vel *= -1
		# next time, do not call this function anymore, but the next one
		# (stopUnloading) instead
		fCheck.command = 'stopUnloading()'


def stopUnloading():
	if abs(O.forces.f(plate.id)[2]) == 0:
		# O.tags can be used to retrieve unique identifiers of the simulation
		# if running in batch, subsequent simulation would overwrite each other's output files otherwise
		# d (or description) is simulation description (composed of parameter values)
		# while the id is composed of time and process number
		# plot.saveDataTxt(O.tags['d.id'] + '.txt')
		plot.saveDataTxt('data' + O.tags['id'] + '.txt')
		print(timing.stats())
		O.pause()


def addPlotData():
	if not isinstance(O.bodies[-1].shape, Wall):
		plot.addData()
		return
	Fz = O.forces.f(plate.id)[2]
	plot.addData(
	        Fz=Fz,
	        w=plate.state.pos[2] - (-4 * Diameter),  # hight of the box (Bottom to upper Wall)
	        unbalanced=unbalancedForce(),
	        i=O.iter
	)


O.timingEnabled = True
O.run(1, True)
plot.plots = {'w': ('Fz', None)}
plot.plot()
