sensotwin.stress_selection.ui
1import ipywidgets as widgets 2from . import configuration 3from . import style 4from .. import global_style 5from .. import store_connection 6import matplotlib.pyplot as plt 7import matplotlib.transforms as trans 8import matplotlib.patches as patches 9import math 10from traitlets.utils.bunch import Bunch 11 12 13class StressSelectionUIElements: 14 """Contains all UI functionality of operational parameter configuration UI (Step 3). 15 16 In order to use, show the top level widget 'self.dashboard' in one cell of a notebook 17 and the apply css styling calling 'self.apply_styling()' an another cell 18 """ 19 WIND_PROFILE_FILE_PATH = "Inputs/wind_data.vtt" 20 def __init__(self): 21 """Initialize UI objects and associated UI functionality.""" 22 self.dashboard = widgets.Tab().add_class("global_tab_container") 23 self.dashboard.children = [ 24 self.init_data_source_box(), 25 self.init_graph_input_box() 26 ] 27 tab_titles = ['Input', 'Simulation'] 28 for i in range(len(tab_titles)): 29 self.dashboard.set_title(i, tab_titles[i]) 30 31 def init_data_source_box(self) -> widgets.Box: 32 """Initialize first tab of dashboard, choosing data source.""" 33 local_database_label = widgets.Label(value="Use local owlready2 database:").add_class("global_headline") 34 use_local_data_button = widgets.Button( 35 description='Use local database', 36 disabled=False, 37 tooltip='Use local database', 38 icon='play', 39 ).add_class("global_load_data_button").add_class("global_basic_button") 40 use_local_data_button.on_click(self.load_local_data) 41 42 remote_database_label = widgets.Label(value="Use remote Apache Jena Fuseki database:").add_class("global_headline") 43 self.remote_database_input = widgets.Text( 44 value=None, 45 placeholder="insert SPARQL url here", 46 description="SPARQL Endpoint:", 47 disabled=False 48 ).add_class("global_url_input").add_class("global_basic_input") 49 use_remote_data_button = widgets.Button( 50 description='Use remote database', 51 disabled=False, 52 tooltip='Use remote database', 53 icon='play', 54 ).add_class("global_load_data_button").add_class("global_basic_button") 55 use_remote_data_button.on_click(self.load_remote_data) 56 57 local_data_box = widgets.VBox([ 58 local_database_label, 59 use_local_data_button 60 ]) 61 remote_data_box = widgets.VBox([ 62 remote_database_label, 63 self.remote_database_input, 64 use_remote_data_button 65 ]) 66 data_source_box = widgets.VBox([ 67 local_data_box, 68 remote_data_box 69 ]).add_class("global_data_tab_container") 70 71 return data_source_box 72 73 def init_graph_input_box(self) -> widgets.Box: 74 """Initialize second tab of dashboard, configuring operational parameters.""" 75 with plt.ioff(): 76 fig, (self.ax, self.pitch_ax) = plt.subplots(1, 2, width_ratios=[3, 1], figsize=(11.0, 6)) 77 fig.canvas.toolbar_visible = False 78 fig.canvas.header_visible = False 79 fig.canvas.footer_visible = False 80 fig.canvas.resizable = False 81 fig.set_facecolor((0.0, 0.0, 0.0, 0.0)) 82 self.ax.set_facecolor((0.0, 0.0, 0.0, 0.0)) 83 self.pitch_ax.set_facecolor((0.0, 0.0, 0.0, 0.0)) 84 85 manual_parameters_label = widgets.Label(value="Manual parameters:").add_class("global_headline") 86 self.windspeed_slider = widgets.FloatSlider( 87 description='Windspeed:', 88 disabled=False, 89 continuous_update=True, 90 orientation='horizontal', 91 readout=True, 92 readout_format='.2f', 93 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 94 self.windspeed_input = widgets.FloatText( 95 description='Windspeed (m/s):', 96 disabled=False, 97 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 98 self.duration_input = widgets.FloatText( 99 description='Duration (y):', 100 disabled=False, 101 value=10, 102 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 103 self.windspeed_slider.observe(self.on_windspeed_slider_change, names="value") 104 self.windspeed_input.observe(self.on_windspeed_input_change, names="value") 105 self.duration_input.observe(self.on_duration_input_change, names="value") 106 107 derived_parameters_label = widgets.Label(value="Derived parameters:").add_class("global_headline") 108 self.pitch_angle_display = widgets.FloatText( 109 value=None, 110 description='Pitch angle (°):', 111 disabled=True, 112 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 113 self.rotation_speed_display = widgets.FloatText( 114 value=None, 115 description='Rotation speed (rpm):', 116 disabled=True, 117 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 118 self.rotation_total_display = widgets.IntText( 119 value=None, 120 description='Total rotations:', 121 disabled=True, 122 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 123 124 input_set_selection_label = widgets.Label(value="Select Input Set:").add_class("global_headline") 125 self.input_set_selection = widgets.Select( 126 options=['No data loaded'], 127 value='No data loaded', 128 rows=10, 129 description='Input sets:', 130 disabled=False 131 ).add_class("global_input_set_selection").add_class("global_basic_input") 132 display_input_set_button = widgets.Button( 133 description='Load Input Set', 134 disabled=False, 135 tooltip='Load Input Set', 136 icon='play', 137 ).add_class("global_basic_button") 138 display_input_set_button.on_click(self.display_input_set) 139 140 selection_box = widgets.VBox([ 141 manual_parameters_label, 142 self.windspeed_slider, 143 self.windspeed_input, 144 self.duration_input, 145 derived_parameters_label, 146 self.pitch_angle_display, 147 self.rotation_speed_display, 148 self.rotation_total_display 149 ]).add_class("wind_sel_selection_box") 150 self.input_set_box = widgets.VBox([ 151 input_set_selection_label, 152 self.input_set_selection, 153 display_input_set_button, 154 selection_box 155 ]) 156 157 self.save_input_set_button = widgets.Button( 158 description='Save selected wind speed and duration as new input set', 159 disabled=False, 160 tooltip='Save selected wind speed and duration as new input set', 161 icon='save', 162 ).add_class("global_save_input_set_button").add_class("global_basic_button") 163 self.save_input_set_button.on_click(self.save_new_input_set) 164 graph_input_box = widgets.VBox([ 165 widgets.HBox([ 166 self.input_set_box, 167 fig.canvas 168 ]).add_class("wind_sel_graph_tab_container"), 169 self.save_input_set_button 170 ]) 171 172 return graph_input_box 173 174 def update_plot(self, x_value: float, y_value: float): 175 """Update crosshair on 2D plot and rotate wind turbine display. 176 177 Args: 178 x_value: X value of crosshair, wind speed in m/s 179 y_value: Y value of crosshair, pitch angle in ° 180 """ 181 if self.x_line: 182 self.x_line.remove() 183 self.y_line.remove() 184 # set new placement lines on graph 185 self.y_line = self.ax.axhline(y=y_value, color='r', linestyle='dotted') 186 self.x_line = self.ax.axvline(x=x_value, color='r', linestyle='dotted') 187 # rotate pitch display 188 base = plt.gca().transData 189 rot = trans.Affine2D().rotate_deg(-y_value) 190 self.pitch_bar[0].set_transform(rot + base) 191 192 def init_wind_display(self, ui_element=None): 193 """Initialize 2D wind speed plot and plot for displaying pitch angle. 194 195 Args: 196 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 197 """ 198 self.wind_data = configuration.WindSpeedDataSet(self.WIND_PROFILE_FILE_PATH) 199 x_data = [] 200 y_data = [] 201 for _, data in self.wind_data.get_data().iterrows(): 202 x_data.append(data["windspeed_mpers"]) 203 y_data.append(data["pitch_angle_deg"]) 204 # create windspeed to pitch diagram 205 self.ax.plot(x_data, y_data) 206 self.ax.set_xlabel('Wind speed (m/s)') 207 self.ax.set_ylabel('Pitch (°)') 208 # create pitch angle display diagram 209 self.y_line = None 210 self.x_line = None 211 self.pitch_bar = None 212 pitch_x, pitch_y = self.get_pitch_angle_graph_definition() 213 self.pitch_bar = self.pitch_ax.plot(pitch_x, pitch_y, color='tab:blue') 214 circle = patches.Circle((0,0), radius=1, facecolor=(0, 0, 0, 0), edgecolor="black", linestyle="solid",linewidth=2) 215 self.pitch_ax.add_patch(circle) 216 self.pitch_ax.set_aspect(1, adjustable='box') 217 self.pitch_ax.axis('off') 218 # adjust values last, it triggers change events and thus re-renders of the graph 219 self.windspeed_slider.min = x_data[0] 220 self.windspeed_slider.max = x_data[-1] 221 self.windspeed_slider.step = round(x_data[1]-x_data[0], 5) 222 initial_speed_value = x_data[math.floor(len(x_data) / 2)] 223 self.windspeed_slider.value = initial_speed_value 224 225 def on_windspeed_slider_change(self, ui_element=None): 226 """Update UI after windspeed slider has changed value. 227 228 Args: 229 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 230 """ 231 self.windspeed_changed(self.windspeed_slider.value) 232 233 def on_windspeed_input_change(self, ui_element=None): 234 """Update UI after windspeed number input has changed value. 235 236 Args: 237 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 238 """ 239 self.windspeed_changed(self.windspeed_input.value) 240 241 def on_duration_input_change(self, change: Bunch): 242 """Update UI after duration input has changed value.""" 243 self.update_total_rotations(change["new"], self.rotation_speed_display.value) 244 245 def windspeed_changed(self, speed: float): 246 """Update UI with data for new displayed wind speed.""" 247 rounded_speed = round(speed,5) 248 self.windspeed_slider.value = rounded_speed 249 self.windspeed_input.value = rounded_speed 250 data = self.wind_data.get_single_entry(rounded_speed) 251 self.rotation_speed_display.value = data["rpm"] 252 self.pitch_angle_display.value = data["pitch_angle_deg"] 253 self.update_total_rotations(self.duration_input.value, data["rpm"]) 254 self.update_plot(speed, data["pitch_angle_deg"]) 255 256 def update_total_rotations(self, duration: float, rpm: float): 257 """Recalculate total rotations and update corresponding widget. 258 259 Args: 260 duration: Duration in years 261 rpm: Rotations per minute 262 """ 263 self.rotation_total_display.value = math.floor(duration * rpm * 60 * 24 * 365) 264 265 def get_pitch_angle_graph_definition(self)-> tuple: 266 """Return static point display used to draw wind turbine blade in matplot. 267 268 Returns: 269 list of floats as X data, list of floats a Y data 270 """ 271 x_data = [0.98, 0.92, 0.86, 0.8, 0.65, 0.5, 0.35, 0.2, 0.05, -0.1, -0.16, -0.22, -0.28, -0.34, 272 -0.4, -0.46, -0.52, -0.58, -0.64, -0.7, -0.76, -0.81, -0.84, -0.87, -0.9, -0.93, -0.96, 273 -0.975, -0.984, -0.99, -0.996, -0.9975, -0.999, -0.9996, -0.9998, -0.9992, -0.998, 274 -0.9965, -0.992, -0.986, -0.98, -0.965, -0.94, -0.91, -0.88, -0.85, -0.82, -0.78, -0.72, 275 -0.66, -0.6, -0.54, -0.48, -0.42, -0.36, -0.3, -0.24, -0.18, -0.12, 0, 0.15, 0.3, 0.45, 276 0.6, 0.75, 0.84, 0.9, 0.96] 277 y_data = [0.00676, 0.02272, 0.03808, 0.05346, 0.092, 0.12932, 0.16354, 0.1932, 0.2166, 0.23198, 278 0.23534, 0.23652, 0.23522, 0.23188, 0.22672, 0.21972, 0.2109, 0.20016, 0.18736, 0.17222, 279 0.15438, 0.13698, 0.12516, 0.11206, 0.0973, 0.0802, 0.05938, 0.0464, 0.0369, 0.02914, 280 0.01846, 0.01462, 0.00934, 0.00594, -0.00418, -0.00834, -0.01314, -0.0173, -0.02546, 281 -0.03234, -0.0375, -0.04704, -0.0582, -0.06812, -0.07608, -0.08276, -0.08854, -0.09516, 282 -0.10338, -0.10998, -0.11524, -0.11932, -0.12232, -0.12428, -0.12516, -0.12492, -0.12344, 283 -0.12028, -0.11524, -0.1017, -0.08064, -0.05716, -0.0336, -0.01224, 0.00372, 0.00878, 284 0.00922, 0.00608] 285 return x_data, y_data 286 287 def load_local_data(self, ui_element=None): 288 """Use locally stored data for UI. 289 290 Args: 291 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 292 """ 293 self.conn = store_connection.LocalStoreConnection("sensotwin_world") 294 self.load_input_set_data() 295 self.init_wind_display() 296 297 def load_remote_data(self, ui_element=None): 298 """Use remotely stored data for UI. 299 300 Args: 301 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 302 """ 303 self.conn = store_connection.FusekiConnection(self.remote_database_input.value) 304 self.load_input_set_data() 305 self.init_wind_display() 306 307 def save_new_input_set(self, ui_element=None): 308 """Save currently set data for wind profile and duration of simulation. 309 310 Args: 311 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 312 """ 313 new_id = max(self.input_set_data.keys()) + 1 if self.input_set_data else 1 314 new_set = configuration.StressStrainInputDataSet( 315 uniq_id = new_id, 316 wind_speed = self.windspeed_input.value, 317 rotation_speed = self.rotation_speed_display.value, 318 pitch_angle = self.pitch_angle_display.value, 319 total_rotations = self.rotation_total_display.value, 320 duration = self.duration_input.value, 321 conn = self.conn) 322 new_set.save_entry_to_store() 323 self.load_input_set_data() 324 325 def load_input_set_data(self): 326 """Load data from previously set input source and populate UI.""" 327 self.input_set_data = configuration.StressStrainInputDataSet.get_all_entries_from_store(self.conn) 328 if self.input_set_data: 329 options = [] 330 for key, input_set in self.input_set_data.items(): 331 label = input_set.generate_input_set_display_label() 332 options.append((label, key)) 333 self.input_set_selection.options = options 334 self.input_set_selection.value = list(self.input_set_data.keys())[0] 335 336 def display_input_set(self, ui_element=None): 337 """Display data for currently selected input set in UI. 338 339 Args: 340 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 341 """ 342 displayed_set = self.input_set_data[self.input_set_selection.value] 343 344 self.windspeed_input.value = displayed_set.get_wind_speed() 345 self.rotation_speed_display.value = displayed_set.get_rotation_speed() 346 self.pitch_angle_display.value = displayed_set.get_pitch_angle() 347 self.rotation_total_display.value = displayed_set.get_total_rotations() 348 self.duration_input.value = displayed_set.get_duration() 349 350 self.windspeed_changed(displayed_set.get_wind_speed()) 351 352 def apply_styling(self): 353 css = """ 354 <style> 355 {} 356 {} 357 </style> 358 """.format(global_style.global_css, style.local_css) 359 return widgets.HTML(css)
14class StressSelectionUIElements: 15 """Contains all UI functionality of operational parameter configuration UI (Step 3). 16 17 In order to use, show the top level widget 'self.dashboard' in one cell of a notebook 18 and the apply css styling calling 'self.apply_styling()' an another cell 19 """ 20 WIND_PROFILE_FILE_PATH = "Inputs/wind_data.vtt" 21 def __init__(self): 22 """Initialize UI objects and associated UI functionality.""" 23 self.dashboard = widgets.Tab().add_class("global_tab_container") 24 self.dashboard.children = [ 25 self.init_data_source_box(), 26 self.init_graph_input_box() 27 ] 28 tab_titles = ['Input', 'Simulation'] 29 for i in range(len(tab_titles)): 30 self.dashboard.set_title(i, tab_titles[i]) 31 32 def init_data_source_box(self) -> widgets.Box: 33 """Initialize first tab of dashboard, choosing data source.""" 34 local_database_label = widgets.Label(value="Use local owlready2 database:").add_class("global_headline") 35 use_local_data_button = widgets.Button( 36 description='Use local database', 37 disabled=False, 38 tooltip='Use local database', 39 icon='play', 40 ).add_class("global_load_data_button").add_class("global_basic_button") 41 use_local_data_button.on_click(self.load_local_data) 42 43 remote_database_label = widgets.Label(value="Use remote Apache Jena Fuseki database:").add_class("global_headline") 44 self.remote_database_input = widgets.Text( 45 value=None, 46 placeholder="insert SPARQL url here", 47 description="SPARQL Endpoint:", 48 disabled=False 49 ).add_class("global_url_input").add_class("global_basic_input") 50 use_remote_data_button = widgets.Button( 51 description='Use remote database', 52 disabled=False, 53 tooltip='Use remote database', 54 icon='play', 55 ).add_class("global_load_data_button").add_class("global_basic_button") 56 use_remote_data_button.on_click(self.load_remote_data) 57 58 local_data_box = widgets.VBox([ 59 local_database_label, 60 use_local_data_button 61 ]) 62 remote_data_box = widgets.VBox([ 63 remote_database_label, 64 self.remote_database_input, 65 use_remote_data_button 66 ]) 67 data_source_box = widgets.VBox([ 68 local_data_box, 69 remote_data_box 70 ]).add_class("global_data_tab_container") 71 72 return data_source_box 73 74 def init_graph_input_box(self) -> widgets.Box: 75 """Initialize second tab of dashboard, configuring operational parameters.""" 76 with plt.ioff(): 77 fig, (self.ax, self.pitch_ax) = plt.subplots(1, 2, width_ratios=[3, 1], figsize=(11.0, 6)) 78 fig.canvas.toolbar_visible = False 79 fig.canvas.header_visible = False 80 fig.canvas.footer_visible = False 81 fig.canvas.resizable = False 82 fig.set_facecolor((0.0, 0.0, 0.0, 0.0)) 83 self.ax.set_facecolor((0.0, 0.0, 0.0, 0.0)) 84 self.pitch_ax.set_facecolor((0.0, 0.0, 0.0, 0.0)) 85 86 manual_parameters_label = widgets.Label(value="Manual parameters:").add_class("global_headline") 87 self.windspeed_slider = widgets.FloatSlider( 88 description='Windspeed:', 89 disabled=False, 90 continuous_update=True, 91 orientation='horizontal', 92 readout=True, 93 readout_format='.2f', 94 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 95 self.windspeed_input = widgets.FloatText( 96 description='Windspeed (m/s):', 97 disabled=False, 98 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 99 self.duration_input = widgets.FloatText( 100 description='Duration (y):', 101 disabled=False, 102 value=10, 103 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 104 self.windspeed_slider.observe(self.on_windspeed_slider_change, names="value") 105 self.windspeed_input.observe(self.on_windspeed_input_change, names="value") 106 self.duration_input.observe(self.on_duration_input_change, names="value") 107 108 derived_parameters_label = widgets.Label(value="Derived parameters:").add_class("global_headline") 109 self.pitch_angle_display = widgets.FloatText( 110 value=None, 111 description='Pitch angle (°):', 112 disabled=True, 113 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 114 self.rotation_speed_display = widgets.FloatText( 115 value=None, 116 description='Rotation speed (rpm):', 117 disabled=True, 118 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 119 self.rotation_total_display = widgets.IntText( 120 value=None, 121 description='Total rotations:', 122 disabled=True, 123 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 124 125 input_set_selection_label = widgets.Label(value="Select Input Set:").add_class("global_headline") 126 self.input_set_selection = widgets.Select( 127 options=['No data loaded'], 128 value='No data loaded', 129 rows=10, 130 description='Input sets:', 131 disabled=False 132 ).add_class("global_input_set_selection").add_class("global_basic_input") 133 display_input_set_button = widgets.Button( 134 description='Load Input Set', 135 disabled=False, 136 tooltip='Load Input Set', 137 icon='play', 138 ).add_class("global_basic_button") 139 display_input_set_button.on_click(self.display_input_set) 140 141 selection_box = widgets.VBox([ 142 manual_parameters_label, 143 self.windspeed_slider, 144 self.windspeed_input, 145 self.duration_input, 146 derived_parameters_label, 147 self.pitch_angle_display, 148 self.rotation_speed_display, 149 self.rotation_total_display 150 ]).add_class("wind_sel_selection_box") 151 self.input_set_box = widgets.VBox([ 152 input_set_selection_label, 153 self.input_set_selection, 154 display_input_set_button, 155 selection_box 156 ]) 157 158 self.save_input_set_button = widgets.Button( 159 description='Save selected wind speed and duration as new input set', 160 disabled=False, 161 tooltip='Save selected wind speed and duration as new input set', 162 icon='save', 163 ).add_class("global_save_input_set_button").add_class("global_basic_button") 164 self.save_input_set_button.on_click(self.save_new_input_set) 165 graph_input_box = widgets.VBox([ 166 widgets.HBox([ 167 self.input_set_box, 168 fig.canvas 169 ]).add_class("wind_sel_graph_tab_container"), 170 self.save_input_set_button 171 ]) 172 173 return graph_input_box 174 175 def update_plot(self, x_value: float, y_value: float): 176 """Update crosshair on 2D plot and rotate wind turbine display. 177 178 Args: 179 x_value: X value of crosshair, wind speed in m/s 180 y_value: Y value of crosshair, pitch angle in ° 181 """ 182 if self.x_line: 183 self.x_line.remove() 184 self.y_line.remove() 185 # set new placement lines on graph 186 self.y_line = self.ax.axhline(y=y_value, color='r', linestyle='dotted') 187 self.x_line = self.ax.axvline(x=x_value, color='r', linestyle='dotted') 188 # rotate pitch display 189 base = plt.gca().transData 190 rot = trans.Affine2D().rotate_deg(-y_value) 191 self.pitch_bar[0].set_transform(rot + base) 192 193 def init_wind_display(self, ui_element=None): 194 """Initialize 2D wind speed plot and plot for displaying pitch angle. 195 196 Args: 197 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 198 """ 199 self.wind_data = configuration.WindSpeedDataSet(self.WIND_PROFILE_FILE_PATH) 200 x_data = [] 201 y_data = [] 202 for _, data in self.wind_data.get_data().iterrows(): 203 x_data.append(data["windspeed_mpers"]) 204 y_data.append(data["pitch_angle_deg"]) 205 # create windspeed to pitch diagram 206 self.ax.plot(x_data, y_data) 207 self.ax.set_xlabel('Wind speed (m/s)') 208 self.ax.set_ylabel('Pitch (°)') 209 # create pitch angle display diagram 210 self.y_line = None 211 self.x_line = None 212 self.pitch_bar = None 213 pitch_x, pitch_y = self.get_pitch_angle_graph_definition() 214 self.pitch_bar = self.pitch_ax.plot(pitch_x, pitch_y, color='tab:blue') 215 circle = patches.Circle((0,0), radius=1, facecolor=(0, 0, 0, 0), edgecolor="black", linestyle="solid",linewidth=2) 216 self.pitch_ax.add_patch(circle) 217 self.pitch_ax.set_aspect(1, adjustable='box') 218 self.pitch_ax.axis('off') 219 # adjust values last, it triggers change events and thus re-renders of the graph 220 self.windspeed_slider.min = x_data[0] 221 self.windspeed_slider.max = x_data[-1] 222 self.windspeed_slider.step = round(x_data[1]-x_data[0], 5) 223 initial_speed_value = x_data[math.floor(len(x_data) / 2)] 224 self.windspeed_slider.value = initial_speed_value 225 226 def on_windspeed_slider_change(self, ui_element=None): 227 """Update UI after windspeed slider has changed value. 228 229 Args: 230 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 231 """ 232 self.windspeed_changed(self.windspeed_slider.value) 233 234 def on_windspeed_input_change(self, ui_element=None): 235 """Update UI after windspeed number input has changed value. 236 237 Args: 238 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 239 """ 240 self.windspeed_changed(self.windspeed_input.value) 241 242 def on_duration_input_change(self, change: Bunch): 243 """Update UI after duration input has changed value.""" 244 self.update_total_rotations(change["new"], self.rotation_speed_display.value) 245 246 def windspeed_changed(self, speed: float): 247 """Update UI with data for new displayed wind speed.""" 248 rounded_speed = round(speed,5) 249 self.windspeed_slider.value = rounded_speed 250 self.windspeed_input.value = rounded_speed 251 data = self.wind_data.get_single_entry(rounded_speed) 252 self.rotation_speed_display.value = data["rpm"] 253 self.pitch_angle_display.value = data["pitch_angle_deg"] 254 self.update_total_rotations(self.duration_input.value, data["rpm"]) 255 self.update_plot(speed, data["pitch_angle_deg"]) 256 257 def update_total_rotations(self, duration: float, rpm: float): 258 """Recalculate total rotations and update corresponding widget. 259 260 Args: 261 duration: Duration in years 262 rpm: Rotations per minute 263 """ 264 self.rotation_total_display.value = math.floor(duration * rpm * 60 * 24 * 365) 265 266 def get_pitch_angle_graph_definition(self)-> tuple: 267 """Return static point display used to draw wind turbine blade in matplot. 268 269 Returns: 270 list of floats as X data, list of floats a Y data 271 """ 272 x_data = [0.98, 0.92, 0.86, 0.8, 0.65, 0.5, 0.35, 0.2, 0.05, -0.1, -0.16, -0.22, -0.28, -0.34, 273 -0.4, -0.46, -0.52, -0.58, -0.64, -0.7, -0.76, -0.81, -0.84, -0.87, -0.9, -0.93, -0.96, 274 -0.975, -0.984, -0.99, -0.996, -0.9975, -0.999, -0.9996, -0.9998, -0.9992, -0.998, 275 -0.9965, -0.992, -0.986, -0.98, -0.965, -0.94, -0.91, -0.88, -0.85, -0.82, -0.78, -0.72, 276 -0.66, -0.6, -0.54, -0.48, -0.42, -0.36, -0.3, -0.24, -0.18, -0.12, 0, 0.15, 0.3, 0.45, 277 0.6, 0.75, 0.84, 0.9, 0.96] 278 y_data = [0.00676, 0.02272, 0.03808, 0.05346, 0.092, 0.12932, 0.16354, 0.1932, 0.2166, 0.23198, 279 0.23534, 0.23652, 0.23522, 0.23188, 0.22672, 0.21972, 0.2109, 0.20016, 0.18736, 0.17222, 280 0.15438, 0.13698, 0.12516, 0.11206, 0.0973, 0.0802, 0.05938, 0.0464, 0.0369, 0.02914, 281 0.01846, 0.01462, 0.00934, 0.00594, -0.00418, -0.00834, -0.01314, -0.0173, -0.02546, 282 -0.03234, -0.0375, -0.04704, -0.0582, -0.06812, -0.07608, -0.08276, -0.08854, -0.09516, 283 -0.10338, -0.10998, -0.11524, -0.11932, -0.12232, -0.12428, -0.12516, -0.12492, -0.12344, 284 -0.12028, -0.11524, -0.1017, -0.08064, -0.05716, -0.0336, -0.01224, 0.00372, 0.00878, 285 0.00922, 0.00608] 286 return x_data, y_data 287 288 def load_local_data(self, ui_element=None): 289 """Use locally stored data for UI. 290 291 Args: 292 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 293 """ 294 self.conn = store_connection.LocalStoreConnection("sensotwin_world") 295 self.load_input_set_data() 296 self.init_wind_display() 297 298 def load_remote_data(self, ui_element=None): 299 """Use remotely stored data for UI. 300 301 Args: 302 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 303 """ 304 self.conn = store_connection.FusekiConnection(self.remote_database_input.value) 305 self.load_input_set_data() 306 self.init_wind_display() 307 308 def save_new_input_set(self, ui_element=None): 309 """Save currently set data for wind profile and duration of simulation. 310 311 Args: 312 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 313 """ 314 new_id = max(self.input_set_data.keys()) + 1 if self.input_set_data else 1 315 new_set = configuration.StressStrainInputDataSet( 316 uniq_id = new_id, 317 wind_speed = self.windspeed_input.value, 318 rotation_speed = self.rotation_speed_display.value, 319 pitch_angle = self.pitch_angle_display.value, 320 total_rotations = self.rotation_total_display.value, 321 duration = self.duration_input.value, 322 conn = self.conn) 323 new_set.save_entry_to_store() 324 self.load_input_set_data() 325 326 def load_input_set_data(self): 327 """Load data from previously set input source and populate UI.""" 328 self.input_set_data = configuration.StressStrainInputDataSet.get_all_entries_from_store(self.conn) 329 if self.input_set_data: 330 options = [] 331 for key, input_set in self.input_set_data.items(): 332 label = input_set.generate_input_set_display_label() 333 options.append((label, key)) 334 self.input_set_selection.options = options 335 self.input_set_selection.value = list(self.input_set_data.keys())[0] 336 337 def display_input_set(self, ui_element=None): 338 """Display data for currently selected input set in UI. 339 340 Args: 341 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 342 """ 343 displayed_set = self.input_set_data[self.input_set_selection.value] 344 345 self.windspeed_input.value = displayed_set.get_wind_speed() 346 self.rotation_speed_display.value = displayed_set.get_rotation_speed() 347 self.pitch_angle_display.value = displayed_set.get_pitch_angle() 348 self.rotation_total_display.value = displayed_set.get_total_rotations() 349 self.duration_input.value = displayed_set.get_duration() 350 351 self.windspeed_changed(displayed_set.get_wind_speed()) 352 353 def apply_styling(self): 354 css = """ 355 <style> 356 {} 357 {} 358 </style> 359 """.format(global_style.global_css, style.local_css) 360 return widgets.HTML(css)
Contains all UI functionality of operational parameter configuration UI (Step 3).
In order to use, show the top level widget 'self.dashboard' in one cell of a notebook and the apply css styling calling 'self.apply_styling()' an another cell
21 def __init__(self): 22 """Initialize UI objects and associated UI functionality.""" 23 self.dashboard = widgets.Tab().add_class("global_tab_container") 24 self.dashboard.children = [ 25 self.init_data_source_box(), 26 self.init_graph_input_box() 27 ] 28 tab_titles = ['Input', 'Simulation'] 29 for i in range(len(tab_titles)): 30 self.dashboard.set_title(i, tab_titles[i])
Initialize UI objects and associated UI functionality.
32 def init_data_source_box(self) -> widgets.Box: 33 """Initialize first tab of dashboard, choosing data source.""" 34 local_database_label = widgets.Label(value="Use local owlready2 database:").add_class("global_headline") 35 use_local_data_button = widgets.Button( 36 description='Use local database', 37 disabled=False, 38 tooltip='Use local database', 39 icon='play', 40 ).add_class("global_load_data_button").add_class("global_basic_button") 41 use_local_data_button.on_click(self.load_local_data) 42 43 remote_database_label = widgets.Label(value="Use remote Apache Jena Fuseki database:").add_class("global_headline") 44 self.remote_database_input = widgets.Text( 45 value=None, 46 placeholder="insert SPARQL url here", 47 description="SPARQL Endpoint:", 48 disabled=False 49 ).add_class("global_url_input").add_class("global_basic_input") 50 use_remote_data_button = widgets.Button( 51 description='Use remote database', 52 disabled=False, 53 tooltip='Use remote database', 54 icon='play', 55 ).add_class("global_load_data_button").add_class("global_basic_button") 56 use_remote_data_button.on_click(self.load_remote_data) 57 58 local_data_box = widgets.VBox([ 59 local_database_label, 60 use_local_data_button 61 ]) 62 remote_data_box = widgets.VBox([ 63 remote_database_label, 64 self.remote_database_input, 65 use_remote_data_button 66 ]) 67 data_source_box = widgets.VBox([ 68 local_data_box, 69 remote_data_box 70 ]).add_class("global_data_tab_container") 71 72 return data_source_box
Initialize first tab of dashboard, choosing data source.
74 def init_graph_input_box(self) -> widgets.Box: 75 """Initialize second tab of dashboard, configuring operational parameters.""" 76 with plt.ioff(): 77 fig, (self.ax, self.pitch_ax) = plt.subplots(1, 2, width_ratios=[3, 1], figsize=(11.0, 6)) 78 fig.canvas.toolbar_visible = False 79 fig.canvas.header_visible = False 80 fig.canvas.footer_visible = False 81 fig.canvas.resizable = False 82 fig.set_facecolor((0.0, 0.0, 0.0, 0.0)) 83 self.ax.set_facecolor((0.0, 0.0, 0.0, 0.0)) 84 self.pitch_ax.set_facecolor((0.0, 0.0, 0.0, 0.0)) 85 86 manual_parameters_label = widgets.Label(value="Manual parameters:").add_class("global_headline") 87 self.windspeed_slider = widgets.FloatSlider( 88 description='Windspeed:', 89 disabled=False, 90 continuous_update=True, 91 orientation='horizontal', 92 readout=True, 93 readout_format='.2f', 94 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 95 self.windspeed_input = widgets.FloatText( 96 description='Windspeed (m/s):', 97 disabled=False, 98 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 99 self.duration_input = widgets.FloatText( 100 description='Duration (y):', 101 disabled=False, 102 value=10, 103 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 104 self.windspeed_slider.observe(self.on_windspeed_slider_change, names="value") 105 self.windspeed_input.observe(self.on_windspeed_input_change, names="value") 106 self.duration_input.observe(self.on_duration_input_change, names="value") 107 108 derived_parameters_label = widgets.Label(value="Derived parameters:").add_class("global_headline") 109 self.pitch_angle_display = widgets.FloatText( 110 value=None, 111 description='Pitch angle (°):', 112 disabled=True, 113 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 114 self.rotation_speed_display = widgets.FloatText( 115 value=None, 116 description='Rotation speed (rpm):', 117 disabled=True, 118 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 119 self.rotation_total_display = widgets.IntText( 120 value=None, 121 description='Total rotations:', 122 disabled=True, 123 ).add_class("global_basic_input").add_class("wind_sel_basic_input") 124 125 input_set_selection_label = widgets.Label(value="Select Input Set:").add_class("global_headline") 126 self.input_set_selection = widgets.Select( 127 options=['No data loaded'], 128 value='No data loaded', 129 rows=10, 130 description='Input sets:', 131 disabled=False 132 ).add_class("global_input_set_selection").add_class("global_basic_input") 133 display_input_set_button = widgets.Button( 134 description='Load Input Set', 135 disabled=False, 136 tooltip='Load Input Set', 137 icon='play', 138 ).add_class("global_basic_button") 139 display_input_set_button.on_click(self.display_input_set) 140 141 selection_box = widgets.VBox([ 142 manual_parameters_label, 143 self.windspeed_slider, 144 self.windspeed_input, 145 self.duration_input, 146 derived_parameters_label, 147 self.pitch_angle_display, 148 self.rotation_speed_display, 149 self.rotation_total_display 150 ]).add_class("wind_sel_selection_box") 151 self.input_set_box = widgets.VBox([ 152 input_set_selection_label, 153 self.input_set_selection, 154 display_input_set_button, 155 selection_box 156 ]) 157 158 self.save_input_set_button = widgets.Button( 159 description='Save selected wind speed and duration as new input set', 160 disabled=False, 161 tooltip='Save selected wind speed and duration as new input set', 162 icon='save', 163 ).add_class("global_save_input_set_button").add_class("global_basic_button") 164 self.save_input_set_button.on_click(self.save_new_input_set) 165 graph_input_box = widgets.VBox([ 166 widgets.HBox([ 167 self.input_set_box, 168 fig.canvas 169 ]).add_class("wind_sel_graph_tab_container"), 170 self.save_input_set_button 171 ]) 172 173 return graph_input_box
Initialize second tab of dashboard, configuring operational parameters.
175 def update_plot(self, x_value: float, y_value: float): 176 """Update crosshair on 2D plot and rotate wind turbine display. 177 178 Args: 179 x_value: X value of crosshair, wind speed in m/s 180 y_value: Y value of crosshair, pitch angle in ° 181 """ 182 if self.x_line: 183 self.x_line.remove() 184 self.y_line.remove() 185 # set new placement lines on graph 186 self.y_line = self.ax.axhline(y=y_value, color='r', linestyle='dotted') 187 self.x_line = self.ax.axvline(x=x_value, color='r', linestyle='dotted') 188 # rotate pitch display 189 base = plt.gca().transData 190 rot = trans.Affine2D().rotate_deg(-y_value) 191 self.pitch_bar[0].set_transform(rot + base)
Update crosshair on 2D plot and rotate wind turbine display.
Args: x_value: X value of crosshair, wind speed in m/s y_value: Y value of crosshair, pitch angle in °
193 def init_wind_display(self, ui_element=None): 194 """Initialize 2D wind speed plot and plot for displaying pitch angle. 195 196 Args: 197 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 198 """ 199 self.wind_data = configuration.WindSpeedDataSet(self.WIND_PROFILE_FILE_PATH) 200 x_data = [] 201 y_data = [] 202 for _, data in self.wind_data.get_data().iterrows(): 203 x_data.append(data["windspeed_mpers"]) 204 y_data.append(data["pitch_angle_deg"]) 205 # create windspeed to pitch diagram 206 self.ax.plot(x_data, y_data) 207 self.ax.set_xlabel('Wind speed (m/s)') 208 self.ax.set_ylabel('Pitch (°)') 209 # create pitch angle display diagram 210 self.y_line = None 211 self.x_line = None 212 self.pitch_bar = None 213 pitch_x, pitch_y = self.get_pitch_angle_graph_definition() 214 self.pitch_bar = self.pitch_ax.plot(pitch_x, pitch_y, color='tab:blue') 215 circle = patches.Circle((0,0), radius=1, facecolor=(0, 0, 0, 0), edgecolor="black", linestyle="solid",linewidth=2) 216 self.pitch_ax.add_patch(circle) 217 self.pitch_ax.set_aspect(1, adjustable='box') 218 self.pitch_ax.axis('off') 219 # adjust values last, it triggers change events and thus re-renders of the graph 220 self.windspeed_slider.min = x_data[0] 221 self.windspeed_slider.max = x_data[-1] 222 self.windspeed_slider.step = round(x_data[1]-x_data[0], 5) 223 initial_speed_value = x_data[math.floor(len(x_data) / 2)] 224 self.windspeed_slider.value = initial_speed_value
Initialize 2D wind speed plot and plot for displaying pitch angle.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event
226 def on_windspeed_slider_change(self, ui_element=None): 227 """Update UI after windspeed slider has changed value. 228 229 Args: 230 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 231 """ 232 self.windspeed_changed(self.windspeed_slider.value)
Update UI after windspeed slider has changed value.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event
234 def on_windspeed_input_change(self, ui_element=None): 235 """Update UI after windspeed number input has changed value. 236 237 Args: 238 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 239 """ 240 self.windspeed_changed(self.windspeed_input.value)
Update UI after windspeed number input has changed value.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event
242 def on_duration_input_change(self, change: Bunch): 243 """Update UI after duration input has changed value.""" 244 self.update_total_rotations(change["new"], self.rotation_speed_display.value)
Update UI after duration input has changed value.
246 def windspeed_changed(self, speed: float): 247 """Update UI with data for new displayed wind speed.""" 248 rounded_speed = round(speed,5) 249 self.windspeed_slider.value = rounded_speed 250 self.windspeed_input.value = rounded_speed 251 data = self.wind_data.get_single_entry(rounded_speed) 252 self.rotation_speed_display.value = data["rpm"] 253 self.pitch_angle_display.value = data["pitch_angle_deg"] 254 self.update_total_rotations(self.duration_input.value, data["rpm"]) 255 self.update_plot(speed, data["pitch_angle_deg"])
Update UI with data for new displayed wind speed.
257 def update_total_rotations(self, duration: float, rpm: float): 258 """Recalculate total rotations and update corresponding widget. 259 260 Args: 261 duration: Duration in years 262 rpm: Rotations per minute 263 """ 264 self.rotation_total_display.value = math.floor(duration * rpm * 60 * 24 * 365)
Recalculate total rotations and update corresponding widget.
Args: duration: Duration in years rpm: Rotations per minute
266 def get_pitch_angle_graph_definition(self)-> tuple: 267 """Return static point display used to draw wind turbine blade in matplot. 268 269 Returns: 270 list of floats as X data, list of floats a Y data 271 """ 272 x_data = [0.98, 0.92, 0.86, 0.8, 0.65, 0.5, 0.35, 0.2, 0.05, -0.1, -0.16, -0.22, -0.28, -0.34, 273 -0.4, -0.46, -0.52, -0.58, -0.64, -0.7, -0.76, -0.81, -0.84, -0.87, -0.9, -0.93, -0.96, 274 -0.975, -0.984, -0.99, -0.996, -0.9975, -0.999, -0.9996, -0.9998, -0.9992, -0.998, 275 -0.9965, -0.992, -0.986, -0.98, -0.965, -0.94, -0.91, -0.88, -0.85, -0.82, -0.78, -0.72, 276 -0.66, -0.6, -0.54, -0.48, -0.42, -0.36, -0.3, -0.24, -0.18, -0.12, 0, 0.15, 0.3, 0.45, 277 0.6, 0.75, 0.84, 0.9, 0.96] 278 y_data = [0.00676, 0.02272, 0.03808, 0.05346, 0.092, 0.12932, 0.16354, 0.1932, 0.2166, 0.23198, 279 0.23534, 0.23652, 0.23522, 0.23188, 0.22672, 0.21972, 0.2109, 0.20016, 0.18736, 0.17222, 280 0.15438, 0.13698, 0.12516, 0.11206, 0.0973, 0.0802, 0.05938, 0.0464, 0.0369, 0.02914, 281 0.01846, 0.01462, 0.00934, 0.00594, -0.00418, -0.00834, -0.01314, -0.0173, -0.02546, 282 -0.03234, -0.0375, -0.04704, -0.0582, -0.06812, -0.07608, -0.08276, -0.08854, -0.09516, 283 -0.10338, -0.10998, -0.11524, -0.11932, -0.12232, -0.12428, -0.12516, -0.12492, -0.12344, 284 -0.12028, -0.11524, -0.1017, -0.08064, -0.05716, -0.0336, -0.01224, 0.00372, 0.00878, 285 0.00922, 0.00608] 286 return x_data, y_data
Return static point display used to draw wind turbine blade in matplot.
Returns: list of floats as X data, list of floats a Y data
288 def load_local_data(self, ui_element=None): 289 """Use locally stored data for UI. 290 291 Args: 292 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 293 """ 294 self.conn = store_connection.LocalStoreConnection("sensotwin_world") 295 self.load_input_set_data() 296 self.init_wind_display()
Use locally stored data for UI.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event
298 def load_remote_data(self, ui_element=None): 299 """Use remotely stored data for UI. 300 301 Args: 302 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 303 """ 304 self.conn = store_connection.FusekiConnection(self.remote_database_input.value) 305 self.load_input_set_data() 306 self.init_wind_display()
Use remotely stored data for UI.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event
308 def save_new_input_set(self, ui_element=None): 309 """Save currently set data for wind profile and duration of simulation. 310 311 Args: 312 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 313 """ 314 new_id = max(self.input_set_data.keys()) + 1 if self.input_set_data else 1 315 new_set = configuration.StressStrainInputDataSet( 316 uniq_id = new_id, 317 wind_speed = self.windspeed_input.value, 318 rotation_speed = self.rotation_speed_display.value, 319 pitch_angle = self.pitch_angle_display.value, 320 total_rotations = self.rotation_total_display.value, 321 duration = self.duration_input.value, 322 conn = self.conn) 323 new_set.save_entry_to_store() 324 self.load_input_set_data()
Save currently set data for wind profile and duration of simulation.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event
326 def load_input_set_data(self): 327 """Load data from previously set input source and populate UI.""" 328 self.input_set_data = configuration.StressStrainInputDataSet.get_all_entries_from_store(self.conn) 329 if self.input_set_data: 330 options = [] 331 for key, input_set in self.input_set_data.items(): 332 label = input_set.generate_input_set_display_label() 333 options.append((label, key)) 334 self.input_set_selection.options = options 335 self.input_set_selection.value = list(self.input_set_data.keys())[0]
Load data from previously set input source and populate UI.
337 def display_input_set(self, ui_element=None): 338 """Display data for currently selected input set in UI. 339 340 Args: 341 ui_element: Override for ipywidgets to not pass the UI element that triggered the event 342 """ 343 displayed_set = self.input_set_data[self.input_set_selection.value] 344 345 self.windspeed_input.value = displayed_set.get_wind_speed() 346 self.rotation_speed_display.value = displayed_set.get_rotation_speed() 347 self.pitch_angle_display.value = displayed_set.get_pitch_angle() 348 self.rotation_total_display.value = displayed_set.get_total_rotations() 349 self.duration_input.value = displayed_set.get_duration() 350 351 self.windspeed_changed(displayed_set.get_wind_speed())
Display data for currently selected input set in UI.
Args: ui_element: Override for ipywidgets to not pass the UI element that triggered the event