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 dispersion_config.toml
pyrightconfig.json pyrightconfig.json
.DS_Store .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: 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 # Usage

View File

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

View File

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

View File

@@ -7,8 +7,9 @@ from functools import cache
import numpy as np import numpy as np
import scgenerator as sc import scgenerator as sc
from dispersionapp.core import Config, LimitValues, N_ion_max, N_sf_max, b2, energy from dispersionapp.core import (Config, LimitValues, N_ion_max, N_sf_max, b2,
from dispersionapp.plotapp import PlotApp energy)
from dispersionapp.plotapp import PlotApp, QtWidgets
def app(config_file: os.PathLike | None = None): 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) wl_ind = sc.math.argclosest(wl, config.wl_pump * 1e-9)
w0 = w[wl_ind] w0 = w[wl_ind]
gas = sc.materials.Gas(config.gas) gas = sc.materials.Gas(config.gas)
with PlotApp( with PlotApp(
f"Dispersion design with {config.gas.title()}", f"Dispersion design with {config.gas.title()}",
core_diameter_um=np.arange(50, 301, dtype=float), core_diameter_um=np.arange(50, 301, dtype=float),
pressure_mbar=np.geomspace(1, 2000), 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), n_tubes=np.arange(6, 16),
gap_um=np.arange(1, 15.5, 0.5), gap_um=np.arange(1, 15.5, 0.5),
t_fwhm_fs=np.arange(10, 201, dtype=float), t_fwhm_fs=np.arange(10, 201, dtype=float),
) as app: ) as app:
# initial setup # initial setup
ax = app["Dispersion plot"] beta_ax = app["Dispersion plot"]
ax.horizontal_line("reference", 0, color="gray") beta_ax.horizontal_line("reference", 0, color="gray")
ax.set_xlabel("wavelength (nm)") beta_ax.set_xlabel("wavelength (nm)")
ax.set_ylabel("beta2 (fs^2/cm)") 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: if config.current_state is not None:
app.params["core_diameter_um"].value = config.current_state.core_diameter_um app.params["core_diameter_um"].value = config.current_state.core_diameter_um
app.params["pressure_mbar"].value = config.current_state.pressure_mbar app.params["pressure_mbar"].value = config.current_state.pressure_mbar
app.params["wall_thickness_um"].value = config.current_state.wall_thickness_um 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["n_tubes"].value = config.current_state.n_tubes
app.params["gap_um"].value = config.current_state.gap_um app.params["gap_um"].value = config.current_state.gap_um
app.params["t_fwhm_fs"].value = config.current_state.t_fwhm_fs 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["core_diameter_um"].value = 100
app.params["pressure_mbar"].value = 500 app.params["pressure_mbar"].value = 500
app.params["wall_thickness_um"].value = 1 app.params["wall_thickness_um"].value = 1
app.params["num_resonances"].value = 6
app.params["n_tubes"].value = 7 app.params["n_tubes"].value = 7
app.params["gap_um"].value = 5 app.params["gap_um"].value = 5
app.params["t_fwhm_fs"].value = 100 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) app.update(config.update_current)
@@ -86,13 +103,14 @@ def app(config_file: os.PathLike | None = None):
) )
@cache @cache
def compute_vincetti( def compute_n_eff_vincetti(
wall_thickness_um: float, wall_thickness_um: float,
core_diameter_um: float, core_diameter_um: float,
pressure_mbar: float, pressure_mbar: float,
num_resonances: int,
n_tubes: int, n_tubes: int,
gap_um: float, gap_um: float,
) -> np.ndarray: ):
core_diameter = core_diameter_um * 1e-6 core_diameter = core_diameter_um * 1e-6
wall_thickness = wall_thickness_um * 1e-6 wall_thickness = wall_thickness_um * 1e-6
gap = gap_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) 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_gas_2 = gas.sellmeier.n_gas_2(wl, None, pressure)
n_eff_vinc = sc.fiber.n_eff_vincetti( return sc.fiber.n_eff_vincetti(
wl, wl, 800e-9, n_gas_2, wall_thickness, tr, gap, n_tubes, n_terms=num_resonances
800e-9, )
n_gas_2,
wall_thickness, @cache
tr, def compute_vincetti_beta(
gap, wall_thickness_um: float,
n_tubes, core_diameter_um: float,
n_terms=config.num_resonances, 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) return b2(w, n_eff_vinc)
@@ -121,22 +145,37 @@ def app(config_file: os.PathLike | None = None):
return b2(w, n_eff_cap) return b2(w, n_eff_cap)
@app.update @app.update
def draw_vincetty( def draw_vincetty_n_eff(
wall_thickness_um: float, wall_thickness_um: float,
core_diameter_um: float, core_diameter_um: float,
pressure_mbar: float, pressure_mbar: float,
num_resonances: int,
n_tubes: int, n_tubes: int,
gap_um: float, gap_um: float,
): ):
b2 = compute_vincetti( n_eff = compute_n_eff_vincetti(
wall_thickness_um, core_diameter_um, pressure_mbar, n_tubes, gap_um 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 @app.update
def draw_capillary(core_diameter_um: float, pressure_mbar: float): def draw_capillary(core_diameter_um: float, pressure_mbar: float):
b2 = compute_capillary(core_diameter_um, pressure_mbar) 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 @app.update
def draw_energy_limit(core_diameter_um: float, pressure_mbar: float, t_fwhm_fs: float): 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 return
zdw = lim.wl_zero_disp * 1e9 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") info_lines.append(f"ZDW = {zdw:.0f}nm")
if lim.ion_lim > lim.sf_lim: 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}", f"N = {lim.soliton_sf_limit:.1f}",
"limited by ionization", "limited by ionization",
] ]
app.info_label.setText("\n".join(info_lines)) app.info_label.setText(" ".join(info_lines))
config.save() config.save()

View File

@@ -5,7 +5,8 @@ import itertools
from collections.abc import MutableMapping, Sequence from collections.abc import MutableMapping, Sequence
from functools import cache from functools import cache
from types import MethodType 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 numpy as np
import pyqtgraph as pg import pyqtgraph as pg
@@ -29,30 +30,17 @@ MPL_COLORS = [
key_type = Union[str, int] key_type = Union[str, int]
class Field(QtWidgets.QWidget): class SliderField(QtWidgets.QWidget):
dtype: Type dtype: Type
value: Any value: Any
value_changed: QtCore.Signal 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 dtype: Type
possible_values: np.ndarray possible_values: np.ndarray
_slider_max = 100 _slider_max = 100
_tuple_signal = QtCore.Signal(tuple)
_int_signal = QtCore.Signal(int) _int_signal = QtCore.Signal(int)
_float_signal = QtCore.Signal(float) _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__() super().__init__()
self.__value = None self.__value = None
self.slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal) self.slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
@@ -64,16 +52,18 @@ class SliderField(Field):
self.field.editingFinished.connect(self.field_changed) self.field.editingFinished.connect(self.field_changed)
self.step_backward_button = QtWidgets.QPushButton("<") self.step_backward_button = QtWidgets.QPushButton("<")
self.step_backward_button.setFixedWidth(30)
self.step_backward_button.clicked.connect(self.step_backward) self.step_backward_button.clicked.connect(self.step_backward)
self.step_forward_button = QtWidgets.QPushButton(">") self.step_forward_button = QtWidgets.QPushButton(">")
self.step_forward_button.setFixedWidth(30)
self.step_forward_button.clicked.connect(self.step_forward) self.step_forward_button.clicked.connect(self.step_forward)
self._layout = QtWidgets.QHBoxLayout() self._layout = QtWidgets.QHBoxLayout()
self.setLayout(self._layout) self.setLayout(self._layout)
self._layout.setContentsMargins(10, 0, 10, 0) 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.name_label = QtWidgets.QLabel(pretty_name + " :")
self._layout.addWidget(self.name_label) self._layout.addWidget(self.name_label)
@@ -86,8 +76,6 @@ class SliderField(Field):
self.value_changed = { self.value_changed = {
int: self._int_signal, int: self._int_signal,
float: self._float_signal, float: self._float_signal,
str: self._str_signal,
tuple: self._tuple_signal,
}[self.dtype] }[self.dtype]
self.value_changed.emit(self.__value) self.value_changed.emit(self.__value)
@@ -130,11 +118,8 @@ class SliderField(Field):
self.value_changed.emit(new_value) self.value_changed.emit(new_value)
def field_changed(self): def field_changed(self):
try:
new_val = self.dtype(self.field.text()) new_val = self.dtype(self.field.text())
except (ValueError, TypeError): new_val = min(self.value_to_slider_map, key=lambda el: abs(el - new_val))
self.update_label()
return
self.value = new_val self.value = new_val
def slider_changed(self): def slider_changed(self):
@@ -168,7 +153,7 @@ class SliderField(Field):
if self.dtype is int: if self.dtype is int:
return format(self.value) return format(self.value)
elif self.dtype is float: elif self.dtype is float:
return format(self.value, ".3g") return format(self.value, ".5g")
else: else:
return format(self.value) return format(self.value)
@@ -176,66 +161,6 @@ class SliderField(Field):
return list(self.possible_values) 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: class Plot:
name: str name: str
dock: Dock dock: Dock
@@ -511,11 +436,12 @@ class PlotApp:
self.params_widget = QtWidgets.QWidget() self.params_widget = QtWidgets.QWidget()
self.header_widget = QtWidgets.QWidget() self.header_widget = QtWidgets.QWidget()
self.info_label = QtWidgets.QLabel() self.info_label = QtWidgets.QLabel()
# self.info_label.setMaximumWidth(120)
self.window.setCentralWidget(self.central_widget) self.window.setCentralWidget(self.central_widget)
self.central_layout = QtWidgets.QVBoxLayout() self.central_layout = QtWidgets.QVBoxLayout()
self.params_layout = QtWidgets.QVBoxLayout() self.params_layout = QtWidgets.QGridLayout()
self.header_layout = QtWidgets.QHBoxLayout() self.header_layout = QtWidgets.QVBoxLayout()
_pl = QtWidgets.QSizePolicy.Policy.Preferred _pl = QtWidgets.QSizePolicy.Policy.Preferred
info_sp = QtWidgets.QSizePolicy(_pl, _pl) info_sp = QtWidgets.QSizePolicy(_pl, _pl)
@@ -540,10 +466,10 @@ class PlotApp:
self.__ran = False self.__ran = False
self.params = {} self.params = {}
for p_name, values in params.items(): for i, (p_name, values) in enumerate(params.items()):
field = AnimatedSliderField(p_name, values) field = SliderField(p_name, values)
self.params[p_name] = field self.params[p_name] = field
self.params_layout.addWidget(field) self.params_layout.addWidget(field, *divmod(i, 2))
def set_antialiasing(self, val: bool): def set_antialiasing(self, val: bool):
pg.setConfigOptions(antialias=val) pg.setConfigOptions(antialias=val)