Skip to content

Commit d7f711b

Browse files
committed
fixed full reader
1 parent ae98163 commit d7f711b

File tree

6 files changed

+172
-197
lines changed

6 files changed

+172
-197
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*.cpp
77
*.so
88
*.o
9+
*.cache
910

1011
# OS generated files #
1112
######################

doc/loading_km.rst

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,39 @@ Reading a Full File
77
-------------------
88
This example reads in the mass and stiffness matrices associated with the above example. ``LoadKM`` sorts degrees of freedom such that the nodes are ordered from minimum to maximum, and each degree of freedom (i.e. X, Y, Z), are sorted within each node. The matrices ``k`` and ``m`` are sparse by default, but if ``scipy`` is not installed, or if the optional parameter ``as_sparse=False`` then they will be full numpy arrays.
99

10-
By default ``LoadKM`` outputs the upper triangle of both matrices, to output the full matrix, set ``utri=False``. Additionally, the constrained nodes of the analysis can be identified by accessing ``fobj.const`` where the constrained degrees of freedom are True and all others are False. This corresponds to the degrees of reference in ``dof_ref``.
10+
By default ``LoadKM`` outputs the upper triangle of both matrices. The constrained nodes of the analysis can be identified by accessing ``fobj.const`` where the constrained degrees of freedom are True and all others are False. This corresponds to the degrees of reference in ``dof_ref``.
11+
12+
By default dof_ref is unsorted. To sort these values, set ``sort==True``. It is enabled for this example to allow for plotting of the values later on.
1113

1214
.. code:: python
1315
1416
# Load pyansys
1517
import pyansys
18+
from pyansys import examples
1619
1720
# Create result reader object and read in full file
18-
full = pyansys.FullReader(pyansys.examples.fullfile)
19-
dof_ref, k, m = full.LoadKM(utri=False) # return the full matrix
21+
full = pyansys.FullReader(examples.fullfile)
22+
dof_ref, k, m = full.LoadKM(sort=True)
23+
2024
21-
If you have ``scipy`` installed, you can solve solve for the natural frequencies and mode shapes of a system. Realize that constrained degrees of freedom must be removed from the ``k`` and ``m`` matrices for the correct solution.
25+
ANSYS only stores the upper triangular matrix in the full file. To make the full matrix:
2226

2327
.. code:: python
2428
25-
import numpy as np
26-
# remove the constrained degrees of freedom
27-
# NOTE: There are more efficient way to remove these indices
28-
free = np.logical_not(full.const).nonzero()[0]
29-
k = k[free][:, free]
30-
m = m[free][:, free]
29+
k += sparse.triu(k, 1).T
30+
m += sparse.triu(m, 1).T
31+
32+
If you have ``scipy`` installed, you can solve solve for the natural frequencies and mode shapes of a system.
33+
34+
.. code:: python
3135
36+
import numpy as np
3237
from scipy.sparse import linalg
3338
39+
# condition the k matrix
40+
# to avoid getting the "Factor is exactly singular" error
41+
k += sparse.diags(np.random.random(k.shape[0])/1E20, shape=k.shape)
42+
3443
# Solve
3544
w, v = linalg.eigsh(k, k=20, M=m, sigma=10000)
3645
@@ -46,6 +55,7 @@ If you have ``scipy`` installed, you can solve solve for the natural frequencies
4655
First four natural frequencies
4756
1283.200 Hz
4857
1283.200 Hz
58+
4959
5781.975 Hz
5060
6919.399 Hz
5161
@@ -59,11 +69,7 @@ You can also plot the mode shape of this finite element model. Since the constr
5969
import vtkInterface
6070
6171
# Get the 4th mode shape
62-
mode_shape = v[:, 3] # x, y, z displacement for each node
63-
64-
# create the full mode shape including the constrained nodes
65-
full_mode_shape = np.zeros(dof_ref.shape[0])
66-
full_mode_shape[np.logical_not(full.const)] = mode_shape
72+
full_mode_shape = v[:, 3] # x, y, z displacement for each node
6773
6874
# reshape and compute the normalized displacement
6975
disp = full_mode_shape.reshape((-1, 3))

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, 23, 0
2+
version_info = 0, 24, 0
33

44
# Nice string for the version
55
__version__ = '.'.join(map(str, version_info))

pyansys/binary_reader.py

Lines changed: 87 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def __init__(self, filename):
108108
raise Exception(
109109
"Unable to read an unsymmetric mass/stiffness matrix.")
110110

111-
def LoadKM(self, as_sparse=True, utri=True):
111+
def LoadKM(self, as_sparse=True, sort=False):
112112
"""
113113
Load and construct mass and stiffness matrices from an ANSYS full file.
114114
@@ -118,29 +118,28 @@ def LoadKM(self, as_sparse=True, utri=True):
118118
Outputs the mass and stiffness matrices as scipy csc sparse arrays
119119
when True by default.
120120
121-
utri : bool, optional
122-
Outputs only the upper triangle of both the mass and stiffness
123-
arrays.
121+
sort : bool, optional
122+
Rearranges the k and m matrices such that the rows correspond to
123+
to the sorted rows and columns in dor_ref. Also sorts dor_ref.
124124
125125
Returns
126126
-------
127127
dof_ref : (n x 2) np.int32 array
128128
This array contains the node and degree corresponding to each row
129-
and column in the mass and stiffness matrices. When the sort
130-
parameter is set to True this array will be sorted by node number
131-
first and then by the degree of freedom. In a 3 DOF analysis the
132-
intergers will correspond to:
129+
and column in the mass and stiffness matrices. In a 3 DOF
130+
analysis the dof intergers will correspond to:
133131
0 - x
134132
1 - y
135133
2 - z
134+
Sort these values by node number and DOF by enabling the sort
135+
parameter
136136
137137
k : (n x n) np.float or scipy.csc array
138138
Stiffness array
139139
140140
m : (n x n) np.float or scipy.csc array
141141
Mass array
142142
"""
143-
# check file exists
144143
if not os.path.isfile(self.filename):
145144
raise Exception('%s not found' % self.filename)
146145

@@ -157,84 +156,89 @@ def LoadKM(self, as_sparse=True, utri=True):
157156
ntermK = self.header[9] # number of terms in stiffness matrix
158157
ptrSTF = self.header[19] # Location of stiffness matrix
159158
ptrMAS = self.header[27] # Location in file to mass matrix
160-
nNodes = self.header[33] # Number of nodes considered by assembly
159+
# nNodes = self.header[33] # Number of nodes considered by assembly
161160
ntermM = self.header[34] # number of terms in mass matrix
162161
ptrDOF = self.header[36] # pointer to DOF info
163162

164-
# get details for reading the mass and stiffness arrays
165-
node_info = _rstHelper.FullNodeInfo(self.filename, ptrDOF, nNodes,
166-
neqn)
163+
# DOF information
164+
ptrDOF = self.header[36] # pointer to DOF info
165+
with open(self.filename, 'rb') as f:
166+
ReadTable(f, skip=True) # standard header
167+
ReadTable(f, skip=True) # full header
168+
ReadTable(f, skip=True) # number of degrees of freedom
169+
neqv = ReadTable(f) # Nodal equivalence table
170+
171+
f.seek(ptrDOF*4)
172+
ndof = ReadTable(f)
173+
const = ReadTable(f)
167174

168-
nref, dref, index_arr, const, ndof = node_info
169-
dof_ref = np.vstack((nref, dref)).T # stack these references
175+
# dof_ref = np.vstack((ndof, neqv)).T # stack these references
176+
dof_ref = [ndof, neqv]
170177

171178
# Read k and m blocks (see help(ReadArray) for block description)
172179
if ntermK:
173-
k_block = _rstHelper.ReadArray(self.filename, ptrSTF, ntermK, neqn,
174-
index_arr)
175-
k_diag = k_block[3]
176-
k_data_diag = k_block[4]
180+
krow, kcol, kdata = _rstHelper.ReadArray(self.filename,
181+
ptrSTF,
182+
ntermK,
183+
neqn,
184+
const)
177185
else:
178186
warnings.warn('Missing stiffness matrix')
179-
k_block = None
187+
kdata = None
180188

181189
if ntermM:
182-
m_block = _rstHelper.ReadArray(self.filename, ptrMAS, ntermM, neqn,
183-
index_arr)
184-
m_diag = m_block[3]
185-
m_data_diag = m_block[4]
190+
mrow, mcol, mdata = _rstHelper.ReadArray(self.filename,
191+
ptrMAS,
192+
ntermM,
193+
neqn,
194+
const)
186195
else:
187196
warnings.warn('Missing mass matrix')
188-
m_block = None
189-
190-
self.m_block = m_block
191-
self.k_block = k_block
192-
193-
# assemble data
194-
if utri:
195-
if k_block:
196-
# stiffness matrix
197-
krow = np.hstack((k_block[1], k_diag)) # row and diag
198-
kcol = np.hstack((k_block[0], k_diag)) # col and diag
199-
kdata = np.hstack((k_block[2], k_data_diag)) # data and diag
200-
201-
if m_block:
202-
# mass matrix
203-
mrow = np.hstack((m_block[1], m_diag)) # row and diag
204-
mcol = np.hstack((m_block[0], m_diag)) # col and diag
205-
mdata = np.hstack((m_block[2], m_data_diag)) # data and diag
206-
197+
mdata = None
198+
199+
# remove constrained entries
200+
if np.any(const < 0):
201+
if kdata is not None:
202+
remove = np.nonzero(const < 0)[0]
203+
mask = ~np.logical_or(np.in1d(krow, remove), np.in1d(kcol, remove))
204+
krow = krow[mask]
205+
kcol = kcol[mask]
206+
kdata = kdata[mask]
207+
208+
if mdata is not None:
209+
mask = ~np.logical_or(np.in1d(mrow, remove), np.in1d(mcol, remove))
210+
mrow = mrow[mask]
211+
mcol = mcol[mask]
212+
mdata = mdata[mask]
213+
214+
dof_ref, index, nref, dref = _rstHelper.SortNodalEqlv(neqn, neqv, ndof)
215+
if sort: # make sorting the same as ANSYS rdfull would output
216+
# resort to make in upper triangle
217+
krow = index[krow]
218+
kcol = index[kcol]
219+
krow, kcol = np.sort(np.vstack((krow, kcol)), 0)
220+
221+
mrow = index[mrow]
222+
mcol = index[mcol]
223+
mrow, mcol = np.sort(np.vstack((mrow, mcol)), 0)
207224
else:
208-
if k_block:
209-
# stiffness matrix
210-
krow = np.hstack((k_block[0], k_block[1], k_diag))
211-
kcol = np.hstack((k_block[1], k_block[0], k_diag))
212-
kdata = np.hstack((k_block[2], k_block[2], k_data_diag))
213-
214-
if m_block:
215-
# mass matrix
216-
mrow = np.hstack((m_block[0], m_block[1], m_diag))
217-
mcol = np.hstack((m_block[1], m_block[0], m_diag))
218-
mdata = np.hstack((m_block[2], m_block[2], m_data_diag))
225+
dof_ref = np.vstack((nref, dref)).T
219226

220227
# store data for later reference
221-
if k_block:
228+
if kdata is not None:
222229
self.krow = krow
223230
self.kcol = kcol
224231
self.kdata = kdata
225-
if m_block:
232+
if mdata is not None:
226233
self.mrow = mrow
227234
self.mcol = mcol
228235
self.mdata = mdata
229236

230-
# number of dimentions
231-
ndim = nref.size
232-
233237
# output as a sparse matrix
234238
if as_sparse:
235239

236-
if k_block:
237-
k = coo_matrix((ndim,) * 2)
240+
if kdata is not None:
241+
k = coo_matrix((neqn,) * 2)
238242
k.data = kdata # data has to be set first
239243
k.row = krow
240244
k.col = kcol
@@ -244,8 +248,8 @@ def LoadKM(self, as_sparse=True, utri=True):
244248
else:
245249
k = None
246250

247-
if m_block:
248-
m = coo_matrix((ndim,) * 2)
251+
if mdata is not None:
252+
m = coo_matrix((neqn,) * 2)
249253
m.data = mdata
250254
m.row = mrow
251255
m.col = mcol
@@ -256,14 +260,14 @@ def LoadKM(self, as_sparse=True, utri=True):
256260
m = None
257261

258262
else:
259-
if k_block:
260-
k = np.zeros((ndim,) * 2)
263+
if kdata is not None:
264+
k = np.zeros((neqn,) * 2)
261265
k[krow, kcol] = kdata
262266
else:
263267
k = None
264268

265-
if m_block:
266-
m = np.zeros((ndim,) * 2)
269+
if mdata is not None:
270+
m = np.zeros((neqn,) * 2)
267271
m[mrow, mcol] = mdata
268272
else:
269273
m = None
@@ -376,8 +380,7 @@ def AddCyclicProperties(self):
376380

377381
vtkappend.AddInputData(sector)
378382

379-
# Combine meshes and add VTK_Utilities functions
380-
# vtkappend.MergePointsOn()
383+
# Combine meshes
381384
vtkappend.Update()
382385
self.rotor = vtkInterface.UnstructuredGrid(vtkappend.GetOutput())
383386

@@ -1454,3 +1457,18 @@ def delete_row_csc(mat, i):
14541457
mat.indptr[i:] -= n
14551458
mat.indptr = mat.indptr[:-1]
14561459
mat._shape = (mat._shape[0] - 1, mat._shape[1])
1460+
1461+
1462+
def ReadTable(f, dtype='i', skip=False):
1463+
""" read fortran style table """
1464+
tablesize = np.fromfile(f, 'i', 1)[0]
1465+
f.seek(4, 1) # skip padding
1466+
if skip:
1467+
f.seek((tablesize + 1)*4, 1)
1468+
return
1469+
else:
1470+
if dtype == 'double':
1471+
tablesize //= 2
1472+
table = np.fromfile(f, dtype, tablesize)
1473+
f.seek(4, 1) # skip padding
1474+
return table

0 commit comments

Comments
 (0)