From 5e4ed8e32ee8c2301ea8b376b3cb40d97cc72b71 Mon Sep 17 00:00:00 2001 From: dzalkind Date: Thu, 16 Jan 2025 14:44:26 -0700 Subject: [PATCH 1/2] Add characteristic load processing (family methods) --- weis/aeroelasticse/openmdao_openfast.py | 62 +++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/weis/aeroelasticse/openmdao_openfast.py b/weis/aeroelasticse/openmdao_openfast.py index c1ed81c62..912d5f2a5 100644 --- a/weis/aeroelasticse/openmdao_openfast.py +++ b/weis/aeroelasticse/openmdao_openfast.py @@ -1996,6 +1996,9 @@ def setup_cases(self,modeling_options,inputs,discrete_inputs,fst_vt): case_df.insert(0,'case_name',case_name) text_table = case_df.to_string(index=False) + # Save case_df + self.case_df = case_df + # Write the text table to a yaml, text file write_yaml(case_df.to_dict(),os.path.join(self.FAST_runDirectory,'case_matrix_combined.yaml')) with open(os.path.join(self.FAST_runDirectory,'case_matrix_combined.txt'), 'w') as file: @@ -2219,6 +2222,8 @@ def post_process(self, summary_stats, extreme_table, DELs, damage, case_list, ca outputs, discrete_outputs = self.get_control_measures(summary_stats, chan_time, inputs, discrete_inputs, outputs, discrete_outputs) + self.get_charateristic_loads(summary_stats,inputs,outputs) + if modopt['flags']['floating'] or (modopt['Level3']['from_openfast'] and self.fst_vt['Fst']['CompMooring']>0): self.get_floating_measures(summary_stats, chan_time, inputs, discrete_inputs,outputs, discrete_outputs) @@ -2707,6 +2712,63 @@ def get_floating_measures(self,sum_stats, chan_time, inputs, discrete_inputs, ou # Max platform offset outputs['Max_Offset'] = sum_stats['PtfmOffset']['max'].max() + def get_charateristic_loads(self,sum_stats,inputs,outputs): + # Characteristic loads are described in IEC 61400-1:2019, Section 7.6.2.2 + + cm = self.case_df + + # Get all unique channel names + channel_names = sum_stats.columns.get_level_values(0).unique().to_list() + channel_names = [str(cn) for cn in channel_names] # Convert to non-numpy strings + + # Get all DLCs + dlcs = cm['DLC'].unique() + + char_loads = {} + + for dlc in dlcs: + + # Filter for only stats of this dlc + dlc_ind = cm['DLC'] == dlc + ss_dlc = sum_stats[dlc_ind] + cm_dlc = cm[dlc_ind] + + # Init this dlc char_load dict + char_loads[dlc] = {} + + mean_wind_speed = cm_dlc[('InflowWind', 'HWindSpeed')] + unique_mws = mean_wind_speed.unique() + + # For each channel in sum_stats + for chan in channel_names: + char_loads[dlc][chan] = {} + char_loads[dlc][chan]['wind_speed'] = unique_mws + char_loads[dlc][chan]['load_values'] = np.zeros_like(unique_mws) + + # Over each wind speed simulated + for i, mws in enumerate(unique_mws): + mws_ind = cm_dlc[('InflowWind', 'HWindSpeed')] == mws + ss_mws = ss_dlc[mws_ind] + + # average maximums (absolute value), most cases + char_loads[dlc][chan]['load_values'][i] = ss_mws[chan]['abs'].mean() + + # average top-half (DLC 5.1, 2.1, 2.2) + if dlc in ['2.1','2.2','5.1']: + n_cases = len(ss_mws[chan]['abs']) + n_top_half = int(np.ceil(n_cases/2)) + ss_top_half = ss_mws[chan].sort_values('abs',ascending=False)[:n_top_half] + char_loads[dlc][chan]['load_values'][i] = ss_top_half['abs'].mean() + + + + # The characteristic load is the max of all the means + char_loads[dlc][chan]['characteristic_load'] = np.max(char_loads[dlc][chan]['load_values']) + + save_dir = os.path.join(self.FAST_runDirectory,'iteration_'+str(self.of_inumber)) + write_yaml(char_loads,os.path.join(save_dir,'charateristic_loads.yaml')) + + def get_OL2CL_error(self,chan_time,outputs): ol_case_names = [os.path.join( weis_dir, From fc035328de0190f78b9ab6bea1fcd2fbb827df04 Mon Sep 17 00:00:00 2001 From: AbhineetGupta Date: Thu, 16 Jan 2025 16:26:01 -0700 Subject: [PATCH 2/2] Add initial condition option for ramp dlc and add some ramp dlcs in example 19 --- .../testbench_options.yaml | 59 +++++++++++++------ weis/dlc_driver/dlc_generator.py | 13 +++- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/examples/19_controller_test_bench/testbench_options.yaml b/examples/19_controller_test_bench/testbench_options.yaml index dc4b29248..e2eea042f 100644 --- a/examples/19_controller_test_bench/testbench_options.yaml +++ b/examples/19_controller_test_bench/testbench_options.yaml @@ -1,3 +1,8 @@ +General: + openfast_configuration: + save_iterations: True + save_timeseries: True + Testbench_Options: output_directory: outputs/9_test_ramp # relative to this file output_filebase: testbench @@ -56,22 +61,40 @@ DLC_driver: - DLC: "AEP" TI_factor: 0.5 wind_speed: [12] - - DLC: "Ramp" # Up + - DLC: "Ramp" # 0 to cutin+1 + turbine_status: parked-still + wind_speed: [0] + transient_time: 20 + analysis_time: 200. + ramp_speeddelta: 6 + ramp_duration: 200. + rot_speed_initial: 0 + pitch_initial: 90 + - DLC: "Ramp" # Cutin-1 to cutin+1 + turbine_status: parked-still + wind_speed: [4] + transient_time: 20 + analysis_time: 200. + ramp_speeddelta: 2 + ramp_duration: 200. + rot_speed_initial: 0 + pitch_initial: 90 + - DLC: "Ramp" # Cutin to rated wind_speed: [5] - analysis_time: 1000. - ramp_speeddelta: 20 - ramp_duration: 1000. - - DLC: "Ramp" # Down - wind_speed: [25] - analysis_time: 1000. - ramp_speeddelta: -20 - ramp_duration: 1000. - - DLC: "Step" # Down - wind_speed: [18] - analysis_time: 100. - step_speeddelta: 2.0 - step_time: 50.0 - # - DLC: "Steady" - # wind_speed: [8] - # analysis_time: 10. - # transient_time: 5.0 + transient_time: 20 + analysis_time: 200. + ramp_speeddelta: 6.4 + ramp_duration: 200. + - DLC: "Ramp" # Rated-2 to rated+2 + wind_speed: [9.4] + transient_time: 20 + analysis_time: 400. + ramp_speeddelta: 13.4 + ramp_duration: 400. + - DLC: "Ramp" # Rated to Cutout + 2 + wind_speed: [11.4] + transient_time: 20 + analysis_time: 400. + ramp_speeddelta: 27 + ramp_duration: 400. + diff --git a/weis/dlc_driver/dlc_generator.py b/weis/dlc_driver/dlc_generator.py index b046f5df5..96b76fcb1 100644 --- a/weis/dlc_driver/dlc_generator.py +++ b/weis/dlc_driver/dlc_generator.py @@ -870,10 +870,21 @@ def generate_Ramp(self, dlc_options): else: dlc_options['gust_wait_time'] = 0 + generic_case_inputs = [] + + # Add initial conditions + # Note that for initial conditions to not be overwritten, turbine_status should be parked (still or idling) + group0 = ['total_time','transient_time','wake_mod','wave_model'] + if 'pitch_initial' in dlc_options: + group0.extend(['pitch_initial']) + if 'rot_speed_initial' in dlc_options: + group0.extend(['rot_speed_initial']) + + # DLC-specific: define groups # These options should be the same length and we will generate a matrix of all cases generic_case_inputs = [] - generic_case_inputs.append(['total_time','transient_time','wake_mod','wave_model']) # group 0, (usually constants) turbine variables, DT, aero_modeling + generic_case_inputs.append(group0) # group 0, (usually constants) turbine variables, DT, aero_modeling 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 generic_case_inputs.append(['yaw_misalign']) # group 2