Skip to content

Commit

Permalink
Improved handling of microstructure with sparse particles (precipitat…
Browse files Browse the repository at this point in the history
…es/porosity)
  • Loading branch information
AHartmaier committed Feb 8, 2024
1 parent b23e6c1 commit e89445b
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 1,730 deletions.
59 changes: 25 additions & 34 deletions examples/dual_phase_microstructures/porosity.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,63 +9,54 @@
January 2024
"""
import kanapy as knpy
import numpy as np
from math import pi

periodic = True # create periodic RVE
vf0 = 0.75 # volume fraction of phase 1 (dense phase)
vf1 = 1. - vf0 # volume fraction of phase 1 (porosity)
name0 = 'Austenite'
name1 = 'Pores'
nvox = 30 # number of voxels in each Cartesian direction (cuboid RVE)
lside = 40 # side length in micron in each Cartesian direction
vf_pores = 0.10 # volume fraction of pores (or precipitates)
name = 'Pores'
nvox = 40 # number of voxels in each Cartesian direction (cuboid RVE)
lside = 30 # side length in micron in each Cartesian direction

ms_stats_0 = { # statistical data for dense phase
ms_stats = { # statistical data for dense phase
"Grain type": "Elongated",
"Equivalent diameter": {
"sig": 1.0, "scale": 12.0, "loc": 5.0, "cutoff_min": 8.0, "cutoff_max": 14.0},
"sig": 1.0, "scale": 10.0, "loc": 3.5, "cutoff_min": 6.0, "cutoff_max": 8.0},
"Aspect ratio": {
"sig": 0.5, "scale": 1.0, "loc": -0.1, "cutoff_min": 0.0, "cutoff_max": 2.},
"sig": 1.0, "scale": 1.0, "loc": -0.1, "cutoff_min": 0.15, "cutoff_max": 0.85},
"Tilt angle": {
"kappa": 1.0, "loc": 0.5*pi, "cutoff_min": 0.0, "cutoff_max": 2*pi},
"kappa": 1.0, "loc": 0.5*pi, "cutoff_min": 0.0, "cutoff_max": pi},
"RVE": {
"sideX": lside, "sideY": lside, "sideZ": lside,
"Nx": nvox, "Ny": nvox, "Nz": nvox},
"Simulation": {
"periodicity": str(periodic), "output_units": "um"},
"Phase": {
"Name": name0, "Number": 0, "Volume fraction": vf0}
}

ms_stats_1 = { # statistical data for porosity (will not be considered explicitly)
"Grain type": "Elongated",
"Equivalent diameter": {
"sig": 1.5, "scale": 9.0, "loc": 6.0, "cutoff_min": 8.0, "cutoff_max": 12.0},
"Aspect ratio": {
"sig": 1.0, "scale": 3.5, "loc": 1.0, "cutoff_min": 1.6, "cutoff_max": 5.0},
"Tilt angle": {
"kappa": 1.5, "loc": 0.5*pi, "cutoff_min": 0.0, "cutoff_max": 2*pi},
"RVE": {
"sideX": lside, "sideY": lside, "sideZ": lside,
"Nx": nvox, "Ny": nvox, "Nz": nvox},
"Simulation": {
"periodicity": str(periodic), "output_units": "um"},
"Phase": {
"Name": name1, "Number": 1, "Volume fraction": vf1}
"Name": name, "Number": 0, "Volume fraction": vf_pores}
}

# Generate microstructure object
ms = knpy.Microstructure(descriptor=[ms_stats_0, ms_stats_1], name='porous') # generate microstructure object
ms = knpy.Microstructure(descriptor=ms_stats, name='porous') # generate microstructure object
ms.plot_stats_init() # plot initial microstructure statistics and cut-offs for both phases

# Create and visualize synthetic RVE
ms.init_RVE(porosity=vf1) # initial RVE, keyword porosity implies that only particles of phase 0 are generated
ms.init_RVE() # setup RVE geometry
ms.pack(k_rep=0.01, k_att=0.01) # packing will be stopped when desired volume fraction is reached
ms.plot_ellipsoids() # plot particles at the end of growth phase
ms.voxelize() # assigning particles to voxels, empty voxels will be considered as phase 1 (porosity)
ms.voxelize() # assigning particles to voxels, empty voxels will be considered as phase 1 (matrix)
ms.plot_voxels(sliced=True, dual_phase=False) # plot voxel structure, dual_phase=True will plot green/red contrast
ms.generate_grains() # construct polyhedral hull for each grain
ms.plot_grains() # plot grain structure
ms.plot_stats() # plot statistical distribution of grain sizes and aspect ratios and compare to input statistics

# plot voxels of porous phase
mask = np.full(ms.mesh.dim, False, dtype=bool)
for igr, ip in ms.mesh.grain_phase_dict.items():
if ip == 0:
for nv in ms.mesh.grain_dict[igr]:
i, j, k = np.unravel_index(nv-1, ms.mesh.dim, order='F')
mask[i, j, k] = True
knpy.plot_voxels_3D(ms.mesh.grains, Ngr=ms.Ngr, mask=mask)
#ms.generate_grains() # construct polyhedral hull for each grain
#ms.plot_grains() # plot grain structure
#ms.plot_stats() # plot statistical distribution of grain sizes and aspect ratios and compare to input statistics

# Write voxel structure to JSON file
ms.write_voxels(script_name=__file__, mesh=False, system=False)
44 changes: 28 additions & 16 deletions src/kanapy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ class Microstructure(object):
descriptor : list
List of dictionaries describing the microstructure of each phase;
Dict Keys: "Grains type", "Equivalent diameter", "Aspect ratio", "Tilt Angle", "RVE", "Simulation"
porosity : None or float
Indicates porosity of microstructure, if float gives volume fraction of pores
precipit : None or float
Indicates microstructure with precipitates/pores/particles in continuous matrix. If type is float,
it gives volume the fraction of that precipitate phase
from_voxels : bool
Indicates whether microstructure object is imported from voxel file, not generated from particle simulation
particles : list
Expand All @@ -60,7 +61,7 @@ class Microstructure(object):
Contains information about geometry of simulation box for particle simulation
mesh : object of class mesh_creator
Attributes: dim, grain_dict, grain_ori_dict, grain_phase_dict, grains, ngrains_phase. nodes, nodes_smooth,
nphases, nvox, phases, porosity_voxels, vox_center_dict, voxel_dict
nphases, nvox, phases, prec_vf_voxels, vox_center_dict, voxel_dict
geometry : dict
Dictionary of grain geometries;
Dict keys: "Ngrains", "Vertices", "Points", "Simplices", "Facets", "Grains", "GBnodes", GBarea" "GBfaces"
Expand All @@ -79,7 +80,7 @@ def __init__(self, descriptor=None, file=None, name='Microstructure'):
self.nphases = None
self.ngrains = None
self.nparticles = None
self.porosity = None
self.precipit = None
self.rve = None
self.particles = None
self.geometry = None
Expand Down Expand Up @@ -114,13 +115,23 @@ def __init__(self, descriptor=None, file=None, name='Microstructure'):
if file is not None:
logging.warning(
'WARNING: Input parameter (descriptor) and file are given. Only descriptor will be used.')
if self.nphases == 1 and 'Phase' in self.descriptor[0].keys():
vf = self.descriptor[0]['Phase']['Volume fraction']
if vf < 1.0:
# consider precipitates/pores/particles with volume fraction vf in a matrix
# precipitates will be phase 0, matrix phase will get number 1 and be assigned to grain with ID 0
self.precipit = vf
self.nphases = 2
logging.info(f'Only one phase with volume fraction {vf} is given.')
logging.info('Will consider a sparse distribution in a matrix phase with phase number 1, ' +
'which will be assigned to grain with ID 0.')
return

"""
-------- Routines for user interface --------
"""

def init_RVE(self, descriptor=None, nsteps=1000, porosity=None):
def init_RVE(self, descriptor=None, nsteps=1000):
"""
Creates particle distribution inside simulation box (RVE) based on
the data provided in the data file.
Expand All @@ -129,7 +140,7 @@ def init_RVE(self, descriptor=None, nsteps=1000, porosity=None):
----------
descriptor
nsteps
porosity
precipit
Returns
-------
Expand All @@ -139,10 +150,12 @@ def init_RVE(self, descriptor=None, nsteps=1000, porosity=None):
descriptor = self.descriptor
if type(descriptor) is not list:
descriptor = [descriptor]
self.porosity = porosity

# initialize RVE, including mesh dimensions and particle distribution
self.rve = RVE_creator(descriptor, nsteps=nsteps, porosity=porosity)
self.rve = RVE_creator(descriptor, nsteps=nsteps)
if self.precipit is not None:
self.rve.phase_names.append('Matrix')
self.rve.phase_vf.append(1.0 - self.precipit)
self.nparticles = self.rve.nparticles
# store geometry in simbox object
self.simbox = Simulation_Box(self.rve.size)
Expand All @@ -155,10 +168,9 @@ def pack(self, particle_data=None,
particle_data = self.rve.particle_data
if particle_data is None:
raise ValueError('No particle_data in pack. Run create_RVE first.')
if vf is None and type(self.porosity) is float:
vf = np.minimum(1. - self.porosity, 0.7) # 70% is maximum packing density of ellipsoids
print(f'Porosity: Packing up to particle volume fraction of {vf}.')
print(f'Porosity: Packing up to particle volume fraction of {vf}.')
if vf is None and self.precipit is not None:
vf = np.minimum(self.precipit, 0.7) # 70% is maximum packing density of ellipsoids
print(f'Sparse particles (precipitates/pores): Packing up to particle volume fraction of {vf}.')
self.particles, self.simbox = \
packingRoutine(particle_data, self.rve.periodic,
self.rve.packing_steps, self.simbox,
Expand All @@ -184,7 +196,7 @@ def voxelize(self, particles=None, dim=None):
self.mesh.create_voxels(self.simbox)

self.mesh = \
voxelizationRoutine(particles, self.mesh, porosity=self.porosity)
voxelizationRoutine(particles, self.mesh, prec_vf=self.precipit)
if np.any(self.nparticles != self.mesh.ngrains_phase):
logging.info(f'Number of grains per phase changed from {self.nparticles} to ' +
f'{list(self.mesh.ngrains_phase)} during voxelization.')
Expand Down Expand Up @@ -227,8 +239,8 @@ def generate_grains(self):

if self.mesh is None or self.mesh.grains is None:
raise ValueError('No information about voxelized microstructure. Run voxelize first.')
if self.porosity and 0 in self.mesh.grain_dict.keys():
# in case of porosity, remove irregular grain 0 from analysis
if self.precipit and 0 in self.mesh.grain_dict.keys():
# in case of precipit, remove irregular grain 0 from analysis
empty_vox = self.mesh.grain_dict.pop(0)
grain_store = self.mesh.grain_phase_dict.pop(0)
nphases = self.rve.nphases - 1
Expand Down Expand Up @@ -1082,7 +1094,7 @@ def import_particles(self, file, path='./'):
def init_stats(self, descriptor=None, gs_data=None, ar_data=None, porous=False, save_files=False):
""" Legacy function for plot_stats_init."""
logging.warning('"init_stats" is a legacy function and will be depracted, please use "plot_stats_init()".')
self.plot_stats_init(descriptor, gs_data=gs_data, ar_data=ar_data, porous=porous, save_files=save_files)
self.plot_stats_init(descriptor, gs_data=gs_data, ar_data=ar_data, save_files=save_files)

def output_abq(self, nodes=None, name=None,
voxel_dict=None, grain_dict=None, faces=None,
Expand Down
Loading

0 comments on commit e89445b

Please sign in to comment.