Module Create_LG_IC

@author: Alexandre Sac–Morane alexandre.sac-morane@uclouvain.be

The goal of this file is to define an initial configuration. Grains are considered as a square or a disk. We have 2 temporary classes about grains and contact.

Expand source code
# -*- coding: utf-8 -*-
"""
@author: Alexandre Sac--Morane
alexandre.sac-morane@uclouvain.be

The goal of this file is to define an initial configuration.
Grains are considered as a square or a disk.
We have 2 temporary classes about grains and contact."""

#-------------------------------------------------------------------------------
#Librairy
#-------------------------------------------------------------------------------

import numpy as np
import math
import random
import matplotlib.pyplot as plt
import Grain

#-------------------------------------------------------------------------------
#Classes definition
#-------------------------------------------------------------------------------

class Grain_Tempo:
  """
  A temporary grain used to generated an initial condition.
  """

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

  def __init__(self, ID, Center, Lenght, dict_material, Type):
    """Defining the grain.

        Input :
            itself (a grain_tempo)
            an id (a int)
            a center coordinate (a 1 x 2 numpy array)
            a dimension, radius if it is a disk or the size if it is a square (a float)
            a material dictionnary (a dict)
            a grain type, disk or square (a float)
        Output :
            Nothing, but a temporary grain is generated (a grain_tempo)
    """
    n_border = 20 #number of vertices
    L_border = []
    L_border_x = []
    L_border_y = []
    L_r = []
    L_theta_r = []
    if Type == 'Disk':
        #Build the border
        for i in range(n_border):
            theta = 2*math.pi*i/n_border
            p = np.array(Center) + np.array([Lenght*math.cos(theta),Lenght*math.sin(theta)])
            L_border.append(p)
            L_border_x.append(p[0])
            L_border_y.append(p[1])
            L_r.append(Lenght)
            L_theta_r.append(theta)
        L_border.append(L_border[0])
        L_border_x.append(L_border_x[0])
        L_border_y.append(L_border_y[0])
        self.radius = Lenght
        self.theta = 0
        self.rho_surf = dict_material['rho_surf_disk']
        self.surface = math.pi*Lenght**2
        self.mass = math.pi*Lenght**2*dict_material['rho_surf_disk']
        self.inertia = self.mass*Lenght**2
    if Type == 'Square':
        if False:
            #initial random rotation
            Theta = random.uniform(0,math.pi/2)
            #Build the border
            P_rot = np.array([[math.cos(Theta), -math.sin(Theta)],
                              [math.sin(Theta),  math.cos(Theta)]]) #initial rotation
            for i in range(n_border):
                angle = 2*math.pi*i/n_border #angle of the vertex
                angle_to_determine_R = angle%(math.pi/2)
                if angle_to_determine_R > math.pi/4:
                    angle_to_determine_R = angle_to_determine_R - math.pi/2
                p = np.array([Lenght/2/math.cos(angle_to_determine_R),0]) #compute the radius, considering the grain as a square
                P_rot_p = np.array([[math.cos(angle), -math.sin(angle)],
                                    [math.sin(angle),  math.cos(angle)]])
                p = np.dot(P_rot_p,p)
                p = np.dot(P_rot,p)
                p = p + np.array(Center)
                L_border.append(p)
                L_border_x.append(p[0])
                L_border_y.append(p[1])
                L_r.append(Lenght/2/math.cos(angle_to_determine_R))
                angle_r = Theta + angle
                while angle_r >= 2*math.pi:
                    angle_r = angle_r - 2*math.pi
                while angle_r < 0:
                    angle_r = angle_r + 2*math.pi
                L_theta_r.append(angle_r)
            L_border.append(L_border[0])
            L_border_x.append(L_border_x[0])
            L_border_y.append(L_border_y[0])
            self.dimension = Lenght
            self.theta = Theta
            self.rho_surf = dict_material['rho_surf_square']
            self.surface = Lenght**2
            self.mass = Lenght**2*dict_material['rho_surf_square']
            self.inertia = 1/6*self.mass*Lenght**2
        else :
            #Build the border
            for i in range(n_border):
                theta = 2*math.pi*i/n_border
                p = np.array(Center) + np.array([Lenght/2*math.cos(theta),Lenght/2*math.sin(theta)])
                L_border.append(p)
                L_border_x.append(p[0])
                L_border_y.append(p[1])
                L_r.append(Lenght/2)
                L_theta_r.append(theta)
            L_border.append(L_border[0])
            L_border_x.append(L_border_x[0])
            L_border_y.append(L_border_y[0])
            self.dimension = Lenght/2
            self.theta = 0
            self.rho_surf = dict_material['rho_surf_disk']
            self.surface = math.pi*(Lenght/2)**2
            self.mass = math.pi*(Lenght/2)**2*dict_material['rho_surf_disk']
            self.inertia = self.mass*(Lenght/2)**2

    #save
    self.id = ID
    self.type = Type
    self.center = np.array(Center)
    self.l_border = L_border
    self.l_border_x = L_border_x
    self.l_border_y = L_border_y
    self.l_theta_r = L_theta_r
    self.l_r = L_r
    self.r_min = min(L_r)
    self.r_max = max(L_r)
    self.y = dict_material['Y']
    self.nu = dict_material['nu']
    self.g = dict_material['Y']/2/(1+dict_material['nu']) #shear modulus
    self.fx = 0
    self.fy = 0
    self.v = np.array([0,0])
    self.w = 0

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

  def add_F(self, F, p_application):
      """
      Add a force to the grain.

        Input :
            itself (a grain_tempo)
            a force applied (a 1 x 2 numpy array)
            a application point (a 1 x 2 numpy array)
        Output :
            Nothing, but attributes are updated (three floats)
      """
      self.fx = self.fx + F[0]
      self.fy = self.fy + F[1]
      v1 = np.array([p_application[0]-self.center[0], p_application[1]-self.center[1], 0])
      v2 = np.array([F[0], F[1], 0])
      self.mz = self.mz + np.cross(v1,v2)[2]

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

  def init_F_control(self,g):
      """
      Initialize the force applied to the grain.

      A gravity is assumed.

        Input :
            itself (a grain_tempo)
            a gravity value (a float)
        Output :
            Nothing, but attributes concerning the force applied are initialized (three floats)
      """
      self.fx = 0
      self.fy = -g*self.mass
      self.mz = 0

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

  def euler_semi_implicite(self,dt_DEM,factor):
    """
    Move the grain following a semi implicit euler scheme.

        Input :
            itself (a grain_tempo)
            a time step (a float)
            a factor to limite the displacement (a float)
        Output :
            Nothing, but the grain is moved
    """
    #translation
    a_i = np.array([self.fx,self.fy])/self.mass
    self.v = self.v + a_i*dt_DEM
    if self.type == 'Disk':
        if np.linalg.norm(self.v) > self.radius*factor/dt_DEM: #limitation of the speed
            self.v = self.v * self.radius*factor/dt_DEM/np.linalg.norm(self.v)
    elif self.type == 'Square':
        if np.linalg.norm(self.v) > self.dimension*factor/dt_DEM: #limitation of the speed
            self.v = self.v * self.dimension*factor/dt_DEM/np.linalg.norm(self.v)
    for i in range(len(self.l_border)):
        self.l_border[i] = self.l_border[i] + self.v*dt_DEM
        self.l_border_x[i] = self.l_border_x[i] + self.v[0]*dt_DEM
        self.l_border_y[i] = self.l_border_y[i] + self.v[1]*dt_DEM
    self.center = self.center + self.v*dt_DEM

    #rotation
    dw_i = self.mz/self.inertia
    self.w = self.w + dw_i*dt_DEM
    self.theta = self.theta + self.w*dt_DEM
    for i_theta_r in range(len(self.l_theta_r)) :
        theta_r = self.l_theta_r[i_theta_r]
        theta_r = theta_r + self.w*dt_DEM
        while theta_r >= 2*math.pi:
            theta_r = theta_r - 2*math.pi
        while theta_r < 0 :
            theta_r = theta_r + 2*math.pi
        self.l_theta_r[i_theta_r] = theta_r
    #rigib body rotation
    for i in range(len(self.l_border)):
        p = self.l_border[i] - self.center
        Rot_Matrix = np.array([[math.cos(self.w*dt_DEM), -math.sin(self.w*dt_DEM)],
                               [math.sin(self.w*dt_DEM),  math.cos(self.w*dt_DEM)]])
        p = np.dot(Rot_Matrix,p)
        self.l_border[i] = p + self.center
        self.l_border_x[i] = p[0] + self.center[0]
        self.l_border_y[i] = p[1] + self.center[1]

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

class Contact_Tempo:
  """
  A temporary contact grain - grain used to generated an initial condition.
  """

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

  def __init__(self, ID, G1, G2, dict_material):
    """
    Defining the contact grain-grain.

        Input :
            itself (a contact_tempo)
            an id (a int)
            two grains (two grain_tempos)
            a material dictionnary (a dict)
        Output :
            Nothing, but the contact grain - grain is generated (a contact_tempo)
    """
    self.id = ID
    self.g1 = G1
    self.g2 = G2
    self.ft = 0
    self.mu = 0
    self.coeff_restitution = dict_material['coeff_restitution']
    self.tangential_old_statut = False
    self.overlap_tangential = 0

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

  def normal(self):
    """
    Compute the normal reaction of a contact grain-grain.

    Here a pontual spring is considered.

        Input :
            itself (a contact_tempo)
        Output :
            Nothing, but attributes are updated
    """
    #compute angle between grains
    g1_to_g2 = self.g2.center - self.g1.center
    if g1_to_g2[1] >= 0 :
        angle_g1_to_g2 = math.acos(g1_to_g2[0]/np.linalg.norm(g1_to_g2))
        angle_g2_to_g1 = angle_g1_to_g2 + math.pi
    else :
        angle_g1_to_g2 = math.pi + math.acos(-g1_to_g2[0]/np.linalg.norm(g1_to_g2))
        angle_g2_to_g1 = angle_g1_to_g2 - math.pi

    #extract
    L_i_vertices_1 = extract_vertices(self.g1, angle_g1_to_g2)
    L_i_vertices_2 = extract_vertices(self.g2, angle_g2_to_g1)

    #looking for the nearest nodes
    d_virtual = max(self.g1.r_max,self.g2.r_max)
    ij_min = [0,0]
    d_ij_min = 100*d_virtual #Large
    for i in L_i_vertices_1:
        for j in L_i_vertices_2:
            d_ij = np.linalg.norm(self.g2.l_border[:-1][j]-self.g1.l_border[:-1][i]+d_virtual*(self.g2.center-self.g1.center)/np.linalg.norm(self.g2.center-self.g1.center))
            if d_ij < d_ij_min :
                d_ij_min = d_ij
                ij_min = [i,j]
    self.ij_min = ij_min

    #-----------------------------------------------------------------------------
    #Computing CP
    #-----------------------------------------------------------------------------

    M = (self.g1.l_border[:-1][ij_min[0]]+self.g2.l_border[:-1][ij_min[1]])/2
    # 5 candidates for CP
    N = np.array([self.g1.l_border[:-1][ij_min[0]][0] - self.g2.l_border[:-1][ij_min[1]][0],
                  self.g1.l_border[:-1][ij_min[0]][1] - self.g2.l_border[:-1][ij_min[1]][1]])
    N = N/np.linalg.norm(N)
    PB = np.array([-N[1] ,N[0]])
    PB = PB/np.linalg.norm(PB)

    #candidats from grain 1
    if ij_min[0] <len(self.g1.l_border[:-1]) - 1:
        M1 = self.g1.l_border[:-1][ij_min[0]+1]-self.g1.l_border[:-1][ij_min[0]]
    else :
        M1 = self.g1.l_border[:-1][0]-self.g1.l_border[:-1][ij_min[0]]
    M1 = M1/np.linalg.norm(M1)
    M3 = self.g1.l_border[:-1][ij_min[0]-1]-self.g1.l_border[:-1][ij_min[0]]
    M3 = M3/np.linalg.norm(M3)
    #reorganize the candidats
    if np.dot(M1,PB) < 0:
        Mtempo = M1.copy()
        M1 = M3.copy()
        M3 = Mtempo.copy()

    #candidats from grain 2
    if ij_min[1] <len(self.g2.l_border[:-1]) - 1:
        M2 = self.g2.l_border[:-1][ij_min[1]+1]-self.g2.l_border[:-1][ij_min[1]]
    else :
        M2 = self.g2.l_border[:-1][0]-self.g2.l_border[:-1][ij_min[1]]
    M2 = M2/np.linalg.norm(M2)
    M4 = self.g2.l_border[:-1][ij_min[1]-1]-self.g2.l_border[:-1][ij_min[1]]
    M4 = M4/np.linalg.norm(M4)
    #reorganize the candidats
    if np.dot(M2,PB) < 0:
      Mtempo = M2.copy()
      M2 = M4.copy()
      M4 = Mtempo.copy()

    #compute the different angles
    theta_PB = math.pi/2
    theta_M1 =  math.acos(np.dot(M1,N))
    theta_M2 =  math.acos(np.dot(M2,N))
    theta_M3 = -math.acos(np.dot(M3,N))
    theta_M4 = -math.acos(np.dot(M4,N))

    #find the PC
    if theta_M2 < theta_PB and theta_PB < theta_M1\
       and theta_M3 < -theta_PB and -theta_PB < theta_M4:
       PC = PB
    else:
      L_Mi = [M1,M2,M3,M4]
      L_theta_Mi_PB=[theta_M1-theta_PB, theta_PB-theta_M2, -theta_M3-theta_PB, theta_PB+theta_M4]
      PC = L_Mi[L_theta_Mi_PB.index(min(L_theta_Mi_PB))]

    #-----------------------------------------------------------------------------
    # Compute the normal and tangential planes
    #-----------------------------------------------------------------------------

    PC_normal = np.array([PC[1],-PC[0]])
    if np.dot(PC_normal,(self.g2.center-self.g1.center)/np.linalg.norm(self.g2.center-self.g1.center))<0 :
        PC_normal = np.array([-PC[1],PC[0]])
    self.pc_normal = PC_normal #n12
    self.pc_tangential = np.array([-PC_normal[1],PC_normal[0]])

    #-----------------------------------------------------------------------------
    # Compute the overlap
    #-----------------------------------------------------------------------------

    d_b = np.dot(M-self.g2.l_border[:-1][ij_min[1]],PC_normal)
    d_a = np.dot(M-self.g1.l_border[:-1][ij_min[0]],PC_normal)
    overlap = d_b - d_a
    self.overlap_normal = overlap

    if overlap > 0:
    #-----------------------------------------------------------------------------
    # Compute the reaction
    #-----------------------------------------------------------------------------

        #Spring term
        Y_eq = 1/((1-self.g1.nu*self.g1.nu)/self.g1.y+(1-self.g2.nu*self.g2.nu)/self.g2.y)
        R_eq = 1/(1/np.linalg.norm(self.g1.center-self.g1.l_border[self.ij_min[0]])+1/np.linalg.norm(self.g2.center-self.g2.l_border[self.ij_min[1]]))
        k = 4/3*Y_eq*math.sqrt(R_eq)
        F_2_1_n = -k * overlap**(3/2)  #unlinear spring
        F_2_1 = F_2_1_n * PC_normal
        self.F_2_1_n = F_2_1_n
        self.Ep_n = 2/5 * k * overlap**(5/2) #-dEp/dx = F_2_1_n
        self.g1.add_F( F_2_1, self.g1.l_border[:-1][ij_min[0]])
        self.g2.add_F(-F_2_1, self.g2.l_border[:-1][ij_min[1]])

        #Damping term
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g1.mass*self.g2.mass/(self.g1.mass+self.g2.mass)
        eta = 2 * gamma * math.sqrt(mass_eq*k)
        F_2_1_damp_n = np.dot(self.g2.v - self.g1.v,PC_normal)*eta
        F_2_1_damp = F_2_1_damp_n *PC_normal
        self.F_2_1_damp = F_2_1_damp_n
        self.g1.add_F( F_2_1_damp, self.g1.l_border[:-1][ij_min[0]])
        self.g2.add_F(-F_2_1_damp, self.g2.l_border[:-1][ij_min[1]])

    #no contact finally
    else :
        self.F_2_1_n = 0
        self.F_2_1_damp = 0
        self.Ep_n = 0

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

  def tangential(self,dt_DEM):
    """
    Compute the tangential reaction of a contact grain-grain.

    Here a pontual spring is considered

        Input :
            itself (a contact_tempo)
            a time step (a float)
        Output :
            Nothing, but attributes are updated
    """
    if self.overlap_normal > 0 and self.mu > 0:

        if self.tangential_old_statut:
          #if a reaction has been already computed
          #need to project the tangential reaction on the new tangential plane
          self.ft = self.ft*np.dot(self.tangential_old,self.pc_tangential)
        else:
          self.tangential_old_statut = True

        G_eq = 1/((1-self.g1.nu)/self.g1.g+(1-self.g2.nu)/self.g2.g)
        R_eq = 1/(1/np.linalg.norm(self.g1.center-self.g1.l_border[self.ij_min[0]])+1/np.linalg.norm(self.g2.center-self.g2.l_border[self.ij_min[1]]))
        kt0 = 8 * G_eq *math.sqrt(R_eq*abs(self.overlap_normal))
        kt = kt0*math.sqrt(max(1-2/3*kt0*abs(self.overlap_tangential)/self.mu/abs(self.F_2_1_n),0)) #not linear spring

        r1 = np.linalg.norm(self.g1.l_border[:-1][self.ij_min[0]] - self.g1.center) - self.overlap_normal/2
        r2 = np.linalg.norm(self.g2.l_border[:-1][self.ij_min[1]] - self.g2.center) - self.overlap_normal/2
        Delta_Us = (np.dot(self.g1.v-self.g2.v,self.pc_tangential) + r1*self.g1.w + r2*self.g2.w)*dt_DEM
        self.overlap_tangential = self.overlap_tangential + Delta_Us
        self.ft = self.ft - kt*Delta_Us
        self.tangential_old = self.pc_tangential
        if abs(self.ft) > abs(self.mu*self.F_2_1_n) or kt == 0: #Coulomb criteria
            self.ft = self.mu * abs(self.F_2_1_n) * np.sign(self.ft)

        self.g1.add_F( self.ft*self.pc_tangential, self.g1.l_border[:-1][self.ij_min[0]])
        self.g2.add_F(-self.ft*self.pc_tangential, self.g2.l_border[:-1][self.ij_min[1]])

        #Damping term
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g1.mass*self.g2.mass/(self.g1.mass+self.g2.mass)
        eta = 2 * gamma * math.sqrt(mass_eq*kt)
        F_2_1_damp_t = -Delta_Us/dt_DEM*eta/2
        F_2_1_damp = F_2_1_damp_t *self.pc_tangential
        self.ft_damp = F_2_1_damp_t
        self.g1.add_F( F_2_1_damp, self.g1.l_border[:-1][self.ij_min[0]])
        self.g2.add_F(-F_2_1_damp, self.g2.l_border[:-1][self.ij_min[1]])

    #no contact finally
    else :
        tangential_old_statut = False
        self.overlap_tangential = 0
        self.ft = 0
        self.ft_damp = 0

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

class Contact_gw_Tempo:
  """
  A temporary contact grain - wall used to generated an initial condition.
  """

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

  def __init__(self, ID, G, dict_material, Nature, Limit, Overlap):
    """
    Defining the contact grain-wall.

        Input :
             itself (a contact_gw_tempo)
             an id (a int)
             a grain (a grain_tempo)
             a material dictionnary (a dict)
             the nature of the wall (a string)
             the coordinate of the wall (a float)
             an overlap (a float)
    """
    self.id = ID
    self.g = G
    factor = 5 #factor just to increase the stiffness
    self.k = factor*4/3*self.g.y/(1-self.g.nu*self.g.nu)*math.sqrt(self.g.r_max) #Hertz law
    self.kt = 0
    self.ft = 0
    self.limit = Limit
    self.nature = Nature
    self.mu = 0
    self.coeff_restitution = dict_material['coeff_restitution']
    self.overlap = Overlap
    self.tangential_old_statut = False
    self.overlap_tangential = 0

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

  def update_overlap(self,new_overlap):
    '''
    Update the overlap of a contact already created.

        Input :
            itself (a contact_gw_tempo)
            an overlap (a float)
        Output :
            Nothing, but the attribut concerning the overlap is updated (a float)
    '''
    self.overlap = new_overlap

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

  def  normal(self):
    """
    Compute the normal reaction of a contact grain-wall.

    Here a pontual spring is considered

        Input :
            itself (a contact_gw_tempo)
        Output :
            Nothing, but attributes are updated
    """
    #conditions "if" are defined and same for each wall nature
    if self.nature == 'gwy_min':
        #unlinear stiffness
        nwg = np.array([0,1])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])
        #damping
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g.mass
        eta = 2 * gamma * math.sqrt(mass_eq*self.k)
        Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
        Fwg_damp = Fwg_damp_n*nwg
        self.Fwg_damp_n = Fwg_damp_n
        self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])

    elif self.nature == 'gwy_max':
        #unlinear stiffness
        nwg = np.array([0,-1])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(max(self.g.l_border_y))])
        #damping
        Fwg_damp_n = 0
        self.Fwg_damp_n = Fwg_damp_n

    elif self.nature == 'gwx_min':
        #unlinear stiffness
        nwg = np.array([1,0])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])
        #damping
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g.mass
        eta = 2 * gamma * math.sqrt(mass_eq*self.k)
        Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
        Fwg_damp = Fwg_damp_n*nwg
        self.Fwg_damp_n = Fwg_damp_n
        self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])

    elif self.nature == 'gwx_max':
        #unlinear stiffness
        nwg = np.array([-1,0])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])
        #damping
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g.mass
        eta = 2 * gamma * math.sqrt(mass_eq*self.k)
        Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
        Fwg_damp = Fwg_damp_n*nwg
        self.Fwg_damp_n = Fwg_damp_n
        self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])

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

  def tangential(self, dt_DEM):
   """
   Compute the tangential reaction of a contact grain-wall.

   Here a pontual spring is considered.

        Input :
            itself (a contact_gw_tempo)
            a time step (a float)
        Output :
            Nothing, but attributes are updated
   """
   #conditions "if" are defined and same for each wall nature
   if self.nature == 'gwy_min':
       #unlinear stiffness
       twg = np.array([-1, 0])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_y.index(min(self.g.l_border_y))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])

   elif self.nature == 'gwy_max':
       #unlinear stiffness
       twg = np.array([1, 0])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_y.index(max(self.g.l_border_y))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(max(self.g.l_border_y))])

   elif self.nature == 'gwx_min':
       #unlinear stiffness
       twg = np.array([0, 1])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_x.index(min(self.g.l_border_x))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])

   elif self.nature == 'gwx_max':
       #linear stiffness
       twg = np.array([0, -1])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_x.index(max(self.g.l_border_x))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])

#-------------------------------------------------------------------------------
#Function Definition
#-------------------------------------------------------------------------------

def LG_tempo(dict_algorithm, dict_geometry, dict_ic, dict_material, dict_sample, dict_sollicitations, simulation_report):
    """
    Create an initial condition

        Input :
            an algorithm dictionnary (a dict)
            a geometry dictionnary (a dict)
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
            a sample dictionnary (a dict)
            a sollicitations dictionnary (a dict)
            a simulation report (a report)
        Output :
            Nothing, but dictionnaries are updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    n_generation = dict_ic['n_generation']
    if n_generation != 2:
        simulation_report.write('n_generation must be equal to 2 !')
        raise ValueError('n_generation must be equal to 2 !')
    factor = dict_ic['factor_ymax_box']
    N_grain_disk = dict_geometry['N_grain_disk']
    N_grain_square = dict_geometry['N_grain_square']
    N_grain = dict_geometry['N_grain_disk'] + dict_geometry['N_grain_square']
    L_radius = dict_geometry['L_R']
    L_percentage_radius = dict_geometry['L_percentage_R']
    L_dimension = dict_geometry['L_Dimension']
    L_percentage_dimension = dict_geometry['L_percentage_Dimension']
    x_min = dict_sample['x_box_min']
    x_max = dict_sample['x_box_max']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #define the y_max for the grains generation
    radius_mean = 0
    for i in range(len(L_radius)):
        radius_mean = radius_mean + L_radius[i]*L_percentage_radius[i]
    dimension_mean = 0
    for i in range(len(L_dimension)):
        dimension_mean = dimension_mean + L_dimension[i]*L_percentage_dimension[i]
    dy_creation = N_grain_disk/n_generation*factor*(2*radius_mean)**2/(x_max-x_min) + N_grain_square/n_generation*factor*(dimension_mean)**2/(x_max-x_min)

    #plan the grains generation
    L_n_grain_radius_try_one = []
    L_n_grain_radius = []
    L_n_grain_radius_done = []
    for percentage in L_percentage_radius:
        L_n_grain_radius_try_one.append(int(N_grain_disk*percentage/n_generation))
        L_n_grain_radius.append(int(N_grain_disk*percentage))
        L_n_grain_radius_done.append(0)
    L_n_grain_dimension_try_one = []
    L_n_grain_dimension = []
    L_n_grain_dimension_done = []
    for percentage in L_percentage_dimension:
        L_n_grain_dimension_try_one.append(int(N_grain_square*percentage/n_generation))
        L_n_grain_dimension.append(int(N_grain_square*percentage))
        L_n_grain_dimension_done.append(0)

    #Creation of grains
    #grains generation is decomposed in several steps (creation of grain then settlement)
    i_DEM = 0
    L_L_g_tempo = []

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

    print('First generation of grains')
    L_g_tempo = []

    #add elements in dicts
    dict_ic['L_g_tempo'] = L_g_tempo
    dict_ic['L_L_g_tempo'] = L_L_g_tempo
    dict_ic['i_DEM_IC'] = i_DEM
    dict_ic['L_n_grain_radius_try_one'] = L_n_grain_radius_try_one
    dict_ic['L_n_grain_radius'] = L_n_grain_radius
    dict_ic['L_n_grain_radius_done'] = L_n_grain_radius_done
    dict_ic['L_n_grain_dimension_try_one'] = L_n_grain_dimension_try_one
    dict_ic['L_n_grain_dimension'] = L_n_grain_dimension
    dict_ic['L_n_grain_dimension_done'] = L_n_grain_dimension_done
    dict_ic['last_id'] = 0
    dict_sample['y_box_min_ic'] = dict_sample['y_box_min']
    dict_sample['dy_creation'] = dy_creation

    Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, 1, simulation_report)

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #DEM to find the steady-state configuration after loading
    #find the maximum y (center+radius)
    y_max = dict_sample['y_box_min_ic']
    for grain in L_g_tempo:
        if max(grain.l_border_y) > y_max:
            y_max = max(grain.l_border_y)

    #add element in dict
    dict_sample['y_box_max'] = y_max

    DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, False, simulation_report)

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

    print('Second generation of grains')

    #Update dict
    L_g_tempo = []
    dict_ic['L_g_tempo'] = L_g_tempo

    Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, 2, simulation_report)

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #DEM to find the steady-state configuration after loading
    #find the maximum y (center+radius)
    y_max = dict_sample['y_box_min_ic']
    for grain in L_g_tempo:
        if max(grain.l_border_y) > y_max:
            y_max = max(grain.l_border_y)

    DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, False, simulation_report)

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

    print('Combine generations of grains')

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_L_g_tempo = dict_ic['L_L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    L_g = []
    for L_g_tempo in L_L_g_tempo:
        for g_tempo in L_g_tempo:
            L_g.append(g_tempo)

    #update element in dict
    dict_ic['L_g_tempo'] = L_g

    DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, True, simulation_report)

    #update element in dict
    dict_sample['y_box_max'] = dict_ic['y_box_max']

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    simulation_report.write_and_print(str(len(L_g_tempo))+' / '+str(N_grain)+' grains have been created\n','\n'+str(len(L_g_tempo))+' / '+str(N_grain)+' grains have been created\n')

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

def DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, multi_generation, simulation_report):
    """
    Loading the granular system.

        Input :
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
            a smaple dictionnary (a dict)
            a sollicitations dictionnary (a dict)
            a Boolean to determine if multiple generations are considered (a Boolean)
            a simultion report (a report)
        Output :
            Nothing, but initial condition dictionnary is updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    dt_DEM = dict_ic['dt_DEM_IC']
    i_DEM_stop = dict_ic['i_DEM_stop_IC']
    i_DEM = dict_ic['i_DEM_IC']
    Ecin_ratio_IC = dict_ic['Ecin_ratio_IC']
    i_print_plot_IC = dict_ic['i_print_plot_IC']
    factor_neighborhood_IC = dict_ic['factor_neighborhood_IC']
    if multi_generation :
        i_update_neighbouroods = dict_ic['i_update_neighborhoods_com']
        y_min = dict_sample['y_box_min']
    else :
        i_update_neighbouroods = dict_ic['i_update_neighborhoods_gen']
        y_min = dict_sample['y_box_min_ic']
    x_min = dict_sample['x_box_min']
    x_max = dict_sample['x_box_max']
    y_max = dict_sample['y_box_max']
    Forcev_target = dict_sollicitations['Vertical_Confinement_Force']
    gravity = dict_sollicitations['gravity']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    i_DEM_0 = i_DEM
    DEM_loop_statut = True

    #Initialisation
    dict_ic['L_contact'] = []
    dict_ic['L_contact_ij'] = []
    dict_ic['L_contact_gw'] = []
    dict_ic['L_contact_gw_ij'] = []
    dict_ic['id_contact'] = 0

    #trackers and stop conditions
    Force_tracker = []
    Force_stop = 0
    Ecin_tracker = []
    Ecin_stop = 0
    Ymax_tracker = []
    Ymax_stop = 0
    for grain in L_g_tempo:
        Force_stop = Force_stop + 0.5*grain.mass*gravity
        Ecin_stop = Ecin_stop + 0.5*grain.mass*(Ecin_ratio_IC*grain.r_max/dt_DEM)**2

    while DEM_loop_statut :

        i_DEM = i_DEM + 1

        #Contact detection
        if (i_DEM-i_DEM_0-1) % i_update_neighbouroods  == 0:
            Update_Neighbouroods(dict_ic)
        Grains_Polyhedral_contact_Neighbouroods(dict_ic,dict_material)
        # Detection of contacts between grain and walls
        if (i_DEM-i_DEM_0-1) % i_update_neighbouroods  == 0:
            wall_neighborhood = Update_wall_Neighborhoods(L_g_tempo,factor_neighborhood_IC,x_min,x_max,y_min,y_max)
        Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood,x_min,x_max,y_min,y_max, dict_ic, dict_material)

        #Sollicitation computation
        for grain in dict_ic['L_g_tempo']:
             grain.init_F_control(gravity)
        for contact in  dict_ic['L_contact']+dict_ic['L_contact_gw']:
            contact.normal()
            contact.tangential(dt_DEM)

        #Move grains
        for grain in dict_ic['L_g_tempo']:
            grain.euler_semi_implicite(dt_DEM,100*Ecin_ratio_IC)

        #check if some grains are outside of the study box
        L_ig_to_delete = []
        for id_grain in range(len(dict_ic['L_g_tempo'])):
            if dict_ic['L_g_tempo'][id_grain].center[0] < x_min :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[0] > x_max :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[1] < y_min :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[1] > y_max :
                L_ig_to_delete.append(id_grain)
        L_ig_to_delete.reverse()
        for id_grain in L_ig_to_delete:
            simulation_report.write_and_print('Grain '+str(dict_ic['L_g_tempo'][id_grain].id)+' has been deleted because it is out of the box\n','Grain '+str(dict_ic['L_g_tempo'][id_grain].id)+' has been deleted because it is out of the box')
            dict_ic['L_g_tempo'].pop(id_grain)

        #Control the y_max to have the pressure target
        y_max, Fv = Control_y_max_NR(y_max,Forcev_target,dict_ic['L_contact_gw'],dict_ic['L_g_tempo'])

        #Tracker
        F = F_total(dict_ic['L_g_tempo'])
        Ecin = E_cin_total(dict_ic['L_g_tempo'])
        Force_tracker.append(F)
        Ecin_tracker.append(Ecin)
        Ymax_tracker.append(y_max)

        if i_DEM % i_print_plot_IC ==0:
            if gravity > 0 :
                print('i_DEM',i_DEM,'and Ecin',int(100*Ecin/Ecin_stop),'% and Force',int(100*F/Force_stop),'% and Confinement',int(100*Fv/Forcev_target),'%')
            else :
                print('i_DEM',i_DEM,'and Ecin',int(100*Ecin/Ecin_stop),'% and Confinement',int(100*Fv/Forcev_target),'%')
            if dict_ic['Debug_DEM']:
                Plot_Config_Loaded(dict_ic['L_g_tempo'],x_min,x_max,y_min,y_max,i_DEM)

        #Check stop conditions for DEM
        if i_DEM >= i_DEM_stop + i_DEM_0:
             DEM_loop_statut = False
        if gravity > 0:
            if Ecin < Ecin_stop and F < Force_stop and (0.95*Forcev_target<Fv and Fv<1.05*Forcev_target):
                  DEM_loop_statut = False
        else:
            if Ecin < Ecin_stop and i_DEM >= i_DEM_stop*0.1 + i_DEM_0 and (0.95*Forcev_target<Fv and Fv<1.05*Forcev_target):
                DEM_loop_statut = False
        if dict_ic['L_g_tempo'] == []:
            DEM_loop_statut = False

    #Update dict
    dict_ic['L_g_tempo'] = L_g_tempo
    L_L_g_tempo = dict_ic['L_L_g_tempo']
    L_L_g_tempo.append(L_g_tempo.copy())
    dict_ic['L_L_g_tempo'] = L_L_g_tempo
    dict_ic['y_box_max'] = y_max
    dict_ic['i_DEM_IC'] = i_DEM

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

def Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, id_generation, simulation_report):
    """
    Generate the grains.

    A position is tried. This position must not create overlap with already creared temporary grain. If there is no overlap, a temporary grai nis created.

        Input :
            an initial condition dictionnary (a dict)
            a geometry dictionnary (a dict)
            a sample dictionnary (a dict)
            a material dictionnary (a dict)
            a generation id (a int)
            a simulation report (a report)
        Output :
            Nothing, but initial configuration dictionnary is updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    N_test_max = dict_ic['N_test_max']
    if id_generation == 1:
        L_n_grain_radius = dict_ic['L_n_grain_radius_try_one']
        L_n_grain_dimension = dict_ic['L_n_grain_dimension_try_one']
    else :
        L_n_grain_radius = dict_ic['L_n_grain_radius']
        L_n_grain_dimension = dict_ic['L_n_grain_dimension']
    L_n_grain_radius_done = dict_ic['L_n_grain_radius_done']
    L_radius = dict_geometry['L_R']
    L_n_grain_dimension_done = dict_ic['L_n_grain_dimension_done']
    L_dimension = dict_geometry['L_Dimension']
    x_min = dict_sample['x_box_min']
    x_max = dict_sample['x_box_max']
    y_min = dict_sample['y_box_min_ic']
    dy_creation = dict_sample['dy_creation']
    y_max = y_min + dy_creation
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #Parameters for the method
    n_not_created = 0

    for i in range(len(L_radius)):
        radius = L_radius[i]
        n_grain = L_n_grain_radius[i]
        n_grain_done = L_n_grain_radius_done[i]
        last_id_grain_created = dict_ic['last_id']
        for id_grain in range(last_id_grain_created, last_id_grain_created + n_grain - n_grain_done):
            i_test = 0
            grain_created = False
            while (not grain_created) and i_test < N_test_max:
                i_test = i_test + 1
                center = np.array([random.uniform(x_min+1.1*radius,x_max-1.1*radius),random.uniform(y_min+1.1*radius,y_max)])
                g_tempo = Grain_Tempo(id_grain-n_not_created,center,radius,dict_material,'Disk')
                grain_created = True
                for grain in L_g_tempo:
                    if Grains_Polyhedral_contact_f(g_tempo,grain):
                        grain_created = False
            if i_test == N_test_max and not grain_created:
                n_not_created = n_not_created + 1
                simulation_report.write_and_print('Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries\n','Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries')
            else :
                L_g_tempo.append(g_tempo)
                L_n_grain_radius_done[i] = L_n_grain_radius_done[i] + 1
                dict_ic['last_id'] = dict_ic['last_id'] + 1
    for i in range(len(L_dimension)):
        dimension = L_dimension[i]
        n_grain = L_n_grain_dimension[i]
        n_grain_done = L_n_grain_dimension_done[i]
        last_id_grain_created = dict_ic['last_id']
        for id_grain in range(last_id_grain_created, last_id_grain_created + n_grain - n_grain_done):
            i_test = 0
            grain_created = False
            while (not grain_created) and i_test < N_test_max:
                i_test = i_test + 1
                center = np.array([random.uniform(x_min+1.1*dimension/2,x_max-1.1*dimension/2),random.uniform(y_min+1.1*dimension/2,y_max)])
                g_tempo = Grain_Tempo(id_grain-n_not_created,center,dimension,dict_material,'Square')
                grain_created = True
                for grain in L_g_tempo:
                    if Grains_Polyhedral_contact_f(g_tempo,grain):
                        grain_created = False
            if i_test == N_test_max and not grain_created:
                n_not_created = n_not_created + 1
                simulation_report.write_and_print('Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries\n','Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries')
            else :
                L_g_tempo.append(g_tempo)
                L_n_grain_dimension_done[i] = L_n_grain_dimension_done[i] + 1
                dict_ic['last_id'] = dict_ic['last_id'] + 1

    #Update dict
    dict_ic['L_g_tempo'] = L_g_tempo
    dict_ic['L_n_grain_dimension_done'] = L_n_grain_dimension_done
    dict_ic['L_n_grain_radius_done'] = L_n_grain_radius_done

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

def Grains_Polyhedral_contact_f(g1,g2):
  """
  Detect the contact grain-grain.

    Input :
        two temporary grains (two grain_tempos)
    Output :
        a Boolean, True if there is contaxct between the twwo grains (a Boolean)
  """
  if np.linalg.norm(g1.center-g2.center) < 1.5*(g1.r_max+g2.r_max):

      #looking for the nearest nodes
      d_virtual = max(g1.r_max,g2.r_max)
      ij_min = [0,0]
      d_ij_min = 100*d_virtual #Large
      for i in range(len(g1.l_border[:-1])):
        for j in range(len(g2.l_border[:-1])):
            d_ij = np.linalg.norm(g2.l_border[:-1][j]-g1.l_border[:-1][i]+d_virtual*(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
            if d_ij < d_ij_min :
                d_ij_min = d_ij
                ij_min = [i,j]

      d_ij_min = np.dot(g2.l_border[:-1][ij_min[1]]-g1.l_border[:-1][ij_min[0]],-(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
      return d_ij_min > 0

  else:
    return False

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

def E_cin_total(L_g):
    """
    Compute total kinetic energy.

        Input :
            a list of temporary grains (a list)
        Output :
            the total kinetic energy (a float)
    """
    Ecin = 0
    for grain in L_g:
        Ecin = Ecin + 1/2*grain.mass*np.dot(grain.v,grain.v)
    return Ecin

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

def F_total(L_g):
    """
    Compute total force applied on grains in the sample.

        Input :
            a list of temporary grains (a list)
        Output :
            the total force applied (a float)
    """
    F = 0
    for grain in L_g:
        F = F + np.linalg.norm([grain.fx, grain.fy])
    return F

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

def Control_y_max_NR(y_max,Force_target,L_contact_gw,L_g):
    """
    Control the upper wall to apply force.

    A Newton-Raphson method is applied to verify the confinement.
        Input :
            a coordinate of the upper wall (a float)
            a confinement value (a float)
            a list of contact grain - wall (a list)
            a list of temporary grain (a list)
        Output :
            the coordinate of the upper wall (a float)
            a force applied on the upper wall before control (a float)
    """
    F = 0
    overlap_L = []
    k_L = []
    for contact in L_contact_gw:
        if contact.nature == 'gwy_max':
            F = F + contact.Fwg_n
            overlap_L.append(contact.overlap)
            k_L.append(contact.k)
            #compute force applied, save contact overlap and spring

    if overlap_L != []:
        i_NR = 0
        dy = 0
        ite_criteria = True
        #control the upper wall
        if -0.01*Force_target<error_on_ymax_f(dy,overlap_L,k_L,Force_target) and error_on_ymax_f(dy,overlap_L,k_L,Force_target)<0.01*Force_target:
            ite_criteria = False
        while ite_criteria :
            i_NR = i_NR + 1
            dy = dy - error_on_ymax_f(dy,overlap_L,k_L,Force_target)/error_on_ymax_df(dy,overlap_L,k_L)
            if i_NR > 100: #Maximum try
                ite_criteria = False
            if -0.01*Force_target<error_on_ymax_f(dy,overlap_L,k_L,Force_target) and error_on_ymax_f(dy,overlap_L,k_L,Force_target)<0.01*Force_target:
                ite_criteria = False
        y_max = y_max + dy

    else :
        #if there is no contact with the upper wall, the wall is reset
        y_max = Reset_y_max(L_g,Force_target)

    for contact in L_contact_gw:
        if contact.nature == 'gwy_max':
            #reactualisation
            contact.limit = y_max

    return y_max, F

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

def error_on_ymax_f(dy,overlap_L,k_L,Force_target) :
    """
    Compute the function f to control the upper wall. It is the difference between the force applied and the target value.

        Input :
            an increment of the upper wall position (a float)
            a list of overlap for contact between temporary grain and upper wall (a list)
            a list of spring for contact between temporary grain and upper wall (a list)
            a confinement force (a float)
        Output :
            the difference between the force applied and the confinement (a float)
    """
    f = Force_target
    for i in range(len(overlap_L)):
        f = f - k_L[i]*(max(overlap_L[i]-dy,0))**(3/2)
    return f

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

def error_on_ymax_df(dy,overlap_L,k_L) :
    """
    Compute the derivative function df to control the upper wall (error_on_ymax_f()).

        Input :
            an increment of the upper wall position (a float)
            a list of overlap for contact between temporary grain and upper wall (a list)
            a list of spring for contact between temporary grain and upper wall (a list)
        Output :
            the derivative of error_on_ymax_f() (a float)
    """
    df = 0
    for i in range(len(overlap_L)):
        df = df + 3/2*k_L[i]*(max(overlap_L[i]-dy,0))**(1/2)
    return df

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

def Reset_y_max(L_g,Force):
    """
    The upper wall is located as a single contact verify the target value.

        Input :
            the list of temporary grains (a list)
            the confinement force (a float)
        Output :
            the upper wall position (a float)
    """
    y_max = None
    id_grain_max = None
    for id_grain in range(len(L_g)):
        grain = L_g[id_grain]
        y_max_grain = max(grain.l_border_y)

        if y_max != None and y_max_grain > y_max:
            y_max = y_max_grain
            id_grain_max = id_grain
        elif y_max == None:
            y_max = y_max_grain
            id_grain_max = id_grain

    factor = 5
    k = factor*4/3*L_g[id_grain_max].y/(1-L_g[id_grain_max].nu*L_g[id_grain_max].nu)*math.sqrt(L_g[id_grain_max].r_max)
    y_max = y_max - (Force/k)**(2/3)

    return y_max

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

def Grains_Polyhedral_contact_Neighborhoods_f(g1,g2):
  """
  Detect contact grain - grain.

    Input :
        two temporary grains (two grain_tempo)
    Output :
        a Boolean, True if there is a contact (a Boolean)
  """
  #looking for the nearest nodes
  d_virtual = max(g1.r_max,g2.r_max)
  ij_min = [0,0]
  d_ij_min = 100*d_virtual #Large
  for i in range(len(g1.l_border[:-1])):
    for j in range(len(g2.l_border[:-1])):
        d_ij = np.linalg.norm(g2.l_border[:-1][j]-g1.l_border[:-1][i]+d_virtual*(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
        if d_ij < d_ij_min :
            d_ij_min = d_ij
            ij_min = [i,j]

  d_ij_min = np.dot(g2.l_border[:-1][ij_min[1]]-g1.l_border[:-1][ij_min[0]],-(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
  return d_ij_min > 0

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

def Update_Neighbouroods(dict_ic):
    """
    Determine a neighborhood for each grain.

    This function is called every x time step. The contact is determined by Grains_Polyhedral_contact_Neighbouroods().
    Notice that if there is a potential contact between grain_i and grain_j, grain_i is not in the neighborhood of grain_j.
    Whereas grain_j is in the neighbourood of grain_i. With i_grain < j_grain.

        Input :
            an initial condition dictionnary (a dict)
        Output :
            Nothing, but the neighborhood of the temporary grains is updated
    """
    for i_grain in range(len(dict_ic['L_g_tempo'])-1) :
        neighbourood = []
        for j_grain in range(i_grain+1,len(dict_ic['L_g_tempo'])):
            if np.linalg.norm(dict_ic['L_g_tempo'][i_grain].center-dict_ic['L_g_tempo'][j_grain].center) < dict_ic['factor_neighborhood_IC']*(dict_ic['L_g_tempo'][i_grain].r_max+dict_ic['L_g_tempo'][j_grain].r_max):
                neighbourood.append(dict_ic['L_g_tempo'][j_grain])
        dict_ic['L_g_tempo'][i_grain].neighbourood = neighbourood

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

def Grains_Polyhedral_contact_Neighbouroods(dict_ic,dict_material):
    """
    Detect contact between a grain and grains from its neighbourood.

    The neighbourood is updated with Update_Neighbouroods().

        Input :
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
        Output :
            Nothing, but the initial condition dictionnary is updated with grain - grain contacts
    """
    for i_grain in range(len(dict_ic['L_g_tempo'])-1) :
        grain_i = dict_ic['L_g_tempo'][i_grain]
        for neighbour in dict_ic['L_g_tempo'][i_grain].neighbourood:
            j_grain = neighbour.id
            grain_j = neighbour
            if Grains_Polyhedral_contact_Neighborhoods_f(grain_i,grain_j):
                if (i_grain,j_grain) not in dict_ic['L_contact_ij']:  #contact not detected previously
                   #creation of contact
                   dict_ic['L_contact_ij'].append((i_grain,j_grain))
                   dict_ic['L_contact'].append(Contact_Tempo(dict_ic['id_contact'], grain_i, grain_j, dict_material))
                   dict_ic['id_contact'] = dict_ic['id_contact'] + 1

            else :
                if (i_grain,j_grain) in dict_ic['L_contact_ij'] : #contact detected previously is not anymore
                       dict_ic['L_contact'].pop(dict_ic['L_contact_ij'].index((i_grain,j_grain)))
                       dict_ic['L_contact_ij'].remove((i_grain,j_grain))

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

def Update_wall_Neighborhoods(L_g_tempo,factor_neighborhood_IC,x_min,x_max,y_min,y_max):
    """
    Determine a neighborhood for wall.

    This function is called every x time step. The grain - wall contact is determined by Grains_Polyhedral_Wall_contact_Neighborhood().
    A factor determines the size of the neighborhood window.

        Input :
            a list of temporary grains (a list)
            a factor to determine the neighborhood window (a float)
            the coordinates of the left, right, lower, upper walls (four floats)
        Output :
            a list of temporary grains in the neighborhood of the walls (a list)
    """
    wall_neighborhood = []
    for grain in L_g_tempo:

        p_x_min = min(grain.l_border_x)
        p_x_max = max(grain.l_border_x)
        p_y_min = min(grain.l_border_y)
        p_y_max = max(grain.l_border_y)

        #grain-wall x_min
        if abs(p_x_min-x_min) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)
        #grain-wall x_max
        if abs(p_x_max-x_max) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)
        #grain-wall y_min
        if abs(p_y_min-y_min) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)
        #grain-wall y_max
        if abs(p_y_max-y_max) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)

    return wall_neighborhood

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

def Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood,x_box_min,x_box_max,y_box_min,y_box_max, dict_ic, dict_material):
  """
  Detect contact grain in the neighborhood of the wall and the wall.

  The neighbourood is updated with Update_wall_Neighborhoods(). An iteration over the grains in the wall neighborhood is done. A comparison is done with the coordinates of the wall to determine if there is a contact.

        Input :
            a walls neighborhood (a list)
            the coordinates of the walls (four floats)
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
        Output :
            Nothing, but the initial condition dictionnary is updated with the contact grain - walls.
  """
  #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
  #load data needed
  L_ij_contact_gw = dict_ic['L_contact_gw_ij']
  L_contact_gw = dict_ic['L_contact_gw']
  id_contact = dict_ic['id_contact']
  #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-

  for grain in wall_neighborhood:

      p_x_min = min(grain.l_border_x)
      p_x_max = max(grain.l_border_x)
      p_y_min = min(grain.l_border_y)
      p_y_max = max(grain.l_border_y)

      #grain-wall x_min
      if p_x_min < x_box_min and (grain.id,-1) not in L_ij_contact_gw:
          overlap = x_box_min - p_x_min
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwx_min', x_box_min, overlap))
          L_ij_contact_gw.append((grain.id,-1))
          id_contact = id_contact + 1
      elif p_x_min < x_box_min and (grain.id,-1) in L_ij_contact_gw:
          overlap = x_box_min - p_x_min
          L_contact_gw[L_ij_contact_gw.index((grain.id,-1))].update_overlap(overlap)
      elif p_x_min > x_box_min and (grain.id,-1) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-1))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)
      #grain-wall x_max
      if p_x_max > x_box_max and (grain.id,-2) not in L_ij_contact_gw:
          overlap = p_x_max - x_box_max
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwx_max', x_box_max, overlap))
          L_ij_contact_gw.append((grain.id,-2))
          id_contact = id_contact + 1
      elif p_x_max > x_box_max and (grain.id,-2) in L_ij_contact_gw:
          overlap = p_x_max - x_box_max
          L_contact_gw[L_ij_contact_gw.index((grain.id,-2))].update_overlap(overlap)
      elif p_x_max < x_box_max and (grain.id,-2) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-2))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)
      #grain-wall y_min
      if p_y_min < y_box_min and (grain.id,-3) not in L_ij_contact_gw:
          overlap = y_box_min - p_y_min
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwy_min', y_box_min, overlap))
          L_ij_contact_gw.append((grain.id,-3))
          id_contact = id_contact + 1
      elif p_y_min < y_box_min and (grain.id,-3) in L_ij_contact_gw:
          overlap = y_box_min - p_y_min
          L_contact_gw[L_ij_contact_gw.index((grain.id,-3))].update_overlap(overlap)
      elif p_y_min > y_box_min and (grain.id,-3) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-3))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)
      #grain-wall y_max
      if p_y_max > y_box_max and (grain.id,-4) not in L_ij_contact_gw:
          overlap = p_y_max - y_box_max
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwy_max', y_box_max, overlap))
          L_ij_contact_gw.append((grain.id,-4))
          id_contact = id_contact + 1
      elif p_y_max > y_box_max and (grain.id,-4) in L_ij_contact_gw:
          overlap = p_y_max - y_box_max
          L_contact_gw[L_ij_contact_gw.index((grain.id,-4))].update_overlap(overlap)
      elif p_y_max < y_box_max and (grain.id,-4) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-4))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)

      #Update dict
      dict_ic['L_contact_gw_ij'] = L_ij_contact_gw
      dict_ic['L_contact_gw'] = L_contact_gw
      dict_ic['id_contact'] = id_contact

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

def Plot_Config_Loaded(L_g,x_min,x_max,y_min,y_max,i):
    """
    Plot loaded configuration.

        Input :
            a list of temporary grain (a list)
            the coordinates of the walls (four floats)
            an iteration (a int)
        Output :
            Nothing, but a .png file is generated (a file)
    """
    plt.figure(1,figsize=(16,9))
    L_x = []
    L_y = []
    L_u = []
    L_v = []
    for grain in L_g:
        plt.plot(grain.l_border_x,grain.l_border_y,'k')
        plt.plot(grain.center[0],grain.center[1],'xk')
        L_x.append(grain.center[0])
        L_y.append(grain.center[1])
        L_u.append(grain.fx)
        L_v.append(grain.fy)
    plt.plot([x_min,x_min,x_max,x_max,x_min],[y_max,y_min,y_min,y_max,y_max],'k')
    plt.axis('equal')
    plt.savefig('Debug/DEM_ite/Init/Config_Loaded_'+str(i)+'.png')
    plt.close(1)

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

def Plot_Config_Loaded_End(L_g,x_min,x_max,y_min,y_max):
    """
    Plot loaded configuration at the end of the initial configuration.

        Input :
            a list of temporary grain (a list)
            the coordinates of the walls (four floats)
            an iteration (a int)
        Output :
            Nothing, but a .png file is generated (a file)
    """
    plt.figure(1,figsize=(16,9))
    L_x = []
    L_y = []
    L_u = []
    L_v = []
    for grain in L_g:
        plt.plot(grain.l_border_x,grain.l_border_y,'k')
        plt.plot(grain.center[0],grain.center[1],'xk')
        L_x.append(grain.center[0])
        L_y.append(grain.center[1])
        L_u.append(grain.fx)
        L_v.append(grain.fy)
    plt.plot([x_min,x_min,x_max,x_max,x_min],[y_max,y_min,y_min,y_max,y_max],'k')
    plt.axis('equal')
    plt.savefig('Debug/ConfigLoaded.png')
    plt.close(1)

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

def From_LG_tempo_to_usable(dict_ic, dict_geometry, dict_material, dict_sample):
    """
    Create a rea lgrain from a temporary grain.

    The phase variable is built. The distance between the point of the mesh and the particle center determines the value of the variable.
    A cosine profile is applied inside the interface.

        Input :
            an initial condition dictionnary (a dict)
            a geometry dictionnary (a dict)
            a material dictionnary (a dict)
            a sample dictionnary (a dict)
        Output :
            Nothing, but the sample dictionnary is updated with the list of real grains
    """
    L_g = []
    for grain_tempo in dict_ic['L_g_tempo']:

        dict_ic_to_real = {
        'Id' : grain_tempo.id,
        'Type' : grain_tempo.type,
        'Y' : grain_tempo.y,
        'Nu' : grain_tempo.nu,
        'Rho_surf' : grain_tempo.rho_surf,
        'Center' : grain_tempo.center,
        'L_border' : grain_tempo.l_border,
        'L_border_x' : grain_tempo.l_border_x,
        'L_border_y' : grain_tempo.l_border_y,
        'L_r' : grain_tempo.l_r,
        'L_theta_r' : grain_tempo.l_theta_r,
        'R_min' : min(grain_tempo.l_r),
        'R_max' : max(grain_tempo.l_r),
        'R_mean' : np.mean(grain_tempo.l_r),
        'Surface' : grain_tempo.surface,
        'Mass' : grain_tempo.mass,
        'Inertia' : grain_tempo.inertia
        }
        #create real grain
        L_g.append(Grain.Grain(dict_ic_to_real))

    #Add element in dict
    dict_sample['L_g'] = L_g

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

def extract_vertices(g, angle_g_to_other_g) :
    """
    Extract a list of indices of vertices inside a angular window.

        Input :
            a grain (a grain)
            an angle (a float)
        Output :
            a list of indices (a list)
    """
    dtheta = 3*2*math.pi/len(g.l_border)
    angle_minus = angle_g_to_other_g - dtheta/2
    if angle_minus < 0 :
        angle_minus = angle_minus + 2*math.pi
    angle_plus  = angle_g_to_other_g + dtheta/2
    if 2*math.pi <= angle_plus :
        angle_plus = angle_plus - 2*math.pi
    i_minus = find_value_in_list(g.l_theta_r.copy(), angle_minus)
    i_plus = i_minus + find_value_in_list(g.l_theta_r[i_minus:].copy() + g.l_theta_r[:i_minus].copy(), angle_plus)

    L_i_vertices = []
    for i in range(i_minus, i_plus+1):
        if i < len(g.l_theta_r):
            L_i_vertices.append(i)
        else :
            L_i_vertices.append(i-len(g.l_theta_r))

    return L_i_vertices

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

def find_value_in_list(List_to_search, value_to_find) :
    """
    Extract the index of the nearest value from a target in a list.

        Input :
            a list of float (a list)
            a target (a float)
        Output :
            an index (an int)
    """
    L_search = list(abs(np.array(List_to_search)-value_to_find))
    return L_search.index(min(L_search))

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

Functions

def Control_y_max_NR(y_max, Force_target, L_contact_gw, L_g)

Control the upper wall to apply force.

A Newton-Raphson method is applied to verify the confinement. Input : a coordinate of the upper wall (a float) a confinement value (a float) a list of contact grain - wall (a list) a list of temporary grain (a list) Output : the coordinate of the upper wall (a float) a force applied on the upper wall before control (a float)

Expand source code
def Control_y_max_NR(y_max,Force_target,L_contact_gw,L_g):
    """
    Control the upper wall to apply force.

    A Newton-Raphson method is applied to verify the confinement.
        Input :
            a coordinate of the upper wall (a float)
            a confinement value (a float)
            a list of contact grain - wall (a list)
            a list of temporary grain (a list)
        Output :
            the coordinate of the upper wall (a float)
            a force applied on the upper wall before control (a float)
    """
    F = 0
    overlap_L = []
    k_L = []
    for contact in L_contact_gw:
        if contact.nature == 'gwy_max':
            F = F + contact.Fwg_n
            overlap_L.append(contact.overlap)
            k_L.append(contact.k)
            #compute force applied, save contact overlap and spring

    if overlap_L != []:
        i_NR = 0
        dy = 0
        ite_criteria = True
        #control the upper wall
        if -0.01*Force_target<error_on_ymax_f(dy,overlap_L,k_L,Force_target) and error_on_ymax_f(dy,overlap_L,k_L,Force_target)<0.01*Force_target:
            ite_criteria = False
        while ite_criteria :
            i_NR = i_NR + 1
            dy = dy - error_on_ymax_f(dy,overlap_L,k_L,Force_target)/error_on_ymax_df(dy,overlap_L,k_L)
            if i_NR > 100: #Maximum try
                ite_criteria = False
            if -0.01*Force_target<error_on_ymax_f(dy,overlap_L,k_L,Force_target) and error_on_ymax_f(dy,overlap_L,k_L,Force_target)<0.01*Force_target:
                ite_criteria = False
        y_max = y_max + dy

    else :
        #if there is no contact with the upper wall, the wall is reset
        y_max = Reset_y_max(L_g,Force_target)

    for contact in L_contact_gw:
        if contact.nature == 'gwy_max':
            #reactualisation
            contact.limit = y_max

    return y_max, F
def Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, id_generation, simulation_report)

Generate the grains.

A position is tried. This position must not create overlap with already creared temporary grain. If there is no overlap, a temporary grai nis created.

Input :
    an initial condition dictionnary (a dict)
    a geometry dictionnary (a dict)
    a sample dictionnary (a dict)
    a material dictionnary (a dict)
    a generation id (a int)
    a simulation report (a report)
Output :
    Nothing, but initial configuration dictionnary is updated
Expand source code
def Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, id_generation, simulation_report):
    """
    Generate the grains.

    A position is tried. This position must not create overlap with already creared temporary grain. If there is no overlap, a temporary grai nis created.

        Input :
            an initial condition dictionnary (a dict)
            a geometry dictionnary (a dict)
            a sample dictionnary (a dict)
            a material dictionnary (a dict)
            a generation id (a int)
            a simulation report (a report)
        Output :
            Nothing, but initial configuration dictionnary is updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    N_test_max = dict_ic['N_test_max']
    if id_generation == 1:
        L_n_grain_radius = dict_ic['L_n_grain_radius_try_one']
        L_n_grain_dimension = dict_ic['L_n_grain_dimension_try_one']
    else :
        L_n_grain_radius = dict_ic['L_n_grain_radius']
        L_n_grain_dimension = dict_ic['L_n_grain_dimension']
    L_n_grain_radius_done = dict_ic['L_n_grain_radius_done']
    L_radius = dict_geometry['L_R']
    L_n_grain_dimension_done = dict_ic['L_n_grain_dimension_done']
    L_dimension = dict_geometry['L_Dimension']
    x_min = dict_sample['x_box_min']
    x_max = dict_sample['x_box_max']
    y_min = dict_sample['y_box_min_ic']
    dy_creation = dict_sample['dy_creation']
    y_max = y_min + dy_creation
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #Parameters for the method
    n_not_created = 0

    for i in range(len(L_radius)):
        radius = L_radius[i]
        n_grain = L_n_grain_radius[i]
        n_grain_done = L_n_grain_radius_done[i]
        last_id_grain_created = dict_ic['last_id']
        for id_grain in range(last_id_grain_created, last_id_grain_created + n_grain - n_grain_done):
            i_test = 0
            grain_created = False
            while (not grain_created) and i_test < N_test_max:
                i_test = i_test + 1
                center = np.array([random.uniform(x_min+1.1*radius,x_max-1.1*radius),random.uniform(y_min+1.1*radius,y_max)])
                g_tempo = Grain_Tempo(id_grain-n_not_created,center,radius,dict_material,'Disk')
                grain_created = True
                for grain in L_g_tempo:
                    if Grains_Polyhedral_contact_f(g_tempo,grain):
                        grain_created = False
            if i_test == N_test_max and not grain_created:
                n_not_created = n_not_created + 1
                simulation_report.write_and_print('Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries\n','Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries')
            else :
                L_g_tempo.append(g_tempo)
                L_n_grain_radius_done[i] = L_n_grain_radius_done[i] + 1
                dict_ic['last_id'] = dict_ic['last_id'] + 1
    for i in range(len(L_dimension)):
        dimension = L_dimension[i]
        n_grain = L_n_grain_dimension[i]
        n_grain_done = L_n_grain_dimension_done[i]
        last_id_grain_created = dict_ic['last_id']
        for id_grain in range(last_id_grain_created, last_id_grain_created + n_grain - n_grain_done):
            i_test = 0
            grain_created = False
            while (not grain_created) and i_test < N_test_max:
                i_test = i_test + 1
                center = np.array([random.uniform(x_min+1.1*dimension/2,x_max-1.1*dimension/2),random.uniform(y_min+1.1*dimension/2,y_max)])
                g_tempo = Grain_Tempo(id_grain-n_not_created,center,dimension,dict_material,'Square')
                grain_created = True
                for grain in L_g_tempo:
                    if Grains_Polyhedral_contact_f(g_tempo,grain):
                        grain_created = False
            if i_test == N_test_max and not grain_created:
                n_not_created = n_not_created + 1
                simulation_report.write_and_print('Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries\n','Grain '+str(id_grain)+' has not been created after '+str(i_test)+' tries')
            else :
                L_g_tempo.append(g_tempo)
                L_n_grain_dimension_done[i] = L_n_grain_dimension_done[i] + 1
                dict_ic['last_id'] = dict_ic['last_id'] + 1

    #Update dict
    dict_ic['L_g_tempo'] = L_g_tempo
    dict_ic['L_n_grain_dimension_done'] = L_n_grain_dimension_done
    dict_ic['L_n_grain_radius_done'] = L_n_grain_radius_done
def DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, multi_generation, simulation_report)

Loading the granular system.

Input :
    an initial condition dictionnary (a dict)
    a material dictionnary (a dict)
    a smaple dictionnary (a dict)
    a sollicitations dictionnary (a dict)
    a Boolean to determine if multiple generations are considered (a Boolean)
    a simultion report (a report)
Output :
    Nothing, but initial condition dictionnary is updated
Expand source code
def DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, multi_generation, simulation_report):
    """
    Loading the granular system.

        Input :
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
            a smaple dictionnary (a dict)
            a sollicitations dictionnary (a dict)
            a Boolean to determine if multiple generations are considered (a Boolean)
            a simultion report (a report)
        Output :
            Nothing, but initial condition dictionnary is updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    dt_DEM = dict_ic['dt_DEM_IC']
    i_DEM_stop = dict_ic['i_DEM_stop_IC']
    i_DEM = dict_ic['i_DEM_IC']
    Ecin_ratio_IC = dict_ic['Ecin_ratio_IC']
    i_print_plot_IC = dict_ic['i_print_plot_IC']
    factor_neighborhood_IC = dict_ic['factor_neighborhood_IC']
    if multi_generation :
        i_update_neighbouroods = dict_ic['i_update_neighborhoods_com']
        y_min = dict_sample['y_box_min']
    else :
        i_update_neighbouroods = dict_ic['i_update_neighborhoods_gen']
        y_min = dict_sample['y_box_min_ic']
    x_min = dict_sample['x_box_min']
    x_max = dict_sample['x_box_max']
    y_max = dict_sample['y_box_max']
    Forcev_target = dict_sollicitations['Vertical_Confinement_Force']
    gravity = dict_sollicitations['gravity']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    i_DEM_0 = i_DEM
    DEM_loop_statut = True

    #Initialisation
    dict_ic['L_contact'] = []
    dict_ic['L_contact_ij'] = []
    dict_ic['L_contact_gw'] = []
    dict_ic['L_contact_gw_ij'] = []
    dict_ic['id_contact'] = 0

    #trackers and stop conditions
    Force_tracker = []
    Force_stop = 0
    Ecin_tracker = []
    Ecin_stop = 0
    Ymax_tracker = []
    Ymax_stop = 0
    for grain in L_g_tempo:
        Force_stop = Force_stop + 0.5*grain.mass*gravity
        Ecin_stop = Ecin_stop + 0.5*grain.mass*(Ecin_ratio_IC*grain.r_max/dt_DEM)**2

    while DEM_loop_statut :

        i_DEM = i_DEM + 1

        #Contact detection
        if (i_DEM-i_DEM_0-1) % i_update_neighbouroods  == 0:
            Update_Neighbouroods(dict_ic)
        Grains_Polyhedral_contact_Neighbouroods(dict_ic,dict_material)
        # Detection of contacts between grain and walls
        if (i_DEM-i_DEM_0-1) % i_update_neighbouroods  == 0:
            wall_neighborhood = Update_wall_Neighborhoods(L_g_tempo,factor_neighborhood_IC,x_min,x_max,y_min,y_max)
        Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood,x_min,x_max,y_min,y_max, dict_ic, dict_material)

        #Sollicitation computation
        for grain in dict_ic['L_g_tempo']:
             grain.init_F_control(gravity)
        for contact in  dict_ic['L_contact']+dict_ic['L_contact_gw']:
            contact.normal()
            contact.tangential(dt_DEM)

        #Move grains
        for grain in dict_ic['L_g_tempo']:
            grain.euler_semi_implicite(dt_DEM,100*Ecin_ratio_IC)

        #check if some grains are outside of the study box
        L_ig_to_delete = []
        for id_grain in range(len(dict_ic['L_g_tempo'])):
            if dict_ic['L_g_tempo'][id_grain].center[0] < x_min :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[0] > x_max :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[1] < y_min :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[1] > y_max :
                L_ig_to_delete.append(id_grain)
        L_ig_to_delete.reverse()
        for id_grain in L_ig_to_delete:
            simulation_report.write_and_print('Grain '+str(dict_ic['L_g_tempo'][id_grain].id)+' has been deleted because it is out of the box\n','Grain '+str(dict_ic['L_g_tempo'][id_grain].id)+' has been deleted because it is out of the box')
            dict_ic['L_g_tempo'].pop(id_grain)

        #Control the y_max to have the pressure target
        y_max, Fv = Control_y_max_NR(y_max,Forcev_target,dict_ic['L_contact_gw'],dict_ic['L_g_tempo'])

        #Tracker
        F = F_total(dict_ic['L_g_tempo'])
        Ecin = E_cin_total(dict_ic['L_g_tempo'])
        Force_tracker.append(F)
        Ecin_tracker.append(Ecin)
        Ymax_tracker.append(y_max)

        if i_DEM % i_print_plot_IC ==0:
            if gravity > 0 :
                print('i_DEM',i_DEM,'and Ecin',int(100*Ecin/Ecin_stop),'% and Force',int(100*F/Force_stop),'% and Confinement',int(100*Fv/Forcev_target),'%')
            else :
                print('i_DEM',i_DEM,'and Ecin',int(100*Ecin/Ecin_stop),'% and Confinement',int(100*Fv/Forcev_target),'%')
            if dict_ic['Debug_DEM']:
                Plot_Config_Loaded(dict_ic['L_g_tempo'],x_min,x_max,y_min,y_max,i_DEM)

        #Check stop conditions for DEM
        if i_DEM >= i_DEM_stop + i_DEM_0:
             DEM_loop_statut = False
        if gravity > 0:
            if Ecin < Ecin_stop and F < Force_stop and (0.95*Forcev_target<Fv and Fv<1.05*Forcev_target):
                  DEM_loop_statut = False
        else:
            if Ecin < Ecin_stop and i_DEM >= i_DEM_stop*0.1 + i_DEM_0 and (0.95*Forcev_target<Fv and Fv<1.05*Forcev_target):
                DEM_loop_statut = False
        if dict_ic['L_g_tempo'] == []:
            DEM_loop_statut = False

    #Update dict
    dict_ic['L_g_tempo'] = L_g_tempo
    L_L_g_tempo = dict_ic['L_L_g_tempo']
    L_L_g_tempo.append(L_g_tempo.copy())
    dict_ic['L_L_g_tempo'] = L_L_g_tempo
    dict_ic['y_box_max'] = y_max
    dict_ic['i_DEM_IC'] = i_DEM
def E_cin_total(L_g)

Compute total kinetic energy.

Input :
    a list of temporary grains (a list)
Output :
    the total kinetic energy (a float)
Expand source code
def E_cin_total(L_g):
    """
    Compute total kinetic energy.

        Input :
            a list of temporary grains (a list)
        Output :
            the total kinetic energy (a float)
    """
    Ecin = 0
    for grain in L_g:
        Ecin = Ecin + 1/2*grain.mass*np.dot(grain.v,grain.v)
    return Ecin
def F_total(L_g)

Compute total force applied on grains in the sample.

Input :
    a list of temporary grains (a list)
Output :
    the total force applied (a float)
Expand source code
def F_total(L_g):
    """
    Compute total force applied on grains in the sample.

        Input :
            a list of temporary grains (a list)
        Output :
            the total force applied (a float)
    """
    F = 0
    for grain in L_g:
        F = F + np.linalg.norm([grain.fx, grain.fy])
    return F
def From_LG_tempo_to_usable(dict_ic, dict_geometry, dict_material, dict_sample)

Create a rea lgrain from a temporary grain.

The phase variable is built. The distance between the point of the mesh and the particle center determines the value of the variable. A cosine profile is applied inside the interface.

Input :
    an initial condition dictionnary (a dict)
    a geometry dictionnary (a dict)
    a material dictionnary (a dict)
    a sample dictionnary (a dict)
Output :
    Nothing, but the sample dictionnary is updated with the list of real grains
Expand source code
def From_LG_tempo_to_usable(dict_ic, dict_geometry, dict_material, dict_sample):
    """
    Create a rea lgrain from a temporary grain.

    The phase variable is built. The distance between the point of the mesh and the particle center determines the value of the variable.
    A cosine profile is applied inside the interface.

        Input :
            an initial condition dictionnary (a dict)
            a geometry dictionnary (a dict)
            a material dictionnary (a dict)
            a sample dictionnary (a dict)
        Output :
            Nothing, but the sample dictionnary is updated with the list of real grains
    """
    L_g = []
    for grain_tempo in dict_ic['L_g_tempo']:

        dict_ic_to_real = {
        'Id' : grain_tempo.id,
        'Type' : grain_tempo.type,
        'Y' : grain_tempo.y,
        'Nu' : grain_tempo.nu,
        'Rho_surf' : grain_tempo.rho_surf,
        'Center' : grain_tempo.center,
        'L_border' : grain_tempo.l_border,
        'L_border_x' : grain_tempo.l_border_x,
        'L_border_y' : grain_tempo.l_border_y,
        'L_r' : grain_tempo.l_r,
        'L_theta_r' : grain_tempo.l_theta_r,
        'R_min' : min(grain_tempo.l_r),
        'R_max' : max(grain_tempo.l_r),
        'R_mean' : np.mean(grain_tempo.l_r),
        'Surface' : grain_tempo.surface,
        'Mass' : grain_tempo.mass,
        'Inertia' : grain_tempo.inertia
        }
        #create real grain
        L_g.append(Grain.Grain(dict_ic_to_real))

    #Add element in dict
    dict_sample['L_g'] = L_g
def Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood, x_box_min, x_box_max, y_box_min, y_box_max, dict_ic, dict_material)

Detect contact grain in the neighborhood of the wall and the wall.

The neighbourood is updated with Update_wall_Neighborhoods(). An iteration over the grains in the wall neighborhood is done. A comparison is done with the coordinates of the wall to determine if there is a contact.

  Input :
      a walls neighborhood (a list)
      the coordinates of the walls (four floats)
      an initial condition dictionnary (a dict)
      a material dictionnary (a dict)
  Output :
      Nothing, but the initial condition dictionnary is updated with the contact grain - walls.
Expand source code
def Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood,x_box_min,x_box_max,y_box_min,y_box_max, dict_ic, dict_material):
  """
  Detect contact grain in the neighborhood of the wall and the wall.

  The neighbourood is updated with Update_wall_Neighborhoods(). An iteration over the grains in the wall neighborhood is done. A comparison is done with the coordinates of the wall to determine if there is a contact.

        Input :
            a walls neighborhood (a list)
            the coordinates of the walls (four floats)
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
        Output :
            Nothing, but the initial condition dictionnary is updated with the contact grain - walls.
  """
  #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
  #load data needed
  L_ij_contact_gw = dict_ic['L_contact_gw_ij']
  L_contact_gw = dict_ic['L_contact_gw']
  id_contact = dict_ic['id_contact']
  #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-

  for grain in wall_neighborhood:

      p_x_min = min(grain.l_border_x)
      p_x_max = max(grain.l_border_x)
      p_y_min = min(grain.l_border_y)
      p_y_max = max(grain.l_border_y)

      #grain-wall x_min
      if p_x_min < x_box_min and (grain.id,-1) not in L_ij_contact_gw:
          overlap = x_box_min - p_x_min
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwx_min', x_box_min, overlap))
          L_ij_contact_gw.append((grain.id,-1))
          id_contact = id_contact + 1
      elif p_x_min < x_box_min and (grain.id,-1) in L_ij_contact_gw:
          overlap = x_box_min - p_x_min
          L_contact_gw[L_ij_contact_gw.index((grain.id,-1))].update_overlap(overlap)
      elif p_x_min > x_box_min and (grain.id,-1) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-1))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)
      #grain-wall x_max
      if p_x_max > x_box_max and (grain.id,-2) not in L_ij_contact_gw:
          overlap = p_x_max - x_box_max
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwx_max', x_box_max, overlap))
          L_ij_contact_gw.append((grain.id,-2))
          id_contact = id_contact + 1
      elif p_x_max > x_box_max and (grain.id,-2) in L_ij_contact_gw:
          overlap = p_x_max - x_box_max
          L_contact_gw[L_ij_contact_gw.index((grain.id,-2))].update_overlap(overlap)
      elif p_x_max < x_box_max and (grain.id,-2) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-2))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)
      #grain-wall y_min
      if p_y_min < y_box_min and (grain.id,-3) not in L_ij_contact_gw:
          overlap = y_box_min - p_y_min
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwy_min', y_box_min, overlap))
          L_ij_contact_gw.append((grain.id,-3))
          id_contact = id_contact + 1
      elif p_y_min < y_box_min and (grain.id,-3) in L_ij_contact_gw:
          overlap = y_box_min - p_y_min
          L_contact_gw[L_ij_contact_gw.index((grain.id,-3))].update_overlap(overlap)
      elif p_y_min > y_box_min and (grain.id,-3) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-3))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)
      #grain-wall y_max
      if p_y_max > y_box_max and (grain.id,-4) not in L_ij_contact_gw:
          overlap = p_y_max - y_box_max
          L_contact_gw.append(Contact_gw_Tempo(id_contact, grain, dict_material, 'gwy_max', y_box_max, overlap))
          L_ij_contact_gw.append((grain.id,-4))
          id_contact = id_contact + 1
      elif p_y_max > y_box_max and (grain.id,-4) in L_ij_contact_gw:
          overlap = p_y_max - y_box_max
          L_contact_gw[L_ij_contact_gw.index((grain.id,-4))].update_overlap(overlap)
      elif p_y_max < y_box_max and (grain.id,-4) in L_ij_contact_gw:
          i_contact = L_ij_contact_gw.index((grain.id,-4))
          L_contact_gw.pop(i_contact)
          L_ij_contact_gw.pop(i_contact)

      #Update dict
      dict_ic['L_contact_gw_ij'] = L_ij_contact_gw
      dict_ic['L_contact_gw'] = L_contact_gw
      dict_ic['id_contact'] = id_contact
def Grains_Polyhedral_contact_Neighborhoods_f(g1, g2)

Detect contact grain - grain.

Input : two temporary grains (two grain_tempo) Output : a Boolean, True if there is a contact (a Boolean)

Expand source code
def Grains_Polyhedral_contact_Neighborhoods_f(g1,g2):
  """
  Detect contact grain - grain.

    Input :
        two temporary grains (two grain_tempo)
    Output :
        a Boolean, True if there is a contact (a Boolean)
  """
  #looking for the nearest nodes
  d_virtual = max(g1.r_max,g2.r_max)
  ij_min = [0,0]
  d_ij_min = 100*d_virtual #Large
  for i in range(len(g1.l_border[:-1])):
    for j in range(len(g2.l_border[:-1])):
        d_ij = np.linalg.norm(g2.l_border[:-1][j]-g1.l_border[:-1][i]+d_virtual*(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
        if d_ij < d_ij_min :
            d_ij_min = d_ij
            ij_min = [i,j]

  d_ij_min = np.dot(g2.l_border[:-1][ij_min[1]]-g1.l_border[:-1][ij_min[0]],-(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
  return d_ij_min > 0
def Grains_Polyhedral_contact_Neighbouroods(dict_ic, dict_material)

Detect contact between a grain and grains from its neighbourood.

The neighbourood is updated with Update_Neighbouroods().

Input :
    an initial condition dictionnary (a dict)
    a material dictionnary (a dict)
Output :
    Nothing, but the initial condition dictionnary is updated with grain - grain contacts
Expand source code
def Grains_Polyhedral_contact_Neighbouroods(dict_ic,dict_material):
    """
    Detect contact between a grain and grains from its neighbourood.

    The neighbourood is updated with Update_Neighbouroods().

        Input :
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
        Output :
            Nothing, but the initial condition dictionnary is updated with grain - grain contacts
    """
    for i_grain in range(len(dict_ic['L_g_tempo'])-1) :
        grain_i = dict_ic['L_g_tempo'][i_grain]
        for neighbour in dict_ic['L_g_tempo'][i_grain].neighbourood:
            j_grain = neighbour.id
            grain_j = neighbour
            if Grains_Polyhedral_contact_Neighborhoods_f(grain_i,grain_j):
                if (i_grain,j_grain) not in dict_ic['L_contact_ij']:  #contact not detected previously
                   #creation of contact
                   dict_ic['L_contact_ij'].append((i_grain,j_grain))
                   dict_ic['L_contact'].append(Contact_Tempo(dict_ic['id_contact'], grain_i, grain_j, dict_material))
                   dict_ic['id_contact'] = dict_ic['id_contact'] + 1

            else :
                if (i_grain,j_grain) in dict_ic['L_contact_ij'] : #contact detected previously is not anymore
                       dict_ic['L_contact'].pop(dict_ic['L_contact_ij'].index((i_grain,j_grain)))
                       dict_ic['L_contact_ij'].remove((i_grain,j_grain))
def Grains_Polyhedral_contact_f(g1, g2)

Detect the contact grain-grain.

Input : two temporary grains (two grain_tempos) Output : a Boolean, True if there is contaxct between the twwo grains (a Boolean)

Expand source code
def Grains_Polyhedral_contact_f(g1,g2):
  """
  Detect the contact grain-grain.

    Input :
        two temporary grains (two grain_tempos)
    Output :
        a Boolean, True if there is contaxct between the twwo grains (a Boolean)
  """
  if np.linalg.norm(g1.center-g2.center) < 1.5*(g1.r_max+g2.r_max):

      #looking for the nearest nodes
      d_virtual = max(g1.r_max,g2.r_max)
      ij_min = [0,0]
      d_ij_min = 100*d_virtual #Large
      for i in range(len(g1.l_border[:-1])):
        for j in range(len(g2.l_border[:-1])):
            d_ij = np.linalg.norm(g2.l_border[:-1][j]-g1.l_border[:-1][i]+d_virtual*(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
            if d_ij < d_ij_min :
                d_ij_min = d_ij
                ij_min = [i,j]

      d_ij_min = np.dot(g2.l_border[:-1][ij_min[1]]-g1.l_border[:-1][ij_min[0]],-(g2.center-g1.center)/np.linalg.norm(g2.center-g1.center))
      return d_ij_min > 0

  else:
    return False
def LG_tempo(dict_algorithm, dict_geometry, dict_ic, dict_material, dict_sample, dict_sollicitations, simulation_report)

Create an initial condition

Input :
    an algorithm dictionnary (a dict)
    a geometry dictionnary (a dict)
    an initial condition dictionnary (a dict)
    a material dictionnary (a dict)
    a sample dictionnary (a dict)
    a sollicitations dictionnary (a dict)
    a simulation report (a report)
Output :
    Nothing, but dictionnaries are updated
Expand source code
def LG_tempo(dict_algorithm, dict_geometry, dict_ic, dict_material, dict_sample, dict_sollicitations, simulation_report):
    """
    Create an initial condition

        Input :
            an algorithm dictionnary (a dict)
            a geometry dictionnary (a dict)
            an initial condition dictionnary (a dict)
            a material dictionnary (a dict)
            a sample dictionnary (a dict)
            a sollicitations dictionnary (a dict)
            a simulation report (a report)
        Output :
            Nothing, but dictionnaries are updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    n_generation = dict_ic['n_generation']
    if n_generation != 2:
        simulation_report.write('n_generation must be equal to 2 !')
        raise ValueError('n_generation must be equal to 2 !')
    factor = dict_ic['factor_ymax_box']
    N_grain_disk = dict_geometry['N_grain_disk']
    N_grain_square = dict_geometry['N_grain_square']
    N_grain = dict_geometry['N_grain_disk'] + dict_geometry['N_grain_square']
    L_radius = dict_geometry['L_R']
    L_percentage_radius = dict_geometry['L_percentage_R']
    L_dimension = dict_geometry['L_Dimension']
    L_percentage_dimension = dict_geometry['L_percentage_Dimension']
    x_min = dict_sample['x_box_min']
    x_max = dict_sample['x_box_max']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #define the y_max for the grains generation
    radius_mean = 0
    for i in range(len(L_radius)):
        radius_mean = radius_mean + L_radius[i]*L_percentage_radius[i]
    dimension_mean = 0
    for i in range(len(L_dimension)):
        dimension_mean = dimension_mean + L_dimension[i]*L_percentage_dimension[i]
    dy_creation = N_grain_disk/n_generation*factor*(2*radius_mean)**2/(x_max-x_min) + N_grain_square/n_generation*factor*(dimension_mean)**2/(x_max-x_min)

    #plan the grains generation
    L_n_grain_radius_try_one = []
    L_n_grain_radius = []
    L_n_grain_radius_done = []
    for percentage in L_percentage_radius:
        L_n_grain_radius_try_one.append(int(N_grain_disk*percentage/n_generation))
        L_n_grain_radius.append(int(N_grain_disk*percentage))
        L_n_grain_radius_done.append(0)
    L_n_grain_dimension_try_one = []
    L_n_grain_dimension = []
    L_n_grain_dimension_done = []
    for percentage in L_percentage_dimension:
        L_n_grain_dimension_try_one.append(int(N_grain_square*percentage/n_generation))
        L_n_grain_dimension.append(int(N_grain_square*percentage))
        L_n_grain_dimension_done.append(0)

    #Creation of grains
    #grains generation is decomposed in several steps (creation of grain then settlement)
    i_DEM = 0
    L_L_g_tempo = []

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

    print('First generation of grains')
    L_g_tempo = []

    #add elements in dicts
    dict_ic['L_g_tempo'] = L_g_tempo
    dict_ic['L_L_g_tempo'] = L_L_g_tempo
    dict_ic['i_DEM_IC'] = i_DEM
    dict_ic['L_n_grain_radius_try_one'] = L_n_grain_radius_try_one
    dict_ic['L_n_grain_radius'] = L_n_grain_radius
    dict_ic['L_n_grain_radius_done'] = L_n_grain_radius_done
    dict_ic['L_n_grain_dimension_try_one'] = L_n_grain_dimension_try_one
    dict_ic['L_n_grain_dimension'] = L_n_grain_dimension
    dict_ic['L_n_grain_dimension_done'] = L_n_grain_dimension_done
    dict_ic['last_id'] = 0
    dict_sample['y_box_min_ic'] = dict_sample['y_box_min']
    dict_sample['dy_creation'] = dy_creation

    Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, 1, simulation_report)

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #DEM to find the steady-state configuration after loading
    #find the maximum y (center+radius)
    y_max = dict_sample['y_box_min_ic']
    for grain in L_g_tempo:
        if max(grain.l_border_y) > y_max:
            y_max = max(grain.l_border_y)

    #add element in dict
    dict_sample['y_box_max'] = y_max

    DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, False, simulation_report)

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

    print('Second generation of grains')

    #Update dict
    L_g_tempo = []
    dict_ic['L_g_tempo'] = L_g_tempo

    Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, 2, simulation_report)

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    #DEM to find the steady-state configuration after loading
    #find the maximum y (center+radius)
    y_max = dict_sample['y_box_min_ic']
    for grain in L_g_tempo:
        if max(grain.l_border_y) > y_max:
            y_max = max(grain.l_border_y)

    DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, False, simulation_report)

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

    print('Combine generations of grains')

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_L_g_tempo = dict_ic['L_L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    L_g = []
    for L_g_tempo in L_L_g_tempo:
        for g_tempo in L_g_tempo:
            L_g.append(g_tempo)

    #update element in dict
    dict_ic['L_g_tempo'] = L_g

    DEM_loading(dict_ic, dict_material, dict_sample, dict_sollicitations, True, simulation_report)

    #update element in dict
    dict_sample['y_box_max'] = dict_ic['y_box_max']

    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    L_g_tempo = dict_ic['L_g_tempo']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    simulation_report.write_and_print(str(len(L_g_tempo))+' / '+str(N_grain)+' grains have been created\n','\n'+str(len(L_g_tempo))+' / '+str(N_grain)+' grains have been created\n')
def Plot_Config_Loaded(L_g, x_min, x_max, y_min, y_max, i)

Plot loaded configuration.

Input :
    a list of temporary grain (a list)
    the coordinates of the walls (four floats)
    an iteration (a int)
Output :
    Nothing, but a .png file is generated (a file)
Expand source code
def Plot_Config_Loaded(L_g,x_min,x_max,y_min,y_max,i):
    """
    Plot loaded configuration.

        Input :
            a list of temporary grain (a list)
            the coordinates of the walls (four floats)
            an iteration (a int)
        Output :
            Nothing, but a .png file is generated (a file)
    """
    plt.figure(1,figsize=(16,9))
    L_x = []
    L_y = []
    L_u = []
    L_v = []
    for grain in L_g:
        plt.plot(grain.l_border_x,grain.l_border_y,'k')
        plt.plot(grain.center[0],grain.center[1],'xk')
        L_x.append(grain.center[0])
        L_y.append(grain.center[1])
        L_u.append(grain.fx)
        L_v.append(grain.fy)
    plt.plot([x_min,x_min,x_max,x_max,x_min],[y_max,y_min,y_min,y_max,y_max],'k')
    plt.axis('equal')
    plt.savefig('Debug/DEM_ite/Init/Config_Loaded_'+str(i)+'.png')
    plt.close(1)
def Plot_Config_Loaded_End(L_g, x_min, x_max, y_min, y_max)

Plot loaded configuration at the end of the initial configuration.

Input :
    a list of temporary grain (a list)
    the coordinates of the walls (four floats)
    an iteration (a int)
Output :
    Nothing, but a .png file is generated (a file)
Expand source code
def Plot_Config_Loaded_End(L_g,x_min,x_max,y_min,y_max):
    """
    Plot loaded configuration at the end of the initial configuration.

        Input :
            a list of temporary grain (a list)
            the coordinates of the walls (four floats)
            an iteration (a int)
        Output :
            Nothing, but a .png file is generated (a file)
    """
    plt.figure(1,figsize=(16,9))
    L_x = []
    L_y = []
    L_u = []
    L_v = []
    for grain in L_g:
        plt.plot(grain.l_border_x,grain.l_border_y,'k')
        plt.plot(grain.center[0],grain.center[1],'xk')
        L_x.append(grain.center[0])
        L_y.append(grain.center[1])
        L_u.append(grain.fx)
        L_v.append(grain.fy)
    plt.plot([x_min,x_min,x_max,x_max,x_min],[y_max,y_min,y_min,y_max,y_max],'k')
    plt.axis('equal')
    plt.savefig('Debug/ConfigLoaded.png')
    plt.close(1)
def Reset_y_max(L_g, Force)

The upper wall is located as a single contact verify the target value.

Input :
    the list of temporary grains (a list)
    the confinement force (a float)
Output :
    the upper wall position (a float)
Expand source code
def Reset_y_max(L_g,Force):
    """
    The upper wall is located as a single contact verify the target value.

        Input :
            the list of temporary grains (a list)
            the confinement force (a float)
        Output :
            the upper wall position (a float)
    """
    y_max = None
    id_grain_max = None
    for id_grain in range(len(L_g)):
        grain = L_g[id_grain]
        y_max_grain = max(grain.l_border_y)

        if y_max != None and y_max_grain > y_max:
            y_max = y_max_grain
            id_grain_max = id_grain
        elif y_max == None:
            y_max = y_max_grain
            id_grain_max = id_grain

    factor = 5
    k = factor*4/3*L_g[id_grain_max].y/(1-L_g[id_grain_max].nu*L_g[id_grain_max].nu)*math.sqrt(L_g[id_grain_max].r_max)
    y_max = y_max - (Force/k)**(2/3)

    return y_max
def Update_Neighbouroods(dict_ic)

Determine a neighborhood for each grain.

This function is called every x time step. The contact is determined by Grains_Polyhedral_contact_Neighbouroods(). Notice that if there is a potential contact between grain_i and grain_j, grain_i is not in the neighborhood of grain_j. Whereas grain_j is in the neighbourood of grain_i. With i_grain < j_grain.

Input :
    an initial condition dictionnary (a dict)
Output :
    Nothing, but the neighborhood of the temporary grains is updated
Expand source code
def Update_Neighbouroods(dict_ic):
    """
    Determine a neighborhood for each grain.

    This function is called every x time step. The contact is determined by Grains_Polyhedral_contact_Neighbouroods().
    Notice that if there is a potential contact between grain_i and grain_j, grain_i is not in the neighborhood of grain_j.
    Whereas grain_j is in the neighbourood of grain_i. With i_grain < j_grain.

        Input :
            an initial condition dictionnary (a dict)
        Output :
            Nothing, but the neighborhood of the temporary grains is updated
    """
    for i_grain in range(len(dict_ic['L_g_tempo'])-1) :
        neighbourood = []
        for j_grain in range(i_grain+1,len(dict_ic['L_g_tempo'])):
            if np.linalg.norm(dict_ic['L_g_tempo'][i_grain].center-dict_ic['L_g_tempo'][j_grain].center) < dict_ic['factor_neighborhood_IC']*(dict_ic['L_g_tempo'][i_grain].r_max+dict_ic['L_g_tempo'][j_grain].r_max):
                neighbourood.append(dict_ic['L_g_tempo'][j_grain])
        dict_ic['L_g_tempo'][i_grain].neighbourood = neighbourood
def Update_wall_Neighborhoods(L_g_tempo, factor_neighborhood_IC, x_min, x_max, y_min, y_max)

Determine a neighborhood for wall.

This function is called every x time step. The grain - wall contact is determined by Grains_Polyhedral_Wall_contact_Neighborhood(). A factor determines the size of the neighborhood window.

Input :
    a list of temporary grains (a list)
    a factor to determine the neighborhood window (a float)
    the coordinates of the left, right, lower, upper walls (four floats)
Output :
    a list of temporary grains in the neighborhood of the walls (a list)
Expand source code
def Update_wall_Neighborhoods(L_g_tempo,factor_neighborhood_IC,x_min,x_max,y_min,y_max):
    """
    Determine a neighborhood for wall.

    This function is called every x time step. The grain - wall contact is determined by Grains_Polyhedral_Wall_contact_Neighborhood().
    A factor determines the size of the neighborhood window.

        Input :
            a list of temporary grains (a list)
            a factor to determine the neighborhood window (a float)
            the coordinates of the left, right, lower, upper walls (four floats)
        Output :
            a list of temporary grains in the neighborhood of the walls (a list)
    """
    wall_neighborhood = []
    for grain in L_g_tempo:

        p_x_min = min(grain.l_border_x)
        p_x_max = max(grain.l_border_x)
        p_y_min = min(grain.l_border_y)
        p_y_max = max(grain.l_border_y)

        #grain-wall x_min
        if abs(p_x_min-x_min) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)
        #grain-wall x_max
        if abs(p_x_max-x_max) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)
        #grain-wall y_min
        if abs(p_y_min-y_min) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)
        #grain-wall y_max
        if abs(p_y_max-y_max) < factor_neighborhood_IC*grain.r_max :
            wall_neighborhood.append(grain)

    return wall_neighborhood
def error_on_ymax_df(dy, overlap_L, k_L)

Compute the derivative function df to control the upper wall (error_on_ymax_f()).

Input :
    an increment of the upper wall position (a float)
    a list of overlap for contact between temporary grain and upper wall (a list)
    a list of spring for contact between temporary grain and upper wall (a list)
Output :
    the derivative of error_on_ymax_f() (a float)
Expand source code
def error_on_ymax_df(dy,overlap_L,k_L) :
    """
    Compute the derivative function df to control the upper wall (error_on_ymax_f()).

        Input :
            an increment of the upper wall position (a float)
            a list of overlap for contact between temporary grain and upper wall (a list)
            a list of spring for contact between temporary grain and upper wall (a list)
        Output :
            the derivative of error_on_ymax_f() (a float)
    """
    df = 0
    for i in range(len(overlap_L)):
        df = df + 3/2*k_L[i]*(max(overlap_L[i]-dy,0))**(1/2)
    return df
def error_on_ymax_f(dy, overlap_L, k_L, Force_target)

Compute the function f to control the upper wall. It is the difference between the force applied and the target value.

Input :
    an increment of the upper wall position (a float)
    a list of overlap for contact between temporary grain and upper wall (a list)
    a list of spring for contact between temporary grain and upper wall (a list)
    a confinement force (a float)
Output :
    the difference between the force applied and the confinement (a float)
Expand source code
def error_on_ymax_f(dy,overlap_L,k_L,Force_target) :
    """
    Compute the function f to control the upper wall. It is the difference between the force applied and the target value.

        Input :
            an increment of the upper wall position (a float)
            a list of overlap for contact between temporary grain and upper wall (a list)
            a list of spring for contact between temporary grain and upper wall (a list)
            a confinement force (a float)
        Output :
            the difference between the force applied and the confinement (a float)
    """
    f = Force_target
    for i in range(len(overlap_L)):
        f = f - k_L[i]*(max(overlap_L[i]-dy,0))**(3/2)
    return f
def extract_vertices(g, angle_g_to_other_g)

Extract a list of indices of vertices inside a angular window.

Input :
    a grain (a grain)
    an angle (a float)
Output :
    a list of indices (a list)
Expand source code
def extract_vertices(g, angle_g_to_other_g) :
    """
    Extract a list of indices of vertices inside a angular window.

        Input :
            a grain (a grain)
            an angle (a float)
        Output :
            a list of indices (a list)
    """
    dtheta = 3*2*math.pi/len(g.l_border)
    angle_minus = angle_g_to_other_g - dtheta/2
    if angle_minus < 0 :
        angle_minus = angle_minus + 2*math.pi
    angle_plus  = angle_g_to_other_g + dtheta/2
    if 2*math.pi <= angle_plus :
        angle_plus = angle_plus - 2*math.pi
    i_minus = find_value_in_list(g.l_theta_r.copy(), angle_minus)
    i_plus = i_minus + find_value_in_list(g.l_theta_r[i_minus:].copy() + g.l_theta_r[:i_minus].copy(), angle_plus)

    L_i_vertices = []
    for i in range(i_minus, i_plus+1):
        if i < len(g.l_theta_r):
            L_i_vertices.append(i)
        else :
            L_i_vertices.append(i-len(g.l_theta_r))

    return L_i_vertices
def find_value_in_list(List_to_search, value_to_find)

Extract the index of the nearest value from a target in a list.

Input :
    a list of float (a list)
    a target (a float)
Output :
    an index (an int)
Expand source code
def find_value_in_list(List_to_search, value_to_find) :
    """
    Extract the index of the nearest value from a target in a list.

        Input :
            a list of float (a list)
            a target (a float)
        Output :
            an index (an int)
    """
    L_search = list(abs(np.array(List_to_search)-value_to_find))
    return L_search.index(min(L_search))

Classes

class Contact_Tempo (ID, G1, G2, dict_material)

A temporary contact grain - grain used to generated an initial condition.

Defining the contact grain-grain.

Input :
    itself (a contact_tempo)
    an id (a int)
    two grains (two grain_tempos)
    a material dictionnary (a dict)
Output :
    Nothing, but the contact grain - grain is generated (a contact_tempo)
Expand source code
class Contact_Tempo:
  """
  A temporary contact grain - grain used to generated an initial condition.
  """

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

  def __init__(self, ID, G1, G2, dict_material):
    """
    Defining the contact grain-grain.

        Input :
            itself (a contact_tempo)
            an id (a int)
            two grains (two grain_tempos)
            a material dictionnary (a dict)
        Output :
            Nothing, but the contact grain - grain is generated (a contact_tempo)
    """
    self.id = ID
    self.g1 = G1
    self.g2 = G2
    self.ft = 0
    self.mu = 0
    self.coeff_restitution = dict_material['coeff_restitution']
    self.tangential_old_statut = False
    self.overlap_tangential = 0

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

  def normal(self):
    """
    Compute the normal reaction of a contact grain-grain.

    Here a pontual spring is considered.

        Input :
            itself (a contact_tempo)
        Output :
            Nothing, but attributes are updated
    """
    #compute angle between grains
    g1_to_g2 = self.g2.center - self.g1.center
    if g1_to_g2[1] >= 0 :
        angle_g1_to_g2 = math.acos(g1_to_g2[0]/np.linalg.norm(g1_to_g2))
        angle_g2_to_g1 = angle_g1_to_g2 + math.pi
    else :
        angle_g1_to_g2 = math.pi + math.acos(-g1_to_g2[0]/np.linalg.norm(g1_to_g2))
        angle_g2_to_g1 = angle_g1_to_g2 - math.pi

    #extract
    L_i_vertices_1 = extract_vertices(self.g1, angle_g1_to_g2)
    L_i_vertices_2 = extract_vertices(self.g2, angle_g2_to_g1)

    #looking for the nearest nodes
    d_virtual = max(self.g1.r_max,self.g2.r_max)
    ij_min = [0,0]
    d_ij_min = 100*d_virtual #Large
    for i in L_i_vertices_1:
        for j in L_i_vertices_2:
            d_ij = np.linalg.norm(self.g2.l_border[:-1][j]-self.g1.l_border[:-1][i]+d_virtual*(self.g2.center-self.g1.center)/np.linalg.norm(self.g2.center-self.g1.center))
            if d_ij < d_ij_min :
                d_ij_min = d_ij
                ij_min = [i,j]
    self.ij_min = ij_min

    #-----------------------------------------------------------------------------
    #Computing CP
    #-----------------------------------------------------------------------------

    M = (self.g1.l_border[:-1][ij_min[0]]+self.g2.l_border[:-1][ij_min[1]])/2
    # 5 candidates for CP
    N = np.array([self.g1.l_border[:-1][ij_min[0]][0] - self.g2.l_border[:-1][ij_min[1]][0],
                  self.g1.l_border[:-1][ij_min[0]][1] - self.g2.l_border[:-1][ij_min[1]][1]])
    N = N/np.linalg.norm(N)
    PB = np.array([-N[1] ,N[0]])
    PB = PB/np.linalg.norm(PB)

    #candidats from grain 1
    if ij_min[0] <len(self.g1.l_border[:-1]) - 1:
        M1 = self.g1.l_border[:-1][ij_min[0]+1]-self.g1.l_border[:-1][ij_min[0]]
    else :
        M1 = self.g1.l_border[:-1][0]-self.g1.l_border[:-1][ij_min[0]]
    M1 = M1/np.linalg.norm(M1)
    M3 = self.g1.l_border[:-1][ij_min[0]-1]-self.g1.l_border[:-1][ij_min[0]]
    M3 = M3/np.linalg.norm(M3)
    #reorganize the candidats
    if np.dot(M1,PB) < 0:
        Mtempo = M1.copy()
        M1 = M3.copy()
        M3 = Mtempo.copy()

    #candidats from grain 2
    if ij_min[1] <len(self.g2.l_border[:-1]) - 1:
        M2 = self.g2.l_border[:-1][ij_min[1]+1]-self.g2.l_border[:-1][ij_min[1]]
    else :
        M2 = self.g2.l_border[:-1][0]-self.g2.l_border[:-1][ij_min[1]]
    M2 = M2/np.linalg.norm(M2)
    M4 = self.g2.l_border[:-1][ij_min[1]-1]-self.g2.l_border[:-1][ij_min[1]]
    M4 = M4/np.linalg.norm(M4)
    #reorganize the candidats
    if np.dot(M2,PB) < 0:
      Mtempo = M2.copy()
      M2 = M4.copy()
      M4 = Mtempo.copy()

    #compute the different angles
    theta_PB = math.pi/2
    theta_M1 =  math.acos(np.dot(M1,N))
    theta_M2 =  math.acos(np.dot(M2,N))
    theta_M3 = -math.acos(np.dot(M3,N))
    theta_M4 = -math.acos(np.dot(M4,N))

    #find the PC
    if theta_M2 < theta_PB and theta_PB < theta_M1\
       and theta_M3 < -theta_PB and -theta_PB < theta_M4:
       PC = PB
    else:
      L_Mi = [M1,M2,M3,M4]
      L_theta_Mi_PB=[theta_M1-theta_PB, theta_PB-theta_M2, -theta_M3-theta_PB, theta_PB+theta_M4]
      PC = L_Mi[L_theta_Mi_PB.index(min(L_theta_Mi_PB))]

    #-----------------------------------------------------------------------------
    # Compute the normal and tangential planes
    #-----------------------------------------------------------------------------

    PC_normal = np.array([PC[1],-PC[0]])
    if np.dot(PC_normal,(self.g2.center-self.g1.center)/np.linalg.norm(self.g2.center-self.g1.center))<0 :
        PC_normal = np.array([-PC[1],PC[0]])
    self.pc_normal = PC_normal #n12
    self.pc_tangential = np.array([-PC_normal[1],PC_normal[0]])

    #-----------------------------------------------------------------------------
    # Compute the overlap
    #-----------------------------------------------------------------------------

    d_b = np.dot(M-self.g2.l_border[:-1][ij_min[1]],PC_normal)
    d_a = np.dot(M-self.g1.l_border[:-1][ij_min[0]],PC_normal)
    overlap = d_b - d_a
    self.overlap_normal = overlap

    if overlap > 0:
    #-----------------------------------------------------------------------------
    # Compute the reaction
    #-----------------------------------------------------------------------------

        #Spring term
        Y_eq = 1/((1-self.g1.nu*self.g1.nu)/self.g1.y+(1-self.g2.nu*self.g2.nu)/self.g2.y)
        R_eq = 1/(1/np.linalg.norm(self.g1.center-self.g1.l_border[self.ij_min[0]])+1/np.linalg.norm(self.g2.center-self.g2.l_border[self.ij_min[1]]))
        k = 4/3*Y_eq*math.sqrt(R_eq)
        F_2_1_n = -k * overlap**(3/2)  #unlinear spring
        F_2_1 = F_2_1_n * PC_normal
        self.F_2_1_n = F_2_1_n
        self.Ep_n = 2/5 * k * overlap**(5/2) #-dEp/dx = F_2_1_n
        self.g1.add_F( F_2_1, self.g1.l_border[:-1][ij_min[0]])
        self.g2.add_F(-F_2_1, self.g2.l_border[:-1][ij_min[1]])

        #Damping term
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g1.mass*self.g2.mass/(self.g1.mass+self.g2.mass)
        eta = 2 * gamma * math.sqrt(mass_eq*k)
        F_2_1_damp_n = np.dot(self.g2.v - self.g1.v,PC_normal)*eta
        F_2_1_damp = F_2_1_damp_n *PC_normal
        self.F_2_1_damp = F_2_1_damp_n
        self.g1.add_F( F_2_1_damp, self.g1.l_border[:-1][ij_min[0]])
        self.g2.add_F(-F_2_1_damp, self.g2.l_border[:-1][ij_min[1]])

    #no contact finally
    else :
        self.F_2_1_n = 0
        self.F_2_1_damp = 0
        self.Ep_n = 0

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

  def tangential(self,dt_DEM):
    """
    Compute the tangential reaction of a contact grain-grain.

    Here a pontual spring is considered

        Input :
            itself (a contact_tempo)
            a time step (a float)
        Output :
            Nothing, but attributes are updated
    """
    if self.overlap_normal > 0 and self.mu > 0:

        if self.tangential_old_statut:
          #if a reaction has been already computed
          #need to project the tangential reaction on the new tangential plane
          self.ft = self.ft*np.dot(self.tangential_old,self.pc_tangential)
        else:
          self.tangential_old_statut = True

        G_eq = 1/((1-self.g1.nu)/self.g1.g+(1-self.g2.nu)/self.g2.g)
        R_eq = 1/(1/np.linalg.norm(self.g1.center-self.g1.l_border[self.ij_min[0]])+1/np.linalg.norm(self.g2.center-self.g2.l_border[self.ij_min[1]]))
        kt0 = 8 * G_eq *math.sqrt(R_eq*abs(self.overlap_normal))
        kt = kt0*math.sqrt(max(1-2/3*kt0*abs(self.overlap_tangential)/self.mu/abs(self.F_2_1_n),0)) #not linear spring

        r1 = np.linalg.norm(self.g1.l_border[:-1][self.ij_min[0]] - self.g1.center) - self.overlap_normal/2
        r2 = np.linalg.norm(self.g2.l_border[:-1][self.ij_min[1]] - self.g2.center) - self.overlap_normal/2
        Delta_Us = (np.dot(self.g1.v-self.g2.v,self.pc_tangential) + r1*self.g1.w + r2*self.g2.w)*dt_DEM
        self.overlap_tangential = self.overlap_tangential + Delta_Us
        self.ft = self.ft - kt*Delta_Us
        self.tangential_old = self.pc_tangential
        if abs(self.ft) > abs(self.mu*self.F_2_1_n) or kt == 0: #Coulomb criteria
            self.ft = self.mu * abs(self.F_2_1_n) * np.sign(self.ft)

        self.g1.add_F( self.ft*self.pc_tangential, self.g1.l_border[:-1][self.ij_min[0]])
        self.g2.add_F(-self.ft*self.pc_tangential, self.g2.l_border[:-1][self.ij_min[1]])

        #Damping term
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g1.mass*self.g2.mass/(self.g1.mass+self.g2.mass)
        eta = 2 * gamma * math.sqrt(mass_eq*kt)
        F_2_1_damp_t = -Delta_Us/dt_DEM*eta/2
        F_2_1_damp = F_2_1_damp_t *self.pc_tangential
        self.ft_damp = F_2_1_damp_t
        self.g1.add_F( F_2_1_damp, self.g1.l_border[:-1][self.ij_min[0]])
        self.g2.add_F(-F_2_1_damp, self.g2.l_border[:-1][self.ij_min[1]])

    #no contact finally
    else :
        tangential_old_statut = False
        self.overlap_tangential = 0
        self.ft = 0
        self.ft_damp = 0

Methods

def normal(self)

Compute the normal reaction of a contact grain-grain.

Here a pontual spring is considered.

Input :
    itself (a contact_tempo)
Output :
    Nothing, but attributes are updated
Expand source code
def normal(self):
  """
  Compute the normal reaction of a contact grain-grain.

  Here a pontual spring is considered.

      Input :
          itself (a contact_tempo)
      Output :
          Nothing, but attributes are updated
  """
  #compute angle between grains
  g1_to_g2 = self.g2.center - self.g1.center
  if g1_to_g2[1] >= 0 :
      angle_g1_to_g2 = math.acos(g1_to_g2[0]/np.linalg.norm(g1_to_g2))
      angle_g2_to_g1 = angle_g1_to_g2 + math.pi
  else :
      angle_g1_to_g2 = math.pi + math.acos(-g1_to_g2[0]/np.linalg.norm(g1_to_g2))
      angle_g2_to_g1 = angle_g1_to_g2 - math.pi

  #extract
  L_i_vertices_1 = extract_vertices(self.g1, angle_g1_to_g2)
  L_i_vertices_2 = extract_vertices(self.g2, angle_g2_to_g1)

  #looking for the nearest nodes
  d_virtual = max(self.g1.r_max,self.g2.r_max)
  ij_min = [0,0]
  d_ij_min = 100*d_virtual #Large
  for i in L_i_vertices_1:
      for j in L_i_vertices_2:
          d_ij = np.linalg.norm(self.g2.l_border[:-1][j]-self.g1.l_border[:-1][i]+d_virtual*(self.g2.center-self.g1.center)/np.linalg.norm(self.g2.center-self.g1.center))
          if d_ij < d_ij_min :
              d_ij_min = d_ij
              ij_min = [i,j]
  self.ij_min = ij_min

  #-----------------------------------------------------------------------------
  #Computing CP
  #-----------------------------------------------------------------------------

  M = (self.g1.l_border[:-1][ij_min[0]]+self.g2.l_border[:-1][ij_min[1]])/2
  # 5 candidates for CP
  N = np.array([self.g1.l_border[:-1][ij_min[0]][0] - self.g2.l_border[:-1][ij_min[1]][0],
                self.g1.l_border[:-1][ij_min[0]][1] - self.g2.l_border[:-1][ij_min[1]][1]])
  N = N/np.linalg.norm(N)
  PB = np.array([-N[1] ,N[0]])
  PB = PB/np.linalg.norm(PB)

  #candidats from grain 1
  if ij_min[0] <len(self.g1.l_border[:-1]) - 1:
      M1 = self.g1.l_border[:-1][ij_min[0]+1]-self.g1.l_border[:-1][ij_min[0]]
  else :
      M1 = self.g1.l_border[:-1][0]-self.g1.l_border[:-1][ij_min[0]]
  M1 = M1/np.linalg.norm(M1)
  M3 = self.g1.l_border[:-1][ij_min[0]-1]-self.g1.l_border[:-1][ij_min[0]]
  M3 = M3/np.linalg.norm(M3)
  #reorganize the candidats
  if np.dot(M1,PB) < 0:
      Mtempo = M1.copy()
      M1 = M3.copy()
      M3 = Mtempo.copy()

  #candidats from grain 2
  if ij_min[1] <len(self.g2.l_border[:-1]) - 1:
      M2 = self.g2.l_border[:-1][ij_min[1]+1]-self.g2.l_border[:-1][ij_min[1]]
  else :
      M2 = self.g2.l_border[:-1][0]-self.g2.l_border[:-1][ij_min[1]]
  M2 = M2/np.linalg.norm(M2)
  M4 = self.g2.l_border[:-1][ij_min[1]-1]-self.g2.l_border[:-1][ij_min[1]]
  M4 = M4/np.linalg.norm(M4)
  #reorganize the candidats
  if np.dot(M2,PB) < 0:
    Mtempo = M2.copy()
    M2 = M4.copy()
    M4 = Mtempo.copy()

  #compute the different angles
  theta_PB = math.pi/2
  theta_M1 =  math.acos(np.dot(M1,N))
  theta_M2 =  math.acos(np.dot(M2,N))
  theta_M3 = -math.acos(np.dot(M3,N))
  theta_M4 = -math.acos(np.dot(M4,N))

  #find the PC
  if theta_M2 < theta_PB and theta_PB < theta_M1\
     and theta_M3 < -theta_PB and -theta_PB < theta_M4:
     PC = PB
  else:
    L_Mi = [M1,M2,M3,M4]
    L_theta_Mi_PB=[theta_M1-theta_PB, theta_PB-theta_M2, -theta_M3-theta_PB, theta_PB+theta_M4]
    PC = L_Mi[L_theta_Mi_PB.index(min(L_theta_Mi_PB))]

  #-----------------------------------------------------------------------------
  # Compute the normal and tangential planes
  #-----------------------------------------------------------------------------

  PC_normal = np.array([PC[1],-PC[0]])
  if np.dot(PC_normal,(self.g2.center-self.g1.center)/np.linalg.norm(self.g2.center-self.g1.center))<0 :
      PC_normal = np.array([-PC[1],PC[0]])
  self.pc_normal = PC_normal #n12
  self.pc_tangential = np.array([-PC_normal[1],PC_normal[0]])

  #-----------------------------------------------------------------------------
  # Compute the overlap
  #-----------------------------------------------------------------------------

  d_b = np.dot(M-self.g2.l_border[:-1][ij_min[1]],PC_normal)
  d_a = np.dot(M-self.g1.l_border[:-1][ij_min[0]],PC_normal)
  overlap = d_b - d_a
  self.overlap_normal = overlap

  if overlap > 0:
  #-----------------------------------------------------------------------------
  # Compute the reaction
  #-----------------------------------------------------------------------------

      #Spring term
      Y_eq = 1/((1-self.g1.nu*self.g1.nu)/self.g1.y+(1-self.g2.nu*self.g2.nu)/self.g2.y)
      R_eq = 1/(1/np.linalg.norm(self.g1.center-self.g1.l_border[self.ij_min[0]])+1/np.linalg.norm(self.g2.center-self.g2.l_border[self.ij_min[1]]))
      k = 4/3*Y_eq*math.sqrt(R_eq)
      F_2_1_n = -k * overlap**(3/2)  #unlinear spring
      F_2_1 = F_2_1_n * PC_normal
      self.F_2_1_n = F_2_1_n
      self.Ep_n = 2/5 * k * overlap**(5/2) #-dEp/dx = F_2_1_n
      self.g1.add_F( F_2_1, self.g1.l_border[:-1][ij_min[0]])
      self.g2.add_F(-F_2_1, self.g2.l_border[:-1][ij_min[1]])

      #Damping term
      gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
      mass_eq = self.g1.mass*self.g2.mass/(self.g1.mass+self.g2.mass)
      eta = 2 * gamma * math.sqrt(mass_eq*k)
      F_2_1_damp_n = np.dot(self.g2.v - self.g1.v,PC_normal)*eta
      F_2_1_damp = F_2_1_damp_n *PC_normal
      self.F_2_1_damp = F_2_1_damp_n
      self.g1.add_F( F_2_1_damp, self.g1.l_border[:-1][ij_min[0]])
      self.g2.add_F(-F_2_1_damp, self.g2.l_border[:-1][ij_min[1]])

  #no contact finally
  else :
      self.F_2_1_n = 0
      self.F_2_1_damp = 0
      self.Ep_n = 0
def tangential(self, dt_DEM)

Compute the tangential reaction of a contact grain-grain.

Here a pontual spring is considered

Input :
    itself (a contact_tempo)
    a time step (a float)
Output :
    Nothing, but attributes are updated
Expand source code
def tangential(self,dt_DEM):
  """
  Compute the tangential reaction of a contact grain-grain.

  Here a pontual spring is considered

      Input :
          itself (a contact_tempo)
          a time step (a float)
      Output :
          Nothing, but attributes are updated
  """
  if self.overlap_normal > 0 and self.mu > 0:

      if self.tangential_old_statut:
        #if a reaction has been already computed
        #need to project the tangential reaction on the new tangential plane
        self.ft = self.ft*np.dot(self.tangential_old,self.pc_tangential)
      else:
        self.tangential_old_statut = True

      G_eq = 1/((1-self.g1.nu)/self.g1.g+(1-self.g2.nu)/self.g2.g)
      R_eq = 1/(1/np.linalg.norm(self.g1.center-self.g1.l_border[self.ij_min[0]])+1/np.linalg.norm(self.g2.center-self.g2.l_border[self.ij_min[1]]))
      kt0 = 8 * G_eq *math.sqrt(R_eq*abs(self.overlap_normal))
      kt = kt0*math.sqrt(max(1-2/3*kt0*abs(self.overlap_tangential)/self.mu/abs(self.F_2_1_n),0)) #not linear spring

      r1 = np.linalg.norm(self.g1.l_border[:-1][self.ij_min[0]] - self.g1.center) - self.overlap_normal/2
      r2 = np.linalg.norm(self.g2.l_border[:-1][self.ij_min[1]] - self.g2.center) - self.overlap_normal/2
      Delta_Us = (np.dot(self.g1.v-self.g2.v,self.pc_tangential) + r1*self.g1.w + r2*self.g2.w)*dt_DEM
      self.overlap_tangential = self.overlap_tangential + Delta_Us
      self.ft = self.ft - kt*Delta_Us
      self.tangential_old = self.pc_tangential
      if abs(self.ft) > abs(self.mu*self.F_2_1_n) or kt == 0: #Coulomb criteria
          self.ft = self.mu * abs(self.F_2_1_n) * np.sign(self.ft)

      self.g1.add_F( self.ft*self.pc_tangential, self.g1.l_border[:-1][self.ij_min[0]])
      self.g2.add_F(-self.ft*self.pc_tangential, self.g2.l_border[:-1][self.ij_min[1]])

      #Damping term
      gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
      mass_eq = self.g1.mass*self.g2.mass/(self.g1.mass+self.g2.mass)
      eta = 2 * gamma * math.sqrt(mass_eq*kt)
      F_2_1_damp_t = -Delta_Us/dt_DEM*eta/2
      F_2_1_damp = F_2_1_damp_t *self.pc_tangential
      self.ft_damp = F_2_1_damp_t
      self.g1.add_F( F_2_1_damp, self.g1.l_border[:-1][self.ij_min[0]])
      self.g2.add_F(-F_2_1_damp, self.g2.l_border[:-1][self.ij_min[1]])

  #no contact finally
  else :
      tangential_old_statut = False
      self.overlap_tangential = 0
      self.ft = 0
      self.ft_damp = 0
class Contact_gw_Tempo (ID, G, dict_material, Nature, Limit, Overlap)

A temporary contact grain - wall used to generated an initial condition.

Defining the contact grain-wall.

Input :
     itself (a contact_gw_tempo)
     an id (a int)
     a grain (a grain_tempo)
     a material dictionnary (a dict)
     the nature of the wall (a string)
     the coordinate of the wall (a float)
     an overlap (a float)
Expand source code
class Contact_gw_Tempo:
  """
  A temporary contact grain - wall used to generated an initial condition.
  """

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

  def __init__(self, ID, G, dict_material, Nature, Limit, Overlap):
    """
    Defining the contact grain-wall.

        Input :
             itself (a contact_gw_tempo)
             an id (a int)
             a grain (a grain_tempo)
             a material dictionnary (a dict)
             the nature of the wall (a string)
             the coordinate of the wall (a float)
             an overlap (a float)
    """
    self.id = ID
    self.g = G
    factor = 5 #factor just to increase the stiffness
    self.k = factor*4/3*self.g.y/(1-self.g.nu*self.g.nu)*math.sqrt(self.g.r_max) #Hertz law
    self.kt = 0
    self.ft = 0
    self.limit = Limit
    self.nature = Nature
    self.mu = 0
    self.coeff_restitution = dict_material['coeff_restitution']
    self.overlap = Overlap
    self.tangential_old_statut = False
    self.overlap_tangential = 0

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

  def update_overlap(self,new_overlap):
    '''
    Update the overlap of a contact already created.

        Input :
            itself (a contact_gw_tempo)
            an overlap (a float)
        Output :
            Nothing, but the attribut concerning the overlap is updated (a float)
    '''
    self.overlap = new_overlap

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

  def  normal(self):
    """
    Compute the normal reaction of a contact grain-wall.

    Here a pontual spring is considered

        Input :
            itself (a contact_gw_tempo)
        Output :
            Nothing, but attributes are updated
    """
    #conditions "if" are defined and same for each wall nature
    if self.nature == 'gwy_min':
        #unlinear stiffness
        nwg = np.array([0,1])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])
        #damping
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g.mass
        eta = 2 * gamma * math.sqrt(mass_eq*self.k)
        Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
        Fwg_damp = Fwg_damp_n*nwg
        self.Fwg_damp_n = Fwg_damp_n
        self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])

    elif self.nature == 'gwy_max':
        #unlinear stiffness
        nwg = np.array([0,-1])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(max(self.g.l_border_y))])
        #damping
        Fwg_damp_n = 0
        self.Fwg_damp_n = Fwg_damp_n

    elif self.nature == 'gwx_min':
        #unlinear stiffness
        nwg = np.array([1,0])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])
        #damping
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g.mass
        eta = 2 * gamma * math.sqrt(mass_eq*self.k)
        Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
        Fwg_damp = Fwg_damp_n*nwg
        self.Fwg_damp_n = Fwg_damp_n
        self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])

    elif self.nature == 'gwx_max':
        #unlinear stiffness
        nwg = np.array([-1,0])
        self.nwg = nwg
        Fwg_n = self.k*self.overlap**(3/2)
        Fwg = Fwg_n*nwg
        self.Fwg_n = Fwg_n
        self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])
        #damping
        gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
        mass_eq = self.g.mass
        eta = 2 * gamma * math.sqrt(mass_eq*self.k)
        Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
        Fwg_damp = Fwg_damp_n*nwg
        self.Fwg_damp_n = Fwg_damp_n
        self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])

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

  def tangential(self, dt_DEM):
   """
   Compute the tangential reaction of a contact grain-wall.

   Here a pontual spring is considered.

        Input :
            itself (a contact_gw_tempo)
            a time step (a float)
        Output :
            Nothing, but attributes are updated
   """
   #conditions "if" are defined and same for each wall nature
   if self.nature == 'gwy_min':
       #unlinear stiffness
       twg = np.array([-1, 0])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_y.index(min(self.g.l_border_y))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])

   elif self.nature == 'gwy_max':
       #unlinear stiffness
       twg = np.array([1, 0])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_y.index(max(self.g.l_border_y))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(max(self.g.l_border_y))])

   elif self.nature == 'gwx_min':
       #unlinear stiffness
       twg = np.array([0, 1])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_x.index(min(self.g.l_border_x))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])

   elif self.nature == 'gwx_max':
       #linear stiffness
       twg = np.array([0, -1])
       self.twg = twg
       r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_x.index(max(self.g.l_border_x))] - self.g.center) - self.overlap
       Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
       self.overlap_tangential = self.overlap_tangential + Delta_Us
       self.ft = self.ft - self.kt*Delta_Us
       if abs(self.ft) > abs(self.mu*self.Fwg_n) :
           self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
       Fwg = self.ft*twg
       self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])

Methods

def normal(self)

Compute the normal reaction of a contact grain-wall.

Here a pontual spring is considered

Input :
    itself (a contact_gw_tempo)
Output :
    Nothing, but attributes are updated
Expand source code
def  normal(self):
  """
  Compute the normal reaction of a contact grain-wall.

  Here a pontual spring is considered

      Input :
          itself (a contact_gw_tempo)
      Output :
          Nothing, but attributes are updated
  """
  #conditions "if" are defined and same for each wall nature
  if self.nature == 'gwy_min':
      #unlinear stiffness
      nwg = np.array([0,1])
      self.nwg = nwg
      Fwg_n = self.k*self.overlap**(3/2)
      Fwg = Fwg_n*nwg
      self.Fwg_n = Fwg_n
      self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])
      #damping
      gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
      mass_eq = self.g.mass
      eta = 2 * gamma * math.sqrt(mass_eq*self.k)
      Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
      Fwg_damp = Fwg_damp_n*nwg
      self.Fwg_damp_n = Fwg_damp_n
      self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])

  elif self.nature == 'gwy_max':
      #unlinear stiffness
      nwg = np.array([0,-1])
      self.nwg = nwg
      Fwg_n = self.k*self.overlap**(3/2)
      Fwg = Fwg_n*nwg
      self.Fwg_n = Fwg_n
      self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(max(self.g.l_border_y))])
      #damping
      Fwg_damp_n = 0
      self.Fwg_damp_n = Fwg_damp_n

  elif self.nature == 'gwx_min':
      #unlinear stiffness
      nwg = np.array([1,0])
      self.nwg = nwg
      Fwg_n = self.k*self.overlap**(3/2)
      Fwg = Fwg_n*nwg
      self.Fwg_n = Fwg_n
      self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])
      #damping
      gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
      mass_eq = self.g.mass
      eta = 2 * gamma * math.sqrt(mass_eq*self.k)
      Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
      Fwg_damp = Fwg_damp_n*nwg
      self.Fwg_damp_n = Fwg_damp_n
      self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])

  elif self.nature == 'gwx_max':
      #unlinear stiffness
      nwg = np.array([-1,0])
      self.nwg = nwg
      Fwg_n = self.k*self.overlap**(3/2)
      Fwg = Fwg_n*nwg
      self.Fwg_n = Fwg_n
      self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])
      #damping
      gamma = -math.log(self.coeff_restitution)/math.sqrt(math.pi**2+math.log(self.coeff_restitution)**2)
      mass_eq = self.g.mass
      eta = 2 * gamma * math.sqrt(mass_eq*self.k)
      Fwg_damp_n = -np.dot(self.g.v,nwg)*eta
      Fwg_damp = Fwg_damp_n*nwg
      self.Fwg_damp_n = Fwg_damp_n
      self.g.add_F(Fwg_damp, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])
def tangential(self, dt_DEM)

Compute the tangential reaction of a contact grain-wall.

Here a pontual spring is considered.

 Input :
     itself (a contact_gw_tempo)
     a time step (a float)
 Output :
     Nothing, but attributes are updated
Expand source code
def tangential(self, dt_DEM):
 """
 Compute the tangential reaction of a contact grain-wall.

 Here a pontual spring is considered.

      Input :
          itself (a contact_gw_tempo)
          a time step (a float)
      Output :
          Nothing, but attributes are updated
 """
 #conditions "if" are defined and same for each wall nature
 if self.nature == 'gwy_min':
     #unlinear stiffness
     twg = np.array([-1, 0])
     self.twg = twg
     r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_y.index(min(self.g.l_border_y))] - self.g.center) - self.overlap
     Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
     self.overlap_tangential = self.overlap_tangential + Delta_Us
     self.ft = self.ft - self.kt*Delta_Us
     if abs(self.ft) > abs(self.mu*self.Fwg_n) :
         self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
     Fwg = self.ft*twg
     self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(min(self.g.l_border_y))])

 elif self.nature == 'gwy_max':
     #unlinear stiffness
     twg = np.array([1, 0])
     self.twg = twg
     r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_y.index(max(self.g.l_border_y))] - self.g.center) - self.overlap
     Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
     self.overlap_tangential = self.overlap_tangential + Delta_Us
     self.ft = self.ft - self.kt*Delta_Us
     if abs(self.ft) > abs(self.mu*self.Fwg_n) :
         self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
     Fwg = self.ft*twg
     self.g.add_F(Fwg, self.g.l_border[self.g.l_border_y.index(max(self.g.l_border_y))])

 elif self.nature == 'gwx_min':
     #unlinear stiffness
     twg = np.array([0, 1])
     self.twg = twg
     r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_x.index(min(self.g.l_border_x))] - self.g.center) - self.overlap
     Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
     self.overlap_tangential = self.overlap_tangential + Delta_Us
     self.ft = self.ft - self.kt*Delta_Us
     if abs(self.ft) > abs(self.mu*self.Fwg_n) :
         self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
     Fwg = self.ft*twg
     self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(min(self.g.l_border_x))])

 elif self.nature == 'gwx_max':
     #linear stiffness
     twg = np.array([0, -1])
     self.twg = twg
     r = np.linalg.norm(self.g.l_border[:-1][self.g.l_border_x.index(max(self.g.l_border_x))] - self.g.center) - self.overlap
     Delta_Us = (np.dot(self.g.v,self.twg) - r*self.g.w) * dt_DEM
     self.overlap_tangential = self.overlap_tangential + Delta_Us
     self.ft = self.ft - self.kt*Delta_Us
     if abs(self.ft) > abs(self.mu*self.Fwg_n) :
         self.ft = self.mu * abs(self.Fwg_n) * np.sign(self.ft)
     Fwg = self.ft*twg
     self.g.add_F(Fwg, self.g.l_border[self.g.l_border_x.index(max(self.g.l_border_x))])
def update_overlap(self, new_overlap)

Update the overlap of a contact already created.

Input :
    itself (a contact_gw_tempo)
    an overlap (a float)
Output :
    Nothing, but the attribut concerning the overlap is updated (a float)
Expand source code
def update_overlap(self,new_overlap):
  '''
  Update the overlap of a contact already created.

      Input :
          itself (a contact_gw_tempo)
          an overlap (a float)
      Output :
          Nothing, but the attribut concerning the overlap is updated (a float)
  '''
  self.overlap = new_overlap
class Grain_Tempo (ID, Center, Lenght, dict_material, Type)

A temporary grain used to generated an initial condition.

Defining the grain.

Input : itself (a grain_tempo) an id (a int) a center coordinate (a 1 x 2 numpy array) a dimension, radius if it is a disk or the size if it is a square (a float) a material dictionnary (a dict) a grain type, disk or square (a float) Output : Nothing, but a temporary grain is generated (a grain_tempo)

Expand source code
class Grain_Tempo:
  """
  A temporary grain used to generated an initial condition.
  """

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

  def __init__(self, ID, Center, Lenght, dict_material, Type):
    """Defining the grain.

        Input :
            itself (a grain_tempo)
            an id (a int)
            a center coordinate (a 1 x 2 numpy array)
            a dimension, radius if it is a disk or the size if it is a square (a float)
            a material dictionnary (a dict)
            a grain type, disk or square (a float)
        Output :
            Nothing, but a temporary grain is generated (a grain_tempo)
    """
    n_border = 20 #number of vertices
    L_border = []
    L_border_x = []
    L_border_y = []
    L_r = []
    L_theta_r = []
    if Type == 'Disk':
        #Build the border
        for i in range(n_border):
            theta = 2*math.pi*i/n_border
            p = np.array(Center) + np.array([Lenght*math.cos(theta),Lenght*math.sin(theta)])
            L_border.append(p)
            L_border_x.append(p[0])
            L_border_y.append(p[1])
            L_r.append(Lenght)
            L_theta_r.append(theta)
        L_border.append(L_border[0])
        L_border_x.append(L_border_x[0])
        L_border_y.append(L_border_y[0])
        self.radius = Lenght
        self.theta = 0
        self.rho_surf = dict_material['rho_surf_disk']
        self.surface = math.pi*Lenght**2
        self.mass = math.pi*Lenght**2*dict_material['rho_surf_disk']
        self.inertia = self.mass*Lenght**2
    if Type == 'Square':
        if False:
            #initial random rotation
            Theta = random.uniform(0,math.pi/2)
            #Build the border
            P_rot = np.array([[math.cos(Theta), -math.sin(Theta)],
                              [math.sin(Theta),  math.cos(Theta)]]) #initial rotation
            for i in range(n_border):
                angle = 2*math.pi*i/n_border #angle of the vertex
                angle_to_determine_R = angle%(math.pi/2)
                if angle_to_determine_R > math.pi/4:
                    angle_to_determine_R = angle_to_determine_R - math.pi/2
                p = np.array([Lenght/2/math.cos(angle_to_determine_R),0]) #compute the radius, considering the grain as a square
                P_rot_p = np.array([[math.cos(angle), -math.sin(angle)],
                                    [math.sin(angle),  math.cos(angle)]])
                p = np.dot(P_rot_p,p)
                p = np.dot(P_rot,p)
                p = p + np.array(Center)
                L_border.append(p)
                L_border_x.append(p[0])
                L_border_y.append(p[1])
                L_r.append(Lenght/2/math.cos(angle_to_determine_R))
                angle_r = Theta + angle
                while angle_r >= 2*math.pi:
                    angle_r = angle_r - 2*math.pi
                while angle_r < 0:
                    angle_r = angle_r + 2*math.pi
                L_theta_r.append(angle_r)
            L_border.append(L_border[0])
            L_border_x.append(L_border_x[0])
            L_border_y.append(L_border_y[0])
            self.dimension = Lenght
            self.theta = Theta
            self.rho_surf = dict_material['rho_surf_square']
            self.surface = Lenght**2
            self.mass = Lenght**2*dict_material['rho_surf_square']
            self.inertia = 1/6*self.mass*Lenght**2
        else :
            #Build the border
            for i in range(n_border):
                theta = 2*math.pi*i/n_border
                p = np.array(Center) + np.array([Lenght/2*math.cos(theta),Lenght/2*math.sin(theta)])
                L_border.append(p)
                L_border_x.append(p[0])
                L_border_y.append(p[1])
                L_r.append(Lenght/2)
                L_theta_r.append(theta)
            L_border.append(L_border[0])
            L_border_x.append(L_border_x[0])
            L_border_y.append(L_border_y[0])
            self.dimension = Lenght/2
            self.theta = 0
            self.rho_surf = dict_material['rho_surf_disk']
            self.surface = math.pi*(Lenght/2)**2
            self.mass = math.pi*(Lenght/2)**2*dict_material['rho_surf_disk']
            self.inertia = self.mass*(Lenght/2)**2

    #save
    self.id = ID
    self.type = Type
    self.center = np.array(Center)
    self.l_border = L_border
    self.l_border_x = L_border_x
    self.l_border_y = L_border_y
    self.l_theta_r = L_theta_r
    self.l_r = L_r
    self.r_min = min(L_r)
    self.r_max = max(L_r)
    self.y = dict_material['Y']
    self.nu = dict_material['nu']
    self.g = dict_material['Y']/2/(1+dict_material['nu']) #shear modulus
    self.fx = 0
    self.fy = 0
    self.v = np.array([0,0])
    self.w = 0

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

  def add_F(self, F, p_application):
      """
      Add a force to the grain.

        Input :
            itself (a grain_tempo)
            a force applied (a 1 x 2 numpy array)
            a application point (a 1 x 2 numpy array)
        Output :
            Nothing, but attributes are updated (three floats)
      """
      self.fx = self.fx + F[0]
      self.fy = self.fy + F[1]
      v1 = np.array([p_application[0]-self.center[0], p_application[1]-self.center[1], 0])
      v2 = np.array([F[0], F[1], 0])
      self.mz = self.mz + np.cross(v1,v2)[2]

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

  def init_F_control(self,g):
      """
      Initialize the force applied to the grain.

      A gravity is assumed.

        Input :
            itself (a grain_tempo)
            a gravity value (a float)
        Output :
            Nothing, but attributes concerning the force applied are initialized (three floats)
      """
      self.fx = 0
      self.fy = -g*self.mass
      self.mz = 0

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

  def euler_semi_implicite(self,dt_DEM,factor):
    """
    Move the grain following a semi implicit euler scheme.

        Input :
            itself (a grain_tempo)
            a time step (a float)
            a factor to limite the displacement (a float)
        Output :
            Nothing, but the grain is moved
    """
    #translation
    a_i = np.array([self.fx,self.fy])/self.mass
    self.v = self.v + a_i*dt_DEM
    if self.type == 'Disk':
        if np.linalg.norm(self.v) > self.radius*factor/dt_DEM: #limitation of the speed
            self.v = self.v * self.radius*factor/dt_DEM/np.linalg.norm(self.v)
    elif self.type == 'Square':
        if np.linalg.norm(self.v) > self.dimension*factor/dt_DEM: #limitation of the speed
            self.v = self.v * self.dimension*factor/dt_DEM/np.linalg.norm(self.v)
    for i in range(len(self.l_border)):
        self.l_border[i] = self.l_border[i] + self.v*dt_DEM
        self.l_border_x[i] = self.l_border_x[i] + self.v[0]*dt_DEM
        self.l_border_y[i] = self.l_border_y[i] + self.v[1]*dt_DEM
    self.center = self.center + self.v*dt_DEM

    #rotation
    dw_i = self.mz/self.inertia
    self.w = self.w + dw_i*dt_DEM
    self.theta = self.theta + self.w*dt_DEM
    for i_theta_r in range(len(self.l_theta_r)) :
        theta_r = self.l_theta_r[i_theta_r]
        theta_r = theta_r + self.w*dt_DEM
        while theta_r >= 2*math.pi:
            theta_r = theta_r - 2*math.pi
        while theta_r < 0 :
            theta_r = theta_r + 2*math.pi
        self.l_theta_r[i_theta_r] = theta_r
    #rigib body rotation
    for i in range(len(self.l_border)):
        p = self.l_border[i] - self.center
        Rot_Matrix = np.array([[math.cos(self.w*dt_DEM), -math.sin(self.w*dt_DEM)],
                               [math.sin(self.w*dt_DEM),  math.cos(self.w*dt_DEM)]])
        p = np.dot(Rot_Matrix,p)
        self.l_border[i] = p + self.center
        self.l_border_x[i] = p[0] + self.center[0]
        self.l_border_y[i] = p[1] + self.center[1]

Methods

def add_F(self, F, p_application)

Add a force to the grain.

Input : itself (a grain_tempo) a force applied (a 1 x 2 numpy array) a application point (a 1 x 2 numpy array) Output : Nothing, but attributes are updated (three floats)

Expand source code
def add_F(self, F, p_application):
    """
    Add a force to the grain.

      Input :
          itself (a grain_tempo)
          a force applied (a 1 x 2 numpy array)
          a application point (a 1 x 2 numpy array)
      Output :
          Nothing, but attributes are updated (three floats)
    """
    self.fx = self.fx + F[0]
    self.fy = self.fy + F[1]
    v1 = np.array([p_application[0]-self.center[0], p_application[1]-self.center[1], 0])
    v2 = np.array([F[0], F[1], 0])
    self.mz = self.mz + np.cross(v1,v2)[2]
def euler_semi_implicite(self, dt_DEM, factor)

Move the grain following a semi implicit euler scheme.

Input :
    itself (a grain_tempo)
    a time step (a float)
    a factor to limite the displacement (a float)
Output :
    Nothing, but the grain is moved
Expand source code
def euler_semi_implicite(self,dt_DEM,factor):
  """
  Move the grain following a semi implicit euler scheme.

      Input :
          itself (a grain_tempo)
          a time step (a float)
          a factor to limite the displacement (a float)
      Output :
          Nothing, but the grain is moved
  """
  #translation
  a_i = np.array([self.fx,self.fy])/self.mass
  self.v = self.v + a_i*dt_DEM
  if self.type == 'Disk':
      if np.linalg.norm(self.v) > self.radius*factor/dt_DEM: #limitation of the speed
          self.v = self.v * self.radius*factor/dt_DEM/np.linalg.norm(self.v)
  elif self.type == 'Square':
      if np.linalg.norm(self.v) > self.dimension*factor/dt_DEM: #limitation of the speed
          self.v = self.v * self.dimension*factor/dt_DEM/np.linalg.norm(self.v)
  for i in range(len(self.l_border)):
      self.l_border[i] = self.l_border[i] + self.v*dt_DEM
      self.l_border_x[i] = self.l_border_x[i] + self.v[0]*dt_DEM
      self.l_border_y[i] = self.l_border_y[i] + self.v[1]*dt_DEM
  self.center = self.center + self.v*dt_DEM

  #rotation
  dw_i = self.mz/self.inertia
  self.w = self.w + dw_i*dt_DEM
  self.theta = self.theta + self.w*dt_DEM
  for i_theta_r in range(len(self.l_theta_r)) :
      theta_r = self.l_theta_r[i_theta_r]
      theta_r = theta_r + self.w*dt_DEM
      while theta_r >= 2*math.pi:
          theta_r = theta_r - 2*math.pi
      while theta_r < 0 :
          theta_r = theta_r + 2*math.pi
      self.l_theta_r[i_theta_r] = theta_r
  #rigib body rotation
  for i in range(len(self.l_border)):
      p = self.l_border[i] - self.center
      Rot_Matrix = np.array([[math.cos(self.w*dt_DEM), -math.sin(self.w*dt_DEM)],
                             [math.sin(self.w*dt_DEM),  math.cos(self.w*dt_DEM)]])
      p = np.dot(Rot_Matrix,p)
      self.l_border[i] = p + self.center
      self.l_border_x[i] = p[0] + self.center[0]
      self.l_border_y[i] = p[1] + self.center[1]
def init_F_control(self, g)

Initialize the force applied to the grain.

A gravity is assumed.

Input : itself (a grain_tempo) a gravity value (a float) Output : Nothing, but attributes concerning the force applied are initialized (three floats)

Expand source code
def init_F_control(self,g):
    """
    Initialize the force applied to the grain.

    A gravity is assumed.

      Input :
          itself (a grain_tempo)
          a gravity value (a float)
      Output :
          Nothing, but attributes concerning the force applied are initialized (three floats)
    """
    self.fx = 0
    self.fy = -g*self.mass
    self.mz = 0