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

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()