Skip to content

Commit 18ca9e0

Browse files
committed
added principal stress computation
1 parent 4f62032 commit 18ca9e0

File tree

7 files changed

+198
-25
lines changed

7 files changed

+198
-25
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
######################
1212
.fuse_hidden*
1313
*~
14+
*swp
1415

1516
# Pip generated folders #
1617
#########################
@@ -24,4 +25,3 @@ pyansys/Interface.py
2425

2526
# Testing
2627
Testing/
27-

doc/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Dependencies:
3030
- ``vtkInterface``
3131
- ``vtk`` (optional)
3232

33-
Minimum requirements are numpy to extract results from a results file. To convert the raw data to a VTK unstructured grid, VTK 5.0 or greater must be installed with Python bindings.
33+
Minimum requirements are numpy to extract results from a results file. To convert the raw data to a VTK unstructured grid, VTK 7.0 or greater must be installed with Python bindings.
3434

3535
See `Install VTK <http://vtkinterface.readthedocs.io/en/latest/installation.html>`_ for details instructions for installing VTK.
3636

doc/loading_results.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The ANSYS result file is a FORTRAN formatted binary file containing the results
55
- Nodal DOF results from a static analysis or modal analysis.
66
- Nodal DOF results from a cyclic static or modal analysis.
77
- Nodal averaged component stresses (i.e. x, y, z, xy, xz, yz)
8+
- Nodal principal stresses (i.e. S1, S2, S3, SEQV, SINT)
89

910
We're working on adding additional plotting and retrieval functions to the code If you would like us to add an additional result type to be loaded, please open an issue in `GitHub <https://github.com/akaszynski/pyansys>`_ and include result file for the result type you wish to load.
1011

@@ -84,7 +85,7 @@ The DOF solution for an analysis for each node in the analysis can be obtained u
8485
# normalized displacement can be plotted by excluding the direction string
8586
result.PlotNodalResult(0, label='Normalized')
8687
87-
Stress can be obtained as well using the below code. The nodal stress is computed in the same manner as ANSYS by averaging the stress evaluated at that node for all attached elements. For now, only component stresses can be displayed.
88+
Stress can be obtained as well using the below code. The nodal stress is computed in the same manner as ANSYS by averaging the stress evaluated at that node for all attached elements.
8889

8990
.. code:: python
9091
@@ -95,6 +96,10 @@ Stress can be obtained as well using the below code. The nodal stress is comput
9596
# Display node averaged stress in x direction for result 6
9697
result.PlotNodalStress(5, 'Sx')
9798
99+
# Compute principal nodal stresses and plot SEQV for result 1
100+
pstress = result.PrincipalNodalStress(0)
101+
result.PlotPrincipalNodalStress(0, 'SEQV')
102+
98103
99104
Results from a Cyclic Analysis
100105
------------------------------

pyansys/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# major, minor, patch
2-
version_info = 0, 20, 0
2+
version_info = 0, 21, 0
33

44

55
# Nice string for the version

pyansys/binary_reader.py

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -846,18 +846,18 @@ def StoreGeometry(self):
846846
self.edge_node_num_idx = np.unique(self.edge_idx)
847847

848848
# catch the disassociated node bug
849-
# try:
850-
# self.edge_node_num = self.geometry['nnum'][self.edge_node_num_idx]
851-
# except:
852-
# logging.warning('unable to generate edge_node_num')
849+
try:
850+
self.edge_node_num = self.geometry['nnum'][self.edge_node_num_idx]
851+
except:
852+
logging.warning('unable to generate edge_node_num')
853853

854854
def NodalStress(self, rnum):
855855
"""
856856
Retrives the component stresses for each node in the solution.
857857
858858
The order of the results corresponds to the sorted node numbering.
859859
860-
This algorthim, like ANSYS, computes the nodal stress by averaging the
860+
This algorithm, like ANSYS, computes the nodal stress by averaging the
861861
stress for each element at each node. Due to the discontinunities
862862
across elements, stresses will vary based on the element they are
863863
evaluated from.
@@ -869,12 +869,12 @@ def NodalStress(self, rnum):
869869
870870
Returns
871871
-------
872-
stress : array
872+
stress : numpy.ndarray
873873
Stresses at Sx Sy Sz Sxy Syz Sxz averaged at each corner node.
874-
For the corresponding node numbers, see "edge_node_num"
874+
For the corresponding node numbers, see "result.edge_node_num"
875+
where result is the result object.
875876
876877
"""
877-
878878
# Get the header information from the header dictionary
879879
endian = self.resultheader['endian']
880880
rpointers = self.resultheader['rpointers']
@@ -960,6 +960,90 @@ def NodalStress(self, rnum):
960960

961961
return s_node
962962

963+
def PrincipalNodalStress(self, rnum):
964+
"""
965+
Computes the principal component stresses for each node in the
966+
solution.
967+
968+
The order of the results corresponds to the sorted node numbering.
969+
See result.edge_node_num
970+
971+
Parameters
972+
----------
973+
rnum : interger
974+
Result set to load using zero based indexing.
975+
976+
Returns
977+
-------
978+
pstress : numpy.ndarray
979+
Principal stresses, stress intensity, and equivalant stress.
980+
[sigma1, sigma2, sigma3, sint, seqv]
981+
982+
Notes
983+
-----
984+
ANSYS equivalant of:
985+
PRNSOL, S, PRIN
986+
987+
which returns:
988+
S1, S2, S3 principal stresses, SINT stress intensity, and SEQV
989+
equivalent stress.
990+
991+
"""
992+
# get component stress
993+
stress = self.NodalStress(rnum)
994+
995+
# compute principle stress
996+
if stress.dtype != np.float32:
997+
stress = stress.astype(np.float32)
998+
return _rstHelper.ComputePrincipalStress(stress)
999+
1000+
def PlotPrincipalNodalStress(self, rnum, stype):
1001+
"""
1002+
Plot the principal stress at each node in the solution.
1003+
1004+
Parameters
1005+
----------
1006+
rnum : interger
1007+
Result set using zero based indexing.
1008+
1009+
stype : string
1010+
Stress type to plot. S1, S2, S3 principal stresses, SINT stress
1011+
intensity, and SEQV equivalent stress.
1012+
1013+
Stress type must be a string from the following list:
1014+
1015+
['S1', 'S2', 'S3', 'SINT', 'SEQV']
1016+
1017+
Returns
1018+
-------
1019+
cpos : list
1020+
VTK camera position.
1021+
1022+
"""
1023+
stress_types = ['S1', 'S2', 'S3', 'SINT', 'SEQV']
1024+
if stype not in stress_types:
1025+
raise Exception('Stress type not in \n' + str(stress_types))
1026+
1027+
sidx = stress_types.index(stype)
1028+
1029+
# create a zero array for all nodes
1030+
stress = np.zeros(self.resultheader['nnod'])
1031+
1032+
# Populate with nodal stress at edge nodes
1033+
pstress = self.PrincipalNodalStress(rnum)
1034+
stress[self.edge_node_num_idx] = pstress[:, sidx]
1035+
stitle = 'Nodal Stress\n{:s}'.format(stype)
1036+
1037+
# Generate plot
1038+
plobj = vtkInterface.PlotClass()
1039+
plobj.AddMesh(self.grid, scalars=stress, stitle=stitle,
1040+
flipscalars=True, interpolatebeforemap=True)
1041+
1042+
text = 'Result {:d} at {:f}'.format(rnum + 1, self.tvalues[rnum])
1043+
plobj.AddText(text)
1044+
1045+
return plobj.Plot() # store camera position
1046+
9631047
def PlotNodalStress(self, rnum, stype):
9641048
"""
9651049
Plots the stresses at each node in the solution.
@@ -1002,7 +1086,6 @@ def PlotNodalStress(self, rnum, stype):
10021086
plobj.AddText(text)
10031087

10041088
cpos = plobj.Plot() # store camera position
1005-
del plobj
10061089

10071090
return cpos
10081091

pyansys/cython/_rstHelper.pyx

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import ctypes
66
import numpy as np
77
cimport numpy as np
88

9+
from libc.math cimport sqrt, fabs
910
from libc.stdio cimport fopen, FILE, fclose, fread, fseek
1011
from libc.stdio cimport SEEK_CUR, ftell, SEEK_SET
1112
from libc.string cimport memcpy
@@ -425,4 +426,88 @@ def FullNodeInfo(filename, int ptrDOF, int nNodes, int neqn):
425426
const_sort[i] = const[s_neqv_dof[i]]
426427

427428
return np.asarray(nref_sort), np.asarray(dref_sort), np.asarray(index_arr), \
428-
np.asarray(const_sort), np.asarray(ndof)
429+
np.asarray(const_sort), np.asarray(ndof)
430+
431+
432+
def ComputePrincipalStress(float [:, ::1] stress):
433+
"""
434+
Returns the principal stresses based on component stresses.
435+
436+
Parameters
437+
----------
438+
stress : numpy.ndarray
439+
Stresses at Sx Sy Sz Sxy Syz Sxz averaged at each corner node.
440+
441+
Returns
442+
-------
443+
pstress : numpy.ndarray
444+
Principal stresses, stress intensity, and equivalant stress.
445+
[sigma1, sigma2, sigma3, sint, seqv]
446+
447+
Notes
448+
-----
449+
ANSYS equivalant of:
450+
PRNSOL, S, PRIN
451+
452+
Which returns:
453+
S1, S2, S3 principal stresses, SINT stress intensity, and SEQV
454+
equivalent stress.
455+
"""
456+
# reshape the stress array into 3x3 stress tensor arrays
457+
cdef int nnode = stress.shape[0]
458+
cdef float [:, :, ::1] stress_tensor = np.empty((nnode, 3, 3), np.float32)
459+
cdef float s_xx, x_yy, s_zz, s_xy, s_yz, s_xz
460+
461+
for i in range(nnode):
462+
s_xx = stress[i, 0]
463+
s_yy = stress[i, 1]
464+
s_zz = stress[i, 2]
465+
s_xy = stress[i, 3]
466+
s_yz = stress[i, 4]
467+
s_xz = stress[i, 5]
468+
469+
# populate stress tensor
470+
stress_tensor[i, 0, 0] = s_xx
471+
stress_tensor[i, 0, 1] = s_xy
472+
stress_tensor[i, 0, 2] = s_xz
473+
stress_tensor[i, 1, 0] = s_xy
474+
stress_tensor[i, 1, 1] = s_yy
475+
stress_tensor[i, 1, 2] = s_yz
476+
stress_tensor[i, 2, 0] = s_xz
477+
stress_tensor[i, 2, 1] = s_yz
478+
stress_tensor[i, 2, 2] = s_zz
479+
480+
# compute principle stresses
481+
w, v = np.linalg.eig(np.asarray(stress_tensor))
482+
w[:, ::-1].sort(1)
483+
484+
temp = np.empty((nnode, 5), np.float32)
485+
temp[:, :3] = w
486+
487+
cdef float [:, ::1] pstress = temp
488+
cdef float p1, p2, p3, c1, c2, c3
489+
490+
# compute stress intensity and von mises (equivalent) stress
491+
for i in range(nnode):
492+
p1 = pstress[i, 0]
493+
p2 = pstress[i, 1]
494+
p3 = pstress[i, 2]
495+
496+
c1 = fabs(p1 - p2)
497+
c2 = fabs(p2 - p3)
498+
c3 = fabs(p3 - p1)
499+
500+
if c1 > c2:
501+
if c1 > c3:
502+
pstress[i, 3] = c1
503+
else:
504+
pstress[i, 3] = c3
505+
else:
506+
if c2 > c3:
507+
pstress[i, 3] = c2
508+
else:
509+
pstress[i, 3] = c3
510+
511+
pstress[i, 4] = sqrt(0.5*(c1**2 + c2**2 + c3**2))
512+
513+
return np.asarray(pstress)

setup.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@
66
from io import open as io_open
77

88
from setuptools import setup, Extension
9-
from setuptools.command.build_ext import build_ext
10-
11-
# from setuptools.command.build_ext import build_ext as _build_ext
12-
# # Create a build class that includes numpy directory
13-
# class build_ext(_build_ext):
14-
# def finalize_options(self):
15-
# _build_ext.finalize_options(self)
16-
# # Prevent numpy from thinking it is still in its setup process:
17-
# __builtins__.__NUMPY_SETUP__ = False
18-
# import numpy
19-
# self.include_dirs.append(numpy.get_include())
9+
#from setuptools.command.build_ext import build_ext
10+
11+
from setuptools.command.build_ext import build_ext as _build_ext
12+
# Create a build class that includes numpy directory
13+
class build_ext(_build_ext):
14+
def finalize_options(self):
15+
_build_ext.finalize_options(self)
16+
# Prevent numpy from thinking it is still in its setup process:
17+
__builtins__.__NUMPY_SETUP__ = False
18+
import numpy
19+
self.include_dirs.append(numpy.get_include())
2020

2121

2222
def compilerName():

0 commit comments

Comments
 (0)