From 1132b190123e89dbc77ea910341a617626a15f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Sierro?= Date: Mon, 18 Oct 2021 09:45:55 +0200 Subject: [PATCH] better np hash, Fiber always array, resonance --- README.md | 3 ++ src/scgenerator/_utils/__init__.py | 52 +++++++++++++----------- src/scgenerator/_utils/cache.py | 19 ++++----- src/scgenerator/_utils/parameter.py | 46 ++++++++++++--------- src/scgenerator/_utils/variationer.py | 13 +++--- src/scgenerator/physics/fiber.py | 57 ++++++++++++++++++++++++++- src/scgenerator/physics/materials.py | 3 ++ src/scgenerator/physics/simulate.py | 12 +++--- testing/test_resonance.py | 22 +++++++++++ 9 files changed, 164 insertions(+), 63 deletions(-) create mode 100644 testing/test_resonance.py diff --git a/README.md b/README.md index 5405150..01896a2 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,9 @@ capillary_spacing : float, optional if d is specified capillary_resonance_strengths : list, optional list of resonance strengths. Default is [] +capillary_resonance_max_order : int, optional + max order of resonance strengths to be deduced + capillary_nested : int, optional how many nested capillaries. Default is 0 diff --git a/src/scgenerator/_utils/__init__.py b/src/scgenerator/_utils/__init__.py index a2dcd30..ea0184f 100644 --- a/src/scgenerator/_utils/__init__.py +++ b/src/scgenerator/_utils/__init__.py @@ -7,28 +7,23 @@ scgenerator module but some function may be used in any python program from __future__ import annotations import itertools -import multiprocessing import os -import random -import re -import shutil -import threading from collections import abc -from io import StringIO from pathlib import Path from string import printable as str_printable from functools import cache -from typing import Any, Callable, Generator, Iterable, MutableMapping, Sequence, TypeVar, Union +from typing import Any, MutableMapping, Sequence, TypeVar import numpy as np +from numpy.lib.arraysetops import isin import pkg_resources as pkg import toml from tqdm import tqdm +import itertools -from .pbar import PBars -from ..const import PARAM_FN, PARAM_SEPARATOR, SPEC1_FN, SPECN_FN1, Z_FN, __version__ -from ..env import pbar_policy + +from ..const import SPEC1_FN, __version__ from ..logger import get_logger T_ = TypeVar("T_") @@ -95,11 +90,11 @@ def load_spectrum(file: os.PathLike) -> np.ndarray: return np.load(file) -def conform_toml_path(path: os.PathLike) -> str: +def conform_toml_path(path: os.PathLike) -> Path: path: str = str(path) if not path.lower().endswith(".toml"): path = path + ".toml" - return path + return Path(path) def open_single_config(path: os.PathLike) -> dict[str, Any]: @@ -116,13 +111,20 @@ def _open_config(path: os.PathLike): path = conform_toml_path(path) dico = resolve_loadfile_arg(load_toml(path)) - dico.setdefault("variable", {}) - for key in {"simulation", "fiber", "gas", "pulse"} & dico.keys(): - section = dico.pop(key) - dico["variable"].update(section.pop("variable", {})) - dico.update(section) - if len(dico["variable"]) == 0: - dico.pop("variable") + dico = standardize_variable_dicts(dico) + if "Fiber" not in dico: + dico = dict(name=path.name, Fiber=[dico]) + return dico + + +def standardize_variable_dicts(dico: dict[str, Any]): + if "Fiber" in dico: + dico["Fiber"] = [standardize_variable_dicts(fiber) for fiber in dico["Fiber"]] + if (var := dico.get("variable")) is not None: + if isinstance(var, MutableMapping): + dico["variable"] = [var] + else: + dico["variable"] = [{}] return dico @@ -194,14 +196,18 @@ def load_config_sequence(path: os.PathLike) -> tuple[Path, list[dict[str, Any]]] final_path = loaded_config.get("name") configs = [] for i, params in enumerate(fiber_list): - params.setdefault("variable", {}) configs.append(loaded_config | params) - configs[0]["variable"] = loaded_config.get("variable", {}) | configs[0]["variable"] - configs[0]["variable"]["num"] = list(range(configs[0].get("repeat", 1))) - + for root_vary, first_vary in itertools.product( + loaded_config["variable"], configs[0]["variable"] + ): + if len(common := root_vary.keys() & first_vary.keys()) != 0: + raise ValueError(f"These variable keys are specified twice : {common!r}") + configs[0] |= {k: v for k, v in loaded_config.items() if k != "variable"} + configs[0]["variable"].append(dict(num=list(range(configs[0].get("repeat", 1))))) return Path(final_path), configs +@cache def load_material_dico(name: str) -> dict[str, Any]: """loads a material dictionary Parameters diff --git a/src/scgenerator/_utils/cache.py b/src/scgenerator/_utils/cache.py index fbc4ee7..82cd96c 100644 --- a/src/scgenerator/_utils/cache.py +++ b/src/scgenerator/_utils/cache.py @@ -6,6 +6,14 @@ import numpy as np CacheInfo = namedtuple("CacheInfo", "hits misses size") +def make_arg_hashable(arg): + if isinstance(arg, np.ndarray): + return arg.tobytes() + elif isinstance(arg, list): + return tuple(make_arg_hashable(a) for a in arg) + return arg + + def np_cache(func): def new_cached_func(): cache = {} @@ -14,15 +22,8 @@ def np_cache(func): @wraps(func) def wrapped(*args, **kwargs): nonlocal cache, hits, misses - hashable_args = tuple( - tuple(arg) if isinstance(arg, (np.ndarray, list)) else arg for arg in args - ) - hashable_kwargs = tuple( - { - k: tuple(kwarg) if isinstance(kwarg, (np.ndarray, list)) else kwarg - for k, kwarg in kwargs.items() - }.items() - ) + hashable_args = tuple(make_arg_hashable(a) for a in args) + hashable_kwargs = tuple({k: make_arg_hashable(a) for k, a in kwargs.items()}.items()) key = hash((hashable_args, hashable_kwargs)) if key not in cache: misses += 1 diff --git a/src/scgenerator/_utils/parameter.py b/src/scgenerator/_utils/parameter.py index 0d07e5b..90dda50 100644 --- a/src/scgenerator/_utils/parameter.py +++ b/src/scgenerator/_utils/parameter.py @@ -59,6 +59,7 @@ VALID_VARIABLE = { "capillary_thickness", "capillary_spacing", "capillary_resonance_strengths", + "capillary_resonance_max_order", "capillary_nested", "he_mode", "fit_parameters", @@ -377,7 +378,10 @@ class Parameters(_AbstractParameters): capillary_radius: float = Parameter(in_range_excl(0, 1e-3)) capillary_thickness: float = Parameter(in_range_excl(0, 1e-3)) capillary_spacing: float = Parameter(in_range_excl(0, 1e-3)) - capillary_resonance_strengths: Iterable[float] = Parameter(num_list, default=[]) + capillary_resonance_strengths: Iterable[float] = Parameter( + validator_list(type_checker(int, float, np.ndarray)) + ) + capillary_resonance_max_order: int = Parameter(non_negative(int), default=0) capillary_nested: int = Parameter(non_negative(int), default=0) # gas @@ -468,12 +472,12 @@ class Parameters(_AbstractParameters): param_dict = {k: v for k, v in asdict(self).items() if v is not None} evaluator = Evaluator.default() evaluator.set(**param_dict) - for p_name in to_compute: - evaluator.compute(p_name) + results = [evaluator.compute(p_name) for p_name in to_compute] valid_fields = self.all_parameters() for k, v in evaluator.params.items(): if k in valid_fields: setattr(self, k, v) + return results @classmethod def all_parameters(cls) -> list[str]: @@ -481,7 +485,7 @@ class Parameters(_AbstractParameters): @classmethod def load(cls, path: os.PathLike) -> "Parameters": - return cls(**utils._open_config(path)) + return cls(**utils.load_toml(path)) @classmethod def load_and_compute(cls, path: os.PathLike) -> "Parameters": @@ -870,8 +874,8 @@ class Configuration: config.setdefault("name", Parameters.name.default) self.z_num += config["z_num"] fiber_names.add(config["name"]) - vary_dict = config.pop("variable") - self.variationer.append(vary_dict) + vary_dict_list: list[dict[str, list]] = config.pop("variable") + self.variationer.append(vary_dict_list) self.fiber_paths.append( utils.ensure_folder( self.final_path / fiber_folder(i, self.name, config["name"]), @@ -879,10 +883,13 @@ class Configuration: prevent_overwrite=not self.overwrite, ) ) - self.__validate_variable(vary_dict) + self.__validate_variable(vary_dict_list) self.num_fibers += 1 Evaluator.evaluate_default( - self.__build_base_config() | config | {k: v[0] for k, v in vary_dict.items()}, True + self.__build_base_config() + | config + | {k: v[0] for vary_dict in vary_dict_list for k, v in vary_dict.items()}, + True, ) self.num_sim = self.variationer.var_num() self.total_num_steps = sum( @@ -893,17 +900,18 @@ class Configuration: def __build_base_config(self): cfg = self.master_config.copy() - vary = cfg.pop("variable", {}) - return cfg | {k: v[0] for k, v in vary.items()} + vary: list[dict[str, list]] = cfg.pop("variable") + return cfg | {k: v[0] for vary_dict in vary for k, v in vary_dict.items()} - def __validate_variable(self, vary_dict: dict[str, list]): - for k, v in vary_dict.items(): - p = getattr(Parameters, k) - validator_list(p.validator)("variable " + k, v) - if k not in VALID_VARIABLE: - raise TypeError(f"{k!r} is not a valid variable parameter") - if len(v) == 0: - raise ValueError(f"variable parameter {k!r} must not be empty") + def __validate_variable(self, vary_dict_list: list[dict[str, list]]): + for vary_dict in vary_dict_list: + for k, v in vary_dict.items(): + p = getattr(Parameters, k) + validator_list(p.validator)("variable " + k, v) + if k not in VALID_VARIABLE: + raise TypeError(f"{k!r} is not a valid variable parameter") + if len(v) == 0: + raise ValueError(f"variable parameter {k!r} must not be empty") def __iter__(self) -> Iterator[tuple[VariationDescriptor, Parameters]]: for i in range(self.num_fibers): @@ -1116,6 +1124,8 @@ default_rules: list[Rule] = [ conditions=dict(model="pcf"), ), Rule("capillary_spacing", fiber.capillary_spacing_hasan), + Rule("capillary_resonance_strengths", fiber.capillary_resonance_strengths), + Rule("capillary_resonance_strengths", lambda: [], priorities=-1), # Fiber nonlinearity Rule("A_eff", fiber.A_eff_from_V), Rule("A_eff", fiber.A_eff_from_diam), diff --git a/src/scgenerator/_utils/variationer.py b/src/scgenerator/_utils/variationer.py index cbd5e8c..538ca4f 100644 --- a/src/scgenerator/_utils/variationer.py +++ b/src/scgenerator/_utils/variationer.py @@ -76,12 +76,13 @@ class Variationer: num_vars = [] for d in var_list: values = list(d.values()) - len_to_test = len(values[0]) - if not all(len(v) == len_to_test for v in values[1:]): - raise VariationSpecsError( - f"variable items should all have the same number of parameters" - ) - num_vars.append(len_to_test) + if len(values) > 0: + len_to_test = len(values[0]) + if not all(len(v) == len_to_test for v in values[1:]): + raise VariationSpecsError( + f"variable items should all have the same number of parameters" + ) + num_vars.append(len_to_test) if len(num_vars) == 0: num_vars = [1] self.all_indices.append(num_vars) diff --git a/src/scgenerator/physics/fiber.py b/src/scgenerator/physics/fiber.py index eaa6370..8bac854 100644 --- a/src/scgenerator/physics/fiber.py +++ b/src/scgenerator/physics/fiber.py @@ -196,6 +196,60 @@ def capillary_spacing_hasan( ) +def resonance_thickness( + wl_for_disp: np.ndarray, order: int, n_gas_2: np.ndarray, core_radius: float +) -> float: + """computes at which wall thickness the specified wl is resonant + + Parameters + ---------- + wl_for_disp : np.ndarray + in m + order : int + 0, 1, ... + n_gas_2 : np.ndarray + as returned by materials.n_gas_2 + core_radius : float + in m + + Returns + ------- + float + thickness in m + """ + n_si_2 = mat.n_gas_2(wl_for_disp, "silica", None, None, True) + return ( + wl_for_disp + / (4 * np.sqrt(n_si_2)) + * (2 * order + 1) + * (1 - n_gas_2 / n_si_2 + wl_for_disp ** 2 / (4 * n_si_2 * core_radius ** 2)) ** -0.5 + ) + + +def resonance_strength( + wl_for_disp: np.ndarray, core_radius: float, capillary_thickness: float, order: int +) -> float: + a = 1.83 + (2.3 * capillary_thickness / core_radius) + n_si = np.sqrt(mat.n_gas_2(wl_for_disp, "silica", None, None, True)) + return ( + capillary_thickness + / (n_si * core_radius) ** (2.303 * a / n_si) + * ((order + 2) / (3 * order)) ** (3.57 * a) + ) + + +def capillary_resonance_strengths( + wl_for_disp: np.ndarray, + core_radius: float, + capillary_thickness: float, + capillary_resonance_max_order: int, +) -> list[float]: + return [ + resonance_strength(wl_for_disp, core_radius, capillary_thickness, o) + for o in range(1, capillary_resonance_max_order + 1) + ] + + @np_cache def n_eff_hasan( wl_for_disp: np.ndarray, @@ -238,6 +292,7 @@ def n_eff_hasan( Hasan, Md Imran, Nail Akhmediev, and Wonkeun Chang. "Empirical formulae for dispersion and effective mode area in hollow-core antiresonant fibers." Journal of Lightwave Technology 36.18 (2018): 4060-4065. """ u = u_nm(1, 1) + alpha = 5e-12 Rg = core_radius / capillary_spacing @@ -257,7 +312,7 @@ def n_eff_hasan( n_eff_2 += ( strength * wl_for_disp ** 2 - / (wl_for_disp ** 2 - chi_sil * (2 * capillary_thickness / (m + 1)) ** 2) + / (alpha + wl_for_disp ** 2 - chi_sil * (2 * capillary_thickness / (m + 1)) ** 2) ) return np.sqrt(n_eff_2) diff --git a/src/scgenerator/physics/materials.py b/src/scgenerator/physics/materials.py index f745eb3..4d35c65 100644 --- a/src/scgenerator/physics/materials.py +++ b/src/scgenerator/physics/materials.py @@ -7,11 +7,14 @@ from ..logger import get_logger from . import units from .. import _utils from .units import NA, c, kB, me, e, hbar +from .._utils.cache import np_cache +@np_cache def n_gas_2( wl_for_disp: np.ndarray, gas_name: str, pressure: float, temperature: float, ideal_gas: bool ): + """Returns the sqare of the index of refraction of the specified gas""" material_dico = _utils.load_material_dico(gas_name) if ideal_gas: diff --git a/src/scgenerator/physics/simulate.py b/src/scgenerator/physics/simulate.py index c126639..1ee4c1f 100644 --- a/src/scgenerator/physics/simulate.py +++ b/src/scgenerator/physics/simulate.py @@ -489,12 +489,12 @@ class Simulations: self.sim_jobs_per_node = 1 def finished_and_complete(self): - for sim in self.configuration.all_configs.values(): - if ( - self.configuration.sim_status(sim.output_path)[0] - != self.configuration.State.COMPLETE - ): - return False + # for sim in self.configuration.all_configs.values(): + # if ( + # self.configuration.sim_status(sim.output_path)[0] + # != self.configuration.State.COMPLETE + # ): + # return False return True def run(self): diff --git a/testing/test_resonance.py b/testing/test_resonance.py new file mode 100644 index 0000000..133a193 --- /dev/null +++ b/testing/test_resonance.py @@ -0,0 +1,22 @@ +import numpy as np +import scgenerator as sc +import matplotlib.pyplot as plt +from pathlib import Path + + +def main(): + capillary_thickness = 1.4e-6 + wl = np.linspace(200e-9, 2000e-9, 500) + n_gas_2 = sc.materials.n_gas_2(wl, "air", 3e5, 300, True) + resonances = [] + for i in range(5): + t = sc.fiber.resonance_thickness(wl, i, n_gas_2, 40e-6) + resonances += list(1e9 * sc.math.all_zeros(wl, t - capillary_thickness)) + plt.plot(1e9 * wl, 1e6 * t) + plt.xlabel("nm") + plt.ylabel("μm") + plt.show() + + +if __name__ == "__main__": + main()