diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bf0432a5..c0e75520 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,6 +36,12 @@ jobs: pip install pylint pylint $(git ls-files '*.py') + - name: Install packages for spell check and perform spell check + run: | + sudo apt install aspell-en + pip install pyspelling + pyspelling + echo finished spell checking - name: install femmt package run: | diff --git a/.gitignore b/.gitignore index e82a5301..073ab3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ femmt.egg-info/ onelab/ */working_directory/ tests/integration/temp/ +docs/_templates/ # FEMMT: generated files femmt/electro_magnetic/Parameter.pro @@ -76,3 +77,6 @@ femmt/thermal/solver/PostOperation.pro femmt/examples/paper_thermal_validation.py !femmt/examples/example_log.json femmt/examples/example_results + +# pyspelling +dictionary.dic diff --git a/.pyspelling.yml b/.pyspelling.yml new file mode 100644 index 00000000..ef124fce --- /dev/null +++ b/.pyspelling.yml @@ -0,0 +1,37 @@ +matrix: +- name: Python Source + aspell: + lang: en + d: en_US + sources: + - femmt/*.py + - femmt/*/*.py + dictionary: + wordlists: + - docs/wordlist + pipeline: + - pyspelling.filters.python: + strings: true + comments: false + ignore_regexp: + - '.*\b(TODO|FIXME|NOTE)\b.*' +- name: markdown + sources: + - README.rst + #- CHANGELOG.md + aspell: + lang: en + d: en_US + dictionary: + wordlists: + - docs/wordlist + pipeline: + - pyspelling.filters.markdown: + - pyspelling.filters.html: + comments: false + attributes: + - title + - alt + ignores: + - code + - pre \ No newline at end of file diff --git a/README.rst b/README.rst index 91ddab70..c7724d29 100644 --- a/README.rst +++ b/README.rst @@ -39,7 +39,7 @@ Stable features * Litz wire database * Core geometry database * Conductor materials: temperature curves - * Ferrit materials: permeability / permittivity `material database `__ (datasheets and own measurements) + * Ferrite materials: permeability / permittivity `material database `__ (datasheets and own measurements) * Solver features: * Implemented using `ONELAB `__ @@ -63,7 +63,7 @@ Stable features * Transformer equivalent diagrams for 2- and 3-winding transformer * Optimization: - * Parallel computing to speed up simulations (no cpu core limitation) + * Parallel computing to speed up simulations (no CPU core limitation) * Examples for Pareto optimization to adapt to your own optimization problem @@ -124,7 +124,7 @@ Further steps for macOS(ARM) - Go to https://onelab.info/ - Download the Desktop Version for Windows - Copy the "conveks.py" and "Onelab.py" files and paste them into the "onelab" folder -- Go to http://getdp.info/ +- Go to https://getdp.info/ - Download the macOS(ARM) Version - Open the downloaded folder, navigate to "bin" and copy the "getdp" application file - Paste the copied file into your "onelab" folder diff --git a/docs/wordlist b/docs/wordlist new file mode 100644 index 00000000..e227b54d --- /dev/null +++ b/docs/wordlist @@ -0,0 +1,462 @@ + +femmt +FEMMT +onelab +Onelab +py +https +windings +Roadmap +gmsh +ferrite +Ferrite +datasheets +FFT +fft +Litz +litz +axi +README +Magnetics +bool +param +params +iso +str +insulations +json +txt +csv +np +Numpy +numpy +optuna +sqlite +mysql +localhost +monty +mydb +nan +NSGAIIISampler +NSGAIISampler +NSGAII +NSGAIII +FemSimulation +StackedTransformerOptimization +config +bool +sto +bo +gc +StoCtTargetAndFixedParameters +preconfigured +Toroidal +precisions +MeshAccuracies +num +lm +Tonnenwicklung +Blockprop +snm +NbStepsPerPeriod +Rr +TimeStep +FEH +Uncomment +MOGetCircuitProperties +getb +getcircuitproperties +icoil +mH +plt +vals +xlabel +ylabel +zee + +SurfaceEdges +getdp +macOS +PostOperationPro +StoTargetAndFixedParameters +InductorOptimizationTargetAndFixedParameters +StoCtSingleInputConfig +MouseEvent +xdata +smallestIndex +WWR +ro +wwr +ib +ilm +il +ln +Runtimes +ItoSingleResultFiles +gui +OneDrive + + +PQ +ndarray +paderborn +FEMM +matlab +png +Changelog +dat +conveks +Pre +inductor +qVol +EndIf +pos +GmshParsed +SimpleTable +Neumann +GroupPro +FunctionPro +hyst +FrozenTrial +ItoSingleResultFile +daxi +iso +df +magnetoquasistatic +waveforms +geo +ThermalConfig +mariadb +elif +rtype +rms +dto +vec +attrs +inductances +conc +electro +pkl +wb +rb +DTO +FemInput +FemOutput +DataFrame +plotly +html +pareto +url +changelog +github +msh +myGetDP +filepath +Filepath +ToSingleInputConfig +Dataframe +dataframe +dataframes +PostOperation +UsingPost +br +OnElementsOf +OnPoint +ReluctanceModelInput +ReluctanceModelOutput +logifle +ConstraintPro +ParametersPro +ItoTargetAndFixedParameters +ToTargetAndFixedParameters +StoSingleInputConfig +stackoverflow +postgresql +STO +WorkingDirectories +numPoints +conductivities +raz +addboundprop +drawrectangle +selectsegment +setsegmentprop +logfile +TimeList +Rc +RrSurfaceEdges +SaturateValues +ShowTime +jH +Magb +NbTimeStep +coeff +PreParameter +myGmsh +MagDyn +qB +Profil +mqs + +nt +fourier +timestep +Elektrisola + +RdYlGn +ItoSingleInputConfig +InductorOptimizationDTO +DTOs +cls +io +MyJSONEncoder +fmt +datasource +ffT +pytest +args +subdirectories +femm +ConductorRow +ConductorType +RectangularSolid +RoundLitz +RoundSolid +WindingTag +cond +conds +CenterTappedGroup +ThreeWindingIsolation +TypeA +TypeB +TypeC +TypeD +ter +SweepData +FileData +SweepTypes +Christoffel +Muelethaler +Mühlethaler +Schwarz +MagneticCircuit +MagneticComponent +metres +CenterLeg +VirtualWindingWindow +AirGaps +vww +FoilVertical +enums + +Butterworths +Ferrites +Ecklebe +Kolar +reluctances +GetDP +src +cond +SaveAll +MshFileVersio +SurfaceFaces +RightCase +TopRightCase +TopCase +BottomRightCase +rasterization +Rasterization +MTB +LK +datasheet +MagNet +DMR +AirGaps +StrayPath +WindingWindow +ConductorArrangement +SquareFullWidth +AirGapMethod +AirGapLegPosition +CenterLeg +OuterLeg +StackedPosition +axisymmetric +ToEdges +WindingScheme +WrapParaType +datasource +ConductorRows +InterleavingSchemesFoilLitz +CenterTappedInterleavingType +ConductorStack +Snelling +MagneticComponents +addCurveLoop +MshFileVersion +PostView +BottomCase +insulationS +NoSplit +gapped +ooo +CenterOnHorizontalAxis +CenterOnVerticalAxis +ConductorPlacingStrategy +FoilVerticalDistribution +InterleavedWindingScheme +HorizontalAndVerticalSplit +HorizontalSplit +VerticalSplit +WindingWindowSplit +vwws +ConductorStack +dataclass +ki +steinmetz +LeftLeg +RightLeg +AirGap +FoilHorizontal +WindingType +topologies +FoilHorizontalDistribution +InterleavingSchemesFoilLitz +lh +imag +scipy +AnalyticalCoreData +Entwurf +eines +einstufigen +Ladewandlers +auf +Basis +eines +LLC +Resonanzwandlers +Keuck +cyl +xy +APEC +Bifilar +bifilar +FoilHorizontal +HorizontalAlternating +VerticalAlternating +VerticalStacked +fs +ExcitationMeshingType +MeshEachFrequency +MeshOnlyHighestFrequency +colorfile +pre +nd +ww +utf +ECD +Sqrt +sqrt +Lh +ImposedVoltageNbrStrands +ImposedVoltage +HomogenisedModel +nCoreParts +NbrStrands +DirRes +DirResFields +DirResVals +DirResValsCore +DirStrandCoeff +ScaleType +pB +pI +nf +qI +NeumannBoundary +Burkart +npt +NDArray +matplotlib +dir +kwargs +dicts +rect +si +nanocristalline +Enum +enum +tolist +ValueError +AreaCell +RUPALIT +AutomatedDesign + +NbrCond +NbrLayers +EE +CurrentList +NL +mur +postquantities +DirResValsWinding +DirResCirc +OptionPos +CoreEddyCurrentLosses +dt +ww +isolations +mult +dest +cpu +os +hpc +len +tmp +xN +onelab's +pyfemm +colormap +TODO +sublist +superordinate +rotationally +mWb +ComponentType +accuracies +von +Dirichlet +filepaths +Prolog +MeshOnlyLowestFrequency +Logfile +ohmic +ColormapNumber +CustomMax +CustomMin +IntervalsType +NbIso +RangeType +ScaleType +getNumber +setNumber +subdirectory +ToConsole +ToFile +th +sublist +timemax +NbSetpsPerPeriod +NbSteps + + + + + + + + +# logging +FEMMTLogger +asctime +levelname \ No newline at end of file diff --git a/femmt/__init__.py b/femmt/__init__.py index b3d3eb3d..4e5d59f2 100644 --- a/femmt/__init__.py +++ b/femmt/__init__.py @@ -1,4 +1,4 @@ -"""Init file for the FEMMT package.""" +"""Initialize file for the FEMMT package.""" from femmt.enumerations import * from femmt.logparser import * from femmt.data import * diff --git a/femmt/component.py b/femmt/component.py index 89acb668..05c13b2c 100644 --- a/femmt/component.py +++ b/femmt/component.py @@ -260,7 +260,7 @@ def thermal_simulation(self, thermal_conductivity_dict: Dict, boundary_temperatu :type thermal_conductivity_dict: Dict :param boundary_temperatures_dict: Contains the temperatures at each boundary line :type boundary_temperatures_dict: Dict - :param boundary_flags_dict: Sets the boundary type (dirichlet or von neumann) for each boundary line + :param boundary_flags_dict: Sets the boundary type (Dirichlet or von Neumann) for each boundary line :type boundary_flags_dict: Dict :param case_gap_top: Size of the top case :type case_gap_top: float @@ -390,7 +390,7 @@ def onelab_setup(self, is_gui: bool): while onelab_path_wrong: onelab_path = os.path.normpath(input( - "Enter the path of onelab's parent folder (path to folder which contains getdp, onelab executables): ")) + "Enter the path of onelab's parent folder (path to folder which contains getdp, onelab executable files): ")) if os.path.exists(onelab_path): onelab_path_wrong = False @@ -552,14 +552,14 @@ def create_model(self, freq: float, skin_mesh_factor: float = 0.5, pre_visualize :type freq: float :param skin_mesh_factor: [default to 0.5] :type skin_mesh_factor: float - :param pre_visualize_geometry: True for a pre-visualisation (e.g. check your geometry) and after this a + :param pre_visualize_geometry: True for a pre-visualization (e.g. check your geometry) and after this a simulation runs, False for a direct simulation :type pre_visualize_geometry: bool :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 - :param colors_geometry: definition for e.g. core is grey, winding is orange, ... + :param colors_geometry: definition for e.g. core is gray, winding is orange, ... :type colors_geometry: Dict :param benchmark: Benchmark simulation (stop time). Defaults to False. :type benchmark: bool @@ -1514,11 +1514,11 @@ def excitation_sweep(self, frequency_list: List, current_list_list: List, phi_de :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 genarated mesh before the simulation is run + :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 - :param colors_geometry: definition for e.g. core is grey, winding is orange, ... + :param colors_geometry: definition for e.g. core is gray, winding is orange, ... :type colors_geometry: Dict :param save_png: True to save a .png :type save_png: bool @@ -3308,7 +3308,7 @@ def write_electro_magnetic_post_pro(self): text_file.write( f"DirStrandCoeff = \"{self.file_data.e_m_strands_coefficients_folder_path.replace(backslash, '/')}/\";\n") - # Visualisation + # visualization if self.plot_fields == "standard": text_file.write("Flag_show_standard_fields = 1;\n") else: @@ -5269,7 +5269,7 @@ def encode_settings(o) -> Dict: @staticmethod def decode_settings_from_log(log_file_path: str, working_directory: str = None, verbosity: Verbosity = Verbosity.Silent): """ - Read the given log and returns the magnetic component from th elog. + Read the given log and returns the magnetic component from the log. :param log_file_path: Path to the log file :type log_file_path: str diff --git a/femmt/examples/__init__.py b/femmt/examples/__init__.py index 889e426a..959dcf1f 100644 --- a/femmt/examples/__init__.py +++ b/femmt/examples/__init__.py @@ -1 +1 @@ -"""Init-file for the FEMMT examples.""" +"""Initialize the FEMMT examples.""" diff --git a/femmt/examples/advanced_sto.py b/femmt/examples/advanced_ct_sto.py similarity index 97% rename from femmt/examples/advanced_sto.py rename to femmt/examples/advanced_ct_sto.py index 38f77524..205b639d 100644 --- a/femmt/examples/advanced_sto.py +++ b/femmt/examples/advanced_ct_sto.py @@ -1,5 +1,5 @@ """ -Advanced example to show an optimization workflow for the stacked transformer. +Advanced example to show an optimization workflow for the center-tapped stacked transformer. A stacked transformer should be optimized. The target parameters are: * l_s12_target=5.8e-6, @@ -21,7 +21,7 @@ interleaving_type_list=[fmt.CenterTappedInterleavingType.TypeC], interleaving_scheme_list=[fmt.InterleavingSchemesFoilLitz.ter_3_4_sec_ter_4_3_sec], -For the optimization, a genetic algorythm (e.g. NSGAII or NSGAIII) is used. The external "optuna" +For the optimization, a genetic algorithm (e.g. NSGAII or NSGAIII) is used. The external "optuna" toolbox is used to perform the optimization. The optimizer makes several "trials" to suggest geometry and material parameters out of the given lists. In case of invalid designs are suggested, the trials will fail "Trial 1 failed with value (nan, nan, nan, nan)". Others will work fine. diff --git a/femmt/examples/femmt_benchmark.py b/femmt/examples/femmt_benchmark.py index 146be85a..3aa74983 100644 --- a/femmt/examples/femmt_benchmark.py +++ b/femmt/examples/femmt_benchmark.py @@ -1,6 +1,6 @@ """Speed up of FEMMT by using parallel processing. File to generate benchmarks of different speed-up techniques. -It contains multiple benchmarking functions in order to analyse the runtime and the accuracy of the simulation results. +It contains multiple benchmark functions in order to analyze the runtime and the accuracy of the simulation results. """ # Python standard libraries @@ -382,7 +382,7 @@ def benchmark_rectangular_conductor_offset(working_directory): axis[1].plot(left_bound_deltas, current_winding_losses, "o") axis[2].plot(left_bound_deltas, current_execution_times, "o", label=f"Mesh accuracy: {mesh_accuracy}") - axis[0].set_ylabel("|Self indutance|") + axis[0].set_ylabel("|Self inductance|") axis[0].set_xticks(left_bound_deltas) axis[0].grid() @@ -401,7 +401,7 @@ def benchmark_rectangular_conductor_offset(working_directory): def benchmark_rectangular_conductor(working_directory: str): """ - Benchmark mesh accuracies inside a rectangular condutor. + Benchmark mesh accuracies inside a rectangular conductor. :param working_directory: working directory :type working_directory: str diff --git a/femmt/examples/inductor_optimization.py b/femmt/examples/inductor_optimization.py index 43616b93..ff46c947 100644 --- a/femmt/examples/inductor_optimization.py +++ b/femmt/examples/inductor_optimization.py @@ -1223,7 +1223,7 @@ def save_automated_design_settings(self): ad.fem_simulation() elif task == 'load': - working_directory = '/home/nikolasf/Dokumente/01_git/30_Python/FEMMT/femmt/examples/example_results/2023-02-28_inductor_optimization_N95_360u_5A' + working_directory = '' # Load design and plot various plots for analysis inductance, total_loss, total_volume, total_cost, annotation_list, automated_design_settings = load_fem_simulation_results( @@ -1239,20 +1239,3 @@ def save_automated_design_settings(self): plot_2d(x_value=plot_data[:, 1], y_value=plot_data[:, 3], z_value=plot_data[:, 2], x_label='Volume / m\u00b3', y_label='Cost / \u20ac', z_label='Loss / W', title='Volume vs Cost', annotations=plot_data[:, 4], plot_color='RdYlGn_r', inductance_value=plot_data[:, 0]) - - # plot_2d(x_value=data_array[:, 1], y_value=data_array[:, 3], z_value=data_array[:, 2], x_label='Volume / m\u00b3', - # y_label='Cost / \u20ac', z_label='Loss / W', - # title='Volume vs Cost', plot_color='red') - - # plot_2d(x_value=total_volume, y_value=total_loss, x_label='Volume / m\u00b3', y_label='Loss / W', - # title='Volume vs Loss', annotations=annotation_list, plot_color='red') - # plot_2d(x_value=total_volume, y_value=total_cost, x_label='Volume / m\u00b3', y_label='Cost / \u20ac', - # title='Volume vs Cost', annotations=annotation_list, plot_color='red') - # plot_2d(x_value=total_volume, y_value=inductance, x_label='Volume / m\u00b3', y_label='Inductance / H', - # title='Volume vs Inductance', annotations=annotation_list, plot_color='red') - # plot_3d(x_value=total_volume, y_value=total_loss, z_value=total_cost, x_label='Volume / m\u00b3', - # y_label='Loss / W', z_label='Cost / \u20ac', title='Volume vs Loss vs Cost', - # annotations=annotation_list, plot_color='red') - - # load_from_single_file(working_directory='D:/Personal_data/MS_Paderborn/Sem4/Project_2/2022-11-27_fem_simulation_data', - # file_name='case4.json') diff --git a/femmt/examples/ito_brute_force_example.py b/femmt/examples/ito_brute_force_example.py deleted file mode 100644 index bfad044f..00000000 --- a/femmt/examples/ito_brute_force_example.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Example how to perform a brute-force optimization.""" -import femmt as fmt -import numpy as np -import os - -core_database = fmt.core_database() -pq2016 = core_database["PQ 20/16"] -pq3230 = core_database["PQ 32/30"] -pq4040 = core_database["PQ 40/40"] -pq5050 = core_database["PQ 50/50"] -pq6560 = core_database["PQ 65/60"] - - -i_1 = [[0.0, 3.265248131976911e-07, 2.5e-06, 2.8265248131976912e-06, 5e-06], - [-0.9996115022426437, 4.975792579275104, 0.9996115022426446, -4.975792579275103, -0.9996115022426437]] -i_2 = [[0.0, 3.265248131976911e-07, 2.5e-06, 2.8265248131976912e-06, 5e-06], - [-0.9196195846583147, -19.598444313231134, 0.9196195846583122, 19.59844431323113, -0.9196195846583147]] - -dab_transformer_config = fmt.ItoSingleInputConfig( - l_s_target=85e-6, - l_h_target=600e-6, - n_target=2.9, - time_current_1_vec=np.array(i_1), - time_current_2_vec=np.array(i_2), - material_list=["N95"], - core_inner_diameter_min_max_list=[pq2016["core_inner_diameter"], pq4040["core_inner_diameter"], 2], - window_w_min_max_list=[pq2016["window_w"], pq4040["window_w"], 3], - window_h_top_min_max_list=[5 / 6 * pq2016["window_h"], 5 / 6 * pq3230["window_h"], 2], - window_h_bot_min_max_list=[1 / 6 * pq2016["window_h"], 1 / 6 * pq3230["window_h"], 2], - factor_max_flux_density=1, - primary_litz_wire_list=["1.4x200x0.071"], - secondary_litz_wire_list=["1.4x200x0.071"], - temperature=100, - working_directory=os.path.join(os.path.dirname(__file__), "example_results", "integrated_transformer_optimization") -) - -# task = 'simulation_reluctance' -task = 'load_reluctance_and_filter' -# task = 'load_reluctance_filter_and_simulate_fem' -# task = 'plot_and_filter_fem_simulations_results' -# task = "single_fem_simulation_from_reluctance_result" -# task = 'load_fem_simulation_results_and_perform_thermal_simulations' -# task = 'load_and_filter_thermal_simulations' -# task = 'plot_thermal_fem_simulation_results' - - -if task == 'simulation_reluctance': - - # reluctance model brute force calculation - valid_reluctance_model_designs = fmt.IntegratedTransformerOptimization.ReluctanceModel.BruteForce.brute_force_calculation(dab_transformer_config) - - # save all calculated and valid reluctance model calculations - fmt.IntegratedTransformerOptimization.ReluctanceModel.save_unfiltered_results(config_file=dab_transformer_config, - result_file_list=valid_reluctance_model_designs) - -elif task == 'load_reluctance_and_filter': - # load all calculated and valid reluctance model calculations - valid_reluctance_model_designs = fmt.IntegratedTransformerOptimization.ReluctanceModel.load_unfiltered_results(dab_transformer_config.working_directory) - print(f"{len(valid_reluctance_model_designs)=}") - - # filter air gaps - filtered_air_gaps_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.filter_min_air_gap_length(valid_reluctance_model_designs) - print(f"{len(filtered_air_gaps_dto_list)=}") - - # filter for Pareto front - pareto_reluctance_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.filter_loss_list(filtered_air_gaps_dto_list, factor_min_dc_losses=0.5) - print(f"{len(pareto_reluctance_dto_list)=}") - - # save results - fmt.IntegratedTransformerOptimization.ReluctanceModel.save_dto_list(pareto_reluctance_dto_list, os.path.join(dab_transformer_config.working_directory, - '01_reluctance_model_results_filtered')) - - # plot unfiltered and filtered Pareto planes - fmt.IntegratedTransformerOptimization.plot(valid_reluctance_model_designs) - fmt.IntegratedTransformerOptimization.plot(pareto_reluctance_dto_list) - - -elif task == 'load_reluctance_filter_and_simulate_fem': - - # load filtered reluctance models - pareto_reluctance_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.load_filtered_results(dab_transformer_config.working_directory) - print(f"{len(pareto_reluctance_dto_list)=}") - - # start FEM simulation - fmt.IntegratedTransformerOptimization.FemSimulation.simulate(config_dto=dab_transformer_config, - simulation_dto_list=pareto_reluctance_dto_list) - -elif task == 'plot_and_filter_fem_simulations_results': - - # load unfiltered results - unfiltered_fem_results = fmt.IntegratedTransformerOptimization.FemSimulation.load_unfiltered_results(dab_transformer_config.working_directory) - print(f"{len(unfiltered_fem_results)=}") - - # plot unfiltered results - fmt.IntegratedTransformerOptimization.FemSimulation.plot(unfiltered_fem_results) - - # filter results - filtered_fem_results = fmt.IntegratedTransformerOptimization.FemSimulation.filter_loss_list(unfiltered_fem_results, factor_min_dc_losses=0.1) - print(f"{len(filtered_fem_results)=}") - - # save filtered results - # fmt.IntegratedTransformerOptimization.FemSimulation.save_filtered_results(filtered_fem_results, dab_transformer_config.working_directory) - - # plot filtered results - fmt.IntegratedTransformerOptimization.FemSimulation.plot(filtered_fem_results) - -elif task == 'load_fem_simulation_results_and_perform_thermal_simulations': - - # load filtered FEM simulation results - filtered_fem_simulation_results = fmt.IntegratedTransformerOptimization.FemSimulation.load_filtered_results(dab_transformer_config.working_directory) - - # start thermal FEM simulation - fmt.IntegratedTransformerOptimization.ThermalSimulation.simulation(config_dto=dab_transformer_config, result_log_dict_list=filtered_fem_simulation_results) - -elif task == 'plot_thermal_fem_simulation_results': - # load thermal simulation results - unfiltered_thermal_fem_results = fmt.IntegratedTransformerOptimization.ThermalSimulation.load_unfiltered_simulations( - dab_transformer_config.working_directory) - print(f"{len(unfiltered_thermal_fem_results)=}") - - # filter thermal FEM simulations - filtered_thermal_fem_results = fmt.IntegratedTransformerOptimization.ThermalSimulation.filter_max_temperature(unfiltered_thermal_fem_results, 125, 125) - print(f"{len(filtered_thermal_fem_results)=}") - - # load filtered FEM simulation results - unfiltered_fem_simulation_results = fmt.IntegratedTransformerOptimization.FemSimulation.load_unfiltered_results( - dab_transformer_config.working_directory) - - # find common cases of loss vs. volume and temperature vs. volume - # du to the plot will contain the loss vs. volume area, so non-working cases of temperature vs. volume need - # to be removed in loss vs. volume plane. - valid_thermal_simulations = fmt.IntegratedTransformerOptimization.ThermalSimulation.find_common_cases( - unfiltered_fem_simulation_results, filtered_thermal_fem_results) - print(f"{len(valid_thermal_simulations)=}") - - # plot all thermal simulation results, and plot - fmt.IntegratedTransformerOptimization.FemSimulation.plot(unfiltered_thermal_fem_results) - fmt.IntegratedTransformerOptimization.FemSimulation.plot(valid_thermal_simulations) diff --git a/femmt/examples/ito_optuna_example.py b/femmt/examples/ito_optuna_example.py index 3b383fca..6db4b9cf 100644 --- a/femmt/examples/ito_optuna_example.py +++ b/femmt/examples/ito_optuna_example.py @@ -21,58 +21,92 @@ i_2 = [[0.0, 3.265248131976911e-07, 2.5e-06, 2.8265248131976912e-06, 5e-06], [-0.9196195846583147, -19.598444313231134, 0.9196195846583122, 19.59844431323113, -0.9196195846583147]] +ito_insulations = fmt.ItoInsulation( + # insulation for top core window + iso_window_top_core_top=1e-3, + iso_window_top_core_bot=1e-3, + iso_window_top_core_left=1e-3, + iso_window_top_core_right=1e-3, + # insulation for bottom core window + iso_window_bot_core_top=1e-3, + iso_window_bot_core_bot=1e-3, + iso_window_bot_core_left=1e-3, + iso_window_bot_core_right=1e-3, + # winding-to-winding insulation + iso_primary_to_primary=10e-6, + iso_secondary_to_secondary=10e-6, + iso_primary_to_secondary=10e-6, +) + +material_data_sources = fmt.IntegratedTransformerMaterialDataSources( + permeability_datasource=fmt.MaterialDataSource.Measurement, + permeability_datatype=fmt.MeasurementDataType.ComplexPermeability, + permeability_measurement_setup=fmt.MeasurementSetup.MagNet, + permittivity_datasource=fmt.MaterialDataSource.ManufacturerDatasheet, + permittivity_datatype=fmt.MeasurementDataType.ComplexPermittivity, + permittivity_measurement_setup=fmt.MeasurementSetup.LEA_MTB_small_signal +) + dab_transformer_config = fmt.ItoSingleInputConfig( - l_s_target=85e-6, + integrated_transformer_study_name="2025-02-28", + integrated_transformer_optimization_directory=os.path.join(os.path.dirname(__file__), "example_results", "optuna_integrated_transformer_optimization"), + + # target parameters + l_s12_target=85e-6, l_h_target=600e-6, n_target=2.9, + + # fix parameters time_current_1_vec=np.array(i_1), time_current_2_vec=np.array(i_2), - material_list=["N95"], - core_inner_diameter_min_max_list=[pq3230["core_inner_diameter"], pq5050["core_inner_diameter"]], - window_w_min_max_list=[pq3230["window_w"], pq5050["window_w"]], - window_h_top_min_max_list=[5 / 6 * pq3230["window_h"], 5 / 6 * pq5050["window_h"]], + insulations=ito_insulations, + + # optimization parameters + material_list=["3C95"], + core_name_list=["PQ 50/50", "PQ 40/40"], + core_inner_diameter_min_max_list=None, + window_w_min_max_list=None, + window_h_top_min_max_list=None, window_h_bot_min_max_list=[1 / 6 * pq3230["window_h"], 1 / 6 * pq5050["window_h"]], + + n_1_top_min_max_list=[1, 30], + n_1_bot_min_max_list=[1, 30], + n_2_top_min_max_list=[1, 30], + n_2_bot_min_max_list=[1, 30], factor_max_flux_density=1, - primary_litz_wire_list=["1.4x200x0.071"], - secondary_litz_wire_list=["1.4x200x0.071"], + litz_wire_list_1=["1.1x60x0.1"], + litz_wire_list_2=["1.1x60x0.1"], temperature=100, - working_directory=os.path.join(os.path.dirname(__file__), "example_results", "optuna_integrated_transformer_optimization") + + material_data_sources=material_data_sources, + ) -task = 'start_study' +# task = 'start_proceed_study' # task = 'filter_reluctance_model' # task = 'fem_simulation_from_filtered_reluctance_model_results' -# task = 'plot_study_results' - -study_name = "workflow_2023-04-15" +task = 'plot_study_results' if __name__ == '__main__': time_start = datetime.datetime.now() - if task == 'start_study': - fmt.IntegratedTransformerOptimization.ReluctanceModel.NSGAII.start_study(study_name, dab_transformer_config, 1000, storage='sqlite') + if task == 'start_proceed_study': + fmt.IntegratedTransformerOptimization.ReluctanceModel.start_proceed_study(dab_transformer_config, 10000, storage='sqlite') elif task == 'filter_reluctance_model': - # load trials from reluctance model - reluctance_result_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.NSGAII.load_study_to_dto(study_name, dab_transformer_config) - print(f"{len(reluctance_result_list)=}") + # load trials from reluctance model to a pandas dataframe (df) + reluctance_result_df = fmt.IntegratedTransformerOptimization.ReluctanceModel.study_to_df(dab_transformer_config) # filter air gaps - filtered_air_gaps_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.filter_min_air_gap_length(reluctance_result_list) - print(f"{len(filtered_air_gaps_dto_list)=}") + # filtered_air_gaps_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.filter_min_air_gap_length(reluctance_result_df) # filter for Pareto front - pareto_reluctance_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.filter_loss_list( - filtered_air_gaps_dto_list, factor_min_dc_losses=0.5) - print(f"{len(pareto_reluctance_dto_list)=}") - - fmt.IntegratedTransformerOptimization.plot(reluctance_result_list) - fmt.IntegratedTransformerOptimization.plot(pareto_reluctance_dto_list) + pareto_reluctance_dto_list = fmt.IntegratedTransformerOptimization.ReluctanceModel.filter_loss_list_df( + reluctance_result_df, factor_min_dc_losses=0.5) - # save results - fmt.IntegratedTransformerOptimization.ReluctanceModel.save_dto_list(pareto_reluctance_dto_list, os.path.join(dab_transformer_config.working_directory, - '01_reluctance_model_results_filtered')) + fmt.IntegratedTransformerOptimization.ReluctanceModel.df_plot_pareto_front( + reluctance_result_df, pareto_reluctance_dto_list, label_list=["all", "filtered"], interactive=False) elif task == 'fem_simulation_from_filtered_reluctance_model_results': # load filtered reluctance models @@ -84,7 +118,7 @@ simulation_dto_list=pareto_reluctance_dto_list) elif task == 'plot_study_results': - fmt.IntegratedTransformerOptimization.ReluctanceModel.NSGAII.show_study_results(study_name, dab_transformer_config) + fmt.IntegratedTransformerOptimization.ReluctanceModel.show_study_results(dab_transformer_config) time_stop = datetime.datetime.now() diff --git a/femmt/functions.py b/femmt/functions.py index da3f8451..b06de25b 100644 --- a/femmt/functions.py +++ b/femmt/functions.py @@ -28,7 +28,7 @@ "orange": (230, 97, 0), "purple": (129, 61, 156), "brown": (134, 94, 60), - "grey": (119, 118, 123), + "gray": (119, 118, 123), "yellow": (245, 194, 17), "black": (0, 0, 0), "white": (255, 255, 255) @@ -37,11 +37,11 @@ colors_geometry_femmt_default = { "core": "black", "air_gap": "yellow", - "winding": ["orange", "brown", "yellow", "green", "red", "black", "grey", "blue", "orange", "purple", "grey", + "winding": ["orange", "brown", "yellow", "green", "red", "black", "gray", "blue", "orange", "purple", "gray", "blue", "orange", "purple"], "insulation": "blue", - "potting_inner": "grey", - "potting_outer": "grey", + "potting_inner": "gray", + "potting_outer": "gray", } colors_ba_jonas = {"blue": (28, 113, 216), @@ -50,30 +50,30 @@ "orange": (230, 97, 0), "purple": (129, 61, 156), "brown": (134, 94, 60), - "grey": (193, 193, 193), + "gray": (193, 193, 193), "yellow": (255, 171, 6), "black": (58, 58, 58), "white": (255, 255, 255), - "grey_dark": (109, 109, 109), - "grey_dark_dark": (50, 50, 50) + "gray_dark": (109, 109, 109), + "gray_dark_dark": (50, 50, 50) } colors_geometry_ba_jonas = { "core": "black", "air_gap": "yellow", "winding": ["green", "red", "yellow"], - "insulation": "grey_dark", - "potting_inner": "grey", - "potting_outer": "grey_dark_dark", + "insulation": "gray_dark", + "potting_inner": "gray", + "potting_outer": "gray_dark_dark", } colors_geometry_draw_only_lines = { - "core": "grey_dark", - "air_gap": "grey_dark", + "core": "gray_dark", + "air_gap": "gray_dark", "winding": ["green", "green", "green"], - "insulation": "grey_dark", - "potting_inner": "grey_dark", - "potting_outer": "grey_dark", + "insulation": "gray_dark", + "potting_inner": "gray_dark", + "potting_outer": "gray_dark", } @@ -390,7 +390,7 @@ def litz_database() -> Dict: def wire_material_database() -> Dict[str, WireMaterial]: """ - Return wire materials e.g. copper, aluminium in a dictionary. + Return wire materials e.g. copper, aluminum in a dictionary. :return: Dict with materials and conductivity :rtype: Dict @@ -406,8 +406,8 @@ def wire_material_database() -> Dict[str, WireMaterial]: volumetric_mass_density=8920, ) - wire_material["Aluminium"] = WireMaterial( - name="aluminium", + wire_material["Aluminum"] = WireMaterial( + name="aluminum", sigma=3.7e7, temperature=25, temperature_coefficient=3.9e-3, @@ -786,7 +786,7 @@ def compare_fft_list(input_data_list: list, sample_factor: int = 1000, mode: str :type mode: str :param f0: fundamental frequency. Needs to be set in 'rad'- or 'deg'-mode :type f0: float - :param sample_factor: samle factor, defaults to 1000 + :param sample_factor: sample factor, defaults to 1000 :type sample_factor: int """ out = [] @@ -985,7 +985,7 @@ def calculate_cylinder_volume(cylinder_diameter: float, cylinder_height: float): def create_physical_group(dim: int, entities: int, name: str): """ - Greate a physical group, what is used inside ONELAB. + Create a physical group, what is used inside ONELAB. :param dim: dim inside onelab :type dim: int @@ -1243,11 +1243,11 @@ def find_result_log_file(result_log_folder: str, keyword_list: list, value_min_m """ Find a result log-file in a folder with many result-log files. - Check a dictornary keyword list for matching a certain value (equel, greater equal, smaller equal). + Check a dictionary keyword list for matching a certain value (equal, greater equal, smaller equal). :param result_log_folder: filepath to result-log folder :type result_log_folder: str - :param keyword_list: list with hirarchical keywords for dictionary structure, e.g. ["simulation_settings", "core", "core_inner_diameter"] + :param keyword_list: list with hierarchical keywords for dictionary structure, e.g. ["simulation_settings", "core", "core_inner_diameter"] :type keyword_list: list :param value_min_max: value to check for :type value_min_max: list @@ -1578,7 +1578,7 @@ def visualize_mean_coupling_factors(mean_coupling_factors: List, silent: bool): def visualize_mean_mutual_inductances(inductance_matrix: np.array, silent: bool): """ - Print the mean mutal inductances to the terminal (or file-) output. + Print the mean mutual inductances to the terminal (or file-) output. :param inductance_matrix: inductance matrix :type inductance_matrix: np.array @@ -1602,7 +1602,7 @@ def visualize_mean_mutual_inductances(inductance_matrix: np.array, silent: bool) def visualize_mutual_inductances(self_inductances: List, coupling_factors: List, silent: bool): """ - Print the mutal inductances to the terminal (or file-) output. + Print the mutual inductances to the terminal (or file-) output. :param self_inductances: Matrix with self inductances :type self_inductances: List @@ -1687,7 +1687,7 @@ def calculate_quadrature_integral(time_steps: List[float], data: List[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.. + 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] diff --git a/femmt/functions_drawing.py b/femmt/functions_drawing.py index 1a3ecd2a..3561f82d 100644 --- a/femmt/functions_drawing.py +++ b/femmt/functions_drawing.py @@ -195,7 +195,7 @@ def group_center_tapped(primary_number_of_rows: int, secondary_number_of_rows: i def get_height_of_group(group: CenterTappedGroup) -> float: """ - Return the total height of thr conductors and insulation. + Return the total height of the conductors and insulation. :param group: center tapped group :type group: CenterTappedGroup diff --git a/femmt/functions_model.py b/femmt/functions_model.py index 02467a8c..2327d1d9 100644 --- a/femmt/functions_model.py +++ b/femmt/functions_model.py @@ -11,7 +11,7 @@ def define_center_tapped_insulation(primary_to_primary: float, secondary_to_seco :type primary_to_primary: float :param secondary_to_secondary: Secondary winding to secondary winding insulation in m :type secondary_to_secondary: float - :param primary_to_secondary: Primary winding to secondary winding insulaiton in m + :param primary_to_secondary: Primary winding to secondary winding insulation in m :type primary_to_secondary: float :return: """ diff --git a/femmt/functions_reluctance.py b/femmt/functions_reluctance.py index 3a3f06d7..8e1b974e 100644 --- a/femmt/functions_reluctance.py +++ b/femmt/functions_reluctance.py @@ -439,7 +439,7 @@ 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]]): +def time_vec_current_vec_from_time_current_vec(time_current_vec: List[List[float]] | np.array): """ Split a time-current vector into time and current vector. @@ -1023,7 +1023,7 @@ def resistance_solid_wire(core_inner_diameter: float, window_w: float, turns_cou :type turns_count: int :param conductor_radius: conductor radius :type conductor_radius: float - :param material: Material, e.g. "Copper" or "Aluminium" + :param material: Material, e.g. "Copper" or "Aluminum" :type material: str :param temperature: temperature in °C :type temperature: float @@ -1061,7 +1061,7 @@ def resistance_litz_wire(core_inner_diameter: float, window_w: float, window_h: :type window_w: float :param turns_count: number of turns :type turns_count: int - :param material: Material, e.g. "Copper" or "Aluminium" + :param material: Material, e.g. "Copper" or "Aluminum" :type material: str :param temperature: temperature in °C :type temperature: float @@ -1115,8 +1115,30 @@ def resistance_litz_wire(core_inner_diameter: float, window_w: float, window_h: # get the total turn length total_turn_length = np.sum(np.multiply(windings_per_column, turn_length_per_column)) - if scheme == "horizontal_first": - raise NotImplementedError("'horizontal_first'-scheme not implemented yet.") + elif scheme == "horizontal_first": + number_of_columns = (window_w - iso_core_left - iso_core_right + iso_primary_to_primary) / (litz_wire_diameter + iso_primary_to_primary) + length_row_per_turn = np.zeros(int(number_of_columns)) + + # figure out the turn length per row + r_inner = np.array([core_inner_diameter / 2 + iso_core_left]) + middle_radius_per_column = np.array([]) + for count, _ in enumerate(length_row_per_turn): + middle_radius_per_column = np.append(middle_radius_per_column, r_inner + count * iso_primary_to_primary + (count + 0.5) * litz_wire_diameter) + turn_length_per_column = 2 * middle_radius_per_column * np.pi # diameter * pi + + # figure out the windings per row + number_of_rows = np.ceil(turns_count / possible_number_turns_per_row) + windings_per_row = possible_number_turns_per_row * np.ones(int(number_of_rows)) + last_row_turns = np.mod(turns_count, possible_number_turns_per_row) + windings_per_row[-1] = last_row_turns + + # get the total turn length + total_turn_length = 0 + for windings_in_row in windings_per_row: + print(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'.") sigma_copper = ff.conductivity_temperature(material, temperature) # return R = rho * l / A @@ -1160,7 +1182,7 @@ def calc_skin_depth(frequency: float, material_name: str = "Copper", temperature :param frequency: Frequency in Hz :type frequency: float - :param material_name: material name, e.g. 'Copper' or 'Aluminium' + :param material_name: material name, e.g. 'Copper' or 'Aluminum' :type material_name: str :param temperature: Temperature in °C :type temperature: float @@ -1189,7 +1211,7 @@ def calc_proximity_factor(litz_wire_name: str, number_turns: int, window_h: floa :type iso_core_bot: float :param frequency: Frequency in Hz :type frequency: float - :param litz_wire_material_name: material name, e.g. 'Copper' or 'Aluminium' + :param litz_wire_material_name: material name, e.g. 'Copper' or 'Aluminum' :type litz_wire_material_name: str :param temperature: Temperature in °C :type temperature: float @@ -1218,7 +1240,7 @@ def calc_proximity_factor_air_gap(litz_wire_name: str, number_turns: int, r_1: f :type winding_area: float :param frequency: Frequency in Hz :type frequency: float - :param litz_wire_material_name: material name, e.g. 'Copper' or 'Aluminium' + :param litz_wire_material_name: material name, e.g. 'Copper' or 'Aluminum' :type litz_wire_material_name: str :param temperature: Temperature in °C :type temperature: float diff --git a/femmt/mesh.py b/femmt/mesh.py index c695383c..c368d23c 100644 --- a/femmt/mesh.py +++ b/femmt/mesh.py @@ -1142,7 +1142,7 @@ def generate_hybrid_mesh(self, color_scheme: Dict = ff.colors_femmt_default, col visualize_before: bool = False, save_png: bool = True, refine=0, alternative_error=0) -> None: """ - Generate the hybird mesh. + Generate the hybrid mesh. - interaction with gmsh - mesh generation @@ -1841,7 +1841,7 @@ def rasterize_winding_window(left_bound, right_bound, bot_bound, top_bound): valid = False break else: - print(f"Winding window rasterization skipped becuase ConductorType {winding.conductor_type.name} is not supported.") + print(f"Winding window rasterization skipped because ConductorType {winding.conductor_type.name} is not supported.") return if not valid: diff --git a/femmt/model.py b/femmt/model.py index 9ae74439..a17eb686 100644 --- a/femmt/model.py +++ b/femmt/model.py @@ -185,7 +185,7 @@ class Core: """ Creates the core base for the model. - # TODO More documentation and get rid of double initializations + # TODO More documentation and get rid of double initialization frequency = 0: mu_r_abs only used if non_linear == False frequency > 0: mu_r_abs is used """ @@ -1347,7 +1347,7 @@ def flexible_split(self, split_distance: float = 0, def split_with_stack(self, stack: ConductorStack): """ - Split the winding window according to a ConductorStack dataclass. + Split the winding window according to a ConductorStack data class. :param stack: :return: diff --git a/femmt/optimization/__init__.py b/femmt/optimization/__init__.py index cba0cf0a..ae52cabd 100644 --- a/femmt/optimization/__init__.py +++ b/femmt/optimization/__init__.py @@ -1,4 +1,4 @@ -"""Init file for the optimization section.""" +"""Initialize file for the optimization section.""" from femmt.optimization.ito import * from femmt.optimization.functions_optimization import * from femmt.optimization.ito_dtos import * diff --git a/femmt/optimization/functions_optimization.py b/femmt/optimization/functions_optimization.py index f011371e..2a48b7da 100644 --- a/femmt/optimization/functions_optimization.py +++ b/femmt/optimization/functions_optimization.py @@ -213,11 +213,11 @@ def pareto_front_from_df(df: pd.DataFrame) -> pd.DataFrame: :return: Pandas dataframe with pareto efficient points :rtype: pd.DataFrame """ - x_vec = df["values_0"][~np.isnan(df["values_0"])] - y_vec = df["values_1"][~np.isnan(df["values_0"])] + x_vec = df["values_0"][~pd.isnull(df["values_0"])] + y_vec = df["values_1"][~pd.isnull(df["values_0"])] numpy_zip = np.column_stack((x_vec, y_vec)) pareto_tuple_mask_vec = is_pareto_efficient(numpy_zip) - pareto_df = df[~np.isnan(df['values_0'])][pareto_tuple_mask_vec] + pareto_df = df[~pd.isnull(df['values_0'])][pareto_tuple_mask_vec] return pareto_df diff --git a/femmt/optimization/io.py b/femmt/optimization/io.py index afac8c2e..477a9cea 100644 --- a/femmt/optimization/io.py +++ b/femmt/optimization/io.py @@ -63,7 +63,7 @@ def filter_df(df: pd.DataFrame, x: str = "values_0", y: str = "values_1", factor x_pareto_vec = sorted_vector[0] y_pareto_vec = sorted_vector[1] - total_losses_list = df[y][~np.isnan(df[y])].to_numpy() + total_losses_list = df[y][~pd.isnull(df[y])].to_numpy() min_total_dc_losses = total_losses_list[np.argmin(total_losses_list)] loss_offset = factor_min_dc_losses * min_total_dc_losses @@ -221,8 +221,8 @@ 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: - logging.warning("bot air gap: No fitting air gap length") + except ValueError as e: + logging.info("bot air gap: No fitting air gap length") return float('nan'), float('nan') trial.set_user_attr('p_winding', reluctance_output.p_winding) @@ -509,7 +509,7 @@ def df_plot_pareto_front(*dataframes: list[pd.DataFrame], label_list: list[str], :type interactive: bool """ if color_list is None: - color_list = ['red', 'blue', 'green', 'grey'] + color_list = ['red', 'blue', 'green', 'gray'] for count, df in enumerate(dataframes): # color_list was before list(ff.colors_femmt_default.keys()) df['color_r'], df['color_g'], df['color_b'] = ff.colors_femmt_default[color_list[count]] @@ -604,7 +604,7 @@ def fem_simulations_from_reluctance_df(reluctance_df: pd.DataFrame, config: Indu :type reluctance_df: pandas.DataFrame :param config: Configuration for the optimization of the transformer :type config: InductorOptimizationDTO - :param show_visual_outputs: Ture to show visual outputs like the geometry + :param show_visual_outputs: True to show visual outputs like the geometry :type show_visual_outputs: bool :param process_number: Process number for parallel simulations on multiple cpu cores :type process_number: int @@ -726,7 +726,7 @@ def fem_vs_reluctance_pareto(df: pd.DataFrame) -> None: fig, ax = plt.subplots() legend_list = [] plt.legend(handles=legend_list) - plt.scatter(df["values_0"], df["values_1"], s=10, label='Relucatance Model') # c=color_array + plt.scatter(df["values_0"], df["values_1"], s=10, label='Reluctance Model') # c=color_array df["fem_total_loss"] = df["fem_core"] + df["fem_p_loss_winding"] plt.scatter(df["values_0"], df["fem_total_loss"], s=10, label='FEM simulation') # c=color_array plt.scatter(df["values_0"], df["combined_losses"], s=10, label="combined_losses") @@ -862,7 +862,7 @@ def full_simulation(df_geometry: pd.DataFrame, current_waveform: List, inductor_ :type inductor_config_filepath: str :param process_number: process number to run the simulation on :type process_number: int - :param print_derivations: True to print derivation from FEM simulaton to reluctance model + :param print_derivations: True to print derivation from FEM simulation to reluctance model :type print_derivations: bool :return: volume, loss :rtype: tuple diff --git a/femmt/optimization/ito.py b/femmt/optimization/ito.py index 096d003d..9aa94297 100644 --- a/femmt/optimization/ito.py +++ b/femmt/optimization/ito.py @@ -2,22 +2,25 @@ # Python libraries import os import json -import itertools -import shutil -import dataclasses -from typing import List, Dict, Tuple +from typing import List, Dict, Optional +import datetime +import pickle +import logging # 3rd party library import import materialdatabase as mdb from scipy import optimize import optuna +import pandas as pd +from matplotlib import pyplot as plt +import matplotlib.patches as mpatches +import magnethub as mh # femmt import import femmt.functions as ff import femmt.functions_reluctance as fr import femmt.optimization.functions_optimization as fo from femmt.optimization.ito_dtos import * -import femmt.optimization.optuna_femmt_parser as op import femmt.optimization.ito_functions as itof import femmt @@ -78,10 +81,10 @@ def result_file_dict_to_dto(result_file_dict: Dict) -> ItoSingleResultFile: flux_density_stray_max=result_file_dict["flux_density_stray_max"], p_hyst=result_file_dict["p_hyst"], core_2daxi_total_volume=result_file_dict["core_2daxi_total_volume"], - primary_litz_wire=result_file_dict["primary_litz_wire"], - secondary_litz_wire=result_file_dict["secondary_litz_wire"], - primary_litz_wire_loss=result_file_dict["primary_litz_wire_loss"], - secondary_litz_wire_loss=result_file_dict["secondary_litz_wire_loss"], + litz_wire_name_1=result_file_dict["primary_litz_wire"], + litz_wire_name_2=result_file_dict["secondary_litz_wire"], + litz_wire_loss_1=result_file_dict["primary_litz_wire_loss"], + litz_wire_loss_2=result_file_dict["secondary_litz_wire_loss"], total_loss=result_file_dict["total_loss"] ) return result_file_dto @@ -131,8 +134,7 @@ def is_pareto_efficient_dumb(costs: np.array) -> np.array: @staticmethod def calculate_fix_parameters(config: ItoSingleInputConfig) -> ItoTargetAndFixedParameters: - """ - Calculate fix parameters what can be derived from the input configuration. + """Calculate fix parameters what can be derived from the input configuration. return values are: @@ -162,32 +164,63 @@ def calculate_fix_parameters(config: ItoSingleInputConfig) -> ItoTargetAndFixedP i_rms_1 = fr.i_rms(config.time_current_1_vec) i_rms_2 = fr.i_rms(config.time_current_2_vec) + i_peak_1, i_peak_2 = fr.max_value_from_value_vec(current_extracted_1_vec, current_extracted_2_vec) + phi_deg_1, phi_deg_2 = fr.phases_deg_from_time_current(time_extracted, current_extracted_1_vec, + current_extracted_2_vec) + + # phi_deg_2 = phi_deg_2 - 180 + # target inductances - target_inductance_matrix = fr.calculate_inductance_matrix_from_ls_lh_n(config.l_s_target, config.l_h_target, + target_inductance_matrix = fr.calculate_inductance_matrix_from_ls_lh_n(config.l_s12_target, + config.l_h_target, config.n_target) + (fft_frequencies_1, fft_amplitudes_1, fft_phases_1) = ff.fft( + period_vector_t_i=config.time_current_1_vec, sample_factor=1000, plot='no', mode='time', filter_type='factor', filter_value_factor=0.03) + + (fft_frequencies_2, fft_amplitudes_2, fft_phases_2) = ff.fft( + period_vector_t_i=config.time_current_2_vec, sample_factor=1000, plot='no', mode='time', filter_type='factor', filter_value_factor=0.03) + # material properties material_db = mdb.MaterialDatabase(is_silent=True) material_data_list = [] + magnet_model_list = [] for material_name in config.material_list: - material_dto = material_db.material_data_interpolation_to_dto(material_name, fundamental_frequency, config.temperature) + material_dto: mdb.MaterialCurve = material_db.material_data_interpolation_to_dto(material_name, fundamental_frequency, config.temperature) material_data_list.append(material_dto) + # instantiate material-specific model + mdl: mh.loss.LossModel = mh.loss.LossModel(material=material_name, team="paderborn") + magnet_model_list.append(mdl) # set up working directories - working_directories = itof.set_up_folder_structure(config.working_directory) + working_directories = itof.set_up_folder_structure(config.integrated_transformer_optimization_directory) # finalize data to dto target_and_fix_parameters = ItoTargetAndFixedParameters( i_rms_1=i_rms_1, i_rms_2=i_rms_2, + i_peak_1=i_peak_1, + i_peak_2=i_peak_2, + i_phase_deg_1=phi_deg_1, + i_phase_deg_2=phi_deg_2, time_extracted_vec=time_extracted, + magnet_hub_model_list=magnet_model_list, current_extracted_1_vec=current_extracted_1_vec, current_extracted_2_vec=current_extracted_2_vec, material_dto_curve_list=material_data_list, fundamental_frequency=fundamental_frequency, target_inductance_matrix=target_inductance_matrix, - working_directories=working_directories + working_directories=working_directories, + # winding 1 + fft_frequency_list_1=fft_frequencies_1, + fft_amplitude_list_1=fft_amplitudes_1, + fft_phases_list_1=fft_phases_1, + + # winding 2 + fft_frequency_list_2=fft_frequencies_2, + fft_amplitude_list_2=fft_amplitudes_2, + fft_phases_list_2=fft_phases_2 ) return target_and_fix_parameters @@ -195,890 +228,520 @@ def calculate_fix_parameters(config: ItoSingleInputConfig) -> ItoTargetAndFixedP class ReluctanceModel: """Create and calculate the reluctance model for the integrated transformer.""" - class BruteForce: - """Brute force calculation for the integrated transformer.""" - - ############################# - # initial optimization - ############################# - @staticmethod - def brute_force_calculation(config_file: ItoSingleInputConfig) -> List: - """ - Brute force calculation for the integrated transformer. - - :param config_file: configuration file - :type config_file: ItoSingleInputConfig - :return: List of valid designs - :rtype: List - """ - case_number = 0 - - # 0. Empty folder - if os.path.exists(config_file.working_directory): - shutil.rmtree(config_file.working_directory) - - material_db = mdb.MaterialDatabase(is_silent=True) - - sweep_dto = femmt.IntegratedTransformerOptimization.ReluctanceModel.BruteForce.calculate_sweep_tensors(config_file) - - wire_database = ff.wire_material_database() - litz_database = ff.litz_database() - - # 1. Extract fundamental frequency from current vectors - time_extracted, current_extracted_1_vec = fr.time_vec_current_vec_from_time_current_vec( - sweep_dto.time_current_1_vec) - time_extracted, current_extracted_2_vec = fr.time_vec_current_vec_from_time_current_vec( - sweep_dto.time_current_2_vec) - fundamental_frequency = np.around(1 / time_extracted[-1], decimals=0) - print(f"{fundamental_frequency=}") - - i_rms_1 = fr.i_rms(sweep_dto.time_current_1_vec) - i_rms_2 = fr.i_rms(sweep_dto.time_current_2_vec) - - # generate list of all parameter combinations - t2_core_geometry_sweep = np.array(list(itertools.product(sweep_dto.t1_window_w, sweep_dto.t1_window_h_top, - sweep_dto.t1_window_h_bot, - sweep_dto.t1_core_inner_diameter))) - - t2_litz_sweep = np.array( - list(itertools.product(sweep_dto.t1_primary_litz_wire_list, sweep_dto.t1_secondary_litz_wire_list))) - - # report simulation progress - number_of_geometry_simulations = len(t2_core_geometry_sweep) * len(sweep_dto.t1_core_material) - - geometry_simulations_per_percent = int(number_of_geometry_simulations / 99) - simulation_progress_percent = 0 - - valid_design_list = [] - - # initialize parameters staying same form simulation - t2_inductance_matrix = [ - [sweep_dto.l_s_target_value + sweep_dto.l_h_target_value, - sweep_dto.l_h_target_value / sweep_dto.n_target_value], - [sweep_dto.l_h_target_value / sweep_dto.n_target_value, - sweep_dto.l_h_target_value / (sweep_dto.n_target_value ** 2)]] - - geometry_simulation_counter = 0 - for _, material_name in enumerate(sweep_dto.t1_core_material): - """ - outer core material loop loads material properties from material database - * mu_r_abs - * saturation_flux_density and calculates the dimensioning_flux_density from it - * material vectors for mu_r_real and mu_r_imag depending on flux_density - - """ - mu_r_abs = material_db.get_material_attribute(material_name=material_name, - attribute="initial_permeability") - - saturation_flux_density = material_db.get_saturation_flux_density(material_name=material_name) - dimensioning_max_flux_density = saturation_flux_density * sweep_dto.factor_max_flux_density - - # get material data from material database. - material_dto = material_db.material_data_interpolation_to_dto(material_name, fundamental_frequency, config_file.temperature) - - for count_geometry, t1d_core_geometry_material in enumerate(t2_core_geometry_sweep): - - window_w = t1d_core_geometry_material[0] - window_h_top = t1d_core_geometry_material[1] - window_h_bot = t1d_core_geometry_material[2] - core_inner_diameter = t1d_core_geometry_material[3] - - # report about simulation progress - # if geometry_simulation_counter == geometry_simulations_per_percent * simulation_progress_percent: - # simulation_progress_percent += 1 - # print(f"{simulation_progress_percent} simulation_progress_percent") - # geometry_simulation_counter += 1 - simulation_progress_percent = count_geometry / number_of_geometry_simulations * 100 - print(f"{simulation_progress_percent=} %") - - # print(geometry_simulation_counter) - - for primary_litz_wire, secondary_litz_wire in t2_litz_sweep: - primary_litz = litz_database[primary_litz_wire] - secondary_litz = litz_database[secondary_litz_wire] - - # cross-section comparison is according to a square for round wire. - # this approximation is more realistic - # insulation - insulation_distance = 1e-3 - insulation_cross_section_top = 2 * insulation_distance * (window_w + window_h_top) - insulation_cross_section_bot = 2 * insulation_distance * (window_w + window_h_bot) - - total_available_window_cross_section_top = window_h_top * window_w - insulation_cross_section_top - - ######################################################### - # set dynamic wire count parameters as optimization parameters - ######################################################### - # set the winding search space dynamic - # https://optuna.readthedocs.io/en/stable/faq.html#what-happens-when-i-dynamically-alter-a-search-space - - # n_p_top suggestion - n_p_top_max = total_available_window_cross_section_top / (2 * primary_litz["conductor_radii"]) ** 2 - t1_n_p_top_max = np.arange(0, n_p_top_max + 1) - - for _, n_p_top in enumerate(t1_n_p_top_max): - winding_cross_section_n_p_top = n_p_top * (2 * primary_litz["conductor_radii"]) ** 2 - - winding_cross_section_n_p_top_max = n_p_top * (2 * primary_litz["conductor_radii"]) ** 2 - n_s_top_max = int((total_available_window_cross_section_top - winding_cross_section_n_p_top_max) / ( - 2 * secondary_litz["conductor_radii"]) ** 2) - t1_n_s_top_max = np.arange(0, n_s_top_max + 1) - - for _, n_s_top in enumerate(t1_n_s_top_max): - - total_available_window_cross_section_bot = window_h_bot * window_w - insulation_cross_section_bot - - # n_p_bot suggestion - n_p_bot_max = total_available_window_cross_section_bot / (2 * primary_litz["conductor_radii"]) ** 2 - t1_n_p_bot = np.arange(0, n_p_bot_max + 1) - - for _, n_p_bot in enumerate(t1_n_p_bot): - winding_cross_section_n_p_bot = n_p_bot * (2 * primary_litz["conductor_radii"]) ** 2 - - # n_s_bot suggestion - winding_cross_section_n_p_bot_max = n_p_bot * (2 * primary_litz["conductor_radii"]) ** 2 - n_s_bot_max = int((total_available_window_cross_section_bot - winding_cross_section_n_p_bot_max) / ( - 2 * secondary_litz["conductor_radii"]) ** 2) - t1_n_s_bot = np.arange(0, n_s_bot_max + 1) - - for _, n_s_bot in enumerate(t1_n_s_bot): - - core_top_bot_height = core_inner_diameter / 4 - core_cross_section = (core_inner_diameter / 2) ** 2 * np.pi - - # generate winding matrix - # note that the t11 winding-matrix will be reshaped later! - t2_winding_matrix = [[n_p_top, n_s_top], [n_p_bot, n_s_bot]] - - # matrix reshaping - t2_winding_matrix_transpose = np.transpose(t2_winding_matrix, (1, 0)) - - t2_reluctance_matrix = femmt.IntegratedTransformerOptimization.ReluctanceModel.BruteForce.\ - t2_calculate_reluctance_matrix(t2_inductance_matrix, t2_winding_matrix, t2_winding_matrix_transpose) - - if np.linalg.det(t2_reluctance_matrix) != 0 and np.linalg.det( - np.transpose(t2_winding_matrix)) != 0 and np.linalg.det(t2_inductance_matrix) != 0: - # calculate the flux - flux_top_vec, flux_bot_vec, flux_stray_vec = fr.flux_vec_from_current_vec( - current_extracted_1_vec, - current_extracted_2_vec, - t2_winding_matrix, - t2_inductance_matrix) - - # calculate maximum values - flux_top_max, flux_bot_max, flux_stray_max = fr.max_value_from_value_vec( - flux_top_vec, flux_bot_vec, - flux_stray_vec) - - flux_density_top_max = flux_top_max / core_cross_section - flux_density_bot_max = flux_bot_max / core_cross_section - flux_density_middle_max = flux_stray_max / core_cross_section - - if (flux_density_top_max < dimensioning_max_flux_density) and ( - flux_density_bot_max < dimensioning_max_flux_density) and ( - flux_density_middle_max < dimensioning_max_flux_density): - - # calculate target values for r_top and r_bot out of reluctance matrix - r_core_middle_cylinder_radial = fr.r_core_top_bot_radiant( - core_inner_diameter, window_w, mu_r_abs, core_top_bot_height) - - r_middle_target = -t2_reluctance_matrix[0][1] - r_top_target = t2_reluctance_matrix[0][0] - r_middle_target - r_bot_target = t2_reluctance_matrix[1][1] - r_middle_target - - # calculate the core reluctance of top and bottom and middle part - r_core_top_cylinder_inner = fr.r_core_round(core_inner_diameter, - window_h_top, mu_r_abs) - r_core_top = 2 * r_core_top_cylinder_inner + r_core_middle_cylinder_radial - r_air_gap_top_target = r_top_target - r_core_top - - r_core_bot_cylinder_inner = fr.r_core_round(core_inner_diameter, - window_h_bot, mu_r_abs) - r_core_bot = 2 * r_core_bot_cylinder_inner + r_core_middle_cylinder_radial - r_air_gap_bot_target = r_bot_target - r_core_bot - - r_air_gap_middle_target = r_middle_target - r_core_middle_cylinder_radial - - if r_air_gap_top_target > 0 and r_air_gap_bot_target > 0 and r_air_gap_middle_target > 0: - - minimum_air_gap_length = 1e-6 - maximum_air_gap_length = 1e-3 - minimum_sort_out_air_gap_length = 100e-6 - - try: - l_top_air_gap = optimize.brentq( - fr.r_air_gap_round_inf_sct, minimum_air_gap_length, maximum_air_gap_length, - args=(core_inner_diameter, window_h_top, r_air_gap_top_target), full_output=True)[0] - l_bot_air_gap = optimize.brentq(fr.r_air_gap_round_round_sct, minimum_air_gap_length, - maximum_air_gap_length, args=(core_inner_diameter, window_h_bot / 2, - window_h_bot / 2, - r_air_gap_bot_target), - full_output=True)[0] - l_middle_air_gap = optimize.brentq(fr.r_air_gap_tablet_cylinder_sct, - minimum_air_gap_length, - maximum_air_gap_length, args=(core_inner_diameter, - core_inner_diameter / 4, window_w, - r_air_gap_middle_target), - full_output=True)[0] - except ValueError: - break - - if l_top_air_gap > minimum_sort_out_air_gap_length and l_bot_air_gap > minimum_sort_out_air_gap_length \ - and l_middle_air_gap > minimum_sort_out_air_gap_length: - p_hyst_top = fr.hyst_losses_core_half_mu_r_imag(core_inner_diameter, - window_h_top, - window_w, - mu_r_abs, - flux_top_max, - fundamental_frequency, - material_dto.material_flux_density_vec, - material_dto.material_mu_r_imag_vec) - - p_hyst_middle = fr.power_losses_hysteresis_cylinder_radial_direction_mu_r_imag( - flux_stray_max, core_inner_diameter / 4, core_inner_diameter / 2, - core_inner_diameter / 2 + window_w, fundamental_frequency, - mu_r_abs, material_dto.material_flux_density_vec, material_dto.material_mu_r_imag_vec) - - p_hyst_bot = fr.hyst_losses_core_half_mu_r_imag(core_inner_diameter, - window_h_bot, - window_w, - mu_r_abs, - flux_bot_max, - fundamental_frequency, - material_dto.material_flux_density_vec, - material_dto.material_mu_r_imag_vec) - - p_hyst = p_hyst_top + p_hyst_bot + p_hyst_middle - - core_2daxi_total_volume = fr.calculate_core_2daxi_total_volume( - core_inner_diameter, - (window_h_bot + window_h_top + core_inner_diameter / 4), - window_w) - - primary_effective_conductive_cross_section = primary_litz["strands_numbers"] * \ - primary_litz["strand_radii"] ** 2 * np.pi - primary_effective_conductive_radius = np.sqrt( - primary_effective_conductive_cross_section / np.pi) - primary_resistance = fr.resistance_solid_wire( - core_inner_diameter, window_w, - n_p_top + n_p_bot, - primary_effective_conductive_radius, - material='Copper') - primary_dc_loss = primary_resistance * i_rms_1 ** 2 - - secondary_effective_conductive_cross_section = secondary_litz["strands_numbers"] * \ - secondary_litz["strand_radii"] ** 2 * np.pi - secondary_effective_conductive_radius = np.sqrt( - secondary_effective_conductive_cross_section / np.pi) - secondary_resistance = fr.resistance_solid_wire( - core_inner_diameter, window_w, - n_s_top + n_s_bot, - secondary_effective_conductive_radius, - material='Copper') - secondary_dc_loss = secondary_resistance * i_rms_2 ** 2 - - total_loss = p_hyst + primary_dc_loss + secondary_dc_loss - - valid_design_dict = ItoSingleResultFile( - case=case_number, - air_gap_top=l_top_air_gap, - air_gap_bot=l_bot_air_gap, - air_gap_middle=l_middle_air_gap, - n_p_top=n_p_top, - n_p_bot=n_p_bot, - n_s_top=n_s_top, - n_s_bot=n_s_bot, - window_h_top=window_h_top, - window_h_bot=window_h_bot, - window_w=window_w, - core_material=material_name, - core_inner_diameter=core_inner_diameter, - primary_litz_wire=primary_litz_wire, - secondary_litz_wire=secondary_litz_wire, - # results - flux_top_max=flux_top_max, - flux_bot_max=flux_bot_max, - flux_stray_max=flux_stray_max, - flux_density_top_max=flux_density_top_max, - flux_density_bot_max=flux_density_bot_max, - flux_density_stray_max=flux_density_middle_max, - p_hyst=p_hyst, - core_2daxi_total_volume=core_2daxi_total_volume, - primary_litz_wire_loss=primary_dc_loss, - secondary_litz_wire_loss=secondary_dc_loss, - total_loss=total_loss - - ) - - # Add dict to list of valid designs - valid_design_list.append(valid_design_dict) - case_number += 1 - - print(f"Number of valid designs: {len(valid_design_list)}") - return valid_design_list - - @staticmethod - def t2_calculate_reluctance_matrix(t2_inductance_matrix: np.array, t2_winding_matrix: np.array, t2_winding_matrix_transpose: np.array): - """ - Calculate the inductance matrix out of reluctance matrix and winding matrix. - - :param t2_inductance_matrix: matrix of transformer inductance - :type t2_inductance_matrix: np.array - :param t2_winding_matrix: matrix of transformer windings - :type t2_winding_matrix: np.array - :param t2_winding_matrix_transpose: transponsed winding matrix - :type t2_winding_matrix_transpose: np.array - - :return: reluctance matrix - - winding matrix e.g. - N = [ [N_1a, N_2b], [N_1b, N_2b] ] - - inductance matrix e.g. - L = [ [L_11, M], [M, L_22] ] - - returns reluctance matrix e.g. - r = [ [], [] ] - """ - # invert inductance matrix - t2_inductance_matrix_invert = np.linalg.inv(t2_inductance_matrix) - - # Formular: L = N^T * R^-1 * N - # Note: Be careful when trying to multiply the matrices in one single step. Some pre-tests failed. - # The following commented example returns a different result as the code-version. The code-version is - # verified with a 2x2 example. - # So this line is not correct! - # return np.einsum('...ij, ...jj, ...jk -> ...ik', t11_winding_matrix_transpose, t11_reluctance_matrix_invert, - # t11_winding_matrix), t9_valid_design_mask - - # minimal example to understand the operation - # matrix1 = np.array([[1, 2], [3, 4]]) - # matrix2 = np.array([[5, 6], [7, 8]]) - # matrix3 = np.array([[9, 10], [11, 12]]) - # - # # reference code - # normal_multiplication = np.matmul(np.matmul(matrix1, matrix2), matrix3) - # print(f"{normal_multiplication = }") - # - # # This does not macht to the reference code!!! - # einsum_multiplication = np.einsum('...ij, ...jj, ...jk -> ...ik', matrix1, matrix2, matrix3) - # print(f"{einsum_multiplication = }") - # - # # two einsum multiplications: same result as reference code - # einsum_multiplication_part_1 = np.einsum('...ij, ...jh -> ...ih', matrix1, matrix2) - # einsum_multiplication_part_2 = np.einsum('...ij, ...jh -> ...ih', einsum_multiplication_part_1, matrix3) - # print(f"{einsum_multiplication_part_2 = }") - einsum_multiplication_part_1 = np.einsum('...ij, ...jh -> ...ih', t2_winding_matrix, - t2_inductance_matrix_invert) - einsum_multiplication_part_2 = np.einsum('...ij, ...jh -> ...ih', einsum_multiplication_part_1, - t2_winding_matrix_transpose) - - return einsum_multiplication_part_2 - - @staticmethod - def calculate_sweep_tensors(input_parameters_dto: ItoSingleInputConfig) -> SweepTensor: - """ - Calculate the SweepTensor from the integrated-transformer input config file (ItoSingleInputConfig). - - ItoSingleInputConfig: core_inner_diameter = [10e-3, 30e-3, 5] - ->> SweepTensor: t1_core_inner_diameter = [10e-3, 15e-3, 20e-3, 25e-3, 30e-3] - - :param input_parameters_dto: integrated transformer input configuration - :type input_parameters_dto: IntegratedTransformerOptimization - :return: returns the sweep tensor as mentioned in the example - :rtype: SweepTensor - """ - sweep_tensor = SweepTensor( - # tensors: outer core geometry and material - t1_window_h_top=np.linspace(input_parameters_dto.window_h_top_min_max_list[0], - input_parameters_dto.window_h_top_min_max_list[1], - input_parameters_dto.window_h_top_min_max_list[2]), - t1_window_h_bot=np.linspace(input_parameters_dto.window_h_bot_min_max_list[0], - input_parameters_dto.window_h_bot_min_max_list[1], - input_parameters_dto.window_h_bot_min_max_list[2]), - t1_window_w=np.linspace(input_parameters_dto.window_w_min_max_list[0], - input_parameters_dto.window_w_min_max_list[1], - input_parameters_dto.window_w_min_max_list[2]), - t1_core_material=input_parameters_dto.material_list, - t1_core_inner_diameter=np.linspace(input_parameters_dto.core_inner_diameter_min_max_list[0], - input_parameters_dto.core_inner_diameter_min_max_list[1], - input_parameters_dto.core_inner_diameter_min_max_list[2]), - - time_current_1_vec=input_parameters_dto.time_current_1_vec, - time_current_2_vec=input_parameters_dto.time_current_2_vec, - - l_s_target_value=input_parameters_dto.l_s_target, - l_h_target_value=input_parameters_dto.l_h_target, - n_target_value=input_parameters_dto.n_target, - factor_max_flux_density=input_parameters_dto.factor_max_flux_density, - t1_primary_litz_wire_list=input_parameters_dto.primary_litz_wire_list, - t1_secondary_litz_wire_list=input_parameters_dto.secondary_litz_wire_list - ) - return sweep_tensor - - ############################# - # filters - ############################# - - class NSGAII: - """NSGAII algorithm to find the pareto front.""" - - ############################## - # simulation - ############################## - - @staticmethod - def objective(trial: optuna.Trial, config: ItoSingleInputConfig, - target_and_fixed_parameters: ItoTargetAndFixedParameters) -> Tuple: - """ - Objective function to optimize. + @staticmethod + def single_reluctance_model_simulation(reluctance_input: ItoReluctanceModelInput) -> ItoReluctanceModelOutput: + """ + Perform a single reluctance model simulation. - Using optuna. Some hints: + :param reluctance_input: Reluctance model input data. + :type reluctance_input: ReluctanceModelInput + :return: Reluctance model output data + :rtype: ReluctanceModelOutput + """ + core_total_height = reluctance_input.window_h_top + reluctance_input.window_h_bot + reluctance_input.core_inner_diameter * 3 / 4 + r_outer = fr.calculate_r_outer(reluctance_input.core_inner_diameter, reluctance_input.window_w) + volume = ff.calculate_cylinder_volume(cylinder_diameter=2 * r_outer, cylinder_height=core_total_height) + + # calculate the reluctance and flux matrix + winding_matrix = np.array([[reluctance_input.turns_1_top, reluctance_input.turns_2_top], + [reluctance_input.turns_1_bot, reluctance_input.turns_2_bot]]) + + reluctance_matrix = fr.calculate_reluctance_matrix(winding_matrix, reluctance_input.target_inductance_matrix) + + current_matrix = np.array([reluctance_input.current_extracted_vec_1, reluctance_input.current_extracted_vec_2]) + + flux_matrix = fr.calculate_flux_matrix(reluctance_matrix, winding_matrix, current_matrix) + + flux_top = flux_matrix[0] + flux_bot = flux_matrix[1] + flux_middle = flux_bot - flux_top + + core_cross_section = (reluctance_input.core_inner_diameter / 2) ** 2 * np.pi + + flux_density_top = flux_top / core_cross_section + flux_density_bot = flux_bot / core_cross_section + flux_density_middle = flux_middle / core_cross_section + + # calculate the core reluctance + core_inner_cylinder_top = fr.r_core_round(reluctance_input.core_inner_diameter, reluctance_input.window_h_top, + reluctance_input.material_dto.material_mu_r_abs) + core_inner_cylinder_bot = fr.r_core_round(reluctance_input.core_inner_diameter, reluctance_input.window_h_bot, + reluctance_input.material_dto.material_mu_r_abs) + core_top_bot_radiant = fr.r_core_top_bot_radiant(reluctance_input.core_inner_diameter, reluctance_input.window_w, + reluctance_input.material_dto.material_mu_r_abs, reluctance_input.core_inner_diameter / 4) + + r_core_top = 2 * core_inner_cylinder_top + core_top_bot_radiant + r_core_bot = 2 * core_inner_cylinder_bot + core_top_bot_radiant + r_core_middle = core_top_bot_radiant + + r_top_target = reluctance_matrix[0][0] + reluctance_matrix[0][1] + r_bot_target = reluctance_matrix[1][1] + reluctance_matrix[0][1] + r_middle_target = - reluctance_matrix[0][1] + + r_air_gap_top_target = r_top_target - r_core_top + r_air_gap_bot_target = r_bot_target - r_core_bot + r_air_gap_middle_target = r_middle_target - r_core_middle + + # calculate air gaps to reach the target parameters + minimum_air_gap_length = 0.001e-3 + maximum_air_gap_length = 2e-3 + maximum_air_gap_length_middle = 4e-3 + + l_top_air_gap = optimize.brentq( + fr.r_air_gap_round_inf_sct, minimum_air_gap_length, maximum_air_gap_length, + args=(reluctance_input.core_inner_diameter, reluctance_input.window_h_top, r_air_gap_top_target), full_output=True)[0] + + l_bot_air_gap = optimize.brentq( + fr.r_air_gap_round_round_sct, minimum_air_gap_length, maximum_air_gap_length, + args=(reluctance_input.core_inner_diameter, reluctance_input.window_h_bot / 2, reluctance_input.window_h_bot / 2, r_air_gap_bot_target), + full_output=True)[0] + + # Note: ideal calculation (360 degree) + # needs to be translated when it comes to the real setup. + l_middle_air_gap = optimize.brentq( + fr.r_air_gap_tablet_cylinder_sct, minimum_air_gap_length, maximum_air_gap_length_middle, + args=(reluctance_input.core_inner_diameter, reluctance_input.core_inner_diameter/4, reluctance_input.window_w, r_air_gap_middle_target), + full_output=True)[0] + + # calculate hysteresis losses from mag-net-hub + interp_points = np.arange(0, 1024) * reluctance_input.time_extracted_vec[-1] / 1024 + flux_density_top_interp = np.interp(interp_points, reluctance_input.time_extracted_vec, flux_density_top) + flux_density_bot_interp = np.interp(interp_points, reluctance_input.time_extracted_vec, flux_density_bot) + flux_density_middle_interp = np.interp(interp_points, reluctance_input.time_extracted_vec, flux_density_middle) + + # get power loss in W/m³ and estimated H wave in A/m + p_density_top, _ = reluctance_input.magnet_material_model(flux_density_top_interp, + reluctance_input.fundamental_frequency, reluctance_input.temperature) + p_density_bot, _ = reluctance_input.magnet_material_model(flux_density_bot_interp, + reluctance_input.fundamental_frequency, reluctance_input.temperature) + p_density_middle, _ = reluctance_input.magnet_material_model(flux_density_middle_interp, + reluctance_input.fundamental_frequency, reluctance_input.temperature) + + volume_core_top = (2 * ff.calculate_cylinder_volume(reluctance_input.core_inner_diameter, reluctance_input.window_h_top) - \ + ff.calculate_cylinder_volume(reluctance_input.core_inner_diameter, l_top_air_gap) + \ + ff.calculate_cylinder_volume(2 * r_outer, reluctance_input.core_inner_diameter / 4)) + volume_core_bot = (2 * ff.calculate_cylinder_volume(reluctance_input.core_inner_diameter, reluctance_input.window_h_bot) - \ + ff.calculate_cylinder_volume(reluctance_input.core_inner_diameter, l_bot_air_gap) + \ + ff.calculate_cylinder_volume(2 * r_outer, reluctance_input.core_inner_diameter / 4)) + volume_core_middle = ff.calculate_cylinder_volume(2 * r_outer, reluctance_input.core_inner_diameter / 4) + + p_top = p_density_top * volume_core_top + p_bot = p_density_bot * volume_core_bot + p_middle = p_density_middle * volume_core_middle + + p_hyst = p_top + p_bot + p_middle + + # calculate winding losses + primary_effective_conductive_cross_section = ( + reluctance_input.litz_dict_1["strands_numbers"] * reluctance_input.litz_dict_1["strand_radii"] ** 2 * np.pi) + primary_effective_conductive_radius = np.sqrt(primary_effective_conductive_cross_section / np.pi) + primary_resistance_top = fr.resistance_solid_wire( + reluctance_input.core_inner_diameter, reluctance_input.window_w, reluctance_input.turns_1_top, + primary_effective_conductive_radius, material='Copper') + + number_bot_prim_turns_per_column = ( + int((reluctance_input.window_h_bot - reluctance_input.insulations.iso_window_bot_core_top - \ + reluctance_input.insulations.iso_window_bot_core_bot + reluctance_input.insulations.iso_primary_to_primary) / \ + (2 * reluctance_input.litz_dict_1["conductor_radii"] + reluctance_input.insulations.iso_primary_to_primary))) + if number_bot_prim_turns_per_column > reluctance_input.turns_1_bot: + # single row window only + primary_resistance_bot_inner = fr.resistance_solid_wire( + reluctance_input.core_inner_diameter, reluctance_input.window_w, reluctance_input.turns_1_bot, + primary_effective_conductive_radius, material='Copper') + primary_resistance_bot_outer = 0 + else: + # multiple row window + primary_resistance_bot_inner = fr.resistance_solid_wire( + reluctance_input.core_inner_diameter, reluctance_input.window_w, number_bot_prim_turns_per_column, + primary_effective_conductive_radius, material='Copper') + + primary_resistance_bot_outer = fr.resistance_solid_wire( + reluctance_input.core_inner_diameter, reluctance_input.window_w, reluctance_input.turns_1_bot - number_bot_prim_turns_per_column, + primary_effective_conductive_radius, material='Copper') + + secondary_effective_conductive_cross_section = ( + reluctance_input.litz_dict_2["strands_numbers"] * reluctance_input.litz_dict_2["strand_radii"] ** 2 * np.pi) + secondary_effective_conductive_radius = np.sqrt(secondary_effective_conductive_cross_section / np.pi) + secondary_resistance = fr.resistance_solid_wire( + reluctance_input.core_inner_diameter, reluctance_input.window_w, reluctance_input.turns_2_bot, secondary_effective_conductive_radius, + material='Copper') + + winding_area_1_top = ( + 2 * reluctance_input.litz_dict_1["conductor_radii"] * \ + (reluctance_input.turns_1_top * 2 * reluctance_input.litz_dict_1["conductor_radii"] + \ + (reluctance_input.turns_1_top - 1) * reluctance_input.insulations.iso_primary_to_primary)) + + p_winding_1_top = 0 + p_winding_1_bot = 0 + for count, fft_frequency in enumerate(reluctance_input.fft_frequency_list_1): + proximity_factor_1_top = fr.calc_proximity_factor_air_gap( + litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=reluctance_input.turns_1_top, + r_1=reluctance_input.insulations.iso_window_top_core_left, + frequency=fft_frequency, winding_area=winding_area_1_top, + litz_wire_material_name='Copper', temperature=reluctance_input.temperature) + + p_winding_1_top += proximity_factor_1_top * primary_resistance_top * reluctance_input.fft_amplitude_list_1[count] ** 2 + + if number_bot_prim_turns_per_column > reluctance_input.turns_1_bot: + winding_area_1_bot = 2 * reluctance_input.litz_dict_1["conductor_radii"] * \ + (reluctance_input.turns_1_bot * 2 * reluctance_input.litz_dict_1["conductor_radii"] + \ + (reluctance_input.turns_1_bot - 1) * reluctance_input.insulations.iso_primary_to_primary) + + proximity_factor_1_bot_inner = fr.calc_proximity_factor_air_gap( + litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=reluctance_input.turns_1_bot, + r_1=reluctance_input.insulations.iso_window_bot_core_left, + frequency=fft_frequency, winding_area=winding_area_1_bot, + litz_wire_material_name='Copper', temperature=reluctance_input.temperature) + + proximity_factor_1_bot_outer = 0 + else: + winding_area_1_bot = (2 * reluctance_input.litz_dict_1["conductor_radii"] * ( + number_bot_prim_turns_per_column * 2 * reluctance_input.litz_dict_1["conductor_radii"] + \ + (number_bot_prim_turns_per_column - 1) * reluctance_input.insulations.iso_primary_to_primary)) + + proximity_factor_1_bot_inner = fr.calc_proximity_factor_air_gap( + litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=number_bot_prim_turns_per_column, + r_1=reluctance_input.insulations.iso_window_bot_core_left, + frequency=fft_frequency, winding_area=winding_area_1_bot, + litz_wire_material_name='Copper', temperature=reluctance_input.temperature) + + proximity_factor_1_bot_outer = fr.calc_proximity_factor( + litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=reluctance_input.turns_1_bot - number_bot_prim_turns_per_column, + window_h=reluctance_input.window_h_bot, + iso_core_top=reluctance_input.insulations.iso_window_bot_core_top, iso_core_bot=reluctance_input.insulations.iso_window_bot_core_bot, + frequency=fft_frequency, litz_wire_material_name='Copper', temperature=reluctance_input.temperature) + + p_winding_1_bot_inner = proximity_factor_1_bot_inner * primary_resistance_bot_inner * reluctance_input.fft_amplitude_list_1[count] ** 2 + p_winding_1_bot_outer = proximity_factor_1_bot_outer * primary_resistance_bot_outer * reluctance_input.fft_amplitude_list_1[count] ** 2 + + p_winding_1_bot += p_winding_1_bot_inner + p_winding_1_bot_outer + + p_winding_2 = 0 + for count, fft_frequency in enumerate(reluctance_input.fft_frequency_list_2): + proximity_factor_assumption_2 = fr.calc_proximity_factor( + litz_wire_name=reluctance_input.litz_wire_name_2, number_turns=reluctance_input.turns_2_bot, window_h=reluctance_input.window_h_bot, + iso_core_top=reluctance_input.insulations.iso_window_bot_core_top, iso_core_bot=reluctance_input.insulations.iso_window_bot_core_bot, + frequency=fft_frequency, litz_wire_material_name='Copper', temperature=reluctance_input.temperature) + + p_winding_2 += proximity_factor_assumption_2 * secondary_resistance * reluctance_input.fft_amplitude_list_2[count] ** 2 + + p_loss_total = p_hyst + p_winding_1_top + p_winding_1_bot + p_winding_2 + + area_to_heat_sink = r_outer ** 2 * np.pi + + reluctance_output = ItoReluctanceModelOutput( + # set additional attributes + p_hyst=p_hyst, + p_hyst_top=p_top, + p_hyst_bot=p_bot, + p_hyst_middle=p_middle, + b_max_top=np.max(flux_density_top_interp), + b_max_bot=np.max(flux_density_bot_interp), + b_max_middle=np.max(flux_density_middle_interp), + winding_1_loss=p_winding_1_top + p_winding_1_bot, + winding_2_loss=p_winding_2, + l_top_air_gap=l_top_air_gap, + l_bot_air_gap=l_bot_air_gap, + l_middle_air_gap=l_middle_air_gap, + volume=volume, + area_to_heat_sink=area_to_heat_sink, + p_loss=p_loss_total, + ) + + return reluctance_output - * returning failed trails by using return float('nan'), float('nan'), - see https://optuna.readthedocs.io/en/stable/faq.html#how-are-nans-returned-by-trials-handled - * speed up the search for NSGA-II algorithm with dynamic alter the search space, see https://optuna.readthedocs.io/en/stable/faq.html#id10 + @staticmethod + def objective(trial: optuna.Trial, config: ItoSingleInputConfig, target_and_fixed_parameters: ItoTargetAndFixedParameters): + """ + Objective function to optimize. Uses reluctance model calculation. + Once core_name_list is not None, the objective function uses fixed core sizes. Cores are picked from the core_database(). + Otherwise, core_inner_diameter_min_max_list, window_w_min_max_list and window_h_bot_min_max_list are used. - :param trial: parameter suggesting by optuna - :type trial: optuna.Trial - :param config: input configuration file - :type config: ItoSingleInputConfig - :param target_and_fixed_parameters: target and fix parameters - :type target_and_fixed_parameters: ItoTargetAndFixedParameters - """ - # pass multiple arguments to the objective function used by optuna - # https://www.kaggle.com/general/261870 - - ######################################################### - # set core geometry optimization parameters - ######################################################### - core_inner_diameter = trial.suggest_float("core_inner_diameter", - config.core_inner_diameter_min_max_list[0], + :param trial: Optuna trial + :type trial: optuna.Trial + :param config: Stacked transformer optimization configuration file + :type config: StoSingleInputConfig + :param target_and_fixed_parameters: Target and fixed parameters + :type target_and_fixed_parameters: StoTargetAndFixedParameters + """ + # fixed cores + if config.core_name_list is not None: + # using fixed core sizes from the database with flexible height. + core_name = trial.suggest_categorical("core_name", config.core_name_list) + core = ff.core_database()[core_name] + core_inner_diameter = core["core_inner_diameter"] + window_w = core["window_w"] + window_h_half_max = (core["window_h"] - core_inner_diameter / 4) / 2 + + trial.set_user_attr('core_inner_diameter', core_inner_diameter) + trial.set_user_attr('window_w', window_w) + + else: + # using arbitrary core sizes + core_inner_diameter = trial.suggest_float("core_inner_diameter", config.core_inner_diameter_min_max_list[0], config.core_inner_diameter_min_max_list[1]) - window_w = trial.suggest_float("window_w", config.window_w_min_max_list[0], - config.window_w_min_max_list[1]) - window_h_top = trial.suggest_float("window_h_top", config.window_h_top_min_max_list[0], - config.window_h_top_min_max_list[1]) - window_h_bot = trial.suggest_float("window_h_bot", config.window_h_bot_min_max_list[0], - config.window_h_bot_min_max_list[1]) - - material = trial.suggest_categorical("material", config.material_list) - primary_litz_wire = trial.suggest_categorical("primary_litz_wire", config.primary_litz_wire_list) - secondary_litz_wire = trial.suggest_categorical("secondary_litz_wire", config.secondary_litz_wire_list) - - # cross-section comparison is according to a square for round wire. - # this approximation is more realistic - # insulation - insulation_distance = 1e-3 - insulation_cross_section_top = 2 * insulation_distance * (window_w + window_h_top) - insulation_cross_section_bot = 2 * insulation_distance * (window_w + window_h_bot) - - litz_database = ff.litz_database() - - primary_litz = litz_database[primary_litz_wire] - secondary_litz = litz_database[secondary_litz_wire] - - total_available_window_cross_section_top = window_h_top * window_w - insulation_cross_section_top - total_available_window_cross_section_bot = window_h_bot * window_w - insulation_cross_section_bot - - ######################################################### - # set dynamic wire count parameters as optimization parameters - ######################################################### - # set the winding search space dynamic - # https://optuna.readthedocs.io/en/stable/faq.html#what-happens-when-i-dynamically-alter-a-search-space - - # n_p_top suggestion - n_p_top_max = total_available_window_cross_section_top / (2 * primary_litz["conductor_radii"]) ** 2 - n_p_top = trial.suggest_int("n_p_top", 0, n_p_top_max) - - # n_s_top_suggestion - winding_cross_section_n_p_top_max = n_p_top * (2 * primary_litz["conductor_radii"]) ** 2 - n_s_top_max = int((total_available_window_cross_section_top - winding_cross_section_n_p_top_max) / ( - 2 * secondary_litz["conductor_radii"]) ** 2) - n_s_top = trial.suggest_int("n_s_top", 0, n_s_top_max) - - # n_p_bot suggestion - n_p_bot_max = total_available_window_cross_section_bot / (2 * primary_litz["conductor_radii"]) ** 2 - n_p_bot = trial.suggest_int("n_p_bot", 0, n_p_bot_max) - - # n_s_bot suggestion - winding_cross_section_n_p_bot_max = n_p_bot * (2 * primary_litz["conductor_radii"]) ** 2 - n_s_bot_max = int((total_available_window_cross_section_bot - winding_cross_section_n_p_bot_max) / (2 * secondary_litz["conductor_radii"]) ** 2) - n_s_bot = trial.suggest_int("n_s_bot", 0, n_s_bot_max) - - winding_cross_section_top = n_p_top * (2 * primary_litz["conductor_radii"]) ** 2 + n_s_top * (2 * secondary_litz["conductor_radii"]) ** 2 - winding_cross_section_bot = n_p_bot * (2 * primary_litz["conductor_radii"]) ** 2 + n_s_bot * (2 * secondary_litz["conductor_radii"]) ** 2 - - thousand_simulations = trial.number / 1000 - - if thousand_simulations.is_integer(): - print(f"simulation count: {trial.number}") - - for material_dto in target_and_fixed_parameters.material_dto_curve_list: - if material_dto.material_name == material: - material_data = material_dto - - material_mu_r_initial = material_data.material_mu_r_abs - flux_density_data_vec = material_data.material_flux_density_vec - mu_r_imag_data_vec = material_data.material_mu_r_imag_vec - - core_top_bot_height = core_inner_diameter / 4 - core_cross_section = (core_inner_diameter / 2) ** 2 * np.pi - - t2_winding_matrix = [[n_p_top, n_s_top], [n_p_bot, n_s_bot]] - - target_inductance_matrix = fr.calculate_inductance_matrix_from_ls_lh_n(config.l_s_target, - config.l_h_target, - config.n_target) - t2_reluctance_matrix = fr.calculate_reluctance_matrix(t2_winding_matrix, target_inductance_matrix) - - core_2daxi_total_volume = fr.calculate_core_2daxi_total_volume(core_inner_diameter, - (window_h_bot + window_h_top + core_inner_diameter / 4), window_w) - - if np.linalg.det(t2_reluctance_matrix) != 0 and np.linalg.det( - np.transpose(t2_winding_matrix)) != 0 and np.linalg.det(target_inductance_matrix) != 0: - # calculate the flux - flux_top_vec, flux_bot_vec, flux_stray_vec = fr.flux_vec_from_current_vec( - target_and_fixed_parameters.current_extracted_1_vec, - target_and_fixed_parameters.current_extracted_2_vec, - t2_winding_matrix, - target_inductance_matrix) - - # calculate maximum values - flux_top_max, flux_bot_max, flux_stray_max = fr.max_value_from_value_vec(flux_top_vec, flux_bot_vec, - flux_stray_vec) - - flux_density_top_max = flux_top_max / core_cross_section - flux_density_bot_max = flux_bot_max / core_cross_section - flux_density_middle_max = flux_stray_max / core_cross_section - - # calculate target values for r_top and r_bot out of reluctance matrix - r_core_middle_cylinder_radial = fr.r_core_top_bot_radiant(core_inner_diameter, window_w, - material_data.material_mu_r_abs, - core_top_bot_height) - - r_middle_target = -t2_reluctance_matrix[0][1] - r_top_target = t2_reluctance_matrix[0][0] - r_middle_target - r_bot_target = t2_reluctance_matrix[1][1] - r_middle_target - - # calculate the core reluctance of top and bottom and middle part - r_core_top_cylinder_inner = fr.r_core_round(core_inner_diameter, window_h_top, - material_data.material_mu_r_abs) - r_core_top = 2 * r_core_top_cylinder_inner + r_core_middle_cylinder_radial - r_air_gap_top_target = r_top_target - r_core_top - - r_core_bot_cylinder_inner = fr.r_core_round(core_inner_diameter, window_h_bot, - material_data.material_mu_r_abs) - r_core_bot = 2 * r_core_bot_cylinder_inner + r_core_middle_cylinder_radial - r_air_gap_bot_target = r_bot_target - r_core_bot - - r_air_gap_middle_target = r_middle_target - r_core_middle_cylinder_radial - - if r_air_gap_top_target > 0 and r_air_gap_bot_target > 0 and r_air_gap_middle_target > 0: - - # Note: a minimum air gap length of zero is not allowed. This will lead to failure calculation - # when trying to solve (using brentq) r_gap_round_round-function. Calculating an air gap - # reluctance with length of zero is not realistic. - minimum_air_gap_length = 1e-15 - maximum_air_gap_length = 5e-3 - minimum_sort_out_air_gap_length = 0 - try: - # solving brentq needs to be in try/except statement, - # as it can be that there is no sign changing in the given interval - # to search for the zero. - # Note: setting full output to true and taking object [0] is only - # to avoid linting error! - l_top_air_gap = optimize.brentq(fr.r_air_gap_round_inf_sct, minimum_air_gap_length, maximum_air_gap_length, - args=(core_inner_diameter, window_h_top, r_air_gap_top_target), full_output=True)[0] - - l_bot_air_gap = optimize.brentq(fr.r_air_gap_round_round_sct, minimum_air_gap_length, - maximum_air_gap_length, args=(core_inner_diameter, window_h_bot / 2, window_h_bot / 2, - r_air_gap_bot_target), full_output=True)[0] - - l_middle_air_gap = optimize.brentq(fr.r_air_gap_tablet_cylinder_sct, minimum_air_gap_length, - maximum_air_gap_length, args=(core_inner_diameter, - core_inner_diameter / 4, window_w, r_air_gap_middle_target), - full_output=True)[0] - - except ValueError: - # ValueError is raised in case of an air gap with length of zero - return float('nan'), float('nan') - - if l_top_air_gap > core_inner_diameter or l_bot_air_gap > core_inner_diameter: - return float('nan'), float('nan') - - if l_top_air_gap >= minimum_sort_out_air_gap_length and l_bot_air_gap >= minimum_sort_out_air_gap_length \ - and l_middle_air_gap >= minimum_sort_out_air_gap_length: - p_hyst_top = fr.hyst_losses_core_half_mu_r_imag(core_inner_diameter, window_h_top, window_w, - material_data.material_mu_r_abs, - flux_top_max, - target_and_fixed_parameters.fundamental_frequency, - material_data.material_flux_density_vec, - material_data.material_mu_r_imag_vec) - - p_hyst_middle = fr.power_losses_hysteresis_cylinder_radial_direction_mu_r_imag( - flux_stray_max, - core_inner_diameter / 4, - core_inner_diameter / 2, - core_inner_diameter / 2 + window_w, - target_and_fixed_parameters.fundamental_frequency, - material_data.material_mu_r_abs, - material_data.material_flux_density_vec, - material_data.material_mu_r_imag_vec) - - p_hyst_bot = fr.hyst_losses_core_half_mu_r_imag(core_inner_diameter, window_h_bot, window_w, - material_data.material_mu_r_abs, - flux_bot_max, - target_and_fixed_parameters.fundamental_frequency, - material_data.material_flux_density_vec, - material_data.material_mu_r_imag_vec) - - p_hyst = p_hyst_top + p_hyst_bot + p_hyst_middle - - primary_effective_conductive_cross_section = primary_litz["strands_numbers"] * primary_litz[ - "strand_radii"] ** 2 * np.pi - primary_effective_conductive_radius = np.sqrt( - primary_effective_conductive_cross_section / np.pi) - primary_resistance = fr.resistance_solid_wire(core_inner_diameter, window_w, - n_p_top + n_p_bot, - primary_effective_conductive_radius, - material='Copper') - primary_dc_loss = primary_resistance * target_and_fixed_parameters.i_rms_1 ** 2 - - secondary_effective_conductive_cross_section = secondary_litz["strands_numbers"] * secondary_litz["strand_radii"] ** 2 * np.pi - secondary_effective_conductive_radius = np.sqrt( - secondary_effective_conductive_cross_section / np.pi) - secondary_resistance = fr.resistance_solid_wire(core_inner_diameter, window_w, - n_s_top + n_s_bot, - secondary_effective_conductive_radius, - material='Copper') - secondary_dc_loss = secondary_resistance * target_and_fixed_parameters.i_rms_2 ** 2 - - total_loss = p_hyst + primary_dc_loss + secondary_dc_loss - - trial.set_user_attr("air_gap_top", l_top_air_gap) - trial.set_user_attr("air_gap_bot", l_bot_air_gap) - trial.set_user_attr("air_gap_middle", l_middle_air_gap) - - trial.set_user_attr("flux_top_max", flux_top_max) - trial.set_user_attr("flux_bot_max", flux_bot_max) - trial.set_user_attr("flux_stray_max", flux_stray_max) - trial.set_user_attr("flux_density_top_max", flux_density_top_max) - trial.set_user_attr("flux_density_bot_max", flux_density_bot_max) - trial.set_user_attr("flux_density_stray_max", flux_density_middle_max) - trial.set_user_attr("p_hyst", p_hyst) - trial.set_user_attr("primary_litz_wire_loss", primary_dc_loss) - trial.set_user_attr("secondary_litz_wire_loss", secondary_dc_loss) - - print(f"successfully calculated trial {trial.number}") - - valid_design_dict = ItoSingleResultFile( - case=trial.number, - air_gap_top=l_top_air_gap, - air_gap_bot=l_bot_air_gap, - air_gap_middle=l_middle_air_gap, - n_p_top=n_p_top, - n_p_bot=n_p_bot, - n_s_top=n_s_top, - n_s_bot=n_s_bot, - window_h_top=window_h_top, - window_h_bot=window_h_bot, - window_w=window_w, - core_material=material_data.material_name, - core_inner_diameter=core_inner_diameter, - primary_litz_wire=primary_litz_wire, - secondary_litz_wire=secondary_litz_wire, - # results - flux_top_max=flux_top_max, - flux_bot_max=flux_bot_max, - flux_stray_max=flux_stray_max, - flux_density_top_max=flux_density_top_max, - flux_density_bot_max=flux_density_bot_max, - flux_density_stray_max=flux_density_middle_max, - p_hyst=p_hyst, - core_2daxi_total_volume=core_2daxi_total_volume, - primary_litz_wire_loss=primary_dc_loss, - secondary_litz_wire_loss=secondary_dc_loss, - total_loss=total_loss - - ) - - return core_2daxi_total_volume, total_loss - else: - return float('nan'), float('nan') - else: - return float('nan'), float('nan') - else: - return float('nan'), float('nan') - - @staticmethod - def start_study(study_name: str, config: ItoSingleInputConfig, number_trials: int, - storage: str = None) -> None: + window_w = trial.suggest_float("window_w", config.window_w_min_max_list[0], config.window_w_min_max_list[1]) + + # suggest turn counts + n_1_top = trial.suggest_int('n_p_top', config.n_1_top_min_max_list[0], config.n_1_top_min_max_list[1]) + n_1_bot = trial.suggest_int('n_p_bot', config.n_1_bot_min_max_list[0], config.n_1_bot_min_max_list[1]) + n_2_top = trial.suggest_int('n_s_top', config.n_2_top_min_max_list[0], config.n_2_top_min_max_list[1]) + n_2_bot = trial.suggest_int('n_s_bot', config.n_2_bot_min_max_list[0], config.n_2_bot_min_max_list[1]) + + # suggest litz wire 1 + litz_name_1 = trial.suggest_categorical('litz_name_1', config.litz_wire_list_1) + litz_dict_1 = ff.litz_database()[litz_name_1] + litz_diameter_1 = 2 * litz_dict_1['conductor_radii'] + + # suggest litz wire 2 + litz_name_2 = trial.suggest_categorical('litz_name_2', config.litz_wire_list_2) + litz_dict_2 = ff.litz_database()[litz_name_2] + litz_diameter_2 = 2 * litz_dict_2['conductor_radii'] + + # suggest core material + material_name = trial.suggest_categorical('material_name', config.material_list) + for count, material_dto in enumerate(target_and_fixed_parameters.material_dto_curve_list): + if material_dto.material_name == material_name: + material_dto: mdb.MaterialCurve = material_dto + magnet_material_model = target_and_fixed_parameters.magnet_hub_model_list[count] + + # calculation of window_h_top: assumption: winding ist stacked + available_width_top = window_w - config.insulations.iso_window_top_core_left - config.insulations.iso_window_top_core_right + available_width_bot = window_w - config.insulations.iso_window_bot_core_left - config.insulations.iso_window_bot_core_right + + def calc_winding_height(available_width: float, iso_winding_to_winding: float, litz_diameter: float, + number_turns: int) -> float: """ - Start a study to optimize an integrated transformer. - - Note: Due to performance reasons, the study is calculated in RAM. - After finishing the study, the results are copied to sqlite or mysql database by the use of a new study. - - :param study_name: Name of the study - :type study_name: str - :param config: simulation configuration - :type config: ItoSingleInputConfig - :param number_trials: number of trials - :type number_trials: int - :param storage: "sqlite" or "mysql" - :type storage: str + Calculate the needed winding height depending on the winding geometry and available width. Takes winding-to-winding insulation into account. + + :param available_width: available width of the winding window in meter + :type available_width: float + :param iso_winding_to_winding: winding-to-winding insulation in meter + :type iso_winding_to_winding: float + :param litz_diameter: litz diameter in meter + :type litz_diameter: float + :param number_turns: number of turns + :type number_turns: int + :return: needed height of the winding in meter + :rtype: float """ - # calculate the target and fixed parameters - # and generate the folder structure inside this function - target_and_fixed_parameters = femmt.optimization.IntegratedTransformerOptimization.calculate_fix_parameters(config) - - # Wrap the objective inside a lambda and call objective inside it - func = lambda trial: femmt.IntegratedTransformerOptimization.ReluctanceModel.NSGAII.objective(trial, config, target_and_fixed_parameters) - - # Pass func to Optuna studies - study_in_memory = optuna.create_study(directions=["minimize", "minimize"], - # sampler=optuna.samplers.TPESampler(), - sampler=optuna.samplers.NSGAIISampler(), - ) - - # set logging verbosity: - # https://optuna.readthedocs.io/en/stable/reference/generated/optuna.logging.set_verbosity.html#optuna.logging.set_verbosity - # .INFO: all messages (default) - # .WARNING: fails and warnings - # .ERROR: only errors - optuna.logging.set_verbosity(optuna.logging.ERROR) + number_turns_per_row = int((available_width + iso_winding_to_winding) / (litz_diameter + iso_winding_to_winding)) + if number_turns_per_row < 1: + return float('nan'), float('nan') + number_of_rows = np.ceil(number_turns / number_turns_per_row) + winding_height = number_of_rows * litz_diameter + (number_of_rows - 1) * iso_winding_to_winding + return winding_height + + # height of top core needed + window_h_1_top = calc_winding_height(available_width_top, config.insulations.iso_primary_to_primary, litz_diameter_1, n_1_top) + window_h_2_top = calc_winding_height(available_width_top, config.insulations.iso_secondary_to_secondary, litz_diameter_2, n_2_top) + window_h_top = (window_h_1_top + window_h_2_top + config.insulations.iso_primary_to_secondary + \ + config.insulations.iso_window_top_core_top + config.insulations.iso_window_top_core_bot) + if window_h_top > window_h_half_max: + logging.warning("window_h_top > window_h_half_max") + return float('nan'), float('nan') + + # height of bot core needed + window_h_1_bot = calc_winding_height(available_width_bot, config.insulations.iso_primary_to_primary, litz_diameter_1, n_1_bot) + window_h_2_bot = calc_winding_height(available_width_bot, config.insulations.iso_secondary_to_secondary, litz_diameter_2, n_2_bot) + window_h_bot = (window_h_1_bot + window_h_2_bot + config.insulations.iso_primary_to_secondary + \ + config.insulations.iso_window_bot_core_top + config.insulations.iso_window_bot_core_bot) + if window_h_bot > window_h_half_max: + logging.warning("window_h_bot > window_h_half_max") + return float('nan'), float('nan') + + reluctance_model_intput = ItoReluctanceModelInput( + target_inductance_matrix=target_and_fixed_parameters.target_inductance_matrix, + core_inner_diameter=core_inner_diameter, + window_w=window_w, + window_h_bot=window_h_bot, + window_h_top=window_h_top, + turns_1_top=n_1_top, + turns_1_bot=n_1_bot, + turns_2_top=n_2_top, + turns_2_bot=n_2_bot, + litz_wire_name_1=litz_name_1, + litz_wire_diameter_1=litz_diameter_1, + litz_wire_name_2=litz_name_2, + litz_wire_diameter_2=litz_diameter_2, + + insulations=config.insulations, + material_dto=material_dto, + magnet_material_model=magnet_material_model, + + temperature=config.temperature, + time_extracted_vec=target_and_fixed_parameters.time_extracted_vec, + current_extracted_vec_1=target_and_fixed_parameters.current_extracted_1_vec, + current_extracted_vec_2=target_and_fixed_parameters.current_extracted_2_vec, + fundamental_frequency=target_and_fixed_parameters.fundamental_frequency, + i_rms_1=target_and_fixed_parameters.i_rms_1, + i_rms_2=target_and_fixed_parameters.i_rms_2, + litz_dict_1=litz_dict_1, + litz_dict_2=litz_dict_2, + + # winding 1 + fft_frequency_list_1=target_and_fixed_parameters.fft_frequency_list_1, + fft_amplitude_list_1=target_and_fixed_parameters.fft_amplitude_list_1, + fft_phases_list_1=target_and_fixed_parameters.fft_phases_list_1, + # winding 2 + fft_frequency_list_2=target_and_fixed_parameters.fft_frequency_list_2, + fft_amplitude_list_2=target_and_fixed_parameters.fft_amplitude_list_2, + fft_phases_list_2=target_and_fixed_parameters.fft_phases_list_2, + ) + try: + reluctance_output = IntegratedTransformerOptimization.ReluctanceModel.single_reluctance_model_simulation(reluctance_model_intput) + except ValueError as e: + print(e) + return float('nan'), float('nan') + + # set additional attributes + trial.set_user_attr('p_hyst', reluctance_output.p_hyst) + trial.set_user_attr('p_hyst_top', reluctance_output.p_hyst_top) + trial.set_user_attr('p_hyst_bot', reluctance_output.p_hyst_bot) + trial.set_user_attr('p_hyst_middle', reluctance_output.p_hyst_middle) + trial.set_user_attr('b_max_top', reluctance_output.b_max_top) + trial.set_user_attr('b_max_bot', reluctance_output.b_max_bot) + trial.set_user_attr('b_max_middle', reluctance_output.b_max_middle) + trial.set_user_attr('window_h_top', window_h_top) + trial.set_user_attr('winding_losses', reluctance_output.winding_1_loss + reluctance_output.winding_2_loss) + trial.set_user_attr('l_top_air_gap', reluctance_output.l_top_air_gap) + trial.set_user_attr('l_bot_air_gap', reluctance_output.l_bot_air_gap) + + return reluctance_output.volume, reluctance_output.p_loss + + ######################################################### + # set dynamic wire count parameters as optimization parameters + ######################################################### + # set the winding search space dynamic + # https://optuna.readthedocs.io/en/stable/faq.html#what-happens-when-i-dynamically-alter-a-search-space + + # if np.linalg.det(t2_reluctance_matrix) != 0 and np.linalg.det( + # np.transpose(t2_winding_matrix)) != 0 and np.linalg.det(target_inductance_matrix) != 0: + # # calculate the flux + # flux_top_vec, flux_bot_vec, flux_stray_vec = fr.flux_vec_from_current_vec( + # target_and_fixed_parameters.current_extracted_1_vec, + # target_and_fixed_parameters.current_extracted_2_vec, + # t2_winding_matrix, + # target_inductance_matrix) + @staticmethod + def start_proceed_study(config: ItoSingleInputConfig, number_trials: Optional[int] = None, + target_number_trials: Optional[int] = None, storage: str = 'sqlite', + sampler=optuna.samplers.NSGAIIISampler(), + ) -> None: + """ + Proceed a study which is stored as sqlite database. + + :param config: Simulation configuration + :type config: ItoSingleInputConfig + :param number_trials: Number of trials adding to the existing study + :type number_trials: int + :param storage: storage database, e.g. 'sqlite' or 'mysql' + :type storage: str + :param target_number_trials: Number of target trials for the existing study + :type target_number_trials: int + :param sampler: optuna.samplers.NSGAIISampler() or optuna.samplers.NSGAIIISampler(). Note about the brackets () !! + :type sampler: optuna.sampler-object + """ + if os.path.exists(f"{config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}.sqlite3"): + print("Existing study found. Proceeding.") + + target_and_fixed_parameters = IntegratedTransformerOptimization.calculate_fix_parameters(config) + + # introduce study in storage, e.g. sqlite or mysql + if storage == 'sqlite': + # Note: for sqlite operation, there needs to be three slashes '///' even before the path '/home/...' + # Means, in total there are four slashes including the path itself '////home/.../database.sqlite3' + storage = f"sqlite:///{config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}.sqlite3" + elif storage == 'mysql': + storage = "mysql://monty@localhost/mydb", + + # set logging verbosity: https://optuna.readthedocs.io/en/stable/reference/generated/optuna.logging.set_verbosity.html#optuna.logging.set_verbosity + # .INFO: all messages (default) + # .WARNING: fails and warnings + # .ERROR: only errors + optuna.logging.set_verbosity(optuna.logging.ERROR) + + func = lambda trial: IntegratedTransformerOptimization.ReluctanceModel.objective(trial, config, target_and_fixed_parameters) + + study_in_storage = optuna.create_study(study_name=config.integrated_transformer_study_name, + storage=storage, + directions=['minimize', 'minimize'], + load_if_exists=True, sampler=sampler) + + if target_number_trials is not None: + # simulation for a given number of target trials + if len(study_in_storage.trials) < target_number_trials: + study_in_memory = optuna.create_study(directions=['minimize', 'minimize'], + study_name=config.integrated_transformer_study_name, sampler=sampler) + print(f"Sampler is {study_in_memory.sampler.__class__.__name__}") + study_in_memory.add_trials(study_in_storage.trials) + number_trials = target_number_trials - len(study_in_memory.trials) + study_in_memory.optimize(func, n_trials=number_trials, show_progress_bar=True) + study_in_storage.add_trials(study_in_memory.trials[-number_trials:]) + print(f"Finished {number_trials} trials.") + print(f"current time: {datetime.datetime.now()}") + else: + print(f"Study has already {len(study_in_storage.trials)} trials, and target is {target_number_trials} trials.") + + else: + # normal simulation with number_trials + study_in_memory = optuna.create_study(directions=['minimize', 'minimize'], study_name=config.integrated_transformer_study_name, sampler=sampler) print(f"Sampler is {study_in_memory.sampler.__class__.__name__}") - study_in_memory.optimize(func, n_trials=number_trials, n_jobs=-1, gc_after_trial=False) - - # in-memory calculation is shown before saving the data to database - fig = optuna.visualization.plot_pareto_front(study_in_memory, target_names=["volume", "losses"]) - fig.show() - - # introduce study in storage, e.g. sqlite or mysql - if storage == 'sqlite': - # Note: for sqlite operation, there needs to be three slashes '///' even before the path '/home/...' - # Means, in total there are four slashes including the path itself '////home/.../database.sqlite3' - storage = f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3" - elif storage == 'mysql': - storage = "mysql://monty@localhost/mydb", - - study_in_storage = optuna.create_study(directions=["minimize", "minimize"], study_name=study_name, - storage=storage) - study_in_storage.add_trials(study_in_memory.trials) - - @staticmethod - def proceed_study(study_name: str, config: ItoSingleInputConfig, number_trials: int) -> None: - """ - Proceed a study which is stored as sqlite database. - - :param study_name: Name of the study - :type study_name: str - :param config: Simulation configuration - :type config: ItoSingleInputConfig - :param number_trials: Number of trials adding to the existing study - :type number_trials: int - """ - target_and_fixed_parameters = femmt.optimization.IntegratedTransformerOptimization.calculate_fix_parameters(config) - - # Wrap the objective inside a lambda and call objective inside it - func = lambda trial: femmt.optimization.IntegratedTransformerOptimization.ReluctanceModel.NSGAII.objective(trial, config, - target_and_fixed_parameters) - - study = optuna.create_study(study_name=study_name, storage=f"sqlite:///study_{study_name}.sqlite3", - load_if_exists=True) - study.optimize(func, n_trials=number_trials) + study_in_memory.add_trials(study_in_storage.trials) + study_in_memory.optimize(func, n_trials=number_trials, show_progress_bar=True) - @staticmethod - def show_study_results(study_name: str, config: ItoSingleInputConfig) -> None: - """ - Show the results of a study. - - :param study_name: Name of the study - :type study_name: str - :param config: Integrated transformer configuration file - :type config: ItoSingleInputConfig - """ - study = optuna.create_study(study_name=study_name, - storage=f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3", - load_if_exists=True) - - fig = optuna.visualization.plot_pareto_front(study, target_names=["volume", "losses"]) - fig.show() - - ############################## - # load - ############################## - - @staticmethod - def load_study_to_dto(study_name: str, config: ItoSingleInputConfig) -> List[ItoSingleResultFile]: - """ - Load all trials of a study to a DTO-list. - - :param study_name: Name of the study - :type study_name: str - :param config: Integrated transformer configuration file - :type config: ItoSingleInputConfig - :return: List of all trials - :rtype: List[ItoSingleResultFile] - - """ - study = optuna.create_study(study_name=study_name, - storage=f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3", - load_if_exists=True) - - dto_list = [op.OptunaFemmtParser.parse(frozen_object) for frozen_object in study.trials \ - if frozen_object.state == optuna.trial.TrialState.COMPLETE] - - return dto_list - - @staticmethod - def load_study_best_trials_to_dto(study_name: str, config: ItoSingleInputConfig) -> List[ItoSingleResultFile]: - """ - Load the best trials (Pareto front) of a study. + study_in_storage.add_trials(study_in_memory.trials[-number_trials:]) + print(f"Finished {number_trials} trials.") + print(f"current time: {datetime.datetime.now()}") + IntegratedTransformerOptimization.ReluctanceModel.save_config(config) - :param study_name: Name of the study - :type study_name: str - :param config: Integrated transformer configuration file - :type config: ItoSingleInputConfig - :return: List of the best trials. - :rtype: List[ItoSingleResultFile] - - """ - study = optuna.create_study(study_name=study_name, - storage=f"sqlite:///{config.working_directory}/study_{study_name}.sqlite3", - load_if_exists=True) - - print(study.best_trials[0]) - - dto_list = [op.OptunaFemmtParser.parse(frozen_object) for frozen_object in study.best_trials] + @staticmethod + def study_to_df(config: ItoSingleInputConfig) -> pd.DataFrame: + """ + Create a Pandas dataframe from a study. - return dto_list + :param config: configuration + :type config: InductorOptimizationDTO + :return: Study results as Pandas Dataframe + :rtype: pd.DataFrame + """ + database_url = f'sqlite:///{config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}.sqlite3' + if os.path.isfile(database_url.replace('sqlite:///', '')): + print("Existing study found.") + else: + raise ValueError(f"Can not find database: {database_url}") + loaded_study = optuna.load_study(study_name=config.integrated_transformer_study_name, storage=database_url) + df = loaded_study.trials_dataframe() + df.to_csv(f'{config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}.csv') + logging.info(f"Exported study as .csv file: {config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}.csv") + return df ############################# # filter ############################# @staticmethod - def filter_loss_list(valid_design_list: List[ItoSingleResultFile], factor_min_dc_losses: float = 1.2) -> List[ItoSingleResultFile]: + def filter_loss_list_df(df: pd.DataFrame, factor_min_dc_losses: float = 1.2, factor_max_dc_losses: float = 10) -> pd.DataFrame: """ Remove designs with too high losses compared to the minimum losses. - :param valid_design_list: list of valid DTOs - :type valid_design_list: List[ItoSingleResultFile] + :param df: list of valid DTOs + :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] """ # 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) - x_pareto_vec, y_pareto_vec = fo.pareto_front_from_dtos(valid_design_list) + pareto_df: pd.DataFrame = fo.pareto_front_from_df(df) - vector_to_sort = np.array([x_pareto_vec, y_pareto_vec]) + vector_to_sort = np.array([pareto_df["values_0"], pareto_df["values_1"]]) # sorting 2d array by 1st row # https://stackoverflow.com/questions/49374253/sort-a-numpy-2d-array-by-1st-row-maintaining-columns @@ -1086,21 +749,18 @@ def filter_loss_list(valid_design_list: List[ItoSingleResultFile], factor_min_dc x_pareto_vec = sorted_vector[0] y_pareto_vec = sorted_vector[1] - total_losses_list = [] - filtered_design_dto_list = [] - - for dto in valid_design_list: - total_losses_list.append(dto.total_loss) + total_losses_list = df["values_1"][~np.isnan(df['values_1'])].to_numpy() min_total_dc_losses = total_losses_list[np.argmin(total_losses_list)] loss_offset = factor_min_dc_losses * min_total_dc_losses - for dto in valid_design_list: - ref_loss = np.interp(dto.core_2daxi_total_volume, x_pareto_vec, y_pareto_vec) + loss_offset - if dto.total_loss < ref_loss: - filtered_design_dto_list.append(dto) + ref_loss_max = np.interp(df["values_0"], x_pareto_vec, y_pareto_vec) + loss_offset + # clip losses to a maximum of the minimum losses + ref_loss_max = np.clip(ref_loss_max, a_min=-1, a_max=factor_max_dc_losses * min_total_dc_losses) - return filtered_design_dto_list + pareto_df_offset = df[df['values_1'] < ref_loss_max] + + 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]: @@ -1139,104 +799,122 @@ def filter_min_air_gap_length(dto_list_to_filter: List[ItoSingleResultFile], min return filtered_dtos ############################# - # save and load + # show results ############################# @staticmethod - def save_dto_list(result_dto_list: List[ItoSingleResultFile], filepath: str): - """ - Save the ItoSingleResultFile-List to the file structure. - - :param result_dto_list: - :type result_dto_list: List[ItoSingleResultFile] - :param filepath: filepath - :type filepath: str - """ - if not os.path.exists(filepath): - os.mkdir(filepath) + def show_study_results(config: ItoSingleInputConfig) -> None: + """Show the results of a study. - for _, dto in enumerate(result_dto_list): - file_name = os.path.join(filepath, f"case_{dto.case}.json") + A local .html file is generated under config.working_directory to store the interactive plotly plots on disk. - result_dict = dataclasses.asdict(dto) - with open(file_name, "w+", encoding='utf-8') as outfile: - json.dump(result_dict, outfile, indent=2, ensure_ascii=False, cls=MyJSONEncoder) - - @staticmethod - def save_unfiltered_results(config_file: ItoSingleInputConfig, result_file_list: List[ItoSingleResultFile]): + :param config: Integrated transformer configuration file + :type config: ItoSingleInputConfig """ - Save the results of the reluctance model into the file structure. + study = optuna.load_study(study_name=config.integrated_transformer_study_name, + storage=f"sqlite:///{config.integrated_transformer_optimization_directory}/" + f"{config.integrated_transformer_study_name}.sqlite3") - :param config_file: integrated transformer configuration file - :type config_file: ItoSingleInputConfig - :param result_file_list: list of ItoSingleResultFiles - :type result_file_list: List[ItoSingleResultFile] - """ - # generate folder structure - femmt.set_up_folder_structure(config_file.working_directory) - - # save optimization input parameters - config_dict = dataclasses.asdict(config_file) - - integrated_transformer_optimization_input_parameters_file = os.path.join(config_file.working_directory, "optimization_input_parameters.json") - integrated_transformer_reluctance_model_results_directory = os.path.join(config_file.working_directory, "01_reluctance_model_results") - - with open(integrated_transformer_optimization_input_parameters_file, "w+", encoding='utf-8') as outfile: - json.dump(config_dict, outfile, indent=2, ensure_ascii=False, cls=MyJSONEncoder) - - # save reluctance parameters winning candidates - femmt.IntegratedTransformerOptimization.ReluctanceModel.save_dto_list(result_file_list, integrated_transformer_reluctance_model_results_directory) + fig = optuna.visualization.plot_pareto_front(study, targets=lambda t: (t.values[0], t.values[1]), target_names=["volume in m³", "loss in W"]) + fig.update_layout(title=f"{config.integrated_transformer_study_name}
{config.integrated_transformer_optimization_directory}") + fig.write_html(f"{config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}" + f"_{datetime.datetime.now().isoformat(timespec='minutes')}.html") + fig.show() @staticmethod - def load_list(filepath: str) -> List[ItoSingleResultFile]: + def df_plot_pareto_front(*dataframes: pd.DataFrame, label_list: list[str], color_list: list[str] = None, + interactive: bool = True) -> None: """ - Load the list of the reluctance models from the folder structure. - - :param filepath: filepath - :type filepath: str - :return: List of ItoSingleResultFiles - :rtype: List[ItoSingleResultFile] + Plot an interactive Pareto diagram (losses vs. volume) to select the transformers to re-simulate. + + :param dataframes: Dataframe, generated from an optuna study (exported by optuna) + :type dataframes: pd.Dataframe + :param label_list: list of labels for the legend. Same order as df. + :type label_list: list[str] + :param color_list: list of colors for the points and legend. Same order as df. + :type color_list: list[str] + :param interactive: True to show trial numbers if one moves the mouse over it + :type interactive: bool """ - valid_design_list = [] - for file in os.listdir(filepath): - if file.endswith(".json"): - json_file_path = os.path.join(filepath, file) - with open(json_file_path, "r") as fd: - loaded_data_dict = json.loads(fd.read()) + if color_list is None: + color_list = ['red', 'blue', 'green', 'gray'] + for count, df in enumerate(dataframes): + # color_list was before list(ff.colors_femmt_default.keys()) + df['color_r'], df['color_g'], df['color_b'] = ff.colors_femmt_default[color_list[count]] + + df_all = pd.concat(dataframes, axis=0) + color_array = np.transpose(np.array([df_all['color_r'].to_numpy(), df_all['color_g'].to_numpy(), df_all['color_b'].to_numpy()])) / 255 + + names = df_all["number"].to_numpy() + fig, ax = plt.subplots() + legend_list = [] + for count, label_text in enumerate(label_list): + legend_list.append(mpatches.Patch(color=np.array(ff.colors_femmt_default[color_list[count]]) / 255, label=label_text)) + plt.legend(handles=legend_list) + sc = plt.scatter(df_all["values_0"], df_all["values_1"], s=10, c=color_array) + + if interactive: + annot = ax.annotate("", xy=(0, 0), xytext=(20, 20), textcoords="offset points", + bbox=dict(boxstyle="round", fc="w"), + arrowprops=dict(arrowstyle="->")) + annot.set_visible(False) + + def update_annot(ind): + pos = sc.get_offsets()[ind["ind"][0]] + annot.xy = pos + text = f"{[names[n] for n in ind['ind']]}" + annot.set_text(text) + annot.get_bbox_patch().set_alpha(0.4) + + def hover(event): + vis = annot.get_visible() + if event.inaxes == ax: + cont, ind = sc.contains(event) + if cont: + update_annot(ind) + annot.set_visible(True) + fig.canvas.draw_idle() + else: + if vis: + annot.set_visible(False) + fig.canvas.draw_idle() - valid_design_list.append(result_file_dict_to_dto(loaded_data_dict)) - if len(valid_design_list) == 0: - raise ValueError("Specified file path is empty") + fig.canvas.mpl_connect("motion_notify_event", hover) + plt.xlabel('Volume in m³') + plt.ylabel('Losses in W') + plt.grid() + plt.show() - return valid_design_list + ############################# + # save and load + ############################# @staticmethod - def load_unfiltered_results(working_directory: str) -> List[ItoSingleResultFile]: + def save_config(config: ItoSingleInputConfig) -> None: """ - Load the results of the reluctance model and returns the ItoSingleResultFiles as a list. + Save the configuration file as pickle file on the disk. - :param working_directory: working directory - :type working_directory: str - :return: List of ItoSingleResultFiles - :rtype: List[ItoSingleResultFile] + :param config: configuration + :type config: InductorOptimizationDTO """ - integrated_transformer_reluctance_model_results_directory = os.path.join(working_directory, "01_reluctance_model_results") - print(f"Read results from {integrated_transformer_reluctance_model_results_directory}") - return femmt.IntegratedTransformerOptimization.ReluctanceModel.load_list(integrated_transformer_reluctance_model_results_directory) + # convert config path to an absolute filepath + config.inductor_optimization_directory = os.path.abspath(config.integrated_transformer_optimization_directory) + os.makedirs(config.integrated_transformer_optimization_directory, exist_ok=True) + with open(f"{config.integrated_transformer_optimization_directory}/{config.integrated_transformer_study_name}.pkl", 'wb') as output: + pickle.dump(config, output, pickle.HIGHEST_PROTOCOL) @staticmethod - def load_filtered_results(working_directory: str) -> List[ItoSingleResultFile]: + def load_config(config_pickle_filepath: str) -> ItoSingleInputConfig: """ - Load the results of the reluctance model and returns the ItoSingleResultFiles as a list. + Load pickle configuration file from disk. - :param working_directory: working directory - :type working_directory: str - :return: List of ItoSingleResultFiles - :rtype: List[ItoSingleResultFile] + :param config_pickle_filepath: filepath to the pickle configuration file + :type config_pickle_filepath: str + :return: Configuration file as InductorOptimizationDTO object + :rtype: InductorOptimizationDTO """ - integrated_transformer_reluctance_model_results_directory = os.path.join(working_directory, "01_reluctance_model_results_filtered") - print(f"Read results from {integrated_transformer_reluctance_model_results_directory}") - return femmt.IntegratedTransformerOptimization.ReluctanceModel.load_list(integrated_transformer_reluctance_model_results_directory) + with open(config_pickle_filepath, 'rb') as pickle_file_data: + return pickle.load(pickle_file_data) class FemSimulation: """Group functions to perform FEM simulations.""" diff --git a/femmt/optimization/ito_dtos.py b/femmt/optimization/ito_dtos.py index e2a955f2..47227e3d 100644 --- a/femmt/optimization/ito_dtos.py +++ b/femmt/optimization/ito_dtos.py @@ -6,7 +6,38 @@ # 3rd party libraries import numpy as np from materialdatabase.dtos import MaterialCurve +from magnethub.loss import LossModel +from femmt.enumerations import * +@dataclass +class ItoInsulation: + """Insulation definition for the integrated transformer optimization.""" + + # insulation for top core window + iso_window_top_core_top: float + iso_window_top_core_bot: float + iso_window_top_core_left: float + iso_window_top_core_right: float + # insulation for bottom core window + iso_window_bot_core_top: float + iso_window_bot_core_bot: float + iso_window_bot_core_left: float + iso_window_bot_core_right: float + # winding-to-winding insulation + iso_primary_to_primary: float + iso_secondary_to_secondary: float + iso_primary_to_secondary: float + +@dataclass +class IntegratedTransformerMaterialDataSources: + """Data sources for the FEM simulation.""" + + permeability_datasource: MaterialDataSource + permeability_datatype: MeasurementDataType + permeability_measurement_setup: MeasurementSetup + permittivity_datasource: MaterialDataSource + permittivity_datatype: MeasurementDataType + permittivity_measurement_setup: MeasurementSetup @dataclass class ItoSingleInputConfig: @@ -17,21 +48,39 @@ class ItoSingleInputConfig: Also specifies the working directory where to store the results. """ - l_s_target: float + integrated_transformer_study_name: str + integrated_transformer_optimization_directory: str + + # target parameters + l_s12_target: float l_h_target: float n_target: float + + # fix input parameters time_current_1_vec: np.ndarray time_current_2_vec: np.ndarray + + # parameters to optimize material_list: list - core_inner_diameter_min_max_list: list - window_w_min_max_list: list - window_h_top_min_max_list: list - window_h_bot_min_max_list: list + core_name_list: list | None + core_inner_diameter_min_max_list: list | None + window_w_min_max_list: list | None + window_h_top_min_max_list: list | None + window_h_bot_min_max_list: list | None + n_1_top_min_max_list: list + n_1_bot_min_max_list: list + n_2_top_min_max_list: list + n_2_bot_min_max_list: list factor_max_flux_density: float - primary_litz_wire_list: list - secondary_litz_wire_list: list + litz_wire_list_1: list[str] + litz_wire_list_2: list[str] temperature: float - working_directory: str + + # fix parameters: insulations + insulations: ItoInsulation + + # data sources + material_data_sources: IntegratedTransformerMaterialDataSources @dataclass class WorkingDirectories: @@ -54,7 +103,12 @@ class ItoTargetAndFixedParameters: i_rms_1: float i_rms_2: float + i_peak_1: float + 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 @@ -62,29 +116,15 @@ class ItoTargetAndFixedParameters: target_inductance_matrix: np.ndarray working_directories: WorkingDirectories -@dataclass -class SweepTensor: - """ - Dataclass contains the concrete sweep vectors. + # winding 1 + fft_frequency_list_1: List[float] + fft_amplitude_list_1: List[float] + fft_phases_list_1: List[float] - This class is calculated from the integrated-transformer input config file (ItoSingleInputConfig). - ItoSingleInputConfig: core_inner_diameter = [10e-3, 30e-3, 5] - ->> SweepTensor: t1_core_inner_diameter = [10e-3, 15e-3, 20e-3, 25e-3, 30e-3] - """ - - t1_window_h_top: np.ndarray - t1_window_h_bot: np.ndarray - t1_window_w: np.ndarray - t1_core_material: list - t1_core_inner_diameter: np.ndarray - t1_primary_litz_wire_list: list - t1_secondary_litz_wire_list: list - time_current_1_vec: np.ndarray - time_current_2_vec: np.ndarray - l_s_target_value: float - l_h_target_value: float - n_target_value: float - factor_max_flux_density: float + # winding 2 + fft_frequency_list_2: List[float] + fft_amplitude_list_2: List[float] + fft_phases_list_2: List[float] @dataclass class ItoSingleResultFile: @@ -108,8 +148,8 @@ class ItoSingleResultFile: window_w: float core_material: str core_inner_diameter: float - primary_litz_wire: str - secondary_litz_wire: str + litz_wire_name_1: str + litz_wire_name_2: str # reluctance model results flux_top_max: float @@ -119,7 +159,72 @@ class ItoSingleResultFile: flux_density_bot_max: float flux_density_stray_max: float p_hyst: float - primary_litz_wire_loss: float - secondary_litz_wire_loss: float + litz_wire_loss_1: float + litz_wire_loss_2: float core_2daxi_total_volume: float total_loss: float + +@dataclass +class ItoReluctanceModelInput: + """Input DTO for reluctance model simulation within the inductor optimization.""" + + target_inductance_matrix: np.array + core_inner_diameter: float + window_w: float + window_h_bot: float + window_h_top: float + turns_1_top: int + turns_2_top: int + turns_1_bot: int + turns_2_bot: int + litz_wire_name_1: str + litz_wire_diameter_1: float + litz_wire_name_2: str + litz_wire_diameter_2: float + + insulations: ItoInsulation + material_dto: MaterialCurve + magnet_material_model: LossModel + + temperature: float + time_extracted_vec: List + current_extracted_vec_1: List + current_extracted_vec_2: List + fundamental_frequency: float + + i_rms_1: float + i_rms_2: float + + litz_dict_1: dict + litz_dict_2: dict + + # winding 1 + 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] + +@dataclass +class ItoReluctanceModelOutput: + """output DTO for reluctance model simulation within the inductor optimization.""" + + # set additional attributes + p_hyst: float + p_hyst_top: float + p_hyst_bot: float + p_hyst_middle: float + b_max_top: float + b_max_bot: float + b_max_middle: float + winding_1_loss: float + winding_2_loss: float + l_top_air_gap: float + l_bot_air_gap: float + l_middle_air_gap: float + volume: float + area_to_heat_sink: float + p_loss: float diff --git a/femmt/optimization/ito_functions.py b/femmt/optimization/ito_functions.py index 2250fb95..e8871e1a 100644 --- a/femmt/optimization/ito_functions.py +++ b/femmt/optimization/ito_functions.py @@ -219,8 +219,8 @@ def integrated_transformer_fem_simulation_from_result_dto(config_dto: ItoSingleI top, bot = winding_window.split_window(fmt.WindingWindowSplit.HorizontalSplit) # 6. set conductor parameters - primary_litz = ff.litz_database()[dto.primary_litz_wire] - secondary_litz = ff.litz_database()[dto.secondary_litz_wire] + primary_litz = ff.litz_database()[dto.litz_wire_name_1] + secondary_litz = ff.litz_database()[dto.litz_wire_name_2] winding1 = fmt.Conductor(0, fmt.Conductivity.Copper) winding1.set_litz_round_conductor(primary_litz["conductor_radii"], primary_litz["strands_numbers"], diff --git a/femmt/optimization/optuna_femmt_parser.py b/femmt/optimization/optuna_femmt_parser.py index ec65bb1a..b6479cda 100644 --- a/femmt/optimization/optuna_femmt_parser.py +++ b/femmt/optimization/optuna_femmt_parser.py @@ -33,8 +33,8 @@ def parse(frozen_trial: optuna.trial.FrozenTrial) -> ItoSingleResultFile: window_w=frozen_trial.params["window_w"], core_material=frozen_trial.params["material"], core_inner_diameter=frozen_trial.params["core_inner_diameter"], - primary_litz_wire=frozen_trial.params["primary_litz_wire"], - secondary_litz_wire=frozen_trial.params["secondary_litz_wire"], + litz_wire_name_1=frozen_trial.params["primary_litz_wire"], + litz_wire_name_2=frozen_trial.params["secondary_litz_wire"], # reluctance model results flux_top_max=frozen_trial.user_attrs["flux_top_max"], @@ -44,8 +44,8 @@ def parse(frozen_trial: optuna.trial.FrozenTrial) -> ItoSingleResultFile: flux_density_bot_max=frozen_trial.user_attrs["flux_density_bot_max"], flux_density_stray_max=frozen_trial.user_attrs["flux_density_stray_max"], p_hyst=frozen_trial.user_attrs["p_hyst"], - primary_litz_wire_loss=frozen_trial.user_attrs["primary_litz_wire_loss"], - secondary_litz_wire_loss=frozen_trial.user_attrs["secondary_litz_wire_loss"], + litz_wire_loss_1=frozen_trial.user_attrs["primary_litz_wire_loss"], + litz_wire_loss_2=frozen_trial.user_attrs["secondary_litz_wire_loss"], core_2daxi_total_volume=frozen_trial.values[0], total_loss=frozen_trial.values[1], ) diff --git a/femmt/optimization/sto.py b/femmt/optimization/sto.py index cade2e9e..57f3d8f9 100644 --- a/femmt/optimization/sto.py +++ b/femmt/optimization/sto.py @@ -131,7 +131,7 @@ def calculate_fix_parameters(config: StoSingleInputConfig) -> StoTargetAndFixedP @staticmethod def objective(trial: optuna.Trial, config: StoSingleInputConfig, target_and_fixed_parameters: StoTargetAndFixedParameters): """ - Objective funktion to optimize. Uses reluctance model calculation. + Objective function to optimize. Uses reluctance model calculation. Once core_name_list is not None, the objective function uses fixed core sizes. Cores are picked from the core_database(). Otherwise, core_inner_diameter_min_max_list, window_w_min_max_list and window_h_bot_min_max_list are used. @@ -424,7 +424,7 @@ def single_reluctance_model_simulation(reluctance_input: ReluctanceModelInput) - p_winding_1_top = 0 p_winding_1_bot = 0 for count, fft_frequency in enumerate(reluctance_input.fft_frequency_list_1): - proximity_factor_1_top = fmt.calc_proximity_factor_air_gap( + proximity_factor_1_top = fr.calc_proximity_factor_air_gap( litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=reluctance_input.turns_1_top, r_1=reluctance_input.insulations.iso_window_top_core_left, frequency=fft_frequency, winding_area=winding_area_1_top, @@ -437,7 +437,7 @@ def single_reluctance_model_simulation(reluctance_input: ReluctanceModelInput) - (reluctance_input.turns_1_bot * 2 * reluctance_input.primary_litz_dict["conductor_radii"] + \ (reluctance_input.turns_1_bot - 1) * reluctance_input.insulations.iso_primary_to_primary) - proximity_factor_1_bot_inner = fmt.calc_proximity_factor_air_gap( + proximity_factor_1_bot_inner = fr.calc_proximity_factor_air_gap( litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=reluctance_input.turns_1_bot, r_1=reluctance_input.insulations.iso_window_bot_core_left, frequency=fft_frequency, winding_area=winding_area_1_bot, @@ -449,13 +449,13 @@ def single_reluctance_model_simulation(reluctance_input: ReluctanceModelInput) - number_bot_prim_turns_per_column * 2 * reluctance_input.primary_litz_dict["conductor_radii"] + \ (number_bot_prim_turns_per_column - 1) * reluctance_input.insulations.iso_primary_to_primary)) - proximity_factor_1_bot_inner = fmt.calc_proximity_factor_air_gap( + proximity_factor_1_bot_inner = fr.calc_proximity_factor_air_gap( litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=number_bot_prim_turns_per_column, r_1=reluctance_input.insulations.iso_window_bot_core_left, frequency=fft_frequency, winding_area=winding_area_1_bot, litz_wire_material_name='Copper', temperature=reluctance_input.temperature) - proximity_factor_1_bot_outer = fmt.calc_proximity_factor( + proximity_factor_1_bot_outer = fr.calc_proximity_factor( litz_wire_name=reluctance_input.litz_wire_name_1, number_turns=reluctance_input.turns_1_bot - number_bot_prim_turns_per_column, window_h=reluctance_input.window_h_bot, iso_core_top=reluctance_input.insulations.iso_window_bot_core_top, iso_core_bot=reluctance_input.insulations.iso_window_bot_core_bot, @@ -468,7 +468,7 @@ def single_reluctance_model_simulation(reluctance_input: ReluctanceModelInput) - p_winding_2 = 0 for count, fft_frequency in enumerate(reluctance_input.fft_frequency_list_2): - proximity_factor_assumption_2 = fmt.calc_proximity_factor( + proximity_factor_assumption_2 = fr.calc_proximity_factor( litz_wire_name=reluctance_input.litz_wire_name_2, number_turns=reluctance_input.turns_2_bot, window_h=reluctance_input.window_h_bot, iso_core_top=reluctance_input.insulations.iso_window_bot_core_top, iso_core_bot=reluctance_input.insulations.iso_window_bot_core_bot, frequency=fft_frequency, litz_wire_material_name='Copper', temperature=reluctance_input.temperature) @@ -643,7 +643,7 @@ def df_plot_pareto_front(*dataframes: list[pd.DataFrame], label_list: list[str], :type interactive: bool """ if color_list is None: - color_list = ['red', 'blue', 'green', 'grey'] + color_list = ['red', 'blue', 'green', 'gray'] for count, df in enumerate(dataframes): # color_list was before list(ff.colors_femmt_default.keys()) df['color_r'], df['color_g'], df['color_b'] = ff.colors_femmt_default[color_list[count]] @@ -783,13 +783,13 @@ def fem_simulations_from_reluctance_df(reluctance_df: pd.DataFrame, config: StoS """ Perform FEM simulations from a given Pandas dataframe. The dataframe is from the reluctance model results. - :param reluctance_df: Pandas dataframe containing relults from the relutance model + :param reluctance_df: Pandas dataframe containing results from the reluctance model :type reluctance_df: pandas.DataFrame :param config: Configuration for the optimization of the transformer :type config: StoSingleInputConfig - :param show_visual_outputs: Ture to show visual outputs like the geometry + :param show_visual_outputs: True to show visual outputs like the geometry :type show_visual_outputs: bool - :param process_number: Process number for parallel simulations on multiple cpu cores + :param process_number: Process number for parallel simulations on multiple CPU cores :type process_number: int """ target_and_fix_parameters = StackedTransformerOptimization.ReluctanceModel.calculate_fix_parameters(config) @@ -1055,9 +1055,9 @@ def full_simulation(df_geometry: pd.DataFrame, current_waveform: List, stacked_t :type stacked_transformer_config_filepath: str :param process_number: process number to run the simulation on :type process_number: int - :param show_visual_outputs: True to show visual outpus (geometries) + :param show_visual_outputs: True to show visual outputs (geometries) :type show_visual_outputs: bool - :param print_derivations: True to print derivation from FEM simulaton to reluctance model + :param print_derivations: True to print derivation from FEM simulation to reluctance model :type print_derivations: bool """ for index, _ in df_geometry.iterrows(): diff --git a/femmt/optimization/sto_dtos.py b/femmt/optimization/sto_dtos.py index c3f39dd4..59782a8c 100644 --- a/femmt/optimization/sto_dtos.py +++ b/femmt/optimization/sto_dtos.py @@ -110,7 +110,7 @@ class StoSingleInputConfig: 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: list + window_h_bot_min_max_list: Optional[List[float]] primary_litz_wire_list: list secondary_litz_wire_list: list diff --git a/femmt/reluctance.py b/femmt/reluctance.py index 6220554a..e82b12a4 100644 --- a/femmt/reluctance.py +++ b/femmt/reluctance.py @@ -68,7 +68,7 @@ def plot_r_basic(): (using Schwarz-Christoffel transformation) at page no. 35. It plots the Reluctance formula with respect to its variables (h/l and w/l). - It is an independent function and has been used to analyse the expression and its limitation. + It is an independent function and has been used to analyze the expression and its limitation. """ # # Uncomment this section of code if Reluctance change with respect to h/l is desired # width = 100 @@ -217,8 +217,8 @@ def create_data_matrix(core_inner_diameter: list, window_h: list, window_w: list :param mu_rel: Relative permeability of the core [in F/m] :type mu_rel: list :param mult_air_gap_type: Two types of equally distributed air-gaps (used only for air-gaps more than 1) - Type 1: Equally distributed air-gaps including corner air-gaps (eg: air-gaps-position = [0, 50, 100] for 3 air-gaps) - Type 2: Equally distributed air-gaps excluding corner air-gaps (eg: air-gaps-position = [25, 50, 75] for 3 air-gaps) + Type 1: Equally distributed air-gaps including corner air-gaps (e.g.: air-gaps-position = [0, 50, 100] for 3 air-gaps) + Type 2: Equally distributed air-gaps excluding corner air-gaps (e.g.: air-gaps-position = [25, 50, 75] for 3 air-gaps) :type mult_air_gap_type: list """ # Structure: data_matrix = [core_inner_diameter, window_h, window_w, mu_rel, no_of_turns, n_air_gaps, air_gap_h, @@ -284,7 +284,7 @@ def __init__(self, core_inner_diameter: list, window_h: list, window_w: list, no air_gap_h: list, air_gap_position: list, mu_r_abs: list, mult_air_gap_type: list = None, air_gap_method: str = 'Percent', component_type: str = 'inductor', sim_type: str = 'single'): """ - Init the MagneticCircuit class. + Initialize the MagneticCircuit class. :param core_inner_diameter: Diameter of center leg of the core in meter :type core_inner_diameter: list @@ -303,8 +303,8 @@ def __init__(self, core_inner_diameter: list, window_h: list, window_w: list, no :param mu_r_abs: Relative permeability of the core [in F/m] :type mu_r_abs: list :param mult_air_gap_type: Two types of equally distributed air-gaps (used only for air-gaps more than 1) - Type 1: Equally distributed air-gaps including corner air-gaps (eg: air-gaps-position = [0, 50, 100] for 3 air-gaps) - Type 2: Equally distributed air-gaps excluding corner air-gaps (eg: air-gaps-position = [25, 50, 75] for 3 air-gaps) + Type 1: Equally distributed air-gaps including corner air-gaps (e.g.: air-gaps-position = [0, 50, 100] for 3 air-gaps) + Type 2: Equally distributed air-gaps excluding corner air-gaps (e.g.: air-gaps-position = [25, 50, 75] for 3 air-gaps) :type mult_air_gap_type: list :param air_gap_h: Air-gap height [in meter] :param air_gap_method: Input method of air gap position ( either in 'Percent', 'Center' or 'Manually') diff --git a/femmt/thermal/__init__.py b/femmt/thermal/__init__.py index 80610457..afb1141c 100644 --- a/femmt/thermal/__init__.py +++ b/femmt/thermal/__init__.py @@ -1,4 +1,4 @@ -"""Init file for the thermal section.""" +"""Initialize file for the thermal section.""" from femmt.thermal.thermal_classes import * from femmt.thermal.thermal_functions import * from femmt.thermal.thermal_simulation import * diff --git a/femmt/thermal/thermal_classes.py b/femmt/thermal/thermal_classes.py index c14e1fff..43d43136 100644 --- a/femmt/thermal/thermal_classes.py +++ b/femmt/thermal/thermal_classes.py @@ -141,17 +141,17 @@ def __init__(self): self.q_vol = {} @staticmethod - def dict_as_function_str(name: str, dct: Dict): + def dict_as_function_str(name: str, dictionary: Dict): """ Write dictionary as a string. :param name: name :type name: str - :param dct: Dictionary - :type dct: Dict + :param dictionary: Dictionary + :type dictionary: Dict """ dict_as_str = "" - for key, value in dct.items(): + for key, value in dictionary.items(): dict_as_str += f"\t{name}[{key}] = {value};\n" return dict_as_str diff --git a/tests/unit/test_femmt_functions.py b/tests/unit/test_femmt_functions.py index da622465..e4e04c17 100644 --- a/tests/unit/test_femmt_functions.py +++ b/tests/unit/test_femmt_functions.py @@ -162,10 +162,10 @@ def test_conductivity_temperature(): temperature = 100 copper_sigma_100_degree_calculated = femmt.conductivity_temperature("Copper", temperature) - aluminium_sigma_100_degree_calculated = femmt.conductivity_temperature("Aluminium", temperature) + aluminum_sigma_100_degree_calculated = femmt.conductivity_temperature("Aluminum", temperature) assert copper_sigma_100_degree_calculated == pytest.approx(4.4874e7, rel=1e-3) - assert aluminium_sigma_100_degree_calculated == pytest.approx(2.8627e7, rel=1e-3) + assert aluminum_sigma_100_degree_calculated == pytest.approx(2.8627e7, rel=1e-3) def test_skin_depth(): """Unittest for calculating the skin depth.""" diff --git a/tests/unit/test_functions_reluctance.py b/tests/unit/test_functions_reluctance.py new file mode 100644 index 00000000..aafde1f7 --- /dev/null +++ b/tests/unit/test_functions_reluctance.py @@ -0,0 +1,20 @@ +"""Unit tests for the functions_reluctance.py module.""" +# 3rd party libraries +import pytest + +# own modules +import femmt.functions_reluctance as fr + +def test_resistance_litz_wire(): + """Test function for resistance_litz_wire().""" + # horizontal winding scheme + resistance_horizontal = fr.resistance_litz_wire( + 0.01, window_w=0.01, window_h=0.012, turns_count=20, iso_core_top=1e-3, iso_core_bot=1e-3, iso_core_left=1e-3, iso_core_right=1e-3, + iso_primary_to_primary=10e-6, temperature=100, scheme="horizontal_first", material="Copper", litz_wire_name="1.1x60x0.1") + assert resistance_horizontal == pytest.approx(0.05772275287356322) + + # vertical winding scheme + resistance_vertical = fr.resistance_litz_wire( + 0.01, window_w=0.01, window_h=0.012, turns_count=20, iso_core_top=1e-3, iso_core_bot=1e-3, iso_core_left=1e-3, iso_core_right=1e-3, + iso_primary_to_primary=10e-6, temperature=100, scheme="vertical_first", material="Copper", litz_wire_name="1.1x60x0.1") + assert resistance_vertical == pytest.approx(0.043211097701149434)