Source code for Muscat.IO.SamcefTools

from Muscat.IO.ReaderBase import ReaderBase
import subprocess
import os

[docs] def all_strings_are_floats(string_list): for string in string_list: try: float(string) except ValueError: return False return True
[docs] class PropertiesReader(ReaderBase): """Samcef Reader class """ def __init__(self, folderpath, projectname): super().__init__() self.folderpath = folderpath self.projectname = projectname
[docs] def get_next_element(self, string_list, s): try: index = string_list.index(s) if index < len(string_list) - 1: return string_list[index + 1] else: return None except ValueError: return None
[docs] def ReadElasticMat(self, fileName=None, string=None): """ Parse a trace file (.tra) describing ELASTIC materials and return a dictionary of parsed information. This method processes a .tra file resulting from the baconpost execution ".MAT LIST I n1 n2", which returns information on material definitions for identifiers between n1 and n2. The .tra file contains the identifier, name of the material, as well as: - Poisson number (described by reference values and a multiplicative temporal function) - Young modulus (described by reference values and a multiplicative temporal function) - Thermal expansion (described by reference values and a multiplicative temporal function) - Mass density - Reference temperature Parameters ---------- fileName : str The file path of the result of baconpost execution (.tra file). Returns ------- dict A dictionary where keys are ids of the materials and values are the corresponding material descriptions. Notes ----- - Don't forget to enter trace mode with "MODE TRACE" before executing ".MAT LIST I n1 n2" so that baconpost creates the .tra file. - This function may ignore some information from the .tra file. """ ReaderBase.SetFileName(self,fileName) self.StartReading() mat_id_to_mat_info = {} reading_young_modulus = False reading_poisson_number = False reading_thermal_expansion = False while True: line = self.ReadCleanLine() if line is None: break line = line.strip() if line.startswith("Material Nr"): lis = line.split() id = int(lis[3]) name = lis[4][1:-1] mat_id_to_mat_info[id] = {} mat_id_to_mat_info[id]["Name"] = name if line.startswith("YT") or line.startswith("NT") or line.startswith("A "): lis = line.split() multiplicative_coefficients = [float(x) for x in lis[2:]] save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()).split() while all_strings_are_floats(next_sec): multiplicative_coefficients.extend([float(x) for x in next_sec]) save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()).split() self.Seek(save_pos) if line.startswith("YT"): reading_young_modulus = True mat_id_to_mat_info[id]["YT_multiplicative_coefficients"] = multiplicative_coefficients if line.startswith("NT"): reading_poisson_number = True mat_id_to_mat_info[id]["NT_multiplicative_coefficients"] = multiplicative_coefficients if line.startswith("A "): reading_thermal_expansion = True mat_id_to_mat_info[id]["A_multiplicative_coefficients"] = multiplicative_coefficients if line.startswith("M "): lis = line.split() mass_density = float(lis[-1]) mat_id_to_mat_info[id]["mass_density"] = mass_density if line.startswith("<NF>"): lis = line.split() multiplicative_function_ids = [int(func_nbr) for func_nbr in lis[1:]] if reading_young_modulus : mat_id_to_mat_info[id]["YT_multiplicative_functions_ids"] = multiplicative_function_ids if reading_poisson_number : mat_id_to_mat_info[id]["NT_multiplicative_functions_ids"] = multiplicative_function_ids if reading_thermal_expansion : mat_id_to_mat_info[id]["A_multiplicative_functions_ids"] = multiplicative_function_ids #We assume that after reading <NF>, there is no other things to learn about young modulus (or poisson numbers) #This is not a general property and must be adapted in some cases reading_young_modulus = False reading_poisson_number = False reading_thermal_expansion = False if line.startswith("TREF"): lis = line.split() t_ref = float(lis[-1]) mat_id_to_mat_info[id]["TREF"] = t_ref return mat_id_to_mat_info
[docs] def ReadElementAttributes(self): """ Create a trace file and parse element/material affectations. This method creates a trace file (.tra) describing element/material affectations and returns a dictionary of parsed information. The .tra file is the result of the baconpost execution ".AEL LIST" (returns information of attributions of properties to element). The .tra file contains the element ids, the corresponding degree, the material name affected to this element, and the integration rules. Returns ------- dict A dictionary where keys are ids of the elements and values are the corresponding material. Notes ----- CURRENTLY, THIS METHOD ONLY ALLOWS READING MATERIALS (affected with .AEL in the .dat file), and is not designed to behave well if there are other attributes affected with .AEL in the .dat file. This function may ignore some information. """ project = os.path.join(self.folderpath, self.projectname) cmd = ( f'samcef ba {project} n 1 zone=1000000000 >> {project}.log <<INPUTCMD\n' f';.DOC DB "{project}"\n' f';ASSIGN FAC "{project}_as"\n' f';MODE TRACE "{self.folderpath}/list_ael.tra"\n' f';.AEL LIST\n' f';MODE TRACE 0\n' f';.FIN 1\n' f'INPUTCMD\n' ) subprocess.run(cmd, shell=True, check=True) fileName = os.path.join(self.folderpath, "list_ael.tra") ReaderBase.SetFileName(self,fileName) self.StartReading() element_id_to_element_info = {} while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if lis[0].startswith("Element"): element_id = int(lis[2]) element_id_to_element_info[element_id] = {} lis = lis[3:] #the element description may start on the same line elif lis[0].startswith("DEG"): element_degree = int(lis[1]) element_id_to_element_info[element_id]["DEG"] = element_degree elif lis[0].startswith("MAT"): element_mat_name = lis[-1][1:-1] element_id_to_element_info[element_id]["MAT_NAME"] = element_mat_name elif lis[0].startswith("NG"): element_integration_rules = [int(x) for x in lis[1:]] element_id_to_element_info[element_id]["NG"] = element_integration_rules return element_id_to_element_info
[docs] def ReadMechanicalBoundaryConditions(self): """ Create a trace file describing mechanical boundary conditions. This reader only considers conditions imposed on Node, Edge, and Structure. It creates a .tra file describing mechanical boundary conditions and returns a dictionary of parsed information. The .tra file is the result of the baconpost execution ".CLM LIST TOUT" and contains element ids (for mechanical conditions on edges), node ids, or structure, as well as the nature of the mechanical boundary condition, components, reference values, and corresponding function ids. Returns ------- dict A dictionary containing three key-value pairs: - node_id_to_clm : dict Keys are ids of the nodes, values are the corresponding boundary conditions. - element_id_to_clm : dict Keys are ids of the elements, values are the corresponding boundary conditions. - structure_id_to_clm : dict Keys are ids of the structures, values are the corresponding boundary conditions. Notes ----- This function may ignore some information. This function assumes that only Node, Edge, and Structure are concerned, which may be restrictive. The .tra file contains information about mechanical boundary conditions such as "Prescribed pressure", "Prescribed displacement", etc. """ project = os.path.join(self.folderpath, self.projectname) commands = ( f'samcef ba {project} n 1 zone=1000000000 >> {project}.log <<INPUTCMD\n' f';.DOC DB "{project}"\n' f';ASSIGN FAC "{project}_as"\n' f';MODE TRACE "{self.folderpath}/list_clm.tra"\n' f';.CLM LIST TOUT\n' f';MODE TRACE 0\n' f';.FIN 1\n' f'INPUTCMD\n' ) subprocess.run(commands, shell=True, check=True) fileName = os.path.join(self.folderpath, "list_clm.tra") ReaderBase.SetFileName(self,fileName) self.StartReading() #We assume for the moment that the only keywords appearing in the list are "Edge" (corresponding to an element), "Node", "Structure" node_id_to_clm = {} element_id_to_clm = {} structure_id_to_clm = {} reading_edge = False while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if lis[0].startswith("*"): if lis[1].startswith("Node"): current_dict = node_id_to_clm elif lis[1].startswith("Edge"): reading_edge = True current_dict = element_id_to_clm edge = int(lis[-1]) elif lis[1].startswith("Element"): current_dict = element_id_to_clm elif lis[1].startswith("Structure"): current_dict = structure_id_to_clm else: raise(Exception("Unknown keyword: "+lis[1])) if lis[2] == ":": id = int(lis[3]) else: id = int(lis[2].replace(":", "")) current_dict[id] = {} if reading_edge: current_dict[id]["Edge number"] = edge reading_edge = False if lis[0].startswith("-"): nature_of_condition = line.partition("[")[0][1:] current_dict[id]["condition nature"] = nature_of_condition elif lis[0].startswith("Function"): functions_ids = [int(x) for x in lis[2:]] if "functions" in current_dict[id]: current_dict[id]["functions"] += functions_ids else: current_dict[id]["functions"] = functions_ids elif lis[0].startswith("V"): ref_values = [float(x) for x in lis[2:]] if "reference values" in current_dict[id]: current_dict[id]["reference values"] += ref_values else: current_dict[id]["reference values"] = ref_values elif lis[0].startswith("Component"): components = [int(x) for x in lis[2:]] if "components" in current_dict[id]: current_dict[id]["components"] += components else: current_dict[id]["components"] = components return node_id_to_clm, element_id_to_clm, structure_id_to_clm
[docs] def ContactRead(self, fileName=None): """ Parse a trace file (.tra) describing contacts and return a dictionary of parsed information. This method processes a .tra file resulting from the baconpost execution ".MCT LIST n1 A n2 PAS k", which provides information on contacts. Before executing this command, ensure to enter trace mode with "MODE TRACE" so that baconpost creates the .tra file. The .tra file contains contact IDs along with the following information: - Attributes (int) - Connected nodes (connected to a face) - Target group (the facet) - Max Radius - Max number of elements - Triangulation option - Shells reverse option - Minimal distance DMIN - UN3 - OPT Parameters ---------- fileName : str The file path of the result of baconpost execution (.tra file). Returns ------- dict A dictionary where keys are IDs of the contacts and values are the corresponding definitions. Notes ----- This function may ignore some information from the trace file. This reader only considers Node-Facet contacts """ ReaderBase.SetFileName(self,fileName) self.StartReading() contact_id_to_infos = {} while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if lis[0].startswith("MCT"): contact_id = int(lis[1]) contact_id_to_infos[contact_id] = {} if len(lis) > 1 and lis[0].startswith("---") and lis[1].startswith("Attribute"): contact_id_to_infos[contact_id]["attribute"] = int(lis[-1]) if len(lis) > 1 and lis[0].startswith("Connected") and lis[1].startswith("node"): #extract connected nodes (we must also extract the facets later) connected_nodes_ids = [int(x) for x in lis[2:]] save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()).split() while all_strings_are_floats(next_sec): connected_nodes_ids.extend([int(x) for x in next_sec]) save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()).split() self.Seek(save_pos) contact_id_to_infos[contact_id]["connected nodes"] = connected_nodes_ids if len(lis) > 1 and lis[0].startswith("Target") and lis[1].startswith("group"): target_group_id = int(lis[-1]) contact_id_to_infos[contact_id]["Group Target (GTAR)"] = target_group_id if len(lis) > 1 and lis[0].startswith("Max") and lis[1].startswith("radius"): max_radius = line.partition(":")[2] try : #try in case max_radius is not convertible to a float -> e.g. max_radius = "* no limit *" max_radius = float(max_radius) except : contact_id_to_infos[contact_id]["Max Radius (RMAX)"] = max_radius if line.startswith("Max. number of elem."): max_number_of_elem = int(line.partition(":")[2]) contact_id_to_infos[contact_id]["Max Number of elem. (NLIM)"] = max_number_of_elem if line.startswith("Triangulation option"): triangulation_option = int(line.partition(":")[2]) contact_id_to_infos[contact_id]["Triangulation option"] = triangulation_option if line.startswith("Shells reverse option"): shells_reverse_option = int(line.partition(":")[2]) contact_id_to_infos[contact_id]["Shells reverse option"] = shells_reverse_option if line.startswith("DMIN") or line.startswith("UN3") or line.startswith("OPT") : value = float(line.partition("=")[2]) variable_name = line.partition("=")[0] contact_id_to_infos[contact_id][variable_name] = value return contact_id_to_infos
[docs] def SelRead(self): """ Create a trace file and parse group contents. This method creates a trace file (.tra) describing group contents and returns a dictionary of parsed information. The .tra file is the result of the baconpost execution ".SEL LIST TOUT". It contains the group IDs as well as: - Nature of objects composing the group (cells, nodes, or faces) - Group name - Identifiers of the objects composing the group (for faces, a tuple instead of a single ID) Returns ------- dict A dictionary where keys are IDs of the groups, and values are the corresponding information (name, nature of objects, IDs of objects). Notes ----- This function may ignore some information. """ project = os.path.join(self.folderpath, self.projectname) commands = ( f'samcef ba {project} n 1 zone=1000000000 >> {project}.log <<INPUTCMD\n' f';.DOC DB "{project}"\n' f';ASSIGN FAC "{project}_as"\n' f';MODE TRACE "{self.folderpath}/list_sel.tra"\n' f';.SEL LIST TOUT\n' f';MODE TRACE 0\n' f';.FIN 1\n' f'INPUTCMD\n' ) subprocess.run(commands, shell=True, check=True) fileName = os.path.join(self.folderpath, "list_sel.tra") ReaderBase.SetFileName(self,fileName) self.StartReading() group_id_to_infos = {} while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if lis[0].startswith("Group"): group_id = int(lis[1]) type = lis[-1] #wether we are considering cells or nodes (important to know if the ids refer to cells or to nodes) group_id_to_infos[group_id] = {} group_id_to_infos[group_id]["type"] = type if lis[0].startswith("name"): name = line.partition(":")[2] group_id_to_infos[group_id]['name'] = name ids = [] save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine().replace(";", "")).split() while all_strings_are_floats(next_sec): if type.startswith("face"): int_version = [int(x) for x in next_sec] ids.extend(list(zip(int_version[::2], int_version[1::2]))) else : ids.extend([int(x) for x in next_sec]) save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()) if next_sec is None: break else: next_sec = next_sec.replace(";", "").split() self.Seek(save_pos) group_id_to_infos[group_id]["ids"] = ids return group_id_to_infos
[docs] def FctRead(self, fileName=None): """ Parse a trace file and extract function information. This method takes a trace file (.tra) describing functions and returns a dictionary of parsed information. The .tra file is the result of the baconpost execution ".FCT LIST n1 n2" (n1, n2 refer to min function id and max id). Before executing ".FCT LIST n1 n2", enter trace mode with "MODE TRACE" so that baconpost creates the .tra file. The .tra file contains the function ids as well as the function descriptions. Parameters ---------- fileName : str The file path of the result of baconpost execution (.tra file). Returns ------- dict A dictionary where keys are ids of the functions and values are the corresponding function definitions. Notes ----- This function may ignore some information. We assume that we are only facing two types of function definitions: 1. ONLY ONE description WITH abscissae/ordinates format 2. Multiple linear descriptions on intervals giving the interval extremities and the values on those extremities """ ReaderBase.SetFileName(self,fileName) self.StartReading() function_id_to_definition = {} while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if line.startswith("Function Dim."): fct_id = int(lis[4]) function_id_to_definition[fct_id] = {} fct_dim = int(lis[2]) function_id_to_definition[fct_id]["Function Dim."] = fct_dim if not line.endswith("\" \""): fct_name = lis[5][1:-1] function_id_to_definition[fct_id]["Function Name"] = fct_name intervals = [] #in case we are defining a piecewise linear function values_on_extremities_of_the_interval = [] if len(lis) > 1 and lis[1].startswith("Description"): #starting the description of the function save_pos = self.filePointer.tell() #we go two lines after to start the description next_sec = (self.ReadCleanLine()).split() #First possibility : abscissae/ordinates definition if next_sec[0].startswith("Abscissae"): #we are considering the definition of a function on some given points (without prescribing interpolation method) function_id_to_definition[fct_id]["Description type"] = "abscissae/ordinates" abscissae = [] #the function is described by given values at given points next_sec = next_sec[1:] while all_strings_are_floats(next_sec): abscissae.extend([float(x) for x in next_sec]) next_sec = (self.ReadCleanLine()).split() #we save position after calling ReadCleanLine so that we can really read next line in the next part (Ordinates) save_pos = self.filePointer.tell() self.Seek(save_pos) function_id_to_definition[fct_id]["abscissae"] = abscissae elif next_sec[0].startswith("Ordinates"): ordinates = [] next_sec = next_sec[1:] while all_strings_are_floats(next_sec): ordinates.extend([float(x) for x in next_sec]) save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()) if next_sec is None: break else: next_sec = next_sec.split() self.Seek(save_pos) function_id_to_definition[fct_id]["ordinates"] = ordinates #second possibility : piecewise linear function (given interpolation method) if next_sec is not None and next_sec[0].startswith("LIN"): intervals.append([float(lis[-2].replace(",", "")), float(lis[-1].replace("]", ""))]) function_id_to_definition[fct_id]["Description type"] = "piecewise linear" next_sec = next_sec[1:] values_on_extremities_of_the_interval.append([float(x) for x in next_sec]) function_id_to_definition[fct_id]["intervals"] = intervals function_id_to_definition[fct_id]["values on extremities"] = values_on_extremities_of_the_interval return function_id_to_definition
[docs] def NodesRead(self): """ Create a trace file and parse node information. This method creates a trace file (.tra) describing nodes and returns parsed node information. The .tra file is the result of the baconpost execution ".NOE DLIST" (returns information on contacts). It contains the IDs and spatial coordinates of the nodes. Returns ------- list Original IDs of the nodes. list Corresponding x coordinates. list Corresponding y coordinates. list Corresponding z coordinates. Notes ----- This function may ignore some information. """ project = os.path.join(self.folderpath, self.projectname) commands = ( f'samcef ba {project} n 1 zone=1000000000 >> {project}.log <<INPUTCMD\n' f';.DOC DB "{project}"\n' f';ASSIGN FAC "{project}_as"\n' f';MODE TRACE "{self.folderpath}/list_noe.tra"\n' f';.NOE DLIST\n' f';MODE TRACE 0\n' f';.FIN 1\n' f'INPUTCMD\n' ) subprocess.run(commands, shell=True) fileName = os.path.join(self.folderpath, "list_noe.tra") ReaderBase.SetFileName(self,fileName) self.StartReading() original_ids = [] xs, ys, zs = [], [], [] while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if lis[0].startswith("I"): node_id = int(self.get_next_element(lis, "I")) original_ids.append(node_id) xs.append(float(self.get_next_element(lis, "X"))) ys.append(float(self.get_next_element(lis, "Y"))) zs.append(float(self.get_next_element(lis, "Z"))) return original_ids, xs, ys, zs
[docs] def ElementRead(self): """ Parse a trace file (.tra) and extract element and node information. This method processes a .tra file generated by the baconpost execution ".MAI DLIST", which contains information about contacts. The file includes element IDs and their composing nodes. Returns ------- dict A dictionary where keys are element IDs and values are the corresponding nodes. list A list of original element IDs. Notes ----- This function may ignore some information from the .tra file. """ project = os.path.join(self.folderpath, self.projectname) commands = ( f'samcef ba {project} n 1 zone=1000000000 >> {project}.log <<INPUTCMD\n' f';.DOC DB "{project}"\n' f';ASSIGN FAC "{project}_as"\n' f';MODE TRACE "{self.folderpath}/list_mai.tra"\n' f';.MAI DLIST\n' f';MODE TRACE 0\n' f';.FIN 1\n' f'INPUTCMD\n' ) subprocess.run(commands, shell=True) fileName = os.path.join(self.folderpath, "list_mai.tra") ReaderBase.SetFileName(self,fileName) self.StartReading() element_id_to_nodes = {} original_ids_elements = [] while True: line = self.ReadCleanLine() if line is None: break line = line.strip() lis = line.split() if lis[0].startswith("I"): element_id = int(self.get_next_element(lis, "I")) original_ids_elements.append(element_id) composing_nodes_ids = lis[lis.index("N")+1:] save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()).split() while all_strings_are_floats(next_sec): composing_nodes_ids.extend(next_sec) save_pos = self.filePointer.tell() next_sec = (self.ReadCleanLine()).split() self.Seek(save_pos) element_id_to_nodes[element_id] = [int(x) for x in composing_nodes_ids] return element_id_to_nodes, original_ids_elements
[docs] def CheckIntegrity(): try: subprocess.run("samcef", shell=True, check=True) except: return "skip" data = u""".ssls06-m020 & ! ! THIN CYLINDER UNDER NORMAL PRESSURE ! Linear static analysis ! .DEL.* ABRE '/L' '4' ! Length, m ABRE '/R' '1' ! Radius, m ABRE '/T' '0.02' ! Thickness, m ! ABRE '/Young' '2.1E11' ! Young modulus ABRE '/nu' '0.3' ! Poisson ratio ABRE '/P' '10000' ! Pressure, Pa ABRE '/NL' '4' ! Number of elements ! ! A. GEOMETRY ! =========== ! A.1. 3D Geometry ! ---------------- .3POINT I 1 X 0. Y 0. Z 0. I 2 X 0. Y (/R) Z 0. I 3 X 0. Y -(/R) Z 0. .ARC I 1 CENTRE 1 RAYON (/R) ! ! A.2. Mesher ! ----------- .CONTOUR AUTO OUVERT .DOMAINE AUTO ! .GEN DEGRE 1 MAILLE 1 GENERE .EXT ATT 1 TZ (/L:2.) ELEMENT (/NL) EXECUTE 1 ! ! ! B. Hypothesis and material ! ========================== .HYP MINDLIN .MAT I 1 Yt (/Young) Nt (/nu) .PHP THICK ATT 1 V (/T) .AEL MAT 1 ! ! C. Boundary conditions ! ====================== ! C.1. Fixations ! -------------- !.AXL LIGNE 1 ! AXE 1 NORMAL ! AXE 2 TANGENT .CLM FIX LIGNE 1 COMPO 3 4 5 FIX NOEUD 84 COMPO 1 2 6 ! ! C.2. internal pressure ! ---------------------- .CLM ATT 1 PRESS V (/P) ! ! D. Run management ! ================= ! (standard) ! ! E. Output control ! ================= ! (standard + example of code removal) .sai archive struct stype -1251 -1438 EXIT ! ! POSTPROCESSING ! ============== ! .POST & .DEL.* .DOC DB .DES GRAP VISEE 1 1 1 CODE 163 COMP 1 ! Horizontal displacement TRI 2 LIST 3 AFF VI WAIT ! CODE 163 COMP 3 ! Vertical displacement TRI 2 LIST 3 AFF VI WAIT ! CODE 1431 COMP 1 ! Stress AFF VI WAIT COMP 2 AFF VI RETURN """ from Muscat.Helpers.CheckTools import SkipTest from Muscat.Helpers.IO.TemporaryDirectory import TemporaryDirectory from Muscat.Helpers.IO.Which import Which if SkipTest("SAMCEF_NO_FAIL"): return "ok" samcefExecName = Which("samcef") if samcefExecName == '' or samcefExecName == None: return "skip" tempdir = TemporaryDirectory.GetTempPath()+"test/" os.mkdir(tempdir) projectname = "test" filename = tempdir+projectname f = open(filename+".dat", "w") f.write(data) f.close() #Launch Samcef simulation (creates a .sdb file) cmd = ( f'cd {tempdir} && samcef ba,as test n 1 zone=1000000000 >> test.log <<INPUTCMD\n' f';INPUT .ASEF\n' f';.FIN 1\n' f'INPUTCMD\n' ) subprocess.run(cmd, shell=True, check=True) reader = PropertiesReader(folderpath=tempdir, projectname=projectname) res = reader.ElementRead() return 'ok'
if __name__ == '__main__': print(CheckIntegrity()) # pragma: no cover