diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 9c943194..a96746ab 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -27,7 +27,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v3 with: - python-version: '3.x' + python-version: '3.12' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.github/workflows/sphinx_render_docs.yml b/.github/workflows/sphinx_render_docs.yml index 3c7ac696..303e1ce1 100644 --- a/.github/workflows/sphinx_render_docs.yml +++ b/.github/workflows/sphinx_render_docs.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.10' + python-version: '3.12' - uses: actions/checkout@v4 - name: install femmt package run: | diff --git a/femmt/component.py b/femmt/component.py index 05c13b2c..14ef2769 100644 --- a/femmt/component.py +++ b/femmt/component.py @@ -9,7 +9,6 @@ import inspect import re import pandas as pd -from typing import List, Dict, Optional, Tuple from datetime import datetime import time import logging @@ -17,7 +16,6 @@ from matplotlib import pyplot as plt import ast - # Third party libraries from onelab import onelab import materialdatabase as mdb @@ -35,6 +33,7 @@ from femmt.dtos import * import femmt.functions_reluctance as fr +logger = logging.getLogger(__name__) class MagneticComponent: """ @@ -48,13 +47,12 @@ class MagneticComponent: # Initialization of all class variables # Common variables for all instances onelab_folder_path: str = None - silent: bool = False + is_onelab_silent: bool = False def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain, component_type: ComponentType = ComponentType.Inductor, working_directory: str = None, - - clean_previous_results: bool = True, verbosity: Verbosity = 2, is_gui: bool = False, - simulation_name: Optional[str] = None, wwr_enabled=True): + clean_previous_results: bool = True, onelab_verbosity: Verbosity = 1, is_gui: bool = False, + simulation_name: str | None = None, wwr_enabled=True): # TODO Add a enum? for the verbosity to combine silent and print_output_to_file variables """ Initialize the magnetic component. @@ -87,28 +85,22 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain, self.file_data.clear_previous_simulation_results() # Variable to set silent mode - self.verbosity = verbosity - self.logger = logging.getLogger("FEMMTLogger") - logging.basicConfig(format='%(levelname)s,%(asctime)s:%(message)s', encoding='utf-8') - self.logger.setLevel(logging.INFO) + self.verbosity = onelab_verbosity if not gmsh.isInitialized(): gmsh.initialize() - if verbosity != Verbosity.ToConsole: + if onelab_verbosity != Verbosity.ToConsole: gmsh.option.setNumber("General.Terminal", 0) - self.silent = True + self.is_onelab_silent = True - if verbosity == Verbosity.ToFile: - fh = logging.FileHandler(self.file_data.femmt_log, mode="w") - fh.setLevel(logging.INFO) - self.logger.addHandler(fh) - self.silent = True + if onelab_verbosity == Verbosity.ToFile: + self.is_onelab_silent = True self.wwr_enabled = wwr_enabled - self.femmt_print(f"\n" - f"Initialized a new Magnetic Component of type {component_type.name}\n" - f"--- --- --- ---") + logger.info(f"\n" + f"Initialized a new Magnetic Component of type {component_type.name}\n" + f"--- --- --- ---") # To make sure femm is only imported once self.femm_is_imported = False @@ -121,7 +113,7 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain, # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Components self.core: Core = None # Contains all information about the cores - self.air_gaps: Optional[AirGaps] = None # Contains every air gap + self.air_gaps: AirGaps | None = None # Contains every air gap self.windings = None # self.windings: List of the different winding objects which the following structure: # windings[0]: primary, windings[1]: secondary, windings[2]: tertiary .... @@ -161,13 +153,13 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain, # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Steinmetz loss material coefficients and current waveform - self.Ipeak: Optional[float] = None - self.ki: Optional[float] = None - self.alpha: Optional[float] = None - self.beta: Optional[float] = None - self.t_rise: Optional[float] = None - self.t_fall: Optional[float] = None - self.f_switch: Optional[float] = None + self.Ipeak: float | None = None + self.ki: float | None = None + self.alpha: float | None = None + self.beta: float | None = None + self.t_rise: float | None = None + self.t_fall: float | None = None + self.f_switch: float | None = None # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # MeshData to store the mesh size for different points @@ -217,16 +209,6 @@ def __init__(self, simulation_type: SimulationType = SimulationType.FreqDomain, self.onelab_client = onelab.client(__file__) self.simulation_name = simulation_name - def femmt_print(self, text: str): - """ - Print text to terminal or to log-file, dependent on the current verbosity. - - :param text: text to print - :type text: str - """ - if self.verbosity != Verbosity.Silent: - self.logger.info(text) - def update_mesh_accuracies(self, mesh_accuracy_core: float, mesh_accuracy_window: float, mesh_accuracy_conductor, mesh_accuracy_air_gaps: float): """ @@ -248,20 +230,20 @@ def update_mesh_accuracies(self, mesh_accuracy_core: float, mesh_accuracy_window # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Thermal simulation - def thermal_simulation(self, thermal_conductivity_dict: Dict, boundary_temperatures_dict: Dict, - boundary_flags_dict: Dict, case_gap_top: float, + def thermal_simulation(self, thermal_conductivity_dict: dict, boundary_temperatures_dict: dict, + boundary_flags_dict: dict, case_gap_top: float, case_gap_right: float, case_gap_bot: float, show_thermal_simulation_results: bool = True, - pre_visualize_geometry: bool = False, color_scheme: Dict = ff.colors_femmt_default, - colors_geometry: Dict = ff.colors_geometry_femmt_default, flag_insulation: bool = True): + pre_visualize_geometry: bool = False, color_scheme: dict = ff.colors_femmt_default, + colors_geometry: dict = ff.colors_geometry_femmt_default, flag_insulation: bool = True): """ Start the thermal simulation using thermal_simulation.py. :param thermal_conductivity_dict: Contains the thermal conductivities for every region - :type thermal_conductivity_dict: Dict + :type thermal_conductivity_dict: dict :param boundary_temperatures_dict: Contains the temperatures at each boundary line - :type boundary_temperatures_dict: Dict + :type boundary_temperatures_dict: dict :param boundary_flags_dict: Sets the boundary type (Dirichlet or von Neumann) for each boundary line - :type boundary_flags_dict: Dict + :type boundary_flags_dict: dict :param case_gap_top: Size of the top case :type case_gap_top: float :param case_gap_right: Size of the right case @@ -273,9 +255,9 @@ def thermal_simulation(self, thermal_conductivity_dict: Dict, boundary_temperatu :param pre_visualize_geometry: Shows the thermal model before simulation, defaults to False :type pre_visualize_geometry: bool, optional :param color_scheme: Color scheme for visualization, defaults to ff.colors_femmt_default - :type color_scheme: Dict, optional + :type color_scheme: dict, optional :param colors_geometry: Color geometry for visualization, defaults to ff.colors_geometry_femmt_default - :type colors_geometry: Dict, optional + :type colors_geometry: dict, optional :param flag_insulation: True to simulate the insulation :type flag_insulation: bool """ @@ -349,8 +331,8 @@ def thermal_simulation(self, thermal_conductivity_dict: Dict, boundary_temperatu } thermal_simulation.run_thermal(**thermal_parameters) - self.femmt_print(f"The electromagnetic results are stored here: {self.file_data.e_m_results_log_path}") - self.femmt_print(f"The thermal results are stored here: {self.file_data.results_folder_path}/results_thermal.json") + logger.info(f"The electromagnetic results are stored here: {self.file_data.e_m_results_log_path}") + logger.info(f"The thermal results are stored here: {self.file_data.results_folder_path}/results_thermal.json") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Setup @@ -396,7 +378,7 @@ def onelab_setup(self, is_gui: bool): onelab_path_wrong = False break else: - self.femmt_print('onelab not found! Tool searches for onelab.py in the folder. Please re-enter path!') + logger.info('onelab not found! Tool searches for onelab.py in the folder. Please re-enter path!') self.file_data.onelab_folder_path = onelab_path # Write the path to the config.json @@ -423,13 +405,12 @@ def high_level_geo_gen(self, frequency: float = None, skin_mesh_factor: float = # Create model self.two_d_axi = TwoDaxiSymmetric(self.core, self.mesh_data, self.air_gaps, self.winding_windows, self.stray_path, - self.insulation, self.component_type, len(self.windings), self.verbosity, - self.logger) + self.insulation, self.component_type, len(self.windings), self.verbosity) self.two_d_axi.draw_model() # Create mesh self.mesh = Mesh(self.two_d_axi, self.windings, self.winding_windows, self.core.correct_outer_leg, - self.file_data, self.verbosity, self.logger, None, self.wwr_enabled) + self.file_data, self.verbosity, None, self.wwr_enabled) # self.mesh = Mesh(self.two_d_axi, self.windings, self.core.correct_outer_leg, self.file_data, None, ff.silent) def mesh(self, frequency: float = None, skin_mesh_factor: float = None): @@ -479,15 +460,15 @@ def set_air_gaps(self, air_gaps: AirGaps): self.air_gaps = air_gaps - def set_winding_windows(self, winding_windows: List[WindingWindow]): + def set_winding_windows(self, winding_windows: list[WindingWindow]): """ Add the winding windows to the model. Creates the windings list, which contains the conductors from the virtual winding windows but sorted by the winding_number (ascending). Sets empty lists for excitation parameters. - :param winding_windows: List of WindingWindow objects - :type winding_windows: List[WindingWindow] + :param winding_windows: list of WindingWindow objects + :type winding_windows: list[WindingWindow] """ self.winding_windows = winding_windows windings = [] @@ -499,20 +480,18 @@ def set_winding_windows(self, winding_windows: List[WindingWindow]): if winding not in windings: windings.append(winding) - # print(f"{winding_window.virtual_winding_windows = }") - # print(f"{windings = }") self.windings = sorted(windings, key=lambda x: x.winding_number) # Print statement was moved here so the silence functionality is not needed in Conductors class. # TODO Can this be even removed? for winding in self.windings: if winding.conductor_type == ConductorType.RoundLitz: - self.femmt_print(f"Updated Litz Configuration: \n" - f" ff: {winding.ff} \n" - f" Number of layers/strands: {winding.n_layers}/{winding.n_strands} \n" - f" Strand radius: {winding.strand_radius} \n" - f" Conductor radius: {winding.conductor_radius}\n" - f"---") + logger.info(f"Updated Litz Configuration: \n" + f" ff: {winding.ff} \n" + f" Number of layers/strands: {winding.n_layers}/{winding.n_strands} \n" + f" Strand radius: {winding.strand_radius} \n" + f" Conductor radius: {winding.conductor_radius}\n" + f"---") # Set excitation parameter lists self.current = [None] * len(windings) @@ -543,8 +522,8 @@ def set_core(self, core: Core): # Pre-Processing def create_model(self, freq: float, skin_mesh_factor: float = 0.5, pre_visualize_geometry: bool = False, - save_png: bool = False, color_scheme: Dict = ff.colors_femmt_default, - colors_geometry: Dict = ff.colors_geometry_femmt_default, benchmark: bool = False): + save_png: bool = False, color_scheme: dict = ff.colors_femmt_default, + colors_geometry: dict = ff.colors_geometry_femmt_default, benchmark: bool = False): """ Create a model from the abstract geometry description inside onelab including optional mesh generation. @@ -558,9 +537,9 @@ def create_model(self, freq: float, skin_mesh_factor: float = 0.5, pre_visualize :param save_png: True to save a png-figure, false for no figure :type save_png: bool :param color_scheme: color file (definition for red, green, blue, ...) - :type color_scheme: Dict + :type color_scheme: dict :param colors_geometry: definition for e.g. core is gray, winding is orange, ... - :type colors_geometry: Dict + :type colors_geometry: dict :param benchmark: Benchmark simulation (stop time). Defaults to False. :type benchmark: bool """ @@ -568,7 +547,7 @@ def create_model(self, freq: float, skin_mesh_factor: float = 0.5, pre_visualize raise Exception("A core class needs to be added to the magnetic component") if self.air_gaps is None: self.air_gaps = AirGaps(None, None) - self.femmt_print("No air gaps are added") + logger.info("No air gaps are added") if self.insulation is None: raise Exception("An insulation class need to be added to the magnetic component") if self.winding_windows is None: @@ -604,7 +583,7 @@ def get_single_complex_permeability(self): complex_permeability = mu_0 * mdb.MaterialDatabase( self.verbosity == Verbosity.Silent).get_material_attribute(material_name=self.core.material, attribute="initial_permeability") - self.femmt_print(f"{complex_permeability=}") + logger.info(f"{complex_permeability=}") if self.core.permeability_type == PermeabilityType.FixedLossAngle: complex_permeability = mu_0 * self.core.mu_r_abs * complex(np.cos(np.deg2rad(self.core.phi_mu_deg)), np.sin(np.deg2rad(self.core.phi_mu_deg))) @@ -633,20 +612,20 @@ def check_model_mqs_condition(self) -> None: complex_permittivity = epsilon_0 * epsilon_r * complex(np.cos(np.deg2rad(epsilon_phi_deg)), np.sin(np.deg2rad(epsilon_phi_deg))) - self.femmt_print(f"{complex_permittivity=}\n" - f"{epsilon_r=}\n" - f"{epsilon_phi_deg=}") + logger.info(f"{complex_permittivity=}\n" + f"{epsilon_r=}\n" + f"{epsilon_phi_deg=}") ff.check_mqs_condition(radius=self.core.core_inner_diameter / 2, frequency=self.frequency, complex_permeability=self.get_single_complex_permeability(), complex_permittivity=complex_permittivity, conductivity=self.core.sigma, - relative_margin_to_first_resonance=0.5, silent=self.silent) + relative_margin_to_first_resonance=0.5) else: ff.check_mqs_condition(radius=self.core.core_inner_diameter / 2, frequency=self.frequency, complex_permeability=self.get_single_complex_permeability(), complex_permittivity=0, conductivity=self.core.sigma, - relative_margin_to_first_resonance=0.5, silent=self.silent) + relative_margin_to_first_resonance=0.5) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # Miscellaneous @@ -938,11 +917,11 @@ def calculate_core_weight(self) -> float: material_name=self.core.material, attribute="volumetric_mass_density") return self.calculate_core_volume() * volumetric_mass_density - def get_wire_distances(self) -> List[List[float]]: + def get_wire_distances(self) -> list[list[float]]: """Return the distance (radius) of each conductor to the y-axis. :return: Wire distances - :rtype: List[List[float]] + :rtype: list[list[float]] """ # wire_distance = [] # for winding in self.two_d_axi.p_conductor: @@ -969,7 +948,7 @@ def get_wire_distances(self) -> List[List[float]]: return wire_distance - def calculate_wire_lengths(self) -> List[float]: + def calculate_wire_lengths(self) -> list[float]: """Calculate the wire length of all conductors inside the magnetic component.""" distances = self.get_wire_distances() lengths = [] @@ -978,7 +957,7 @@ def calculate_wire_lengths(self) -> List[float]: return lengths - def calculate_wire_volumes(self) -> List[float]: + def calculate_wire_volumes(self) -> list[float]: """Calculate the wire volume of the magnetic component.""" wire_volumes = [] wire_lengths = self.calculate_wire_lengths() @@ -1027,7 +1006,7 @@ def calculate_wire_volumes(self) -> List[float]: return wire_volumes - def calculate_wire_weight(self) -> List[float]: + def calculate_wire_weight(self) -> list[float]: """Calculate the weight of all wires used inside the magnetic component.""" wire_material = ff.wire_material_database() @@ -1041,7 +1020,7 @@ def calculate_wire_weight(self) -> List[float]: # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # GetDP Interaction / Simulation / Excitation - def excitation(self, frequency: float, amplitude_list: List, phase_deg_list: List = None, ex_type: str = 'current', + def excitation(self, frequency: float, amplitude_list: list, phase_deg_list: list = None, ex_type: str = 'current', plot_interpolation: bool = False): """ Run the electromagnetic simulation. @@ -1054,9 +1033,9 @@ def excitation(self, frequency: float, amplitude_list: List, phase_deg_list: Lis :param frequency: Frequency :type frequency: float :param amplitude_list: Current amplitudes according to windings - :type amplitude_list: List + :type amplitude_list: list :param phase_deg_list: Current phases in degree according to the current amplitudes (according to windings) - :type phase_deg_list: List + :type phase_deg_list: list :param ex_type: Excitation type. 'Current' implemented only. Future use may: 'voltage' and 'current_density' :type ex_type: str """ @@ -1068,11 +1047,11 @@ def excitation(self, frequency: float, amplitude_list: List, phase_deg_list: Lis raise ValueError( "Negative currents are not allowed. Use the phase + 180 degree to generate a negative current.") - self.femmt_print(f"\n---\n" - f"Excitation: \n" - f"Frequency: {frequency}\n" - f"Current(s): {amplitude_list}\n" - f"Phase(s): {phase_deg_list}\n") + logger.info(f"\n---\n" + f"Excitation: \n" + f"Frequency: {frequency}\n" + f"Current(s): {amplitude_list}\n" + f"Phase(s): {phase_deg_list}\n") # -- Excitation -- self.flag_excitation_type = ex_type # 'current', 'current_density', 'voltage' @@ -1130,8 +1109,8 @@ def excitation(self, frequency: float, amplitude_list: List, phase_deg_list: Lis elif self.windings[num].conductor_type == ConductorType.RoundSolid: self.red_freq[num] = self.windings[num].conductor_radius / self.delta else: - self.femmt_print("Reduced Frequency does not have a physical value here") - self.femmt_print(self.windings[num].conductor_type) + logger.info("Reduced Frequency does not have a physical value here") + logger.info(self.windings[num].conductor_type) self.red_freq[ num] = 1 # TODO: doesn't make sense like this -> rewrite fore conductor windings shape else: @@ -1144,16 +1123,16 @@ def excitation(self, frequency: float, amplitude_list: List, phase_deg_list: Lis if self.core.material != "custom": self.reluctance_model_pre_check() - def excitation_time_domain(self, current_list: List[List[float]], time_list: List[float], + def excitation_time_domain(self, current_list: list[list[float]], time_list: list[float], number_of_periods: int, ex_type: str = 'current', plot_interpolation: bool = False, imposed_red_f=0): """ Excites the electromagnetic problem in the time domain with specified current and time settings. :param current_list: A nested list containing current values for each time step and winding. - :type current_list: List[List[float]] + :type current_list: list[list[float]] :param time_list: A nested list containing the corresponding time values for each current value. - :type time_list: List[float] + :type time_list: list[float] :param number_of_periods: The total number of periods within the provided time period of simulation. :type number_of_periods: int :param ex_type: Excitation type. 'current' implemented only. Future use may include 'voltage' and 'current_density'. @@ -1166,11 +1145,11 @@ def excitation_time_domain(self, current_list: List[List[float]], time_list: Lis if any(len(sublist) != len(time_list) for sublist in current_list): raise ValueError("The length of at least one sublist in current_list does not match the length of time_list.") - self.femmt_print(f"\n---\n" - f"Excitation: \n" - f"Maximum Time(sec): {number_of_periods}\n" - f"Current(s): {current_list}\n" - f"Time(s): {time_list}\n") + logger.info(f"\n---\n" + f"Excitation: \n" + f"Maximum Time(sec): {number_of_periods}\n" + f"Current(s): {current_list}\n" + f"Time(s): {time_list}\n") self.frequency = 1/time_list[-1] # -- Excitation -- self.flag_excitation_type = ex_type # 'current', 'current_density', 'voltage' @@ -1184,8 +1163,8 @@ def excitation_time_domain(self, current_list: List[List[float]], time_list: Lis self.initial_time = 0 # defined 0 self.step_time = time_list[1] # convention!!! for fixed time steps self.time_period = time_list[-1] - print(f"{1/self.frequency=}") - print(f"{time_list[-1]=}") + logger.info(f"{1/self.frequency=}") + logger.info(f"{time_list[-1]=}") self.nb_steps_per_period = len(time_list) self.max_time = number_of_periods * (self.time_period + self.step_time) # current excitation @@ -1229,8 +1208,8 @@ def excitation_time_domain(self, current_list: List[List[float]], time_list: Lis elif self.windings[num].conductor_type == ConductorType.RoundSolid: self.red_freq[num] = self.windings[num].conductor_radius / self.delta else: - self.femmt_print("Reduced Frequency does not have a physical value here") - self.femmt_print(self.windings[num].conductor_type) + logger.info("Reduced Frequency does not have a physical value here") + logger.info(self.windings[num].conductor_type) self.red_freq[ num] = 1 # TODO: doesn't make sense like this -> rewrite fore conductor windings shape else: @@ -1241,9 +1220,9 @@ def excitation_time_domain(self, current_list: List[List[float]], time_list: Lis def simulate(self): """Initialize the onelab client. Provides the GetDP based solver with the created mesh file.""" - self.femmt_print("\n---\n" - "Initialize ONELAB API\n" - "Run Simulation\n") + logger.info("\n---\n" + "Initialize ONELAB API\n" + "Run Simulation\n") self.log_material_properties() # -- Simulation -- @@ -1291,8 +1270,8 @@ def write_simulation_parameters_to_pro_files(self): """ # All shared control variables and parameters are passed to a temporary Prolog file - self.femmt_print("\n---\n" - "Write simulation parameters to .pro files (file communication).\n") + logger.info("\n---\n" + "Write simulation parameters to .pro files (file communication).\n") # Write initialization parameters for simulation in 'Parameter.pro' file self.write_electro_magnetic_parameter_pro() @@ -1335,7 +1314,7 @@ def overwrite_air_conductors_with_conductors(self, physical_surfaces_to_overwrit with open(os.path.join(os.path.join(self.file_data.e_m_mesh_file)), "w") as mesh_file: mesh_file.write(mesh_data) - def single_simulation(self, freq: float, current: List[float], phi_deg: List[float] = None, + def single_simulation(self, freq: float, current: list[float], phi_deg: list[float] = None, plot_interpolation: bool = False, show_fem_simulation_results: bool = True, benchmark: bool = False): """ @@ -1346,7 +1325,7 @@ def single_simulation(self, freq: float, current: List[float], phi_deg: List[flo :type freq: float :param current: current to simulate :param phi_deg: phase angle in degree - :type phi_deg: List[float] + :type phi_deg: list[float] :param show_fem_simulation_results: Set to True to show the simulation results after the simulation has finished :type show_fem_simulation_results: bool :param benchmark: Benchmark simulation (stop time). Defaults to False. @@ -1403,9 +1382,9 @@ def single_simulation(self, freq: float, current: List[float], phi_deg: List[flo self.log_reluctance_and_inductance() if show_fem_simulation_results: self.visualize() - self.femmt_print(f"The electromagnetic results are stored here: {self.file_data.e_m_results_log_path}") + logger.info(f"The electromagnetic results are stored here: {self.file_data.e_m_results_log_path}") - def time_domain_simulation(self, current_period_vec: List[List[float]], time_period_vec: List[float], + def time_domain_simulation(self, current_period_vec: list[list[float]], time_period_vec: list[float], number_of_periods: int, plot_interpolation: bool = False, show_fem_simulation_results: bool = True, show_rolling_average: bool = True, rolling_avg_window_size: int = 5, benchmark: bool = False): @@ -1415,9 +1394,9 @@ def time_domain_simulation(self, current_period_vec: List[List[float]], time_per :param plot_interpolation: Plot interpolation for the used material between the given data from the material database :type plot_interpolation: bool :param current_period_vec: current to simulate in a vector for all windings. - :type current_period_vec: List[List[float]] + :type current_period_vec: list[list[float]] :param time_period_vec: time list - :type time_period_vec: List[float] + :type time_period_vec: list[float] :param number_of_periods: periods (1, 2, 3,...) :type number_of_periods: int :param show_fem_simulation_results: Set to True to show the simulation results after the simulation has finished @@ -1476,13 +1455,13 @@ def time_domain_simulation(self, current_period_vec: List[List[float]], time_per if show_rolling_average: self.get_rolling_average(window_size=rolling_avg_window_size) - def excitation_sweep(self, frequency_list: List, current_list_list: List, phi_deg_list_list: List, + def excitation_sweep(self, frequency_list: list, current_list_list: list, phi_deg_list_list: list, show_last_fem_simulation: bool = False, excitation_meshing_type: ExcitationMeshingType = None, skin_mesh_factor: float = 0.5, visualize_before: bool = False, save_png: bool = False, - color_scheme: Dict = ff.colors_femmt_default, - colors_geometry: Dict = ff.colors_geometry_femmt_default, - inductance_dict: Dict = None, core_hyst_loss: List[float] | np.ndarray = None) -> None: + color_scheme: dict = ff.colors_femmt_default, + colors_geometry: dict = ff.colors_geometry_femmt_default, + inductance_dict: dict = None, core_hyst_loss: list[float] | np.ndarray = None) -> None: """ Perform a sweep simulation for frequency-current pairs. @@ -1507,26 +1486,26 @@ def excitation_sweep(self, frequency_list: List, current_list_list: List, phi_de >>> phi_deg_list_list=phase_list_list) :param frequency_list: Frequency in a list - :type frequency_list: List + :type frequency_list: list :param current_list_list: current amplitude, must be a list in a list, see example! - :type current_list_list: List + :type current_list_list: list :param phi_deg_list_list: phase in degree, must be a list in a list, see example! - :type phi_deg_list_list: List + :type phi_deg_list_list: list :param show_last_fem_simulation: shows last simulation in gmsh if set to True :type show_last_fem_simulation: bool :param visualize_before: show generated mesh before the simulation is run :type visualize_before: bool :param color_scheme: colorfile (definition for red, green, blue, ...) - :type color_scheme: Dict + :type color_scheme: dict :param colors_geometry: definition for e.g. core is gray, winding is orange, ... - :type colors_geometry: Dict + :type colors_geometry: dict :param save_png: True to save a .png :type save_png: bool :param inductance_dict: result dictionary from get_inductances()-function - :type inductance_dict: Dict - :param core_hyst_loss: List with hysteresis list. If given, the hysteresis losses in this function be + :type inductance_dict: dict + :param core_hyst_loss: list with hysteresis list. If given, the hysteresis losses in this function be overwritten in the result log. - :type core_hyst_loss: List + :type core_hyst_loss: list :param excitation_meshing_type: MeshOnlyLowestFrequency / MeshOnlyHighestFrequency / MeshEachFrequency :type excitation_meshing_type: ExcitationMeshingType :param skin_mesh_factor: Define the fineness of the mesh @@ -1607,14 +1586,14 @@ def excitation_sweep(self, frequency_list: List, current_list_list: List, phi_de if show_last_fem_simulation: self.write_simulation_parameters_to_pro_files() self.visualize() - self.femmt_print(f"The electromagnetic results are stored here: {self.file_data.e_m_results_log_path}") + logger.info(f"The electromagnetic results are stored here: {self.file_data.e_m_results_log_path}") - def component_study(self, time_current_vectors: List[List[List[float]]], fft_filter_value_factor: float = 0.01): + def component_study(self, time_current_vectors: list[list[list[float]]], fft_filter_value_factor: float = 0.01): """ Full study for the component: inductance values and losses. :param time_current_vectors: .... - :type time_current_vectors: List[List[List[float]]] + :type time_current_vectors: list[list[list[float]]] :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 @@ -1688,7 +1667,7 @@ def component_study(self, time_current_vectors: List[List[List[float]]], fft_fil self.excitation_sweep(frequency_list, current_list_list, phi_deg_list_list, inductance_dict=inductance_dict, core_hyst_loss=p_hyst_core_parts) - def stacked_core_study_excitation(self, time_current_vectors: List[List[List[float]]], transfer_ratio_n: float, + def stacked_core_study_excitation(self, time_current_vectors: list[list[list[float]]], transfer_ratio_n: float, plot_waveforms: bool = False, fft_filter_value_factor: float = 0.01): """ Generate the current waveforms needed for the stacked_core_study(). @@ -1697,7 +1676,7 @@ def stacked_core_study_excitation(self, time_current_vectors: List[List[List[flo The transfer_ratio_n is used to neglect the transformer loss part when the hysteresis loss part of the inductor is calculated. :param time_current_vectors: time-current vectors for primary and secondary, e.g. [[time, current_prim], [time, current_sec]] - :type time_current_vectors: List[List[List[float]]] + :type time_current_vectors: list[list[list[float]]] :param plot_waveforms: True to watch the pre-calculated waveforms :type plot_waveforms: bool :param fft_filter_value_factor: Factor to filter frequencies from the fft. E.g. 0.01 [default] removes all @@ -1791,7 +1770,7 @@ def stacked_core_study_excitation(self, time_current_vectors: List[List[List[flo return stacked_core_study_excitation def stacked_core_study(self, number_primary_coil_turns: int, - time_current_vectors: List[List[List[float]]], + time_current_vectors: list[list[list[float]]], plot_waveforms: bool = False, fft_filter_value_factor: float = 0.01) -> None: """ Comprehensive component analysis for the 2 winding stacked transformers with dedicated choke. @@ -1802,7 +1781,7 @@ def stacked_core_study(self, number_primary_coil_turns: int, transformer losses without effect of the choke :type number_primary_coil_turns: int :param time_current_vectors: time-current vectors for primary and secondary, e.g. [[time, current_prim], [time, current_sec]] - :type time_current_vectors: List[List[List[float]]] + :type time_current_vectors: list[list[list[float]]] :param plot_waveforms: True to watch the pre-calculated waveforms :type plot_waveforms: bool :param fft_filter_value_factor: Factor to filter frequencies from the fft. E.g. 0.01 [default] removes all @@ -1811,7 +1790,7 @@ def stacked_core_study(self, number_primary_coil_turns: int, """ hyst_frequency, _, _ = ff.hysteresis_current_excitation(time_current_vectors) # get the inductance - inductance_dict = self.get_inductances(I0=1, skin_mesh_factor=1, op_frequency=hyst_frequency, silent=self.silent) + inductance_dict = self.get_inductances(I0=1, skin_mesh_factor=1, op_frequency=hyst_frequency, silent=self.is_onelab_silent) study_excitation = self.stacked_core_study_excitation(time_current_vectors, plot_waveforms=plot_waveforms, fft_filter_value_factor=fft_filter_value_factor, @@ -1871,8 +1850,8 @@ def stacked_core_study(self, number_primary_coil_turns: int, study_excitation["linear_losses"]["current_phases_deg"], inductance_dict=inductance_dict, core_hyst_loss=p_hyst_core_parts) - def stacked_core_center_tapped_pre_study(self, time_current_vectors: List[List[List[float]]], plot_waveforms: bool = False, - fft_filter_value_factor: float = 0.01) -> Dict: + def stacked_core_center_tapped_pre_study(self, time_current_vectors: list[list[list[float]]], plot_waveforms: bool = False, + fft_filter_value_factor: float = 0.01) -> dict: """ Generate the current waveforms needed for the stacked_core_center_tapped_study(). @@ -1881,14 +1860,14 @@ def stacked_core_center_tapped_pre_study(self, time_current_vectors: List[List[L inductance values and so on. :param time_current_vectors: time-current vectors for primary and secondary, e.g. [[time, current_prim], [time, current_sec]] - :type time_current_vectors: List[List[List[float]]] + :type time_current_vectors: list[list[list[float]]] :param plot_waveforms: True to watch the pre-calculated waveforms :type plot_waveforms: bool :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 :return: new current waveform vector - :rtype: Dict + :rtype: dict return dict: center_tapped_study_excitation = { @@ -1910,28 +1889,28 @@ def stacked_core_center_tapped_pre_study(self, time_current_vectors: List[List[L } } """ - def split_hysteresis_loss_excitation_center_tapped(hyst_frequency: List, hyst_loss_amplitudes: List, hyst_loss_phases_deg: List): + def split_hysteresis_loss_excitation_center_tapped(hyst_frequency: list, hyst_loss_amplitudes: list, hyst_loss_phases_deg: list): """ Split the last winding (2nd) peak current into half and add a 3rd winding with the same value. :param hyst_frequency: list with the fundamental frequency of core losses - :type hyst_frequency: List + :type hyst_frequency: list :param hyst_loss_amplitudes: amplitudes for all windings in a list - :type hyst_loss_amplitudes: List + :type hyst_loss_amplitudes: list :param hyst_loss_phases_deg: phases in degree for all windings in a list - :type hyst_loss_phases_deg: List + :type hyst_loss_phases_deg: list """ hyst_loss_amplitudes[-1] = hyst_loss_amplitudes[-1] / 2 hyst_loss_amplitudes.append(hyst_loss_amplitudes[-1]) hyst_loss_phases_deg.append(hyst_loss_phases_deg[-1]) return hyst_frequency, hyst_loss_amplitudes, hyst_loss_phases_deg - def split_time_current_vectors_center_tapped(time_current_vectors: List[List[List[float]]]): + def split_time_current_vectors_center_tapped(time_current_vectors: list[list[list[float]]]): """ Split the given time-current vectors (primary and a common secondary) into primary, secondary and tertiary current. :param time_current_vectors: e.g. [[time_vec, i_primary_vec], [time_vec, i_secondary_vec]] - :type time_current_vectors: List[List[List[float]]] + :type time_current_vectors: list[list[list[float]]] """ positive_secondary_current = np.copy(time_current_vectors[1][1]) positive_secondary_current[positive_secondary_current < 0] = 0 @@ -2055,7 +2034,7 @@ def factor_triangular_hysteresis_loss_iGSE(D, alpha): # get the inductance inductance_dict = self.get_inductances(I0=1, skin_mesh_factor=1, op_frequency=center_tapped_study_excitation["hysteresis"]["frequency"], - silent=self.silent) + silent=self.is_onelab_silent) # Initialize the hysteresis losses with zero # Note: To calculate the hysteresis losses, two steps are performed: @@ -2092,7 +2071,6 @@ def factor_triangular_hysteresis_loss_iGSE(D, alpha): # temperature=self.core.temperature, material_name=self.core.material, datasource="measurements", # datatype=mdb.MeasurementDataType.Steinmetz, measurement_setup="LEA_LK",interpolation_type="linear") # p_hyst_core_parts = factor_triangular_hysteresis_loss_iGSE(D=0.5, alpha=alpha_from_db) * p_hyst_core_parts - # print(f"{p_hyst_core_parts = }") # From here on, inductor losses are calculated # Therefore, the first done overwrite of inductor conductors is restored @@ -2614,9 +2592,9 @@ def reluctance_model_pre_check(self, saturation_threshold: float = 0.7): f"Core saturation detected! B-field ({abs(b_field)} T) exceeds {saturation_threshold * 100}% of the saturation flux density" f" ({saturation_flux_density} T).") - self.femmt_print(f"B-field: {b_field:.4f} T") - self.femmt_print(f"Flux: {total_flux:.4f} Wb") - self.femmt_print(f"Total Reluctance: {reluctance:.6e} A/Wb") + logger.info(f"B-field: {b_field:.4f} T") + logger.info(f"Flux: {total_flux:.4f} Wb") + logger.info(f"Total Reluctance: {reluctance:.6e} A/Wb") else: # single core with stray path @@ -2675,7 +2653,7 @@ def reluctance_model_pre_check(self, saturation_threshold: float = 0.7): ]) # Calculate flux matrix flux_matrix = fr.calculate_flux_matrix(reluctance_matrix, winding_matrix, current_matrix) - print(flux_matrix) + logger.info(flux_matrix) # Extract flux values from the flux matrix flux_top = flux_matrix[0, 0] flux_bot = flux_matrix[1, 0] @@ -2709,16 +2687,16 @@ def reluctance_model_pre_check(self, saturation_threshold: float = 0.7): f"Core saturation detected in middle section! B-field ({abs(b_field_middle)} T) exceeds " f"{saturation_threshold * 100}% of the saturation flux density ({saturation_flux_density} T).") - self.femmt_print(f"B-field Top: {b_field_top:.4f} T") - self.femmt_print(f"B-field Bottom: {b_field_bot:.4f} T") - self.femmt_print(f"B-field Middle: {b_field_middle:.4f} T") + logger.info(f"B-field Top: {b_field_top:.4f} T") + logger.info(f"B-field Bottom: {b_field_bot:.4f} T") + logger.info(f"B-field Middle: {b_field_middle:.4f} T") else: if self.core.core_type == CoreType.Stacked or self.stray_path: # Raise a warning if there are more than 2 windings in a stacked core with stray path raise Warning("Warning: More than two windings detected in a stacked core with a stray path. " "This configuration is not fully supported.") - self.femmt_print("Reluctance model pre-check passed.") + logger.info("Reluctance model pre-check passed.") def get_inductance_from_reluctance(self): """ @@ -2759,8 +2737,8 @@ def get_inductance_from_reluctance(self): # Mutual inductance inductance_matrix[i, j] = (turns_i * turns_j) / reluctance # Print the inductance matrix - self.femmt_print("Inductance Matrix from reluctance:") - self.femmt_print(f"{inductance_matrix}") + logger.info("Inductance Matrix from reluctance:") + logger.info(f"{inductance_matrix}") return inductance_matrix else: # Initialize the inductance matrix @@ -2819,8 +2797,8 @@ def get_inductance_from_reluctance(self): # Mutual inductance inductance_matrix[i, j] = (turns_i_top * turns_j_top / top_reluctance) + (turns_i_bottom * turns_j_bottom / bot_reluctance) # Print the inductance matrix - self.femmt_print("Inductance Matrix from reluctance:") - self.femmt_print(f"{inductance_matrix}") + logger.info("Inductance Matrix from reluctance:") + logger.info(f"{inductance_matrix}") return inductance_matrix # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2876,17 +2854,14 @@ def get_inductances(self, I0: float, op_frequency: float = 0, skin_mesh_factor: inductance_matrix = ff.get_inductance_matrix(self_inductances, mean_coupling_factors, coupling_matrix) if not silent: - ff.visualize_self_inductances(self_inductances, flux_linkages, silent=self.silent) - ff.visualize_self_resistances(self_inductances, flux_linkages, op_frequency, silent=self.silent) - ff.visualize_flux_linkages(flux_linkages, silent=self.silent) + ff.visualize_self_inductances(self_inductances, flux_linkages) + ff.visualize_self_resistances(self_inductances, flux_linkages, op_frequency) + ff.visualize_flux_linkages(flux_linkages) # visualize_coupling_factors(coupling_matrix) - ff.visualize_mean_coupling_factors(mean_coupling_factors, silent=self.silent) + ff.visualize_mean_coupling_factors(mean_coupling_factors) # visualize_mean_mutual_inductances(inductance_matrix) # visualize_mutual_inductances(self_inductances, coupling_matrix) - ff.visualize_inductance_matrix(inductance_matrix, silent=self.silent) - # print(np.array(inductance_matrix).real) - # print("") - # print(np.array(inductance_matrix).imag) + ff.visualize_inductance_matrix(inductance_matrix) # Read the inductance matrix from the log file if compare_with_inductance_from_reluctance: # Extract the real part of the inductance matrix using the existing visualization function @@ -2908,8 +2883,8 @@ def get_inductances(self, I0: float, op_frequency: float = 0, skin_mesh_factor: inductance_matrix_log[i, j] = log_data["inductance_matrix_from_reluctance"].get(label, 0) # Print the inductance matrix from reluctance - print("\nInductance Matrix from Reluctance Calculation:") - ff.visualize_inductance_matrix(inductance_matrix_log, silent=silent) + logger.info("\nInductance Matrix from Reluctance Calculation:") + ff.visualize_inductance_matrix(inductance_matrix_log) # Prepare data for plotting labels = [] @@ -2953,7 +2928,7 @@ def get_inductances(self, I0: float, op_frequency: float = 0, skin_mesh_factor: plt.show() else: - print(f"Log file not found at {log_file_path}. Skipping comparison.") + logger.info(f"Log file not found at {log_file_path}. Skipping comparison.") if len(self.windings) == 2: # Self inductances. Only real parts are taken into account, this is for the LOSSLESS equivalent circuit. @@ -2971,16 +2946,16 @@ def get_inductances(self, I0: float, op_frequency: float = 0, skin_mesh_factor: self.L_s_conc = (1 - k ** 2) * self.L_1_1 self.L_h_conc = self.M ** 2 / self.L_2_2 - self.femmt_print(f"\n" - f"Lossless T-ECD (primary side concentrated):\n" - f"[Under-determined System: n := M / L_2_2 --> L_s2 = L_2_2 - M / n = 0]\n" - f" - Transformation Ratio: n\n" - f" - (Primary) Stray Inductance: L_s1\n" - f" - Primary Side Main Inductance: L_h\n" - f"n := M / L_2_2 = k * Sqrt(L_1_1 / L_2_2) = {self.n_conc}\n" - f"L_s1 = (1 - k^2) * L_1_1 = {self.L_s_conc}\n" - f"L_h = M^2 / L_2_2 = k^2 * L_1_1 = {self.L_h_conc}\n" - ) + logger.info(f"\n" + f"Lossless T-ECD (primary side concentrated):\n" + f"[Under-determined System: n := M / L_2_2 --> L_s2 = L_2_2 - M / n = 0]\n" + f" - Transformation Ratio: n\n" + f" - (Primary) Stray Inductance: L_s1\n" + f" - Primary Side Main Inductance: L_h\n" + f"n := M / L_2_2 = k * Sqrt(L_1_1 / L_2_2) = {self.n_conc}\n" + f"L_s1 = (1 - k^2) * L_1_1 = {self.L_s_conc}\n" + f"L_h = M^2 / L_2_2 = k^2 * L_1_1 = {self.L_h_conc}\n" + ) inductance = TransformerInductance( l_h_conc=self.L_h_conc, @@ -3017,26 +2992,26 @@ def get_inductances(self, I0: float, op_frequency: float = 0, skin_mesh_factor: self.L_s13 = self.L_s1 + self.n_13 ** 2 * self.L_s3 self.L_s23 = self.L_s2 + (self.n_13 / self.n_12) ** 2 * self.L_s3 - self.femmt_print(f"\n" - f"Lossless T-ECD (Lh on primary side):\n" - f" - Primary Side Stray Inductance: L_s1\n" - f" - Secondary Side Stray Inductance: L_s2\n" - f" - Tertiary Side Stray Inductance: L_s3\n" - f" - Transformation Ratio with respect to the primary and the Secondary: n2\n" - f" - Transformation Ratio with respect to the primary and the Tertiary: n3\n" - f" - Primary Side Main Inductance: L_h\n" - f"L_s1 = L_1_1 - M_12 * M_13 / M_23 = {self.L_s1}\n" - f"L_s2 = L_2_2 - M_12 * M_23 / M_13 = {self.L_s2}\n" - f"L_s3 = L_3_3 - M_13 * M_23 / M_12 = {self.L_s3}\n" - f"n_12 = np.sqrt(self.L_1_1/self.L_2_2) = {self.n_12}\n" - f"n_13 = np.sqrt(self.L_1_1/self.L_3_3) = {self.n_13}\n" - f"n_23 = np.sqrt(self.L_2_2/self.L_3_3) = {self.n_23}\n" - f"L_h = M_12 * M_13 / M_23 = {self.L_h}\n\n" - f"Shortcut Inductances L_snm measured on winding n with short applied to winding m\n" - f"L_s12 = L_s1 + n_12**2 * L_s2 = {self.L_s12}\n" - f"L_s13 = L_s1 + n_13**2 * L_s3 = {self.L_s13}\n" - f"L_s23 = L_s2 + (n_13/n_12)**2 * L_s3 = {self.L_s23}\n" - ) + logger.info(f"\n" + f"Lossless T-ECD (Lh on primary side):\n" + f" - Primary Side Stray Inductance: L_s1\n" + f" - Secondary Side Stray Inductance: L_s2\n" + f" - Tertiary Side Stray Inductance: L_s3\n" + f" - Transformation Ratio with respect to the primary and the Secondary: n2\n" + f" - Transformation Ratio with respect to the primary and the Tertiary: n3\n" + f" - Primary Side Main Inductance: L_h\n" + f"L_s1 = L_1_1 - M_12 * M_13 / M_23 = {self.L_s1}\n" + f"L_s2 = L_2_2 - M_12 * M_23 / M_13 = {self.L_s2}\n" + f"L_s3 = L_3_3 - M_13 * M_23 / M_12 = {self.L_s3}\n" + f"n_12 = np.sqrt(self.L_1_1/self.L_2_2) = {self.n_12}\n" + f"n_13 = np.sqrt(self.L_1_1/self.L_3_3) = {self.n_13}\n" + f"n_23 = np.sqrt(self.L_2_2/self.L_3_3) = {self.n_23}\n" + f"L_h = M_12 * M_13 / M_23 = {self.L_h}\n\n" + f"Shortcut Inductances L_snm measured on winding n with short applied to winding m\n" + f"L_s12 = L_s1 + n_12**2 * L_s2 = {self.L_s12}\n" + f"L_s13 = L_s1 + n_13**2 * L_s3 = {self.L_s13}\n" + f"L_s23 = L_s2 + (n_13/n_12)**2 * L_s3 = {self.L_s23}\n" + ) inductances = ThreeWindingTransformerInductance( M_12=self.M_12, @@ -3224,12 +3199,12 @@ def write_electro_magnetic_parameter_pro(self): text_file.write(f"Val_EE_{winding_number + 1} = {self.voltage[winding_number]};\n") raise NotImplementedError - self.femmt_print(f"Cell surface area: {self.windings[winding_number].a_cell} \n" - f"Reduced frequency: {self.red_freq[winding_number]}") + logger.info(f"Cell surface area: {self.windings[winding_number].a_cell} \n" + f"Reduced frequency: {self.red_freq[winding_number]}") if self.red_freq[winding_number] > self.max_reduced_frequency and self.windings[winding_number].conductor_type == ConductorType.RoundLitz: # TODO: Allow higher reduced frequencies - self.femmt_print(f"Litz Coefficients only implemented for X<={self.max_reduced_frequency}") + logger.info(f"Litz Coefficients only implemented for X<={self.max_reduced_frequency}") raise Warning("Reduced frequency exceeds limit for Litz Coefficients.") # Reduced Frequency text_file.write(f"Rr_{winding_number + 1} = {self.red_freq[winding_number]};\n") @@ -3316,9 +3291,9 @@ def write_electro_magnetic_post_pro(self): text_file.close() - def calculate_and_write_freq_domain_log(self, number_frequency_simulations: int = 1, current_amplitude_list: List = None, - phase_deg_list: List = None, frequencies: List = None, - core_hyst_losses: List[float] = None, inductance_dict: Optional[List] = None): + def calculate_and_write_freq_domain_log(self, number_frequency_simulations: int = 1, current_amplitude_list: list = None, + phase_deg_list: list = None, frequencies: list = None, + core_hyst_losses: list[float] = None, inductance_dict: list | None = None): """ Read back the results from the .dat result files created by the ONELAB simulation client. @@ -3348,9 +3323,9 @@ def calculate_and_write_freq_domain_log(self, number_frequency_simulations: int :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: List[float] + :type core_hyst_losses: list[float] :param inductance_dict: Given inductance dictionary to write to the result log. - :type inductance_dict: Dict + :type inductance_dict: dict """ fundamental_index = 0 # index of the fundamental frequency @@ -3540,7 +3515,7 @@ def calculate_and_write_freq_domain_log(self, number_frequency_simulations: int log_dict["total_losses"]["eddy_core"] = sum( log_dict["single_sweeps"][d]["core_eddy_losses"] for d in range(len(log_dict["single_sweeps"]))) - if isinstance(core_hyst_losses, List) or isinstance(core_hyst_losses, np.ndarray): + if isinstance(core_hyst_losses, list) or isinstance(core_hyst_losses, np.ndarray): # overwrite the hysteresis losses for each core part # Note: This generation MUST BE before summing up the total core losses # 1. set all hysteresis losses for all frequencies to zero @@ -3602,12 +3577,12 @@ def calculate_and_write_freq_domain_log(self, number_frequency_simulations: int with open(self.file_data.e_m_results_log_path, "w+", encoding='utf-8') as outfile: json.dump(final_log_dict, outfile, indent=2, ensure_ascii=False) - def calculate_and_write_time_domain_log(self, inductance_dict: Optional[List] = None): + def calculate_and_write_time_domain_log(self, inductance_dict: list | None = None): """ Process and log the results of time domain simulations. :param inductance_dict: Given inductance dictionary to write to the result log. - :type inductance_dict: Dict + :type inductance_dict: dict Reads back the results from the simulation output files and stores them in a JSON format. The log includes detailed information about each time step of the simulation, as well as average and total losses. @@ -3872,17 +3847,17 @@ def write_and_calculate_common_log(self, inductance_dict: dict = None): # ---- Print current configuration ---- log_dict["simulation_settings"] = MagneticComponent.encode_settings(self) - if isinstance(inductance_dict, Dict): + if isinstance(inductance_dict, dict): log_dict["inductances"] = inductance_dict return log_dict - def read_log(self) -> Dict: + def read_log(self) -> dict: """ Read results from electromagnetic simulation. :return: Logfile as a dictionary - :rtype: Dict + :rtype: dict """ with open(self.file_data.e_m_results_log_path, "r") as fd: content = json.loads(fd.read()) @@ -4010,12 +3985,12 @@ def log_material_properties(self) -> None: with open(self.file_data.material_log_path, "w+", encoding='utf-8') as outfile: json.dump(material_dict, outfile, indent=2, ensure_ascii=False) - def read_thermal_log(self) -> Dict: + def read_thermal_log(self) -> dict: """ Read results from electromagnetic simulation. :return: Logfile as a dictionary - :rtype: Dict + :rtype: dict """ with open(os.path.join(self.file_data.results_folder_path, "results_thermal.json"), "r") as fd: content = json.loads(fd.read()) @@ -4033,8 +4008,7 @@ def visualize(self): """ # ---------------------------------------- Visualization in gmsh --------------------------------------- - self.femmt_print("\n---\n" - "Visualize fields in GMSH front end:\n") + logger.info("\n---\nVisualize fields in GMSH front end:\n") # gmsh.initialize() epsilon = 1e-9 @@ -4069,7 +4043,7 @@ def visualize(self): gmsh.option.setNumber(f"View[{view}].IntervalsType", 2) gmsh.option.setNumber(f"View[{view}].NbIso", 40) gmsh.option.setNumber(f"View[{view}].ShowTime", 0) - self.femmt_print(gmsh.option.getNumber(f"View[{view}].Max")) + logger.info(gmsh.option.getNumber(f"View[{view}].Max")) view += 1 if any(self.windings[i].conductor_type == ConductorType.RoundLitz for i in range(len(self.windings))): @@ -4139,7 +4113,6 @@ def visualize(self): gmsh.option.setNumber(f"View[{v[1]}].TimeStep", 1) gmsh.option.setNumber(f"View[{v[1]}].Time", 1) gmsh.option.setNumber(f"View[{v[1]}].NbTimeStep", 1) - # ff.femmt_print(gmsh.option.getNumber(f"View[{v[1]}].Max")) # Magnetic flux density # gmsh.open(os.path.join(self.file_data.e_m_fields_folder_path, "Magb.pos")) @@ -4430,13 +4403,13 @@ def generate_load_litz_approximation_parameters(self): # f"/Strands_Coefficients/coeff/pB_RS_la{self.windings[num].ff}_{self.n_layers[num]}layer.dat"): if os.path.exists(os.path.join(self.file_data.e_m_strands_coefficients_folder_path, "coeff", f"pB_RS_la{self.windings[num].ff}_4layer.dat")): - self.femmt_print("Coefficients for stands approximation are found.") + logger.info("Coefficients for stands approximation are found.") else: # Rounding X to fit it with corresponding parameters from the database X = self.red_freq[num] X = np.around(X, decimals=3) - self.femmt_print(f"Rounded Reduced frequency X = {X}") + logger.info(f"Rounded Reduced frequency X = {X}") self.create_strand_coeff(num) def create_strand_coeff(self, winding_number: int) -> None: @@ -4453,10 +4426,9 @@ def create_strand_coeff(self, winding_number: int) -> None: :param winding_number: Winding number :type winding_number: int """ - self.femmt_print("\n" - "Pre-Simulation\n" - "-----------------------------------------\n" - "Create coefficients for strands approximation\n") + logger.info("\nPre-Simulation\n" + "-----------------------------------------\n" + "Create coefficients for strands approximation\n") text_file = open(os.path.join(self.file_data.e_m_strands_coefficients_folder_path, "PreParameter.pro"), "w") @@ -4469,7 +4441,7 @@ def create_strand_coeff(self, winding_number: int) -> None: text_file.close() cell_geo = os.path.join(self.file_data.e_m_strands_coefficients_folder_path, "cell.geo") - verbose = "-verbose 1" if self.silent else "-verbose 5" + verbose = "-verbose 1" if self.is_onelab_silent else "-verbose 5" # Run gmsh as a sub client gmsh_client = os.path.join(self.file_data.onelab_folder_path, "gmsh") @@ -4596,7 +4568,7 @@ def femm_reference(self, freq: float, current: float, sign: bool = None, non_vis # self.core.sigma = 2 * np.pi * self.frequency * epsilon_0 * f_N95_er_imag(f=self.frequency) + 1 / 6 self.core.sigma = 1 / 6 - self.femmt_print(f"{self.core.permeability_type=}, {self.core.sigma=}") + logger.info(f"{self.core.permeability_type=}, {self.core.sigma=}") if self.core.permeability_type == PermeabilityType.FixedLossAngle: femm.mi_addmaterial('Ferrite', self.core.mu_r_abs, self.core.mu_r_abs, 0, 0, self.core.sigma / 1e6, 0, 0, 1, 0, self.core.phi_mu_deg, self.core.phi_mu_deg) @@ -4613,8 +4585,8 @@ def femm_reference(self, freq: float, current: float, sign: bool = None, non_vis femm.mi_addmaterial('Litz', 1, 1, 0, 0, self.windings[i].cond_sigma / 1e6, 0, 0, 1, 5, 0, 0, self.windings[i].n_strands, 2 * 1000 * self.windings[i].strand_radius) # type := 5. last argument - self.femmt_print(f"Number of strands: {self.windings[i].n_strands}") - self.femmt_print(f"Diameter of strands in mm: {2 * 1000 * self.windings[i].strand_radius}") + logger.info(f"Number of strands: {self.windings[i].n_strands}") + logger.info(f"Diameter of strands in mm: {2 * 1000 * self.windings[i].strand_radius}") if self.windings[i].conductor_type == ConductorType.RoundSolid \ or self.windings[i].conductor_type == ConductorType.RectangularSolid: femm.mi_addmaterial('Copper', 1, 1, 0, 0, self.windings[i].cond_sigma / 1e6, 0, 0, 1, 0, 0, 0, 0, 0) @@ -4827,9 +4799,9 @@ def femm_reference(self, freq: float, current: float, sign: bool = None, non_vis # If we were interested in the flux density at specific positions, # we could inquire at specific points directly: b0 = femm.mo_getb(0, 0) - self.femmt_print('Flux density at the center of the bar is %g T' % np.abs(b0[1])) + logger.info('Flux density at the center of the bar is %g T' % np.abs(b0[1])) b1 = femm.mo_getb(0.01, 0.05) - self.femmt_print(f"Flux density at r=1cm, z=5cm is {np.abs(b1[1])} T") + logger.info(f"Flux density at r=1cm, z=5cm is {np.abs(b1[1])} T") # The program will report the terminal properties of the circuit: # current, voltage, and flux linkage @@ -4841,7 +4813,7 @@ def femm_reference(self, freq: float, current: float, sign: bool = None, non_vis # If we were interested in inductance, it could be obtained by # dividing flux linkage by current L = 1000 * np.abs(vals[2]) / np.abs(vals[0]) - self.femmt_print('The self-inductance of the coil is %g mH' % L) + logger.info('The self-inductance of the coil is %g mH' % L) # Or we could, for example, plot the results along a line using zee = [] @@ -4875,11 +4847,11 @@ def write_femm_log(self): log = {} - # self.femmt_print(hyst_loss) + # logger.info(hyst_loss) # tmp = femm.mo_getcircuitproperties('Primary') - # self.femmt_print(tmp) + # logger.info(tmp) # self.tot_loss_femm = 0.5 * tmp[0] * tmp[1] - # self.femmt_print(self.tot_loss_femm) + # logger.info(self.tot_loss_femm) # Write Circuit Properties # log["Circuit Properties"] = femm.mo_getcircuitproperties('Primary') @@ -4920,7 +4892,7 @@ def write_femm_log(self): file.close() @staticmethod - def calculate_point_average(x1: float, y1: float, x2: float, y2: float) -> Tuple[float, float]: + def calculate_point_average(x1: float, y1: float, x2: float, y2: float) -> tuple[float, float]: """Calculate the middle point between two given points. :param x1: Point1 x @@ -4937,14 +4909,14 @@ def calculate_point_average(x1: float, y1: float, x2: float, y2: float) -> Tuple # TODO Move to femmt_functions return (x1 + x2) / 2, (y1 + y2) / 2 - def femm_thermal_validation(self, thermal_conductivity_dict: Dict, boundary_temperature: Dict, case_gap_top: float, + def femm_thermal_validation(self, thermal_conductivity_dict: dict, boundary_temperature: dict, case_gap_top: float, case_gap_right: float, case_gap_bot: float): """Create a thermal model in femm and simulates it with the given thermal conductivities. - :param thermal_conductivity_dict: Dict containing conductivities for air, winding, case, core - :type thermal_conductivity_dict: Dict - :param boundary_temperature: Dict containing temperatures on boundary lines - :type boundary_temperature: Dict + :param thermal_conductivity_dict: dict containing conductivities for air, winding, case, core + :type thermal_conductivity_dict: dict + :param boundary_temperature: dict containing temperatures on boundary lines + :type boundary_temperature: dict :param case_gap_top: Length top case :type case_gap_top: float :param case_gap_right: Length right case @@ -5238,13 +5210,13 @@ def femm_thermal_validation(self, thermal_conductivity_dict: Dict, boundary_temp # femm.closefemm() @staticmethod - def encode_settings(o) -> Dict: + def encode_settings(o) -> dict: """Encode the magnetic component in a dictionary. :param o: Magnetic component containing the model. :type o: MagneticComponent :return: Model encodes as dictionary - :rtype: Dict + :rtype: dict """ content = { "simulation_name": o.simulation_name, @@ -5291,7 +5263,7 @@ def decode_settings_from_log(log_file_path: str, working_directory: str = None, if settings is not None: cwd = working_directory if working_directory is not None else settings["working_directory"] geo = MagneticComponent(component_type=ComponentType[settings["component_type"]], working_directory=cwd, - verbosity=verbosity) + onelab_verbosity=verbosity) settings["core"]["loss_approach"] = LossApproach[settings["core"]["loss_approach"]] core_type = settings["core"]["core_type"] @@ -5311,7 +5283,7 @@ def decode_settings_from_log(log_file_path: str, working_directory: str = None, else: raise ValueError("unknown core_type for decoding from result_log.") - if isinstance(settings["core"]["sigma"], List): + if isinstance(settings["core"]["sigma"], list): # in case of sigma is a complex number, it is given as a list and needs to translated to complex. settings["core"]["sigma"] = complex(settings["core"]["sigma"][0], settings["core"]["sigma"][1]) @@ -5383,12 +5355,12 @@ def decode_settings_from_log(log_file_path: str, working_directory: str = None, vww["right_bound"]) winding_type = WindingType[vww["winding_type"]] if winding_type == WindingType.Single: - print("Winding Type Single") + logger.info("Winding Type Single") winding_scheme = WindingScheme[vww["winding_scheme"]] if vww["winding_scheme"] \ is not None else None wrap_para_type = WrapParaType[vww["wrap_para"]] if vww["wrap_para"] is not None else None new_vww.set_winding(conductors[0], turns[winding_number], winding_scheme, wrap_para_type) - print(turns[0]) + logger.info(turns[0]) elif winding_type == WindingType.TwoInterleaved: new_vww.set_interleaved_winding(conductors[0], turns[0], conductors[1], turns[1], InterleavedWindingScheme[vww["winding_scheme"]], diff --git a/femmt/data.py b/femmt/data.py index cfae389a..76230bff 100644 --- a/femmt/data.py +++ b/femmt/data.py @@ -1,15 +1,14 @@ """Contains information about the file structure.""" # Python standard libraries import os - +import logging import numpy as np -from typing import List # Local libraries from femmt.enumerations import ConductorType from femmt.model import Conductor -from typing import Optional +logger = logging.getLogger(__name__) class FileData: """Contains paths to every folder and file needed in femmt.""" @@ -17,7 +16,7 @@ class FileData: def __init__(self, working_directory: str, electro_magnetic_folder_path: str = None, strands_coefficients_folder_path: str = None): if working_directory is not None: self.update_paths(working_directory, electro_magnetic_folder_path, strands_coefficients_folder_path) - self.onelab_folder_path: Optional[str] = None + self.onelab_folder_path: str | None = None @staticmethod def create_folders(*args: str) -> None: @@ -57,10 +56,9 @@ def clean_folder_structure(folder_path: str): for file in files: file_path = os.path.join(root, file) os.remove(file_path) - # print(f"remove {file_path}") - # print("All simulation results from previous simulations have been deleted successfully.") + # logger.info("All simulation results from previous simulations have been deleted successfully.") except OSError: - print("Error occurred while deleting files and subdirectories.") + logger.warning("Error occurred while deleting files and subdirectories.") def update_paths(self, working_directory: str, electro_magnetic_folder_path: str = None, strands_coefficients_folder_path: str = None) -> None: """Set the local path based on the given working directory. @@ -123,8 +121,8 @@ class MeshData: skin_mesh_factor: float c_core: float c_window: float - c_conductor = List[float] - c_center_conductor = List[float] + c_conductor = list[float] + c_center_conductor = list[float] c_air_gaps: float center_factor: float @@ -132,7 +130,7 @@ class MeshData: mu0: float core_w: float window_w: float - windings: List["Conductor"] # This is written as string because it is a forward import + windings: list["Conductor"] # This is written as string because it is a forward import frequency: float @@ -153,7 +151,7 @@ def __init__(self, mesh_accuracy_core: float, # The value 4 has good impact on the runtime and does not affect the simulation results too much. self.center_factor = 4 - def update_spatial_data(self, core_w: float, window_w: float, windings: List["Conductor"]): + def update_spatial_data(self, core_w: float, window_w: float, windings: list["Conductor"]): """ Update geometry data of the core of the magnetic component. @@ -162,7 +160,7 @@ def update_spatial_data(self, core_w: float, window_w: float, windings: List["Co :param window_w: window width :type window_w: float :param windings: list of windings - :type windings: List["Conductor"] + :type windings: list["Conductor"] """ self.core_w = core_w self.window_w = window_w diff --git a/femmt/drawing.py b/femmt/drawing.py index b2e7269c..8c431f5e 100644 --- a/femmt/drawing.py +++ b/femmt/drawing.py @@ -1,14 +1,14 @@ """Draw structures inside Onelab.""" # Python standard libraries import numpy as np -from logging import Logger -from typing import List +import logging # Local libraries from femmt.enumerations import * from femmt.data import MeshData from femmt.model import Core, WindingWindow, AirGaps, StrayPath, Insulation +logger = logging.getLogger(__name__) class TwoDaxiSymmetric: """ @@ -18,7 +18,7 @@ class TwoDaxiSymmetric: """ core: Core - winding_windows: List[WindingWindow] + winding_windows: list[WindingWindow] air_gaps: AirGaps stray_path: StrayPath insulation: Insulation @@ -26,7 +26,6 @@ class TwoDaxiSymmetric: mesh_data: MeshData number_of_windings: int verbosity: Verbosity - logger: Logger # List of points which represent the model # Every List is a List of 4 Points: x, y, z, mesh_factor @@ -34,12 +33,12 @@ class TwoDaxiSymmetric: p_region_bound: np.ndarray p_window: np.ndarray p_air_gaps: np.ndarray - # p_conductor: List[List[float]] - p_iso_core: List[List[float]] - p_iso_pri_sec: List[List[float]] + # p_conductor: list[list[float]] + p_iso_core: list[list[float]] + p_iso_pri_sec: list[list[float]] - def __init__(self, core: Core, mesh_data: MeshData, air_gaps: AirGaps, winding_windows: List[WindingWindow], - stray_path: StrayPath, insulation: Insulation, component_type: ComponentType, number_of_windings: int, verbosity: Verbosity, logger: Logger): + def __init__(self, core: Core, mesh_data: MeshData, air_gaps: AirGaps, winding_windows: list[WindingWindow], + stray_path: StrayPath, insulation: Insulation, component_type: ComponentType, number_of_windings: int, verbosity: Verbosity): self.core = core self.mesh_data = mesh_data self.winding_windows = winding_windows @@ -49,7 +48,6 @@ def __init__(self, core: Core, mesh_data: MeshData, air_gaps: AirGaps, winding_w self.insulation = insulation self.number_of_windings = number_of_windings self.verbosity = verbosity - self.logger = logger # -- Arrays for geometry data -- # TODO Is the zero initialization necessary? @@ -71,16 +69,6 @@ def __init__(self, core: Core, mesh_data: MeshData, air_gaps: AirGaps, winding_w self.r_inner = core.r_inner self.r_outer = core.r_outer - def femmt_print(self, text: str): - """ - Print text to terminal or to log-file, dependent on the current verbosity. - - :param text: text to print - :type text: str - """ - if not self.verbosity == Verbosity.Silent: - self.logger.info(text) - def draw_outer(self): """Draws the outer points of the main core (single core).""" # Outer Core @@ -96,7 +84,6 @@ def draw_outer(self): def draw_single_window(self): """Draw a single window.""" # At this point both windows (in a cut) are modeled - # print(f"win: c_window: {self.component.mesh.c_window}") self.p_window[0] = [-self.r_inner, -self.core.window_h / 2, 0, @@ -277,9 +264,6 @@ def draw_conductors(self, draw_top_down: bool = True): right_bound = virtual_winding_window.right_bound # Check, if virtual winding window fits in physical window - # print(f"{bot_bound = }\n", f"{top_bound = }\n", f"{left_bound = }\n", f"{right_bound = }\n") - # print(f"{winding_window.max_bot_bound = }\n", f"{winding_window.max_top_bound = }\n", f"{winding_window.max_left_bound = }\n", - # f"{winding_window.max_right_bound = }\n") if bot_bound < winding_window.max_bot_bound or top_bound > winding_window.max_top_bound or \ left_bound < winding_window.max_left_bound or right_bound > winding_window.max_right_bound: # Set valid to False, so that geometry is to be neglected in geometry sweep @@ -309,9 +293,9 @@ def draw_conductors(self, draw_top_down: bool = True): """ if winding0.conductor_radius != winding1.conductor_radius: - print("For bifilar winding scheme both conductors must be of the same radius!") + logger.warning("For bifilar winding scheme both conductors must be of the same radius!") else: - print("Bifilar winding scheme is applied") + logger.info("Bifilar winding scheme is applied") raise Exception("Bifilar winding scheme is not implemented yet.") @@ -1304,8 +1288,8 @@ def check_number_of_drawn_conductors(self): drawn_number_of_turns += int(self.p_conductor[winding.winding_number].shape[0] / 5) # rectangular conductors if drawn_number_of_turns != needed_number_of_turns: - self.femmt_print(f"{drawn_number_of_turns=}") - self.femmt_print(f"{needed_number_of_turns=}") + logger.info(f"{drawn_number_of_turns=}") + logger.info(f"{needed_number_of_turns=}") raise Exception("Winding mismatch. Probably too many turns that do not fit in the winding window") def draw_region_single(self): @@ -1347,7 +1331,7 @@ def draw_insulations(self): if self.component_type == ComponentType.IntegratedTransformer: # TODO: insulations implement for integrated_transformers # TODO Change back to warnings? - self.femmt_print("Insulations are not set because they are not implemented for integrated transformers.") + logger.info("Insulations are not set because they are not implemented for integrated transformers.") else: window_h = self.core.window_h iso = self.insulation @@ -1587,18 +1571,18 @@ def draw_model(self): # self.draw_region_stacked() @staticmethod - def get_center_of_rect(p1: List[float], p2: List[float], p3: List[float], p4: List[float]): + def get_center_of_rect(p1: list[float], p2: list[float], p3: list[float], p4: list[float]): """ Get center point of a rectangular conductor. :param p1: Point 1 as a x,y-List - :type p1: List[float] + :type p1: list[float] :param p2: Point 1 as a x,y-List - :type p2: List[float] + :type p2: list[float] :param p3: Point 1 as a x,y-List - :type p3: List[float] + :type p3: list[float] :param p4: Point 1 as a x,y-List - :type p4: List[float] + :type p4: list[float] """ x_list = [p1[0], p2[0], p3[0], p4[0]] y_list = [p1[1], p2[1], p3[1], p4[1]] diff --git a/femmt/dtos.py b/femmt/dtos.py index 0106786b..807d473d 100644 --- a/femmt/dtos.py +++ b/femmt/dtos.py @@ -1,7 +1,6 @@ """Data transfer objects (DTOs) used by this toolbox.""" from dataclasses import dataclass from femmt.enumerations import WindingTag -from typing import Optional, List @dataclass @@ -29,10 +28,10 @@ class ConductorRow: """Defines the conductors in one row.""" number_of_conds_per_winding: int - number_of_conds_per_row: Optional[int] - row_height: Optional[float] + number_of_conds_per_row: int | None + row_height: float | None winding_tag: WindingTag - number_of_rows: Optional[int] + number_of_rows: int | None additional_bobbin: float @@ -55,11 +54,11 @@ class ThreeWindingIsolation: class CenterTappedGroup: """Definitions for the center tapped group. A group is made of several primary and secondary rows.""" - primary_number_of_rows: Optional[int] - secondary_number_of_rows: Optional[int] - primary_rest: Optional[int] - secondary_rest: Optional[int] - stack: List[WindingTag] + primary_number_of_rows: int | None + secondary_number_of_rows: int | None + primary_rest: int | None + secondary_rest: int | None + stack: list[WindingTag] @dataclass @@ -67,8 +66,8 @@ class ConductorStack: """Definitions for the conductor stack.""" number_of_groups: int - number_of_single_rows: Optional[int] - order: List[int] + number_of_single_rows: int | None + order: list[int] @dataclass @@ -106,16 +105,16 @@ class TransformerInductance: class ThreeWindingTransformerInductance: """Inductance definitions for a three-winding transformer.""" - M_12: Optional[float] - M_13: Optional[float] - M_23: Optional[float] - L_s1: Optional[float] - L_s2: Optional[float] - L_s3: Optional[float] - L_h: Optional[float] - n_12: Optional[float] - n_13: Optional[float] - n_23: Optional[float] - L_s12: Optional[float] - L_s13: Optional[float] - L_s23: Optional[float] + M_12: float | None + M_13: float | None + M_23: float | None + L_s1: float | None + L_s2: float | None + L_s3: float | None + L_h: float | None + n_12: float | None + n_13: float | None + n_23: float | None + L_s12: float | None + L_s13: float | None + L_s23: float | None diff --git a/femmt/examples/advanced_inductor_air_gap_sweep.py b/femmt/examples/advanced_inductor_air_gap_sweep.py index 610e15b3..e74d3ec0 100644 --- a/femmt/examples/advanced_inductor_air_gap_sweep.py +++ b/femmt/examples/advanced_inductor_air_gap_sweep.py @@ -2,9 +2,8 @@ import matplotlib.pyplot as plt import femmt as fmt import os -from typing import Optional -def basic_example_sweep(onelab_folder: Optional[str] = None, show_visual_outputs: bool = True, is_test: bool = False): +def basic_example_sweep(onelab_folder: str | None = None, show_visual_outputs: bool = True, is_test: bool = False): """ Advanced example to demonstrate an air gap sweep for an inductor. @@ -39,7 +38,7 @@ def basic_example_sweep(onelab_folder: Optional[str] = None, show_visual_outputs working_directories.append(directory) - geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=directory, verbosity=fmt.Verbosity.Silent, is_gui=is_test) + geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=directory, is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/advanced_inductor_sweep.py b/femmt/examples/advanced_inductor_sweep.py index 91c085b8..21a0051d 100644 --- a/femmt/examples/advanced_inductor_sweep.py +++ b/femmt/examples/advanced_inductor_sweep.py @@ -27,8 +27,7 @@ def advanced_example_inductor_sweep(onelab_folder: str = None, show_visual_outpu os.mkdir(working_directory) # 1. chose simulation type - geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_inductor.py b/femmt/examples/basic_inductor.py index def404d4..4e9e0acf 100644 --- a/femmt/examples/basic_inductor.py +++ b/femmt/examples/basic_inductor.py @@ -10,9 +10,12 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) -def basic_example_inductor(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): +def basic_example_inductor(onelab_folder: str = None, show_visual_outputs: bool = False, is_test: bool = False): """ Run the example code for the inductor. @@ -100,7 +103,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.FreqDomain, component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_inductor_excitation_sweep.py b/femmt/examples/basic_inductor_excitation_sweep.py index 6ad51239..27c463b4 100644 --- a/femmt/examples/basic_inductor_excitation_sweep.py +++ b/femmt/examples/basic_inductor_excitation_sweep.py @@ -12,7 +12,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) example_results_folder = os.path.join(os.path.dirname(__file__), "example_results") if not os.path.exists(example_results_folder): @@ -42,7 +45,7 @@ def basic_example_inductor_excitation_sweep(onelab_folder: str = None, show_visu # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_inductor_foil.py b/femmt/examples/basic_inductor_foil.py index 9e84008e..d84e75cd 100644 --- a/femmt/examples/basic_inductor_foil.py +++ b/femmt/examples/basic_inductor_foil.py @@ -12,6 +12,10 @@ """ import femmt as fmt import os +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_inductor_foil(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): @@ -221,7 +225,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # Set is_gui = True so FEMMT won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_inductor_foil_vertical.py b/femmt/examples/basic_inductor_foil_vertical.py index f2ef93cc..75d716f7 100644 --- a/femmt/examples/basic_inductor_foil_vertical.py +++ b/femmt/examples/basic_inductor_foil_vertical.py @@ -10,6 +10,10 @@ """ import femmt as fmt import os +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_inductor_foil_vertical(onelab_folder: str = None, show_visual_outputs: bool = True, @@ -104,7 +108,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_split_windings.py b/femmt/examples/basic_split_windings.py index 5af8eb3c..c4381a39 100644 --- a/femmt/examples/basic_split_windings.py +++ b/femmt/examples/basic_split_windings.py @@ -12,6 +12,10 @@ """ import femmt as fmt import os +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def run_transformer_vvw_split_examples(num_windings: int, onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): """ @@ -37,7 +41,7 @@ def run_transformer_vvw_split_examples(num_windings: int, onelab_folder: str = N def setup_simulation(working_directory, horizontal_split_factors, vertical_split_factors): geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer.py b/femmt/examples/basic_transformer.py index f4770386..7dff8f98 100644 --- a/femmt/examples/basic_transformer.py +++ b/femmt/examples/basic_transformer.py @@ -10,7 +10,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): """ @@ -100,7 +103,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_5_windings.py b/femmt/examples/basic_transformer_5_windings.py index 040c2016..1e86d853 100644 --- a/femmt/examples/basic_transformer_5_windings.py +++ b/femmt/examples/basic_transformer_5_windings.py @@ -10,6 +10,10 @@ """ import femmt as fmt import os +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_5_windings(onelab_folder: str = None, show_visual_outputs: bool = True, @@ -100,7 +104,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_6_windings.py b/femmt/examples/basic_transformer_6_windings.py index 5c41d96d..fb1e5f10 100644 --- a/femmt/examples/basic_transformer_6_windings.py +++ b/femmt/examples/basic_transformer_6_windings.py @@ -10,6 +10,10 @@ """ import femmt as fmt import os +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_6_windings(onelab_folder: str = None, show_visual_outputs: bool = True, @@ -100,7 +104,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_center_tapped.py b/femmt/examples/basic_transformer_center_tapped.py index d7c026e3..2e621b37 100644 --- a/femmt/examples/basic_transformer_center_tapped.py +++ b/femmt/examples/basic_transformer_center_tapped.py @@ -10,6 +10,10 @@ """ import femmt as fmt import os +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_center_tapped(onelab_folder: str = None, show_visual_outputs: bool = True, @@ -98,7 +102,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in os.mkdir(working_directory) geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_excitation_sweep.py b/femmt/examples/basic_transformer_excitation_sweep.py index b05e3030..01540ecf 100644 --- a/femmt/examples/basic_transformer_excitation_sweep.py +++ b/femmt/examples/basic_transformer_excitation_sweep.py @@ -10,7 +10,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) example_results_folder = os.path.join(os.path.dirname(__file__), "example_results") if not os.path.exists(example_results_folder): @@ -40,7 +43,7 @@ def basic_example_transformer_excitation_sweep(onelab_folder: str = None, show_v # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_integrated.py b/femmt/examples/basic_transformer_integrated.py index c8d95dc3..93ea0316 100644 --- a/femmt/examples/basic_transformer_integrated.py +++ b/femmt/examples/basic_transformer_integrated.py @@ -10,7 +10,10 @@ """ import os import femmt as fmt +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_integrated(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): @@ -99,7 +102,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.FreqDomain, component_type=fmt.ComponentType.IntegratedTransformer, - working_directory=working_directory, verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + working_directory=working_directory, is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_interleaved.py b/femmt/examples/basic_transformer_interleaved.py index c41c5de9..ccd4f702 100644 --- a/femmt/examples/basic_transformer_interleaved.py +++ b/femmt/examples/basic_transformer_interleaved.py @@ -10,7 +10,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_interleaved(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): @@ -99,7 +102,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_n_winding.py b/femmt/examples/basic_transformer_n_winding.py index 043422a4..30f83e2e 100644 --- a/femmt/examples/basic_transformer_n_winding.py +++ b/femmt/examples/basic_transformer_n_winding.py @@ -10,7 +10,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_n_winding(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): @@ -100,7 +103,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_stacked.py b/femmt/examples/basic_transformer_stacked.py index cb8215ab..3d8a5922 100644 --- a/femmt/examples/basic_transformer_stacked.py +++ b/femmt/examples/basic_transformer_stacked.py @@ -10,7 +10,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_stacked(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): @@ -99,7 +102,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.IntegratedTransformer, - working_directory=working_directory, verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + working_directory=working_directory, is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_stacked_center_tapped.py b/femmt/examples/basic_transformer_stacked_center_tapped.py index fe6a3503..73fa57b8 100644 --- a/femmt/examples/basic_transformer_stacked_center_tapped.py +++ b/femmt/examples/basic_transformer_stacked_center_tapped.py @@ -10,7 +10,10 @@ """ import femmt as fmt import os +import logging +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_transformer_stacked_center_tapped(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): @@ -98,7 +101,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in os.mkdir(working_directory) geo = fmt.MagneticComponent(component_type=fmt.ComponentType.IntegratedTransformer, - working_directory=working_directory, verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + working_directory=working_directory, is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/basic_transformer_three_winding.py b/femmt/examples/basic_transformer_three_winding.py index 78be4324..18c9bf1d 100644 --- a/femmt/examples/basic_transformer_three_winding.py +++ b/femmt/examples/basic_transformer_three_winding.py @@ -10,9 +10,12 @@ """ import femmt as fmt import os -from typing import Optional +import logging -def basic_example_transformer_three_winding(onelab_folder: Optional[str] = None, show_visual_outputs: bool = True, +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + +def basic_example_transformer_three_winding(onelab_folder: str | None = None, show_visual_outputs: bool = True, is_test: bool = False): """ Run the example code for the three-winding transformer. @@ -100,7 +103,7 @@ def example_thermal_simulation(show_thermal_visual_outputs: bool = True, flag_in # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/component_study/transformer_component_study.py b/femmt/examples/component_study/transformer_component_study.py index 3d0a87cc..0054deb7 100644 --- a/femmt/examples/component_study/transformer_component_study.py +++ b/femmt/examples/component_study/transformer_component_study.py @@ -2,9 +2,8 @@ import femmt as fmt import os import numpy as np -from typing import Optional -def transformer_component_study(onelab_folder: Optional[str] = None, show_visual_outputs: bool = True, is_test: bool = False): +def transformer_component_study(onelab_folder: str | None = None, show_visual_outputs: bool = True, is_test: bool = False): """ Perform a component study. @@ -38,7 +37,7 @@ def transformer_component_study(onelab_folder: Optional[str] = None, show_visual # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=is_test) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/experimental_inductor_time_domain.py b/femmt/examples/experimental_inductor_time_domain.py index 7f4b8b9b..193fccea 100644 --- a/femmt/examples/experimental_inductor_time_domain.py +++ b/femmt/examples/experimental_inductor_time_domain.py @@ -3,7 +3,10 @@ import femmt as fmt import materialdatabase as mdb import os -# from matplotlib import pyplot as plt +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) def basic_example_inductor_time_domain(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): """ @@ -28,7 +31,7 @@ def basic_example_inductor_time_domain(onelab_folder: str = None, show_visual_ou # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.TimeDomain, component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/experimental_transformer_three_winding_time_domain.py b/femmt/examples/experimental_transformer_three_winding_time_domain.py index ef14bd26..811700c2 100644 --- a/femmt/examples/experimental_transformer_three_winding_time_domain.py +++ b/femmt/examples/experimental_transformer_three_winding_time_domain.py @@ -4,6 +4,11 @@ import os # from matplotlib import pyplot as plt import numpy as np +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + def basic_example_transformer_three_windings_time_domain(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): """ Demonstrate how to simulate a three winding transformer in time domain. @@ -27,7 +32,7 @@ def basic_example_transformer_three_windings_time_domain(onelab_folder: str = No # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.TimeDomain, component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on github only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/experimental_transformer_time_domain.py b/femmt/examples/experimental_transformer_time_domain.py index c7d11fa2..85ad13cf 100644 --- a/femmt/examples/experimental_transformer_time_domain.py +++ b/femmt/examples/experimental_transformer_time_domain.py @@ -4,6 +4,11 @@ import os # from matplotlib import pyplot as plt import numpy as np +import logging + +# configure logging to show femmt terminal output +logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + def basic_example_transformer_time_domain(onelab_folder: str = None, show_visual_outputs: bool = True, is_test: bool = False): """ Demonstrate how to simulate a two winding transformer in time domain. @@ -27,7 +32,7 @@ def basic_example_transformer_time_domain(onelab_folder: str = None, show_visual # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.TimeDomain, component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.ToConsole, is_gui=is_test) + is_gui=is_test) # This line is for automated pytest running on GitHub only. Please ignore this line! if onelab_folder is not None: diff --git a/femmt/examples/femmt_benchmark.py b/femmt/examples/femmt_benchmark.py index 3aa74983..39b3c931 100644 --- a/femmt/examples/femmt_benchmark.py +++ b/femmt/examples/femmt_benchmark.py @@ -4,7 +4,6 @@ """ # Python standard libraries -from typing import List, Optional from dataclasses import dataclass import os import json @@ -41,7 +40,7 @@ class SingleBenchmark: execution_time_: float # all measurements summed up - flux_over_current: List[float] + flux_over_current: list[float] total_losses: float total_winding_losses: float @@ -141,7 +140,7 @@ def to_json(self, file_path: str): # ------ Generic Functions ------ -def create_model(working_directory: str, mesh_accuracies=Optional[MeshAccuracies], aspect_ratio: float = 10, +def create_model(working_directory: str, mesh_accuracies=MeshAccuracies, aspect_ratio: float = 10, wwr_enabled: bool = True, number_of_conductors: int = 9): """ Create the model for benchmark. @@ -162,7 +161,7 @@ def create_model(working_directory: str, mesh_accuracies=Optional[MeshAccuracies inductor_frequency = 270000 geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, wwr_enabled=wwr_enabled) + onelab_verbosity=fmt.Verbosity.Silent, wwr_enabled=wwr_enabled) core_db = fmt.core_database()["PQ 40/40"] core_dimensions = fmt.dtos.SingleCoreDimensions(core_inner_diameter=core_db["core_inner_diameter"], @@ -230,7 +229,7 @@ def create_rectangular_conductor_model(working_directory: str, mesh_accuracies: inductor_frequency = 270000 geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, wwr_enabled=wwr_enabled) + onelab_verbosity=fmt.Verbosity.Silent, wwr_enabled=wwr_enabled) core_db = fmt.core_database()["PQ 40/40"] core_dimensions = fmt.dtos.SingleCoreDimensions(core_inner_diameter=core_db["core_inner_diameter"], @@ -299,14 +298,14 @@ def create_rectangular_conductor_model(working_directory: str, mesh_accuracies: return geo -def plot_mesh_over_precision(benchmarks, x_list: List, x_label: str, title: str): +def plot_mesh_over_precision(benchmarks, x_list: list, x_label: str, title: str): """ Plot the mesh over precision. :param benchmarks: :type benchmarks: :param x_list: list with simulation numbers - :type x_list: List + :type x_list: list :param x_label: x-label for the plot :type x_label: str :param title: title for the plot diff --git a/femmt/examples/hpc_examples.py b/femmt/examples/hpc_examples.py index 08514fa1..24319b23 100644 --- a/femmt/examples/hpc_examples.py +++ b/femmt/examples/hpc_examples.py @@ -1,6 +1,5 @@ """Examples for the parallel simulation.""" # Python standard libraries -from typing import Dict from itertools import product import os import time @@ -23,7 +22,7 @@ # ---- Utility functions ---- def create_parallel_example_transformer() -> fmt.MagneticComponent: """Create an example model which is used for the parallel execution example. This does implement a simple transformer.""" - geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, verbosity=fmt.Verbosity.ToFile) + geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, onelab_verbosity=fmt.Verbosity.ToFile) core_dimensions = fmt.dtos.SingleCoreDimensions(core_inner_diameter=0.015, window_w=0.012, window_h=0.0295, core_h=0.015/2) core = fmt.Core(core_dimensions=core_dimensions, non_linear=False, sigma=1, re_mu_rel=3200, phi_mu_deg=10, permeability_datasource=fmt.MaterialDataSource.Custom, permittivity_datasource=fmt.MaterialDataSource.Custom, @@ -59,7 +58,7 @@ def create_parallel_example_inductor(inductor_frequency: int, air_gap_height: fl :type air_gap_position: int """ geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=None, # Can be set to None since it will be overwritten anyway - clean_previous_results=False, verbosity=fmt.Verbosity.ToFile) + clean_previous_results=False, onelab_verbosity=fmt.Verbosity.ToFile) core_db = fmt.core_database()["PQ 40/40"] core_dimensions = fmt.dtos.SingleCoreDimensions(core_inner_diameter=core_db["core_inner_diameter"], window_w=core_db["window_w"], @@ -94,7 +93,7 @@ def create_parallel_example_inductor(inductor_frequency: int, air_gap_height: fl return geo -def custom_hpc(parameters: Dict): +def custom_hpc(parameters: dict): """Very simple example for a custom hpc_function which can be given to the hpc.run() function. :param parameters: Dictionary containing the model and the given simulation_parameters. diff --git a/femmt/examples/inductor_optimization.py b/femmt/examples/inductor_optimization.py index ff46c947..4a6f0b3e 100644 --- a/femmt/examples/inductor_optimization.py +++ b/femmt/examples/inductor_optimization.py @@ -9,7 +9,6 @@ # import logging import inspect import time -from typing import Optional # 3rd party libraries import numpy as np @@ -21,7 +20,6 @@ # femmt libraries import femmt as fmt - material_db = mdb.MaterialDatabase() @@ -945,12 +943,12 @@ def filter_reluctance_pareto_front_tolerance(self, data_matrix: np.array, factor return return_data_matrix - def fem_simulation(self, count: Optional[int] = None): + def fem_simulation(self, count: int | None = None): """ Perform FEM simulation of the design cases. Save the result in the given working directory for later analysis. :param count: Number of cases to simulate - :type count: Optional[int] + :type count: int | None """ start_time = time.time() @@ -963,7 +961,7 @@ def fem_simulation(self, count: Optional[int] = None): for count in cases: # MagneticComponent class object geo = fmt.MagneticComponent(component_type=self.component_type_dict[self.magnetic_component], - working_directory=self.femmt_working_directory, verbosity=fmt.Verbosity.Silent) + working_directory=self.femmt_working_directory, onelab_verbosity=fmt.Verbosity.Silent) core_dimensions = fmt.dtos.SingleCoreDimensions(core_inner_diameter=self.data_matrix_fem[count, self.param["core_inner_diameter"]], window_w=self.data_matrix_fem[count, self.param["window_w"]], diff --git a/femmt/examples/winding_test.py b/femmt/examples/winding_test.py index 0e7c31f9..38e1bbb5 100644 --- a/femmt/examples/winding_test.py +++ b/femmt/examples/winding_test.py @@ -1,8 +1,6 @@ """Test different winding options.""" # python libraries import os -from typing import Dict, List - import femmt as fmt inductor_combinations = [ @@ -76,14 +74,14 @@ }, ] -def run_inductor_simulations(working_directory: str, combinations: List[Dict]): +def run_inductor_simulations(working_directory: str, combinations: list[dict]): """ Run the simulations to test several winding options for the inductor. :param working_directory: working directory :type working_directory: str :param combinations: combinations to simulate in a dictionary which are stored in a list - :type combinations: List + :type combinations: list """ not_working = [] for combination in combinations: @@ -135,14 +133,14 @@ def run_inductor_simulations(working_directory: str, combinations: List[Dict]): print(not_working) -def run_transformer_simulations(working_directory: str, combinations: List[Dict]): +def run_transformer_simulations(working_directory: str, combinations: list[dict]): """ Run the simulations to test several winding options for the transformer. :param working_directory: working directory :type working_directory: str :param combinations: combinations to simulate in a dictionary which are stored in a list - :type combinations: List + :type combinations: list """ not_working = [] for combination in combinations: diff --git a/femmt/functions.py b/femmt/functions.py index b06de25b..b9b081de 100644 --- a/femmt/functions.py +++ b/femmt/functions.py @@ -6,9 +6,8 @@ import sys import os import warnings -from typing import Union, List, Tuple, Dict from scipy.integrate import quad - +import logging # Third parry libraries import gmsh @@ -22,6 +21,8 @@ from femmt.dtos import * import femmt.functions_reluctance as fr +logger = logging.getLogger(__name__) + colors_femmt_default = {"blue": (28, 113, 216), 'red': (192, 28, 40), "green": (46, 194, 126), @@ -77,7 +78,7 @@ } -def core_database() -> Dict: +def core_database() -> dict: """ Return a core geometry for defined core structure. @@ -93,7 +94,7 @@ def core_database() -> Dict: max derivation / mean = 1.07 (< 7% accuracy) :return: Dict including core_h, core_inner_diameter, window_h, window_w - :rtype: Dict + :rtype: dict """ core_dict = {} @@ -242,12 +243,12 @@ def core_database() -> Dict: return core_dict -def litz_database() -> Dict: +def litz_database() -> dict: """ Return litz parameters for defined litz wires. :return: Dict including litz parameters like strand_numbers, strand_radii and conductor_radii - :rtype: Dict + :rtype: dict """ litz_dict = {} @@ -388,12 +389,12 @@ def litz_database() -> Dict: return litz_dict -def wire_material_database() -> Dict[str, WireMaterial]: +def wire_material_database() -> dict[str, WireMaterial]: """ Return wire materials e.g. copper, aluminum in a dictionary. :return: Dict with materials and conductivity - :rtype: Dict + :rtype: dict """ wire_material = {} @@ -454,7 +455,7 @@ def create_folders(*args: str) -> None: os.mkdir(folder) -def cost_material_database() -> Dict: +def cost_material_database() -> dict: """ Return costs for core and winding. This is split in material and fabrication costs. @@ -523,11 +524,11 @@ def install_pyfemm_if_missing() -> None: missing = required - installed if missing: - print("Missing 'pyfemm' installation.") - print("Installing 'pyfemm' ...") + logger.info("Missing 'pyfemm' installation.") + logger.info("Installing 'pyfemm' ...") python = sys.executable subprocess.check_call([python, '-m', 'pip', 'install', *missing], stdout=subprocess.DEVNULL) - print("'pyfemm' is now installed!") + logger.info("'pyfemm' is now installed!") def litz_calculate_number_strands(n_layers: int) -> int: """ @@ -561,9 +562,9 @@ def litz_calculate_number_layers(n_strands: int) -> int: def fft(period_vector_t_i: npt.ArrayLike, sample_factor: int = 1000, plot: str = 'no', mode: str = 'rad', - f0: Union[float, None] = None, title: str = 'ffT', filter_type: str = 'factor', + f0: float | None = None, title: str = 'ffT', filter_type: str = 'factor', filter_value_factor: float = 0.01, filter_value_harmonic: int = 100, - figure_size: Tuple = None, figure_directory: str = None) -> npt.NDArray[list]: + figure_size: tuple = None, figure_directory: str = None) -> npt.NDArray[list]: """ Calculate the FFT for a given input signal. Input signal is in vector format and should include one period. @@ -597,9 +598,9 @@ def fft(period_vector_t_i: npt.ArrayLike, sample_factor: int = 1000, plot: str = Note: count 1 is DC component, count 2 is the fundamental frequency :type filter_value_harmonic: int :param figure_directory: full path with file extension - :type figure_directory: Tuple + :type figure_directory: tuple :param figure_size: None for auto-fit; fig_size for matplotlib (width, length) - :type figure_size: Tuple + :type figure_size: tuple :return: numpy-array [[frequency-vector],[amplitude-vector],[phase-vector]] :rtype: npt.NDArray[list] @@ -610,7 +611,7 @@ def fft(period_vector_t_i: npt.ArrayLike, sample_factor: int = 1000, plot: str = # check for input is list. Convert to numpy-array if isinstance(period_vector_t_i, list): if plot != 'no' and plot is not False: - print("Input is list, convert to np.array()") + logger.warning("Input is list, convert to np.array()") period_vector_t_i = np.array(period_vector_t_i) # first value of time vector must be zero @@ -670,14 +671,14 @@ def fft(period_vector_t_i: npt.ArrayLike, sample_factor: int = 1000, plot: str = f"filter_type '{filter_value_harmonic}' not available: Must be 'factor','harmonic' or 'disabled ") if plot != 'no' and plot is not False: - print(f"{title=}") - print(f"{t[-1]=}") - print(f"{f0=}") - print(f"{Fs=}") - print(f"{sample_factor=}") - print(f"f_out = {np.around(f_out, decimals=0)}") - print(f"x_out = {np.around(x_out, decimals=3)}") - print(f"phi_rad_out = {np.around(phi_rad_out, decimals=3)}") + logger.info(f"{title=}") + logger.info(f"{t[-1]=}") + logger.info(f"{f0=}") + logger.info(f"{Fs=}") + logger.info(f"{sample_factor=}") + logger.info(f"f_out = {np.around(f_out, decimals=0)}") + logger.info(f"x_out = {np.around(x_out, decimals=3)}") + logger.info(f"phi_rad_out = {np.around(phi_rad_out, decimals=3)}") reconstructed_signal = 0 for i_range in range(len(f_out)): @@ -712,17 +713,17 @@ def fft(period_vector_t_i: npt.ArrayLike, sample_factor: int = 1000, plot: str = return np.array([f_out, x_out, phi_rad_out]) -def plot_fourier_coefficients(frequency_list: List, amplitude_list: List, phi_rad_list: List, +def plot_fourier_coefficients(frequency_list: list, amplitude_list: list, phi_rad_list: list, sample_factor: int = 1000, figure_directory: str = None): """ Plot fourier coefficients in a visual figure. :param frequency_list: List of frequencies in Hz - :type frequency_list: List + :type frequency_list: list :param amplitude_list: List of amplitudes in A - :type amplitude_list: List + :type amplitude_list: list :param phi_rad_list: List of angles in rad - :type phi_rad_list: List + :type phi_rad_list: list :param sample_factor: sample factor :type sample_factor: int :param figure_directory: directory of figure to save @@ -770,7 +771,7 @@ def plot_fourier_coefficients(frequency_list: List, amplitude_list: List, phi_ra def compare_fft_list(input_data_list: list, sample_factor: int = 1000, mode: str = 'rad', - f0: Union[float, None] = None) -> None: + f0: float | None = None) -> None: """ Generate fft curves from input curves and compare them to each other. @@ -826,12 +827,12 @@ def store_as_npy_in_directory(dir_path: str, file_name: str, numpy_data) -> None np.save(dir_path + "/" + file_name, numpy_data) -def get_dicts_with_keys_and_values(data, **kwargs) -> Dict: +def get_dicts_with_keys_and_values(data, **kwargs) -> dict: """ Return a list of dictionaries out of a list of dictionaries which contains pairs of the given key(s) and value(s). :param data: list of dicts - :type data: List + :type data: list :param kwargs: keys and values in dicts """ invalid_index = [] @@ -844,7 +845,7 @@ def get_dicts_with_keys_and_values(data, **kwargs) -> Dict: return valid_data -def get_dict_with_unique_keys(data: list[dict], *keys) -> Dict: +def get_dict_with_unique_keys(data: list[dict], *keys) -> dict: """ Return a dictionary out of a list of dictionaries which contains the given key(s). @@ -866,25 +867,25 @@ def get_dict_with_unique_keys(data: list[dict], *keys) -> Dict: return valid_data[0] -def find_common_frequencies(frequency_list_1: List, amplitude_list_1: List, phase_list_1_rad_or_deg: List, - frequency_list_2: List, amplitude_list_2: List, phase_list_2_rad_or_deg: List) -> List: +def find_common_frequencies(frequency_list_1: list, amplitude_list_1: list, phase_list_1_rad_or_deg: list, + frequency_list_2: list, amplitude_list_2: list, phase_list_2_rad_or_deg: list) -> list: """ Find common frequencies and returns a list of intersections. :param amplitude_list_1: Amplitudes signal 1 - :type amplitude_list_1: List + :type amplitude_list_1: list :param phase_list_1_rad_or_deg: Phases signal 1, can be degree or rad. return is same as input. - :type phase_list_1_rad_or_deg: List + :type phase_list_1_rad_or_deg: list :param frequency_list_1: Frequencies signal 1 - :type frequency_list_1: List + :type frequency_list_1: list :param amplitude_list_2: Amplitudes signal 2 - :type amplitude_list_2: List + :type amplitude_list_2: list :param phase_list_2_rad_or_deg: Phases signal 2, can be degree or rad. return is same as input - :type phase_list_2_rad_or_deg: List + :type phase_list_2_rad_or_deg: list :param frequency_list_2: Frequencies signal 2 - :type frequency_list_2: List + :type frequency_list_2: list :return: [current_pair_list, phase_pair_list, common_frequency_list] - :rtype: Tuple + :rtype: tuple :Example: @@ -908,12 +909,12 @@ def find_common_frequencies(frequency_list_1: List, amplitude_list_1: List, phas common_phases_list_2 = [] common_frequency_list = list(set(frequency_list_1).intersection(frequency_list_2)) - print(f"{common_frequency_list=}") + logger.info(f"{common_frequency_list=}") common_frequency_list.sort() - print(f"{common_frequency_list=}") + logger.info(f"{common_frequency_list=}") # Delete the corresponding phases and amplitudes - if isinstance(amplitude_list_1, List): + if isinstance(amplitude_list_1, list): for frequency in common_frequency_list: common_amplitude_list_1.append(amplitude_list_1[frequency_list_1.index(frequency)]) common_phase_list_1.append(phase_list_1_rad_or_deg[frequency_list_1.index(frequency)]) @@ -934,21 +935,21 @@ def find_common_frequencies(frequency_list_1: List, amplitude_list_1: List, phas return [common_frequency_list, current_pair_list, phase_pair_list] -def sort_out_small_harmonics(frequency_list: List, amplitude_pair_list: List, - phase_pair_list_rad_or_deg: List, sort_out_factor: float) -> List: +def sort_out_small_harmonics(frequency_list: list, amplitude_pair_list: list, + phase_pair_list_rad_or_deg: list, sort_out_factor: float) -> list: """ Sort out small harmonics from a given fft-output of a signal. - :param frequency_list: List of input frequencies - :type frequency_list: List + :param frequency_list: list of input frequencies + :type frequency_list: list :param amplitude_pair_list: list of amplitude pairs - :type amplitude_pair_list: List + :type amplitude_pair_list: list :param phase_pair_list_rad_or_deg: list of phase pairs (can be rad or degree) - :type phase_pair_list_rad_or_deg: List + :type phase_pair_list_rad_or_deg: list :param sort_out_factor: sort out factor [0...1] :type sort_out_factor: float :return: [frequency_list, amplitude_pair_list, phase_pair_list_rad_or_deg] - :rtype: List + :rtype: list """ # Calculate geometric lengths amp_tot = np.sqrt(np.sum(np.array(amplitude_pair_list) ** 2, axis=0)) @@ -1088,7 +1089,7 @@ def visualize_simulation_results(simulation_result_file_path: str, store_figure_ return loaded_results_dict -def point_is_in_rect(x: float, y: float, rect: List): +def point_is_in_rect(x: float, y: float, rect: list): """ Check if a given x-y point is inside a rectangular field (e.g. inside a conductor). @@ -1097,7 +1098,7 @@ def point_is_in_rect(x: float, y: float, rect: List): :param y: y coordinate of the point to check :type y: float :param rect: rectangular - :type rect: List + :type rect: list """ # x, y of the point # List of 4 points given as tuples with (x, y) in the order top-right, top-left, bottom-right, bottom-left @@ -1108,14 +1109,14 @@ def point_is_in_rect(x: float, y: float, rect: List): return False -def get_number_of_turns_of_winding(winding_windows: List, windings: List, winding_number: int): +def get_number_of_turns_of_winding(winding_windows: list, windings: list, winding_number: int): """ Get the number of turns of a winding. - :param winding_windows: List of winding windows - :type winding_windows: List - :param windings: List of windings - :type windings: List + :param winding_windows: list of winding windows + :type winding_windows: list + :param windings: list of windings + :type windings: list :param winding_number: number of winding :type winding_number: int """ @@ -1150,8 +1151,8 @@ def cost_function_core(core_weight: float, core_type: str = "ferrite") -> float: return sigma_core * core_weight -def cost_function_winding(wire_weight_list: List[float], wire_type_list: List[str], - single_strand_cross_section_list: Union[List[float], None] = None): +def cost_function_winding(wire_weight_list: list[float], wire_type_list: list[str], + single_strand_cross_section_list: list[float] | None = None): """ Calculate single winding material and fabrication costs depending on winding-type and weight. @@ -1159,11 +1160,11 @@ def cost_function_winding(wire_weight_list: List[float], wire_type_list: List[st of Switched-Mode Power Converters" :param wire_weight_list: winding weight in kg in list-form - :type wire_weight_list: List[float] + :type wire_weight_list: list[float] :param wire_type_list: winding type. Must fit to enum-names in ConductorType-Enum - :type wire_type_list: List[str] + :type wire_type_list: list[str] :param single_strand_cross_section_list: single strand cross-section in list-form - :type single_strand_cross_section_list: List[float] + :type single_strand_cross_section_list: list[float] :return: winding cost of single winding :rtype: float """ @@ -1201,8 +1202,8 @@ def cost_function_winding(wire_weight_list: List[float], wire_type_list: List[st return winding_cost_list -def cost_function_total(core_weight: float, core_type: str, wire_weight_list: List[float], wire_type_list: List[str], - single_strand_cross_section_list: Union[None, List[float]] = None) -> float: +def cost_function_total(core_weight: float, core_type: str, wire_weight_list: list[float], wire_type_list: list[str], + single_strand_cross_section_list: None | list[float] = None) -> float: """ Calculate the total costs for an inductive element. @@ -1218,9 +1219,9 @@ def cost_function_total(core_weight: float, core_type: str, wire_weight_list: Li :param wire_weight_list: winding weight in kg :type wire_weight_list: float :param wire_type_list: winding type in list-form. Must fit to enum-names in ConductorType-Enum - :type wire_type_list: List[str] + :type wire_type_list: list[str] :param single_strand_cross_section_list: single strand cross-section in list-form - :type single_strand_cross_section_list: List[float] + :type single_strand_cross_section_list: list[float] :return: total costs for inductive element :rtype: float """ @@ -1280,7 +1281,7 @@ def find_result_log_file(result_log_folder: str, keyword_list: list, value_min_m keyword_list[4]] if value_min <= data_to_compare <= value_max: - print(f"{value_min} <= {data_to_compare} <= {value_max} for file named {file}") + logger.info(f"{value_min} <= {data_to_compare} <= {value_max} for file named {file}") def wave_vector(f: float, complex_permeability: complex, complex_permittivity: complex, conductivity: float): @@ -1320,7 +1321,7 @@ def axial_wavelength(f: float, complex_permeability: float, complex_permittivity def check_mqs_condition(radius: float, frequency: float, complex_permeability: float, complex_permittivity: float, - conductivity: float, relative_margin_to_first_resonance: float = 0.5, silent: bool = False): + conductivity: float, relative_margin_to_first_resonance: float = 0.5): """ Check if the condition for a magnetoquasistatic simulation is fulfilled. @@ -1340,8 +1341,6 @@ def check_mqs_condition(radius: float, frequency: float, complex_permeability: f :type conductivity: float :param relative_margin_to_first_resonance: relative margin to the first resonance. Defaults to 0.5. :type relative_margin_to_first_resonance: float - :param silent: True for no terminal output - :type silent: bool """ if frequency == 0: raise ValueError("check_mqs_condition() only works for frequencies != 0") @@ -1352,11 +1351,10 @@ def check_mqs_condition(radius: float, frequency: float, complex_permeability: f if diameter_to_wavelength_ratio > diameter_to_wavelength_ratio_of_first_resonance * relative_margin_to_first_resonance: # raise Warning(f"Resonance Ratio: {diameter_to_wavelength_ratio / diameter_to_wavelength_ratio_of_first_resonance} - " # f"1 means 1st resonance - should be kept well below 1 to ensure MQS approach to be correct! ") - if not silent: - print(f"Resonance Ratio: {diameter_to_wavelength_ratio / diameter_to_wavelength_ratio_of_first_resonance}") + logger.info(f"Resonance Ratio: {diameter_to_wavelength_ratio / diameter_to_wavelength_ratio_of_first_resonance}") -def create_open_circuit_excitation_sweep(I0: float, n: float, frequency: float) -> List[List[float]]: +def create_open_circuit_excitation_sweep(I0: float, n: float, frequency: float) -> list[list[float]]: """ Create a circuit excitation sweep with the other windings unloaded. @@ -1392,14 +1390,14 @@ def list_to_complex(complex_list: list): return complex(complex_list[0], complex_list[1]) -def get_self_inductances_from_log(log: Dict) -> List: +def get_self_inductances_from_log(log: dict) -> list: """ Read the self-inductances from the result log file (dictionary). :param log: Result log dictionary - :type log: Dict + :type log: dict :return: self-inductances in a list - :rtype: List + :rtype: list """ self_inductances = [] for ol_index, open_loop_result in enumerate(log["single_sweeps"]): @@ -1408,14 +1406,14 @@ def get_self_inductances_from_log(log: Dict) -> List: return self_inductances -def get_flux_linkages_from_log(log: Dict) -> List: +def get_flux_linkages_from_log(log: dict) -> list: """ Read the flux-linkages from the result log file (dictionary). :param log: Result log dictionary - :type log: Dict + :type log: dict :return: flux-linkages in a list - :rtype: List + :rtype: list """ flux_linkages = [] for ol_index, open_loop_result in enumerate(log["single_sweeps"]): @@ -1425,12 +1423,12 @@ def get_flux_linkages_from_log(log: Dict) -> List: return flux_linkages -def get_coupling_matrix(flux_linkages: List) -> np.array: +def get_coupling_matrix(flux_linkages: list) -> np.array: """ Calculate the coupling factors from the given flux linkages. :param flux_linkages: flux-linkages - :type flux_linkages: List + :type flux_linkages: list :return: coupling-matrix in a matrix (np.array) :rtype: np.array """ @@ -1475,115 +1473,99 @@ def get_inductance_matrix(self_inductances: np.array, mean_coupling_factors: np. return inductance_matrix -def visualize_flux_linkages(flux_linkages: List, silent: bool) -> None: +def visualize_flux_linkages(flux_linkages: list) -> None: """ Print the flux linkages to the terminal (or file-) output. :param flux_linkages: flux-linkages in a list - :type flux_linkages: List - :param silent: True for no output - :type silent: bool + :type flux_linkages: list """ string_to_print = "" for x in range(0, len(flux_linkages)): for y in range(0, len(flux_linkages)): string_to_print += f"Phi_{x+1}{y+1} = {flux_linkages[x][y]} Induced by I_{y+1} in Winding{x+1}\n" - if not silent: - print("\nFluxes: ") - print(string_to_print) + + logger.info("\nFluxes: ") + logger.info(string_to_print) -def visualize_self_inductances(self_inductances: Union[List, np.array], flux_linkages: Union[List, np.array], silent: bool) -> None: +def visualize_self_inductances(self_inductances: list | np.ndarray, flux_linkages: list | np.ndarray) -> None: """ Print the self-inductances to the terminal (or file-) output. :param self_inductances: self-inductances in H in a list or numpy array - :type self_inductances: Union[List, np.array] + :type self_inductances: list | np.ndarray :param flux_linkages: flux linkages - :type flux_linkages: Union[List, np.array] - :param silent: True for no output - :type silent: bool + :type flux_linkages: st | np.ndarray """ string_to_print = "" for x in range(0, len(flux_linkages)): string_to_print += f"L_{x+1}_{x+1} = {self_inductances[x]}\n" - if not silent: - print("\n" - "Self Inductances: ") - print(string_to_print) + logger.info("\n" + "Self Inductances: ") + logger.info(string_to_print) -def visualize_self_resistances(self_inductances: List, flux_linkages: List, frequency: float, silent: bool) -> None: +def visualize_self_resistances(self_inductances: list, flux_linkages: list, frequency: float) -> None: """ Calculate and print the self resistances to the terminal (or file-) output. :param self_inductances: self-inductances in a list - :type self_inductances: List + :type self_inductances: list :param flux_linkages: flux-linkage - :type flux_linkages: List + :type flux_linkages: list :param frequency: Frequency in Hz :type frequency: float - :param silent: True for no output - :type silent: bool """ string_to_print = "" for x in range(0, len(flux_linkages)): string_to_print += f"Z_{x+1}_{x+1} = {self_inductances[x].imag*2*np.pi*frequency}\n" - if not silent: - print("\n" - "Self Resistances: ") - print(string_to_print) + logger.info("\n" + "Self Resistances: ") + logger.info(string_to_print) -def visualize_coupling_factors(coupling_matrix: np.array, flux_linkages: List, silent: bool): +def visualize_coupling_factors(coupling_matrix: np.array, flux_linkages: list): """ Print the coupling factors to the terminal (or file-) output. :param coupling_matrix: matrix with coupling factors between the windings :type coupling_matrix: np.array :param flux_linkages: flux-linkages in a list - :type flux_linkages: List - :param silent: True for no output - :type silent: bool + :type flux_linkages: list """ string_to_print = "" for x in range(0, len(flux_linkages)): for y in range(0, len(coupling_matrix)): string_to_print += f"K_{x + 1}{y + 1} = Phi_{x + 1}{y + 1} / Phi_{y + 1}{y + 1} = {coupling_matrix[x][y]}\n" - if not silent: - print("\n" - "Coupling Factors: ") - print(string_to_print) + logger.info("\n" + "Coupling Factors: ") + + logger.info(string_to_print) -def visualize_mean_coupling_factors(mean_coupling_factors: List, silent: bool): +def visualize_mean_coupling_factors(mean_coupling_factors: list): """ Print the mean coupling factors to the terminal (or file-) output. :param mean_coupling_factors: mean_coupling_factors in a list - :type mean_coupling_factors: List - :param silent: True for no output - :type silent: bool + :type mean_coupling_factors: list """ string_to_print = "" for x in range(0, len(mean_coupling_factors)): for y in range(0, len(mean_coupling_factors)): string_to_print += f"k_{x + 1}{y + 1} = Sqrt(K_{x + 1}{y + 1} * K_{y + 1}{x + 1}) = M_{x + 1}{y + 1} " \ f"/ Sqrt(L_{x + 1}_{x + 1} * L_{y + 1}_{y + 1}) = {mean_coupling_factors[x][y]}\n" - if not silent: - print("\n" - "Mean Coupling Factors: ") - print(string_to_print) + logger.info("\nMean Coupling Factors: ") + logger.info(string_to_print) -def visualize_mean_mutual_inductances(inductance_matrix: np.array, silent: bool): +def visualize_mean_mutual_inductances(inductance_matrix: np.array): """ Print the mean mutual inductances to the terminal (or file-) output. :param inductance_matrix: inductance matrix :type inductance_matrix: np.array - :param silent: True for no output - :type silent: bool e.g. M_12 = M_21 = k_12 * (L_11 * L_22) ** 0.5 """ @@ -1594,22 +1576,18 @@ def visualize_mean_mutual_inductances(inductance_matrix: np.array, silent: bool) pass else: string_to_print += f"M_{x + 1}{y + 1} = {inductance_matrix[x][y].real}\n" - if not silent: - print("\n" - "Mean Mutual Inductances: ") - print(string_to_print) + logger.info("\nMean Mutual Inductances: ") + logger.info(string_to_print) -def visualize_mutual_inductances(self_inductances: List, coupling_factors: List, silent: bool): +def visualize_mutual_inductances(self_inductances: list, coupling_factors: list): """ Print the mutual inductances to the terminal (or file-) output. :param self_inductances: Matrix with self inductances - :type self_inductances: List + :type self_inductances: list :param coupling_factors: Matrix with coupling factors - :type coupling_factors: List - :param silent: True for no output - :type silent: bool + :type coupling_factors: list e.g. M_12 = L_11 * K_21 != M_21 = L_22 * K_12 (ideally, they are the same) """ @@ -1620,21 +1598,17 @@ def visualize_mutual_inductances(self_inductances: List, coupling_factors: List, pass else: string_to_print += f"M_{x + 1}{y + 1} = {self_inductances[y].real * coupling_factors[x][y]}\n" - if not silent: - print("\n" - "Mutual Inductances: ") - print(string_to_print) + logger.info("\nMutual Inductances: ") + logger.info(string_to_print) -def visualize_inductance_matrix_coefficients(inductance_matrix: np.array, silent: bool): +def visualize_inductance_matrix_coefficients(inductance_matrix: np.array): """Visualize the inductance matrix coefficients in the terminal. e.g. M_12 = L_11 * K_21 != M_21 = L_22 * K_12 (ideally, they are the same) :param inductance_matrix: inductance matrix of transformer :type inductance_matrix: np.array - :param silent: False to show the terminal output - :type silent: bool """ string_to_print = "" for x in range(0, len(inductance_matrix)): @@ -1643,20 +1617,16 @@ def visualize_inductance_matrix_coefficients(inductance_matrix: np.array, silent string_to_print += f"L_{x + 1}{y + 1} = {inductance_matrix[x][y].real}\n" else: string_to_print += f"M_{x + 1}{y + 1} = {inductance_matrix[x][y].real}\n" - if not silent: - print("\n" - "Inductance Matrix Coefficients: ") - print(string_to_print) + logger.info("Inductance Matrix Coefficients: ") + logger.info(string_to_print) -def visualize_inductance_matrix(inductance_matrix: np.array, silent: bool) -> None: +def visualize_inductance_matrix(inductance_matrix: np.array) -> None: """ Visualize the inductance matrix in the terminal. :param inductance_matrix: inductance matrix in H :type inductance_matrix: np.array - :param silent: True for no output - :type silent: bool e.g. M_12 = L_11 * K_21 != M_21 = L_22 * K_12 (ideally, they are the same) """ @@ -1666,47 +1636,45 @@ def visualize_inductance_matrix(inductance_matrix: np.array, silent: bool) -> No string_to_print += f"{np.round(inductance_matrix[x][y].real, 12)} " string_to_print += "\n" - if not silent: - print("\n" - "Inductance Matrix: ") - print(string_to_print) + logger.info("\nInductance Matrix: ") + logger.info(string_to_print) -def calculate_quadrature_integral(time_steps: List[float], data: List[float]) -> float: +def calculate_quadrature_integral(time_steps: list[float], data: list[float]) -> float: """ Calculate the integral of given data over specific time steps using the quad method. :param time_steps: List of time steps. - :type time_steps: List[float] + :type time_steps: list[float] :param data: List of data corresponding to each timestep. - :type data: List[float] + :type data: list[float] :return: The calculated integral. :rtype: float """ func = lambda x: np.interp(x, time_steps, data) return quad(func, time_steps[0], time_steps[-1])[0] -def calculate_squared_quadrature_integral(time_steps: List[float], data: List[float]) -> float: +def calculate_squared_quadrature_integral(time_steps: list[float], data: list[float]) -> float: """ Calculate the integral of squared given data over specific time steps using the quad method. :param time_steps: List of time steps. - :type time_steps: List[float] + :type time_steps: list[float] :param data: List of data corresponding to each timestep. - :type data: List[float] + :type data: list[float] :return: The calculated integral. :rtype: float """ func = lambda x: np.interp(x, time_steps, data) ** 2 return quad(func, time_steps[0], time_steps[-1])[0] -def calculate_average(integral: float, time_steps: List[float]) -> float: +def calculate_average(integral: float, time_steps: list[float]) -> float: """ Compute the average in general. :param integral: The integral value. :type integral: float :param time_steps: List of time steps. - :type time_steps: List[float] + :type time_steps: list[float] Returns: :return: The calculated average. @@ -1717,14 +1685,14 @@ def calculate_average(integral: float, time_steps: List[float]) -> float: raise ValueError("Total time cannot be zero.") return integral / total_time -def calculate_rms(squared_integral: float, time_steps: List[float]) -> float: +def calculate_rms(squared_integral: float, time_steps: list[float]) -> float: """ Compute the RMS. :param squared_integral: The integral value. :type squared_integral: float :param time_steps: List of time steps. - :type time_steps: List[float] + :type time_steps: list[float] Returns: :return: The calculated average. @@ -1762,14 +1730,14 @@ def convert_air_gap_corner_points_to_center_and_distance(corner_points: list) -> return centers, heights -def time_current_vector_to_fft_excitation(time_current_vectors: List[List[List[float]]], fft_filter_value_factor: float = 0.01): +def time_current_vector_to_fft_excitation(time_current_vectors: list[list[list[float]]], fft_filter_value_factor: float = 0.01): """ Perform FFT to get the primary and secondary currents e.g. to calculate the wire losses. For further calculations e.g. calculating wire losses, the single frequencies can be 'linear added' to get the total winding losses. :param time_current_vectors: primary and secondary current waveforms over time - :type time_current_vectors: List[List[List[float]]] + :type time_current_vectors: list[list[list[float]]] :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 @@ -1817,7 +1785,7 @@ def time_current_vector_to_fft_excitation(time_current_vectors: List[List[List[f return frequency_list, current_list_list, phi_deg_list_list -def hysteresis_current_excitation(input_time_current_vectors: List[List[List[float]]]): +def hysteresis_current_excitation(input_time_current_vectors: list[list[list[float]]]): """ Collect the peak current and the corresponding phase shift for the fundamental frequency for all windings. @@ -1825,10 +1793,10 @@ def hysteresis_current_excitation(input_time_current_vectors: List[List[List[flo In case of a center-tapped transformer, halving the amplitudes will be done by split_hysteresis_loss_excitation_center_tapped. :param input_time_current_vectors: e.g. [[time_vec, i_primary_vec], [time_vec, i_secondary_vec]] - :type input_time_current_vectors: List[List[List[float]]] + :type input_time_current_vectors: list[list[list[float]]] :raises ValueError: if time vector does not start at zero seconds. :return: hyst_frequency, hyst_current_amplitudes, hyst_phases_deg, e.g. 200400.80170764355 [6.13, 26.65] [49.13, 229.49] - :rtype: List[List[float]] + :rtype: list[list[float]] """ if input_time_current_vectors[0][0][0] != 0: raise ValueError("time must start at 0 seconds!") diff --git a/femmt/functions_drawing.py b/femmt/functions_drawing.py index 3561f82d..ce9e4c7e 100644 --- a/femmt/functions_drawing.py +++ b/femmt/functions_drawing.py @@ -1,7 +1,6 @@ """Functions to draw different conductor schemes for the FEM simulation.""" # python libraries import copy -from typing import List # 3rd party libraries import numpy as np @@ -26,7 +25,7 @@ def number_of_rows(row: ConductorRow) -> int: def single_row(number_of_conds_per_winding: int, window_width: float, winding_tag: WindingTag, conductor_type: ConductorType, - thickness: Optional[float] = None, radius: Optional[float] = None, cond_cond_isolation=None, + thickness: float | None = None, radius: float | None = None, cond_cond_isolation=None, additional_bobbin: float = 0) -> ConductorRow: """ Define a full row of the defined conductor in the specified window width. @@ -211,15 +210,15 @@ def get_height_of_group(group: CenterTappedGroup) -> float: return total_height -def add_tertiary_winding_to_stack(stack_order_without_tertiary: List[ConductorRow], - tertiary_row_to_be_added: List[ConductorRow]): +def add_tertiary_winding_to_stack(stack_order_without_tertiary: list[ConductorRow], + tertiary_row_to_be_added: list[ConductorRow]): """ Add tertiary winding to stack. :param stack_order_without_tertiary: list of ConductorRows - :type stack_order_without_tertiary: List[ConductorRow] + :type stack_order_without_tertiary: list[ConductorRow] :param tertiary_row_to_be_added: list of ConductorRows - :type tertiary_row_to_be_added: List[ConductorRow] + :type tertiary_row_to_be_added: list[ConductorRow] """ secondary_tags = [] number_of_added_tertiaries = 0 @@ -233,12 +232,12 @@ def add_tertiary_winding_to_stack(stack_order_without_tertiary: List[ConductorRo # Insert insulations into the stack_order -def insert_insulations_to_stack(stack_order: List, isolations: ThreeWindingIsolation): +def insert_insulations_to_stack(stack_order: list, isolations: ThreeWindingIsolation): """ Insert insulations to a stack. :param stack_order: order of the stack - :type stack_order: List + :type stack_order: list :param isolations: isolations according to the ThreeWindingIsolation class :type isolations: ThreeWindingIsolation """ @@ -292,13 +291,13 @@ def insert_insulations_to_stack(stack_order: List, isolations: ThreeWindingIsola # print(f"{insulation_tags = }") -def get_set_of_integers_from_string_list(string_list: List[str]): +def get_set_of_integers_from_string_list(string_list: list[str]): """Get the list of the set of integers in a list of strings. Used by winding_scheme key. :param string_list: - :type string_list: List[str] + :type string_list: list[str] """ integer_list = [] for single_string in string_list: @@ -310,7 +309,7 @@ def get_set_of_integers_from_string_list(string_list: List[str]): return integer_list -def stack_order_from_interleaving_scheme(interleaving_scheme: InterleavingSchemesFoilLitz, stack_order: List, +def stack_order_from_interleaving_scheme(interleaving_scheme: InterleavingSchemesFoilLitz, stack_order: list, primary_additional_bobbin: float, center_foil_additional_bobbin: float, primary_row: ConductorRow, secondary_row: ConductorRow, tertiary_row: ConductorRow, insulations: ThreeWindingIsolation): @@ -320,7 +319,7 @@ def stack_order_from_interleaving_scheme(interleaving_scheme: InterleavingScheme :param interleaving_scheme: interleaving scheme :type interleaving_scheme: InterleavingSchemesFoilLitz :param stack_order: order of the stack - :type stack_order: List + :type stack_order: list :param primary_additional_bobbin: primary additional bobbin thickness in m in the center :type primary_additional_bobbin: float :param center_foil_additional_bobbin: additional bobbin thickness in m for the center foil @@ -469,8 +468,8 @@ def stack_center_tapped_transformer(primary_row: ConductorRow, secondary_row: Co :type center_foil_additional_bobbin: float """ if not check_secondary_and_tertiary_are_the_same(secondary_row, tertiary_row): - print("Secondary and tertiary winding are not defined similar. " - "That is not a nice center-tapped transformer :(") + logger.warning("Secondary and tertiary winding are not defined similar. " + "That is not a nice center-tapped transformer :(") elif interleaving_type == CenterTappedInterleavingType.TypeA: # Usually for center-tapped it is the secondary/=tertiary winding number @@ -607,31 +606,31 @@ def is_even(x: int): return True -def center(l_list: List): +def center(l_list: list): """ Return the center index of a list. Rounds off. :param l_list: list to return the center index - :type l_list: List + :type l_list: list """ return int(len(l_list) / 2) -def mix_x_and_i(input_x: List, input_i: List): +def mix_x_and_i(input_x: list, input_i: list): """General usage to interleave windings. One winding could be input_x and the other input_i. Experimental. Example: 16 primary windings (input_x), 3 secondary windings (input_i). Tries to fit these windings symmetric into the winding window. :param input_x: List of e.g. primary windings - :type input_x: List + :type input_x: list :param input_i: List of e.g. secondary windings - :type input_i: List + :type input_i: list """ len_x = len(input_x) len_i = len(input_i) if len_x > len_i: - print("x must be smaller or equal I") + logger.warning("x must be smaller or equal I") else: if len_x == 0: return input_i @@ -656,6 +655,5 @@ def mix_x_and_i(input_x: List, input_i: List): temp_mixed = mix_x_and_i(x[0:-1], current) temp_mixed.insert(center(temp_mixed), temp_x) current = temp_mixed - # print(f"{I = }") input_list = [input_x[0], input_i[0]] return [input_list[i] for i in current] diff --git a/femmt/functions_reluctance.py b/femmt/functions_reluctance.py index 8e1b974e..de5612e5 100644 --- a/femmt/functions_reluctance.py +++ b/femmt/functions_reluctance.py @@ -1,6 +1,6 @@ """Functions to calculate reluctance models.""" # python libraries -from typing import Union, List +import logging # femmt libraries from femmt.constants import * @@ -11,13 +11,14 @@ import scipy from matplotlib import pyplot as plt +logger = logging.getLogger(__name__) -def calculate_ls_lh_n_from_inductance_matrix(inductance_matrix: Union[float, np.array]): +def calculate_ls_lh_n_from_inductance_matrix(inductance_matrix: float | np.ndarray): """ Calculate the transformer primary concentrated circuit parameters from matrix. :param inductance_matrix: input reluctance matrix in form of [[L_11, M], [M, L_22]] in H - :type inductance_matrix: Union[float, np.array] + :type inductance_matrix: float | np.ndarray :return l_s: primary concentrated stray inductance in H :return l_h: primary concentrated main inductance in H :return n: ratio @@ -35,19 +36,19 @@ def calculate_ls_lh_n_from_inductance_matrix(inductance_matrix: Union[float, np. return l_s, l_h, n -def calculate_inductance_matrix_from_ls_lh_n(l_s_target_value: Union[float, np.array], l_h_target_value: Union[float, np.array], - n_target_value: Union[float, np.array]) -> Union[float, np.array]: +def calculate_inductance_matrix_from_ls_lh_n(l_s_target_value: float | np.ndarray, l_h_target_value: float | np.ndarray, + n_target_value: float | np.ndarray) -> float | np.ndarray: """ Calculate the inductance matrix from ls, lh, n parameters. :param l_s_target_value: serial inductance in H - :type l_s_target_value: Union[float, np.array] + :type l_s_target_value: float | np.ndarray :param l_h_target_value: mutual inductance in H - :type l_h_target_value: Union[float, np.array] + :type l_h_target_value: float | np.ndarray :param n_target_value: transfer ratio - :type n_target_value: Union[float, np.array] + :type n_target_value: float | np.ndarray :return: inductance matrix in H - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ inductance_matrix = [ [l_s_target_value + l_h_target_value, l_h_target_value / n_target_value], @@ -62,25 +63,25 @@ def power_losses_hysteresis_cylinder_radial_direction_mu_r_imag( Calculate the hysteresis losses inside a cylinder, where the flux flows in radial direction. :param flux: flux in Wb - :type flux: Union[float, np.array] + :type flux: float | np.ndarray :param cylinder_height: cylinder height in m - :type cylinder_height: Union[float, np.array] + :type cylinder_height: float | np.ndarray :param cylinder_inner_radius: cylinder inner radius in m - :type cylinder_inner_radius: Union[float, np.array] + :type cylinder_inner_radius: float | np.ndarray :param cylinder_outer_radius: cylinder outer radius in m - :type cylinder_outer_radius: Union[float, np.array] + :type cylinder_outer_radius: float | np.ndarray :param fundamental_frequency: fundamental frequency in Hz - :type fundamental_frequency: Union[float, np.array] + :type fundamental_frequency: float | np.ndarray :param mu_r_imag_data_vec: imaginary part of u_r as vector - :type mu_r_imag_data_vec: Union[List, np.array] + :type mu_r_imag_data_vec: list | np.ndarray :param mu_r_abs: absolute value of mu_r: abs(mu_r) - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param flux_density_data_vec: flux-density data vector in T - :type flux_density_data_vec: Union[List, np.array] + :type flux_density_data_vec: list | np.ndarray """ - def flux_density_cylinder_envelope(cylinder_radius: Union[float, np.array], flux_in_cylinder: Union[float, np.array], - height_of_cylinder: Union[float, np.array]): + def flux_density_cylinder_envelope(cylinder_radius: float | np.ndarray, flux_in_cylinder: float | np.ndarray, + height_of_cylinder: float | np.ndarray): """ Helper-function, what is used as a function to integrate by scipy.integrate.quad. @@ -90,16 +91,16 @@ def flux_density_cylinder_envelope(cylinder_radius: Union[float, np.array], flux Note: function parameter names differ from outer parameters to avoid 'shadows name from outer scope'. :param cylinder_radius: cylinder radius - :type cylinder_radius: Union[float, np.array] + :type cylinder_radius: float | np.ndarray :param flux_in_cylinder: flux trough cylinder envelope depending on its radius - :type flux_in_cylinder: Union[float, np.array] + :type flux_in_cylinder: float | np.ndarray :param height_of_cylinder: cylinder height - :type height_of_cylinder: Union[float, np.array] + :type height_of_cylinder: float | np.ndarray """ return flux_in_cylinder / (2 * np.pi * cylinder_radius * height_of_cylinder) - def power_loss_density_cylinder_envelope(cylinder_radius: Union[float, np.array], flux_in_cylinder: Union[float, np.array], - height_of_cylinder: Union[float, np.array]) -> Union[float, np.array]: + def power_loss_density_cylinder_envelope(cylinder_radius: float | np.ndarray, flux_in_cylinder: float | np.ndarray, + height_of_cylinder: float | np.ndarray) -> float | np.ndarray: """ Helper-function, what is used as a function to integrate by scipy.integrate.quad. @@ -117,11 +118,11 @@ def power_loss_density_cylinder_envelope(cylinder_radius: Union[float, np.array] Note: function parameter names differ from outer parameters to avoid 'shadows name from outer scope'. :param cylinder_radius: cylinder radius in m - :type cylinder_radius: Union[float, np.array] + :type cylinder_radius: float | np.ndarray :param flux_in_cylinder: flux in Wb inside the cylinder - :type flux_in_cylinder: Union[float, np.array] + :type flux_in_cylinder: float | np.ndarray :param height_of_cylinder: height of the cylinder in m - :type height_of_cylinder: Union[float, np.array] + :type height_of_cylinder: float | np.ndarray """ mu_r_imag = np.interp(flux_density_cylinder_envelope(cylinder_radius, flux_in_cylinder, height_of_cylinder), flux_density_data_vec, mu_r_imag_data_vec) @@ -134,9 +135,9 @@ def power_loss_density_cylinder_envelope(cylinder_radius: Union[float, np.array] args=(flux, cylinder_height), epsabs=1e-4)[0] -def hyst_losses_core_half_mu_r_imag(core_inner_diameter: Union[float, np.array], window_h_half: Union[float, np.array], window_w: Union[float, np.array], - mu_r_abs: Union[float, np.array], flux_max: Union[float, np.array], fundamental_frequency: Union[float, np.array], - flux_density_data_vec: Union[List, np.array], mu_r_imag_data_vec: Union[List, np.array]) -> Union[float, np.array]: +def hyst_losses_core_half_mu_r_imag(core_inner_diameter: float | np.ndarray, window_h_half: float | np.ndarray, window_w: float | np.ndarray, + mu_r_abs: float | np.ndarray, flux_max: float | np.ndarray, fundamental_frequency: float | np.ndarray, + flux_density_data_vec: list | np.ndarray, mu_r_imag_data_vec: list | np.ndarray) -> float | np.ndarray: """ Calculate the losses of a core cylinder half. @@ -146,23 +147,23 @@ def hyst_losses_core_half_mu_r_imag(core_inner_diameter: Union[float, np.array], with each the half window_h :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_h_half: window height of the core-half to consider in m - :type window_h_half: Union[float, np.array] + :type window_h_half: float | np.ndarray :param window_w: window width in m - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray :param mu_r_abs: (absolute) mu_r value from core material datasheet - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param mu_r_imag_data_vec: imaginary part of mu_r as data vector - :type mu_r_imag_data_vec: Union[List, np.array] + :type mu_r_imag_data_vec: list | np.ndarray :param flux_max: maximum flux in Wb what appears in the core-half - :type flux_max: Union[float, np.array] + :type flux_max: float | np.ndarray :param fundamental_frequency: fundamental frequency of flux in Hz - :type fundamental_frequency: Union[float, np.array] + :type fundamental_frequency: float | np.ndarray :param flux_density_data_vec: flux density as data vector - :type flux_density_data_vec: Union[float, np.array] + :type flux_density_data_vec: float | np.ndarray :return: hysteresis losses of the core half in W - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ core_cross_section = (core_inner_diameter / 2) ** 2 * np.pi flux_density_max = flux_max / core_cross_section @@ -180,7 +181,7 @@ def hyst_losses_core_half_mu_r_imag(core_inner_diameter: Union[float, np.array], return 2 * losses_cylinder_inner + losses_cylinder_radial -def calculate_core_2daxi_total_volume(core_inner_diameter: Union[float, np.array], window_h: Union[float, np.array], window_w: Union[float, np.array]): +def calculate_core_2daxi_total_volume(core_inner_diameter: float | np.ndarray, window_h: float | np.ndarray, window_w: float | np.ndarray): """ Calculate the full volume of a rotationally symmetric core. @@ -188,11 +189,11 @@ def calculate_core_2daxi_total_volume(core_inner_diameter: Union[float, np.array This is the total volume used by the magnetic itself. :param core_inner_diameter: core inner diameter - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_h: winding window height - :type window_h: Union[float, np.array] + :type window_h: float | np.ndarray :param window_w: winding window width - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray """ outer_core_radius = calculate_r_outer(core_inner_diameter, window_w) @@ -201,50 +202,50 @@ def calculate_core_2daxi_total_volume(core_inner_diameter: Union[float, np.array return core_2daxi_total_volume -def calculate_r_outer(core_inner_diameter: Union[float, np.array], window_w: Union[float, np.array], - outer_core_cross_section_scale: Union[float, np.array] = 1.0) -> Union[float, np.array]: +def calculate_r_outer(core_inner_diameter: float | np.ndarray, window_w: float | np.ndarray, + outer_core_cross_section_scale: float | np.ndarray = 1.0) -> float | np.ndarray: """ Calculate outer core radius. Default assumption: outer core cross-section is same as inner core cross-section. :param outer_core_cross_section_scale: scales the outer legs cross-section relative to the center leg cross-section - :type outer_core_cross_section_scale: Union[float, np.array] + :type outer_core_cross_section_scale: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_w: width of core window in m - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray """ outer_core_radius = np.sqrt(outer_core_cross_section_scale * (core_inner_diameter/2)**2 + (core_inner_diameter / 2 + window_w)**2) return outer_core_radius def power_losses_hysteresis_cylinder_radial_direction( - flux: Union[float, np.array], cylinder_height: Union[float, np.array], cylinder_inner_radius: Union[float, np.array], - cylinder_outer_radius: Union[float, np.array], fundamental_frequency: Union[float, np.array], mu_r_imag: Union[float, np.array], - mu_r_abs: Union[float, np.array]) -> Union[float, np.array]: + flux: float | np.ndarray, cylinder_height: float | np.ndarray, cylinder_inner_radius: float | np.ndarray, + cylinder_outer_radius: float | np.ndarray, fundamental_frequency: float | np.ndarray, mu_r_imag: float | np.ndarray, + mu_r_abs: float | np.ndarray) -> float | np.ndarray: """ Calculate the hysteresis losses inside a cylinder, where the flux flows in radial direction. :param flux: flux in Wb - :type flux: Union[float, np.array] + :type flux: float | np.ndarray :param cylinder_height: cylinder height in m - :type cylinder_height: Union[float, np.array] + :type cylinder_height: float | np.ndarray :param cylinder_inner_radius: cylinder inner radius in m - :type cylinder_inner_radius: Union[float, np.array] + :type cylinder_inner_radius: float | np.ndarray :param cylinder_outer_radius: cylinder outer radius in m - :type cylinder_outer_radius: Union[float, np.array] + :type cylinder_outer_radius: float | np.ndarray :param fundamental_frequency: fundamental frequency in Hz - :type fundamental_frequency: Union[float, np.array] + :type fundamental_frequency: float | np.ndarray :param mu_r_imag: imaginary part of u_r - :type mu_r_imag: Union[float, np.array] + :type mu_r_imag: float | np.ndarray :param mu_r_abs: absolute value of mu_r: abs(mu_r) - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :return: Hysteresis losses in W of cylinder with flux in radial direction - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ - def flux_density_cylinder_envelope(cylinder_radius: Union[float, np.array], flux_in_cylinder: Union[float, np.array], - height_of_cylinder: Union[float, np.array]) -> Union[float, np.array]: + def flux_density_cylinder_envelope(cylinder_radius: float | np.ndarray, flux_in_cylinder: float | np.ndarray, + height_of_cylinder: float | np.ndarray) -> float | np.ndarray: """ Helper-function, what is used as a function to integrate by scipy.integrate.quad. @@ -254,18 +255,18 @@ def flux_density_cylinder_envelope(cylinder_radius: Union[float, np.array], flux Note: function parameter names differ from outer parameters to avoid 'shadows name from outer scope'. :param cylinder_radius: cylinder radius in m - :type cylinder_radius: Union[float, np.array] + :type cylinder_radius: float | np.ndarray :param flux_in_cylinder: flux in Wb trough cylinder envelope depending on its radius - :type flux_in_cylinder: Union[float, np.array] + :type flux_in_cylinder: float | np.ndarray :param height_of_cylinder: cylinder height in m - :type height_of_cylinder: Union[float, np.array] + :type height_of_cylinder: float | np.ndarray :return: Flux density in T - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ return flux_in_cylinder / (2 * np.pi * cylinder_radius * height_of_cylinder) - def power_loss_density_cylinder_envelope(cylinder_radius: Union[float, np.array], flux_in_cylinder: Union[float, np.array], - height_of_cylinder: Union[float, np.array]) -> Union[float, np.array]: + def power_loss_density_cylinder_envelope(cylinder_radius: float | np.ndarray, flux_in_cylinder: float | np.ndarray, + height_of_cylinder: float | np.ndarray) -> float | np.ndarray: """ Helper-function, what is used as a function to integrate by scipy.integrate.quad. @@ -289,13 +290,13 @@ def power_loss_density_cylinder_envelope(cylinder_radius: Union[float, np.array] Note: function parameter names differ from outer parameters to avoid 'shadows name from outer scope'. :param cylinder_radius: cylinder radius in m - :type cylinder_radius: Union[float, np.array] + :type cylinder_radius: float | np.ndarray :param flux_in_cylinder: Flux in cylinder in Wb - :type flux_in_cylinder: Union[float, np.array] + :type flux_in_cylinder: float | np.ndarray :param height_of_cylinder: height of cylinder in m - :type height_of_cylinder: Union[float, np.array] + :type height_of_cylinder: float | np.ndarray :return: Power loss density in W/m³ - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ return 2 * np.pi * cylinder_radius * height_of_cylinder * np.pi * fundamental_frequency * mu_0 * mu_r_imag * \ (flux_density_cylinder_envelope(cylinder_radius, @@ -305,9 +306,9 @@ def power_loss_density_cylinder_envelope(cylinder_radius: Union[float, np.array] args=(flux, cylinder_height), epsabs=1e-4)[0] -def hyst_losses_core_half(core_inner_diameter: Union[float, np.array], window_h_half: Union[float, np.array], window_w: Union[float, np.array], - mu_r_imag: Union[float, np.array], mu_r_abs: Union[float, np.array], flux_max: Union[float, np.array], - fundamental_frequency: Union[float, np.array]) -> Union[float, np.array]: +def hyst_losses_core_half(core_inner_diameter: float | np.ndarray, window_h_half: float | np.ndarray, window_w: float | np.ndarray, + mu_r_imag: float | np.ndarray, mu_r_abs: float | np.ndarray, flux_max: float | np.ndarray, + fundamental_frequency: float | np.ndarray) -> float | np.ndarray: """ Calculate the losses of a core cylinder half. @@ -317,21 +318,21 @@ def hyst_losses_core_half(core_inner_diameter: Union[float, np.array], window_h_ function twice with each the half window_h :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_h_half: window height in m of the core-half to consider - :type window_h_half: Union[float, np.array] + :type window_h_half: float | np.ndarray :param window_w: window width in m - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray :param mu_r_abs: (absolute) mu_r value from core material datasheet - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param mu_r_imag: imaginary part of mu_r - :type mu_r_imag: Union[float, np.array] + :type mu_r_imag: float | np.ndarray :param flux_max: maximum flux in Wb what appears in the core-half - :type flux_max: Union[float, np.array] + :type flux_max: float | np.ndarray :param fundamental_frequency: fundamental frequency of flux in Hz - :type fundamental_frequency: Union[float, np.array] + :type fundamental_frequency: float | np.ndarray :return: Hysteresis losses in W of the core half - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ core_cross_section = (core_inner_diameter / 2) ** 2 * np.pi flux_density_max = flux_max / core_cross_section @@ -376,16 +377,16 @@ def calculate_reluctance_matrix(winding_matrix: np.array, inductance_matrix: np. return np.matmul(np.matmul(winding_matrix, L_invert), np.transpose(winding_matrix)) -def calculate_inductance_matrix(reluctance_matrix: Union[float, np.array], winding_matrix: Union[float, np.array]) -> Union[float, np.array]: +def calculate_inductance_matrix(reluctance_matrix: float | np.ndarray, winding_matrix: float | np.ndarray) -> float | np.ndarray: """ Calculate the inductance matrix out of reluctance matrix and winding matrix. :param reluctance_matrix: matrix of transformer reluctance - :type reluctance_matrix: Union[float, np.array] + :type reluctance_matrix: float | np.ndarray :param winding_matrix: matrix of transformer windings - :type winding_matrix: Union[float, np.array] + :type winding_matrix: float | np.ndarray :return: inductance matrix in H - :rtype: Union[float, np.array] + :rtype: float | np.ndarray reluctance matrix e.g. @@ -405,8 +406,8 @@ def calculate_inductance_matrix(reluctance_matrix: Union[float, np.array], windi return np.matmul(np.matmul(np.transpose(winding_matrix), reluctance_matrix_invert), winding_matrix) -def calculate_flux_matrix(reluctance_matrix: Union[float, np.array], winding_matrix: Union[float, np.array], - current_matrix: Union[float, np.array]) -> Union[float, np.array]: +def calculate_flux_matrix(reluctance_matrix: float | np.ndarray, winding_matrix: float | np.ndarray, + current_matrix: float | np.ndarray) -> float | np.ndarray: """ Calculate the flux for e.g. an integrated transformer. @@ -423,13 +424,13 @@ def calculate_flux_matrix(reluctance_matrix: Union[float, np.array], winding_mat flux_matrix = [ [flux_1], [flux_2] ] :param reluctance_matrix: matrix of transformer reluctance - :type reluctance_matrix: Union[float, np.array] + :type reluctance_matrix: float | np.ndarray :param winding_matrix: matrix of transformer windings - :type winding_matrix: Union[float, np.array] + :type winding_matrix: float | np.ndarray :return: inductance matrix in H - :rtype: Union[float, np.array] + :rtype: float | np.ndarray :param current_matrix: matrix of currents in A - :type current_matrix: Union[float, np.array] + :type current_matrix: float | np.ndarray """ if np.ndim(reluctance_matrix) == 0: reluctance_matrix_invert = 1 / reluctance_matrix @@ -439,12 +440,12 @@ def calculate_flux_matrix(reluctance_matrix: Union[float, np.array], winding_mat return np.matmul(np.matmul(reluctance_matrix_invert, winding_matrix), current_matrix) -def time_vec_current_vec_from_time_current_vec(time_current_vec: List[List[float]] | np.array): +def time_vec_current_vec_from_time_current_vec(time_current_vec: list[list[float]] | np.ndarray): """ Split a time-current vector into time and current vector. :param time_current_vec: [time_vector, current_vector] - :type time_current_vec: List[List[float]] + :type time_current_vec: list[list[float]] """ return time_current_vec[0], time_current_vec[1] @@ -487,17 +488,17 @@ def visualize_current_and_flux(time, flux_top_vec, flux_bot_vec, flux_stray_vec, Visualize current and flux over time for the integrated transformer. :param time: time vector - :type time: Union[List, np.array] + :type time: list | np.ndarray :param flux_top_vec: flux vector in top core part - :type flux_top_vec: Union[List, np.array] + :type flux_top_vec: list | np.ndarray :param flux_bot_vec: flux vector in bottom core part - :type flux_bot_vec: Union[List, np.array] + :type flux_bot_vec: list | np.ndarray :param flux_stray_vec: flux vector in stray core part - :type flux_stray_vec: Union[List, np.array] + :type flux_stray_vec: list | np.ndarray :param current_1_vec: current vector of winding 1 - :type current_1_vec: Union[List, np.array] + :type current_1_vec: list | np.ndarray :param current_2_vec: current vector of winding 2 - :type current_2_vec: Union[List, np.array] + :type current_2_vec: list | np.ndarray """ figure, axis = plt.subplots(2, figsize=(4, 4)) @@ -555,58 +556,58 @@ def phases_deg_from_time_current(time_vec: list, *args): return tuple(phases_deg) -def power_loss_hysteresis_simple_volume(fundamental_frequency: Union[float, np.array], mu_r_imag: Union[float, np.array], - flux_density_max: Union[float, np.array], - mu_r_abs: Union[float, np.array], core_volume: Union[float, np.array]) -> Union[float, np.array]: +def power_loss_hysteresis_simple_volume(fundamental_frequency: float | np.ndarray, mu_r_imag: float | np.ndarray, + flux_density_max: float | np.ndarray, + mu_r_abs: float | np.ndarray, core_volume: float | np.ndarray) -> float | np.ndarray: """ Calculate the hysteresis losses depending on the input parameters. The output are the losses for a certain volume of core. :param fundamental_frequency: fundamental frequency in Hz - :type fundamental_frequency: Union[float, np.array] + :type fundamental_frequency: float | np.ndarray :param mu_r_imag: imaginary part of u_r - :type mu_r_imag: Union[float, np.array] + :type mu_r_imag: float | np.ndarray :param flux_density_max: maximum flux density - :type flux_density_max: Union[float, np.array] + :type flux_density_max: float | np.ndarray :param mu_r_abs: abs(mu_r) - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param core_volume: core volume in m³ - :type core_volume: Union[float, np.array] + :type core_volume: float | np.ndarray :return: Hysteresis losses in W for a given volume - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ return core_volume * np.pi * fundamental_frequency * mu_r_imag * mu_0 * (flux_density_max / mu_0 / mu_r_abs) ** 2 -def power_loss_hysteresis_simple_volume_mu_r_imag(fundamental_frequency: Union[float, np.array], flux_density_max: Union[float, np.array], - mu_r_abs: Union[float, np.array], core_volume: Union[float, np.array], - flux_density_data_vec: Union[float, np.array], mu_r_imag_data_vec: Union[float, np.array]): +def power_loss_hysteresis_simple_volume_mu_r_imag(fundamental_frequency: float | np.ndarray, flux_density_max: float | np.ndarray, + mu_r_abs: float | np.ndarray, core_volume: float | np.ndarray, + flux_density_data_vec: float | np.ndarray, mu_r_imag_data_vec: float | np.ndarray): """ Calculate the hysteresis losses depending on the input parameters. The output are the losses for a certain volume of core. :param fundamental_frequency: fundamental frequency in Hz - :type fundamental_frequency: Union[float, np.array] + :type fundamental_frequency: float | np.ndarray :param mu_r_imag_data_vec: imaginary part of u_r as data vector - :type mu_r_imag_data_vec: Union[float, np.array] + :type mu_r_imag_data_vec: float | np.ndarray :param flux_density_max: maximum flux density - :type flux_density_max: Union[float, np.array] + :type flux_density_max: float | np.ndarray :param mu_r_abs: abs(mu_r) - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param core_volume: core volume - :type core_volume: Union[float, np.array] + :type core_volume: float | np.ndarray :param flux_density_data_vec: flux density as data input vector - :type flux_density_data_vec: Union[float, np.array] + :type flux_density_data_vec: float | np.ndarray """ mu_r_imag = np.interp(flux_density_max, flux_density_data_vec, mu_r_imag_data_vec) return core_volume * np.pi * fundamental_frequency * mu_r_imag * mu_0 * (flux_density_max / mu_0 / mu_r_abs) ** 2 -def r_basic_round_inf(air_gap_radius: Union[float, np.array], air_gap_basic_height: Union[float, np.array], - core_height: Union[float, np.array]) -> Union[float, np.array]: +def r_basic_round_inf(air_gap_radius: float | np.ndarray, air_gap_basic_height: float | np.ndarray, + core_height: float | np.ndarray) -> float | np.ndarray: """ Calculate the r_basic for a round to infinite structure. Do not use this function directly. @@ -619,14 +620,14 @@ def r_basic_round_inf(air_gap_radius: Union[float, np.array], air_gap_basic_heig [according to "A Novel Approach for 3D Air Gap Reluctance Calculations" - J. Mühlethaler, J.W. Kolar, A. Ecklebe] :param air_gap_radius: air gap radius in m - :type air_gap_radius: Union[float, np.array] + :type air_gap_radius: float | np.ndarray :param air_gap_basic_height: air gap height in m for the BASIC-AIR-GAP (e.g. if you use a round-round structure, this is half of the total air gap). - :type air_gap_basic_height: Union[float, np.array] + :type air_gap_basic_height: float | np.ndarray :param core_height: core height in m - :type core_height: Union[float, np.array] + :type core_height: float | np.ndarray :return: basic reluctance for round - infinite structure - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ conductance_basic = mu_0 * (air_gap_radius * 2 / 2 / air_gap_basic_height + 2 / np.pi * \ (1 + np.log(np.pi * core_height / 4 / air_gap_basic_height))) @@ -634,8 +635,8 @@ def r_basic_round_inf(air_gap_radius: Union[float, np.array], air_gap_basic_heig return 1 / conductance_basic -def sigma_round(r_equivalent: Union[float, np.array], air_gap_radius: Union[float, np.array], air_gap_total_height: Union[float, np.array])\ - -> Union[float, np.array]: +def sigma_round(r_equivalent: float | np.ndarray, air_gap_radius: float | np.ndarray, air_gap_total_height: float | np.ndarray)\ + -> float | np.ndarray: """ Calculate sigma for a round structure. Do not use this function directly. @@ -645,32 +646,32 @@ def sigma_round(r_equivalent: Union[float, np.array], air_gap_radius: Union[floa instead! :param r_equivalent: this is a series/parallel connection of r_basic, depending on the air gap structure - :type r_equivalent: Union[float, np.array] + :type r_equivalent: float | np.ndarray :param air_gap_radius: air gap radius in m - :type air_gap_radius: Union[float, np.array] + :type air_gap_radius: float | np.ndarray :param air_gap_total_height: air gap total height in m (for the total air gap, also for round-round structures) - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :return: fringing factor 'sigma' - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ return r_equivalent * mu_0 * air_gap_radius / air_gap_total_height -def r_air_gap_round_round(air_gap_total_height: Union[float, np.array], core_inner_diameter: Union[float, np.array], core_height_upper: Union[float, np.array], - core_height_lower: Union[float, np.array]) -> Union[float, np.array]: +def r_air_gap_round_round(air_gap_total_height: float | np.ndarray, core_inner_diameter: float | np.ndarray, core_height_upper: float | np.ndarray, + core_height_lower: float | np.ndarray) -> float | np.ndarray: """ Return the reluctance of a round-round air gap structure and includes fringing effects. :param air_gap_total_height: total air gap height of the air gap - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :param core_inner_diameter: core inner diameter - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param core_height_upper: core height upper (needed for better calculating fringing effects) - :type core_height_upper: Union[float, np.array] + :type core_height_upper: float | np.ndarray :param core_height_lower: core height lower (needed for better calculating fringing effects) - :type core_height_lower: Union[float, np.array] + :type core_height_lower: float | np.ndarray :return: air gap reluctance for round-round structure including fringing effects - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ if np.any(air_gap_total_height == 0): raise ValueError("'air_gap_total_height' can not be Zero!") @@ -703,39 +704,39 @@ def r_air_gap_round_round(air_gap_total_height: Union[float, np.array], core_inn return r_air_gap -def r_air_gap_round_round_sct(air_gap_total_height: Union[float, np.array], core_inner_diameter: Union[float, np.array], - core_height_upper: Union[float, np.array], core_height_lower: Union[float, np.array], - target_reluctance: Union[float, np.array]) -> Union[float, np.array]: +def r_air_gap_round_round_sct(air_gap_total_height: float | np.ndarray, core_inner_diameter: float | np.ndarray, + core_height_upper: float | np.ndarray, core_height_lower: float | np.ndarray, + target_reluctance: float | np.ndarray) -> float | np.ndarray: """ Calculate the air gap length of a round-round structure by a given target reluctance. :param air_gap_total_height: total height in m of all air gaps - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param target_reluctance: target reluctance - :type target_reluctance: Union[float, np.array] + :type target_reluctance: float | np.ndarray :param core_height_upper: height of upper core part in m - :type core_height_upper: Union[float, np.array] + :type core_height_upper: float | np.ndarray :param core_height_lower: height of lower core part in m - :type core_height_lower: Union[float, np.array] + :type core_height_lower: float | np.ndarray """ return r_air_gap_round_round(air_gap_total_height, core_inner_diameter, core_height_upper, core_height_lower) - \ target_reluctance -def r_air_gap_round_inf(air_gap_total_height: Union[float, np.array], core_inner_diameter: Union[float, np.array], core_height: Union[float, np.array]): +def r_air_gap_round_inf(air_gap_total_height: float | np.ndarray, core_inner_diameter: float | np.ndarray, core_height: float | np.ndarray): """ Return the reluctance of a round-infinite air gap structure and includes fringing effects. :param air_gap_total_height: total air gap height of the air gap in m - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param core_height: core height in m (needed for better calculating fringing effects) - :type core_height: Union[float, np.array] + :type core_height: float | np.ndarray :return: air gap reluctance for round-inf structure including fringing effects - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ air_gap_total_height = np.array(air_gap_total_height) core_inner_diameter = np.array(core_inner_diameter) @@ -753,25 +754,25 @@ def r_air_gap_round_inf(air_gap_total_height: Union[float, np.array], core_inner return r_air_gap -def r_air_gap_round_inf_sct(air_gap_total_height: Union[float, np.array], core_inner_diameter: Union[float, np.array], - core_height: Union[float, np.array], target_reluctance: Union[float, np.array]) -> Union[float, np.array]: +def r_air_gap_round_inf_sct(air_gap_total_height: float | np.ndarray, core_inner_diameter: float | np.ndarray, + core_height: float | np.ndarray, target_reluctance: float | np.ndarray) -> float | np.ndarray: """ Calculate the air gap length of a round infinite structure by a given target reluctance. :param air_gap_total_height: total height in m of all air gaps - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param target_reluctance: target reluctance - :type target_reluctance: Union[float, np.array] + :type target_reluctance: float | np.ndarray :param core_height: height of core in m - :type core_height: Union[float, np.array] + :type core_height: float | np.ndarray """ return r_air_gap_round_inf(air_gap_total_height, core_inner_diameter, core_height) - target_reluctance -def r_basic_tablet_cyl(tablet_height: Union[float, np.array], air_gap_basic_height: Union[float, np.array], - tablet_radius: Union[float, np.array]) -> Union[float, np.array]: +def r_basic_tablet_cyl(tablet_height: float | np.ndarray, air_gap_basic_height: float | np.ndarray, + tablet_radius: float | np.ndarray) -> float | np.ndarray: """ Calculate the r_basic for round to infinite structure. Do not use this function directly. @@ -785,14 +786,14 @@ def r_basic_tablet_cyl(tablet_height: Union[float, np.array], air_gap_basic_heig Note: this is the same function as r_basic_round_inf, but with clear variable names for tablet-cylinder structure :param tablet_height: tablet height in m = air gap width for tablet-cylinder structure - :type tablet_height: Union[float, np.array] + :type tablet_height: float | np.ndarray :param air_gap_basic_height: air gap height in m for the BASIC-AIR-GAP (e.g. if you use a round-round structure, this is half of the total air gap). - :type air_gap_basic_height: Union[float, np.array] + :type air_gap_basic_height: float | np.ndarray :param tablet_radius: tablet radius in m - :type tablet_radius: Union[float, np.array] + :type tablet_radius: float | np.ndarray :return: basic reluctance for tablet in m - cylinder structure - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ if air_gap_basic_height == 0: raise ZeroDivisionError(f"Division by zero: {air_gap_basic_height=}") @@ -803,7 +804,7 @@ def r_basic_tablet_cyl(tablet_height: Union[float, np.array], air_gap_basic_heig return 1 / conductance_basic -def sigma_tablet_cyl(r_equivalent: Union[float, np.array], tablet_height: Union[float, np.array], air_gap_total_height: Union[float, np.array]): +def sigma_tablet_cyl(r_equivalent: float | np.ndarray, tablet_height: float | np.ndarray, air_gap_total_height: float | np.ndarray): """ Do not use this function directly! Calculate sigma for a tablet-cylinder structure. @@ -814,34 +815,34 @@ def sigma_tablet_cyl(r_equivalent: Union[float, np.array], tablet_height: Union[ Note: this is the same function as sigma_round, but with clear variable names for tablet-cylinder structure :param r_equivalent: this is a series/parallel connection of r_basic, depending on the air gap structure - :type r_equivalent: Union[float, np.array] + :type r_equivalent: float | np.ndarray :param tablet_height: tablet height - :type tablet_height: Union[float, np.array] + :type tablet_height: float | np.ndarray :param air_gap_total_height: air gap total height (for the total air gap) - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :return: fringing factor 'sigma' for tablet - cylinder structure - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ return r_equivalent * mu_0 * tablet_height / air_gap_total_height -def r_air_gap_tablet_cyl(tablet_height: Union[float, np.array], air_gap_total_height: Union[float, np.array], core_inner_diameter: Union[float, np.array], - window_w: Union[float, np.array]) -> Union[float, np.array]: +def r_air_gap_tablet_cyl(tablet_height: float | np.ndarray, air_gap_total_height: float | np.ndarray, core_inner_diameter: float | np.ndarray, + window_w: float | np.ndarray) -> float | np.ndarray: """ Return the reluctance of a cylinder-tablet air gap structure and includes fringing effects. This function calculates the air gap reluctance for a 2D-axisymmetric core. :param tablet_height: tablet height in m - :type tablet_height: Union[float, np.array] + :type tablet_height: float | np.ndarray :param air_gap_total_height: total air gap height in m - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_w: core window width in m - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray :return: air gap reluctance for tablet - cylinder structure including air gap fringing - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ r_inner = core_inner_diameter / 2 + window_w @@ -863,22 +864,22 @@ def r_air_gap_tablet_cyl(tablet_height: Union[float, np.array], air_gap_total_he return r_air_gap -def r_air_gap_tablet_cylinder_sct(air_gap_total_height: Union[float, np.array], core_inner_diameter: Union[float, np.array], - tablet_height: Union[float, np.array], window_w: Union[float, np.array], - target_reluctance: Union[float, np.array]) -> Union[float, np.array]: +def r_air_gap_tablet_cylinder_sct(air_gap_total_height: float | np.ndarray, core_inner_diameter: float | np.ndarray, + tablet_height: float | np.ndarray, window_w: float | np.ndarray, + target_reluctance: float | np.ndarray) -> float | np.ndarray: """ Calculate the air gap length of a table cylinder by a given target reluctance. :param air_gap_total_height: total height in m of all air gaps - :type air_gap_total_height: Union[float, np.array] + :type air_gap_total_height: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param tablet_height: height of tablet in m - :type tablet_height: Union[float, np.array] + :type tablet_height: float | np.ndarray :param window_w: window width in m - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray :param target_reluctance: target reluctance - :type target_reluctance: Union[float, np.array] + :type target_reluctance: float | np.ndarray """ return r_air_gap_tablet_cyl(tablet_height, air_gap_total_height, core_inner_diameter, window_w) - target_reluctance @@ -895,15 +896,15 @@ def r_air_gap_tablet_cyl_no_2d_axi(tablet_height, air_gap_total_length, core_inn core when you are in a xy-coordinate system. :param tablet_height: tablet height in m - :type tablet_height: Union[float, np.array] + :type tablet_height: float | np.ndarray :param air_gap_total_length: air gap total length - :type air_gap_total_length: Union[float, np.array] + :type air_gap_total_length: float | np.ndarray :param core_inner_diameter: core inner diameter in m - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_w: core window width in m - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray :return: air gap reluctance for tablet - cylinder structure including air gap fringing - :rtype: Union[float, np.array] + :rtype: float | np.ndarray """ r_inner = core_inner_diameter / 2 + window_w @@ -946,19 +947,19 @@ def r_air_gap_tablet_cyl_no_2d_axi(tablet_height, air_gap_total_length, core_inn return r_air_gap -def r_core_tablet(tablet_height: Union[float, np.array], tablet_radius: Union[float, np.array], mu_r_abs: Union[float, np.array], - core_inner_diameter: Union[float, np.array]): +def r_core_tablet(tablet_height: float | np.ndarray, tablet_radius: float | np.ndarray, mu_r_abs: float | np.ndarray, + core_inner_diameter: float | np.ndarray): """ Calculate the magnetic resistance of the core tablet. :param tablet_height: tablet height in m - :type tablet_height: Union[float, np.array] + :type tablet_height: float | np.ndarray :param tablet_radius: tablet radius in m - :type tablet_radius: Union[float, np.array] + :type tablet_radius: float | np.ndarray :param mu_r_abs: relative permeability (mu_r) of the core material from datasheet - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param core_inner_diameter: core inner diameter. For idealized core material, this value can be 0.001. - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray """ return np.log(tablet_radius / (core_inner_diameter / 2)) / (2 * np.pi * mu_0 * mu_r_abs * tablet_height) @@ -978,34 +979,34 @@ def r_core_tablet_2(tablet_height, tablet_radius, mu_r_abs): return tablet_height / (np.pi * mu_0 * mu_r_abs * tablet_radius ** 2) -def r_core_top_bot_radiant(core_inner_diameter: Union[float, np.array], window_w: Union[float, np.array], mu_r_abs: Union[float, np.array], - core_top_bot_height: Union[float, np.array]) -> Union[float, np.array]: +def r_core_top_bot_radiant(core_inner_diameter: float | np.ndarray, window_w: float | np.ndarray, mu_r_abs: float | np.ndarray, + core_top_bot_height: float | np.ndarray) -> float | np.ndarray: """ Calculate the top or bottom core material part. :param core_inner_diameter: core inner diameter - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param window_w: width of winding window - :type window_w: Union[float, np.array] + :type window_w: float | np.ndarray :param mu_r_abs: relative permeability (mu_r) of the core material from datasheet - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray :param core_top_bot_height: height of the core material top / bottom of the winding window - :type core_top_bot_height: Union[float, np.array] + :type core_top_bot_height: float | np.ndarray """ return np.log((core_inner_diameter + 2 * window_w) / core_inner_diameter) / \ (2 * np.pi * mu_0 * mu_r_abs * core_top_bot_height) -def r_core_round(core_inner_diameter: Union[float, np.array], core_round_height: Union[float, np.array], mu_r_abs: Union[float, np.array]): +def r_core_round(core_inner_diameter: float | np.ndarray, core_round_height: float | np.ndarray, mu_r_abs: float | np.ndarray): """ Calculate the core reluctance for a round structure. :param core_round_height: height of the round core part section - :type core_round_height: Union[float, np.array] + :type core_round_height: float | np.ndarray :param core_inner_diameter: core inner diameter - :type core_inner_diameter: Union[float, np.array] + :type core_inner_diameter: float | np.ndarray :param mu_r_abs: relative permeability (mu_r) of the core material from datasheet - :type mu_r_abs: Union[float, np.array] + :type mu_r_abs: float | np.ndarray """ return core_round_height / (mu_0 * mu_r_abs * (core_inner_diameter / 2) ** 2 * np.pi) @@ -1135,7 +1136,7 @@ def resistance_litz_wire(core_inner_diameter: float, window_w: float, window_h: # get the total turn length total_turn_length = 0 for windings_in_row in windings_per_row: - print(f"{windings_in_row=}") + logger.info(f"{windings_in_row=}") total_turn_length += np.sum(turn_length_per_column[:int(windings_in_row)]) else: raise ValueError(f"{scheme} not defined. Must be 'horizontal_first' or 'vertical_first'.") diff --git a/femmt/functions_topologies.py b/femmt/functions_topologies.py index 3276c693..be71d70a 100644 --- a/femmt/functions_topologies.py +++ b/femmt/functions_topologies.py @@ -1,7 +1,6 @@ """Includes functions to generate different winding topologies.""" # python libraries import copy -from typing import Optional # 3rd party libraries @@ -212,7 +211,7 @@ def set_center_tapped_windings(core: Core, iso_primary_to_primary: float, iso_secondary_to_secondary: float, iso_primary_to_secondary: float, interleaving_type: CenterTappedInterleavingType, interleaving_scheme: InterleavingSchemesFoilLitz, bobbin_coil_top: float = None, bobbin_coil_bot: float = None, bobbin_coil_left: float = None, bobbin_coil_right: float = None, - primary_coil_turns: int = None, winding_temperature: Optional[float] = None, + primary_coil_turns: int = None, winding_temperature: float | None = None, wrap_para_type: WrapParaType = WrapParaType.FixedThickness, foil_horizontal_placing_strategy: FoilHorizontalDistribution = None): """ @@ -265,7 +264,7 @@ def set_center_tapped_windings(core: Core, :param primary_coil_turns: Number of primary coil turns :type primary_coil_turns: int :param winding_temperature: winding temperature in °C - :type winding_temperature: Optional[float] + :type winding_temperature: float | None :param wrap_para_type: wrap parameter type :type wrap_para_type: WrapParaType :param foil_horizontal_placing_strategy: strategy for placing foil horizontal windings diff --git a/femmt/hpc.py b/femmt/hpc.py index 093d2fb3..afe3270b 100644 --- a/femmt/hpc.py +++ b/femmt/hpc.py @@ -1,13 +1,14 @@ """Functions to provide parallel computing on multiple processes. Each process has its own thread.""" # Python standard libraries from multiprocessing import Pool -from typing import List, Dict, Callable import os import shutil +import logging # Third parry libraries from femmt import MagneticComponent +logger = logging.getLogger(__name__) def _copy_electro_magnetic_necessary_files(src_folder: str, dest_folder: str): """ @@ -27,7 +28,7 @@ def _copy_electro_magnetic_necessary_files(src_folder: str, dest_folder: str): shutil.copy(from_path, to_path) -def hpc_single_simulation(parameters: Dict): +def hpc_single_simulation(parameters: dict): """ Perform single simulations in parallel. @@ -39,15 +40,15 @@ def hpc_single_simulation(parameters: Dict): :param parameters: Dictionary containing the needed parameters. One parameter is always 'model' which is the MagneticComponent. The other one is always 'simulation_parameters' which is also a dict and then contains the parameters given to the run() function (for this specific model). - :type parameters: Dict + :type parameters: dict """ model = parameters["model"] if "freq" not in parameters["simulation_parameters"]: - print("'freq' argument is missing. Simulation will be skipped.") + logger.info("'freq' argument is missing. Simulation will be skipped.") return if "current" not in parameters["simulation_parameters"]: - print("'current' argument is missing. Simulation will be skipped.") + logger.info("'current' argument is missing. Simulation will be skipped.") return freq = parameters["simulation_parameters"]["freq"] @@ -57,8 +58,8 @@ def hpc_single_simulation(parameters: Dict): model.single_simulation(freq=freq, current=current, plot_interpolation=False, show_fem_simulation_results=False) -def run_hpc(n_processes: int, models: List[MagneticComponent], simulation_parameters: List[Dict], - working_directory: str, custom_hpc: Callable = None): +def run_hpc(n_processes: int, models: list[MagneticComponent], simulation_parameters: list[dict], + working_directory: str, custom_hpc: callable = None): """Execute the given models on the given number of parallel processes. Typically, this number shouldn't be higher than the number of cores of the processor. @@ -66,21 +67,21 @@ def run_hpc(n_processes: int, models: List[MagneticComponent], simulation_parame :param n_processes: Number of parallel processes. If None, the number returned by os.cpu_count() is used. :type n_processes: int :param models: List of MagneticComponents which shall be simulated in parallel. - :type models: List[MagneticComponent] + :type models: list[MagneticComponent] :param simulation_parameters: List of dictionaries containing the parameters for the parallel simulation. The Nth item corresponds to the Nth MagneticComponent in models. For the default hpc_function this dictionary needs the frequency and the current (which is needed for the single_simulation function). Since the dictionary is given to the hpc_function directly, if a custom_hpc function is used the needed parameters can be added through this dict. - :type simulation_parameters: List[Dict] + :type simulation_parameters: list[dict] :param working_directory: The directory stores the model data and results data for every parallel simulation. :type working_directory: str :param custom_hpc: If set to None the default hpc will be used (create_model() and single_simulation() are executed). If a custom_hpc is set this function will be called for the parallel execution and the function parameters is the simulation_parameter dict taken from the simulation_parameters list., defaults to None - :type custom_hpc: Callable, optional + :type custom_hpc: callable, optional """ electro_magnetic_folder = os.path.join(os.path.dirname(os.path.abspath(__file__)), "electro_magnetic") strands_coefficients_folder = os.path.join(electro_magnetic_folder, "Strands_Coefficients") diff --git a/femmt/logparser.py b/femmt/logparser.py index 5ae4024b..27433cf1 100644 --- a/femmt/logparser.py +++ b/femmt/logparser.py @@ -2,7 +2,6 @@ import os import json import matplotlib.pyplot as plt -from typing import List, Dict from enum import Enum from dataclasses import dataclass @@ -35,7 +34,7 @@ class SweepData: core_eddy_losses: float core_hyst_losses: float winding_losses: float - windings: List[WindingData] + windings: list[WindingData] @dataclass @@ -43,7 +42,7 @@ class FileData: """General data for the result-log file.""" file_path: str - sweeps: List[SweepData] + sweeps: list[SweepData] total_winding_losses: float total_core_eddy_losses: float total_core_hyst_losses: float @@ -59,13 +58,13 @@ class FEMMTLogParser: """ # Contains the complete data - data: Dict[str, FileData] + data: dict[str, FileData] - def __init__(self, file_paths_dict: Dict): + def __init__(self, file_paths_dict: dict): """Create the data dict out of the given file_paths. :param file_paths_dict: List of paths to every log file that should be added to the data. - :type file_paths_dict: List[str] + :type file_paths_dict: list[str] """ self.data = {} @@ -75,7 +74,7 @@ def __init__(self, file_paths_dict: Dict): self.data[name] = self.parse_file(file_path, SweepTypes.SingleSweep) - def plot_frequency_sweep_losses(self, data_names: List[str], loss_parameter: str, plot_label: str = "") -> None: + def plot_frequency_sweep_losses(self, data_names: list[str], loss_parameter: str, plot_label: str = "") -> None: """ Sweep over the frequency of different simulations from one or multiple files. @@ -151,7 +150,7 @@ def plot_frequency_sweep_winding_params(self, data_names: str, winding_number: i plt.show() @staticmethod - def get_log_files_from_working_directories(working_directories: List[str]) -> Dict: + def get_log_files_from_working_directories(working_directories: list[str]) -> dict: """ Get the log files as dict for each given working directory together with the name of the directory as key. diff --git a/femmt/mesh.py b/femmt/mesh.py index c368d23c..3f372721 100644 --- a/femmt/mesh.py +++ b/femmt/mesh.py @@ -11,8 +11,7 @@ import os import numpy as np import warnings -from logging import Logger -from typing import Dict, List +import logging # Third parry libraries import gmsh @@ -24,6 +23,7 @@ from femmt.model import Conductor, Core, StrayPath, AirGaps, Insulation, WindingWindow from femmt.drawing import TwoDaxiSymmetric +logger = logging.getLogger(__name__) class Mesh: """Create a mesh from the given model.""" @@ -33,9 +33,9 @@ class Mesh: stray_path: StrayPath insulation: Insulation component_type: ComponentType - windings: List[Conductor] - winding_windows: List[WindingWindow] - air_gaps: List[AirGaps] + windings: list[Conductor] + winding_windows: list[WindingWindow] + air_gaps: list[AirGaps] correct_outer_leg: bool region: bool @@ -47,14 +47,13 @@ class Mesh: thermal_mesh_file: str verbosity: Verbosity - logger: Logger wwr_enabled: bool # Additionally there are all the needed lists for points, lines, curve_loops and plane_surfaces # See set_empty_lists() - def __init__(self, model: TwoDaxiSymmetric, windings: List[Conductor], winding_windows: List[WindingWindow], correct_outer_leg: bool, - file_paths: FileData, verbosity: Verbosity, logger: Logger, region: bool = None, wwr_enabled: bool = True): + def __init__(self, model: TwoDaxiSymmetric, windings: list[Conductor], winding_windows: list[WindingWindow], correct_outer_leg: bool, + file_paths: FileData, verbosity: Verbosity, region: bool = None, wwr_enabled: bool = True): self.verbosity = verbosity self.logger = logger @@ -97,16 +96,6 @@ def __init__(self, model: TwoDaxiSymmetric, windings: List[Conductor], winding_w self.thermal_mesh_file = file_paths.thermal_mesh_file self.gmsh_log = file_paths.gmsh_log - def femmt_print(self, text: str): - """ - Print text to terminal or to log-file, dependent on the current verbosity. - - :param text: text to print - :type text: str - """ - if not self.verbosity == Verbosity.Silent: - self.logger.info(text) - def set_empty_point_lists(self): """Initialize point lists. For internal overview as a mirrored gmsh information set.""" # Add empty lists @@ -175,7 +164,7 @@ def single_core(self, Set a single core with one physical winding window. :param p_core: points for the core - :type p_core: List + :type p_core: list :param p_island: points for the island :type p_island: list :param l_bound_core: list of bounds for the core @@ -775,12 +764,12 @@ def conductors(self, p_cond: list, l_cond: list, curve_loop_cond: list): for i, center_point in enumerate(center_points): gmsh.model.mesh.embed(0, [center_point], 2, self.plane_surface_cond[num][i]) - def insulations_core_cond(self, p_iso_core: list) -> List: + def insulations_core_cond(self, p_iso_core: list) -> list: """ Set the rectangular electrical insulation between conductors and core. :param p_iso_core: - :type p_iso_core: List + :type p_iso_core: list :return: :rtype: list @@ -878,16 +867,16 @@ def air_single(self, l_core_air: list, l_air_gaps_air: list, curve_loop_air: lis # else: # self.plane_surface_air.append(gmsh.model.geo.addPlaneSurface(curve_loop_air + flatten_curve_loop_cond)) - def air_stacked(self, l_core_air: list, l_bound_air: List, curve_loop_cond: list): + def air_stacked(self, l_core_air: list, l_bound_air: list, curve_loop_cond: list): """ Generate gmsh entities (points, lines, closed loops and planes) and draw the air gaps for the stacked core. :param l_core_air: line - :type l_core_air: List + :type l_core_air: list :param l_bound_air: - :type l_bound_air: List + :type l_bound_air: list :param curve_loop_cond: - :type curve_loop_cond: List + :type curve_loop_cond: list """ # Air # Points are partwise double designated @@ -1138,7 +1127,7 @@ def visualize(self, visualize_before: bool, save_png: bool): gmsh.write(self.hybrid_color_png_file) # save png gmsh.fltk.finalize() - def generate_hybrid_mesh(self, color_scheme: Dict = ff.colors_femmt_default, colors_geometry: Dict = ff.colors_geometry_femmt_default, + def generate_hybrid_mesh(self, color_scheme: dict = ff.colors_femmt_default, colors_geometry: dict = ff.colors_geometry_femmt_default, visualize_before: bool = False, save_png: bool = True, refine=0, alternative_error=0) -> None: """ @@ -1153,9 +1142,9 @@ def generate_hybrid_mesh(self, color_scheme: Dict = ff.colors_femmt_default, col :param colors_geometry: colors of the geometry - :type colors_geometry: Dict + :type colors_geometry: dict :param color_scheme: color scheme - :type color_scheme: Dict + :type color_scheme: dict :param visualize_before: visualize geometry before FEM simulation :type visualize_before: bool :param save_png: True to save a .png figure @@ -1165,7 +1154,7 @@ def generate_hybrid_mesh(self, color_scheme: Dict = ff.colors_femmt_default, col :param alternative_error: :type alternative_error: """ - self.femmt_print("Hybrid Mesh Generation in Gmsh") + logger.info("Hybrid Mesh Generation in Gmsh") # Clear gmsh gmsh.clear() # Initialization @@ -1238,7 +1227,7 @@ def generate_electro_magnetic_mesh(self, refine: int = 0): :param refine: :type refine: int """ - self.femmt_print("Electro Magnetic Mesh Generation in Gmsh (write physical entities)") + logger.info("Electro Magnetic Mesh Generation in Gmsh (write physical entities)") self.PN_BOUND = 111111 self.PN_AIR = 110000 @@ -1335,16 +1324,16 @@ def set_physical_line_bound(): # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # TODO: Adaptive Meshing if refine == 1: - self.femmt_print("\n ------- \nRefined Mesh Creation ") + logger.info("\n ------- \nRefined Mesh Creation ") # mesh the new gmsh.model using the size field bg_field = gmsh.model.mesh.field.add("PostView") # TODO: gmsh.model.mesh.field.setNumber(bg_field, "ViewTag", sf_view) gmsh.model.mesh.field.setAsBackgroundMesh(bg_field) - self.femmt_print("\nMeshing...\n") + logger.info("\nMeshing...\n") gmsh.model.mesh.generate(2) else: # Mesh the model - self.femmt_print("\nMeshing...\n") + logger.info("\nMeshing...\n") gmsh.model.mesh.generate(2) if not os.path.exists(self.mesh_folder_path): @@ -1361,7 +1350,7 @@ def set_physical_line_bound(): fd.write(text) def generate_thermal_mesh(self, case_gap_top: float, case_gap_right: float, case_gap_bot: float, - color_scheme: Dict, colors_geometry: Dict, visualize_before: bool) -> None: + color_scheme: dict, colors_geometry: dict, visualize_before: bool) -> None: """ Generate the mesh for the thermal FEM simulation. @@ -1372,13 +1361,13 @@ def generate_thermal_mesh(self, case_gap_top: float, case_gap_right: float, case :param case_gap_bot: gap length in meter on the bottom of the case :type case_gap_bot: float :param color_scheme: color scheme - :type color_scheme: Dict + :type color_scheme: dict :param colors_geometry: colors of the geometry - :type colors_geometry: Dict + :type colors_geometry: dict :param visualize_before: True to visualize geometry before performing the simulation :type visualize_before: bool """ - self.femmt_print("Thermal Mesh Generation in Gmsh (write physical entities)") + logger.info("Thermal Mesh Generation in Gmsh (write physical entities)") gmsh.open(self.model_geo_file) @@ -1682,12 +1671,12 @@ def generate_thermal_mesh(self, case_gap_top: float, case_gap_right: float, case gmsh.write(self.thermal_mesh_file) - def inter_conductor_meshing(self, p_cond: List): + def inter_conductor_meshing(self, p_cond: list): """ To be defined. :param p_cond: - :type p_cond: List + :type p_cond: list """ p_inter = None for ww in self.model.winding_windows: @@ -1724,7 +1713,7 @@ def inter_conductor_meshing(self, p_cond: List): # TODO: Inter conductor meshing! if all(winding.conductor_type == ConductorType.RoundSolid for winding in self.windings): - self.femmt_print("Making use of skin based meshing\n") + logger.info("Making use of skin based meshing\n") for num in range(len(self.windings)): for i in range(0, int(len(p_cond[num]) / 5)): gmsh.model.mesh.embed(0, [p_cond[num][5 * i + 0]], 2, self.plane_surface_cond[num][i]) @@ -1841,7 +1830,7 @@ def rasterize_winding_window(left_bound, right_bound, bot_bound, top_bound): valid = False break else: - print(f"Winding window rasterization skipped because ConductorType {winding.conductor_type.name} is not supported.") + logger.info(f"Winding window rasterization skipped because ConductorType {winding.conductor_type.name} is not supported.") return if not valid: @@ -1914,8 +1903,8 @@ def rasterize_winding_window(left_bound, right_bound, bot_bound, top_bound): if self.wwr_enabled: # Winding Windows list has exactly two winding windows: top and bot: if len(self.winding_windows) != 2: - print(f"Winding Window Rasterization is only implemented for stacked core with exactly 2 winding windows. " - f"{len(self.winding_windows)} winding windows were given.") + logger.info(f"Winding Window Rasterization is only implemented for stacked core with exactly 2 winding windows. " + f"{len(self.winding_windows)} winding windows were given.") gmsh.model.geo.synchronize() return @@ -1940,7 +1929,7 @@ def rasterize_winding_window(left_bound, right_bound, bot_bound, top_bound): def rectangular_conductor_center_points(self): """Add center points for rectangular conductors for better meshing.""" def calculate_center_points(left_bound: float, right_bound: float, top_bound: float, bottom_bound: float, - center_point: List, min_distance: float): + center_point: list, min_distance: float): """ Calculate the coordinates for the new center points. @@ -1953,7 +1942,7 @@ def calculate_center_points(left_bound: float, right_bound: float, top_bound: fl :param bottom_bound: bottom bound :type bottom_bound: float :param center_point: center point - :type center_point: List + :type center_point: list :param min_distance: minimum distance :type min_distance: float """ diff --git a/femmt/model.py b/femmt/model.py index a17eb686..170f60ae 100644 --- a/femmt/model.py +++ b/femmt/model.py @@ -4,7 +4,6 @@ """ # Python standard libraries from dataclasses import dataclass -from typing import List, Tuple, Optional, Union # 3rd party libraries import numpy as np @@ -30,13 +29,13 @@ class Conductor: # TODO More documentation conductor_type: ConductorType - conductor_arrangement: Optional[ConductorArrangement] = None - wrap_para: Optional[WrapParaType] = None - conductor_radius: Optional[float] = None + conductor_arrangement: ConductorArrangement | None = None + wrap_para: WrapParaType | None = None + conductor_radius: float | None = None winding_number: int - thickness: Optional[float] = None - ff: Optional[float] = None - strand_radius: Optional[float] = None + thickness: float | None = None + ff: float | None = None + strand_radius: float | None = None n_strands: int = 0 n_layers: int a_cell: float @@ -45,7 +44,7 @@ class Conductor: conductor_is_set: bool # Not used in femmt_classes. Only needed for to_dict() - conductivity: Optional[Conductivity] = None + conductivity: Conductivity | None = None def __init__(self, winding_number: int, conductivity: Conductivity, parallel: bool = False, winding_material_temperature: float = 100): @@ -93,14 +92,14 @@ def set_rectangular_conductor(self, thickness: float = None): self.a_cell = None # can only be set after the width is determined self.conductor_radius = 1 # Revisit - def set_solid_round_conductor(self, conductor_radius: float, conductor_arrangement: Optional[ConductorArrangement]): + def set_solid_round_conductor(self, conductor_radius: float, conductor_arrangement: ConductorArrangement | None): """ Set a solid round conductor. :param conductor_radius: conductor radius in m :type conductor_radius: float :param conductor_arrangement: conductor arrangement (Square / SquareFullWidth / Hexagonal) - :type conductor_arrangement: Optional[ConductorArrangement] + :type conductor_arrangement: ConductorArrangement | None """ if self.conductor_is_set: raise Exception("Only one conductor can be set for each winding!") @@ -111,22 +110,22 @@ def set_solid_round_conductor(self, conductor_radius: float, conductor_arrangeme self.conductor_radius = conductor_radius self.a_cell = np.pi * conductor_radius ** 2 - def set_litz_round_conductor(self, conductor_radius: Optional[float], number_strands: Optional[int], - strand_radius: Optional[float], - fill_factor: Optional[float], conductor_arrangement: ConductorArrangement): + def set_litz_round_conductor(self, conductor_radius: float | None, number_strands: int | None, + strand_radius: float | None, + fill_factor: float | None, conductor_arrangement: ConductorArrangement): """ Set a round conductor made of litz wire. Only 3 of the 4 parameters are needed. The other one needs to be none. :param conductor_radius: conductor radius in m - :type conductor_radius: Optional[float] + :type conductor_radius: float | None :param number_strands: number of strands inside the litz wire - :type number_strands: Optional[int] + :type number_strands: int | None :param strand_radius: radius of a single strand in m - :type strand_radius: Optional[float] + :type strand_radius: float | None :param fill_factor: fill factor of the litz wire - :type fill_factor: Optional[float] + :type fill_factor: float | None :param conductor_arrangement: conductor arrangement (Square, SquareFullWidth, Hexagonal) :type conductor_arrangement: ConductorArrangement """ @@ -463,7 +462,7 @@ def update_core_material_pro_file(self, frequency: int, electro_magnetic_folder: :type plot_interpolation: bool """ if self.mdb_verbosity == Verbosity.ToConsole: - print(f"{self.permeability['datasource']=}") + logger.info(f"{self.permeability['datasource']=}") self.material_database.permeability_data_to_pro_file(temperature=self.temperature, frequency=frequency, material_name=self.material, datasource=self.permeability["datasource"], @@ -530,13 +529,13 @@ class AirGaps: """ core: Core - midpoints: List[List[float]] #: list: [position_tag, air_gap_position, air_gap_h] + midpoints: list[list[float]] #: list: [position_tag, air_gap_position, air_gap_h] number: int # Needed for to_dict - air_gap_settings: List + air_gap_settings: list - def __init__(self, method: Optional[AirGapMethod], core: Optional[Core]): + def __init__(self, method: AirGapMethod | None, core: Core | None): """Create an AirGaps object. An AirGapMethod needs to be set. This determines the way the air gap will be added to the model. In order to calculate the air gap positions the core object needs to be given. @@ -552,7 +551,7 @@ def __init__(self, method: Optional[AirGapMethod], core: Optional[Core]): self.number = 0 self.air_gap_settings = [] - def add_air_gap(self, leg_position: AirGapLegPosition, height: float, position_value: Optional[float] = 0, + def add_air_gap(self, leg_position: AirGapLegPosition, height: float, position_value: float | None = 0, stacked_position: StackedPosition = None): """ Brings a single air gap to the core. @@ -665,9 +664,9 @@ class Insulation: In general, it is not necessary to add an insulation object at all when no insulation is needed. """ - cond_cond: List[List[ + cond_cond: list[list[ float]] # two-dimensional list with size NxN, where N is the number of windings (symmetrical isolation matrix) - core_cond: List[ + core_cond: list[ float] # list with size 4x1, with respectively isolation of cond_n -> [top_core, bot_core, left_core, right_core] flag_insulation: bool = True @@ -694,14 +693,14 @@ def set_flag_insulation(self, flag: bool): # to differentiate between the simul """ self.flag_insulation = flag - def add_winding_insulations(self, inner_winding_insulation: List[List[float]]): + def add_winding_insulations(self, inner_winding_insulation: list[list[float]]): """Add insulations between turns of one winding and insulation between virtual winding windows. Insulation between virtual winding windows is not always needed. :param inner_winding_insulation: List of floats which represent the insulations between turns of the same winding. This does not correspond to the order conductors are added to the winding! Instead, the winding number is important. The conductors are sorted by ascending winding number. The lowest winding number therefore is combined with index 0. The second lowest with index 1 and so on. - :type inner_winding_insulation: List[List[float]] + :type inner_winding_insulation: list[list[float]] """ if inner_winding_insulation == [[]]: raise Exception("Inner winding insulations list cannot be empty.") @@ -777,12 +776,11 @@ class VirtualWindingWindow: right_bound: float winding_type: WindingType - winding_scheme: Union[ - WindingScheme, InterleavedWindingScheme] # Either WindingScheme or InterleavedWindingScheme (Depending on the winding) + winding_scheme: WindingScheme | InterleavedWindingScheme # Either WindingScheme or InterleavedWindingScheme (Depending on the winding) wrap_para: WrapParaType - windings: List[Conductor] - turns: List[int] + windings: list[Conductor] + turns: list[int] winding_is_set: bool winding_insulation: float @@ -808,10 +806,10 @@ def __init__(self, bot_bound: float, top_bound: float, left_bound: float, right_ self.right_bound = right_bound self.winding_is_set = False - def set_winding(self, conductor: Conductor, turns: int, winding_scheme: WindingScheme, alignment: Optional[Align] = None, - placing_strategy: Optional[ConductorDistribution] = None, zigzag: bool = False, - wrap_para_type: WrapParaType = None, foil_vertical_placing_strategy: Optional[FoilVerticalDistribution] = None, - foil_horizontal_placing_strategy: Optional[FoilHorizontalDistribution] = None): + def set_winding(self, conductor: Conductor, turns: int, winding_scheme: WindingScheme, alignment: Align | None = None, + placing_strategy: ConductorDistribution | None = None, zigzag: bool = False, + wrap_para_type: WrapParaType = None, foil_vertical_placing_strategy: FoilVerticalDistribution | None = None, + foil_horizontal_placing_strategy: FoilHorizontalDistribution | None = None): """Set a single winding to the current virtual winding window. A single winding always contains one conductor. :param conductor: Conductor which will be set to the vww. @@ -831,7 +829,7 @@ def set_winding(self, conductor: Conductor, turns: int, winding_scheme: WindingS :param foil_horizontal_placing_strategy: foil_horizontal_placing_strategy defines the way the rectangular foil Horizontal conductors are placing in vww :type foil_horizontal_placing_strategy: foil_horizontal_placing_strategy, optional :param alignment: List of alignments: ToEdges, CenterOnVerticalAxis, CenterOnHorizontalAxis - :type alignment: Optional[Align] + :type alignment: Align | None """ self.winding_type = WindingType.Single self.winding_scheme = winding_scheme @@ -980,10 +978,10 @@ class WindingWindow: # 4 different insulations which can be Null if there is a vww overlapping # The lists contain 4 values x1, y1, x2, y2 where (x1, y1) is the lower left and (x2, y2) the upper right point - top_iso: List[float] - left_iso: List[float] - bot_iso: List[float] - right_iso: List[float] + top_iso: list[float] + left_iso: list[float] + bot_iso: list[float] + right_iso: list[float] insulations: Insulation split_type: WindingWindowSplit @@ -993,7 +991,7 @@ class WindingWindow: horizontal_split_factor: float vertical_split_factor: float - virtual_winding_windows: List[VirtualWindingWindow] + virtual_winding_windows: list[VirtualWindingWindow] def __init__(self, core: Core, insulations: Insulation, stray_path: StrayPath = None, air_gaps: AirGaps = None): """Create a winding window which then creates up to 4 virtual winding windows. @@ -1044,7 +1042,7 @@ def to_dict(self): def split_window(self, split_type: WindingWindowSplit, split_distance: float = 0, horizontal_split_factor: float = 0.5, vertical_split_factor: float = 0.5, top_bobbin: float = None, bot_bobbin: float = None, left_bobbin: float = None, - right_bobbin: float = None) -> Tuple[VirtualWindingWindow]: + right_bobbin: float = None) -> tuple[VirtualWindingWindow]: """Create up to 4 virtual winding windows depending on the split type and the horizontal and vertical split factors. The split factors are values between 0 and 1 and determine a horizontal and vertical line at which the window is split. @@ -1069,7 +1067,7 @@ def split_window(self, split_type: WindingWindowSplit, split_distance: float = 0 :param vertical_split_factor: Vertical split factor 0...1, defaults to 0.5 :type vertical_split_factor: float, optional :return: Tuple containing the virtual winding windows - :rtype: Tuple[VirtualWindingWindow] + :rtype: tuple[VirtualWindingWindow] :param top_bobbin: top bobbin thickness in m :type top_bobbin: float :param bot_bobbin: bottom bobbin thickness in m @@ -1187,7 +1185,7 @@ def split_window(self, split_type: WindingWindowSplit, split_distance: float = 0 else: raise Exception(f"Winding window split type {split_type} not found") - def NCellsSplit(self, split_distance: float = 0, horizontal_split_factors: List[float] = None, vertical_split_factor: float = 0.5): + def NCellsSplit(self, split_distance: float = 0, horizontal_split_factors: list[float] = None, vertical_split_factor: float = 0.5): """ Split a winding window into N columns (horizontal). @@ -1269,8 +1267,8 @@ def NCellsSplit(self, split_distance: float = 0, horizontal_split_factors: List[ return self.virtual_winding_windows def flexible_split(self, split_distance: float = 0, - horizontal_split_factors: Optional[List[float]] = None, - vertical_split_factors: Optional[List[List[float]]] = None) -> List[VirtualWindingWindow]: + horizontal_split_factors: list[float] | None = None, + vertical_split_factors: list[list[float]] | None = None) -> list[VirtualWindingWindow]: """ Flexible split function to divide a window into sections based on provided horizontal and vertical split factors. diff --git a/femmt/optimization/functions_optimization.py b/femmt/optimization/functions_optimization.py index 2a48b7da..75eb5d90 100644 --- a/femmt/optimization/functions_optimization.py +++ b/femmt/optimization/functions_optimization.py @@ -1,6 +1,5 @@ """Functions used by the optimization methods in general.""" # python libraries -from typing import List, Dict # 3rd party libraries from matplotlib import pyplot as plt @@ -169,12 +168,12 @@ def is_pareto_efficient(costs: np.array, return_mask: bool = True): else: return is_efficient -def pareto_front_from_dtos(dto_list: List[ItoSingleResultFile]) -> tuple: +def pareto_front_from_dtos(dto_list: list[ItoSingleResultFile]) -> tuple: """ Calculate the Pareto front from a list of ItoSingleResultFiles. :param dto_list: List of ItoSingleResultFiles - :type dto_list: List[ItoSingleResultFiles] + :type dto_list: list[ItoSingleResultFiles] :return: x-Pareto vector, y-Pareto vector :rtype: tuple """ @@ -221,12 +220,12 @@ def pareto_front_from_df(df: pd.DataFrame) -> pd.DataFrame: return pareto_df -def pareto_front_from_result_dicts(result_dict_list: List[Dict]) -> tuple: +def pareto_front_from_result_dicts(result_dict_list: list[dict]) -> tuple: """ Calculate the Pareto front from a list of result log dictionaries. :param result_dict_list: List of result log dictionaries - :type result_dict_list: List[Dict] + :type result_dict_list: list[dict] :return: x-Pareto vector, y-Pareto vector :rtype: tuple """ diff --git a/femmt/optimization/io.py b/femmt/optimization/io.py index 477a9cea..a4e92431 100644 --- a/femmt/optimization/io.py +++ b/femmt/optimization/io.py @@ -5,7 +5,6 @@ import pickle import logging import shutil -from typing import List, Optional import json # 3rd party libraries @@ -28,6 +27,8 @@ import femmt as fmt import materialdatabase as mdb +logger = logging.getLogger(__name__) + class InductorOptimization: """Reluctance model and FEM simulation for the inductor optimization.""" @@ -188,7 +189,7 @@ def objective(trial: optuna.Trial, config: InductorOptimizationDTO, target_and_f (available_height + config.insulations.primary_to_primary) / (litz_wire_diameter + config.insulations.primary_to_primary)) max_turns = possible_number_turns_per_row * possible_number_turns_per_column if max_turns < 1: - logging.warning("Max. number of turns per window < 1") + logger.warning("Max. number of turns per window < 1") return float('nan'), float('nan') turns = trial.suggest_int('turns', 1, max_turns) @@ -222,7 +223,7 @@ def objective(trial: optuna.Trial, config: InductorOptimizationDTO, target_and_f try: reluctance_output: ReluctanceModelOutput = InductorOptimization.ReluctanceModel.single_reluctance_model_simulation(reluctance_model_input) except ValueError as e: - logging.info("bot air gap: No fitting air gap length") + logger.info("bot air gap: No fitting air gap length") return float('nan'), float('nan') trial.set_user_attr('p_winding', reluctance_output.p_winding) @@ -326,8 +327,8 @@ def single_reluctance_model_simulation(reluctance_input: ReluctanceModelInput) - return reluctance_model_output @staticmethod - def start_proceed_study(config: InductorOptimizationDTO, number_trials: Optional[int] = None, - target_number_trials: Optional[int] = None, storage: str = 'sqlite', + def start_proceed_study(config: InductorOptimizationDTO, number_trials: int | None = None, + target_number_trials: int | None = None, storage: str = 'sqlite', sampler=optuna.samplers.NSGAIIISampler(), ) -> None: """ @@ -473,7 +474,7 @@ def study_to_df(config: InductorOptimizationDTO) -> pd.DataFrame: loaded_study = optuna.load_study(study_name=config.inductor_study_name, storage=database_url) df = loaded_study.trials_dataframe() df.to_csv(f'{config.inductor_optimization_directory}/{config.inductor_study_name}.csv') - logging.info(f"Exported study as .csv file: {config.inductor_optimization_directory}/{config.inductor_study_name}.csv") + logger.info(f"Exported study as .csv file: {config.inductor_optimization_directory}/{config.inductor_study_name}.csv") return df @staticmethod @@ -576,14 +577,14 @@ def filter_loss_list_df(df: pd.DataFrame, factor_min_dc_losses: float = 1.2, fac return filtered_df @staticmethod - def df_from_trial_numbers(df: pd.DataFrame, trial_number_list: List[int]) -> pd.DataFrame: + def df_from_trial_numbers(df: pd.DataFrame, trial_number_list: list[int]) -> pd.DataFrame: """ Generate a new dataframe from a given one, just with the trial numbers from the trial_number_list. :param df: input dataframe :type df: pandas.DataFrame :param trial_number_list: list of trials, e.g. [1530, 1870, 3402] - :type trial_number_list: List[int] + :type trial_number_list: list[int] :return: dataframe with trial numbers from trial_number_list :rtype: pandas.DataFrame """ @@ -775,7 +776,7 @@ def single_fem_simulation(fem_input: FemInput, show_visual_outputs: bool = False """ # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.FreqDomain, component_type=fmt.ComponentType.Inductor, - working_directory=fem_input.working_directory, verbosity=fmt.Verbosity.Silent, + working_directory=fem_input.working_directory, onelab_verbosity=fmt.Verbosity.Silent, simulation_name=fem_input.simulation_name) # 2. set core parameters @@ -849,7 +850,7 @@ def single_fem_simulation(fem_input: FemInput, show_visual_outputs: bool = False return fem_output @staticmethod - def full_simulation(df_geometry: pd.DataFrame, current_waveform: List, inductor_config_filepath: str, process_number: int = 1, + def full_simulation(df_geometry: pd.DataFrame, current_waveform: list, inductor_config_filepath: str, process_number: int = 1, print_derivations: bool = False) -> tuple: """ Reluctance model (hysteresis losses) and FEM simulation (winding losses and eddy current losses) for geometries from df_geometry. @@ -857,7 +858,7 @@ def full_simulation(df_geometry: pd.DataFrame, current_waveform: List, inductor_ :param df_geometry: Pandas dataframe with geometries :type df_geometry: pd.DataFrame :param current_waveform: Current waveform to simulate - :type current_waveform: List + :type current_waveform: list :param inductor_config_filepath: Filepath of the inductor optimization configuration file :type inductor_config_filepath: str :param process_number: process number to run the simulation on diff --git a/femmt/optimization/io_dtos.py b/femmt/optimization/io_dtos.py index 0593be87..b5961fc3 100644 --- a/femmt/optimization/io_dtos.py +++ b/femmt/optimization/io_dtos.py @@ -1,7 +1,6 @@ """Data transfer objects (DTOs) for the inductor optimization.""" # python libraries import dataclasses -from typing import List, Optional # 3rd party libraries from magnethub.loss import LossModel @@ -46,15 +45,15 @@ class InductorOptimizationDTO: # fixed parameters insulations: InductorInsulationDTO temperature: float - time_current_vec: List + time_current_vec: list # optimization parameters - material_name_list: List[str] - core_name_list: Optional[List[str]] - core_inner_diameter_list: Optional[List[float]] - window_w_list: Optional[List[float]] - window_h_list: Optional[List[float]] - litz_wire_list: List[str] + material_name_list: list[str] + core_name_list: list[str] | None + core_inner_diameter_list: list[float] | None + window_w_list: list[float] | None + window_h_list: list[float] | None + litz_wire_list: list[str] # FEM simulation material_data_sources: InductorMaterialDataSources @@ -69,15 +68,15 @@ class InductorOptimizationTargetAndFixedParameters: i_rms: float i_peak: float - material_dto_curve_list: List[MaterialCurve] - magnet_hub_model_list: List[LossModel] - time_extracted_vec: List - current_extracted_vec: List + material_dto_curve_list: list[MaterialCurve] + magnet_hub_model_list: list[LossModel] + time_extracted_vec: list + current_extracted_vec: list fundamental_frequency: float working_directories: WorkingDirectories - fft_frequency_list: List[float] - fft_amplitude_list: List[float] - fft_phases_list: List[float] + fft_frequency_list: list[float] + fft_amplitude_list: list[float] + fft_phases_list: list[float] @dataclasses.dataclass class FemInput: @@ -103,9 +102,9 @@ class FemInput: # operating point conditions temperature: float fundamental_frequency: float - fft_frequency_list: List[float] - fft_amplitude_list: List[float] - fft_phases_list: List[float] + fft_frequency_list: list[float] + fft_amplitude_list: list[float] + fft_phases_list: list[float] @dataclasses.dataclass class FemOutput: @@ -134,11 +133,11 @@ class ReluctanceModelInput: magnet_material_model: LossModel temperature: float - current_extracted_vec: List + current_extracted_vec: list fundamental_frequency: float - fft_frequency_list: List[float] - fft_amplitude_list: List[float] - fft_phases_list: List[float] + fft_frequency_list: list[float] + fft_amplitude_list: list[float] + fft_phases_list: list[float] @dataclasses.dataclass class ReluctanceModelOutput: diff --git a/femmt/optimization/ito.py b/femmt/optimization/ito.py index 9aa94297..d899b158 100644 --- a/femmt/optimization/ito.py +++ b/femmt/optimization/ito.py @@ -2,7 +2,6 @@ # Python libraries import os import json -from typing import List, Dict, Optional import datetime import pickle import logging @@ -35,12 +34,12 @@ class MyJSONEncoder(json.JSONEncoder): https://python-forum.io/thread-35245.html """ - def default(self, o: Dict): + def default(self, o: dict): """ Transform the dictionary to a .json file. :param o: Dictionary to transform - :type o: Dict + :type o: dict """ try: return o.tolist() # works with any object that has .tolist() method @@ -50,12 +49,12 @@ def default(self, o: Dict): return json.JSONEncoder.default(self, o) -def result_file_dict_to_dto(result_file_dict: Dict) -> ItoSingleResultFile: +def result_file_dict_to_dto(result_file_dict: dict) -> ItoSingleResultFile: """ Translate the result file dictionary to a data transfer object (DTO). :param result_file_dict: dictionary to translate to the DTO structure - :type result_file_dict: Dict + :type result_file_dict: dict :return: DTO :rtype: ItoSingleResultFile """ @@ -94,12 +93,12 @@ class IntegratedTransformerOptimization: """Perform different optimization methods for the integrated transformer.""" @staticmethod - def plot(valid_design_list: List[ItoSingleResultFile]) -> None: + def plot(valid_design_list: list[ItoSingleResultFile]) -> None: """ Plot the pareto diagram out of the reluctance model calculation. :param valid_design_list: - :type valid_design_list: List[ItoSingleResultFile] + :type valid_design_list: list[ItoSingleResultFile] """ volume_list = [] core_hyst_loss_list = [] @@ -626,8 +625,8 @@ def calc_winding_height(available_width: float, iso_winding_to_winding: float, l # target_inductance_matrix) @staticmethod - def start_proceed_study(config: ItoSingleInputConfig, number_trials: Optional[int] = None, - target_number_trials: Optional[int] = None, storage: str = 'sqlite', + def start_proceed_study(config: ItoSingleInputConfig, number_trials: int | None = None, + target_number_trials: int | None = None, storage: str = 'sqlite', sampler=optuna.samplers.NSGAIIISampler(), ) -> None: """ @@ -728,13 +727,13 @@ def filter_loss_list_df(df: pd.DataFrame, factor_min_dc_losses: float = 1.2, fac Remove designs with too high losses compared to the minimum losses. :param df: list of valid DTOs - :type df: List[ItoSingleResultFile] + :type df: list[ItoSingleResultFile] :param factor_min_dc_losses: filter factor for the minimum dc losses :type factor_min_dc_losses: float :param factor_max_dc_losses: dc_max_loss = factor_max_dc_losses * min_available_dc_losses_in_pareto_front :type factor_max_dc_losses: float :returns: list with removed objects (too small air gaps) - :rtype: List[ItoSingleResultFile] + :rtype: list[ItoSingleResultFile] """ # figure out pareto front # pareto_volume_list, pareto_core_hyst_list, pareto_dto_list = self.pareto_front(volume_list, core_hyst_loss_list, valid_design_list) @@ -763,16 +762,16 @@ def filter_loss_list_df(df: pd.DataFrame, factor_min_dc_losses: float = 1.2, fac return pareto_df_offset @staticmethod - def filter_max_air_gap_length(dto_list_to_filter: List[ItoSingleResultFile], max_air_gap_length: float = 1e-6) -> List[ItoSingleResultFile]: + def filter_max_air_gap_length(dto_list_to_filter: list[ItoSingleResultFile], max_air_gap_length: float = 1e-6) -> list[ItoSingleResultFile]: """ Remove designs with a too large air gap. :param dto_list_to_filter: list of DTOs to filter for too small air gaps - :type dto_list_to_filter: List[ItoSingleResultFile] + :type dto_list_to_filter: list[ItoSingleResultFile] :param max_air_gap_length: minimum air gap length :type max_air_gap_length: float :returns: list with removed objects (too small air gaps) - :rtype: List[ItoSingleResultFile] + :rtype: list[ItoSingleResultFile] """ filtered_dtos = [] for dto in dto_list_to_filter: @@ -781,16 +780,16 @@ def filter_max_air_gap_length(dto_list_to_filter: List[ItoSingleResultFile], max return filtered_dtos @staticmethod - def filter_min_air_gap_length(dto_list_to_filter: List[ItoSingleResultFile], min_air_gap_length: float = 1e-6) -> List[ItoSingleResultFile]: + def filter_min_air_gap_length(dto_list_to_filter: list[ItoSingleResultFile], min_air_gap_length: float = 1e-6) -> list[ItoSingleResultFile]: """ Remove designs with a too small air gap. :param dto_list_to_filter: list of DTOs to filter for too small air gaps - :type dto_list_to_filter: List[ItoSingleResultFile] + :type dto_list_to_filter: list[ItoSingleResultFile] :param min_air_gap_length: minimum air gap length :type min_air_gap_length: float :returns: list with removed objects (too small air gaps) - :rtype: List[ItoSingleResultFile] + :rtype: list[ItoSingleResultFile] """ filtered_dtos = [] for dto in dto_list_to_filter: @@ -920,30 +919,30 @@ class FemSimulation: """Group functions to perform FEM simulations.""" @staticmethod - def simulate(config_dto: ItoSingleInputConfig, simulation_dto_list: List[ItoSingleResultFile], visualize: bool = False): + def simulate(config_dto: ItoSingleInputConfig, simulation_dto_list: list[ItoSingleResultFile], visualize: bool = False): """ Perform the FEM simulation. :param config_dto: Configuration DTO (data transfer object) :type config_dto: ItoSingleInputConfig :param simulation_dto_list: List of DTOs to simulate - :type simulation_dto_list: List[ItoSingleResultFile] + :type simulation_dto_list: list[ItoSingleResultFile] :param visualize: True to visualize the results. :type visualize: bool """ femmt.integrated_transformer_fem_simulations_from_result_dtos(config_dto, simulation_dto_list, visualize) @staticmethod - def filter_loss_list(fem_simulations_dict_list: List[Dict], factor_min_dc_losses: float = 0.5) -> List[Dict]: + def filter_loss_list(fem_simulations_dict_list: list[dict], factor_min_dc_losses: float = 0.5) -> list[dict]: """ Remove too high losses from the given dictionary. :param fem_simulations_dict_list: List of dictionaries to filter - :type fem_simulations_dict_list: List[Dict] + :type fem_simulations_dict_list: list[dict] :param factor_min_dc_losses: factor of the minimum dc losses to filter :type factor_min_dc_losses: float :return: filtered dictionary list - :rtype: List[Dict] + :rtype: list[dict] """ # figure out pareto front # pareto_volume_list, pareto_core_hyst_list, pareto_dto_list = self.pareto_front(volume_list, core_hyst_loss_list, valid_design_list) @@ -977,7 +976,7 @@ def filter_loss_list(fem_simulations_dict_list: List[Dict], factor_min_dc_losses return filtered_design_dto_list @staticmethod - def plot(result_log_dict_list: List[Dict]) -> None: + def plot(result_log_dict_list: list[dict]) -> None: """ Plot the pareto diagram out of the fem simulation results. @@ -1007,7 +1006,7 @@ def plot(result_log_dict_list: List[Dict]) -> None: ############################# @staticmethod - def load(filepath: str) -> List[Dict]: + def load(filepath: str) -> list[dict]: """ Load FEM simulations results from a directory. @@ -1016,7 +1015,7 @@ def load(filepath: str) -> List[Dict]: :param filepath: filepath to FEM simulations :type: str :return: List of result-log dictionaries - :rtype: List[Dict] + :rtype: list[dict] """ data_dict_list = [] @@ -1034,38 +1033,38 @@ def load(filepath: str) -> List[Dict]: return data_dict_list @staticmethod - def load_unfiltered_results(working_directory: str) -> List[Dict]: + def load_unfiltered_results(working_directory: str) -> list[dict]: """ Load the FEM simulations results as a dict into a list. :param working_directory: file path :type working_directory: str :return: - :rtype: List[Dict] + :rtype: list[dict] """ filepath = os.path.join(working_directory, "02_fem_simulation_results") return femmt.IntegratedTransformerOptimization.FemSimulation.load(filepath) @staticmethod - def load_filtered_results(working_directory: str) -> List[Dict]: + def load_filtered_results(working_directory: str) -> list[dict]: """ Load the FEM simulation results as a dict into a list. :param working_directory: file path :type working_directory: str :return: - :rtype: List[Dict] + :rtype: list[dict] """ filepath = os.path.join(working_directory, "02_fem_simulation_results_filtered") return femmt.IntegratedTransformerOptimization.FemSimulation.load(filepath) @staticmethod - def save_filtered_results(filtered_dict_list: List[Dict], working_directory: str): + def save_filtered_results(filtered_dict_list: list[dict], working_directory: str): """ Save filtered FEM simulation results to hard disk. :param filtered_dict_list: list of dictionaries to store - :type filtered_dict_list: List[Dict] + :type filtered_dict_list: list[dict] :param working_directory: working directory of the optimization :param working_directory: str """ @@ -1079,14 +1078,14 @@ class ThermalSimulation: """Perform thermal simulations.""" @staticmethod - def simulation(config_dto: ItoSingleInputConfig, result_log_dict_list: List[Dict], visualize: bool = False): + def simulation(config_dto: ItoSingleInputConfig, result_log_dict_list: list[dict], visualize: bool = False): """ Perform a thermal simulation. :param config_dto: Configuration DTO (data transfer object) :type config_dto: ItoSingleInputConfig :param result_log_dict_list: list of result log dictionaries - :type result_log_dict_list: List[Dict] + :type result_log_dict_list: list[dict] :param visualize: True to visualize the results. :type visualize: bool """ @@ -1105,7 +1104,7 @@ def simulation(config_dto: ItoSingleInputConfig, result_log_dict_list: List[Dict femmt.integrated_transformer_fem_thermal_simulations_from_result_dtos(config_dto, simulation_dto_list, visualize) @staticmethod - def load_unfiltered_simulations(working_directory: str) -> List[Dict]: + def load_unfiltered_simulations(working_directory: str) -> list[dict]: """ Load all simulations from a given working directory. @@ -1117,12 +1116,12 @@ def load_unfiltered_simulations(working_directory: str) -> List[Dict]: return femmt.IntegratedTransformerOptimization.FemSimulation.load(filepath) @staticmethod - def filter_max_temperature(thermal_result_log_list: List[Dict], max_core_temperature: float, max_winding_temperature: float) -> List[Dict]: + def filter_max_temperature(thermal_result_log_list: list[dict], max_core_temperature: float, max_winding_temperature: float) -> list[dict]: """ Filter out designs with too high core and winding temperatures. :param thermal_result_log_list: list of thermal result logs - :type thermal_result_log_list: List[Dict] + :type thermal_result_log_list: list[dict] :param max_core_temperature: maximum core temperature :type max_core_temperature: float :param max_winding_temperature: maximum winding temperature @@ -1137,16 +1136,16 @@ def filter_max_temperature(thermal_result_log_list: List[Dict], max_core_tempera return filtered_thermal_result_log_list @staticmethod - def find_common_cases(fem_results_list: List[Dict], thermal_fem_results_list: List[Dict]) -> List[Dict]: + def find_common_cases(fem_results_list: list[dict], thermal_fem_results_list: list[dict]) -> list[dict]: """ Find out common cases in FEM simulation list and thermal FEM simulation list. :param fem_results_list: List of FEM result-logs - :type fem_results_list: List[Dict] + :type fem_results_list: list[dict] :param thermal_fem_results_list: List of thermal FEM result-logs - :type thermal_fem_results_list: List[Dict] + :type thermal_fem_results_list: list[dict] :return: List of FEM result-logs - :rtype: List[Dict] + :rtype: list[dict] """ common_case_list = [] for thermal_fem_result in thermal_fem_results_list: diff --git a/femmt/optimization/ito_dtos.py b/femmt/optimization/ito_dtos.py index 47227e3d..d69ba3d4 100644 --- a/femmt/optimization/ito_dtos.py +++ b/femmt/optimization/ito_dtos.py @@ -1,7 +1,6 @@ """DTOs for the integrated transformer optimization.""" # python libraries from dataclasses import dataclass -from typing import List # 3rd party libraries import numpy as np @@ -107,24 +106,24 @@ class ItoTargetAndFixedParameters: i_peak_2: float i_phase_deg_1: float i_phase_deg_2: float - material_dto_curve_list: List[MaterialCurve] - magnet_hub_model_list: List[LossModel] - time_extracted_vec: List - current_extracted_1_vec: List - current_extracted_2_vec: List + material_dto_curve_list: list[MaterialCurve] + magnet_hub_model_list: list[LossModel] + time_extracted_vec: list + current_extracted_1_vec: list + current_extracted_2_vec: list fundamental_frequency: float target_inductance_matrix: np.ndarray working_directories: WorkingDirectories # winding 1 - fft_frequency_list_1: List[float] - fft_amplitude_list_1: List[float] - fft_phases_list_1: List[float] + fft_frequency_list_1: list[float] + fft_amplitude_list_1: list[float] + fft_phases_list_1: list[float] # winding 2 - fft_frequency_list_2: List[float] - fft_amplitude_list_2: List[float] - fft_phases_list_2: List[float] + fft_frequency_list_2: list[float] + fft_amplitude_list_2: list[float] + fft_phases_list_2: list[float] @dataclass class ItoSingleResultFile: @@ -187,9 +186,9 @@ class ItoReluctanceModelInput: magnet_material_model: LossModel temperature: float - time_extracted_vec: List - current_extracted_vec_1: List - current_extracted_vec_2: List + time_extracted_vec: list + current_extracted_vec_1: list + current_extracted_vec_2: list fundamental_frequency: float i_rms_1: float @@ -199,14 +198,14 @@ class ItoReluctanceModelInput: litz_dict_2: dict # winding 1 - fft_frequency_list_1: List[float] - fft_amplitude_list_1: List[float] - fft_phases_list_1: List[float] + fft_frequency_list_1: list[float] + fft_amplitude_list_1: list[float] + fft_phases_list_1: list[float] # winding 2 - fft_frequency_list_2: List[float] - fft_amplitude_list_2: List[float] - fft_phases_list_2: List[float] + fft_frequency_list_2: list[float] + fft_amplitude_list_2: list[float] + fft_phases_list_2: list[float] @dataclass class ItoReluctanceModelOutput: diff --git a/femmt/optimization/ito_functions.py b/femmt/optimization/ito_functions.py index e8871e1a..e2fb1cd6 100644 --- a/femmt/optimization/ito_functions.py +++ b/femmt/optimization/ito_functions.py @@ -2,7 +2,6 @@ # python libraries import shutil import os -from typing import List, Tuple import inspect import femmt @@ -35,14 +34,14 @@ def _copy_electro_magnetic_necessary_files(src_folder: str, dest_folder: str): shutil.copy(from_path, to_path) -def dto_list_to_vec(dto_list: List[ItoSingleResultFile]) -> Tuple: +def dto_list_to_vec(dto_list: list[ItoSingleResultFile]) -> tuple: """ Brings a list of dto-objects to two lists. Use case is to bring the pareto-front into two vectors for further calculations :param dto_list: list of ItoSingleResultFile-DTOs - :type dto_list: List[ItoSingleResultFile] + :type dto_list: list[ItoSingleResultFile] """ for dto in dto_list: @@ -164,7 +163,7 @@ def integrated_transformer_fem_simulation_from_result_dto(config_dto: ItoSingleI # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.IntegratedTransformer, working_directory=fem_working_directory, - verbosity=femmt.Verbosity.Silent) + onelab_verbosity=femmt.Verbosity.Silent) window_h = dto.window_h_bot + dto.window_h_top + dto.core_inner_diameter / 4 @@ -248,7 +247,7 @@ def integrated_transformer_fem_simulation_from_result_dto(config_dto: ItoSingleI def integrated_transformer_fem_simulations_from_result_dtos(config_dto: ItoSingleInputConfig, - simulation_dto_list: List[ItoSingleResultFile], + simulation_dto_list: list[ItoSingleResultFile], visualize: bool = False, ): """ @@ -257,7 +256,7 @@ def integrated_transformer_fem_simulations_from_result_dtos(config_dto: ItoSingl :param config_dto: configuration DTO (data transfer object) :type config_dto: ItoSingleInputConfig :param simulation_dto_list: List of simulation DTOs to perform the FEM simulation - :type simulation_dto_list: List[ItoSingleResultFile] + :type simulation_dto_list: list[ItoSingleResultFile] :param visualize: True to visualize the results :type visualize: bool """ @@ -302,7 +301,7 @@ def integrated_transformer_fem_simulations_from_result_dtos(config_dto: ItoSingl def integrated_transformer_fem_thermal_simulations_from_result_dtos( - config_dto: ItoSingleInputConfig, simulation_dto_list: List[ItoSingleResultFile], + config_dto: ItoSingleInputConfig, simulation_dto_list: list[ItoSingleResultFile], visualize: bool = False): """ Thermal FEM simulation for the integrated transformer from a result DTO. @@ -310,7 +309,7 @@ def integrated_transformer_fem_thermal_simulations_from_result_dtos( :param config_dto: configuration DTO (data transfer object) :type config_dto: ItoSingleInputConfig :param simulation_dto_list: List of simulation DTOs to perform thermal FEM simulation - :type simulation_dto_list: List[ItoSingleResultFile] + :type simulation_dto_list: list[ItoSingleResultFile] :param visualize: True to visualize the results :type visualize: bool """ diff --git a/femmt/optimization/sto.py b/femmt/optimization/sto.py index 57f3d8f9..7287158e 100644 --- a/femmt/optimization/sto.py +++ b/femmt/optimization/sto.py @@ -3,7 +3,6 @@ import datetime import logging import os -from typing import List, Optional import shutil import json import pickle @@ -500,8 +499,8 @@ def single_reluctance_model_simulation(reluctance_input: ReluctanceModelInput) - return reluctance_output @staticmethod - def start_proceed_study(config: StoSingleInputConfig, number_trials: Optional[int] = None, - target_number_trials: Optional[int] = None, storage: str = 'sqlite', + def start_proceed_study(config: StoSingleInputConfig, number_trials: int | None = None, + target_number_trials: int | None = None, storage: str = 'sqlite', sampler=optuna.samplers.NSGAIIISampler(), ) -> None: """ @@ -697,13 +696,13 @@ def filter_loss_list_df(df: pd.DataFrame, factor_min_dc_losses: float = 1.2, fac Remove designs with too high losses compared to the minimum losses. :param df: list of valid DTOs - :type df: List[ItoSingleResultFile] + :type df: list[ItoSingleResultFile] :param factor_min_dc_losses: filter factor for the minimum dc losses :type factor_min_dc_losses: float :param factor_max_dc_losses: dc_max_loss = factor_max_dc_losses * min_available_dc_losses_in_pareto_front :type factor_max_dc_losses: float :returns: list with removed objects (too small air gaps) - :rtype: List[ItoSingleResultFile] + :rtype: list[ItoSingleResultFile] """ # figure out pareto front # pareto_volume_list, pareto_core_hyst_list, pareto_dto_list = self.pareto_front(volume_list, core_hyst_loss_list, valid_design_list) @@ -732,14 +731,14 @@ def filter_loss_list_df(df: pd.DataFrame, factor_min_dc_losses: float = 1.2, fac return pareto_df_offset @staticmethod - def df_trial_numbers(df: pd.DataFrame, trial_number_list: List[int]) -> pd.DataFrame: + def df_trial_numbers(df: pd.DataFrame, trial_number_list: list[int]) -> pd.DataFrame: """ Generate a new dataframe from a given one, just with the trial numbers from the trial_number_list. :param df: input dataframe :type df: pandas.DataFrame :param trial_number_list: list of trials, e.g. [1530, 1870, 3402] - :type trial_number_list: List[int] + :type trial_number_list: list[int] :return: dataframe with trial numbers from trial_number_list :rtype: pandas.DataFrame """ @@ -893,7 +892,7 @@ def single_fem_simulation(fem_input: FemInput, show_visual_outputs: bool = False # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.IntegratedTransformer, - working_directory=fem_input.working_directory, verbosity=fmt.Verbosity.Silent) + working_directory=fem_input.working_directory, onelab_verbosity=fmt.Verbosity.Silent) # 2. set core parameters core_dimensions = fmt.dtos.StackedCoreDimensions(core_inner_diameter=fem_input.core_inner_diameter, @@ -1042,7 +1041,7 @@ def fem_logs_to_df(reluctance_df: pd.DataFrame, fem_results_folder_path: str) -> return reluctance_df @staticmethod - def full_simulation(df_geometry: pd.DataFrame, current_waveform: List, stacked_transformer_config_filepath: str, process_number: int = 1, + def full_simulation(df_geometry: pd.DataFrame, current_waveform: list, stacked_transformer_config_filepath: str, process_number: int = 1, show_visual_outputs: bool = False, print_derivations: bool = False): """ Reluctance model (hysteresis losses) and FEM simulation (winding losses and eddy current losses) for geometries from df_geometry. @@ -1050,7 +1049,7 @@ def full_simulation(df_geometry: pd.DataFrame, current_waveform: List, stacked_t :param df_geometry: Pandas dataframe with geometries :type df_geometry: pd.DataFrame :param current_waveform: Current waveform to simulate - :type current_waveform: List + :type current_waveform: list :param stacked_transformer_config_filepath: Filepath of the inductor optimization configuration file :type stacked_transformer_config_filepath: str :param process_number: process number to run the simulation on diff --git a/femmt/optimization/sto_ct.py b/femmt/optimization/sto_ct.py index 19149b27..f23aad37 100644 --- a/femmt/optimization/sto_ct.py +++ b/femmt/optimization/sto_ct.py @@ -196,7 +196,7 @@ def objective(trial: optuna.Trial, config: StoCtSingleInputConfig, geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, working_directory=working_directory_single_process, - verbosity=verbosity, simulation_name=f"Case_{trial.number}") + onelab_verbosity=verbosity, simulation_name=f"Case_{trial.number}") geo.update_mesh_accuracies(config.mesh_accuracy, config.mesh_accuracy, config.mesh_accuracy, config.mesh_accuracy) @@ -676,7 +676,7 @@ def re_simulate_single_result(study_name: str, config: StoCtSingleInputConfig, n geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, working_directory=target_and_fixed_parameters.working_directories.fem_working_directory, - verbosity=femmt.Verbosity.Silent, + onelab_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 @@ -832,7 +832,7 @@ def re_simulate_from_df(df: pd.DataFrame, config: StoCtSingleInputConfig, number geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, working_directory=target_and_fixed_parameters.working_directories.fem_working_directory, - verbosity=femmt.Verbosity.Silent, + onelab_verbosity=femmt.Verbosity.Silent, simulation_name=f"Single_Case_{loaded_trial_params['number']}") geo.update_mesh_accuracies(mesh_accuracy_air_gaps=mesh_accuracy, mesh_accuracy_core=mesh_accuracy, @@ -981,7 +981,7 @@ def save_png_from_df(df: pd.DataFrame, config: StoCtSingleInputConfig, number_tr geo = femmt.MagneticComponent(component_type=femmt.ComponentType.IntegratedTransformer, working_directory=target_and_fixed_parameters.working_directories.fem_working_directory, - verbosity=femmt.Verbosity.Silent, + onelab_verbosity=femmt.Verbosity.Silent, simulation_name=f"Single_Case_{loaded_trial_params['number']}") core_dimensions = femmt.dtos.StackedCoreDimensions(core_inner_diameter=core_inner_diameter, window_w=window_w, @@ -1119,7 +1119,7 @@ def hover(event): @staticmethod def create_full_report(df: pd.DataFrame, trials_numbers: list[int], config: StoCtSingleInputConfig, thermal_config: ThermalConfig, - current_waveforms_operating_points: List[CurrentWorkingPoint], + current_waveforms_operating_points: list[CurrentWorkingPoint], fft_filter_value_factor: float = 0.01, mesh_accuracy: float = 0.5): """Create for several geometries and several working points a report. @@ -1129,13 +1129,13 @@ def create_full_report(df: pd.DataFrame, trials_numbers: list[int], config: StoC :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] + :type trials_numbers: list[int] :param config: stacked transformer optimization configuration file :type config: StoCtSingleInputConfig :param thermal_config: thermal configuration file :type thermal_config: ThermalConfig :param current_waveforms_operating_points: Trial numbers in a list to re-simulate - :type current_waveforms_operating_points: List[int] + :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 @@ -1195,7 +1195,7 @@ def create_pngs(df: pd.DataFrame, trials_numbers: list[int], config: StoCtSingle :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] + :type trials_numbers: list[int] :param config: stacked transformer optimization configuration file :type config: StoCtSingleInputConfig """ diff --git a/femmt/optimization/sto_ct_dtos.py b/femmt/optimization/sto_ct_dtos.py index e5b2e8ed..d2785a32 100644 --- a/femmt/optimization/sto_ct_dtos.py +++ b/femmt/optimization/sto_ct_dtos.py @@ -1,7 +1,6 @@ """DTOs for the stacked center-tapped transformer optimization.""" # python libraries from dataclasses import dataclass -from typing import List, Union # 3rd party libraries import numpy as np @@ -112,10 +111,10 @@ class StoCtTargetAndFixedParameters: i_peak_2: float i_phase_deg_1: float i_phase_deg_2: float - material_dto_curve_list: List[MaterialCurve] - time_extracted_vec: List - current_extracted_1_vec: List - current_extracted_2_vec: List + material_dto_curve_list: list[MaterialCurve] + time_extracted_vec: list + current_extracted_1_vec: list + current_extracted_2_vec: list fundamental_frequency: float target_inductance_matrix: np.ndarray working_directories: WorkingDirectories @@ -126,5 +125,5 @@ class CurrentWorkingPoint: """Stores the working point of currents together with a human-readable name.""" name: str - time_current_1_vec: Union[np.ndarray, list] - time_current_2_vec: Union[np.ndarray, list] + time_current_1_vec: np.ndarray | list + time_current_2_vec: np.ndarray | list diff --git a/femmt/optimization/sto_dtos.py b/femmt/optimization/sto_dtos.py index 59782a8c..e1bdad3b 100644 --- a/femmt/optimization/sto_dtos.py +++ b/femmt/optimization/sto_dtos.py @@ -1,7 +1,6 @@ """DTOs for the stacked transformer optimization.""" # python libraries from dataclasses import dataclass -from typing import List, Optional # 3rd party libraries import numpy as np @@ -34,23 +33,23 @@ class StoTargetAndFixedParameters: i_peak_2: float i_phase_deg_1: float i_phase_deg_2: float - material_dto_curve_list: List[MaterialCurve] - magnet_hub_model_list: List[LossModel] - time_extracted_vec: List - current_extracted_1_vec: List - current_extracted_2_vec: List + material_dto_curve_list: list[MaterialCurve] + magnet_hub_model_list: list[LossModel] + time_extracted_vec: list + current_extracted_1_vec: list + current_extracted_2_vec: list fundamental_frequency: float target_inductance_matrix: np.ndarray working_directories: WorkingDirectories # winding 1 - fft_frequency_list_1: List[float] - fft_amplitude_list_1: List[float] - fft_phases_list_1: List[float] + fft_frequency_list_1: list[float] + fft_amplitude_list_1: list[float] + fft_phases_list_1: list[float] # winding 2 - fft_frequency_list_2: List[float] - fft_amplitude_list_2: List[float] - fft_phases_list_2: List[float] + fft_frequency_list_2: list[float] + fft_amplitude_list_2: list[float] + fft_phases_list_2: list[float] @dataclass class StoInsulation: @@ -107,10 +106,10 @@ class StoSingleInputConfig: n_p_top_min_max_list: list n_p_bot_min_max_list: list material_list: list - core_name_list: Optional[List[str]] - core_inner_diameter_min_max_list: Optional[List[float]] - window_w_min_max_list: Optional[List[float]] - window_h_bot_min_max_list: Optional[List[float]] + core_name_list: list[str] | None + core_inner_diameter_min_max_list: list[float] | None + window_w_min_max_list: list[float] | None + window_h_bot_min_max_list: list[float] | None primary_litz_wire_list: list secondary_litz_wire_list: list @@ -196,9 +195,9 @@ class ReluctanceModelInput: magnet_material_model: LossModel temperature: float - time_extracted_vec: List - current_extracted_vec_1: List - current_extracted_vec_2: List + time_extracted_vec: list + current_extracted_vec_1: list + current_extracted_vec_2: list fundamental_frequency: float i_rms_1: float @@ -208,14 +207,14 @@ class ReluctanceModelInput: secondary_litz_dict: dict # # winding 1 - fft_frequency_list_1: List[float] - fft_amplitude_list_1: List[float] - fft_phases_list_1: List[float] + fft_frequency_list_1: list[float] + fft_amplitude_list_1: list[float] + fft_phases_list_1: list[float] # # # winding 2 - fft_frequency_list_2: List[float] - fft_amplitude_list_2: List[float] - fft_phases_list_2: List[float] + fft_frequency_list_2: list[float] + fft_amplitude_list_2: list[float] + fft_phases_list_2: list[float] @dataclass class ReluctanceModelOutput: diff --git a/femmt/optimization/to.py b/femmt/optimization/to.py index baf864b2..3d3da82e 100644 --- a/femmt/optimization/to.py +++ b/femmt/optimization/to.py @@ -168,7 +168,7 @@ def objective(trial: int, config: ToSingleInputConfig, geo = femmt.MagneticComponent(component_type=femmt.ComponentType.Transformer, working_directory=working_directory_single_process, - verbosity=verbosity, simulation_name=f"Case_{trial.number}") + onelab_verbosity=verbosity, simulation_name=f"Case_{trial.number}") geo.update_mesh_accuracies(config.mesh_accuracy, config.mesh_accuracy, config.mesh_accuracy, config.mesh_accuracy) @@ -465,7 +465,7 @@ def re_simulate_from_df(df: pd.DataFrame, config: ToSingleInputConfig, number_tr working_directory=os.path.join( target_and_fixed_parameters.working_directories.fem_working_directory, 'process_1'), - verbosity=femmt.Verbosity.Silent, + onelab_verbosity=femmt.Verbosity.Silent, simulation_name=f"Single_Case_{loaded_trial_params['number']}") geo.update_mesh_accuracies(mesh_accuracy_air_gaps=mesh_accuracy, mesh_accuracy_core=mesh_accuracy, @@ -641,7 +641,7 @@ def hover(event): @staticmethod def create_full_report(df: pd.DataFrame, trials_numbers: list[int], config: ToSingleInputConfig, thermal_config: ThermalConfig, - current_waveforms_operating_points: List[CurrentWorkingPoint], + current_waveforms_operating_points: list[CurrentWorkingPoint], fft_filter_value_factor: float = 0.01, mesh_accuracy: float = 0.5): """ Create for several geometries and several working points a report. @@ -652,13 +652,13 @@ def create_full_report(df: pd.DataFrame, trials_numbers: list[int], config: ToSi :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] + :type trials_numbers: list[int] :param config: stacked transformer optimization configuration file :type config: ToSingleInputConfig :param thermal_config: thermal configuration file :type thermal_config: ThermalConfig :param current_waveforms_operating_points: Trial numbers in a list to re-simulate - :type current_waveforms_operating_points: List[int] + :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 diff --git a/femmt/optimization/to_dtos.py b/femmt/optimization/to_dtos.py index d6df5924..3d0fb31c 100644 --- a/femmt/optimization/to_dtos.py +++ b/femmt/optimization/to_dtos.py @@ -1,7 +1,6 @@ """Includes the DTOs to perform a transformer optimization.""" # python libraries from dataclasses import dataclass -from typing import List, Union # 3rd party libraries import numpy as np @@ -110,10 +109,10 @@ class ToTargetAndFixedParameters: i_peak_2: float i_phase_deg_1: float i_phase_deg_2: float - material_dto_curve_list: List[MaterialCurve] - time_extracted_vec: List - current_extracted_1_vec: List - current_extracted_2_vec: List + material_dto_curve_list: list[MaterialCurve] + time_extracted_vec: list + current_extracted_1_vec: list + current_extracted_2_vec: list fundamental_frequency: float target_inductance_matrix: np.ndarray working_directories: WorkingDirectories @@ -124,5 +123,5 @@ class CurrentWorkingPoint: """Stores the working point of currents together with a human-readable name.""" name: str - time_current_1_vec: Union[np.ndarray, list] - time_current_2_vec: Union[np.ndarray, list] + time_current_1_vec: np.ndarray | list + time_current_2_vec: np.ndarray | list diff --git a/femmt/reluctance.py b/femmt/reluctance.py index e82b12a4..b3db85cd 100644 --- a/femmt/reluctance.py +++ b/femmt/reluctance.py @@ -1,5 +1,6 @@ """Create and calculate reluctance models.""" # python imports +import logging # 3rd library imports import numpy as np @@ -9,6 +10,7 @@ import femmt.functions_reluctance as fr from femmt.constants import mu_0 +logger = logging.getLogger(__name__) def plot_limitation(): """Plot limitation.""" @@ -18,7 +20,7 @@ def plot_limitation(): r_mx = 1 / (mu_0 * (width / 2 / length + 2 / np.pi * ( 1 + np.log(np.pi * height / 4 / length)))) - print(height) + logger.info(height) # width_c = 100 # length_c = 0.5 @@ -436,8 +438,8 @@ def input_pre_check(self): zip(*sorted(zip(self.air_gap_position, self.air_gap_h)))] self.air_gap_h = np.array(self.air_gap_h) self.air_gap_position = np.array(self.air_gap_position) - print(self.air_gap_position) - print(self.air_gap_h) + logger.info(self.air_gap_position) + logger.info(self.air_gap_h) if self.sim_type == 'sweep': if not self.air_gap_method == 'Percent': raise Exception("For sim_type = 'sweep', 'air_gap_method' should be only provided in percent") @@ -649,8 +651,8 @@ def air_gap_reluctance_single(self): if self.air_gap_method == 'Percent': self.position = np.array( self.air_gap_position) / 100 * self.window_h # Convert percent position to absolute value position - print(f"Max percent: {self.max_percent_position}") - print(f"Min percent: {self.min_percent_position}") + logger.info(f"Max percent: {self.max_percent_position}") + logger.info(f"Min percent: {self.min_percent_position}") if self.air_gap_position[0] <= self.min_percent_position: flag_0 = 1 @@ -663,7 +665,7 @@ def air_gap_reluctance_single(self): self.reluctance[:, 5] = self.reluctance[:, 5] + fr.r_air_gap_round_inf([self.air_gap_h[0]], [self.core_inner_diameter], [h]) - print('air gap is at lower corner') + logger.info('air gap is at lower corner') if self.air_gap_position[self.n_air_gaps - 1] >= self.max_percent_position: flag_1 = 1 @@ -676,7 +678,7 @@ def air_gap_reluctance_single(self): self.reluctance[:, 5] = self.reluctance[:, 5] + fr.r_air_gap_round_inf( [self.air_gap_h[self.n_air_gaps - 1]], [self.core_inner_diameter], [h]) - print('air gap is at upper corner') + logger.info('air gap is at upper corner') for i in range(self.n_air_gaps[0]): if self.min_percent_position < self.air_gap_position[i] < self.max_percent_position: @@ -702,23 +704,23 @@ def air_gap_reluctance_single(self): if flag_0 == 0 and flag_1 == 0: h1 = (self.position[i + 1] - self.position[i] - self.air_gap_h[i + 1] / 2 - self.air_gap_h[i] / 2) / 2 h2 = (self.position[i + 2] - self.position[i + 1] - self.air_gap_h[i + 2] / 2 - self.air_gap_h[i + 1] / 2) / 2 - print('No corner air gap detected') + logger.info('No corner air gap detected') elif flag_0 == 1 and flag_1 == 0: h1 = (self.position[i] - self.position[i - 1] - self.air_gap_h[i] / 2 - self.air_gap_h[ i - 1] / 2) / 2 h2 = (self.position[i + 1] - self.position[i] - self.air_gap_h[i + 1] / 2 - self.air_gap_h[ i] / 2) / 2 - print('Lower air gap detected') + logger.info('Lower air gap detected') elif flag_0 == 0 and flag_1 == 1: h1 = (self.position[i + 1] - self.position[i] - self.air_gap_h[i + 1] / 2 - self.air_gap_h[i] / 2) / 2 h2 = (self.position[i + 2] - self.position[i + 1] - self.air_gap_h[i + 2] / 2 - self.air_gap_h[i + 1] / 2) / 2 - print('Upper air gap detected') + logger.info('Upper air gap detected') else: h1 = (self.position[i] - self.position[i - 1] - self.air_gap_h[i] / 2 - self.air_gap_h[ i - 1] / 2) / 2 h2 = (self.position[i + 1] - self.position[i] - self.air_gap_h[i + 1] / 2 - self.air_gap_h[ i] / 2) / 2 - print('Both air gap detected') + logger.info('Both air gap detected') self.reluctance[:, 5] = self.reluctance[:, 5] + fr.r_air_gap_round_round([self.air_gap_h[i]], [ self.core_inner_diameter], [h1], [h2]) @@ -755,7 +757,7 @@ def calculate_inductance(self): """Calculate the inductance from Number of turns and total reluctance (L = N^2 / R_m).""" self.cal_inductance = (self.no_of_turns * self.no_of_turns) / np.sum(self.reluctance, axis=1) self.data_matrix[:, 9] = self.cal_inductance - print(f"Inductance:{self.cal_inductance}") + logger.info(f"Inductance:{self.cal_inductance}") def add_column_to_data_matrix(self, data_matrix, column_value, column_name: str): """ diff --git a/femmt/thermal/thermal_classes.py b/femmt/thermal/thermal_classes.py index 43d43136..46511cb2 100644 --- a/femmt/thermal/thermal_classes.py +++ b/femmt/thermal/thermal_classes.py @@ -1,14 +1,13 @@ """Classes for thermal simulation.""" # Python standard libraries -from typing import List, Tuple, Dict class ConstraintPro: """Boundary constraints.""" # For boundary constraints, the tuple contains (key, region, value) - boundary_constraints: List[Tuple[str, str, str]] + boundary_constraints: list[tuple[str, str, str]] def __init__(self): self.boundary_constraints = [] @@ -27,12 +26,12 @@ def create_boundary_if_string(flag: int, region: int, value: float): """ return f"\t\tIf({flag}==1)\n\t\t\t{{ Region {region} ; Type Assign; Value {value} ; }}\n\t\tEndIf\n" - def add_boundary_constraint(self, more_constraints: List): + def add_boundary_constraint(self, more_constraints: list): """ Add boundary constraint. :param more_constraints: constraints - :type more_constraints: List + :type more_constraints: list """ for constraint in more_constraints: self.boundary_constraints.append(constraint) @@ -59,12 +58,12 @@ class GroupPro: def __init__(self): self.regions = {} - def add_regions(self, more_regions: Dict): + def add_regions(self, more_regions: dict): """ Add given regions to the group. :param more_regions: - :type more_regions: Dict + :type more_regions: dict """ self.regions.update(more_regions) @@ -104,12 +103,12 @@ class ParametersPro: def __init__(self): self.parameters = {} - def add_to_parameters(self, more_parameters: Dict): + def add_to_parameters(self, more_parameters: dict): """ Add more_parameters to existing parameters. :param more_parameters: - :type more_parameters: Dict + :type more_parameters: dict """ self.parameters.update(more_parameters) @@ -141,14 +140,14 @@ def __init__(self): self.q_vol = {} @staticmethod - def dict_as_function_str(name: str, dictionary: Dict): + def dict_as_function_str(name: str, dictionary: dict): """ Write dictionary as a string. :param name: name :type name: str - :param dictionary: Dictionary - :type dictionary: Dict + :param dictionary: dictionary + :type dictionary: dict """ dict_as_str = "" for key, value in dictionary.items(): @@ -189,7 +188,7 @@ def create_file(self, file_path: str): class PostOperationPro: """For creating a post_operation.pro.""" - statements: List[str] + statements: list[str] def __init__(self): self.statements = [] diff --git a/femmt/thermal/thermal_functions.py b/femmt/thermal/thermal_functions.py index cd9565c8..8e80dc7a 100644 --- a/femmt/thermal/thermal_functions.py +++ b/femmt/thermal/thermal_functions.py @@ -2,7 +2,6 @@ # Python standard libraries import json import os -from typing import Dict # 3rd party libraries import numpy as np @@ -25,7 +24,7 @@ def calculate_heat_flux_round_wire(power, wire_radius, wire_distance): return power/volume -def read_results_log(results_log_file_path: str) -> Dict: +def read_results_log(results_log_file_path: str) -> dict: """ Read loss results from logfile to get the losses to perform a thermal simulation. diff --git a/femmt/thermal/thermal_simulation.py b/femmt/thermal/thermal_simulation.py index ab1461c3..e333cb77 100644 --- a/femmt/thermal/thermal_simulation.py +++ b/femmt/thermal/thermal_simulation.py @@ -4,7 +4,7 @@ import json import numpy as np import os -from typing import Dict, List +import logging # Third parry libraries import gmsh @@ -15,19 +15,21 @@ from femmt.thermal.thermal_classes import ConstraintPro, FunctionPro, GroupPro, ParametersPro, PostOperationPro from femmt.data import FileData -def create_case(boundary_regions: Dict, boundary_physical_groups: Dict, boundary_temperatures: Dict, - boundary_flags: Dict, k_case: float, +logger = logging.getLogger(__name__) + +def create_case(boundary_regions: dict, boundary_physical_groups: dict, boundary_temperatures: dict, + boundary_flags: dict, k_case: float, function_pro: FunctionPro, parameters_pro: ParametersPro, group_pro: GroupPro, constraint_pro: ConstraintPro): """ Set boundary conditions and material parameters for the case around the core. :param boundary_regions: - :type boundary_regions: Dict + :type boundary_regions: dict :param boundary_physical_groups: - :type boundary_physical_groups: Dict + :type boundary_physical_groups: dict :param boundary_flags: Dict with flags to turn-on or turn-off the boundary conditions of the case - :type boundary_flags: Dict + :type boundary_flags: dict :param k_case: Thermal conductivity of the case :type k_case: float :param function_pro: function.pro file @@ -39,7 +41,7 @@ def create_case(boundary_regions: Dict, boundary_physical_groups: Dict, boundary :param constraint_pro: constraint.pro file :type constraint_pro: ConstraintPro :param boundary_temperatures: Boundary temperatures as a dictionary - :type boundary_temperatures: Dict + :type boundary_temperatures: dict """ group_pro.add_regions(boundary_regions) parameters_pro.add_to_parameters(boundary_temperatures) @@ -158,20 +160,20 @@ def create_core_and_air_gaps(core_tags: list, k_core: float, core_area: list, co group_pro.add_regions(regions) function_pro.add_dicts(k, q_vol) -def create_windings(winding_tags: List, k_windings: float, winding_losses, conductor_radii: List[float], wire_distances: List[List[float]], +def create_windings(winding_tags: list, k_windings: float, winding_losses, conductor_radii: list[float], wire_distances: list[list[float]], function_pro: FunctionPro, group_pro: GroupPro): """Create windings for the thermal simulation. :param winding_tags: - :type winding_tags: List + :type winding_tags: list :param k_windings: Thermal conductivity of windings :type k_windings: float :param winding_losses: List of winding losses - :type winding_losses: List + :type winding_losses: list :param conductor_radii: List of conductor radius - :type conductor_radii: List + :type conductor_radii: list :param wire_distances: List of wire distances per winding - :type wire_distances: List[List[float]] + :type wire_distances: list[list[float]] :param function_pro: function.pro file :type function_pro: FunctionPro :param group_pro: group.pro file @@ -198,7 +200,7 @@ def create_windings(winding_tags: List, k_windings: float, winding_losses, condu conductor_radii[winding_index], wire_distances[winding_index][index]) - print(q_vol[name]) + logger.info(q_vol[name]) k[name] = k_windings if tag not in tag_counters: # The counter is needed here to create a single region for every turn in case of parallel windings tag_counters[tag] = 0 @@ -447,7 +449,7 @@ def post_operation(case_volume: float, output_file: str, sensor_points_file: str "max": core_part_max, "mean": mean_sum / len(core_values.keys()) } - print(len(core_values.keys())) + logger.info(len(core_values.keys())) # windings winding_values = parse_gmsh_parsed(winding_file) windings = {} @@ -511,8 +513,8 @@ def post_operation(case_volume: float, output_file: str, sensor_points_file: str with open(output_file, "w") as fd: json.dump(data, fd, indent=2) -def run_thermal(file_data: FileData, tags_dict: Dict, thermal_conductivity_dict: Dict, boundary_temperatures: Dict, - boundary_flags: Dict, boundary_physical_groups: Dict, core_area: List, conductor_radii: float, +def run_thermal(file_data: FileData, tags_dict: dict, thermal_conductivity_dict: dict, boundary_temperatures: dict, + boundary_flags: dict, boundary_physical_groups: dict, core_area: list, conductor_radii: float, wire_distances: float, case_volume: float, show_thermal_fem_results: bool, print_sensor_values: bool, silent: bool, flag_insulation: bool = True): """ diff --git a/gui/femmt_gui.py b/gui/femmt_gui.py index d577d9f6..155e7498 100644 --- a/gui/femmt_gui.py +++ b/gui/femmt_gui.py @@ -11,7 +11,6 @@ import femmt as fmt import json import os -from typing import List import PIL import webbrowser import shutil @@ -2941,7 +2940,7 @@ def md_redraw_input_signals(self) -> None: # Simulation tab # ---------------------------------------------------------- - def md_get_frequency_lists(self) -> List: + def md_get_frequency_lists(self) -> list: """ Read frequency, amplitude and phase depending on the checked frequencies and return it as a list. @@ -3062,7 +3061,7 @@ def md_setup_geometry(self): geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=self.md_working_directory_lineEdit.text(), is_gui=True, - verbosity=fmt.Verbosity.ToConsole) + onelab_verbosity=fmt.Verbosity.ToConsole) self.check_onelab_config(geo) core_dimensions = fmt.dtos.SingleCoreDimensions(core_inner_diameter=comma_str_to_point_float(self.md_core_width_lineEdit.text()), @@ -3246,7 +3245,7 @@ def md_setup_geometry(self): geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=self.md_working_directory_lineEdit.text(), is_gui=True, - verbosity=fmt.Verbosity.ToConsole) + onelab_verbosity=fmt.Verbosity.ToConsole) self.check_onelab_config(geo) # ----------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..0a069639 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[project] +name = "femmt" +version = "0.5.4" +authors = [ + { name = "UPB-LEA" }, +] +description = "FEM Magnetics Toolbox" +readme = "README.rst" +requires-python = ">=3.11" +classifiers = [ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +dynamic = ["dependencies"] # commented due to packaging issues: "optional-dependencies" + +[tool.setuptools.dynamic] +dependencies = { file = ["requirements.txt"] } +optional-dependencies = { dev = { file = ["requirements-dev.txt"] } } + +[project.urls] +Homepage = "https://github.com/upb-lea/FEM_Magnetics_Toolbox" +Issues = "https://github.com/upb-lea/FEM_Magnetics_Toolbox/issues" + +[build-system] +requires = ["hatchling", "hatch-requirements-txt"] +build-backend = "hatchling.build" + +[tool.hatch.metadata.hooks.requirements_txt] +files = ["requirements.txt"] + +[tool.hatch.build.targets.wheel] +packages = ["femmt/"] + +[tool.hatch.build.targets.sdist] +include = [ + "femmt/*.py", + "tests", + "requirements.txt" +] + + + +[tool.ruff] +exclude = [ + ".eggs", + ".git", + ".venv", + "venv"] + +line-length = 120 +indent-width = 4 + +target-version = "py311" + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "B", "D", "D417"] +# extend-select = ["D417"] deactivated by default in case of pep257 codestyle. +# see also: https://docs.astral.sh/ruff/rules/undocumented-param/ +ignore = ["B008", "D107", "D203", "D212", "D213", "D402", "D413", "D415", "D416", "E722", "E731", "F403", "F405", "F841",] +fixable = ["ALL"] +unfixable = [] +# ignore list in docstring according to numpy codestyles for Dxxx. +# http://www.pydocstyle.org/en/5.0.1/error_codes.html#default-conventions + +[tool.ruff.lint.pydocstyle] +convention = "pep257" + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + + +[tool.mypy] +python_version = "3.11" +warn_return_any = true +warn_unused_configs = true +disallow_incomplete_defs = true +exclude = [ + 'examples/pareto_summary.py', # TOML literal string (single-quotes, no escaping necessary) +] + +ignore_missing_imports = true \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..17e25185 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +pandas +numpy +matplotlib +gmsh>=4.13.1 +onelab>=1.0 +scipy +pytest +pycodestyle +PyQt5>=5.15.6 +mplcursors>=0.5.1 +deepdiff>=6.2.1 +materialdatabase==0.3.0 +optuna +plotly +mag-net-hub +tqdm \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index f599af54..00000000 --- a/setup.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python - -"""The setup script.""" - -from setuptools import setup, find_packages - -with open('README.rst') as readme_file: - readme = readme_file.read() - -with open('CHANGELOG.md') as history_file: - history = history_file.read() - -# with open('requirements.txt', 'r') as f: -# requirements = f.read().splitlines() - -setup_requirements = ['setuptools_scm'] - -test_requirements = [] - -setup( - author="LEA - Uni Paderborn", - author_email='upblea@mail.upb.de', - python_requires='>=3.10', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', - 'Natural Language :: English', - 'Topic :: Scientific/Engineering', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX :: Linux', - 'Environment :: MacOS X' - ], - description="FEM Magnetics Toolbox", - install_requires=['pandas', - 'numpy', - 'matplotlib', - 'gmsh>=4.13.1', - 'onelab>=1.0', - 'scipy', - 'pytest', - 'pycodestyle', - 'PyQt5>=5.15.6', - 'mplcursors>=0.5.1', # TODO Is this necessary? - 'deepdiff>=6.2.1', # comparing result dicts for pytests - 'materialdatabase==0.3.0', - 'optuna', - 'plotly', - 'mag-net-hub', - 'tqdm'], - license="GNU General Public License v3", - long_description=readme + '\n\n' + history, - long_description_content_type="text/markdown", - include_package_data=True, - keywords='femmt', - name='femmt', - packages=find_packages(include=['femmt', 'femmt.*']), - setup_requires=setup_requirements, - test_suite='tests', - tests_require=test_requirements, - extras_require={}, - url='https://github.com/upb-lea/FEM_Magnetics_Toolbox', - project_urls={ - "Documentation": "https://upb-lea.github.io/FEM_Magnetics_Toolbox/main/intro.html", - "Source Code": "https://github.com/upb-lea/FEM_Magnetics_Toolbox", - }, - version='0.5.4', - zip_safe=False, - data_files=[('', ['CHANGELOG.md'])] -) diff --git a/tests/integration/test_femmt.py b/tests/integration/test_femmt.py index 0a6c2045..1eb5c06e 100644 --- a/tests/integration/test_femmt.py +++ b/tests/integration/test_femmt.py @@ -147,7 +147,7 @@ def fixture_inductor_core_material_database(temp_folder: pytest.fixture): # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -268,7 +268,7 @@ def fixture_inductor_core_material_database_measurement(temp_folder: pytest.fixt # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -393,7 +393,7 @@ def fixture_inductor_core_fixed_loss_angle(temp_folder: pytest.fixture): # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -513,7 +513,7 @@ def fixture_inductor_core_fixed_loss_angle_dc(temp_folder: pytest.fixture): # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -632,7 +632,7 @@ def fixture_inductor_core_fixed_loss_angle_litz_wire(temp_folder: pytest.fixture # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -755,7 +755,7 @@ def fixture_inductor_core_fixed_loss_angle_foil_vertical(temp_folder: pytest.fix # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -879,7 +879,7 @@ def fixture_inductor_core_fixed_loss_angle_foil_horizontal(temp_folder: pytest.f # Set is_gui = True so FEMMt won't ask for the onelab path if no config is found. geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Inductor, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1000,7 +1000,7 @@ def fixture_transformer_core_fixed_loss_angle(temp_folder: pytest.fixture): # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1129,7 +1129,7 @@ def fixture_transformer_interleaved_core_fixed_loss_angle(temp_folder: pytest.fi # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1257,7 +1257,7 @@ def fixture_transformer_integrated_core_fixed_loss_angle(temp_folder: pytest.fix # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.IntegratedTransformer, - working_directory=working_directory, verbosity=fmt.Verbosity.Silent, is_gui=True) + working_directory=working_directory, onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1394,7 +1394,7 @@ def fixture_transformer_stacked_center_tapped(temp_folder: pytest.fixture): # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.IntegratedTransformer, - working_directory=working_directory, verbosity=fmt.Verbosity.Silent, + working_directory=working_directory, onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually @@ -1547,7 +1547,7 @@ def fixture_transformer_5_windings(temp_folder: pytest.fixture): # 1. chose simulation type geo = fmt.MagneticComponent(component_type=fmt.ComponentType.Transformer, working_directory=working_directory, - verbosity=fmt.Verbosity.Silent, is_gui=True) + onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1712,7 +1712,7 @@ def fixture_inductor_time_domain(temp_folder: pytest.fixture): # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.TimeDomain, component_type=fmt.ComponentType.Inductor, - working_directory=working_directory, verbosity=fmt.Verbosity.Silent, is_gui=True) + working_directory=working_directory, onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1803,7 +1803,7 @@ def fixture_transformer_time_domain(temp_folder: pytest.fixture): # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.TimeDomain, component_type=fmt.ComponentType.Transformer, - working_directory=working_directory, verbosity=fmt.Verbosity.Silent, is_gui=True) + working_directory=working_directory, onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder @@ -1895,7 +1895,7 @@ def fixture_transformer_3_windings_time_domain(temp_folder: pytest.fixture): # 1. chose simulation type geo = fmt.MagneticComponent(simulation_type=fmt.SimulationType.TimeDomain, component_type=fmt.ComponentType.Transformer, - working_directory=working_directory, verbosity=fmt.Verbosity.Silent, is_gui=True) + working_directory=working_directory, onelab_verbosity=fmt.Verbosity.Silent, is_gui=True) # Set onelab path manually geo.file_data.onelab_folder_path = onelab_folder