Source code for cratepy.clustering.citoperations

"""Green operator and cluster interaction tensors related operations.

This module includes several procedures required to compute the cluster
interaction tensors, namely (1) the frequency discretization of the spatial
domain and (2) the computation of the Green operator, as well as the assembly
of the global cluster interaction matrix.

Functions
---------
set_discrete_freqs
    Perform frequency discretization of the spatial domain.
gop_material_independent_terms
    Compute Green operator material independent terms in frequency domain.
assemble_cit
    Assemble global cluster interaction matrix.
"""
#
#                                                                       Modules
# =============================================================================
# Standard
import copy
import itertools as it
# Third-party
import numpy as np
import numpy.matlib
# Local
import tensor.matrixoperations as mop
#
#                                                          Authorship & Credits
# =============================================================================
__author__ = 'Bernardo Ferreira (bernardo_ferreira@brown.edu)'
__credits__ = ['Bernardo Ferreira', ]
__status__ = 'Stable'
# =============================================================================
#
# =============================================================================
#
#                                                          Discrete frequencies
# =============================================================================
[docs]def set_discrete_freqs(n_dim, rve_dims, n_voxels_dims): """Perform frequency discretization of the spatial domain. Perform frequency discretization by setting the spatial discrete frequencies (rad/m) for each dimension. *2D case*: .. math:: \\boldsymbol{\\zeta}_{s_{1}, s_{2}} = \\left( \\dfrac{2\\pi}{(l_{\\mathrm{RVE}})_{1}} s_{1}, \\, \\dfrac{2\\pi}{(l_{\\mathrm{RVE}})_{2}} s_{2} \\right) \\, , .. math:: s_{i}=0, 1, \\dots, n_{i}-1 \\, , \\quad i=1,2 \\, . where :math:`\\boldsymbol{\\zeta}_{s_{1}, s_{2}} \\equiv \\boldsymbol{\\zeta}(s_{1}, s_{2})` denotes a sampling angular frequency, :math:`(l_{\\mathrm{RVE}})_{i}` is the RVE size in the :math:`i` th dimension, and :math:`n_{i}` is the number of voxels in the :math:`i` th dimension. ---- *3D case*: .. math:: \\boldsymbol{\\zeta}_{s_{1}, s_{2}, s_{3}} = \\left( \\dfrac{2\\pi}{(l_{\\mathrm{RVE}})_{1}} s_{1}, \\, \\dfrac{2\\pi}{(l_{\\mathrm{RVE}})_{2}} s_{2}, \\, \\dfrac{2\\pi}{(l_{\\mathrm{RVE}})_{3}} s_{3} \\right) \\, , .. math:: s_{i}=0, 1, \\dots, n_{i}-1 \\, , \\quad i=1,2,3 \\, . where :math:`\\boldsymbol{\\zeta}_{s_{1}, s_{2}, s_{3}} \\equiv \\boldsymbol{\\zeta}(s_{1}, s_{2}, s_{3})` denotes a sampling angular frequency, :math:`(l_{\\mathrm{RVE}})_{i}` is the RVE size in the :math:`i` th dimension, and :math:`n_{i}` is the number of voxels in the :math:`i` th dimension. ---- Parameters ---------- n_dim : int Problem number of spatial dimensions. rve_dims : list RVE size in each dimension. n_voxels_dims : list[int] Number of voxels in each dimension of the regular grid (spatial discretization of the RVE). Returns ------- freq_dims : list[numpy.ndarray (1d)] List containing the sample frequencies (numpy.ndarray (1d) of shape (n_voxels_dim,)) for each dimension. """ freqs_dims = list() for i in range(n_dim): # Set sampling spatial period sampling_period = rve_dims[i]/n_voxels_dims[i] # Set discrete frequencies freqs_dims.append(2*np.pi*np.fft.fftfreq(n_voxels_dims[i], sampling_period)) # Return return freqs_dims
# # Green operator # =============================================================================
[docs]def gop_material_independent_terms(strain_formulation, problem_type, rve_dims, n_voxels_dims): """Compute Green operator material independent terms in frequency domain. The Green operator is here assumed to be associated with an isotropic elastic reference material. The material independent terms of the Green operator are conveniently computed to perform an efficient update of the Green operator if the associated reference material elastic properties are updated by any means (e.g., self-consistent scheme). *Infinitesimal strains*: .. math:: (\\breve{\\Phi}^{0}_{1})_{klij} (\\boldsymbol{\\zeta}) = \\dfrac{ \\delta_{ki} \\zeta_{j} \\zeta_{l} + \\delta_{kj} \\zeta_{i} \\zeta_{l} + \\delta_{li} \\zeta_{j} \\zeta_{k} + \\delta_{lj} \\zeta_{i} \\zeta_{k}} {||\\boldsymbol{\\zeta}||^{2}} \\, , .. math:: (\\breve{\\Phi}^{0}_{2})_{klij} (\\boldsymbol{\\zeta}) = - \\dfrac{\\zeta_{k}\\zeta_{l}\\zeta_{i}\\zeta_{j}} {||\\boldsymbol{\\zeta}||^{4}} \\, , .. math:: i,j,k,l =1, \\dots, n_{\\text{dim}} \\, , where :math:`(\\breve{\\Phi}^{0}_{1})_{klij}` and :math:`(\\breve{\\Phi}^{0}_{2})_{klij}` are the first and second fourth-order Green operator material independent terms, respectively, :math:`\\boldsymbol{\\zeta}` is the frequency wave vector, :math:`\\delta_{ij}` is the Kronecker delta, and :math:`n_{\\text{dim}}` is the number of spatial dimensions. ---- *Finite strains*: .. math:: (\\breve{\\Phi}^{0}_{1})_{klij} (\\boldsymbol{\\zeta}) = \\dfrac{ \\delta_{ki} \\zeta_{j} \\zeta_{l}} {||\\boldsymbol{\\zeta}||^{2}} \\, , \\qquad (\\breve{\\Phi}^{0}_{2})_{klij} (\\boldsymbol{\\zeta}) = - \\dfrac{\\zeta_{k}\\zeta_{l}\\zeta_{i}\\zeta_{j}} {||\\boldsymbol{\\zeta}||^{4}} \\, , .. math:: i,j,k,l =1, \\dots, n_{\\text{dim}} \\, . where :math:`(\\breve{\\Phi}^{0}_{1})_{klij}` and :math:`(\\breve{\\Phi}^{0}_{2})_{klij}` are the first and second fourth-order Green operator material independent terms, respectively, :math:`\\boldsymbol{\\zeta}` is the frequency wave vector, :math:`\\delta_{ij}` is the Kronecker delta, and :math:`n_{\\text{dim}}` is the number of spatial dimensions. A detailed description of the computational implementation based on Hadamard (element-wise) operations can be found in Section 4.6 of Ferreira (2022) [#]_. .. [#] Ferreira, B.P. (2022). *Towards Data-driven Multi-scale Optimization of Thermoplastic Blends: Microstructural Generation, Constitutive Development and Clustering-based Reduced-Order Modeling.* PhD Thesis, University of Porto (see `here <https://repositorio-aberto.up.pt/handle/10216/ 146900?locale=en>`_) ---- Parameters ---------- strain_formulation: {'infinitesimal', 'finite'} Problem strain formulation. problem_type : int Problem type: 2D plane strain (1), 2D plane stress (2), 2D axisymmetric (3) and 3D (4). rve_dims : list[float] RVE size in each dimension. n_voxels_dims : list[int] Number of voxels in each dimension of the regular grid (spatial discretization of the RVE). Returns ------- gop_1_dft_vox : dict Regular grid shaped matrix (item, numpy.ndarray) containing each fourth-order matricial form component (key, str) of the first Green operator material independent term in the frequency domain (discrete Fourier transform). gop_2_dft_vox : dict Regular grid shaped matrix (item, numpy.ndarray) containing each fourth-order matricial form component (key, str) of the second Green operator material independent term in the frequency domain (discrete Fourier transform). gop_0_freq_dft_vox : dict Regular grid shaped matrix (item, numpy.ndarray) containing each fourth-order matricial form component (key, str) of the Green operator zero-frequency (material independent) term in the frequency domain (discrete Fourier transform). """ # Get problem type parameters n_dim, comp_order_sym, comp_order_nsym = \ mop.get_problem_type_parameters(problem_type) # Set strain/stress components order according to problem strain # formulation if strain_formulation == 'infinitesimal': comp_order = comp_order_sym elif strain_formulation == 'finite': comp_order = comp_order_nsym else: raise RuntimeError('Unknown problem strain formulation.') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set discrete frequencies (rad/m) for each dimension freqs_dims = set_discrete_freqs(n_dim, rve_dims, n_voxels_dims) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Set Green operator matricial form components comps = list(it.product(comp_order, comp_order)) # Set mapping between Green operator fourth-order tensor and matricial form # components fo_indexes = list() mf_indexes = list() for i in range(len(comp_order)**2): fo_indexes.append([int(x) - 1 for x in list(comps[i][0] + comps[i][1])]) mf_indexes.append([x for x in [comp_order.index(comps[i][0]), comp_order.index(comps[i][1])]]) # Set optimized variables var1 = [*np.meshgrid(*freqs_dims, indexing='ij')] var2 = dict() for fo_idx in fo_indexes: if str(fo_idx[1]) + str(fo_idx[3]) not in var2.keys(): var2[str(fo_idx[1]) + str(fo_idx[3])] = \ np.multiply(var1[fo_idx[1]], var1[fo_idx[3]]) if ''.join([str(x) for x in fo_idx]) not in var2.keys(): var2[''.join([str(x) for x in fo_idx])] = \ np.multiply(np.multiply(var1[fo_idx[0]], var1[fo_idx[1]]), np.multiply(var1[fo_idx[2]], var1[fo_idx[3]])) if strain_formulation == 'infinitesimal': if str(fo_idx[1]) + str(fo_idx[2]) not in var2.keys(): var2[str(fo_idx[1]) + str(fo_idx[2])] = \ np.multiply(var1[fo_idx[1]], var1[fo_idx[2]]) if str(fo_idx[0]) + str(fo_idx[2]) not in var2.keys(): var2[str(fo_idx[0]) + str(fo_idx[2])] = \ np.multiply(var1[fo_idx[0]], var1[fo_idx[2]]) if str(fo_idx[0]) + str(fo_idx[3]) not in var2.keys(): var2[str(fo_idx[0]) + str(fo_idx[3])] = \ np.multiply(var1[fo_idx[0]], var1[fo_idx[3]]) if n_dim == 2: var3 = np.sqrt(np.add(np.square(var1[0]), np.square(var1[1]))) else: var3 = np.sqrt(np.add(np.add(np.square(var1[0]), np.square(var1[1])), np.square(var1[2]))) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Initialize Green operator material independent terms gop_1_dft_vox = {''.join([str(x+1) for x in idx]): np.zeros(tuple(n_voxels_dims)) for idx in fo_indexes} gop_2_dft_vox = {''.join([str(x+1) for x in idx]): np.zeros(tuple(n_voxels_dims)) for idx in fo_indexes} gop_0_freq_dft_vox = {''.join([str(x+1) for x in idx]): np.zeros(tuple(n_voxels_dims)) for idx in fo_indexes} # Compute Green operator matricial form components for i in range(len(mf_indexes)): # Get fourth-order tensor indexes fo_idx = fo_indexes[i] # Get Green operator component comp = ''.join([str(x+1) for x in fo_idx]) # Set optimized variables var4 = [fo_idx[0] == fo_idx[2], fo_idx[0] == fo_idx[3], fo_idx[1] == fo_idx[3], fo_idx[1] == fo_idx[2]] var5 = [str(fo_idx[1]) + str(fo_idx[3]), str(fo_idx[1]) + str(fo_idx[2]), str(fo_idx[0]) + str(fo_idx[2]), str(fo_idx[0]) + str(fo_idx[3])] var4 = [fo_idx[0] == fo_idx[2], ] var5 = [str(fo_idx[1]) + str(fo_idx[3]), ] if strain_formulation == 'infinitesimal': var4 += [fo_idx[0] == fo_idx[3], fo_idx[1] == fo_idx[3], fo_idx[1] == fo_idx[2]] var5 += [str(fo_idx[1]) + str(fo_idx[2]), str(fo_idx[0]) + str(fo_idx[2]), str(fo_idx[0]) + str(fo_idx[3])] # Compute first material independent term of Green operator first_term = np.zeros(tuple(n_voxels_dims)) for j in range(len(var4)): if var4[j]: first_term = np.add(first_term, var2[var5[j]]) first_term = np.divide(first_term, np.square(var3), where=abs(var3) > 1e-10) gop_1_dft_vox[comp] = copy.copy(first_term) # Compute second material independent term of Green operator gop_2_dft_vox[comp] = -1.0*np.divide( var2[''.join([str(x) for x in fo_idx])], np.square(np.square(var3)), where=abs(var3) > 1e-10) # Compute Green operator zero-frequency term gop_0_freq_dft_vox[comp][tuple(n_dim*(0,))] = 1.0 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return gop_1_dft_vox, gop_2_dft_vox, gop_0_freq_dft_vox
# # Global cluster interaction matrix assembly # =============================================================================
[docs]def assemble_cit(strain_formulation, problem_type, mat_prop_ref, material_phases, phase_n_clusters, phase_clusters, cit_1_mf, cit_2_mf, cit_0_freq_mf): """Assemble global cluster interaction matrix. Update the cluster interaction tensors by taking into account the material properties of the reference material and assemble them in the global cluster interaction matrix. The dependency on the material properties of the reference material stems from the definition of the Green operator as shown below. *Infinitesimal strains*: .. math:: \\breve{\\mathbf{\\Phi}}^{0} (\\boldsymbol{\\zeta}) = c_{1}(\\lambda^{0}, \\mu^{0}) \\, \\breve{\\mathbf{\\Phi}}^{0}_{1} + c_{2}(\\lambda^{0}, \\mu^{0}) \\, \\breve{\\mathbf{\\Phi}}^{0}_{2} \\, , .. math:: c_{1}(\\lambda^{0}, \\mu^{0}) = \\dfrac{1}{4 \\mu^{0}} \\, , \\qquad c_{2}(\\lambda^{0}, \\mu^{0}) = \\dfrac{\\lambda^{0} + \\mu^{0}}{\\mu^{0}(\\lambda^{0} + 2 \\mu^{0})} where :math:`\\breve{\\mathbf{\\Phi}}^{0}_{1}` and :math:`\\breve{\\mathbf{\\Phi}}^{0}_{2}` are the first and second fourth-order Green operator material independent terms, respectively, and :math:`(\\lambda^{0}, \\mu^{0})` are the reference elastic (isotropic) material Lamé parameters. ---- *Finite strains*: .. math:: \\breve{\\mathbf{\\Phi}}^{0} (\\boldsymbol{\\zeta}) = c_{1}(\\lambda^{0}, \\mu^{0}) \\, \\breve{\\mathbf{\\Phi}}^{0}_{1} + c_{2}(\\lambda^{0}, \\mu^{0}) \\, \\breve{\\mathbf{\\Phi}}^{0}_{2} \\, , .. math:: c_{1}(\\lambda^{0}, \\mu^{0}) = \\dfrac{1}{2 \\mu^{0}} \\, , \\qquad c_{2}(\\lambda^{0}, \\mu^{0}) = \\dfrac{\\lambda^{0}}{2 \\mu^{0}(\\lambda^{0} + 2 \\mu^{0})} where :math:`\\breve{\\mathbf{\\Phi}}^{0}_{1}` and :math:`\\breve{\\mathbf{\\Phi}}^{0}_{2}` are the first and second fourth-order Green operator material independent terms, respectively, and :math:`(\\lambda^{0}, \\mu^{0})` are the reference elastic (isotropic) material Lamé parameters. ---- Parameters ---------- strain_formulation: {'infinitesimal', 'finite'} Problem strain formulation. problem_type : int Problem type: 2D plane strain (1), 2D plane stress (2), 2D axisymmetric (3) and 3D (4). mat_prop_ref : dict Reference material properties. material_phases : list[str] RVE material phases labels (str). phase_n_clusters : dict Number of clusters (item, int) prescribed for each material phase (key, str). phase_clusters : dict Clusters labels (item, list[int]) associated to each material phase (key, str). cit_1_mf : dict Cluster interaction tensors associated with the first Green operator material independent term. Each tensor is stored in a dictionary (item, dict) for each pair of material phases (key, str), which in turn contains the corresponding matricial form (item, numpy.ndarray) associated to each pair of clusters (key, str). cit_2_mf : dict Cluster interaction tensors associated with the second Green operator material independent term. Each tensor is stored in a dictionary (item, dict) for each pair of material phases (key, str), which in turn contains the corresponding matricial form (item, numpy.ndarray) associated to each pair of clusters (key, str). cit_0_freq_mf : dict Cluster interaction tensors associated with the zero-frequency Green operator (material independent) term. Each tensor is stored in a dictionary (item, dict) for each pair of material phases (key, str), which in turn contains the corresponding matricial form (item, numpy.ndarray) associated to each pair of clusters (key, str). Returns ------- global_cit_mf : numpy.ndarray Global cluster interaction matrix. Assembly positions are assigned according to the order of material_phases (1st) and phase_clusters (2nd). """ # Get problem type parameters _, comp_order_sym, comp_order_nsym = \ mop.get_problem_type_parameters(problem_type) # Set strain/stress components order according to problem strain # formulation if strain_formulation == 'infinitesimal': comp_order = comp_order_sym elif strain_formulation == 'finite': comp_order = comp_order_nsym else: raise RuntimeError('Unknown problem strain formulation.') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get total number of clusters n_total_clusters = sum([phase_n_clusters[mat_phase] for mat_phase in material_phases]) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get reference material Young modulus and Poisson ratio E_ref = mat_prop_ref['E'] v_ref = mat_prop_ref['v'] # Compute reference material Lamé parameters lam_ref = (E_ref*v_ref)/((1.0 + v_ref)*(1.0 - 2.0*v_ref)) miu_ref = E_ref/(2.0*(1.0 + v_ref)) # Compute Green operator's reference material coefficients if strain_formulation == 'infinitesimal': gop_factor_1 = 1.0/(4.0*miu_ref) gop_factor_2 = (lam_ref + miu_ref)/(miu_ref*(lam_ref + 2.0*miu_ref)) else: gop_factor_1 = 1.0/(2.0*miu_ref) gop_factor_2 = lam_ref/(2.0*miu_ref*(lam_ref + 2.0*miu_ref)) gop_factor_0_freq = numpy.matlib.repmat( np.zeros((len(comp_order), len(comp_order))), n_total_clusters, n_total_clusters) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Initialize global clustering interaction matrices dims = (n_total_clusters*len(comp_order), n_total_clusters*len(comp_order)) global_cit_1_mf = np.zeros(dims) global_cit_2_mf = np.zeros(dims) global_cit_0_freq_mf = np.zeros(dims) # Initialize column cluster index jclst = 0 # Loop over material phases for mat_phase_B in material_phases: # Loop over material phase B clusters for cluster_J in phase_clusters[mat_phase_B]: # Initialize row cluster index iclst = 0 # Loop over material phases for mat_phase_A in material_phases: # Set material phase pair mat_phase_pair = mat_phase_A + '_' + mat_phase_B # Loop over material phase A clusters for cluster_I in phase_clusters[mat_phase_A]: # Set cluster pair cluster_pair = str(cluster_I) + '_' + str(cluster_J) # Set assembling ranges i_init = iclst*len(comp_order) i_end = i_init + len(comp_order) j_init = jclst*len(comp_order) j_end = j_init + len(comp_order) # Assemble cluster interaction tensors global_cit_1_mf[i_init:i_end, j_init:j_end] = \ cit_1_mf[mat_phase_pair][cluster_pair] global_cit_2_mf[i_init:i_end, j_init:j_end] = \ cit_2_mf[mat_phase_pair][cluster_pair] global_cit_0_freq_mf[i_init:i_end, j_init:j_end] = \ cit_0_freq_mf[mat_phase_pair][cluster_pair] # Increment row cluster index iclst = iclst + 1 # Increment column cluster index jclst = jclst + 1 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Assemble global cluster interaction matrix global_cit_mf = np.add( np.add(np.multiply(gop_factor_1, global_cit_1_mf), np.multiply(gop_factor_2, global_cit_2_mf)), np.multiply(gop_factor_0_freq, global_cit_0_freq_mf)) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return global_cit_mf