@@ -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
0 commit comments