Implementation Details¶
This document describes the implementation details of each module, including the computational flow and key algorithms.
Configuration (config.py)¶
The configuration module defines all simulation parameters:
Geometry Settings:
SINGLE_GRAIN: Boolean to use single grain vs. image-based geometryDIMENSION: Problem dimension (3 for 3D)X_MIN, X_MAX, Y_MIN, Y_MAX, Z_MIN, Z_MAX: Domain boundaries (meters)
Material Properties:
DIFFUSION_ELECTROLYTE,DIFFUSION_ELECTRODE,DIFFUSION_PORE: Diffusion coefficientsE_ELECTROLYTE,E_ELECTRODE: Young’s modulus (Pa)NU_ELECTROLYTE,NU_ELECTRODE: Poisson’s ratioLAMBDA_MECHANICAL_*,MU_*: Computed Lamé constants
Electrochemical Parameters:
FARADAY_CONSTANT: 96,485 C/molGAS_CONSTANT: 8.314 J/mol·KI_0: Exchange current densityTEMPERATURE: Operating temperature (K)
Backend Abstraction (common.py)¶
The common.py module provides a unified interface for NumPy/SciPy or
Legate/cuNumeric backends:
# Attempts to import Legate, falls back to NumPy/SciPy
try:
import cupynumeric as np
import legate_sparse as sparse
except ImportError:
import numpy as np
import scipy.sparse as sparse
Exported Symbols:
np: NumPy or cuNumeric array operationssparse,sp: Sparse matrix modulecsr_array,dia_array: Sparse matrix constructorsspsolve: Sparse linear solverblock_diag,vstack,bmat: Sparse matrix utilitiestime(): High-resolution timer function
Image Reading (read_image.py)¶
The read_in_image() function reads voxelized microstructure data:
Input:
TIFF file with phase labels (0: pore, 1: electrolyte, 2: electrode)
Output:
img_: 3D NumPy array of phase IDsunic_grain_id: List of unique phase IDsnum_pixels_xyz: [nx, ny, nz] voxel counts
def read_in_image(file_name, studied_physics, dimension):
img_ = tifffile.imread(file_name)
# Extract unique grain IDs and dimensions
return img_, unic_grain_id, num_pixels_xyz
Node and Gauss Point Generation¶
get_nodes_gauss_points.py¶
Key Function: ``get_x_nodes_fuel_cell_3d_toy_image()``
Generates nodes and cell connectivity from voxelized geometry:
Creates nodes at voxel corners for each phase
Identifies boundary cells on domain faces
Detects interface cells between phases
Returns cell vertex coordinates for integration
Output arrays:
x_nodes_*: Node coordinates for each phasecell_nodes_*_x/y/z: Cell vertex coordinates (n_cells, 8)nodes_id_*: Node IDs on boundaries
get_nodes_gauss_points_vectorized.py¶
Function: ``x_G_and_def_J_time_weight_3d_fuelcell_domain_vectorized()``
Computes Gauss point coordinates and Jacobian-weighted measures for 3D hexahedral cells:
Evaluates 8-node trilinear shape functions at Gauss points
Computes Jacobian matrix from shape function derivatives
Returns physical coordinates and det(J) × weight
# Shape functions for 8-node hexahedron
N[:, 0] = (1 + xi) * (1 - eta) * (1 - zeta) / 8.0
N[:, 1] = (1 + xi) * (1 + eta) * (1 - zeta) / 8.0
# ... (8 shape functions total)
# Jacobian determinant
det_J = J11*(J22*J33 - J23*J32) - J12*(J21*J33 - J23*J31) + J13*(J21*J32 - J22*J31)
Shape Function Computation¶
shape_function_in_domain.py¶
Function: ``compute_phi_M()``
Computes RKPM kernel values and moment matrices:
For each Gauss point, loops over nodes within support radius
Computes normalized distance \(z = \|x - x_I\| / a\)
Evaluates cubic B-spline kernel \(\phi(z)\)
Accumulates moment matrix \(M\) and derivatives
Kernel Evaluation:
if z >= 0 and z < 0.5:
phi = 2.0/3 - 4*z**2 + 4*z**3
phi_P_z = -8.0*z + 12.0*z**2
elif z >= 0.5 and z <= 1:
phi = 4.0/3 - 4*z + 4*z**2 - 4.0/3*z**3
phi_P_z = -4 + 8*z - 4*z**2
Function: ``shape_grad_shape_func()``
Computes shape function values and gradients:
shape_function_vectorized.py¶
Function: ``compute_phi_M_standard_sparse()``
Optimized vectorized implementation using advanced NumPy indexing:
Computes all Gauss-node distances at once using broadcasting
Uses
np.where()to find valid pairs (z ≤ 1)Accumulates M matrices using
np.add.at()for efficiency
# Vectorized distance computation
diff = x_G[:, np.newaxis, :] - x_nodes[np.newaxis, :, :] # (n_gauss, n_nodes, 3)
distances = np.sqrt(np.sum(diff**2, axis=2)) # (n_gauss, n_nodes)
z = distances / a[np.newaxis, :] # Normalized distances
# Find valid pairs
valid_mask = (z >= 0) & (z <= 1.0)
gauss_indices, node_indices = np.where(valid_mask)
Diffusion Matrix Assembly¶
define_diffusion_matrix_form.py¶
Function: ``diffusion_matrix_fuel_cell()``
Assembles the diffusion stiffness matrix using Nitsche’s method:
Stiffness Matrix Components:
# K1: Domain integral
K1 = (grad_shape_x_weighted.T * D) @ grad_shape_x + (grad_shape_y_weighted.T * D) @ grad_shape_y
# K2: Flux consistency term
K2 = -(grad_shape_b_x_weighted * n_x + grad_shape_b_y_weighted * n_y).T @ shape_b
# K3: Nitsche penalty term
K3 = (shape_b * beta).T @ shape_b_weighted
K = K1 + K2 + K3
Force Vector Components:
# f1: Boundary value penalty
f1 = (shape_b_weighted * beta).T @ g_dirichlet
# f2: Flux contribution
f2 = -(grad_shape_b_weighted * n).T @ g_dirichlet
# f3: Point/body source
f3 = shape_func_source.T @ point_source
f = f1 + f2 + f3
Mechanical Stiffness Assembly¶
define_mechanical_stiffness_matrix.py¶
Function: ``mechanical_C_tensor_3d()``
Constructs the 6×6 elasticity tensor in Voigt notation:
Computes Lamé constants from E and ν
Applies rotation for anisotropic materials
Applies damage degradation: \(C_{eff} = (1-D) C\)
Function: ``mechanical_stiffness_matrix_3d_fuel_cell()``
Assembles the 3×3 block mechanical stiffness matrix:
Each block involves combinations of stress components:
# K11: Coupling of u_x with u_x
K11 = (C11 * grad_x_weighted).T @ grad_x
+ (C44 * grad_y_weighted).T @ grad_y
+ (C55 * grad_z_weighted).T @ grad_z
# K12: Coupling of u_x with u_y
K12 = (C12 * grad_x_weighted).T @ grad_y
+ (C44 * grad_y_weighted).T @ grad_x
Main Simulation Flow (main.py)¶
The main script orchestrates the full simulation:
Setup Phase (lines 1-350): - Load configuration - Read microstructure image - Generate nodes for each phase - Create cell connectivity
Gauss Point Generation (lines 360-550): - Compute domain Gauss points and Jacobians - Compute boundary Gauss points - Compute interface Gauss points
Shape Function Computation (lines 550-1500): - Compute kernel values φ for all Gauss-node pairs - Assemble moment matrices M, M_P_x, M_P_y, M_P_z - Compute shape functions and gradients - Build sparse shape function matrices
Matrix Assembly (lines 1500-2500): - Assemble diffusion matrices for each phase - Apply boundary conditions - Assemble mechanical stiffness matrix - Build coupled system
Solution (lines 2500-3500): - Solve diffusion equations (electrolyte, electrode, pore) - Compute electrochemical source terms - Iterate for coupled solution - Solve mechanical problem
Post-processing (lines 3500-4400): - Compute stress and strain fields - Generate visualization plots - Output results