diff --git a/femmt/functions.py b/femmt/functions.py index c37358a4..2fb50280 100644 --- a/femmt/functions.py +++ b/femmt/functions.py @@ -800,7 +800,13 @@ def fft(period_vector_t_i: npt.ArrayLike, sample_factor: int = 1000, plot: str = def plot_fourier_coefficients(frequency_list, amplitude_list, phi_rad_list, sample_factor: int = 1000, figure_directory: str = None): """Plot fourier coefficients in a visual figure.""" - time_period = 1 / min(frequency_list) + # dc and ac handling + nonzero_frequencies = [f for f in frequency_list if f != 0] + if nonzero_frequencies: + time_period = 1 / min(nonzero_frequencies) + else: + time_period = 1 + # time_period = 1 / min(frequency_list) t_interp = np.linspace(0, time_period, sample_factor) reconstructed_signal = 0 @@ -830,6 +836,7 @@ def plot_fourier_coefficients(frequency_list, amplitude_list, phi_rad_list, samp plt.tight_layout() if figure_directory is not None: plt.savefig(figure_directory, bbox_inches="tight") + plt.close('all') # close the figures to remove the warning when you run many figures # plt.show() @@ -1064,43 +1071,73 @@ def visualize_simulation_results(simulation_result_file_path: str, store_figure_ """Visualize the simulation results by a figure.""" with open(simulation_result_file_path, "r") as fd: loaded_results_dict = json.loads(fd.read()) - # core eddy and hysteresis losses - loss_core_eddy_current = loaded_results_dict["total_losses"]["eddy_core"] - loss_core_hysteresis = loaded_results_dict["total_losses"]["hyst_core_fundamental_freq"] - # Inductances and winding losses - windings_inductance = [] - windings_loss = [] + + # Initialize accumulators for cumulative losses and inductances + cumulative_core_hysteresis = 0 + cumulative_core_eddy = 0 + cumulative_losses = [] + cumulative_inductances = [] windings_labels = [] - # Dynamically for 3 windings - for i in range(1, 3): - winding_key = f"winding{i}" - if winding_key in loaded_results_dict["single_sweeps"][0]: - windings_inductance.append(loaded_results_dict["single_sweeps"][0][winding_key]["flux_over_current"][0]) - windings_loss.append(loaded_results_dict["total_losses"][winding_key]["total"]) - windings_labels.append(f"Winding {i}") - - # Plotting results - fig, ax = plt.subplots() - bar_width = 0.35 - # Plot core losses - ax.bar(0, loss_core_hysteresis, width=bar_width, label='Core Hysteresis Loss') - ax.bar(0, loss_core_eddy_current, bottom=loss_core_hysteresis, width=bar_width, label='Core Eddy Current Loss') + for index, sweep in enumerate(loaded_results_dict["single_sweeps"]): + freq = sweep['f'] + loss_core_eddy_current = sweep.get("core_eddy_losses", 0) + loss_core_hysteresis = sweep.get("core_hyst_losses", 0) + + # Accumulate core losses + cumulative_core_hysteresis += loss_core_hysteresis + cumulative_core_eddy += loss_core_eddy_current + + # Plotting for each frequency + fig, ax = plt.subplots() + ax.bar(0, loss_core_hysteresis, width=0.35, label='Core Hysteresis Loss') + ax.bar(0, loss_core_eddy_current, bottom=loss_core_hysteresis, width=0.35, label='Core Eddy Current Loss') + + for i in range(1, 4): + winding_key = f"winding{i}" + if winding_key in sweep: + inductance = sweep[winding_key].get("flux_over_current", [0])[0] + loss = sweep[winding_key].get("winding_losses", 0) + + if len(cumulative_losses) < i: + cumulative_losses.append(loss) + cumulative_inductances.append(inductance) + windings_labels.append(f"Winding {i}") + else: + cumulative_losses[i - 1] += loss + cumulative_inductances[i - 1] += inductance + + # Plot for current frequency + ax.bar(i, loss, width=0.35, label=f'{windings_labels[i - 1]} Loss at {freq} Hz') + + ax.set_ylabel('Losses in W') + ax.set_title(f'Loss Distribution at {freq} Hz') + ax.legend() + plt.grid(True) + + # Save plot for the current frequency + base_path, ext = os.path.splitext(store_figure_file_path) + filename = f"{base_path}_{index}{ext}" + plt.savefig(filename, bbox_inches="tight") + plt.close(fig) + + # Plot cumulative results for core and windings + fig, ax = plt.subplots() + ax.bar(0, cumulative_core_hysteresis, width=0.35, label='Cumulative Core Hysteresis Loss') + ax.bar(0, cumulative_core_eddy, bottom=cumulative_core_hysteresis, width=0.35, label='Cumulative Core Eddy Current Loss') - # Plot winding inductances and losses - for index, (inductance, loss) in enumerate(zip(windings_inductance, windings_loss), start=1): - ax.bar(index, loss, width=bar_width, label=f'{windings_labels[index - 1]} Loss') - print(f"{windings_labels[index - 1]} Inductance: {inductance} H") - print(f"{windings_labels[index - 1]} Loss: {loss} W") + for index, loss in enumerate(cumulative_losses): + ax.bar(index + 1, loss, width=0.35, label=f'{windings_labels[index]} Cumulative Loss') - ax.set_ylabel('Losses in W') - ax.set_title('Loss Distribution in Magnetic Components') - ax.set_xticks(list(range(len(windings_labels) + 1))) + ax.set_ylabel('total Losses in W') + ax.set_title('Loss Distribution in Magnetic Components for all frequencies') + ax.set_xticks(range(len(windings_labels) + 1)) ax.set_xticklabels(['Core'] + windings_labels) ax.legend() - plt.grid(True) - plt.savefig(store_figure_file_path, bbox_inches="tight") + base_path, ext = os.path.splitext(store_figure_file_path) + cumulative_filename = f"{base_path}_total_freq{ext}" + plt.savefig(cumulative_filename, bbox_inches="tight") if show_plot: plt.show() diff --git a/femmt/model.py b/femmt/model.py index 02a9d18d..5ec936d4 100644 --- a/femmt/model.py +++ b/femmt/model.py @@ -562,9 +562,19 @@ def add_air_gap(self, leg_position: AirGapLegPosition, height: float, position_v elif self.method == AirGapMethod.Percent: if position_value > 100 or position_value < 0: raise Exception("AirGap position values for the percent method need to be between 0 and 100.") + # Calculate the maximum and minimum position in percent considering the winding window height and air gap length + max_position = 100 - (height / self.core.window_h) * 51 + min_position = (height / self.core.window_h) * 51 + + # Adjust the position value if it exceeds the bounds of 0 to 100 percent + if position_value > max_position: + position_value = max_position + elif position_value < min_position: + position_value = min_position + position = position_value / 100 * self.core.window_h - self.core.window_h / 2 - # When the position is above the winding window it needs to be adjusted + # # When the position is above the winding window it needs to be adjusted if position + height / 2 > self.core.window_h / 2: position -= (position + height / 2) - self.core.window_h / 2 elif position - height / 2 < -self.core.window_h / 2: diff --git a/gui/femmt_gui.py b/gui/femmt_gui.py index 6090ddac..031139d9 100644 --- a/gui/femmt_gui.py +++ b/gui/femmt_gui.py @@ -2753,7 +2753,16 @@ def md_get_frequency_lists(self) -> List: winding2_frequency_list = [] winding2_amplitude_list = [] winding2_phi_rad_list = [] + # case to handle dc in exitation sweeb + if self.md_dc_checkBox.isChecked(): + winding1_frequency_list.append(0) # DC frequency is 0 Hz + winding1_amplitude_list.append(comma_str_to_point_float(self.md_winding1_idc_lineEdit.text())) + winding1_phi_rad_list.append(0) # DC phase is typically 0 + if self.md_simulation_type_comboBox.currentText() != self.translation_dict['inductor']: + winding2_frequency_list.append(0) # DC frequency is 0 Hz + winding2_amplitude_list.append(comma_str_to_point_float(self.md_winding2_idc_lineEdit.text())) + winding2_phi_rad_list.append(0) # DC phase is typically 0 if self.md_fk1_checkBox.isChecked(): winding1_frequency_list.append(1 * comma_str_to_point_float(self.md_base_frequency_lineEdit.text())) winding1_amplitude_list.append(comma_str_to_point_float(self.md_winding1_ik1_lineEdit.text())) @@ -3240,47 +3249,46 @@ def md_action_run_simulation(self) -> None: # Read back results # ----------------------------------------------- - self.md_simulation_QLabel.setText('simulation fertig.') - - # loaded_results_dict = fmt.visualize_simulation_results(geo.file_data.femm_results_log_path, './results.png', show_plot=False) - loaded_results_dict = fmt.visualize_simulation_results(geo.file_data.e_m_results_log_path, - geo.file_data.results_em_simulation, show_plot=False) - # pixmap = QPixmap("./results.png") - pixmap = QPixmap(geo.file_data.results_em_simulation) - self.md_loss_plot_label.setPixmap(pixmap) - self.md_loss_plot_label.setMask(pixmap.mask()) - self.md_loss_plot_label.show() - - # inductance = loaded_results_dict["single_sweeps"][0]["winding1"]["flux_over_current"][0] - loss_core_eddy_current = loaded_results_dict["total_losses"]["eddy_core"] - loss_core_hysteresis = loaded_results_dict["total_losses"]["hyst_core_fundamental_freq"] - # loss_winding_1 = loaded_results_dict["total_losses"]["winding1"]["total"] - self.md_loss_core_hysteresis_label.setText(f"Core Hysteresis loss: {loss_core_hysteresis} W") - self.md_loss_core_eddy_current_label.setText(f"Core Eddy Current loss: {loss_core_eddy_current} W") - # self.md_loss_winding1_label.setText(f"Winding 1 loss: {loss_winding_1} W") - # self.md_inductance_label.setText(f"Primary Inductance: {inductance} H") - inductances = [] - windings_loss = [] - for i in range(1, 3): # for 3 windings - winding_key = f"winding{i}" - if winding_key in loaded_results_dict["single_sweeps"][0]: - inductances.append(loaded_results_dict["single_sweeps"][0][winding_key]["flux_over_current"][0]) - windings_loss.append(loaded_results_dict["total_losses"][winding_key]["total"]) - - # show them just for 2 windings in GUI - if self.md_simulation_type_comboBox.currentText() == self.translation_dict['inductor']: - self.md_loss_winding1_label.setText(f"Winding 1 loss: {windings_loss[0]} W") - self.md_inductance1_label.setText(f"Primary Inductance: {inductances[0]} H") - elif self.md_simulation_type_comboBox.currentText() == self.translation_dict['transformer']: - self.md_loss_winding1_label.setText(f"Winding 1 loss: {windings_loss[0]} W") - self.md_loss_winding2_label.setText(f"Winding 2 loss: {windings_loss[1]} W") - self.md_inductance1_label.setText(f"Primary Inductance: {inductances[0]} H") - self.md_inductance2_label.setText(f"Secondary Inductance: {inductances[1]} H") - - # log_path = geo.e_m_results_log_path - # simulation_results = str(fmt.read_results_log(log_path)) - # print(simulation_results) - # self.md_simulation_output_textBrowser.setText(simulation_results) + self.md_simulation_QLabel.setText('simulation complete.') + loaded_results_dict = fmt.visualize_simulation_results(geo.file_data.e_m_results_log_path, geo.file_data.results_em_simulation, show_plot=False) + + for index, sweep in enumerate(loaded_results_dict["single_sweeps"]): + # Frequency-specific losses + freq_label = getattr(self, f'md_freq{index + 1}') + # loss_plot_label = getattr(self, f'md_loss_plot_label1') + hysteresis_label = getattr(self, f'md_loss_core_hysteresis_label{index + 1}') + eddy_current_label = getattr(self, f'md_loss_core_eddy_current_label{index + 1}') + winding1_loss_label = getattr(self, f'md_loss_winding1_label{index + 1}') + inductance1_label = getattr(self, f'md_inductance1_label{index + 1}') + + if self.md_simulation_type_comboBox.currentText() == self.translation_dict['transformer']: + winding2_loss_label = getattr(self, f'md_loss_winding2_label{index + 1}') + inductance2_label = getattr(self, f'md_inductance2_label{index + 1}') + + # Update frequency label + freq_label.setText(f"Frequency: {sweep['f']} Hz") + + # image for loss plot + base_path, ext = os.path.splitext(geo.file_data.results_em_simulation) + cumulative_filename = f"{base_path}_total_freq{ext}" + pixmap = QPixmap(cumulative_filename) + if not pixmap.isNull(): + # to show more than one figure in the future: + # loss_plot_label.setPixmap(pixmap) + # loss_plot_label.show() + # just for shown one figure: + self.md_loss_plot_label1.setPixmap(pixmap) + self.md_loss_plot_label1.show() + + # loss labels + hysteresis_label.setText(f"Core Hysteresis loss: {sweep.get('core_hyst_losses', 0)} W") + eddy_current_label.setText(f"Core Eddy Current loss: {sweep.get('core_eddy_losses', 0)} W") + winding1_loss_label.setText(f"Winding 1 loss: {sweep['winding1'].get('winding_losses', 0)} W") + inductance1_label.setText(f"Primary Inductance: {sweep['winding1'].get('flux_over_current', [0])[0]} H") + # transformer case + if self.md_simulation_type_comboBox.currentText() == self.translation_dict['transformer']: + winding2_loss_label.setText(f"Winding 2 loss: {sweep['winding2'].get('winding_losses', 0)} W") + inductance2_label.setText(f"Secondary Inductance: {sweep['winding2'].get('flux_over_current', [0])[0]} H") def inductancecalc(self): """Calculate inductance from given geometries.""" diff --git a/gui/femmt_gui.ui b/gui/femmt_gui.ui index b1ec0549..7ad82d03 100644 --- a/gui/femmt_gui.ui +++ b/gui/femmt_gui.ui @@ -6,8 +6,8 @@ 0 0 - 1656 - 1076 + 1545 + 1177 @@ -37,7 +37,7 @@ 0 0 - 1619 + 1508 1280 @@ -45,7 +45,7 @@ - 1 + 2 @@ -61,7 +61,7 @@ - 3 + 2 @@ -1836,47 +1836,690 @@ Simulation Text Output - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + false + + + + 0 + 0 + 641 + 1137 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1887,12 +2530,37 @@ Simulation Visual Output - - - - - + + + + + false + + + + 0 + 0 + 619 + 1137 + + + + + + + + 0 + 0 + + + + + + + + + @@ -3854,7 +4522,7 @@ 0 0 - 635 + 98 518 @@ -4026,7 +4694,7 @@ 0 0 - 468 + 98 518 @@ -4081,8 +4749,8 @@ 0 0 - 1551 - 1172 + 268 + 253 @@ -4212,8 +4880,8 @@ 0 0 - 1511 - 807 + 98 + 518 @@ -4283,7 +4951,7 @@ 0 0 - 1514 + 1403 2036 @@ -8776,7 +9444,7 @@ - T + Hz @@ -8786,7 +9454,7 @@ - T + Hz @@ -8796,7 +9464,7 @@ - T + Hz @@ -8806,7 +9474,7 @@ - T + Hz @@ -8816,7 +9484,7 @@ - T + Hz @@ -8825,6 +9493,9 @@ Qt::Horizontal + + QSizePolicy::Expanding + 40 @@ -8872,7 +9543,7 @@ 0 0 - 1514 + 1403 1024 @@ -8926,15 +9597,18 @@ + + false + - true + false 0 0 - 1531 + 1329 967 @@ -9483,7 +10157,7 @@ 0 0 - 1656 + 1545 21