Source code for simulators.links.links

"""Finite element method simulator: Links.

Links (Large Strain Implicit Nonlinear Analysis of Solids Linking Scales) is
a finite element code developed by the CM2S research group at the Faculty of
Engineering, University of Porto.

Classes
-------
LinksSimulator
    Finite element method simulator: Links.
"""
#
#                                                                       Modules
# =============================================================================
# Standard
import os
import subprocess
import re
import copy
import itertools
# Third-party
import numpy as np
import matplotlib.pyplot as plt
# Local
from ioput.iostandard import make_directory, new_file_path_with_int
from ioput.plots import plot_xy_data
from simulators.links.discretization.finite_element import FiniteElement
from simulators.links.models.links_elastic import LinksElastic
from simulators.links.models.links_von_mises import LinksVonMises
from simulators.links.models.links_drucker_prager import LinksDruckerPrager
from simulators.links.models.links_lou import LinksLou
#
#                                                          Authorship & Credits
# =============================================================================
__author__ = 'Bernardo Ferreira (bernardo_ferreira@brown.edu)'
__credits__ = ['Bernardo Ferreira', ]
__status__ = 'Alpha'
# =============================================================================
#
# =============================================================================
[docs]class LinksSimulator: """Finite element method simulator: Links. Attributes ---------- _links_bin_path : str Links binary absolute path. _strain_formulation: {'infinitesimal', 'finite'} Links strain formulation. _analysis_type : {'plane_stress', 'plane_strain', 'axisymmetric', \ 'tridimensional'} Links analysis type. Methods ------- _get_n_dim(self) Get number of spatial dimensions from analysis type. generate_input_data_file(self, filename, directory, patch, \ patch_material_data, links_input_params=None) Generate Links input data file. run_links_simulation(self, links_file_path) Run Links simulation. read_links_simulation_results(self, links_output_directory, \ results_keywords) Read Links simulation results. _write_links_input_data_file(self, links_file_path, input_params, \ node_coords, elements, elements_mat_phase, \ element_type, n_gauss_points, \ mesh_elem_material, mat_phases_descriptors, \ node_displacements) Write Links input data file. _get_links_default_input_params(self) Get Links default input data file parameters. _get_links_mesh_data(self, patch, mesh_elem_material) Get Links finite element mesh data. _get_links_elem_type_data(self, patch) Get Links finite element type data. _get_links_loading_incrementation(self) Get Links loading incrementation. remove_mesh_elements(cls, remove_elements_labels, node_coords, elements, \ elements_mat_phase, node_disps=None, \ boundary_nodes_labels=None) Remove elements from finite element mesh. """
[docs] def __init__(self, links_bin_path, strain_formulation, analysis_type): """Constructor. Parameters ---------- links_bin_path : str Links binary absolute path. strain_formulation: {'infinitesimal', 'finite'} Links strain formulation. analysis_type : {'plane_stress', 'plane_strain', 'axisymmetric', \ 'tridimensional'} Links analysis type. """ self._links_bin_path = links_bin_path self._strain_formulation = strain_formulation self._analysis_type = analysis_type
# -------------------------------------------------------------------------
[docs] def _get_n_dim(self): """Get number of spatial dimensions from analysis type. Returns ------- n_dim : int Number of spatial dimensions. """ if self._analysis_type == 'tridimensional': n_dim = 3 else: n_dim = 2 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return n_dim
# -------------------------------------------------------------------------
[docs] def generate_input_data_file(self, filename, directory, patch, patch_material_data, remove_elements_labels=None, links_input_params=None, is_overwrite_file=False, is_save_increm_file=False, is_verbose=False): """Generate Links input data file. Parameters ---------- filename : str Links input data file name. directory : str Directory where Links input data file is stored. patch : FiniteElementPatch Finite element patch. If `is_admissible` is False, then returns None. patch_material_data : dict Finite element patch material data. Expecting 'mesh_elem_material': numpy.ndarray [int](n_elems_per_dim) (finite element mesh elements material matrix where each element corresponds to a given finite element position and whose value is the corresponding material phase (int)) and 'mat_phases_descriptors': dict (constitutive model descriptors (item, dict) for each material phase (key, str[int])). remove_elements_labels : tuple[int], default=None Finite elements to be removed from finite element mesh. links_input_params : dict, default=None Links input data file parameters. If None, default parameters are set. is_overwrite_file : bool, default=False Overwrite existing Links input data file if True, otherwise generate non-existent file path by extending the original file path with an integer. is_save_increm_file : bool, default=False If True, then save data file with Links input data file loading incrementation data. is_verbose : bool, default=False If True, enable verbose output. Returns ------- links_file_path : str Links input data file path. """ if is_verbose: print('\nGenerating Links simulation input data file' '\n-------------------------------------------') print('\n> Setting input data file path...') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Create directory if it does not exist if not os.path.exists(directory): make_directory(directory) # Set Links input data file path links_file_path = \ os.path.join(os.path.normpath(directory), filename) + '.dat' if os.path.isfile(links_file_path) and not is_overwrite_file: links_file_path = new_file_path_with_int(links_file_path) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if is_verbose: print('\n> Setting Links input data file parameters...') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get Links default input data file parameters input_params = self._get_links_default_input_params() # Set Links input data file parameters if links_input_params is not None: for param in links_input_params.keys(): if param in input_params.keys(): input_params[param] = links_input_params[param] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if is_verbose: print('\n> Generating finite element mesh data...') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get Links finite element mesh elements material matrix mesh_elem_material = patch_material_data['mesh_elem_material'] # Get Links finite element mesh data node_coords, elements, elements_mat_phase = \ self._get_links_mesh_data(patch, mesh_elem_material) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if is_verbose: print('\n> Getting prescribed node displacements...') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get Links prescribed node displacements node_displacements = patch.get_mesh_boundary_nodes_disps() # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Remove elements from Links finite elements mesh if remove_elements_labels is not None: # Get mesh boundary nodes labels boundary_nodes_labels = patch.get_boundary_nodes_labels() # Remove elements from Links finite elements mesh node_coords, elements, elements_mat_phase, node_displacements = \ self.remove_mesh_elements( remove_elements_labels, node_coords, elements, elements_mat_phase, node_disps=node_displacements, boundary_nodes_labels=boundary_nodes_labels) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get Links finite element type element_type, n_gauss_points = self._get_links_elem_type_data(patch) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get Links material phases descriptors mat_phases_descriptors = patch_material_data['mat_phases_descriptors'] # Check if descriptors were provided for all material phases if not set(mesh_elem_material.flatten()).issubset( set([int(id) for id in mat_phases_descriptors.keys()])): raise RuntimeError('Material descriptors were not provided ' 'for all material phases in the material ' 'patch.') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if is_verbose: print('\n> Writing Links simulation input data file...') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Write Links input data file self._write_links_input_data_file( links_file_path, input_params, node_coords, elements, elements_mat_phase, element_type, n_gauss_points, mesh_elem_material, mat_phases_descriptors, node_displacements, is_save_increm_file=is_save_increm_file) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if is_verbose: print('\n> File: ' + links_file_path + '\n') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return links_file_path
# ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # -------------------------------------------------------------------------
[docs] @classmethod def remove_mesh_elements(cls, remove_elements_labels, node_coords, elements, elements_mat_phase, node_disps=None, boundary_nodes_labels=None): """Remove elements from finite element mesh. Parameters ---------- remove_elements_labels : tuple[int] Finite elements to be removed from finite element mesh. node_coords : dict Coordinates (item, numpy.ndarray(n_dim)) of each finite element mesh node (key, str[int]). elements : dict Nodes (item, tuple[int]) of each finite element (key, str[int]). elements_mat_phase : dict Material phase (item, int) of each finite element (key, str). node_disps : dict, default=None Displacements (item, numpy.ndarray(n_dim)) of each finite element mesh node (key, str[int]). boundary_nodes_labels : tuple[int], default=None Finite element mesh boundary nodes labels. If provided, then removal of elements that would lead to the removal of a boundary node raises error. Returns ------- node_coords : dict Coordinates (item, numpy.ndarray(n_dim)) of each finite element mesh node (key, str[int]). elements : dict Nodes (item, tuple[int]) of each finite element (key, str[int]). elements_mat_phase : dict Material phase (item, int) of each finite element (key, str). node_disps : dict Displacements (item, numpy.ndarray(n_dim)) of each finite element mesh node (key, str[int]). None if displacements are not provided. """ # Store old nodes coordinates node_coords_old = copy.deepcopy(node_coords) # Store old elements elements_old = copy.deepcopy(elements) # Store old elements material phases elements_mat_phase_old = copy.deepcopy(elements_mat_phase) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get old nodes labels nodes_old_labels = set(itertools.chain(*list(elements_old.values()))) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Initialize element number elem_label = 1 # Initialize elements elements = {} # Initialize elements mapping (old to new) elem_label_map = {} # Loop over old elements for elem_old_label, elem_old_nodes in elements_old.items(): # Process remaining element if int(elem_old_label) not in remove_elements_labels: # Collect remaining element nodes elements[str(elem_label)] = elem_old_nodes # Assemble elements mapping (old to new) elem_label_map[str(elem_old_label)] = str(elem_label) # Increment element number elem_label += 1 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Get current nodes nodes_labels = set(itertools.chain(*list(elements.values()))) # Get nodes to be removed remove_nodes_labels = nodes_old_labels - nodes_labels # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Check removal of boundary nodes if boundary_nodes_labels is not None: # Loop over nodes for node_label in remove_nodes_labels: # Check if removed node is boundary node if node_label in boundary_nodes_labels: # Initialize invalid removed elements inv_remove_elements_labels = [] # Loop over elements for elem_label in remove_elements_labels: # Collect invalid removed elements if node_label in elements_old[str(elem_label)]: inv_remove_elements_labels.append(elem_label) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ raise RuntimeError( f'Removing elements that would lead to the removal of ' f'boundary nodes is not allowed. The following ' f'elements cannot be removed due to boundary node ' f'{node_label}: {inv_remove_elements_labels}') # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Initialize node number node_label = 1 # Initialize nodes coordinates node_coords = {} # Initialize nodes mapping (old to new) node_label_map = {} # Loop over old nodes for node_old_label, node_old_coords in node_coords_old.items(): # Process remaining node if int(node_old_label) not in remove_nodes_labels: # Collect remaining node coordinates node_coords[str(node_label)] = node_old_coords # Assemble nodes mapping (old to new) node_label_map[str(node_old_label)] = node_label # Increment node number node_label += 1 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Loop over elements for elem_label in elements.keys(): # Store old element nodes elem_old_nodes = elements[elem_label] # Build new element nodes elem_nodes = [node_label_map[str(node_old_label)] for node_old_label in elem_old_nodes] # Store new element nodes elements[elem_label] = tuple(elem_nodes) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Initialize elements material phases elements_mat_phase = {} # Loop over elements for elem_old_label, mat_phase in elements_mat_phase_old.items(): # Process remaining element if elem_old_label in elem_label_map.keys(): # Collect remaining element material phase elements_mat_phase[str(elem_label_map[elem_old_label])] = \ mat_phase # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # Map nodes displacements if isinstance(node_disps, dict): # Store old nodes displacements node_disps_old = copy.deepcopy(node_disps) # Initialize nodes displacements node_disps = {} # Loop over old nodes for node_old_label, node_old_disps in node_disps_old.items(): # Get node label node_label = node_label_map[str(node_old_label)] # Collect node displacements node_disps[str(node_label)] = node_old_disps else: node_disps = None # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return node_coords, elements, elements_mat_phase, node_disps