• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

WISDEM / WEIS / 12283078613

11 Dec 2024 06:59PM UTC coverage: 78.249% (-0.6%) from 78.802%
12283078613

Pull #308

github

dzalkind
Merge remote-tracking branch 'origin/DLC_RefactorCaseInputs' into DLC_RefactorCaseInputs
Pull Request #308: DLC Generation - Refactor and New Cases

446 of 665 new or added lines in 9 files covered. (67.07%)

3 existing lines in 3 files now uncovered.

21416 of 27369 relevant lines covered (78.25%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.34
/weis/dlc_driver/dlc_generator.py
1
import numpy as np
1✔
2
import os
1✔
3
import logging
1✔
4
import weis.inputs as sch
1✔
5
from weis.dlc_driver.turbulence_models import IEC_TurbulenceModels
1✔
6
from weis.aeroelasticse.CaseGen_General import CaseGen_General
1✔
7
from weis.aeroelasticse.FileTools import remove_numpy
1✔
8
from weis.aeroelasticse.utils import OLAFParams
1✔
9

10
logger = logging.getLogger("wisdem/weis")
1✔
11

12
class DLCInstance(object):
1✔
13

14
    def __init__(self, options=None):
1✔
15
        # Set default DLC with empty properties
16
        self.URef = 0.0
1✔
17
        self.wind_heading = 0.0
1✔
18
        self.yaw_misalign = 0.0
1✔
19
        self.wave_height = 0.0
1✔
20
        self.wave_period = 0.0
1✔
21
        self.wave_heading = 0.0
1✔
22
        self.wave_gamma = 0.0
1✔
23
        self.probability = 0.0
1✔
24
        self.analysis_time = 600.
1✔
25
        self.transient_time = 120.
1✔
26
        self.shutdown_time = 9999.
1✔
27
        self.IEC_WindType = 'NTM'
1✔
28
        self.turbine_status = 'operating'
1✔
29
        self.wave_spectrum = 'JONSWAP'
1✔
30
        self.turbulent_wind = False
1✔
31
        self.direction_pn = '' # Positive (p) or negative (n), used for ECD
1✔
32
        self.shear_hv = '' # Horizontal (h) or vertical (v), used for EWS
1✔
33
        self.sigma1 = '' # Standard deviation of the wind
1✔
34
        self.RandSeed1 = 0
1✔
35
        self.wave_seed1 = 0
1✔
36
        self.label = '' # For 1.1/Custom
1✔
37
        self.wind_file = ''
1✔
38
        self.PSF = 1.35 # Partial Safety Factor
1✔
39
        self.azimuth_init = 0.0
1✔
40

41
        if not options is None:
1✔
42
            self.default_turbsim_props(options)
1✔
43

44
    def default_turbsim_props(self, options):
1✔
45
        for key in options['turbulent_wind'].keys():
1✔
46
            setattr(self, key, options['turbulent_wind'][key])
1✔
47

48
class DLCGenerator(object):
1✔
49

50
    dlc_schema = sch.validation.get_modeling_schema()['properties']['DLC_driver']['properties']['DLCs']['items']['properties']
1✔
51

52
    # TODO: not sure where this should live, so it's a global for now
53
    # Could it be an input yaml?
54
    openfast_input_map = {
1✔
55
        # Generic name: OpenFAST input (list if necessary)
56
        'total_time': ("Fst","TMax"),
57
        'transient_time': ("Fst","TStart"),
58
        
59
        'WindFile_type': ("InflowWind","WindType"),
60
        'wind_speed': ("InflowWind","HWindSpeed"),
61
        'WindFile_name': ("InflowWind","FileName_BTS"),
62
        'WindFile_name': ("InflowWind","Filename_Uni"),
63
        'rotorD': ("InflowWind","RefLength"),
64
        'WindHd': ("InflowWind","PropagationDir"),
65
        'hub_height': ("InflowWind","RefHt_Uni"),
66
        
67
        'rot_speed_initial': ("ElastoDyn","RotSpeed"),
68
        'pitch_initial': [("ElastoDyn","BlPitch1"),("ElastoDyn","BlPitch2"),("ElastoDyn","BlPitch3")],
69
        'azimuth_init': ("ElastoDyn","Azimuth"),
70
        'yaw_misalign': ("ElastoDyn","NacYaw"),
71
        
72
        'wave_height': ("HydroDyn","WaveHs"),
73
        'wave_period': ("HydroDyn","WaveTp"),
74
        'WaveHd': ("HydroDyn","WaveDir"),
75
        'WaveGamma': ("HydroDyn","WavePkShp"),
76
        'wave_seed': ("HydroDyn","WaveSeed1"),
77

78
        'wave_model': ("HydroDyn","WaveMod"),
79
        
80
        'shutdown_time': [
81
            ("ServoDyn","TPitManS1"),
82
            ("ServoDyn","TPitManS2"),
83
            ("ServoDyn","TPitManS3"),
84
            ("ServoDyn","TimGenOf"),
85
            ],
86

87
        'startup_time': [
88
            ("ServoDyn","TimGenOn"),
89
            ("ServoDyn","TPCOn"),
90
        ],
91
            
92

93
        'final_blade_pitch': [
94
            ("ServoDyn","BlPitchF(1)"),
95
            ("ServoDyn","BlPitchF(2)"),
96
            ("ServoDyn","BlPitchF(3)"),
97
            
98
        ],
99
        'pitchfault_time1': ("ServoDyn","TPitManS1"),
100
        'pitchfault_time2': ("ServoDyn","TPitManS2"),
101
        'pitchfault_time3': ("ServoDyn","TPitManS3"),
102
        'pitchfault_blade1pos': ("ServoDyn","BlPitchF(1)"),
103
        'pitchfault_blade2pos': ("ServoDyn","BlPitchF(2)"),
104
        'pitchfault_blade3pos': ("ServoDyn","BlPitchF(3)"),
105
        'genfault_time': ("ServoDyn","TimGenOf"),
106
        'yawfault_time': ("ServoDyn","TYawManS"),
107
        'yawfault_yawpos': ("ServoDyn","NacYawF"),
108
        
109
        'aero_mod': ("AeroDyn15","AFAeroMod"),
110
        'wake_mod': ("AeroDyn15","WakeMod"),
111
        'tau1_const': ("AeroDyn15","tau1_const"),
112

113

114
        # 'dlc_label': ("DLC","Label"),
115
        # 'wind_seed': ("DLC","WindSeed"),
116
        # 'wind_speed': ("DLC","MeanWS"),
117

118
        # TODO: where should turbsim live?
119
        # These aren't actually used to generate turbsim, the generic inputs are used
120
        # However, I think it's better to be over-thorough and check that inputs are applied than the uncertainty of not checking any
121
        'wind_seed': ("TurbSim", "RandSeed1"),
122
        'direction': ("TurbSim", "direction_pn"),
123
        'shear': ("TurbSim", "shear_hv")
124
    }
125

126
    
127

128
    def __init__(
1✔
129
            self, 
130
            ws_cut_in=4.0, 
131
            ws_cut_out=25.0, 
132
            ws_rated=10.0, 
133
            wind_speed_class = 'I',
134
            wind_turbulence_class = 'B', 
135
            fix_wind_seeds=True, 
136
            fix_wave_seeds=True, 
137
            metocean={},
138
            initial_condition_table = {},
139
            default_options = {},
140
            ):
141
        self.ws_cut_in = ws_cut_in
1✔
142
        self.ws_cut_out = ws_cut_out
1✔
143
        self.wind_speed_class = wind_speed_class
1✔
144
        self.wind_turbulence_class = wind_turbulence_class
1✔
145
        self.ws_rated = ws_rated
1✔
146
        self.cases = []
1✔
147
        if fix_wind_seeds:
1✔
148
            self.rng_wind = np.random.default_rng(12345)
1✔
149
        else:
150
            self.rng_wind = np.random.default_rng()
×
151
        if fix_wave_seeds:
1✔
152
            self.rng_wave = np.random.default_rng(6789)
1✔
153
        else:
154
            self.rng_wave = np.random.default_rng()
×
155
        self.n_cases = 0
1✔
156
        self.n_ws_dlc11 = 0
1✔
157

158
        # Set and update default_options, applied to dlc_options and first group in case_inputs
159
        self.default_options = {
1✔
160
            'wake_mod': 1,
161
            'wave_model': 2,
162
        }
163
        self.default_options.update(default_options)
1✔
164

165
        # Init openfast case list
166
        self.openfast_case_inputs = []
1✔
167

168
        # Metocean conditions
169
        self.mo_ws = metocean['wind_speed']
1✔
170
        self.mo_Hs_NSS = metocean['wave_height_NSS']
1✔
171
        self.mo_Tp_NSS = metocean['wave_period_NSS']
1✔
172
        self.mo_Hs_F = metocean['wave_height_fatigue']
1✔
173
        self.mo_Tp_F = metocean['wave_period_fatigue']
1✔
174
        self.mo_Hs_SSS = metocean['wave_height_SSS']
1✔
175
        self.mo_Tp_SSS = metocean['wave_period_SSS']
1✔
176
        if len(self.mo_ws)!=len(self.mo_Hs_NSS):
1✔
177
            raise Exception('The vector of metocean conditions wave_height_NSS in the modeling options must have the same length of the tabulated wind speeds')
×
178
        if len(self.mo_ws)!=len(self.mo_Tp_NSS):
1✔
179
            raise Exception('The vector of metocean conditions wave_period_NSS in the modeling options must have the same length of the tabulated wind speeds')
×
180
        if len(self.mo_ws)!=len(self.mo_Hs_F):
1✔
181
            raise Exception('The vector of metocean conditions wave_height_fatigue in the modeling options must have the same length of the tabulated wind speeds')
×
182
        if len(self.mo_ws)!=len(self.mo_Tp_F):
1✔
183
            raise Exception('The vector of metocean conditions wave_period_fatigue in the modeling options must have the same length of the tabulated wind speeds')
×
184
        if len(self.mo_ws)!=len(self.mo_Hs_SSS):
1✔
185
            raise Exception('The vector of metocean conditions wave_height_SSS in the modeling options must have the same length of the tabulated wind speeds')
×
186
        if len(self.mo_ws)!=len(self.mo_Tp_SSS):
1✔
187
            raise Exception('The vector of metocean conditions wave_period_SSS in the modeling options must have the same length of the tabulated wind speeds')
×
188

189
        # Load extreme wave heights and periods
190
        self.wave_height50 = np.array([metocean['wave_height50']])
1✔
191
        self.wave_period50 = np.array([metocean['wave_period50']])
1✔
192
        self.wave_height1 = np.array([metocean['wave_height1']])
1✔
193
        self.wave_period1 = np.array([metocean['wave_period1']])
1✔
194

195
        self.initial_condition_table = initial_condition_table
1✔
196

197
    def IECwind(self):
1✔
198
        self.IECturb = IEC_TurbulenceModels()
1✔
199
        self.IECturb.Turbine_Class = self.wind_speed_class
1✔
200
        self.IECturb.Turbulence_Class = self.wind_turbulence_class
1✔
201
        self.IECturb.setup()
1✔
202
        _, self.V_e50, self.V_e1, _, _ = self.IECturb.EWM(0.)
1✔
203
        self.V_ref = self.IECturb.V_ref
1✔
204
        self.wind_speed_class_num = self.IECturb.Turbine_Class_Num
1✔
205

206
    def to_dict(self):
1✔
207
        return [vars(m) for m in self.cases]
×
208

209
    def get_wind_speeds(self, options):
1✔
210
        if len(options['wind_speed']) > 0:
1✔
211
            wind_speed = np.array( [float(m) for m in options['wind_speed']] )
1✔
212
        else:
213
            wind_speed = np.arange(self.ws_cut_in, self.ws_cut_out+0.5*options['ws_bin_size'], options['ws_bin_size'])
1✔
214
            if wind_speed[-1] != self.ws_cut_out:
1✔
215
                wind_speed = np.append(wind_speed, self.ws_cut_out)
1✔
216

217
        return wind_speed
1✔
218

219
    def get_wind_seeds(self, options, wind_speed):
1✔
220
        
221
        if len(options['wind_seed']) > 0:
1✔
222
            wind_seed = np.array( [int(m) for m in options['wind_seed']] )
1✔
223
        else:
224
            wind_seed = self.rng_wind.integers(2147483648, size=options['n_seeds']*len(wind_speed), dtype=int)
1✔
225
            wind_speed = np.repeat(wind_speed, options['n_seeds'])
1✔
226

227
        return wind_speed, wind_seed
1✔
228

229
    def get_wave_seeds(self, options, wind_speed):
1✔
230
        if len(options['wave_seed']) > 0:
1✔
231
            wave_seed = np.array( [int(m) for m in options['wave_seed']] )
1✔
232
        else:
233
            wave_seed = self.rng_wave.integers(2147483648, size=len(wind_speed), dtype=int)
1✔
234

235
        return wave_seed
1✔
236

237
    def get_wind_heading(self, options):
1✔
238
        if len(options['wind_heading']) > 0:
1✔
239
            wind_heading = np.array( [float(m) for m in options['wind_heading']] )
1✔
240
        else:
241
            wind_heading = np.array([])
×
242
        return wind_heading
1✔
243

244
    def get_wave_height(self, options):
1✔
245
        if len(options['wave_height']) > 0:
1✔
246
            wave_height = np.array( [float(m) for m in options['wave_height']] )
1✔
247
        else:
248
            wave_height = np.array([])
1✔
249
        return wave_height
1✔
250

251
    def get_wave_period(self, options):
1✔
252
        if len(options['wave_period']) > 0:
1✔
253
            wave_period = np.array( [float(m) for m in options['wave_period']] )
1✔
254
        else:
255
            wave_period = np.array([])
1✔
256
        return wave_period
1✔
257

258
    def get_wave_gamma(self, options):
1✔
259
        if len(options['wave_gamma']) > 0:
1✔
260
            wave_gamma = np.array( [float(m) for m in options['wave_gamma']] )
1✔
261
        else:
262
            wave_gamma = np.array([])
×
263
        return wave_gamma
1✔
264

265
    def get_wave_heading(self, options):
1✔
266
        if len(options['wave_heading']) > 0:
1✔
267
            wave_heading = np.array( [float(m) for m in options['wave_heading']] )
1✔
268
        else:
269
            wave_heading = np.array([])
×
270
        return wave_heading
1✔
271

272
    def get_probabilities(self, options):
1✔
273
        if len(options['probabilities']) > 0:
1✔
274
            probabilities = np.array( [float(m) for m in options['probabilities']] )
1✔
275
        else:
276
            probabilities = np.array([])
×
277
        return probabilities
1✔
278

279
    def get_metocean(self, options):
1✔
280
        wind_speeds_indiv = self.get_wind_speeds(options)
1✔
281
        wind_speed, wind_seed = self.get_wind_seeds(options, wind_speeds_indiv)
1✔
282
        wave_seed = self.get_wave_seeds(options, wind_speed)
1✔
283
        wind_heading = self.get_wind_heading(options)
1✔
284
        wave_height = self.get_wave_height(options)
1✔
285
        wave_period = self.get_wave_period(options)
1✔
286
        wave_gamma = self.get_wave_gamma(options)
1✔
287
        wave_heading = self.get_wave_heading(options)
1✔
288
        probabilities = self.get_probabilities(options)
1✔
289

290
        if len(wind_seed) > 1 and len(wind_seed) != len(wind_speed):
1✔
NEW
291
            raise Exception("The vector of wind_seed must have either length=1 or the same length of wind speeds")
×
292
        if len(wind_heading) > 1 and len(wind_heading) != len(wind_speed):
1✔
UNCOV
293
            raise Exception("The vector of wind_heading must have either length=1 or the same length of wind speeds")
×
294
        if len(wave_seed) > 1 and len(wave_seed) != len(wind_speed):
1✔
295
            raise Exception("The vector of wave seeds must have the same length of wind speeds or not defined")
×
296
        if len(wave_height) > 1 and len(wave_height) != len(wind_speed):
1✔
297
            raise Exception("The vector of wave heights must have either length=1 or the same length of wind speeds")
×
298
        if len(wave_period) > 1 and len(wave_period) != len(wind_speed):
1✔
299
            raise Exception("The vector of wave periods must have either length=1 or the same length of wind speeds")
×
300
        if len(wave_gamma) > 1 and len(wave_gamma) != len(wind_speed):
1✔
301
            raise Exception("The vector of wave_gamma must have either length=1 or the same length of wind speeds")
×
302
        if len(wave_heading) > 1 and len(wave_heading) != len(wind_speed):
1✔
303
            raise Exception("The vector of wave heading must have either length=1 or the same length of wind speeds")
×
304
        if len(probabilities) > 1 and len(probabilities) != len(wind_speed):
1✔
305
            raise Exception("The vector of probabilities must have either length=1 or the same length of wind speeds")
×
306
        if abs(sum(probabilities) - 1.) > 1.e-3:
1✔
307
            raise Exception("The vector of probabilities must sum to 1")
×
308
        
309
        metocean_case_info = {}
1✔
310
        metocean_case_info['wind_speed'] = wind_speed
1✔
311
        metocean_case_info['wind_seed'] = wind_seed
1✔
312
        metocean_case_info['wave_seed'] = wave_seed
1✔
313
        metocean_case_info['wind_heading'] = wind_heading
1✔
314
        metocean_case_info['wave_height'] = wave_height
1✔
315
        metocean_case_info['wave_period'] = wave_period
1✔
316
        # metocean_case_info['current_speeds'] = current_speeds
317
        metocean_case_info['wave_gamma'] = wave_gamma
1✔
318
        metocean_case_info['wave_heading'] = wave_heading
1✔
319
        metocean_case_info['probabilities'] = probabilities       
1✔
320
        # metocean_case_info['current_std'] = self.mo_current_std       
321
        
322
        return metocean_case_info
1✔
323

324

325
    def generate(self, label, options):
1✔
326
        # Use schema to determine known_dlcs (weis/inputs/modeling_schema.yaml)
327
        known_dlcs = self.dlc_schema['DLC']['enum']
1✔
328
        self.OF_dlccaseinputs = {key: None for key in known_dlcs}
1✔
329

330
        # Get extreme wind speeds
331
        self.IECwind()
1✔
332

333
        found = False
1✔
334
        for ilab in known_dlcs:
1✔
335
            func_name = 'generate_'+str(ilab).replace('.','p')
1✔
336

337
            if label in [ilab, str(ilab)]: # Match either 1.1 or '1.1'
1✔
338
                found = True
1✔
339
                getattr(self, func_name)(options) # calls self.generate_1p1(options)
1✔
340
                break
1✔
341

342
        if not found:
1✔
343
            raise ValueError(f'DLC {label} is not currently supported')
×
344

345
        self.n_cases = len(self.cases)
1✔
346

347
    def generate_cases(self,generic_case_inputs,dlc_options):
1✔
348
        '''
349
        This method will generate the simulation inputs for each design load case
350

351
        generic_case_inputs is a list of lists of strings with the options used to create a case matrix
352
        dlc_options is a dictionary, some of its keys will be in generic_case_inputs and used to generate the cases
353
            Other keys include:
354
            sea_state is a string: either severe or normal
355
            label is the string label used in when the wind inputs are created
356
        '''
357

358
        # Handle default options
359
        if 'sea_state' not in dlc_options:
1✔
NEW
360
            dlc_options['sea_state'] = 'normal'
×
361

362
        if 'PSF' not in dlc_options:
1✔
363
            dlc_options['PSF'] = 1.35
1✔
364

365
       
366
        # Generate case list, both generic and OpenFAST specific
367
        self.set_time_options(dlc_options)
1✔
368
        met_options = self.gen_met_options(dlc_options, sea_state=dlc_options['sea_state'])
1✔
369
        
370
        # Add met options to dlc_options for output reporting
371
        dlc_options.update(met_options)
1✔
372
        dlc_options = remove_numpy(dlc_options)
1✔
373

374
        self.apply_initial_conditions(generic_case_inputs,dlc_options, met_options)
1✔
375
        generic_case_list = self.gen_case_list(dlc_options,met_options,generic_case_inputs)
1✔
376

377
        # DLC specific: Make idlc for other parts of WEIS (mostly turbsim generation)
378
        for _, case in enumerate(generic_case_list):
1✔
379
            idlc = DLCInstance(options=dlc_options)
1✔
380
            if dlc_options['IEC_WindType'] == 'ECD':
1✔
381
                idlc.turbulent_wind = False
1✔
382
                idlc.direction_pn = case['direction']
1✔
383
            elif dlc_options['IEC_WindType'] == 'EOG':
1✔
NEW
384
                idlc.turbulent_wind = False
×
NEW
385
                idlc.sigma1,idlc.V_e1 = self.IECturb.EOG(case['wind_speed'])
×
386
            elif dlc_options['IEC_WindType'] == 'EWS':
1✔
387
                idlc.turbulent_wind = False
1✔
388
                idlc.direction_pn = case['direction']
1✔
389
                idlc.shear_hv = case['shear']
1✔
390
                idlc.sigma1 = self.IECturb.NTM(case['wind_speed'])
1✔
391
            else:
392
                idlc.turbulent_wind = True
1✔
393
                idlc.RandSeed1 = case['wind_seed']
1✔
394
            idlc.URef = case['wind_speed']
1✔
395
            idlc.label = dlc_options['label']
1✔
396
            idlc.total_time = case['total_time']
1✔
397
            idlc.IEC_WindType = dlc_options['IEC_WindType']
1✔
398
            idlc.turbine_status = dlc_options['turbine_status']
1✔
399

400
            # Apply case_list info to idlc
401
            for key in case:
1✔
402
                setattr(idlc,key,case[key])
1✔
403

404
            #if dlc_options['label'] == '1.2':
405
            #    idlc.probability = probabilities[i_WaH]
406
            self.cases.append(idlc)
1✔
407

408
            # AEP DLC: set constant turbulence intensity
409
            if dlc_options['label'] == 'AEP':
1✔
NEW
410
                idlc.IECturbc = self.IECturb.NTM(idlc.URef) * dlc_options['TI_factor'] / idlc.URef * 100
×
411

412
            
413
    def apply_sea_state(self,met_options,sea_state='normal'):
1✔
414
        '''
415
        Apply waves based on the expected values provided in the metocean inputs
416
        Will use met_options as an input and modify that dict
417
        sea_state can be normal, severe
418
        '''
419
        allowed_sea_states = ['normal','severe','50-year','1-year']
1✔
420
        if sea_state not in allowed_sea_states:
1✔
NEW
421
            raise Exception(f'Selected sea state of {sea_state} is not in allowed_sea_states: {allowed_sea_states}')
×
422
        
423
        # Select wind speed, sea state lookup
424
        if sea_state == 'normal':
1✔
425
            wind_speed_table = self.mo_ws
1✔
426
            wave_height_table = self.mo_Hs_NSS
1✔
427
            wave_period_table = self.mo_Tp_NSS
1✔
428
        elif sea_state == 'severe':
1✔
429
            wind_speed_table = self.mo_ws
1✔
430
            wave_height_table = self.mo_Hs_SSS
1✔
431
            wave_period_table = self.mo_Tp_SSS
1✔
432
        elif sea_state == '50-year':
1✔
433
            wind_speed_table = [50.]
1✔
434
            wave_height_table = self.wave_height50
1✔
435
            wave_period_table = self.wave_period50
1✔
436
        elif sea_state == '1-year':
1✔
437
            wind_speed_table = [50.]
1✔
438
            wave_height_table = self.wave_height1
1✔
439
            wave_period_table = self.wave_period1
1✔
440

441

442
        # If the user has not defined Hs (wave_height in modopts) and Tp (wave_period in modopts), apply the metocean conditions defined by the table
443
        if len(met_options['wave_height'])==0:
1✔
444
            met_options['wave_height'] = np.interp(met_options['wind_speed'], wind_speed_table, wave_height_table)
1✔
445
        if len(met_options['wave_period'])==0:
1✔
446
            met_options['wave_period'] = np.interp(met_options['wind_speed'], wind_speed_table, wave_period_table)
1✔
447

448
    def set_time_options(self, options):
1✔
449
        '''
450
        Handle time options and add total_time to dict
451
        Default for analysis and transient_time is 0
452
        '''
453
        if options['analysis_time'] > 0:
1✔
454
            options['analysis_time'] = options['analysis_time']
1✔
455
        else:
NEW
456
            options['analysis_time'] = 600.
×
457
        if options['transient_time'] >= 0:
1✔
458
            options['transient_time'] = options['transient_time']
1✔
459
        options['total_time'] = options['analysis_time'] + options['transient_time']
1✔
460

461
    def gen_case_list(self,dlc_options, met_options, generic_case_inputs):
1✔
462
        '''
463
        Generate case list from generic_case_inputs
464
        TODO: this whole thing could be moved into generate_cases, thoughts?
465
        '''
466

467
        
468
        # Combine
469
        comb_options = combine_options(dlc_options,met_options)
1✔
470

471
        # Check that all inputs are valid options
472
        all_inputs = sum(generic_case_inputs, [])
1✔
473
        for input in all_inputs:
1✔
474
            if not input in comb_options:
1✔
NEW
475
                raise Exception(f'The desired input {input} is not defined. Options include {comb_options.keys()}')
×
476

477
        # Setup generic cross product of inputs: 
478
        gen_case_inputs = {}
1✔
479
        for i_group, group in enumerate(generic_case_inputs):
1✔
480
            first_array_len = len(comb_options[group[0]])
1✔
481
            for input in group:
1✔
482
                
483
                # Check that all inputs are of equal length
484
                if len(comb_options[input]) != first_array_len:
1✔
NEW
485
                    raise Exception(f'The input options in group {i_group} are not equal.  This group contains: {group}')
×
486

487
                gen_case_inputs[input] = {'vals': comb_options[input], 'group': i_group}
1✔
488
            
489
        # Generate generic case list
490
        generic_case_list, _ = CaseGen_General(gen_case_inputs,save_matrix=False)
1✔
491

492
        case_inputs_openfast = self.map_generic_to_openfast(generic_case_inputs, comb_options)
1✔
493
        self.openfast_case_inputs.append(case_inputs_openfast)
1✔
494
        return generic_case_list
1✔
495

496
    def gen_met_options(self, dlc_options, sea_state='normal'):
1✔
497
        '''
498
        Determine metocean options based on dlcs and sea state requested
499
        met_options includes wind, waves, seeds, etc.
500

501
        TODO: what input conditions are required of self?
502
        TODO: what is required in dlc_options?
503
        '''
504
        met_options = self.get_metocean(dlc_options)
1✔
505
        
506
        # Apply wave conditions based on wind speeds
507
        self.apply_sea_state(met_options,sea_state=sea_state)
1✔
508
        
509
        make_equal_length(met_options,'wind_speed')
1✔
510
        return met_options
1✔
511

512
    def apply_initial_conditions(self,generic_case_inputs, dlc_options, met_options):
1✔
513
        '''
514
        Add available initial conditions to generic_case_inputs and interpolate options based on initial_condition_table
515
        This is performed within each dlc generator function
516

517
        '''
518
        
519
        # These allowed_ics should map to input in openfast_input_map
520
        allowed_ics = ['pitch_initial','rot_speed_initial','tau1_const']
1✔
521

522
        
523
        if self.initial_condition_table and dlc_options['turbine_status'] == 'operating': # there is an IC table that's not empty
1✔
524
            dlc_wind_speeds = met_options['wind_speed']  # need to use met_options wind speeds because it accounts for seeds
1✔
525
            # find group with wind_speed
526
            wind_group = ['wind_speed' in gci for gci in generic_case_inputs].index(True)
1✔
527
            group = generic_case_inputs[wind_group]
1✔
528

529
            for initial_condition in allowed_ics:
1✔
530
                if initial_condition in self.initial_condition_table:
1✔
531
                    group.append(initial_condition)
1✔
532
                    dlc_options[initial_condition] = np.interp(dlc_wind_speeds,self.initial_condition_table['U'],self.initial_condition_table[initial_condition])
1✔
533
                
534
            # Apply new group
535
            generic_case_inputs[wind_group] = group
1✔
536
            
537
    
538
    def map_generic_to_openfast(self,generic_case_inputs, comb_options):
1✔
539
        case_inputs_openfast = {}
1✔
540
        for i_group, generic_case_group in enumerate(generic_case_inputs):
1✔
541
            for generic_input in generic_case_group:
1✔
542
                
543
                if generic_input not in self.openfast_input_map.keys():
1✔
NEW
544
                    raise Exception(f'The input {generic_input} does not map to an OpenFAST input key in openfast_input_map')
×
545

546
                openfast_input = self.openfast_input_map[generic_input]
1✔
547

548
                if type(openfast_input) == list:
1✔
549
                    # Apply to all list of openfast_inputs
550
                    for of_input in openfast_input:
1✔
551
                        case_inputs_openfast[of_input] = {'vals': comb_options[generic_input], 'group': i_group}
1✔
552

553
                else:
554
                    case_inputs_openfast[openfast_input] = {'vals': comb_options[generic_input], 'group': i_group}
1✔
555

556
        return case_inputs_openfast
1✔
557

558
    def generate_1p1(self, dlc_options):
1✔
559
        # Power production normal turbulence model - normal sea state
560
        
561
        # Get default options
562
        dlc_options.update(self.default_options)   
1✔
563
        
564
        # Handle DLC Specific options:
565
        dlc_options['label'] = '1.1'
1✔
566
        dlc_options['sea_state'] = 'normal'
1✔
567
        dlc_options['PSF'] = 1.35
1✔
568

569
        # Set yaw_misalign, else default
570
        if 'yaw_misalign' in dlc_options:
1✔
571
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
1✔
572
        else: # default
573
            dlc_options['yaw_misalign'] = [0]
1✔
574

575
        # DLC-specific: define groups
576
        # These options should be the same length and we will generate a matrix of all cases
577
        generic_case_inputs = []
1✔
578
        generic_case_inputs.append(['total_time','transient_time'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
579
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed','wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
580
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
581

582
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
583

584
    def generate_AEP(self, dlc_options):
1✔
585
        # Same as DLC 1.1, but with a constant TI
586
        
587
        # Get default options
NEW
588
        dlc_options.update(self.default_options)   
×
589
        
590
        # Handle DLC Specific options:
NEW
591
        dlc_options['label'] = 'AEP'
×
NEW
592
        dlc_options['sea_state'] = 'normal'
×
NEW
593
        dlc_options['PSF'] = 1.35
×
NEW
594
        if 'TI_factor' not in dlc_options:
×
NEW
595
            raise Exception('A TI_factor must be set for the AEP DLC.')
×
596
        
NEW
597
        if 'turbulence_class' in dlc_options:
×
NEW
598
            self.IECturb.Turbulence_Class = dlc_options['turbulence_class']
×
NEW
599
            self.IECturb.setup()
×
600
            
601

602
        # Set yaw_misalign, else default
NEW
603
        if 'yaw_misalign' in dlc_options:
×
NEW
604
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
605
        else: # default
NEW
606
            dlc_options['yaw_misalign'] = [0]
×
607

608
        # DLC-specific: define groups
609
        # These options should be the same length and we will generate a matrix of all cases
NEW
610
        generic_case_inputs = []
×
NEW
611
        generic_case_inputs.append(['total_time','transient_time'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
×
NEW
612
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
613
        generic_case_inputs.append(['yaw_misalign']) # group 2
×
614

NEW
615
        self.generate_cases(generic_case_inputs,dlc_options)
×
616

617
    def generate_1p2(self, dlc_options):
1✔
618
        # Power production normal turbulence model - fatigue loads
619
        
620
        # Get default options
621
        dlc_options.update(self.default_options)   
1✔
622
        
623
        # Handle DLC Specific options:
624
        dlc_options['label'] = '1.2'
1✔
625
        dlc_options['sea_state'] = 'normal'
1✔
626

627
        # Set yaw_misalign, else default
628
        if 'yaw_misalign' in dlc_options:
1✔
NEW
629
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
630
        else: # default
631
            dlc_options['yaw_misalign'] = [0]
1✔
632

633
        # DLC-specific: define groups
634
        # These options should be the same length and we will generate a matrix of all cases
635
        generic_case_inputs = []
1✔
636
        generic_case_inputs.append(['total_time','transient_time'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
637
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
638
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
639

640
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
641

642

643
    def generate_1p3(self, dlc_options):
1✔
644
        # Power production extreme turbulence model - ultimate loads
645
        
646
        # Get default options
647
        dlc_options.update(self.default_options)   
1✔
648
        
649
        # Handle DLC Specific options:
650
        dlc_options['label'] = '1.3'
1✔
651
        dlc_options['sea_state'] = 'normal'
1✔
652
        dlc_options['IEC_WindType'] = '1ETM'
1✔
653

654
        # Set yaw_misalign, else default
655
        if 'yaw_misalign' in dlc_options:
1✔
NEW
656
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
657
        else: # default
658
            dlc_options['yaw_misalign'] = [0]
1✔
659

660
        # DLC-specific: define groups
661
        # These options should be the same length and we will generate a matrix of all cases
662
        generic_case_inputs = []
1✔
663
        generic_case_inputs.append(['total_time','transient_time'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
664
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
665
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
666

667
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
668

669
    def generate_1p4(self, dlc_options):
1✔
670
        # Extreme coherent gust with direction change - ultimate loads
671
        
672
        # Get default options
673
        dlc_options.update(self.default_options)   
1✔
674
        
675
        # Handle DLC Specific options:
676
        dlc_options['label'] = '1.4'
1✔
677
        dlc_options['sea_state'] = 'normal'
1✔
678
        dlc_options['IEC_WindType'] = 'ECD'
1✔
679
        dlc_options['direction'] = ['n', 'p']
1✔
680
        dlc_options['aero_mod'] = 1     # don't use unsteady aero
1✔
681
        
682
        dlc_options['azimuth_init'] = np.linspace(0.,120.,dlc_options['n_azimuth'],endpoint=False)
1✔
683

684
        # Set yaw_misalign, else default
685
        if 'yaw_misalign' in dlc_options:
1✔
NEW
686
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
687
        else: # default
688
            dlc_options['yaw_misalign'] = [0]*len(dlc_options['azimuth_init'])
1✔
689

690

691
        # DLC-specific: define groups
692
        # These options should be the same length and we will generate a matrix of all cases
693
        generic_case_inputs = []
1✔
694
        generic_case_inputs.append(['total_time','transient_time','aero_mod'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
695
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
696
        generic_case_inputs.append(['yaw_misalign','azimuth_init']) # group 2: 
1✔
697
        generic_case_inputs.append(['direction']) # group 3: 
1✔
698

699
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
700

701
        
702
    def generate_1p5(self, dlc_options):
1✔
703
        # Extreme wind shear - ultimate loads
704
        
705
        # Get default options
706
        dlc_options.update(self.default_options)   
1✔
707
        
708
        # Handle DLC Specific options:
709
        dlc_options['label'] = '1.5'
1✔
710
        dlc_options['sea_state'] = 'normal'
1✔
711
        dlc_options['IEC_WindType'] = 'EWS'
1✔
712
        dlc_options['direction'] = ['p', 'n']
1✔
713
        dlc_options['shear'] = ['h', 'v']
1✔
714
        
715

716
        # Set yaw_misalign, else default
717
        if 'yaw_misalign' in dlc_options:
1✔
NEW
718
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
719
        else: # default
720
            dlc_options['yaw_misalign'] = [0]
1✔
721
        
722
        # DLC-specific: define groups
723
        # These options should be the same length and we will generate a matrix of all cases
724
        generic_case_inputs = []
1✔
725
        generic_case_inputs.append(['total_time','transient_time'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
726
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
727
        generic_case_inputs.append(['yaw_misalign']) # group 2: 
1✔
728
        generic_case_inputs.append(['direction']) # group 3: 
1✔
729
        generic_case_inputs.append(['shear']) # group 4: 
1✔
730

731
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
732

733
    def generate_1p6(self, dlc_options):
1✔
734
        # Power production normal turbulence model - severe sea state
735

736
        # Get default options
737
        dlc_options.update(self.default_options)   
1✔
738
        
739
        # DLC Specific options:
740
        dlc_options['label'] = '1.6'
1✔
741
        dlc_options['sea_state'] = 'severe'
1✔
742
        dlc_options['IEC_WindType'] = 'NTM'
1✔
743

744
        # Set yaw_misalign, else default
745
        if 'yaw_misalign' in dlc_options:
1✔
746
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
1✔
747
        else: # default
748
            dlc_options['yaw_misalign'] = [0]
1✔
749

750
        # DLC-specific: define groups
751
        # These options should be the same length and we will generate a matrix of all cases
752
        generic_case_inputs = []
1✔
753
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
754
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
755
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
756

757
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
758

759
    def generate_2p1(self, dlc_options):
1✔
760
        # Power production plus occurrence of fault
761
        # Normal control system fault
762

763
        # Get default options
NEW
764
        dlc_options.update(self.default_options)   
×
765
        
766
        # DLC Specific options:
NEW
767
        dlc_options['label'] = '2.1'
×
NEW
768
        dlc_options['sea_state'] = 'normal'
×
NEW
769
        dlc_options['IEC_WindType'] = 'NTM'
×
NEW
770
        dlc_options['PSF'] = 1.35  # For fault cases, psf depends on the mean-time between faults
×
771

772
        # azimuth starting positions
NEW
773
        dlc_options['azimuth_init'] = np.linspace(0.,120.,dlc_options['n_azimuth'],endpoint=False)
×
774

775

776
        # DLC-specific: define groups
777
        # These options should be the same length and we will generate a matrix of all cases
NEW
778
        generic_case_inputs = []
×
NEW
779
        group0 = ['total_time','transient_time','wake_mod','wave_model']
×
780
        
NEW
781
        if 'pitchfault_time1' in dlc_options:
×
NEW
782
            group0.extend(['pitchfault_time1','pitchfault_blade1pos'])
×
NEW
783
        if 'pitchfault_time2' in dlc_options:
×
NEW
784
            group0.extend(['pitchfault_time2','pitchfault_blade2pos'])
×
NEW
785
        if 'pitchfault_time3' in dlc_options:
×
NEW
786
            group0.extend(['pitchfault_time3','pitchfault_blade3pos'])
×
NEW
787
        if 'yawfault_time' in dlc_options:
×
NEW
788
            group0.extend(['yawfault_time','yawfault_yawpos'])
×
NEW
789
        if 'genfault_time' in dlc_options:
×
NEW
790
            group0.extend(['genfault_time'])
×
791
        
792

NEW
793
        generic_case_inputs.append(group0)  # group 0, (usually constants) turbine variables, DT, aero_modeling
×
NEW
794
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
795
        generic_case_inputs.append(['azimuth_init']) # group 2
×
796

NEW
797
        self.generate_cases(generic_case_inputs,dlc_options)
×
798

799
    def generate_2p3(self, dlc_options):
1✔
800
        # Power production plus occurrence of fault
801
        # Normal control system fault
802

803
        # Get default options
NEW
804
        dlc_options.update(self.default_options)   
×
805
        
806
        # DLC Specific options:
NEW
807
        dlc_options['label'] = '2.3'
×
NEW
808
        dlc_options['sea_state'] = 'normal'
×
NEW
809
        dlc_options['IEC_WindType'] = 'EOG'
×
NEW
810
        dlc_options['PSF'] = 1.1  # For fault cases, psf depends on the mean-time between faults
×
811

812
        # azimuth starting positions
NEW
813
        dlc_options['azimuth_init'] = np.linspace(0.,120.,dlc_options['n_azimuth'],endpoint=False)
×
814

815
        # DLC-specific: define groups
816
        # These options should be the same length and we will generate a matrix of all cases
NEW
817
        generic_case_inputs = []
×
818
        
NEW
819
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model','genfault_time'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
×
NEW
820
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
821
        generic_case_inputs.append(['azimuth_init']) # group 2
×
822

NEW
823
        self.generate_cases(generic_case_inputs,dlc_options)
×
824

825
    def generate_3p1(self, dlc_options):
1✔
826
        # Start up - normal wind - fatigue
827
        # 
828
        
829
        # Get default options
NEW
830
        dlc_options.update(self.default_options)      
×
831
        
832
        # DLC Specific options:
NEW
833
        dlc_options['label'] = '3.1'
×
NEW
834
        dlc_options['sea_state'] = 'normal'
×
NEW
835
        dlc_options['IEC_WindType'] = 'NTM'
×
NEW
836
        dlc_options['pitch_initial'] = 90.
×
NEW
837
        dlc_options['turbine_status'] = 'parked-idling'     # initial turbine status is what matters here
×
838

839
        # Specify startup time for this case
NEW
840
        if dlc_options['startup_time'] > dlc_options['analysis_time']:
×
NEW
841
            raise Exception(f"DLC 3.1 was selected, but the startup_time ({dlc_options['startup_time']}) option is greater than the analysis_time ({dlc_options['analysis_time']})")
×
842
        else:
NEW
843
            dlc_options['startup_time'] = dlc_options['startup_time']
×
844

845
        # DLC-specific: define groups
846
        # These options should be the same length and we will generate a matrix of all cases
NEW
847
        generic_case_inputs = []
×
NEW
848
        generic_case_inputs.append(['total_time','transient_time','startup_time','wake_mod','wave_model','pitch_initial'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
×
NEW
849
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
850
        # generic_case_inputs.append(['azimuth_init']) # group 2
851
      
NEW
852
        self.generate_cases(generic_case_inputs,dlc_options)
×
853

854
    
855
    def generate_5p1(self, dlc_options):
1✔
856
        # Power production normal turbulence model - shutdown with varous azimuth initial conditions
857
        # 
858
        
859
        # Get default options
860
        dlc_options.update(self.default_options)      
1✔
861
        
862
        # DLC Specific options:
863
        dlc_options['label'] = '5.1'
1✔
864
        dlc_options['sea_state'] = 'normal'
1✔
865
        dlc_options['IEC_WindType'] = 'NTM'
1✔
866
        dlc_options['final_blade_pitch'] = 90.
1✔
867

868
        # Time options, set defaults if not provided
869
        if dlc_options['analysis_time'] == self.dlc_schema['analysis_time']['default']: 
1✔
NEW
870
            dlc_options['analysis_time'] = 600
×
871

872
        if dlc_options['shutdown_time'] == self.dlc_schema['shutdown_time']['default']:
1✔
NEW
873
            dlc_options['shutdown_time'] = 300
×
874

875
        
876
        # azimuth starting positions
877
        dlc_options['azimuth_init'] = np.linspace(0.,120.,dlc_options['n_azimuth'],endpoint=False)
1✔
878

879
        # Specify shutdown time for this case
880
        if dlc_options['shutdown_time'] > dlc_options['analysis_time']:
1✔
NEW
881
            raise Exception(f"DLC 5.1 was selected, but the shutdown_time ({dlc_options['shutdown_time']}) option is greater than the analysis_time ({dlc_options['analysis_time']})")
×
882
        else:
883
            dlc_options['shutdown_time'] = dlc_options['shutdown_time']
1✔
884

885
        # DLC-specific: define groups
886
        # These options should be the same length and we will generate a matrix of all cases
887
        generic_case_inputs = []
1✔
888
        generic_case_inputs.append(['total_time','transient_time','shutdown_time','wake_mod','wave_model','final_blade_pitch'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1✔
889
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
890
        generic_case_inputs.append(['azimuth_init']) # group 2
1✔
891
      
892
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
893

894

895
    def generate_6p1(self, dlc_options):
1✔
896
        # Parked (standing still or idling) - extreme wind model 50-year return period - ultimate loads
897
        # extra dlc_options: 
898
        # yaw_misalign: default = [-8,8]
899

900
        # Get default options
901
        dlc_options.update(self.default_options)
1✔
902

903
        # DLC Specific options:
904
        dlc_options['label'] = '6.1'
1✔
905
        dlc_options['sea_state'] = '50-year'
1✔
906
        dlc_options['IEC_WindType'] = self.wind_speed_class_num + 'EWM50'
1✔
907

908
        # yaw_misalign
909
        if 'yaw_misalign' not in dlc_options:
1✔
910
            dlc_options['yaw_misalign'] = [-8,8]
1✔
911

912
        if not dlc_options['wind_speed']:
1✔
913
            dlc_options['wind_speed'] = [self.V_e50]
1✔
914

915
        # parked options
916
        dlc_options['turbine_status'] = 'parked-idling'
1✔
917
        dlc_options['wake_mod'] = 0
1✔
918
        dlc_options['pitch_initial'] = 90.
1✔
919
        dlc_options['rot_speed_initial'] = 0.
1✔
920
        dlc_options['shutdown_time'] = 0.
1✔
921
        dlc_options['final_blade_pitch'] = 90.
1✔
922

923

924
        # DLC-specific: define groups
925
        # These options should be the same length and we will generate a matrix of all cases
926
        generic_case_inputs = []
1✔
927
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model','pitch_initial',
1✔
928
                                    'rot_speed_initial','shutdown_time','final_blade_pitch'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
929
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
930
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
931
      
932
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
933

934
    def generate_6p2(self, dlc_options):
1✔
935
        # Parked (standing still or idling) - extreme wind model 50-year return period - ultimate loads
936
        # This is the same as DLC 6.1 in the 61400-3-1 standards, except there's a loss of electrical network.
937
        # In DLC 6.1, the generator is disabled already, so the only difference in 6.2 may be that users may want to simulate larger yaw misalignments
938
        # extra dlc_options: 
939
        # yaw_misalign: default = [-180 to 180]
940

941
        # Get default options
NEW
942
        dlc_options.update(self.default_options)
×
943

944
        # DLC Specific options:
NEW
945
        dlc_options['label'] = '6.2'
×
NEW
946
        dlc_options['sea_state'] = '50-year'
×
NEW
947
        dlc_options['IEC_WindType'] = self.wind_speed_class_num + 'EWM50'
×
NEW
948
        dlc_options['PSF'] = 1.1
×
949

950
        # yaw_misalign
NEW
951
        if 'yaw_misalign' not in dlc_options:
×
NEW
952
            dlc_options['yaw_misalign'] = np.arange(-180+15,180+15,15).tolist()     # -180 is not valid in OF
×
953

NEW
954
        if not dlc_options['wind_speed']:
×
NEW
955
            dlc_options['wind_speed'] = [self.V_e50]
×
956

957
        # parked options
NEW
958
        dlc_options['turbine_status'] = 'parked-idling'
×
NEW
959
        dlc_options['wake_mod'] = 0
×
NEW
960
        dlc_options['pitch_initial'] = 90.
×
NEW
961
        dlc_options['rot_speed_initial'] = 0.
×
NEW
962
        dlc_options['shutdown_time'] = 0.
×
NEW
963
        dlc_options['final_blade_pitch'] = 90.
×
964

965

966
        # DLC-specific: define groups
967
        # These options should be the same length and we will generate a matrix of all cases
NEW
968
        generic_case_inputs = []
×
NEW
969
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model','pitch_initial',
×
970
                                    'rot_speed_initial','shutdown_time','final_blade_pitch'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
NEW
971
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
972
        generic_case_inputs.append(['yaw_misalign']) # group 2
×
973
      
NEW
974
        self.generate_cases(generic_case_inputs,dlc_options)
×
975

976

977
    def generate_6p3(self, dlc_options):
1✔
978
        # Parked (standing still or idling) - extreme wind model 1-year return period - ultimate loads, usually larger (20 deg) yaw offset
979

980
        # Get default options
981
        dlc_options.update(self.default_options)   
1✔
982
        
983
        # Set DLC Specific options:
984
        # These three are required
985
        dlc_options['label'] = '6.3'
1✔
986
        dlc_options['sea_state'] = '1-year'
1✔
987
        dlc_options['IEC_WindType'] = self.wind_speed_class_num + 'EWM1'
1✔
988

989
        # Set dlc-specific options, like yaw_misalign, initial azimuth
990
        if 'yaw_misalign' in dlc_options:
1✔
NEW
991
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
992
        else: # default
993
            dlc_options['yaw_misalign'] = [-20.,20.]
1✔
994

995
        if not dlc_options['wind_speed']:
1✔
996
            dlc_options['wind_speed'] = [self.V_e1]
1✔
997
            
998
        # parked options
999
        dlc_options['turbine_status'] = 'parked-idling'
1✔
1000
        dlc_options['wake_mod'] = 0
1✔
1001
        dlc_options['pitch_initial'] = 90.
1✔
1002
        dlc_options['rot_speed_initial'] = 0.
1✔
1003
        dlc_options['shutdown_time'] = 0.
1✔
1004
        dlc_options['final_blade_pitch'] = 90.
1✔
1005

1006
        # DLC-specific: define groups
1007
        # Groups are dependent variables, the cases are a cross product of the independent groups
1008
        # The options in each group should have the same length
1009
        generic_case_inputs = []
1✔
1010
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model','pitch_initial',
1✔
1011
                                    'rot_speed_initial','shutdown_time','final_blade_pitch'])  # group 0, (usually constants) turbine variables, DT, aero_modeling        
1012
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
1013
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
1014

1015
        # This function does the rest and generates the individual cases for each DLC
1016
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
1017

1018

1019
    def generate_6p4(self, dlc_options):
1✔
1020
        # Parked (standing still or idling) - normal turbulence model - fatigue loads
1021

1022
        # Get default options
1023
        dlc_options.update(self.default_options)   
1✔
1024
        
1025
        # Set DLC Specific options:
1026
        # These three are required
1027
        dlc_options['label'] = '6.4'
1✔
1028
        dlc_options['sea_state'] = 'normal'
1✔
1029
        dlc_options['IEC_WindType'] = 'NTM'
1✔
1030
        # Set wind speeds to DLC spec if not defined by the user
1031
        if len(dlc_options['wind_speed']) == 0:
1✔
1032
            dlc_options['wind_speed'] = np.arange(self.ws_cut_in, 0.7 * self.V_ref, dlc_options['ws_bin_size'])
1✔
1033
            # Include V_ref
1034
            if dlc_options['wind_speed'][-1] != self.V_ref:
1✔
1035
                dlc_options['wind_speed'] = np.append(dlc_options['wind_speed'], self.V_ref)
1✔
1036
            dlc_options['wind_speed'] = dlc_options['wind_speed'].tolist()
1✔
1037

1038
        # Set dlc-specific options, like yaw_misalign, initial azimuth
1039
        if 'yaw_misalign' in dlc_options:
1✔
NEW
1040
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
1041
        else: # default
1042
            dlc_options['yaw_misalign'] = [0.]
1✔
1043

1044
        # parked options
1045
        dlc_options['turbine_status'] = 'parked-idling'
1✔
1046
        dlc_options['wake_mod'] = 0
1✔
1047
        dlc_options['pitch_initial'] = 90.
1✔
1048
        dlc_options['rot_speed_initial'] = 0.
1✔
1049
        dlc_options['shutdown_time'] = 0.
1✔
1050
        dlc_options['final_blade_pitch'] = 90.
1✔
1051

1052
        # DLC-specific: define groups
1053
        # Groups are dependent variables, the cases are a cross product of the independent groups
1054
        # The options in each group should have the same length
1055
        generic_case_inputs = []
1✔
1056
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model','pitch_initial',
1✔
1057
                                    'rot_speed_initial','shutdown_time','final_blade_pitch'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
1058
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
1✔
1059
        generic_case_inputs.append(['yaw_misalign']) # group 2
1✔
1060

1061
        # This function does the rest and generates the individual cases for each DLC
1062
        self.generate_cases(generic_case_inputs,dlc_options)
1✔
1063

1064
    def generate_7p1(self, dlc_options):
1✔
1065
        # Parked (standing still or idling) - extreme wind model 1-year return period - ultimate loads, usually larger (20 deg) yaw offset
1066

1067
        # Get default options
NEW
1068
        dlc_options.update(self.default_options)   
×
1069
        
1070
        # Set DLC Specific options:
1071
        # These three are required
NEW
1072
        dlc_options['label'] = '7.1'
×
NEW
1073
        dlc_options['sea_state'] = '1-year'
×
NEW
1074
        dlc_options['IEC_WindType'] = self.wind_speed_class_num + 'EWM1'
×
NEW
1075
        dlc_options['PSF'] = 1.1
×
1076

1077
        # Set dlc-specific options, like yaw_misalign, initial azimuth
NEW
1078
        if 'yaw_misalign' in dlc_options:
×
NEW
1079
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
1080
        else: # default
NEW
1081
            dlc_options['yaw_misalign'] = [0.] # default
×
1082

NEW
1083
        if not dlc_options['wind_speed']:
×
NEW
1084
            dlc_options['wind_speed'] = [self.V_e1]
×
1085
            
1086
        # parked options
NEW
1087
        dlc_options['turbine_status'] = 'parked-idling'
×
NEW
1088
        dlc_options['wake_mod'] = 0
×
NEW
1089
        dlc_options['pitch_initial'] = 90.
×
NEW
1090
        dlc_options['rot_speed_initial'] = 0.
×
NEW
1091
        dlc_options['shutdown_time'] = 0.
×
NEW
1092
        dlc_options['final_blade_pitch'] = 90.
×
1093

1094
        # DLC-specific: define groups
1095
        # Groups are dependent variables, the cases are a cross product of the independent groups
1096
        # The options in each group should have the same length
NEW
1097
        generic_case_inputs = []
×
1098

NEW
1099
        group0 = ['total_time','transient_time','wake_mod','wave_model','pitch_initial',
×
1100
                'rot_speed_initial','shutdown_time','final_blade_pitch'] # group 0, (usually constants) turbine variables, DT, aero_modeling
1101
        
NEW
1102
        if 'pitchfault_time1' in dlc_options:
×
NEW
1103
            group0.extend(['pitchfault_time1','pitchfault_blade1pos'])
×
NEW
1104
        if 'pitchfault_time2' in dlc_options:
×
NEW
1105
            group0.extend(['pitchfault_time2','pitchfault_blade2pos'])
×
NEW
1106
        if 'pitchfault_time3' in dlc_options:
×
NEW
1107
            group0.extend(['pitchfault_time3','pitchfault_blade3pos'])
×
NEW
1108
        if 'yawfault_time' in dlc_options:
×
NEW
1109
            group0.extend(['yawfault_time','yawfault_yawpos'])
×
NEW
1110
        if 'genfault_time' in dlc_options:
×
NEW
1111
            group0.extend(['genfault_time'])
×
1112

NEW
1113
        generic_case_inputs.append(group0)  
×
NEW
1114
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
1115
        generic_case_inputs.append(['yaw_misalign']) # group 2
×
1116

1117
        # This function does the rest and generates the individual cases for each DLC
NEW
1118
        self.generate_cases(generic_case_inputs,dlc_options)
×
1119

1120
    def generate_7p2(self, dlc_options):
1✔
1121
        # Parked (standing still or idling) - normal turbulence model - fatigue loads
1122

1123
        # Get default options
NEW
1124
        dlc_options.update(self.default_options)   
×
1125
        
1126
        # Set DLC Specific options:
1127
        # These three are required
NEW
1128
        dlc_options['label'] = '7.2'
×
NEW
1129
        dlc_options['sea_state'] = 'normal'
×
NEW
1130
        dlc_options['IEC_WindType'] = 'NTM'
×
1131
        
1132
        # Set wind speeds to DLC spec if not defined by the user
NEW
1133
        if len(dlc_options['wind_speed']) == 0:
×
NEW
1134
            dlc_options['wind_speed'] = np.arange(0,self.ws_cut_out, dlc_options['ws_bin_size'])
×
NEW
1135
            dlc_options['wind_speed'] = dlc_options['wind_speed'].tolist()
×
1136

1137
        # Set dlc-specific options, like yaw_misalign, initial azimuth
NEW
1138
        if 'yaw_misalign' in dlc_options:
×
NEW
1139
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
1140
        else: # default
NEW
1141
            dlc_options['yaw_misalign'] = [0.]
×
1142

1143
        # parked options
NEW
1144
        dlc_options['turbine_status'] = 'parked-idling'
×
NEW
1145
        dlc_options['wake_mod'] = 0
×
NEW
1146
        dlc_options['pitch_initial'] = 90.
×
NEW
1147
        dlc_options['rot_speed_initial'] = 0.
×
NEW
1148
        dlc_options['shutdown_time'] = 0.
×
NEW
1149
        dlc_options['final_blade_pitch'] = 90.
×
1150

1151
        # DLC-specific: define groups
1152
        # Groups are dependent variables, the cases are a cross product of the independent groups
1153
        # The options in each group should have the same length
NEW
1154
        generic_case_inputs = []
×
NEW
1155
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model','pitch_initial',
×
1156
                                    'rot_speed_initial','shutdown_time','final_blade_pitch'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
NEW
1157
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
1158
        generic_case_inputs.append(['yaw_misalign']) # group 2
×
1159

1160
        # This function does the rest and generates the individual cases for each DLC
NEW
1161
        self.generate_cases(generic_case_inputs,dlc_options)
×
1162

1163
    def generate_new_dlc(self,dlc_options):
1✔
1164
        # Describe the new design load case
1165

1166
        # Get default options
NEW
1167
        dlc_options.update(self.default_options)   
×
1168
        
1169
        # Set DLC Specific options:
1170
        # These three are required
NEW
1171
        dlc_options['label'] = '1.6'
×
NEW
1172
        dlc_options['sea_state'] = 'severe'
×
NEW
1173
        dlc_options['IEC_WindType'] = 'NTM'
×
1174

1175
        # Set dlc-specific options, like yaw_misalign, initial azimuth
NEW
1176
        if 'yaw_misalign' in dlc_options:
×
NEW
1177
            dlc_options['yaw_misalign'] = dlc_options['yaw_misalign']
×
1178
        else: # default
NEW
1179
            dlc_options['yaw_misalign'] = [0]
×
1180

1181
        # DLC-specific: define groups
1182
        # Groups are dependent variables, the cases are a cross product of the independent groups
1183
        # The options in each group should have the same length
NEW
1184
        generic_case_inputs = []
×
NEW
1185
        generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model'])  # group 0, (usually constants) turbine variables, DT, aero_modeling
×
NEW
1186
        generic_case_inputs.append(['wind_speed','wave_height','wave_period', 'wind_seed', 'wave_seed']) # group 1, initial conditions will be added here, define some method that maps wind speed to ICs and add those variables to this group
×
NEW
1187
        generic_case_inputs.append(['yaw_misalign']) # group 2
×
1188

1189
        # This function does the rest and generates the individual cases for each DLC
NEW
1190
        self.generate_cases(generic_case_inputs,dlc_options)
×
1191

1192
def make_equal_length(option_dict,target_name):
1✔
1193
    '''
1194
    This function will set the length of all the option_dicts to that of option_dict[target_name] if it's a scalar
1195
    '''
1196
    target_len = len(option_dict[target_name])
1✔
1197
    for key in option_dict:
1✔
1198
        if len(option_dict[key]) == 1:
1✔
1199
            if isinstance(option_dict[key], np.ndarray):
1✔
1200
                option_dict[key] = np.tile(option_dict[key],target_len)
1✔
NEW
1201
            elif isinstance(option_dict[key], list):
×
NEW
1202
                option_dict[key] = option_dict[key] * target_len
×
1203
            else:
NEW
1204
                raise Exception(f'Cannot coerce {key} into an array with same length as wind_speed')
×
1205
            
1206
            # re-normalize probabilities
1207
            if key == 'probabilities':
1✔
1208
                option_dict['probabilities'] /= target_len
1✔
1209
            
1210

1211
def combine_options(*dicts):
1✔
1212
    """
1213
    Combine option dictionarys and do standard processing, 
1214
    like removing numpy and turning everything into lists for case_inputs
1215
    
1216
    Args:
1217
        *dicts: Variable number of dictionaries.
1218
        
1219
    Returns:
1220
        dict: Combined dictionary.
1221
    """
1222
    comb_options = {}
1✔
1223
    for d in dicts:
1✔
1224
        comb_options.update(d)
1✔
1225

1226
    comb_options = remove_numpy(comb_options)
1✔
1227
    
1228
    # Make all options a list
1229
    for opt in comb_options:
1✔
1230
        if not isinstance(comb_options[opt], list):  # if not a list
1✔
1231
            comb_options[opt] = [comb_options[opt]]
1✔
1232

1233
    return comb_options
1✔
1234

1235

1236

1237
if __name__ == "__main__":
1✔
1238

1239
    # Wind turbine inputs that will eventually come in from somewhere
1240
    ws_cut_in = 4.
×
1241
    ws_cut_out = 25.
×
1242
    ws_rated = 10.
×
1243
    wind_speed_class = 'I'
×
1244
    wind_turbulence_class = 'B'
×
1245

1246
    # Load modeling options file
1247
    weis_dir                = os.path.dirname( os.path.dirname( os.path.dirname( os.path.realpath(__file__) ) ) ) + os.sep
×
1248
    fname_modeling_options = os.path.join(weis_dir , "examples", "05_IEA-3.4-130-RWT", "modeling_options.yaml")
×
1249
    modeling_options = sch.load_modeling_yaml(fname_modeling_options)
×
1250

1251
    # Extract user defined list of cases
1252
    DLCs = modeling_options['DLC_driver']['DLCs']
×
1253

1254
    # Initialize the generator
1255
    fix_wind_seeds = modeling_options['DLC_driver']['fix_wind_seeds']
×
1256
    fix_wave_seeds = modeling_options['DLC_driver']['fix_wave_seeds']
×
1257
    metocean = modeling_options['DLC_driver']['metocean_conditions']
×
1258
    dlc_generator = DLCGenerator(ws_cut_in, ws_cut_out, ws_rated, wind_speed_class, wind_turbulence_class, fix_wind_seeds, fix_wave_seeds, metocean)
×
1259

1260
    # Generate cases from user inputs
1261
    for i_DLC in range(len(DLCs)):
×
1262
        DLCopt = DLCs[i_DLC]
×
1263
        dlc_generator.generate(DLCopt['DLC'], DLCopt)
×
1264

1265
    # print(dlc_generator.cases[1].URef)
1266
    # print(dlc_generator.n_cases)
1267

NEW
1268
    FAST_runDirectory = '/Users/dzalkind/Tools/WEIS-DLC/examples/05_IEA-3.4-130-RWT/outputs/05_DLC15_new_setup/openfast_runs'
×
NEW
1269
    FAST_InputFile = 'weis_job'
×
1270

NEW
1271
    case_list_all = []
×
NEW
1272
    for case_inputs in dlc_generator.openfast_case_inputs:
×
NEW
1273
        case_list, case_name = CaseGen_General(case_inputs, FAST_runDirectory, FAST_InputFile)
×
NEW
1274
        print('here')
×
1275

1276

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc