From 159433f654abc67b086e8bcc3bf3ae4b4a285531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Sierro?= Date: Fri, 13 Oct 2023 15:23:22 +0200 Subject: [PATCH] new parameters display logic --- pyproject.toml | 2 +- src/scgenerator/const.py | 2 +- src/scgenerator/parameter.py | 89 ++++++++++++--------------- src/scgenerator/physics/units.py | 34 ++++++++++ src/scgenerator/variableparameters.py | 36 +---------- 5 files changed, 77 insertions(+), 86 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 255816a..1c93e8e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "scgenerator" -version = "0.3.18" +version = "0.3.19" description = "Simulate nonlinear pulse propagation in optical fibers" readme = "README.md" authors = [{ name = "Benoit Sierro", email = "benoit.sierro@iap.unibe.ch" }] diff --git a/src/scgenerator/const.py b/src/scgenerator/const.py index ffb780b..d16abed 100644 --- a/src/scgenerator/const.py +++ b/src/scgenerator/const.py @@ -27,7 +27,7 @@ SPEC1_FN_N = "spectrum_{}_{}.npy" Z_FN = "z.npy" PARAM_FN = "params.toml" PARAM_SEPARATOR = " " - +DECIMALS_DISPLAY = 6 MANDATORY_PARAMETERS = { "name", diff --git a/src/scgenerator/parameter.py b/src/scgenerator/parameter.py index da296e5..380d365 100644 --- a/src/scgenerator/parameter.py +++ b/src/scgenerator/parameter.py @@ -12,18 +12,19 @@ from typing import Any, Callable, ClassVar, Iterable, Iterator, Set, Type, TypeV import numpy as np -from scgenerator.const import MANDATORY_PARAMETERS, __version__ +from scgenerator.const import DECIMALS_DISPLAY, MANDATORY_PARAMETERS, __version__ from scgenerator.evaluator import Evaluator, EvaluatorError from scgenerator.io import CustomEncoder, DataFile, custom_decode_hook from scgenerator.operators import Qualifier, SpecOperator, VariableQuantity +from scgenerator.physics.units import unit_formatter T = TypeVar("T") -DISPLAY_INFO = {} +DISPLAY_FUNCTIONS: dict[str, Callable[[float], str]] = {} def _format_display_info(name: str, value) -> str: try: - return DISPLAY_INFO[name](value) + return DISPLAY_FUNCTIONS[name](value) except KeyError: return format(value, ".9g") @@ -217,7 +218,7 @@ class Parameter: validator: Callable[[str, Any], None], converter: Callable = None, default=None, - display_info: tuple[float, str] = None, + unit: str | None = None, can_pickle: bool = True, ): """ @@ -233,15 +234,14 @@ class Parameter: converts a valid value (for example, str.lower), by default None default : callable, optional factory function for a default value (for example, list), by default None - display_info : tuple[float, str], optional - a factor by which to multiply the value and a string to be appended as a suffix - when displaying the value - example : (1e-6, "MW") will mean the value 1.12e6 is displayed as '1.12MW' + display_info : tuple[str, int], optional + a unit name (without prefix) and a max number of decimals for display purposes + example : ("W", 3) will mean the value 1.120045e6 is displayed as '1.12MW' """ self._validator = validator self.converter = converter self.default = default - self.display_info = display_info + self.unit = unit self.can_pickle = can_pickle def __set_name__(self, owner: Type[Parameters], name): @@ -252,7 +252,8 @@ class Parameter: pass if self.default is not None: Evaluator.register_default_param(self.name, self.default) - DISPLAY_INFO[self.name] = self.display + if self.unit is not None: + DISPLAY_FUNCTIONS[self.name] = unit_formatter(self.unit, DECIMALS_DISPLAY) def __get__(self, instance: Parameters, owner): if instance is None: @@ -275,16 +276,6 @@ class Parameter: else: self.__delete__(instance) - def display(self, num: float) -> str: - if self.display_info is None: - return str(num) - else: - fac, unit = self.display_info - num_str = format(num * fac, ".2f") - if num_str.endswith(".00"): - num_str = num_str[:-3] - return f"{num_str} {unit}" - def validate(self: Parameter, v) -> tuple[bool, Any]: if v is None: is_value = False @@ -325,9 +316,9 @@ class Parameters: effective_area: float = Parameter(non_negative(float, int)) effective_area_file: DataFile = Parameter(DataFile.validate) numerical_aperture: float = Parameter(in_range_excl(0, 1)) - pcf_pitch: float = Parameter(in_range_excl(0, 1e-3), display_info=(1e6, "μm")) + pcf_pitch: float = Parameter(in_range_excl(0, 1e-3), unit="m") pcf_pitch_ratio: float = Parameter(in_range_excl(0, 1)) - core_radius: float = Parameter(in_range_excl(0, 1e-3), display_info=(1e6, "μm")) + core_radius: float = Parameter(in_range_excl(0, 1e-3), unit="m") he_mode: tuple[int, int] = Parameter(int_pair, default=(1, 1)) fit_parameters: tuple[int, int] = Parameter(float_pair, default=(0.08, 200e-9)) beta2_coefficients: Iterable[float] = Parameter(num_list) @@ -336,13 +327,13 @@ class Parameters: literal("pcf", "marcatili", "marcatili_adjusted", "hasan", "custom"), ) zero_dispersion_wavelength: float = Parameter( - validator_list(non_negative(float, int)), display_info=(1e9, "nm") + validator_list(non_negative(float, int)), unit="m" ) - length: float = Parameter(non_negative(float, int), display_info=(1e2, "cm")) + length: float = Parameter(non_negative(float, int), unit="m") capillary_num: int = Parameter(positive(int)) - capillary_radius: float = Parameter(in_range_excl(0, 1e-3), display_info=(1e6, "μm")) - capillary_thickness: float = Parameter(in_range_excl(0, 1e-3), display_info=(1e6, "μm")) - capillary_spacing: float = Parameter(in_range_excl(0, 1e-3), display_info=(1e6, "μm")) + capillary_radius: float = Parameter(in_range_excl(0, 1e-3), unit="m") + capillary_thickness: float = Parameter(in_range_excl(0, 1e-3), unit="m") + capillary_spacing: float = Parameter(in_range_excl(0, 1e-3), unit="m") capillary_resonance_strengths: Iterable[float] = Parameter( validator_list(type_checker(int, float, np.ndarray)) ) @@ -351,31 +342,29 @@ class Parameters: # gas gas_name: str = Parameter(low_string, default="vacuum") - pressure: float = Parameter(non_negative(float, int), display_info=(1e-5, "bar")) - pressure_in: float = Parameter(non_negative(float, int), display_info=(1e-5, "bar")) - pressure_out: float = Parameter(non_negative(float, int), display_info=(1e-5, "bar")) - temperature: float = Parameter(positive(float, int), display_info=(1, "K"), default=300) + pressure: float = Parameter(non_negative(float, int), unit="bar") + pressure_in: float = Parameter(non_negative(float, int), unit="bar") + pressure_out: float = Parameter(non_negative(float, int), unit="bar") + temperature: float = Parameter(positive(float, int), unit="K", default=300) plasma_density: float = Parameter(non_negative(float, int), default=0) # pulse field_file: DataFile = Parameter(DataFile.validate) input_time: np.ndarray = Parameter(type_checker(np.ndarray)) input_field: np.ndarray = Parameter(type_checker(np.ndarray)) - repetition_rate: float = Parameter( - non_negative(float, int), display_info=(1e-3, "kHz"), default=40e6 - ) - peak_power: float = Parameter(positive(float, int), display_info=(1e-3, "kW")) - mean_power: float = Parameter(positive(float, int), display_info=(1e3, "mW")) - energy: float = Parameter(positive(float, int), display_info=(1e6, "μJ")) + repetition_rate: float = Parameter(non_negative(float, int), unit="Hz", default=40e6) + peak_power: float = Parameter(positive(float, int), unit="W") + mean_power: float = Parameter(positive(float, int), unit="W") + energy: float = Parameter(positive(float, int), unit="J") soliton_num: float = Parameter(non_negative(float, int)) additional_noise_factor: float = Parameter(positive(float, int), default=1) shape: str = Parameter(literal("gaussian", "sech"), default="gaussian") - wavelength: float = Parameter(in_range_incl(100e-9, 10000e-9), display_info=(1e9, "nm")) - intensity_noise: float = Parameter(in_range_incl(0, 1), display_info=(1e2, "%"), default=0) + wavelength: float = Parameter(in_range_incl(100e-9, 10000e-9), unit="m") + intensity_noise: float = Parameter(in_range_incl(0, 1), unit="%", default=0) noise_correlation: float = Parameter(in_range_incl(-10, 10), default=0) - width: float = Parameter(in_range_excl(0, 1e-9), display_info=(1e15, "fs")) - t0: float = Parameter(in_range_excl(0, 1e-9), display_info=(1e15, "fs")) - delay: float = Parameter(type_checker(float, int), display_info=(1e15, "fs")) + width: float = Parameter(in_range_excl(0, 1e-9), unit="s") + t0: float = Parameter(in_range_excl(0, 1e-9), unit="s") + delay: float = Parameter(type_checker(float, int), unit="s") # Behaviors to include quantum_noise: bool = Parameter(boolean, default=False) @@ -394,13 +383,12 @@ class Parameters: repeat: int = Parameter(positive(int), default=1) t_num: int = Parameter(positive(int), default=4096) z_num: int = Parameter(positive(int), default=128) - time_window: float = Parameter(positive(float, int), display_info=(1e12, "ps")) - dt: float = Parameter(in_range_excl(0, 10e-15), display_info=(1e15, "fs")) + time_window: float = Parameter(positive(float, int), unit="s") + dt: float = Parameter(in_range_excl(0, 10e-15), unit="s") tolerated_error: float = Parameter(in_range_excl(1e-15, 1e-3), default=1e-11) step_size: float = Parameter(non_negative(float, int), default=0) wavelength_window: tuple[float, float] = Parameter( - validator_and(float_pair, validator_list(in_range_incl(100e-9, 10000e-9))), - display_info=(1e9, "nm"), + validator_and(float_pair, validator_list(in_range_incl(100e-9, 10000e-9))), unit="m" ) interpolation_degree: int = Parameter(validator_and(type_checker(int), in_range_incl(2, 18))) prev_sim_dir: str = Parameter(string) @@ -428,9 +416,9 @@ class Parameters: w_c: np.ndarray = Parameter(type_checker(np.ndarray)) w0: float = Parameter(positive(float)) t: np.ndarray = Parameter(type_checker(np.ndarray)) - dispersion_length: float = Parameter(non_negative(float, int), display_info=(1e2, "cm")) - nonlinear_length: float = Parameter(non_negative(float, int), display_info=(1e2, "cm")) - soliton_length: float = Parameter(non_negative(float, int), display_info=(1e2, "cm")) + dispersion_length: float = Parameter(non_negative(float, int), unit="m") + nonlinear_length: float = Parameter(non_negative(float, int), unit="m") + soliton_length: float = Parameter(non_negative(float, int), unit="m") adapt_step_size: bool = Parameter(boolean) hr_w: np.ndarray = Parameter(type_checker(np.ndarray)) z_targets: np.ndarray = Parameter(type_checker(np.ndarray)) @@ -621,9 +609,10 @@ class Parameters: if isinstance(exclude, str): exclude = [exclude] p_pairs = [(k, format_value(k, getattr(self, k))) for k in params if k not in exclude] + p_pairs.sort() max_left = max(len(el[0]) for el in p_pairs) max_right = max(len(el[1]) for el in p_pairs if "\n" not in el[1]) - return "\n".join("{:>{l}} = {:{r}}".format(*p, l=max_left, r=max_right) for p in p_pairs) + return "\n".join("{:<{l}} = {:{r}}".format(*p, l=max_left, r=max_right) for p in p_pairs) def strip_params_dict(self, copy=False) -> dict[str, Any]: """ diff --git a/src/scgenerator/physics/units.py b/src/scgenerator/physics/units.py index 999e973..44a8af0 100644 --- a/src/scgenerator/physics/units.py +++ b/src/scgenerator/physics/units.py @@ -93,6 +93,40 @@ def W_to_Vm(n0: float, effective_area: float) -> float: return 1.0 / np.sqrt(effective_area * 0.5 * epsilon0 * c * n0) +def unit_formatter( + unit: str, decimals: int = 1, vmin: float | None = 1e-28 +) -> Callable[[float | int], str]: + if not unit: + + def formatter(val): + return f"{val:.{decimals}g}" + + elif unit == "%": + + def formatter(val): + return f"{100*val:.{decimals}g}%" + + else: + + def formatter(val): + if vmin is not None and abs(val) < vmin: + return f"0{unit}" + base, true_exp = format(val, f".{3+decimals}e").split("e") + true_exp = int(true_exp) + exp, mult = divmod(true_exp, 3) + if exp < -8: + mult -= -3 * (8 + exp) + exp = -8 + elif exp > 8: + mult += 3 * (exp - 8) + exp = 8 + + prefix = "yzafpnµm kMGTPEZY"[8 + exp] if exp else "" + return f"{float(base)*10**mult:.{decimals}g}{prefix}{unit}" + + return formatter + + class unit: inv = None diff --git a/src/scgenerator/variableparameters.py b/src/scgenerator/variableparameters.py index f6ef0fb..db6f05d 100644 --- a/src/scgenerator/variableparameters.py +++ b/src/scgenerator/variableparameters.py @@ -5,6 +5,8 @@ from typing import Callable, Generic, Iterator, ParamSpec, Sequence, TypeVar import numpy as np +from scgenerator.physics.units import unit_formatter + T = TypeVar("T") P = ParamSpec("P") @@ -75,40 +77,6 @@ def int_linear(id, d_min, d_max, d_num) -> int: return d_min + id * ((d_max - d_min) // (d_num - 1)) -def unit_formatter( - unit: str, decimals: int = 1, vmin: float | None = 1e-28 -) -> Callable[[float | int], str]: - if not unit: - - def formatter(val): - return f"{val:.{decimals}g}" - - elif unit == "%": - - def formatter(val): - return f"{100*val:.{decimals}g}%" - - else: - - def formatter(val): - if vmin is not None and abs(val) < vmin: - return f"0{unit}" - base, true_exp = format(val, f".{3+decimals}e").split("e") - true_exp = int(true_exp) - exp, mult = divmod(true_exp, 3) - if exp < -8: - mult -= -3 * (8 + exp) - exp = -8 - elif exp > 8: - mult += 3 * (exp - 8) - exp = 8 - - prefix = "yzafpnµm kMGTPEZY"[8 + exp] if exp else "" - return f"{float(base)*10**mult:.{decimals}g}{prefix}{unit}" - - return formatter - - def sequence_from_specs( start: float | int, stop: float | int, num: int, kind: str = "linear" ) -> np.ndarray: