From f57a0948200e21bc1e5cf8208586298c32011685 Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Thu, 2 Oct 2025 16:18:14 +0200 Subject: [PATCH 01/12] add filters for the complex permeability parameter fitting (amplitude and losses) --- examples/export_material2grid.py | 9 +- .../processing/complex_permeability.py | 137 +++++++++++++++--- 2 files changed, 123 insertions(+), 23 deletions(-) diff --git a/examples/export_material2grid.py b/examples/export_material2grid.py index 6f9c150..c334be2 100644 --- a/examples/export_material2grid.py +++ b/examples/export_material2grid.py @@ -23,11 +23,13 @@ def export_material2grid_example(): permeability = mdb_data.get_complex_permeability(material=material_name, data_source=permeability_data_source, pv_fit_function=mdb.FitFunction.enhancedSteinmetz) - permeability.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_permeability_grid.txt"), + permeability.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_reduced_permeability_grid.txt"), frequencies=np.linspace(1e5, 1.5e6, 50), temperatures=np.linspace(25, 70, 20), - b_vals=np.linspace(0, 0.2, 50)) - print(f"Exemplary complex permeability data: \n {permeability.measurement_data} \n") + b_vals=np.linspace(0, 0.2, 50), + f_min_measurement=1e5, f_max_measurement=None, + T_min_measurement=28, T_max_measurement=None, + b_min_measurement=None, b_max_measurement=0.15) # Permittivity permittivity = mdb_data.get_complex_permittivity(material=material_name, @@ -35,7 +37,6 @@ def export_material2grid_example(): permittivity.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permittivity_data_source.value}_permittivity_grid.txt"), frequencies=np.linspace(1e5, 1.5e6, 50), temperatures=np.linspace(25, 70, 20)) - print(f"Exemplary complex permittivity data: \n {permittivity.measurement_data} \n ") if __name__ == '__main__': diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index 3feda6d..e6e4adf 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -1,7 +1,7 @@ """Class to represent the data structure and load material data.""" # python libraries import os -from typing import Tuple +from typing import Tuple, Optional # 3rd party libraries import numpy as np @@ -91,7 +91,41 @@ def generate_1d_interpolation_arrays(self, f_points: int = 20, T_points: int = 2 return grid - def fit_permeability_magnitude(self) -> Any: + @staticmethod + def filter_fTb(df: pd.DataFrame, + f_min: Optional[float] = None, f_max: Optional[float] = None, + T_min: Optional[float] = None, T_max: Optional[float] = None, + b_min: Optional[float] = None, b_max: Optional[float] = None) -> pd.DataFrame: + """ + Filter a material dataframe df for min/max frequency, temperature, and flux density. + + :param df: original material dataframe + :param f_min: minimum frequency (default: None) + :param f_max: maximum frequency (default: None) + :param T_min: minimum temperature (default: None) + :param T_max: maximum temperature (default: None) + :param b_min: minimum flux density (default: None) + :param b_max: maximum flux density (default: None) + :return: filtered material dataframe + """ + if f_min is not None: + df = df[df['f'] >= f_min] + if f_max is not None: + df = df[df['f'] <= f_max] + if T_min is not None: + df = df[df['T'] >= T_min] + if T_max is not None: + df = df[df['T'] <= T_max] + if b_min is not None: + df = df[df['b'] >= b_min] + if b_max is not None: + df = df[df['b'] <= b_max] + return df + + def fit_permeability_magnitude(self, + f_min: Optional[float] = None, f_max: Optional[float] = None, + T_min: Optional[float] = None, T_max: Optional[float] = None, + b_min: Optional[float] = None, b_max: Optional[float] = None) -> Any: """ Fit the permeability magnitude μ_abs as a function of frequency, temperature, and magnetic flux density. @@ -100,41 +134,89 @@ def fit_permeability_magnitude(self) -> Any: It then fits this data using a predefined model function `fit_mu_abs_...(f, T, b, ...)`. + :param f_min: measurements for lower frequencies will be excluded from fitting + :param f_max: measurements for higher frequencies will be excluded from fitting + :param T_min: measurements for lower temperatures will be excluded from fitting + :param T_max: measurements for higher temperatures will be excluded from fitting + :param b_min: measurements for lower flux densities will be excluded from fitting + :param b_max: measurements for higher flux densities will be excluded from fitting :return: Fitted parameters (popt_mu_abs) of the μ_abs model. :rtype: np.ndarray """ + fit_data = self.filter_fTb(self.measurement_data, + f_min=f_min, f_max=f_max, + T_min=T_min, T_max=T_max, + b_min=b_min, b_max=b_max) + fit_mu_a = self.mu_a_fit_function.get_function() - mu_a = np.sqrt(self.measurement_data["mu_real"] ** 2 + self.measurement_data["mu_imag"] ** 2) + logger.info(f"\n" + f"Fitting of the permeability amplitude with the fit function'{self.mu_a_fit_function.value}'.\n" + f" Following limits are applied to the measurement data:\n" + f" {f_min = }\n" + f" {f_max = }\n" + f" {T_min = }\n" + f" {T_max = }\n" + f" {b_min = }\n" + f" {b_max = }\n" + f" Following data is used for the loss fitting:\n " + f" {fit_data}") + + mu_a = np.sqrt(fit_data["mu_real"] ** 2 + fit_data["mu_imag"] ** 2) popt_mu_a, pcov_mu_a = curve_fit(fit_mu_a, - (self.measurement_data["f"], - self.measurement_data["T"], - self.measurement_data["b"]), + (fit_data["f"], + fit_data["T"], + fit_data["b"]), mu_a, maxfev=int(1e6)) self.params_mu_a = popt_mu_a return popt_mu_a - def fit_losses(self) -> Any: + def fit_losses(self, + f_min: Optional[float] = None, f_max: Optional[float] = None, + T_min: Optional[float] = None, T_max: Optional[float] = None, + b_min: Optional[float] = None, b_max: Optional[float] = None) -> Any: """ Fit the magnetic power loss density p_v as a function of frequency, temperature, and magnetic flux density. The losses are calculated from the imaginary part of the permeability using the helper function `pv_mag()`, and then fitted using e.g. the Steinmetz-based `..._steinmetz_...(f, T, b, ...)`. + :param f_min: measurements for lower frequencies will be excluded from fitting + :param f_max: measurements for higher frequencies will be excluded from fitting + :param T_min: measurements for lower temperatures will be excluded from fitting + :param T_max: measurements for higher temperatures will be excluded from fitting + :param b_min: measurements for lower flux densities will be excluded from fitting + :param b_max: measurements for higher flux densities will be excluded from fitting :return: Fitted parameters (popt_pv) of the Steinmetz-based power loss model. :rtype: np.ndarray """ log_pv_fit_function = self.pv_fit_function.get_log_function() - pv_fit_function = self.pv_fit_function.get_function() - mu_abs = np.sqrt(self.measurement_data["mu_real"] ** 2 + self.measurement_data["mu_imag"] ** 2) - pv = pv_mag(self.measurement_data["f"].to_numpy(), - (-self.measurement_data["mu_imag"] * mu_0).to_numpy(), - (self.measurement_data["b"] / mu_abs / mu_0).to_numpy()) + fit_data = self.filter_fTb(self.measurement_data, + f_min=f_min, f_max=f_max, + T_min=T_min, T_max=T_max, + b_min=b_min, b_max=b_max) + + logger.info(f"\n" + f"Fitting of the magnetic losses with the fit function'{self.pv_fit_function.value}'.\n" + f" Following limits are applied to the measurement data:\n" + f" {f_min = }\n" + f" {f_max = }\n" + f" {T_min = }\n" + f" {T_max = }\n" + f" {b_min = }\n" + f" {b_max = }\n" + f" Following data is used for the loss fitting:\n " + f" {fit_data}") + + mu_abs = np.sqrt(fit_data["mu_real"] ** 2 + fit_data["mu_imag"] ** 2) + pv = pv_mag(fit_data["f"].to_numpy(), + (-fit_data["mu_imag"] * mu_0).to_numpy(), + (fit_data["b"] / mu_abs / mu_0).to_numpy()) popt_pv, pcov_pv = curve_fit(log_pv_fit_function, - (self.measurement_data["f"], - self.measurement_data["T"], - self.measurement_data["b"]), + (fit_data["f"], + fit_data["T"], + fit_data["b"]), np.log(pv), maxfev=100000) self.params_pv = popt_pv return popt_pv @@ -213,8 +295,15 @@ def txt2grid3d(df: pd.DataFrame, path: os.PathLike | str) -> None: mu_imag = df[4].values.tolist() f.write(str(mu_imag)[1:-1]) - def export_to_txt(self, path: str | os.PathLike, frequencies: npt.NDArray[Any], temperatures: npt.NDArray[Any], - b_vals: npt.NDArray[Any]) -> None: + def export_to_txt(self, + path: str | os.PathLike, + frequencies: npt.NDArray[Any], + temperatures: npt.NDArray[Any], + b_vals: npt.NDArray[Any], + f_min_measurement: Optional[float] = None, f_max_measurement: Optional[float] = None, + T_min_measurement: Optional[float] = None, T_max_measurement: Optional[float] = None, + b_min_measurement: Optional[float] = None, b_max_measurement: Optional[float] = None + ) -> None: """ Export fitted permeability data (real & imaginary parts) to a txt grid file. @@ -222,11 +311,21 @@ def export_to_txt(self, path: str | os.PathLike, frequencies: npt.NDArray[Any], :param frequencies: frequencies for the interpolation grid :param temperatures: temperatures for the interpolation grid :param b_vals: magnetic flux density values for the interpolation grid + :param f_min_measurement: measurements for lower frequencies will be excluded from fitting + :param f_max_measurement: measurements for higher frequencies will be excluded from fitting + :param T_min_measurement: measurements for lower temperatures will be excluded from fitting + :param T_max_measurement: measurements for higher temperatures will be excluded from fitting + :param b_min_measurement: measurements for lower flux densities will be excluded from fitting + :param b_max_measurement: measurements for higher flux densities will be excluded from fitting """ if self.params_pv is None: - self.fit_losses() + self.fit_losses(f_min_measurement, f_max_measurement, + T_min_measurement, T_max_measurement, + b_min_measurement, b_max_measurement) if self.params_mu_a is None: - self.fit_permeability_magnitude() + self.fit_permeability_magnitude(f_min_measurement, f_max_measurement, + T_min_measurement, T_max_measurement, + b_min_measurement, b_max_measurement) records: list[list[float]] = [] for T in temperatures: From c5118ad00f933212773b950ff3a230750c76a87d Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Thu, 2 Oct 2025 16:23:15 +0200 Subject: [PATCH 02/12] renamed the export file name --- examples/export_material2grid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/export_material2grid.py b/examples/export_material2grid.py index c334be2..fef6aa0 100644 --- a/examples/export_material2grid.py +++ b/examples/export_material2grid.py @@ -23,7 +23,7 @@ def export_material2grid_example(): permeability = mdb_data.get_complex_permeability(material=material_name, data_source=permeability_data_source, pv_fit_function=mdb.FitFunction.enhancedSteinmetz) - permeability.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_reduced_permeability_grid.txt"), + permeability.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_permeability_grid.txt"), frequencies=np.linspace(1e5, 1.5e6, 50), temperatures=np.linspace(25, 70, 20), b_vals=np.linspace(0, 0.2, 50), From 3435dcfbf4a681f23e5e9f71cf5a69de1444b6f3 Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Tue, 7 Oct 2025 13:04:03 +0200 Subject: [PATCH 03/12] split export and grid generation of material data --- examples/export_material2grid.py | 28 +++++++------ .../processing/complex_permeability.py | 39 +++++++++---------- .../processing/complex_permittivity.py | 19 ++++----- 3 files changed, 45 insertions(+), 41 deletions(-) diff --git a/examples/export_material2grid.py b/examples/export_material2grid.py index fef6aa0..7bd6d33 100644 --- a/examples/export_material2grid.py +++ b/examples/export_material2grid.py @@ -9,6 +9,7 @@ # configure logging to show femmt terminal output logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) + def export_material2grid_example(): """Get different material data example.""" # init a material database instance @@ -16,27 +17,30 @@ def export_material2grid_example(): path2grid_export = Path(get_user_paths().grid_export_data) print(f"Data exported to {path2grid_export}") material_name = mdb.Material.N49 - permeability_data_source = mdb.DataSource.LEA_MTB - permittivity_data_source = mdb.DataSource.LEA_MTB # Permeability + permeability_data_source = mdb.DataSource.LEA_MTB + link2permeability_grid = path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_permeability_grid.txt") permeability = mdb_data.get_complex_permeability(material=material_name, data_source=permeability_data_source, pv_fit_function=mdb.FitFunction.enhancedSteinmetz) - permeability.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_permeability_grid.txt"), - frequencies=np.linspace(1e5, 1.5e6, 50), - temperatures=np.linspace(25, 70, 20), - b_vals=np.linspace(0, 0.2, 50), - f_min_measurement=1e5, f_max_measurement=None, - T_min_measurement=28, T_max_measurement=None, - b_min_measurement=None, b_max_measurement=0.15) + df_permeability_grid = permeability.to_grid(grid_frequency=np.linspace(1e5, 1.5e6, 50), + grid_temperature=np.linspace(25, 70, 20), + grid_flux_density=np.linspace(0, 0.2, 50), + f_min_measurement=1e5, f_max_measurement=None, + T_min_measurement=28, T_max_measurement=None, + b_min_measurement=None, b_max_measurement=0.15) + + permeability.grid2txt(df_permeability_grid, link2permeability_grid) # Permittivity + permittivity_data_source = mdb.DataSource.LEA_MTB + link2permittivity_grid = path2grid_export.joinpath(f"{material_name.value}_{permittivity_data_source.value}_permittivity_grid.txt") permittivity = mdb_data.get_complex_permittivity(material=material_name, data_source=permittivity_data_source) - permittivity.export_to_txt(path2grid_export.joinpath(f"{material_name.value}_{permittivity_data_source.value}_permittivity_grid.txt"), - frequencies=np.linspace(1e5, 1.5e6, 50), - temperatures=np.linspace(25, 70, 20)) + df_permittivity_grid = permittivity.to_grid(grid_frequency=np.linspace(1e5, 1.5e6, 50), + grid_temperature=np.linspace(25, 70, 20)) + permittivity.grid2txt(df_permittivity_grid, link2permittivity_grid) if __name__ == '__main__': diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index e6e4adf..21e4de4 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -265,7 +265,7 @@ def fit_real_and_imaginary_part_at_f_and_T( return np.array(mu_real), np.array(mu_imag) @staticmethod - def txt2grid3d(df: pd.DataFrame, path: os.PathLike | str) -> None: + def grid2txt(df: pd.DataFrame, path: os.PathLike | str) -> None: """ Export 3D permeability data to grid text file format. @@ -295,22 +295,20 @@ def txt2grid3d(df: pd.DataFrame, path: os.PathLike | str) -> None: mu_imag = df[4].values.tolist() f.write(str(mu_imag)[1:-1]) - def export_to_txt(self, - path: str | os.PathLike, - frequencies: npt.NDArray[Any], - temperatures: npt.NDArray[Any], - b_vals: npt.NDArray[Any], - f_min_measurement: Optional[float] = None, f_max_measurement: Optional[float] = None, - T_min_measurement: Optional[float] = None, T_max_measurement: Optional[float] = None, - b_min_measurement: Optional[float] = None, b_max_measurement: Optional[float] = None - ) -> None: + def to_grid(self, + grid_frequency: npt.NDArray[Any], + grid_temperature: npt.NDArray[Any], + grid_flux_density: npt.NDArray[Any], + f_min_measurement: Optional[float] = None, f_max_measurement: Optional[float] = None, + T_min_measurement: Optional[float] = None, T_max_measurement: Optional[float] = None, + b_min_measurement: Optional[float] = None, b_max_measurement: Optional[float] = None + ) -> pd.DataFrame: """ Export fitted permeability data (real & imaginary parts) to a txt grid file. - :param path: path to exported txt-file - :param frequencies: frequencies for the interpolation grid - :param temperatures: temperatures for the interpolation grid - :param b_vals: magnetic flux density values for the interpolation grid + :param grid_frequency: frequencies for the interpolation grid + :param grid_temperature: temperatures for the interpolation grid + :param grid_flux_density: magnetic flux density values for the interpolation grid :param f_min_measurement: measurements for lower frequencies will be excluded from fitting :param f_max_measurement: measurements for higher frequencies will be excluded from fitting :param T_min_measurement: measurements for lower temperatures will be excluded from fitting @@ -328,11 +326,12 @@ def export_to_txt(self, b_min_measurement, b_max_measurement) records: list[list[float]] = [] - for T in temperatures: - for f in frequencies: - mu_real, mu_imag = self.fit_real_and_imaginary_part_at_f_and_T(f, T, b_vals) - for i, b in enumerate(b_vals): + for T in grid_temperature: + for f in grid_frequency: + mu_real, mu_imag = self.fit_real_and_imaginary_part_at_f_and_T(f, T, grid_flux_density) + for i, b in enumerate(grid_flux_density): records.append([f, T, b, mu_real[i], mu_imag[i]]) - df_export: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3, 4]) - self.txt2grid3d(df_export, path) + df_grid: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3, 4]) + return df_grid + diff --git a/materialdatabase/processing/complex_permittivity.py b/materialdatabase/processing/complex_permittivity.py index 6f21dcc..cbf7596 100644 --- a/materialdatabase/processing/complex_permittivity.py +++ b/materialdatabase/processing/complex_permittivity.py @@ -171,7 +171,7 @@ def fit_real_and_imaginary_part_at_f_and_T(self, f: float, T: float) -> tuple[fl return eps_real, eps_imag @staticmethod - def txt2grid2d(df: pd.DataFrame, path: os.PathLike | str) -> None: + def grid2txt(df: pd.DataFrame, path: os.PathLike | str) -> None: """ Export 2D permittivity data to grid text file format. @@ -197,13 +197,14 @@ def txt2grid2d(df: pd.DataFrame, path: os.PathLike | str) -> None: eps_imag = df[3].values.tolist() f.write(str(eps_imag)[1:-1]) - def export_to_txt(self, path: str | os.PathLike, frequencies: npt.NDArray[Any], temperatures: npt.NDArray[Any]) -> None: + def to_grid(self, + grid_frequency: npt.NDArray[Any], + grid_temperature: npt.NDArray[Any]) -> pd.DataFrame: """ Export fitted permittivity data (real & imaginary parts) to a txt grid file. - :param path: path to exported txt-file - :param frequencies: frequencies for the interpolation grid - :param temperatures: temperatures for the interpolation grid + :param grid_frequency: frequencies for the interpolation grid + :param grid_temperature: temperatures for the interpolation grid """ if self.params_eps_a is None: self.fit_permittivity_magnitude() @@ -211,10 +212,10 @@ def export_to_txt(self, path: str | os.PathLike, frequencies: npt.NDArray[Any], self.fit_loss_angle() records: list[list[float]] = [] - for T in temperatures: - for f in frequencies: + for T in grid_temperature: + for f in grid_frequency: eps_real, eps_imag = self.fit_real_and_imaginary_part_at_f_and_T(f, T) records.append([f, T, eps_real, eps_imag]) - df_export: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3]) - self.txt2grid2d(df_export, path) + df_grid: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3]) + return df_grid From 7b710422ce3af97451e6bacc672a4f9b26b08a8e Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Tue, 7 Oct 2025 13:07:18 +0200 Subject: [PATCH 04/12] linting --- materialdatabase/processing/complex_permeability.py | 1 - 1 file changed, 1 deletion(-) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index 21e4de4..beb3df3 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -334,4 +334,3 @@ def to_grid(self, df_grid: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3, 4]) return df_grid - From 12125f2f484faf1a7c162dcb1237f3d789180e04 Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Tue, 7 Oct 2025 13:26:43 +0200 Subject: [PATCH 05/12] replaced positions by column names --- examples/export_material2grid.py | 2 +- .../processing/complex_permeability.py | 14 ++++++++------ .../processing/complex_permittivity.py | 12 ++++++------ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/examples/export_material2grid.py b/examples/export_material2grid.py index 7bd6d33..2262096 100644 --- a/examples/export_material2grid.py +++ b/examples/export_material2grid.py @@ -15,6 +15,7 @@ def export_material2grid_example(): # init a material database instance mdb_data = mdb.Data() path2grid_export = Path(get_user_paths().grid_export_data) + path2grid_plot = Path(get_user_paths().graphics) print(f"Data exported to {path2grid_export}") material_name = mdb.Material.N49 @@ -30,7 +31,6 @@ def export_material2grid_example(): f_min_measurement=1e5, f_max_measurement=None, T_min_measurement=28, T_max_measurement=None, b_min_measurement=None, b_max_measurement=0.15) - permeability.grid2txt(df_permeability_grid, link2permeability_grid) # Permittivity diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index beb3df3..e5f22da 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -7,6 +7,8 @@ import numpy as np import pandas as pd from scipy.optimize import curve_fit +from scipy.interpolate import griddata +from matplotlib import pyplot as plt # own libraries from materialdatabase.meta.data_enums import * @@ -276,23 +278,23 @@ def grid2txt(df: pd.DataFrame, path: os.PathLike | str) -> None: f.write("%Grid\n") # magnetic flux density - b_grid = sorted(set(df[2].values.tolist())) + b_grid = sorted(set(df["b"].values.tolist())) f.write(str(b_grid)[1:-1] + "\n") # frequency - f_grid = sorted(set(df[0].values.tolist())) + f_grid = sorted(set(df["f"].values.tolist())) f.write(str(f_grid)[1:-1] + "\n") # temperature - T_grid = sorted(set(df[1].values.tolist())) + T_grid = sorted(set(df["T"].values.tolist())) f.write(str(T_grid)[1:-1] + "\n") f.write("%Data\n") - mu_real = df[3].values.tolist() + mu_real = df["mu_real"].values.tolist() f.write(str(mu_real)[1:-1] + "\n") f.write("%Data\n") - mu_imag = df[4].values.tolist() + mu_imag = df["mu_imag"].values.tolist() f.write(str(mu_imag)[1:-1]) def to_grid(self, @@ -332,5 +334,5 @@ def to_grid(self, for i, b in enumerate(grid_flux_density): records.append([f, T, b, mu_real[i], mu_imag[i]]) - df_grid: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3, 4]) + df_grid: pd.DataFrame = pd.DataFrame(records, columns=["f", "T", "b", "mu_real", "mu_imag"]) return df_grid diff --git a/materialdatabase/processing/complex_permittivity.py b/materialdatabase/processing/complex_permittivity.py index cbf7596..423e628 100644 --- a/materialdatabase/processing/complex_permittivity.py +++ b/materialdatabase/processing/complex_permittivity.py @@ -175,26 +175,26 @@ def grid2txt(df: pd.DataFrame, path: os.PathLike | str) -> None: """ Export 2D permittivity data to grid text file format. - :param df: Expected DataFrame columns: [frequency, temperature, real part of permeability, imaginary part of permeability] + :param df: Expected DataFrame columns: [frequency, temperature, real part of permittivity, imaginary part of permittivity] :param path: path where the txt file should be stored """ with open(path, "w+") as f: f.write("%Grid\n") # frequency - f_grid = sorted(set(df[0].values.tolist())) + f_grid = sorted(set(df["f"].values.tolist())) f.write(str(f_grid)[1:-1] + "\n") # temperature - T_grid = sorted(set(df[1].values.tolist())) + T_grid = sorted(set(df["T"].values.tolist())) f.write(str(T_grid)[1:-1] + "\n") f.write("%Data\n") - eps_real = df[2].values.tolist() + eps_real = df["eps_real"].values.tolist() f.write(str(eps_real)[1:-1] + "\n") f.write("%Data\n") - eps_imag = df[3].values.tolist() + eps_imag = df["eps_imag"].values.tolist() f.write(str(eps_imag)[1:-1]) def to_grid(self, @@ -217,5 +217,5 @@ def to_grid(self, eps_real, eps_imag = self.fit_real_and_imaginary_part_at_f_and_T(f, T) records.append([f, T, eps_real, eps_imag]) - df_grid: pd.DataFrame = pd.DataFrame(records, columns=[0, 1, 2, 3]) + df_grid: pd.DataFrame = pd.DataFrame(records, columns=["f", "T", "eps_real", "eps_imag"]) return df_grid From b2f80c12ebe78024f6a21ae97a8272b05bbe2dab Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 09:34:44 +0200 Subject: [PATCH 06/12] add documentation-ready plots of permeability and permittivity --- docs/wordlist | 4 + examples/export_material2grid.py | 33 ++++- .../processing/complex_permeability.py | 138 +++++++++++++++++- .../processing/complex_permittivity.py | 115 ++++++++++++++- tests/example_test.py | 2 +- 5 files changed, 283 insertions(+), 9 deletions(-) diff --git a/docs/wordlist b/docs/wordlist index bb54897..db16efe 100644 --- a/docs/wordlist +++ b/docs/wordlist @@ -103,3 +103,7 @@ bool customizable atan iterable +Colorbars +usetex +GridSpec +pdf diff --git a/examples/export_material2grid.py b/examples/export_material2grid.py index 2262096..441b3d5 100644 --- a/examples/export_material2grid.py +++ b/examples/export_material2grid.py @@ -10,18 +10,23 @@ logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO) -def export_material2grid_example(): - """Get different material data example.""" +def export_material2grid_example(plot: bool, save_to_txt: bool) -> None: + """ + Get different material data example. + + :param plot: whether to plot or not + :param save_to_txt: whether to save to txt file + """ # init a material database instance mdb_data = mdb.Data() path2grid_export = Path(get_user_paths().grid_export_data) path2grid_plot = Path(get_user_paths().graphics) - print(f"Data exported to {path2grid_export}") material_name = mdb.Material.N49 # Permeability permeability_data_source = mdb.DataSource.LEA_MTB link2permeability_grid = path2grid_export.joinpath(f"{material_name.value}_{permeability_data_source.value}_permeability_grid.txt") + link2permeability_plot = path2grid_plot.joinpath(f"{material_name.value}_{permeability_data_source.value}_permeability_grid.pdf") permeability = mdb_data.get_complex_permeability(material=material_name, data_source=permeability_data_source, pv_fit_function=mdb.FitFunction.enhancedSteinmetz) @@ -31,17 +36,33 @@ def export_material2grid_example(): f_min_measurement=1e5, f_max_measurement=None, T_min_measurement=28, T_max_measurement=None, b_min_measurement=None, b_max_measurement=0.15) - permeability.grid2txt(df_permeability_grid, link2permeability_grid) # Permittivity permittivity_data_source = mdb.DataSource.LEA_MTB link2permittivity_grid = path2grid_export.joinpath(f"{material_name.value}_{permittivity_data_source.value}_permittivity_grid.txt") + link2permittivity_plot = path2grid_plot.joinpath(f"{material_name.value}_{permittivity_data_source.value}_permittivity_grid.pdf") permittivity = mdb_data.get_complex_permittivity(material=material_name, data_source=permittivity_data_source) df_permittivity_grid = permittivity.to_grid(grid_frequency=np.linspace(1e5, 1.5e6, 50), grid_temperature=np.linspace(25, 70, 20)) - permittivity.grid2txt(df_permittivity_grid, link2permittivity_grid) + + if plot: + permeability.plot_grid(df_permeability_grid, + save_path=link2permeability_plot, + temps=[25], + no_levels=20, + f_min=0.95e5, f_max=1.05e6, + b_min=20e-3, b_max=105e-3) + permittivity.plot_grid(df_permittivity_grid, + no_levels=20, + save_path=link2permittivity_plot, + f_min=0.95e5, f_max=1.05e6) + + if save_to_txt: + permeability.grid2txt(df_permeability_grid, link2permeability_grid) + permittivity.grid2txt(df_permittivity_grid, link2permittivity_grid) + print(f"Data exported to {path2grid_export}") if __name__ == '__main__': - export_material2grid_example() + export_material2grid_example(plot=True, save_to_txt=True) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index e5f22da..45214ca 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -1,7 +1,7 @@ """Class to represent the data structure and load material data.""" # python libraries import os -from typing import Tuple, Optional +from typing import Tuple, Optional, List # 3rd party libraries import numpy as np @@ -9,6 +9,8 @@ from scipy.optimize import curve_fit from scipy.interpolate import griddata from matplotlib import pyplot as plt +from matplotlib import gridspec +from matplotlib.ticker import MaxNLocator # own libraries from materialdatabase.meta.data_enums import * @@ -336,3 +338,137 @@ def to_grid(self, df_grid: pd.DataFrame = pd.DataFrame(records, columns=["f", "T", "b", "mu_real", "mu_imag"]) return df_grid + + @staticmethod + def plot_grid(df: pd.DataFrame, + save_path: str | Path, + temps: List[float], + no_levels: int = 100, + f_min: Optional[float] = None, f_max: Optional[float] = None, + b_min: Optional[float] = None, b_max: Optional[float] = None) -> None: + """ + Plot |mu| and phase(mu) as contour maps vs. f (kHz) and b (mT) for multiple temperatures. All temperatures share color scales per row. + + :param df: complex permeability data as pandas dataframe + :param save_path: path to save plot + :param temps: temperatures to plot + :param no_levels: number of levels to plot + :param f_min: minimum frequency + :param f_max: maximum frequency + :param b_min: minimum frequency + :param b_max: maximum frequency + """ + # ------------------------- + # Matplotlib settings + # ------------------------- + plt.rcParams.update({ + "text.usetex": True, + "font.family": "serif", + "font.size": 10.0, + "text.latex.preamble": r""" + \usepackage{amsmath} + \usepackage{upgreek} + \usepackage{bm} + """ + }) + # --- Sanity checks --- + required_columns = ['f', 'T', 'b', 'mu_real', 'mu_imag'] + if not all(col in df.columns for col in required_columns): + raise ValueError(f"DataFrame must contain columns: {required_columns}") + if not temps: + raise ValueError("At least one temperature must be provided in `temps`.") + + # --- Filter DataFrame by optional limits --- + df_plot = df.copy() + if f_min is not None: + df_plot = df_plot[df_plot['f'] >= f_min] + if f_max is not None: + df_plot = df_plot[df_plot['f'] <= f_max] + if b_min is not None: + df_plot = df_plot[df_plot['b'] >= b_min] + if b_max is not None: + df_plot = df_plot[df_plot['b'] <= b_max] + + # --- Create common interpolation grid --- + b_vals = np.linspace(df_plot['b'].min(), df_plot['b'].max(), 200) + f_vals = np.linspace(df_plot['f'].min(), df_plot['f'].max(), 200) + grid_b, grid_f = np.meshgrid(b_vals, f_vals) + grid_f_kHz = grid_f * 1e-3 # convert Hz → kHz + + # --- Helper function to compute |mu| and phase --- + def interpolate_mu(dft): + mu_abs = np.sqrt(dft['mu_real']**2 + dft['mu_imag']**2) + mu_phi = np.rad2deg(np.arctan2(dft['mu_imag'], dft['mu_real'])) + grid_abs = griddata((dft['b'], dft['f']*1e-3), mu_abs, (grid_b, grid_f_kHz), method='cubic') + grid_phi = griddata((dft['b'], dft['f']*1e-3), mu_phi, (grid_b, grid_f_kHz), method='cubic') + return grid_abs, grid_phi + + # --- Interpolate all temperatures --- + grid_mu_abs_all, grid_mu_phi_all = [], [] + for T in temps: + dft = df_plot[df_plot['T'] == T] + if dft.empty: + raise ValueError(f"No data found for T = {T} °C.") + abs_grid, phi_grid = interpolate_mu(dft) + grid_mu_abs_all.append(abs_grid) + grid_mu_phi_all.append(phi_grid) + + # --- Shared color scales --- + abs_min, abs_max = np.nanmin(grid_mu_abs_all), np.nanmax(grid_mu_abs_all) + phi_min, phi_max = np.nanmin(grid_mu_phi_all), np.nanmax(grid_mu_phi_all) + levels_abs = np.linspace(abs_min, abs_max, no_levels) + levels_phi = np.linspace(phi_min, phi_max, no_levels) + + # --- Figure and GridSpec setup --- + n_cols = len(temps) + cm = 1 / 2.54 + fig = plt.figure(figsize=(9 * cm, 9 * cm)) + gs = gridspec.GridSpec(2, n_cols + 1, width_ratios=[1]*n_cols + [0.05], + wspace=0.15, hspace=0.15) + + # Create axes array + ax = np.array([[fig.add_subplot(gs[r, c]) for c in range(n_cols)] for r in range(2)]) + cax_abs = fig.add_subplot(gs[0, -1]) + cax_phi = fig.add_subplot(gs[1, -1]) + + # --- Plot contours --- + for i, T in enumerate(temps): + cont_abs = ax[0, i].contourf(grid_b*1000, grid_f_kHz, grid_mu_abs_all[i], + levels=levels_abs, cmap='plasma', + vmin=abs_min, vmax=abs_max) + cont_phi = ax[1, i].contourf(grid_b*1000, grid_f_kHz, grid_mu_phi_all[i], + levels=levels_phi, cmap='plasma', + vmin=phi_min, vmax=phi_max) + ax[0, i].set_title(f"{int(T)} °C") + ax[1, i].set_xlabel(r"$|\underline{\boldsymbol{B}}|$ / mT") + + # --- Y labels for first column only --- + ax[0, 0].set_ylabel(r"$f$ / kHz") + ax[1, 0].set_ylabel(r"$f$ / kHz") + + # --- Colorbars --- + for cont, cax, label in zip([cont_abs, cont_phi], + [cax_abs, cax_phi], + [r'$\mu_\mathrm{r}$', r'$\zeta_\mathrm{\upmu}$ / °'], strict=False): + cbar = fig.colorbar(cont, cax=cax) + cbar.set_label(label) + cbar.locator = MaxNLocator(nbins=5) + cbar.update_ticks() + + # --- Simplify inner ticks --- + n_rows, n_cols = ax.shape + for r in range(n_rows): + for c in range(n_cols): + if c != 0: + # ax[r, c].tick_params(axis='y', which='both', length=0) + ax[r, c].set_yticklabels([]) + if r != n_rows - 1: + # ax[r, c].tick_params(axis='x', which='both', length=0) + ax[r, c].set_xticklabels([]) + # optional: simplify outer ticks + ax[r, c].xaxis.set_major_locator(MaxNLocator(nbins=4)) + ax[r, c].yaxis.set_major_locator(MaxNLocator(nbins=4)) + + # --- Save and show --- + plt.savefig(save_path, bbox_inches='tight', dpi=300) + plt.show() diff --git a/materialdatabase/processing/complex_permittivity.py b/materialdatabase/processing/complex_permittivity.py index 423e628..71f7ded 100644 --- a/materialdatabase/processing/complex_permittivity.py +++ b/materialdatabase/processing/complex_permittivity.py @@ -1,12 +1,16 @@ """Class to represent the data structure and load material data.""" # python libraries -from typing import List +from typing import List, Optional import os # 3rd party libraries import pandas as pd from scipy.optimize import curve_fit from scipy.interpolate import interp1d +from scipy.interpolate import griddata +from matplotlib import pyplot as plt +from matplotlib import gridspec +from matplotlib.ticker import MaxNLocator # own libraries from materialdatabase.meta.data_enums import * @@ -219,3 +223,112 @@ def to_grid(self, df_grid: pd.DataFrame = pd.DataFrame(records, columns=["f", "T", "eps_real", "eps_imag"]) return df_grid + + @staticmethod + def plot_grid(df: pd.DataFrame, + save_path: str | Path, + no_levels: int = 100, + f_min: Optional[float] = None, f_max: Optional[float] = None, + T_min: Optional[float] = None, T_max: Optional[float] = None) -> None: + """ + Plot |ε| and phase(ε) as contour maps vs. f (kHz) and T (°C) with shared color scales for magnitude and phase. + + :param df: complex permittivity data as pandas DataFrame + :param save_path: path where the plot should be saved + :param no_levels: number of levels to show + :param f_min: minimum frequency + :param f_max: maximum frequency + :param T_min: minimum temperature + :param T_max: maximum temperature + """ + # ------------------------- + # Matplotlib settings + # ------------------------- + plt.rcParams.update({ + "text.usetex": True, + "font.family": "serif", + "font.size": 10.0, + "text.latex.preamble": r""" + \usepackage{amsmath} + \usepackage{upgreek} + \usepackage{bm} + """ + }) + + # --- Sanity checks --- + required_columns = ['f', 'T', 'eps_real', 'eps_imag'] + if not all(col in df.columns for col in required_columns): + raise ValueError(f"DataFrame must contain columns: {required_columns}") + + # --- Filter by optional limits --- + df_plot = df.copy() + if f_min is not None: + df_plot = df_plot[df_plot['f'] >= f_min] + if f_max is not None: + df_plot = df_plot[df_plot['f'] <= f_max] + if T_min is not None: + df_plot = df_plot[df_plot['T'] >= T_min] + if T_max is not None: + df_plot = df_plot[df_plot['T'] <= T_max] + if df_plot.empty: + raise ValueError("No data remaining after applying filter limits.") + + # --- Compute magnitude and phase --- + eps_abs = np.sqrt(df_plot['eps_real'] ** 2 + df_plot['eps_imag'] ** 2) + eps_phi = np.rad2deg(np.arctan2(df_plot['eps_imag'], df_plot['eps_real'])) + + # --- Common grid for interpolation --- + f_vals = np.linspace(df_plot['f'].min(), df_plot['f'].max(), 200) + T_vals = np.linspace(df_plot['T'].min(), df_plot['T'].max(), 200) + grid_f, grid_T = np.meshgrid(f_vals, T_vals) + grid_f_kHz = grid_f * 1e-3 # Hz → kHz + + # --- Interpolation --- + grid_eps_abs = griddata((df_plot['f'], df_plot['T']), eps_abs, (grid_f, grid_T), method='cubic') + grid_eps_phi = griddata((df_plot['f'], df_plot['T']), eps_phi, (grid_f, grid_T), method='cubic') + + # --- Shared color scales --- + abs_min, abs_max = np.nanmin(grid_eps_abs), np.nanmax(grid_eps_abs) + phi_min, phi_max = np.nanmin(grid_eps_phi), np.nanmax(grid_eps_phi) + levels_abs = np.linspace(abs_min, abs_max, no_levels) + levels_phi = np.linspace(phi_min, phi_max, no_levels) + + # --- Figure setup with GridSpec --- + cm = 1 / 2.54 + fig = plt.figure(figsize=(5 * cm, 9 * cm)) + gs = gridspec.GridSpec(2, 2, width_ratios=[1, 0.05], wspace=0.15, hspace=0.15) + ax_abs = fig.add_subplot(gs[0, 0]) + ax_phi = fig.add_subplot(gs[1, 0]) + cax_abs = fig.add_subplot(gs[0, 1]) + cax_phi = fig.add_subplot(gs[1, 1]) + + # --- Contour plots --- + cont_abs = ax_abs.contourf(grid_f_kHz, grid_T, grid_eps_abs, levels=levels_abs, cmap='plasma', + vmin=abs_min, vmax=abs_max) + cont_phi = ax_phi.contourf(grid_f_kHz, grid_T, grid_eps_phi, levels=levels_phi, cmap='plasma', + vmin=phi_min, vmax=phi_max) + + # --- Labels --- + ax_abs.set_ylabel(r"$T$ / °C") + ax_phi.set_ylabel(r"$T$ / °C") + ax_phi.set_xlabel(r"$f$ / kHz") + + # --- Colorbars --- + for cont, cax, label in zip([cont_abs, cont_phi], + [cax_abs, cax_phi], + [r"$|\tilde{\varepsilon}_r|$", r"$\zeta_{\tilde{\varepsilon}}$ / °"], strict=False): + cbar = fig.colorbar(cont, cax=cax) + cbar.set_label(label) + cbar.locator = MaxNLocator(nbins=5) + cbar.update_ticks() + + # --- Simplify ticks --- + for a in [ax_abs, ax_phi]: + a.xaxis.set_major_locator(MaxNLocator(nbins=4)) + a.yaxis.set_major_locator(MaxNLocator(nbins=4)) + + # --- Remove upper plot x-axis tick labels --- + ax_abs.set_xticklabels([]) # <-- hide x-axis tick labels on top plot + + plt.savefig(save_path, bbox_inches='tight', dpi=300) + plt.show() diff --git a/tests/example_test.py b/tests/example_test.py index 9779feb..0e15cb4 100644 --- a/tests/example_test.py +++ b/tests/example_test.py @@ -14,7 +14,7 @@ get_datasheet_information_example() get_material_example() fit_single_operation_point_example(is_plot=False) -export_material2grid_example() +export_material2grid_example(plot=False, save_to_txt=False) fit_complex_permeability_lea_mtb_example(mu_abs_flag=True, pv_flag=True, is_plot=False) fit_complex_permeability_tdk_mdt_example(mu_abs_flag=True, pv_flag=True, is_plot=False) fit_complex_permittivity_example(is_plot=False) From f62ecc96ec6294eadb2731d6d7d69b14a60a2b8d Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 09:59:57 +0200 Subject: [PATCH 07/12] cleaned whitespaces --- .../processing/complex_permeability.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index 45214ca..297b5d5 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -203,15 +203,15 @@ def fit_losses(self, logger.info(f"\n" f"Fitting of the magnetic losses with the fit function'{self.pv_fit_function.value}'.\n" - f" Following limits are applied to the measurement data:\n" - f" {f_min = }\n" - f" {f_max = }\n" - f" {T_min = }\n" - f" {T_max = }\n" - f" {b_min = }\n" - f" {b_max = }\n" + f"Following limits are applied to the measurement data:\n" + f"{f_min = }\n" + f"{f_max = }\n" + f"{T_min = }\n" + f"{T_max = }\n" + f"{b_min = }\n" + f"{b_max = }\n" f" Following data is used for the loss fitting:\n " - f" {fit_data}") + f"{fit_data}") mu_abs = np.sqrt(fit_data["mu_real"] ** 2 + fit_data["mu_imag"] ** 2) pv = pv_mag(fit_data["f"].to_numpy(), From 0a4ffaf4dbe547ad848eaef8895038e257c3dcbe Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 10:03:32 +0200 Subject: [PATCH 08/12] cleaned whitespaces --- .../processing/complex_permeability.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index 297b5d5..c723e2b 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -204,13 +204,13 @@ def fit_losses(self, logger.info(f"\n" f"Fitting of the magnetic losses with the fit function'{self.pv_fit_function.value}'.\n" f"Following limits are applied to the measurement data:\n" - f"{f_min = }\n" - f"{f_max = }\n" - f"{T_min = }\n" - f"{T_max = }\n" - f"{b_min = }\n" - f"{b_max = }\n" - f" Following data is used for the loss fitting:\n " + f"{f_min=}\n" + f"{f_max=}\n" + f"{T_min=}\n" + f"{T_max=}\n" + f"{b_min=}\n" + f"{b_max=}\n" + f"Following data is used for the loss fitting:\n" f"{fit_data}") mu_abs = np.sqrt(fit_data["mu_real"] ** 2 + fit_data["mu_imag"] ** 2) From 566aa23f43ba750cf3c5ffe21759df3f1688c225 Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 10:05:56 +0200 Subject: [PATCH 09/12] removed pycodestyle-related problematic logging --- materialdatabase/processing/complex_permeability.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index c723e2b..aa7ce16 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -201,18 +201,6 @@ def fit_losses(self, T_min=T_min, T_max=T_max, b_min=b_min, b_max=b_max) - logger.info(f"\n" - f"Fitting of the magnetic losses with the fit function'{self.pv_fit_function.value}'.\n" - f"Following limits are applied to the measurement data:\n" - f"{f_min=}\n" - f"{f_max=}\n" - f"{T_min=}\n" - f"{T_max=}\n" - f"{b_min=}\n" - f"{b_max=}\n" - f"Following data is used for the loss fitting:\n" - f"{fit_data}") - mu_abs = np.sqrt(fit_data["mu_real"] ** 2 + fit_data["mu_imag"] ** 2) pv = pv_mag(fit_data["f"].to_numpy(), (-fit_data["mu_imag"] * mu_0).to_numpy(), From 52c6a5f0d512e677b76786f3b606dd1b564fdc4a Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 10:09:34 +0200 Subject: [PATCH 10/12] removed pycodestyle-related logging --- materialdatabase/processing/complex_permeability.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index aa7ce16..cb4e3b6 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -154,18 +154,6 @@ def fit_permeability_magnitude(self, fit_mu_a = self.mu_a_fit_function.get_function() - logger.info(f"\n" - f"Fitting of the permeability amplitude with the fit function'{self.mu_a_fit_function.value}'.\n" - f" Following limits are applied to the measurement data:\n" - f" {f_min = }\n" - f" {f_max = }\n" - f" {T_min = }\n" - f" {T_max = }\n" - f" {b_min = }\n" - f" {b_max = }\n" - f" Following data is used for the loss fitting:\n " - f" {fit_data}") - mu_a = np.sqrt(fit_data["mu_real"] ** 2 + fit_data["mu_imag"] ** 2) popt_mu_a, pcov_mu_a = curve_fit(fit_mu_a, (fit_data["f"], From 9d88b97341653680604dbc36a0f92c6ab3c21407 Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 10:40:07 +0200 Subject: [PATCH 11/12] resolved comments --- .../processing/complex_permeability.py | 45 ++++++++++--------- .../processing/complex_permittivity.py | 17 ++++--- 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index cb4e3b6..21a4583 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -1,7 +1,6 @@ """Class to represent the data structure and load material data.""" # python libraries import os -from typing import Tuple, Optional, List # 3rd party libraries import numpy as np @@ -97,9 +96,9 @@ def generate_1d_interpolation_arrays(self, f_points: int = 20, T_points: int = 2 @staticmethod def filter_fTb(df: pd.DataFrame, - f_min: Optional[float] = None, f_max: Optional[float] = None, - T_min: Optional[float] = None, T_max: Optional[float] = None, - b_min: Optional[float] = None, b_max: Optional[float] = None) -> pd.DataFrame: + f_min: float | None = None, f_max: float | None = None, + T_min: float | None = None, T_max: float | None = None, + b_min: float | None = None, b_max: float | None = None) -> pd.DataFrame: """ Filter a material dataframe df for min/max frequency, temperature, and flux density. @@ -127,9 +126,9 @@ def filter_fTb(df: pd.DataFrame, return df def fit_permeability_magnitude(self, - f_min: Optional[float] = None, f_max: Optional[float] = None, - T_min: Optional[float] = None, T_max: Optional[float] = None, - b_min: Optional[float] = None, b_max: Optional[float] = None) -> Any: + f_min: float | None = None, f_max: float | None = None, + T_min: float | None = None, T_max: float | None = None, + b_min: float | None = None, b_max: float | None = None) -> Any: """ Fit the permeability magnitude μ_abs as a function of frequency, temperature, and magnetic flux density. @@ -164,9 +163,9 @@ def fit_permeability_magnitude(self, return popt_mu_a def fit_losses(self, - f_min: Optional[float] = None, f_max: Optional[float] = None, - T_min: Optional[float] = None, T_max: Optional[float] = None, - b_min: Optional[float] = None, b_max: Optional[float] = None) -> Any: + f_min: float | None = None, f_max: float | None = None, + T_min: float | None = None, T_max: float | None = None, + b_min: float | None = None, b_max: float | None = None) -> Any: """ Fit the magnetic power loss density p_v as a function of frequency, temperature, and magnetic flux density. @@ -206,14 +205,14 @@ def fit_real_and_imaginary_part_at_f_and_T( f_op: float, T_op: float, b_vals: npt.NDArray - ) -> Tuple[npt.NDArray, npt.NDArray]: + ) -> tuple[npt.NDArray, npt.NDArray]: """ Fit permeability and losses for a given material and return real/imaginary parts. :param f_op: Operating frequency in Hz :param T_op: Operating temperature in °C :param b_vals: 1D np.ndarray of magnetic flux density values in T - :return: Tuple (mu_real, mu_imag) of fitted real and imaginary parts of permeability + :return: tuple (mu_real, mu_imag) of fitted real and imaginary parts of permeability """ assert isinstance(b_vals, np.ndarray), "b_vals must be a NumPy array" @@ -279,9 +278,9 @@ def to_grid(self, grid_frequency: npt.NDArray[Any], grid_temperature: npt.NDArray[Any], grid_flux_density: npt.NDArray[Any], - f_min_measurement: Optional[float] = None, f_max_measurement: Optional[float] = None, - T_min_measurement: Optional[float] = None, T_max_measurement: Optional[float] = None, - b_min_measurement: Optional[float] = None, b_max_measurement: Optional[float] = None + f_min_measurement: float | None = None, f_max_measurement: float | None = None, + T_min_measurement: float | None = None, T_max_measurement: float | None = None, + b_min_measurement: float | None = None, b_max_measurement: float | None = None ) -> pd.DataFrame: """ Export fitted permeability data (real & imaginary parts) to a txt grid file. @@ -318,10 +317,10 @@ def to_grid(self, @staticmethod def plot_grid(df: pd.DataFrame, save_path: str | Path, - temps: List[float], + temps: list[float], no_levels: int = 100, - f_min: Optional[float] = None, f_max: Optional[float] = None, - b_min: Optional[float] = None, b_max: Optional[float] = None) -> None: + f_min: float | None = None, f_max: float | None = None, + b_min: float | None = None, b_max: float | None = None) -> None: """ Plot |mu| and phase(mu) as contour maps vs. f (kHz) and b (mT) for multiple temperatures. All temperatures share color scales per row. @@ -372,7 +371,13 @@ def plot_grid(df: pd.DataFrame, grid_f_kHz = grid_f * 1e-3 # convert Hz → kHz # --- Helper function to compute |mu| and phase --- - def interpolate_mu(dft): + def get_abs_and_phi_of_mu_grid_at_temperature(dft): + """ + Compute the magnitude and phase as grid over b and f. + + :param dft: permeability data at certain temperature + :return: magnitude and phase as grid + """ mu_abs = np.sqrt(dft['mu_real']**2 + dft['mu_imag']**2) mu_phi = np.rad2deg(np.arctan2(dft['mu_imag'], dft['mu_real'])) grid_abs = griddata((dft['b'], dft['f']*1e-3), mu_abs, (grid_b, grid_f_kHz), method='cubic') @@ -385,7 +390,7 @@ def interpolate_mu(dft): dft = df_plot[df_plot['T'] == T] if dft.empty: raise ValueError(f"No data found for T = {T} °C.") - abs_grid, phi_grid = interpolate_mu(dft) + abs_grid, phi_grid = get_abs_and_phi_of_mu_grid_at_temperature(dft) grid_mu_abs_all.append(abs_grid) grid_mu_phi_all.append(phi_grid) diff --git a/materialdatabase/processing/complex_permittivity.py b/materialdatabase/processing/complex_permittivity.py index 71f7ded..51cbd5b 100644 --- a/materialdatabase/processing/complex_permittivity.py +++ b/materialdatabase/processing/complex_permittivity.py @@ -1,6 +1,5 @@ """Class to represent the data structure and load material data.""" # python libraries -from typing import List, Optional import os # 3rd party libraries @@ -58,9 +57,9 @@ def fit_permittivity_magnitude(self) -> Any: df["eps_a"] = eps_a # Step 2: Interpolate to uniform frequency grid for each T - interpolated_f: List[float] = [] - interpolated_T: List[float] = [] - interpolated_eps_a: List[float] = [] + interpolated_f: list[float] = [] + interpolated_T: list[float] = [] + interpolated_eps_a: list[float] = [] unique_Ts = np.unique(df["T"]) for T in unique_Ts: @@ -109,9 +108,9 @@ def fit_loss_angle(self) -> Any: df["eps_angle"] = eps_angle # Step 2: Interpolate to uniform frequency grid for each T - interpolated_f: List[float] = [] - interpolated_T: List[float] = [] - interpolated_eps_angle: List[float] = [] + interpolated_f: list[float] = [] + interpolated_T: list[float] = [] + interpolated_eps_angle: list[float] = [] unique_Ts = np.unique(df["T"]) for T in unique_Ts: @@ -228,8 +227,8 @@ def to_grid(self, def plot_grid(df: pd.DataFrame, save_path: str | Path, no_levels: int = 100, - f_min: Optional[float] = None, f_max: Optional[float] = None, - T_min: Optional[float] = None, T_max: Optional[float] = None) -> None: + f_min: float | None = None, f_max: float | None = None, + T_min: float | None = None, T_max: float | None = None) -> None: """ Plot |ε| and phase(ε) as contour maps vs. f (kHz) and T (°C) with shared color scales for magnitude and phase. From 016e2907d849c528cf78c1af48fb2f1530fe0fb9 Mon Sep 17 00:00:00 2001 From: Till Piepenbrock Date: Fri, 17 Oct 2025 10:57:05 +0200 Subject: [PATCH 12/12] linting --- docs/wordlist | 1 + materialdatabase/processing/complex_permeability.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/wordlist b/docs/wordlist index db16efe..5e53bd9 100644 --- a/docs/wordlist +++ b/docs/wordlist @@ -107,3 +107,4 @@ Colorbars usetex GridSpec pdf +dft diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index 21a4583..cd0780c 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -371,7 +371,7 @@ def plot_grid(df: pd.DataFrame, grid_f_kHz = grid_f * 1e-3 # convert Hz → kHz # --- Helper function to compute |mu| and phase --- - def get_abs_and_phi_of_mu_grid_at_temperature(dft): + def get_abs_and_phi_of_mu_grid_at_temperature(dft: pd.DataFrame) -> tuple[np.ndarray, np.ndarray]: """ Compute the magnitude and phase as grid over b and f.