initial commit

This commit is contained in:
Benoît Sierro
2023-03-17 11:17:18 +01:00
commit 71ec5dd0af
3 changed files with 400 additions and 0 deletions

162
.gitignore vendored Normal file
View File

@@ -0,0 +1,162 @@
pyrightconfig.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

7
config.toml Normal file
View File

@@ -0,0 +1,7 @@
wl_min = 160
wl_max = 1600
wl_pump = 800
rep_rate = 8e3
safety_factor = 10

231
dispersion_app.py Executable file
View File

@@ -0,0 +1,231 @@
from __future__ import annotations
import os
import sys
import warnings
from functools import cache
from typing import Any, NamedTuple
import click
import numpy as np
import scgenerator as sc
import tomli
from customfunc.app import PlotApp
from pydantic import BaseModel
DEFAULT_CONFIG_FILE = "config.toml"
class Config(BaseModel):
wl_min: float
wl_max: float
wl_pump: float
rep_rate: float
gas: str
@classmethod
def load(cls, config_file: str | None = None) -> Config:
config_file = config_file or DEFAULT_CONFIG_FILE
with open(config_file, "rb") as file:
d = tomli.load(file)
d = cls.default() | d
return cls(**d)
@classmethod
def default(cls) -> dict[str, Any]:
return dict(wl_min=160, wl_max=1600, wl_pump=800, rep_rate=8e3, gas="argon")
class LimitValues(NamedTuple):
wl_zero_disp: float
ion_lim: float
sf_lim: float
def b2(w, n_eff):
dw = w[1] - w[0]
beta = sc.fiber.beta(w, n_eff)
return sc.math.differentiate_arr(beta, 2, 4, dw)
def N_sf_max(
wl: np.ndarray, t0: float, wl_zero_disp: float, gas: sc.materials.Gas, safety: float = 10.0
) -> np.ndarray:
"""
maximum soliton number according to self focusing
eq. S15 in Travers2019
"""
delta_gas = gas.sellmeier.delta(wl, wl_zero_disp)
return t0 * np.sqrt(wl / (safety * np.abs(delta_gas)))
def N_ion_max(
wl: np.ndarray, t0: float, wl_zero_disp: float, gas: sc.materials.Gas, safety: float = 10.0
) -> np.ndarray:
"""
eq. S16 in Travers2019
"""
ind = sc.math.argclosest(wl, wl_zero_disp)
f = np.gradient(np.gradient(gas.sellmeier.chi(wl), wl), wl)
factor = (sc.math.u_nm(1, 1) / sc.units.c) ** 2 * (0.5 * wl / np.pi) ** 3
delta = factor * (f / f[ind] - 1)
denom = safety * np.pi * wl * np.abs(delta) * f[ind]
return t0 * sc.math.u_nm(1, 1) * np.sqrt(gas.n2() * gas.barrier_suppression / denom)
def solition_num(
t0: float, w0: float, beta2: float, n2: float, core_radius: float, peak_power: float
) -> float:
gamma = sc.fiber.gamma_parameter(n2, w0, sc.fiber.A_eff_marcatili(core_radius))
ld = sc.pulse.L_D(t0, beta2)
return np.sqrt(gamma * ld * peak_power)
def energy(
t0: float, w0: float, beta2: float, n2: float, core_radius: float, solition_num: float
) -> float:
gamma = sc.fiber.gamma_parameter(n2, w0, sc.fiber.A_eff_marcatili(core_radius))
peak_power = solition_num**2 * abs(beta2) / (t0**2 * gamma)
return sc.pulse.P0_to_E0(peak_power, t0, "sech")
def app(config_file: os.PathLike | None = None):
config = Config.load(config_file)
# grid stuff
w = sc.w_from_wl(config.wl_min, config.wl_max, 2048)
wl = sc.units.m.inv(w)
wl_ind = sc.math.argclosest(wl, config.wl_pump * 1e-9)
w0 = w[wl_ind]
gas = sc.materials.Gas(config.gas)
with PlotApp(
f"Dispersion design with {config.gas.title()}",
wall_thickness_um=np.geomspace(0.01, 10),
core_diameter_um=np.linspace(50, 300),
pressure_mbar=np.geomspace(1, 2000),
n_tubes=np.arange(6, 16),
gap_um=np.linspace(1, 15),
t_fwhm_fs=np.linspace(10, 200),
) as app:
# initial setup
app[0].horizontal_line("reference", 0, color="gray")
app[0].set_xlabel("wavelength (nm)")
app[0].set_ylabel("beta2 (fs^2/cm)")
app.params["wall_thickness_um"].value = 1
app.params["core_diameter_um"].value = 100
app.params["pressure_mbar"].value = 500
app.params["n_tubes"].value = 7
app.params["gap_um"].value = 5
app.params["t_fwhm_fs"].value = 100
app[0].set_lim(ylim=(-4, 2))
@cache
def compute_max_energy(
core_diameter_um: float, pressure_mbar: float, t_fwhm_fs: float
) -> LimitValues:
t_fwhm = 1e-15 * t_fwhm_fs
pressure = 1e2 * pressure_mbar
core_radius = 0.5e-6 * core_diameter_um
t0 = sc.pulse.fwhm_to_T0_fac["sech"] * t_fwhm
disp = compute_capillary(core_diameter_um, pressure_mbar)
wl_zero_disp = sc.math.all_zeros(wl, disp)
if len(wl_zero_disp) == 0:
return
wl_zero_disp = wl_zero_disp[0]
with warnings.catch_warnings():
warnings.simplefilter("ignore")
ion_limit = N_ion_max(wl, t0, wl_zero_disp, gas)[wl_ind]
sf_limit = N_sf_max(wl, t0, wl_zero_disp, gas)[wl_ind]
beta2 = disp[wl_ind]
n2 = gas.n2(pressure=pressure)
return LimitValues(
wl_zero_disp,
energy(t0, w0, beta2, n2, core_radius, ion_limit),
energy(t0, w0, beta2, n2, core_radius, sf_limit),
)
@cache
def compute_vincetti(
wall_thickness_um: float,
core_diameter_um: float,
pressure_mbar: float,
n_tubes: int,
gap_um: float,
) -> np.ndarray:
core_diameter = core_diameter_um * 1e-6
wall_thickness = wall_thickness_um * 1e-6
gap = gap_um * 1e-6
pressure = pressure_mbar * 1e2
tr = sc.fiber.tube_radius_from_gap(core_diameter / 2, gap, n_tubes)
n_gas_2 = gas.sellmeier.n_gas_2(wl, None, pressure)
n_eff_vinc = sc.fiber.n_eff_vincetti(
wl, 800e-9, n_gas_2, wall_thickness, tr, gap, n_tubes
)
return b2(w, n_eff_vinc)
@cache
def compute_capillary(core_diameter_um: float, pressure_mbar: float) -> np.ndarray:
pressure = pressure_mbar * 1e2
core_diameter = core_diameter_um * 1e-6
n_gas_2 = gas.sellmeier.n_gas_2(wl, None, pressure)
n_eff_cap = sc.fiber.n_eff_marcatili(wl, n_gas_2, core_diameter / 2)
return b2(w, n_eff_cap)
@app.update
def draw_vincetty(
wall_thickness_um: float,
core_diameter_um: float,
pressure_mbar: float,
n_tubes: int,
gap_um: float,
):
b2 = compute_vincetti(
wall_thickness_um, core_diameter_um, pressure_mbar, n_tubes, gap_um
)
app[0].set_line_data("Vincetti", wl * 1e9, sc.units.beta2_fs_cm_inv(b2))
@app.update
def draw_capillary(core_diameter_um: float, pressure_mbar: float):
b2 = compute_capillary(core_diameter_um, pressure_mbar)
app[0].set_line_data("Capillary", wl * 1e9, sc.units.beta2_fs_cm_inv(b2))
@app.update
def draw_energy_limit(core_diameter_um: float, pressure_mbar: float, t_fwhm_fs: float):
lim = compute_max_energy(core_diameter_um, pressure_mbar, t_fwhm_fs)
if lim.ion_lim > lim.sf_lim:
power = lim.sf_lim * 1e3 * config.rep_rate
app[0].set_line_name(
"Capillary", f"Capillary, max energy = {power:.0f}mW (self-focusing)"
)
else:
power = lim.ion_lim * 1e3 * config.rep_rate
app[0].set_line_name(
"Capillary", f"Capillary, max energy = {power:.0f}mW (ionization)"
)
zdw = lim.wl_zero_disp * 1e9
app[0].set_line_data("zdw", [zdw, zdw], [0, 1])
app[0].set_line_name("zdw", f"ZDW = {zdw:.0f}nm")
@click.command()
@click.option(
"-c",
"--config",
default=DEFAULT_CONFIG_FILE,
help="configuration file in TOML format",
type=click.Path(exists=True, file_okay=True, dir_okay=False, resolve_path=True),
)
def main(config: os.PathLike):
app(config)
if __name__ == "__main__" and not hasattr(sys, "ps1"):
main()