added refractive index plot

This commit is contained in:
Benoît Sierro
2023-07-10 16:03:41 +02:00
parent 7b41673594
commit 3fbd8da66f
6 changed files with 98 additions and 124 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.conda-env
dispersion_config.toml
pyrightconfig.json
.DS_Store

View File

@@ -9,7 +9,7 @@ conda activate app-env
3. The prompt should now read '(app-env)' on the left. The app is not published on Github or anywhere else. The link below points to my own personnal home server (I didn't find any way of getting a direct download link You are now ready to install everything with this command:
pip install "git+file://<path to the git repository>"
pip install http://130.92.113.172/dispersionapp_v0.1.0.zip
# Usage

View File

@@ -1,13 +1,13 @@
[project]
name = "dispersionapp"
version = "0.1.0"
version = "0.1.3"
description = "Model hollow capillary and revolver fiber interactively"
authors = [{ name = "Benoît Sierro", email = "benoit.sierro@unibe.ch" }]
dependencies = [
"scgenerator @ git+https://github.com/bsierro/scgenerator.git",
"click",
"pydantic",
"pydantic < 2",
"tomli",
"tomli_w",
"PySide6 >= 6.4.0",

View File

@@ -17,6 +17,7 @@ class CurrentState(BaseModel):
core_diameter_um: float
pressure_mbar: float
wall_thickness_um: float
num_resonances: conint(ge=6, le=40) = 6
n_tubes: int
gap_um: float
t_fwhm_fs: float
@@ -27,7 +28,6 @@ class Config(BaseModel):
wl_max: confloat(ge=500, le=6000) = 1600
wl_pump: confloat(ge=200, le=6000) = 800
rep_rate: confloat(gt=0) = 8e3
num_resonances: conint(ge=6, le=20) = 6
gas: str = "argon"
safety_factor: float = 10.0
current_state: CurrentState | None = None
@@ -45,7 +45,7 @@ class Config(BaseModel):
try:
out = cls(**d)
except ValidationError as e:
s = f"invalid input in config file {config_file}:\n{e}"
s = f"invalid input in config file {config_file}:\n{e}\ncreating new config"
print(s)
sys.exit(1)
out._file_name = Path(config_file)
@@ -58,12 +58,20 @@ class Config(BaseModel):
tmp.replace(self._file_name)
def update_current(
self, core_diameter_um, pressure_mbar, wall_thickness_um, n_tubes, gap_um, t_fwhm_fs
self,
core_diameter_um: float,
pressure_mbar: float,
wall_thickness_um: float,
num_resonances: int,
n_tubes: int,
gap_um: float,
t_fwhm_fs: float,
):
self.current_state = CurrentState(
core_diameter_um=core_diameter_um,
pressure_mbar=pressure_mbar,
wall_thickness_um=wall_thickness_um,
num_resonances=num_resonances,
n_tubes=n_tubes,
gap_um=gap_um,
t_fwhm_fs=t_fwhm_fs,

View File

@@ -7,8 +7,9 @@ from functools import cache
import numpy as np
import scgenerator as sc
from dispersionapp.core import Config, LimitValues, N_ion_max, N_sf_max, b2, energy
from dispersionapp.plotapp import PlotApp
from dispersionapp.core import (Config, LimitValues, N_ion_max, N_sf_max, b2,
energy)
from dispersionapp.plotapp import PlotApp, QtWidgets
def app(config_file: os.PathLike | None = None):
@@ -19,27 +20,34 @@ def app(config_file: os.PathLike | None = None):
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()}",
core_diameter_um=np.arange(50, 301, dtype=float),
pressure_mbar=np.geomspace(1, 2000),
wall_thickness_um=np.geomspace(0.01, 10),
wall_thickness_um=np.geomspace(0.01, 10, 201),
num_resonances=np.arange(6, 41),
n_tubes=np.arange(6, 16),
gap_um=np.arange(1, 15.5, 0.5),
t_fwhm_fs=np.arange(10, 201, dtype=float),
) as app:
# initial setup
ax = app["Dispersion plot"]
ax.horizontal_line("reference", 0, color="gray")
ax.set_xlabel("wavelength (nm)")
ax.set_ylabel("beta2 (fs^2/cm)")
beta_ax = app["Dispersion plot"]
beta_ax.horizontal_line("reference", 0, color="gray")
beta_ax.set_xlabel("wavelength (nm)")
beta_ax.set_ylabel("beta2 (fs^2/cm)")
n_ax = app["Refractive index (real)"]
n_ax.link_x(beta_ax)
n_ax.set_xlabel("wavelength (nm)")
n_ax.set_ylabel("n")
if config.current_state is not None:
app.params["core_diameter_um"].value = config.current_state.core_diameter_um
app.params["pressure_mbar"].value = config.current_state.pressure_mbar
app.params["wall_thickness_um"].value = config.current_state.wall_thickness_um
app.params["num_resonances"].value = config.current_state.num_resonances
app.params["n_tubes"].value = config.current_state.n_tubes
app.params["gap_um"].value = config.current_state.gap_um
app.params["t_fwhm_fs"].value = config.current_state.t_fwhm_fs
@@ -47,10 +55,19 @@ def app(config_file: os.PathLike | None = None):
app.params["core_diameter_um"].value = 100
app.params["pressure_mbar"].value = 500
app.params["wall_thickness_um"].value = 1
app.params["num_resonances"].value = 6
app.params["n_tubes"].value = 7
app.params["gap_um"].value = 5
app.params["t_fwhm_fs"].value = 100
ax.set_lim(ylim=(-4, 2))
def reset_view():
# reseting beta_ax resets n_ax as well since they're linked
beta_ax.set_lim(ylim=(-4, 2))
reset_button = QtWidgets.QPushButton("Reset axes")
reset_button.clicked.connect(reset_view)
app.params_layout.addWidget(reset_button)
reset_view()
app.update(config.update_current)
@@ -86,13 +103,14 @@ def app(config_file: os.PathLike | None = None):
)
@cache
def compute_vincetti(
def compute_n_eff_vincetti(
wall_thickness_um: float,
core_diameter_um: float,
pressure_mbar: float,
num_resonances: int,
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
@@ -100,15 +118,21 @@ def app(config_file: os.PathLike | None = None):
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,
n_terms=config.num_resonances,
return sc.fiber.n_eff_vincetti(
wl, 800e-9, n_gas_2, wall_thickness, tr, gap, n_tubes, n_terms=num_resonances
)
@cache
def compute_vincetti_beta(
wall_thickness_um: float,
core_diameter_um: float,
pressure_mbar: float,
num_resonances: int,
n_tubes: int,
gap_um: float,
) -> np.ndarray:
n_eff_vinc = compute_n_eff_vincetti(
wall_thickness_um, core_diameter_um, pressure_mbar, num_resonances, n_tubes, gap_um
)
return b2(w, n_eff_vinc)
@@ -121,22 +145,37 @@ def app(config_file: os.PathLike | None = None):
return b2(w, n_eff_cap)
@app.update
def draw_vincetty(
def draw_vincetty_n_eff(
wall_thickness_um: float,
core_diameter_um: float,
pressure_mbar: float,
num_resonances: int,
n_tubes: int,
gap_um: float,
):
b2 = compute_vincetti(
wall_thickness_um, core_diameter_um, pressure_mbar, n_tubes, gap_um
n_eff = compute_n_eff_vincetti(
wall_thickness_um, core_diameter_um, pressure_mbar, num_resonances, n_tubes, gap_um
)
ax.set_line_data("Vincetti", wl * 1e9, sc.units.beta2_fs_cm_inv(b2))
n_ax.set_line_data("Vincetti", wl * 1e9, n_eff)
@app.update
def draw_vincetty_beta(
wall_thickness_um: float,
core_diameter_um: float,
pressure_mbar: float,
num_resonances: int,
n_tubes: int,
gap_um: float,
):
b2 = compute_vincetti_beta(
wall_thickness_um, core_diameter_um, pressure_mbar, num_resonances, n_tubes, gap_um
)
beta_ax.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)
ax.set_line_data("Capillary", wl * 1e9, sc.units.beta2_fs_cm_inv(b2))
beta_ax.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):
@@ -153,7 +192,7 @@ def app(config_file: os.PathLike | None = None):
return
zdw = lim.wl_zero_disp * 1e9
ax.set_line_data("zdw", [zdw, zdw], [-3, 3])
beta_ax.set_line_data("zdw", [zdw, zdw], [-3, 3])
info_lines.append(f"ZDW = {zdw:.0f}nm")
if lim.ion_lim > lim.sf_lim:
@@ -170,6 +209,6 @@ def app(config_file: os.PathLike | None = None):
f"N = {lim.soliton_sf_limit:.1f}",
"limited by ionization",
]
app.info_label.setText("\n".join(info_lines))
app.info_label.setText(" ".join(info_lines))
config.save()

View File

@@ -5,7 +5,8 @@ import itertools
from collections.abc import MutableMapping, Sequence
from functools import cache
from types import MethodType
from typing import Any, Callable, Iterable, Iterator, Optional, Type, Union, overload
from typing import (Any, Callable, Iterable, Iterator, Optional, Type, Union,
overload)
import numpy as np
import pyqtgraph as pg
@@ -29,30 +30,17 @@ MPL_COLORS = [
key_type = Union[str, int]
class Field(QtWidgets.QWidget):
class SliderField(QtWidgets.QWidget):
dtype: Type
value: Any
value_changed: QtCore.Signal
timer: QtCore.QTimer
@property
def value(self) -> Any:
raise NotImplementedError()
def values(self) -> list[Any]:
raise NotImplementedError()
class SliderField(Field):
dtype: Type
possible_values: np.ndarray
_slider_max = 100
_tuple_signal = QtCore.Signal(tuple)
_int_signal = QtCore.Signal(int)
_float_signal = QtCore.Signal(float)
_str_signal = QtCore.Signal(str)
def __init__(self, name: str, values: Iterable) -> None:
def __init__(self, name: str, values: Iterable[float] | Iterable[int]) -> None:
super().__init__()
self.__value = None
self.slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
@@ -64,16 +52,18 @@ class SliderField(Field):
self.field.editingFinished.connect(self.field_changed)
self.step_backward_button = QtWidgets.QPushButton("<")
self.step_backward_button.setFixedWidth(30)
self.step_backward_button.clicked.connect(self.step_backward)
self.step_forward_button = QtWidgets.QPushButton(">")
self.step_forward_button.setFixedWidth(30)
self.step_forward_button.clicked.connect(self.step_forward)
self._layout = QtWidgets.QHBoxLayout()
self.setLayout(self._layout)
self._layout.setContentsMargins(10, 0, 10, 0)
pretty_name = " ".join(s.title() for s in name.split("_"))
pretty_name = " ".join(s for s in name.split("_"))
self.name_label = QtWidgets.QLabel(pretty_name + " :")
self._layout.addWidget(self.name_label)
@@ -86,8 +76,6 @@ class SliderField(Field):
self.value_changed = {
int: self._int_signal,
float: self._float_signal,
str: self._str_signal,
tuple: self._tuple_signal,
}[self.dtype]
self.value_changed.emit(self.__value)
@@ -130,11 +118,8 @@ class SliderField(Field):
self.value_changed.emit(new_value)
def field_changed(self):
try:
new_val = self.dtype(self.field.text())
except (ValueError, TypeError):
self.update_label()
return
new_val = min(self.value_to_slider_map, key=lambda el: abs(el - new_val))
self.value = new_val
def slider_changed(self):
@@ -168,7 +153,7 @@ class SliderField(Field):
if self.dtype is int:
return format(self.value)
elif self.dtype is float:
return format(self.value, ".3g")
return format(self.value, ".5g")
else:
return format(self.value)
@@ -176,66 +161,6 @@ class SliderField(Field):
return list(self.possible_values)
class AnimatedSliderField(SliderField):
def __init__(self, name: str, values: Iterable) -> None:
super().__init__(name, values)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.step_forward)
self.play_button = QtWidgets.QPushButton("")
self.play_button.clicked.connect(self.toggle)
self.play_button.setMaximumWidth(30)
self.playing = False
self.interval_field = QtWidgets.QLineEdit()
self.interval_field.setMaximumWidth(60)
# self.interval_field.setValidator(QtGui.QIntValidator(1, 5000))
self.interval_field.editingFinished.connect(self.set_interval)
self.interval_field.inputRejected.connect(self.set_interval)
self.interval = 16
self.set_interval()
self._layout.addWidget(self.play_button)
self._layout.addWidget(self.interval_field)
def toggle(self):
if self.playing:
self.stop()
self.playing = False
else:
self.play()
self.playing = True
def play(self):
if self.slider.value() == self._slider_max:
self.slider.setValue(0)
self.timer.start(self.interval)
self.play_button.setStyleSheet("QPushButton {background-color: #DEF2DD;}")
def stop(self):
self.timer.stop()
self.play_button.setStyleSheet("QPushButton {background-color: none;}")
def set_interval(self):
try:
self.interval = max(1, int(self.interval_field.text()))
except ValueError:
self.interval_field.setText(str(self.interval))
if self.interval < 16:
self.increment = int(np.ceil(16 / self.interval))
self.interval *= self.increment
else:
self.increment = 1
def step_forward(self):
current = self.slider.value()
if current + self.increment <= self._slider_max:
self.slider.setValue(current + self.increment)
else:
self.slider.setValue(self._slider_max)
self.stop()
class Plot:
name: str
dock: Dock
@@ -511,11 +436,12 @@ class PlotApp:
self.params_widget = QtWidgets.QWidget()
self.header_widget = QtWidgets.QWidget()
self.info_label = QtWidgets.QLabel()
# self.info_label.setMaximumWidth(120)
self.window.setCentralWidget(self.central_widget)
self.central_layout = QtWidgets.QVBoxLayout()
self.params_layout = QtWidgets.QVBoxLayout()
self.header_layout = QtWidgets.QHBoxLayout()
self.params_layout = QtWidgets.QGridLayout()
self.header_layout = QtWidgets.QVBoxLayout()
_pl = QtWidgets.QSizePolicy.Policy.Preferred
info_sp = QtWidgets.QSizePolicy(_pl, _pl)
@@ -540,10 +466,10 @@ class PlotApp:
self.__ran = False
self.params = {}
for p_name, values in params.items():
field = AnimatedSliderField(p_name, values)
for i, (p_name, values) in enumerate(params.items()):
field = SliderField(p_name, values)
self.params[p_name] = field
self.params_layout.addWidget(field)
self.params_layout.addWidget(field, *divmod(i, 2))
def set_antialiasing(self, val: bool):
pg.setConfigOptions(antialias=val)