diff --git a/pywit/interface.py b/pywit/interface.py index bd98bbe..a14b130 100644 --- a/pywit/interface.py +++ b/pywit/interface.py @@ -1,18 +1,21 @@ import os - -from pywit.component import Component -from pywit.element import Element - import subprocess -from typing import Tuple, List, Optional, Dict, Any, Union -from dataclasses import dataclass from pathlib import Path -from hashlib import sha256 +from typing import Any, List, Sequence, Tuple, Union import numpy as np -from yaml import load, BaseLoader +from IW2D import (Eps1FromResistivity, FlatIW2DInput, InputFileFreqParams, + InputFileWakeParams, IW2DLayer, IW2DResult, + Mu1FromSusceptibility, RoundIW2DInput, + iw2d_impedance, load_iw2d_result) from joblib import Parallel, delayed +from numpy.typing import ArrayLike +from scipy.constants import speed_of_light from scipy.interpolate import interp1d +from yaml import BaseLoader, load + +from pywit.component import Component +from pywit.element import Element # A dictionary mapping the datafile-prefixes (as used in IW2D) to (is_impedance, plane, (a, b, c, d)) # Where is impedance is True if the component in question is an impedance component, and False if it is a @@ -61,16 +64,16 @@ def get_iw2d_config_value(key: str) -> Any: return config[key] -def import_data_iw2d(directory: Union[str, Path], - common_string: str) -> List[Tuple[bool, str, Tuple[int, int, int, int], np.ndarray]]: +def component_recipes_from_legacy_iw2d_files(directory: Union[str, Path], + common_string: str) -> List[Tuple[bool, str, Tuple[int, int, int, int], np.ndarray, np.ndarray]]: """ - Imports data on the format generated by the IW2D library and prepares it for construction of Components and + Imports data on the format generated by the C++ executables of the IW2D library and prepares it for construction of Components and Elements in PyWIT :param directory: The directory where the .dat files are located. All .dat files must be in the root of this directory :param common_string: A string preceding ".dat" in the filenames of all files to be imported - :return: A list of tuples, one for each imported file, on the form (is_impedance, plane, (a, b, c, d), data), - where data is a numpy array with 2 or 3 columns, one for each column of the imported datafile. + :return: A list of tuples, one for each imported file, on the form (is_impedance, plane, (a, b, c, d), x, y), + where x and y are numpy arrays with , one for the frequency/time of the data file and one for the impedance/wake """ # The tuples are iteratively appended to this array component_recipes = [] @@ -98,39 +101,73 @@ def import_data_iw2d(directory: Union[str, Path], seen_configs.append((is_impedance, plane, exponents)) # Loads the data from the file as a numpy array - data = np.loadtxt(f"{directory}/{filename}", delimiter=" ", skiprows=1) + raw_data = np.loadtxt(f"{directory}/{filename}", delimiter=" ", skiprows=1) + + # Extracts the position/frequency column of the data array + x = raw_data[:, 0] + + # Extracts the wake/impedances from the data array + y = raw_data[:, 1] + (1j * raw_data[:, 2] if raw_data.shape[1] == 3 else 0) # Appends the constructed tuple to component_recipes - component_recipes.append((is_impedance, plane, exponents, data)) + component_recipes.append((is_impedance, plane, exponents, x, y)) # Validates that at least one file in the directory matched the user-specified common_string assert component_recipes, f"No files in '{directory}' matched the common string '{common_string}'." return component_recipes +def component_recipes_from_iw2dresult(iw2d_result: IW2DResult) -> List[Tuple[bool, str, Tuple[int, int, int, int], np.ndarray, np.ndarray]]: + """Prepare the data of an IW2DResult for construction of PyWIT Component objects + + :param directory: An IW2DResult + :type directory: IW2DResult + :return: A list of Component recipes, each being a tuple containing the following: + (is_impedance, plane, exponents, frequencies, impedance_values) + :rtype: List[Tuple[bool, str, Tuple[int, int, int, int], np.ndarray, np.ndarray]] + """ + + frequencies = iw2d_result.data.index + + component_recipes = [] + + for component_name in iw2d_result.data.columns: + + # Make sure we are reading an impedance. + # At the time of writing, only impedances are implemented in the IW2D Python interface + # In the future, the wake could possibly be read as well, but the implementation has to be + # considered then. + assert component_name.lower().startswith("z") + + component_metadata = iw2d_result.metadata[component_name] + plane = component_metadata["Plane"] + exponents = component_metadata["Exponents"] + + component_recipes.append( + (True, plane, exponents, frequencies, iw2d_result.data[component_name]) + ) + + return component_recipes + + def create_component_from_data(is_impedance: bool, plane: str, exponents: Tuple[int, int, int, int], - data: np.ndarray, relativistic_gamma: float) -> Component: + x: np.ndarray, y: np.ndarray, relativistic_gamma: float) -> Component: """ Creates a Component from a component recipe, e.g. as generated by import_data_iw2d :param is_impedance: a bool which is True if the component to be generated is an impedance component, and False if it is a wake component :param plane: the plane of the component :param exponents: the exponents of the component on the form (a, b, c, d) - :param data: a numpy-array with 2 or 3 columns corresponding to (frequency, Re[impedance], Im[impedance]) or - (position, Re[wake], Im[wake]), where the imaginary column is optional + :param x: a 1D numpy-array containing frequencies [Hz] if is_impedance==True, else z values [m] + :param y: a 1D numpy-array containing impedance values if is_impedance==True, else wake values :param relativistic_gamma: The relativistic gamma used in the computation of the data files. Necessary for converting the position-data of IW2D into time-data for PyWIT :return: A Component object as specified by the input """ - # Extracts the position/frequency column of the data array - x = data[:, 0] - + if not is_impedance: # Converts position-data to time-data using Lorentz factor - x /= 299792458 * np.sqrt(1 - (1 / relativistic_gamma ** 2)) - - # Extracts the wake/values from the data array - y = data[:, 1] + (1j * data[:, 2] if data.shape[1] == 3 else 0) + x /= speed_of_light * np.sqrt(1 - (1 / relativistic_gamma ** 2)) # Creates a callable impedance/wake function from the data array func = interp1d(x=x, y=y, kind='linear', assume_sorted=True, bounds_error=False, fill_value=(0., 0.)) @@ -143,234 +180,23 @@ def create_component_from_data(is_impedance: bool, plane: str, exponents: Tuple[ test_exponents=exponents[2:], ) -@dataclass(frozen=True, eq=True) -class Layer: - # The distance in mm of the inner surface of the layer from the reference orbit - thickness: float - dc_resistivity: float - resistivity_relaxation_time: float - re_dielectric_constant: float - magnetic_susceptibility: float - permeability_relaxation_frequency: float - - -@dataclass(frozen=True, eq=True) -class Sampling: - start: float - stop: float - # 0 = logarithmic, 1 = linear, 2 = both - scan_type: int - added: Tuple[float] - sampling_exponent: Optional[float] = None - points_per_decade: Optional[float] = None - min_refine: Optional[float] = None - max_refine: Optional[float] = None - n_refine: Optional[float] = None - - -# Define several dataclasses for IW2D input elements. We must split mandatory -# and optional arguments into private dataclasses to respect the resolution -# order. The public classes RoundIW2DInput and FlatIW2D input inherit from -# from the private classes. -# https://stackoverflow.com/questions/51575931/class-inheritance-in-python-3-7-dataclasses - -@dataclass(frozen=True, eq=True) -class _IW2DInputBase: - machine: str - length: float - relativistic_gamma: float - calculate_wake: bool - f_params: Sampling - - -@dataclass(frozen=True, eq=True) -class _IW2DInputOptional: - z_params: Optional[Sampling] = None - long_factor: Optional[float] = None - wake_tol: Optional[float] = None - freq_lin_bisect: Optional[float] = None - comment: Optional[str] = None - - -@dataclass(frozen=True, eq=True) -class IW2DInput(_IW2DInputOptional, _IW2DInputBase): - pass - - -@dataclass(frozen=True, eq=True) -class _RoundIW2DInputBase(_IW2DInputBase): - layers: Tuple[Layer] - inner_layer_radius: float - # (long, xdip, ydip, xquad, yquad) - yokoya_factors: Tuple[float, float, float, float, float] - - -@dataclass(frozen=True, eq=True) -class _RoundIW2DInputOptional(_IW2DInputOptional): - pass - - -@dataclass(frozen=True, eq=True) -class RoundIW2DInput(_RoundIW2DInputOptional, _RoundIW2DInputBase): - pass - - -@dataclass(frozen=True, eq=True) -class _FlatIW2DInputBase(_IW2DInputBase): - top_bottom_symmetry: bool - top_layers: Tuple[Layer] - top_half_gap: float - - -@dataclass(frozen=True, eq=True) -class _FlatIW2DInputOptional(_IW2DInputOptional): - bottom_layers: Optional[Tuple[Layer]] = None - bottom_half_gap: Optional[float] = None - - -@dataclass(frozen=True, eq=True) -class FlatIW2DInput(_FlatIW2DInputOptional, _FlatIW2DInputBase): - pass - - -def _iw2d_format_layer(layer: Layer, n: int) -> str: - """ - Formats the information describing a single layer into a string in accordance with IW2D standards. - Intended only as a helper-function for create_iw2d_input_file. - :param layer: A Layer object - :param n: The 1-indexed index of the layer - :return: A string on the correct format for IW2D - """ - return (f"Layer {n} DC resistivity (Ohm.m):\t{layer.dc_resistivity}\n" - f"Layer {n} relaxation time for resistivity (ps):\t{layer.resistivity_relaxation_time * 1e12}\n" - f"Layer {n} real part of dielectric constant:\t{layer.re_dielectric_constant}\n" - f"Layer {n} magnetic susceptibility:\t{layer.magnetic_susceptibility}\n" - f"Layer {n} relaxation frequency of permeability (MHz):\t{layer.permeability_relaxation_frequency / 1e6}\n" - f"Layer {n} thickness in mm:\t{layer.thickness * 1e3}\n") - - -def _iw2d_format_freq_params(params: Sampling) -> str: - """ - Formats the frequency-parameters of an IW2DInput object to a string in accordance with IW2D standards. - Intended only as a helper-function for create_iw2d_input_file. - :param params: Parameters specifying a frequency-sampling - :return: A string on the correct format for IW2D - """ - lines = [f"start frequency exponent (10^) in Hz:\t{np.log10(params.start)}", - f"stop frequency exponent (10^) in Hz:\t{np.log10(params.stop)}", - f"linear (1) or logarithmic (0) or both (2) frequency scan:\t{params.scan_type}"] - - if params.sampling_exponent is not None: - lines.append(f"sampling frequency exponent (10^) in Hz (for linear):\t{np.log10(params.sampling_exponent)}") - - if params.points_per_decade is not None: - lines.append(f"Number of points per decade (for log):\t{params.points_per_decade}") - - if params.min_refine is not None: - lines.append(f"when both, fmin of the refinement (in THz):\t{params.min_refine / 1e12}") - - if params.max_refine is not None: - lines.append(f"when both, fmax of the refinement (in THz):\t{params.max_refine / 1e12}") - - if params.n_refine is not None: - lines.append(f"when both, number of points in the refinement:\t{params.n_refine}") - - lines.append(f"added frequencies [Hz]:\t{' '.join(str(f) for f in params.added)}") - - return "\n".join(lines) + "\n" - - -def _iw2d_format_z_params(params: Sampling) -> str: - """ - Formats the position-parameters of an IW2DInput object to a string in accordance with IW2D standards. - Intended only as a helper-function for create_iw2d_input_file. - :param params: Parameters specifying a position-sampling - :return: A string on the correct format for IW2D - """ - lines = [f"linear (1) or logarithmic (0) or both (2) scan in z for the wake:\t{params.scan_type}"] - - if params.sampling_exponent is not None: - lines.append(f"sampling distance in m for the linear sampling:\t{params.sampling_exponent}") - - if params.min_refine is not None: - lines.append(f"zmin in m of the linear sampling:\t{params.min_refine}") - - if params.max_refine is not None: - lines.append(f"zmax in m of the linear sampling:\t{params.max_refine}") - - if params.points_per_decade is not None: - lines.append(f"Number of points per decade for the logarithmic sampling:\t{params.points_per_decade}") - - lines.append(f"exponent (10^) of zmin (in m) of the logarithmic sampling:\t{np.log10(params.start)}") - lines.append(f"exponent (10^) of zmax (in m) of the logarithmic sampling:\t{np.log10(params.stop)}") - lines.append(f"added z [m]:\t{' '.join(str(z) for z in params.added)}") - - return "\n".join(lines) + "\n" +def standard_layer(thickness: float, dc_resistivity: float, resistivity_relaxation_time: float, re_dielectric_constant: float, + magnetic_susceptibility: float, permeability_relaxation_frequency: float) -> IW2DLayer: + return IW2DLayer( + thickness=thickness, + eps1=Eps1FromResistivity( + dc_resistivity=dc_resistivity, + resistivity_relaxation_time=resistivity_relaxation_time, + re_dielectric_constant=re_dielectric_constant + ), + mu1=Mu1FromSusceptibility( + magnetic_susceptibility=magnetic_susceptibility, + permeability_relaxation_frequency=permeability_relaxation_frequency + ) + ) -def create_iw2d_input_file(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], filename: Union[str, Path]) -> None: - """ - Writes an IW2DInput object to the specified filename using the appropriate format for interfacing with the IW2D - software. - :param iw2d_input: An IW2DInput object to be written - :param filename: The filename (including path) of the file the IW2DInput object will be written to - :return: Nothing - """ - # Creates the input-file at the location specified by filename - file = open(filename, 'w') - - file.write(f"Machine:\t{iw2d_input.machine}\n" - f"Relativistic Gamma:\t{iw2d_input.relativistic_gamma}\n" - f"Impedance Length in m:\t{iw2d_input.length}\n") - - # Just pre-defining layers to avoid potentially unbound variable later on - layers = [] - if isinstance(iw2d_input, RoundIW2DInput): - file.write(f"Number of layers:\t{len(iw2d_input.layers)}\n" - f"Layer 1 inner radius in mm:\t{iw2d_input.inner_layer_radius * 1e3}\n") - layers = iw2d_input.layers - elif isinstance(iw2d_input, FlatIW2DInput): - if iw2d_input.bottom_layers: - print("WARNING: bottom layers of IW2D input object are being ignored because the top_bottom_symmetry flag " - "is enabled") - file.write(f"Number of upper layers in the chamber wall:\t{len(iw2d_input.top_layers)}\n") - if iw2d_input.top_layers: - file.write(f"Layer 1 inner half gap in mm:\t{iw2d_input.top_half_gap * 1e3}\n") - layers = iw2d_input.top_layers - - for i, layer in enumerate(layers): - file.write(_iw2d_format_layer(layer, i + 1)) - - if isinstance(iw2d_input, FlatIW2DInput) and not iw2d_input.top_bottom_symmetry: - file.write(f"Number of lower layers in the chamber wall:\t{len(iw2d_input.bottom_layers)}\n") - if iw2d_input.bottom_layers: - file.write(f"Layer -1 inner half gap in mm:\t{iw2d_input.bottom_half_gap * 1e3}\n") - for i, layer in enumerate(iw2d_input.bottom_layers): - file.write(_iw2d_format_layer(layer, -(i + 1))) - - if isinstance(iw2d_input, FlatIW2DInput): - file.write(f"Top bottom symmetry (yes or no):\t{'yes' if iw2d_input.top_bottom_symmetry else 'no'}\n") - - file.write(_iw2d_format_freq_params(iw2d_input.f_params)) - if iw2d_input.z_params is not None: - file.write(_iw2d_format_z_params(iw2d_input.z_params)) - - if isinstance(iw2d_input, RoundIW2DInput): - file.write(f"Yokoya factors long, xdip, ydip, xquad, yquad:\t" - f"{' '.join(str(n) for n in iw2d_input.yokoya_factors)}\n") - - for desc, val in zip(["factor weighting the longitudinal impedance error", - "tolerance (in wake units) to achieve", - "frequency above which the mesh bisecting is linear [Hz]", - "Comments for the output files names"], - [iw2d_input.long_factor, iw2d_input.wake_tol, iw2d_input.freq_lin_bisect, iw2d_input.comment]): - if val is not None: - file.write(f"{desc}:\t{val}\n") - - file.close() - - -def check_already_computed(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], +def check_already_computed(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], additional_iw2d_params: Union[InputFileFreqParams, InputFileWakeParams], name: str) -> Tuple[bool, str, Union[str, Path]]: """ Checks if a simulation with inputs iw2d_input is already present in the hash database. @@ -384,9 +210,8 @@ def check_already_computed(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], """ projects_path = Path(get_iw2d_config_value('project_directory')) - # initialize read ready to all False for convenience # create the hash key - input_hash = sha256(iw2d_input.__str__().encode()).hexdigest() + input_hash = iw2d_input.input_file_hash(additional_iw2d_params) # we have three levels of directories: the first two are given by the first and second letters of the hash keys, # the third is given by the rest of the hash keys. @@ -394,6 +219,8 @@ def check_already_computed(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], directory_level_2 = directory_level_1.joinpath(input_hash[2:4]) working_directory = directory_level_2.joinpath(input_hash[4:]) + calculate_wake = isinstance(additional_iw2d_params, InputFileWakeParams) + already_computed = True # check if the directories exist. If they do not exist we create @@ -410,7 +237,7 @@ def check_already_computed(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], os.mkdir(working_directory) components = [] - if not iw2d_input.calculate_wake: + if not calculate_wake: for component in component_names.keys(): # the ycst component is only given in the case of a flat chamber and the x component is never given if component.startswith('z') and 'cst' not in component: @@ -481,7 +308,7 @@ def check_valid_working_directory(working_directory: Path): check_valid_hash_chunk(working_directory.name, 60)) -def add_iw2d_input_to_database(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], input_hash: str, +def add_iw2d_input_to_database(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], additional_iw2d_params: Union[InputFileFreqParams, InputFileWakeParams], input_hash: str, working_directory: Union[str, Path]): """ Add the iw2d input to the repository containing the simulations @@ -509,11 +336,15 @@ def add_iw2d_input_to_database(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], if not os.path.exists(working_directory): os.mkdir(working_directory) - create_iw2d_input_file(iw2d_input, working_directory.joinpath(f"input.txt")) + save_path = working_directory.joinpath(f"input.txt") + if isinstance(additional_iw2d_params, InputFileWakeParams): + iw2d_input.to_wake_input_file(save_path, additional_iw2d_params) + else: + iw2d_input.to_impedance_input_file(save_path, additional_iw2d_params) -def create_element_using_iw2d(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], name: str, beta_x: float, beta_y: float, - tag: str = 'IW2D') -> Element: +def create_element_using_iw2d_legacy(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], additional_iw2d_params: Union[InputFileFreqParams, InputFileWakeParams], + name: str, beta_x: float, beta_y: float, tag: str = 'IW2D') -> Element: """ Create and return an Element using IW2D object. :param iw2d_input: the IW2DInput object @@ -528,38 +359,30 @@ def create_element_using_iw2d(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], assert verify_iw2d_config_file(), "The binary and/or project directories specified in config/iw2d_settings.yaml " \ "do not exist or do not contain the required files and directories." - # when looking for this IW2DInput in the database, the comment and the machine name don't necessarily need to be - # the same as the in the old simulation so we ignore it for creating the hash - iw2d_input_dict = iw2d_input.__dict__ - comment = iw2d_input_dict['comment'] - machine = iw2d_input_dict['machine'] - iw2d_input_dict['comment'] = '' - iw2d_input_dict['machine'] = '' - # the path to the folder containing the IW2D executables bin_path = Path(get_iw2d_config_value('binary_directory')) # the path to the folder containing the database of already computed elements + # Check if wake should be calculated + calculate_wake = isinstance(additional_iw2d_params, InputFileWakeParams) + # check if the element is already present in the database and create the hash key corresponding to the IW2D input - already_computed, input_hash, working_directory = check_already_computed(iw2d_input, name) + already_computed, input_hash, working_directory = check_already_computed(iw2d_input, additional_iw2d_params, name) # if an element with the same inputs is not found inside the database, perform the computations and add the results # to the database if not already_computed: add_iw2d_input_to_database(iw2d_input, input_hash, working_directory) - bin_string = ("wake_" if iw2d_input.calculate_wake else "") + \ + bin_string = ("wake_" if calculate_wake else "") + \ ("round" if isinstance(iw2d_input, RoundIW2DInput) else "flat") + "chamber.x" subprocess.run(f'{bin_path.joinpath(bin_string)} < input.txt', shell=True, cwd=working_directory) # When the wake is computed with IW2D, a second set of files is provided by IW2D. These correspond to a "converged" # simulation with double the number of mesh points for the wake. They files have the _precise suffix to their name. # If the wake is computed, we retrieve these file to create the pywit element. - common_string = "_precise" if iw2d_input.calculate_wake else '' + common_string = "_precise" if calculate_wake else '' - component_recipes = import_data_iw2d(directory=working_directory, common_string=common_string) - - iw2d_input_dict['comment'] = comment - iw2d_input_dict['machine'] = machine + component_recipes = component_recipes_from_legacy_iw2d_files(directory=working_directory, common_string=common_string) return Element(length=iw2d_input.length, beta_x=beta_x, beta_y=beta_y, @@ -568,6 +391,51 @@ def create_element_using_iw2d(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], name=name, tag=tag, description='A resistive wall element created using IW2D') +def create_element_using_iw2d_python_interface(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], + frequencies: ArrayLike, name: str, + beta_x: float, beta_y: float, tag: str = 'IW2D') -> Element: + + assert " " not in name, "Spaces are not allowed in element name" + + frequencies = np.asarray(frequencies) + + # Make hash to key database directory + input_hash = iw2d_input.output_identification_hash() + iw2d_result_directory = Path(get_iw2d_config_value('project_directory')) / input_hash[0:2] / input_hash[2:4] / input_hash[4:] + + # If directory does not exist, make it and needed parent directories + if not iw2d_result_directory.is_dir(): + iw2d_result_directory.mkdir(parents=True) + + # See if all files are present + files_present = [file_name in os.listdir(iw2d_result_directory) for file_name in ["metadata.json", "input_object.pickle", "data.pickle"]] + if all(files_present): + # Load the existing result and see if all frequencies are present + existing_iw2d_result = load_iw2d_result(iw2d_result_directory) + missing_freqs = frequencies[~np.isin(frequencies, existing_iw2d_result.data.index)] + + if len(missing_freqs) > 0: + # If not all freqs were present, calculate the missing impedances and save + # the result, merging with the existing data + # NOTE: With update_inplace set to True, iw2d_result.data.index might contain frequencies + # not present in `frequencies` given as input parameter above. + iw2d_result = iw2d_impedance(iw2d_input, missing_freqs) + iw2d_result.save(iw2d_result_directory, attempt_merge=True, update_inplace=True) + else: + iw2d_result = existing_iw2d_result + else: + iw2d_result = iw2d_impedance(iw2d_input, frequencies) + iw2d_result.save(iw2d_result_directory, attempt_merge=True, update_inplace=True) + + return Element( + length=iw2d_result.input_object.length, + beta_x=beta_x, beta_y=beta_y, + components=[create_component_from_data(*recipe, iw2d_result.input_object.relativistic_gamma) + for recipe in component_recipes_from_iw2dresult(iw2d_result)], + name=name, tag=tag, description='A resistive wall element created using IW2D' + ) + + def verify_iw2d_config_file() -> bool: bin_path = Path(get_iw2d_config_value('binary_directory')) projects_path = Path(get_iw2d_config_value('project_directory')) @@ -582,106 +450,9 @@ def verify_iw2d_config_file() -> bool: return True -def _typecast_sampling_dict(d: Dict[str, str]) -> Dict[str, Any]: - added = [float(f) for f in d['added'].split()] if 'added' in d else [] - added = tuple(added) - scan_type = int(d['scan_type']) - d.pop('added'), d.pop('scan_type') - - new_dict = {k: float(v) for k, v in d.items()} - new_dict['added'] = added - new_dict['scan_type'] = scan_type - return new_dict - - -def _create_iw2d_input_from_dict(d: Dict[str, Any]) -> Union[FlatIW2DInput, RoundIW2DInput]: - is_round = d['is_round'].lower() in ['true', 'yes', 'y', '1'] - d.pop('is_round') - layers, inner_layer_radius, yokoya_factors = list(), float(), tuple() - top_layers, top_half_gap, bottom_layers, bottom_half_gap = list(), float(), None, None - - if is_round: - inner_layer_radius = d['inner_layer_radius'] - if 'layers' in d: - layers_dicts = [{k: float(v) for k, v in layer.items()} for layer in d['layers']] - layers = [Layer(**kwargs) for kwargs in layers_dicts] - d.pop('layers') - else: - if 'top_layers' in d: - top_layers_dicts = [{k: float(v) for k, v in layer.items()} for layer in d['top_layers']] - top_layers = [Layer(**kwargs) for kwargs in top_layers_dicts] - top_half_gap = d['top_half_gap'] - d.pop('top_layers') - if d['top_bottom_symmetry'].lower() in ['true', 'yes', 'y', '1']: - bottom_layers = None - else: - bottom_layers_dicts = [{k: float(v) for k, v in layer.items()} for layer in d['bottom_layers']] - bottom_layers = [Layer(**kwargs) for kwargs in bottom_layers_dicts] - bottom_half_gap = d['bottom_half_gap'] - d.pop('bottom_layers') - - if 'yokoya_factors' in d: - yokoya_factors = tuple(float(x) for x in d['yokoya_factors'].split()) - d.pop('yokoya_factors') - - f_params = Sampling(**_typecast_sampling_dict(d['f_params'])) - z_params = Sampling(**_typecast_sampling_dict(d['z_params'])) \ - if d['calculate_wake'].lower() in ['true', 'yes', 'y', '1'] else None - - d.pop('f_params') - d.pop('z_params', None) - - transformations = { - 'machine': str, - 'length': float, - 'relativistic_gamma': float, - 'calculate_wake': lambda x: x.lower() in ['true', 'yes', 'y', '1'], - 'long_factor': float, - 'wake_tol': float, - 'freq_lin_bisect': float, - 'comment': str - } - - new_dict = {k: transformations[k](d[k]) if k in d else None for k in transformations} - - if is_round: - return RoundIW2DInput( - f_params=f_params, - z_params=z_params, - layers=tuple(layers), - inner_layer_radius=inner_layer_radius, - yokoya_factors=yokoya_factors, - **new_dict - ) - else: - return FlatIW2DInput( - f_params=f_params, - z_params=z_params, - top_bottom_symmetry=d['top_bottom_symmetry'].lower() in ['true', 'yes', 'y', '1'], - top_layers=tuple(top_layers), - top_half_gap=top_half_gap, - bottom_layers=bottom_layers, - bottom_half_gap=bottom_half_gap, - **new_dict - ) - - -def create_iw2d_input_from_yaml(name: str) -> Union[FlatIW2DInput, RoundIW2DInput]: - """ - Create a IW2DInput object from one of the inputs specified in the `pywit/config/iw2d_inputs.yaml` database - :param name: the name of the input which is read from the yaml database - :return: the newly initialized IW2DInput object - """ - path = Path.home().joinpath('pywit').joinpath('config').joinpath('iw2d_inputs.yaml') - with open(path) as file: - inputs = load(file, Loader=BaseLoader) - d = inputs[name] - - return _create_iw2d_input_from_dict(d) - - -def create_multiple_elements_using_iw2d(iw2d_inputs: List[IW2DInput], names: List[str], - beta_xs: List[float], beta_ys: List[float]) -> List[Element]: +def create_multiple_elements_using_iw2d_legacy(iw2d_inputs: Sequence[Union[FlatIW2DInput, RoundIW2DInput]], + additional_iw2d_params: Sequence[Union[InputFileFreqParams, InputFileWakeParams]], + names: Sequence[str], beta_xs: Sequence[float], beta_ys: Sequence[float]) -> List[Element]: """ Create and return a list of Element's using a list of IW2D objects. :param iw2d_inputs: the list of IW2DInput objects @@ -699,8 +470,33 @@ def create_multiple_elements_using_iw2d(iw2d_inputs: List[IW2DInput], names: Lis assert verify_iw2d_config_file(), "The binary and/or project directories specified in config/iw2d_settings.yaml " \ "do not exist or do not contain the required files and directories." - elements = Parallel(n_jobs=-1, prefer='threads')(delayed(create_element_using_iw2d)( + elements = Parallel(n_jobs=-1, prefer='threads')(delayed(create_element_using_iw2d_legacy)( + iw2d_inputs[i], + additional_iw2d_params[i], + names[i], + beta_xs[i], + beta_ys[i] + ) for i in range(len(names))) + + return elements + + +def create_multiple_elements_using_iw2d_python_interface(iw2d_inputs: Sequence[Union[FlatIW2DInput, RoundIW2DInput]], + frequencies: Sequence[ArrayLike], names: Sequence[str], + beta_xs: Sequence[float], beta_ys: Sequence[float]) -> List[Element]: + + assert len(iw2d_inputs) == len(names) == len(beta_xs) == len(beta_ys), "All input lists need to have the same" \ + "number of elements" + + for name in names: + assert " " not in name, "Spaces are not allowed in element name" + + assert verify_iw2d_config_file(), "The binary and/or project directories specified in config/iw2d_settings.yaml " \ + "do not exist or do not contain the required files and directories." + + elements = Parallel(n_jobs=-1, prefer='threads')(delayed(create_element_using_iw2d_python_interface)( iw2d_inputs[i], + frequencies[i], names[i], beta_xs[i], beta_ys[i] @@ -709,7 +505,7 @@ def create_multiple_elements_using_iw2d(iw2d_inputs: List[IW2DInput], names: Lis return elements -def create_htcondor_input_file(iw2d_input: IW2DInput, name: str, directory: Union[str, Path]) -> None: +def create_htcondor_input_file(iw2d_input: Union[FlatIW2DInput, RoundIW2DInput], name: str, directory: Union[str, Path]) -> None: exec_string = "" if iw2d_input.calculate_wake: exec_string += "wake_" diff --git a/pywit/materials.py b/pywit/materials.py index 8fba285..1f55990 100644 --- a/pywit/materials.py +++ b/pywit/materials.py @@ -1,4 +1,5 @@ -from pywit.interface import Layer +from IW2D import IW2DLayer +from pywit.interface import standard_layer from pywit.utils import round_sigfigs from pathlib import Path @@ -7,7 +8,7 @@ from typing import Callable, Tuple -def layer_from_dict(thickness: float, material_dict: dict) -> Layer: +def layer_from_dict(thickness: float, material_dict: dict) -> IW2DLayer: """ Define a layer from a dictionary containing the materials properties. @@ -31,7 +32,7 @@ def layer_from_dict(thickness: float, material_dict: dict) -> Layer: assert not any(missing_properties_list), '{} missing from the input dictionary'.format( ", ".join(required_material_properties[np.asarray(missing_properties_list)])) - return Layer(thickness=thickness, + return standard_layer(thickness=thickness, dc_resistivity=material_dict['dc_resistivity'], resistivity_relaxation_time=material_dict['resistivity_relaxation_time'], re_dielectric_constant=material_dict['re_dielectric_constant'], @@ -40,7 +41,7 @@ def layer_from_dict(thickness: float, material_dict: dict) -> Layer: def layer_from_json_material_library(thickness: float, material_key: str, - library_path: Path = Path(__file__).parent.joinpath('materials.json')) -> Layer: + library_path: Path = Path(__file__).parent.joinpath('materials.json')) -> IW2DLayer: """ Define a layer using the materials.json library of materials properties. @@ -132,7 +133,7 @@ def magnetoresistance_Kohler(B_times_Sratio: float, P: Tuple[float, ...]) -> flo return 10.**np.polyval(P, np.log10(B_times_Sratio)) -def copper_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B: float = 0) -> Layer: +def copper_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B: float = 0) -> IW2DLayer: """ Define a layer of pure copper material at any temperature, any B field and any RRR. We use a magnetoresistance law fitted from the UPPER curve of the plot in NIST, "Properties of copper and copper @@ -178,7 +179,7 @@ def copper_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B: n = 6.022e23*Z*rho_m*1e6 / A tauAC = round_sigfigs(me / (n*dc_resistivity*e**2),3) # relaxation time (s) (3 significant digits) - return Layer(thickness=thickness, + return standard_layer(thickness=thickness, dc_resistivity=dc_resistivity, resistivity_relaxation_time=tauAC, re_dielectric_constant=1, @@ -186,7 +187,7 @@ def copper_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B: permeability_relaxation_frequency=np.inf) -def tungsten_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B: float = 0) -> Layer: +def tungsten_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B: float = 0) -> IW2DLayer: """ Define a layer of tungsten at any temperature, any B field and any RRR. The resistivity vs. temperature and RRR is found from Hust & Lankford (see above). @@ -232,7 +233,7 @@ def tungsten_at_temperature(thickness: float, T: float = 300, RRR: float = 70, B n = 6.022e23 * Z * rhom * 1e6 / A tauAC = round_sigfigs(me/(n*dc_resistivity*e**2),3) # relaxation time (s) (3 significant digits) - return Layer(thickness=thickness, + return standard_layer(thickness=thickness, dc_resistivity=dc_resistivity, resistivity_relaxation_time=tauAC, re_dielectric_constant=1, diff --git a/pywit/utilities.py b/pywit/utilities.py index f2ce7c1..d173a0f 100644 --- a/pywit/utilities.py +++ b/pywit/utilities.py @@ -1,6 +1,6 @@ from pywit.component import Component from pywit.element import Element -from pywit.interface import Layer, FlatIW2DInput, RoundIW2DInput +from IW2D import IW2DLayer, Eps1FromResistivity, Mu1FromSusceptibility, FlatIW2DInput, RoundIW2DInput from pywit.interface import component_names from yaml import load, SafeLoader @@ -207,7 +207,7 @@ def create_many_resonators_element(length: float, beta_x: float, beta_y: float, def create_classic_thick_wall_component(plane: str, exponents: Tuple[int, int, int, int], - layer: Layer, radius: float) -> Component: + layer: IW2DLayer, radius: float) -> Component: """ Creates a single component object modeling a resistive wall impedance/wake, based on the "classic thick wall formula" (see e.g. A. W. Chao, chap. 2 in @@ -220,10 +220,16 @@ def create_classic_thick_wall_component(plane: str, exponents: Tuple[int, int, i :param radius: the chamber radius in m :return: A component object """ + + if not isinstance(layer.eps1, Eps1FromResistivity): + raise ValueError(f"Input files can only be generated using IW2D.Eps1FromResistivity as eps1 attribute of IW2D.IW2DLayer") + + if not isinstance(layer.mu1, Mu1FromSusceptibility): + raise ValueError(f"Input files can only be generated using IW2D.Mu1FromSusceptibility as mu1 attribute of IW2D.IW2DLayer") # Material properties required for the skin depth computation are derived from the input Layer attributes - material_resistivity = layer.dc_resistivity - material_relative_permeability = 1. + layer.magnetic_susceptibility + material_resistivity = layer.eps1.dc_resistivity + material_relative_permeability = 1. + layer.mu1.magnetic_susceptibility material_permeability = material_relative_permeability * mu0 # Create the skin depth as a function offrequency and layer properties @@ -250,7 +256,7 @@ def create_classic_thick_wall_component(plane: str, exponents: Tuple[int, int, i def _zlong_round_single_layer_approx(frequencies: ArrayLike, gamma: float, - layer: Layer, radius: float, length: float) -> ArrayLike: + layer: IW2DLayer, radius: float, length: float) -> ArrayLike: """ Function to compute the longitudinal resistive-wall impedance from the single-layer, approximated formula for a cylindrical structure, @@ -271,10 +277,8 @@ def _zlong_round_single_layer_approx(frequencies: ArrayLike, gamma: float, omega = 2*pi*frequencies k = omega/(beta*c_light) - rho = layer.dc_resistivity - tau = layer.resistivity_relaxation_time - mu1 = 1.+layer.magnetic_susceptibility - eps1 = 1. - 1j/(eps0*rho*omega*(1.+1j*omega*tau)) + mu1 = layer.mu1(frequencies) + eps1 = layer.eps1(frequencies) nu = k*sqrt(1.-beta**2*eps1*mu1) coef_long = 1j*omega*mu0*length/(2.*pi*beta**2*gamma**2) @@ -289,7 +293,7 @@ def _zlong_round_single_layer_approx(frequencies: ArrayLike, gamma: float, def _zdip_round_single_layer_approx(frequencies: ArrayLike, gamma: float, - layer: Layer, radius: float, length: float) -> ArrayLike: + layer: IW2DLayer, radius: float, length: float) -> ArrayLike: """ Function to compute the transverse dipolar resistive-wall impedance from the single-layer, approximated formula for a cylindrical structure, @@ -310,10 +314,8 @@ def _zdip_round_single_layer_approx(frequencies: ArrayLike, gamma: float, omega = 2*pi*frequencies k = omega/(beta*c_light) - rho = layer.dc_resistivity - tau = layer.resistivity_relaxation_time - mu1 = 1.+layer.magnetic_susceptibility - eps1 = 1. - 1j/(eps0*rho*omega*(1.+1j*omega*tau)) + mu1 = layer.mu1(frequencies) + eps1 = layer.eps1(frequencies) nu = k*sqrt(1.-beta**2*eps1*mu1) coef_dip = 1j*k**2*Z0*length/(4.*pi*beta*gamma**4) @@ -377,11 +379,11 @@ def create_resistive_wall_single_layer_approx_component(plane: str, exponents: T if len(input_data.layers) > 1: raise NotImplementedError("Input data can have only one layer") layer = input_data.layers[0] - yok_long = input_data.yokoya_factors[0] - yok_dipx = input_data.yokoya_factors[1] - yok_dipy = input_data.yokoya_factors[2] - yok_quax = input_data.yokoya_factors[3] - yok_quay = input_data.yokoya_factors[4] + yok_long = input_data.yokoya_zlong + yok_dipx = input_data.yokoya_zxdip + yok_dipy = input_data.yokoya_zydip + yok_quax = input_data.yokoya_zxquad + yok_quay = input_data.yokoya_zyquad else: raise NotImplementedError("Input of type neither FlatIW2DInput nor RoundIW2DInput cannot be handled") diff --git a/setup.py b/setup.py index b6a1dbf..189b14f 100644 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ def run(self): "sortednp", "pyyaml", "joblib", + "IW2D", ], "test":[ "pytest", diff --git a/test/test_interface.py b/test/test_interface.py index 61ff977..091b2b5 100644 --- a/test/test_interface.py +++ b/test/test_interface.py @@ -2,11 +2,12 @@ import pytest -from pywit.interface import import_data_iw2d, create_component_from_data, Sampling +from pywit.interface import component_recipes_from_legacy_iw2d_files, create_component_from_data from pywit.interface import check_valid_working_directory, get_component_name from pywit.interface import check_already_computed, get_iw2d_config_value, RoundIW2DInput, FlatIW2DInput, add_iw2d_input_to_database from pywit.parameters import * from pywit.materials import layer_from_json_material_library +from IW2D import RoundIW2DInput, FlatIW2DInput, InputFileFreqParams, InputFileWakeParams from pathlib import Path import glob @@ -41,7 +42,7 @@ def test_get_component_name_raise(is_impedance, plane, exponents): def test_duplicate_component_iw2d_import(): with raises(AssertionError) as error_message: - import_data_iw2d(directory=Path("test/test_data/iw2d/duplicate_components").resolve(), + component_recipes_from_legacy_iw2d_files(directory=Path("test/test_data/iw2d/duplicate_components").resolve(), common_string="WLHC_2layersup_0layersdown6.50mm") assert error_message.value.args[0] in ["The wake files 'WlongWLHC_2layersup_0layersdown6.50mm.dat' and " @@ -55,7 +56,7 @@ def test_duplicate_component_iw2d_import(): def test_no_matching_filename_iw2d_import(): with raises(AssertionError) as error_message: - import_data_iw2d(directory=Path("test/test_data/iw2d/valid_directory").resolve(), + component_recipes_from_legacy_iw2d_files(directory=Path("test/test_data/iw2d/valid_directory").resolve(), common_string="this_string_matches_no_file") expected_error_message = f"No files in " \ @@ -69,13 +70,11 @@ def test_valid_iw2d_component_import(): # Normally, the relativistic gamma would be an attribute of a required IW2DInput object, but here it has been # hard-coded instead relativstic_gamma = 479.605064966 - recipes = import_data_iw2d(directory=Path("test/test_data/iw2d/valid_directory").resolve(), + recipes = component_recipes_from_legacy_iw2d_files(directory=Path("test/test_data/iw2d/valid_directory").resolve(), common_string="precise") for recipe in recipes: component = create_component_from_data(*recipe, relativistic_gamma=relativstic_gamma) - data = recipe[-1] - x = data[:, 0] - y_actual = data[:, 1] + (1j * data[:, 2] if data.shape[1] == 3 else 0) + x, y_actual = recipe[-2:] if recipe[0]: np.testing.assert_allclose(y_actual, component.impedance(x), rtol=REL_TOL, atol=ABS_TOL) else: @@ -84,20 +83,29 @@ def test_valid_iw2d_component_import(): @pytest.fixture def iw2d_input(request): - f_params = Sampling(start=1, stop=1e9, scan_type=0, added=(1e2,)) - z_params = Sampling(start=1e-9, stop=1, scan_type=0, added=(1e-6,)) + f_params = InputFileFreqParams(use_log_sampling=True, use_lin_sampling=False, log_fmin=1, log_fmax=1e9, log_f_per_decade=1, added_f=(1e2,)) + wake_params = InputFileWakeParams(long_error_weight=1, wake_abs_tolerance=1, freq_lin_bisect=1e9, + use_log_sampling=True, use_lin_sampling=False, log_zmin=1e-9, log_zmax=1, log_z_per_decade=1, added_z=(1e-6,)) layers_tung = (layer_from_json_material_library(thickness=np.inf, material_key='W'),) if request.param['chamber_type'] == 'round': - return RoundIW2DInput(machine='test', length=1, relativistic_gamma=7000, - calculate_wake=request.param['wake_computation'], f_params=f_params, comment='test', - layers=layers_tung, inner_layer_radius=5e-2, yokoya_factors=(1, 1, 1, 1, 1), z_params=z_params) + input_object = RoundIW2DInput(length=1, relativistic_gamma=7000, + layers=layers_tung, inner_layer_radius=5e-2, + yokoya_zlong = 1, yokoya_zxdip = 1, + yokoya_zydip = 1, yokoya_zxquad = 0, + yokoya_zyquad = 0) if request.param['chamber_type'] == 'flat': - return FlatIW2DInput(machine='test', length=1, relativistic_gamma=7000, - calculate_wake=request.param['wake_computation'], f_params=f_params, comment='test', - top_bottom_symmetry=True, top_layers=layers_tung, top_half_gap=5e-2, z_params=z_params) - + input_object = FlatIW2DInput(length=1, relativistic_gamma=7000, + top_bottom_symmetry=True, top_layers=layers_tung, top_half_gap=5e-2) + + if request.param['wake_computation'] == True: + additional_params = wake_params + else: + additional_params = f_params + + return (input_object, additional_params) + def _remove_non_empty_directory(directory_path: Path): if not os.path.exists(directory_path): @@ -116,11 +124,12 @@ def _remove_non_empty_directory(directory_path: Path): ({'chamber_type': 'flat', 'wake_computation': True}, ['Zlong', 'Zxdip', 'Zydip', 'Zxquad', 'Zyquad', 'Zycst', 'Wlong', 'Wxdip', 'Wydip', 'Wxquad', 'Wyquad', 'Wycst'])] @pytest.mark.parametrize("iw2d_input, components_to_test", list_of_inputs_to_test, indirect=["iw2d_input"]) def test_check_already_computed(iw2d_input, components_to_test): + iw2d_input, additional_input_params = iw2d_input name = 'test_hash' # create the expected directories for the dummy input projects_path = Path(get_iw2d_config_value('project_directory')) - input_hash = sha256(iw2d_input.__str__().encode()).hexdigest() + input_hash = iw2d_input.input_file_hash(additional_input_params) directory_level_1 = projects_path.joinpath(input_hash[0:2]) directory_level_2 = directory_level_1.joinpath(input_hash[2:4]) working_directory = directory_level_2.joinpath(input_hash[4:]) @@ -143,18 +152,18 @@ def test_check_already_computed(iw2d_input, components_to_test): f.write(dummy_string) # check that the input is detected in the hashmap - already_computed, input_hash, working_directory = check_already_computed(iw2d_input, name) + already_computed, input_hash, working_directory = check_already_computed(iw2d_input, additional_input_params, name) assert already_computed # now we remove one component and verify that check_already_computed gives false os.remove(f'{working_directory}/Zlong_test.txt') - already_computed, input_hash, working_directory = check_already_computed(iw2d_input, name) + already_computed, input_hash, working_directory = check_already_computed(iw2d_input, additional_input_params, name) assert not already_computed # now we remove the folder and check that check_already_computed gives false _remove_non_empty_directory(working_directory) - already_computed, input_hash, working_directory = check_already_computed(iw2d_input, name) + already_computed, input_hash, working_directory = check_already_computed(iw2d_input, additional_input_params, name) assert not already_computed @@ -164,13 +173,14 @@ def test_check_already_computed(iw2d_input, components_to_test): @pytest.mark.parametrize("iw2d_input", [{'chamber_type': 'round', 'wake_computation': False}], indirect=["iw2d_input"]) def test_add_iw2d_input_to_database(iw2d_input): + iw2d_input, additional_input_params = iw2d_input projects_path = Path(get_iw2d_config_value('project_directory')) - input_hash = sha256(iw2d_input.__str__().encode()).hexdigest() + input_hash = iw2d_input.input_file_hash(additional_input_params) directory_level_1 = projects_path.joinpath(input_hash[0:2]) directory_level_2 = directory_level_1.joinpath(input_hash[2:4]) working_directory = directory_level_2.joinpath(input_hash[4:]) - add_iw2d_input_to_database(iw2d_input, input_hash, working_directory) + add_iw2d_input_to_database(iw2d_input, additional_input_params, input_hash, working_directory) assert os.path.exists(f"{working_directory}/input.txt") diff --git a/test/test_materials.py b/test/test_materials.py index 967f445..9549f33 100644 --- a/test/test_materials.py +++ b/test/test_materials.py @@ -40,7 +40,7 @@ def test_layer_from_dict_two_missing_keys(): def test_copper(T,B,rho): RRR = 70. copper_layer = materials.copper_at_temperature(1., T, RRR, B) - testing.assert_allclose(copper_layer.dc_resistivity, rho, atol=1e-11) + testing.assert_allclose(copper_layer.eps1.dc_resistivity, rho, atol=1e-11) @mark.parametrize("material_key, material_function, T, RRR", @@ -48,11 +48,11 @@ def test_copper(T,B,rho): ["W", 'tungsten_at_temperature', 300, 70], ]) @mark.parametrize("material_property, tolerance", - [ ['dc_resistivity', 2e-9], - ['resistivity_relaxation_time', 3e-15], - ['re_dielectric_constant', 0], - ['magnetic_susceptibility', 0], - ['permeability_relaxation_frequency', 0], + [ ['eps1.dc_resistivity', 2e-9], + ['eps1.resistivity_relaxation_time', 3e-15], + ['eps1.re_dielectric_constant', 0], + ['mu1.magnetic_susceptibility', 0], + ['mu1.permeability_relaxation_frequency', 0], ]) def test_materials_at_temperature(material_key, material_function, T, RRR, material_property, tolerance): @@ -61,7 +61,15 @@ def test_materials_at_temperature(material_key, material_function, T, layer_from_library = materials.layer_from_json_material_library(1.,material_key) assert layer_at_temperature.thickness == layer_from_library.thickness == 1. - testing.assert_allclose(getattr(layer_at_temperature,material_property), - getattr(layer_from_library,material_property), - atol=tolerance) + + eps_or_mu, property_name = material_property.split(".") + if eps_or_mu == "eps1": + testing.assert_allclose(getattr(layer_at_temperature.eps1,property_name), + getattr(layer_from_library.eps1,property_name), + atol=tolerance) + else: + testing.assert_allclose(getattr(layer_at_temperature.mu1,property_name), + getattr(layer_from_library.mu1,property_name), + atol=tolerance) + diff --git a/test/test_utilities.py b/test/test_utilities.py index a0ff8d0..efd7193 100644 --- a/test/test_utilities.py +++ b/test/test_utilities.py @@ -3,7 +3,7 @@ create_resistive_wall_single_layer_approx_component) from test_common import relative_error from pywit.parameters import * -from pywit.interface import Layer, FlatIW2DInput, RoundIW2DInput, Sampling, component_names +from pywit.interface import FlatIW2DInput, RoundIW2DInput, component_names from pywit.materials import layer_from_json_material_library, copper_at_temperature from typing import Dict @@ -225,9 +225,8 @@ def test_f_roi_resonator_component(): @fixture def flat_symmetric_approx_rw_element(): - flat_input = FlatIW2DInput(machine='LHC', length=1., - relativistic_gamma=7460.5232328, calculate_wake=False, - f_params=Sampling(1e3,1e13,0,added=(1e4,1e9),points_per_decade=0), + flat_input = FlatIW2DInput(length=1., + relativistic_gamma=7460.5232328, top_bottom_symmetry=True, top_layers=[layer_from_json_material_library(thickness=np.inf, material_key='Mo')], @@ -241,9 +240,8 @@ def flat_symmetric_approx_rw_element(): @fixture def flat_single_plate_approx_rw_element(): - flat_input = FlatIW2DInput(machine='LHC', length=1., - relativistic_gamma=7460.5232328, calculate_wake=False, - f_params=Sampling(1e3,1e13,0,added=(1e5,1e9),points_per_decade=0), + flat_input = FlatIW2DInput(length=1., + relativistic_gamma=7460.5232328, top_bottom_symmetry=False, top_layers=[layer_from_json_material_library(thickness=np.inf, material_key='graphite')], @@ -260,12 +258,15 @@ def flat_single_plate_approx_rw_element(): @fixture def round_single_layer_approx_rw_element(): - round_input = RoundIW2DInput(machine='LHC', length=0.03, - relativistic_gamma=479.6, calculate_wake=False, - f_params=Sampling(1e3,1e13,0,added=(1e8),points_per_decade=0), + round_input = RoundIW2DInput(length=0.03, + relativistic_gamma=479.6, layers=[copper_at_temperature(thickness=np.inf,T=293)], inner_layer_radius=0.02, - yokoya_factors=(1,1,1,0,0), + yokoya_zlong = 1, + yokoya_zxdip = 1, + yokoya_zydip = 1, + yokoya_zxquad = 0, + yokoya_zyquad = 0, ) return create_resistive_wall_single_layer_approx_element( @@ -300,9 +301,8 @@ def test_single_layer_RW_approx_flat_sym(freq, component_id, expected_Z, rtol, def test_single_plate_RW_approx_error(component_id): with raises(NotImplementedError): - flat_input = FlatIW2DInput(machine='LHC', length=1., - relativistic_gamma=7460.5232328, calculate_wake=False, - f_params=Sampling(1e3,1e13,0,added=(1e4,1e9),points_per_decade=0), + flat_input = FlatIW2DInput(length=1., + relativistic_gamma=7460.5232328, top_bottom_symmetry=False, top_layers=[layer_from_json_material_library(thickness=np.inf, material_key='Mo')], @@ -352,4 +352,4 @@ def test_single_layer_RW_approx_flat_sym(freq, component_id, expected_Z, rtol, round_single_layer_approx_rw_element): npt.assert_allclose(round_single_layer_approx_rw_element.get_component(component_id).impedance(freq), - expected_Z, rtol=rtol) \ No newline at end of file + expected_Z, rtol=rtol)