diff --git a/docs/wordlist b/docs/wordlist index cda1a30..245d765 100644 --- a/docs/wordlist +++ b/docs/wordlist @@ -109,3 +109,6 @@ GridSpec pdf dft Epcos +DataSource +pathname + diff --git a/examples/combine_material_data.py b/examples/combine_material_data.py new file mode 100644 index 0000000..7f789f4 --- /dev/null +++ b/examples/combine_material_data.py @@ -0,0 +1,39 @@ +"""Example to combine material data. + +This script enables users to combine material data, if this is still no done. +""" + +import logging + +import numpy as np + +import materialdatabase as mdb +from materialdatabase.meta.data_classes import ComplexPermeabilityConfig + +# --------------------------------------------- +# Configuration +# --------------------------------------------- + +logging.basicConfig(format='%(levelname)s: %(message)s', level=logging.INFO) + +# Flags to control which plots to generate +PLOT_MU_ABS = True +PLOT_PV = True + +# Operating points of interest +FREQS = np.linspace(2e5, 4e5, 8) # Frequency range in Hertz +FLUX_DENSITIES = np.linspace(0.14, 0.07, 8) # Flux densities in Tesla +TEMPS = np.ones_like(FREQS) * 100 # Temperatures in Celsius + +# Materials to evaluate +mat_cfg = ComplexPermeabilityConfig(material=mdb.Material.N27, + setup=mdb.DataSource.MagNet, + pv_fit_function=mdb.FitFunction.enhancedSteinmetz) + +# --------------------------------------------- +# Load Material Data and combine material data with different h-offset +# --------------------------------------------- + +mdb_data = mdb.Data() + +mdb_data.combine_material_permeability_data(material=mat_cfg.material, data_source=mat_cfg.setup) diff --git a/examples/compare_operation_points.py b/examples/compare_operation_points.py index b89dad4..c664d45 100644 --- a/examples/compare_operation_points.py +++ b/examples/compare_operation_points.py @@ -26,8 +26,8 @@ PLOT_PV = True # Operating points of interest -FREQS = np.linspace(2e5, 4e5, 5) # Frequency range in Hertz -FLUX_DENSITIES = np.linspace(0.14, 0.07, 5) # Flux densities in Tesla +FREQS = np.linspace(2e5, 4e5, 16) # Frequency range in Hertz +FLUX_DENSITIES = np.linspace(0.14, 0.07, 16) # Flux densities in Tesla TEMPS = np.ones_like(FREQS) * 100 # Temperatures in Celsius # Materials to evaluate @@ -84,6 +84,7 @@ mdb_data = mdb.Data() + # Create sweep grid df_common = pd.DataFrame( columns=["f", "T", "b"] diff --git a/materialdatabase/meta/data_enums.py b/materialdatabase/meta/data_enums.py index 940f2c1..6e6c8ef 100644 --- a/materialdatabase/meta/data_enums.py +++ b/materialdatabase/meta/data_enums.py @@ -18,6 +18,7 @@ class FitFunction(str, Enum): enhancedSteinmetz = "enhanced_steinmetz" mu_abs_TDK_MDT = "mu_abs_TDK_MDT" mu_abs_LEA_MTB = "mu_abs_LEA_MTB" + mu_abs_MagNet = "mu_abs_MagNet" eps_abs = "fit_eps_qT" def get_log_function(self) -> Any: @@ -42,6 +43,7 @@ def get_function(self) -> Any: FitFunction.enhancedSteinmetz: enhanced_steinmetz_qT, FitFunction.mu_abs_TDK_MDT: fit_mu_abs_TDK_MDT, FitFunction.mu_abs_LEA_MTB: fit_mu_abs_LEA_MTB, + FitFunction.mu_abs_MagNet: fit_mu_abs_LEA_MTB, FitFunction.eps_abs: fit_eps_qT }[self] diff --git a/materialdatabase/meta/mapping.py b/materialdatabase/meta/mapping.py index db7207e..3f8038b 100644 --- a/materialdatabase/meta/mapping.py +++ b/materialdatabase/meta/mapping.py @@ -6,6 +6,7 @@ DataSource.TDK_MDT: FitFunction.mu_abs_TDK_MDT, DataSource.Datasheet: FitFunction.mu_abs_TDK_MDT, DataSource.LEA_MTB: FitFunction.mu_abs_LEA_MTB, + DataSource.MagNet: FitFunction.mu_abs_MagNet } def get_fit_function_from_setup(setup: DataSource) -> Any: diff --git a/materialdatabase/processing/complex_permeability.py b/materialdatabase/processing/complex_permeability.py index cd0780c..077e4e8 100644 --- a/materialdatabase/processing/complex_permeability.py +++ b/materialdatabase/processing/complex_permeability.py @@ -190,8 +190,8 @@ def fit_losses(self, 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()) + - (fit_data["mu_imag"].to_numpy() * mu_0), + fit_data["b"].to_numpy() / mu_abs / mu_0) popt_pv, pcov_pv = curve_fit(log_pv_fit_function, (fit_data["f"], fit_data["T"], diff --git a/materialdatabase/processing/data_structure.py b/materialdatabase/processing/data_structure.py index cc3cd26..e27f07d 100644 --- a/materialdatabase/processing/data_structure.py +++ b/materialdatabase/processing/data_structure.py @@ -194,53 +194,301 @@ def plot_available_data(self, exclude_dc_bias: bool = True) -> None: :param exclude_dc_bias: exclude DC-bias data to prevent an overcrowded plot """ + # logger.info(self.build_overview_table()) + # self.plot_boolean_dataframe(self.build_overview_table()) available_data = self.build_overview_table() - if exclude_dc_bias: + # exclude_dc_bias: + if False: available_data = available_data[~available_data.index.str.contains("_")] logger.info(available_data) self.plot_boolean_dataframe(available_data) - def get_complex_data_set(self, material: Material, data_source: DataSource, data_type: ComplexDataType) -> pd.DataFrame: + def get_available_h_offset(self, material: Material, data_source: DataSource) -> list[float]: """ - Get a complex data set of a certain material, data type and measurement. + Get the list of available h-offsets of the certain material. - :param material: e.g. mdb.Material.N95 - :param data_source: e.g. mdb.MeasurementSetup.TDK_MDT - :param data_type: e.g. mdb.ComplexDataType.complex_permeability - :return: + :param material: Material from material database, e.g. mdb.Material.N95 + :type material: Material + :param data_source: Source folder of the material database, e.g. mdb.MeasurementSetup.TDK_MDT + :type data_source: DataSource + :return: List of material data + :rtype: float """ + # Check if requested data is without h-offset + path2file = Path(f"{self.root_dir}/complex_permeability/{data_source.value}/{material.value}.csv") + + if path2file not in self.all_paths: + raise ValueError(f"The specified data file with path {path2file} does not exist.") + else: + logger.info(f"h-offset read from {path2file}.") + data_set = pd.read_csv(path2file, sep=",") + + if "h_offset" not in data_set.columns: + # Create combined dataset + self.combine_material_permeability_data(material, data_source) + # Read updated CSV-File again + data_set = pd.read_csv(path2file, sep=",") + # Get column with H-offset data + h_offset_series = data_set['h_offset'] + h_array = h_offset_series.unique() + # Assemble the result list + h_offset_list = h_array.tolist() + + return h_offset_list + + def get_complex_data_set(self, material: Material, data_source: DataSource, data_type: ComplexDataType, h_offset: float = 0) -> pd.DataFrame: + """ + Get a complex data set of a certain material, data type and measurement. + + If the h_offset = 0 (Default parameter) and the data set has no h-offset-value (older format), + the h-offset will be added to the data. A copy of the old data will be provided. + + :param material: Material from material database, e.g. mdb.Material.N95 + :type material: Material + :param data_source: Source folder of the material database, e.g. mdb.MeasurementSetup.TDK_MDT + :type data_source: DataSource + :param data_type: Type of requested data e.g. mdb.ComplexDataType.complex_permeability + :type data_type: ComplexDataType + :param h_offset: H-Offset of the requested data + :type h_offset: float + :return: Requested data within a data frame + :rtype: pd.DataFrame + """ + # Check data type if data_type not in {item.value for item in ComplexDataType}: raise ValueError(f"{data_type} is no valid complex data type.\n" f"Valid complex data types are: {[item.value for item in ComplexDataType]}") else: + # Check if requested data is without h-offset path2file = Path(f"{self.root_dir}/{data_type.value}/{data_source.value}/{material.value}.csv") + if path2file not in self.all_paths: raise ValueError(f"The specified data file with path {path2file} does not exist.") else: logger.info(f"Complex data read from {path2file}.") - return pd.read_csv(path2file, sep=",") + data_set = pd.read_csv(path2file, sep=",") + + if data_type == ComplexDataType.complex_permeability: + if "h_offset" not in data_set.columns: + # Create combined dataset + self.combine_material_permeability_data(material, data_source) + # Read updated CSV-File again + data_set = pd.read_csv(path2file, sep=",") + # Filter requested H-offset data + result_data_set = data_set[data_set['h_offset'] == h_offset] + # Check if H-offset dataset is not found + if result_data_set.empty: + raise ValueError(f"A dataset with h_offset={h_offset} is not available.\n" + f"Please use the 'get_available_h_offset' method to retrieve the list of available h-offsets.") + else: + result_data_set = data_set + + return result_data_set def get_complex_permeability(self, material: Material, data_source: DataSource, - pv_fit_function: FitFunction) -> ComplexPermeability: + pv_fit_function: FitFunction, + h_offset: float = 0) -> ComplexPermeability: """ Get a complex permeability data set of a certain material and measurement type. - :param material: e.g. mdb.Material.N95 - :param data_source: e.g. mdb.MeasurementSetup.TDK_MDT - :param pv_fit_function: - :return: + :param material: Material from material database, e.g. mdb.Material.N95 + :type material: Material + :param data_source: Source folder of the material database, e.g. mdb.MeasurementSetup.TDK_MDT + :type data_source: DataSource + :param pv_fit_function: Algorithm to fit data point by given measurements + :type pv_fit_function: FitFunction + :param h_offset: H-Offset of the requested data + :type h_offset: float + :return: Requested data within a data frame + :rtype: pd.DataFrame """ dataset = self.get_complex_data_set( material=material, data_source=data_source, - data_type=ComplexDataType.complex_permeability + data_type=ComplexDataType.complex_permeability, + h_offset=h_offset ) return ComplexPermeability(dataset, material, data_source, pv_fit_function) + def combine_material_permeability_data(self, material: Material, data_source: DataSource) -> bool: + """ + Combine all files with different h-offset of one material. + + :param material: Material from material database, e.g. mdb.Material.N95 + :type material: Material + :param data_source: Source folder of the material database, e.g. mdb.MeasurementSetup.TDK_MDT + :type data_source: DataSource + :return: List of material data + :rtype: float + """ + # Return value + is_done_successfull = False + + result_list: list[tuple[float, str]] + df_list: list[pd.DataFrame] = [] + + # Assemble path + data_path = Path(f"{self.root_dir}/complex_permeability/{data_source.value}") + # Check if data path exists + if data_path.is_dir(): + # Check for base file + data_base_file = Path(f"{data_path}/{material.value}.csv") + # Search for suitable backup name + data_base_backup_file = Data._get_next_backup_filename(str(data_path), data_base_file.stem + "_backup") + if data_base_file.is_file(): + # Load base file data + df_base = pd.read_csv(data_base_file, sep=",") + df_backup = df_base.copy(deep=True) + # Search for all h-offset data + result_list = Data._get_h_dc_offset_data_list(data_path, material) + # Read and merge dataframes + for item in result_list: + path2file = Path(item[1]) + df = pd.read_csv(path2file, sep=",") + df['h_offset'] = item[0] + df_list.append(df) + + # Check if h-offset data frames are found + if len(df_list): + # Check if h_offset is still part of the file + if "h_offset" not in df_base.columns: + df_base['h_offset'] = 0 + df_list.append(df_base) + # Combine all data frames + df_combined = pd.concat(df_list, ignore_index=True) + # Remove duplicates + df_combined = df_combined.drop_duplicates() + # Sort ascending and reset index + df_combined = df_combined.sort_values(by=['h_offset', 'T', 'f', 'b'], ascending=[True, True, True, True]) + + # Backup origin file and store new one + if not df_combined.equals(df_base): + df_backup.to_csv(data_base_backup_file, index=False) + df_combined.to_csv(data_base_file, index=False) + is_done_successfull = True + else: + # Notify, that current base file contains all data + logger.info(f"File {data_base_file.name} still contains all h-offset data.") + else: + # Notify, that no h-offset file is found + logger.info(f"No offset file correspondent to file {data_base_file.name} is found.") + # Check if h_offset is still part of the file + if "h_offset" not in df_base.columns: + # Notify, that actual file has no H-offset column, which is now added + logger.info(f"H-offset column is added to file {data_base_file.name}.") + df_base['h_offset'] = 0 + df_backup.to_csv(data_base_backup_file, index=False) + df_base.to_csv(data_base_file, index=False) + + else: + # Log as warning, that no database file exists + logger.warning(f"{data_base_file.name} does not exist.") + else: + # Log as warning about wrong path + logger.warning(f"Path {data_path.name} does not exist.") + + return is_done_successfull + + @staticmethod + def _get_next_backup_filename(pathname: str, base_name: str, extension: str = '.csv') -> Path: + """ + Provide a 'free' file name with a suitable number. + + :param pathname: root directory of material database + :type pathname: str + :param base_name: Material from material database, e.g. mdb.Material.N95 + :type base_name: Material + :param extension: Source folder of the material database, e.g. mdb.MeasurementSetup.TDK_MDT + :type extension: DataSource + :return: Path-object with file name, which still not exists in path + :rtype: Path + """ + base_path = pathname + "/" + base_name + + orig_file_path = Path(base_path + extension) + # Check, if name does not exists + if not orig_file_path.exists(): + return orig_file_path + + # Search for the next free name (number) + i = 1 + while True: + new_name = f"{base_path}{i}{extension}" + new_file_path = Path(new_name) + if not new_file_path.exists(): + return new_file_path + i += 1 + + @staticmethod + def _get_h_dc_offset_data_list(data_path: Path, material: Material) -> list[tuple[float, str]]: + """ + Get a list of all files with h-dc-offset. + + :param material: Material from material database, e.g. mdb.Material.N95 + :type material: Material + :param data_source: Source folder of the material database, e.g. mdb.MeasurementSetup.TDK_MDT + :type data_source: DataSource + :return: List of tuple: ( h offset, path file name) + :rtype: list[tuple[float, str]] + """ + # Variable declaration + # Number of files in this folder + number_of_files = 0 + # Result list with parameter and file names + result_list: list[tuple[float, str]] = [] + + prefix = f"{material.value}" + # Loop over the files + for file in data_path.iterdir(): + if file.is_file() and file.stem.startswith(prefix): + number_of_files += 1 + file_name = str(file.stem) + # Get the dc-parameter from file name + h_offset_parameter = Data._get_h_offset_parameter(file_name) + # Check for valid parameter (h-offset>0) + if h_offset_parameter != 0: + list_item = (h_offset_parameter, str(file)) + result_list.append(list_item) + + # Evaluate, if minimum 1 file is found + logger.info(f"{number_of_files} are found.") + + return result_list + + @staticmethod + def _get_h_offset_parameter(file_name: str) -> float: + """ + Get a list of all files with h-dc-offset. + + :param file_name: name of the file with h-dc-offset information + :type file_name: str + :return: h dc offset parameter + :rtype: float + """ + # Variable declaration + parameter_value: float = 0 + + # Check if prefix is part of the string (if first letter is a '_' this is to ignore + start_pos = file_name.find("_", 1) + if start_pos == -1: + # No h-dc offset identified + return parameter_value + end_pos = file_name.find("Am", start_pos) + if end_pos == -1: + # No h-dc offset identified + return 0 + # Check for only numbers + try: + parameter_value = float(file_name[start_pos + 1:end_pos]) + except: + pass + + return parameter_value + def get_complex_permittivity(self, material: Material, data_source: DataSource) -> ComplexPermittivity: """ Get a complex permittivity data set of a certain material and measurement type. diff --git a/materialdatabase/processing/utils/empirical.py b/materialdatabase/processing/utils/empirical.py index f0bd7af..49459ba 100644 --- a/materialdatabase/processing/utils/empirical.py +++ b/materialdatabase/processing/utils/empirical.py @@ -205,6 +205,39 @@ def fit_mu_abs_LEA_MTB( return (mur_0 * k_0 + k_1 * (mur_1 * B + mur_2 * B ** 2)) * k_f +# Added by ASA only guess +def fit_mu_abs_MagNet( + _Tb: tuple[float | np.ndarray, float | np.ndarray, float | np.ndarray], + mur_0: float, + mur_1: float, + mur_2: float, + mur_3: float, + mur_4: float, + c_0: float, + c_1: float +) -> float | npt.NDArray[np.float64]: + """ + Fit function for amplitude permeability μₐ(B, T) based on polynomial B-dependence and linear temperature scaling. + + Typically accurate for b < 0.3 T. + + :param _Tb: + :param mur_0: Base relative permeability (T-independent offset) + :param mur_1: Polynomial coefficients B^1 + :param mur_2: Polynomial coefficients B^2 + :param mur_3: Polynomial coefficients B^3 + :param mur_4: Polynomial coefficients B^4 + :param c_0: Temperature scaling coefficient for constant offset term + :param c_1: Temperature scaling coefficient for B-dependent terms + :return: Amplitude permeability μₐ + """ + _, T, b = _Tb + + k_0 = 1 + T * c_0 # Temperature scaling for base permeability + k_1 = 1 + T * c_1 # Temperature scaling for B-dependent polynomial + + return mur_0 * k_0 + k_1 * (mur_1 * b + mur_2 * b ** 2 + mur_3 * b ** 3 + mur_4 * b ** 4) + # ---------------- # Permittivity Fits # ----------------