├── .gitignore ├── LICENSE.md ├── README.md ├── numExp01_stationarySolution ├── main_findStationarySolution.py ├── pp_figure │ ├── FIGS │ │ └── stationarySolution.png │ ├── generateFigure.sh │ └── main_figure_stationarySolution.py └── run.sh ├── numExp02_propagationScenarios ├── main_findStationarySolution.py ├── main_propagateInitialCondition.py ├── pp_figure_propagationScenarios │ ├── FIGS │ │ ├── GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.000000_d40.000000_x00.000000.dat.png │ │ ├── GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.000000_d40.001000_x00.000000.dat.png │ │ └── GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.040000_d40.000000_x00.000000.dat.png │ ├── figure_base_propagationDynamics.py │ ├── generateFigures.sh │ └── main_figure_propagationDynamics_stationarySolution.py └── run.sh ├── scripts └── pyGLLE.py └── src ├── data_handler.py ├── solver.py └── stationary_solution.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.npz 2 | *.png 3 | *.pyc -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 O. Melchert 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyGLLE 2 | 3 | [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT) 4 | 5 | pyGLLE is a Python toolkit for simulating the propagation dynamics of 6 | dissipative solitons in a variant of the 7 | [Lugiato-Lefever equation](https://en.wikipedia.org/wiki/Lugiato–Lefever_equation) (LLE). 8 | Including dispersion terms of third and fourth order, this variant is here 9 | referred to as the generalised LLE (GLLE). 10 | 11 | The provided software implements a solver for a variant of the LLE including 12 | dispersion terms of third and fourth order. It also includes the functionality 13 | to solve for stationary solutions of the standard LLE, containing one or 14 | several localized dissipative structures. 15 | 16 | ### Prerequisites 17 | 18 | The tools provided by the `pyGLLE` package require the functionality of 19 | 20 | * numpy (>=1.8.0rc1) 21 | * scipy (>=0.13.0b1) 22 | 23 | Further, the figure generation scripts included with the examples require the 24 | funcality of 25 | 26 | * matplotlib (>=1.2.1) 27 | 28 | ## Included materials 29 | 30 | The repository follows a modular structure: 31 | 32 | ``` 33 | pyGLLE/ 34 | ├── LICENSE.md 35 | ├── README.md 36 | ├── numExp01_stationarySolution 37 | │   ├── data_stationary_solution 38 | │   ├── main_findStationarySolution.py 39 | │   ├── pp_figure 40 | │   │   ├── FIGS 41 | │   │   ├── generateFigure.sh 42 | │   │   └── main_figure_stationarySolution.py 43 | │   └── run.sh 44 | ├── numExp02_propagationScenarios 45 | │   ├── data 46 | │   ├── data_stationary_solution 47 | │   ├── main_findStationarySolution.py 48 | │   ├── main_propagateInitialCondition.py 49 | │   ├── pp_figure_propagationScenarios 50 | │   │   ├── FIGS 51 | │   │   ├── figure_base_propagationDynamics.py 52 | │   │   ├── generateFigures.sh 53 | │   │   └── main_figure_propagationDynamics_stationarySolution.py 54 | │   └── run.sh 55 | ├── scripts 56 | │   └── pyGLLE.py 57 | └── src 58 | ├── data_handler.py 59 | ├── solver.py 60 | └── stationary_solution.py 61 | ``` 62 | 63 | Subfolder `/src` contains Python modules implementing the basic functionality of the software: 64 | * `data_handler.py`: provides a class, handling data accumulation and data 65 | * ouput. Output data is stored using the numpy native npz-format. 66 | * `stationary_solution.py`: 67 | provides functions allowing to obtain stationary localized solution of the standard LLE. 68 | * `solver.py`: implements a solver for the numerical integration of the generalized LLE using a Runge-Kutta method. 69 | 70 | The folder `/scripts` contains the main Python module implementing the 71 | interface between the user supplied code and the algorithms and data structures 72 | contained in the modules in folder `\src`: 73 | * `pyGLLE.py`: defines the main functions `findStationarySolution` and 74 | `propagateInitialCondition`. 75 | 76 | Further, the folders `\numExp01_stationarySolution` and 77 | `\numExp02_propagationScenarios` contain scripts that implement example 78 | workflows ranging from the specification of a propagation scenario to the 79 | visualization of the generated raw data. 80 | 81 | The repository further contains 82 | * `LICENSE`, a license file. 83 | * `Readme.md`, this file. 84 | 85 | For a more detailed description of functions, defined in the above modules, 86 | their parameters and return values we refer to the example cases and 87 | documentation provided within the code. 88 | 89 | ## Availability of the software 90 | 91 | The pyGLLE software package is derived from our research software and is meant to work as a (system-)local software tool. There is no need to install it once you got a local [clone](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository) of the repository, e.g. via 92 | 93 | ``$ git clone https://github.com/omelchert/pyGLLE`` 94 | 95 | We further prepared a [pyGLLE compute capsule](https://codeocean.com/capsule/e0ed77d4-9589-45b4-abc8-3b21f3ce92c8/) on [Code Ocean](https://codeocean.com), allowing to directly run and modify an exemplary simulation without the need to create a local copy of the repository. 96 | 97 | ## Links 98 | 99 | The pyGLLE software package is described in 100 | 101 | > O. Melchert, A. Demircan, "pyGLLE: A Python toolkit for solving the generalized Lugiato–Lefever equation," SoftwareX 15 (2021) 100741, DOI: [10.1016/j.softx.2021.100741](https://doi.org/10.1016/j.softx.2021.100741). 102 | 103 | The presented software has been extensively used in our research work, 104 | including the study of resonant emission of multi-frequency radiation by 105 | oscillating dissipative solitons in the LLE including third order dispersion 106 | 107 | > O. Melchert, A. Demircan, A. Yulin, "Multi-frequency radiation of dissipative solitons in optical fiber cavities," Scientific Reports 10 (2020) 8849, DOI: [10.1038/s41598-020-65426-x](https://doi.org/10.1038/s41598-020-65426-x). 108 | 109 | and the dynamics of localized dissipative structures in the generalized LLE with negative quartic group-velocity dispersion 110 | 111 | > O. Melchert, A. Yulin, A. Demircan, "Dynamics of localized dissipative structures in a generalized Lugiato-Lefever model with negative quartic group-velocity dispersion," Optics Letters 45 (2020) 2764, DOI: [10.1364/OL.392180](https://www.doi.org/10.1364/OL.392180). 112 | 113 | ## License 114 | 115 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. 116 | 117 | ## Acknowledgments 118 | 119 | This work received funding from the Deutsche Forschungsgemeinschaft (DFG) under 120 | Germany’s Excellence Strategy within the Cluster of Excellence PhoenixD 121 | (Photonics, Optics, and Engineering – Innovation Across Disciplines) (EXC 2122, 122 | projectID 390833453). 123 | -------------------------------------------------------------------------------- /numExp01_stationarySolution/main_findStationarySolution.py: -------------------------------------------------------------------------------- 1 | import sys; sys.path.append('../scripts/') 2 | import pyGLLE 3 | from numpy import pi, sqrt, cosh 4 | 5 | class SIM_SETUP: 6 | 7 | xMax = 20. 8 | Nx = 2**10 9 | P = 8. 10 | theta = 15. 11 | d3 = 0.0 12 | x0 = 0.0 13 | fName = 'stationary_solution.dat' 14 | 15 | def initial_field(self, x): 16 | Ax_fnc = lambda x: sqrt(2*self.theta)/cosh(sqrt(self.theta)*x) 17 | cosZeta = sqrt(8*self.theta)/self.P/pi 18 | sinZeta = sqrt(1-cosZeta*cosZeta) 19 | return Ax_fnc(x-self.x0)*(cosZeta+1j*sinZeta) 20 | 21 | pyGLLE.findStationarySolution(SIM_SETUP()) 22 | -------------------------------------------------------------------------------- /numExp01_stationarySolution/pp_figure/FIGS/stationarySolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omelchert/pyGLLE/cb88029853a786384c96fed628e2fdb7ec93e3e5/numExp01_stationarySolution/pp_figure/FIGS/stationarySolution.png -------------------------------------------------------------------------------- /numExp01_stationarySolution/pp_figure/generateFigure.sh: -------------------------------------------------------------------------------- 1 | python3 main_figure_stationarySolution.py 2 | -------------------------------------------------------------------------------- /numExp01_stationarySolution/pp_figure/main_figure_stationarySolution.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import matplotlib as mpl 4 | import numpy as np 5 | import scipy.fftpack as sfft 6 | import matplotlib.pyplot as plt 7 | import matplotlib.colors as col 8 | from matplotlib.gridspec import GridSpec, GridSpecFromSubplotSpec 9 | 10 | 11 | def fetchIdx(t,t0): 12 | return np.argmin(np.abs(t-t0)) 13 | 14 | def generateFigure(): 15 | 16 | mm2inch = lambda x: x/10./2.54 17 | mpl.rcParams['figure.figsize'] = mm2inch(85),mm2inch(1.8/3.*85) 18 | mpl.rcParams['xtick.direction']= 'out' 19 | mpl.rcParams['ytick.direction']= 'out' 20 | mpl.rcParams['xtick.labelsize'] = 6 21 | mpl.rcParams['ytick.labelsize'] = 6 22 | mpl.rcParams['lines.linewidth'] = 1.0 23 | mpl.rcParams['axes.linewidth'] = 0.5 24 | mpl.rcParams['axes.labelsize'] = 6 25 | mpl.rcParams['font.size'] = 6 26 | mpl.rcParams['legend.fontsize'] = 6 27 | 28 | mpl.rcParams['text.usetex'] = True 29 | mpl.rcParams['text.latex.preamble'] = [ 30 | r'\usepackage{siunitx}', # i need upright \micro symbols, but you need... 31 | r'\sisetup{detect-all}', # ...this to force siunitx to actually use your fonts 32 | r'\usepackage{helvet}', # set the normal font here 33 | r'\usepackage{sansmath}', # load up the sansmath so that math -> helvet 34 | r'\sansmath' # <- tricky! -- gotta actually tell tex to use! 35 | ] 36 | 37 | cmap=mpl.cm.get_cmap('jet') 38 | 39 | fig = plt.figure() 40 | plt.subplots_adjust(left=0.1, bottom=0.14, right=0.98, top= 0.98, wspace=0.45, hspace=0.3) 41 | 42 | gs00 = GridSpec(1,1) 43 | gsA = GridSpecFromSubplotSpec(2,1,subplot_spec=gs00[0,0],wspace=0.1, hspace=0.1) 44 | axA1 = plt.subplot(gsA[0,0]) 45 | axA2 = plt.subplot(gsA[1,0]) 46 | 47 | def _setKey(ax, lns, ncol=2): 48 | """key helper""" 49 | labs = [l.get_label() for l in lns] 50 | lg = ax.legend(lns, labs, title=r"", loc=1, fontsize=6, ncol=ncol) 51 | lg = ax.legend_ 52 | lg.get_frame().set_linewidth(0.5) 53 | 54 | inFile = '../data_stationary_solution/stationary_solution.dat.npz' 55 | (x, Ax_ini, Ax_opt) = fetchNpz(inFile) 56 | 57 | xLim = (-3.,3.) 58 | xTicks = (-3,-2,-1,0,1,2,3) 59 | 60 | l1 = axA1.plot(x,np.abs(Ax_ini), color='red', dashes=[2,2], label=r'$|A_{\rm{trial}}(x)|$',zorder=1) 61 | l2 = axA1.plot(x,np.abs(Ax_opt), color='k', label=r'$|A_{\rm{opt}}(x)|$',zorder=0) 62 | 63 | axA1.xaxis.set_ticks_position('bottom') 64 | axA1.yaxis.set_ticks_position('left') 65 | axA1.tick_params(axis='y',length=2.,pad=1) 66 | axA1.tick_params(axis='x',length=2.,pad=1,labelbottom=False) 67 | axA1.set_xlim(xLim) 68 | axA1.set_xticks(xTicks) 69 | axA1.set_ylim(0,6.5) 70 | axA1.set_yticks((0,2,4,6)) 71 | 72 | _setKey(axA1,l1+l2,1) 73 | 74 | l1 = axA2.plot(x,np.real(Ax_ini), color='red', label=r'$\sf{Re}[A_{\rm{trial}}(x)]$',zorder=1) 75 | l2 = axA2.plot(x,np.real(Ax_opt), color='k', label=r'$\sf{Re}[A_{\rm{opt}}(x)]$',zorder=0) 76 | 77 | l3 = axA2.plot(x,np.imag(Ax_ini), color='red', dashes=[2,2], label=r'$\sf{Im}[A_{\rm{trial}}(x)]$',zorder=1) 78 | l4 = axA2.plot(x,np.imag(Ax_opt), color='k', dashes=[2,2], label=r'$\sf{Im}[A_{\rm{opt}}(x)]$',zorder=0) 79 | 80 | axA2.xaxis.set_ticks_position('bottom') 81 | axA2.yaxis.set_ticks_position('left') 82 | axA2.tick_params(axis='y',length=2.,pad=1) 83 | axA2.tick_params(axis='x',length=2.,pad=1) 84 | axA2.set_xlim(xLim) 85 | axA2.set_xticks(xTicks) 86 | axA2.set_ylim(-2,6.5) 87 | axA2.set_yticks((-2,0,2,4,6)) 88 | axA2.set_xlabel(r'$x$') 89 | 90 | _setKey(axA2,l1+l3+l2+l4,1) 91 | 92 | DO_FORMAT='png' 93 | if DO_FORMAT=='png': 94 | fName = './FIGS/stationarySolution.png' 95 | plt.savefig(fName,format='png',dpi=600) 96 | elif DO_FORMAT=='svg': 97 | fName = './SVGS/stationarySolution.svg' 98 | plt.savefig(fName,format='svg',dpi=600) 99 | else: 100 | fName = './FIGS/stationarysolution.png' 101 | print('# saved under:', fName) 102 | plt.savefig(fName,format='png',dpi=600) 103 | 104 | 105 | 106 | def fetchNpz(iPath): 107 | data = np.load(iPath) 108 | x = data['x'] 109 | Ax_ini = data['Ax0_ini'] 110 | Ax_opt = data['Ax0'] 111 | return x, Ax_ini, Ax_opt 112 | 113 | 114 | def main(): 115 | generateFigure() 116 | 117 | 118 | main() 119 | -------------------------------------------------------------------------------- /numExp01_stationarySolution/run.sh: -------------------------------------------------------------------------------- 1 | python3 main_findStationarySolution.py 2 | -------------------------------------------------------------------------------- /numExp02_propagationScenarios/main_findStationarySolution.py: -------------------------------------------------------------------------------- 1 | import sys; sys.path.append('../scripts/') 2 | import pyGLLE 3 | import numpy as np 4 | 5 | 6 | class SIM_SETUP: 7 | 8 | xMax = 160. 9 | Nx = 2**13 10 | P = 8. 11 | theta = 15. 12 | d2 = -1.0 13 | d3 = 0.0 14 | d4 = 0.0 15 | x0 = 0.0 16 | 17 | fName = 'stationary_solution.dat' 18 | 19 | def initial_field(self, x): 20 | Ax_fnc = lambda x: np.sqrt(2*self.theta)/np.cosh(x*np.sqrt(self.theta)) 21 | cosZeta = np.sqrt(8*self.theta)/self.P/np.pi 22 | sinZeta = np.sqrt(1-cosZeta*cosZeta) 23 | return Ax_fnc(x-self.x0)*(cosZeta+1j*sinZeta) 24 | 25 | 26 | pyGLLE.findStationarySolution(SIM_SETUP()) 27 | -------------------------------------------------------------------------------- /numExp02_propagationScenarios/main_propagateInitialCondition.py: -------------------------------------------------------------------------------- 1 | import sys; sys.path.append('../scripts/') 2 | import pyGLLE 3 | import numpy as np 4 | 5 | 6 | 7 | class SIM_SETUP: 8 | 9 | _inFileName = './data_stationary_solution/stationary_solution.dat.npz' 10 | _data = np.load(_inFileName) 11 | _x = _data['x'] 12 | _Ax0 = _data['Ax0'] 13 | xMax = float(_data['xMax']) 14 | Nx = int(_data['Nx']) 15 | P = float(_data['P']) 16 | theta = float(_data['theta']) 17 | x0 = float(_data['x0']) 18 | 19 | tMax = 6.0 20 | Nt = 10000 21 | nSkip = 20 22 | d2 = -1.00 23 | d3 = float(sys.argv[1]) 24 | d4 = float(sys.argv[2]) 25 | 26 | fName = 'GLLE_nCS1_xMax%lf_Nx%d_tMax%lf_Nt%d_P%lf_theta%lf_d2%lf_d3%lf_d4%lf_x0%lf.dat'%(xMax,Nx,tMax,Nt,P,theta,d2,d3,d4,x0) 27 | 28 | def initial_field(self, x): 29 | return self._Ax0 30 | 31 | 32 | pyGLLE.propagateInitialCondition(SIM_SETUP()) 33 | -------------------------------------------------------------------------------- /numExp02_propagationScenarios/pp_figure_propagationScenarios/FIGS/GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.000000_d40.000000_x00.000000.dat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omelchert/pyGLLE/cb88029853a786384c96fed628e2fdb7ec93e3e5/numExp02_propagationScenarios/pp_figure_propagationScenarios/FIGS/GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.000000_d40.000000_x00.000000.dat.png -------------------------------------------------------------------------------- /numExp02_propagationScenarios/pp_figure_propagationScenarios/FIGS/GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.000000_d40.001000_x00.000000.dat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omelchert/pyGLLE/cb88029853a786384c96fed628e2fdb7ec93e3e5/numExp02_propagationScenarios/pp_figure_propagationScenarios/FIGS/GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.000000_d40.001000_x00.000000.dat.png -------------------------------------------------------------------------------- /numExp02_propagationScenarios/pp_figure_propagationScenarios/FIGS/GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.040000_d40.000000_x00.000000.dat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omelchert/pyGLLE/cb88029853a786384c96fed628e2fdb7ec93e3e5/numExp02_propagationScenarios/pp_figure_propagationScenarios/FIGS/GLLE_nCS1_xMax160.000000_Nx8192_tMax6.000000_Nt10000_P8.000000_theta15.000000_d2-1.000000_d30.040000_d40.000000_x00.000000.dat.png -------------------------------------------------------------------------------- /numExp02_propagationScenarios/pp_figure_propagationScenarios/figure_base_propagationDynamics.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import matplotlib as mpl 4 | import numpy as np 5 | import scipy.fftpack as sfft 6 | import matplotlib.pyplot as plt 7 | import matplotlib.colors as col 8 | from matplotlib.gridspec import GridSpec, GridSpecFromSubplotSpec 9 | 10 | 11 | def fetchIdx(t,t0): 12 | return np.argmin(np.abs(t-t0)) 13 | 14 | def generateFigure(x, k, t, At, xLim, xTicks, kLim, kTicks, tLim, tTicks, oName, DO_FORMAT='png'): 15 | 16 | mm2inch = lambda x: x/10./2.54 17 | mpl.rcParams['figure.figsize'] = mm2inch(1.*85),mm2inch(1.3/3.*85) 18 | mpl.rcParams['xtick.direction']= 'out' 19 | mpl.rcParams['ytick.direction']= 'out' 20 | mpl.rcParams['xtick.labelsize'] = 6 21 | mpl.rcParams['ytick.labelsize'] = 6 22 | mpl.rcParams['lines.linewidth'] = 1.0 23 | mpl.rcParams['axes.linewidth'] = 0.5 24 | mpl.rcParams['axes.labelsize'] = 6 25 | mpl.rcParams['font.size'] = 6 26 | mpl.rcParams['legend.fontsize'] = 6 27 | 28 | mpl.rcParams['text.usetex'] = True 29 | mpl.rcParams['text.latex.preamble'] = [ 30 | r'\usepackage{siunitx}', # i need upright \micro symbols, but you need... 31 | r'\sisetup{detect-all}', # ...this to force siunitx to actually use your fonts 32 | r'\usepackage{helvet}', # set the normal font here 33 | r'\usepackage{sansmath}', # load up the sansmath so that math -> helvet 34 | r'\sansmath' # <- tricky! -- gotta actually tell tex to use! 35 | ] 36 | 37 | cmap=mpl.cm.get_cmap('jet') 38 | 39 | fig = plt.figure() 40 | plt.subplots_adjust(left=0.08, bottom=0.19, right=0.98, top= 0.78, wspace=0.45, hspace=0.05) 41 | 42 | gs00 = GridSpec(1,1) 43 | gsA = GridSpecFromSubplotSpec(6,5,subplot_spec=gs00[0,0],wspace=0.1, hspace=0.1) 44 | axA1 = plt.subplot(gsA[1:,0:2]) 45 | axA2 = plt.subplot(gsA[1:,2:]) 46 | axA1t = plt.subplot(gsA[0,0:2]) 47 | axA2t = plt.subplot(gsA[0,2:]) 48 | 49 | # -- SUBPLOT: X-DOMAIN 50 | 51 | def _colorbar_Ix(im,refPos,refPos2): 52 | """colorbar helper""" 53 | x0, y0, w, h = refPos.x0, refPos.y0, refPos.width, refPos.height 54 | x2, y2, w2, h2 = refPos2.x0, refPos2.y0, refPos2.width, refPos2.height 55 | cax = fig.add_axes([x0, y0+1.05*h+h2, w, 0.065*h]) 56 | cbar = fig.colorbar(im, cax=cax, orientation='horizontal', extend='max') 57 | cbar.ax.tick_params(color='k', 58 | labelcolor='k', 59 | bottom=False, 60 | direction='out', 61 | labelbottom=False, 62 | labeltop=True, 63 | top=True, 64 | size=2, 65 | labelsize=4.5, 66 | pad=0. 67 | ) 68 | 69 | cbar.set_ticks((0,0.2,0.4,0.6,0.8,1.)) 70 | return cbar 71 | 72 | Ix = np.abs(At)**2 73 | Ix = Ix/Ix[0].max() 74 | img1 = axA1.pcolorfast(x,t,Ix[:-1,:-1],norm=col.Normalize(vmin=0.,vmax=1.0),cmap=cmap) 75 | cbar1 = _colorbar_Ix(img1,axA1.get_position(), axA1t.get_position() ) 76 | cbar1.ax.set_title(r"Normalized intensity $|A|^2$",color='k',fontsize=6., y=2.) 77 | axA1.tick_params(axis='both',length=2.,pad=1) 78 | axA1.xaxis.set_ticks_position('bottom') 79 | axA1.yaxis.set_ticks_position('left') 80 | axA1.set_xlim(xLim) 81 | axA1.set_xticks(xTicks) 82 | axA1.set_xlabel(r"$x$") 83 | axA1.set_ylim(tLim) 84 | axA1.set_yticks(tTicks) 85 | axA1.set_ylabel(r"$t$") 86 | 87 | 88 | # -- SUBPLOT: K-DOMAIN 89 | 90 | def _colorbar_Ik(im,refPos,refPos2): 91 | """colorbar helper""" 92 | x0, y0, w, h = refPos.x0, refPos.y0, refPos.width, refPos.height 93 | x2, y2, w2, h2 = refPos2.x0, refPos2.y0, refPos2.width, refPos2.height 94 | cax = fig.add_axes([x0, y0+1.05*h+h2, w, 0.065*h]) 95 | cbar = fig.colorbar(im, cax=cax, orientation='horizontal') 96 | cbar.ax.tick_params(color='k', 97 | labelcolor='k', 98 | bottom=False, 99 | direction='out', 100 | labelbottom=False, 101 | labeltop=True, 102 | top=True, 103 | size=2, 104 | labelsize=4.5, 105 | pad=0. 106 | ) 107 | 108 | dBLabels = np.asarray([-150,-120,-90,-60,-30,0]) 109 | _fromdB = lambda x: 10.**(0.1*x) 110 | cbar.set_ticks(_fromdB(dBLabels)) 111 | cbar.set_ticklabels(dBLabels) 112 | return cbar 113 | 114 | Ak = sfft.ifft(At,axis=-1) 115 | Ik = np.abs(Ak)**2 116 | Ik = Ik/Ik[0].max() 117 | img2 = axA2.pcolorfast(sfft.fftshift(k),t, sfft.fftshift(Ik[:-1,:-1],axes=-1), norm=col.LogNorm(vmin=1e-15,vmax=1.0),cmap=cmap) 118 | cbar1 = _colorbar_Ik(img2,axA2.get_position(), axA2t.get_position()) 119 | cbar1.ax.set_title(r"Normalized spectral intensity $|A_k|^2\,\mathrm{(dB)}$",color='k',fontsize=6., y=2.) 120 | 121 | axA2.xaxis.set_ticks_position('bottom') 122 | axA2.yaxis.set_ticks_position('left') 123 | axA2.xaxis.set_ticks_position('bottom') 124 | axA2.yaxis.set_ticks_position('left') 125 | axA2.tick_params(axis='y',length=2.,pad=1,labelleft=False) 126 | axA2.tick_params(axis='x',length=2.,pad=1) 127 | axA2.set_xlim(kLim) 128 | axA2.set_xticks(kTicks) 129 | axA2.set_xlabel(r"$k$") 130 | axA2.set_ylim(tLim) 131 | axA2.set_yticks(tTicks) 132 | 133 | # -- SUBPLOT: INTENSITY AT FINAL SLICE 134 | 135 | tFinId = fetchIdx(t,tLim[1]) 136 | 137 | Ax_fin = np.copy(At[tFinId]) 138 | Ix_fin = np.abs(Ax_fin)**2 139 | Ix_fin /= np.max(Ix_fin) 140 | 141 | axA1t.plot(x,Ix_fin,color='black',linewidth=0.5) 142 | 143 | axA1t.tick_params(axis='y',length=2.,pad=1,labelleft=False) 144 | axA1t.tick_params(axis='x',length=2.,pad=1,labelbottom=False) 145 | axA1t.set_xlim(xLim) 146 | axA1t.set_xticks(xTicks) 147 | axA1t.set_ylim(-0.02,1.02) 148 | axA1t.spines['left'].set_visible(False) 149 | axA1t.spines['right'].set_visible(False) 150 | axA1t.spines['top'].set_visible(False) 151 | axA1t.spines['bottom'].set_visible(False) 152 | axA1t.tick_params(axis='x',bottom=False,top=False) 153 | axA1t.tick_params(axis='y',left=False,right=False) 154 | 155 | # -- SUBPLOT: SPECTRAL INTENSITY AT FINAL SLICE 156 | 157 | def dB(x): 158 | return 10.*np.log10(x) 159 | 160 | Ak_fin = np.copy(Ak[tFinId]) 161 | Ik_fin = np.abs(Ak_fin)**2 162 | Ik_fin /= np.max(Ik_fin) 163 | 164 | #axA2t.vlines(sfft.fftshift(k),-150,dB(sfft.fftshift(Ik_fin)),color='black',linewidth=0.5) 165 | axA2t.plot(sfft.fftshift(k),dB(sfft.fftshift(Ik_fin)),color='black',linewidth=0.5) 166 | 167 | axA2t.tick_params(axis='y',length=2.,pad=1,labelleft=False) 168 | axA2t.tick_params(axis='x',length=2.,pad=1,labelbottom=False) 169 | axA2t.set_xlim(kLim) 170 | axA2t.set_xticks(kTicks) 171 | axA2t.set_ylim(-150,0.) 172 | axA2t.set_yticks((-150,-100.,-50,0)) 173 | axA2t.spines['left'].set_visible(False) 174 | axA2t.spines['right'].set_visible(False) 175 | axA2t.spines['top'].set_visible(False) 176 | axA2t.spines['bottom'].set_visible(False) 177 | axA2t.tick_params(axis='x',bottom=False, top=False) 178 | axA2t.tick_params(axis='y',left=False, right=False) 179 | 180 | 181 | if DO_FORMAT=='png': 182 | fName = './FIGS/'+oName+'.png' 183 | plt.savefig(fName,format='png',dpi=600) 184 | elif DO_FORMAT=='svg': 185 | fName = './SVGS/'+oName+'.svg' 186 | plt.savefig(fName,format='svg',dpi=600) 187 | else: 188 | fName = './FIGS/'+oName+'.png' 189 | print('# saved under:', fName) 190 | plt.savefig(fName,format='png',dpi=600) 191 | 192 | -------------------------------------------------------------------------------- /numExp02_propagationScenarios/pp_figure_propagationScenarios/generateFigures.sh: -------------------------------------------------------------------------------- 1 | for F in ../data/*.npz; do python3 main_figure_propagationDynamics_stationarySolution.py $F; done 2 | -------------------------------------------------------------------------------- /numExp02_propagationScenarios/pp_figure_propagationScenarios/main_figure_propagationDynamics_stationarySolution.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import numpy as np 4 | import scipy.fftpack as sfft 5 | from figure_base_propagationDynamics import generateFigure 6 | 7 | 8 | def fetchNpz(iPath): 9 | data = np.load(iPath) 10 | x = data['x'] 11 | k = sfft.fftfreq(x.size,d=x[1]-x[0])*2*np.pi 12 | t = data['t'] 13 | Axt = data['Axt'] 14 | return x, k, t, Axt 15 | 16 | 17 | def main(): 18 | 19 | inFile = sys.argv[1] 20 | 21 | (x,k,t,At) = fetchNpz(inFile) 22 | xLim = (-8,8) 23 | xTicks = (-8,-4,0,4,8) 24 | tLim = (0,5.) 25 | tTicks = (0,1,2,3,4,5) 26 | kLim = (-65,65) 27 | kTicks = (-60,-40,-20,0,20,40,60) 28 | 29 | path, inFileName = os.path.split(inFile) 30 | inFileBasename = os.path.splitext(inFileName)[0] 31 | 32 | generateFigure(x,k,t,At,xLim,xTicks,kLim,kTicks,tLim,tTicks,inFileBasename,DO_FORMAT='png') 33 | 34 | 35 | main() 36 | -------------------------------------------------------------------------------- /numExp02_propagationScenarios/run.sh: -------------------------------------------------------------------------------- 1 | python main_findStationarySolution.py 2 | 3 | python main_propagateInitialCondition.py 0.000 0.000 4 | python main_propagateInitialCondition.py 0.040 0.000 5 | python main_propagateInitialCondition.py 0.000 0.001 6 | -------------------------------------------------------------------------------- /scripts/pyGLLE.py: -------------------------------------------------------------------------------- 1 | """ pyGLLE.py 2 | 3 | Main script implementing functions for determining stationary solutions to the 4 | standard Lugiato-Lefever equation (LLE) and for propagating a user supplied 5 | initial condtions in terms of the genaralized LLE. 6 | 7 | AUTHOR: O. Melchert 8 | DATE: 2020-01-17 9 | """ 10 | import sys #; sys.path.append('../src/') 11 | import os 12 | sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src')) 13 | import datetime 14 | import numpy as np 15 | import scipy.fftpack as sfft 16 | from stationary_solution import stationarySolution, stationarySolution_homogeneous 17 | from data_handler import DataHandler 18 | from solver import solve 19 | 20 | __version__='1.0' 21 | 22 | 23 | def findStationarySolution(setup, tol=1e-10): 24 | """determine stationary solution for standard LLE 25 | 26 | uses root-finding procedure to determine stationary solution to the 27 | standard Lugiato-Lefever equation 28 | 29 | Args: 30 | setup (object): interface class holding simulation paramters 31 | tol (float): tolerance for root-finding procedure (default 1e-10) 32 | 33 | Returns: nothing, but saves result of root-finding procedure in folder 34 | ./data_stationary_solution/. The stored data consists of 35 | 36 | x (array): discrete x-mesh defining the computational domain 37 | Ax0_ini (array): trial solution 38 | Ax0 (array): result of root-finding procedure 39 | P (float): amplitude of homogeneous driving field 40 | theta (float): detuning 41 | Nx (int): number of mesh-points for discretizing x 42 | xMax (float): bound of x domain 43 | """ 44 | 45 | # -- INITIALIZE COMPUTATIONAL DOMAIN 46 | x = np.linspace(-setup.xMax, setup.xMax, setup.Nx, endpoint=False) 47 | k = sfft.fftfreq(x.size,d=x[1]-x[0])*2*np.pi 48 | 49 | # -- RIGHT HAND SIDE GENERATOR FOR GENERALIZED LUGIATO-LEFEVER PDE 50 | def _LLE_rhs(P, theta): 51 | return lambda A: P -(1+1j*theta)*A + sfft.fft((-1j*k*k)*sfft.ifft(A)) + 1j*np.abs(A)**2*A 52 | 53 | # -- FETCH USER DEFINED TRIAL SOLUTION 54 | Ax0_loc = setup.initial_field(x) 55 | 56 | # -- DETERMINE HOMOGENEOUS STATIONARY SOLUTION 57 | reA0, imA0 = stationarySolution_homogeneous(setup.theta,setup.P) 58 | 59 | # -- COMPOSE INITIAL GUESS FOR STATIONARY SOLUTION 60 | Ax0_ini = Ax0_loc + (reA0+1j*imA0) 61 | 62 | # -- DETERMINE STATIONARY SOLUTION FOR STANDART LLE USING INITIAL GUESS 63 | A_statSol = stationarySolution(x, Ax0_ini, _LLE_rhs(setup.P, setup.theta), tol) 64 | 65 | # -- SAVE DATA 66 | path = './data_stationary_solution/' 67 | try: 68 | os.makedirs(path) 69 | except OSError: 70 | pass 71 | np.savez_compressed(path+setup.fName, x = np.asarray(x), Ax0 = np.asarray(A_statSol), P = setup.P, theta=setup.theta, d3=setup.d3, x0=setup.x0, Nx = setup.Nx, xMax=setup.xMax, Ax0_ini=np.asarray(Ax0_ini)) 72 | 73 | 74 | def propagateInitialCondition(setup): 75 | """propagate inital condition under the generalized Lugiato-Lefever equation 76 | 77 | uses pseudospectral approach to propagate a user supplied initial condition 78 | in terms of the generalized Lugiato-Lefever equation with third and fourth 79 | order dispersion. 80 | 81 | Args: 82 | setup (object): interface class holding simulation paramters 83 | 84 | Returns: nothing, but saves result in folder ./data/. The stored data 85 | consists of 86 | 87 | x (array): discrete x-mesh defining the computational domain 88 | t (array): discrete t-mesh defining t-coordinates at which data is stored 89 | Axt (array): field obtained during the simulation run 90 | info (str): metadata for data-management 91 | """ 92 | 93 | # -- CATCH POSSIBLE DATATYPE ERRORS OF SUPPLIED PARAMETERS 94 | if not isinstance(setup.xMax, float): 95 | raise ValueError("xMax: expected float, got %s"%(type(setup.xMax))) 96 | 97 | if not isinstance(setup.Nx, int): 98 | raise ValueError("Nx: expected int, got %s"%(type(setup.Nx))) 99 | 100 | if not isinstance(setup.tMax, float): 101 | raise ValueError("tMax: expected float, got %s"%(type(setup.tMax))) 102 | 103 | if not isinstance(setup.Nt, int): 104 | raise ValueError("Nt: expected int, got %s"%(type(setup.Nt))) 105 | 106 | if not isinstance(setup.nSkip, int): 107 | raise ValueError("nSkip: expected int, got %s"%(type(setup.nSkip))) 108 | 109 | if not isinstance(setup.P, float): 110 | raise ValueError("P: expected float, got %s"%(type(setup.P))) 111 | 112 | if not isinstance(setup.theta, float): 113 | raise ValueError("theta: expected float, got %s"%(type(setup.theta))) 114 | 115 | if not isinstance(setup.d3, float): 116 | raise ValueError("d3: expected float, got %s"%(type(setup.d3))) 117 | 118 | if not isinstance(setup.d4, float): 119 | raise ValueError("d4: expected float, got %s"%(type(setup.d4))) 120 | 121 | if not isinstance(setup.fName, str): 122 | raise ValueError("fName: expected str, got %s"%(type(setup.fName))) 123 | 124 | # -- ASSEMBLE META-DATA 125 | info = dict() 126 | info["I01 OS-USER"] = "%s"%(os.path.expanduser('~')) 127 | info["I02 OS-ENV"] = "%s"%(str(sys.platform)) 128 | info["I03 OS-PID"] = "%s"%(str(os.getpid())) 129 | info["I04 FILE"] = "%s"%(sys.argv[0]) 130 | info["I05 VERSION"] = "%s"%(__version__) 131 | info["I06 DATE"] = "%s"%(datetime.datetime.now()) 132 | info["I07 FNAME"] = "%s"%(setup.fName) 133 | 134 | # -- INITIALIZE COMPUTATIONAL DOMAIN 135 | x = np.linspace(-setup.xMax, setup.xMax, setup.Nx, endpoint=False) 136 | k = sfft.fftfreq(x.size,d=x[1]-x[0])*2*np.pi 137 | t = np.linspace(0,setup.tMax,setup.Nt,endpoint=True) 138 | 139 | # -- RIGHT HAND SIDE GENERATOR FOR GENERALIZED LUGIATO-LEFEVER PDE 140 | def _GLLE_rhs(P, theta, d2, d3, d4): 141 | return lambda A: P -(1+1j*theta)*A + sfft.fft((1j*d2*k*k + 1j*d3*k*k*k + 1j*d4*k*k*k*k)*sfft.ifft(A)) + 1j*np.abs(A)**2*A 142 | 143 | # -- SET INITIAL CONDITION 144 | Ax0 = setup.initial_field(x) 145 | 146 | # -- PROPAGATE FIELD 147 | dat = DataHandler(setup.nSkip) 148 | solve(x, t, Ax0, _GLLE_rhs(setup.P, setup.theta, setup.d2, setup.d3, setup.d4), dat.measure) 149 | 150 | # -- SAVE DATA 151 | dat.save(setup.fName, path='./data/', **info) 152 | 153 | # EOF: pyGLLE.py 154 | -------------------------------------------------------------------------------- /src/data_handler.py: -------------------------------------------------------------------------------- 1 | """ data_handler.py 2 | 3 | Contains class data structure handling data accumulation and output. 4 | 5 | AUTHOR: O. Melchert 6 | DATE: 2020-01-17 7 | """ 8 | import os 9 | import numpy as np 10 | 11 | 12 | class DataHandler(): 13 | """data structure holding accumulated data 14 | """ 15 | def __init__(self, nSkip=1): 16 | """generates instance of data handler 17 | 18 | Attrib: 19 | w (numpy-array, ndim=1): anglular frequency axis 20 | t (numpy-array, ndim=1): time axis 21 | z (numpy-array, ndim=1): z-axis, i.e. propagation direction axis 22 | u (numpy-array, ndim=2): frequency components of field 23 | """ 24 | self.nSkip=nSkip 25 | self.Axt = [] 26 | self.t = [] 27 | self.x = [] 28 | self.info = "00 -- I:INFO, D:DATA\n" 29 | 30 | def measure(self, n, t, x, Ax): 31 | """measure 32 | 33 | Callback function facilitating measurement 34 | 35 | Args: 36 | n (int): current propagation step 37 | t (numpy-array, ndim=1): time-axis 38 | x (numpy-array, ndim=1): x coordinate axis 39 | Ax (numpy-array, ndim=1): field components 40 | """ 41 | if n%self.nSkip==0: 42 | self.Axt.append(Ax) 43 | self.t.append(t) 44 | self.x = x 45 | 46 | def save(self, fName, path='./',**kwargs): 47 | """save data in numpy format 48 | 49 | Saves data in numpy native compressed npz format 50 | 51 | Args: 52 | oPath (str): path to folder where output will be stored 53 | fdBase (str): base name for output files 54 | Dat (object): data structure holding system data 55 | kwargs (dict): info dictionary 56 | """ 57 | try: 58 | os.makedirs(path) 59 | except OSError: 60 | pass 61 | 62 | for key, val in sorted(kwargs.items()): 63 | self.info += "%s: %s\n"%(key,val) 64 | 65 | np.savez_compressed(path + fName, 66 | info=self.info, 67 | x=np.asarray(self.x), 68 | t=np.asarray(self.t), 69 | Axt=np.asarray(self.Axt)) 70 | 71 | # EOF: data_handler.py 72 | -------------------------------------------------------------------------------- /src/solver.py: -------------------------------------------------------------------------------- 1 | """solver.py 2 | 3 | Contains function implementing a numerical integration scheme using the 4 | complex_ode class of scipys integrate module. 5 | 6 | AUTHOR: O. Melchert 7 | DATE: 2020-01-17 8 | """ 9 | from numpy import asarray 10 | from scipy.integrate import complex_ode 11 | 12 | 13 | def solve(x, t, A0, fvec, callbackFunc): 14 | """ solve 15 | 16 | implements numerical integration scheme for complex field based on the 17 | explicit higher-order Runge-Kutta method "DOP853" (hard-coded). 18 | 19 | Args: 20 | x (numpy-array): discrete x-domain 21 | t (numpy-array): discrete t-domain 22 | A0 (numpy-array): initial condition 23 | fvec (object): right-hand-side of first order propagation equation 24 | callbaclFunc (object): callback function facilitating the measurement 25 | at distinct values of t. It takes 4 paramters in the form 26 | callbackFunc(n, tCurr, x, Ax), where: 27 | 28 | n (int): current propagation step 29 | t_curr (float): current time coordinate 30 | x (numpy-array): discrete x-domain 31 | Ax (numpy-array): field configuration at t_curr 32 | 33 | Returns: (t_fin,A_fin) 34 | t_fin (float): final time coordinate 35 | A_fin (numpy-array): final field configuration 36 | """ 37 | 38 | dt = t[1]-t[0] 39 | it = 0 40 | 41 | solver = complex_ode(lambda t, A: fvec(A)) 42 | solver.set_integrator('dop853') 43 | solver.set_initial_value(A0, t.min()) 44 | 45 | while solver.successful() and solver.t < t.max(): 46 | solver.integrate(solver.t+dt,step=1) 47 | callbackFunc(it, solver.t, x, solver.y) 48 | it += 1 49 | 50 | return solver.t, solver.y 51 | 52 | # EOF: solver.py 53 | -------------------------------------------------------------------------------- /src/stationary_solution.py: -------------------------------------------------------------------------------- 1 | """stationary_solution.py 2 | 3 | Contains function definitions allowing to obtain a homogeneous stationary 4 | solution, possibly including one or several localized dissipatice structures, 5 | for the standard Lugiato-Lefever equation. 6 | 7 | AUTHOR: O. Melchert 8 | DATE: 2020-01-17 9 | """ 10 | import sys 11 | import numpy as np 12 | import scipy.fftpack as sfft 13 | from scipy.optimize import root 14 | 15 | 16 | def stationarySolution_homogeneous(theta,P): 17 | """determine homogeneous stationary solution 18 | 19 | Args: 20 | theta (float): detuning 21 | P (float): amplitude of homogeneous driving field 22 | 23 | Returns: 24 | reA0 (float): real part of homogeneous stationary solution 25 | imA0 (float): imaginary part of homogeneous stationary solution 26 | """ 27 | I0_ini = np.abs(1j*P/(theta + 1j)) 28 | I0_opt = float(root( lambda I0: I0*(1+(theta-I0)**2) - P**2, I0_ini , tol=1e-8).x) 29 | reA0 = P/(1.+(I0_opt-theta)**2) 30 | imA0 = (I0_opt-theta)*P/(1.+(I0_opt-theta)**2) 31 | return reA0, imA0 32 | 33 | def stationarySolution(x, A_ini, LLE_rhs, tol): 34 | """determine stationary solution for standard Lugiato-Lefever equation 35 | 36 | uses newton-krylov method (hardcoded) to perform root-finding 37 | procedure for standard Lugiato-Lefever equation (LLE) 38 | 39 | Args: 40 | x (numpy-array): discretized x-domain 41 | A_ini (numpy-array): trial solution used for root-finding procedure 42 | LLE_rhs (object): right-hand side of LLE 43 | tol (float): tolerance value used to terminate root-finding procedure 44 | 45 | Returns: 46 | A_opt (numpy-array): field resulting from root-finding procedure 47 | """ 48 | k = sfft.fftfreq(x.size,d=x[1]-x[0])*2*np.pi 49 | 50 | A_opt = root( 51 | lambda A_r: (LLE_rhs(A_r.view(np.complex128))).view(np.float64), 52 | np.array(A_ini, dtype=np.complex128).view(np.float64), 53 | method='krylov', 54 | tol=tol 55 | ).x.view(np.complex128) 56 | 57 | return A_opt 58 | 59 | # EOF: stationary_solution.py 60 | --------------------------------------------------------------------------------