new parameters display logic

This commit is contained in:
Benoît Sierro
2023-10-13 15:23:22 +02:00
parent f9b75efd6a
commit 159433f654
5 changed files with 77 additions and 86 deletions

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "scgenerator" name = "scgenerator"
version = "0.3.18" version = "0.3.19"
description = "Simulate nonlinear pulse propagation in optical fibers" description = "Simulate nonlinear pulse propagation in optical fibers"
readme = "README.md" readme = "README.md"
authors = [{ name = "Benoit Sierro", email = "benoit.sierro@iap.unibe.ch" }] authors = [{ name = "Benoit Sierro", email = "benoit.sierro@iap.unibe.ch" }]

View File

@@ -27,7 +27,7 @@ SPEC1_FN_N = "spectrum_{}_{}.npy"
Z_FN = "z.npy" Z_FN = "z.npy"
PARAM_FN = "params.toml" PARAM_FN = "params.toml"
PARAM_SEPARATOR = " " PARAM_SEPARATOR = " "
DECIMALS_DISPLAY = 6
MANDATORY_PARAMETERS = { MANDATORY_PARAMETERS = {
"name", "name",

View File

@@ -12,18 +12,19 @@ from typing import Any, Callable, ClassVar, Iterable, Iterator, Set, Type, TypeV
import numpy as np 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.evaluator import Evaluator, EvaluatorError
from scgenerator.io import CustomEncoder, DataFile, custom_decode_hook from scgenerator.io import CustomEncoder, DataFile, custom_decode_hook
from scgenerator.operators import Qualifier, SpecOperator, VariableQuantity from scgenerator.operators import Qualifier, SpecOperator, VariableQuantity
from scgenerator.physics.units import unit_formatter
T = TypeVar("T") T = TypeVar("T")
DISPLAY_INFO = {} DISPLAY_FUNCTIONS: dict[str, Callable[[float], str]] = {}
def _format_display_info(name: str, value) -> str: def _format_display_info(name: str, value) -> str:
try: try:
return DISPLAY_INFO[name](value) return DISPLAY_FUNCTIONS[name](value)
except KeyError: except KeyError:
return format(value, ".9g") return format(value, ".9g")
@@ -217,7 +218,7 @@ class Parameter:
validator: Callable[[str, Any], None], validator: Callable[[str, Any], None],
converter: Callable = None, converter: Callable = None,
default=None, default=None,
display_info: tuple[float, str] = None, unit: str | None = None,
can_pickle: bool = True, can_pickle: bool = True,
): ):
""" """
@@ -233,15 +234,14 @@ class Parameter:
converts a valid value (for example, str.lower), by default None converts a valid value (for example, str.lower), by default None
default : callable, optional default : callable, optional
factory function for a default value (for example, list), by default None factory function for a default value (for example, list), by default None
display_info : tuple[float, str], optional display_info : tuple[str, int], optional
a factor by which to multiply the value and a string to be appended as a suffix a unit name (without prefix) and a max number of decimals for display purposes
when displaying the value example : ("W", 3) will mean the value 1.120045e6 is displayed as '1.12MW'
example : (1e-6, "MW") will mean the value 1.12e6 is displayed as '1.12MW'
""" """
self._validator = validator self._validator = validator
self.converter = converter self.converter = converter
self.default = default self.default = default
self.display_info = display_info self.unit = unit
self.can_pickle = can_pickle self.can_pickle = can_pickle
def __set_name__(self, owner: Type[Parameters], name): def __set_name__(self, owner: Type[Parameters], name):
@@ -252,7 +252,8 @@ class Parameter:
pass pass
if self.default is not None: if self.default is not None:
Evaluator.register_default_param(self.name, self.default) 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): def __get__(self, instance: Parameters, owner):
if instance is None: if instance is None:
@@ -275,16 +276,6 @@ class Parameter:
else: else:
self.__delete__(instance) 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]: def validate(self: Parameter, v) -> tuple[bool, Any]:
if v is None: if v is None:
is_value = False is_value = False
@@ -325,9 +316,9 @@ class Parameters:
effective_area: float = Parameter(non_negative(float, int)) effective_area: float = Parameter(non_negative(float, int))
effective_area_file: DataFile = Parameter(DataFile.validate) effective_area_file: DataFile = Parameter(DataFile.validate)
numerical_aperture: float = Parameter(in_range_excl(0, 1)) 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)) 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)) he_mode: tuple[int, int] = Parameter(int_pair, default=(1, 1))
fit_parameters: tuple[int, int] = Parameter(float_pair, default=(0.08, 200e-9)) fit_parameters: tuple[int, int] = Parameter(float_pair, default=(0.08, 200e-9))
beta2_coefficients: Iterable[float] = Parameter(num_list) beta2_coefficients: Iterable[float] = Parameter(num_list)
@@ -336,13 +327,13 @@ class Parameters:
literal("pcf", "marcatili", "marcatili_adjusted", "hasan", "custom"), literal("pcf", "marcatili", "marcatili_adjusted", "hasan", "custom"),
) )
zero_dispersion_wavelength: float = Parameter( 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_num: int = Parameter(positive(int))
capillary_radius: 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), display_info=(1e6, "μm")) capillary_thickness: float = Parameter(in_range_excl(0, 1e-3), unit="m")
capillary_spacing: float = Parameter(in_range_excl(0, 1e-3), display_info=(1e6, "μm")) capillary_spacing: float = Parameter(in_range_excl(0, 1e-3), unit="m")
capillary_resonance_strengths: Iterable[float] = Parameter( capillary_resonance_strengths: Iterable[float] = Parameter(
validator_list(type_checker(int, float, np.ndarray)) validator_list(type_checker(int, float, np.ndarray))
) )
@@ -351,31 +342,29 @@ class Parameters:
# gas # gas
gas_name: str = Parameter(low_string, default="vacuum") gas_name: str = Parameter(low_string, default="vacuum")
pressure: float = Parameter(non_negative(float, int), display_info=(1e-5, "bar")) pressure: float = Parameter(non_negative(float, int), unit="bar")
pressure_in: float = Parameter(non_negative(float, int), display_info=(1e-5, "bar")) pressure_in: float = Parameter(non_negative(float, int), unit="bar")
pressure_out: float = Parameter(non_negative(float, int), display_info=(1e-5, "bar")) pressure_out: float = Parameter(non_negative(float, int), unit="bar")
temperature: float = Parameter(positive(float, int), display_info=(1, "K"), default=300) temperature: float = Parameter(positive(float, int), unit="K", default=300)
plasma_density: float = Parameter(non_negative(float, int), default=0) plasma_density: float = Parameter(non_negative(float, int), default=0)
# pulse # pulse
field_file: DataFile = Parameter(DataFile.validate) field_file: DataFile = Parameter(DataFile.validate)
input_time: np.ndarray = Parameter(type_checker(np.ndarray)) input_time: np.ndarray = Parameter(type_checker(np.ndarray))
input_field: np.ndarray = Parameter(type_checker(np.ndarray)) input_field: np.ndarray = Parameter(type_checker(np.ndarray))
repetition_rate: float = Parameter( repetition_rate: float = Parameter(non_negative(float, int), unit="Hz", default=40e6)
non_negative(float, int), display_info=(1e-3, "kHz"), default=40e6 peak_power: float = Parameter(positive(float, int), unit="W")
) mean_power: float = Parameter(positive(float, int), unit="W")
peak_power: float = Parameter(positive(float, int), display_info=(1e-3, "kW")) energy: float = Parameter(positive(float, int), unit="J")
mean_power: float = Parameter(positive(float, int), display_info=(1e3, "mW"))
energy: float = Parameter(positive(float, int), display_info=(1e6, "μJ"))
soliton_num: float = Parameter(non_negative(float, int)) soliton_num: float = Parameter(non_negative(float, int))
additional_noise_factor: float = Parameter(positive(float, int), default=1) additional_noise_factor: float = Parameter(positive(float, int), default=1)
shape: str = Parameter(literal("gaussian", "sech"), default="gaussian") shape: str = Parameter(literal("gaussian", "sech"), default="gaussian")
wavelength: float = Parameter(in_range_incl(100e-9, 10000e-9), display_info=(1e9, "nm")) wavelength: float = Parameter(in_range_incl(100e-9, 10000e-9), unit="m")
intensity_noise: float = Parameter(in_range_incl(0, 1), display_info=(1e2, "%"), default=0) intensity_noise: float = Parameter(in_range_incl(0, 1), unit="%", default=0)
noise_correlation: float = Parameter(in_range_incl(-10, 10), 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")) width: float = Parameter(in_range_excl(0, 1e-9), unit="s")
t0: float = Parameter(in_range_excl(0, 1e-9), display_info=(1e15, "fs")) t0: float = Parameter(in_range_excl(0, 1e-9), unit="s")
delay: float = Parameter(type_checker(float, int), display_info=(1e15, "fs")) delay: float = Parameter(type_checker(float, int), unit="s")
# Behaviors to include # Behaviors to include
quantum_noise: bool = Parameter(boolean, default=False) quantum_noise: bool = Parameter(boolean, default=False)
@@ -394,13 +383,12 @@ class Parameters:
repeat: int = Parameter(positive(int), default=1) repeat: int = Parameter(positive(int), default=1)
t_num: int = Parameter(positive(int), default=4096) t_num: int = Parameter(positive(int), default=4096)
z_num: int = Parameter(positive(int), default=128) z_num: int = Parameter(positive(int), default=128)
time_window: float = Parameter(positive(float, int), display_info=(1e12, "ps")) time_window: float = Parameter(positive(float, int), unit="s")
dt: float = Parameter(in_range_excl(0, 10e-15), display_info=(1e15, "fs")) 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) tolerated_error: float = Parameter(in_range_excl(1e-15, 1e-3), default=1e-11)
step_size: float = Parameter(non_negative(float, int), default=0) step_size: float = Parameter(non_negative(float, int), default=0)
wavelength_window: tuple[float, float] = Parameter( wavelength_window: tuple[float, float] = Parameter(
validator_and(float_pair, validator_list(in_range_incl(100e-9, 10000e-9))), validator_and(float_pair, validator_list(in_range_incl(100e-9, 10000e-9))), unit="m"
display_info=(1e9, "nm"),
) )
interpolation_degree: int = Parameter(validator_and(type_checker(int), in_range_incl(2, 18))) interpolation_degree: int = Parameter(validator_and(type_checker(int), in_range_incl(2, 18)))
prev_sim_dir: str = Parameter(string) prev_sim_dir: str = Parameter(string)
@@ -428,9 +416,9 @@ class Parameters:
w_c: np.ndarray = Parameter(type_checker(np.ndarray)) w_c: np.ndarray = Parameter(type_checker(np.ndarray))
w0: float = Parameter(positive(float)) w0: float = Parameter(positive(float))
t: np.ndarray = Parameter(type_checker(np.ndarray)) t: np.ndarray = Parameter(type_checker(np.ndarray))
dispersion_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), display_info=(1e2, "cm")) nonlinear_length: float = Parameter(non_negative(float, int), unit="m")
soliton_length: float = Parameter(non_negative(float, int), display_info=(1e2, "cm")) soliton_length: float = Parameter(non_negative(float, int), unit="m")
adapt_step_size: bool = Parameter(boolean) adapt_step_size: bool = Parameter(boolean)
hr_w: np.ndarray = Parameter(type_checker(np.ndarray)) hr_w: np.ndarray = Parameter(type_checker(np.ndarray))
z_targets: 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): if isinstance(exclude, str):
exclude = [exclude] exclude = [exclude]
p_pairs = [(k, format_value(k, getattr(self, k))) for k in params if k not in 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_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]) 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]: def strip_params_dict(self, copy=False) -> dict[str, Any]:
""" """

View File

@@ -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) 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: class unit:
inv = None inv = None

View File

@@ -5,6 +5,8 @@ from typing import Callable, Generic, Iterator, ParamSpec, Sequence, TypeVar
import numpy as np import numpy as np
from scgenerator.physics.units import unit_formatter
T = TypeVar("T") T = TypeVar("T")
P = ParamSpec("P") 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)) 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( def sequence_from_specs(
start: float | int, stop: float | int, num: int, kind: str = "linear" start: float | int, stop: float | int, num: int, kind: str = "linear"
) -> np.ndarray: ) -> np.ndarray: