From 78159da649347e35e6c3f29e12bdd0a3907c122b Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Thu, 19 Mar 2026 15:49:09 +0000 Subject: [PATCH 1/8] Add toml as a dependency. Add toml file input. Add data classes for a range of toml file sections. Temporarily set current switchboard values to those provided by toml file (in future will just user toml file values directly). Add expansion capability of variable sets in (IEDF/NEDF/Phase/multi)variables. Add expert options ignore_div_zero and ignore_empty_contours. --- pyproject.toml | 1 + src/helena/helena.py | 685 ++++++++++++++++++++++++++++++++++--------- uv.lock | 11 + 3 files changed, 556 insertions(+), 141 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9145e85..3bfcedd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "matplotlib>=3.10.8", "numpy>=2.4.2", "scipy>=1.17.1", + "toml>=0.10.2", "tqdm>=4.67.3", ] dynamic = ["version"] diff --git a/src/helena/helena.py b/src/helena/helena.py index ffdc3a3..db4ca14 100644 --- a/src/helena/helena.py +++ b/src/helena/helena.py @@ -30,6 +30,7 @@ def run(argv=None): if argv is None: argv = sys.argv[1:] (options, args) = parser.parse_args(argv) + input_file = "input.toml" if len(args) == 0 else args[0] if 'True' in str(options): import os, sys @@ -70,6 +71,8 @@ def run(argv=None): import csv import re import gc + import toml + from typing import TypeVar, Callable #Enforce matplotlib to avoid instancing undisplayed windows #matplotlib-tcl-asyncdelete-async-handler-deleted-by-the-wrong-thread @@ -85,222 +88,622 @@ def run(argv=None): from matplotlib import ticker from scipy import ndimage from tqdm import tqdm + from dataclasses import dataclass, field #from Read_data_functions.py import overlay_GEC_geometry + #Commonly used variable sets. + variable_sets = { + "Phys": ['E','S-E','SEB-E','TE','PPOT','P-POT','POW-RF','POW-RF-E','POW-ICP','POW-ICP1','POW-ICP2','POW-ICP3', + 'POW-ICP4','POW-ALL','EB-ESORC','COLF','SIGMA','EF-TOT', 'ERADIAL','ETHETA','EAXIAL','PHASEER','PHASE', + 'PHASEEZ','EAMB-Z','EAMB-R','RHO','BR','BRS','BZ','BZS','BT','BTS','BRF', 'PHASEBR','PHASEBT', + 'PHASEBZ','VR-ION+','VZ-ION+','E FLUX-R','FR-E','E FLUX-Z','FZ-E','JZ-NET','JR-NET','J-THETA', + 'J-TH(MAG)','J-TH(PHA)','PRESSURE','TG-AVE','VR-NEUTRAL','VZ-NEUTRAL'], + "PhysCoilsEF": ['ERADIAL-2','ETHETA-2','EAXIAL-2','PHASEER-2','PHASEEZ-2','ERADIAL-3','ETHETA-3','EAXIAL-3', + 'PHASEER-3','PHASEEZ-3','ERADIAL-4','ETHETA-4','EAXIAL-4','PHASEER-4','PHASEEZ-4','ERADIAL-5', + 'ETHETA-5','EAXIAL-5','PHASEER-5','PHASEEZ-5','ERADIAL-6','ETHETA-6','EAXIAL-6','PHASEER-6', + 'PHASEEZ-6','ERADIAL-7','ETHETA-7','EAXIAL-7','PHASEER-7','PHASEEZ-7','ERADIAL-8','ETHETA-8', + 'EAXIAL-8','PHASEER-8','PHASEEZ-8'], + "PhysCoilsBF": ['BT-2','BT-3','BT-4','BT-5','BT-6','BT-7','BT-8','BRF-2','BRF-3','BRF-4','BRF-5','BRF-6', + 'BRF-7','BRF-8','PHASEBT-2','PHASEBT-3','PHASEBT-4','PHASEBT-5','PHASEBT-6','PHASEBT-7', + 'PHASEBT-8'], + + "Conv": ['E','TE','PPOT','POW-RF','SIGMA','EF-TOT','TG-AVE'], + + "TEST": ['AR2+'], + "Ar": ['AR3S','AR4SM','AR4SR','AR4SPM','AR4SPR','AR4P','AR4D','AR','AR+','AR2+','AR2*','S-AR+','S-AR4P', + 'SEB-AR+','SEB-AR4P','FZ-AR3S','FR-AR3S','FR-AR+','FZ-AR+','FZ-AR3S','FR-AR3S'], + "O2": ['O3','O2','O2V','O2*','O2*1S','O2+','O2-','O','O1S','O+','O-','O*','S-O3','S-O2+','S-O+','S-O-','SEB-O3', + 'SEB-O+','SEB-O2+','SEB-O-','FR-O+','FZ-O+','FR-O-','FZ-O-'], + "H2": ['H2V0','H2V1','H2V2','H2V3','H1','H*','H**','H2+','H+','H-','S-H+','SEB-H+','S-2H+','SEB-2H+','S-H-', + 'SEB-H-','FZ-H2V0','FR-H2V0','FZ-H1','FR-H1','FZ-H+','FR-H+','FZ-H2+','FR-H2+','FZ-H-','FR-H-'], + "N2": ['N2','N2V','N2*','N2**','N2+','N','N*','N+'], + "Cl": ['Cl2','Cl','CL+','CL-','Cl2V','Cl2+','CL*','CL**','CL***'], + "F": ['F2','F2*','F2+','F','F*','F+','F-','S-F','S-F+','S-F-','SEB-F','SEB-F+','SEB-F-','FZ-F','FR-F','FZ-F+', + 'FR-F+','FZ-F-','FR-F-','FZ-F+','FR-F+'], + "H2O": ['H2O','H2O+','OH','OH-','H2OV','H2O2','S-H2O','SEB-H2O','S-H2OV','SEB-H2OV','S-H2O+','SEB-H2O+','S-OH', + 'SEB-OH','S-OH-','SEB-OH-','S-OH+','SEB-OH+'], + "COx": ['CO2','CO2V','CO+','CO','CO+','C','C+'], + "CHx": ['CH4','CH3','CH2','CH','C','CH5+','CH4+','CH3+','CH2+','CH+','C+'], + "NHx": ['NH3','NH2','NH','NH4+','NH3+','NH2+','NH+'], + "NOx": ['NO2','NO2+','N2O','N2O+','NO','NO+'], + "NFx": ['NF3A','NF2A','NFA','NF3B','NF2B','NFB','NF3+','NF2+','NF+'], + "SFx": ['SF6','SF5','SF4','SF3','SF2','SF','S','SF5+','SF4+','SF3+','SF2+','SF+','S+','SF6-','SF5-'], + "Al": ['AL','AL*','AL**','AL+','S-AL','SEB-AL','S-AL*','SEB-AL*','S-AL**','SEB-AL**','S-AL+','SEB-AL+','FZ-AL+', + 'FR-AL+'], + "Be": ['BE','BE1','BE2','BE3','BE4','BE5','BE6','BE7','BE8','BE9','BE+','FR-BE','FZ-BE','FR-BE+','FZ-BE+'], + + "Ar_Phase": ['S-E','S-AR+','S-AR4P','SEB-E','SEB-AR+','SEB-AR4P','SRCE-2437','FR-E','FZ-E','TE','PPOT', + 'POW-ALL'], + "O2_Phase": ['S-E','S-O+','S-O-','S-O2+','SEB-O+','SEB-O-','SEB-O2+','S-O3P3P','SEB-O3P3P','TE','PPOT','FR-E', + 'FZ-E'], + + "PRCCPAr_PCMC": ['AR^0.35','EB-0.35','ION-TOT0.35'], + "PRCCPO2_PCMC": ['O^0.35','EB-0.35','ION-TOT0.35'], + "GECCCP2a_BE_PCMC": ['AR^ 2.6 L','BE^ 2.6 L','EB- 2.6 L'], + } + + + _T = TypeVar("T") + + def enforce_float(value: int | float, field: str) -> float: + if isinstance(value, bool) or not isinstance(value, int | float): + raise TypeError(f"Field {field} is not a float or int.") + + return float(value) + + def enforce_int(value: int, field: str) -> int: + if isinstance(value, bool) or not isinstance(value, int): + raise TypeError(f"Field {field} is not an int.") + + return value + + def enforce_str(value: str, field: str) -> str: + if not isinstance(value, str): + raise TypeError(f"Field {field} is not a string.") + + return value + + def enforce_bool(value: bool, field: str) -> bool: + if not isinstance(value, bool): + raise TypeError(f"Field {field} is not a bool.") + + return value + + def enforce_list_obj(lst: list[_T], func_checker: Callable[_T, str], field: str) -> list[_T]: + return [func_checker(element, field) for element in lst] + + + @dataclass + class ChemistryInput: + variables: list[str] = field(default_factory=lambda: variable_sets["Phys"] + variable_sets["Ar"]) + multivar: list[str] = field(default_factory=list) + radial_profiles: list[int] = field(default_factory=list) + axial_profiles: list[int] = field(default_factory=lambda: [0]) + probe_loc: list[int] = field(default_factory=list) + + # EDF. + IEDF_variables: list[str] = field(default_factory=lambda: variable_sets["GECCCP2a_BE_PCMC"].copy()) + NEDF_variables: list[str] = field(default_factory=list) + + # Movie. + phase_variables: list[str] = field(default_factory=lambda: variable_sets["Ar_Phase"].copy()) + electrode_loc: list[int] = field(default_factory=lambda: [0, 0]) + waveform_locs: list[int] = field(default_factory=list) + + # Diagnostic. + sheath_ROI: list[int] = field(default_factory=list) + source_width: list[int] = field(default_factory=list) + + def __post_init__(self): + self.variables = enforce_list_obj(self.variables, enforce_str, "variables") + self.multivar = enforce_list_obj(self.multivar, enforce_str, "multivar") + self.radial_profiles = enforce_list_obj(self.radial_profiles, enforce_int, "radial_profiles") + self.axial_profiles = enforce_list_obj(self.axial_profiles, enforce_int, "axial_profiles") + self.probe_loc = enforce_list_obj(self.probe_loc, enforce_int, "probe_loc") + + self.IEDF_variables = enforce_list_obj(self.IEDF_variables, enforce_str, "IEDF_variables") + self.NEDF_variables = enforce_list_obj(self.NEDF_variables, enforce_str, "NEDF_variables") + + self.phase_variables = enforce_list_obj(self.phase_variables, enforce_str, "phase_variables") + self.electrode_loc = enforce_list_obj(self.electrode_loc, enforce_int, "electrode_loc") + self.waveform_locs = enforce_list_obj(self.waveform_locs, enforce_int, "waveform_locs") + + self.sheath_ROI = enforce_list_obj(self.sheath_ROI, enforce_int, "sheath_ROI") + self.source_width = enforce_list_obj(self.source_width, enforce_int, "source_width") + + # Expand variable sets. + self.variables = self.expand_variable_set(self.variables) + self.multivar = self.expand_variable_set(self.multivar) + self.IEDF_variables = self.expand_variable_set(self.IEDF_variables) + self.NEDF_variables = self.expand_variable_set(self.NEDF_variables) + self.phase_variables = self.expand_variable_set(self.phase_variables) + + @staticmethod + def expand_variable_set(items: list[str]) -> list[str]: + out = [] + + for item in items: + if item in variable_sets: + out.extend(variable_sets[item]) + else: + out.append(item) + + return out + + @dataclass + class FiguresInput: + tecplot2D: bool = False + + # Movie. + movieicp2D: bool = False + movieicp1D: bool = False + timeaxis1D: bool = False + convergence: bool = False + iterstep: int = 1 + + # Profiles. + monoprofiles: bool = False + multiprofiles: bool = False + compare_profiles: bool = False + + # Trends. + trend_phase_averaged: bool = False + trend_phase_resolved: bool = False + thrust_loc: int = 45 + + # Resolve. + phase_resolve2D: bool = False + phase_resolve1D: bool = False + sheath_dynamics: bool = False + PROES: bool = False + phase_cycles: float = 1.01 + do_Fwidth: int = 0 + + # EDF. + IEDF_angular: bool = False + IEDF_trends: bool = False + EEDF: bool = False + + def __post_init__(self): + self.tecplot2D = enforce_bool(self.tecplot2D, "tecplot2D") + + self.movieicp2D = enforce_bool(self.movieicp2D, "movieicp2D") + self.movieicp1D = enforce_bool(self.movieicp1D, "movieicp1D") + self.timeaxis1D = enforce_bool(self.timeaxis1D, "timeaxis1D") + self.convergence = enforce_bool(self.convergence, "convergence") + self.iterstep = enforce_int(self.iterstep, "iterstep") + + self.monoprofiles = enforce_bool(self.monoprofiles, "monoprofiles") + self.multiprofiles = enforce_bool(self.multiprofiles, "multiprofiles") + self.compare_profiles = enforce_bool(self.compare_profiles, "compare_profiles") + + self.trend_phase_averaged = enforce_bool(self.trend_phase_averaged, "trend_phase_averaged") + self.trend_phase_resolved = enforce_bool(self.trend_phase_resolved, "trend_phase_resolved") + self.thrust_loc = enforce_int(self.thrust_loc, "thrust_loc") + + self.phase_resolve2D = enforce_bool(self.phase_resolve2D, "phase_resolve2D") + self.phase_resolve1D = enforce_bool(self.phase_resolve1D, "phase_resolve1D") + self.sheath_dynamics = enforce_bool(self.sheath_dynamics, "sheath_dynamics") + self.PROES = enforce_bool(self.PROES, "PROES") + self.phase_cycles = enforce_float(self.phase_cycles, "phase_cycles") + self.do_Fwidth = enforce_int(self.do_Fwidth, "do_Fwidth") + + self.IEDF_angular = enforce_bool(self.IEDF_angular, "IEDF_angular") + self.IEDF_trends = enforce_bool(self.IEDF_trends, "IEDF_trends") + self.EEDF = enforce_bool(self.EEDF, "EEDF") + + @dataclass + class WriteInput: + ASCII: bool = False + CSV: bool = True + + def __post_init__(self): + self.ASCII = enforce_bool(self.ASCII, "ASCII") + self.CSV = enforce_bool(self.CSV, "CSV") + + @dataclass + class PrintoutInput: + general_trends: bool = False + Knudsen_number: bool = False + total_power: bool = False + Reynolds: bool = False + DC_bias: bool = False + thrust: bool = False + sheath: bool = False + + def __post_init__(self): + self.general_trends = enforce_bool(self.general_trends, "general_trends") + self.Knudsen_number = enforce_bool(self.Knudsen_number, "Knudsen_number") + self.total_power = enforce_bool(self.total_power, "total_power") + self.Reynolds = enforce_bool(self.Reynolds, "Reynolds") + self.DC_bias = enforce_bool(self.DC_bias, "DC_bias") + self.thrust = enforce_bool(self.thrust, "thrust") + self.sheath = enforce_bool(self.sheath, "sheath") + + @dataclass + class ImageInput: + extension: str = ".png" + + # Style. + interp: str = "spline36" + cmap: str = "plasma" + + # Geometry. + aspect_ratio: list[float] = field(default_factory=lambda: [10.0, 10.0]) + radial_crop: list[float] = field(default_factory=list) + axial_crop: list[float] = field(default_factory=list) + rotate: bool = False + + # Display. + plot_symmetry: bool = False + plot_mesh: bool = True + plot_grid: bool = False + + # Contours. + plot_colourfill: bool = True + plot_contours: bool = True + contour_lvls: int = 10 + + # Axes. + axis_ticks: bool = True + axis_labels: bool = True + legend_loc: str = 'best' + + # Colorbar. + cbar_ticks: bool = True + cbar_bins: int = 5 + cbar_limit: list[float] = field(default_factory=list) + + # Vector. + plot_vector: bool = True + vector_density: float = 1.5 + vector_lw: float = 1.0 + + # Processing. + normalise: bool = False + log_plot: bool = False + + # Overlays. + plot_sheath: bool = False + plot_phase_waveform: bool = False + plot_overlay: bool = False + + def __post_init__(self): + self.extension = enforce_str(self.extension, "extension") + + self.interp = enforce_str(self.interp, "interp") + self.cmap = enforce_str(self.cmap, "cmap") + + self.aspect_ratio = enforce_list_obj(self.aspect_ratio, enforce_float, "aspect_ratio") + self.radial_crop = enforce_list_obj(self.radial_crop, enforce_float, "radial_crop") + self.axial_crop = enforce_list_obj(self.axial_crop, enforce_float, "axial_crop") + self.rotate = enforce_bool(self.rotate, "rotate") + + self.plot_symmetry = enforce_bool(self.plot_symmetry, "plot_symmetry") + self.plot_mesh = enforce_bool(self.plot_mesh, "plot_mesh") + self.plot_grid = enforce_bool(self.plot_grid, "plot_grid") + + self.plot_colourfill = enforce_bool(self.plot_colourfill, "plot_colourfill") + self.plot_contours = enforce_bool(self.plot_contours, "plot_contours") + self.contour_lvls = enforce_int(self.contour_lvls, "contour_lvls") + + self.axis_ticks = enforce_bool(self.axis_ticks, "axis_ticks") + self.axis_labels = enforce_bool(self.axis_labels, "axis_labels") + self.legend_loc = enforce_str(self.legend_loc, "legend_loc") + + self.cbar_ticks = enforce_bool(self.cbar_ticks, "cbar_ticks") + self.cbar_bins = enforce_int(self.cbar_bins, "cbar_bins") + self.cbar_limit = enforce_list_obj(self.cbar_limit, enforce_float, "cbar_limit") + + self.plot_vector = enforce_bool(self.plot_vector, "plot_vector") + self.vector_density = enforce_float(self.vector_density, "vector_density") + self.vector_lw = enforce_float(self.vector_lw, "vector_lw") + + self.normalise = enforce_bool(self.normalise, "normalise") + self.log_plot = enforce_bool(self.log_plot, "log_plot") + + self.plot_sheath = enforce_bool(self.plot_sheath, "plot_sheath") + self.plot_phase_waveform = enforce_bool(self.plot_phase_waveform, "plot_phase_waveform") + self.plot_overlay = enforce_bool(self.plot_overlay, "plot_overlay") + + + + @dataclass + class OverridesInput: + title: list[str] = field(default_factory=list) + legend: list[str] = field(default_factory=list) + xaxis: list[str] = field(default_factory=list) + xlabel: list[str] = field(default_factory=list) + ylabel: list[str] = field(default_factory=list) + + def __post_init__(self): + self.title = enforce_list_obj(self.title, enforce_str, "title") + self.legend = enforce_list_obj(self.legend, enforce_str, "legend") + self.xaxis = enforce_list_obj(self.xaxis, enforce_str, "xaxis") + self.xlabel = enforce_list_obj(self.xlabel, enforce_str, "xlabel") + self.ylabel = enforce_list_obj(self.ylabel, enforce_str, "ylabel") + + @dataclass + class ExpertInput: + # Debug. + magmesh: int = 1 + ffmpeg_movies: bool = False + IDEBUG: bool = False + + # Warnings. + ignore_div_zero: bool = True + ignore_empty_contours: bool = True + + # Numerical. + sheath_method: str = 'AbsDensity' + thrust_method: str = 'AxialMomentum' + mean_calculation: str = 'MeanFraction' + + # Overrides. + DC_bias_axis: str = 'Auto' + sheath_ion_species: list[str] = field(default_factory=lambda: ['AR+']) + + # Filtering. + kinetic_filtering: bool = True + plot_kinetic_filtering: bool = False + sav_window: int = 25 + sav_poly_order: int = 3 + + # Misc. + sheath_ion_ratio_threshold: float = 1.03 + conv_azimuthal_phase: bool = True + EDF_threshold: float = 0.01 + units: str = 'SI' + + def __post_init__(self): + self.magmesh = enforce_int(self.magmesh, "magmesh") + self.ffmpeg_movies = enforce_bool(self.ffmpeg_movies, "ffmpeg_movies") + self.IDEBUG = enforce_bool(self.IDEBUG, "IDEBUG") + + self.ignore_div_zero = enforce_bool(self.ignore_div_zero, "ignore_div_zero") + self.ignore_empty_contours = enforce_bool(self.ignore_empty_contours, "ignore_empty_contours") + + self.sheath_method = enforce_str(self.sheath_method, "sheath_method") + self.thrust_method = enforce_str(self.thrust_method, "thrust_method") + self.mean_calculation = enforce_str(self.mean_calculation, "mean_calculation") + + self.DC_bias_axis = enforce_str(self.DC_bias_axis, "DC_bias_axis") + self.sheath_ion_species = enforce_list_obj(self.sheath_ion_species, enforce_str, "sheath_ion_species") + + self.kinetic_filtering = enforce_bool(self.kinetic_filtering, "kinetic_filtering") + self.plot_kinetic_filtering = enforce_bool(self.plot_kinetic_filtering, "plot_kinetic_filtering") + self.sav_window = enforce_int(self.sav_window, "sav_window") + self.sav_poly_order = enforce_int(self.sav_poly_order, "sav_poly_order") + + self.sheath_ion_ratio_threshold = enforce_float(self.sheath_ion_ratio_threshold, "sheath_ion_ratio_threshold") + self.conv_azimuthal_phase = enforce_bool(self.conv_azimuthal_phase, "conv_azimuthal_phase") + self.EDF_threshold = enforce_float(self.EDF_threshold, "EDF_threshold") + self.units = enforce_str(self.units, "units") + + + @dataclass + class Config: + chemistry: ChemistryInput = field(default_factory=ChemistryInput) + figures: FiguresInput = field(default_factory=FiguresInput) + write: WriteInput = field(default_factory=WriteInput) + printout: PrintoutInput = field(default_factory=PrintoutInput) + image: ImageInput = field(default_factory=ImageInput) + overrides: OverridesInput = field(default_factory=OverridesInput) + expert: ExpertInput = field(default_factory=ExpertInput) + + + def create_input(data: dict, category: str, output_obj: type[_T], sections: list[str] | None = None) -> _T: + # data: the whole toml data + # category: the specific category we are creating an input for + # output_obj: the category's output object + # sections: the additional sections of this category + + if sections is None: + sections = [] + + category_dict = data.get(category, {}) + + output_dict = {k: v for k, v in category_dict.items() if k not in sections} + + for section in sections: + output_dict.update(category_dict.get(section, {})) + + return output_obj(**output_dict) + + + def load_config(toml_dict): + chemistry_input = create_input(toml_dict, "chemistry", ChemistryInput, ["EDF", "movie", "diagnostic"]) + figures_input = create_input(toml_dict, "figures", FiguresInput, ["movie", "profiles", "trends", "resolve", "EDF"]) + write_input = create_input(toml_dict, "write", WriteInput) + printout_input = create_input(toml_dict, "print", PrintoutInput) + image_input = create_input(toml_dict, "image", ImageInput, ["style", "geometry", "display", "contours", "axes", "colorbar", "vector", "processing", "overlays"]) + overrides_input = create_input(toml_dict, "overrides", OverridesInput) + expert_input = create_input(toml_dict, "expert", ExpertInput, ["debug", "warnings", "numerical", "overrides", "filtering", "misc"]) + + return Config( + chemistry=chemistry_input, + figures=figures_input, + write=write_input, + printout=printout_input, + image=image_input, + overrides=overrides_input, + expert=expert_input, + ) + + with open(input_file, "r") as toml_file: + toml_dict = toml.load(toml_file) + + config = load_config(toml_dict) #====================================================================# #LOW LEVEL INPUTS# #====================================================================# #Various debug and streamlining options. - Magmesh = 1 #initmesh.exe magnification factor. (Obsolete - legacy) - ffmpegMovies = False #If False: Suppresses ffmpeg routines, saves RAM. - IDEBUG = False #Produces debug outputs for most diagnostics. + Magmesh = config.expert.magmesh #initmesh.exe magnification factor. (Obsolete - legacy) + ffmpegMovies = config.expert.ffmpeg_movies #If False: Suppresses ffmpeg routines, saves RAM. + IDEBUG = config.expert.IDEBUG #Produces debug outputs for most diagnostics. #Warning suppressions - np.seterr(divide='ignore', invalid='ignore') #Suppresses divide by zero errors - #Fix: "can't invoke "event" command: application has been destroyed" error with PROES images - #Fix: "Exception KeyError: KeyError(,)" error + if config.expert.ignore_div_zero: + np.seterr(divide='ignore', invalid='ignore') #Suppresses divide by zero errors + #Fix: "can't invoke "event" command: application has been destroyed" error with PROES images + #Fix: "Exception KeyError: KeyError(,)" error - warnings.filterwarnings("ignore", message="No contour levels were found within the data range.") - #Fix: Suppress above warning on plotting of empty contour plots + if config.expert.ignore_empty_contours: + warnings.filterwarnings("ignore", message="No contour levels were found within the data range.") + #Fix: Suppress above warning on plotting of empty contour plots #Numerical Calculation Methods: - GlobSheathMethod = 'AbsDensity' #Set Global Sheath Calculation Method. - #Choices: ('AbsDensity','IntDensity') - GlobThrustMethod = 'AxialMomentum' #Set Global Thrust Calculation Method. - #Choices:('ThermalVelocity','AxialMomentum') - GlobMeanCalculation = 'MeanFraction' #Definition of 'mean' EDF value - #Choices: ('MeanEnergy','MeanFraction') + GlobSheathMethod = config.expert.sheath_method #Set Global Sheath Calculation Method. + GlobThrustMethod = config.expert.thrust_method #Set Global Thrust Calculation Method. + GlobMeanCalculation = config.expert.mean_calculation #Definition of 'mean' EDF value #Overrides or 'fudge factors' for diagnostics - DCbiasaxis = 'Auto' #Force Direction Over Which DCBias is Calculated - #Choices:('Axial','Radial','Auto') - SheathIonSpecies = ['AR+'] #Force Sheath Ion Species (blank for auto) - #['AR+'] #['O+'] + DCbiasaxis = config.expert.DC_bias_axis #Force Direction Over Which DCBias is Calculated + SheathIonSpecies = config.expert.sheath_ion_species #Force Sheath Ion Species (blank for auto) #Ratio of electrons to ions that determines the edge of the sheath #Ideally this should be 1.00, but in practice it's slightly over 1 at lower resolutions - Sheath_IonRatio_Threshold = 1.03 + Sheath_IonRatio_Threshold = config.expert.sheath_ion_ratio_threshold #Data Filtering and Smoothing Methods: - KineticFiltering = True #Pre-fit kinetic data employing a SavGol filter - PlotKineticFiltering = False #Plot Filtered Profiles, or employ only in trends. - Glob_SavWindow, Glob_SavPolyOrder = 25, 3 #Window > FeatureSize, Polyorder ~= Smoothness + KineticFiltering = config.expert.kinetic_filtering #Pre-fit kinetic data employing a SavGol filter + PlotKineticFiltering = config.expert.plot_kinetic_filtering #Plot Filtered Profiles, or employ only in trends. + Glob_SavWindow = config.expert.sav_window #Window > FeatureSize + Glob_SavPolyOrder = config.expert.sav_poly_order #Polyorder ~= Smoothness #Apply azimuthal direction (phase) to relevant variables if true, else plot magnitude only - ConvAzimuthalPhase = True + ConvAzimuthalPhase = config.expert.conv_azimuthal_phase #Minimum plotted EDF energy fraction, cuts x-axis at index where :: f(e) = f(e)*EDF_Threshold #Note: IEDF/EEDF trends are only taken within range :: EDF_threshold < f(e) < 1.0 - EDF_Threshold = 0.01 # i.e. = 0.0 to plot all + EDF_Threshold = config.expert.EDF_threshold # i.e. = 0.0 to plot all #Define units for particular variables - Units = 'SI' #'SI','CGS' + Units = config.expert.units #'SI','CGS' # FUNCTION MATHS ASSUMES SI, CGS WILL GIVE INCORRECT RESULTS - #################### - - #Commonly used variable sets. - Phys = ['E','S-E','SEB-E','TE','PPOT','P-POT','POW-RF','POW-RF-E','POW-ICP','POW-ICP1','POW-ICP2','POW-ICP3','POW-ICP4','POW-ALL','EB-ESORC','COLF','SIGMA','EF-TOT', 'ERADIAL','ETHETA','EAXIAL','PHASEER','PHASE','PHASEEZ','EAMB-Z','EAMB-R','RHO','BR','BRS','BZ','BZS','BT','BTS','BRF', 'PHASEBR','PHASEBT','PHASEBZ','VR-ION+','VZ-ION+','E FLUX-R','FR-E','E FLUX-Z','FZ-E','JZ-NET','JR-NET','J-THETA','J-TH(MAG)','J-TH(PHA)','PRESSURE','TG-AVE','VR-NEUTRAL','VZ-NEUTRAL'] - PhysCoilsEF = \ - ['ERADIAL-2','ETHETA-2','EAXIAL-2','PHASEER-2','PHASEEZ-2','ERADIAL-3','ETHETA-3','EAXIAL-3','PHASEER-3','PHASEEZ-3', \ - 'ERADIAL-4','ETHETA-4','EAXIAL-4','PHASEER-4','PHASEEZ-4','ERADIAL-5','ETHETA-5','EAXIAL-5','PHASEER-5','PHASEEZ-5', \ - 'ERADIAL-6','ETHETA-6','EAXIAL-6','PHASEER-6','PHASEEZ-6','ERADIAL-7','ETHETA-7','EAXIAL-7','PHASEER-7','PHASEEZ-7', \ - 'ERADIAL-8','ETHETA-8','EAXIAL-8','PHASEER-8','PHASEEZ-8'] - PhysCoilsBF = \ - ['BT-2','BT-3','BT-4','BT-5','BT-6','BT-7','BT-8', \ - 'BRF-2','BRF-3','BRF-4','BRF-5','BRF-6','BRF-7','BRF-8', \ - 'PHASEBT-2','PHASEBT-3','PHASEBT-4','PHASEBT-5','PHASEBT-6','PHASEBT-7','PHASEBT-8'] - - Conv = ['E','TE','PPOT','POW-RF','SIGMA','EF-TOT','TG-AVE'] - - TEST = ['AR2+'] - Ar = ['AR3S','AR4SM','AR4SR','AR4SPM','AR4SPR','AR4P','AR4D','AR','AR+','AR2+','AR2*','S-AR+','S-AR4P','SEB-AR+','SEB-AR4P','FZ-AR3S','FR-AR3S','FR-AR+','FZ-AR+','FZ-AR3S','FR-AR3S'] - O2 = ['O3','O2','O2V','O2*','O2*1S','O2+','O2-','O','O1S','O+','O-','O*','S-O3','S-O2+','S-O+','S-O-','SEB-O3','SEB-O+','SEB-O2+','SEB-O-','FR-O+','FZ-O+','FR-O-','FZ-O-'] - H2 = ['H2V0','H2V1','H2V2','H2V3','H1','H*','H**','H2+','H+','H-','S-H+','SEB-H+','S-2H+','SEB-2H+','S-H-','SEB-H-','FZ-H2V0','FR-H2V0','FZ-H1','FR-H1','FZ-H+','FR-H+','FZ-H2+','FR-H2+','FZ-H-','FR-H-'] - N2 = ['N2','N2V','N2*','N2**','N2+','N','N*','N+'] - Cl = ['Cl2','Cl','CL+','CL-','Cl2V','Cl2+','CL*','CL**','CL***'] - F = ['F2','F2*','F2+','F','F*','F+','F-','S-F','S-F+','S-F-','SEB-F','SEB-F+','SEB-F-','FZ-F','FR-F','FZ-F+','FR-F+','FZ-F-','FR-F-','FZ-F+','FR-F+'] - H2O = ['H2O','H2O+','OH','OH-','H2OV','H2O2','S-H2O','SEB-H2O','S-H2OV','SEB-H2OV','S-H2O+','SEB-H2O+','S-OH','SEB-OH','S-OH-','SEB-OH-','S-OH+','SEB-OH+'] - COx = ['CO2','CO2V','CO+','CO','CO+','C','C+'] - CHx = ['CH4','CH3','CH2','CH','C','CH5+','CH4+','CH3+','CH2+','CH+','C+'] - NHx = ['NH3','NH2','NH','NH4+','NH3+','NH2+','NH+'] - NOx = ['NO2','NO2+','N2O','N2O+','NO','NO+'] - NFx = ['NF3A','NF2A','NFA','NF3B','NF2B','NFB','NF3+','NF2+','NF+'] - SFx = ['SF6','SF5','SF4','SF3','SF2','SF','S','SF5+','SF4+','SF3+','SF2+','SF+','S+','SF6-','SF5-'] - Al = ['AL','AL*','AL**','AL+','S-AL','SEB-AL','S-AL*','SEB-AL*','S-AL**','SEB-AL**','S-AL+','SEB-AL+','FZ-AL+','FR-AL+'] - Be = ['BE','BE1','BE2','BE3','BE4','BE5','BE6','BE7','BE8','BE9','BE+','FR-BE','FZ-BE','FR-BE+','FZ-BE+'] - - Ar_Phase = ['S-E','S-AR+','S-AR4P','SEB-E','SEB-AR+','SEB-AR4P','SRCE-2437','FR-E','FZ-E','TE','PPOT','POW-ALL'] - O2_Phase = ['S-E','S-O+','S-O-','S-O2+','SEB-O+','SEB-O-','SEB-O2+','S-O3P3P','SEB-O3P3P','TE','PPOT','FR-E','FZ-E'] - - PRCCPAr_PCMC = ['AR^0.35','EB-0.35','ION-TOT0.35'] - PRCCPO2_PCMC = ['O^0.35','EB-0.35','ION-TOT0.35'] - GECCCP2a_BE_PCMC = ['AR^ 2.6 L','BE^ 2.6 L','EB- 2.6 L'] - - #################### - #====================================================================# #SWITCHBOARD AND DIAGNOSTICS# #====================================================================# # Requested IEDF/NEDF Variables. - IEDFVariables = GECCCP2a_BE_PCMC # Requested Variables from iprofile_2d.pdt - NEDFVariables = [] # Requested Variables from nprofile_2d.pdt + IEDFVariables = config.chemistry.IEDF_variables # Requested Variables from iprofile_2d.pdt + NEDFVariables = config.chemistry.NEDF_variables # Requested Variables from nprofile_2d.pdt # Requested movie1/movie_icp Variables. - PhaseVariables = Ar_Phase # Requested Movie1 (phase) Variables. - electrodeloc = [0,0] # Cell location of powered electrode [R,Z]. - waveformlocs = [] # Cell locations of additional waveforms [R,Z]. + PhaseVariables = config.chemistry.phase_variables # Requested Movie1 (phase) Variables. + electrodeloc = config.chemistry.electrode_loc # Cell location of powered electrode [R,Z]. + waveformlocs = config.chemistry.waveform_locs # Cell locations of additional waveforms [R,Z]. # Requested variables and plotting locations. - Variables = Phys+Ar # Requested Variables from Tecplot2D.pdt, tecplot_kin.pdt, and movie_icp.pdt - multivar = [] # Additional variables plotted ontop of [Variables] - radialprofiles = [] # Radial 1D-Profiles to be plotted (fixed Z-mesh) -- - axialprofiles = [0] # Axial 1D-Profiles to be plotted (fixed R-mesh) | - probeloc = [] # Cell location For Trend Analysis [R,Z], (leave empty for global min/max) + Variables = config.chemistry.variables # Requested Variables from Tecplot2D.pdt, tecplot_kin.pdt, and movie_icp.pdt + multivar = config.chemistry.multivar # Additional variables plotted ontop of [Variables] + radialprofiles = config.chemistry.radial_profiles # Radial 1D-Profiles to be plotted (fixed Z-mesh) -- + axialprofiles = config.chemistry.axial_profiles # Axial 1D-Profiles to be plotted (fixed R-mesh) | + probeloc = config.chemistry.probe_loc # Cell location For Trend Analysis [R,Z], (leave empty for global min/max) # Various Diagnostic Settings >>> OUTDATED, TO BE RETIRED <<< - sheathROI = [] # Sheath Region of Interest, (Start,End) [cells] - sourcewidth = [] # Source Dimension at ROI, leave empty for auto. [cells] + sheathROI = config.chemistry.sheath_ROI # Sheath Region of Interest, (Start,End) [cells] + sourcewidth = config.chemistry.source_width # Source Dimension at ROI, leave empty for auto. [cells] # Requested diagnostics and plotting routines. - savefig_tecplot2D = False # 2D Single-Variables: TECPLOT2D.PDT < .csv File Save + savefig_tecplot2D = config.figures.tecplot2D # 2D Single-Variables: TECPLOT2D.PDT < .csv File Save - savefig_movieicp2D = False # 2D Variables against space-axis: movie_icp.pdt < MAXITER SHOULD BE AN ARRAY - savefig_movieicp1D = False # 1D Variables against space-axis: movie_icp.pdt < MAXITER SHOULD BE AN ARRAY - savefig_timeaxis1D = False # 1D Variables against time-axis: movie_icp.pdt - savefig_convergence = False # 1D variables against ITER-axis: movie_icp.pdt - iterstep = 1 # movie_icp.pdt iteration step size + savefig_movieicp2D = config.figures.movieicp2D # 2D Variables against space-axis: movie_icp.pdt < MAXITER SHOULD BE AN ARRAY + savefig_movieicp1D = config.figures.movieicp1D # 1D Variables against space-axis: movie_icp.pdt < MAXITER SHOULD BE AN ARRAY + savefig_timeaxis1D = config.figures.timeaxis1D # 1D Variables against time-axis: movie_icp.pdt + savefig_convergence = config.figures.convergence # 1D variables against ITER-axis: movie_icp.pdt + iterstep = config.figures.iterstep # movie_icp.pdt iteration step size - savefig_monoprofiles = False # 1D Variables against space-axis: TECPLOT2D < .csv File Save - savefig_multiprofiles = False # 1D Variables Compared Same Sims: TECPLOT2D - savefig_compareprofiles = False # 1D Variables Compared Between Sims: TECPLOT2D + savefig_monoprofiles = config.figures.monoprofiles # 1D Variables against space-axis: TECPLOT2D < .csv File Save + savefig_multiprofiles = config.figures.multiprofiles # 1D Variables Compared Same Sims: TECPLOT2D + savefig_compareprofiles = config.figures.compare_profiles # 1D Variables Compared Between Sims: TECPLOT2D # ^^^^ # NOTE: IMAGEPLOTTER1D RETURNS ARRAY ORDERED AS [0,height] <<< REVERSED RELATIVE TO HPEM # IMAGEPLOTTER2D RETURNS ARRAY ORDERED AS [height,0] <<< SAME ORIENTATION AS HPEM # 1D PROFILES ARE MANUALLY REVERSED ([::-1]) IN THE 1D DIAGNOSTICS TO ACCOUNT FOR THIS # IT WOULD BE BETTER TO USE "DataExtent" TO PROVIDE THE CORRECT ORIENTATION - savefig_trendphaseaveraged = False # Phase averaged trends at axial/radial cells # CHANGE TO 'ProbeLoc' cell - savefig_trendphaseresolved = False # Phase resolved trends at axial/radial cells # CHANGE TO 'ProbeLoc' cell - thrustloc = 45 # Z-axis cell for thrust calculation [Cells] + savefig_trendphaseaveraged = config.figures.trend_phase_averaged # Phase averaged trends at axial/radial cells # CHANGE TO 'ProbeLoc' cell + savefig_trendphaseresolved = config.figures.trend_phase_resolved # Phase resolved trends at axial/radial cells # CHANGE TO 'ProbeLoc' cell + thrustloc = config.figures.thrust_loc # Z-axis cell for thrust calculation [Cells] - savefig_phaseresolve2D = False # 2D Phase Resolved Images < .csv File Save - savefig_phaseresolve1D = False # 1D Phase Resolved Images < .csv File Save - savefig_sheathdynamics = False # 1D and 2D sheath dynamics images - savefig_PROES = False # Simulated PROES Diagnostic - phasecycles = 1.01 # Vaveform phase cycles to be plotted. [Float] - DoFwidth = 0 # PROES Depth of Field (symmetric about image plane) [Cells] + savefig_phaseresolve2D = config.figures.phase_resolve2D # 2D Phase Resolved Images < .csv File Save + savefig_phaseresolve1D = config.figures.phase_resolve1D # 1D Phase Resolved Images < .csv File Save + savefig_sheathdynamics = config.figures.sheath_dynamics # 1D and 2D sheath dynamics images + savefig_PROES = config.figures.PROES # Simulated PROES Diagnostic + phasecycles = config.figures.phase_cycles # Vaveform phase cycles to be plotted. [Float] + DoFwidth = config.figures.do_Fwidth # PROES Depth of Field (symmetric about image plane) [Cells] - savefig_IEDFangular = False # 2D images of angular IEDF; single folders < .csv File Save - savefig_IEDFtrends = False # 1D IEDF trends; all folders - savefig_EEDF = False # 1D EEDF trends; all folders < No Routine + savefig_IEDFangular = config.figures.IEDF_angular # 2D images of angular IEDF; single folders < .csv File Save + savefig_IEDFtrends = config.figures.IEDF_trends # 1D IEDF trends; all folders + savefig_EEDF = config.figures.EEDF # 1D EEDF trends; all folders < No Routine # Write processed data to ASCII files. - write_ASCII = False # Data underpinning figs written in ASCII format < Outdated - Write_CSV = True # Data underpinning figs written in .csv format + write_ASCII = config.write.ASCII # Data underpinning figs written in ASCII format < Outdated + Write_CSV = config.write.CSV # Data underpinning figs written in .csv format # ^^^^ # NOTE: SHEATH EXTENT SAVES WITH WRONG NAME, IN WRONG FILE FORMAT, IN ROOT DIRECTORY # Steady-State diagnostics terminal output toggles. - print_generaltrends = False # Verbose Min/Max Trend Outputs. - print_Knudsennumber = False # Print cell averaged Knudsen Number - print_totalpower = False # Print all requested total powers - print_Reynolds = False # Print cell averaged sound speed - print_DCbias = False # Print DC bias at electrodeloc - print_thrust = False # Print neutral, ion and total thrust - print_sheath = False # Print sheath width at electrodeloc + print_generaltrends = config.printout.general_trends # Verbose Min/Max Trend Outputs. + print_Knudsennumber = config.printout.Knudsen_number # Print cell averaged Knudsen Number + print_totalpower = config.printout.total_power # Print all requested total powers + print_Reynolds = config.printout.Reynolds # Print cell averaged sound speed + print_DCbias = config.printout.DC_bias # Print DC bias at electrodeloc + print_thrust = config.printout.thrust # Print neutral, ion and total thrust + print_sheath = config.printout.sheath # Print sheath width at electrodeloc # Image plotting options. - image_extension = '.png' # Define image extension ('.png', '.jpg', '.eps') - image_interp = 'spline36' # Define image smoothing ('none', 'bilinear','quadric','spline36') - image_cmap = 'plasma' # Define global colourmap ('jet','plasma','inferno','gnuplot','tecmodern') + image_extension = config.image.extension # Define image extension ('.png', '.jpg', '.eps') + image_interp = config.image.interp # Define image smoothing ('none', 'bilinear','quadric','spline36') + image_cmap = config.image.cmap # Define global colourmap ('jet','plasma','inferno','gnuplot','tecmodern') - image_aspectratio = [10,10] # Real Size of [X,Y] in cm [Doesn't Rotate - X is always horizontal] - image_radialcrop = [] # Crops 2D images to [R1,R2] in cm - image_axialcrop = [] # Crops 2D images to [Z1,Z2] in cm + image_aspectratio = config.image.aspect_ratio # Real Size of [X,Y] in cm [Doesn't Rotate - X is always horizontal] + image_radialcrop = config.image.radial_crop # Crops 2D images to [R1,R2] in cm + image_axialcrop = config.image.axial_crop # Crops 2D images to [Z1,Z2] in cm - image_plotsymmetry = False # Plot radial symmetry - mirrors across the ISYM axis - image_plotmesh = True # Plot material mesh outlines - image_plotgrid = False # Plot major/minor gridlines on 1D profiles - image_rotate = False # Rotate 2D images 90 degrees to the right. + image_plotsymmetry = config.image.plot_symmetry # Plot radial symmetry - mirrors across the ISYM axis + image_plotmesh = config.image.plot_mesh # Plot material mesh outlines + image_plotgrid = config.image.plot_grid # Plot major/minor gridlines on 1D profiles + image_rotate = config.image.rotate # Rotate 2D images 90 degrees to the right. - image_plotcolourfill = True # Plot 2D image colour fill - image_plotcontours = True # Plot 2D image contour lines - image_contourlvls = 10 # Number of contour levels + image_plotcolourfill = config.image.plot_colourfill # Plot 2D image colour fill + image_plotcontours = config.image.plot_contours # Plot 2D image contour lines + image_contourlvls = config.image.contour_lvls # Number of contour levels - image_axisticks = True # Toggle to show axis ticks and associated values or not - image_axislabels = True # Toggle to show axis labels or not - image_legendloc = 'best' # Set Legend Location, "1-9" or 'best' for automatic - image_cbarticks = True # Toggle to show cbar ticks and associated values or not - image_cbarbins = 5 # Set number of colourbar bins - image_cbarlimit = [] # Set arbitrary [min,max] colourbar limits + image_axisticks = config.image.axis_ticks # Toggle to show axis ticks and associated values or not + image_axislabels = config.image.axis_labels # Toggle to show axis labels or not + image_legendloc = config.image.legend_loc # Set Legend Location, "1-9" or 'best' for automatic + image_cbarticks = config.image.cbar_ticks # Toggle to show cbar ticks and associated values or not + image_cbarbins = config.image.cbar_bins # Set number of colourbar bins + image_cbarlimit = config.image.cbar_limit # Set arbitrary [min,max] colourbar limits - image_plotvector = True # Plot vector arrows onto 2D images (uses FR-XX, FZ-XX if they exist) - image_vectordensity = 1.5 # Vector line density, higher means more dense streamlines - image_vectorlw = 1.0 # Vector line width, higher means thicker streamlines + image_plotvector = config.image.plot_vector # Plot vector arrows onto 2D images (uses FR-XX, FZ-XX if they exist) + image_vectordensity = config.image.vector_density # Vector line density, higher means more dense streamlines + image_vectorlw = config.image.vector_lw # Vector line width, higher means thicker streamlines - image_normalise = False # Plot Data normlised to maximum value (Applies to all outputs) - image_logplot = False # Plot log10(Data) (Applies to all outputs) + image_normalise = config.image.normalise # Plot Data normlised to maximum value (Applies to all outputs) + image_logplot = config.image.log_plot # Plot log10(Data) (Applies to all outputs) - image_plotsheath = False # Plot sheath extent onto 2D images 'Axial','Radial' - image_plotphasewaveform = False # Plot waveform sub-figure on phaseresolve2D images - image_plotoverlay = False # Plot location(s) of 1D radial/axial profiles onto 2D images + image_plotsheath = config.image.plot_sheath # Plot sheath extent onto 2D images 'Axial','Radial' + image_plotphasewaveform = config.image.plot_phase_waveform # Plot waveform sub-figure on phaseresolve2D images + image_plotoverlay = config.image.plot_overlay # Plot location(s) of 1D radial/axial profiles onto 2D images # Image Overrides (Applies to all images) - titleoverride = [] - legendoverride = [] - xaxisoverride = [] - xlabeloverride = [] - ylabeloverride = [] + titleoverride = config.overrides.title + legendoverride = config.overrides.legend + xaxisoverride = config.overrides.xaxis + xlabeloverride = config.overrides.xlabel + ylabeloverride = config.overrides.ylabel #============================# diff --git a/uv.lock b/uv.lock index f541658..b96ac74 100644 --- a/uv.lock +++ b/uv.lock @@ -158,6 +158,7 @@ dependencies = [ { name = "matplotlib" }, { name = "numpy" }, { name = "scipy" }, + { name = "toml" }, { name = "tqdm" }, ] @@ -178,6 +179,7 @@ requires-dist = [ { name = "ruff", marker = "extra == 'lint'", specifier = ">=0.15.4" }, { name = "scipy", specifier = ">=1.17.1" }, { name = "snaptol", marker = "extra == 'tests'", specifier = ">=0.0.3" }, + { name = "toml", specifier = ">=0.10.2" }, { name = "tqdm", specifier = ">=4.67.3" }, ] provides-extras = ["tests", "lint"] @@ -693,6 +695,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e7/99/c52a1cabc65247a419ca6dcb9114a488c5fc3f5d1a176430d053bf055271/snaptol-0.0.3-py3-none-any.whl", hash = "sha256:af5a191209f934e4e1f65079dd601aec9aa85e5283c832ad67c27bfc00b353d9", size = 13013, upload-time = "2026-02-17T10:27:45.563Z" }, ] +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + [[package]] name = "tqdm" version = "4.67.3" From 0cac469637fffbc723f291a2eec1cae5673074c2 Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Tue, 24 Mar 2026 16:02:58 +0000 Subject: [PATCH 2/8] Change first test from test_regression file to test_input file. Test some standard defaults with the new toml input file capabilities. --- ...plus.json => test_input.test_AR2plus.json} | 0 tests/input_files/input1.toml | 151 ++++++++++++++++++ tests/{test_regression.py => test_input.py} | 19 ++- 3 files changed, 163 insertions(+), 7 deletions(-) rename tests/__snapshots__/{test_regression.test_AR2plus.json => test_input.test_AR2plus.json} (100%) create mode 100644 tests/input_files/input1.toml rename tests/{test_regression.py => test_input.py} (59%) diff --git a/tests/__snapshots__/test_regression.test_AR2plus.json b/tests/__snapshots__/test_input.test_AR2plus.json similarity index 100% rename from tests/__snapshots__/test_regression.test_AR2plus.json rename to tests/__snapshots__/test_input.test_AR2plus.json diff --git a/tests/input_files/input1.toml b/tests/input_files/input1.toml new file mode 100644 index 0000000..68f101a --- /dev/null +++ b/tests/input_files/input1.toml @@ -0,0 +1,151 @@ +[chemistry] +variables = ["TEST"] +multivar = [] +radial_profiles = [] +axial_profiles = [0] +probe_loc = [] + +[chemistry.EDF] +IEDF_variables = ["GECCCP2a_BE_PCMC"] +NEDF_variables = [] + +[chemistry.movie] +phase_variables = ["Ar_Phase"] +electrode_loc = [0,0] +waveform_locs = [] + +[chemistry.diagnostic] +sheath_ROI = [] +source_width = [] + +[figures] +tecplot2D = true + +[figures.movie] +movieicp2D = false +movieicp1D = false +timeaxis1D = false +convergence = false +iterstep = 1 + +[figures.profiles] +monoprofiles = false +multiprofiles = false +compare_profiles = false + +[figures.trends] +trend_phase_averaged = false +trend_phase_resolved = false +thrust_loc = 45 + +[figures.resolve] +phase_resolve2D = false +phase_resolve1D = false +sheath_dynamics = false +PROES = false +phase_cycles = 1.01 +do_Fwidth = 0 + +[figures.EDF] +IEDF_angular = false +IEDF_trends = false +EEDF = false + +[write] +ASCII = false +CSV = true + +[print] +general_trends = false +Knudsen_number = false +total_power = false +Reynolds = false +DC_bias = false +thrust = false +sheath = false + +[image] +extension = '.png' + +[image.style] +interp = 'spline36' +cmap = 'plasma' + +[image.geometry] +aspect_ratio = [10,10] +radial_crop = [] +axial_crop = [] +rotate = false + +[image.display] +plot_symmetry = false +plot_mesh = true +plot_grid = false + +[image.contours] +plot_colourfill = true +plot_contours = true +contour_lvls = 10 + +[image.axes] +axis_ticks = true +axis_labels = true +legend_loc = 'best' + +[image.colorbar] +cbar_ticks = true +cbar_bins = 5 +cbar_limit = [] + +[image.vector] +plot_vector = true +vector_density = 1.5 +vector_lw = 1.0 + +[image.processing] +normalise = false +log_plot = false + +[image.overlays] +plot_sheath = false +plot_phase_waveform = false +plot_overlay = false + +[overrides] +title = [] +legend = [] +xaxis = [] +xlabel = [] +ylabel = [] + +#=======================================================# + +[expert.debug] +magmesh = 1 +ffmpeg_movies = false +IDEBUG = false + +[expert.warnings] +ignore_div_zero = true +ignore_empty_contours = true + +[expert.numerical] +sheath_method = 'AbsDensity' +thrust_method = 'AxialMomentum' +mean_calculation = 'MeanFraction' + +[expert.overrides] +DC_bias_axis = 'Auto' +sheath_ion_species = ['AR+'] + +[expert.filtering] +kinetic_filtering = true +plot_kinetic_filtering = false +sav_window = 25 +sav_poly_order = 3 + +[expert.misc] +sheath_ion_ratio_threshold = 1.03 +conv_azimuthal_phase = true +EDF_threshold = 0.01 +units = 'SI' \ No newline at end of file diff --git a/tests/test_regression.py b/tests/test_input.py similarity index 59% rename from tests/test_regression.py rename to tests/test_input.py index 66b9857..1ed7f97 100644 --- a/tests/test_regression.py +++ b/tests/test_input.py @@ -1,10 +1,18 @@ import pathlib +import pytest +import shutil +import os from helena import run -def test_AR2plus(snaptolshot): - import os +@pytest.fixture +def directory(): + return pathlib.Path(__file__).parent / "input_files" + + +def test_AR2plus(snaptolshot, directory): + input_file = directory / "input1.toml" os.chdir("tests/") @@ -12,7 +20,7 @@ def test_AR2plus(snaptolshot): output_file.unlink(missing_ok=True) - run(argv=[]) + run(argv=[str(input_file)]) assert output_file.exists() @@ -24,9 +32,6 @@ def test_AR2plus(snaptolshot): assert snaptolshot == output_lines pathlib.Path("data/meshnodes.dat").unlink() - pathlib.Path("data/TECPlot2D/2DPlots_Data/AR2+.csv").unlink() - pathlib.Path("data/TECPlot2D/2DPlot_AR2+.png").unlink() - pathlib.Path("data/TECPlot2D/2DPlots_Data").rmdir() - pathlib.Path("data/TECPlot2D").rmdir() + shutil.rmtree(pathlib.Path("data/TECPlot2D/")) os.chdir("../") From f9d2f1e0cba1469ec50993570b6188b1f1ba7ad3 Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Thu, 26 Mar 2026 15:31:03 +0000 Subject: [PATCH 3/8] Update test suite structure to use more intelligent way of directory management. --- tests/conftest.py | 47 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_input.py | 33 +++++-------------------------- 2 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 tests/conftest.py diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..f4a5aa0 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,47 @@ +import os +import shutil +import pathlib +import pytest + + +@pytest.fixture(scope="session") +def input_directory(): + return pathlib.Path(__file__).parent / "input_files" + + +@pytest.fixture(scope="session") +def data_directory(): + return pathlib.Path(__file__).parent / "data" + + +@pytest.fixture(scope="session") +def test_env(tmp_path_factory, data_directory): + """ + Copy the expensive/shared input data once per test session. + """ + root = tmp_path_factory.mktemp("helena_env") + shutil.copytree(data_directory, root / "data") + return root + + +@pytest.fixture +def work_env(tmp_path, test_env): + """ + Give each test its own isolated copy of the seeded environment. + """ + workdir = tmp_path / "work" + shutil.copytree(test_env / "data", workdir / "data") + return workdir + + +@pytest.fixture +def work_env_with_chdir(work_env): + """ + Temporarily run the test from inside its isolated working directory. + """ + old_cwd = pathlib.Path.cwd() + os.chdir(work_env) + try: + yield work_env + finally: + os.chdir(old_cwd) diff --git a/tests/test_input.py b/tests/test_input.py index 1ed7f97..d3c320d 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -1,37 +1,14 @@ -import pathlib -import pytest -import shutil -import os - from helena import run -@pytest.fixture -def directory(): - return pathlib.Path(__file__).parent / "input_files" - - -def test_AR2plus(snaptolshot, directory): - input_file = directory / "input1.toml" - - os.chdir("tests/") - - output_file = pathlib.Path("data/TECPlot2D/2DPlots_Data/AR2+.csv") - - output_file.unlink(missing_ok=True) +def test_AR2plus(snaptolshot, input_directory, work_env_with_chdir): + input_file = input_directory / "input1.toml" run(argv=[str(input_file)]) + output_file = work_env_with_chdir / "data/TECPlot2D/2DPlots_Data/AR2+.csv" assert output_file.exists() - with output_file.open("r") as f: - output_lines = f.readlines() - - assert len(output_lines) > 0 - + output_lines = output_file.read_text().splitlines(keepends=True) + assert output_lines assert snaptolshot == output_lines - - pathlib.Path("data/meshnodes.dat").unlink() - shutil.rmtree(pathlib.Path("data/TECPlot2D/")) - - os.chdir("../") From 103a69b6ce73aebb85205f0271bacd7a8e7fe4bd Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Thu, 26 Mar 2026 19:31:33 +0000 Subject: [PATCH 4/8] Update optional tests dependency to require snaptol 0.0.4 that now allows for parameterised tests. --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3bfcedd..d6fccf6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ helena = "helena.helena:run" [project.optional-dependencies] tests = [ "pytest>=9.0.2", - "snaptol>=0.0.3", + "snaptol>=0.0.4", ] lint = [ "ruff>=0.15.4", diff --git a/uv.lock b/uv.lock index b96ac74..f8279a6 100644 --- a/uv.lock +++ b/uv.lock @@ -178,7 +178,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.2" }, { name = "ruff", marker = "extra == 'lint'", specifier = ">=0.15.4" }, { name = "scipy", specifier = ">=1.17.1" }, - { name = "snaptol", marker = "extra == 'tests'", specifier = ">=0.0.3" }, + { name = "snaptol", marker = "extra == 'tests'", specifier = ">=0.0.4" }, { name = "toml", specifier = ">=0.10.2" }, { name = "tqdm", specifier = ">=4.67.3" }, ] @@ -684,15 +684,15 @@ wheels = [ [[package]] name = "snaptol" -version = "0.0.3" +version = "0.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/36/d78a0002c921b0297b72ac35d852d6892790563d4b6a696d59d876cb69bb/snaptol-0.0.3.tar.gz", hash = "sha256:6892e6bfc6108c8d94a4c1242a82b96751c3ce9c639bf3ec9e821d72ad9a0919", size = 102884, upload-time = "2026-02-17T10:27:47.249Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/f3/745fe4e9248cf7372bc3ced73d52e83185e60ffc0f3193520e69198ae0cf/snaptol-0.0.4.tar.gz", hash = "sha256:62b7759add1aab7cd8abb3a2a25ff824259ac837fab851568c01df1dd12577be", size = 102905, upload-time = "2026-03-26T19:22:29.689Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/99/c52a1cabc65247a419ca6dcb9114a488c5fc3f5d1a176430d053bf055271/snaptol-0.0.3-py3-none-any.whl", hash = "sha256:af5a191209f934e4e1f65079dd601aec9aa85e5283c832ad67c27bfc00b353d9", size = 13013, upload-time = "2026-02-17T10:27:45.563Z" }, + { url = "https://files.pythonhosted.org/packages/f9/67/3c5cb4cf7e7a8902c66dfd4b775528b813fa03bce959aedd1ab63ac03ccb/snaptol-0.0.4-py3-none-any.whl", hash = "sha256:f22a1a6f982c9d9030a36652157c966f89928c95789ef89b35fc238e7e755cff", size = 13024, upload-time = "2026-03-26T19:22:28.018Z" }, ] [[package]] From 91f7f8a33d18915ff4a903d2de493c5c87f092ac Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Thu, 26 Mar 2026 19:32:14 +0000 Subject: [PATCH 5/8] Change test to a parameterised test in anticipation of adding more varieties of input files to check. --- ...input.py::test_AR2plus[input_tecplot2D.toml].json} | 0 .../input_files/{input1.toml => input_tecplot2D.toml} | 0 tests/test_input.py | 11 +++++++---- 3 files changed, 7 insertions(+), 4 deletions(-) rename tests/__snapshots__/{test_input.test_AR2plus.json => test_input.py::test_AR2plus[input_tecplot2D.toml].json} (100%) rename tests/input_files/{input1.toml => input_tecplot2D.toml} (100%) diff --git a/tests/__snapshots__/test_input.test_AR2plus.json b/tests/__snapshots__/test_input.py::test_AR2plus[input_tecplot2D.toml].json similarity index 100% rename from tests/__snapshots__/test_input.test_AR2plus.json rename to tests/__snapshots__/test_input.py::test_AR2plus[input_tecplot2D.toml].json diff --git a/tests/input_files/input1.toml b/tests/input_files/input_tecplot2D.toml similarity index 100% rename from tests/input_files/input1.toml rename to tests/input_files/input_tecplot2D.toml diff --git a/tests/test_input.py b/tests/test_input.py index d3c320d..b269a30 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -1,10 +1,13 @@ -from helena import run +import pytest +from helena import run -def test_AR2plus(snaptolshot, input_directory, work_env_with_chdir): - input_file = input_directory / "input1.toml" - run(argv=[str(input_file)]) +@pytest.mark.parametrize( + "input_file", ["input_tecplot2D.toml"], +) +def test_AR2plus(input_file, input_directory, snaptolshot, work_env_with_chdir): + run(argv=[str(input_directory / input_file)]) output_file = work_env_with_chdir / "data/TECPlot2D/2DPlots_Data/AR2+.csv" assert output_file.exists() From c3f185b73950bd47799e27e49d16934e8dd58012 Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Wed, 1 Apr 2026 13:35:28 +0100 Subject: [PATCH 6/8] Add pydantic as a dependency --- pyproject.toml | 1 + uv.lock | 144 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index d6fccf6..b3d8f66 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,7 @@ license-files = ["LICEN[CS]E*"] dependencies = [ "matplotlib>=3.10.8", "numpy>=2.4.2", + "pydantic>=2.12.5", "scipy>=1.17.1", "toml>=0.10.2", "tqdm>=4.67.3", diff --git a/uv.lock b/uv.lock index f8279a6..50f2545 100644 --- a/uv.lock +++ b/uv.lock @@ -2,6 +2,15 @@ version = 1 revision = 2 requires-python = ">=3.11" +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -157,6 +166,7 @@ source = { editable = "." } dependencies = [ { name = "matplotlib" }, { name = "numpy" }, + { name = "pydantic" }, { name = "scipy" }, { name = "toml" }, { name = "tqdm" }, @@ -175,6 +185,7 @@ tests = [ requires-dist = [ { name = "matplotlib", specifier = ">=3.10.8" }, { name = "numpy", specifier = ">=2.4.2" }, + { name = "pydantic", specifier = ">=2.12.5" }, { name = "pytest", marker = "extra == 'tests'", specifier = ">=9.0.2" }, { name = "ruff", marker = "extra == 'lint'", specifier = ">=0.15.4" }, { name = "scipy", specifier = ">=1.17.1" }, @@ -531,6 +542,118 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -715,3 +838,24 @@ sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd wheels = [ { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] From b46867e692484e80ef5e074d7c68b90b92ad2c35 Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Wed, 1 Apr 2026 14:25:12 +0100 Subject: [PATCH 7/8] Use pydantic for input parameter type checking. Add test for incorrect type in an input. --- src/helena/helena.py | 217 ++++-------------------- tests/conftest.py | 42 ++--- tests/input_files/input_wrong_type.toml | 2 + tests/test_input.py | 10 +- 4 files changed, 63 insertions(+), 208 deletions(-) create mode 100644 tests/input_files/input_wrong_type.toml diff --git a/src/helena/helena.py b/src/helena/helena.py index bd28601..ec70027 100644 --- a/src/helena/helena.py +++ b/src/helena/helena.py @@ -88,7 +88,8 @@ def run(argv=None): from matplotlib import ticker from scipy import ndimage from tqdm import tqdm - from dataclasses import dataclass, field + from pydantic import Field + from pydantic.dataclasses import dataclass #from Read_data_functions.py import overlay_GEC_geometry @@ -146,73 +147,29 @@ def run(argv=None): _T = TypeVar("T") - def enforce_float(value: int | float, field: str) -> float: - if isinstance(value, bool) or not isinstance(value, int | float): - raise TypeError(f"Field {field} is not a float or int.") - - return float(value) - - def enforce_int(value: int, field: str) -> int: - if isinstance(value, bool) or not isinstance(value, int): - raise TypeError(f"Field {field} is not an int.") - - return value - - def enforce_str(value: str, field: str) -> str: - if not isinstance(value, str): - raise TypeError(f"Field {field} is not a string.") - - return value - - def enforce_bool(value: bool, field: str) -> bool: - if not isinstance(value, bool): - raise TypeError(f"Field {field} is not a bool.") - - return value - - def enforce_list_obj(lst: list[_T], func_checker: Callable[_T, str], field: str) -> list[_T]: - return [func_checker(element, field) for element in lst] - @dataclass class ChemistryInput: - variables: list[str] = field(default_factory=lambda: variable_sets["Phys"] + variable_sets["Ar"]) - multivar: list[str] = field(default_factory=list) - radial_profiles: list[int] = field(default_factory=list) - axial_profiles: list[int] = field(default_factory=lambda: [0]) - probe_loc: list[int] = field(default_factory=list) + variables: list[str] = Field(default_factory=lambda: variable_sets["Phys"] + variable_sets["Ar"]) + multivar: list[str] = Field(default_factory=list) + radial_profiles: list[int] = Field(default_factory=list) + axial_profiles: list[int] = Field(default_factory=lambda: [0]) + probe_loc: list[int] = Field(default_factory=list) # EDF. - IEDF_variables: list[str] = field(default_factory=lambda: variable_sets["GECCCP2a_BE_PCMC"].copy()) - NEDF_variables: list[str] = field(default_factory=list) + IEDF_variables: list[str] = Field(default_factory=lambda: variable_sets["GECCCP2a_BE_PCMC"].copy()) + NEDF_variables: list[str] = Field(default_factory=list) # Movie. - phase_variables: list[str] = field(default_factory=lambda: variable_sets["Ar_Phase"].copy()) - electrode_loc: list[int] = field(default_factory=lambda: [0, 0]) - waveform_locs: list[int] = field(default_factory=list) + phase_variables: list[str] = Field(default_factory=lambda: variable_sets["Ar_Phase"].copy()) + electrode_loc: list[int] = Field(default_factory=lambda: [0, 0]) + waveform_locs: list[int] = Field(default_factory=list) # Diagnostic. - sheath_ROI: list[int] = field(default_factory=list) - source_width: list[int] = field(default_factory=list) + sheath_ROI: list[int] = Field(default_factory=list) + source_width: list[int] = Field(default_factory=list) def __post_init__(self): - self.variables = enforce_list_obj(self.variables, enforce_str, "variables") - self.multivar = enforce_list_obj(self.multivar, enforce_str, "multivar") - self.radial_profiles = enforce_list_obj(self.radial_profiles, enforce_int, "radial_profiles") - self.axial_profiles = enforce_list_obj(self.axial_profiles, enforce_int, "axial_profiles") - self.probe_loc = enforce_list_obj(self.probe_loc, enforce_int, "probe_loc") - - self.IEDF_variables = enforce_list_obj(self.IEDF_variables, enforce_str, "IEDF_variables") - self.NEDF_variables = enforce_list_obj(self.NEDF_variables, enforce_str, "NEDF_variables") - - self.phase_variables = enforce_list_obj(self.phase_variables, enforce_str, "phase_variables") - self.electrode_loc = enforce_list_obj(self.electrode_loc, enforce_int, "electrode_loc") - self.waveform_locs = enforce_list_obj(self.waveform_locs, enforce_int, "waveform_locs") - - self.sheath_ROI = enforce_list_obj(self.sheath_ROI, enforce_int, "sheath_ROI") - self.source_width = enforce_list_obj(self.source_width, enforce_int, "source_width") - - # Expand variable sets. self.variables = self.expand_variable_set(self.variables) self.multivar = self.expand_variable_set(self.multivar) self.IEDF_variables = self.expand_variable_set(self.IEDF_variables) @@ -265,43 +222,11 @@ class FiguresInput: IEDF_trends: bool = False EEDF: bool = False - def __post_init__(self): - self.tecplot2D = enforce_bool(self.tecplot2D, "tecplot2D") - - self.movieicp2D = enforce_bool(self.movieicp2D, "movieicp2D") - self.movieicp1D = enforce_bool(self.movieicp1D, "movieicp1D") - self.timeaxis1D = enforce_bool(self.timeaxis1D, "timeaxis1D") - self.convergence = enforce_bool(self.convergence, "convergence") - self.iterstep = enforce_int(self.iterstep, "iterstep") - - self.monoprofiles = enforce_bool(self.monoprofiles, "monoprofiles") - self.multiprofiles = enforce_bool(self.multiprofiles, "multiprofiles") - self.compare_profiles = enforce_bool(self.compare_profiles, "compare_profiles") - - self.trend_phase_averaged = enforce_bool(self.trend_phase_averaged, "trend_phase_averaged") - self.trend_phase_resolved = enforce_bool(self.trend_phase_resolved, "trend_phase_resolved") - self.thrust_loc = enforce_int(self.thrust_loc, "thrust_loc") - - self.phase_resolve2D = enforce_bool(self.phase_resolve2D, "phase_resolve2D") - self.phase_resolve1D = enforce_bool(self.phase_resolve1D, "phase_resolve1D") - self.sheath_dynamics = enforce_bool(self.sheath_dynamics, "sheath_dynamics") - self.PROES = enforce_bool(self.PROES, "PROES") - self.phase_cycles = enforce_float(self.phase_cycles, "phase_cycles") - self.do_Fwidth = enforce_int(self.do_Fwidth, "do_Fwidth") - - self.IEDF_angular = enforce_bool(self.IEDF_angular, "IEDF_angular") - self.IEDF_trends = enforce_bool(self.IEDF_trends, "IEDF_trends") - self.EEDF = enforce_bool(self.EEDF, "EEDF") - @dataclass class WriteInput: ASCII: bool = False CSV: bool = True - def __post_init__(self): - self.ASCII = enforce_bool(self.ASCII, "ASCII") - self.CSV = enforce_bool(self.CSV, "CSV") - @dataclass class PrintoutInput: general_trends: bool = False @@ -312,15 +237,6 @@ class PrintoutInput: thrust: bool = False sheath: bool = False - def __post_init__(self): - self.general_trends = enforce_bool(self.general_trends, "general_trends") - self.Knudsen_number = enforce_bool(self.Knudsen_number, "Knudsen_number") - self.total_power = enforce_bool(self.total_power, "total_power") - self.Reynolds = enforce_bool(self.Reynolds, "Reynolds") - self.DC_bias = enforce_bool(self.DC_bias, "DC_bias") - self.thrust = enforce_bool(self.thrust, "thrust") - self.sheath = enforce_bool(self.sheath, "sheath") - @dataclass class ImageInput: extension: str = ".png" @@ -330,9 +246,9 @@ class ImageInput: cmap: str = "plasma" # Geometry. - aspect_ratio: list[float] = field(default_factory=lambda: [10.0, 10.0]) - radial_crop: list[float] = field(default_factory=list) - axial_crop: list[float] = field(default_factory=list) + aspect_ratio: list[float] = Field(default_factory=lambda: [10.0, 10.0]) + radial_crop: list[float] = Field(default_factory=list) + axial_crop: list[float] = Field(default_factory=list) rotate: bool = False # Display. @@ -353,7 +269,7 @@ class ImageInput: # Colorbar. cbar_ticks: bool = True cbar_bins: int = 5 - cbar_limit: list[float] = field(default_factory=list) + cbar_limit: list[float] = Field(default_factory=list) # Vector. plot_vector: bool = True @@ -369,60 +285,14 @@ class ImageInput: plot_phase_waveform: bool = False plot_overlay: bool = False - def __post_init__(self): - self.extension = enforce_str(self.extension, "extension") - - self.interp = enforce_str(self.interp, "interp") - self.cmap = enforce_str(self.cmap, "cmap") - - self.aspect_ratio = enforce_list_obj(self.aspect_ratio, enforce_float, "aspect_ratio") - self.radial_crop = enforce_list_obj(self.radial_crop, enforce_float, "radial_crop") - self.axial_crop = enforce_list_obj(self.axial_crop, enforce_float, "axial_crop") - self.rotate = enforce_bool(self.rotate, "rotate") - - self.plot_symmetry = enforce_bool(self.plot_symmetry, "plot_symmetry") - self.plot_mesh = enforce_bool(self.plot_mesh, "plot_mesh") - self.plot_grid = enforce_bool(self.plot_grid, "plot_grid") - - self.plot_colourfill = enforce_bool(self.plot_colourfill, "plot_colourfill") - self.plot_contours = enforce_bool(self.plot_contours, "plot_contours") - self.contour_lvls = enforce_int(self.contour_lvls, "contour_lvls") - - self.axis_ticks = enforce_bool(self.axis_ticks, "axis_ticks") - self.axis_labels = enforce_bool(self.axis_labels, "axis_labels") - self.legend_loc = enforce_str(self.legend_loc, "legend_loc") - - self.cbar_ticks = enforce_bool(self.cbar_ticks, "cbar_ticks") - self.cbar_bins = enforce_int(self.cbar_bins, "cbar_bins") - self.cbar_limit = enforce_list_obj(self.cbar_limit, enforce_float, "cbar_limit") - - self.plot_vector = enforce_bool(self.plot_vector, "plot_vector") - self.vector_density = enforce_float(self.vector_density, "vector_density") - self.vector_lw = enforce_float(self.vector_lw, "vector_lw") - - self.normalise = enforce_bool(self.normalise, "normalise") - self.log_plot = enforce_bool(self.log_plot, "log_plot") - - self.plot_sheath = enforce_bool(self.plot_sheath, "plot_sheath") - self.plot_phase_waveform = enforce_bool(self.plot_phase_waveform, "plot_phase_waveform") - self.plot_overlay = enforce_bool(self.plot_overlay, "plot_overlay") - - @dataclass class OverridesInput: - title: list[str] = field(default_factory=list) - legend: list[str] = field(default_factory=list) - xaxis: list[str] = field(default_factory=list) - xlabel: list[str] = field(default_factory=list) - ylabel: list[str] = field(default_factory=list) - - def __post_init__(self): - self.title = enforce_list_obj(self.title, enforce_str, "title") - self.legend = enforce_list_obj(self.legend, enforce_str, "legend") - self.xaxis = enforce_list_obj(self.xaxis, enforce_str, "xaxis") - self.xlabel = enforce_list_obj(self.xlabel, enforce_str, "xlabel") - self.ylabel = enforce_list_obj(self.ylabel, enforce_str, "ylabel") + title: list[str] = Field(default_factory=list) + legend: list[str] = Field(default_factory=list) + xaxis: list[str] = Field(default_factory=list) + xlabel: list[str] = Field(default_factory=list) + ylabel: list[str] = Field(default_factory=list) @dataclass class ExpertInput: @@ -442,7 +312,7 @@ class ExpertInput: # Overrides. DC_bias_axis: str = 'Auto' - sheath_ion_species: list[str] = field(default_factory=lambda: ['AR+']) + sheath_ion_species: list[str] = Field(default_factory=lambda: ['AR+']) # Filtering. kinetic_filtering: bool = True @@ -456,41 +326,16 @@ class ExpertInput: EDF_threshold: float = 0.01 units: str = 'SI' - def __post_init__(self): - self.magmesh = enforce_int(self.magmesh, "magmesh") - self.ffmpeg_movies = enforce_bool(self.ffmpeg_movies, "ffmpeg_movies") - self.IDEBUG = enforce_bool(self.IDEBUG, "IDEBUG") - - self.ignore_div_zero = enforce_bool(self.ignore_div_zero, "ignore_div_zero") - self.ignore_empty_contours = enforce_bool(self.ignore_empty_contours, "ignore_empty_contours") - - self.sheath_method = enforce_str(self.sheath_method, "sheath_method") - self.thrust_method = enforce_str(self.thrust_method, "thrust_method") - self.mean_calculation = enforce_str(self.mean_calculation, "mean_calculation") - - self.DC_bias_axis = enforce_str(self.DC_bias_axis, "DC_bias_axis") - self.sheath_ion_species = enforce_list_obj(self.sheath_ion_species, enforce_str, "sheath_ion_species") - - self.kinetic_filtering = enforce_bool(self.kinetic_filtering, "kinetic_filtering") - self.plot_kinetic_filtering = enforce_bool(self.plot_kinetic_filtering, "plot_kinetic_filtering") - self.sav_window = enforce_int(self.sav_window, "sav_window") - self.sav_poly_order = enforce_int(self.sav_poly_order, "sav_poly_order") - - self.sheath_ion_ratio_threshold = enforce_float(self.sheath_ion_ratio_threshold, "sheath_ion_ratio_threshold") - self.conv_azimuthal_phase = enforce_bool(self.conv_azimuthal_phase, "conv_azimuthal_phase") - self.EDF_threshold = enforce_float(self.EDF_threshold, "EDF_threshold") - self.units = enforce_str(self.units, "units") - @dataclass class Config: - chemistry: ChemistryInput = field(default_factory=ChemistryInput) - figures: FiguresInput = field(default_factory=FiguresInput) - write: WriteInput = field(default_factory=WriteInput) - printout: PrintoutInput = field(default_factory=PrintoutInput) - image: ImageInput = field(default_factory=ImageInput) - overrides: OverridesInput = field(default_factory=OverridesInput) - expert: ExpertInput = field(default_factory=ExpertInput) + chemistry: ChemistryInput = Field(default_factory=ChemistryInput) + figures: FiguresInput = Field(default_factory=FiguresInput) + write: WriteInput = Field(default_factory=WriteInput) + printout: PrintoutInput = Field(default_factory=PrintoutInput) + image: ImageInput = Field(default_factory=ImageInput) + overrides: OverridesInput = Field(default_factory=OverridesInput) + expert: ExpertInput = Field(default_factory=ExpertInput) def create_input(data: dict, category: str, output_obj: type[_T], sections: list[str] | None = None) -> _T: diff --git a/tests/conftest.py b/tests/conftest.py index f4a5aa0..34da163 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,32 +16,32 @@ def data_directory(): @pytest.fixture(scope="session") def test_env(tmp_path_factory, data_directory): - """ - Copy the expensive/shared input data once per test session. - """ - root = tmp_path_factory.mktemp("helena_env") - shutil.copytree(data_directory, root / "data") - return root + """ + Copy the expensive/shared input data once per test session. + """ + root = tmp_path_factory.mktemp("helena_env") + shutil.copytree(data_directory, root / "data") + return root @pytest.fixture def work_env(tmp_path, test_env): - """ - Give each test its own isolated copy of the seeded environment. - """ - workdir = tmp_path / "work" - shutil.copytree(test_env / "data", workdir / "data") - return workdir + """ + Give each test its own isolated copy of the seeded environment. + """ + workdir = tmp_path / "work" + shutil.copytree(test_env / "data", workdir / "data") + return workdir @pytest.fixture def work_env_with_chdir(work_env): - """ - Temporarily run the test from inside its isolated working directory. - """ - old_cwd = pathlib.Path.cwd() - os.chdir(work_env) - try: - yield work_env - finally: - os.chdir(old_cwd) + """ + Temporarily run the test from inside its isolated working directory. + """ + old_cwd = pathlib.Path.cwd() + os.chdir(work_env) + try: + yield work_env + finally: + os.chdir(old_cwd) diff --git a/tests/input_files/input_wrong_type.toml b/tests/input_files/input_wrong_type.toml new file mode 100644 index 0000000..6080216 --- /dev/null +++ b/tests/input_files/input_wrong_type.toml @@ -0,0 +1,2 @@ +[chemistry] +variables = [3] \ No newline at end of file diff --git a/tests/test_input.py b/tests/test_input.py index b269a30..e385cf2 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -1,10 +1,12 @@ import pytest +from pydantic import ValidationError from helena import run @pytest.mark.parametrize( - "input_file", ["input_tecplot2D.toml"], + "input_file", + ["input_tecplot2D.toml"], ) def test_AR2plus(input_file, input_directory, snaptolshot, work_env_with_chdir): run(argv=[str(input_directory / input_file)]) @@ -15,3 +17,9 @@ def test_AR2plus(input_file, input_directory, snaptolshot, work_env_with_chdir): output_lines = output_file.read_text().splitlines(keepends=True) assert output_lines assert snaptolshot == output_lines + + +@pytest.mark.usefixtures("work_env_with_chdir") +def test_input(input_directory): + with pytest.raises(ValidationError): + run(argv=[str(input_directory / "input_wrong_type.toml")]) From 5d7a504e2f71dff728e273e4384e67dd21d3c643 Mon Sep 17 00:00:00 2001 From: Ava Dean Date: Wed, 1 Apr 2026 14:43:59 +0100 Subject: [PATCH 8/8] Update input tests to make it clearer that they are ran in a temp directory - rather than doing it under the hood. --- tests/conftest.py | 11 +++-------- tests/test_input.py | 7 ++++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 34da163..22b30f0 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,13 +35,8 @@ def work_env(tmp_path, test_env): @pytest.fixture -def work_env_with_chdir(work_env): +def run_in_tmp_dir(work_env, monkeypatch): """ - Temporarily run the test from inside its isolated working directory. + Temporarily change the directory to the working directory. """ - old_cwd = pathlib.Path.cwd() - os.chdir(work_env) - try: - yield work_env - finally: - os.chdir(old_cwd) + monkeypatch.chdir(work_env) diff --git a/tests/test_input.py b/tests/test_input.py index e385cf2..00167a7 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -4,14 +4,15 @@ from helena import run +@pytest.mark.usefixtures("run_in_tmp_dir") @pytest.mark.parametrize( "input_file", ["input_tecplot2D.toml"], ) -def test_AR2plus(input_file, input_directory, snaptolshot, work_env_with_chdir): +def test_AR2plus(input_file, input_directory, work_env, snaptolshot): run(argv=[str(input_directory / input_file)]) - output_file = work_env_with_chdir / "data/TECPlot2D/2DPlots_Data/AR2+.csv" + output_file = work_env / "data/TECPlot2D/2DPlots_Data/AR2+.csv" assert output_file.exists() output_lines = output_file.read_text().splitlines(keepends=True) @@ -19,7 +20,7 @@ def test_AR2plus(input_file, input_directory, snaptolshot, work_env_with_chdir): assert snaptolshot == output_lines -@pytest.mark.usefixtures("work_env_with_chdir") +@pytest.mark.usefixtures("run_in_tmp_dir") def test_input(input_directory): with pytest.raises(ValidationError): run(argv=[str(input_directory / "input_wrong_type.toml")])