Module Grain
@author: Alexandre Sac–Morane alexandre.sac-morane@uclouvain.be
The goal of this file is to define a new class. The new class is about the grains
Expand source code
# -*- coding: utf-8 -*-
"""
@author: Alexandre Sac--Morane
alexandre.sac-morane@uclouvain.be
The goal of this file is to define a new class.
The new class is about the grains
"""
#-------------------------------------------------------------------------------
#Libs
#-------------------------------------------------------------------------------
import numpy as np
import math
import random
#Own functions and classes
import Owntools
#-------------------------------------------------------------------------------
#Class
#-------------------------------------------------------------------------------
class Grain:
#---------------------------------------------------------------------------
def __init__(self,id,radius,center,dict_material,dict_sample):
'''
Defining a disk grain
Input :
an id (an integer)
a radius (a float)
a center (a 2 x 1 numpy array)
a material dictionnary (a dictionnary)
a sample dictionnary (a dictionnary)
Output :
a grain (a grain)
'''
self.id = id
self.center = center
L_r = []
L_theta_r = []
L_border = []
L_border_x = []
L_border_y = []
for i in range(dict_sample['grain_discretisation']):
theta = 2*math.pi*i/dict_sample['grain_discretisation']
L_r.append(radius)
L_theta_r.append(theta)
L_border.append(np.array([center[0]+radius*math.cos(theta),center[1]+radius*math.sin(theta)]))
L_border_x.append(center[0]+radius*math.cos(theta))
L_border_y.append(center[1]+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])
#save description
self.r_mean = radius
self.l_r = L_r
self.l_theta_r = L_theta_r
self.l_border = L_border
self.l_border_x = L_border_x
self.l_border_y = L_border_y
#save initial
self.center_init = center.copy()
self.l_border_x_init = L_border_x.copy()
self.l_border_y_init = L_border_y.copy()
self.y = dict_material['Y']
self.nu = dict_material['nu']
self.build_etai_M(dict_material,dict_sample)
#---------------------------------------------------------------------------
def build_etai_M(self,dict_material,dict_sample):
'''
Build the phase field for one grain.
A cosine profile is assumed (see https://mooseframework.inl.gov/source/ics/SmoothCircleIC.html).
Input :
itself (a grain)
a material dictionnary (a dictionnary)
a sample dictionnary (a dictionnary)
Output :
Nothing but the grain gets a new attribute (a n_y x n_x numpy array)
'''
#initilization
self.etai_M = np.array(np.zeros((len(dict_sample['y_L']),len(dict_sample['x_L']))))
#extract a spatial zone
x_min = min(self.l_border_x)-dict_material['w']
x_max = max(self.l_border_x)+dict_material['w']
y_min = min(self.l_border_y)-dict_material['w']
y_max = max(self.l_border_y)+dict_material['w']
#look for this part inside the global mesh
#create search list
x_L_search_min = abs(np.array(dict_sample['x_L'])-x_min)
x_L_search_max = abs(np.array(dict_sample['x_L'])-x_max)
y_L_search_min = abs(np.array(dict_sample['y_L'])-y_min)
y_L_search_max = abs(np.array(dict_sample['y_L'])-y_max)
#get index
i_x_min = list(x_L_search_min).index(min(x_L_search_min))
i_x_max = list(x_L_search_max).index(min(x_L_search_max))
i_y_min = list(y_L_search_min).index(min(y_L_search_min))
i_y_max = list(y_L_search_max).index(min(y_L_search_max))
for l in range(i_y_min,i_y_max+1):
for c in range(i_x_min,i_x_max+1):
y = dict_sample['y_L'][-1-l]
x = dict_sample['x_L'][c]
p = np.array([x,y])
r = np.linalg.norm(self.center - p)
#look for the radius on this direction
if p[1]>self.center[1]:
theta = math.acos((p[0]-self.center[0])/np.linalg.norm(self.center-p))
else :
theta= 2*math.pi - math.acos((p[0]-self.center[0])/np.linalg.norm(self.center-p))
L_theta_R_i = list(abs(np.array(self.l_theta_r)-theta))
R = self.l_r[L_theta_R_i.index(min(L_theta_R_i))]
#build etai_M
self.etai_M[l][c] = Owntools.Cosine_Profile(R,r,dict_material['w'])
#---------------------------------------------------------------------------
def geometric_study(self,dict_sample):
'''
Searching limits of the grain
Not best method but first approach
We iterate on y constant, we look for a value under and over 0.5
If both conditions are verified, there is a limit at this y
Same with iteration on x constant
Once the border of the grain is defined, a Monte Carlo method is used to computed some geometrical properties.
Input :
itself (a grain)
a sample dictionnary (a dictionnary)
Output :
Nothing but the grain gets new attributes
r_min : the minimum radius of the grain (a float)
r_max : the maximum radius of the grain (a float)
r_mean : the mean radius of the grain (a float)
l_r : a list of radius of the grain, work with l_theta_r (a list)
l_theta_r : a list of angle to see the distribution of the radius of the grain, work with l_r (a list)
surface : the surface of the grain (a float)
center : the coordinate of the grain center (a 2 x 1 numpy array)
l_border_x : the list of the coordinate x of the grain vertices (a list)
l_border_y : the list of the coordinate y of the grain vertices (a list)
l_border : the list of the coordinate [x,y] of the grain vertices (a list)
'''
#-------------------------------------------------------------------------
#load data needed
n = dict_sample['grain_discretisation']
x_L = dict_sample['x_L']
y_L = dict_sample['y_L']
#-------------------------------------------------------------------------
L_border_old = []
for y_i in range(len(y_L)):
L_extract_x = self.etai_M[y_i][:]
if id == 1:
L_extract_x = list(L_extract_x)
L_extract_x.reverse()
if max(L_extract_x)>0.5 and min(L_extract_x)<0.5:
y_intersect = y_L[len(y_L)-1-y_i]
for x_i in range(len(x_L)-1):
if (L_extract_x[x_i]-0.5)*(L_extract_x[x_i+1]-0.5)<0:
x_intersect = (0.5-L_extract_x[x_i])/(L_extract_x[x_i+1]-L_extract_x[x_i])*\
(x_L[x_i+1]-x_L[x_i]) + x_L[x_i]
L_border_old.append(np.array([x_intersect,y_intersect]))
for x_i in range(len(x_L)):
L_extract_y = []
for y_i in range(len(y_L)):
L_extract_y.append(self.etai_M[y_i][x_i])
if max(L_extract_y)>0.5 and min(L_extract_y)<0.5:
x_intersect = x_L[x_i]
for y_i in range(len(y_L)-1):
if (L_extract_y[y_i]-0.5)*(L_extract_y[y_i+1]-0.5)<0:
y_intersect = (0.5-L_extract_y[y_i])/(L_extract_y[y_i+1]-L_extract_y[y_i])*\
(y_L[len(y_L)-1-y_i-1]-y_L[len(y_L)-1-y_i]) + y_L[len(y_L)-1-y_i]
L_border_old.append(np.array([x_intersect,y_intersect]))
#Adaptating
L_id_used = [0]
L_border = [L_border_old[0]]
HighValue = 100000000 #Large
current_node = L_border_old[0]
for j in range(1,len(L_border_old)):
L_d = list(np.zeros(len(L_border_old)))
for i in range(0,len(L_border_old)):
node = L_border_old[i]
if i not in L_id_used:
d = np.linalg.norm(node - current_node)
L_d[i] = d
else :
L_d[i] = HighValue #Value need to be larger than potential distance between node
index_nearest_node = L_d.index(min(L_d))
nearest_node = L_border_old[index_nearest_node]
current_node = nearest_node
L_border.append(nearest_node)
L_id_used.append(index_nearest_node)
#Correcting
L_d_final = []
for i in range(len(L_border)-1):
L_d_final.append(np.linalg.norm(L_border[i+1] - L_border[i]))
#look for really far points, we assume the first point is accurate
d_final_mean = np.mean(L_d_final)
while np.max(L_d_final) > 5 * d_final_mean : #5 here is an user choixe value
i_error = L_d_final.index(np.max(L_d_final))+1
simulation_report.write('Point '+str(L_border[i_error])+' is deleted because it is detected as an error\n')
L_border.pop(i_error)
L_id_used.pop(i_error)
L_d_final = []
for i in range(len(L_border)-1):
L_d_final.append(np.linalg.norm(L_border[i+1] - L_border[i]))
#-------------------------------------------------------------------------------
#Reduce the number of nodes for a grain
#-------------------------------------------------------------------------------
Perimeter = 0
for i_p in range(len(L_border)-1):
Perimeter = Perimeter + np.linalg.norm(L_border[i_p+1]-L_border[i_p])
Perimeter = Perimeter + np.linalg.norm(L_border[-1]-L_border[0])
distance_min = Perimeter/n
L_border_adapted = [L_border[0]]
for p in L_border[1:]:
distance = np.linalg.norm(p-L_border_adapted[-1])
if distance >= distance_min:
L_border_adapted.append(p)
L_border = L_border_adapted
L_border.append(L_border[0])
self.l_border = L_border
#-------------------------------------------------------------------------------
#Searching Surface, Center of mass and Inertia.
#Monte Carlo Method
#A box is defined, we take a random point and we look if it is inside or outside the grain
#Properties are the statistic times the box properties
#-------------------------------------------------------------------------------
min_max_defined = False
for p in L_border[:-1] :
if not min_max_defined:
box_min_x = p[0]
box_max_x = p[0]
box_min_y = p[1]
box_max_y = p[1]
min_max_defined = True
else:
if p[0] < box_min_x:
box_min_x = p[0]
elif p[0] > box_max_x:
box_max_x = p[0]
if p[1] < box_min_y:
box_min_y = p[1]
elif p[1] > box_max_y:
box_max_y = p[1]
N_MonteCarlo = 3000 #The larger it is, the more accurate it is
sigma = 1
M_Mass = 0
M_Center_Mass = np.array([0,0])
for i in range(N_MonteCarlo):
P = np.array([random.uniform(box_min_x,box_max_x),random.uniform(box_min_y,box_max_y)])
if self.P_is_inside(P):
M_Mass = M_Mass + sigma
M_Center_Mass = M_Center_Mass + sigma*P
Mass = (box_max_x-box_min_x)*(box_max_y-box_min_y)/N_MonteCarlo*M_Mass
Center_Mass = (box_max_x-box_min_x)*(box_max_y-box_min_y)/N_MonteCarlo*M_Center_Mass/Mass
#-------------------------------------------------------------------------------
#Updating the grain geometry and properties
#-------------------------------------------------------------------------------
L_R = []
L_theta_R = []
L_border_x = []
L_border_y = []
for p in L_border[:-1]:
L_R.append(np.linalg.norm(p-Center_Mass))
L_border_x.append(p[0])
L_border_y.append(p[1])
if (p-Center_Mass)[1] > 0:
theta = math.acos((p-Center_Mass)[0]/np.linalg.norm(p-Center_Mass))
else :
theta = 2*math.pi - math.acos((p-Center_Mass)[0]/np.linalg.norm(p-Center_Mass))
L_theta_R.append(theta)
L_border_x.append(L_border_x[0])
L_border_y.append(L_border_y[0])
#reorganize lists
L_R.reverse()
L_theta_R.reverse()
i_min_theta = L_theta_R.index(min(L_theta_R))
L_R = L_R[i_min_theta:]+L_R[:i_min_theta]
L_theta_R = L_theta_R[i_min_theta:]+L_theta_R[:i_min_theta]
self.r_min = np.min(L_R)
self.r_max = np.max(L_R)
self.r_mean = np.mean(L_R)
self.l_r = L_R
self.l_theta_r = L_theta_R
self.surface = Mass/sigma
self.center = Center_Mass
self.l_border_x = L_border_x
self.l_border_y = L_border_y
#-------------------------------------------------------------------------------
def P_is_inside(self,P):
'''Determine if a point P is inside of a grain
Make a slide on constant y. Every time a border is crossed, the point switches between in and out.
see Franklin 1994, see Alonso-Marroquin 2009
Input :
itself (a grain)
a point (a 2 x 1 numpy array)
Output :
True or False, depending on the fact that the point is inside the grain or not (a bool)
'''
counter = 0
for i_p_border in range(len(self.l_border)-1):
#consider only points if the coordinates frame the y-coordinate of the point
if (self.l_border[i_p_border][1]-P[1])*(self.l_border[i_p_border+1][1]-P[1]) < 0 :
x_border = self.l_border[i_p_border][0] + (self.l_border[i_p_border+1][0]-self.l_border[i_p_border][0])*(P[1]-self.l_border[i_p_border][1])/(self.l_border[i_p_border+1][1]-self.l_border[i_p_border][1])
if x_border > P[0] :
counter = counter + 1
if counter % 2 == 0:
return False
else :
return True
#-------------------------------------------------------------------------------
def Compute_sphericity(self, dict_algorithm):
'''Compute sphericity of the particle with five parameters.
The parameters used are the area, the diameter, the circle ratio, the perimeter and the width to length ratio sphericity.
See Zheng, J., Hryciw, R.D. (2015) Traditional soil particle sphericity, roundness and surface roughness by computational geometry, Geotechnique, Vol 65
Input :
itself (a grain)
an algorithm dictionnary (a dict)
Output :
Nothing, but the grain gets updated attributes (five floats)
'''
#Find the minimum circumscribing circle
#look for the two farthest and nearest points
MaxDistance = 0
for i_p in range(0,len(self.l_border)-2):
for j_p in range(i_p+1,len(self.l_border)-1):
Distance = np.linalg.norm(self.l_border[i_p]-self.l_border[j_p])
if Distance > MaxDistance :
ij_farthest = (i_p,j_p)
MaxDistance = Distance
#Trial circle
center_circumscribing = (self.l_border[ij_farthest[0]]+self.l_border[ij_farthest[1]])/2
radius_circumscribing = MaxDistance/2
Circumscribing_Found = True
Max_outside_distance = radius_circumscribing
for i_p in range(len(self.l_border)-1):
#there is a margin here because of the numerical approximation
if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > (1+dict_algorithm['sphericity_margin'])*radius_circumscribing and i_p not in ij_farthest: #vertex outside the trial circle
Circumscribing_Found = False
if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > Max_outside_distance:
k_outside_farthest = i_p
Max_outside_distance = np.linalg.norm(self.l_border[i_p]-center_circumscribing)
#The trial guess does not work
if not Circumscribing_Found:
L_ijk_circumscribing = [ij_farthest[0],ij_farthest[1],k_outside_farthest]
center_circumscribing, radius_circumscribing = FindCircleFromThreePoints(self.l_border[L_ijk_circumscribing[0]],self.l_border[L_ijk_circumscribing[1]],self.l_border[L_ijk_circumscribing[2]])
Circumscribing_Found = True
for i_p in range(len(self.l_border)-1):
#there is 1% margin here because of the numerical approximation
if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > (1+dict_algorithm['sphericity_margin'])*radius_circumscribing and i_p not in L_ijk_circumscribing: #vertex outside the circle computed
Circumscribing_Found = False
#see article for other case
if not Circumscribing_Found:
raise ValueError('This algorithm is not developped for this case...')
#look for length and width
length = MaxDistance
u_maxDistance = (self.l_border[ij_farthest[0]]-self.l_border[ij_farthest[1]])/np.linalg.norm(self.l_border[ij_farthest[0]]-self.l_border[ij_farthest[1]])
v_maxDistance = np.array([u_maxDistance[1], -u_maxDistance[0]])
MaxWidth = 0
for i_p in range(0,len(self.l_border)-2):
for j_p in range(i_p+1,len(self.l_border)-1):
Distance = abs(np.dot(self.l_border[i_p]-self.l_border[j_p],v_maxDistance))
if Distance > MaxWidth :
ij_width = (i_p,j_p)
MaxWidth = Distance
width = MaxWidth
#look for maximum inscribed circle
#discretisation of the grain
l_x_inscribing = np.linspace(min(self.l_border_x),max(self.l_border_x),dict_algorithm['n_spatial_inscribing'])
l_y_inscribing = np.linspace(min(self.l_border_y),max(self.l_border_y),dict_algorithm['n_spatial_inscribing'])
#creation of an Euclidean distance map to the nearest boundary vertex
map_inscribing = np.zeros((dict_algorithm['n_spatial_inscribing'],dict_algorithm['n_spatial_inscribing']))
#compute the map
for i_x in range(dict_algorithm['n_spatial_inscribing']):
for i_y in range(dict_algorithm['n_spatial_inscribing']):
p = np.array([l_x_inscribing[i_x], l_y_inscribing[-1-i_y]])
#work only if the point is inside the grain
if self.P_is_inside(p):
#look for the nearest vertex
MinDistance = None
for q in self.l_border[:-1]:
Distance = np.linalg.norm(p-q)
if MinDistance == None or Distance < MinDistance:
MinDistance = Distance
map_inscribing[-1-i_y][i_x] = MinDistance
else :
map_inscribing[-1-i_y][i_x] = 0
#look for the peak of the map
index_max = np.argmax(map_inscribing)
l = index_max//dict_algorithm['n_spatial_inscribing']
c = index_max%dict_algorithm['n_spatial_inscribing']
radius_inscribing = map_inscribing[l][c]
#Area Sphericity
SurfaceParticle = self.surface
SurfaceCircumscribing = math.pi*radius_circumscribing**2
AreaSphericity = SurfaceParticle / SurfaceCircumscribing
self.area_sphericity = AreaSphericity
#Diameter Sphericity
DiameterSameAreaParticle = 2*math.sqrt(self.surface/math.pi)
DiameterCircumscribing = radius_circumscribing*2
DiameterSphericity = DiameterSameAreaParticle / DiameterCircumscribing
self.diameter_sphericity = DiameterSphericity
#Circle Ratio Sphericity
DiameterInscribing = radius_inscribing*2
CircleRatioSphericity = DiameterInscribing / DiameterCircumscribing
self.circle_ratio_sphericity = CircleRatioSphericity
#Perimeter Sphericity
PerimeterSameAreaParticle = 2*math.sqrt(self.surface*math.pi)
PerimeterParticle = 0
for i in range(len(self.l_border)-1):
PerimeterParticle = PerimeterParticle + np.linalg.norm(self.l_border[i+1]-self.l_border[i])
PerimeterSphericity = PerimeterSameAreaParticle / PerimeterParticle
self.perimeter_sphericity = PerimeterSphericity
#Width to length ratio Spericity
WidthToLengthRatioSpericity = width / length
self.width_to_length_ratio_sphericity = WidthToLengthRatioSpericity
#---------------------------------------------------------------------------
def PFtoDEM_Multi(self,FileToRead,dict_algorithm,dict_sample):
'''
Read file from MOOSE simulation to reconstruct the phase field of the grain.
Input :
itself (a grain)
the name of the file to read (a string)
an algorithm dictionnary (a dictionnary)
a sample dictionnary (a dictionnary)
Output :
Nothing but the grain gets an updated attribute (a n_y x n_x numpy array)
'''
#--------------------------------------------------------------------------
#Global parameters
#---------------------------------------------------------------------------
self.etai_M = np.array(np.zeros((len(dict_sample['y_L']),len(dict_sample['x_L']))))
id_L = None
eta_selector_len = len(' <DataArray type="Float64" Name="etai')
end_len = len(' </DataArray>')
XYZ_selector_len = len(' <DataArray type="Float64" Name="Points"')
data_jump_len = len(' ')
for i_proc in range(dict_algorithm['np_proc']):
L_Work = [[], #X
[], #Y
[]] #etai
#---------------------------------------------------------------------------
#Reading file
#---------------------------------------------------------------------------
f = open(f'{FileToRead}_{i_proc}.vtu','r')
data = f.read()
f.close
lines = data.splitlines()
#iterations on line
for line in lines:
if line[0:eta_selector_len] == ' <DataArray type="Float64" Name="eta'+str(self.id):
id_L = 2
elif line[0:XYZ_selector_len] == ' <DataArray type="Float64" Name="Points"':
id_L = 0
elif (line[0:end_len] == ' </DataArray>' or line[0:len(' <InformationKey')] == ' <InformationKey') and id_L != None:
id_L = None
elif line[0:data_jump_len] == ' ' and id_L == 2: #Read etai
line = line[data_jump_len:]
c_start = 0
for c_i in range(0,len(line)):
if line[c_i]==' ':
c_end = c_i
L_Work[id_L].append(float(line[c_start:c_end]))
c_start = c_i+1
L_Work[id_L].append(float(line[c_start:]))
elif line[0:data_jump_len] == ' ' and id_L == 0: #Read [X, Y, Z]
line = line[data_jump_len:]
XYZ_temp = []
c_start = 0
for c_i in range(0,len(line)):
if line[c_i]==' ':
c_end = c_i
XYZ_temp.append(float(line[c_start:c_end]))
if len(XYZ_temp)==3:
L_Work[0].append(XYZ_temp[0])
L_Work[1].append(XYZ_temp[1])
XYZ_temp = []
c_start = c_i+1
XYZ_temp.append(float(line[c_start:]))
L_Work[0].append(XYZ_temp[0])
L_Work[1].append(XYZ_temp[1])
#Adaptating data and update of etai_M
for i in range(len(L_Work[0])):
#Interpolation method
L_dy = []
for y_i in dict_sample['y_L'] :
L_dy.append(abs(y_i - L_Work[1][i]))
L_dx = []
for x_i in dict_sample['x_L'] :
L_dx.append(abs(x_i - L_Work[0][i]))
self.etai_M[-1-list(L_dy).index(min(L_dy))][list(L_dx).index(min(L_dx))] = L_Work[2][i]
#---------------------------------------------------------------------------
def move_grain_rebuild(self,displacement,dict_material,dict_sample):
'''
Move the grain by updating the phase field of the grain.
The grain is deconstructed and then rebuilt. The mass conservation can not well verified.
It is advised to work with move_grain_interpolation().
Input :
itself (a grain)
the displacement asked (a 2 x 1 numpy array)
a material dictionnary (a dictionnary)
a sample dictionnary (a dictionnary)
Output :
Nothing but the grain gets an updated attribute (a n_y x n_x numpy array)
'''
self.center = self.center + displacement
for i in range(len(self.l_border)):
self.l_border[i] = self.l_border[i] + displacement
self.l_border_x[i] = self.l_border_x[i] + displacement[0]
self.l_border_y[i] = self.l_border_y[i] + displacement[1]
self.build_etai_M(dict_material,dict_sample)
#---------------------------------------------------------------------------
def move_grain_interpolation(self,displacement,dict_sample):
'''
Move the grain by updating the phase field of the grain.
An interpolation on the phase field is done. The mass conservation is better than with move_grain_rebuild().
Input :
itself (a grain)
the displacement asked (a 2 x 1 numpy array)
a sample dictionnary (a dictionnary)
Output :
Nothing but the grain gets an updated attribute (a n_y x n_x numpy array)
'''
self.center = self.center + displacement
for i in range(len(self.l_border)):
self.l_border[i] = self.l_border[i] + displacement
self.l_border_x[i] = self.l_border_x[i] + displacement[0]
self.l_border_y[i] = self.l_border_y[i] + displacement[1]
#displacement over x
dx = dict_sample['x_L'][1]-dict_sample['x_L'][0]
n_dx_disp_x = int(abs(displacement[0])//dx)
disp_x_remainder = abs(displacement[0])%dx
etai_M_old = self.etai_M.copy()
if np.sign(displacement[0]) > 0 : # +x direction
#dx*n_dx_disp_x translation
if n_dx_disp_x > 0:
for l in range(len(dict_sample['y_L'])):
self.etai_M[l][:n_dx_disp_x] = 0 #no information to translate so put equal to 0
self.etai_M[l][n_dx_disp_x:] = etai_M_old[l][:-n_dx_disp_x]
#disp_x_remainder translation
etai_M_old = self.etai_M.copy()
for l in range(len(dict_sample['y_L'])):
for c in range(1,len(dict_sample['x_L'])):
self.etai_M[l][c] = (etai_M_old[l][c]*(dx-disp_x_remainder) + etai_M_old[l][c-1]*disp_x_remainder)/dx
self.etai_M[l][0] = 0 #no information to translate so put equal to 0
else : # -x direction
#dx*n_dx_disp_x translation
if n_dx_disp_x > 0:
for l in range(len(dict_sample['y_L'])):
self.etai_M[l][-n_dx_disp_x:] = 0 #no information to translate so put equal to 0
self.etai_M[l][:-n_dx_disp_x] = etai_M_old[l][n_dx_disp_x:]
#disp_x_remainder translation
etai_M_old = self.etai_M.copy()
for l in range(len(dict_sample['y_L'])):
for c in range(len(dict_sample['x_L'])-1):
self.etai_M[l][c] = (etai_M_old[l][c]*(dx-disp_x_remainder) + etai_M_old[l][c+1]*disp_x_remainder)/dx
self.etai_M[l][0] = 0 #no information to translate so put equal to 0
#-------------------------------------------------------------------------------
#Functions
#-------------------------------------------------------------------------------
def Compute_overlap_2_grains(dict_sample):
'''
Compute the current overlap between two grains.
It is assumed the sample is composed by only 2 grains.
Input :
a sample dictionnary (a dictionnary)
Output :
Nothing but the sample dictionnary gets an updated value (a float)
'''
#2 grains
g1 = dict_sample['L_g'][0]
g2 = dict_sample['L_g'][1]
#compute overlap
#assume the normal n12 is +x axis
overlap = max(g1.l_border_x) - min(g2.l_border_x)
#Add element in dict
dict_sample['overlap'] = overlap
#-------------------------------------------------------------------------------
def Apply_overlap_target(dict_material,dict_sample,dict_sollicitation,dict_tracker):
'''
Move two grains to verify an asked overlap.
It is assumed the sample is composed by only 2 grains.
Input :
a material dictionnary (a dictionnary)
a sample dictionnary (a dictionnary)
a sollicitation dictionnary (a dictionnary)
Output :
Nothing but the sample dictionnary gets updated values (a grain)
'''
#compute the difference between real overlap and target value
delta_overlap = dict_sollicitation['overlap_target'] - dict_sample['overlap']
#save in tracker
dict_tracker['L_displacement'].append(delta_overlap)
dict_tracker['L_int_displacement'].append(dict_tracker['L_int_displacement'][-1] + delta_overlap)
#move grains to apply target overlap
dict_sample['L_g'][0].move_grain_interpolation(np.array([ delta_overlap/2,0]),dict_sample)
dict_sample['L_g'][1].move_grain_interpolation(np.array([-delta_overlap/2,0]),dict_sample)
#-------------------------------------------------------------------------------
def FindCircleFromThreePoints(P1,P2,P3):
'''
Compute the circumscribing circle of a triangle defined by three points.
https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input :
three points (a 2 x 1 numpy array)
Output :
a center (a 2 x 1 numpy array)
a radius (a float)
'''
# Line P1P2 is represented as ax + by = c and line P2P3 is represented as ex + fy = g
a, b, c = lineFromPoints(P1, P2)
e, f, g = lineFromPoints(P2, P3)
# Converting lines P1P2 and P2P3 to perpendicular bisectors.
#After this, L : ax + by = c and M : ex + fy = g
a, b, c = perpendicularBisectorFromLine(P1, P2, a, b, c)
e, f, g = perpendicularBisectorFromLine(P2, P3, e, f, g)
# The point of intersection of L and M gives the circumcenter
circumcenter = lineLineIntersection(a, b, c, e, f, g)
if np.linalg.norm(circumcenter - np.array([10**9,10**9])) == 0:
raise ValueError('The given points do not form a triangle and are collinear...')
else :
#compute the radius
radius = max([np.linalg.norm(P1-circumcenter), np.linalg.norm(P2-circumcenter), np.linalg.norm(P3-circumcenter)])
return circumcenter, radius
#-------------------------------------------------------------------------------
def lineFromPoints(P, Q):
'''
Function to find the line given two points
Used in FindCircleFromThreePoints().
The equation is c = ax + by.
https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input :
two points (a 2 x 1 numpy array)
Output :
three characteristic of the line (three floats)
'''
a = Q[1] - P[1]
b = P[0] - Q[0]
c = a * (P[0]) + b * (P[1])
return a, b, c
#-------------------------------------------------------------------------------
def perpendicularBisectorFromLine(P, Q, a, b, c):
'''
Function which converts the input line to its perpendicular bisector.
Used in FindCircleFromThreePoints().
The equation is c = ax + by.
https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input :
two points (a 2 x 1 numpy array)
three characteristic of the line (three floats)
Output :
three characteristic of the perpendicular bisector (three floats)
'''
mid_point = [(P[0] + Q[0])//2, (P[1] + Q[1])//2]
# c = -bx + ay
c = -b * (mid_point[0]) + a * (mid_point[1])
temp = a
a = -b
b = temp
return a, b, c
#-------------------------------------------------------------------------------
def lineLineIntersection(a1, b1, c1, a2, b2, c2):
'''
Returns the intersection point of two lines.
Used in FindCircleFromThreePoints().
https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input :
six characteristics of the line 1 and 2 (six floats)
Output :
the intersection point (a 2 x 1 numpy array)
'''
determinant = a1 * b2 - a2 * b1
if (determinant == 0):
# The lines are parallel.
return np.array([10**9,10**9])
else:
x = (b2 * c1 - b1 * c2)//determinant
y = (a1 * c2 - a2 * c1)//determinant
return np.array([x, y])
Functions
def Apply_overlap_target(dict_material, dict_sample, dict_sollicitation, dict_tracker)
-
Move two grains to verify an asked overlap.
It is assumed the sample is composed by only 2 grains.
Input : a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) a sollicitation dictionnary (a dictionnary) Output : Nothing but the sample dictionnary gets updated values (a grain)
Expand source code
def Apply_overlap_target(dict_material,dict_sample,dict_sollicitation,dict_tracker): ''' Move two grains to verify an asked overlap. It is assumed the sample is composed by only 2 grains. Input : a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) a sollicitation dictionnary (a dictionnary) Output : Nothing but the sample dictionnary gets updated values (a grain) ''' #compute the difference between real overlap and target value delta_overlap = dict_sollicitation['overlap_target'] - dict_sample['overlap'] #save in tracker dict_tracker['L_displacement'].append(delta_overlap) dict_tracker['L_int_displacement'].append(dict_tracker['L_int_displacement'][-1] + delta_overlap) #move grains to apply target overlap dict_sample['L_g'][0].move_grain_interpolation(np.array([ delta_overlap/2,0]),dict_sample) dict_sample['L_g'][1].move_grain_interpolation(np.array([-delta_overlap/2,0]),dict_sample)
def Compute_overlap_2_grains(dict_sample)
-
Compute the current overlap between two grains.
It is assumed the sample is composed by only 2 grains.
Input : a sample dictionnary (a dictionnary) Output : Nothing but the sample dictionnary gets an updated value (a float)
Expand source code
def Compute_overlap_2_grains(dict_sample): ''' Compute the current overlap between two grains. It is assumed the sample is composed by only 2 grains. Input : a sample dictionnary (a dictionnary) Output : Nothing but the sample dictionnary gets an updated value (a float) ''' #2 grains g1 = dict_sample['L_g'][0] g2 = dict_sample['L_g'][1] #compute overlap #assume the normal n12 is +x axis overlap = max(g1.l_border_x) - min(g2.l_border_x) #Add element in dict dict_sample['overlap'] = overlap
def FindCircleFromThreePoints(P1, P2, P3)
-
Compute the circumscribing circle of a triangle defined by three points.
https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input : three points (a 2 x 1 numpy array) Output : a center (a 2 x 1 numpy array) a radius (a float)
Expand source code
def FindCircleFromThreePoints(P1,P2,P3): ''' Compute the circumscribing circle of a triangle defined by three points. https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/ Input : three points (a 2 x 1 numpy array) Output : a center (a 2 x 1 numpy array) a radius (a float) ''' # Line P1P2 is represented as ax + by = c and line P2P3 is represented as ex + fy = g a, b, c = lineFromPoints(P1, P2) e, f, g = lineFromPoints(P2, P3) # Converting lines P1P2 and P2P3 to perpendicular bisectors. #After this, L : ax + by = c and M : ex + fy = g a, b, c = perpendicularBisectorFromLine(P1, P2, a, b, c) e, f, g = perpendicularBisectorFromLine(P2, P3, e, f, g) # The point of intersection of L and M gives the circumcenter circumcenter = lineLineIntersection(a, b, c, e, f, g) if np.linalg.norm(circumcenter - np.array([10**9,10**9])) == 0: raise ValueError('The given points do not form a triangle and are collinear...') else : #compute the radius radius = max([np.linalg.norm(P1-circumcenter), np.linalg.norm(P2-circumcenter), np.linalg.norm(P3-circumcenter)]) return circumcenter, radius
def lineFromPoints(P, Q)
-
Function to find the line given two points
Used in FindCircleFromThreePoints(). The equation is c = ax + by. https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input : two points (a 2 x 1 numpy array) Output : three characteristic of the line (three floats)
Expand source code
def lineFromPoints(P, Q): ''' Function to find the line given two points Used in FindCircleFromThreePoints(). The equation is c = ax + by. https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/ Input : two points (a 2 x 1 numpy array) Output : three characteristic of the line (three floats) ''' a = Q[1] - P[1] b = P[0] - Q[0] c = a * (P[0]) + b * (P[1]) return a, b, c
def lineLineIntersection(a1, b1, c1, a2, b2, c2)
-
Returns the intersection point of two lines.
Used in FindCircleFromThreePoints(). https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input : six characteristics of the line 1 and 2 (six floats) Output : the intersection point (a 2 x 1 numpy array)
Expand source code
def lineLineIntersection(a1, b1, c1, a2, b2, c2): ''' Returns the intersection point of two lines. Used in FindCircleFromThreePoints(). https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/ Input : six characteristics of the line 1 and 2 (six floats) Output : the intersection point (a 2 x 1 numpy array) ''' determinant = a1 * b2 - a2 * b1 if (determinant == 0): # The lines are parallel. return np.array([10**9,10**9]) else: x = (b2 * c1 - b1 * c2)//determinant y = (a1 * c2 - a2 * c1)//determinant return np.array([x, y])
def perpendicularBisectorFromLine(P, Q, a, b, c)
-
Function which converts the input line to its perpendicular bisector.
Used in FindCircleFromThreePoints(). The equation is c = ax + by. https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/
Input : two points (a 2 x 1 numpy array) three characteristic of the line (three floats) Output : three characteristic of the perpendicular bisector (three floats)
Expand source code
def perpendicularBisectorFromLine(P, Q, a, b, c): ''' Function which converts the input line to its perpendicular bisector. Used in FindCircleFromThreePoints(). The equation is c = ax + by. https://www.geeksforgeeks.org/program-find-circumcenter-triangle-2/ Input : two points (a 2 x 1 numpy array) three characteristic of the line (three floats) Output : three characteristic of the perpendicular bisector (three floats) ''' mid_point = [(P[0] + Q[0])//2, (P[1] + Q[1])//2] # c = -bx + ay c = -b * (mid_point[0]) + a * (mid_point[1]) temp = a a = -b b = temp return a, b, c
Classes
class Grain (id, radius, center, dict_material, dict_sample)
-
Defining a disk grain
Input : an id (an integer) a radius (a float) a center (a 2 x 1 numpy array) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : a grain (a grain)
Expand source code
class Grain: #--------------------------------------------------------------------------- def __init__(self,id,radius,center,dict_material,dict_sample): ''' Defining a disk grain Input : an id (an integer) a radius (a float) a center (a 2 x 1 numpy array) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : a grain (a grain) ''' self.id = id self.center = center L_r = [] L_theta_r = [] L_border = [] L_border_x = [] L_border_y = [] for i in range(dict_sample['grain_discretisation']): theta = 2*math.pi*i/dict_sample['grain_discretisation'] L_r.append(radius) L_theta_r.append(theta) L_border.append(np.array([center[0]+radius*math.cos(theta),center[1]+radius*math.sin(theta)])) L_border_x.append(center[0]+radius*math.cos(theta)) L_border_y.append(center[1]+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]) #save description self.r_mean = radius self.l_r = L_r self.l_theta_r = L_theta_r self.l_border = L_border self.l_border_x = L_border_x self.l_border_y = L_border_y #save initial self.center_init = center.copy() self.l_border_x_init = L_border_x.copy() self.l_border_y_init = L_border_y.copy() self.y = dict_material['Y'] self.nu = dict_material['nu'] self.build_etai_M(dict_material,dict_sample) #--------------------------------------------------------------------------- def build_etai_M(self,dict_material,dict_sample): ''' Build the phase field for one grain. A cosine profile is assumed (see https://mooseframework.inl.gov/source/ics/SmoothCircleIC.html). Input : itself (a grain) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets a new attribute (a n_y x n_x numpy array) ''' #initilization self.etai_M = np.array(np.zeros((len(dict_sample['y_L']),len(dict_sample['x_L'])))) #extract a spatial zone x_min = min(self.l_border_x)-dict_material['w'] x_max = max(self.l_border_x)+dict_material['w'] y_min = min(self.l_border_y)-dict_material['w'] y_max = max(self.l_border_y)+dict_material['w'] #look for this part inside the global mesh #create search list x_L_search_min = abs(np.array(dict_sample['x_L'])-x_min) x_L_search_max = abs(np.array(dict_sample['x_L'])-x_max) y_L_search_min = abs(np.array(dict_sample['y_L'])-y_min) y_L_search_max = abs(np.array(dict_sample['y_L'])-y_max) #get index i_x_min = list(x_L_search_min).index(min(x_L_search_min)) i_x_max = list(x_L_search_max).index(min(x_L_search_max)) i_y_min = list(y_L_search_min).index(min(y_L_search_min)) i_y_max = list(y_L_search_max).index(min(y_L_search_max)) for l in range(i_y_min,i_y_max+1): for c in range(i_x_min,i_x_max+1): y = dict_sample['y_L'][-1-l] x = dict_sample['x_L'][c] p = np.array([x,y]) r = np.linalg.norm(self.center - p) #look for the radius on this direction if p[1]>self.center[1]: theta = math.acos((p[0]-self.center[0])/np.linalg.norm(self.center-p)) else : theta= 2*math.pi - math.acos((p[0]-self.center[0])/np.linalg.norm(self.center-p)) L_theta_R_i = list(abs(np.array(self.l_theta_r)-theta)) R = self.l_r[L_theta_R_i.index(min(L_theta_R_i))] #build etai_M self.etai_M[l][c] = Owntools.Cosine_Profile(R,r,dict_material['w']) #--------------------------------------------------------------------------- def geometric_study(self,dict_sample): ''' Searching limits of the grain Not best method but first approach We iterate on y constant, we look for a value under and over 0.5 If both conditions are verified, there is a limit at this y Same with iteration on x constant Once the border of the grain is defined, a Monte Carlo method is used to computed some geometrical properties. Input : itself (a grain) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets new attributes r_min : the minimum radius of the grain (a float) r_max : the maximum radius of the grain (a float) r_mean : the mean radius of the grain (a float) l_r : a list of radius of the grain, work with l_theta_r (a list) l_theta_r : a list of angle to see the distribution of the radius of the grain, work with l_r (a list) surface : the surface of the grain (a float) center : the coordinate of the grain center (a 2 x 1 numpy array) l_border_x : the list of the coordinate x of the grain vertices (a list) l_border_y : the list of the coordinate y of the grain vertices (a list) l_border : the list of the coordinate [x,y] of the grain vertices (a list) ''' #------------------------------------------------------------------------- #load data needed n = dict_sample['grain_discretisation'] x_L = dict_sample['x_L'] y_L = dict_sample['y_L'] #------------------------------------------------------------------------- L_border_old = [] for y_i in range(len(y_L)): L_extract_x = self.etai_M[y_i][:] if id == 1: L_extract_x = list(L_extract_x) L_extract_x.reverse() if max(L_extract_x)>0.5 and min(L_extract_x)<0.5: y_intersect = y_L[len(y_L)-1-y_i] for x_i in range(len(x_L)-1): if (L_extract_x[x_i]-0.5)*(L_extract_x[x_i+1]-0.5)<0: x_intersect = (0.5-L_extract_x[x_i])/(L_extract_x[x_i+1]-L_extract_x[x_i])*\ (x_L[x_i+1]-x_L[x_i]) + x_L[x_i] L_border_old.append(np.array([x_intersect,y_intersect])) for x_i in range(len(x_L)): L_extract_y = [] for y_i in range(len(y_L)): L_extract_y.append(self.etai_M[y_i][x_i]) if max(L_extract_y)>0.5 and min(L_extract_y)<0.5: x_intersect = x_L[x_i] for y_i in range(len(y_L)-1): if (L_extract_y[y_i]-0.5)*(L_extract_y[y_i+1]-0.5)<0: y_intersect = (0.5-L_extract_y[y_i])/(L_extract_y[y_i+1]-L_extract_y[y_i])*\ (y_L[len(y_L)-1-y_i-1]-y_L[len(y_L)-1-y_i]) + y_L[len(y_L)-1-y_i] L_border_old.append(np.array([x_intersect,y_intersect])) #Adaptating L_id_used = [0] L_border = [L_border_old[0]] HighValue = 100000000 #Large current_node = L_border_old[0] for j in range(1,len(L_border_old)): L_d = list(np.zeros(len(L_border_old))) for i in range(0,len(L_border_old)): node = L_border_old[i] if i not in L_id_used: d = np.linalg.norm(node - current_node) L_d[i] = d else : L_d[i] = HighValue #Value need to be larger than potential distance between node index_nearest_node = L_d.index(min(L_d)) nearest_node = L_border_old[index_nearest_node] current_node = nearest_node L_border.append(nearest_node) L_id_used.append(index_nearest_node) #Correcting L_d_final = [] for i in range(len(L_border)-1): L_d_final.append(np.linalg.norm(L_border[i+1] - L_border[i])) #look for really far points, we assume the first point is accurate d_final_mean = np.mean(L_d_final) while np.max(L_d_final) > 5 * d_final_mean : #5 here is an user choixe value i_error = L_d_final.index(np.max(L_d_final))+1 simulation_report.write('Point '+str(L_border[i_error])+' is deleted because it is detected as an error\n') L_border.pop(i_error) L_id_used.pop(i_error) L_d_final = [] for i in range(len(L_border)-1): L_d_final.append(np.linalg.norm(L_border[i+1] - L_border[i])) #------------------------------------------------------------------------------- #Reduce the number of nodes for a grain #------------------------------------------------------------------------------- Perimeter = 0 for i_p in range(len(L_border)-1): Perimeter = Perimeter + np.linalg.norm(L_border[i_p+1]-L_border[i_p]) Perimeter = Perimeter + np.linalg.norm(L_border[-1]-L_border[0]) distance_min = Perimeter/n L_border_adapted = [L_border[0]] for p in L_border[1:]: distance = np.linalg.norm(p-L_border_adapted[-1]) if distance >= distance_min: L_border_adapted.append(p) L_border = L_border_adapted L_border.append(L_border[0]) self.l_border = L_border #------------------------------------------------------------------------------- #Searching Surface, Center of mass and Inertia. #Monte Carlo Method #A box is defined, we take a random point and we look if it is inside or outside the grain #Properties are the statistic times the box properties #------------------------------------------------------------------------------- min_max_defined = False for p in L_border[:-1] : if not min_max_defined: box_min_x = p[0] box_max_x = p[0] box_min_y = p[1] box_max_y = p[1] min_max_defined = True else: if p[0] < box_min_x: box_min_x = p[0] elif p[0] > box_max_x: box_max_x = p[0] if p[1] < box_min_y: box_min_y = p[1] elif p[1] > box_max_y: box_max_y = p[1] N_MonteCarlo = 3000 #The larger it is, the more accurate it is sigma = 1 M_Mass = 0 M_Center_Mass = np.array([0,0]) for i in range(N_MonteCarlo): P = np.array([random.uniform(box_min_x,box_max_x),random.uniform(box_min_y,box_max_y)]) if self.P_is_inside(P): M_Mass = M_Mass + sigma M_Center_Mass = M_Center_Mass + sigma*P Mass = (box_max_x-box_min_x)*(box_max_y-box_min_y)/N_MonteCarlo*M_Mass Center_Mass = (box_max_x-box_min_x)*(box_max_y-box_min_y)/N_MonteCarlo*M_Center_Mass/Mass #------------------------------------------------------------------------------- #Updating the grain geometry and properties #------------------------------------------------------------------------------- L_R = [] L_theta_R = [] L_border_x = [] L_border_y = [] for p in L_border[:-1]: L_R.append(np.linalg.norm(p-Center_Mass)) L_border_x.append(p[0]) L_border_y.append(p[1]) if (p-Center_Mass)[1] > 0: theta = math.acos((p-Center_Mass)[0]/np.linalg.norm(p-Center_Mass)) else : theta = 2*math.pi - math.acos((p-Center_Mass)[0]/np.linalg.norm(p-Center_Mass)) L_theta_R.append(theta) L_border_x.append(L_border_x[0]) L_border_y.append(L_border_y[0]) #reorganize lists L_R.reverse() L_theta_R.reverse() i_min_theta = L_theta_R.index(min(L_theta_R)) L_R = L_R[i_min_theta:]+L_R[:i_min_theta] L_theta_R = L_theta_R[i_min_theta:]+L_theta_R[:i_min_theta] self.r_min = np.min(L_R) self.r_max = np.max(L_R) self.r_mean = np.mean(L_R) self.l_r = L_R self.l_theta_r = L_theta_R self.surface = Mass/sigma self.center = Center_Mass self.l_border_x = L_border_x self.l_border_y = L_border_y #------------------------------------------------------------------------------- def P_is_inside(self,P): '''Determine if a point P is inside of a grain Make a slide on constant y. Every time a border is crossed, the point switches between in and out. see Franklin 1994, see Alonso-Marroquin 2009 Input : itself (a grain) a point (a 2 x 1 numpy array) Output : True or False, depending on the fact that the point is inside the grain or not (a bool) ''' counter = 0 for i_p_border in range(len(self.l_border)-1): #consider only points if the coordinates frame the y-coordinate of the point if (self.l_border[i_p_border][1]-P[1])*(self.l_border[i_p_border+1][1]-P[1]) < 0 : x_border = self.l_border[i_p_border][0] + (self.l_border[i_p_border+1][0]-self.l_border[i_p_border][0])*(P[1]-self.l_border[i_p_border][1])/(self.l_border[i_p_border+1][1]-self.l_border[i_p_border][1]) if x_border > P[0] : counter = counter + 1 if counter % 2 == 0: return False else : return True #------------------------------------------------------------------------------- def Compute_sphericity(self, dict_algorithm): '''Compute sphericity of the particle with five parameters. The parameters used are the area, the diameter, the circle ratio, the perimeter and the width to length ratio sphericity. See Zheng, J., Hryciw, R.D. (2015) Traditional soil particle sphericity, roundness and surface roughness by computational geometry, Geotechnique, Vol 65 Input : itself (a grain) an algorithm dictionnary (a dict) Output : Nothing, but the grain gets updated attributes (five floats) ''' #Find the minimum circumscribing circle #look for the two farthest and nearest points MaxDistance = 0 for i_p in range(0,len(self.l_border)-2): for j_p in range(i_p+1,len(self.l_border)-1): Distance = np.linalg.norm(self.l_border[i_p]-self.l_border[j_p]) if Distance > MaxDistance : ij_farthest = (i_p,j_p) MaxDistance = Distance #Trial circle center_circumscribing = (self.l_border[ij_farthest[0]]+self.l_border[ij_farthest[1]])/2 radius_circumscribing = MaxDistance/2 Circumscribing_Found = True Max_outside_distance = radius_circumscribing for i_p in range(len(self.l_border)-1): #there is a margin here because of the numerical approximation if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > (1+dict_algorithm['sphericity_margin'])*radius_circumscribing and i_p not in ij_farthest: #vertex outside the trial circle Circumscribing_Found = False if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > Max_outside_distance: k_outside_farthest = i_p Max_outside_distance = np.linalg.norm(self.l_border[i_p]-center_circumscribing) #The trial guess does not work if not Circumscribing_Found: L_ijk_circumscribing = [ij_farthest[0],ij_farthest[1],k_outside_farthest] center_circumscribing, radius_circumscribing = FindCircleFromThreePoints(self.l_border[L_ijk_circumscribing[0]],self.l_border[L_ijk_circumscribing[1]],self.l_border[L_ijk_circumscribing[2]]) Circumscribing_Found = True for i_p in range(len(self.l_border)-1): #there is 1% margin here because of the numerical approximation if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > (1+dict_algorithm['sphericity_margin'])*radius_circumscribing and i_p not in L_ijk_circumscribing: #vertex outside the circle computed Circumscribing_Found = False #see article for other case if not Circumscribing_Found: raise ValueError('This algorithm is not developped for this case...') #look for length and width length = MaxDistance u_maxDistance = (self.l_border[ij_farthest[0]]-self.l_border[ij_farthest[1]])/np.linalg.norm(self.l_border[ij_farthest[0]]-self.l_border[ij_farthest[1]]) v_maxDistance = np.array([u_maxDistance[1], -u_maxDistance[0]]) MaxWidth = 0 for i_p in range(0,len(self.l_border)-2): for j_p in range(i_p+1,len(self.l_border)-1): Distance = abs(np.dot(self.l_border[i_p]-self.l_border[j_p],v_maxDistance)) if Distance > MaxWidth : ij_width = (i_p,j_p) MaxWidth = Distance width = MaxWidth #look for maximum inscribed circle #discretisation of the grain l_x_inscribing = np.linspace(min(self.l_border_x),max(self.l_border_x),dict_algorithm['n_spatial_inscribing']) l_y_inscribing = np.linspace(min(self.l_border_y),max(self.l_border_y),dict_algorithm['n_spatial_inscribing']) #creation of an Euclidean distance map to the nearest boundary vertex map_inscribing = np.zeros((dict_algorithm['n_spatial_inscribing'],dict_algorithm['n_spatial_inscribing'])) #compute the map for i_x in range(dict_algorithm['n_spatial_inscribing']): for i_y in range(dict_algorithm['n_spatial_inscribing']): p = np.array([l_x_inscribing[i_x], l_y_inscribing[-1-i_y]]) #work only if the point is inside the grain if self.P_is_inside(p): #look for the nearest vertex MinDistance = None for q in self.l_border[:-1]: Distance = np.linalg.norm(p-q) if MinDistance == None or Distance < MinDistance: MinDistance = Distance map_inscribing[-1-i_y][i_x] = MinDistance else : map_inscribing[-1-i_y][i_x] = 0 #look for the peak of the map index_max = np.argmax(map_inscribing) l = index_max//dict_algorithm['n_spatial_inscribing'] c = index_max%dict_algorithm['n_spatial_inscribing'] radius_inscribing = map_inscribing[l][c] #Area Sphericity SurfaceParticle = self.surface SurfaceCircumscribing = math.pi*radius_circumscribing**2 AreaSphericity = SurfaceParticle / SurfaceCircumscribing self.area_sphericity = AreaSphericity #Diameter Sphericity DiameterSameAreaParticle = 2*math.sqrt(self.surface/math.pi) DiameterCircumscribing = radius_circumscribing*2 DiameterSphericity = DiameterSameAreaParticle / DiameterCircumscribing self.diameter_sphericity = DiameterSphericity #Circle Ratio Sphericity DiameterInscribing = radius_inscribing*2 CircleRatioSphericity = DiameterInscribing / DiameterCircumscribing self.circle_ratio_sphericity = CircleRatioSphericity #Perimeter Sphericity PerimeterSameAreaParticle = 2*math.sqrt(self.surface*math.pi) PerimeterParticle = 0 for i in range(len(self.l_border)-1): PerimeterParticle = PerimeterParticle + np.linalg.norm(self.l_border[i+1]-self.l_border[i]) PerimeterSphericity = PerimeterSameAreaParticle / PerimeterParticle self.perimeter_sphericity = PerimeterSphericity #Width to length ratio Spericity WidthToLengthRatioSpericity = width / length self.width_to_length_ratio_sphericity = WidthToLengthRatioSpericity #--------------------------------------------------------------------------- def PFtoDEM_Multi(self,FileToRead,dict_algorithm,dict_sample): ''' Read file from MOOSE simulation to reconstruct the phase field of the grain. Input : itself (a grain) the name of the file to read (a string) an algorithm dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array) ''' #-------------------------------------------------------------------------- #Global parameters #--------------------------------------------------------------------------- self.etai_M = np.array(np.zeros((len(dict_sample['y_L']),len(dict_sample['x_L'])))) id_L = None eta_selector_len = len(' <DataArray type="Float64" Name="etai') end_len = len(' </DataArray>') XYZ_selector_len = len(' <DataArray type="Float64" Name="Points"') data_jump_len = len(' ') for i_proc in range(dict_algorithm['np_proc']): L_Work = [[], #X [], #Y []] #etai #--------------------------------------------------------------------------- #Reading file #--------------------------------------------------------------------------- f = open(f'{FileToRead}_{i_proc}.vtu','r') data = f.read() f.close lines = data.splitlines() #iterations on line for line in lines: if line[0:eta_selector_len] == ' <DataArray type="Float64" Name="eta'+str(self.id): id_L = 2 elif line[0:XYZ_selector_len] == ' <DataArray type="Float64" Name="Points"': id_L = 0 elif (line[0:end_len] == ' </DataArray>' or line[0:len(' <InformationKey')] == ' <InformationKey') and id_L != None: id_L = None elif line[0:data_jump_len] == ' ' and id_L == 2: #Read etai line = line[data_jump_len:] c_start = 0 for c_i in range(0,len(line)): if line[c_i]==' ': c_end = c_i L_Work[id_L].append(float(line[c_start:c_end])) c_start = c_i+1 L_Work[id_L].append(float(line[c_start:])) elif line[0:data_jump_len] == ' ' and id_L == 0: #Read [X, Y, Z] line = line[data_jump_len:] XYZ_temp = [] c_start = 0 for c_i in range(0,len(line)): if line[c_i]==' ': c_end = c_i XYZ_temp.append(float(line[c_start:c_end])) if len(XYZ_temp)==3: L_Work[0].append(XYZ_temp[0]) L_Work[1].append(XYZ_temp[1]) XYZ_temp = [] c_start = c_i+1 XYZ_temp.append(float(line[c_start:])) L_Work[0].append(XYZ_temp[0]) L_Work[1].append(XYZ_temp[1]) #Adaptating data and update of etai_M for i in range(len(L_Work[0])): #Interpolation method L_dy = [] for y_i in dict_sample['y_L'] : L_dy.append(abs(y_i - L_Work[1][i])) L_dx = [] for x_i in dict_sample['x_L'] : L_dx.append(abs(x_i - L_Work[0][i])) self.etai_M[-1-list(L_dy).index(min(L_dy))][list(L_dx).index(min(L_dx))] = L_Work[2][i] #--------------------------------------------------------------------------- def move_grain_rebuild(self,displacement,dict_material,dict_sample): ''' Move the grain by updating the phase field of the grain. The grain is deconstructed and then rebuilt. The mass conservation can not well verified. It is advised to work with move_grain_interpolation(). Input : itself (a grain) the displacement asked (a 2 x 1 numpy array) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array) ''' self.center = self.center + displacement for i in range(len(self.l_border)): self.l_border[i] = self.l_border[i] + displacement self.l_border_x[i] = self.l_border_x[i] + displacement[0] self.l_border_y[i] = self.l_border_y[i] + displacement[1] self.build_etai_M(dict_material,dict_sample) #--------------------------------------------------------------------------- def move_grain_interpolation(self,displacement,dict_sample): ''' Move the grain by updating the phase field of the grain. An interpolation on the phase field is done. The mass conservation is better than with move_grain_rebuild(). Input : itself (a grain) the displacement asked (a 2 x 1 numpy array) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array) ''' self.center = self.center + displacement for i in range(len(self.l_border)): self.l_border[i] = self.l_border[i] + displacement self.l_border_x[i] = self.l_border_x[i] + displacement[0] self.l_border_y[i] = self.l_border_y[i] + displacement[1] #displacement over x dx = dict_sample['x_L'][1]-dict_sample['x_L'][0] n_dx_disp_x = int(abs(displacement[0])//dx) disp_x_remainder = abs(displacement[0])%dx etai_M_old = self.etai_M.copy() if np.sign(displacement[0]) > 0 : # +x direction #dx*n_dx_disp_x translation if n_dx_disp_x > 0: for l in range(len(dict_sample['y_L'])): self.etai_M[l][:n_dx_disp_x] = 0 #no information to translate so put equal to 0 self.etai_M[l][n_dx_disp_x:] = etai_M_old[l][:-n_dx_disp_x] #disp_x_remainder translation etai_M_old = self.etai_M.copy() for l in range(len(dict_sample['y_L'])): for c in range(1,len(dict_sample['x_L'])): self.etai_M[l][c] = (etai_M_old[l][c]*(dx-disp_x_remainder) + etai_M_old[l][c-1]*disp_x_remainder)/dx self.etai_M[l][0] = 0 #no information to translate so put equal to 0 else : # -x direction #dx*n_dx_disp_x translation if n_dx_disp_x > 0: for l in range(len(dict_sample['y_L'])): self.etai_M[l][-n_dx_disp_x:] = 0 #no information to translate so put equal to 0 self.etai_M[l][:-n_dx_disp_x] = etai_M_old[l][n_dx_disp_x:] #disp_x_remainder translation etai_M_old = self.etai_M.copy() for l in range(len(dict_sample['y_L'])): for c in range(len(dict_sample['x_L'])-1): self.etai_M[l][c] = (etai_M_old[l][c]*(dx-disp_x_remainder) + etai_M_old[l][c+1]*disp_x_remainder)/dx self.etai_M[l][0] = 0 #no information to translate so put equal to 0
Methods
def Compute_sphericity(self, dict_algorithm)
-
Compute sphericity of the particle with five parameters.
The parameters used are the area, the diameter, the circle ratio, the perimeter and the width to length ratio sphericity. See Zheng, J., Hryciw, R.D. (2015) Traditional soil particle sphericity, roundness and surface roughness by computational geometry, Geotechnique, Vol 65
Input : itself (a grain) an algorithm dictionnary (a dict) Output : Nothing, but the grain gets updated attributes (five floats)
Expand source code
def Compute_sphericity(self, dict_algorithm): '''Compute sphericity of the particle with five parameters. The parameters used are the area, the diameter, the circle ratio, the perimeter and the width to length ratio sphericity. See Zheng, J., Hryciw, R.D. (2015) Traditional soil particle sphericity, roundness and surface roughness by computational geometry, Geotechnique, Vol 65 Input : itself (a grain) an algorithm dictionnary (a dict) Output : Nothing, but the grain gets updated attributes (five floats) ''' #Find the minimum circumscribing circle #look for the two farthest and nearest points MaxDistance = 0 for i_p in range(0,len(self.l_border)-2): for j_p in range(i_p+1,len(self.l_border)-1): Distance = np.linalg.norm(self.l_border[i_p]-self.l_border[j_p]) if Distance > MaxDistance : ij_farthest = (i_p,j_p) MaxDistance = Distance #Trial circle center_circumscribing = (self.l_border[ij_farthest[0]]+self.l_border[ij_farthest[1]])/2 radius_circumscribing = MaxDistance/2 Circumscribing_Found = True Max_outside_distance = radius_circumscribing for i_p in range(len(self.l_border)-1): #there is a margin here because of the numerical approximation if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > (1+dict_algorithm['sphericity_margin'])*radius_circumscribing and i_p not in ij_farthest: #vertex outside the trial circle Circumscribing_Found = False if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > Max_outside_distance: k_outside_farthest = i_p Max_outside_distance = np.linalg.norm(self.l_border[i_p]-center_circumscribing) #The trial guess does not work if not Circumscribing_Found: L_ijk_circumscribing = [ij_farthest[0],ij_farthest[1],k_outside_farthest] center_circumscribing, radius_circumscribing = FindCircleFromThreePoints(self.l_border[L_ijk_circumscribing[0]],self.l_border[L_ijk_circumscribing[1]],self.l_border[L_ijk_circumscribing[2]]) Circumscribing_Found = True for i_p in range(len(self.l_border)-1): #there is 1% margin here because of the numerical approximation if np.linalg.norm(self.l_border[i_p]-center_circumscribing) > (1+dict_algorithm['sphericity_margin'])*radius_circumscribing and i_p not in L_ijk_circumscribing: #vertex outside the circle computed Circumscribing_Found = False #see article for other case if not Circumscribing_Found: raise ValueError('This algorithm is not developped for this case...') #look for length and width length = MaxDistance u_maxDistance = (self.l_border[ij_farthest[0]]-self.l_border[ij_farthest[1]])/np.linalg.norm(self.l_border[ij_farthest[0]]-self.l_border[ij_farthest[1]]) v_maxDistance = np.array([u_maxDistance[1], -u_maxDistance[0]]) MaxWidth = 0 for i_p in range(0,len(self.l_border)-2): for j_p in range(i_p+1,len(self.l_border)-1): Distance = abs(np.dot(self.l_border[i_p]-self.l_border[j_p],v_maxDistance)) if Distance > MaxWidth : ij_width = (i_p,j_p) MaxWidth = Distance width = MaxWidth #look for maximum inscribed circle #discretisation of the grain l_x_inscribing = np.linspace(min(self.l_border_x),max(self.l_border_x),dict_algorithm['n_spatial_inscribing']) l_y_inscribing = np.linspace(min(self.l_border_y),max(self.l_border_y),dict_algorithm['n_spatial_inscribing']) #creation of an Euclidean distance map to the nearest boundary vertex map_inscribing = np.zeros((dict_algorithm['n_spatial_inscribing'],dict_algorithm['n_spatial_inscribing'])) #compute the map for i_x in range(dict_algorithm['n_spatial_inscribing']): for i_y in range(dict_algorithm['n_spatial_inscribing']): p = np.array([l_x_inscribing[i_x], l_y_inscribing[-1-i_y]]) #work only if the point is inside the grain if self.P_is_inside(p): #look for the nearest vertex MinDistance = None for q in self.l_border[:-1]: Distance = np.linalg.norm(p-q) if MinDistance == None or Distance < MinDistance: MinDistance = Distance map_inscribing[-1-i_y][i_x] = MinDistance else : map_inscribing[-1-i_y][i_x] = 0 #look for the peak of the map index_max = np.argmax(map_inscribing) l = index_max//dict_algorithm['n_spatial_inscribing'] c = index_max%dict_algorithm['n_spatial_inscribing'] radius_inscribing = map_inscribing[l][c] #Area Sphericity SurfaceParticle = self.surface SurfaceCircumscribing = math.pi*radius_circumscribing**2 AreaSphericity = SurfaceParticle / SurfaceCircumscribing self.area_sphericity = AreaSphericity #Diameter Sphericity DiameterSameAreaParticle = 2*math.sqrt(self.surface/math.pi) DiameterCircumscribing = radius_circumscribing*2 DiameterSphericity = DiameterSameAreaParticle / DiameterCircumscribing self.diameter_sphericity = DiameterSphericity #Circle Ratio Sphericity DiameterInscribing = radius_inscribing*2 CircleRatioSphericity = DiameterInscribing / DiameterCircumscribing self.circle_ratio_sphericity = CircleRatioSphericity #Perimeter Sphericity PerimeterSameAreaParticle = 2*math.sqrt(self.surface*math.pi) PerimeterParticle = 0 for i in range(len(self.l_border)-1): PerimeterParticle = PerimeterParticle + np.linalg.norm(self.l_border[i+1]-self.l_border[i]) PerimeterSphericity = PerimeterSameAreaParticle / PerimeterParticle self.perimeter_sphericity = PerimeterSphericity #Width to length ratio Spericity WidthToLengthRatioSpericity = width / length self.width_to_length_ratio_sphericity = WidthToLengthRatioSpericity
def PFtoDEM_Multi(self, FileToRead, dict_algorithm, dict_sample)
-
Read file from MOOSE simulation to reconstruct the phase field of the grain.
Input : itself (a grain) the name of the file to read (a string) an algorithm dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array)
Expand source code
def PFtoDEM_Multi(self,FileToRead,dict_algorithm,dict_sample): ''' Read file from MOOSE simulation to reconstruct the phase field of the grain. Input : itself (a grain) the name of the file to read (a string) an algorithm dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array) ''' #-------------------------------------------------------------------------- #Global parameters #--------------------------------------------------------------------------- self.etai_M = np.array(np.zeros((len(dict_sample['y_L']),len(dict_sample['x_L'])))) id_L = None eta_selector_len = len(' <DataArray type="Float64" Name="etai') end_len = len(' </DataArray>') XYZ_selector_len = len(' <DataArray type="Float64" Name="Points"') data_jump_len = len(' ') for i_proc in range(dict_algorithm['np_proc']): L_Work = [[], #X [], #Y []] #etai #--------------------------------------------------------------------------- #Reading file #--------------------------------------------------------------------------- f = open(f'{FileToRead}_{i_proc}.vtu','r') data = f.read() f.close lines = data.splitlines() #iterations on line for line in lines: if line[0:eta_selector_len] == ' <DataArray type="Float64" Name="eta'+str(self.id): id_L = 2 elif line[0:XYZ_selector_len] == ' <DataArray type="Float64" Name="Points"': id_L = 0 elif (line[0:end_len] == ' </DataArray>' or line[0:len(' <InformationKey')] == ' <InformationKey') and id_L != None: id_L = None elif line[0:data_jump_len] == ' ' and id_L == 2: #Read etai line = line[data_jump_len:] c_start = 0 for c_i in range(0,len(line)): if line[c_i]==' ': c_end = c_i L_Work[id_L].append(float(line[c_start:c_end])) c_start = c_i+1 L_Work[id_L].append(float(line[c_start:])) elif line[0:data_jump_len] == ' ' and id_L == 0: #Read [X, Y, Z] line = line[data_jump_len:] XYZ_temp = [] c_start = 0 for c_i in range(0,len(line)): if line[c_i]==' ': c_end = c_i XYZ_temp.append(float(line[c_start:c_end])) if len(XYZ_temp)==3: L_Work[0].append(XYZ_temp[0]) L_Work[1].append(XYZ_temp[1]) XYZ_temp = [] c_start = c_i+1 XYZ_temp.append(float(line[c_start:])) L_Work[0].append(XYZ_temp[0]) L_Work[1].append(XYZ_temp[1]) #Adaptating data and update of etai_M for i in range(len(L_Work[0])): #Interpolation method L_dy = [] for y_i in dict_sample['y_L'] : L_dy.append(abs(y_i - L_Work[1][i])) L_dx = [] for x_i in dict_sample['x_L'] : L_dx.append(abs(x_i - L_Work[0][i])) self.etai_M[-1-list(L_dy).index(min(L_dy))][list(L_dx).index(min(L_dx))] = L_Work[2][i]
def P_is_inside(self, P)
-
Determine if a point P is inside of a grain
Make a slide on constant y. Every time a border is crossed, the point switches between in and out. see Franklin 1994, see Alonso-Marroquin 2009
Input : itself (a grain) a point (a 2 x 1 numpy array) Output : True or False, depending on the fact that the point is inside the grain or not (a bool)
Expand source code
def P_is_inside(self,P): '''Determine if a point P is inside of a grain Make a slide on constant y. Every time a border is crossed, the point switches between in and out. see Franklin 1994, see Alonso-Marroquin 2009 Input : itself (a grain) a point (a 2 x 1 numpy array) Output : True or False, depending on the fact that the point is inside the grain or not (a bool) ''' counter = 0 for i_p_border in range(len(self.l_border)-1): #consider only points if the coordinates frame the y-coordinate of the point if (self.l_border[i_p_border][1]-P[1])*(self.l_border[i_p_border+1][1]-P[1]) < 0 : x_border = self.l_border[i_p_border][0] + (self.l_border[i_p_border+1][0]-self.l_border[i_p_border][0])*(P[1]-self.l_border[i_p_border][1])/(self.l_border[i_p_border+1][1]-self.l_border[i_p_border][1]) if x_border > P[0] : counter = counter + 1 if counter % 2 == 0: return False else : return True
def build_etai_M(self, dict_material, dict_sample)
-
Build the phase field for one grain.
A cosine profile is assumed (see https://mooseframework.inl.gov/source/ics/SmoothCircleIC.html).
Input : itself (a grain) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets a new attribute (a n_y x n_x numpy array)
Expand source code
def build_etai_M(self,dict_material,dict_sample): ''' Build the phase field for one grain. A cosine profile is assumed (see https://mooseframework.inl.gov/source/ics/SmoothCircleIC.html). Input : itself (a grain) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets a new attribute (a n_y x n_x numpy array) ''' #initilization self.etai_M = np.array(np.zeros((len(dict_sample['y_L']),len(dict_sample['x_L'])))) #extract a spatial zone x_min = min(self.l_border_x)-dict_material['w'] x_max = max(self.l_border_x)+dict_material['w'] y_min = min(self.l_border_y)-dict_material['w'] y_max = max(self.l_border_y)+dict_material['w'] #look for this part inside the global mesh #create search list x_L_search_min = abs(np.array(dict_sample['x_L'])-x_min) x_L_search_max = abs(np.array(dict_sample['x_L'])-x_max) y_L_search_min = abs(np.array(dict_sample['y_L'])-y_min) y_L_search_max = abs(np.array(dict_sample['y_L'])-y_max) #get index i_x_min = list(x_L_search_min).index(min(x_L_search_min)) i_x_max = list(x_L_search_max).index(min(x_L_search_max)) i_y_min = list(y_L_search_min).index(min(y_L_search_min)) i_y_max = list(y_L_search_max).index(min(y_L_search_max)) for l in range(i_y_min,i_y_max+1): for c in range(i_x_min,i_x_max+1): y = dict_sample['y_L'][-1-l] x = dict_sample['x_L'][c] p = np.array([x,y]) r = np.linalg.norm(self.center - p) #look for the radius on this direction if p[1]>self.center[1]: theta = math.acos((p[0]-self.center[0])/np.linalg.norm(self.center-p)) else : theta= 2*math.pi - math.acos((p[0]-self.center[0])/np.linalg.norm(self.center-p)) L_theta_R_i = list(abs(np.array(self.l_theta_r)-theta)) R = self.l_r[L_theta_R_i.index(min(L_theta_R_i))] #build etai_M self.etai_M[l][c] = Owntools.Cosine_Profile(R,r,dict_material['w'])
def geometric_study(self, dict_sample)
-
Searching limits of the grain
Not best method but first approach We iterate on y constant, we look for a value under and over 0.5 If both conditions are verified, there is a limit at this y Same with iteration on x constant
Once the border of the grain is defined, a Monte Carlo method is used to computed some geometrical properties.
Input : itself (a grain) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets new attributes r_min : the minimum radius of the grain (a float) r_max : the maximum radius of the grain (a float) r_mean : the mean radius of the grain (a float) l_r : a list of radius of the grain, work with l_theta_r (a list) l_theta_r : a list of angle to see the distribution of the radius of the grain, work with l_r (a list) surface : the surface of the grain (a float) center : the coordinate of the grain center (a 2 x 1 numpy array) l_border_x : the list of the coordinate x of the grain vertices (a list) l_border_y : the list of the coordinate y of the grain vertices (a list) l_border : the list of the coordinate [x,y] of the grain vertices (a list)
Expand source code
def geometric_study(self,dict_sample): ''' Searching limits of the grain Not best method but first approach We iterate on y constant, we look for a value under and over 0.5 If both conditions are verified, there is a limit at this y Same with iteration on x constant Once the border of the grain is defined, a Monte Carlo method is used to computed some geometrical properties. Input : itself (a grain) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets new attributes r_min : the minimum radius of the grain (a float) r_max : the maximum radius of the grain (a float) r_mean : the mean radius of the grain (a float) l_r : a list of radius of the grain, work with l_theta_r (a list) l_theta_r : a list of angle to see the distribution of the radius of the grain, work with l_r (a list) surface : the surface of the grain (a float) center : the coordinate of the grain center (a 2 x 1 numpy array) l_border_x : the list of the coordinate x of the grain vertices (a list) l_border_y : the list of the coordinate y of the grain vertices (a list) l_border : the list of the coordinate [x,y] of the grain vertices (a list) ''' #------------------------------------------------------------------------- #load data needed n = dict_sample['grain_discretisation'] x_L = dict_sample['x_L'] y_L = dict_sample['y_L'] #------------------------------------------------------------------------- L_border_old = [] for y_i in range(len(y_L)): L_extract_x = self.etai_M[y_i][:] if id == 1: L_extract_x = list(L_extract_x) L_extract_x.reverse() if max(L_extract_x)>0.5 and min(L_extract_x)<0.5: y_intersect = y_L[len(y_L)-1-y_i] for x_i in range(len(x_L)-1): if (L_extract_x[x_i]-0.5)*(L_extract_x[x_i+1]-0.5)<0: x_intersect = (0.5-L_extract_x[x_i])/(L_extract_x[x_i+1]-L_extract_x[x_i])*\ (x_L[x_i+1]-x_L[x_i]) + x_L[x_i] L_border_old.append(np.array([x_intersect,y_intersect])) for x_i in range(len(x_L)): L_extract_y = [] for y_i in range(len(y_L)): L_extract_y.append(self.etai_M[y_i][x_i]) if max(L_extract_y)>0.5 and min(L_extract_y)<0.5: x_intersect = x_L[x_i] for y_i in range(len(y_L)-1): if (L_extract_y[y_i]-0.5)*(L_extract_y[y_i+1]-0.5)<0: y_intersect = (0.5-L_extract_y[y_i])/(L_extract_y[y_i+1]-L_extract_y[y_i])*\ (y_L[len(y_L)-1-y_i-1]-y_L[len(y_L)-1-y_i]) + y_L[len(y_L)-1-y_i] L_border_old.append(np.array([x_intersect,y_intersect])) #Adaptating L_id_used = [0] L_border = [L_border_old[0]] HighValue = 100000000 #Large current_node = L_border_old[0] for j in range(1,len(L_border_old)): L_d = list(np.zeros(len(L_border_old))) for i in range(0,len(L_border_old)): node = L_border_old[i] if i not in L_id_used: d = np.linalg.norm(node - current_node) L_d[i] = d else : L_d[i] = HighValue #Value need to be larger than potential distance between node index_nearest_node = L_d.index(min(L_d)) nearest_node = L_border_old[index_nearest_node] current_node = nearest_node L_border.append(nearest_node) L_id_used.append(index_nearest_node) #Correcting L_d_final = [] for i in range(len(L_border)-1): L_d_final.append(np.linalg.norm(L_border[i+1] - L_border[i])) #look for really far points, we assume the first point is accurate d_final_mean = np.mean(L_d_final) while np.max(L_d_final) > 5 * d_final_mean : #5 here is an user choixe value i_error = L_d_final.index(np.max(L_d_final))+1 simulation_report.write('Point '+str(L_border[i_error])+' is deleted because it is detected as an error\n') L_border.pop(i_error) L_id_used.pop(i_error) L_d_final = [] for i in range(len(L_border)-1): L_d_final.append(np.linalg.norm(L_border[i+1] - L_border[i])) #------------------------------------------------------------------------------- #Reduce the number of nodes for a grain #------------------------------------------------------------------------------- Perimeter = 0 for i_p in range(len(L_border)-1): Perimeter = Perimeter + np.linalg.norm(L_border[i_p+1]-L_border[i_p]) Perimeter = Perimeter + np.linalg.norm(L_border[-1]-L_border[0]) distance_min = Perimeter/n L_border_adapted = [L_border[0]] for p in L_border[1:]: distance = np.linalg.norm(p-L_border_adapted[-1]) if distance >= distance_min: L_border_adapted.append(p) L_border = L_border_adapted L_border.append(L_border[0]) self.l_border = L_border #------------------------------------------------------------------------------- #Searching Surface, Center of mass and Inertia. #Monte Carlo Method #A box is defined, we take a random point and we look if it is inside or outside the grain #Properties are the statistic times the box properties #------------------------------------------------------------------------------- min_max_defined = False for p in L_border[:-1] : if not min_max_defined: box_min_x = p[0] box_max_x = p[0] box_min_y = p[1] box_max_y = p[1] min_max_defined = True else: if p[0] < box_min_x: box_min_x = p[0] elif p[0] > box_max_x: box_max_x = p[0] if p[1] < box_min_y: box_min_y = p[1] elif p[1] > box_max_y: box_max_y = p[1] N_MonteCarlo = 3000 #The larger it is, the more accurate it is sigma = 1 M_Mass = 0 M_Center_Mass = np.array([0,0]) for i in range(N_MonteCarlo): P = np.array([random.uniform(box_min_x,box_max_x),random.uniform(box_min_y,box_max_y)]) if self.P_is_inside(P): M_Mass = M_Mass + sigma M_Center_Mass = M_Center_Mass + sigma*P Mass = (box_max_x-box_min_x)*(box_max_y-box_min_y)/N_MonteCarlo*M_Mass Center_Mass = (box_max_x-box_min_x)*(box_max_y-box_min_y)/N_MonteCarlo*M_Center_Mass/Mass #------------------------------------------------------------------------------- #Updating the grain geometry and properties #------------------------------------------------------------------------------- L_R = [] L_theta_R = [] L_border_x = [] L_border_y = [] for p in L_border[:-1]: L_R.append(np.linalg.norm(p-Center_Mass)) L_border_x.append(p[0]) L_border_y.append(p[1]) if (p-Center_Mass)[1] > 0: theta = math.acos((p-Center_Mass)[0]/np.linalg.norm(p-Center_Mass)) else : theta = 2*math.pi - math.acos((p-Center_Mass)[0]/np.linalg.norm(p-Center_Mass)) L_theta_R.append(theta) L_border_x.append(L_border_x[0]) L_border_y.append(L_border_y[0]) #reorganize lists L_R.reverse() L_theta_R.reverse() i_min_theta = L_theta_R.index(min(L_theta_R)) L_R = L_R[i_min_theta:]+L_R[:i_min_theta] L_theta_R = L_theta_R[i_min_theta:]+L_theta_R[:i_min_theta] self.r_min = np.min(L_R) self.r_max = np.max(L_R) self.r_mean = np.mean(L_R) self.l_r = L_R self.l_theta_r = L_theta_R self.surface = Mass/sigma self.center = Center_Mass self.l_border_x = L_border_x self.l_border_y = L_border_y
def move_grain_interpolation(self, displacement, dict_sample)
-
Move the grain by updating the phase field of the grain.
An interpolation on the phase field is done. The mass conservation is better than with move_grain_rebuild().
Input : itself (a grain) the displacement asked (a 2 x 1 numpy array) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array)
Expand source code
def move_grain_interpolation(self,displacement,dict_sample): ''' Move the grain by updating the phase field of the grain. An interpolation on the phase field is done. The mass conservation is better than with move_grain_rebuild(). Input : itself (a grain) the displacement asked (a 2 x 1 numpy array) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array) ''' self.center = self.center + displacement for i in range(len(self.l_border)): self.l_border[i] = self.l_border[i] + displacement self.l_border_x[i] = self.l_border_x[i] + displacement[0] self.l_border_y[i] = self.l_border_y[i] + displacement[1] #displacement over x dx = dict_sample['x_L'][1]-dict_sample['x_L'][0] n_dx_disp_x = int(abs(displacement[0])//dx) disp_x_remainder = abs(displacement[0])%dx etai_M_old = self.etai_M.copy() if np.sign(displacement[0]) > 0 : # +x direction #dx*n_dx_disp_x translation if n_dx_disp_x > 0: for l in range(len(dict_sample['y_L'])): self.etai_M[l][:n_dx_disp_x] = 0 #no information to translate so put equal to 0 self.etai_M[l][n_dx_disp_x:] = etai_M_old[l][:-n_dx_disp_x] #disp_x_remainder translation etai_M_old = self.etai_M.copy() for l in range(len(dict_sample['y_L'])): for c in range(1,len(dict_sample['x_L'])): self.etai_M[l][c] = (etai_M_old[l][c]*(dx-disp_x_remainder) + etai_M_old[l][c-1]*disp_x_remainder)/dx self.etai_M[l][0] = 0 #no information to translate so put equal to 0 else : # -x direction #dx*n_dx_disp_x translation if n_dx_disp_x > 0: for l in range(len(dict_sample['y_L'])): self.etai_M[l][-n_dx_disp_x:] = 0 #no information to translate so put equal to 0 self.etai_M[l][:-n_dx_disp_x] = etai_M_old[l][n_dx_disp_x:] #disp_x_remainder translation etai_M_old = self.etai_M.copy() for l in range(len(dict_sample['y_L'])): for c in range(len(dict_sample['x_L'])-1): self.etai_M[l][c] = (etai_M_old[l][c]*(dx-disp_x_remainder) + etai_M_old[l][c+1]*disp_x_remainder)/dx self.etai_M[l][0] = 0 #no information to translate so put equal to 0
def move_grain_rebuild(self, displacement, dict_material, dict_sample)
-
Move the grain by updating the phase field of the grain.
The grain is deconstructed and then rebuilt. The mass conservation can not well verified. It is advised to work with move_grain_interpolation().
Input : itself (a grain) the displacement asked (a 2 x 1 numpy array) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array)
Expand source code
def move_grain_rebuild(self,displacement,dict_material,dict_sample): ''' Move the grain by updating the phase field of the grain. The grain is deconstructed and then rebuilt. The mass conservation can not well verified. It is advised to work with move_grain_interpolation(). Input : itself (a grain) the displacement asked (a 2 x 1 numpy array) a material dictionnary (a dictionnary) a sample dictionnary (a dictionnary) Output : Nothing but the grain gets an updated attribute (a n_y x n_x numpy array) ''' self.center = self.center + displacement for i in range(len(self.l_border)): self.l_border[i] = self.l_border[i] + displacement self.l_border_x[i] = self.l_border_x[i] + displacement[0] self.l_border_y[i] = self.l_border_y[i] + displacement[1] self.build_etai_M(dict_material,dict_sample)