Package Create_IC

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

This file contains ??.nt functions used in the simulation.

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

This file contains ??.nt functions used in the simulation.
"""

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

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

#Own
import Create_IC.Grain_ic
import Create_IC.Contact_gg_ic
import Create_IC.Contact_gw_ic
import Grain

#-------------------------------------------------------------------------------
#Function
#-------------------------------------------------------------------------------

def LG_tempo(dict_algorithm, dict_geometry, dict_ic, dict_material, dict_sample, dict_sollicitation, 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
    """
    #define the y_max for the grains generation
    radius_mean = 0
    for i in range(len(dict_geometry['L_R'])):
        radius_mean = radius_mean + dict_geometry['L_R'][i]*dict_geometry['L_percentage_R'][i]
    dy_creation = dict_geometry['N_grain']/dict_ic['n_generation']*dict_ic['factor_ymax_box']*(2*radius_mean)**2/(dict_sample['x_box_max']-dict_sample['x_box_min'])
    dict_sample['dy_creation'] = dy_creation

    #plan the grains generation
    L_n_grain_radius_ite = []
    L_n_grain_radius_final = []
    L_n_grain_radius_done = []
    for percentage in dict_geometry['L_percentage_R']:
        L_n_grain_radius_ite.append(round(dict_geometry['N_grain']*percentage/dict_ic['n_generation'],0))
        L_n_grain_radius_final.append(round(dict_geometry['N_grain']*percentage,0))
        L_n_grain_radius_done.append(0)
    dict_ic['L_n_grain_radius_ite'] = L_n_grain_radius_ite
    dict_ic['L_n_grain_radius_final'] = L_n_grain_radius_final
    dict_ic['L_n_grain_radius_done'] = L_n_grain_radius_done

    #Creation of grains
    #grains generation is decomposed in several steps (creation of grain then settlement)
    dict_ic['i_DEM_IC']  = 0
    dict_ic['L_L_g_tempo'] = []
    dict_sample['y_box_min_ic'] = dict_sample['y_box_min']
    dict_ic['last_id'] = 0

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

    for i_generation in range(1,dict_ic['n_generation']+1) :

        print(f'Generation {i_generation} of grains')

        #add elements in dicts
        dict_ic['L_g_tempo'] = []
        dict_ic['i_generation'] = i_generation

        #create not overlaping grains
        Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, simulation_report)

        #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 dict_ic['L_g_tempo']:
            if grain.center[1] + grain.radius > y_max:
                y_max = grain.center[1] + grain.radius

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

        DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, simulation_report)

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

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

    print('Combine generations of grains')

    dict_ic['i_generation'] = dict_ic['n_generation']+1

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

    DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, simulation_report)

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

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

def DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, 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 simultion report (a report)
        Output :
            Nothing, but initial condition dictionnary is updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    if dict_ic['i_generation'] == dict_ic['n_generation']+1 :
        i_update_neighborhoods = dict_ic['i_update_neighborhoods_com']
        y_min = dict_sample['y_box_min']
    else :
        i_update_neighborhoods = dict_ic['i_update_neighborhoods_gen']
        y_min = dict_sample['y_box_min_ic']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    i_DEM_0 = dict_ic['i_DEM_IC']
    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 dict_ic['L_g_tempo']:
        Force_stop = Force_stop + 0.5*grain.mass*dict_sollicitation['gravity']
        Ecin_stop = Ecin_stop + 0.5*grain.mass*(dict_ic['Ecin_ratio_IC']*grain.radius/dict_ic['dt_DEM_IC'])**2

    while DEM_loop_statut :

        dict_ic['i_DEM_IC'] = dict_ic['i_DEM_IC'] + 1

        #Contact detection
        if (dict_ic['i_DEM_IC']-i_DEM_0-1) % i_update_neighborhoods  == 0:
            Contact_gg_ic.Update_Neighborhoods(dict_ic)
        Contact_gg_ic.Grains_contact_Neighborhoods(dict_ic,dict_material)

        # Detection of contacts between grain and walls
        if (dict_ic['i_DEM_IC']-i_DEM_0-1) % i_update_neighborhoods  == 0:
            wall_neighborhood = Contact_gw_ic.Update_wall_Neighborhoods(dict_ic['L_g_tempo'],dict_ic['factor_neighborhood_IC'],dict_sample['x_box_min'],dict_sample['x_box_max'],y_min,dict_sample['y_box_max'])
        Contact_gw_ic.Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood,dict_sample['x_box_min'],dict_sample['x_box_max'],y_min,dict_sample['y_box_max'], dict_ic, dict_material)

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

        #Move grains
        for grain in dict_ic['L_g_tempo']:
            grain.euler_semi_implicite(dict_ic['dt_DEM_IC'],10*dict_ic['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] < dict_sample['x_box_min'] :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[0] > dict_sample['x_box_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] > dict_sample['y_box_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
        dict_sample['y_box_max'], Fv = Control_y_max_NR(dict_sample['y_box_max'],dict_sollicitation['Vertical_Confinement_Force'],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(dict_sample['y_box_max'])

        if dict_ic['i_DEM_IC'] % dict_ic['i_print_plot_IC'] ==0:
            if dict_sollicitation['gravity'] > 0 :
                print('i_DEM',dict_ic['i_DEM_IC'],'and Ecin',int(100*Ecin/Ecin_stop),'% and Force',int(100*F/Force_stop),'% and Confinement',int(100*Fv/dict_sollicitation['Vertical_Confinement_Force']),'%')
            else :
                print('i_DEM',dict_ic['i_DEM_IC'],'and Ecin',int(100*Ecin/Ecin_stop),'% and Confinement',int(100*Fv/dict_sollicitation['Vertical_Confinement_Force']),'%')
            if dict_ic['Debug_DEM'] :
                Plot_Config_Loaded(dict_ic['L_g_tempo'],dict_sample['x_box_min'],dict_sample['x_box_max'],y_min,dict_sample['y_box_max'],dict_ic['i_DEM_IC'])

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

    #Update dict
    dict_ic['L_L_g_tempo'].append(dict_ic['L_g_tempo'].copy())

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

def Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, 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
    """
    #Parameters for the method
    n_not_created = 0
    L_n_grain_radius = []
    if dict_ic['i_generation'] < dict_ic['n_generation']+1:
        for i in range(len(dict_ic['L_n_grain_radius_ite'])):
            L_n_grain_radius.append(dict_ic['L_n_grain_radius_ite'][i]*dict_ic['i_generation'])
    else :
        L_n_grain_radius = dict_ic['L_n_grain_radius_final']

    for i in range(len(dict_geometry['L_R'])):
        radius = dict_geometry['L_R'][i]
        n_grain = L_n_grain_radius[i]
        n_grain_done = dict_ic['L_n_grain_radius_done'][i]
        last_id_grain_created = dict_ic['last_id']
        for id_grain in range(last_id_grain_created, int(last_id_grain_created + n_grain - n_grain_done)):
            i_test = 0
            grain_created = False
            while (not grain_created) and i_test < dict_ic['N_test_max']:
                i_test = i_test + 1
                center = np.array([random.uniform(dict_sample['x_box_min']+1.1*radius,dict_sample['x_box_max']-1.1*radius),random.uniform(dict_sample['y_box_min_ic']+1.1*radius,dict_sample['y_box_min_ic'] + dict_sample['dy_creation'])])
                g_tempo = Grain_ic.Grain_Tempo(id_grain-n_not_created, center, radius, dict_material)
                grain_created = True
                for grain in dict_ic['L_g_tempo']:
                    if Contact_gg_ic.Grains_contact_f(g_tempo,grain):
                        grain_created = False
            if i_test == dict_ic['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 :
                dict_ic['L_g_tempo'].append(g_tempo)
                dict_ic['L_n_grain_radius_done'][i] = dict_ic['L_n_grain_radius_done'][i] + 1
                dict_ic['last_id'] = dict_ic['last_id'] + 1

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

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 = grain.center[1] + grain.radius

        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].radius)
    y_max = y_max - (Force/k)**(2/3)

    return y_max

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

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/Configuration/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_material, dict_sample):
    """
    Create a real grain 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 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']:

        L_r = []
        L_theta_r = []
        L_border = []
        L_border_x = []
        L_border_y = []
        for i in range(dict_sample['grain_discretisation']):
            theta = i/dict_sample['grain_discretisation']*2*math.pi
            L_r.append(grain_tempo.radius)
            L_theta_r.append(theta)
            L_border.append(grain_tempo.center + grain_tempo.radius*np.array([math.cos(theta), math.sin(theta)]))
            L_border_x.append(grain_tempo.center[0] + grain_tempo.radius*math.cos(theta))
            L_border_y.append(grain_tempo.center[1] + grain_tempo.radius*math.sin(theta))
        L_border.append(L_border[0])
        L_border_x.append(L_border_x[0])
        L_border_y.append(L_border_y[0])

        dict_ic_to_real = {
        'Id' : grain_tempo.id,
        'Center' : grain_tempo.center,
        'L_r' : L_r,
        'L_theta_r' : L_theta_r,
        'L_border' : L_border,
        'L_border_x' : L_border_x,
        'L_border_y' : L_border_y,
        'Y' : grain_tempo.y,
        'Nu' : grain_tempo.nu,
        'Rho_surf' : grain_tempo.rho_surf,
        'Surface' : grain_tempo.surface,
        'Mass' : grain_tempo.mass,
        'Inertia' : grain_tempo.inertia
        }
        #create real grain
        L_g.append(Grain.Grain(dict_ic_to_real, dict_material, dict_sample))

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

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

Sub-modules

Create_IC.Contact_gg_ic

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

Create_IC.Contact_gw_ic

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

Create_IC.Grain_ic

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

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, 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, 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
    """
    #Parameters for the method
    n_not_created = 0
    L_n_grain_radius = []
    if dict_ic['i_generation'] < dict_ic['n_generation']+1:
        for i in range(len(dict_ic['L_n_grain_radius_ite'])):
            L_n_grain_radius.append(dict_ic['L_n_grain_radius_ite'][i]*dict_ic['i_generation'])
    else :
        L_n_grain_radius = dict_ic['L_n_grain_radius_final']

    for i in range(len(dict_geometry['L_R'])):
        radius = dict_geometry['L_R'][i]
        n_grain = L_n_grain_radius[i]
        n_grain_done = dict_ic['L_n_grain_radius_done'][i]
        last_id_grain_created = dict_ic['last_id']
        for id_grain in range(last_id_grain_created, int(last_id_grain_created + n_grain - n_grain_done)):
            i_test = 0
            grain_created = False
            while (not grain_created) and i_test < dict_ic['N_test_max']:
                i_test = i_test + 1
                center = np.array([random.uniform(dict_sample['x_box_min']+1.1*radius,dict_sample['x_box_max']-1.1*radius),random.uniform(dict_sample['y_box_min_ic']+1.1*radius,dict_sample['y_box_min_ic'] + dict_sample['dy_creation'])])
                g_tempo = Grain_ic.Grain_Tempo(id_grain-n_not_created, center, radius, dict_material)
                grain_created = True
                for grain in dict_ic['L_g_tempo']:
                    if Contact_gg_ic.Grains_contact_f(g_tempo,grain):
                        grain_created = False
            if i_test == dict_ic['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 :
                dict_ic['L_g_tempo'].append(g_tempo)
                dict_ic['L_n_grain_radius_done'][i] = dict_ic['L_n_grain_radius_done'][i] + 1
                dict_ic['last_id'] = dict_ic['last_id'] + 1
def DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, 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 simultion report (a report)
Output :
    Nothing, but initial condition dictionnary is updated
Expand source code
def DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, 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 simultion report (a report)
        Output :
            Nothing, but initial condition dictionnary is updated
    """
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.
    #load data needed
    if dict_ic['i_generation'] == dict_ic['n_generation']+1 :
        i_update_neighborhoods = dict_ic['i_update_neighborhoods_com']
        y_min = dict_sample['y_box_min']
    else :
        i_update_neighborhoods = dict_ic['i_update_neighborhoods_gen']
        y_min = dict_sample['y_box_min_ic']
    #-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.

    i_DEM_0 = dict_ic['i_DEM_IC']
    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 dict_ic['L_g_tempo']:
        Force_stop = Force_stop + 0.5*grain.mass*dict_sollicitation['gravity']
        Ecin_stop = Ecin_stop + 0.5*grain.mass*(dict_ic['Ecin_ratio_IC']*grain.radius/dict_ic['dt_DEM_IC'])**2

    while DEM_loop_statut :

        dict_ic['i_DEM_IC'] = dict_ic['i_DEM_IC'] + 1

        #Contact detection
        if (dict_ic['i_DEM_IC']-i_DEM_0-1) % i_update_neighborhoods  == 0:
            Contact_gg_ic.Update_Neighborhoods(dict_ic)
        Contact_gg_ic.Grains_contact_Neighborhoods(dict_ic,dict_material)

        # Detection of contacts between grain and walls
        if (dict_ic['i_DEM_IC']-i_DEM_0-1) % i_update_neighborhoods  == 0:
            wall_neighborhood = Contact_gw_ic.Update_wall_Neighborhoods(dict_ic['L_g_tempo'],dict_ic['factor_neighborhood_IC'],dict_sample['x_box_min'],dict_sample['x_box_max'],y_min,dict_sample['y_box_max'])
        Contact_gw_ic.Grains_Polyhedral_Wall_contact_Neighborhood(wall_neighborhood,dict_sample['x_box_min'],dict_sample['x_box_max'],y_min,dict_sample['y_box_max'], dict_ic, dict_material)

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

        #Move grains
        for grain in dict_ic['L_g_tempo']:
            grain.euler_semi_implicite(dict_ic['dt_DEM_IC'],10*dict_ic['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] < dict_sample['x_box_min'] :
                L_ig_to_delete.append(id_grain)
            elif dict_ic['L_g_tempo'][id_grain].center[0] > dict_sample['x_box_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] > dict_sample['y_box_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
        dict_sample['y_box_max'], Fv = Control_y_max_NR(dict_sample['y_box_max'],dict_sollicitation['Vertical_Confinement_Force'],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(dict_sample['y_box_max'])

        if dict_ic['i_DEM_IC'] % dict_ic['i_print_plot_IC'] ==0:
            if dict_sollicitation['gravity'] > 0 :
                print('i_DEM',dict_ic['i_DEM_IC'],'and Ecin',int(100*Ecin/Ecin_stop),'% and Force',int(100*F/Force_stop),'% and Confinement',int(100*Fv/dict_sollicitation['Vertical_Confinement_Force']),'%')
            else :
                print('i_DEM',dict_ic['i_DEM_IC'],'and Ecin',int(100*Ecin/Ecin_stop),'% and Confinement',int(100*Fv/dict_sollicitation['Vertical_Confinement_Force']),'%')
            if dict_ic['Debug_DEM'] :
                Plot_Config_Loaded(dict_ic['L_g_tempo'],dict_sample['x_box_min'],dict_sample['x_box_max'],y_min,dict_sample['y_box_max'],dict_ic['i_DEM_IC'])

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

    #Update dict
    dict_ic['L_L_g_tempo'].append(dict_ic['L_g_tempo'].copy())
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_material, dict_sample)

Create a real grain 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 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_material, dict_sample):
    """
    Create a real grain 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 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']:

        L_r = []
        L_theta_r = []
        L_border = []
        L_border_x = []
        L_border_y = []
        for i in range(dict_sample['grain_discretisation']):
            theta = i/dict_sample['grain_discretisation']*2*math.pi
            L_r.append(grain_tempo.radius)
            L_theta_r.append(theta)
            L_border.append(grain_tempo.center + grain_tempo.radius*np.array([math.cos(theta), math.sin(theta)]))
            L_border_x.append(grain_tempo.center[0] + grain_tempo.radius*math.cos(theta))
            L_border_y.append(grain_tempo.center[1] + grain_tempo.radius*math.sin(theta))
        L_border.append(L_border[0])
        L_border_x.append(L_border_x[0])
        L_border_y.append(L_border_y[0])

        dict_ic_to_real = {
        'Id' : grain_tempo.id,
        'Center' : grain_tempo.center,
        'L_r' : L_r,
        'L_theta_r' : L_theta_r,
        'L_border' : L_border,
        'L_border_x' : L_border_x,
        'L_border_y' : L_border_y,
        'Y' : grain_tempo.y,
        'Nu' : grain_tempo.nu,
        'Rho_surf' : grain_tempo.rho_surf,
        'Surface' : grain_tempo.surface,
        'Mass' : grain_tempo.mass,
        'Inertia' : grain_tempo.inertia
        }
        #create real grain
        L_g.append(Grain.Grain(dict_ic_to_real, dict_material, dict_sample))

    #Add element in dict
    dict_sample['L_g'] = L_g
def LG_tempo(dict_algorithm, dict_geometry, dict_ic, dict_material, dict_sample, dict_sollicitation, 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_sollicitation, 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
    """
    #define the y_max for the grains generation
    radius_mean = 0
    for i in range(len(dict_geometry['L_R'])):
        radius_mean = radius_mean + dict_geometry['L_R'][i]*dict_geometry['L_percentage_R'][i]
    dy_creation = dict_geometry['N_grain']/dict_ic['n_generation']*dict_ic['factor_ymax_box']*(2*radius_mean)**2/(dict_sample['x_box_max']-dict_sample['x_box_min'])
    dict_sample['dy_creation'] = dy_creation

    #plan the grains generation
    L_n_grain_radius_ite = []
    L_n_grain_radius_final = []
    L_n_grain_radius_done = []
    for percentage in dict_geometry['L_percentage_R']:
        L_n_grain_radius_ite.append(round(dict_geometry['N_grain']*percentage/dict_ic['n_generation'],0))
        L_n_grain_radius_final.append(round(dict_geometry['N_grain']*percentage,0))
        L_n_grain_radius_done.append(0)
    dict_ic['L_n_grain_radius_ite'] = L_n_grain_radius_ite
    dict_ic['L_n_grain_radius_final'] = L_n_grain_radius_final
    dict_ic['L_n_grain_radius_done'] = L_n_grain_radius_done

    #Creation of grains
    #grains generation is decomposed in several steps (creation of grain then settlement)
    dict_ic['i_DEM_IC']  = 0
    dict_ic['L_L_g_tempo'] = []
    dict_sample['y_box_min_ic'] = dict_sample['y_box_min']
    dict_ic['last_id'] = 0

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

    for i_generation in range(1,dict_ic['n_generation']+1) :

        print(f'Generation {i_generation} of grains')

        #add elements in dicts
        dict_ic['L_g_tempo'] = []
        dict_ic['i_generation'] = i_generation

        #create not overlaping grains
        Create_grains(dict_ic, dict_geometry, dict_sample, dict_material, simulation_report)

        #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 dict_ic['L_g_tempo']:
            if grain.center[1] + grain.radius > y_max:
                y_max = grain.center[1] + grain.radius

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

        DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, simulation_report)

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

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

    print('Combine generations of grains')

    dict_ic['i_generation'] = dict_ic['n_generation']+1

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

    DEM_loading(dict_algorithm, dict_ic, dict_material, dict_sample, dict_sollicitation, simulation_report)

    simulation_report.write_and_print(str(len(dict_ic['L_g_tempo']))+' / '+str(dict_geometry['N_grain'])+' grains have been created\n','\n'+str(len(dict_ic['L_g_tempo']))+' / '+str(dict_geometry['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/Configuration/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 = grain.center[1] + grain.radius

        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].radius)
    y_max = y_max - (Force/k)**(2/3)

    return y_max
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