diff --git a/femmt/component.py b/femmt/component.py index fd6f6dae..137e50c2 100644 --- a/femmt/component.py +++ b/femmt/component.py @@ -1314,7 +1314,7 @@ def excitation_sweep(self, frequency_list: List, current_list_list: List, phi_de self.simulate() # self.visualize() - self.calculate_and_write_log(sweep_number=len(frequency_list), currents=current_list_list, + self.calculate_and_write_log(number_frequency_simulations=len(frequency_list), currents=current_list_list, frequencies=frequency_list, inductance_dict=inductance_dict, core_hyst_losses=core_hyst_loss) @@ -2123,7 +2123,7 @@ def write_electro_magnetic_post_pro(self): text_file.close() - def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, frequencies: List = None, + def calculate_and_write_log(self, number_frequency_simulations: int = 1, currents: List = None, frequencies: List = None, inductance_dict: dict = None, core_hyst_losses: float = None): """ Method reads back the results from the .dat result files created by the ONELAB simulation client and stores @@ -2135,8 +2135,14 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, * optional parameters can be added to the log, like the inductance values or the core hysteresis losses from external simulations - :param sweep_number: Number of sweep iterations that were done before. For a single simulation sweep_number = 1 - :type sweep_number: int + An inductance dictionary is optional, in case of get_inductance() has been calculated before. In this case, + the get_inductance()-results can be given to this function to be included into the result-log. + + In case of separate calculated hysteresis losses (e.g. triangular magnetization current), this value can be + given as a parameter and will overwrite the hysteresis-loss result. + + :param number_frequency_simulations: Number of sweep iterations that were done before. For a single simulation sweep_number = 1 + :type number_frequency_simulations: int :param currents: Current values of the sweep iterations. Not needed for single simulation :type currents: list :param frequencies: frequencies values of the sweep iterations. Not needed for single simulation @@ -2146,29 +2152,20 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, :param core_hyst_losses: Optional core hysteresis losses value from another simulation. If a value is given, the external value is used in the result-log. Otherwise, the hysteresis losses of the fundamental frequency is used :type core_hyst_losses: float """ - # ---- Print simulation results ---- fundamental_index = 0 # index of the fundamental frequency - # create the dictionary used to log the result data - # - 'single_sweeps' contains a list of n=sweep_number sweep_dicts - # - 'total_losses' contains the sums of all sweeps - # - in case complex core parameters are used: - # - hysteresis losses are taken only from fundamental frequency - # - fundamental frequency is the smallest frequency != 0 - # - 'simulation_settings' contains the simulation configuration (for more info see encode_settings()) - log_dict = {"single_sweeps": [], "total_losses": {}, "simulation_settings": {}} - for sweep_run in range(0, sweep_number): + for single_simulation in range(0, number_frequency_simulations): # create dictionary sweep_dict with 'Winding' as a list of m=n_windings winding_dicts. # Single values, received during one of the n=sweep_number simulations, are added as 'core_eddy_losses' etc. sweep_dict = {} # Frequencies - if sweep_number > 1: + if number_frequency_simulations > 1: # sweep_simulation -> get frequencies from passed currents - sweep_dict["f"] = frequencies[sweep_run] + sweep_dict["f"] = frequencies[single_simulation] fundamental_index = np.argmin(np.ma.masked_where(np.array(frequencies) == 0, np.array(frequencies))) else: # single_simulation -> get frequency from instance variable @@ -2185,19 +2182,17 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, winding_dict = {"turn_losses": [], "flux": [], "flux_over_current": [], - # "mag_field_energy": [], "V": []} - # Number turns turns = ff.get_number_of_turns_of_winding(winding_windows=self.winding_windows, windings=self.windings, winding_number=winding_number) winding_dict["number_turns"] = turns # Currents - if sweep_number > 1: + if number_frequency_simulations > 1: # sweep_simulation -> get currents from passed currents - complex_current_phasor = currents[sweep_run][winding_number] + complex_current_phasor = currents[single_simulation][winding_number] else: # single_simulation -> get current from instance variable complex_current_phasor = self.current[winding_number] @@ -2208,44 +2203,35 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, # Case litz: Load homogenized results if self.windings[winding_number].conductor_type == ConductorType.RoundLitz: winding_dict["winding_losses"] = \ - self.load_result(res_name=f"j2H_{winding_number + 1}", last_n=sweep_number)[sweep_run] + self.load_result(res_name=f"j2H_{winding_number + 1}", last_n=number_frequency_simulations)[single_simulation] for turn in range(0, winding_dict["number_turns"]): winding_dict["turn_losses"].append( self.load_result(res_name=winding_name[winding_number] + f"/Losses_turn_{turn + 1}", - last_n=sweep_number)[sweep_run]) + last_n=number_frequency_simulations)[single_simulation]) # Case Solid: Load results, (pitfall for parallel windings results are only stored in one turn!) else: - winding_dict["winding_losses"] = \ - self.load_result(res_name=f"j2F_{winding_number + 1}", last_n=sweep_number)[sweep_run] + winding_dict["winding_losses"] = self.load_result(res_name=f"j2F_{winding_number + 1}", + last_n=number_frequency_simulations)[single_simulation] if self.windings[winding_number].parallel: winding_dict["turn_losses"].append( self.load_result(res_name=winding_name[winding_number] + f"/Losses_turn_{1}", - last_n=sweep_number)[sweep_run]) + last_n=number_frequency_simulations)[single_simulation]) else: for turn in range(0, winding_dict["number_turns"]): winding_dict["turn_losses"].append( self.load_result(res_name=winding_name[winding_number] + f"/Losses_turn_{turn + 1}", - last_n=sweep_number)[sweep_run]) - - # Magnetic Field Energy - # winding_dict["mag_field_energy"].append(self.load_result(res_name=f"ME", last_n=sweep_number)[sweep_run]) - # winding_dict["mag_field_energy"].append(self.load_result(res_name=f"ME", part="imaginary", last_n=sweep_number)[sweep_run]) + last_n=number_frequency_simulations)[single_simulation]) # Voltage winding_dict["V"].append( - self.load_result(res_name=f"Voltage_{winding_number + 1}", part="real", last_n=sweep_number)[ - sweep_run]) + self.load_result(res_name=f"Voltage_{winding_number + 1}", part="real", last_n=number_frequency_simulations)[ + single_simulation]) winding_dict["V"].append( - self.load_result(res_name=f"Voltage_{winding_number + 1}", part="imaginary", last_n=sweep_number)[ - sweep_run]) + self.load_result(res_name=f"Voltage_{winding_number + 1}", part="imaginary", last_n=number_frequency_simulations)[ + single_simulation]) complex_voltage_phasor = complex(winding_dict["V"][0], winding_dict["V"][1]) - # Inductance - # winding_dict["self_inductance"].append(self.load_result(res_name=f"L_{winding + 1}{winding + 1}", part="real", last_n=sweep_number)[sweep_run]) - # winding_dict["self_inductance"].append(self.load_result(res_name=f"L_{winding + 1}{winding + 1}", part="imaginary", last_n=sweep_number)[sweep_run]) - # Inductance from voltage - if complex_current_phasor == 0 or sweep_dict["f"] == 0: # if-statement to avoid div by zero error winding_dict["flux_over_current"] = [0, 0] else: @@ -2256,49 +2242,49 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, # Flux winding_dict["flux"].append( - self.load_result(res_name=f"Flux_Linkage_{winding_number + 1}", last_n=sweep_number)[sweep_run]) + self.load_result(res_name=f"Flux_Linkage_{winding_number + 1}", last_n=number_frequency_simulations)[single_simulation]) winding_dict["flux"].append( self.load_result(res_name=f"Flux_Linkage_{winding_number + 1}", part="imaginary", - last_n=sweep_number)[sweep_run]) - # Flux from voltage - # winding_dict["flux"].append((complex(winding_dict["self_inductance"][-2], winding_dict["self_inductance"][-1])*self.current[winding]).real) # (L*I).real - # winding_dict["flux"].append((complex(winding_dict["self_inductance"][-2], winding_dict["self_inductance"][-1])*self.current[winding]).imag) # (L*I).imag + last_n=number_frequency_simulations)[single_simulation]) # Power - # using 'winding_dict["V"][0]' to get first element (real part) of V. Use winding_dict["I"][0] to avoid typeerror - winding_dict["P"] = (complex_voltage_phasor * complex_current_phasor.conjugate() / 2).real - winding_dict["Q"] = (complex_voltage_phasor * complex_current_phasor.conjugate() / 2).imag + # using 'winding_dict["V"][0]' to get first element (real part) of V. + # Use winding_dict["I"][0] to avoid typeerror + # As FEMMT uses peak pointers, and the power needs to be halfed. + # i_rms * u_rms = i_peak / sqrt(2) * u_peak / sqrt(2) = i_peak * u_peak / 2 + # Note: For DC-values (frequency = 0), RMS value is same as peak value and so, halve is not allowed. + if sweep_dict["f"] == 0.0: + # Power calculation for DC values. + winding_dict["P"] = (complex_voltage_phasor * complex_current_phasor.conjugate()).real + winding_dict["Q"] = (complex_voltage_phasor * complex_current_phasor.conjugate()).imag + else: + # Power calculation for all other AC values + winding_dict["P"] = (complex_voltage_phasor * complex_current_phasor.conjugate() / 2).real + winding_dict["Q"] = (complex_voltage_phasor * complex_current_phasor.conjugate() / 2).imag winding_dict["S"] = np.sqrt(winding_dict["P"] ** 2 + winding_dict["Q"] ** 2) sweep_dict[f"winding{winding_number + 1}"] = winding_dict # Core losses TODO: Choose between Steinmetz or complex core losses - sweep_dict["core_eddy_losses"] = self.load_result(res_name="CoreEddyCurrentLosses", last_n=sweep_number)[ - sweep_run] - sweep_dict["core_hyst_losses"] = self.load_result(res_name="p_hyst", last_n=sweep_number)[sweep_run] + sweep_dict["core_eddy_losses"] = self.load_result(res_name="CoreEddyCurrentLosses", last_n=number_frequency_simulations)[ + single_simulation] + sweep_dict["core_hyst_losses"] = self.load_result(res_name="p_hyst", last_n=number_frequency_simulations)[single_simulation] # Core Part losses # If there are multiple core parts, calculate losses for each part individually # the core losses are caluclated by summing the eddy and hyst losses - if len(self.mesh.plane_surface_core) > 1: - sweep_dict["core_parts"] = {} - for i in range(0, len(self.mesh.plane_surface_core)): - sweep_dict["core_parts"][f"core_part_{i + 1}"] = {} - sweep_dict["core_parts"][f"core_part_{i + 1}"]["eddy_losses"] = \ - self.load_result(res_name=f"core_parts/CoreEddyCurrentLosses_{i + 1}", last_n=sweep_number)[sweep_run] - sweep_dict["core_parts"][f"core_part_{i + 1}"]["hyst_losses"] = \ - self.load_result(res_name=f"core_parts/p_hyst_{i + 1}", last_n=sweep_number)[sweep_run] - # finding the total losses for every core_part - eddy = sweep_dict["core_parts"][f"core_part_{i + 1}"]["eddy_losses"] - hyst = sweep_dict["core_parts"][f"core_part_{i + 1}"]["hyst_losses"] - sweep_dict["core_parts"][f"core_part_{i + 1}"][f"total_core_part_{i + 1}"] = eddy + hyst - - # For a single core part, total losses are simply the sum of eddy and hysteresis losses - else: - # if I have only one core_part - sweep_dict["core_parts"] = {} # parent dictionary is initialized first - sweep_dict["core_parts"]["core_part_1"] = {} - sweep_dict["core_parts"]["core_part_1"]["total_core_part_1"] = sweep_dict["core_eddy_losses"] + sweep_dict["core_hyst_losses"] + + sweep_dict["core_parts"] = {} + for i in range(0, len(self.mesh.plane_surface_core)): + sweep_dict["core_parts"][f"core_part_{i + 1}"] = {} + sweep_dict["core_parts"][f"core_part_{i + 1}"]["eddy_losses"] = \ + self.load_result(res_name=f"core_parts/CoreEddyCurrentLosses_{i + 1}", last_n=number_frequency_simulations)[single_simulation] + sweep_dict["core_parts"][f"core_part_{i + 1}"]["hyst_losses"] = \ + self.load_result(res_name=f"core_parts/p_hyst_{i + 1}", last_n=number_frequency_simulations)[single_simulation] + # finding the total losses for every core_part + eddy = sweep_dict["core_parts"][f"core_part_{i + 1}"]["eddy_losses"] + hyst = sweep_dict["core_parts"][f"core_part_{i + 1}"]["hyst_losses"] + sweep_dict["core_parts"][f"core_part_{i + 1}"][f"total_core_part_{i + 1}"] = eddy + hyst # Sum losses of all windings of one single run sweep_dict["all_winding_losses"] = sum( @@ -2343,15 +2329,22 @@ def calculate_and_write_log(self, sweep_number: int = 1, currents: List = None, log_dict["total_losses"]["eddy_core"] = sum( log_dict["single_sweeps"][d]["core_eddy_losses"] for d in range(len(log_dict["single_sweeps"]))) # In the total losses calculation, individual core part losses are also accounted for - if len(self.mesh.plane_surface_core) > 1: + for i in range(len(self.mesh.plane_surface_core)): + log_dict["total_losses"][f"total_core_part_{i + 1}"] = 0 + log_dict["total_losses"][f"total_eddy_core_part_{i + 1}"] = 0 + log_dict["total_losses"][f"total_hyst_core_part_{i + 1}"] = 0 + + # sweep through all frequency simulations and sum up the core_part_x losses + for single_simulation in range(0, number_frequency_simulations): for i in range(len(self.mesh.plane_surface_core)): - log_dict["total_losses"][f"total_core_part_{i + 1}"] = sweep_dict["core_parts"][f"core_part_{i + 1}"][ + log_dict["total_losses"][f"total_core_part_{i + 1}"] += log_dict["single_sweeps"][single_simulation]["core_parts"][f"core_part_{i + 1}"][ f"total_core_part_{i + 1}"] - # If there is only one core part, set its total losses directly - if len(self.mesh.plane_surface_core) == 1: - - log_dict["total_losses"]["total_core_part_1"] = sweep_dict["core_parts"]["core_part_1"]["total_core_part_1"] - + log_dict["total_losses"][f"total_eddy_core_part_{i + 1}"] += \ + log_dict["single_sweeps"][single_simulation]["core_parts"][f"core_part_{i + 1}"][ + f"eddy_losses"] + log_dict["total_losses"][f"total_hyst_core_part_{i + 1}"] += \ + log_dict["single_sweeps"][single_simulation]["core_parts"][f"core_part_{i + 1}"][ + f"hyst_losses"] if isinstance(core_hyst_losses, float) or isinstance(core_hyst_losses, int): log_dict["total_losses"]["hyst_core_fundamental_freq"] = core_hyst_losses @@ -2424,6 +2417,18 @@ def read_log(self) -> Dict: return log + def read_thermal_log(self) -> Dict: + """ + Read results from electromagnetic simulation + :return: Logfile as a dictionary + :rtype: Dict + """ + with open(os.path.join(self.file_data.results_folder_path, "results_thermal.json"), "r") as fd: + content = json.loads(fd.read()) + log = content + + return log + def visualize(self): """ - a post simulation viewer diff --git a/femmt/optimization/ito_functions.py b/femmt/optimization/ito_functions.py index 598ab8e4..38f1a1da 100644 --- a/femmt/optimization/ito_functions.py +++ b/femmt/optimization/ito_functions.py @@ -92,9 +92,6 @@ def set_up_folder_structure(working_directory: str) -> WorkingDirectories: electro_magnetic_folder_general = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), os.pardir, "electro_magnetic")) strands_coefficients_folder_general = os.path.join(electro_magnetic_folder_general, "Strands_Coefficients") - print(f"{electro_magnetic_folder_general = }") - print(f"{strands_coefficients_folder_general = }") - if not os.path.isdir(working_directory): os.mkdir(working_directory) diff --git a/femmt/optimization/sto.py b/femmt/optimization/sto.py index 7447ec3e..3c846d56 100644 --- a/femmt/optimization/sto.py +++ b/femmt/optimization/sto.py @@ -6,6 +6,8 @@ # 3rd party libraries import optuna +import pandas as pd +from matplotlib import pyplot as plt # FEMMT and materialdatabase libraries from femmt.optimization.sto_dtos import * @@ -96,556 +98,60 @@ def calculate_fix_parameters(config: StoSingleInputConfig) -> StoTargetAndFixedP return target_and_fix_parameters - class FemSimulation: - @staticmethod - def objective(trial, config: StoSingleInputConfig, - target_and_fixed_parameters: StoTargetAndFixedParameters, - number_objectives: int, show_geometries: bool = False, process_number: int = 1): - """ - Objective for optuna optimization. - - :param trial: optuna trail objective. Used by optuna - :param config: simulation configuration file - :type config: StoSingleInputConfig - :param target_and_fixed_parameters: contains pre-calculated values - :type target_and_fixed_parameters: StoTargetAndFixedParameters - :param number_objectives: number of objectives to give different target output parameters - :type number_objectives: int - :param show_geometries: True to display the geometries - :type show_geometries: bool - """ - # suggest core geometry - core_inner_diameter = trial.suggest_float("core_inner_diameter", config.core_inner_diameter_min_max_list[0], config.core_inner_diameter_min_max_list[1]) - window_w = trial.suggest_float("window_w", config.window_w_min_max_list[0], config.window_w_min_max_list[1]) - air_gap_transformer = trial.suggest_float("air_gap_transformer", 0.1e-3, 5e-3) - - # suggest secondary / tertiary inner winding radius - iso_left_core = trial.suggest_float("iso_left_core", config.insulations.iso_left_core_min, config.insulations.iso_primary_inner_bobbin) - - primary_additional_bobbin = config.insulations.iso_primary_inner_bobbin - iso_left_core - - primary_litz_wire = trial.suggest_categorical("primary_litz_wire", config.primary_litz_wire_list) - - primary_litz_parameters = ff.litz_database()[primary_litz_wire] - primary_litz_diameter = 2 * primary_litz_parameters["conductor_radii"] - - # Will always be calculated from the given parameters - available_width = window_w - iso_left_core - config.insulations.iso_right_core - - # Suggestion of top window coil - # Theoretically also 0 coil turns possible (number_rows_coil_winding must then be recalculated to avoid neg. values) - primary_coil_turns = trial.suggest_int("primary_coil_turns", config.primary_coil_turns_min_max_list[0], config.primary_coil_turns_min_max_list[1]) - # Note: int() is used to round down. - number_rows_coil_winding = int((primary_coil_turns * (primary_litz_diameter + config.insulations.iso_primary_to_primary) - iso_left_core) / available_width) + 1 - window_h_top = config.insulations.iso_top_core + config.insulations.iso_bot_core + number_rows_coil_winding * primary_litz_diameter + (number_rows_coil_winding - 1) * config.insulations.iso_primary_to_primary - - # Maximum coil air gap depends on the maximum window height top - air_gap_coil = trial.suggest_float("air_gap_coil", 0.1e-3, window_h_top - 0.1e-3) - - # suggest categorical - core_material = trial.suggest_categorical("material", config.material_list) - foil_thickness = trial.suggest_categorical("foil_thickness", config.metal_sheet_thickness_list) - interleaving_scheme = trial.suggest_categorical("interleaving_scheme", config.interleaving_scheme_list) - interleaving_type = trial.suggest_categorical("interleaving_type", config.interleaving_type_list) - - try: - if config.max_transformer_total_height is not None: - # Maximum transformer height - window_h_bot_max = config.max_transformer_total_height - 3 * core_inner_diameter / 4 - window_h_top - window_h_bot_min = config.window_h_bot_min_max_list[0] - if window_h_bot_min > window_h_bot_max: - print(f"{number_rows_coil_winding = }") - print(f"{window_h_top = }") - raise ValueError(f"{window_h_bot_min = } > {window_h_bot_max = }") - - window_h_bot = trial.suggest_float("window_h_bot", window_h_bot_min, window_h_bot_max) - - else: - window_h_bot = trial.suggest_float("window_h_bot", config.window_h_bot_min_max_list[0], - config.window_h_bot_min_max_list[1]) - - if show_geometries: - verbosity = femmt.Verbosity.ToConsole - else: - verbosity = femmt.Verbosity.Silent - - working_directory_single_process = os.path.join(target_and_fixed_parameters.working_directories.fem_working_directory, f"process_{process_number}") - - geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, - working_directory=working_directory_single_process, - verbosity=verbosity, simulation_name=f"Case_{trial.number}") - - electro_magnetic_directory_single_process = os.path.join(working_directory_single_process, "electro_magnetic") - strands_coefficients_folder_single_process = os.path.join(electro_magnetic_directory_single_process, "Strands_Coefficients") - - # Update directories for each model - geo.file_data.update_paths(working_directory_single_process, electro_magnetic_directory_single_process, - strands_coefficients_folder_single_process) - - core_dimensions = femmt.dtos.StackedCoreDimensions(core_inner_diameter=core_inner_diameter, window_w=window_w, - window_h_top=window_h_top, window_h_bot=window_h_bot) - core = femmt.Core(core_type=femmt.CoreType.Stacked, core_dimensions=core_dimensions, - material=core_material, temperature=config.temperature, frequency=target_and_fixed_parameters.fundamental_frequency, - permeability_datasource=config.permeability_datasource, - permeability_datatype=config.permeability_datatype, - permeability_measurement_setup=config.permeability_measurement_setup, - permittivity_datasource=config.permittivity_datasource, - permittivity_datatype=config.permittivity_datatype, - permittivity_measurement_setup=config.permittivity_measurement_setup) - - geo.set_core(core) - - air_gaps = femmt.AirGaps(femmt.AirGapMethod.Stacked, core) - air_gaps.add_air_gap(femmt.AirGapLegPosition.CenterLeg, air_gap_coil, stacked_position=femmt.StackedPosition.Top) - air_gaps.add_air_gap(femmt.AirGapLegPosition.CenterLeg, air_gap_transformer, stacked_position=femmt.StackedPosition.Bot) - geo.set_air_gaps(air_gaps) - - # set_center_tapped_windings() automatically places the condu - insulation, coil_window, transformer_window = femmt.functions_topologies.set_center_tapped_windings( - core=core, - - # primary litz - primary_additional_bobbin=primary_additional_bobbin, - primary_turns=config.n_target, - primary_radius=primary_litz_parameters["conductor_radii"], - primary_number_strands=primary_litz_parameters["strands_numbers"], - primary_strand_radius=primary_litz_parameters["strand_radii"], - - # secondary foil - secondary_parallel_turns=2, - secondary_thickness_foil=foil_thickness, - - # insulation - iso_top_core=config.insulations.iso_top_core, - iso_bot_core=config.insulations.iso_bot_core, - iso_left_core=iso_left_core, - iso_right_core=config.insulations.iso_right_core, - iso_primary_to_primary=config.insulations.iso_primary_to_primary, - iso_secondary_to_secondary=config.insulations.iso_secondary_to_secondary, - iso_primary_to_secondary=config.insulations.iso_primary_to_secondary, - bobbin_coil_top=config.insulations.iso_top_core, - bobbin_coil_bot=config.insulations.iso_bot_core, - bobbin_coil_left=config.insulations.iso_primary_inner_bobbin, - bobbin_coil_right=config.insulations.iso_right_core, - center_foil_additional_bobbin=0e-3, - interleaving_scheme=interleaving_scheme, - - # misc - interleaving_type=interleaving_type, - primary_coil_turns=primary_coil_turns, - winding_temperature=config.temperature) - - geo.set_insulation(insulation) - geo.set_winding_windows([coil_window, transformer_window], config.mesh_accuracy) - - geo.create_model(freq=target_and_fixed_parameters.fundamental_frequency, pre_visualize_geometry=show_geometries) - - center_tapped_study_excitation = geo.center_tapped_pre_study( - time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_1_vec], [target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_2_vec]], - fft_filter_value_factor=config.fft_filter_value_factor) - - geo.stacked_core_center_tapped_study(center_tapped_study_excitation, number_primary_coil_turns=primary_coil_turns) - - - #geo.stacked_core_center_tapped_study(time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_1_vec], - # [target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_2_vec]], - # plot_waveforms=False) - - # copy result files to result-file folder - source_json_file = os.path.join( - target_and_fixed_parameters.working_directories.fem_working_directory, f'process_{process_number}', - "results", "log_electro_magnetic.json") - destination_json_file = os.path.join( - target_and_fixed_parameters.working_directories.fem_simulation_results_directory, - f'case_{trial.number}.json') - - shutil.copy(source_json_file, destination_json_file) - - # read result-log - with open(source_json_file, "r") as fd: - loaded_data_dict = json.loads(fd.read()) - - total_volume = loaded_data_dict["misc"]["core_2daxi_total_volume"] - total_loss = loaded_data_dict["total_losses"]["total_losses"] - total_cost = loaded_data_dict["misc"]["total_cost_incl_margin"] - - # Get inductance values - difference_l_h = config.l_h_target - geo.L_h - difference_l_s12 = config.l_s12_target - geo.L_s12 - - trial.set_user_attr("l_h", geo.L_h) - trial.set_user_attr("l_s12", geo.L_s12) - - # TODO: Normalize on goal values here or the whole generation on min and max? for each feature inbetween 0 and 1 - # norm_total_loss, norm_difference_l_h, norm_difference_l_s12 = total_loss/10, abs(difference_l_h/config.l_h_target), abs(difference_l_s12/config.l_s12_target) - # return norm_total_loss, norm_difference_l_h, norm_difference_l_s12 - if number_objectives == 3: - return total_volume, total_loss, abs(difference_l_h / config.l_h_target) + abs( - difference_l_s12 / config.l_s12_target) - elif number_objectives == 4: - return total_volume, total_loss, abs(difference_l_h), abs(difference_l_s12) - - except Exception as e: - print(e) - if number_objectives == 3: - return float('nan'), float('nan'), float('nan') - elif number_objectives == 4: - return float('nan'), float('nan'), float('nan'), float('nan') - - - - @staticmethod - def start_proceed_study(study_name: str, config: StoSingleInputConfig, number_trials: int, - end_time: datetime.datetime = datetime.datetime.now(), - number_objectives: int = None, - storage: str = 'sqlite', - sampler=optuna.samplers.NSGAIISampler(), - show_geometries: bool = False, - ) -> None: - """ - Proceed a study which is stored as sqlite database. - - :param study_name: Name of the study - :type study_name: str - :param config: Simulation configuration - :type config: ItoSingleInputConfig - :param number_trials: Number of trials adding to the existing study - :type number_trials: int - :param number_objectives: number of objectives, e.g. 3 or 4 - :type number_objectives: int - :param storage: storage database, e.g. 'sqlite' or 'mysql' - :type storage: str - :param sampler: optuna.samplers.NSGAIISampler() or optuna.samplers.NSGAIIISampler(). Note about the brackets () !! - :type sampler: optuna.sampler-object - :param show_geometries: True to show the geometry of each suggestion (with valid geometry data) - :type show_geometries: bool - :param end_time: datetime object with the end time of simulation. If the end_time is not reached, a new simulation with number_objectives is started, e.g. datetime.datetime(2023,9,1,13,00) 2023-09-01, 13.00 - :type end_time: datetime.datetime - """ - def objective_directions(number_objectives: int): - """ - Checks if the number of objectives is correct and returns the minimizing targets - :param number_objectives: number of objectives - :type number_objectives: int - :returns: objective targets and optimization function - """ - if number_objectives == 3: - # Wrap the objective inside a lambda and call objective inside it - return ["minimize", "minimize", "minimize"] - if number_objectives == 4: - return ["minimize", "minimize", "minimize", 'minimize'] - else: - raise ValueError("Invalid objective number.") - - if os.path.exists(f"{config.working_directory}/study_{study_name}.sqlite3"): - print("Existing study found. Proceeding.") - - target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) - - # introduce study in storage, e.g. sqlite or mysql - if storage == 'sqlite': - # Note: for sqlite operation, there needs to be three slashes '///' even before the path '/home/...' - # Means, in total there are four slashes including the path itself '////home/.../database.sqlite3' - storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" - elif storage == 'mysql': - storage = "mysql://monty@localhost/mydb", - - # set logging verbosity: https://optuna.readthedocs.io/en/stable/reference/generated/optuna.logging.set_verbosity.html#optuna.logging.set_verbosity - # .INFO: all messages (default) - # .WARNING: fails and warnings - # .ERROR: only errors - #optuna.logging.set_verbosity(optuna.logging.ERROR) - - directions = objective_directions(number_objectives) - - func = lambda \ - trial: femmt.optimization.StackedTransformerOptimization.FemSimulation.objective( - trial, config, - target_and_fixed_parameters, number_objectives, show_geometries) - - if (end_time + datetime.timedelta(seconds=10)) < datetime.datetime.now(): - raise ValueError("May wrong set end time?" - f"\nCurrent time: {datetime.datetime.now()}" - f"\nEnd time: {end_time}") - elif end_time < datetime.datetime.now() + datetime.timedelta(seconds=10): - print("start simulation") - # in case of no given end_time, the end_time is one second after now. - end_time = datetime.datetime.now() + datetime.timedelta(seconds=0.001) - else: - pass - - - while datetime.datetime.now() < end_time: - print(f"Performing another {number_trials} trials.") - - study_in_storage = optuna.create_study(study_name=study_name, - storage=storage, - directions=directions, - load_if_exists=True, sampler=sampler) - - - study_in_memory = optuna.create_study(directions=directions, study_name=study_name, sampler=sampler) - print(f"Sampler is {study_in_memory.sampler.__class__.__name__}") - study_in_memory.add_trials(study_in_storage.trials) - study_in_memory.optimize(func, n_trials=number_trials, show_progress_bar=True) - - study_in_storage.add_trials(study_in_memory.trials[-number_trials:]) - print(f"Finished {number_trials} trials.") - print(f"current time: {datetime.datetime.now()}") - print(f"end time: {end_time}") - - @staticmethod - def proceed_multi_core_study(study_name: str, config: StoSingleInputConfig, number_trials: int, - end_time: datetime.datetime = datetime.datetime.now(), - number_objectives: int = None, - storage: str = "mysql://monty@localhost/mydb", - sampler=optuna.samplers.NSGAIISampler(), - show_geometries: bool = False, - process_number: int = 1, - ) -> None: - """ - Proceed a study which can be paralleled. It is highly recommended to use a mysql-database (or mariadb). - - :param study_name: Name of the study - :type study_name: str - :param config: Simulation configuration - :type config: ItoSingleInputConfig - :param number_trials: Number of trials adding to the existing study - :type number_trials: int - :param number_objectives: number of objectives, e.g. 3 or 4 - :type number_objectives: int - :param storage: storage database, e.g. 'sqlite' or mysql-storage, e.g. "mysql://monty@localhost/mydb" - :type storage: str - :param sampler: optuna.samplers.NSGAIISampler() or optuna.samplers.NSGAIIISampler(). Note about the brackets () !! - :type sampler: optuna.sampler-object - :param show_geometries: True to show the geometry of each suggestion (with valid geometry data) - :type show_geometries: bool - :param end_time: datetime object with the end time of simulation. If the end_time is not reached, a new simulation with number_objectives is started, e.g. datetime.datetime(2023,9,1,13,00) 2023-09-01, 13.00 - :type end_time: datetime.datetime - :type process_number: number of the process, mandatory to split this up for several processes, because they use the same simulation result folder! - :param process_number: int - """ - def objective_directions(number_objectives: int): - """ - Checks if the number of objectives is correct and returns the minimizing targets - :param number_objectives: number of objectives - :type number_objectives: int - :returns: objective targets and optimization function - """ - if number_objectives == 3: - # Wrap the objective inside a lambda and call objective inside it - return ["minimize", "minimize", "minimize"] - if number_objectives == 4: - return ["minimize", "minimize", "minimize", 'minimize'] - else: - raise ValueError("Invalid objective number.") - - # introduce study in storage, e.g. sqlite or mysql - if storage == 'sqlite': - # Note: for sqlite operation, there needs to be three slashes '///' even before the path '/home/...' - # Means, in total there are four slashes including the path itself '////home/.../database.sqlite3' - storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" - elif storage == 'mysql': - storage = "mysql://monty@localhost/mydb", - - target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) - - # set logging verbosity: https://optuna.readthedocs.io/en/stable/reference/generated/optuna.logging.set_verbosity.html#optuna.logging.set_verbosity - # .INFO: all messages (default) - # .WARNING: fails and warnings - # .ERROR: only errors - #optuna.logging.set_verbosity(optuna.logging.ERROR) - - directions = objective_directions(number_objectives) - - func = lambda \ - trial: femmt.optimization.StackedTransformerOptimization.FemSimulation.objective( - trial, config, - target_and_fixed_parameters, number_objectives, show_geometries, process_number) - - if (end_time + datetime.timedelta(seconds=10)) < datetime.datetime.now(): - raise ValueError("May wrong set end time?" - f"\nCurrent time: {datetime.datetime.now()}" - f"\nEnd time: {end_time}") - elif end_time < datetime.datetime.now() + datetime.timedelta(seconds=10): - print("start simulation") - # in case of no given end_time, the end_time is one second after now. - end_time = datetime.datetime.now() + datetime.timedelta(seconds=1) - else: - pass - - - while datetime.datetime.now() < end_time: - print(f"current time: {datetime.datetime.now()}") - print(f"end time: {end_time}") - print(f"Performing another {number_trials} trials.") - - study_in_database = optuna.create_study(study_name=study_name, - storage=storage, - directions=directions, - load_if_exists=True, sampler=sampler) - - - - - - study_in_database.optimize(func, n_trials=number_trials, show_progress_bar=True) - - - - - @staticmethod - def show_study_results(study_name: str, config: StoSingleInputConfig, - percent_error_difference_l_h: float = 20, - percent_error_difference_l_s12: float = 20) -> None: - """ - Show the results of a study. - - A local .html file is generated under config.working_directory to store the interactive plotly plots on disk. - - :param study_name: Name of the study - :type study_name: str - :param config: Integrated transformer configuration file - :type config: ItoSingleInputConfig - :param percent_error_difference_l_h: relative error allowed in l_h - :type percent_error_difference_l_s12: float - :param percent_error_difference_l_s12: relative error allowed in L_s12 - :type percent_error_difference_l_s12: float - """ - study = optuna.create_study(study_name=study_name, - storage=f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3", - load_if_exists=True) - - # Order: total_volume, total_loss, difference_l_h, difference_l_s - l_h_absolute_error = percent_error_difference_l_h / 100 * config.l_h_target - print(f"{config.l_h_target = }") - print(f"{l_h_absolute_error = }") - - l_s_absolute_error = percent_error_difference_l_s12 / 100 * config.l_s12_target - print(f"{config.l_s12_target = }") - print(f"{l_s_absolute_error = }") - - fig = optuna.visualization.plot_pareto_front(study, targets=lambda t: (t.values[0] if -l_h_absolute_error < t.values[2] < l_h_absolute_error else None, t.values[1] if -l_s_absolute_error < t.values[3] < l_s_absolute_error else None), target_names=["volume in m³", "loss in W"]) - fig.update_layout( - title=f"{study_name}: Filtering l_h_absolute_error = {l_h_absolute_error * 100} %, l_s_absolute_error = {l_s_absolute_error * 100} %.") - fig.write_html( - f"{config.working_directory}/{study_name}_error_lh_{l_h_absolute_error}_error_ls_{l_s_absolute_error}_{datetime.datetime.now().isoformat(timespec='minutes')}.html") - fig.show() - - @staticmethod - def show_study_results3(study_name: str, config: StoSingleInputConfig, - error_difference_inductance_sum, storage: str = 'sqlite') -> None: - """ - Show the results of a study. - - A local .html file is generated under config.working_directory to store the interactive plotly plots on disk. - - :param study_name: Name of the study - :type study_name: str - :param config: Integrated transformer configuration file - :type config: ItoSingleInputConfig - :param error_difference_inductance_sum: e.g. 0.05 for 5% - :type error_difference_inductance_sum: float - - """ - if storage == 'sqlite': - storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" - - time_start = datetime.datetime.now() - print(f"Start loading study {study_name} from database") - study = optuna.load_study(study_name=study_name, - storage=storage) - - print(f"Loaded study {study_name} contains {len(study.trials)} trials.") - time_stop = datetime.datetime.now() - print(f"Finished loading study {study_name} from database in time: {time_stop - time_start}") - - print(f"{error_difference_inductance_sum = }") - - time_start = datetime.datetime.now() - print(f"start generating Pareto front....") - fig = optuna.visualization.plot_pareto_front(study, targets=lambda t: (t.values[0] if error_difference_inductance_sum > t.values[2] else None, t.values[1] if error_difference_inductance_sum > t.values[2] else None), target_names=["volume in m³", "loss in W"]) - fig.update_layout(title=f"{study_name}: Filtering {error_difference_inductance_sum * 100} % of |err(Ls_12)| + |err(L_h)|") - time_stop = datetime.datetime.now() - print(f"Finished generating Pareto front in time: {time_stop - time_start}") - - fig.write_html(f"{config.working_directory}/{study_name}_error_diff_{error_difference_inductance_sum}_{datetime.datetime.now().isoformat(timespec='minutes')}.html") - fig.show() - - @staticmethod - def re_simulate_single_result(study_name: str, config: StoSingleInputConfig, number_trial: int, - fft_filter_value_factor: float = 0.01, mesh_accuracy: float = 0.5, - storage: str = "sqlite"): - """ - Performs a single simulation study (inductance, core loss, winding loss) and shows the geometry of - number_trial design inside the study 'study_name'. - - Note: This function does not use the fft_filter_value_factor and mesh_accuracy from the config-file. - The values are given separate. In case of re-simulation, you may want to have more accurate results. - - :param study_name: name of the study - :type study_name: str - :param config: stacked transformer configuration file - :type config: StoSingleInputConfig - :param number_trial: number of trial to simulate - :type number_trial: int - :param fft_filter_value_factor: Factor to filter frequencies from the fft. E.g. 0.01 [default] removes all amplitudes below 1 % of the maximum amplitude from the result-frequency list - :type fft_filter_value_factor: float - :param mesh_accuracy: a mesh_accuracy of 0.5 is recommended. Do not change this parameter, except performing thousands of simulations, e.g. a Pareto optimization. In this case, the value can be set e.g. to 0.8 - :type mesh_accuracy: float - :param storage: storage of the study - :type storage: str - """ - target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) - - if storage == "sqlite": - storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" - - time_start = datetime.datetime.now() - print(f"Start loading study {study_name} from database") - loaded_study = optuna.create_study(study_name=study_name, - storage=storage, - load_if_exists=True) - - print(f"Loaded study {study_name} contains {len(loaded_study.trials)} trials.") - time_stop = datetime.datetime.now() - print(f"Finished loading study {study_name} from database in time: {time_stop - time_start}") - - loaded_trial = loaded_study.trials[number_trial] - loaded_trial_params = loaded_trial.params + @staticmethod + def objective(trial, config: StoSingleInputConfig, + target_and_fixed_parameters: StoTargetAndFixedParameters, + number_objectives: int, show_geometries: bool = False, process_number: int = 1): + """ + Objective for optuna optimization. + + :param trial: optuna trail objective. Used by optuna + :param config: simulation configuration file + :type config: StoSingleInputConfig + :param target_and_fixed_parameters: contains pre-calculated values + :type target_and_fixed_parameters: StoTargetAndFixedParameters + :param number_objectives: number of objectives to give different target output parameters + :type number_objectives: int + :param show_geometries: True to display the geometries + :type show_geometries: bool + :param process_number: process number. Important for parallel computing, each simulation works in a separate folder + :type process_number: int + """ + # suggest core geometry + core_inner_diameter = trial.suggest_float("core_inner_diameter", config.core_inner_diameter_min_max_list[0], config.core_inner_diameter_min_max_list[1]) + window_w = trial.suggest_float("window_w", config.window_w_min_max_list[0], config.window_w_min_max_list[1]) + air_gap_transformer = trial.suggest_float("air_gap_transformer", 0.1e-3, 5e-3) - # suggest core geometry - core_inner_diameter = loaded_trial_params["core_inner_diameter"] - window_w = loaded_trial_params["window_w"] - air_gap_transformer = loaded_trial_params["air_gap_transformer"] - # inner_coil_insulation = trial_params["inner_coil_insulation"] - iso_left_core = loaded_trial_params["iso_left_core"] + # suggest secondary / tertiary inner winding radius + iso_left_core = trial.suggest_float("iso_left_core", config.insulations.iso_left_core_min, config.insulations.iso_primary_inner_bobbin) - primary_litz_wire = loaded_trial_params["primary_litz_wire"] + primary_additional_bobbin = config.insulations.iso_primary_inner_bobbin - iso_left_core - primary_litz_parameters = ff.litz_database()[primary_litz_wire] - primary_litz_diameter = 2 * primary_litz_parameters["conductor_radii"] + primary_litz_wire = trial.suggest_categorical("primary_litz_wire", config.primary_litz_wire_list) - # Will always be calculated from the given parameters - available_width = window_w - iso_left_core - config.insulations.iso_right_core + primary_litz_parameters = ff.litz_database()[primary_litz_wire] + primary_litz_diameter = 2 * primary_litz_parameters["conductor_radii"] - # Re-calculation of top window coil - # Theoretically also 0 coil turns possible (number_rows_coil_winding must then be recalculated to avoid neg. values) - primary_coil_turns = loaded_trial_params["primary_coil_turns"] - # Note: int() is used to round down. - number_rows_coil_winding = int((primary_coil_turns * (primary_litz_diameter + config.insulations.iso_primary_to_primary) - config.insulations.iso_primary_inner_bobbin) / available_width) + 1 - window_h_top = config.insulations.iso_top_core + config.insulations.iso_bot_core + number_rows_coil_winding * primary_litz_diameter + ( - number_rows_coil_winding - 1) * config.insulations.iso_primary_to_primary + # Will always be calculated from the given parameters + available_width = window_w - iso_left_core - config.insulations.iso_right_core - primary_additional_bobbin = config.insulations.iso_primary_inner_bobbin - iso_left_core + # Suggestion of top window coil + # Theoretically also 0 coil turns possible (number_rows_coil_winding must then be recalculated to avoid neg. values) + primary_coil_turns = trial.suggest_int("primary_coil_turns", config.primary_coil_turns_min_max_list[0], config.primary_coil_turns_min_max_list[1]) + # Note: int() is used to round down. + number_rows_coil_winding = int((primary_coil_turns * (primary_litz_diameter + config.insulations.iso_primary_to_primary) - iso_left_core) / available_width) + 1 + window_h_top = config.insulations.iso_top_core + config.insulations.iso_bot_core + number_rows_coil_winding * primary_litz_diameter + (number_rows_coil_winding - 1) * config.insulations.iso_primary_to_primary - # Maximum coil air gap depends on the maximum window height top - air_gap_coil = loaded_trial_params["air_gap_coil"] + # Maximum coil air gap depends on the maximum window height top + air_gap_coil = trial.suggest_float("air_gap_coil", 0.1e-3, window_h_top - 0.1e-3) - # suggest categorical - core_material = Material(loaded_trial_params["material"]) - foil_thickness = loaded_trial_params["foil_thickness"] + # suggest categorical + core_material = trial.suggest_categorical("material", config.material_list) + foil_thickness = trial.suggest_categorical("foil_thickness", config.metal_sheet_thickness_list) + interleaving_scheme = trial.suggest_categorical("interleaving_scheme", config.interleaving_scheme_list) + interleaving_type = trial.suggest_categorical("interleaving_type", config.interleaving_type_list) + try: if config.max_transformer_total_height is not None: # Maximum transformer height window_h_bot_max = config.max_transformer_total_height - 3 * core_inner_diameter / 4 - window_h_top @@ -655,22 +161,34 @@ def re_simulate_single_result(study_name: str, config: StoSingleInputConfig, num print(f"{window_h_top = }") raise ValueError(f"{window_h_bot_min = } > {window_h_bot_max = }") - window_h_bot = loaded_trial_params["window_h_bot"] + window_h_bot = trial.suggest_float("window_h_bot", window_h_bot_min, window_h_bot_max) else: - window_h_bot = loaded_trial_params["window_h_bot"] + window_h_bot = trial.suggest_float("window_h_bot", config.window_h_bot_min_max_list[0], + config.window_h_bot_min_max_list[1]) + + if show_geometries: + verbosity = femmt.Verbosity.ToConsole + else: + verbosity = femmt.Verbosity.Silent + + working_directory_single_process = os.path.join(target_and_fixed_parameters.working_directories.fem_working_directory, f"process_{process_number}") geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, - working_directory=target_and_fixed_parameters.working_directories.fem_working_directory, - verbosity=femmt.Verbosity.Silent, simulation_name=f"Single_Case_{loaded_trial._trial_id - 1}") - # Note: The _trial_id starts counting from 1, while the normal cases count from zero. So a correction needs to be made + working_directory=working_directory_single_process, + verbosity=verbosity, simulation_name=f"Case_{trial.number}") + + electro_magnetic_directory_single_process = os.path.join(working_directory_single_process, "electro_magnetic") + strands_coefficients_folder_single_process = os.path.join(electro_magnetic_directory_single_process, "Strands_Coefficients") + + # Update directories for each model + geo.file_data.update_paths(working_directory_single_process, electro_magnetic_directory_single_process, + strands_coefficients_folder_single_process) core_dimensions = femmt.dtos.StackedCoreDimensions(core_inner_diameter=core_inner_diameter, window_w=window_w, window_h_top=window_h_top, window_h_bot=window_h_bot) - core = femmt.Core(core_type=femmt.CoreType.Stacked, core_dimensions=core_dimensions, - material=core_material, temperature=config.temperature, - frequency=target_and_fixed_parameters.fundamental_frequency, + material=core_material, temperature=config.temperature, frequency=target_and_fixed_parameters.fundamental_frequency, permeability_datasource=config.permeability_datasource, permeability_datatype=config.permeability_datatype, permeability_measurement_setup=config.permeability_measurement_setup, @@ -701,8 +219,10 @@ def re_simulate_single_result(study_name: str, config: StoSingleInputConfig, num secondary_thickness_foil=foil_thickness, # insulation - iso_top_core=config.insulations.iso_top_core, iso_bot_core=config.insulations.iso_bot_core, - iso_left_core=iso_left_core, iso_right_core=config.insulations.iso_right_core, + iso_top_core=config.insulations.iso_top_core, + iso_bot_core=config.insulations.iso_bot_core, + iso_left_core=iso_left_core, + iso_right_core=config.insulations.iso_right_core, iso_primary_to_primary=config.insulations.iso_primary_to_primary, iso_secondary_to_secondary=config.insulations.iso_secondary_to_secondary, iso_primary_to_secondary=config.insulations.iso_primary_to_secondary, @@ -711,29 +231,805 @@ def re_simulate_single_result(study_name: str, config: StoSingleInputConfig, num bobbin_coil_left=config.insulations.iso_primary_inner_bobbin, bobbin_coil_right=config.insulations.iso_right_core, center_foil_additional_bobbin=0e-3, - interleaving_scheme=InterleavingSchemesFoilLitz(loaded_trial_params['interleaving_scheme']), + interleaving_scheme=interleaving_scheme, # misc - interleaving_type=CenterTappedInterleavingType(loaded_trial_params['interleaving_type']), + interleaving_type=interleaving_type, primary_coil_turns=primary_coil_turns, winding_temperature=config.temperature) geo.set_insulation(insulation) - geo.set_winding_windows([coil_window, transformer_window], mesh_accuracy=mesh_accuracy) - - geo.create_model(freq=target_and_fixed_parameters.fundamental_frequency, pre_visualize_geometry=True) + geo.set_winding_windows([coil_window, transformer_window], config.mesh_accuracy) - # geo.single_simulation(freq=target_and_fixed_parameters.fundamental_frequency, - # current=[target_and_fixed_parameters.i_peak_1, target_and_fixed_parameters.i_peak_2 / 2, target_and_fixed_parameters.i_peak_2 / 2], - # phi_deg=[target_and_fixed_parameters.i_phase_deg_1, target_and_fixed_parameters.i_phase_deg_2, target_and_fixed_parameters.i_phase_deg_2], - # show_fem_simulation_results=False) + geo.create_model(freq=target_and_fixed_parameters.fundamental_frequency, pre_visualize_geometry=show_geometries) center_tapped_study_excitation = geo.center_tapped_pre_study( - time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, - target_and_fixed_parameters.current_extracted_1_vec], - [target_and_fixed_parameters.time_extracted_vec, - target_and_fixed_parameters.current_extracted_2_vec]], - fft_filter_value_factor=fft_filter_value_factor) - - geo.stacked_core_center_tapped_study(center_tapped_study_excitation, - number_primary_coil_turns=primary_coil_turns) + time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_1_vec], [target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_2_vec]], + fft_filter_value_factor=config.fft_filter_value_factor) + + geo.stacked_core_center_tapped_study(center_tapped_study_excitation, number_primary_coil_turns=primary_coil_turns) + + # geo.stacked_core_center_tapped_study(time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_1_vec], + # [target_and_fixed_parameters.time_extracted_vec, target_and_fixed_parameters.current_extracted_2_vec]], + # plot_waveforms=False) + + # copy result files to result-file folder + source_json_file = os.path.join( + target_and_fixed_parameters.working_directories.fem_working_directory, f'process_{process_number}', + "results", "log_electro_magnetic.json") + destination_json_file = os.path.join( + target_and_fixed_parameters.working_directories.fem_simulation_results_directory, + f'case_{trial.number}.json') + + shutil.copy(source_json_file, destination_json_file) + + # read result-log + with open(source_json_file, "r") as fd: + loaded_data_dict = json.loads(fd.read()) + + total_volume = loaded_data_dict["misc"]["core_2daxi_total_volume"] + total_loss = loaded_data_dict["total_losses"]["total_losses"] + total_cost = loaded_data_dict["misc"]["total_cost_incl_margin"] + + # Get inductance values + difference_l_h = config.l_h_target - geo.L_h + difference_l_s12 = config.l_s12_target - geo.L_s12 + + trial.set_user_attr("l_h", geo.L_h) + trial.set_user_attr("l_s12", geo.L_s12) + + # TODO: Normalize on goal values here or the whole generation on min and max? for each feature inbetween 0 and 1 + # norm_total_loss, norm_difference_l_h, norm_difference_l_s12 = total_loss/10, abs(difference_l_h/config.l_h_target), abs(difference_l_s12/config.l_s12_target) + # return norm_total_loss, norm_difference_l_h, norm_difference_l_s12 + if number_objectives == 3: + return total_volume, total_loss, abs(difference_l_h / config.l_h_target) + abs( + difference_l_s12 / config.l_s12_target) + elif number_objectives == 4: + return total_volume, total_loss, abs(difference_l_h), abs(difference_l_s12) + + except Exception as e: + print(e) + if number_objectives == 3: + return float('nan'), float('nan'), float('nan') + elif number_objectives == 4: + return float('nan'), float('nan'), float('nan'), float('nan') + + @staticmethod + def start_proceed_study(study_name: str, config: StoSingleInputConfig, number_trials: int, + end_time: datetime.datetime = datetime.datetime.now(), + number_objectives: int = None, + storage: str = 'sqlite', + sampler=optuna.samplers.NSGAIISampler(), + show_geometries: bool = False, + ) -> None: + """ + Proceed a study which is stored as sqlite database. + + :param study_name: Name of the study + :type study_name: str + :param config: Simulation configuration + :type config: ItoSingleInputConfig + :param number_trials: Number of trials adding to the existing study + :type number_trials: int + :param number_objectives: number of objectives, e.g. 3 or 4 + :type number_objectives: int + :param storage: storage database, e.g. 'sqlite' or 'mysql' + :type storage: str + :param sampler: optuna.samplers.NSGAIISampler() or optuna.samplers.NSGAIIISampler(). Note about the brackets () !! + :type sampler: optuna.sampler-object + :param show_geometries: True to show the geometry of each suggestion (with valid geometry data) + :type show_geometries: bool + :param end_time: datetime object with the end time of simulation. If the end_time is not reached, a new simulation with number_objectives is started, e.g. datetime.datetime(2023,9,1,13,00) 2023-09-01, 13.00 + :type end_time: datetime.datetime + """ + def objective_directions(number_objectives: int): + """ + Checks if the number of objectives is correct and returns the minimizing targets + :param number_objectives: number of objectives + :type number_objectives: int + :returns: objective targets and optimization function + """ + if number_objectives == 3: + # Wrap the objective inside a lambda and call objective inside it + return ["minimize", "minimize", "minimize"] + if number_objectives == 4: + return ["minimize", "minimize", "minimize", 'minimize'] + else: + raise ValueError("Invalid objective number.") + + if os.path.exists(f"{config.working_directory}/study_{study_name}.sqlite3"): + print("Existing study found. Proceeding.") + + target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) + + # introduce study in storage, e.g. sqlite or mysql + if storage == 'sqlite': + # Note: for sqlite operation, there needs to be three slashes '///' even before the path '/home/...' + # Means, in total there are four slashes including the path itself '////home/.../database.sqlite3' + storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" + elif storage == 'mysql': + storage = "mysql://monty@localhost/mydb", + + # set logging verbosity: https://optuna.readthedocs.io/en/stable/reference/generated/optuna.logging.set_verbosity.html#optuna.logging.set_verbosity + # .INFO: all messages (default) + # .WARNING: fails and warnings + # .ERROR: only errors + # optuna.logging.set_verbosity(optuna.logging.ERROR) + + directions = objective_directions(number_objectives) + + func = lambda \ + trial: femmt.optimization.StackedTransformerOptimization.objective( + trial, config, + target_and_fixed_parameters, number_objectives, show_geometries) + + if (end_time + datetime.timedelta(seconds=10)) < datetime.datetime.now(): + raise ValueError("May wrong set end time?" + f"\nCurrent time: {datetime.datetime.now()}" + f"\nEnd time: {end_time}") + elif end_time < datetime.datetime.now() + datetime.timedelta(seconds=10): + print("start simulation") + # in case of no given end_time, the end_time is one second after now. + end_time = datetime.datetime.now() + datetime.timedelta(seconds=0.001) + else: + pass + + while datetime.datetime.now() < end_time: + print(f"Performing another {number_trials} trials.") + + study_in_storage = optuna.create_study(study_name=study_name, + storage=storage, + directions=directions, + load_if_exists=True, sampler=sampler) + + study_in_memory = optuna.create_study(directions=directions, study_name=study_name, sampler=sampler) + print(f"Sampler is {study_in_memory.sampler.__class__.__name__}") + study_in_memory.add_trials(study_in_storage.trials) + study_in_memory.optimize(func, n_trials=number_trials, show_progress_bar=True) + + study_in_storage.add_trials(study_in_memory.trials[-number_trials:]) + print(f"Finished {number_trials} trials.") + print(f"current time: {datetime.datetime.now()}") + print(f"end time: {end_time}") + + @staticmethod + def proceed_multi_core_study(study_name: str, config: StoSingleInputConfig, number_trials: int, + end_time: datetime.datetime = datetime.datetime.now(), + number_objectives: int = None, + storage: str = "mysql://monty@localhost/mydb", + sampler=optuna.samplers.NSGAIISampler(), + show_geometries: bool = False, + process_number: int = 1, + ) -> None: + """ + Proceed a study which can be paralleled. It is highly recommended to use a mysql-database (or mariadb). + + :param study_name: Name of the study + :type study_name: str + :param config: Simulation configuration + :type config: ItoSingleInputConfig + :param number_trials: Number of trials adding to the existing study + :type number_trials: int + :param number_objectives: number of objectives, e.g. 3 or 4 + :type number_objectives: int + :param storage: storage database, e.g. 'sqlite' or mysql-storage, e.g. "mysql://monty@localhost/mydb" + :type storage: str + :param sampler: optuna.samplers.NSGAIISampler() or optuna.samplers.NSGAIIISampler(). Note about the brackets () !! + :type sampler: optuna.sampler-object + :param show_geometries: True to show the geometry of each suggestion (with valid geometry data) + :type show_geometries: bool + :param end_time: datetime object with the end time of simulation. If the end_time is not reached, a new simulation with number_objectives is started, e.g. datetime.datetime(2023,9,1,13,00) 2023-09-01, 13.00 + :type end_time: datetime.datetime + :type process_number: number of the process, mandatory to split this up for several processes, because they use the same simulation result folder! + :param process_number: int + """ + def objective_directions(number_objectives: int): + """ + Checks if the number of objectives is correct and returns the minimizing targets + :param number_objectives: number of objectives + :type number_objectives: int + :returns: objective targets and optimization function + """ + if number_objectives == 3: + # Wrap the objective inside a lambda and call objective inside it + return ["minimize", "minimize", "minimize"] + if number_objectives == 4: + return ["minimize", "minimize", "minimize", 'minimize'] + else: + raise ValueError("Invalid objective number.") + + # introduce study in storage, e.g. sqlite or mysql + if storage == 'sqlite': + # Note: for sqlite operation, there needs to be three slashes '///' even before the path '/home/...' + # Means, in total there are four slashes including the path itself '////home/.../database.sqlite3' + storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" + elif storage == 'mysql': + storage = "mysql://monty@localhost/mydb", + + target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) + + # set logging verbosity: https://optuna.readthedocs.io/en/stable/reference/generated/optuna.logging.set_verbosity.html#optuna.logging.set_verbosity + # .INFO: all messages (default) + # .WARNING: fails and warnings + # .ERROR: only errors + # optuna.logging.set_verbosity(optuna.logging.ERROR) + + directions = objective_directions(number_objectives) + + func = lambda \ + trial: femmt.optimization.StackedTransformerOptimization.objective( + trial, config, + target_and_fixed_parameters, number_objectives, show_geometries, process_number) + + if (end_time + datetime.timedelta(seconds=10)) < datetime.datetime.now(): + raise ValueError("May wrong set end time?" + f"\nCurrent time: {datetime.datetime.now()}" + f"\nEnd time: {end_time}") + elif end_time < datetime.datetime.now() + datetime.timedelta(seconds=10): + print("start simulation") + # in case of no given end_time, the end_time is one second after now. + end_time = datetime.datetime.now() + datetime.timedelta(seconds=1) + else: + pass + + while datetime.datetime.now() < end_time: + print(f"current time: {datetime.datetime.now()}") + print(f"end time: {end_time}") + print(f"Performing another {number_trials} trials.") + + study_in_database = optuna.create_study(study_name=study_name, + storage=storage, + directions=directions, + load_if_exists=True, sampler=sampler) + + study_in_database.optimize(func, n_trials=number_trials, show_progress_bar=True) + + @staticmethod + def show_study_results(study_name: str, config: StoSingleInputConfig, + percent_error_difference_l_h: float = 20, + percent_error_difference_l_s12: float = 20) -> None: + """ + Show the results of a study. + + A local .html file is generated under config.working_directory to store the interactive plotly plots on disk. + + :param study_name: Name of the study + :type study_name: str + :param config: Integrated transformer configuration file + :type config: ItoSingleInputConfig + :param percent_error_difference_l_h: relative error allowed in l_h + :type percent_error_difference_l_s12: float + :param percent_error_difference_l_s12: relative error allowed in L_s12 + :type percent_error_difference_l_s12: float + """ + study = optuna.create_study(study_name=study_name, + storage=f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3", + load_if_exists=True) + + # Order: total_volume, total_loss, difference_l_h, difference_l_s + l_h_absolute_error = percent_error_difference_l_h / 100 * config.l_h_target + print(f"{config.l_h_target = }") + print(f"{l_h_absolute_error = }") + + l_s_absolute_error = percent_error_difference_l_s12 / 100 * config.l_s12_target + print(f"{config.l_s12_target = }") + print(f"{l_s_absolute_error = }") + + fig = optuna.visualization.plot_pareto_front(study, targets=lambda t: (t.values[0] if -l_h_absolute_error < t.values[2] < l_h_absolute_error else None, t.values[1] if -l_s_absolute_error < t.values[3] < l_s_absolute_error else None), target_names=["volume in m³", "loss in W"]) + fig.update_layout( + title=f"{study_name}: Filtering l_h_absolute_error = {l_h_absolute_error * 100} %, l_s_absolute_error = {l_s_absolute_error * 100} %.") + fig.write_html( + f"{config.working_directory}/{study_name}_error_lh_{l_h_absolute_error}_error_ls_{l_s_absolute_error}_{datetime.datetime.now().isoformat(timespec='minutes')}.html") + fig.show() + + @staticmethod + def show_study_results3(study_name: str, config: StoSingleInputConfig, + error_difference_inductance_sum, storage: str = 'sqlite') -> None: + """ + Show the results of a study. + + A local .html file is generated under config.working_directory to store the interactive plotly plots on disk. + + :param study_name: Name of the study + :type study_name: str + :param config: Integrated transformer configuration file + :type config: ItoSingleInputConfig + :param error_difference_inductance_sum: e.g. 0.05 for 5% + :type error_difference_inductance_sum: float + :param storage: storage, e.g. 'sqlite' or path to postgresql-database + :type storage: str + + """ + if storage == 'sqlite': + storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" + + time_start = datetime.datetime.now() + print(f"Start loading study {study_name} from database") + study = optuna.load_study(study_name=study_name, + storage=storage) + + print(f"Loaded study {study_name} contains {len(study.trials)} trials.") + time_stop = datetime.datetime.now() + print(f"Finished loading study {study_name} from database in time: {time_stop - time_start}") + + print(f"{error_difference_inductance_sum = }") + + time_start = datetime.datetime.now() + print(f"start generating Pareto front....") + fig = optuna.visualization.plot_pareto_front(study, targets=lambda t: (t.values[0] if error_difference_inductance_sum > t.values[2] else None, t.values[1] if error_difference_inductance_sum > t.values[2] else None), target_names=["volume in m³", "loss in W"]) + fig.update_layout(title=f"{study_name}: Filtering {error_difference_inductance_sum * 100} % of |err(Ls_12)| + |err(L_h)|") + time_stop = datetime.datetime.now() + print(f"Finished generating Pareto front in time: {time_stop - time_start}") + + fig.write_html(f"{config.working_directory}/{study_name}_error_diff_{error_difference_inductance_sum}_{datetime.datetime.now().isoformat(timespec='minutes')}.html") + fig.show() + + @staticmethod + def re_simulate_single_result(study_name: str, config: StoSingleInputConfig, number_trial: int, + fft_filter_value_factor: float = 0.01, mesh_accuracy: float = 0.5, + storage: str = "sqlite"): + """ + Performs a single simulation study (inductance, core loss, winding loss) and shows the geometry of + number_trial design inside the study 'study_name'. Loads from an optuna study and is very slow. + + Note: This function does not use the fft_filter_value_factor and mesh_accuracy from the config-file. + The values are given separate. In case of re-simulation, you may want to have more accurate results. + + :param study_name: name of the study + :type study_name: str + :param config: stacked transformer configuration file + :type config: StoSingleInputConfig + :param number_trial: number of trial to simulate + :type number_trial: int + :param fft_filter_value_factor: Factor to filter frequencies from the fft. E.g. 0.01 [default] removes all amplitudes below 1 % of the maximum amplitude from the result-frequency list + :type fft_filter_value_factor: float + :param mesh_accuracy: a mesh_accuracy of 0.5 is recommended. Do not change this parameter, except performing thousands of simulations, e.g. a Pareto optimization. In this case, the value can be set e.g. to 0.8 + :type mesh_accuracy: float + :param storage: storage of the study + :type storage: str + """ + target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) + + if storage == "sqlite": + storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" + + time_start = datetime.datetime.now() + print(f"Start loading study {study_name} from database") + loaded_study = optuna.create_study(study_name=study_name, + storage=storage, + load_if_exists=True) + + print(f"Loaded study {study_name} contains {len(loaded_study.trials)} trials.") + time_stop = datetime.datetime.now() + print(f"Finished loading study {study_name} from database in time: {time_stop - time_start}") + + loaded_trial = loaded_study.trials[number_trial] + loaded_trial_params = loaded_trial.params + + # suggest core geometry + core_inner_diameter = loaded_trial_params["core_inner_diameter"] + window_w = loaded_trial_params["window_w"] + air_gap_transformer = loaded_trial_params["air_gap_transformer"] + # inner_coil_insulation = trial_params["inner_coil_insulation"] + iso_left_core = loaded_trial_params["iso_left_core"] + + primary_litz_wire = loaded_trial_params["primary_litz_wire"] + + primary_litz_parameters = ff.litz_database()[primary_litz_wire] + primary_litz_diameter = 2 * primary_litz_parameters["conductor_radii"] + + # Will always be calculated from the given parameters + available_width = window_w - iso_left_core - config.insulations.iso_right_core + + # Re-calculation of top window coil + # Theoretically also 0 coil turns possible (number_rows_coil_winding must then be recalculated to avoid neg. values) + primary_coil_turns = loaded_trial_params["primary_coil_turns"] + # Note: int() is used to round down. + number_rows_coil_winding = int((primary_coil_turns * (primary_litz_diameter + config.insulations.iso_primary_to_primary) - config.insulations.iso_primary_inner_bobbin) / available_width) + 1 + window_h_top = config.insulations.iso_top_core + config.insulations.iso_bot_core + number_rows_coil_winding * primary_litz_diameter + ( + number_rows_coil_winding - 1) * config.insulations.iso_primary_to_primary + + primary_additional_bobbin = config.insulations.iso_primary_inner_bobbin - iso_left_core + + # Maximum coil air gap depends on the maximum window height top + air_gap_coil = loaded_trial_params["air_gap_coil"] + + # suggest categorical + core_material = Material(loaded_trial_params["material"]) + foil_thickness = loaded_trial_params["foil_thickness"] + + if config.max_transformer_total_height is not None: + # Maximum transformer height + window_h_bot_max = config.max_transformer_total_height - 3 * core_inner_diameter / 4 - window_h_top + window_h_bot_min = config.window_h_bot_min_max_list[0] + if window_h_bot_min > window_h_bot_max: + print(f"{number_rows_coil_winding = }") + print(f"{window_h_top = }") + raise ValueError(f"{window_h_bot_min = } > {window_h_bot_max = }") + + window_h_bot = loaded_trial_params["window_h_bot"] + + else: + window_h_bot = loaded_trial_params["window_h_bot"] + + geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, + working_directory=target_and_fixed_parameters.working_directories.fem_working_directory, + verbosity=femmt.Verbosity.Silent, simulation_name=f"Single_Case_{loaded_trial._trial_id - 1}") + # Note: The _trial_id starts counting from 1, while the normal cases count from zero. So a correction needs to be made + + core_dimensions = femmt.dtos.StackedCoreDimensions(core_inner_diameter=core_inner_diameter, window_w=window_w, + window_h_top=window_h_top, window_h_bot=window_h_bot) + + core = femmt.Core(core_type=femmt.CoreType.Stacked, core_dimensions=core_dimensions, + material=core_material, temperature=config.temperature, + frequency=target_and_fixed_parameters.fundamental_frequency, + permeability_datasource=config.permeability_datasource, + permeability_datatype=config.permeability_datatype, + permeability_measurement_setup=config.permeability_measurement_setup, + permittivity_datasource=config.permittivity_datasource, + permittivity_datatype=config.permittivity_datatype, + permittivity_measurement_setup=config.permittivity_measurement_setup) + + geo.set_core(core) + + air_gaps = femmt.AirGaps(femmt.AirGapMethod.Stacked, core) + air_gaps.add_air_gap(femmt.AirGapLegPosition.CenterLeg, air_gap_coil, stacked_position=femmt.StackedPosition.Top) + air_gaps.add_air_gap(femmt.AirGapLegPosition.CenterLeg, air_gap_transformer, stacked_position=femmt.StackedPosition.Bot) + geo.set_air_gaps(air_gaps) + + # set_center_tapped_windings() automatically places the condu + insulation, coil_window, transformer_window = femmt.functions_topologies.set_center_tapped_windings( + core=core, + + # primary litz + primary_additional_bobbin=primary_additional_bobbin, + primary_turns=config.n_target, + primary_radius=primary_litz_parameters["conductor_radii"], + primary_number_strands=primary_litz_parameters["strands_numbers"], + primary_strand_radius=primary_litz_parameters["strand_radii"], + + # secondary foil + secondary_parallel_turns=2, + secondary_thickness_foil=foil_thickness, + + # insulation + iso_top_core=config.insulations.iso_top_core, iso_bot_core=config.insulations.iso_bot_core, + iso_left_core=iso_left_core, iso_right_core=config.insulations.iso_right_core, + iso_primary_to_primary=config.insulations.iso_primary_to_primary, + iso_secondary_to_secondary=config.insulations.iso_secondary_to_secondary, + iso_primary_to_secondary=config.insulations.iso_primary_to_secondary, + bobbin_coil_top=config.insulations.iso_top_core, + bobbin_coil_bot=config.insulations.iso_bot_core, + bobbin_coil_left=config.insulations.iso_primary_inner_bobbin, + bobbin_coil_right=config.insulations.iso_right_core, + center_foil_additional_bobbin=0e-3, + interleaving_scheme=InterleavingSchemesFoilLitz(loaded_trial_params['interleaving_scheme']), + + # misc + interleaving_type=CenterTappedInterleavingType(loaded_trial_params['interleaving_type']), + primary_coil_turns=primary_coil_turns, + winding_temperature=config.temperature) + + geo.set_insulation(insulation) + geo.set_winding_windows([coil_window, transformer_window], mesh_accuracy=mesh_accuracy) + + geo.create_model(freq=target_and_fixed_parameters.fundamental_frequency, pre_visualize_geometry=True) + + # geo.single_simulation(freq=target_and_fixed_parameters.fundamental_frequency, + # current=[target_and_fixed_parameters.i_peak_1, target_and_fixed_parameters.i_peak_2 / 2, target_and_fixed_parameters.i_peak_2 / 2], + # phi_deg=[target_and_fixed_parameters.i_phase_deg_1, target_and_fixed_parameters.i_phase_deg_2, target_and_fixed_parameters.i_phase_deg_2], + # show_fem_simulation_results=False) + + center_tapped_study_excitation = geo.center_tapped_pre_study( + time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, + target_and_fixed_parameters.current_extracted_1_vec], + [target_and_fixed_parameters.time_extracted_vec, + target_and_fixed_parameters.current_extracted_2_vec]], + fft_filter_value_factor=fft_filter_value_factor) + + geo.stacked_core_center_tapped_study(center_tapped_study_excitation, + number_primary_coil_turns=primary_coil_turns) + + @staticmethod + def re_simulate_from_df(df: pd.DataFrame, config: StoSingleInputConfig, number_trial: int, + fft_filter_value_factor: float = 0.01, mesh_accuracy: float = 0.5, + show_simulation_results: bool = False): + """ + Performs a single simulation study (inductance, core loss, winding loss) and shows the geometry of + number_trial design inside the study 'study_name'. Loads from a pandas dataframe and is very fast. + + Note: This function does not use the fft_filter_value_factor and mesh_accuracy from the config-file. + The values are given separate. In case of re-simulation, you may want to have more accurate results. + + :param df: pandas dataframe with the loaded study + :type df: pandas dataframe + :param config: stacked transformer configuration file + :type config: StoSingleInputConfig + :param number_trial: number of trial to simulate + :type number_trial: int + :param fft_filter_value_factor: Factor to filter frequencies from the fft. E.g. 0.01 [default] removes all amplitudes below 1 % of the maximum amplitude from the result-frequency list + :type fft_filter_value_factor: float + :param mesh_accuracy: a mesh_accuracy of 0.5 is recommended. Do not change this parameter, except performing thousands of simulations, e.g. a Pareto optimization. In this case, the value can be set e.g. to 0.8 + :type mesh_accuracy: float + """ + target_and_fixed_parameters = femmt.optimization.StackedTransformerOptimization.calculate_fix_parameters(config) + + loaded_trial_params = df.iloc[number_trial] + + # suggest core geometry + core_inner_diameter = loaded_trial_params["params_core_inner_diameter"] + window_w = loaded_trial_params["params_window_w"] + air_gap_transformer = loaded_trial_params["params_air_gap_transformer"] + # inner_coil_insulation = trial_params["inner_coil_insulation"] + iso_left_core = loaded_trial_params["params_iso_left_core"] + + primary_litz_wire = loaded_trial_params["params_primary_litz_wire"] + + primary_litz_parameters = ff.litz_database()[primary_litz_wire] + primary_litz_diameter = 2 * primary_litz_parameters["conductor_radii"] + + # Will always be calculated from the given parameters + available_width = window_w - iso_left_core - config.insulations.iso_right_core + + # Re-calculation of top window coil + # Theoretically also 0 coil turns possible (number_rows_coil_winding must then be recalculated to avoid neg. values) + primary_coil_turns = int(loaded_trial_params["params_primary_coil_turns"]) + # Note: int() is used to round down. + number_rows_coil_winding = int((primary_coil_turns * (primary_litz_diameter + config.insulations.iso_primary_to_primary) - config.insulations.iso_primary_inner_bobbin) / available_width) + 1 + window_h_top = config.insulations.iso_top_core + config.insulations.iso_bot_core + number_rows_coil_winding * primary_litz_diameter + ( + number_rows_coil_winding - 1) * config.insulations.iso_primary_to_primary + + primary_additional_bobbin = config.insulations.iso_primary_inner_bobbin - iso_left_core + + # Maximum coil air gap depends on the maximum window height top + air_gap_coil = loaded_trial_params["params_air_gap_coil"] + + # suggest categorical + core_material = Material(loaded_trial_params["params_material"]) + foil_thickness = loaded_trial_params["params_foil_thickness"] + + if config.max_transformer_total_height is not None: + # Maximum transformer height + window_h_bot_max = config.max_transformer_total_height - 3 * core_inner_diameter / 4 - window_h_top + window_h_bot_min = config.window_h_bot_min_max_list[0] + if window_h_bot_min > window_h_bot_max: + print(f"{number_rows_coil_winding = }") + print(f"{window_h_top = }") + raise ValueError(f"{window_h_bot_min = } > {window_h_bot_max = }") + + window_h_bot = loaded_trial_params["params_window_h_bot"] + + else: + window_h_bot = loaded_trial_params["params_window_h_bot"] + + geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, + working_directory=target_and_fixed_parameters.working_directories.fem_working_directory, + verbosity=femmt.Verbosity.Silent, simulation_name=f"Single_Case_{loaded_trial_params['number']}") + # Note: The _trial_id starts counting from 1, while the normal cases count from zero. So a correction needs to be made + + core_dimensions = femmt.dtos.StackedCoreDimensions(core_inner_diameter=core_inner_diameter, window_w=window_w, + window_h_top=window_h_top, window_h_bot=window_h_bot) + + core = femmt.Core(core_type=femmt.CoreType.Stacked, core_dimensions=core_dimensions, + material=core_material, temperature=config.temperature, + frequency=target_and_fixed_parameters.fundamental_frequency, + permeability_datasource=config.permeability_datasource, + permeability_datatype=config.permeability_datatype, + permeability_measurement_setup=config.permeability_measurement_setup, + permittivity_datasource=config.permittivity_datasource, + permittivity_datatype=config.permittivity_datatype, + permittivity_measurement_setup=config.permittivity_measurement_setup) + + geo.set_core(core) + + air_gaps = femmt.AirGaps(femmt.AirGapMethod.Stacked, core) + air_gaps.add_air_gap(femmt.AirGapLegPosition.CenterLeg, air_gap_coil, stacked_position=femmt.StackedPosition.Top) + air_gaps.add_air_gap(femmt.AirGapLegPosition.CenterLeg, air_gap_transformer, stacked_position=femmt.StackedPosition.Bot) + geo.set_air_gaps(air_gaps) + + # set_center_tapped_windings() automatically places the condu + insulation, coil_window, transformer_window = femmt.functions_topologies.set_center_tapped_windings( + core=core, + + # primary litz + primary_additional_bobbin=primary_additional_bobbin, + primary_turns=config.n_target, + primary_radius=primary_litz_parameters["conductor_radii"], + primary_number_strands=primary_litz_parameters["strands_numbers"], + primary_strand_radius=primary_litz_parameters["strand_radii"], + + # secondary foil + secondary_parallel_turns=2, + secondary_thickness_foil=foil_thickness, + + # insulation + iso_top_core=config.insulations.iso_top_core, iso_bot_core=config.insulations.iso_bot_core, + iso_left_core=iso_left_core, iso_right_core=config.insulations.iso_right_core, + iso_primary_to_primary=config.insulations.iso_primary_to_primary, + iso_secondary_to_secondary=config.insulations.iso_secondary_to_secondary, + iso_primary_to_secondary=config.insulations.iso_primary_to_secondary, + bobbin_coil_top=config.insulations.iso_top_core, + bobbin_coil_bot=config.insulations.iso_bot_core, + bobbin_coil_left=config.insulations.iso_primary_inner_bobbin, + bobbin_coil_right=config.insulations.iso_right_core, + center_foil_additional_bobbin=0e-3, + interleaving_scheme=InterleavingSchemesFoilLitz(loaded_trial_params['params_interleaving_scheme']), + + # misc + interleaving_type=CenterTappedInterleavingType(loaded_trial_params['params_interleaving_type']), + primary_coil_turns=primary_coil_turns, + winding_temperature=config.temperature) + + geo.set_insulation(insulation) + geo.set_winding_windows([coil_window, transformer_window], mesh_accuracy=mesh_accuracy) + + geo.create_model(freq=target_and_fixed_parameters.fundamental_frequency, pre_visualize_geometry=show_simulation_results) + + #geo.single_simulation(freq=target_and_fixed_parameters.fundamental_frequency, + # current=[target_and_fixed_parameters.i_peak_1, target_and_fixed_parameters.i_peak_2 / 2, target_and_fixed_parameters.i_peak_2 / 2], + # phi_deg=[target_and_fixed_parameters.i_phase_deg_1, target_and_fixed_parameters.i_phase_deg_2, target_and_fixed_parameters.i_phase_deg_2], + # show_fem_simulation_results=show_simulation_results) + + center_tapped_study_excitation = geo.center_tapped_pre_study( + time_current_vectors=[[target_and_fixed_parameters.time_extracted_vec, + target_and_fixed_parameters.current_extracted_1_vec], + [target_and_fixed_parameters.time_extracted_vec, + target_and_fixed_parameters.current_extracted_2_vec]], + fft_filter_value_factor=fft_filter_value_factor) + + geo.stacked_core_center_tapped_study(center_tapped_study_excitation, + number_primary_coil_turns=primary_coil_turns) + + return geo + + @staticmethod + def thermal_simulation_from_geo(geo, thermal_config: ThermalConfig, flag_insulation: bool = False, + show_visual_outputs: bool = True): + """ + Performs the thermal simulation for the transformer. + + :param geo: geometry object + :type geo: femmt.Component + :param thermal_config: thermal configuration file + :type thermal_config: ThermalConfig + :param flag_insulation: True to simulate insulations. As insulations are not fully supported yet, + the insulation is set to false + :type flag_insulation: bool + :param show_visual_outputs: True to show visual simulation result + :type show_visual_outputs: bool + + """ + geo.thermal_simulation(thermal_config.thermal_conductivity_dict, thermal_config.boundary_temperatures, + thermal_config.boundary_flags, thermal_config.case_gap_top, + thermal_config.case_gap_right, thermal_config.case_gap_bot, + show_visual_outputs, color_scheme=femmt.colors_ba_jonas, + colors_geometry=femmt.colors_geometry_ba_jonas, flag_insulation=flag_insulation) + + @staticmethod + def df_plot_pareto_front(df: pd.DataFrame, sum_inductance_error: float): + """ + Plots an interactive Pareto diagram (losses vs. volume) to select the transformers to re-simulate. + + :param df: Dataframe, generated from an optuna study (exported by optuna) + :type df: pd.Dataframe + :param sum_inductance_error: maximum allowed error of |error(L_s12)| + |error(L_h)| + :type sum_inductance_error: float + """ + + print(df.head()) + df_pareto = df[df["values_2"] < sum_inductance_error] + + names = df_pareto["number"].to_numpy() + fig, ax = plt.subplots() + sc = plt.scatter(df_pareto["values_0"], df_pareto["values_1"], s=10) + + annot = ax.annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points", + bbox=dict(boxstyle="round", fc="w"), + arrowprops=dict(arrowstyle="->")) + annot.set_visible(False) + + def update_annot(ind): + pos = sc.get_offsets()[ind["ind"][0]] + annot.xy = pos + text = f"{[names[n] for n in ind['ind']]}" + annot.set_text(text) + annot.get_bbox_patch().set_alpha(0.4) + + def hover(event): + vis = annot.get_visible() + if event.inaxes == ax: + cont, ind = sc.contains(event) + if cont: + update_annot(ind) + annot.set_visible(True) + fig.canvas.draw_idle() + else: + if vis: + annot.set_visible(False) + fig.canvas.draw_idle() + + fig.canvas.mpl_connect("motion_notify_event", hover) + + plt.xlabel('Volume in m³') + plt.ylabel('Losses in W') + plt.title(f"|error(L_s12)| + |error(L_h)| < {sum_inductance_error} %") + plt.grid() + plt.show() + + @staticmethod + def create_full_report(df: pd.DataFrame, trials_numbers: list[int], config: StoSingleInputConfig, + thermal_config: ThermalConfig, + current_waveforms_operating_points: List[CurrentWorkingPoint], + fft_filter_value_factor: float = 0.01, mesh_accuracy: float = 0.5): + """ + Creates for several geometries and several working points a report. + Simulates magnetoquasistatic and thermal for all given geometries and current working points. + Summarizes the losses and temperatures for every working point and geometry. + + :param df: Dataframe, generated from an optuna study (exported by optuna) + :type df: pd.Dataframe + :param trials_numbers: List of trial numbers to re-simulate + :type trials_numbers: List[int] + :param config: stacked transformer optimization configuration file + :type config: StoSingleInputConfig + :param current_waveforms_operating_points: Trial numbers in a list to re-simulate + :type current_waveforms_operating_points: List[int] + :param fft_filter_value_factor: Factor to filter frequencies from the fft. E.g. 0.01 [default] removes all amplitudes below 1 % of the maximum amplitude from the result-frequency list + :type fft_filter_value_factor: float + :param mesh_accuracy: a mesh_accuracy of 0.5 is recommended. Do not change this parameter, except performing thousands of simulations, e.g. a Pareto optimization. In this case, the value can be set e.g. to 0.8 + :type mesh_accuracy: float + """ + + report_df = pd.DataFrame() + + for trial_number in trials_numbers: + simulation_waveforms_dict = {"trial": trial_number} + + for count_current_waveform, current_waveform in enumerate(current_waveforms_operating_points): + + # update currents in the config file + config.time_current_1_vec = current_waveforms_operating_points[ + count_current_waveform].time_current_1_vec + config.time_current_2_vec = current_waveforms_operating_points[ + count_current_waveform].time_current_2_vec + + # perform the electromagnetic simulation + geo_sim = femmt.StackedTransformerOptimization.re_simulate_from_df(df, config, + number_trial=trial_number, + show_simulation_results=False, + fft_filter_value_factor=fft_filter_value_factor, mesh_accuracy=mesh_accuracy) + # perform the thermal simulation + femmt.StackedTransformerOptimization.thermal_simulation_from_geo(geo_sim, thermal_config, show_visual_outputs=False) + + + electromagnetoquasistatic_result_dict = geo_sim.read_log() + thermal_result_dict = geo_sim.read_thermal_log() + + simulation_waveform_dict = {f"total_loss_{current_waveform.name}": electromagnetoquasistatic_result_dict["total_losses"]["total_losses"], + f"max_temp_core_{current_waveform.name}": thermal_result_dict["core_parts"]["total"]["max"], + f"max_temp_winding_{current_waveform.name}": thermal_result_dict["windings"]["total"]["max"], + "volume": electromagnetoquasistatic_result_dict["misc"]["core_2daxi_total_volume"] + } + + simulation_waveforms_dict.update(simulation_waveform_dict) + + simulation_df = pd.DataFrame(data=simulation_waveforms_dict, index=[0]) + + report_df = pd.concat([report_df, simulation_df], axis=0, ignore_index=True) + + report_df.to_csv(f'{config.working_directory}/summary.csv') + print(f"Report exported to {config.working_directory}/summary.csv") + + @staticmethod + def study_to_df(study_name: str, database_url: str): + """ + Creates a dataframe from a study. + + :param study_name: name of study + :type study_name: str + :param database_url: url of database + :type database_url: str + """ + loaded_study = optuna.create_study(study_name=study_name, storage=database_url, load_if_exists=True) + df = loaded_study.trials_dataframe() + df.to_csv() \ No newline at end of file diff --git a/femmt/optimization/sto_dtos.py b/femmt/optimization/sto_dtos.py index 52ad7678..7b3f279e 100644 --- a/femmt/optimization/sto_dtos.py +++ b/femmt/optimization/sto_dtos.py @@ -7,6 +7,7 @@ from materialdatabase.dtos import MaterialCurve from femmt.enumerations import * + @dataclass class StoInsulation: iso_top_core: float @@ -18,6 +19,7 @@ class StoInsulation: iso_primary_to_secondary: float iso_primary_inner_bobbin: float + @dataclass class StoSingleInputConfig: """ @@ -65,6 +67,16 @@ class StoSingleInputConfig: permittivity_measurement_setup: MeasurementSetup +@dataclass +class ThermalConfig: + thermal_conductivity_dict: dict + case_gap_top: float + case_gap_right: float + case_gap_bot: float + boundary_temperatures: dict + boundary_flags: dict + + @dataclass class WorkingDirectories: """ @@ -77,6 +89,7 @@ class WorkingDirectories: fem_thermal_simulation_results_directory: str fem_thermal_filtered_simulation_results_directory: str + @dataclass class StoTargetAndFixedParameters: """ @@ -96,3 +109,13 @@ class StoTargetAndFixedParameters: fundamental_frequency: float target_inductance_matrix: np.ndarray working_directories: WorkingDirectories + + +@dataclass +class CurrentWorkingPoint: + """ + Stores the working point of currents together with a human-readable name + """ + name: str + time_current_1_vec: np.ndarray | list + time_current_2_vec: np.ndarray | list diff --git a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle.json b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle.json index f692f1ad..30e83609 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle.json @@ -71,14 +71,18 @@ "all_windings": 0.4709150486451306, "eddy_core": 0.01571862829606287, "total_core_part_1": 0.47677033841409433, + "total_eddy_core_part_1": 0.00657139049155327, + "total_hyst_core_part_1": 0.4701989479225411, "total_core_part_2": 0.2075621117123285, + "total_eddy_core_part_2": 0.00914723780450959, + "total_hyst_core_part_2": 0.1984148739078189, "hyst_core_fundamental_freq": 0.6686138218303596, "core": 0.6843324501264224, "total_losses": 1.155247498771553 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:33:47", + "date": "2023-11-08 18:18:04", "component_type": "Inductor", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_horizontal.json b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_horizontal.json index 9d04a2de..02c05942 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_horizontal.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_horizontal.json @@ -43,6 +43,8 @@ "core_hyst_losses": 2.227725967733116, "core_parts": { "core_part_1": { + "eddy_losses": 0.05498444728977709, + "hyst_losses": 2.227725967733116, "total_core_part_1": 2.2827104150228927 } }, @@ -70,13 +72,15 @@ "all_windings": 1.371127986412009, "eddy_core": 0.05498444728977709, "total_core_part_1": 2.2827104150228927, + "total_eddy_core_part_1": 0.05498444728977709, + "total_hyst_core_part_1": 2.227725967733116, "hyst_core_fundamental_freq": 2.227725967733116, "core": 2.2827104150228927, "total_losses": 3.6538384014349017 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-08 12:49:22", + "date": "2023-11-08 19:08:12", "component_type": "Inductor", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_vertical.json b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_vertical.json index b35f5e61..f8357d11 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_vertical.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_foil_vertical.json @@ -36,6 +36,8 @@ "core_hyst_losses": 0.3805400084075918, "core_parts": { "core_part_1": { + "eddy_losses": 0.00934704409965321, + "hyst_losses": 0.3805400084075918, "total_core_part_1": 0.389887052507245 } }, @@ -56,13 +58,15 @@ "all_windings": 0.1272686604504369, "eddy_core": 0.00934704409965321, "total_core_part_1": 0.389887052507245, + "total_eddy_core_part_1": 0.00934704409965321, + "total_hyst_core_part_1": 0.3805400084075918, "hyst_core_fundamental_freq": 0.3805400084075918, "core": 0.389887052507245, "total_losses": 0.5171557129576818 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-08 12:47:07", + "date": "2023-11-08 19:07:10", "component_type": "Inductor", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_litz_wire.json b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_litz_wire.json index 71c8cb26..aa7ce3d3 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_litz_wire.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_fixed_loss_angle_litz_wire.json @@ -71,14 +71,18 @@ "all_windings": 0.1120278132410119, "eddy_core": 0.01606920614810965, "total_core_part_1": 0.4865760714240864, + "total_eddy_core_part_1": 0.006588393845274102, + "total_hyst_core_part_1": 0.4799876775788123, "total_core_part_2": 0.21548365847423454, + "total_eddy_core_part_2": 0.00948081230283554, + "total_hyst_core_part_2": 0.206002846171399, "hyst_core_fundamental_freq": 0.685990523750212, "core": 0.7020597298983217, "total_losses": 0.8140875431393335 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:34:18", + "date": "2023-11-08 18:30:17", "component_type": "Inductor", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material.json b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material.json index 96880cb2..12188dd6 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material.json @@ -71,14 +71,18 @@ "all_windings": 0.4706851404414779, "eddy_core": 0.005238272112541359, "total_core_part_1": 0.1387459737341604, + "total_eddy_core_part_1": 0.002189318573992297, + "total_hyst_core_part_1": 0.1365566551601681, "total_core_part_2": 0.0656497288201208, + "total_eddy_core_part_2": 0.003048953538549063, + "total_hyst_core_part_2": 0.06260077528157174, "hyst_core_fundamental_freq": 0.1991574304417403, "core": 0.20439570255428166, "total_losses": 0.6750808429957595 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:32:22", + "date": "2023-11-08 18:16:45", "component_type": "Inductor", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material_measurement.json b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material_measurement.json index 95ca768b..e15212c5 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material_measurement.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_inductor_core_material_measurement.json @@ -71,14 +71,18 @@ "all_windings": 0.4580489999164769, "eddy_core": 0.005970074514340104, "total_core_part_1": 0.29678762989474966, + "total_eddy_core_part_1": 0.002491994027811744, + "total_hyst_core_part_1": 0.2942956358669379, "total_core_part_2": 0.13298701155082568, + "total_eddy_core_part_2": 0.003478080486528357, + "total_hyst_core_part_2": 0.1295089310642973, "hyst_core_fundamental_freq": 0.4238045669312349, "core": 0.42977464144557503, "total_losses": 0.8878236413620519 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:33:17", + "date": "2023-11-08 18:17:34", "component_type": "Inductor", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_transformer_core_fixed_loss_angle.json b/tests/integration/fixtures/results/log_electro_magnetic_transformer_core_fixed_loss_angle.json index 17152892..317577c8 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_transformer_core_fixed_loss_angle.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_transformer_core_fixed_loss_angle.json @@ -76,6 +76,8 @@ "core_hyst_losses": 0.0302902495099854, "core_parts": { "core_part_1": { + "eddy_losses": 0.003742574947502717, + "hyst_losses": 0.0302902495099854, "total_core_part_1": 0.03403282445748812 } }, @@ -116,13 +118,15 @@ "all_windings": 6.80486776861339, "eddy_core": 0.003742574947502717, "total_core_part_1": 0.03403282445748812, + "total_eddy_core_part_1": 0.003742574947502717, + "total_hyst_core_part_1": 0.0302902495099854, "hyst_core_fundamental_freq": 0.0302902495099854, "core": 0.03403282445748812, "total_losses": 6.8389005930708775 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:37:40", + "date": "2023-11-08 19:09:03", "component_type": "Transformer", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_transformer_integrated_core_fixed_loss_angle.json b/tests/integration/fixtures/results/log_electro_magnetic_transformer_integrated_core_fixed_loss_angle.json index f76a2c10..5aeb22dc 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_transformer_integrated_core_fixed_loss_angle.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_transformer_integrated_core_fixed_loss_angle.json @@ -107,14 +107,18 @@ "all_windings": 1.3648193493804854, "eddy_core": 0.0008530748745437754, "total_core_part_1": 0.008215272937120988, + "total_eddy_core_part_1": 0.0007634983801010015, + "total_hyst_core_part_1": 0.007451774557019985, "total_core_part_2": 0.0004057344089877404, + "total_eddy_core_part_2": 8.957649444277318e-05, + "total_hyst_core_part_2": 0.0003161579145449672, "hyst_core_fundamental_freq": 0.007767932471564947, "core": 0.008621007346108722, "total_losses": 1.3734403567265943 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:38:43", + "date": "2023-11-08 18:19:56", "component_type": "IntegratedTransformer", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/log_electro_magnetic_transformer_interleaved_core_fixed_loss_angle.json b/tests/integration/fixtures/results/log_electro_magnetic_transformer_interleaved_core_fixed_loss_angle.json index 845f7590..31b12536 100644 --- a/tests/integration/fixtures/results/log_electro_magnetic_transformer_interleaved_core_fixed_loss_angle.json +++ b/tests/integration/fixtures/results/log_electro_magnetic_transformer_interleaved_core_fixed_loss_angle.json @@ -84,6 +84,8 @@ "core_hyst_losses": 0.003942034545531867, "core_parts": { "core_part_1": { + "eddy_losses": 0.000450816615417936, + "hyst_losses": 0.003942034545531867, "total_core_part_1": 0.004392851160949803 } }, @@ -132,13 +134,15 @@ "all_windings": 5.921688250167905, "eddy_core": 0.000450816615417936, "total_core_part_1": 0.004392851160949803, + "total_eddy_core_part_1": 0.000450816615417936, + "total_hyst_core_part_1": 0.003942034545531867, "hyst_core_fundamental_freq": 0.003942034545531867, "core": 0.004392851160949803, "total_losses": 5.926081101328855 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:38:15", + "date": "2023-11-08 19:09:46", "component_type": "Transformer", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": { diff --git a/tests/integration/fixtures/results/thermal_transformer_stacked_center_tapped.json b/tests/integration/fixtures/results/thermal_transformer_stacked_center_tapped.json index 4de6bb57..cf4beee8 100644 --- a/tests/integration/fixtures/results/thermal_transformer_stacked_center_tapped.json +++ b/tests/integration/fixtures/results/thermal_transformer_stacked_center_tapped.json @@ -1,136 +1,136 @@ { "core_parts": { "core_part_1": { - "min": 20.00504213231957, - "max": 20.01325173179831, - "mean": 20.011868279575204 + "min": 20.20735690100225, + "max": 20.58519186101613, + "mean": 20.392535615485325 }, "core_part_2": { - "min": 20.01310106835559, - "max": 20.01340445661551, - "mean": 20.01319044119993 + "min": 20.32091755132676, + "max": 20.38623908559262, + "mean": 20.349293283639025 }, "core_part_3": { - "min": 20.01137375994016, - "max": 20.01361493427859, - "mean": 20.012987179548873 + "min": 20.23611601138652, + "max": 20.34378222840463, + "mean": 20.2983571803395 }, "core_part_4": { - "min": 20.00964950153988, - "max": 20.01236281149684, - "mean": 20.01113339712848 + "min": 20.20074987963498, + "max": 20.32552218592907, + "mean": 20.25594365118811 }, "core_part_5": { - "min": 20.00723582242617, - "max": 20.01362332697256, - "mean": 20.012922449487938 + "min": 20.13343637150673, + "max": 20.25409521469128, + "mean": 20.236779638845164 }, "total": { - "min": 20.00504213231957, - "max": 20.01362332697256, - "mean": 20.012420349388083 + "min": 20.13343637150673, + "max": 20.58519186101613, + "mean": 20.306581873899425 } }, "windings": { "winding_0_0": { - "min": 20.01358884462404, - "max": 20.01365362185373, - "mean": 20.013633070907694 + "min": 20.24517882458543, + "max": 20.24633799637284, + "mean": 20.245790357546536 }, "winding_0_1": { - "min": 20.0133974957166, - "max": 20.01354954536033, - "mean": 20.01347443589006 + "min": 20.2434522100671, + "max": 20.24486257402297, + "mean": 20.24418282742253 }, "winding_0_2": { - "min": 20.01319892970511, - "max": 20.01335141944104, - "mean": 20.013270678039337 + "min": 20.24156720703316, + "max": 20.24307726657976, + "mean": 20.242330739951832 }, "winding_0_3": { - "min": 20.01298041445354, - "max": 20.01302950076595, - "mean": 20.01300556421406 + "min": 20.35164356332192, + "max": 20.35318336017489, + "mean": 20.352376104430515 }, "winding_0_4": { - "min": 20.01293841019297, - "max": 20.01299644952673, - "mean": 20.012968708199942 + "min": 20.35038775900153, + "max": 20.35165016253476, + "mean": 20.35100399348283 }, "winding_0_5": { - "min": 20.01286368331439, - "max": 20.01293826590749, - "mean": 20.01290327155653 + "min": 20.34933562917739, + "max": 20.35039484606665, + "mean": 20.34986416461715 }, "winding_0_6": { - "min": 20.01305237264373, - "max": 20.01307710784306, - "mean": 20.01306711759319 + "min": 20.35084146977833, + "max": 20.35233575672729, + "mean": 20.35155845127736 }, "winding_0_7": { - "min": 20.01301813161074, - "max": 20.01305491441541, - "mean": 20.01303753670773 + "min": 20.34965402063316, + "max": 20.35096847733062, + "mean": 20.35029674115591 }, "winding_0_8": { - "min": 20.01296023017016, - "max": 20.01301069822877, - "mean": 20.012986526370785 + "min": 20.34863810925235, + "max": 20.34979600206153, + "mean": 20.349203358160906 }, "winding_0_9": { - "min": 20.01288614367097, - "max": 20.01294823314774, - "mean": 20.01291825067283 + "min": 20.34775732669436, + "max": 20.3487876703853, + "mean": 20.348263973554477 }, "winding_0_10": { - "min": 20.01311086225472, - "max": 20.01312723617958, - "mean": 20.0131217619102 + "min": 20.347445309484, + "max": 20.34878602881643, + "mean": 20.34809256952603 }, "winding_0_11": { - "min": 20.01307487646616, - "max": 20.01310971442511, - "mean": 20.013093061345337 + "min": 20.34664448722301, + "max": 20.34783838279142, + "mean": 20.34723355463295 }, "winding_0_12": { - "min": 20.01301778972869, - "max": 20.01306641124155, - "mean": 20.013042683841515 + "min": 20.34583322604515, + "max": 20.34695726297283, + "mean": 20.34639880909033 }, "winding_0_13": { - "min": 20.0129465846782, - "max": 20.01300388543728, - "mean": 20.01297442415403 + "min": 20.3450910792623, + "max": 20.34617261998987, + "mean": 20.345639400747785 }, "winding_0_14": { - "min": 20.01310243315644, - "max": 20.01312084633943, - "mean": 20.013113266443245 + "min": 20.34568892200173, + "max": 20.34676663435861, + "mean": 20.346189546911337 }, "winding_0_15": { - "min": 20.01305423270834, - "max": 20.01309505426024, - "mean": 20.013075878609623 + "min": 20.34496686781165, + "max": 20.34603382288075, + "mean": 20.34549077221932 }, "winding_0_16": { - "min": 20.01298622109277, - "max": 20.01304142507015, - "mean": 20.013013733179548 + "min": 20.34400044780968, + "max": 20.34521686614739, + "mean": 20.344632461219728 }, "winding_1_0": { - "min": 20.01283663921149, - "max": 20.01311602678436, - "mean": 20.013013673335102 + "min": 20.34079452639291, + "max": 20.35177617455224, + "mean": 20.346327986763026 }, "winding_2_0": { - "min": 20.01264733028075, - "max": 20.01309862393823, - "mean": 20.012941231235388 + "min": 20.34578784458133, + "max": 20.3572193291346, + "mean": 20.350064262629253 }, "total": { - "min": 20.01264733028075, - "max": 20.01365362185373, - "mean": 20.01308709864243 + "min": 20.24156720703316, + "max": 20.3572193291346, + "mean": 20.33183895133367 } }, "misc": { diff --git a/tests/integration/fixtures/results/transformer_stacked_center_tapped.json b/tests/integration/fixtures/results/transformer_stacked_center_tapped.json index 2653dcab..f73eff31 100644 --- a/tests/integration/fixtures/results/transformer_stacked_center_tapped.json +++ b/tests/integration/fixtures/results/transformer_stacked_center_tapped.json @@ -419,18 +419,28 @@ }, "all_windings": 0.2493959712332903, "eddy_core": 0.044243987268159354, - "total_core_part_1": 0.0009522186967347043, - "total_core_part_2": 0.00022380722728532486, - "total_core_part_3": 0.00031592087565521123, - "total_core_part_4": 2.57336152848899e-05, - "total_core_part_5": 4.1604813696696985e-08, + "total_core_part_1": 0.1875224300944624, + "total_eddy_core_part_1": 0.022404990649446333, + "total_hyst_core_part_1": 0.1651174394450161, + "total_core_part_2": 0.044120400629463805, + "total_eddy_core_part_2": 0.00988631048845201, + "total_hyst_core_part_2": 0.0342340901410118, + "total_core_part_3": 0.05323775248599742, + "total_eddy_core_part_3": 0.0114985177908131, + "total_hyst_core_part_3": 0.04173923469518432, + "total_core_part_4": 0.005445254807844257, + "total_eddy_core_part_4": 0.00016078899598564748, + "total_hyst_core_part_4": 0.005284465811858609, + "total_core_part_5": 0.002467040143150439, + "total_eddy_core_part_5": 0.0002933793434623247, + "total_hyst_core_part_5": 0.0021736607996881148, "hyst_core_fundamental_freq": 0.2459419134644537, "core": 0.2901859007326131, "total_losses": 0.5395818719659033 }, "simulation_settings": { "simulation_name": null, - "date": "2023-11-03 14:39:56", + "date": "2023-11-08 18:20:45", "component_type": "IntegratedTransformer", "working_directory": "/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/tests/integration/temp", "core": {