Project Granular flow

Granular flow with clump particles.
Granular flow of clum particles

This simulatation was written to participate to the 2nd Round Robin Activity organized by the TC105 Japanese domestic committee.
See the following paper for details:
? (?) ?. ? ??: ?-?. https://doi.org/?
The github

@author: Alexandre Sac–Morane alexandre.sac-morane@enpc.fr
This is the main file with the functions defined.

Expand source code
#-------------------------------------------------------------------------------
#Librairies
#-------------------------------------------------------------------------------

from yade import pack, plot, export
import numpy as np
import matplotlib.pyplot as plt
import os
import shutil
import time
import math
import random
import pickle
from pathlib import Path

#-------------------------------------------------------------------------------
#Functions
#-------------------------------------------------------------------------------

def mk_new_dir(foldername):
    '''
    Create a new folder (erase the preexisting, if it exists).
    '''
    if Path(foldername).exists():
        shutil.rmtree(foldername)
    os.mkdir(foldername)

#---------------------------------------

def report_material(reportname, L_materials):
    '''
    Write in the report the materials randomly generated
    '''
    # open the report
    simulation_report = open(reportname, 'a')
    # write an introduction
    simulation_report.write('List of the materials used:\n')
    # prepare the list
    L_young = []
    L_frictionAngle = []
    # iterate on the materials
    for material_i in L_materials:
        # prepare the sentence
        sentence_i = material_i.label + ': young = ' +str(round(material_i.young/1e6, 0)) +\
                                        ' MPa, poisson = ' +str(round(material_i.poisson, 2)) +\
                                        ', friction angle = ' +str(round(material_i.frictionAngle/math.pi*180, 0)) +\
                                        '°, density = ' +str(material_i.density) + ' kg/m3\n'
        # save
        L_young.append(material_i.young/1e6)
        L_frictionAngle.append(material_i.frictionAngle/math.pi*180)
        # write
        simulation_report.write(sentence_i)
    # add a skipped line
    simulation_report.write('\n')
    # close the report
    simulation_report.close()
    # plot
    fig, (ax1, ax2) = plt.subplots(1,2,figsize=(16,9))
    ax1.hist(L_young)
    ax1.set_xlabel('Young modulus (MPa)')
    ax2.hist(L_frictionAngle)
    ax2.set_xlabel('Friction angle (°)')
    fig.savefig('plot/'+O.tags['id']+'_distribution_properties.png')
    plt.close()

#---------------------------------------

def report_stat_material(reportname, L_p_materials):
    '''
    Write in the report the distribution of the materials assignement
    '''
    # open the report
    simulation_report = open(reportname, 'a')
    # write an introduction
    simulation_report.write('Distribution of the materials assigned:\n')
    # prepare the sentence
    sentence = ''
    # iterate on the materials
    for material_id in range(len(L_p_materials)):
        # prepare the sentence
        sentence = sentence + 'mat'+str(material_id+1) +' ('+str(round(L_p_materials[material_id]*100,1))+'%) '
    # write and add a skipped line
    simulation_report.write(sentence + '\n\n')
    # close the report
    simulation_report.close()

    # plot the distribution
    fig, ax1 = plt.subplots(1,1,figsize=(16,9))
    ax1.plot(L_p_materials, 'k')
    fig.savefig('plot/'+O.tags['id']+'_distribution_materials.png')
    plt.close()

#---------------------------------------

def report_end_step(reportname, stepname):
    '''
    Write in the report the information of the finishing step
    '''
    global tic
    tac = time.perf_counter()
    hours = (tac-tic)//(60*60)
    minutes = (tac-tic -hours*60*60)//(60)
    seconds = int(tac-tic -hours*60*60 -minutes*60)
    tic = tac

    # flag if the clumps are already generated
    flag_clump = False
    for b in O.bodies:
        if not flag_clump and b.isClump:
            flag_clump = True

    # write
    simulation_report = open(reportname, 'a')
    simulation_report.write(stepname+" : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n")
    simulation_report.write(str(O.iter-iter_0)+' Iterations\n')
    if flag_clump:
        simulation_report.write(str(count_grains())+' / '+str(n_grains*4)+' grains\n\n')
    else :
        simulation_report.write(str(count_grains())+' / '+str(n_grains)+' grains\n\n')
    simulation_report.close()
    print(stepname+" : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n")
    
#-------------------------------------------------------------------------------
#User
#-------------------------------------------------------------------------------

# consider n random materials for the artificial particle
n_mat_part = 100

# number of grains in the simulation
n_grains = 3535

# number of steps in the initialization (size increase)
n_steps_ic = 200

# automatically determine the time step from its critial value
#factor_dt_crit_ic = 0.5 # during ic
#factor_dt_crit = 0.25 # during main simulation

# dt settings used by each analyst were set in the range of 10e-6 (s) to 10e-4 (s).
# dt within the range of 0.01 to 1.0 times of dtcr
# dt cr = math.sqrt(M/Kn)

#-------------------------------------------------------------------------------
# Report and vtk
#-------------------------------------------------------------------------------

# generate the report
simulation_report_name = 'report/'+O.tags['d.id']+'_report.txt'
# from now, it can be used to save informations

# prepare the .vtk export 
vtkExporter = export.VTKExporter('vtk/config')

#-------------------------------------------------------------------------------
#Initialisation
#-------------------------------------------------------------------------------

# clock to show performances
tic = time.perf_counter()
tic_0 = tic

# plan simulation
mk_new_dir('plot')
mk_new_dir('vtk')
mk_new_dir('report')

# define wall material
O.materials.append(FrictMat(young=80e6,
                            poisson=0.25,
                            frictionAngle=radians(27.2),
                            density=2650))

# create box
O.bodies.append(aabbWalls([Vector3(-100e-3, -50e-3,  10e-3), 
                           Vector3(   0e-3,  50e-3, 400e-3)], thickness=0., oversizeFactor=1))
# a list of 6 boxes Bodies enclosing the packing, in the order minX, maxX, minY, maxY, minZ, maxZ

# create a box in the flow direction
O.bodies.append(box(center=(2.5e-3, 0, 10e-3), extents=(2.5e-3, 50e-3, 10e-3), fixed=True))

# create the infinite base plate
O.bodies.append(wall(position=Vector3(0, 0, 0), axis=2))

# generate n_mat_part random material
for i_mat_part in range(n_mat_part):
    # fixed value 
    poisson_i = 0.37
    density_i = 1111 # should be corrected considering the overlap of the sphere
    # random sort (use range of 2*standard deviation to get 95% of the population)
    frictionAngle_i = radians(random.uniform(35.5-2*3.83, 35.5+2*3.83))
    shear_i = random.uniform(560-2*158, 560+2*158)*1e6
    young_i = 2*shear_i*(1+poisson_i)
    # save material
    label_i = 'mat'+str(i_mat_part+1)
    O.materials.append(FrictMat(young=young_i, poisson=poisson_i, frictionAngle=frictionAngle_i, density=density_i, label=label_i))
    # save the materials in the report
report_material(simulation_report_name, list(O.materials)[1:])

# generate macro grains
L_r = []
L_p_mat = np.zeros(n_mat_part)
# the idea is in the initialization to use macro spheres before applying the artificial shape (clump of 4 spheres)
for i in range(n_grains):
    # definition of the radius
    radius = (math.sqrt(6)+4)/4 *3.101e-3
    L_r.append(radius)
    # definition of the position
    center_x = random.uniform(-100e-3 +radius/n_steps_ic, 0 -radius/n_steps_ic)
    center_y = random.uniform(-50e-3 +radius/n_steps_ic, 50e-3 -radius/n_steps_ic)
    center_z = random.uniform(10e-3 +radius/n_steps_ic, 400e-3 -radius/n_steps_ic)
    # determination of the material
    mat_id = random.randint(1, n_mat_part)
    # save for stats
    L_p_mat[mat_id-1] = L_p_mat[mat_id-1] + 1/n_grains
    # generation of the particle
    O.bodies.append(sphere(center=[center_x, center_y, center_z], radius=radius/n_steps_ic, material='mat'+str(mat_id)))
# write the stat of the material assignement
report_stat_material(simulation_report_name, L_p_mat)

#-------------------------------------------------------------------------------
#Size increase algorithm
#-------------------------------------------------------------------------------

def count_grains():
    '''
    Count the number of grains in the simulation.
    '''
    counter_grains = 0
    for b in O.bodies :
        if isinstance(b.shape, Sphere):
            counter_grains = counter_grains + 1
    return counter_grains

#---------------------------------------

def grain_in_domain():
    '''
    Delete grains if they are lower than the plate or upper than the box.
    '''
    #detect grain outside the box
    L_id_to_delete = []
    for b in O.bodies :
        if isinstance(b.shape, Sphere):
            # limit x, y, z
            if  b.state.pos[0] < -100e-3 or 0 < b.state.pos[0] or\
                b.state.pos[1] < -50e-3 or 50e-3 < b.state.pos[1] or\
                b.state.pos[2] < 10e-3 or 400e-3 < b.state.pos[2] :    
                L_id_to_delete.append(b.id)
    if L_id_to_delete != []:
        #delete grain detected
        for id in L_id_to_delete:
            O.bodies.erase(id)
        #print and report
        simulation_report = open(simulation_report_name, 'a')
        simulation_report.write(str(len(L_id_to_delete))+" grains erased (outside of the box)\n")
        simulation_report.close()
        print("\n"+str(len(L_id_to_delete))+" grains erased (outside of the box) -> "+\
              str(count_grains())+" grains in the domain\n")

#---------------------------------------

def checkUnbalanced_ir_ic():
    '''
    Increase particle radius until a steady-state is found.
    '''
    # the rest will be run only if unbalanced is < .1 (stabilized packing)
    # Compute the ratio of mean summary force on bodies and mean force magnitude on interactions.
    if unbalancedForce() > .1:
        return
    # increase the radius of particles
    if int(O.tags['Step ic']) < n_steps_ic :
        print('IC step '+O.tags['Step ic']+'/'+str(n_steps_ic)+' done')
        O.tags['Step ic'] = str(int(O.tags['Step ic'])+1)
        i_L_r = 0
        for b in O.bodies :
            if isinstance(b.shape, Sphere):
                growParticle(b.id, int(O.tags['Step ic'])/n_steps_ic*L_r[i_L_r]/b.shape.radius)
                i_L_r = i_L_r + 1
        # update the dt as the radii change
        #O.dt = factor_dt_crit_ic * PWaveTimeStep()
        return
    print('IC step '+O.tags['Step ic']+'/'+str(n_steps_ic)+' done\n')

    # report
    report_end_step(simulation_report_name, 'IC Generated')
    print('application of the gravity, can be long...\n')
    
    # print configuration
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # next time, do not call this function anymore, but the next one instead
    global iter_0
    iter_0 = O.iter
    checker.command = 'checkUnbalanced_gravity_ic()'
    checker.iterPeriod = 500

    # prepare next phase
    global L_cog_z, L_coordination, L_unbalanced, L_Ec
    L_cog_z = []
    L_coordination = []
    L_unbalanced = []
    L_Ec = []
    # apply gravity
    Newton.gravity = (0, 0, -9.81)
    # write report
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Application of the gravity\n')
    simulation_report.close()
    
#---------------------------------------

# prepare the initialization
O.tags['Step ic'] = '1'
iter_0 = 0

# write report
simulation_report = open(simulation_report_name, 'a')
simulation_report.write('Apply a size increase algorithm\n')
simulation_report.close()

# yade algorithm
O.engines = [
        PyRunner(command='grain_in_domain()', iterPeriod = 1000),
        ForceResetter(),
        # sphere, wall
        InsertionSortCollider([Bo1_Sphere_Aabb(), Bo1_Box_Aabb(), Bo1_Wall_Aabb()]),
        InteractionLoop(
                # Ig : compute contact point
                # Ip : compute parameters needed
                # Law : compute contact law with parameters from Ip
                [Ig2_Sphere_Sphere_ScGeom(), Ig2_Box_Sphere_ScGeom(), Ig2_Wall_Sphere_ScGeom()],
                [Ip2_FrictMat_FrictMat_MindlinPhys(en=0.809)],
                [Law2_ScGeom_MindlinPhys_Mindlin()]
        ),
        NewtonIntegrator(gravity=(0, 0, 0), damping=0.1, label = 'Newton'),
        PyRunner(command='checkUnbalanced_ir_ic()', iterPeriod = 200, label='checker')
]
# time step
#O.dt = factor_dt_crit_ic * PWaveTimeStep()
O.dt = 5.e-07

#-------------------------------------------------------------------------------
#Application of the gravity
#-------------------------------------------------------------------------------

def compute_center_of_gravity():
    '''
    Compute the center of gravity of all the grains.
    '''
    Center = np.array([0,0,0])
    Mass = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            Center = Center + b.state.mass*np.array(b.state.pos)
            Mass = Mass + b.state.mass
    return Center/Mass

#---------------------------------------

def plot_trackers_ic(L_cog_z, L_coordination, L_unbalanced, L_Ec, n_window):
    '''
    Plot the evolution of the various trackers.
    '''
    fig, ((ax1,ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16,9))
    
    # coordinate z of the gravity center
    ax1.plot(L_cog_z, color='k')
    if len(L_cog_z)>=n_window:
        ax1.plot(list(range(len(L_cog_z)-n_window, len(L_cog_z))), L_cog_z[-n_window:], color='r')
        ax1.text((len(L_cog_z)-1)*0.5, (np.max(L_cog_z)+np.min(L_cog_z))/2,\
            'max-min='+str(max(L_cog_z[-n_window:])-min(L_cog_z[-n_window:])), color='r')
    ax1.set_ylabel('Coordinate z of the gravity center')
    
    # coordination number
    ax2.plot(L_coordination, color='k')
    if len(L_coordination)>=n_window:
        ax2.plot(list(range(len(L_coordination)-n_window, len(L_coordination))), L_coordination[-n_window:], color='r')
        ax2.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+np.min(L_coordination))/2,\
            'max-min='+str(max(L_coordination[-n_window:])-min(L_coordination[-n_window:])), color='r')
        ax2.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+3*np.min(L_coordination))/4,\
            'mean='+str(np.mean(L_coordination[-n_window:])), color='r')
    ax2.set_ylabel('Coordination number')

    # unbalanced force ratio
    ax3.plot(L_unbalanced, color='k')
    if len(L_unbalanced)>=n_window:
        ax3.plot(list(range(len(L_unbalanced)-n_window, len(L_unbalanced))), L_unbalanced[-n_window:], color='r')
        ax3.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+np.min(L_unbalanced))/2,\
            'max-min='+str(max(L_unbalanced[-n_window:])-min(L_unbalanced[-n_window:])), color='r')
        ax3.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+3*np.min(L_unbalanced))/4,\
            'mean='+str(np.mean(L_unbalanced[-n_window:])), color='r')
    ax3.set_ylabel('Unbalanced ratio')

    # unbalanced force ratio
    ax4.plot(L_Ec, color='k')
    if len(L_Ec)>=n_window:
        ax4.plot(list(range(len(L_Ec)-n_window, len(L_Ec))), L_Ec[-n_window:], color='r')
        ax4.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+np.min(L_Ec))/2,\
            'max-min='+str(max(L_Ec[-n_window:])-min(L_Ec[-n_window:])), color='r')
        ax4.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+3*np.min(L_Ec))/4,\
            'mean='+str(np.mean(L_Ec[-n_window:])), color='r')
    ax4.set_ylabel('Kinetic energy')
    
    # close
    fig.tight_layout()
    fig.savefig('plot/'+O.tags['id']+'_ic_trackers.png')
    plt.close()

#---------------------------------------

def checkUnbalanced_gravity_ic():
    '''
    Apply the gravity to settle the particles.
    '''
    global iter_0
    global L_cog_z, L_coordination, L_unbalanced, L_Ec
    # define the criteria for steady-state
    n_window = 20
    delta_cog_z = 0.00005

    # read the center of gravity of the granular sample
    cog = compute_center_of_gravity()
    # save trackers 
    L_cog_z.append(cog[2]) # coordinate z of the center of gravity
    L_coordination.append(avgNumInteractions()) # coordination number
    L_unbalanced.append(unbalancedForce()) # unbalanced force ratio
    L_Ec.append(kineticEnergy()) # mean kinetic energy  
    # plot trackers
    plot_trackers_ic(L_cog_z, L_coordination, L_unbalanced, L_Ec, n_window)

    # repeat at least a certain amount of times
    if O.iter-iter_0 < checker.iterPeriod*n_window:
        return
    # check that the center of gravity has not moved
    if max(L_cog_z[-n_window:])-min(L_cog_z[-n_window:]) > delta_cog_z:
        return 

    # report
    report_end_step(simulation_report_name, 'Gravity Applied')
    # print configuration
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # next time, do not call this function anymore, but the next one instead
    if count_grains() < n_grains:
        # Need to reinsert grains
        reinsert_grains_ic(n_grains-count_grains())
        # refind an equilibrium
        L_cog_z = []
        L_coordination = []
        L_unbalanced = []
        L_Ec = []
        iter_0 = O.iter
        checker.iterPeriod = 500
    else :
        # generate the clump
        generateClump()
        # print configuration
        vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})
        # refind an equilibrium
        L_cog_z = []
        L_coordination = []
        L_unbalanced = []
        L_Ec = []
        iter_0 = O.iter
        checker.iterPeriod = 500
        checker.command = 'checkUnbalanced_clump_ic()'

#---------------------------------------

def reinsert_grains_ic(n_grains_reinsert):
    '''
    Reinsert grains that have been deleted.
    '''
    # write report
    print('Reinsert grains')
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Reinsert grains\n')
    simulation_report.close()

    # definition of the domain (based on the presence of grains)
    min_center_z = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            # compare the z coordinate with the domain
            if b.state.pos[2] + b.shape.radius > min_center_z:
                min_center_z = b.state.pos[2] + b.shape.radius
    
    # determination of the seed
    n_seed_dim = int(100e-3/(((math.sqrt(6)+4)/4*3.101e-3)*2*2))
    dim_seed = (100e-3)/n_seed_dim

    # generation of the grains
    for i_seed in range(n_grains_reinsert):
        # determine the z of the seed and adapt i_seed
        z_seed_i = i_seed//(n_seed_dim*n_seed_dim)
        i_seed = i_seed-z_seed_i*(n_seed_dim*n_seed_dim)
        # determine the x and y of the seed
        x_seed_i = i_seed%n_seed_dim
        y_seed_i = i_seed//n_seed_dim

        # definition of the radius
        radius = (math.sqrt(6)+4)/4 *3.101e-3
        # definition of the position
        center_x = random.uniform(-100e-3 +x_seed_i*dim_seed +radius, -100e-3 +(x_seed_i+1)*dim_seed -radius)
        center_y = random.uniform(-50e-3 +y_seed_i*dim_seed +radius, -50e-3 +(y_seed_i+1)*dim_seed -radius)
        center_z = min_center_z +z_seed_i*2*radius +radius
        
        # determination of the material
        mat_id = random.randint(1, n_mat_part)
        # generation of the particle
        O.bodies.append(sphere(center=[center_x, center_y, center_z], radius=radius, material='mat'+str(mat_id)))

    # recompute the distribution of the material
    L_p_mat = np.zeros(n_mat_part)
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            L_p_mat[b.mat.id-1] = L_p_mat[b.mat.id-1] + 1/n_grains 
    # write in the report
    report_stat_material(simulation_report_name, L_p_mat)

    # report
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Application of the gravity\n')
    simulation_report.close()

#-------------------------------------------------------------------------------
#Generation of the clump
#-------------------------------------------------------------------------------

def generateClump():
    '''
    Generate the clumps from the preliminary grains.
    '''
    # iteration on the grain
    n_grains0 = count_grains()
    counter = 0
    for b in list(O.bodies)[:n_grains0+8]:
        if isinstance(b.shape, Sphere):
            counter = counter + 1
            # read material
            label_mat = b.mat.label
            # definition of the radius
            radius = 3.101e-3
            # define the elementary spheres
            center_1 = np.array([radius/2, math.sqrt(3)*radius/6, math.sqrt(6)*radius/3])
            center_2 = np.array([       0,                     0,                     0])
            center_3 = np.array([  radius,                     0,                     0])
            center_4 = np.array([radius/2, math.sqrt(3)*radius/2,                     0])
            # compute the center of the clum
            center_clump = (center_1+center_2+center_3+center_4)/4
            # recompute the coordinate of the elementary spheres relative to this center
            center_1 = center_1 - center_clump
            center_2 = center_2 - center_clump
            center_3 = center_3 - center_clump
            center_4 = center_4 - center_clump
            # generate random rotations d'Euler
            phi = random.random()*2*math.pi
            psi = random.random()*2*math.pi
            theta = random.random()*2*math.pi
            # compute the rotation matrice
            M_rot = np.array([[math.cos(psi)*math.cos(phi)-math.sin(psi)*math.cos(theta)*math.sin(phi), -math.cos(psi)*math.sin(phi)-math.sin(psi)*math.cos(theta)*math.cos(phi),  math.sin(psi)*math.sin(theta)],
                              [math.sin(psi)*math.cos(phi)+math.cos(psi)*math.cos(theta)*math.sin(phi), -math.sin(psi)*math.sin(phi)+math.cos(psi)*math.cos(theta)*math.cos(phi), -math.cos(psi)*math.sin(theta)],
                              [                                          math.sin(theta)*math.sin(phi),                                            math.sin(theta)*math.cos(phi),                math.cos(theta)]])
            # rotate the spheres
            center_1 = np.linalg.solve(M_rot, center_1)
            center_2 = np.linalg.solve(M_rot, center_2)
            center_3 = np.linalg.solve(M_rot, center_3)
            center_4 = np.linalg.solve(M_rot, center_4)
            # translate the spheres
            center_1 = center_1 + np.array(b.state.pos)
            center_2 = center_2 + np.array(b.state.pos)
            center_3 = center_3 + np.array(b.state.pos)
            center_4 = center_4 + np.array(b.state.pos)
            # generate the clump
            O.bodies.appendClumped([sphere(center=center_1, radius=radius, material=label_mat),
                                    sphere(center=center_2, radius=radius, material=label_mat),
                                    sphere(center=center_3, radius=radius, material=label_mat),
                                    sphere(center=center_4, radius=radius, material=label_mat)])
    
    # erase the grains
    for b in list(O.bodies)[:n_grains0+8]:
        if isinstance(b.shape, Sphere):
            O.bodies.erase(b.id)
    # erase the existing interactions
    O.interactions.clear()

    # update the time step as the particles change
    #O.dt = factor_dt_crit_ic * PWaveTimeStep()
    #O.dt = 1e-5
    O.dt = 5e-6

    # report and user
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Generation of the clump\n\n')
    simulation_report.write('Application of the gravity\n')
    simulation_report.close()

    print('Generation of the clump and application of the gravity, can be long...\n')

#---------------------------------------

def checkUnbalanced_clump_ic():
    '''
    Apply the gravity to settle the cumpled particles.
    '''
    global iter_0
    global L_cog_z, L_coordination, L_unbalanced, L_Ec
    # define the criteria for steady-state
    n_window = 20
    delta_cog_z = 0.00002

    # read the center of gravity of the granular sample
    cog = compute_center_of_gravity()
    # save trackers 
    L_cog_z.append(cog[2]) # coordinate z of the center of gravity
    L_coordination.append(avgNumInteractions()) # coordination number
    L_unbalanced.append(unbalancedForce()) # unbalanced force ratio
    L_Ec.append(kineticEnergy()) # mean kinetic energy  
    # plot trackers
    plot_trackers_ic(L_cog_z, L_coordination, L_unbalanced, L_Ec, n_window)

    # repeat at least a certain amount of times
    if O.iter-iter_0 < checker.iterPeriod*n_window:
        return
    # check that the center of gravity has not moved
    if max(L_cog_z[-n_window:])-min(L_cog_z[-n_window:]) > delta_cog_z:
        return 

    # report
    report_end_step(simulation_report_name, 'Gravity Applied')
    # print configuration
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # write data for input

    # report and user
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('The initial condition is reached, the sample is ready for the granular flow\n\n')
    simulation_report.close()
    print('The initial condition is reached, the sample is ready for the granular flow\n')

    # export the data
    exportData(True)

    # prepare the next step
    iter_0 = O.iter
    checker.iterPeriod = 5000
                        
    checker.command = 'checkUnbalanced_granularFlow()'
    # reduce the time step for the main simulation
    #O.dt = factor_dt_crit * PWaveTimeStep()
    # reduce strongly the damping
    Newton.damping = 0.00
    # cancel the grain_in_domain() verification 
    O.engines = O.engines[1:]
    # add the control of the trap
    O.bodies[1].state.vel = (0, 0, 6.45e-2)

#-------------------------------------------------------------------------------
#Opening of the trap and granular flow
#-------------------------------------------------------------------------------

def checkUnbalanced_granularFlow():
    '''
    Look for the steady-state and save data of the simulation
    '''
    # save config
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # plot XY view
    plot_XY_view()

    # add data
    addPlotData()

    # look for the steady state
    n_window = 5
    # minimum size of the data
    if len(plot.data['m_pos_x']) < n_window:
        return
    
    # miminum displacement on x
    if plot.data['m_pos_x'][-1] < -0.025:
        return
    
    # small variation in the displacement on x
    if max(plot.data['m_pos_x'][-n_window:])-min(plot.data['m_pos_x'][-n_window:]) > 0.00002:
        return
    
    # check ratio of the force
    if unbalancedForce() > 1e-6:
        return

    # check the kinetic energy
    if kineticEnergy() > 0.0003:
        return
    
    # prepare the next step
    stopLoad()

#---------------------------------------

def addPlotData():
    """
    Save data in plot.
    """
    # find the extrema and the mean position
    mean_center = np.array([0, 0, 0])
    Mass = 0
    y_min = 0
    y_max = 0
    x_max = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            # compute the mean
            mean_center = mean_center + b.state.mass*np.array(b.state.pos)
            Mass = Mass + b.state.mass
            # compute extrema
            if x_max < b.state.pos[0]:
                x_max = b.state.pos[0]
            if y_max < b.state.pos[1]:
                y_max = b.state.pos[1]
            if b.state.pos[1] < y_min :
                y_min = b.state.pos[1]
    # compute the mean position
    mean_center = mean_center/Mass
    # add data
    plot.addData(i=O.iter-iter_0, coordination=avgNumInteractions(), unbalanced=unbalancedForce(), Ec=kineticEnergy(),\
                 m_pos_x=mean_center[0], m_pos_y=mean_center[1], m_pos_z=mean_center[2],\
                 x_max=x_max, y_min=y_min, y_max=y_max,\
                )
    # plot the data
    saveData()

#---------------------------------------

def saveData():
    """
    Save data in .txt file.
    """
    # save data
    plot.saveDataTxt('plot/data_'+O.tags['d.id']+'.txt')
    # prepare plot
    L_ite = []
    L_cog_x = []
    L_max_x = []
    L_max_y = []
    L_min_y = []
    L_coordination = []
    L_unbalanced = []
    L_Ec = []

    # read data
    file = 'plot/data_'+O.tags['d.id']+'.txt'
    data = np.genfromtxt(file, skip_header=1)
    file_read = open(file, 'r')
    lines = file_read.readlines()
    file_read.close()
    if len(lines) >= 3:
        for i in range(len(data)):
            L_Ec.append(data[i][0])
            L_coordination.append(data[i][1])
            L_ite.append(data[i][2])
            L_cog_x.append(data[i][3])
            L_unbalanced.append(data[i][6])
            L_max_x.append(data[i][7])
            L_max_y.append(data[i][8])
            L_min_y.append(data[i][9])

        # plot
        n_window = 5
        fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2,3, figsize=(20,10),num=1)

        # coordinate x of the gravity center
        ax1.plot(L_cog_x, color='k')
        if len(L_cog_x)>=n_window:
            ax1.plot(list(range(len(L_cog_x)-n_window, len(L_cog_x))), L_cog_x[-n_window:], color='r')
            ax1.text((len(L_cog_x)-1)*0.5, (np.max(L_cog_x)+np.min(L_cog_x))/2,\
                'max-min='+str(max(L_cog_x[-n_window:])-min(L_cog_x[-n_window:])), color='r')
        ax1.set_ylabel('Coordinate x of the gravity center')
        
        # coordinate x of the maximum
        ax2.plot(L_max_x, color='k')
        if len(L_max_x)>=n_window:
            ax2.plot(list(range(len(L_max_x)-n_window, len(L_max_x))), L_max_x[-n_window:], color='r')
            ax2.text((len(L_max_x)-1)*0.5, (np.max(L_max_x)+np.min(L_max_x))/2,\
                'max-min='+str(max(L_max_x[-n_window:])-min(L_max_x[-n_window:])), color='r')
        ax2.set_ylabel('Boundary x of the grains')
        
        # coordinates x of the extrema
        ax3.plot(L_max_y, color='k')
        ax3.plot(L_min_y, color='k')
        if len(L_max_y)>=n_window:
            ax3.plot(list(range(len(L_max_y)-n_window, len(L_max_y))), L_max_y[-n_window:], color='r')
            ax3.text((len(L_max_y)-1)*0.5, (np.max(L_max_y)+np.min(L_max_y))/2,\
                'max-min='+str(max(L_max_y[-n_window:])-min(L_max_y[-n_window:])), color='r')
            ax3.plot(list(range(len(L_min_y)-n_window, len(L_min_y))), L_min_y[-n_window:], color='r')
            ax3.text((len(L_min_y)-1)*0.5, (np.max(L_min_y)+np.min(L_min_y))/2,\
                'max-min='+str(max(L_min_y[-n_window:])-min(L_min_y[-n_window:])), color='r')
        ax3.set_ylabel('Boundaries y of the grains')  

        # coordination number
        ax4.plot(L_coordination, color='k')
        if len(L_coordination)>=n_window:
            ax4.plot(list(range(len(L_coordination)-n_window, len(L_coordination))), L_coordination[-n_window:], color='r')
            ax4.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+np.min(L_coordination))/2,\
                'max-min='+str(max(L_coordination[-n_window:])-min(L_coordination[-n_window:])), color='r')
            ax4.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+3*np.min(L_coordination))/4,\
                'mean='+str(np.mean(L_coordination[-n_window:])), color='r')
        ax4.set_ylabel('Coordination number')

        # unbalanced force ratio
        ax5.plot(L_unbalanced, color='k')
        if len(L_unbalanced)>=n_window:
            ax5.plot(list(range(len(L_unbalanced)-n_window, len(L_unbalanced))), L_unbalanced[-n_window:], color='r')
            ax5.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+np.min(L_unbalanced))/2,\
                'max-min='+str(max(L_unbalanced[-n_window:])-min(L_unbalanced[-n_window:])), color='r')
            ax5.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+3*np.min(L_unbalanced))/4,\
                'mean='+str(np.mean(L_unbalanced[-n_window:])), color='r')
        ax5.set_ylabel('Unbalanced ratio')

        # unbalanced force ratio
        ax6.plot(L_Ec, color='k')
        if len(L_Ec)>=n_window:
            ax6.plot(list(range(len(L_Ec)-n_window, len(L_Ec))), L_Ec[-n_window:], color='r')
            ax6.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+np.min(L_Ec))/2,\
                'max-min='+str(max(L_Ec[-n_window:])-min(L_Ec[-n_window:])), color='r')
            ax6.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+3*np.min(L_Ec))/4,\
                'mean='+str(np.mean(L_Ec[-n_window:])), color='r')
        ax6.set_ylabel('Kinetic energy')

        fig.tight_layout()
        fig.savefig('plot/'+O.tags['d.id']+'_trackers.png')
        plt.close()

#---------------------------------------

def plot_XY_view():
    '''
    Plot the XY view of the clump centers.
    '''
    # prepare the plot
    L_x_center = []
    L_y_center = []
    # iterate on the grains
    for b in O.bodies:
        if isinstance(b.shape, Clump):
            L_x_center.append(b.state.pos[0])
            L_y_center.append(b.state.pos[1])
    
    # plot
    fig, (ax1) = plt.subplots(1,1, figsize=(16,9),num=1)
    ax1.scatter(L_x_center, L_y_center, color='k')
    ax1.plot([0, -100e-3, -100e-3, 0, 0], [50e-3, 50e-3, -50e-3, -50e-3, 50e-3], color='gray')
    ax1.set_xlabel('Coordinate x')
    ax1.set_ylabel('Coordinate y')
    ax1.set_aspect('equal')
    fig.savefig('plot/'+O.tags['d.id']+'_XY_view.png')
    plt.close()

#---------------------------------------

def stopLoad():
    """
    Close simulation.
    """
    # write output for submission
    exportData(False)

    # close yade
    O.pause()
    # report
    report_end_step(simulation_report_name, 'Granular flow')
    # characterize the simulation
    tac = time.perf_counter()
    hours = (tac-tic_0)//(60*60)
    minutes = (tac-tic_0 -hours*60*60)//(60)
    seconds = int(tac-tic_0 -hours*60*60 -minutes*60)
    # report
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write("Simulation time : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n\n")
    simulation_report.close()
    print("\nSimulation time : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n")

    # save simulation
    os.mkdir('data/'+O.tags['d.id'])
    shutil.copytree('plot','data/'+O.tags['d.id']+'/plot')
    shutil.copytree('vtk','data/'+O.tags['d.id']+'/vtk')
    shutil.copytree('report', 'data/'+O.tags['d.id']+'/report')
    shutil.copy('TC105_2RRA_ASM.py','data/'+O.tags['d.id']+'/TC105_2RRA_ASM.py')

#-------------------------------------------------------------------------------
# export data for the 2RRA
#-------------------------------------------------------------------------------

def exportData(flag_start):
    '''
    Export the data required (idi, xi, yi, zi) for the 14.140 grains.
    carriage return at end of each line
    separate coordinate value with a comma
    coordinate value in single or double precision floating point
    position in millimeters
    '''
    # the name of the file depends on the start or end state
    if flag_start:
        filename = 'report/start'+O.tags['d.id']+'.dat'
    else :
        filename = 'report/end'+O.tags['d.id']+'.dat'
    # open file
    file = open(filename, 'w')
    
    # iterate on the grains
    id_g = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            id_g = id_g + 1
            # prepare the line
            line = str(id_g)+', '+\
                   str(b.state.pos[0]*1e3)+', '+\
                   str(b.state.pos[1]*1e3)+', '+\
                   str(b.state.pos[2]*1e3)+'\r'
            # write
            file.write(line)

    # close the file
    file.close()

#-------------------------------------------------------------------------------
# start simulation
#-------------------------------------------------------------------------------

O.run()
waitIfBatch()

Functions

def mk_new_dir()

Create a new folder (erase the preexisting, if it exists).

Expand source code

    def mk_new_dir(foldername):
    '''
    Create a new folder (erase the preexisting, if it exists).
    '''
    if Path(foldername).exists():
    shutil.rmtree(foldername)
    os.mkdir(foldername)
def report_material()

Write in the report the materials randomly generated.

Expand source code

def report_material(reportname, L_materials):
    '''
    Write in the report the materials randomly generated
    '''
    # open the report
    simulation_report = open(reportname, 'a')
    # write an introduction
    simulation_report.write('List of the materials used:\n')
    # prepare the list
    L_young = []
    L_frictionAngle = []
    # iterate on the materials
    for material_i in L_materials:
        # prepare the sentence
        sentence_i = material_i.label + ': young = ' +str(round(material_i.young/1e6, 0)) +\
                                        ' MPa, poisson = ' +str(round(material_i.poisson, 2)) +\
                                        ', friction angle = ' +str(round(material_i.frictionAngle/math.pi*180, 0)) +\
                                        '°, density = ' +str(material_i.density) + ' kg/m3\n'
        # save
        L_young.append(material_i.young/1e6)
        L_frictionAngle.append(material_i.frictionAngle/math.pi*180)
        # write
        simulation_report.write(sentence_i)
    # add a skipped line
    simulation_report.write('\n')
    # close the report
    simulation_report.close()
    # plot
    fig, (ax1, ax2) = plt.subplots(1,2,figsize=(16,9))
    ax1.hist(L_young)
    ax1.set_xlabel('Young modulus (MPa)')
    ax2.hist(L_frictionAngle)
    ax2.set_xlabel('Friction angle (°)')
    fig.savefig('plot/'+O.tags['id']+'_distribution_properties.png')
    plt.close()
def report_stat_material()

Write in the report the distribution of the materials assignement.

Expand source code

def report_stat_material(reportname, L_p_materials):
    '''
    Write in the report the distribution of the materials assignement
    '''
    # open the report
    simulation_report = open(reportname, 'a')
    # write an introduction
    simulation_report.write('Distribution of the materials assigned:\n')
    # prepare the sentence
    sentence = ''
    # iterate on the materials
    for material_id in range(len(L_p_materials)):
        # prepare the sentence
        sentence = sentence + 'mat'+str(material_id+1) +' ('+str(round(L_p_materials[material_id]*100,1))+'%) '
    # write and add a skipped line
    simulation_report.write(sentence + '\n\n')
    # close the report
    simulation_report.close()

    # plot the distribution
    fig, ax1 = plt.subplots(1,1,figsize=(16,9))
    ax1.plot(L_p_materials, 'k')
    fig.savefig('plot/'+O.tags['id']+'_distribution_materials.png')
    plt.close()
def report_end_step()

Write in the report the information of the finishing step.

Expand source code

def report_end_step(reportname, stepname):
    '''
    Write in the report the information of the finishing step
    '''
    global tic
    tac = time.perf_counter()
    hours = (tac-tic)//(60*60)
    minutes = (tac-tic -hours*60*60)//(60)
    seconds = int(tac-tic -hours*60*60 -minutes*60)
    tic = tac

    # flag if the clumps are already generated
    flag_clump = False
    for b in O.bodies:
        if not flag_clump and b.isClump:
            flag_clump = True

    # write
    simulation_report = open(reportname, 'a')
    simulation_report.write(stepname+" : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n")
    simulation_report.write(str(O.iter-iter_0)+' Iterations\n')
    if flag_clump:
        simulation_report.write(str(count_grains())+' / '+str(n_grains*4)+' grains\n\n')
    else :
        simulation_report.write(str(count_grains())+' / '+str(n_grains)+' grains\n\n')
    simulation_report.close()
    print(stepname+" : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n")
def count_grains()

Count the number of grains in the simulation.

Expand source code

def count_grains():
    '''
    Count the number of grains in the simulation.
    '''
    counter_grains = 0
    for b in O.bodies :
        if isinstance(b.shape, Sphere):
            counter_grains = counter_grains + 1
    return counter_grains
def grain_in_domain()

Delete grains if they are lower than the plate or upper than the box.

Expand source code

def grain_in_domain():
    '''
    Delete grains if they are lower than the plate or upper than the box.
    '''
    #detect grain outside the box
    L_id_to_delete = []
    for b in O.bodies :
        if isinstance(b.shape, Sphere):
            # limit x, y, z
            if  b.state.pos[0] < -100e-3 or 0 < b.state.pos[0] or\
                b.state.pos[1] < -50e-3 or 50e-3 < b.state.pos[1] or\
                b.state.pos[2] < 10e-3 or 400e-3 < b.state.pos[2] :    
                L_id_to_delete.append(b.id)
    if L_id_to_delete != []:
        #delete grain detected
        for id in L_id_to_delete:
            O.bodies.erase(id)
        #print and report
        simulation_report = open(simulation_report_name, 'a')
        simulation_report.write(str(len(L_id_to_delete))+" grains erased (outside of the box)\n")
        simulation_report.close()
        print("\n"+str(len(L_id_to_delete))+" grains erased (outside of the box) -> "+\
              str(count_grains())+" grains in the domain\n")
def checkUnbalanced_ir_ic()

Increase particle radius until a steady-state is found.

Expand source code

def checkUnbalanced_ir_ic():
    '''
    Increase particle radius until a steady-state is found.
    '''
    # the rest will be run only if unbalanced is < .1 (stabilized packing)
    # Compute the ratio of mean summary force on bodies and mean force magnitude on interactions.
    if unbalancedForce() > .1:
        return
    # increase the radius of particles
    if int(O.tags['Step ic']) < n_steps_ic :
        print('IC step '+O.tags['Step ic']+'/'+str(n_steps_ic)+' done')
        O.tags['Step ic'] = str(int(O.tags['Step ic'])+1)
        i_L_r = 0
        for b in O.bodies :
            if isinstance(b.shape, Sphere):
                growParticle(b.id, int(O.tags['Step ic'])/n_steps_ic*L_r[i_L_r]/b.shape.radius)
                i_L_r = i_L_r + 1
        # update the dt as the radii change
        #O.dt = factor_dt_crit_ic * PWaveTimeStep()
        return
    print('IC step '+O.tags['Step ic']+'/'+str(n_steps_ic)+' done\n')

    # report
    report_end_step(simulation_report_name, 'IC Generated')
    print('application of the gravity, can be long...\n')
    
    # print configuration
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # next time, do not call this function anymore, but the next one instead
    global iter_0
    iter_0 = O.iter
    checker.command = 'checkUnbalanced_gravity_ic()'
    checker.iterPeriod = 500

    # prepare next phase
    global L_cog_z, L_coordination, L_unbalanced, L_Ec
    L_cog_z = []
    L_coordination = []
    L_unbalanced = []
    L_Ec = []
    # apply gravity
    Newton.gravity = (0, 0, -9.81)
    # write report
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Application of the gravity\n')
    simulation_report.close()
def compute_center_of_gravity()

Compute the center of gravity of all the grains.

Expand source code

def compute_center_of_gravity():
    '''
    Compute the center of gravity of all the grains.
    '''
    Center = np.array([0,0,0])
    Mass = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            Center = Center + b.state.mass*np.array(b.state.pos)
            Mass = Mass + b.state.mass
    return Center/Mass
def plot_trackers_ic()

Plot the evolution of the various trackers.

Expand source code

def plot_trackers_ic(L_cog_z, L_coordination, L_unbalanced, L_Ec, n_window):
    '''
    Plot the evolution of the various trackers.
    '''
    fig, ((ax1,ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(16,9))
    
    # coordinate z of the gravity center
    ax1.plot(L_cog_z, color='k')
    if len(L_cog_z)>=n_window:
        ax1.plot(list(range(len(L_cog_z)-n_window, len(L_cog_z))), L_cog_z[-n_window:], color='r')
        ax1.text((len(L_cog_z)-1)*0.5, (np.max(L_cog_z)+np.min(L_cog_z))/2,\
            'max-min='+str(max(L_cog_z[-n_window:])-min(L_cog_z[-n_window:])), color='r')
    ax1.set_ylabel('Coordinate z of the gravity center')
    
    # coordination number
    ax2.plot(L_coordination, color='k')
    if len(L_coordination)>=n_window:
        ax2.plot(list(range(len(L_coordination)-n_window, len(L_coordination))), L_coordination[-n_window:], color='r')
        ax2.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+np.min(L_coordination))/2,\
            'max-min='+str(max(L_coordination[-n_window:])-min(L_coordination[-n_window:])), color='r')
        ax2.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+3*np.min(L_coordination))/4,\
            'mean='+str(np.mean(L_coordination[-n_window:])), color='r')
    ax2.set_ylabel('Coordination number')

    # unbalanced force ratio
    ax3.plot(L_unbalanced, color='k')
    if len(L_unbalanced)>=n_window:
        ax3.plot(list(range(len(L_unbalanced)-n_window, len(L_unbalanced))), L_unbalanced[-n_window:], color='r')
        ax3.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+np.min(L_unbalanced))/2,\
            'max-min='+str(max(L_unbalanced[-n_window:])-min(L_unbalanced[-n_window:])), color='r')
        ax3.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+3*np.min(L_unbalanced))/4,\
            'mean='+str(np.mean(L_unbalanced[-n_window:])), color='r')
    ax3.set_ylabel('Unbalanced ratio')

    # unbalanced force ratio
    ax4.plot(L_Ec, color='k')
    if len(L_Ec)>=n_window:
        ax4.plot(list(range(len(L_Ec)-n_window, len(L_Ec))), L_Ec[-n_window:], color='r')
        ax4.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+np.min(L_Ec))/2,\
            'max-min='+str(max(L_Ec[-n_window:])-min(L_Ec[-n_window:])), color='r')
        ax4.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+3*np.min(L_Ec))/4,\
            'mean='+str(np.mean(L_Ec[-n_window:])), color='r')
    ax4.set_ylabel('Kinetic energy')
    
    # close
    fig.tight_layout()
    fig.savefig('plot/'+O.tags['id']+'_ic_trackers.png')
    plt.close()
def checkUnbalanced_gravity_ic()

Apply the gravity to settle the particles.

Expand source code

def checkUnbalanced_gravity_ic():
    '''
    Apply the gravity to settle the particles.
    '''
    global iter_0
    global L_cog_z, L_coordination, L_unbalanced, L_Ec
    # define the criteria for steady-state
    n_window = 20
    delta_cog_z = 0.00005

    # read the center of gravity of the granular sample
    cog = compute_center_of_gravity()
    # save trackers 
    L_cog_z.append(cog[2]) # coordinate z of the center of gravity
    L_coordination.append(avgNumInteractions()) # coordination number
    L_unbalanced.append(unbalancedForce()) # unbalanced force ratio
    L_Ec.append(kineticEnergy()) # mean kinetic energy  
    # plot trackers
    plot_trackers_ic(L_cog_z, L_coordination, L_unbalanced, L_Ec, n_window)

    # repeat at least a certain amount of times
    if O.iter-iter_0 < checker.iterPeriod*n_window:
        return
    # check that the center of gravity has not moved
    if max(L_cog_z[-n_window:])-min(L_cog_z[-n_window:]) > delta_cog_z:
        return 

    # report
    report_end_step(simulation_report_name, 'Gravity Applied')
    # print configuration
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # next time, do not call this function anymore, but the next one instead
    if count_grains() < n_grains:
        # Need to reinsert grains
        reinsert_grains_ic(n_grains-count_grains())
        # refind an equilibrium
        L_cog_z = []
        L_coordination = []
        L_unbalanced = []
        L_Ec = []
        iter_0 = O.iter
        checker.iterPeriod = 500
    else :
        # generate the clump
        generateClump()
        # print configuration
        vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})
        # refind an equilibrium
        L_cog_z = []
        L_coordination = []
        L_unbalanced = []
        L_Ec = []
        iter_0 = O.iter
        checker.iterPeriod = 500
        checker.command = 'checkUnbalanced_clump_ic()'
def reinsert_grains_ic()

Reinsert grains that have been deleted.

Expand source code

def reinsert_grains_ic(n_grains_reinsert):
    '''
    Reinsert grains that have been deleted.
    '''
    # write report
    print('Reinsert grains')
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Reinsert grains\n')
    simulation_report.close()

    # definition of the domain (based on the presence of grains)
    min_center_z = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            # compare the z coordinate with the domain
            if b.state.pos[2] + b.shape.radius > min_center_z:
                min_center_z = b.state.pos[2] + b.shape.radius
    
    # determination of the seed
    n_seed_dim = int(100e-3/(((math.sqrt(6)+4)/4*3.101e-3)*2*2))
    dim_seed = (100e-3)/n_seed_dim

    # generation of the grains
    for i_seed in range(n_grains_reinsert):
        # determine the z of the seed and adapt i_seed
        z_seed_i = i_seed//(n_seed_dim*n_seed_dim)
        i_seed = i_seed-z_seed_i*(n_seed_dim*n_seed_dim)
        # determine the x and y of the seed
        x_seed_i = i_seed%n_seed_dim
        y_seed_i = i_seed//n_seed_dim

        # definition of the radius
        radius = (math.sqrt(6)+4)/4 *3.101e-3
        # definition of the position
        center_x = random.uniform(-100e-3 +x_seed_i*dim_seed +radius, -100e-3 +(x_seed_i+1)*dim_seed -radius)
        center_y = random.uniform(-50e-3 +y_seed_i*dim_seed +radius, -50e-3 +(y_seed_i+1)*dim_seed -radius)
        center_z = min_center_z +z_seed_i*2*radius +radius
        
        # determination of the material
        mat_id = random.randint(1, n_mat_part)
        # generation of the particle
        O.bodies.append(sphere(center=[center_x, center_y, center_z], radius=radius, material='mat'+str(mat_id)))

    # recompute the distribution of the material
    L_p_mat = np.zeros(n_mat_part)
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            L_p_mat[b.mat.id-1] = L_p_mat[b.mat.id-1] + 1/n_grains 
    # write in the report
    report_stat_material(simulation_report_name, L_p_mat)

    # report
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Application of the gravity\n')
    simulation_report.close()
def generateClump()

Generate the clumps from the preliminary grains.

Expand source code

def generateClump():
    '''
    Generate the clumps from the preliminary grains.
    '''
    # iteration on the grain
    n_grains0 = count_grains()
    counter = 0
    for b in list(O.bodies)[:n_grains0+8]:
        if isinstance(b.shape, Sphere):
            counter = counter + 1
            # read material
            label_mat = b.mat.label
            # definition of the radius
            radius = 3.101e-3
            # define the elementary spheres
            center_1 = np.array([radius/2, math.sqrt(3)*radius/6, math.sqrt(6)*radius/3])
            center_2 = np.array([       0,                     0,                     0])
            center_3 = np.array([  radius,                     0,                     0])
            center_4 = np.array([radius/2, math.sqrt(3)*radius/2,                     0])
            # compute the center of the clum
            center_clump = (center_1+center_2+center_3+center_4)/4
            # recompute the coordinate of the elementary spheres relative to this center
            center_1 = center_1 - center_clump
            center_2 = center_2 - center_clump
            center_3 = center_3 - center_clump
            center_4 = center_4 - center_clump
            # generate random rotations d'Euler
            phi = random.random()*2*math.pi
            psi = random.random()*2*math.pi
            theta = random.random()*2*math.pi
            # compute the rotation matrice
            M_rot = np.array([[math.cos(psi)*math.cos(phi)-math.sin(psi)*math.cos(theta)*math.sin(phi), -math.cos(psi)*math.sin(phi)-math.sin(psi)*math.cos(theta)*math.cos(phi),  math.sin(psi)*math.sin(theta)],
                              [math.sin(psi)*math.cos(phi)+math.cos(psi)*math.cos(theta)*math.sin(phi), -math.sin(psi)*math.sin(phi)+math.cos(psi)*math.cos(theta)*math.cos(phi), -math.cos(psi)*math.sin(theta)],
                              [                                          math.sin(theta)*math.sin(phi),                                            math.sin(theta)*math.cos(phi),                math.cos(theta)]])
            # rotate the spheres
            center_1 = np.linalg.solve(M_rot, center_1)
            center_2 = np.linalg.solve(M_rot, center_2)
            center_3 = np.linalg.solve(M_rot, center_3)
            center_4 = np.linalg.solve(M_rot, center_4)
            # translate the spheres
            center_1 = center_1 + np.array(b.state.pos)
            center_2 = center_2 + np.array(b.state.pos)
            center_3 = center_3 + np.array(b.state.pos)
            center_4 = center_4 + np.array(b.state.pos)
            # generate the clump
            O.bodies.appendClumped([sphere(center=center_1, radius=radius, material=label_mat),
                                    sphere(center=center_2, radius=radius, material=label_mat),
                                    sphere(center=center_3, radius=radius, material=label_mat),
                                    sphere(center=center_4, radius=radius, material=label_mat)])
    
    # erase the grains
    for b in list(O.bodies)[:n_grains0+8]:
        if isinstance(b.shape, Sphere):
            O.bodies.erase(b.id)
    # erase the existing interactions
    O.interactions.clear()

    # update the time step as the particles change
    #O.dt = factor_dt_crit_ic * PWaveTimeStep()
    #O.dt = 1e-5
    O.dt = 5e-6

    # report and user
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('Generation of the clump\n\n')
    simulation_report.write('Application of the gravity\n')
    simulation_report.close()

    print('Generation of the clump and application of the gravity, can be long...\n')
def checkUnbalanced_clump_ic()

Apply the gravity to settle the cumpled particles.

Expand source code

def checkUnbalanced_clump_ic():
    '''
    Apply the gravity to settle the cumpled particles.
    '''
    global iter_0
    global L_cog_z, L_coordination, L_unbalanced, L_Ec
    # define the criteria for steady-state
    n_window = 20
    delta_cog_z = 0.00002

    # read the center of gravity of the granular sample
    cog = compute_center_of_gravity()
    # save trackers 
    L_cog_z.append(cog[2]) # coordinate z of the center of gravity
    L_coordination.append(avgNumInteractions()) # coordination number
    L_unbalanced.append(unbalancedForce()) # unbalanced force ratio
    L_Ec.append(kineticEnergy()) # mean kinetic energy  
    # plot trackers
    plot_trackers_ic(L_cog_z, L_coordination, L_unbalanced, L_Ec, n_window)

    # repeat at least a certain amount of times
    if O.iter-iter_0 < checker.iterPeriod*n_window:
        return
    # check that the center of gravity has not moved
    if max(L_cog_z[-n_window:])-min(L_cog_z[-n_window:]) > delta_cog_z:
        return 

    # report
    report_end_step(simulation_report_name, 'Gravity Applied')
    # print configuration
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # write data for input

    # report and user
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write('The initial condition is reached, the sample is ready for the granular flow\n\n')
    simulation_report.close()
    print('The initial condition is reached, the sample is ready for the granular flow\n')

    # export the data
    exportData(True)

    # prepare the next step
    iter_0 = O.iter
    checker.iterPeriod = 5000
                        
    checker.command = 'checkUnbalanced_granularFlow()'
    # reduce the time step for the main simulation
    #O.dt = factor_dt_crit * PWaveTimeStep()
    # reduce strongly the damping
    Newton.damping = 0.00
    # cancel the grain_in_domain() verification 
    O.engines = O.engines[1:]
    # add the control of the trap
    O.bodies[1].state.vel = (0, 0, 6.45e-2)
def checkUnbalanced_granularFlow()

Look for the steady-state and save data of the simulation.

Expand source code

def checkUnbalanced_granularFlow():
    '''
    Look for the steady-state and save data of the simulation
    '''
    # save config
    vtkExporter.exportSpheres(what={mat_id:'b.mat.id'})

    # plot XY view
    plot_XY_view()

    # add data
    addPlotData()

    # look for the steady state
    n_window = 5
    # minimum size of the data
    if len(plot.data['m_pos_x']) < n_window:
        return
    
    # miminum displacement on x
    if plot.data['m_pos_x'][-1] < -0.025:
        return
    
    # small variation in the displacement on x
    if max(plot.data['m_pos_x'][-n_window:])-min(plot.data['m_pos_x'][-n_window:]) > 0.00002:
        return
    
    # check ratio of the force
    if unbalancedForce() > 1e-6:
        return

    # check the kinetic energy
    if kineticEnergy() > 0.0003:
        return
    
    # prepare the next step
    stopLoad()
def addPlotData()

Save data in plot.

Expand source code

def addPlotData():
    """
    Save data in plot.
    """
    # find the extrema and the mean position
    mean_center = np.array([0, 0, 0])
    Mass = 0
    y_min = 0
    y_max = 0
    x_max = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            # compute the mean
            mean_center = mean_center + b.state.mass*np.array(b.state.pos)
            Mass = Mass + b.state.mass
            # compute extrema
            if x_max < b.state.pos[0]:
                x_max = b.state.pos[0]
            if y_max < b.state.pos[1]:
                y_max = b.state.pos[1]
            if b.state.pos[1] < y_min :
                y_min = b.state.pos[1]
    # compute the mean position
    mean_center = mean_center/Mass
    # add data
    plot.addData(i=O.iter-iter_0, coordination=avgNumInteractions(), unbalanced=unbalancedForce(), Ec=kineticEnergy(),\
                 m_pos_x=mean_center[0], m_pos_y=mean_center[1], m_pos_z=mean_center[2],\
                 x_max=x_max, y_min=y_min, y_max=y_max,\
                )
    # plot the data
    saveData()
def saveData()

Save data in .txt file.

Expand source code

def saveData():
    """
    Save data in .txt file.
    """
    # save data
    plot.saveDataTxt('plot/data_'+O.tags['d.id']+'.txt')
    # prepare plot
    L_ite = []
    L_cog_x = []
    L_max_x = []
    L_max_y = []
    L_min_y = []
    L_coordination = []
    L_unbalanced = []
    L_Ec = []

    # read data
    file = 'plot/data_'+O.tags['d.id']+'.txt'
    data = np.genfromtxt(file, skip_header=1)
    file_read = open(file, 'r')
    lines = file_read.readlines()
    file_read.close()
    if len(lines) >= 3:
        for i in range(len(data)):
            L_Ec.append(data[i][0])
            L_coordination.append(data[i][1])
            L_ite.append(data[i][2])
            L_cog_x.append(data[i][3])
            L_unbalanced.append(data[i][6])
            L_max_x.append(data[i][7])
            L_max_y.append(data[i][8])
            L_min_y.append(data[i][9])

        # plot
        n_window = 5
        fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2,3, figsize=(20,10),num=1)

        # coordinate x of the gravity center
        ax1.plot(L_cog_x, color='k')
        if len(L_cog_x)>=n_window:
            ax1.plot(list(range(len(L_cog_x)-n_window, len(L_cog_x))), L_cog_x[-n_window:], color='r')
            ax1.text((len(L_cog_x)-1)*0.5, (np.max(L_cog_x)+np.min(L_cog_x))/2,\
                'max-min='+str(max(L_cog_x[-n_window:])-min(L_cog_x[-n_window:])), color='r')
        ax1.set_ylabel('Coordinate x of the gravity center')
        
        # coordinate x of the maximum
        ax2.plot(L_max_x, color='k')
        if len(L_max_x)>=n_window:
            ax2.plot(list(range(len(L_max_x)-n_window, len(L_max_x))), L_max_x[-n_window:], color='r')
            ax2.text((len(L_max_x)-1)*0.5, (np.max(L_max_x)+np.min(L_max_x))/2,\
                'max-min='+str(max(L_max_x[-n_window:])-min(L_max_x[-n_window:])), color='r')
        ax2.set_ylabel('Boundary x of the grains')
        
        # coordinates x of the extrema
        ax3.plot(L_max_y, color='k')
        ax3.plot(L_min_y, color='k')
        if len(L_max_y)>=n_window:
            ax3.plot(list(range(len(L_max_y)-n_window, len(L_max_y))), L_max_y[-n_window:], color='r')
            ax3.text((len(L_max_y)-1)*0.5, (np.max(L_max_y)+np.min(L_max_y))/2,\
                'max-min='+str(max(L_max_y[-n_window:])-min(L_max_y[-n_window:])), color='r')
            ax3.plot(list(range(len(L_min_y)-n_window, len(L_min_y))), L_min_y[-n_window:], color='r')
            ax3.text((len(L_min_y)-1)*0.5, (np.max(L_min_y)+np.min(L_min_y))/2,\
                'max-min='+str(max(L_min_y[-n_window:])-min(L_min_y[-n_window:])), color='r')
        ax3.set_ylabel('Boundaries y of the grains')  

        # coordination number
        ax4.plot(L_coordination, color='k')
        if len(L_coordination)>=n_window:
            ax4.plot(list(range(len(L_coordination)-n_window, len(L_coordination))), L_coordination[-n_window:], color='r')
            ax4.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+np.min(L_coordination))/2,\
                'max-min='+str(max(L_coordination[-n_window:])-min(L_coordination[-n_window:])), color='r')
            ax4.text((len(L_coordination)-1)*0.5, (np.max(L_coordination)+3*np.min(L_coordination))/4,\
                'mean='+str(np.mean(L_coordination[-n_window:])), color='r')
        ax4.set_ylabel('Coordination number')

        # unbalanced force ratio
        ax5.plot(L_unbalanced, color='k')
        if len(L_unbalanced)>=n_window:
            ax5.plot(list(range(len(L_unbalanced)-n_window, len(L_unbalanced))), L_unbalanced[-n_window:], color='r')
            ax5.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+np.min(L_unbalanced))/2,\
                'max-min='+str(max(L_unbalanced[-n_window:])-min(L_unbalanced[-n_window:])), color='r')
            ax5.text((len(L_unbalanced)-1)*0.5, (np.max(L_unbalanced)+3*np.min(L_unbalanced))/4,\
                'mean='+str(np.mean(L_unbalanced[-n_window:])), color='r')
        ax5.set_ylabel('Unbalanced ratio')

        # unbalanced force ratio
        ax6.plot(L_Ec, color='k')
        if len(L_Ec)>=n_window:
            ax6.plot(list(range(len(L_Ec)-n_window, len(L_Ec))), L_Ec[-n_window:], color='r')
            ax6.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+np.min(L_Ec))/2,\
                'max-min='+str(max(L_Ec[-n_window:])-min(L_Ec[-n_window:])), color='r')
            ax6.text((len(L_Ec)-1)*0.5, (np.max(L_Ec)+3*np.min(L_Ec))/4,\
                'mean='+str(np.mean(L_Ec[-n_window:])), color='r')
        ax6.set_ylabel('Kinetic energy')

        fig.tight_layout()
        fig.savefig('plot/'+O.tags['d.id']+'_trackers.png')
        plt.close()
def plot_XY_view()

Plot the XY view of the clump centers.

Expand source code

def plot_XY_view():
    '''
    Plot the XY view of the clump centers.
    '''
    # prepare the plot
    L_x_center = []
    L_y_center = []
    # iterate on the grains
    for b in O.bodies:
        if isinstance(b.shape, Clump):
            L_x_center.append(b.state.pos[0])
            L_y_center.append(b.state.pos[1])
    
    # plot
    fig, (ax1) = plt.subplots(1,1, figsize=(16,9),num=1)
    ax1.scatter(L_x_center, L_y_center, color='k')
    ax1.plot([0, -100e-3, -100e-3, 0, 0], [50e-3, 50e-3, -50e-3, -50e-3, 50e-3], color='gray')
    ax1.set_xlabel('Coordinate x')
    ax1.set_ylabel('Coordinate y')
    ax1.set_aspect('equal')
    fig.savefig('plot/'+O.tags['d.id']+'_XY_view.png')
    plt.close()
def stopLoad()

Close simulation.

Expand source code

def stopLoad():
    """
    Close simulation.
    """
    # write output for submission
    exportData(False)

    # close yade
    O.pause()
    # report
    report_end_step(simulation_report_name, 'Granular flow')
    # characterize the simulation
    tac = time.perf_counter()
    hours = (tac-tic_0)//(60*60)
    minutes = (tac-tic_0 -hours*60*60)//(60)
    seconds = int(tac-tic_0 -hours*60*60 -minutes*60)
    # report
    simulation_report = open(simulation_report_name, 'a')
    simulation_report.write("Simulation time : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n\n")
    simulation_report.close()
    print("\nSimulation time : "+str(hours)+" hours "+str(minutes)+" minutes "+str(seconds)+" seconds\n")

    # save simulation
    os.mkdir('data/'+O.tags['d.id'])
    shutil.copytree('plot','data/'+O.tags['d.id']+'/plot')
    shutil.copytree('vtk','data/'+O.tags['d.id']+'/vtk')
    shutil.copytree('report', 'data/'+O.tags['d.id']+'/report')
    shutil.copy('TC105_2RRA_ASM.py','data/'+O.tags['d.id']+'/TC105_2RRA_ASM.py')
def exportData()

Export the data required (idi, xi, yi, zi) for the 14.140 grains.

Expand source code

def exportData(flag_start):
    '''
    Export the data required (idi, xi, yi, zi) for the 14.140 grains.
    carriage return at end of each line
    separate coordinate value with a comma
    coordinate value in single or double precision floating point
    position in millimeters
    '''
    # the name of the file depends on the start or end state
    if flag_start:
        filename = 'report/start'+O.tags['d.id']+'.dat'
    else :
        filename = 'report/end'+O.tags['d.id']+'.dat'
    # open file
    file = open(filename, 'w')
    
    # iterate on the grains
    id_g = 0
    for b in O.bodies:
        if isinstance(b.shape, Sphere):
            id_g = id_g + 1
            # prepare the line
            line = str(id_g)+', '+\
                   str(b.state.pos[0]*1e3)+', '+\
                   str(b.state.pos[1]*1e3)+', '+\
                   str(b.state.pos[2]*1e3)+'\r'
            # write
            file.write(line)

    # close the file
    file.close()