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

WISDEM / WEIS / 7278006245

20 Dec 2023 04:15PM UTC coverage: 84.634%. First build
7278006245

Pull #251

github

web-flow
Merge 900a88d82 into 36b276939
Pull Request #251: Phase 2 Starting Point

122 of 158 new or added lines in 10 files covered. (77.22%)

21228 of 25082 relevant lines covered (84.63%)

0.85 hits per line

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

76.51
/weis/glue_code/gc_LoadInputs.py
1
import os
1✔
2
import os.path as osp
1✔
3
import platform
1✔
4
import multiprocessing as mp
1✔
5
import weis.inputs as sch
1✔
6
from weis.aeroelasticse.FAST_reader import InputReader_OpenFAST
1✔
7
from wisdem.glue_code.gc_LoadInputs import WindTurbineOntologyPython
1✔
8
from weis.dlc_driver.dlc_generator    import DLCGenerator
1✔
9
from wisdem.commonse.mpi_tools              import MPI
1✔
10

11
class WindTurbineOntologyPythonWEIS(WindTurbineOntologyPython):
1✔
12
    # Pure python class inheriting the class WindTurbineOntologyPython from WISDEM
13
    # and adding the WEIS options, namely the paths to the WEIS submodules
14
    # (OpenFAST, ROSCO, TurbSim, XFoil) and initializing the control parameters.
15

16
    def __init__(self, fname_input_wt, fname_input_modeling, fname_input_analysis):
1✔
17

18
        self.modeling_options = sch.load_modeling_yaml(fname_input_modeling)
1✔
19
        self.modeling_options['fname_input_modeling'] = fname_input_modeling
1✔
20
        self.wt_init          = sch.load_geometry_yaml(fname_input_wt)
1✔
21
        self.analysis_options = sch.load_analysis_yaml(fname_input_analysis)
1✔
22

23
        self.set_run_flags()
1✔
24
        self.set_openmdao_vectors()
1✔
25
        self.set_openmdao_vectors_control()
1✔
26
        self.set_weis_data()
1✔
27
        self.set_opt_flags()
1✔
28

29
    def set_weis_data(self):
1✔
30

31
        # BEM dir, all levels
32
        base_run_dir = self.modeling_options['General']['openfast_configuration']['OF_run_dir']
1✔
33
        if MPI:
1✔
34
            rank    = MPI.COMM_WORLD.Get_rank()
×
35
            bemDir = os.path.join(base_run_dir,'rank_%000d'%int(rank),'BEM')
×
36
        else:
37
            bemDir = os.path.join(base_run_dir,'BEM')
1✔
38

39
        self.modeling_options["Level1"]['BEM_dir'] = bemDir
1✔
40
        if MPI:
1✔
41
            # If running MPI, RAFT won't be able to save designs in parallel
42
            self.modeling_options["Level1"]['save_designs'] = False
×
43

44
        # Directory of modeling option input, if we want to use it for relative paths
45
        mod_opt_dir = os.path.split(self.modeling_options['fname_input_modeling'])[0]
1✔
46

47
        # Openfast
48
        if self.modeling_options['Level2']['flag'] or self.modeling_options['Level3']['flag']:
1✔
49
            fast = InputReader_OpenFAST()
1✔
50
            self.modeling_options['General']['openfast_configuration']['fst_vt'] = {}
1✔
51
            self.modeling_options['General']['openfast_configuration']['fst_vt']['outlist'] = fast.fst_vt['outlist']
1✔
52

53
            # OpenFAST prefixes
54
            if self.modeling_options['General']['openfast_configuration']['OF_run_fst'] in ['','None','NONE','none']:
1✔
55
                self.modeling_options['General']['openfast_configuration']['OF_run_fst'] = 'weis_job'
×
56
                
57
            if self.modeling_options['General']['openfast_configuration']['OF_run_dir'] in ['','None','NONE','none']:
1✔
NEW
58
                self.modeling_options['General']['openfast_configuration']['OF_run_dir'] = osp.join(
×
59
                    self.analysis_options['general']['folder_output'], 
60
                    'openfast_runs'
61
                    )
62
                
63
            # Find the path to the WEIS controller
64
            weis_dir = osp.dirname( osp.dirname( osp.dirname( osp.realpath(__file__) ) ) )
1✔
65
            if platform.system() == 'Windows':
1✔
66
                path2dll = osp.join(weis_dir, 'local','lib','libdiscon.dll')
×
67
            elif platform.system() == 'Darwin':
1✔
68
                path2dll = osp.join(weis_dir, 'local','lib','libdiscon.dylib')
×
69
            else:
70
                path2dll = osp.join(weis_dir, 'local','lib','libdiscon.so')
1✔
71

72
            # User-defined control dylib (path2dll)
73
            if self.modeling_options['General']['openfast_configuration']['path2dll'] == 'none':   #Default option, use above
1✔
74
                self.modeling_options['General']['openfast_configuration']['path2dll'] = path2dll
1✔
75
            else:
76
                if not os.path.isabs(self.modeling_options['General']['openfast_configuration']['path2dll']):  # make relative path absolute
×
77
                    self.modeling_options['General']['openfast_configuration']['path2dll'] = \
×
78
                        os.path.join(os.path.dirname(self.options['modeling_options']['fname_input_modeling']), FASTpref['file_management']['FAST_lib'])
79

80
            # Activate HAMS in Level1 if requested for Level 2 or 3
81
            if self.modeling_options["flags"]["offshore"] or self.modeling_options["Level3"]["from_openfast"]:
1✔
82
                if self.modeling_options["Level1"]["potential_model_override"] == 2:
1✔
83
                    self.modeling_options["Level3"]["HydroDyn"]["PotMod"] = 1
1✔
84
                elif ( (self.modeling_options["Level1"]["potential_model_override"] == 0) and
1✔
85
                       (len(self.modeling_options["Level1"]["potential_bem_members"]) > 0) ):
86
                    self.modeling_options["Level3"]["HydroDyn"]["PotMod"] = 1
1✔
87
                elif self.modeling_options["Level1"]["potential_model_override"] == 1:
1✔
88
                    self.modeling_options["Level3"]["HydroDyn"]["PotMod"] = 0
×
89
                else:
90
                    # Keep user defined value of PotMod
91
                    pass
1✔
92

93
                if self.modeling_options["Level3"]["HydroDyn"]["PotMod"] == 1:
1✔
94

95
                    # If user requested PotMod but didn't specify any override or members, just run everything
96
                    if ( (self.modeling_options["Level1"]["potential_model_override"] == 0) and
1✔
97
                       (len(self.modeling_options["Level1"]["potential_bem_members"]) == 0) ):
98
                        self.modeling_options["Level1"]["potential_model_override"] == 2
×
99
                        
100
                    cwd = os.getcwd()
1✔
101
                    weis_dir = osp.dirname(osp.dirname(osp.dirname(osp.abspath(__file__))))
1✔
102
                    potpath = self.modeling_options["Level3"]["HydroDyn"]["PotFile"].replace('.hst','').replace('.12','').replace('.3','').replace('.1','')
1✔
103
                    if ( (len(potpath) == 0) or (potpath.lower() in ['unused','default','none']) ):
1✔
104
                        
105
                        self.modeling_options['Level1']['flag'] = True
×
106
                        self.modeling_options["Level3"]["HydroDyn"]["PotFile"] = osp.join(cwd, bemDir,'Output','Wamit_format','Buoy')
×
107
                        
108

109
                    else:
110
                        if self.modeling_options['Level1']['runPyHAMS']:
1✔
111
                            print('Found existing potential model: {}\n    - Trying to use this instead of running PyHAMS.'.format(potpath))
1✔
112
                            self.modeling_options['Level1']['runPyHAMS'] = False
1✔
113
                        if osp.exists( potpath+'.1' ):
1✔
114
                            self.modeling_options["Level3"]["HydroDyn"]["PotFile"] = osp.realpath(potpath)
1✔
115
                        elif osp.exists( osp.join(cwd, potpath+'.1') ):
1✔
116
                            self.modeling_options["Level3"]["HydroDyn"]["PotFile"] = osp.realpath( osp.join(cwd, potpath) )
×
117
                        elif osp.exists( osp.join(weis_dir, potpath+'.1') ):
1✔
118
                            self.modeling_options["Level3"]["HydroDyn"]["PotFile"] = osp.realpath( osp.join(weis_dir, potpath) )
1✔
NEW
119
                        elif osp.exists( osp.join(mod_opt_dir, potpath+'.1') ):
×
NEW
120
                            self.modeling_options["Level3"]["HydroDyn"]["PotFile"] = osp.realpath( osp.join(mod_opt_dir, potpath) )
×
121
                        else:
122
                            raise Exception(f'No valid Wamit-style output found for specified PotFile option, {potpath}.1')
×
123

124
        # OpenFAST dir
125
        if self.modeling_options["Level3"]["from_openfast"]:
1✔
126
            if not os.path.isabs(self.modeling_options['Level3']['openfast_dir']):
1✔
127
                # Make relative to modeling options input
128
                self.modeling_options['Level3']['openfast_dir'] = os.path.realpath(os.path.join(
1✔
129
                    mod_opt_dir,
130
                    self.modeling_options['Level3']['openfast_dir']
131
                    ))
132
        
133
        # RAFT
134
        if self.modeling_options["flags"]["floating"]:
1✔
135
            bool_init = True if self.modeling_options["Level1"]["potential_model_override"]==2 else False
1✔
136
            self.modeling_options["Level1"]["model_potential"] = [bool_init] * self.modeling_options["floating"]["members"]["n_members"]
1✔
137

138
            if self.modeling_options["Level1"]["potential_model_override"] == 0:
1✔
139
                for k in self.modeling_options["Level1"]["potential_bem_members"]:
1✔
140
                    idx = self.modeling_options["floating"]["members"]["name"].index(k)
1✔
141
                    self.modeling_options["Level1"]["model_potential"][idx] = True
1✔
142
        elif self.modeling_options["flags"]["offshore"]:
1✔
143
            self.modeling_options["Level1"]["model_potential"] = [False]*1000
1✔
144
            
145
        # ROSCO
146
        self.modeling_options['ROSCO']['flag'] = (self.modeling_options['Level1']['flag'] or
1✔
147
                                                  self.modeling_options['Level2']['flag'] or
148
                                                  self.modeling_options['Level3']['flag'])
149
        
150
        if self.modeling_options['ROSCO']['tuning_yaml'] != 'none':  # default is empty
1✔
151
            # Make path absolute if not, relative to modeling options input
152
            if not os.path.isabs(self.modeling_options['ROSCO']['tuning_yaml']):
1✔
153
                self.modeling_options['ROSCO']['tuning_yaml'] = os.path.realpath(os.path.join(
1✔
154
                    mod_opt_dir,
155
                    self.modeling_options['ROSCO']['tuning_yaml']
156
                    ))
157
        
158
        # XFoil
159
        if not osp.isfile(self.modeling_options['Level3']["xfoil"]["path"]) and self.modeling_options['ROSCO']['Flp_Mode']:
1✔
160
            raise Exception("A distributed aerodynamic control device is defined in the geometry yaml, but the path to XFoil in the modeling options is not defined correctly")
×
161

162
        # Compute the number of DLCs that will be run
163
        DLCs = self.modeling_options['DLC_driver']['DLCs']
1✔
164
        # Initialize the DLC generator
165
        cut_in = self.wt_init['control']['supervisory']['Vin']
1✔
166
        cut_out = self.wt_init['control']['supervisory']['Vout']
1✔
167
        metocean = self.modeling_options['DLC_driver']['metocean_conditions']
1✔
168
        dlc_generator = DLCGenerator(cut_in, cut_out, metocean=metocean)
1✔
169
        # Generate cases from user inputs
170
        for i_DLC in range(len(DLCs)):
1✔
171
            DLCopt = DLCs[i_DLC]
1✔
172
            dlc_generator.generate(DLCopt['DLC'], DLCopt)
1✔
173
        self.modeling_options['DLC_driver']['n_cases'] = dlc_generator.n_cases
1✔
174
        if hasattr(dlc_generator,'n_ws_dlc11'):
1✔
175
            self.modeling_options['DLC_driver']['n_ws_dlc11'] = dlc_generator.n_ws_dlc11
1✔
176
        else:
177
            self.modeling_options['DLC_driver']['n_ws_dlc11'] = 0
×
178

179
        self.modeling_options['flags']['TMDs'] = False
1✔
180
        if 'TMDs' in self.wt_init:
1✔
181
            if self.modeling_options['Level3']['flag']:
1✔
182
                self.modeling_options['flags']['TMDs'] = True
1✔
183
            else:
184
                raise Exception("TMDs in Levels 1 and 2 are not supported yet")
×
185

186

187
    def set_openmdao_vectors_control(self):
1✔
188
        # Distributed aerodynamic control devices along blade
189
        self.modeling_options['WISDEM']['RotorSE']['n_te_flaps']      = 0
1✔
190
        if 'aerodynamic_control' in self.wt_init['components']['blade']:
1✔
191
            if 'te_flaps' in self.wt_init['components']['blade']['aerodynamic_control']:
×
192
                self.modeling_options['WISDEM']['RotorSE']['n_te_flaps'] = len(self.wt_init['components']['blade']['aerodynamic_control']['te_flaps'])
×
193
                self.modeling_options['WISDEM']['RotorSE']['n_tab']   = 3
×
194
            else:
195
                raise Exception('A distributed aerodynamic control device is provided in the yaml input file, but not supported by wisdem.')
×
196

197
        if 'TMDs' in self.wt_init:
1✔
198
            n_TMDs = len(self.wt_init['TMDs'])
1✔
199
            self.modeling_options['TMDs'] = {}
1✔
200
            self.modeling_options['TMDs']['n_TMDs']                 = n_TMDs
1✔
201
            # TODO: come back and check how many of these need to be modeling options
202
            self.modeling_options['TMDs']['name']                   = [tmd['name'] for  tmd in self.wt_init['TMDs']]
1✔
203
            self.modeling_options['TMDs']['component']              = [tmd['component'] for  tmd in self.wt_init['TMDs']]
1✔
204
            self.modeling_options['TMDs']['location']               = [tmd['location'] for  tmd in self.wt_init['TMDs']]
1✔
205
            self.modeling_options['TMDs']['mass']                   = [tmd['mass'] for  tmd in self.wt_init['TMDs']]
1✔
206
            self.modeling_options['TMDs']['stiffness']              = [tmd['stiffness'] for  tmd in self.wt_init['TMDs']]
1✔
207
            self.modeling_options['TMDs']['damping']                = [tmd['damping'] for  tmd in self.wt_init['TMDs']]
1✔
208
            self.modeling_options['TMDs']['natural_frequency']      = [tmd['natural_frequency'] for  tmd in self.wt_init['TMDs']]
1✔
209
            self.modeling_options['TMDs']['damping_ratio']          = [tmd['damping_ratio'] for  tmd in self.wt_init['TMDs']]
1✔
210
            self.modeling_options['TMDs']['X_DOF']                  = [tmd['X_DOF'] for  tmd in self.wt_init['TMDs']]
1✔
211
            self.modeling_options['TMDs']['Y_DOF']                  = [tmd['Y_DOF'] for  tmd in self.wt_init['TMDs']]
1✔
212
            self.modeling_options['TMDs']['Z_DOF']                  = [tmd['Z_DOF'] for  tmd in self.wt_init['TMDs']]
1✔
213
            self.modeling_options['TMDs']['preload_spring']         = [tmd['preload_spring'] for  tmd in self.wt_init['TMDs']]
1✔
214

215
            # Check that TMD locations map to somewhere valid (tower or platform member)
216
            self.modeling_options['TMDs']['num_tower_TMDs'] = 0
1✔
217
            self.modeling_options['TMDs']['num_ptfm_TMDs']  = 0
1✔
218
            
219
            for i_TMD, component in enumerate(self.modeling_options['TMDs']['component']):
1✔
220
                if self.modeling_options['flags']['floating'] and component in self.modeling_options['floating']['members']['name']:
1✔
221
                    self.modeling_options['TMDs']['num_ptfm_TMDs'] += 1
1✔
222
                elif component == 'tower':
×
223
                    self.modeling_options['TMDs']['num_tower_TMDs'] += 1
×
224
                else:
225
                    raise Exception('Invalid TMD component mapping for {} on {}'.format(
×
226
                        self.modeling_options['TMDs']['name'][i_TMD],component))      
227

228
            # Set TMD group  mapping: list of length n_groups, with i_TMDs in each group
229
            # Loop through TMD names, assign to own group if not in an analysis group
230
            if 'TMDs' in self.analysis_options['design_variables']:
1✔
231
                tmd_group_map = []
1✔
232
                tmd_names = self.modeling_options['TMDs']['name']
1✔
233
                
234
                for i_group, tmd_group in enumerate(self.analysis_options['design_variables']['TMDs']['groups']):
1✔
235
                    tmds_in_group_i = [tmd_names.index(tmd_name) for tmd_name in tmd_group['names']]
1✔
236

237
                    tmd_group_map.append(tmds_in_group_i)
1✔
238
                
239
                self.modeling_options['TMDs']['group_mapping'] = tmd_group_map
1✔
240

241
    def update_ontology_control(self, wt_opt):
1✔
242
        # Update controller
243
        if self.modeling_options['flags']['control']:
×
244
            self.wt_init['control']['pitch']['omega_pc'] = wt_opt['tune_rosco_ivc.omega_pc']
×
245
            self.wt_init['control']['pitch']['zeta_pc']  = wt_opt['tune_rosco_ivc.zeta_pc']
×
246
            self.wt_init['control']['torque']['omega_vs'] = float(wt_opt['tune_rosco_ivc.omega_vs'])
×
247
            self.wt_init['control']['torque']['zeta_vs']  = float(wt_opt['tune_rosco_ivc.zeta_vs'])
×
248
            self.wt_init['control']['pitch']['Kp_float']  = float(wt_opt['tune_rosco_ivc.Kp_float'])
×
249
            self.wt_init['control']['pitch']['ptfm_freq']  = float(wt_opt['tune_rosco_ivc.ptfm_freq'])
×
250
            self.wt_init['control']['IPC']['IPC_Ki_1P'] = float(wt_opt['tune_rosco_ivc.IPC_Kp1p'])
×
251
            self.wt_init['control']['IPC']['IPC_Kp_1P'] = float(wt_opt['tune_rosco_ivc.IPC_Ki1p'])
×
252
            if self.modeling_options['ROSCO']['Flp_Mode'] > 0:
×
253
                self.wt_init['control']['dac']['flp_kp_norm']= float(wt_opt['tune_rosco_ivc.flp_kp_norm'])
×
254
                self.wt_init['control']['dac']['flp_tau'] = float(wt_opt['tune_rosco_ivc.flp_tau'])
×
255

256

257
    def write_options(self, fname_output):
1✔
258
        # Override the WISDEM version to ensure that the WEIS options files are written instead
259
        sch.write_modeling_yaml(self.modeling_options, fname_output)
1✔
260
        sch.write_analysis_yaml(self.analysis_options, fname_output)
1✔
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

© 2026 Coveralls, Inc