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 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