Skip to content

Commit e0e35a7

Browse files
ptrbortolottirthedin
authored andcommitted
allow toolbox to set los res time step and ensure consistency to high res box. also, remove extra round for dX_high. remove misleading _les naming
1 parent f46b553 commit e0e35a7

File tree

2 files changed

+67
-56
lines changed

2 files changed

+67
-56
lines changed

openfast_toolbox/fastfarm/FASTFarmCaseCreation.py

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,17 @@ class FFCaseCreation:
8888
def __init__(self,
8989
path,
9090
wts,
91-
tmax,
91+
tmax_desired,
9292
zbot,
9393
vhub,
9494
shear,
9595
TIvalue,
9696
inflow_deg,
97-
dt_high_les = None,
98-
ds_high_les = None,
97+
dt_high = None,
98+
ds_high = None,
9999
extent_high = None,
100-
dt_low_les = None,
101-
ds_low_les = None,
100+
dt_low = None,
101+
ds_low = None,
102102
extent_low = None,
103103
ffbin = None,
104104
tsbin = None,
@@ -123,8 +123,8 @@ def __init__(self,
123123
Full path for the target case directory
124124
wts: dictionary
125125
Wind farm layout and turbine parameters in dictionary form
126-
tmax: scalar
127-
Max simulation time given in seconds
126+
tmax_desired: scalar
127+
Max desired simulation time given in seconds. OF toolbox makes small adjustments to ensure compatibility to dT
128128
vhub: list of scalars or single scalar
129129
Wind speeds at hub height to sweep on. Accepts a list or single value
130130
shear: list of scalars or single scalar
@@ -133,19 +133,19 @@ def __init__(self,
133133
TI values at hub height to sweep on. Accepts a list or single value
134134
inflow_deg: list of scalars or single scalar
135135
Inflow angles to sweep on. Accepts a list or single value
136-
dt_high_les: scalar
136+
dt_high: scalar
137137
Time step of the desired high-resolution box. If LES boxes given, should
138138
match LES box; otherwise desired TurbSim boxes. Default values as given in the
139139
modeling guidances are used if none is given
140-
ds_high_les: scalar
140+
ds_high: scalar
141141
Grid resolution of the desired high-resolution box. If LES boxes given, should
142142
match LES box; otherwise desired TurbSim boxes. Default values as given in the
143143
modeling guidances are used if none is given
144-
dt_low_les: scalar
144+
dt_low: scalar
145145
Time step of the desired low-resolution box. If LES boxes given, should match
146146
match LES box; otherwise desired TurbSim boxes. Default values as given in the
147147
modeling guidances are used if none is given
148-
ds_low_les: scalar
148+
ds_low: scalar
149149
Grid resolution of the desired low-resolution box. If LES boxes given, should
150150
match LES box; otherwise desired TurbSim boxes. Default values as given in the
151151
modeling guidances are used if none is given
@@ -190,16 +190,16 @@ def __init__(self,
190190

191191
self.path = path
192192
self.wts = wts
193-
self.tmax = tmax
193+
self.tmax_desired = tmax_desired
194194
self.zbot = zbot
195195
self.vhub = vhub
196196
self.shear = shear
197197
self.TIvalue = TIvalue
198198
self.inflow_deg = inflow_deg
199-
self.dt_high_les = dt_high_les
200-
self.ds_high_les = ds_high_les
201-
self.dt_low_les = dt_low_les
202-
self.ds_low_les = ds_low_les
199+
self.dt_high = dt_high
200+
self.ds_high = ds_high
201+
self.dt_low = dt_low
202+
self.ds_low = ds_low
203203
self.extent_low = extent_low
204204
self.extent_high = extent_high
205205
self.ffbin = ffbin
@@ -286,8 +286,8 @@ def __repr__(self):
286286

287287
if self.TSlowBoxFilesCreatedBool or self.inflowType == 'LES':
288288
s += f' Low-resolution domain: \n'
289-
s += f' - ds low: {self.ds_low_les} m\n'
290-
s += f' - dt low: {self.dt_low_les} s\n'
289+
s += f' - ds low: {self.ds_low} m\n'
290+
s += f' - dt low: {self.dt_low} s\n'
291291
s += f' - Extent of low-res box (in D): xmin = {self.extent_low[0]}, xmax = {self.extent_low[1]}, '
292292
s += f'ymin = {self.extent_low[2]}, ymax = {self.extent_low[3]}, zmax = {self.extent_low[4]}\n'
293293
else:
@@ -296,8 +296,8 @@ def __repr__(self):
296296

297297
if self.TShighBoxFilesCreatedBool or self.inflowType == 'LES':
298298
s += f' High-resolution domain: \n'
299-
s += f' - ds high: {self.ds_high_les} m\n'
300-
s += f' - dt high: {self.dt_high_les} s\n'
299+
s += f' - ds high: {self.ds_high} m\n'
300+
s += f' - dt high: {self.dt_high} s\n'
301301
s += f' - Extent of high-res boxes: {self.extent_high} D total\n'
302302
else:
303303
s += f'High-res boxes not created yet.\n'
@@ -325,6 +325,9 @@ def _checkInputs(self):
325325
self.fmax = self.wts[0]['fmax']
326326
self.Cmeander = self.wts[0]['Cmeander']
327327

328+
if self.inflowType == 'TS':
329+
self.dt_high = 1./(2.*self.fmax)
330+
328331
# Check the platform heading and initialize as zero if needed
329332
if 'phi_deg' not in self.wts[0]: # check key for first turbine
330333
for i in self.wts:
@@ -371,7 +374,7 @@ def _checkInputs(self):
371374
if self.cmax <= 0: raise ValueError('cmax cannot be negative')
372375
if self.fmax <= 0: raise ValueError('fmax cannot be negative')
373376
if self.Cmeander <= 0: raise ValueError('Cmeander cannot be negative')
374-
if self.tmax <= 0: raise ValueError('A positive tmax should be requested')
377+
if self.tmax_desired <= 0: raise ValueError('A positive tmax_desired should be requested')
375378
if self.zbot <= 0: raise ValueError('zbot should be greater than 0 (recommended 1)')
376379

377380
# Ensure quantities are list
@@ -490,9 +493,9 @@ def _checkInputs(self):
490493
if not os.path.isdir(p):
491494
print(f'WARNING: The LES path {p} does not exist')
492495
# LES is requested, so domain limits must be given
493-
if None in (self.dt_high_les, self.ds_high_les, self.dt_low_les, self.ds_low_les):
496+
if None in (self.dt_high, self.ds_high, self.dt_low, self.ds_low):
494497
raise ValueError (f'An LES-driven case was requested, but one or more grid parameters were not given. '\
495-
'Set `dt_high_les`, `ds_high_les`, `dt_low_les`, and `ds_low_les` based on your LES boxes.')
498+
'Set `dt_high`, `ds_high`, `dt_low`, and `ds_low` based on your LES boxes.')
496499
else:
497500
raise ValueError (f"Inflow type `inflowType` should be 'TS' or 'LES'. Received {self.inflowType}.")
498501

@@ -504,18 +507,18 @@ def _checkInputs(self):
504507

505508
# Check the ds and dt for the high- and low-res boxes. If not given, call the
506509
# AMR-Wind auxiliary function with dummy domain limits.
507-
if None in (self.dt_high_les, self.ds_high_les, self.dt_low_les, self.ds_low_les):
510+
if None in (self.dt_high, self.ds_high, self.dt_low, self.ds_low):
508511
mod_wake_str = ['','polar', 'curled', 'cartesian']
509512
print(f'WARNING: One or more temporal or spatial resolution for low- and high-res domains were not given.')
510513
print(f' Estimated values for {mod_wake_str[self.mod_wake]} wake model shown below.')
511514
self._determine_resolutions_from_dummy_amrwind_grid()
512515

513516
# Check the domain extents
514-
if self.dt_low_les%(self.dt_high_les-1e-15) > 1e-12:
517+
if self.dt_low%(self.dt_high-1e-15) > 1e-12:
515518
raise ValueError(f'The temporal resolution dT_Low should be a multiple of dT_High')
516-
if self.dt_low_les < self.dt_high_les:
519+
if self.dt_low < self.dt_high:
517520
raise ValueError(f'The temporal resolution dT_High should not be greater than dT_Low on the LES side')
518-
if self.ds_low_les < self.ds_high_les:
521+
if self.ds_low < self.ds_high:
519522
raise ValueError(f'The grid resolution dS_High should not be greater than dS_Low on the LES side')
520523

521524

@@ -562,19 +565,19 @@ def _determine_resolutions_from_dummy_amrwind_grid(self):
562565
buffer_hr = self.extent_high,
563566
mod_wake = self.mod_wake)
564567

565-
print(f' High-resolution: ds: {amr.ds_hr} m, dt: {amr.dt_high_les} s')
566-
print(f' Low-resolution: ds: {amr.ds_lr} m, dt: {amr.dt_low_les} s\n')
568+
print(f' High-resolution: ds: {amr.ds_hr} m, dt: {amr.dt_high} s')
569+
print(f' Low-resolution: ds: {amr.ds_lr} m, dt: {amr.dt_low} s\n')
567570
print(f'WARNING: If the above values are too fine or manual tuning is warranted, specify them manually.')
568-
print(f' To do that, specify, e.g., `dt_high_les = {2*amr.dt_high_les}` to the call to `FFCaseCreation`.')
569-
print(f' `ds_high_les = {2*amr.ds_high_les}`')
570-
print(f' `dt_low_les = {2*amr.dt_low_les}`')
571-
print(f' `ds_low_les = {2*amr.ds_low_les}`')
571+
print(f' To do that, specify, e.g., `dt_high = {2*amr.dt_high}` to the call to `FFCaseCreation`.')
572+
print(f' `ds_high = {2*amr.ds_high}`')
573+
print(f' `dt_low = {2*amr.dt_low}`')
574+
print(f' `ds_low = {2*amr.ds_low}`')
572575
print(f' If the values above are okay, you can safely ignore this warning.\n')
573576

574-
self.dt_high_les = amr.dt_high_les
575-
self.ds_high_les = amr.dt_high_les
576-
self.dt_low_les = amr.dt_low_les
577-
self.ds_low_les = amr.dt_low_les
577+
self.dt_high = amr.dt_high
578+
self.ds_high = amr.dt_high
579+
self.dt_low = amr.dt_low
580+
self.ds_low = amr.dt_low
578581

579582

580583

@@ -1659,8 +1662,15 @@ def TS_low_setup(self, writeFiles=True, runOnce=False):
16591662
# By passing low_ext, manual mode for the domain size is activated, and by passing ds_low,
16601663
# manual mode for discretization (and further domain size) is also activated
16611664
currentTS = TSCaseCreation(D_, HubHt_, Vhub_, tivalue_, shear_, x=xlocs_, y=ylocs_, zbot=self.zbot, cmax=self.cmax,
1662-
fmax=self.fmax, Cmeander=self.Cmeander, boxType=boxType, low_ext=self.extent_low, ds_low=self.ds_low_les)
1665+
fmax=self.fmax, Cmeander=self.Cmeander, boxType=boxType, low_ext=self.extent_low, ds_low=self.ds_low)
16631666
self.TSlowbox = currentTS
1667+
1668+
# Ensure that dt low is a multiple of dt high and that tmax is a multiple of both dt low and dt high, and that tmax is larger than tmax_desired
1669+
currentTS.dt = getMultipleOf(currentTS.dt, multipleof=self.dt_high)
1670+
self.tmax = getMultipleOf(self.tmax_desired, multipleof=currentTS.dt)
1671+
if self.tmax < self.tmax_desired:
1672+
self.tmax += currentTS.dt
1673+
16641674
if runOnce:
16651675
return
16661676
currentTS.writeTSFile(self.turbsimLowfilepath, currentTSLowFile, tmax=self.tmax, verbose=self.verbose)
@@ -1670,9 +1680,6 @@ def TS_low_setup(self, writeFiles=True, runOnce=False):
16701680
Lowinp['PLExp'] = shear_
16711681
#Lowinp['latitude'] = latitude # Not used when IECKAI model is selected.
16721682
Lowinp['InCDec1'] = Lowinp['InCDec2'] = Lowinp['InCDec3'] = f'"{a} {b/(8.1*Lambda1):.8f}"'
1673-
# The dt was computed for a proper low-res box but here we will want to compare with the high-res
1674-
# and it is convenient to have the same time step. Let's do that change here
1675-
Lowinp['TimeStep'] = 1/(2*self.fmax)
16761683
if writeFiles:
16771684
lowFileName = os.path.join(seedPath, 'Low.inp')
16781685
Lowinp.write(lowFileName)
@@ -1890,11 +1897,17 @@ def TS_high_get_time_series(self):
18901897
uvel = np.roll(bts['u'][0, :, jTurb, kTurb], start_time_step)
18911898
vvel = np.roll(bts['u'][1, :, jTurb, kTurb], start_time_step)
18921899
wvel = np.roll(bts['u'][2, :, jTurb, kTurb], start_time_step)
1900+
1901+
# Map it to high-res time and dt (both)
1902+
time_hr = np.arange(0, self.tmax + self.dt_high, self.dt_high)
1903+
uvel_hr = np.interp(time_hr, time, uvel)
1904+
vvel_hr = np.interp(time_hr, time, vvel)
1905+
wvel_hr = np.interp(time_hr, time, wvel)
18931906

18941907
# Checks
1895-
assert len(time)==len(uvel)
1896-
assert len(uvel)==len(vvel)
1897-
assert len(vvel)==len(wvel)
1908+
assert len(time_hr)==len(uvel_hr)
1909+
assert len(uvel_hr)==len(vvel_hr)
1910+
assert len(vvel_hr)==len(wvel_hr)
18981911

18991912
# Save timeseries as CondXX/Seed_Z/USRTimeSeries_T*.txt. This file will later be copied to CondXX/CaseYY/Seed_Z
19001913
timeSeriesOutputFile = os.path.join(caseSeedPath, f'USRTimeSeries_T{t+1}.txt')
@@ -1912,17 +1925,16 @@ def TS_high_get_time_series(self):
19121925
print(f"Seed {seed}, Case {case}: Turbine {t+1} is not at a grid point location. Tubine is at y={yloc_}",\
19131926
f"on the turbine reference frame, which is y={yt} on the low-res TurbSim reference frame. The",\
19141927
f"nearest grid point in y is {bts['y'][jTurb]} so printing y={yoffset} to the time-series file.")
1915-
writeTimeSeriesFile(timeSeriesOutputFile, yoffset, Hub_series, uvel, vvel, wvel, time)
1916-
1928+
writeTimeSeriesFile(timeSeriesOutputFile, yoffset, Hub_series, uvel_hr, vvel_hr, wvel_hr, time_hr)
19171929

19181930

19191931

19201932
def TS_high_setup(self, writeFiles=True):
19211933

19221934
#todo: Check if the low-res boxes were created successfully
19231935

1924-
if self.ds_high_les != self.cmax:
1925-
print(f'WARNING: The requested ds_high = {self.ds_high_les} m is not actually used. The TurbSim ')
1936+
if self.ds_high != self.cmax:
1937+
print(f'WARNING: The requested ds_high = {self.ds_high} m is not actually used. The TurbSim ')
19261938
print(f' boxes use the default max chord value ({self.cmax} m here) for the spatial resolution.')
19271939

19281940
# Create symbolic links for the low-res boxes
@@ -1969,7 +1981,7 @@ def TS_high_setup(self, writeFiles=True):
19691981
# Modify some values and save file (some have already been set in the call above)
19701982
Highinp = FASTInputFile(currentTSHighFile)
19711983
Highinp['RandSeed1'] = self.seedValues[seed]
1972-
Highinp['TimeStep'] = 1/(2*self.fmax)
1984+
Highinp['TimeStep'] = 1./(2.*self.fmax)
19731985
Highinp['TurbModel'] = f'"TIMESR"'
19741986
Highinp['UserFile'] = f'"USRTimeSeries_T{t+1}.txt"'
19751987
Highinp['RefHt'] = HubHt_
@@ -2264,8 +2276,8 @@ def _FF_setup_LES(self, seedsToKeep=1):
22642276
ff_file['TMax'] = self.tmax
22652277

22662278
# LES-related parameters
2267-
ff_file['DT_Low-VTK'] = self.dt_low_les
2268-
ff_file['DT_High-VTK'] = self.dt_high_les
2279+
ff_file['DT_Low-VTK'] = self.dt_low
2280+
ff_file['DT_High-VTK'] = self.dt_high
22692281
ff_file['WindFilePath'] = f'''"{os.path.join(seedPath, LESboxesDirName)}"'''
22702282
#if checkWindFiles:
22712283
# ff_file['ChkWndFiles'] = 'TRUE'
@@ -2289,7 +2301,7 @@ def _FF_setup_LES(self, seedsToKeep=1):
22892301
self.dr = round(self.D/15)
22902302
ff_file['dr'] = self.dr
22912303
ff_file['NumRadii'] = int(np.ceil(3*D_/(2*self.dr) + 1))
2292-
ff_file['NumPlanes'] = int(np.ceil( 20*D_/(self.dt_low_les*Vhub_*(1-1/6)) ) )
2304+
ff_file['NumPlanes'] = int(np.ceil( 20*D_/(self.dt_low*Vhub_*(1-1/6)) ) )
22932305

22942306
# Ensure radii outputs are within [0, NumRadii-1]
22952307
for i, r in enumerate(ff_file['OutRadii']):
@@ -2363,7 +2375,7 @@ def _FF_setup_TS(self):
23632375
highbts = TurbSimFile(os.path.join(seedPath,'TurbSim', f'HighT1.bts'))
23642376

23652377
# Get dictionary with all the D{X,Y,Z,t}, L{X,Y,Z,t}, N{X,Y,Z,t}, {X,Y,Z}0
2366-
d = self._getBoxesParamsForFF(lowbts, highbts, self.dt_low_les, D_, HubHt_, xWT, yt)
2378+
d = self._getBoxesParamsForFF(lowbts, highbts, self.dt_low, D_, HubHt_, xWT, yt)
23672379

23682380
# Write the file
23692381
writeFastFarm(outputFSTF, templateFSTF, xWT, yt, zWT, d, OutListT1=self.outlistFF,
@@ -2395,7 +2407,7 @@ def _FF_setup_TS(self):
23952407
self.dr = round(self.D/10)
23962408
ff_file['dr'] = self.dr
23972409
ff_file['NumRadii'] = int(np.ceil(3*D_/(2*self.dr) + 1))
2398-
ff_file['NumPlanes'] = int(np.ceil( 20*D_/(self.dt_low_les*Vhub_*(1-1/6)) ) )
2410+
ff_file['NumPlanes'] = int(np.ceil( 20*D_/(self.dt_low*Vhub_*(1-1/6)) ) )
23992411

24002412
# Vizualization outputs
24012413
ff_file['WrDisWind'] = 'False'
@@ -2430,7 +2442,7 @@ def _getBoxesParamsForFF(self, lowbts, highbts, dt_low_desired, D, HubHt, xWT, y
24302442

24312443
dT_High = np.round(highbts.dt, 4)
24322444
# dX_High can sometimes be too high. So get the closest to the cmax, but multiple of what should have been
2433-
dX_High = round(meanU_High*dT_High)
2445+
dX_High = meanU_High*dT_High
24342446
if self.verbose>1:
24352447
print(f'original dX_High is {dX_High}')
24362448
dX_High = round(self.cmax/dX_High) * dX_High

openfast_toolbox/fastfarm/TurbSimCaseCreation.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
99
@author: kshaler
1010
"""
11-
import os, glob, struct
1211
import numpy as np
1312

1413
class TSCaseCreation:

0 commit comments

Comments
 (0)