Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 63cc257

Browse files
committed
Lin: using FASTLinearizationFile reader, adding options for state selection and TXT selection
1 parent c3030fe commit 63cc257

File tree

9 files changed

+645
-282
lines changed

9 files changed

+645
-282
lines changed

pyFAST/input_output/fast_linearization_file.py

Lines changed: 404 additions & 194 deletions
Large diffs are not rendered by default.

pyFAST/input_output/tests/test_fast_linearization.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,49 @@ def test_001_read_all(self, DEBUG=True):
1010
reading_test('FASTLin*.*', FASTLinearizationFile)
1111

1212
def test_FASTLin(self):
13+
14+
# --- Test basic read
1315
F=FASTLinearizationFile(os.path.join(MyDir,'FASTLin.lin'))
1416
self.assertAlmostEqual(F['A'][3,1], 3.91159454E-04 )
1517
self.assertAlmostEqual(F['u'][7] ,4.00176055E+04)
1618

19+
# Test properties
20+
np.testing.assert_almost_equal(F.nx , 4)
21+
np.testing.assert_almost_equal(F.nu , 9)
22+
np.testing.assert_almost_equal(F.ny , 16)
23+
np.testing.assert_almost_equal(F.nz , 0 )
24+
25+
# Test keys
26+
np.testing.assert_almost_equal(F['Azimuth'] , 5.8684 , 4)
27+
np.testing.assert_almost_equal(F['RotSpeed'] , 1.2367 , 4)
28+
self.assertEqual(F['WindSpeed'], None ) # NOTE: might become NaN in the future
29+
30+
# --- Test methods
31+
dfs = F.toDataFrame() # Make sure this runs
32+
33+
# Test EVA
34+
fd, zeta, Q, f0 = F.eva()
35+
np.testing.assert_almost_equal(f0 , [0.394858], 4)
36+
np.testing.assert_almost_equal(zeta, [0.06078], 4)
37+
38+
# Test state removal
39+
F.removeStates(pattern='generator')
40+
fd, zeta, Q, f0 = F.eva()
41+
dfs = F.toDataFrame() # Make sure this runs
42+
np.testing.assert_almost_equal(F.nx , 2)
43+
np.testing.assert_almost_equal(f0 , [0.394258], 4)
44+
np.testing.assert_almost_equal(zeta, [0.0603 ], 4)
45+
46+
47+
# --- Test lin file with M (only for EB's special branch...)
1748
F=FASTLinearizationFile(os.path.join(MyDir,'FASTLin_EDM.lin'))
1849
dfs=F.toDataFrame()
1950
M=dfs['M']
2051
self.assertAlmostEqual(M['7_TwFADOF1']['7_TwFADOF1'],0.436753E+06)
2152
self.assertAlmostEqual(M['13_GeAz']['13_GeAz'] , 0.437026E+08)
2253

54+
55+
2356
if __name__ == '__main__':
2457
# Test().test_000_debug()
2558
unittest.main()

pyFAST/linearization/campbell.py

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@
2828
from pyFAST.linearization.campbell_data import IdentifyModes
2929

3030

31-
def postproCampbell(fstFiles, BladeLen=None, TowerLen=None, verbose=True, nFreqOut=15,
32-
WS_legacy=None, removeTwrAzimuth=False,
33-
writeModes=None,
34-
**kwargs
31+
def postproCampbell(fstFiles, BladeLen=None, TowerLen=None, verbose=True,
32+
WS_legacy=None,
33+
nFreqOut=500, freqRange=None, posDampRange=None, # Options for TXT output
34+
removeTwrAzimuth=False, starSub=None, removeStatesPattern=None, # Options for A matrix selection
35+
writeModes=None, **kwargs # Options for .viz files
3536
):
3637
"""
3738
Postprocess linearization files to extract Campbell diagram (linearization at different Operating points)
@@ -42,6 +43,23 @@ def postproCampbell(fstFiles, BladeLen=None, TowerLen=None, verbose=True, nFreqO
4243
4344
INPUTS:
4445
- fstFiles: list of fst files
46+
47+
INPUTS (related to A matrices):
48+
- removeTwrAzimuth: if False do nothing
49+
otherwise discard lin files where azimuth in [60, 180, 300]+/-4deg (close to tower).
50+
- starSub: if None, raise an error if `****` are present
51+
otherwise replace *** with `starSub` (e.g. 0)
52+
see FASTLinearizationFile.
53+
- removeStatesPattern: remove states matching a giving description pattern.
54+
e.g: 'tower|Drivetrain' or '^AD'
55+
see FASTLinearizationFile.
56+
57+
INPUTS (related to Campbell_Summary.txt output):
58+
- nFreqOut: maximum number of frequencies to write to Campbell_Summary.txt file
59+
- freqRange: range in which frequencies are "accepted", if None: [-np.inf, np.inf]
60+
- posDampRange: range in which damping are "accepted' , if None: [1e-5, 0.96]
61+
62+
INPUTS (related to .viz files):
4563
- writeModes: if True, a binary file and a .viz file is written to disk for OpenFAST VTK visualization.
4664
if None, the binary file is written only if a checkpoint file is present.
4765
For instance, if the main file is : 'main.fst',
@@ -50,6 +68,13 @@ def postproCampbell(fstFiles, BladeLen=None, TowerLen=None, verbose=True, nFreqO
5068
the checkpoint file is expected to be : 'main.ModeShapeVTK.chkp'
5169
- **kwargs: list of key/values to be passed to writeVizFile (see function below)
5270
VTKLinModes=15, VTKLinScale=10, VTKLinTim=1, VTKLinTimes1=True, VTKLinPhase=0, VTKModes=None
71+
72+
OUTPUTS:
73+
- OP: dataframe of operating points
74+
- Freq: dataframe of frequencies for each OP and identified mode
75+
- Damp: dataframe of dampings for each OP and identified mode
76+
- UnMapped:
77+
- ModeData: all mode data all mode data
5378
"""
5479
if len(fstFiles)==0:
5580
raise Exception('postproCampbell requires a list of at least one .fst')
@@ -60,7 +85,8 @@ def postproCampbell(fstFiles, BladeLen=None, TowerLen=None, verbose=True, nFreqO
6085
CD = pickle.load(open(fstFiles[0],'rb'))
6186
else:
6287
# --- Attemps to extract Blade Length and TowerLen from first file...
63-
CD, MBC = getCampbellDataOPs(fstFiles, writeModes=writeModes, BladeLen=BladeLen, TowerLen=TowerLen, removeTwrAzimuth=removeTwrAzimuth, verbose=verbose, **kwargs)
88+
CD, MBC = getCampbellDataOPs(fstFiles, writeModes=writeModes, BladeLen=BladeLen, TowerLen=TowerLen,
89+
removeTwrAzimuth=removeTwrAzimuth, starSub=starSub, removeStatesPattern=removeStatesPattern, verbose=verbose, **kwargs)
6490

6591
# --- Identify modes
6692
modeID_table,modesDesc=IdentifyModes(CD)
@@ -71,7 +97,7 @@ def postproCampbell(fstFiles, BladeLen=None, TowerLen=None, verbose=True, nFreqO
7197
modeID_file = campbellData2CSV(baseName, CD, modeID_table, modesDesc)
7298
# Write summary txt file to help manual identification step..
7399
txtFileName = baseName+'_Summary.txt'
74-
campbellData2TXT(CD, nFreqOut=nFreqOut, txtFileName=txtFileName)
100+
campbellData2TXT(CD, txtFileName=txtFileName, nFreqOut=nFreqOut, freqRange=freqRange, posDampRange=posDampRange)
75101

76102
# --- Return nice dataframes (assuming the identification is correct)
77103
# TODO, for now we reread the files...

pyFAST/linearization/campbell_data.py

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -201,49 +201,68 @@ def printCampbellDataOP(CDDOP, nModesMax=15, nCharMaxDesc=50) :
201201
# --- Manipulation of multiple operating points
202202
# --------------------------------------------------------------------------------{
203203

204-
def campbellData2TXT(CD, nFreqOut=15, txtFileName=None, skipHighDamp=True, skipNonEDBD=True):
204+
def campbellData2TXT(CD, txtFileName=None, nFreqOut=500, freqRange=None, posDampRange=None, skipNonEDBD=True):
205205
""" Write frequencies, damping, and mode contents for each operating points to a string
206206
Write to file if filename provided
207+
INPUTS:
208+
- nFreqOut: maximum number of frequencies to write to Campbell_Summary.txt file
209+
- freqRange: range in which frequencies are "accepted", if None: [-np.inf, np.inf]
210+
- posDampRange: range in which damping are "accepted' , if None: [1e-5, 0.96]
207211
"""
208212
if not isinstance(CD, list):
209213
CD=[CD]
214+
if freqRange is None:
215+
freqRange =[-np.inf, np.inf]
216+
if posDampRange is None:
217+
posDampRange =[1e-5, 0.96]
218+
219+
def mode2txtline(cd, im):
220+
m = cd['Modes'][im]
221+
Desc = cd['ShortModeDescr'][im]
222+
zeta = m['DampingRatio']
223+
return '{:03d} ; {:8.3f} ; {:7.4f} ; {:s}\n'.format(im+1,m['NaturalFreq_Hz'],m['DampingRatio'],Desc)
224+
210225
txt=''
211226
for iOP,cd in enumerate(CD):
212227
WS = cd['WindSpeed']
213228
RPM = cd['RotSpeed_rpm']
214229
nFreqOut_loc = np.min([len(cd['Modes']),nFreqOut])
215-
txt+='------------------------------------------------------------------------\n'
216-
txt+='--- OP {:d} - WS {:.1f} - RPM {:.2f} \n'.format(iOP+1, WS, RPM)
217-
txt+='------------------------------------------------------------------------\n'
230+
txt+='# -----------------------------------------------------------------------\n'
231+
txt+='# --- OP {:d} - WS {:.1f} - RPM {:.2f} \n'.format(iOP+1, WS, RPM)
232+
txt+='# -----------------------------------------------------------------------\n'
233+
txt+='# --- "Selected" modes\n'
234+
txt+='# ID; Freq [Hz]; Zeta [-]; Mode content\n'
218235
skippedDamp=[]
236+
skippedFreq=[]
219237
skippedEDBD=[]
220238
for im in np.arange(nFreqOut_loc):
221239
m = cd['Modes'][im]
222240
Desc = cd['ShortModeDescr'][im]
223241
zeta = m['DampingRatio']
242+
freq = m['NaturalFreq_Hz']
224243
hasED = Desc.find('ED')>=0
225244
hasBD = Desc.find('BD')>=0
226245
hasAD = Desc.find('AD')>=0
227-
if skipHighDamp and (zeta>0.96 or abs(zeta)<1e-5):
246+
if (freq>freqRange[1] or freq<freqRange[0]):
247+
skippedFreq.append(im)
248+
elif (zeta>posDampRange[1] or abs(zeta)<posDampRange[0]):
228249
skippedDamp.append(im)
229250
elif skipNonEDBD and (not (hasBD or hasED)):
230251
skippedEDBD.append(im)
231252
else:
232-
txt+='{:02d} ; {:8.3f} ; {:7.4f} ; {:s}\n'.format(im+1,m['NaturalFreq_Hz'],m['DampingRatio'],Desc)
253+
txt+=mode2txtline(cd, im)
233254
if len(skippedEDBD)>0:
234-
txt+='---- Skipped (No ED/BD)\n'
255+
txt+='# --- Skipped (No ED/BD)\n'
235256
for im in skippedEDBD:
236-
m = cd['Modes'][im]
237-
Desc = cd['ShortModeDescr'][im]
238-
zeta = m['DampingRatio']
239-
txt+='{:02d} ; {:8.3f} ; {:7.4f} ; {:s}\n'.format(im+1,m['NaturalFreq_Hz'],m['DampingRatio'],Desc)
257+
txt+=mode2txtline(cd, im)
258+
if len(skippedFreq)>0:
259+
txt+='# --- Skipped (Frequency outside of `freqRange`={})\n'.format(freqRange)
260+
for im in skippedFreq:
261+
txt+=mode2txtline(cd, im)
240262
if len(skippedDamp)>0:
241-
txt+='---- Skipped (High Damping)\n'
263+
txt+='# --- Skipped (Damping outside of `posDampRange`={})\n'.format(posDampRange)
242264
for im in skippedDamp:
243-
m = cd['Modes'][im]
244-
Desc = cd['ShortModeDescr'][im]
245-
zeta = m['DampingRatio']
246-
txt+='{:02d} ; {:8.3f} ; {:7.4f} ; {:s}\n'.format(im+1,m['NaturalFreq_Hz'],m['DampingRatio'],Desc)
265+
txt+=mode2txtline(cd, im)
247266
if txtFileName is not None:
248267
with open(txtFileName, 'w') as f:
249268
f.write(txt)

pyFAST/linearization/examples/ex1a_OneLinFile_SimpleEigenAnalysis.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
print('Keys available:',lin.keys())
1818

1919
# --- Perform eigenvalue analysis
20-
fd, zeta, Q, f0 = eigA(lin['A'])
20+
#fd, zeta, Q, f0 = eigA(lin['A'])
21+
fd, zeta, Q, f0 = lin.eva()
2122
print('Nat. freq. [Hz], Damping ratio [%]')
2223
print(np.column_stack((np.around(f0,4),np.around(zeta*100,4))))
2324

pyFAST/linearization/linearization.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ def campbell(templateFstFile, operatingPointsFile, workDir, toolboxDir, fastExe,
4343
nPerPeriod=36, baseDict=None, tStart=5400, trim=True, viz=False,
4444
trimGainPitch = 0.001, trimGainGenTorque = 300,
4545
maxTrq= None,
46-
generateInputs=True, runFast=True, runMBC=True, prefix='',sortedSuffix=None, ylim=None, removeTwrAzimuth=False):
46+
generateInputs=True, runFast=True, runMBC=True, prefix='',sortedSuffix=None, ylim=None,
47+
removeTwrAzimuth=False, starSub=None, removeStatesPattern=None # Options for A matrices
48+
):
4749
"""
4850
Wrapper function to perform a Campbell diagram study
4951
see: writeLinearizationFiles, postproLinearization and postproMBC for more description of inputs.
@@ -60,6 +62,14 @@ def campbell(templateFstFile, operatingPointsFile, workDir, toolboxDir, fastExe,
6062
- prefix: strings such that the output files will looked like: [folder prefix ]
6163
- sortedSuffix use a separate file where IDs have been sorted
6264
- runFast Logical to specify whether to run the simulations or not
65+
- removeTwrAzimuth: if False do nothing
66+
otherwise discard lin files where azimuth in [60, 180, 300]+/-4deg (close to tower).
67+
- starSub: if None, raise an error if `****` are present
68+
otherwise replace *** with `starSub` (e.g. 0)
69+
see FASTLinearizationFile.
70+
- removeStatesPattern: remove states matching a giving description pattern.
71+
e.g: 'tower|Drivetrain' or '^AD'
72+
see FASTLinearizationFile.
6373
"""
6474
Cases=pd.read_csv(caseFile); Cases.rename(columns=lambda x: x.strip(), inplace=True)
6575

@@ -80,7 +90,7 @@ def campbell(templateFstFile, operatingPointsFile, workDir, toolboxDir, fastExe,
8090

8191
# --- Postprocess linearization outputs (MBC + modes ID)
8292
if runMBC:
83-
OP, Freq, Damp, _, _, modeID_file = postproCampbell(FSTfilenames, removeTwrAzimuth=removeTwrAzimuth, suffix=suffix)
93+
OP, Freq, Damp, _, _, modeID_file = postproCampbell(FSTfilenames, removeTwrAzimuth=removeTwrAzimuth, starSub=starSub, removeStatesPattern=removeStatesPattern, suffix=suffix)
8494

8595
# --- Plot Campbell
8696
fig, axes = plotCampbell(OP, Freq, Damp, sx='WS_[m/s]', UnMapped=UnMapped, ylim=ylim)

0 commit comments

Comments
 (0)